Skip to content

Commit db60044

Browse files
committed
feat: add check hooks generation to tanstack and swr plugins
1 parent 1243422 commit db60044

File tree

4 files changed

+122
-3
lines changed

4 files changed

+122
-3
lines changed

packages/plugins/swr/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"lower-case-first": "^2.0.2",
4747
"semver": "^7.5.2",
4848
"ts-morph": "^16.0.0",
49+
"ts-pattern": "^4.3.0",
4950
"upper-case-first": "^2.0.2"
5051
},
5152
"devDependencies": {

packages/plugins/swr/src/generator.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
PluginOptions,
3+
RUNTIME_PACKAGE,
34
createProject,
45
ensureEmptyDir,
56
generateModelMeta,
@@ -8,11 +9,12 @@ import {
89
resolvePath,
910
saveProject,
1011
} from '@zenstackhq/sdk';
11-
import { DataModel, Model } from '@zenstackhq/sdk/ast';
12+
import { DataModel, DataModelFieldType, Model, isEnum } from '@zenstackhq/sdk/ast';
1213
import { getPrismaClientImportSpec, type DMMF } from '@zenstackhq/sdk/prisma';
1314
import { paramCase } from 'change-case';
1415
import path from 'path';
1516
import type { OptionalKind, ParameterDeclarationStructure, Project, SourceFile } from 'ts-morph';
17+
import { P, match } from 'ts-pattern';
1618
import { upperCaseFirst } from 'upper-case-first';
1719
import { name } from '.';
1820

@@ -66,6 +68,7 @@ function generateModelHooks(
6668
});
6769
sf.addStatements([
6870
`import { type GetNextArgs, type QueryOptions, type InfiniteQueryOptions, type MutationOptions, type PickEnumerable } from '@zenstackhq/swr/runtime';`,
71+
`import type { PolicyCrudKind } from '${RUNTIME_PACKAGE}'`,
6972
`import metadata from './__model_meta';`,
7073
`import * as request from '@zenstackhq/swr/runtime';`,
7174
]);
@@ -239,6 +242,11 @@ function generateModelHooks(
239242
const returnType = `T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType<T['select'], Prisma.${modelNameCap}CountAggregateOutputType> : number`;
240243
generateQueryHook(sf, model, 'count', argsType, inputType, returnType);
241244
}
245+
246+
// extra `check` hook for ZenStack's permission checker API
247+
{
248+
generateCheckHook(sf, model, prismaImport);
249+
}
242250
}
243251

244252
function makeOptimistic(returnType: string) {
@@ -337,3 +345,47 @@ function generateMutation(
337345

338346
return funcName;
339347
}
348+
349+
function generateCheckHook(sf: SourceFile, model: DataModel, prismaImport: string) {
350+
const mapFilterType = (type: DataModelFieldType) => {
351+
return match(type.type)
352+
.with(P.union('Int', 'BigInt'), () => 'number')
353+
.with('String', () => 'string')
354+
.with('Boolean', () => 'boolean')
355+
.otherwise(() => undefined);
356+
};
357+
358+
const filterFields: Array<{ name: string; type: string }> = [];
359+
const enumsToImport = new Set<string>();
360+
361+
// collect filterable fields and enums to import
362+
model.fields.forEach((f) => {
363+
if (isEnum(f.type.reference?.ref)) {
364+
enumsToImport.add(f.type.reference.$refText);
365+
filterFields.push({ name: f.name, type: f.type.reference.$refText });
366+
}
367+
368+
const mappedType = mapFilterType(f.type);
369+
if (mappedType) {
370+
filterFields.push({ name: f.name, type: mappedType });
371+
}
372+
});
373+
374+
if (enumsToImport.size > 0) {
375+
// import enums
376+
sf.addStatements(`import type { ${Array.from(enumsToImport).join(', ')} } from '${prismaImport}';`);
377+
}
378+
379+
const whereType = `{ ${filterFields.map(({ name, type }) => `${name}?: ${type}`).join('; ')} }`;
380+
381+
const func = sf.addFunction({
382+
name: `useCheck${model.name}`,
383+
isExported: true,
384+
parameters: [
385+
{ name: 'args', type: `{ operation: PolicyCrudKind; where?: ${whereType}; }` },
386+
{ name: 'options?', type: `QueryOptions<boolean>` },
387+
],
388+
});
389+
390+
func.addStatements(`return request.useModelQuery('${model.name}', 'check', args, options);`);
391+
}

packages/plugins/tanstack-query/src/generator.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
PluginError,
33
PluginOptions,
4+
RUNTIME_PACKAGE,
45
createProject,
56
ensureEmptyDir,
67
generateModelMeta,
@@ -9,13 +10,13 @@ import {
910
resolvePath,
1011
saveProject,
1112
} from '@zenstackhq/sdk';
12-
import { DataModel, Model } from '@zenstackhq/sdk/ast';
13+
import { DataModel, DataModelFieldType, Model, isEnum } from '@zenstackhq/sdk/ast';
1314
import { getPrismaClientImportSpec, type DMMF } from '@zenstackhq/sdk/prisma';
1415
import { paramCase } from 'change-case';
1516
import { lowerCaseFirst } from 'lower-case-first';
1617
import path from 'path';
1718
import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph';
18-
import { match } from 'ts-pattern';
19+
import { P, match } from 'ts-pattern';
1920
import { upperCaseFirst } from 'upper-case-first';
2021
import { name } from '.';
2122

@@ -261,6 +262,62 @@ function generateMutationHook(
261262
func.addStatements('return mutation;');
262263
}
263264

265+
function generateCheckHook(
266+
target: string,
267+
version: TanStackVersion,
268+
sf: SourceFile,
269+
model: DataModel,
270+
prismaImport: string
271+
) {
272+
const mapFilterType = (type: DataModelFieldType) => {
273+
return match(type.type)
274+
.with(P.union('Int', 'BigInt'), () => 'number')
275+
.with('String', () => 'string')
276+
.with('Boolean', () => 'boolean')
277+
.otherwise(() => undefined);
278+
};
279+
280+
const filterFields: Array<{ name: string; type: string }> = [];
281+
const enumsToImport = new Set<string>();
282+
283+
// collect filterable fields and enums to import
284+
model.fields.forEach((f) => {
285+
if (isEnum(f.type.reference?.ref)) {
286+
enumsToImport.add(f.type.reference.$refText);
287+
filterFields.push({ name: f.name, type: f.type.reference.$refText });
288+
}
289+
290+
const mappedType = mapFilterType(f.type);
291+
if (mappedType) {
292+
filterFields.push({ name: f.name, type: mappedType });
293+
}
294+
});
295+
296+
if (enumsToImport.size > 0) {
297+
// import enums
298+
sf.addStatements(`import type { ${Array.from(enumsToImport).join(', ')} } from '${prismaImport}';`);
299+
}
300+
301+
const whereType = `{ ${filterFields.map(({ name, type }) => `${name}?: ${type}`).join('; ')} }`;
302+
303+
const func = sf.addFunction({
304+
name: `useCheck${model.name}`,
305+
isExported: true,
306+
typeParameters: ['TError = DefaultError'],
307+
parameters: [
308+
{ name: 'args', type: `{ operation: PolicyCrudKind; where?: ${whereType}; }` },
309+
{ name: 'options?', type: makeQueryOptions(target, 'boolean', 'boolean', false, false, version) },
310+
],
311+
});
312+
313+
func.addStatements([
314+
makeGetContext(target),
315+
`return useModelQuery<boolean, boolean, TError>('${model.name}', \`\${endpoint}/${lowerCaseFirst(
316+
model.name
317+
)}/check\`, args, options, fetch);`,
318+
]);
319+
}
320+
264321
function generateModelHooks(
265322
target: TargetFramework,
266323
version: TanStackVersion,
@@ -494,6 +551,11 @@ function generateModelHooks(
494551
`TArgs extends { select: any; } ? TArgs['select'] extends true ? number : Prisma.GetScalarType<TArgs['select'], Prisma.${modelNameCap}CountAggregateOutputType> : number`
495552
);
496553
}
554+
555+
{
556+
// extra `check` hook for ZenStack's permission checker API
557+
generateCheckHook(target, version, sf, model, prismaImport);
558+
}
497559
}
498560

499561
function generateIndex(
@@ -538,6 +600,7 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
538600
const shared = [
539601
`import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '${runtimeImportBase}/${target}';`,
540602
`import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '${runtimeImportBase}';`,
603+
`import type { PolicyCrudKind } from '${RUNTIME_PACKAGE}'`,
541604
`import metadata from './__model_meta';`,
542605
`type DefaultError = QueryError;`,
543606
];

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)