Skip to content

Commit 19fea2c

Browse files
committed
WIP: progress
1 parent 5e44c0b commit 19fea2c

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { getDataModels } from '@zenstackhq/sdk';
2+
import type { DataModel, DataModelField, Model } from '@zenstackhq/sdk/ast';
3+
import { lowerCaseFirst } from 'lower-case-first';
4+
import { P, match } from 'ts-pattern';
5+
6+
/**
7+
* Generates a `ModelCheckers` interface that contains a `check` method for each model in the schema.
8+
*/
9+
export function generateCheckerType(model: Model) {
10+
return `
11+
type CheckerOperation = 'create' | 'read' | 'update' | 'delete';
12+
13+
export interface ModelCheckers {
14+
${getDataModels(model)
15+
.map((dataModel) => `\t${lowerCaseFirst(dataModel.name)}: ${generateDataModelChecker(dataModel)}`)
16+
.join(',\n')}
17+
}
18+
`;
19+
}
20+
21+
function generateDataModelChecker(dataModel: DataModel) {
22+
return `{
23+
check(op: CheckerOperation, args?: ${generateDataModelArgs(dataModel)}): Promise<boolean>
24+
}`;
25+
}
26+
27+
function generateDataModelArgs(dataModel: DataModel) {
28+
return `{ ${dataModel.fields
29+
.filter((field) => isFieldFilterable(field))
30+
.map((field) => `${field.name}?: ${mapFieldType(field)}`)
31+
.join('; ')} }`;
32+
}
33+
34+
function isFieldFilterable(field: DataModelField) {
35+
return !!mapFieldType(field);
36+
}
37+
38+
function mapFieldType(field: DataModelField) {
39+
return match(field.type.type)
40+
.with('Boolean', () => 'boolean')
41+
.with(P.union('BigInt', 'Int', 'Float', 'Decimal'), () => 'number')
42+
.with('String', () => 'string')
43+
.otherwise(() => undefined);
44+
}

packages/schema/src/plugins/enhancer/enhance/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { trackPrismaSchemaError } from '../../prisma';
4141
import { PrismaSchemaGenerator } from '../../prisma/schema-generator';
4242
import { isDefaultWithAuth } from '../enhancer-utils';
4343
import { generateAuthType } from './auth-type-generator';
44+
import { generateCheckerType } from './checker-type-generator';
4445

4546
// information of delegate models and their sub models
4647
type DelegateInfo = [DataModel, DataModel[]][];
@@ -89,6 +90,8 @@ export class EnhancerGenerator {
8990
const authTypes = authModel ? generateAuthType(this.model, authModel) : '';
9091
const authTypeParam = authModel ? `auth.${authModel.name}` : 'AuthUser';
9192

93+
const checkerTypes = generateCheckerType(this.model);
94+
9295
const enhanceTs = this.project.createSourceFile(
9396
path.join(this.outDir, 'enhance.ts'),
9497
`import { type EnhancementContext, type EnhancementOptions, type ZodSchemas, type AuthUser } from '@zenstackhq/runtime';
@@ -105,6 +108,8 @@ ${
105108
106109
${authTypes}
107110
111+
${checkerTypes}
112+
108113
${
109114
logicalPrismaClientDir
110115
? this.createLogicalPrismaEnhanceFunction(authTypeParam)
@@ -127,14 +132,14 @@ import type * as _P from '${prismaImport}';
127132

128133
private createSimplePrismaEnhanceFunction(authTypeParam: string) {
129134
return `
130-
export function enhance<DbClient extends object>(prisma: DbClient, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions) {
135+
export function enhance<DbClient extends object>(prisma: DbClient, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DbClient & ModelCheckers {
131136
return createEnhancement(prisma, {
132137
modelMeta,
133138
policy,
134139
zodSchemas: zodSchemas as unknown as (ZodSchemas | undefined),
135140
prismaModule: Prisma,
136141
...options
137-
}, context);
142+
}, context) as DbClient & ModelCheckers;
138143
}
139144
`;
140145
}
@@ -157,12 +162,12 @@ import type { Prisma, PrismaClient } from '${logicalPrismaClientDir}/index-fixed
157162
// overload for plain PrismaClient
158163
export function enhance<ExtArgs extends Record<string, any> & InternalArgs>(
159164
prisma: _PrismaClient<any, any, ExtArgs>,
160-
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): PrismaClient;
165+
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): PrismaClient & ModelCheckers;
161166
162167
// overload for extended PrismaClient
163168
export function enhance<TypeMap extends TypeMapDef, TypeMapCb extends TypeMapCbDef, ExtArgs extends Record<string, any> & InternalArgs>(
164169
prisma: DynamicClientExtensionThis<TypeMap, TypeMapCb, ExtArgs>,
165-
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DynamicClientExtensionThis<Prisma.TypeMap, Prisma.TypeMapCb, ExtArgs>;
170+
context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): DynamicClientExtensionThis<Prisma.TypeMap, Prisma.TypeMapCb, ExtArgs> & ModelCheckers;
166171
167172
export function enhance(prisma: any, context?: EnhancementContext<${authTypeParam}>, options?: EnhancementOptions): any {
168173
return createEnhancement(prisma, {

tests/integration/tests/enhancements/with-policy/checker.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,33 @@ describe('Permission checker', () => {
326326
await expect(db.model.check('create')).toResolveTruthy();
327327
await expect(db.model.check('create', { value: 1 })).toResolveTruthy();
328328
});
329+
330+
it('compilation', async () => {
331+
await loadSchema(
332+
`
333+
model Model {
334+
id Int @id @default(autoincrement())
335+
value Int
336+
@@allow('read', value == 1)
337+
}
338+
`,
339+
{
340+
compile: true,
341+
extraSourceFiles: [
342+
{
343+
name: 'main.ts',
344+
content: `
345+
import { PrismaClient } from '@prisma/client';
346+
import { enhance } from '.zenstack/enhance';
347+
348+
const prisma = new PrismaClient();
349+
const db = enhance(prisma);
350+
db.model.check('read');
351+
db.model.check('read', { value: 1 });
352+
`,
353+
},
354+
],
355+
}
356+
);
357+
});
329358
});

0 commit comments

Comments
 (0)