Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/runtime/test/scripts/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { fileURLToPath } from 'node:url';
const dir = path.dirname(fileURLToPath(import.meta.url));

async function main() {
// glob all zmodel files in "e2e" directory
const zmodelFiles = glob.sync(path.resolve(dir, '../schemas/**/*.zmodel'));
for (const file of zmodelFiles) {
console.log(`Generating TS schema for: ${file}`);
Expand Down
111 changes: 55 additions & 56 deletions packages/sdk/src/ts-schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ import {
} from './model-utils';

export class TsSchemaGenerator {
private usedExpressionUtils = false;

async generate(model: Model, outputDir: string) {
fs.mkdirSync(outputDir, { recursive: true });

// Reset the flag for each generation
this.usedExpressionUtils = false;

// the schema itself
this.generateSchema(model, outputDir);

Expand Down Expand Up @@ -82,6 +87,10 @@ export class TsSchemaGenerator {
(d) => isDataModel(d) && d.fields.some((f) => hasAttribute(f, '@computed')),
);

// Generate schema content first to determine if ExpressionUtils is needed
const schemaObject = this.createSchemaObject(model);

// Now generate the import declaration with the correct imports
const runtimeImportDecl = ts.factory.createImportDeclaration(
undefined,
ts.factory.createImportClause(
Expand All @@ -98,7 +107,15 @@ export class TsSchemaGenerator {
),
]
: []),
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier('ExpressionUtils')),
...(this.usedExpressionUtils
? [
ts.factory.createImportSpecifier(
false,
undefined,
ts.factory.createIdentifier('ExpressionUtils'),
),
]
: []),
]),
),
ts.factory.createStringLiteral('@zenstackhq/runtime/schema'),
Expand All @@ -114,10 +131,7 @@ export class TsSchemaGenerator {
undefined,
undefined,
ts.factory.createSatisfiesExpression(
ts.factory.createAsExpression(
this.createSchemaObject(model),
ts.factory.createTypeReferenceNode('const'),
),
ts.factory.createAsExpression(schemaObject, ts.factory.createTypeReferenceNode('const')),
ts.factory.createTypeReferenceNode('SchemaDef'),
),
),
Expand All @@ -137,6 +151,15 @@ export class TsSchemaGenerator {
statements.push(typeDeclaration);
}

private createExpressionUtilsCall(method: string, args?: ts.Expression[]): ts.CallExpression {
this.usedExpressionUtils = true;
return ts.factory.createCallExpression(
ts.factory.createIdentifier(`ExpressionUtils.${method}`),
undefined,
args || [],
);
}

private createSchemaObject(model: Model) {
const properties: ts.PropertyAssignment[] = [
// provider
Expand Down Expand Up @@ -477,40 +500,28 @@ export class TsSchemaGenerator {
ts.factory.createPropertyAssignment(
'default',

ts.factory.createCallExpression(
ts.factory.createIdentifier('ExpressionUtils.call'),
undefined,
[
ts.factory.createStringLiteral(defaultValue.call),
...(defaultValue.args.length > 0
? [
ts.factory.createArrayLiteralExpression(
defaultValue.args.map((arg) => this.createLiteralNode(arg)),
),
]
: []),
],
),
this.createExpressionUtilsCall('call', [
ts.factory.createStringLiteral(defaultValue.call),
...(defaultValue.args.length > 0
? [
ts.factory.createArrayLiteralExpression(
defaultValue.args.map((arg) => this.createLiteralNode(arg)),
),
]
: []),
]),
),
);
} else if ('authMember' in defaultValue) {
objectFields.push(
ts.factory.createPropertyAssignment(
'default',
ts.factory.createCallExpression(
ts.factory.createIdentifier('ExpressionUtils.member'),
undefined,
[
ts.factory.createCallExpression(
ts.factory.createIdentifier('ExpressionUtils.call'),
undefined,
[ts.factory.createStringLiteral('auth')],
),
ts.factory.createArrayLiteralExpression(
defaultValue.authMember.map((m) => ts.factory.createStringLiteral(m)),
),
],
),
this.createExpressionUtilsCall('member', [
this.createExpressionUtilsCall('call', [ts.factory.createStringLiteral('auth')]),
ts.factory.createArrayLiteralExpression(
defaultValue.authMember.map((m) => ts.factory.createStringLiteral(m)),
),
]),
),
);
} else {
Expand Down Expand Up @@ -1015,7 +1026,7 @@ export class TsSchemaGenerator {
}

private createThisExpression() {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils._this'), undefined, []);
return this.createExpressionUtilsCall('_this');
}

private createMemberExpression(expr: MemberAccessExpr) {
Expand All @@ -1034,39 +1045,37 @@ export class TsSchemaGenerator {
ts.factory.createArrayLiteralExpression(members.map((m) => ts.factory.createStringLiteral(m))),
];

return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.member'), undefined, args);
return this.createExpressionUtilsCall('member', args);
}

private createNullExpression() {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils._null'), undefined, []);
return this.createExpressionUtilsCall('_null');
}

private createBinaryExpression(expr: BinaryExpr) {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.binary'), undefined, [
return this.createExpressionUtilsCall('binary', [
this.createExpression(expr.left),
this.createLiteralNode(expr.operator),
this.createExpression(expr.right),
]);
}

private createUnaryExpression(expr: UnaryExpr) {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.unary'), undefined, [
return this.createExpressionUtilsCall('unary', [
this.createLiteralNode(expr.operator),
this.createExpression(expr.operand),
]);
}

private createArrayExpression(expr: ArrayExpr): any {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.array'), undefined, [
return this.createExpressionUtilsCall('array', [
ts.factory.createArrayLiteralExpression(expr.items.map((item) => this.createExpression(item))),
]);
}

private createRefExpression(expr: ReferenceExpr): any {
if (isDataField(expr.target.ref)) {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.field'), undefined, [
this.createLiteralNode(expr.target.$refText),
]);
return this.createExpressionUtilsCall('field', [this.createLiteralNode(expr.target.$refText)]);
} else if (isEnumField(expr.target.ref)) {
return this.createLiteralExpression('StringLiteral', expr.target.$refText);
} else {
Expand All @@ -1075,7 +1084,7 @@ export class TsSchemaGenerator {
}

private createCallExpression(expr: InvocationExpr) {
return ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.call'), undefined, [
return this.createExpressionUtilsCall('call', [
ts.factory.createStringLiteral(expr.function.$refText),
...(expr.args.length > 0
? [ts.factory.createArrayLiteralExpression(expr.args.map((arg) => this.createExpression(arg.value)))]
Expand All @@ -1085,21 +1094,11 @@ export class TsSchemaGenerator {

private createLiteralExpression(type: string, value: string | boolean) {
return match(type)
.with('BooleanLiteral', () =>
ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.literal'), undefined, [
this.createLiteralNode(value),
]),
)
.with('BooleanLiteral', () => this.createExpressionUtilsCall('literal', [this.createLiteralNode(value)]))
.with('NumberLiteral', () =>
ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.literal'), undefined, [
ts.factory.createIdentifier(value as string),
]),
)
.with('StringLiteral', () =>
ts.factory.createCallExpression(ts.factory.createIdentifier('ExpressionUtils.literal'), undefined, [
this.createLiteralNode(value),
]),
this.createExpressionUtilsCall('literal', [ts.factory.createIdentifier(value as string)]),
)
.with('StringLiteral', () => this.createExpressionUtilsCall('literal', [this.createLiteralNode(value)]))
.otherwise(() => {
throw new Error(`Unsupported literal type: ${type}`);
});
Expand Down
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions tests/regression/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { loadDocument } from '@zenstackhq/language';
import { TsSchemaGenerator } from '@zenstackhq/sdk';
import { glob } from 'glob';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const dir = path.dirname(fileURLToPath(import.meta.url));

async function main() {
const zmodelFiles = glob.sync(path.resolve(dir, './test/**/*.zmodel'));
for (const file of zmodelFiles) {
console.log(`Generating TS schema for: ${file}`);
await generate(file);
}
}

async function generate(schemaPath: string) {
const generator = new TsSchemaGenerator();
const outputDir = path.dirname(schemaPath);
const tsPath = path.join(outputDir, 'schema.ts');
const pluginModelFiles = glob.sync(path.resolve(dir, '../../packages/runtime/dist/**/plugin.zmodel'));
const result = await loadDocument(schemaPath, pluginModelFiles);
if (!result.success) {
throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
}
await generator.generate(result.model, outputDir);
}

main();
21 changes: 21 additions & 0 deletions tests/regression/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "regression",
"version": "3.0.0-beta.3",
"private": true,
"type": "module",
"scripts": {
"generate": "tsx generate.ts",
"test": "pnpm generate && tsc && vitest run"
},
"dependencies": {
"@zenstackhq/testtools": "workspace:*"
},
"devDependencies": {
"@zenstackhq/cli": "workspace:*",
"@zenstackhq/sdk": "workspace:*",
"@zenstackhq/language": "workspace:*",
"@zenstackhq/runtime": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*"
}
}
30 changes: 30 additions & 0 deletions tests/regression/test/issue-204/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//////////////////////////////////////////////////////////////////////////////////////////////
// DO NOT MODIFY THIS FILE //
// This file is automatically generated by ZenStack CLI and should not be manually updated. //
//////////////////////////////////////////////////////////////////////////////////////////////

/* eslint-disable */

import { type SchemaType as $Schema } from "./schema";
import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, CreateArgs as $CreateArgs, CreateManyArgs as $CreateManyArgs, CreateManyAndReturnArgs as $CreateManyAndReturnArgs, UpdateArgs as $UpdateArgs, UpdateManyArgs as $UpdateManyArgs, UpdateManyAndReturnArgs as $UpdateManyAndReturnArgs, UpsertArgs as $UpsertArgs, DeleteArgs as $DeleteArgs, DeleteManyArgs as $DeleteManyArgs, CountArgs as $CountArgs, AggregateArgs as $AggregateArgs, GroupByArgs as $GroupByArgs, WhereInput as $WhereInput, SelectInput as $SelectInput, IncludeInput as $IncludeInput, OmitInput as $OmitInput } from "@zenstackhq/runtime";
import type { SimplifiedModelResult as $SimplifiedModelResult, SelectIncludeOmit as $SelectIncludeOmit } from "@zenstackhq/runtime";
export type FooFindManyArgs = $FindManyArgs<$Schema, "Foo">;
export type FooFindUniqueArgs = $FindUniqueArgs<$Schema, "Foo">;
export type FooFindFirstArgs = $FindFirstArgs<$Schema, "Foo">;
export type FooCreateArgs = $CreateArgs<$Schema, "Foo">;
export type FooCreateManyArgs = $CreateManyArgs<$Schema, "Foo">;
export type FooCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Foo">;
export type FooUpdateArgs = $UpdateArgs<$Schema, "Foo">;
export type FooUpdateManyArgs = $UpdateManyArgs<$Schema, "Foo">;
export type FooUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Foo">;
export type FooUpsertArgs = $UpsertArgs<$Schema, "Foo">;
export type FooDeleteArgs = $DeleteArgs<$Schema, "Foo">;
export type FooDeleteManyArgs = $DeleteManyArgs<$Schema, "Foo">;
export type FooCountArgs = $CountArgs<$Schema, "Foo">;
export type FooAggregateArgs = $AggregateArgs<$Schema, "Foo">;
export type FooGroupByArgs = $GroupByArgs<$Schema, "Foo">;
export type FooWhereInput = $WhereInput<$Schema, "Foo">;
export type FooSelect = $SelectInput<$Schema, "Foo">;
export type FooInclude = $IncludeInput<$Schema, "Foo">;
export type FooOmit = $OmitInput<$Schema, "Foo">;
export type FooGetPayload<Args extends $SelectIncludeOmit<$Schema, "Foo", true>> = $SimplifiedModelResult<$Schema, "Foo", Args>;
13 changes: 13 additions & 0 deletions tests/regression/test/issue-204/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//////////////////////////////////////////////////////////////////////////////////////////////
// DO NOT MODIFY THIS FILE //
// This file is automatically generated by ZenStack CLI and should not be manually updated. //
//////////////////////////////////////////////////////////////////////////////////////////////

/* eslint-disable */

import { schema as $schema, type SchemaType as $Schema } from "./schema";
import { type ModelResult as $ModelResult, type TypeDefResult as $TypeDefResult } from "@zenstackhq/runtime";
export type Foo = $ModelResult<$Schema, "Foo">;
export type Configuration = $TypeDefResult<$Schema, "Configuration">;
export const ShirtColor = $schema.enums.ShirtColor;
export type ShirtColor = (typeof ShirtColor)[keyof typeof ShirtColor];
11 changes: 11 additions & 0 deletions tests/regression/test/issue-204/regression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, it } from 'vitest';
import { type Configuration, ShirtColor } from './models';

describe('Issue 204 regression tests', () => {
it('tests issue 204', () => {
const config: Configuration = { teamColors: [ShirtColor.Black, ShirtColor.Blue] };
console.log(config.teamColors?.[0]);
const config1: Configuration = {};
console.log(config1);
});
});
21 changes: 21 additions & 0 deletions tests/regression/test/issue-204/regression.zmodel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}

enum ShirtColor {
Black
White
Red
Green
Blue
}

type Configuration {
teamColors ShirtColor[]? // This should be an optional array
}

model Foo {
id Int @id
config Configuration @json
}
Loading