diff --git a/packages/runtime/test/typing/models.ts b/packages/runtime/test/typing/models.ts index ef232234..cb27df2a 100644 --- a/packages/runtime/test/typing/models.ts +++ b/packages/runtime/test/typing/models.ts @@ -14,3 +14,5 @@ export type Profile = ModelResult; export type Tag = ModelResult; export type Region = ModelResult; export type Meta = ModelResult; +export const Role = schema.enums.Role; +export type Role = (typeof Role)[keyof typeof Role]; diff --git a/packages/runtime/test/typing/schema.ts b/packages/runtime/test/typing/schema.ts index 05d6166e..fb0db9e1 100644 --- a/packages/runtime/test/typing/schema.ts +++ b/packages/runtime/test/typing/schema.ts @@ -37,6 +37,11 @@ export const schema = { unique: true, attributes: [{ name: "@unique" }] }, + role: { + type: "Role", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal("USER") }] }], + default: "USER" + }, posts: { type: "Post", array: true, @@ -241,6 +246,12 @@ export const schema = { } } }, + enums: { + Role: { + ADMIN: "ADMIN", + USER: "USER" + } + }, authType: "User", plugins: {} } as const satisfies SchemaDef; diff --git a/packages/runtime/test/typing/typing-test.zmodel b/packages/runtime/test/typing/typing-test.zmodel index 022bc1a9..84e9d9b2 100644 --- a/packages/runtime/test/typing/typing-test.zmodel +++ b/packages/runtime/test/typing/typing-test.zmodel @@ -3,12 +3,18 @@ datasource db { url = "file:./test.db" } +enum Role { + ADMIN + USER +} + model User { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt name String email String @unique + role Role @default(USER) posts Post[] profile Profile? postCount Int @computed diff --git a/packages/runtime/test/typing/verify-typing.ts b/packages/runtime/test/typing/verify-typing.ts index e292d69c..e616140e 100644 --- a/packages/runtime/test/typing/verify-typing.ts +++ b/packages/runtime/test/typing/verify-typing.ts @@ -1,4 +1,5 @@ import { ZenStackClient } from '../../dist'; +import { Role } from './models'; import { schema } from './schema'; import SQLite from 'better-sqlite3'; @@ -26,12 +27,14 @@ async function main() { await aggregate(); await groupBy(); await queryBuilder(); + enums(); } async function find() { const user1 = await client.user.findFirst({ where: { name: 'Alex', + role: Role.USER, }, }); console.log(user1?.name); @@ -562,4 +565,12 @@ async function queryBuilder() { console.log(r.name); } +function enums() { + const a: Role = 'ADMIN'; + console.log(a); + let b = Role.ADMIN; + b = a; + console.log(b); +} + main(); diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index 61f7cd67..1f06a3f7 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -1021,20 +1021,49 @@ export class TsSchemaGenerator { // generate enums const enums = model.declarations.filter(isEnum); for (const e of enums) { - // generate: - // export const enum Enum = { - // value1 = 'value1', - // value2 = 'value2', - // } - let enumDecl = ts.factory.createEnumDeclaration( + // generate: export const Enum = schema.enums.Enum; + let enumDecl = ts.factory.createVariableStatement( [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - e.name, - e.fields.map((f) => ts.factory.createEnumMember(f.name, ts.factory.createStringLiteral(f.name))), + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + e.name, + undefined, + undefined, + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('schema'), + ts.factory.createIdentifier('enums'), + ), + ts.factory.createIdentifier(e.name), + ), + ), + ], + ts.NodeFlags.Const, + ), ); if (e.comments.length > 0) { enumDecl = this.generateDocs(enumDecl, e); } statements.push(enumDecl); + + // generate: export type Enum = (typeof Enum)[keyof typeof Enum]; + let typeAlias = ts.factory.createTypeAliasDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + e.name, + undefined, + ts.factory.createIndexedAccessTypeNode( + ts.factory.createTypeQueryNode(ts.factory.createIdentifier(e.name)), + ts.factory.createTypeOperatorNode( + ts.SyntaxKind.KeyOfKeyword, + ts.factory.createTypeQueryNode(ts.factory.createIdentifier(e.name)), + ), + ), + ); + if (e.comments.length > 0) { + typeAlias = this.generateDocs(typeAlias, e); + } + statements.push(typeAlias); } this.generateBannerComments(statements); @@ -1047,7 +1076,10 @@ export class TsSchemaGenerator { fs.writeFileSync(outputFile, result); } - private generateDocs(tsDecl: T, decl: DataModel | Enum): T { + private generateDocs( + tsDecl: T, + decl: DataModel | Enum, + ): T { return ts.addSyntheticLeadingComment( tsDecl, ts.SyntaxKind.MultiLineCommentTrivia, diff --git a/samples/blog/zenstack/models.ts b/samples/blog/zenstack/models.ts index 2e6ee65f..455df504 100644 --- a/samples/blog/zenstack/models.ts +++ b/samples/blog/zenstack/models.ts @@ -25,7 +25,8 @@ export type Post = ModelResult; /** * User roles */ -export enum Role { - ADMIN = "ADMIN", - USER = "USER" -} +export const Role = schema.enums.Role; +/** + * User roles + */ +export type Role = (typeof Role)[keyof typeof Role];