Skip to content

Commit e93ca5b

Browse files
ymc9Xemah
andauthored
fix: zod typing for DateTime field, improve overall code generation (#363)
Co-authored-by: Abdullah Hassan <iAbdullahHassan@gmail.com>
1 parent 9f79e51 commit e93ca5b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+327
-182
lines changed

packages/next/tests/request-handler.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { loadSchema } from '@zenstackhq/testtools';
3-
import fs from 'fs';
43
import { createServer, RequestListener } from 'http';
54
import { apiResolver } from 'next/dist/server/api-utils/node';
65
import superjson from 'superjson';
@@ -37,17 +36,13 @@ function makeTestClient(apiPath: string, options: RequestHandlerOptions, queryAr
3736

3837
describe('request handler tests', () => {
3938
let origDir: string;
40-
let projDir: string;
4139

4240
beforeEach(() => {
4341
origDir = process.cwd();
4442
});
4543

4644
afterEach(() => {
4745
process.chdir(origDir);
48-
if (projDir) {
49-
fs.rmSync(projDir, { recursive: true, force: true });
50-
}
5146
});
5247

5348
it('simple crud', async () => {
@@ -58,8 +53,7 @@ model M {
5853
}
5954
`;
6055

61-
const { prisma, projectDir } = await loadSchema(model);
62-
projDir = projectDir;
56+
const { prisma } = await loadSchema(model);
6357

6458
await makeTestClient('/m/create', { getPrisma: () => prisma })
6559
.post('/')
@@ -180,8 +174,7 @@ model M {
180174
}
181175
`;
182176

183-
const { withPresets, projectDir } = await loadSchema(model);
184-
projDir = projectDir;
177+
const { withPresets } = await loadSchema(model);
185178

186179
await makeTestClient('/m/create', { getPrisma: () => withPresets() })
187180
.post('/m/create')

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DMMF } from '@prisma/generator-helper';
2-
import { getDataModels, PluginError, PluginOptions } from '@zenstackhq/sdk';
2+
import { PluginError, PluginOptions, createProject, getDataModels, saveProject } from '@zenstackhq/sdk';
33
import { DataModel, Model } from '@zenstackhq/sdk/ast';
44
import { camelCase, paramCase, pascalCase } from 'change-case';
55
import * as path from 'path';
@@ -16,7 +16,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
1616
outDir = path.join(path.dirname(options.schemaPath), outDir);
1717
}
1818

19-
const project = new Project();
19+
const project = createProject();
2020
const warnings: string[] = [];
2121
const models = getDataModels(model);
2222

@@ -31,7 +31,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
3131
generateModelHooks(project, outDir, dataModel, mapping);
3232
});
3333

34-
await project.save();
34+
await saveProject(project);
3535
return warnings;
3636
}
3737

@@ -319,12 +319,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel,
319319
`T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType<T['select'], Prisma.${model.name}CountAggregateOutputType> : number`
320320
);
321321
}
322-
323-
sf.formatText();
324322
}
325323

326324
function generateIndex(project: Project, outDir: string, models: DataModel[]) {
327325
const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true });
328326
sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`));
329-
sf.formatText();
330327
}

packages/plugins/react/src/generator/swr.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { DMMF } from '@prisma/generator-helper';
2-
import { CrudFailureReason, getDataModels, PluginError, PluginOptions } from '@zenstackhq/sdk';
2+
import {
3+
CrudFailureReason,
4+
PluginError,
5+
PluginOptions,
6+
createProject,
7+
getDataModels,
8+
saveProject,
9+
} from '@zenstackhq/sdk';
310
import { DataModel, Model } from '@zenstackhq/sdk/ast';
411
import { camelCase, paramCase } from 'change-case';
512
import * as path from 'path';
@@ -16,7 +23,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
1623
outDir = path.join(path.dirname(options.schemaPath), outDir);
1724
}
1825

19-
const project = new Project();
26+
const project = createProject();
2027
const warnings: string[] = [];
2128
const models = getDataModels(model);
2229

@@ -31,7 +38,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
3138
generateModelHooks(project, outDir, dataModel, mapping);
3239
});
3340

34-
await project.save();
41+
await saveProject(project);
3542
return warnings;
3643
}
3744

@@ -489,14 +496,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel,
489496
}
490497

491498
useFunc.addStatements([`return { ${methods.join(', ')} };`]);
492-
493-
sf.formatText();
494499
}
495500

496501
function generateIndex(project: Project, outDir: string, models: DataModel[]) {
497502
const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true });
498-
499503
sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`));
500-
501-
sf.formatText();
502504
}

packages/plugins/react/tests/react-hooks.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
/// <reference types="@types/jest" />
22

33
import { loadSchema } from '@zenstackhq/testtools';
4-
import fs from 'fs';
54

65
describe('React Hooks Plugin Tests', () => {
76
let origDir: string;
8-
let projDir: string;
97

108
beforeAll(() => {
119
origDir = process.cwd();
1210
});
1311

1412
afterEach(() => {
1513
process.chdir(origDir);
16-
if (projDir) {
17-
fs.rmSync(projDir, { recursive: true, force: true });
18-
}
1914
});
2015

2116
const sharedModel = `
@@ -46,7 +41,7 @@ model Foo {
4641
`;
4742

4843
it('swr generator', async () => {
49-
const { projectDir } = await loadSchema(
44+
await loadSchema(
5045
`
5146
plugin react {
5247
provider = '${process.cwd()}/dist'
@@ -60,11 +55,10 @@ ${sharedModel}
6055
[`${origDir}/dist`, 'react', '@types/react', 'swr'],
6156
true
6257
);
63-
projDir = projectDir;
6458
});
6559

6660
it('react-query generator', async () => {
67-
const { projectDir } = await loadSchema(
61+
await loadSchema(
6862
`
6963
plugin react {
7064
provider = '${process.cwd()}/dist'
@@ -79,6 +73,5 @@ ${sharedModel}
7973
[`${origDir}/dist`, 'react', '@types/react', '@tanstack/react-query'],
8074
true
8175
);
82-
projDir = projectDir;
8376
});
8477
});

packages/plugins/trpc/src/generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DMMF } from '@prisma/generator-helper';
2-
import { CrudFailureReason, PluginError, PluginOptions, RUNTIME_PACKAGE } from '@zenstackhq/sdk';
2+
import { CrudFailureReason, PluginError, PluginOptions, RUNTIME_PACKAGE, saveProject } from '@zenstackhq/sdk';
33
import { Model } from '@zenstackhq/sdk/ast';
44
import { camelCase } from 'change-case';
55
import { promises as fs } from 'fs';
@@ -42,7 +42,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
4242
createAppRouter(outDir, modelOperations, hiddenModels);
4343
createHelper(outDir);
4444

45-
await project.save();
45+
await saveProject(project);
4646
}
4747

4848
function createAppRouter(outDir: string, modelOperations: DMMF.ModelMapping[], hiddenModels: string[]) {

packages/plugins/trpc/src/zod/generator.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { ConnectorType, DMMF } from '@prisma/generator-helper';
22
import { Dictionary } from '@prisma/internals';
3-
import { getLiteral, PluginOptions } from '@zenstackhq/sdk';
4-
import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast';
3+
import { PluginOptions, getLiteral } from '@zenstackhq/sdk';
4+
import { DataSource, Model, isDataSource } from '@zenstackhq/sdk/ast';
55
import {
6-
addMissingInputObjectTypes,
76
AggregateOperationSupport,
7+
addMissingInputObjectTypes,
88
resolveAggregateOperationSupport,
99
} from '@zenstackhq/sdk/dmmf-helpers';
1010
import { promises as fs } from 'fs';
11+
import path from 'path';
1112
import Transformer from './transformer';
1213
import removeDir from './utils/removeDir';
14+
import { writeFileSafely } from './utils/writeFileSafely';
1315

1416
export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) {
15-
await handleGeneratorOutputValue((options.output as string) ?? './generated');
17+
const output = (options.output as string) ?? './generated';
18+
await handleGeneratorOutputValue(output);
1619

1720
const prismaClientDmmf = dmmf;
1821

@@ -38,7 +41,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
3841

3942
const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes);
4043

41-
await generateObjectSchemas(inputObjectTypes);
44+
await generateObjectSchemas(inputObjectTypes, output);
4245
await generateModelSchemas(models, modelOperations, aggregateOperationSupport);
4346
}
4447

@@ -61,13 +64,19 @@ async function generateEnumSchemas(prismaSchemaEnum: DMMF.SchemaEnum[], modelSch
6164
await transformer.generateEnumSchemas();
6265
}
6366

64-
async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[]) {
67+
async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], output: string) {
68+
const moduleNames: string[] = [];
6569
for (let i = 0; i < inputObjectTypes.length; i += 1) {
6670
const fields = inputObjectTypes[i]?.fields;
6771
const name = inputObjectTypes[i]?.name;
6872
const transformer = new Transformer({ name, fields });
69-
await transformer.generateObjectSchema();
73+
const moduleName = await transformer.generateObjectSchema();
74+
moduleNames.push(moduleName);
7075
}
76+
await writeFileSafely(
77+
path.join(output, `schemas/objects/index.ts`),
78+
moduleNames.map((name) => `export * from './${name}';`).join('\n')
79+
);
7180
}
7281

7382
async function generateModelSchemas(

packages/plugins/trpc/src/zod/transformer.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export default class Transformer {
5959
)}`
6060
);
6161
}
62+
63+
await writeFileSafely(
64+
path.join(Transformer.outputPath, `schemas/enums/index.ts`),
65+
this.enumTypes.map((enumType) => `export * from './${enumType.name}.schema';`).join('\n')
66+
);
6267
}
6368

6469
generateImportZodStatement() {
@@ -77,6 +82,7 @@ export default class Transformer {
7782
path.join(Transformer.outputPath, `schemas/objects/${this.name}.schema.ts`),
7883
'/* eslint-disable */\n' + objectSchema
7984
);
85+
return `${this.name}.schema`;
8086
}
8187

8288
generateObjectSchemaFields() {
@@ -113,10 +119,11 @@ export default class Transformer {
113119
} else if (inputType.type === 'Boolean') {
114120
result.push(this.wrapWithZodValidators('z.boolean()', field, inputType));
115121
} else if (inputType.type === 'DateTime') {
116-
result.push(this.wrapWithZodValidators('z.date()', field, inputType));
122+
result.push(this.wrapWithZodValidators(['z.date()', 'z.string().datetime()'], field, inputType));
123+
} else if (inputType.type === 'Bytes') {
124+
result.push(this.wrapWithZodValidators('z.number().array()', field, inputType));
117125
} else if (inputType.type === 'Json') {
118126
this.hasJson = true;
119-
120127
result.push(this.wrapWithZodValidators('jsonSchema', field, inputType));
121128
} else if (inputType.type === 'True') {
122129
result.push(this.wrapWithZodValidators('z.literal(true)', field, inputType));
@@ -158,19 +165,29 @@ export default class Transformer {
158165
}
159166

160167
wrapWithZodValidators(
161-
mainValidator: string,
168+
mainValidators: string | string[],
162169
field: PrismaDMMF.SchemaArg,
163170
inputType: PrismaDMMF.SchemaArgInputType
164171
) {
165172
let line = '';
166-
line = mainValidator;
167173

168-
if (inputType.isList) {
169-
line += '.array()';
170-
}
174+
const base = Array.isArray(mainValidators) ? mainValidators : [mainValidators];
175+
176+
line += base
177+
.map((validator) => {
178+
let r = validator;
179+
if (inputType.isList) {
180+
r += '.array()';
181+
}
182+
if (!field.isRequired) {
183+
r += '.optional()';
184+
}
185+
return r;
186+
})
187+
.join(', ');
171188

172-
if (!field.isRequired) {
173-
line += '.optional()';
189+
if (base.length > 1) {
190+
line = `z.union([${line}])`;
174191
}
175192

176193
return line;
@@ -356,8 +373,7 @@ export default class Transformer {
356373
}
357374

358375
async generateModelSchemas() {
359-
const globalImports: string[] = [];
360-
let globalExport = '';
376+
const globalExports: string[] = [];
361377

362378
for (const modelOperation of this.modelOperations) {
363379
const {
@@ -381,8 +397,7 @@ export default class Transformer {
381397
// eslint-disable-next-line @typescript-eslint/no-explicit-any
382398
} = modelOperation;
383399

384-
globalImports.push(`import { ${modelName}Schema } from './${modelName}.schema'`);
385-
globalExport += `${modelName}: ${modelName}Schema,`;
400+
globalExports.push(`export { ${modelName}Schema as ${modelName} } from './${modelName}.schema'`);
386401

387402
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
388403
const model = findModelByName(this.models, modelName)!;
@@ -547,13 +562,7 @@ ${indentString(codeBody, 4)}
547562
path.join(Transformer.outputPath, 'schemas/index.ts'),
548563
`
549564
/* eslint-disable */
550-
${globalImports.join(';\n')}
551-
552-
const schemas = {
553-
${indentString(globalExport, 4)}
554-
};
555-
556-
export default schemas;
565+
${globalExports.join(';\n')}
557566
`
558567
);
559568
}

packages/plugins/trpc/tests/trpc.test.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
/// <reference types="@types/jest" />
22

33
import { loadSchema } from '@zenstackhq/testtools';
4-
import fs from 'fs';
54

65
describe('tRPC Plugin Tests', () => {
76
let origDir: string;
8-
let projDir: string;
97

108
beforeAll(() => {
119
origDir = process.cwd();
1210
});
1311

1412
afterEach(() => {
1513
process.chdir(origDir);
16-
if (projDir) {
17-
fs.rmSync(projDir, { recursive: true, force: true });
18-
}
1914
});
2015

2116
it('run plugin', async () => {
22-
const { projectDir } = await loadSchema(
17+
await loadSchema(
2318
`
2419
plugin trpc {
2520
provider = '${process.cwd()}/dist'
@@ -56,6 +51,5 @@ model Foo {
5651
[`${origDir}/dist`, '@trpc/client', '@trpc/server'],
5752
true
5853
);
59-
projDir = projectDir;
6054
});
6155
});

0 commit comments

Comments
 (0)