From 335cbbe6dddde4e6ec19c3e35a3d57b2e3295517 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:16:01 +0800 Subject: [PATCH 1/6] fix(type-check): try improving type checking performance --- packages/common-helpers/src/zip.ts | 2 +- packages/orm/src/client/contract.ts | 45 +++-- packages/orm/src/client/crud-types.ts | 89 +++++----- .../orm/src/client/crud/operations/base.ts | 10 +- .../orm/src/client/crud/validator/index.ts | 2 +- .../orm/src/client/crud/validator/utils.ts | 24 ++- .../src/client/helpers/schema-db-pusher.ts | 4 +- packages/orm/src/utils/object-utils.ts | 4 +- packages/schema/src/schema.ts | 43 ++--- packages/sdk/src/ts-schema-generator.ts | 156 ++++++++++-------- samples/next.js/zenstack/schema-lite.ts | 22 +-- samples/next.js/zenstack/schema.ts | 22 +-- samples/orm/main.ts | 2 +- samples/orm/zenstack/schema.ts | 30 ++-- tests/e2e/apps/rally/zenstack/schema.ts | 26 ++- tests/e2e/github-repos/cal.com/schema.ts | 26 ++- tests/e2e/github-repos/formbricks/schema.ts | 26 ++- tests/e2e/github-repos/trigger.dev/schema.ts | 26 ++- tests/e2e/orm/schemas/auth-type/schema.ts | 26 ++- tests/e2e/orm/schemas/basic/schema.ts | 30 ++-- tests/e2e/orm/schemas/default-auth/schema.ts | 22 +-- tests/e2e/orm/schemas/delegate/schema.ts | 22 +-- tests/e2e/orm/schemas/delegate/typecheck.ts | 40 +++-- tests/e2e/orm/schemas/json/schema.ts | 20 +-- tests/e2e/orm/schemas/name-mapping/schema.ts | 26 ++- tests/e2e/orm/schemas/omit/schema.ts | 22 +-- tests/e2e/orm/schemas/petstore/schema.ts | 22 +-- tests/e2e/orm/schemas/typed-json/schema.ts | 30 ++-- tests/e2e/orm/schemas/typing/schema.ts | 30 ++-- tests/runtimes/bun/schemas/schema.ts | 22 +-- tests/runtimes/edge-runtime/schemas/schema.ts | 22 +-- 31 files changed, 425 insertions(+), 468 deletions(-) diff --git a/packages/common-helpers/src/zip.ts b/packages/common-helpers/src/zip.ts index 35d4981b5..540a52769 100644 --- a/packages/common-helpers/src/zip.ts +++ b/packages/common-helpers/src/zip.ts @@ -1,7 +1,7 @@ /** * Zips two arrays into an array of tuples. */ -export function zip(arr1: T[], arr2: U[]): Array<[T, U]> { +export function zip(arr1: readonly T[], arr2: readonly U[]): Array<[T, U]> { const length = Math.min(arr1.length, arr2.length); const result: Array<[T, U]> = []; for (let i = 0; i < length; i++) { diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index 57cd9b05e..fc552eb3c 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -41,7 +41,6 @@ import type { } from './crud-types'; import type { ClientOptions } from './options'; import type { RuntimePlugin } from './plugin'; -import type { ZenStackPromise } from './promise'; import type { ToKysely } from './query-builder'; type TransactionUnsupportedMethods = (typeof TRANSACTION_UNSUPPORTED_METHODS)[number]; @@ -78,7 +77,7 @@ export type ClientContract; + $executeRaw(query: TemplateStringsArray, ...values: any[]): Promise; /** * Executes a raw query and returns the number of affected rows. @@ -88,7 +87,7 @@ export type ClientContract; + $executeRawUnsafe(query: string, ...values: any[]): Promise; /** * Performs a prepared raw query and returns the `SELECT` data. @@ -97,7 +96,7 @@ export type ClientContract(query: TemplateStringsArray, ...values: any[]): ZenStackPromise; + $queryRaw(query: TemplateStringsArray, ...values: any[]): Promise; /** * Performs a raw query and returns the `SELECT` data. @@ -107,7 +106,7 @@ export type ClientContract(query: string, ...values: any[]): ZenStackPromise; + $queryRawUnsafe(query: string, ...values: any[]): Promise; /** * The current user identity. @@ -157,7 +156,7 @@ export type ClientContract[]>( + $transaction

[]>( arg: [...P], options?: { isolationLevel?: TransactionIsolationLevel }, ): Promise>; @@ -359,7 +358,7 @@ export type AllModelOperations< */ findMany>( args?: SelectSubset>, - ): ZenStackPromise[]>; + ): Promise[]>; /** * Returns a uniquely identified entity. @@ -369,7 +368,7 @@ export type AllModelOperations< */ findUnique>( args: SelectSubset>, - ): ZenStackPromise | null>; + ): Promise | null>; /** * Returns a uniquely identified entity or throws `NotFoundError` if not found. @@ -379,7 +378,7 @@ export type AllModelOperations< */ findUniqueOrThrow>( args: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Returns the first entity. @@ -389,7 +388,7 @@ export type AllModelOperations< */ findFirst>( args?: SelectSubset>, - ): ZenStackPromise | null>; + ): Promise | null>; /** * Returns the first entity or throws `NotFoundError` if not found. @@ -399,7 +398,7 @@ export type AllModelOperations< */ findFirstOrThrow>( args?: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Creates a new entity. @@ -455,7 +454,7 @@ export type AllModelOperations< */ create>( args: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Creates multiple entities. Only scalar fields are allowed. @@ -484,7 +483,7 @@ export type AllModelOperations< */ createMany>( args?: SelectSubset>, - ): ZenStackPromise; + ): Promise; /** * Creates multiple entities and returns them. @@ -506,7 +505,7 @@ export type AllModelOperations< */ createManyAndReturn>( args?: SelectSubset>, - ): ZenStackPromise[]>; + ): Promise[]>; /** * Updates a uniquely identified entity. @@ -627,7 +626,7 @@ export type AllModelOperations< */ update>( args: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Updates multiple entities. @@ -651,7 +650,7 @@ export type AllModelOperations< */ updateMany>( args: Subset>, - ): ZenStackPromise; + ): Promise; /** * Updates multiple entities and returns them. @@ -677,7 +676,7 @@ export type AllModelOperations< */ updateManyAndReturn>( args: Subset>, - ): ZenStackPromise[]>; + ): Promise[]>; /** * Creates or updates an entity. @@ -701,7 +700,7 @@ export type AllModelOperations< */ upsert>( args: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Deletes a uniquely identifiable entity. @@ -724,7 +723,7 @@ export type AllModelOperations< */ delete>( args: SelectSubset>, - ): ZenStackPromise>; + ): Promise>; /** * Deletes multiple entities. @@ -747,7 +746,7 @@ export type AllModelOperations< */ deleteMany>( args?: Subset>, - ): ZenStackPromise; + ): Promise; /** * Counts rows or field values. @@ -769,7 +768,7 @@ export type AllModelOperations< */ count>( args?: Subset>, - ): ZenStackPromise>>; + ): Promise>>; /** * Aggregates rows. @@ -790,7 +789,7 @@ export type AllModelOperations< */ aggregate>( args: Subset>, - ): ZenStackPromise>>; + ): Promise>>; /** * Groups rows by columns. @@ -827,7 +826,7 @@ export type AllModelOperations< */ groupBy>( args: Subset>, - ): ZenStackPromise>>; + ): Promise>>; }; export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert'; diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 59d1b41be..796ea633f 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -12,17 +12,14 @@ import type { GetEnum, GetEnums, GetModel, - GetModelDiscriminator, GetModelField, GetModelFields, GetModelFieldType, GetModels, - GetSubModels, GetTypeDefField, GetTypeDefFields, GetTypeDefFieldType, GetTypeDefs, - IsDelegateModel, ModelFieldIsOptional, NonRelationFields, RelationFields, @@ -62,20 +59,25 @@ export type DefaultModelResult< Optional = false, Array = false, > = WrapType< - IsDelegateModel extends true - ? // delegate model's selection result is a union of all sub-models - DelegateUnionResult, Omit> - : { - [Key in NonRelationFields as ShouldOmitField< - Schema, - Model, - Options, - Key, - Omit - > extends true - ? never - : Key]: MapModelFieldType; - }, + { + [Key in NonRelationFields as ShouldOmitField extends true + ? never + : Key]: MapModelFieldType; + }, + // IsDelegateModel extends true + // ? // delegate model's selection result is a union of all sub-models + // DelegateUnionResult, Omit> + // : { + // [Key in NonRelationFields as ShouldOmitField< + // Schema, + // Model, + // Options, + // Key, + // Omit + // > extends true + // ? never + // : Key]: MapModelFieldType; + // }, Optional, Array >; @@ -120,15 +122,15 @@ type SchemaLevelOmit< Field extends GetModelFields, > = GetModelField['omit'] extends true ? true : false; -type DelegateUnionResult< - Schema extends SchemaDef, - Model extends GetModels, - Options extends ClientOptions, - SubModel extends GetModels, - Omit = undefined, -> = SubModel extends string // typescript union distribution - ? DefaultModelResult & { [K in GetModelDiscriminator]: SubModel } // fixate discriminated field - : never; +// type DelegateUnionResult< +// Schema extends SchemaDef, +// Model extends GetModels, +// Options extends ClientOptions, +// SubModel extends GetModels, +// Omit = undefined, +// > = SubModel extends string // typescript union distribution +// ? DefaultModelResult & { [K in GetModelDiscriminator]: SubModel } // fixate discriminated field +// : never; type ModelSelectResult< Schema extends SchemaDef, @@ -1029,7 +1031,7 @@ type OppositeRelationFields< Model extends GetModels, Field extends GetModelFields, Opposite = OppositeRelation, -> = Opposite extends RelationInfo ? (Opposite['fields'] extends string[] ? Opposite['fields'] : []) : []; +> = Opposite extends RelationInfo ? (Opposite['fields'] extends readonly string[] ? Opposite['fields'] : []) : []; type OppositeRelationAndFK< Schema extends SchemaDef, @@ -1083,21 +1085,16 @@ export type FindArgs< Model extends GetModels, Collection extends boolean, AllowFilter extends boolean = true, -> = - ProviderSupportsDistinct extends true - ? (Collection extends true - ? SortAndTakeArgs & { - /** - * Distinct fields - */ - distinct?: OrArray>; - } - : {}) & - (AllowFilter extends true ? FilterArgs : {}) & - SelectIncludeOmit - : (Collection extends true ? SortAndTakeArgs : {}) & - (AllowFilter extends true ? FilterArgs : {}) & - SelectIncludeOmit; +> = (Collection extends true + ? SortAndTakeArgs & { + /** + * Distinct fields + */ + distinct?: OrArray>; + } + : {}) & + (AllowFilter extends true ? FilterArgs : {}) & + SelectIncludeOmit; export type FindManyArgs> = FindArgs; @@ -2002,7 +1999,7 @@ type NestedDeleteManyInput< type NonOwnedRelationFields> = keyof { [Key in RelationFields as GetModelField['relation'] extends { - references: unknown[]; + references: readonly unknown[]; } ? never : Key]: true; @@ -2014,8 +2011,8 @@ type HasToManyRelations = Schema['provider']['type'] extends 'postgresql' - ? true - : false; +// type ProviderSupportsDistinct = Schema['provider']['type'] extends 'postgresql' +// ? true +// : false; // #endregion diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index f5fbca54f..f17542b7e 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -250,7 +250,7 @@ export abstract class BaseOperationHandler { data: any, fromRelation?: FromRelationContext, creatingForDelegate = false, - returnFields?: string[], + returnFields?: readonly string[], ): Promise { const modelDef = this.requireModel(model); @@ -662,7 +662,7 @@ export abstract class BaseOperationHandler { input: { data: any; skipDuplicates?: boolean }, returnData: ReturnData, fromRelation?: FromRelationContext, - fieldsToReturn?: string[], + fieldsToReturn?: readonly string[], ): Promise { if (!input.data || (Array.isArray(input.data) && input.data.length === 0)) { // nothing todo @@ -901,7 +901,7 @@ export abstract class BaseOperationHandler { fromRelation?: FromRelationContext, allowRelationUpdate = true, throwIfNotFound = true, - fieldsToReturn?: string[], + fieldsToReturn?: readonly string[], ): Promise { if (!data || typeof data !== 'object') { throw createInvalidInputError('data must be an object'); @@ -1207,7 +1207,7 @@ export abstract class BaseOperationHandler { limit: number | undefined, returnData: ReturnData, filterModel?: string, - fieldsToReturn?: string[], + fieldsToReturn?: readonly string[], ): Promise { if (typeof data !== 'object') { throw createInvalidInputError('data must be an object'); @@ -1923,7 +1923,7 @@ export abstract class BaseOperationHandler { where: any, limit?: number, filterModel?: string, - fieldsToReturn?: string[], + fieldsToReturn?: readonly string[], ): Promise> { filterModel ??= model; diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 298b64bca..f67699e7e 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -297,7 +297,7 @@ export class InputValidator { return result; } - private makeScalarSchema(type: string, attributes?: AttributeApplication[]) { + private makeScalarSchema(type: string, attributes?: readonly AttributeApplication[]) { if (this.schema.typeDefs && type in this.schema.typeDefs) { return this.makeTypeDefSchema(type); } else if (this.schema.enums && type in this.schema.enums) { diff --git a/packages/orm/src/client/crud/validator/utils.ts b/packages/orm/src/client/crud/validator/utils.ts index 657ab8049..bbd35900e 100644 --- a/packages/orm/src/client/crud/validator/utils.ts +++ b/packages/orm/src/client/crud/validator/utils.ts @@ -22,7 +22,10 @@ function getArgValue(expr: Expression | und return expr.value as T; } -export function addStringValidation(schema: z.ZodString, attributes: AttributeApplication[] | undefined): z.ZodSchema { +export function addStringValidation( + schema: z.ZodString, + attributes: readonly AttributeApplication[] | undefined, +): z.ZodSchema { if (!attributes || attributes.length === 0) { return schema; } @@ -86,7 +89,10 @@ export function addStringValidation(schema: z.ZodString, attributes: AttributeAp return result; } -export function addNumberValidation(schema: z.ZodNumber, attributes: AttributeApplication[] | undefined): z.ZodSchema { +export function addNumberValidation( + schema: z.ZodNumber, + attributes: readonly AttributeApplication[] | undefined, +): z.ZodSchema { if (!attributes || attributes.length === 0) { return schema; } @@ -114,7 +120,10 @@ export function addNumberValidation(schema: z.ZodNumber, attributes: AttributeAp return result; } -export function addBigIntValidation(schema: z.ZodBigInt, attributes: AttributeApplication[] | undefined): z.ZodSchema { +export function addBigIntValidation( + schema: z.ZodBigInt, + attributes: readonly AttributeApplication[] | undefined, +): z.ZodSchema { if (!attributes || attributes.length === 0) { return schema; } @@ -145,7 +154,7 @@ export function addBigIntValidation(schema: z.ZodBigInt, attributes: AttributeAp export function addDecimalValidation( schema: z.ZodType | z.ZodString, - attributes: AttributeApplication[] | undefined, + attributes: readonly AttributeApplication[] | undefined, addExtraValidation: boolean, ): z.ZodSchema { let result: z.ZodSchema = schema; @@ -224,7 +233,7 @@ export function addDecimalValidation( export function addListValidation( schema: z.ZodArray, - attributes: AttributeApplication[] | undefined, + attributes: readonly AttributeApplication[] | undefined, ): z.ZodSchema { if (!attributes || attributes.length === 0) { return schema; @@ -248,7 +257,10 @@ export function addListValidation( return result; } -export function addCustomValidation(schema: z.ZodSchema, attributes: AttributeApplication[] | undefined): z.ZodSchema { +export function addCustomValidation( + schema: z.ZodSchema, + attributes: readonly AttributeApplication[] | undefined, +): z.ZodSchema { const attrs = attributes?.filter((a) => a.name === '@@validate'); if (!attrs || attrs.length === 0) { return schema; diff --git a/packages/orm/src/client/helpers/schema-db-pusher.ts b/packages/orm/src/client/helpers/schema-db-pusher.ts index f04c14e19..01b265c46 100644 --- a/packages/orm/src/client/helpers/schema-db-pusher.ts +++ b/packages/orm/src/client/helpers/schema-db-pusher.ts @@ -115,9 +115,9 @@ export class SchemaDbPusher { const baseModelDef = requireModel(this.schema, modelDef.baseModel); table = table.addForeignKeyConstraint( `fk_${modelDef.baseModel}_delegate`, - baseModelDef.idFields, + baseModelDef.idFields as string[], modelDef.baseModel, - baseModelDef.idFields, + baseModelDef.idFields as string[], (cb) => cb.onDelete('cascade').onUpdate('cascade'), ); } diff --git a/packages/orm/src/utils/object-utils.ts b/packages/orm/src/utils/object-utils.ts index e05ebe5d9..ba9a0886d 100644 --- a/packages/orm/src/utils/object-utils.ts +++ b/packages/orm/src/utils/object-utils.ts @@ -1,13 +1,13 @@ /** * Extract fields from an object. */ -export function extractFields(obj: any, fields: string[]) { +export function extractFields(obj: any, fields: readonly string[]) { return Object.fromEntries(Object.entries(obj).filter(([key]) => fields.includes(key))); } /** * Create an object with fields as keys and true values. */ -export function fieldsToSelectObject(fields: string[]): Record { +export function fieldsToSelectObject(fields: readonly string[]): Record { return Object.fromEntries(fields.map((f) => [f, true])); } diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 342807346..93c0c9fc4 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -22,7 +22,7 @@ export type ModelDef = { name: string; baseModel?: string; fields: Record; - attributes?: AttributeApplication[]; + attributes?: readonly AttributeApplication[]; uniqueFields: Record< string, // singular unique field @@ -30,16 +30,16 @@ export type ModelDef = { // compound unique field | Record> >; - idFields: string[]; + idFields: readonly string[]; computedFields?: Record; isDelegate?: boolean; - subModels?: string[]; + subModels?: readonly string[]; isView?: boolean; }; export type AttributeApplication = { name: string; - args?: AttributeArg[]; + args?: readonly AttributeArg[]; }; export type AttributeArg = { @@ -51,8 +51,8 @@ export type CascadeAction = 'SetNull' | 'Cascade' | 'Restrict' | 'NoAction' | 'S export type RelationInfo = { name?: string; - fields?: string[]; - references?: string[]; + fields?: readonly string[]; + references?: readonly string[]; hasDefault?: boolean; opposite?: string; onDelete?: CascadeAction; @@ -67,11 +67,11 @@ export type FieldDef = { optional?: boolean; unique?: boolean; updatedAt?: boolean; - attributes?: AttributeApplication[]; - default?: MappedBuiltinType | Expression | unknown[]; + attributes?: readonly AttributeApplication[]; + default?: MappedBuiltinType | Expression | readonly unknown[]; omit?: boolean; relation?: RelationInfo; - foreignKeyFor?: string[]; + foreignKeyFor?: readonly string[]; computed?: boolean; originModel?: string; isDiscriminator?: boolean; @@ -101,19 +101,19 @@ export type MappedBuiltinType = string | boolean | number | bigint | Decimal | D export type EnumField = { name: string; - attributes?: AttributeApplication[]; + attributes?: readonly AttributeApplication[]; }; export type EnumDef = { fields?: Record; values: Record; - attributes?: AttributeApplication[]; + attributes?: readonly AttributeApplication[]; }; export type TypeDefDef = { name: string; fields: Record; - attributes?: AttributeApplication[]; + attributes?: readonly AttributeApplication[]; }; //#region Extraction @@ -127,7 +127,7 @@ export type GetDelegateModels = keyof { export type GetSubModels> = GetModel< Schema, Model ->['subModels'] extends string[] +>['subModels'] extends readonly string[] ? Extract['subModels'][number], GetModels> : never; @@ -193,7 +193,7 @@ export type ScalarFields< > = keyof { [Key in GetModelFields as GetModelField['relation'] extends object ? never - : GetModelField['foreignKeyFor'] extends string[] + : GetModelField['foreignKeyFor'] extends readonly string[] ? never : IncludeComputed extends true ? Key @@ -203,7 +203,11 @@ export type ScalarFields< }; export type ForeignKeyFields> = keyof { - [Key in GetModelFields as GetModelField['foreignKeyFor'] extends string[] + [Key in GetModelFields as GetModelField< + Schema, + Model, + Key + >['foreignKeyFor'] extends readonly string[] ? Key : never]: Key; }; @@ -289,10 +293,11 @@ export type FieldIsRelationArray< Field extends GetModelFields, > = FieldIsRelation extends true ? FieldIsArray : false; -export type IsDelegateModel< - Schema extends SchemaDef, - Model extends GetModels, -> = Schema['models'][Model]['isDelegate'] extends true ? true : false; +export type IsDelegateModel> = string extends Model + ? false + : Schema['models'][Model]['isDelegate'] extends true + ? true + : false; export type FieldIsDelegateRelation< Schema extends SchemaDef, diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index 780817aaa..9620ca671 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -115,14 +115,14 @@ export class TsSchemaGenerator { ); // Generate schema content first to determine if ExpressionUtils is needed - const schemaObject = this.createSchemaObject(model, lite); + const schemaClass = this.createSchemaClass(model, lite); // Now generate the import declaration with the correct imports // import { type SchemaDef, type OperandExpression, ExpressionUtils } from '@zenstackhq/orm/schema'; const runtimeImportDecl = ts.factory.createImportDeclaration( undefined, ts.factory.createImportClause( - false, + undefined, undefined, ts.factory.createNamedImports([ ts.factory.createImportSpecifier(true, undefined, ts.factory.createIdentifier('SchemaDef')), @@ -150,71 +150,24 @@ export class TsSchemaGenerator { ); statements.push(runtimeImportDecl); - // const _schema = { ... } as const satisfies SchemaDef; - const _schemaDecl = ts.factory.createVariableStatement( - [], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - '_schema', - undefined, - undefined, - ts.factory.createSatisfiesExpression( - ts.factory.createAsExpression(schemaObject, ts.factory.createTypeReferenceNode('const')), - ts.factory.createTypeReferenceNode('SchemaDef'), - ), - ), - ], - ts.NodeFlags.Const, - ), - ); - statements.push(_schemaDecl); + statements.push(schemaClass); - // type Schema = typeof _schema & { __brand?: 'schema' }; - // use a branded type to prevent typescript compiler from expanding the schema type - const brandedSchemaType = ts.factory.createTypeAliasDeclaration( - undefined, - 'Schema', - undefined, - ts.factory.createIntersectionTypeNode([ - ts.factory.createTypeQueryNode(ts.factory.createIdentifier('_schema')), - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( - undefined, - '__brand', - ts.factory.createToken(ts.SyntaxKind.QuestionToken), - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('schema')), - ), - ]), - ]), - ); - statements.push(brandedSchemaType); - - // export const schema: Schema = _schema; - const schemaExportDecl = ts.factory.createVariableStatement( + // export const schema = new SchemaType(); + const schemaDecl = ts.factory.createVariableStatement( [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList( [ ts.factory.createVariableDeclaration( 'schema', undefined, - ts.factory.createTypeReferenceNode('Schema'), - ts.factory.createIdentifier('_schema'), + undefined, + ts.factory.createNewExpression(ts.factory.createIdentifier('SchemaType'), undefined, []), ), ], ts.NodeFlags.Const, ), ); - statements.push(schemaExportDecl); - - // export type SchemaType = Schema; - const schemaTypeDeclaration = ts.factory.createTypeAliasDeclaration( - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - 'SchemaType', - undefined, - ts.factory.createTypeReferenceNode('Schema'), - ); - statements.push(schemaTypeDeclaration); + statements.push(schemaDecl); } private createExpressionUtilsCall(method: string, args?: ts.Expression[]): ts.CallExpression { @@ -226,29 +179,54 @@ export class TsSchemaGenerator { ); } - private createSchemaObject(model: Model, lite: boolean): ts.Expression { - const properties: ts.PropertyAssignment[] = [ + private createSchemaClass(model: Model, lite: boolean) { + const members: ts.ClassElement[] = [ // provider - ts.factory.createPropertyAssignment('provider', this.createProviderObject(model)), + ts.factory.createPropertyDeclaration( + undefined, + 'provider', + undefined, + undefined, + this.createAsConst(this.createProviderObject(model)), + ), // models - ts.factory.createPropertyAssignment('models', this.createModelsObject(model, lite)), + ts.factory.createPropertyDeclaration( + undefined, + 'models', + undefined, + undefined, + this.createAsConst(this.createModelsObject(model, lite)), + ), // typeDefs ...(model.declarations.some(isTypeDef) - ? [ts.factory.createPropertyAssignment('typeDefs', this.createTypeDefsObject(model, lite))] + ? [ + ts.factory.createPropertyDeclaration( + undefined, + 'typeDefs', + undefined, + undefined, + this.createAsConst(this.createTypeDefsObject(model, lite)), + ), + ] : []), ]; // enums const enums = model.declarations.filter(isEnum); if (enums.length > 0) { - properties.push( - ts.factory.createPropertyAssignment( + members.push( + ts.factory.createPropertyDeclaration( + undefined, 'enums', - ts.factory.createObjectLiteralExpression( - enums.map((e) => ts.factory.createPropertyAssignment(e.name, this.createEnumObject(e))), - true, + undefined, + undefined, + this.createAsConst( + ts.factory.createObjectLiteralExpression( + enums.map((e) => ts.factory.createPropertyAssignment(e.name, this.createEnumObject(e))), + true, + ), ), ), ); @@ -257,21 +235,59 @@ export class TsSchemaGenerator { // authType const authType = getAuthDecl(model); if (authType) { - properties.push(ts.factory.createPropertyAssignment('authType', this.createLiteralNode(authType.name))); + members.push( + ts.factory.createPropertyDeclaration( + undefined, + 'authType', + undefined, + undefined, + this.createAsConst(this.createLiteralNode(authType.name)), + ), + ); } // procedures const procedures = model.declarations.filter(isProcedure); if (procedures.length > 0) { - properties.push(ts.factory.createPropertyAssignment('procedures', this.createProceduresObject(procedures))); + members.push( + ts.factory.createPropertyDeclaration( + undefined, + 'procedures', + undefined, + undefined, + this.createAsConst(this.createProceduresObject(procedures)), + ), + ); } // plugins - properties.push( - ts.factory.createPropertyAssignment('plugins', ts.factory.createObjectLiteralExpression([], true)), + members.push( + ts.factory.createPropertyDeclaration( + undefined, + 'plugins', + undefined, + undefined, + ts.factory.createObjectLiteralExpression([], true), + ), ); - return ts.factory.createObjectLiteralExpression(properties, true); + const schemaClass = ts.factory.createClassDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], + 'SchemaType', + undefined, + [ + ts.factory.createHeritageClause(ts.SyntaxKind.ImplementsKeyword, [ + ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier('SchemaDef'), undefined), + ]), + ], + members, + ); + + return schemaClass; + } + + private createAsConst(expr: ts.Expression) { + return ts.factory.createAsExpression(expr, ts.factory.createTypeReferenceNode('const')); } private createProviderObject(model: Model): ts.Expression { diff --git a/samples/next.js/zenstack/schema-lite.ts b/samples/next.js/zenstack/schema-lite.ts index e8b2f096e..6153abe9d 100644 --- a/samples/next.js/zenstack/schema-lite.ts +++ b/samples/next.js/zenstack/schema-lite.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -99,12 +99,8 @@ const _schema = { id: { type: "String" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/samples/next.js/zenstack/schema.ts b/samples/next.js/zenstack/schema.ts index 6388c6c2b..c7d690ed5 100644 --- a/samples/next.js/zenstack/schema.ts +++ b/samples/next.js/zenstack/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -108,12 +108,8 @@ const _schema = { id: { type: "String" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/samples/orm/main.ts b/samples/orm/main.ts index c153fabed..59e814c13 100644 --- a/samples/orm/main.ts +++ b/samples/orm/main.ts @@ -1,5 +1,5 @@ -import { PolicyPlugin } from '@zenstackhq/plugin-policy'; import { ZenStackClient } from '@zenstackhq/orm'; +import { PolicyPlugin } from '@zenstackhq/plugin-policy'; import SQLite from 'better-sqlite3'; import { sql, SqliteDialect } from 'kysely'; import { schema } from './zenstack/schema'; diff --git a/samples/orm/zenstack/schema.ts b/samples/orm/zenstack/schema.ts index 1db7031c0..deb07cb9c 100644 --- a/samples/orm/zenstack/schema.ts +++ b/samples/orm/zenstack/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, type OperandExpression, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -204,8 +204,8 @@ const _schema = { id: { type: "String" } } } - }, - typeDefs: { + } as const; + typeDefs = { CommonFields: { name: "CommonFields", fields: { @@ -229,20 +229,16 @@ const _schema = { } } } - }, - enums: { + } as const; + enums = { Role: { values: { ADMIN: "ADMIN", USER: "USER" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/apps/rally/zenstack/schema.ts b/tests/e2e/apps/rally/zenstack/schema.ts index 278bac74a..7d20facea 100644 --- a/tests/e2e/apps/rally/zenstack/schema.ts +++ b/tests/e2e/apps/rally/zenstack/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { Account: { name: "Account", fields: { @@ -2388,8 +2388,8 @@ const _schema = { licenseKey: { type: "String" } } } - }, - enums: { + } as const; + enums = { TimeFormat: { values: { hours12: "hours12", @@ -2516,12 +2516,8 @@ const _schema = { REVOKED: "REVOKED" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/github-repos/cal.com/schema.ts b/tests/e2e/github-repos/cal.com/schema.ts index d10a319ce..8648a17cb 100644 --- a/tests/e2e/github-repos/cal.com/schema.ts +++ b/tests/e2e/github-repos/cal.com/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { Host: { name: "Host", fields: { @@ -9182,8 +9182,8 @@ const _schema = { roleId_resource_action: { roleId: { type: "String" }, resource: { type: "String" }, action: { type: "String" } } } } - }, - enums: { + } as const; + enums = { SchedulingType: { values: { ROUND_ROBIN: "ROUND_ROBIN", @@ -9636,12 +9636,8 @@ const _schema = { CUSTOM: "CUSTOM" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/github-repos/formbricks/schema.ts b/tests/e2e/github-repos/formbricks/schema.ts index 0c55d1cd8..935b8320b 100644 --- a/tests/e2e/github-repos/formbricks/schema.ts +++ b/tests/e2e/github-repos/formbricks/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { Webhook: { name: "Webhook", fields: { @@ -2831,8 +2831,8 @@ const _schema = { projectId_teamId: { projectId: { type: "String" }, teamId: { type: "String" } } } } - }, - enums: { + } as const; + enums = { PipelineTriggers: { values: { responseCreated: "responseCreated", @@ -3018,12 +3018,8 @@ const _schema = { manage: "manage" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/github-repos/trigger.dev/schema.ts b/tests/e2e/github-repos/trigger.dev/schema.ts index 5b19229b7..9b884d078 100644 --- a/tests/e2e/github-repos/trigger.dev/schema.ts +++ b/tests/e2e/github-repos/trigger.dev/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -6078,8 +6078,8 @@ const _schema = { id_createdAt: { id: { type: "String" }, createdAt: { type: "DateTime" } } } } - }, - enums: { + } as const; + enums = { AuthenticationMethod: { values: { GITHUB: "GITHUB", @@ -6337,12 +6337,8 @@ const _schema = { FAILED: "FAILED" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/auth-type/schema.ts b/tests/e2e/orm/schemas/auth-type/schema.ts index 5904569b9..c57c43903 100644 --- a/tests/e2e/orm/schemas/auth-type/schema.ts +++ b/tests/e2e/orm/schemas/auth-type/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { Foo: { name: "Foo", fields: { @@ -26,8 +26,8 @@ const _schema = { id: { type: "Int" } } } - }, - typeDefs: { + } as const; + typeDefs = { Permission: { name: "Permission", fields: { @@ -63,12 +63,8 @@ const _schema = { { name: "@@auth" } ] } - }, - authType: "Auth", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "Auth" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/basic/schema.ts b/tests/e2e/orm/schemas/basic/schema.ts index 3774b2070..5f067685e 100644 --- a/tests/e2e/orm/schemas/basic/schema.ts +++ b/tests/e2e/orm/schemas/basic/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -247,8 +247,8 @@ const _schema = { userId: { type: "String" } } } - }, - typeDefs: { + } as const; + typeDefs = { CommonFields: { name: "CommonFields", fields: { @@ -272,20 +272,16 @@ const _schema = { } } } - }, - enums: { + } as const; + enums = { Role: { values: { ADMIN: "ADMIN", USER: "USER" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/default-auth/schema.ts b/tests/e2e/orm/schemas/default-auth/schema.ts index c5610c0f8..986173e6a 100644 --- a/tests/e2e/orm/schemas/default-auth/schema.ts +++ b/tests/e2e/orm/schemas/default-auth/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -115,12 +115,8 @@ const _schema = { id: { type: "Int" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/delegate/schema.ts b/tests/e2e/orm/schemas/delegate/schema.ts index eaf1725ab..4ad907ed2 100644 --- a/tests/e2e/orm/schemas/delegate/schema.ts +++ b/tests/e2e/orm/schemas/delegate/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -458,12 +458,8 @@ const _schema = { id: { type: "Int" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/delegate/typecheck.ts b/tests/e2e/orm/schemas/delegate/typecheck.ts index c6ed453f2..9bf394766 100644 --- a/tests/e2e/orm/schemas/delegate/typecheck.ts +++ b/tests/e2e/orm/schemas/delegate/typecheck.ts @@ -16,15 +16,17 @@ async function find() { console.log(r.duration); // @ts-expect-error console.log(r.rating); - if (r.assetType === 'Video') { - // video - console.log(r.duration); - // only one choice `RatedVideo` - console.log(r.rating); - } else { - // image - console.log(r.format); - } + + // TODO: discriminated sub-model fields + // if (r.assetType === 'Video') { + // // video + // console.log(r.duration); + // // only one choice `RatedVideo` + // console.log(r.rating); + // } else { + // // image + // console.log(r.format); + // } // if fields are explicitly selected, then no sub-model fields are available const r1 = await client.asset.findFirstOrThrow({ @@ -49,15 +51,17 @@ async function find() { console.log(r2.assets[0]?.duration); // @ts-expect-error console.log(r2.assets[0]?.rating); - if (r2.assets[0]?.assetType === 'Video') { - // video - console.log(r2.assets[0]?.duration); - // only one choice `RatedVideo` - console.log(r2.assets[0]?.rating); - } else { - // image - console.log(r2.assets[0]?.format); - } + + // TODO: discriminated sub-model fields + // if (r2.assets[0]?.assetType === 'Video') { + // // video + // console.log(r2.assets[0]?.duration); + // // only one choice `RatedVideo` + // console.log(r2.assets[0]?.rating); + // } else { + // // image + // console.log(r2.assets[0]?.format); + // } // sub model behavior const r3 = await client.ratedVideo.findFirstOrThrow(); diff --git a/tests/e2e/orm/schemas/json/schema.ts b/tests/e2e/orm/schemas/json/schema.ts index 5c254e03f..b5537a9ff 100644 --- a/tests/e2e/orm/schemas/json/schema.ts +++ b/tests/e2e/orm/schemas/json/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { Foo: { name: "Foo", fields: { @@ -36,11 +36,7 @@ const _schema = { id: { type: "Int" } } } - }, - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/name-mapping/schema.ts b/tests/e2e/orm/schemas/name-mapping/schema.ts index 6b154ed74..8e610ffd0 100644 --- a/tests/e2e/orm/schemas/name-mapping/schema.ts +++ b/tests/e2e/orm/schemas/name-mapping/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -87,8 +87,8 @@ const _schema = { id: { type: "Int" } } } - }, - enums: { + } as const; + enums = { Role: { values: { USER: "USER", @@ -116,12 +116,8 @@ const _schema = { { name: "@@map", args: [{ name: "name", value: ExpressionUtils.literal("user_role") }] } ] } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/omit/schema.ts b/tests/e2e/orm/schemas/omit/schema.ts index 4543ccb71..60d51ad5c 100644 --- a/tests/e2e/orm/schemas/omit/schema.ts +++ b/tests/e2e/orm/schemas/omit/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -131,12 +131,8 @@ const _schema = { id: { type: "Int" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/petstore/schema.ts b/tests/e2e/orm/schemas/petstore/schema.ts index e3b9de6a8..795946d59 100644 --- a/tests/e2e/orm/schemas/petstore/schema.ts +++ b/tests/e2e/orm/schemas/petstore/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -150,12 +150,8 @@ const _schema = { id: { type: "String" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/typed-json/schema.ts b/tests/e2e/orm/schemas/typed-json/schema.ts index 5b4cdfedd..11be6f901 100644 --- a/tests/e2e/orm/schemas/typed-json/schema.ts +++ b/tests/e2e/orm/schemas/typed-json/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -32,8 +32,8 @@ const _schema = { id: { type: "Int" } } } - }, - typeDefs: { + } as const; + typeDefs = { Profile: { name: "Profile", fields: { @@ -91,20 +91,16 @@ const _schema = { } } } - }, - enums: { + } as const; + enums = { Gender: { values: { MALE: "MALE", FEMALE: "FEMALE" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/typing/schema.ts b/tests/e2e/orm/schemas/typing/schema.ts index 57da73ec5..a558c23c6 100644 --- a/tests/e2e/orm/schemas/typing/schema.ts +++ b/tests/e2e/orm/schemas/typing/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, type OperandExpression, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -299,8 +299,8 @@ const _schema = { postId: { type: "Int" } } } - }, - typeDefs: { + } as const; + typeDefs = { Identity: { name: "Identity", fields: { @@ -325,8 +325,8 @@ const _schema = { } } } - }, - enums: { + } as const; + enums = { Role: { values: { ADMIN: "ADMIN", @@ -340,12 +340,8 @@ const _schema = { BANNED: "BANNED" } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/runtimes/bun/schemas/schema.ts b/tests/runtimes/bun/schemas/schema.ts index b7d3bfcdb..199c89967 100644 --- a/tests/runtimes/bun/schemas/schema.ts +++ b/tests/runtimes/bun/schemas/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -93,12 +93,8 @@ const _schema = { id: { type: "String" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/runtimes/edge-runtime/schemas/schema.ts b/tests/runtimes/edge-runtime/schemas/schema.ts index 7f4436f87..13425189b 100644 --- a/tests/runtimes/edge-runtime/schemas/schema.ts +++ b/tests/runtimes/edge-runtime/schemas/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "postgresql" - }, - models: { + } as const; + models = { User: { name: "User", fields: { @@ -93,12 +93,8 @@ const _schema = { id: { type: "String" } } } - }, - authType: "User", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); From 3160a2d03c2c52f13d79e2de8e4bc6d613b09ae8 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:22:26 +0800 Subject: [PATCH 2/6] update --- tests/regression/test/issue-422/schema.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/regression/test/issue-422/schema.ts b/tests/regression/test/issue-422/schema.ts index 587f70aa2..212279b05 100644 --- a/tests/regression/test/issue-422/schema.ts +++ b/tests/regression/test/issue-422/schema.ts @@ -6,11 +6,11 @@ /* eslint-disable */ import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; -const _schema = { - provider: { +export class SchemaType implements SchemaDef { + provider = { type: "sqlite" - }, - models: { + } as const; + models = { Session: { name: "Session", fields: { @@ -111,12 +111,8 @@ const _schema = { userId: { type: "String" } } } - }, - authType: "Session", - plugins: {} -} as const satisfies SchemaDef; -type Schema = typeof _schema & { - __brand?: "schema"; -}; -export const schema: Schema = _schema; -export type SchemaType = Schema; + } as const; + authType = "Session" as const; + plugins = {}; +} +export const schema = new SchemaType(); From a13d8c6a4f134b9e0f001b53ca4a2fc392c0989f Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:24:12 +0800 Subject: [PATCH 3/6] update --- packages/orm/src/client/crud-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 796ea633f..baa12f53e 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -64,6 +64,7 @@ export type DefaultModelResult< ? never : Key]: MapModelFieldType; }, + // TODO: revisit how to efficiently implement discriminated sub model types // IsDelegateModel extends true // ? // delegate model's selection result is a union of all sub-models // DelegateUnionResult, Omit> From 9f1495019e7a032edf97b5a5ad32562f55e99b97 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:26:45 +0800 Subject: [PATCH 4/6] update --- packages/orm/src/client/contract.ts | 3 ++- packages/schema/src/schema.ts | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index fc552eb3c..12595439b 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -41,6 +41,7 @@ import type { } from './crud-types'; import type { ClientOptions } from './options'; import type { RuntimePlugin } from './plugin'; +import type { ZenStackPromise } from './promise'; import type { ToKysely } from './query-builder'; type TransactionUnsupportedMethods = (typeof TRANSACTION_UNSUPPORTED_METHODS)[number]; @@ -156,7 +157,7 @@ export type ClientContract[]>( + $transaction

[]>( arg: [...P], options?: { isolationLevel?: TransactionIsolationLevel }, ): Promise>; diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 93c0c9fc4..83640c357 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -293,11 +293,10 @@ export type FieldIsRelationArray< Field extends GetModelFields, > = FieldIsRelation extends true ? FieldIsArray : false; -export type IsDelegateModel> = string extends Model - ? false - : Schema['models'][Model]['isDelegate'] extends true - ? true - : false; +export type IsDelegateModel< + Schema extends SchemaDef, + Model extends GetModels, +> = Schema['models'][Model]['isDelegate'] extends true ? true : false; export type FieldIsDelegateRelation< Schema extends SchemaDef, From 0fa83336289b5367c454701c92e6e864c34e286d Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:03:46 +0800 Subject: [PATCH 5/6] revert to ZenStackPromise --- packages/orm/src/client/contract.ts | 46 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index 12595439b..c59f90b3e 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -78,7 +78,7 @@ export type ClientContract; + $executeRaw(query: TemplateStringsArray, ...values: any[]): ZenStackPromise; /** * Executes a raw query and returns the number of affected rows. @@ -88,7 +88,7 @@ export type ClientContract; + $executeRawUnsafe(query: string, ...values: any[]): ZenStackPromise; /** * Performs a prepared raw query and returns the `SELECT` data. @@ -97,7 +97,7 @@ export type ClientContract(query: TemplateStringsArray, ...values: any[]): Promise; + $queryRaw(query: TemplateStringsArray, ...values: any[]): ZenStackPromise; /** * Performs a raw query and returns the `SELECT` data. @@ -107,7 +107,7 @@ export type ClientContract(query: string, ...values: any[]): Promise; + $queryRawUnsafe(query: string, ...values: any[]): ZenStackPromise; /** * The current user identity. @@ -152,7 +152,7 @@ export type ClientContract( callback: (tx: Omit, TransactionUnsupportedMethods>) => Promise, options?: { isolationLevel?: TransactionIsolationLevel }, - ): Promise; + ): ZenStackPromise; /** * Starts a sequential transaction. @@ -160,7 +160,7 @@ export type ClientContract[]>( arg: [...P], options?: { isolationLevel?: TransactionIsolationLevel }, - ): Promise>; + ): ZenStackPromise>; /** * Returns a new client with the specified plugin installed. @@ -359,7 +359,7 @@ export type AllModelOperations< */ findMany>( args?: SelectSubset>, - ): Promise[]>; + ): ZenStackPromise[]>; /** * Returns a uniquely identified entity. @@ -369,7 +369,7 @@ export type AllModelOperations< */ findUnique>( args: SelectSubset>, - ): Promise | null>; + ): ZenStackPromise | null>; /** * Returns a uniquely identified entity or throws `NotFoundError` if not found. @@ -379,7 +379,7 @@ export type AllModelOperations< */ findUniqueOrThrow>( args: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Returns the first entity. @@ -389,7 +389,7 @@ export type AllModelOperations< */ findFirst>( args?: SelectSubset>, - ): Promise | null>; + ): ZenStackPromise | null>; /** * Returns the first entity or throws `NotFoundError` if not found. @@ -399,7 +399,7 @@ export type AllModelOperations< */ findFirstOrThrow>( args?: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Creates a new entity. @@ -455,7 +455,7 @@ export type AllModelOperations< */ create>( args: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Creates multiple entities. Only scalar fields are allowed. @@ -484,7 +484,7 @@ export type AllModelOperations< */ createMany>( args?: SelectSubset>, - ): Promise; + ): ZenStackPromise; /** * Creates multiple entities and returns them. @@ -506,7 +506,7 @@ export type AllModelOperations< */ createManyAndReturn>( args?: SelectSubset>, - ): Promise[]>; + ): ZenStackPromise[]>; /** * Updates a uniquely identified entity. @@ -627,7 +627,7 @@ export type AllModelOperations< */ update>( args: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Updates multiple entities. @@ -651,7 +651,7 @@ export type AllModelOperations< */ updateMany>( args: Subset>, - ): Promise; + ): ZenStackPromise; /** * Updates multiple entities and returns them. @@ -677,7 +677,7 @@ export type AllModelOperations< */ updateManyAndReturn>( args: Subset>, - ): Promise[]>; + ): ZenStackPromise[]>; /** * Creates or updates an entity. @@ -701,7 +701,7 @@ export type AllModelOperations< */ upsert>( args: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Deletes a uniquely identifiable entity. @@ -724,7 +724,7 @@ export type AllModelOperations< */ delete>( args: SelectSubset>, - ): Promise>; + ): ZenStackPromise>; /** * Deletes multiple entities. @@ -747,7 +747,7 @@ export type AllModelOperations< */ deleteMany>( args?: Subset>, - ): Promise; + ): ZenStackPromise; /** * Counts rows or field values. @@ -769,7 +769,7 @@ export type AllModelOperations< */ count>( args?: Subset>, - ): Promise>>; + ): ZenStackPromise>>; /** * Aggregates rows. @@ -790,7 +790,7 @@ export type AllModelOperations< */ aggregate>( args: Subset>, - ): Promise>>; + ): ZenStackPromise>>; /** * Groups rows by columns. @@ -827,7 +827,7 @@ export type AllModelOperations< */ groupBy>( args: Subset>, - ): Promise>>; + ): ZenStackPromise>>; }; export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert'; From f55267a3d187860223468f11bc62f5ce0d70a646 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:21:40 +0800 Subject: [PATCH 6/6] update promise --- packages/orm/src/client/contract.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index c59f90b3e..57cd9b05e 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -152,7 +152,7 @@ export type ClientContract( callback: (tx: Omit, TransactionUnsupportedMethods>) => Promise, options?: { isolationLevel?: TransactionIsolationLevel }, - ): ZenStackPromise; + ): Promise; /** * Starts a sequential transaction. @@ -160,7 +160,7 @@ export type ClientContract[]>( arg: [...P], options?: { isolationLevel?: TransactionIsolationLevel }, - ): ZenStackPromise>; + ): Promise>; /** * Returns a new client with the specified plugin installed.