Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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.createPropertyAccessExpression(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