Skip to content

Commit 3e03ab1

Browse files
authored
refactor: remove import from orm package in generated schema (#2387)
1 parent fd7c5ec commit 3e03ab1

File tree

47 files changed

+297
-83
lines changed

Some content is hidden

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

47 files changed

+297
-83
lines changed

packages/cli/src/actions/generate.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ type Options = {
2020
output?: string;
2121
silent: boolean;
2222
watch: boolean;
23-
lite: boolean;
24-
liteOnly: boolean;
23+
lite?: boolean;
24+
liteOnly?: boolean;
25+
generateModels?: boolean;
26+
generateInput?: boolean;
2527
};
2628

2729
/**
@@ -181,12 +183,18 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
181183

182184
// merge CLI options
183185
if (provider === '@core/typescript') {
184-
if (pluginOptions['lite'] === undefined) {
186+
if (options.lite !== undefined) {
185187
pluginOptions['lite'] = options.lite;
186188
}
187-
if (pluginOptions['liteOnly'] === undefined) {
189+
if (options.liteOnly !== undefined) {
188190
pluginOptions['liteOnly'] = options.liteOnly;
189191
}
192+
if (options.generateModels !== undefined) {
193+
pluginOptions['generateModels'] = options.generateModels;
194+
}
195+
if (options.generateInput !== undefined) {
196+
pluginOptions['generateInput'] = options.generateInput;
197+
}
190198
}
191199

192200
processedPlugins.push({ cliPlugin, pluginOptions });
@@ -196,7 +204,12 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
196204
const defaultPlugins = [
197205
{
198206
plugin: corePlugins['typescript'],
199-
options: { lite: options.lite, liteOnly: options.liteOnly },
207+
options: {
208+
lite: options.lite,
209+
liteOnly: options.liteOnly,
210+
generateModels: options.generateModels,
211+
generateInput: options.generateInput,
212+
},
200213
},
201214
];
202215
defaultPlugins.forEach(({ plugin, options }) => {

packages/cli/src/index.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ const proxyAction = async (options: Parameters<typeof actions.proxy>[0]): Promis
4343
await telemetry.trackCommand('proxy', () => actions.proxy(options));
4444
};
4545

46+
function triStateBooleanOption(flag: string, description: string) {
47+
return new Option(flag, description).choices(['true', 'false']).argParser((value) => {
48+
if (value === undefined || value === 'true') return true;
49+
if (value === 'false') return false;
50+
throw new CliError(`Invalid value for ${flag}: ${value}`);
51+
});
52+
}
53+
4654
function createProgram() {
4755
const program = new Command('zen')
4856
.alias('zenstack')
@@ -74,8 +82,20 @@ function createProgram() {
7482
.addOption(noVersionCheckOption)
7583
.addOption(new Option('-o, --output <path>', 'default output directory for code generation'))
7684
.addOption(new Option('-w, --watch', 'enable watch mode').default(false))
77-
.addOption(new Option('--lite', 'also generate a lite version of schema without attributes').default(false))
78-
.addOption(new Option('--lite-only', 'only generate lite version of schema without attributes').default(false))
85+
.addOption(
86+
triStateBooleanOption(
87+
'--lite [boolean]',
88+
'also generate a lite version of schema without attributes, defaults to false',
89+
),
90+
)
91+
.addOption(
92+
triStateBooleanOption(
93+
'--lite-only [boolean]',
94+
'only generate lite version of schema without attributes, defaults to false',
95+
),
96+
)
97+
.addOption(triStateBooleanOption('--generate-models [boolean]', 'generate models.ts file, defaults to true'))
98+
.addOption(triStateBooleanOption('--generate-input [boolean]', 'generate input.ts file, defaults to true'))
7999
.addOption(new Option('--silent', 'suppress all output except errors').default(false))
80100
.action(generateAction);
81101

packages/cli/src/plugins/typescript.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,19 @@ const plugin: CliPlugin = {
2828
throw new Error('The "importWithFileExtension" option must be a string if specified.');
2929
}
3030

31+
// whether to generate models.ts
32+
const generateModelTypes = pluginOptions['generateModels'] !== false;
33+
34+
// whether to generate input.ts
35+
const generateInputTypes = pluginOptions['generateInput'] !== false;
36+
3137
await new TsSchemaGenerator().generate(model, {
3238
outDir,
3339
lite,
3440
liteOnly,
3541
importWithFileExtension: importWithFileExtension as string | undefined,
42+
generateModelTypes,
43+
generateInputTypes,
3644
});
3745
},
3846
};

packages/cli/test/generate.test.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,40 @@ describe('CLI generate command test', () => {
6060
expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true);
6161
});
6262

63+
it('should respect plugin lite options', async () => {
64+
const modelWithPlugin = `
65+
plugin typescript {
66+
provider = "@core/typescript"
67+
lite = true
68+
}
69+
70+
model User {
71+
id String @id @default(cuid())
72+
}
73+
`;
74+
const { workDir } = await createProject(modelWithPlugin);
75+
runCli('generate', workDir);
76+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
77+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
78+
});
79+
80+
it('should respect plugin lite-only options', async () => {
81+
const modelWithPlugin = `
82+
plugin typescript {
83+
provider = "@core/typescript"
84+
liteOnly = true
85+
}
86+
87+
model User {
88+
id String @id @default(cuid())
89+
}
90+
`;
91+
const { workDir } = await createProject(modelWithPlugin);
92+
runCli('generate', workDir);
93+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false);
94+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
95+
});
96+
6397
it('should respect lite option', async () => {
6498
const { workDir } = await createProject(model);
6599
runCli('generate --lite', workDir);
@@ -73,4 +107,114 @@ describe('CLI generate command test', () => {
73107
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false);
74108
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
75109
});
110+
111+
it('should respect explicit liteOnly true option', async () => {
112+
const { workDir } = await createProject(model);
113+
runCli('generate --lite-only=true', workDir);
114+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false);
115+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
116+
});
117+
118+
it('should respect explicit liteOnly false option', async () => {
119+
const { workDir } = await createProject(model);
120+
runCli('generate --lite-only=false', workDir);
121+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
122+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(false);
123+
});
124+
125+
it('should prefer CLI options over @core/typescript plugin settings for lite and liteOnly', async () => {
126+
const modelWithPlugin = `
127+
plugin typescript {
128+
provider = "@core/typescript"
129+
lite = true
130+
liteOnly = true
131+
}
132+
133+
model User {
134+
id String @id @default(cuid())
135+
}
136+
`;
137+
const { workDir } = await createProject(modelWithPlugin);
138+
runCli('generate --lite=false --lite-only=false', workDir);
139+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
140+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(false);
141+
});
142+
143+
it('should generate models.ts and input.ts by default', async () => {
144+
const { workDir } = await createProject(model);
145+
runCli('generate', workDir);
146+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
147+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true);
148+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true);
149+
});
150+
151+
it('should respect plugin options for generateModels and generateInput by default', async () => {
152+
const modelWithPlugin = `
153+
plugin typescript {
154+
provider = "@core/typescript"
155+
generateModels = false
156+
generateInput = false
157+
}
158+
159+
model User {
160+
id String @id @default(cuid())
161+
}
162+
`;
163+
const { workDir } = await createProject(modelWithPlugin);
164+
runCli('generate', workDir);
165+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
166+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(false);
167+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(false);
168+
});
169+
170+
it('should generate models.ts when --generate-models=true is passed', async () => {
171+
const { workDir } = await createProject(model);
172+
runCli('generate --generate-models=true', workDir);
173+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
174+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true);
175+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true);
176+
});
177+
178+
it('should not generate models.ts when --generate-models=false is passed', async () => {
179+
const { workDir } = await createProject(model);
180+
runCli('generate --generate-models=false', workDir);
181+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
182+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(false);
183+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true);
184+
});
185+
186+
it('should generate input.ts when --generate-input=true is passed', async () => {
187+
const { workDir } = await createProject(model);
188+
runCli('generate --generate-input=true', workDir);
189+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
190+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true);
191+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true);
192+
});
193+
194+
it('should not generate input.ts when --generate-input=false is passed', async () => {
195+
const { workDir } = await createProject(model);
196+
runCli('generate --generate-input=false', workDir);
197+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
198+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true);
199+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(false);
200+
});
201+
202+
it('should prefer CLI options over @core/typescript plugin settings for generateModels and generateInput', async () => {
203+
const modelWithPlugin = `
204+
plugin typescript {
205+
provider = "@core/typescript"
206+
generateModels = false
207+
generateInput = false
208+
}
209+
210+
model User {
211+
id String @id @default(cuid())
212+
}
213+
`;
214+
const { workDir } = await createProject(modelWithPlugin);
215+
runCli('generate --generate-models --generate-input', workDir);
216+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
217+
expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true);
218+
expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true);
219+
});
76220
});

packages/clients/tanstack-query/test/schemas/basic/schema-lite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
/* eslint-disable */
77

8-
import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema";
8+
import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema";
99
export class SchemaType implements SchemaDef {
1010
provider = {
1111
type: "sqlite"

packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// NOTE: Test fixture schema used for TanStack Query typing tests. //
33
//////////////////////////////////////////////////////////////////////////////////////////////
44

5-
import { type SchemaDef, ExpressionUtils } from '@zenstackhq/orm/schema';
5+
import { type SchemaDef, ExpressionUtils } from '@zenstackhq/schema';
66

77
export class SchemaType implements SchemaDef {
88
provider = {

packages/orm/src/client/options.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { Dialect, Expression, ExpressionBuilder, KyselyConfig } from 'kysely';
1+
import type { Dialect, Expression, ExpressionBuilder, KyselyConfig, OperandExpression } from 'kysely';
22
import type { GetModel, GetModelFields, GetModels, ProcedureDef, ScalarFields, SchemaDef } from '../schema';
3-
import type { PrependParameter } from '../utils/type-utils';
43
import type { FilterPropertyToKind } from './constants';
54
import type { ClientContract, CRUD_EXT } from './contract';
65
import type { GetProcedureNames, ProcedureHandlerFunc } from './crud-types';
@@ -226,10 +225,15 @@ export type OmitConfig<Schema extends SchemaDef> = {
226225

227226
export type ComputedFieldsOptions<Schema extends SchemaDef> = {
228227
[Model in GetModels<Schema> as 'computedFields' extends keyof GetModel<Schema, Model> ? Model : never]: {
229-
[Field in keyof Schema['models'][Model]['computedFields']]: PrependParameter<
230-
ExpressionBuilder<ToKyselySchema<Schema>, Model>,
231-
Schema['models'][Model]['computedFields'][Field]
232-
>;
228+
[Field in keyof Schema['models'][Model]['computedFields']]: Schema['models'][Model]['computedFields'][Field] extends infer Func
229+
? Func extends (...args: any[]) => infer R
230+
? (
231+
// inject a first parameter for expression builder
232+
p: ExpressionBuilder<ToKyselySchema<Schema>, Model>,
233+
...args: Parameters<Func>
234+
) => OperandExpression<R> // wrap the return type with Kysely `OperandExpression`
235+
: never
236+
: never;
233237
};
234238
};
235239

0 commit comments

Comments
 (0)