diff --git a/packages/orm/src/client/crud/validator/cache-decorator.ts b/packages/orm/src/client/crud/validator/cache-decorator.ts new file mode 100644 index 000000000..bd8dd452f --- /dev/null +++ b/packages/orm/src/client/crud/validator/cache-decorator.ts @@ -0,0 +1,54 @@ +import stableStringify from 'json-stable-stringify'; + +/** + * Method decorator that caches the return value based on method name and arguments. + * + * Requirements: + * - Class must have a `getCache(key: string)` method + * - Class must have a `setCache(key: string, value: any)` method + */ +export function cache() { + return function (_target: any, propertyKey: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + + descriptor.value = function ( + this: { + getCache: (key: string) => unknown; + setCache: (key: string, value: unknown) => void; + } & Record, + ...args: any[] + ) { + // Build cache key object + const cacheKeyObj: Record = { + $call: propertyKey, + $args: args.map((arg) => { + if (Array.isArray(arg)) { + // sort array arguments for consistent cache keys + return [...arg].sort(); + } else { + return arg; + } + }), + }; + + // Generate stable string key + const cacheKey = stableStringify(cacheKeyObj)!; + + // Check cache + const cached = this.getCache(cacheKey); + if (cached !== undefined) { + return cached; + } + + // Execute original method + const result = originalMethod.apply(this, args); + + // Store in cache + this.setCache(cacheKey, result); + + return result; + }; + + return descriptor; + }; +} diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 76dd58529..8cad792e9 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -1,16 +1,13 @@ import { enumerate, invariant } from '@zenstackhq/common-helpers'; import Decimal from 'decimal.js'; -import stableStringify from 'json-stable-stringify'; import { match, P } from 'ts-pattern'; import { z, ZodObject, ZodType } from 'zod'; import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; import { type AttributeApplication, type BuiltinType, - type EnumDef, type FieldDef, type GetModels, - type ModelDef, type ProcedureDef, type SchemaDef, } from '../../../schema'; @@ -54,6 +51,7 @@ import { CoreUpdateOperations, type CoreCrudOperations, } from '../operations/base'; +import { cache } from './cache-decorator'; import { addBigIntValidation, addCustomValidation, @@ -82,119 +80,7 @@ export class InputValidator { return this.client.$options.validateInput !== false; } - validateProcedureInput(proc: string, input: unknown): unknown { - const procDef = (this.schema.procedures ?? {})[proc] as ProcedureDef | undefined; - invariant(procDef, `Procedure "${proc}" not found in schema`); - - const params = Object.values(procDef.params ?? {}); - - // For procedures where every parameter is optional, allow omitting the input entirely. - if (typeof input === 'undefined') { - if (params.length === 0) { - return undefined; - } - if (params.every((p) => p.optional)) { - return undefined; - } - throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); - } - - if (typeof input !== 'object') { - throw createInvalidInputError('Procedure input must be an object', `$procs.${proc}`); - } - - const envelope = input as Record; - const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; - - if (params.length === 0) { - if (typeof argsPayload === 'undefined') { - return input; - } - if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { - throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); - } - if (Object.keys(argsPayload as any).length === 0) { - return input; - } - throw createInvalidInputError('Procedure does not accept arguments', `$procs.${proc}`); - } - - if (typeof argsPayload === 'undefined') { - if (params.every((p) => p.optional)) { - return input; - } - throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); - } - - if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { - throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); - } - - const obj = argsPayload as Record; - - for (const param of params) { - const value = (obj as any)[param.name]; - - if (!Object.prototype.hasOwnProperty.call(obj, param.name)) { - if (param.optional) { - continue; - } - throw createInvalidInputError(`Missing procedure argument: ${param.name}`, `$procs.${proc}`); - } - - if (typeof value === 'undefined') { - if (param.optional) { - continue; - } - throw createInvalidInputError( - `Invalid procedure argument: ${param.name} is required`, - `$procs.${proc}`, - ); - } - - const schema = this.makeProcedureParamSchema(param); - const parsed = schema.safeParse(value); - if (!parsed.success) { - throw createInvalidInputError( - `Invalid procedure argument: ${param.name}: ${formatError(parsed.error)}`, - `$procs.${proc}`, - ); - } - } - - return input; - } - - private makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): z.ZodType { - let schema: z.ZodType; - - if (isTypeDef(this.schema, param.type)) { - schema = this.makeTypeDefSchema(param.type); - } else if (isEnum(this.schema, param.type)) { - schema = this.makeEnumSchema(param.type); - } else if (param.type in (this.schema.models ?? {})) { - // For model-typed values, accept any object (no deep shape validation). - schema = z.record(z.string(), z.unknown()); - } else { - // Builtin scalar types. - schema = this.makeScalarSchema(param.type as BuiltinType); - - // If a type isn't recognized by any of the above branches, `makeScalarSchema` returns `unknown`. - // Treat it as configuration/schema error. - if (schema instanceof z.ZodUnknown) { - throw createInternalError(`Unsupported procedure parameter type: ${param.type}`); - } - } - - if (param.array) { - schema = schema.array(); - } - if (param.optional) { - schema = schema.optional(); - } - - return schema; - } + // #region Entry points validateFindArgs( model: GetModels, @@ -335,27 +221,96 @@ export class InputValidator { ); } - private getSchemaCache(cacheKey: string) { - return this.schemaCache.get(cacheKey); - } + // TODO: turn it into a Zod schema and cache + validateProcedureInput(proc: string, input: unknown): unknown { + const procDef = (this.schema.procedures ?? {})[proc] as ProcedureDef | undefined; + invariant(procDef, `Procedure "${proc}" not found in schema`); - private setSchemaCache(cacheKey: string, schema: ZodType) { - return this.schemaCache.set(cacheKey, schema); - } + const params = Object.values(procDef.params ?? {}); - private validate(model: GetModels, operation: string, getSchema: GetSchemaFunc, args: unknown) { - const cacheKey = stableStringify({ - type: 'model', - model, - operation, - extraValidationsEnabled: this.extraValidationsEnabled, - }); - let schema = this.getSchemaCache(cacheKey!); - if (!schema) { - schema = getSchema(model); - this.setSchemaCache(cacheKey!, schema); + // For procedures where every parameter is optional, allow omitting the input entirely. + if (typeof input === 'undefined') { + if (params.length === 0) { + return undefined; + } + if (params.every((p) => p.optional)) { + return undefined; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw createInvalidInputError('Procedure input must be an object', `$procs.${proc}`); } + const envelope = input as Record; + const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; + + if (params.length === 0) { + if (typeof argsPayload === 'undefined') { + return input; + } + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + if (Object.keys(argsPayload as any).length === 0) { + return input; + } + throw createInvalidInputError('Procedure does not accept arguments', `$procs.${proc}`); + } + + if (typeof argsPayload === 'undefined') { + if (params.every((p) => p.optional)) { + return input; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + + const obj = argsPayload as Record; + + for (const param of params) { + const value = (obj as any)[param.name]; + + if (!Object.prototype.hasOwnProperty.call(obj, param.name)) { + if (param.optional) { + continue; + } + throw createInvalidInputError(`Missing procedure argument: ${param.name}`, `$procs.${proc}`); + } + + if (typeof value === 'undefined') { + if (param.optional) { + continue; + } + throw createInvalidInputError( + `Invalid procedure argument: ${param.name} is required`, + `$procs.${proc}`, + ); + } + + const schema = this.makeProcedureParamSchema(param); + const parsed = schema.safeParse(value); + if (!parsed.success) { + throw createInvalidInputError( + `Invalid procedure argument: ${param.name}: ${formatError(parsed.error)}`, + `$procs.${proc}`, + ); + } + } + + return input; + } + + // #endregion + + // #region Validation helpers + + private validate(model: GetModels, operation: string, getSchema: GetSchemaFunc, args: unknown) { + const schema = getSchema(model); const { error, data } = schema.safeParse(args); if (error) { throw createInvalidInputError( @@ -453,8 +408,11 @@ export class InputValidator { return result; } + // #endregion + // #region Find + @cache() private makeFindSchema(model: string, operation: CoreCrudOperations) { const fields: Record = {}; const unique = operation === 'findUnique'; @@ -493,6 +451,7 @@ export class InputValidator { return result; } + @cache() private makeExistsSchema(model: string) { const baseSchema = z.strictObject({ where: this.makeWhereSchema(model, false).optional(), @@ -539,35 +498,18 @@ export class InputValidator { } } + @cache() private makeEnumSchema(type: string) { - const key = stableStringify({ - type: 'enum', - name: type, - }); - let schema = this.getSchemaCache(key!); - if (schema) { - return schema; - } const enumDef = getEnum(this.schema, type); invariant(enumDef, `Enum "${type}" not found in schema`); - schema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); - this.setSchemaCache(key!, schema); - return schema; + return z.enum(Object.keys(enumDef.values) as [string, ...string[]]); } + @cache() private makeTypeDefSchema(type: string): z.ZodType { - const key = stableStringify({ - type: 'typedef', - name: type, - extraValidationsEnabled: this.extraValidationsEnabled, - }); - let schema = this.getSchemaCache(key!); - if (schema) { - return schema; - } const typeDef = getTypeDef(this.schema, type); invariant(typeDef, `Type definition "${type}" not found in schema`); - schema = z.looseObject( + const schema = z.looseObject( Object.fromEntries( Object.entries(typeDef.fields).map(([field, def]) => { let fieldSchema = this.makeScalarSchema(def.type); @@ -592,10 +534,10 @@ export class InputValidator { } }); - this.setSchemaCache(key!, finalSchema); return finalSchema; } + @cache() private makeWhereSchema( model: string, unique: boolean, @@ -644,7 +586,7 @@ export class InputValidator { // enum if (Object.keys(enumDef.values).length > 0) { fieldSchema = this.makeEnumFilterSchema( - enumDef, + fieldDef.type, !!fieldDef.optional, withAggregations, !!fieldDef.array, @@ -686,7 +628,7 @@ export class InputValidator { // enum if (Object.keys(enumDef.values).length > 0) { fieldSchema = this.makeEnumFilterSchema( - enumDef, + def.type, !!def.optional, false, false, @@ -754,6 +696,7 @@ export class InputValidator { return result; } + @cache() private makeTypedJsonFilterSchema(type: string, optional: boolean, array: boolean) { const typeDef = getTypeDef(this.schema, type); invariant(typeDef, `Type definition "${type}" not found in schema`); @@ -776,7 +719,7 @@ export class InputValidator { const enumDef = getEnum(this.schema, fieldDef.type); if (enumDef) { fieldSchemas[fieldName] = this.makeEnumFilterSchema( - enumDef, + fieldDef.type, !!fieldDef.optional, false, !!fieldDef.array, @@ -832,7 +775,10 @@ export class InputValidator { return this.schema.typeDefs && type in this.schema.typeDefs; } - private makeEnumFilterSchema(enumDef: EnumDef, optional: boolean, withAggregations: boolean, array: boolean) { + @cache() + private makeEnumFilterSchema(enumName: string, optional: boolean, withAggregations: boolean, array: boolean) { + const enumDef = getEnum(this.schema, enumName); + invariant(enumDef, `Enum "${enumName}" not found in schema`); const baseSchema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); if (array) { return this.internalMakeArrayFilterSchema(baseSchema); @@ -840,13 +786,14 @@ export class InputValidator { const components = this.makeCommonPrimitiveFilterComponents( baseSchema, optional, - () => z.lazy(() => this.makeEnumFilterSchema(enumDef, optional, withAggregations, array)), + () => z.lazy(() => this.makeEnumFilterSchema(enumName, optional, withAggregations, array)), ['equals', 'in', 'notIn', 'not'], withAggregations ? ['_count', '_min', '_max'] : undefined, ); return z.union([this.nullableIf(baseSchema, optional), z.strictObject(components)]); } + @cache() private makeArrayFilterSchema(type: BuiltinType) { return this.internalMakeArrayFilterSchema(this.makeScalarSchema(type)); } @@ -861,6 +808,7 @@ export class InputValidator { }); } + @cache() private makePrimitiveFilterSchema(type: BuiltinType, optional: boolean, withAggregations: boolean) { return match(type) .with('String', () => this.makeStringFilterSchema(optional, withAggregations)) @@ -902,6 +850,7 @@ export class InputValidator { return this.nullableIf(schema, nullable); } + @cache() private makeJsonFilterSchema(optional: boolean) { const valueSchema = this.makeJsonValueSchema(optional, true); return z.strictObject({ @@ -918,6 +867,7 @@ export class InputValidator { }); } + @cache() private makeDateTimeFilterSchema(optional: boolean, withAggregations: boolean): ZodType { return this.makeCommonPrimitiveFilterSchema( z.union([z.iso.datetime(), z.date()]), @@ -927,6 +877,7 @@ export class InputValidator { ); } + @cache() private makeBooleanFilterSchema(optional: boolean, withAggregations: boolean): ZodType { const components = this.makeCommonPrimitiveFilterComponents( z.boolean(), @@ -938,6 +889,7 @@ export class InputValidator { return z.union([this.nullableIf(z.boolean(), optional), z.strictObject(components)]); } + @cache() private makeBytesFilterSchema(optional: boolean, withAggregations: boolean): ZodType { const baseSchema = z.instanceof(Uint8Array); const components = this.makeCommonPrimitiveFilterComponents( @@ -1035,27 +987,30 @@ export class InputValidator { return z.union([z.literal('default'), z.literal('insensitive')]); } + @cache() private makeSelectSchema(model: string) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { - fields[field] = this.makeRelationSelectIncludeSchema(fieldDef).optional(); + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); } else { fields[field] = z.boolean().optional(); } } - const _countSchema = this.makeCountSelectionSchema(modelDef); - if (_countSchema) { + const _countSchema = this.makeCountSelectionSchema(model); + if (!(_countSchema instanceof z.ZodNever)) { fields['_count'] = _countSchema; } return z.strictObject(fields); } - private makeCountSelectionSchema(modelDef: ModelDef) { + @cache() + private makeCountSelectionSchema(model: string) { + const modelDef = requireModel(this.schema, model); const toManyRelations = Object.values(modelDef.fields).filter((def) => def.relation && def.array); if (toManyRelations.length > 0) { return z @@ -1082,11 +1037,13 @@ export class InputValidator { ]) .optional(); } else { - return undefined; + return z.never(); } } - private makeRelationSelectIncludeSchema(fieldDef: FieldDef) { + @cache() + private makeRelationSelectIncludeSchema(model: string, field: string) { + const fieldDef = requireField(this.schema, model, field); let objSchema: z.ZodType = z.strictObject({ ...(fieldDef.array || fieldDef.optional ? { @@ -1126,6 +1083,7 @@ export class InputValidator { return z.union([z.boolean(), objSchema]); } + @cache() private makeOmitSchema(model: string) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; @@ -1144,24 +1102,26 @@ export class InputValidator { return z.strictObject(fields); } + @cache() private makeIncludeSchema(model: string) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { - fields[field] = this.makeRelationSelectIncludeSchema(fieldDef).optional(); + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); } } - const _countSchema = this.makeCountSelectionSchema(modelDef); - if (_countSchema) { + const _countSchema = this.makeCountSelectionSchema(model); + if (!(_countSchema instanceof z.ZodNever)) { fields['_count'] = _countSchema; } return z.strictObject(fields); } + @cache() private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; @@ -1210,6 +1170,7 @@ export class InputValidator { return z.strictObject(fields); } + @cache() private makeDistinctSchema(model: string) { const modelDef = requireModel(this.schema, model); const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); @@ -1217,6 +1178,7 @@ export class InputValidator { } private makeCursorSchema(model: string) { + // `makeWhereSchema` is already cached return this.makeWhereSchema(model, true, true).optional(); } @@ -1224,6 +1186,7 @@ export class InputValidator { // #region Create + @cache() private makeCreateSchema(model: string) { const dataSchema = this.makeCreateDataSchema(model, false); const baseSchema = z.strictObject({ @@ -1238,10 +1201,12 @@ export class InputValidator { return schema; } + @cache() private makeCreateManySchema(model: string) { return this.mergePluginArgsSchema(this.makeCreateManyDataSchema(model, []), 'createMany').optional(); } + @cache() private makeCreateManyAndReturnSchema(model: string) { const base = this.makeCreateManyDataSchema(model, []); let result: ZodObject = base.extend({ @@ -1252,6 +1217,7 @@ export class InputValidator { return this.refineForSelectOmitMutuallyExclusive(result).optional(); } + @cache() private makeCreateDataSchema( model: string, canBeArray: boolean, @@ -1294,7 +1260,7 @@ export class InputValidator { } let fieldSchema: ZodType = z.lazy(() => - this.makeRelationManipulationSchema(fieldDef, excludeFields, 'create'), + this.makeRelationManipulationSchema(model, field, excludeFields, 'create'), ); if (fieldDef.optional || fieldDef.array) { @@ -1387,7 +1353,14 @@ export class InputValidator { return discriminatorField === fieldDef.name; } - private makeRelationManipulationSchema(fieldDef: FieldDef, withoutFields: string[], mode: 'create' | 'update') { + @cache() + private makeRelationManipulationSchema( + model: string, + field: string, + withoutFields: string[], + mode: 'create' | 'update', + ) { + const fieldDef = requireField(this.schema, model, field); const fieldType = fieldDef.type; const array = !!fieldDef.array; const fields: Record = { @@ -1461,14 +1434,17 @@ export class InputValidator { return z.strictObject(fields); } + @cache() private makeSetDataSchema(model: string, canBeArray: boolean) { return this.orArray(this.makeWhereSchema(model, true), canBeArray); } + @cache() private makeConnectDataSchema(model: string, canBeArray: boolean) { return this.orArray(this.makeWhereSchema(model, true), canBeArray); } + @cache() private makeDisconnectDataSchema(model: string, canBeArray: boolean) { if (canBeArray) { // to-many relation, must be unique filters @@ -1480,12 +1456,14 @@ export class InputValidator { } } + @cache() private makeDeleteRelationDataSchema(model: string, toManyRelation: boolean, uniqueFilter: boolean) { return toManyRelation ? this.orArray(this.makeWhereSchema(model, uniqueFilter), true) : z.union([z.boolean(), this.makeWhereSchema(model, uniqueFilter)]); } + @cache() private makeConnectOrCreateDataSchema(model: string, canBeArray: boolean, withoutFields: string[]) { const whereSchema = this.makeWhereSchema(model, true); const createSchema = this.makeCreateDataSchema(model, false, withoutFields); @@ -1498,6 +1476,7 @@ export class InputValidator { ); } + @cache() private makeCreateManyDataSchema(model: string, withoutFields: string[]) { return z.strictObject({ data: this.makeCreateDataSchema(model, true, withoutFields, true), @@ -1509,6 +1488,7 @@ export class InputValidator { // #region Update + @cache() private makeUpdateSchema(model: string) { const baseSchema = z.strictObject({ where: this.makeWhereSchema(model, true), @@ -1523,6 +1503,7 @@ export class InputValidator { return schema; } + @cache() private makeUpdateManySchema(model: string) { return this.mergePluginArgsSchema( z.strictObject({ @@ -1534,6 +1515,7 @@ export class InputValidator { ); } + @cache() private makeUpdateManyAndReturnSchema(model: string) { // plugin extended args schema is merged in `makeUpdateManySchema` const baseSchema: ZodObject = this.makeUpdateManySchema(model); @@ -1545,6 +1527,7 @@ export class InputValidator { return schema; } + @cache() private makeUpsertSchema(model: string) { const baseSchema = z.strictObject({ where: this.makeWhereSchema(model, true), @@ -1560,6 +1543,7 @@ export class InputValidator { return schema; } + @cache() private makeUpdateDataSchema(model: string, withoutFields: string[] = [], withoutRelationFields = false) { const uncheckedVariantFields: Record = {}; const checkedVariantFields: Record = {}; @@ -1588,7 +1572,7 @@ export class InputValidator { } } let fieldSchema: ZodType = z - .lazy(() => this.makeRelationManipulationSchema(fieldDef, excludeFields, 'update')) + .lazy(() => this.makeRelationManipulationSchema(model, field, excludeFields, 'update')) .optional(); // optional to-one relation can be null if (fieldDef.optional && !fieldDef.array) { @@ -1670,7 +1654,8 @@ export class InputValidator { // #region Delete - private makeDeleteSchema(model: GetModels) { + @cache() + private makeDeleteSchema(model: string) { const baseSchema = z.strictObject({ where: this.makeWhereSchema(model, true), select: this.makeSelectSchema(model).optional().nullable(), @@ -1683,7 +1668,8 @@ export class InputValidator { return schema; } - private makeDeleteManySchema(model: GetModels) { + @cache() + private makeDeleteManySchema(model: string) { return this.mergePluginArgsSchema( z.strictObject({ where: this.makeWhereSchema(model, false).optional(), @@ -1697,7 +1683,8 @@ export class InputValidator { // #region Count - makeCountSchema(model: GetModels) { + @cache() + makeCountSchema(model: string) { return this.mergePluginArgsSchema( z.strictObject({ where: this.makeWhereSchema(model, false).optional(), @@ -1710,7 +1697,8 @@ export class InputValidator { ).optional(); } - private makeCountAggregateInputSchema(model: GetModels) { + @cache() + private makeCountAggregateInputSchema(model: string) { const modelDef = requireModel(this.schema, model); return z.union([ z.literal(true), @@ -1731,7 +1719,8 @@ export class InputValidator { // #region Aggregate - makeAggregateSchema(model: GetModels) { + @cache() + makeAggregateSchema(model: string) { return this.mergePluginArgsSchema( z.strictObject({ where: this.makeWhereSchema(model, false).optional(), @@ -1748,7 +1737,8 @@ export class InputValidator { ).optional(); } - makeSumAvgInputSchema(model: GetModels) { + @cache() + makeSumAvgInputSchema(model: string) { const modelDef = requireModel(this.schema, model); return z.strictObject( Object.keys(modelDef.fields).reduce( @@ -1764,7 +1754,8 @@ export class InputValidator { ); } - makeMinMaxInputSchema(model: GetModels) { + @cache() + makeMinMaxInputSchema(model: string) { const modelDef = requireModel(this.schema, model); return z.strictObject( Object.keys(modelDef.fields).reduce( @@ -1780,7 +1771,8 @@ export class InputValidator { ); } - private makeGroupBySchema(model: GetModels) { + @cache() + private makeGroupBySchema(model: string) { const modelDef = requireModel(this.schema, model); const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); const bySchema = @@ -1866,18 +1858,79 @@ export class InputValidator { return true; } - private makeHavingSchema(model: GetModels) { + private makeHavingSchema(model: string) { + // `makeWhereSchema` is cached return this.makeWhereSchema(model, false, true, true); } // #endregion + // #region Procedures + + @cache() + private makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): z.ZodType { + let schema: z.ZodType; + + if (isTypeDef(this.schema, param.type)) { + schema = this.makeTypeDefSchema(param.type); + } else if (isEnum(this.schema, param.type)) { + schema = this.makeEnumSchema(param.type); + } else if (param.type in (this.schema.models ?? {})) { + // For model-typed values, accept any object (no deep shape validation). + schema = z.record(z.string(), z.unknown()); + } else { + // Builtin scalar types. + schema = this.makeScalarSchema(param.type as BuiltinType); + + // If a type isn't recognized by any of the above branches, `makeScalarSchema` returns `unknown`. + // Treat it as configuration/schema error. + if (schema instanceof z.ZodUnknown) { + throw createInternalError(`Unsupported procedure parameter type: ${param.type}`); + } + } + + if (param.array) { + schema = schema.array(); + } + if (param.optional) { + schema = schema.optional(); + } + + return schema; + } + + // #endregion + + // #region Cache Management + + getCache(cacheKey: string) { + return this.schemaCache.get(cacheKey); + } + + setCache(cacheKey: string, schema: ZodType) { + return this.schemaCache.set(cacheKey, schema); + } + + // @ts-ignore + private printCacheStats(detailed = false) { + console.log('Schema cache size:', this.schemaCache.size); + if (detailed) { + for (const key of this.schemaCache.keys()) { + console.log(`\t${key}`); + } + } + } + + // #endregion + // #region Helpers + @cache() private makeSkipSchema() { return z.number().int().nonnegative(); } + @cache() private makeTakeSchema() { return z.number().int(); } @@ -1911,5 +1964,6 @@ export class InputValidator { private get providerSupportsCaseSensitivity() { return this.schema.provider.type === 'postgresql'; } + // #endregion } diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 58fc1bc5b..08b0726f6 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -106,6 +106,7 @@ export type EnumField = { }; export type EnumDef = { + name: string; fields?: Record; values: Record; attributes?: readonly AttributeApplication[]; diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index 4a5e0a548..16f81bae2 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -1030,6 +1030,8 @@ export class TsSchemaGenerator { private createEnumObject(e: Enum) { return ts.factory.createObjectLiteralExpression( [ + ts.factory.createPropertyAssignment('name', ts.factory.createStringLiteral(e.name)), + ts.factory.createPropertyAssignment( 'values', ts.factory.createObjectLiteralExpression( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c202a4423..02aef9843 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1120,6 +1120,9 @@ importers: '@zenstackhq/orm': specifier: workspace:* version: link:../../packages/orm + '@zenstackhq/plugin-policy': + specifier: workspace:* + version: link:../../packages/plugins/policy '@zenstackhq/sdk': specifier: workspace:* version: link:../../packages/sdk @@ -13073,8 +13076,8 @@ snapshots: '@babel/parser': 7.28.5 eslint: 9.29.0(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.1.12 - zod-validation-error: 4.0.1(zod@4.1.12) + zod: 4.3.6 + zod-validation-error: 4.0.1(zod@4.3.6) transitivePeerDependencies: - supports-color @@ -17131,6 +17134,10 @@ snapshots: dependencies: zod: 4.1.12 + zod-validation-error@4.0.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod@4.1.12: {} zod@4.3.6: {} diff --git a/samples/orm/zenstack/schema.ts b/samples/orm/zenstack/schema.ts index e3c02e5c6..908df1e71 100644 --- a/samples/orm/zenstack/schema.ts +++ b/samples/orm/zenstack/schema.ts @@ -232,6 +232,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { Role: { + name: "Role", values: { ADMIN: "ADMIN", USER: "USER" diff --git a/tests/e2e/apps/rally/zenstack/schema.ts b/tests/e2e/apps/rally/zenstack/schema.ts index 7d20facea..d9ddfdb78 100644 --- a/tests/e2e/apps/rally/zenstack/schema.ts +++ b/tests/e2e/apps/rally/zenstack/schema.ts @@ -2391,6 +2391,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { TimeFormat: { + name: "TimeFormat", values: { hours12: "hours12", hours24: "hours24" @@ -2400,6 +2401,7 @@ export class SchemaType implements SchemaDef { ] }, UserRole: { + name: "UserRole", values: { admin: "admin", user: "user" @@ -2409,6 +2411,7 @@ export class SchemaType implements SchemaDef { ] }, ParticipantVisibility: { + name: "ParticipantVisibility", values: { full: "full", scoresOnly: "scoresOnly", @@ -2419,6 +2422,7 @@ export class SchemaType implements SchemaDef { ] }, PollStatus: { + name: "PollStatus", values: { live: "live", paused: "paused", @@ -2429,6 +2433,7 @@ export class SchemaType implements SchemaDef { ] }, VoteType: { + name: "VoteType", values: { yes: "yes", no: "no", @@ -2439,12 +2444,14 @@ export class SchemaType implements SchemaDef { ] }, SpaceMemberRole: { + name: "SpaceMemberRole", values: { ADMIN: "ADMIN", MEMBER: "MEMBER" } }, SpaceTier: { + name: "SpaceTier", values: { hobby: "hobby", pro: "pro" @@ -2454,6 +2461,7 @@ export class SchemaType implements SchemaDef { ] }, SubscriptionStatus: { + name: "SubscriptionStatus", values: { incomplete: "incomplete", incomplete_expired: "incomplete_expired", @@ -2469,6 +2477,7 @@ export class SchemaType implements SchemaDef { ] }, SubscriptionInterval: { + name: "SubscriptionInterval", values: { month: "month", year: "year" @@ -2478,6 +2487,7 @@ export class SchemaType implements SchemaDef { ] }, ScheduledEventStatus: { + name: "ScheduledEventStatus", values: { confirmed: "confirmed", canceled: "canceled", @@ -2488,6 +2498,7 @@ export class SchemaType implements SchemaDef { ] }, ScheduledEventInviteStatus: { + name: "ScheduledEventInviteStatus", values: { pending: "pending", accepted: "accepted", @@ -2499,11 +2510,13 @@ export class SchemaType implements SchemaDef { ] }, CredentialType: { + name: "CredentialType", values: { OAUTH: "OAUTH" } }, LicenseType: { + name: "LicenseType", values: { PLUS: "PLUS", ORGANIZATION: "ORGANIZATION", @@ -2511,6 +2524,7 @@ export class SchemaType implements SchemaDef { } }, LicenseStatus: { + name: "LicenseStatus", values: { ACTIVE: "ACTIVE", REVOKED: "REVOKED" diff --git a/tests/e2e/github-repos/cal.com/schema.ts b/tests/e2e/github-repos/cal.com/schema.ts index 7715a296c..47bb801a4 100644 --- a/tests/e2e/github-repos/cal.com/schema.ts +++ b/tests/e2e/github-repos/cal.com/schema.ts @@ -9185,6 +9185,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { SchedulingType: { + name: "SchedulingType", values: { ROUND_ROBIN: "ROUND_ROBIN", COLLECTIVE: "COLLECTIVE", @@ -9212,6 +9213,7 @@ export class SchemaType implements SchemaDef { } }, PeriodType: { + name: "PeriodType", values: { UNLIMITED: "UNLIMITED", ROLLING: "ROLLING", @@ -9246,6 +9248,7 @@ export class SchemaType implements SchemaDef { } }, CreationSource: { + name: "CreationSource", values: { API_V1: "API_V1", API_V2: "API_V2", @@ -9273,6 +9276,7 @@ export class SchemaType implements SchemaDef { } }, IdentityProvider: { + name: "IdentityProvider", values: { CAL: "CAL", GOOGLE: "GOOGLE", @@ -9280,18 +9284,21 @@ export class SchemaType implements SchemaDef { } }, UserPermissionRole: { + name: "UserPermissionRole", values: { USER: "USER", ADMIN: "ADMIN" } }, CreditType: { + name: "CreditType", values: { MONTHLY: "MONTHLY", ADDITIONAL: "ADDITIONAL" } }, MembershipRole: { + name: "MembershipRole", values: { MEMBER: "MEMBER", ADMIN: "ADMIN", @@ -9299,6 +9306,7 @@ export class SchemaType implements SchemaDef { } }, BookingStatus: { + name: "BookingStatus", values: { CANCELLED: "CANCELLED", ACCEPTED: "ACCEPTED", @@ -9340,6 +9348,7 @@ export class SchemaType implements SchemaDef { } }, EventTypeCustomInputType: { + name: "EventTypeCustomInputType", values: { TEXT: "TEXT", TEXTLONG: "TEXTLONG", @@ -9388,17 +9397,20 @@ export class SchemaType implements SchemaDef { } }, ReminderType: { + name: "ReminderType", values: { PENDING_BOOKING_CONFIRMATION: "PENDING_BOOKING_CONFIRMATION" } }, PaymentOption: { + name: "PaymentOption", values: { ON_BOOKING: "ON_BOOKING", HOLD: "HOLD" } }, WebhookTriggerEvents: { + name: "WebhookTriggerEvents", values: { BOOKING_CREATED: "BOOKING_CREATED", BOOKING_PAYMENT_INITIATED: "BOOKING_PAYMENT_INITIATED", @@ -9421,6 +9433,7 @@ export class SchemaType implements SchemaDef { } }, AppCategories: { + name: "AppCategories", values: { calendar: "calendar", messaging: "messaging", @@ -9435,6 +9448,7 @@ export class SchemaType implements SchemaDef { } }, WorkflowTriggerEvents: { + name: "WorkflowTriggerEvents", values: { BEFORE_EVENT: "BEFORE_EVENT", EVENT_CANCELLED: "EVENT_CANCELLED", @@ -9446,6 +9460,7 @@ export class SchemaType implements SchemaDef { } }, WorkflowActions: { + name: "WorkflowActions", values: { EMAIL_HOST: "EMAIL_HOST", EMAIL_ATTENDEE: "EMAIL_ATTENDEE", @@ -9457,6 +9472,7 @@ export class SchemaType implements SchemaDef { } }, TimeUnit: { + name: "TimeUnit", values: { DAY: "DAY", HOUR: "HOUR", @@ -9484,6 +9500,7 @@ export class SchemaType implements SchemaDef { } }, WorkflowTemplates: { + name: "WorkflowTemplates", values: { REMINDER: "REMINDER", CUSTOM: "CUSTOM", @@ -9494,6 +9511,7 @@ export class SchemaType implements SchemaDef { } }, WorkflowMethods: { + name: "WorkflowMethods", values: { EMAIL: "EMAIL", SMS: "SMS", @@ -9501,6 +9519,7 @@ export class SchemaType implements SchemaDef { } }, FeatureType: { + name: "FeatureType", values: { RELEASE: "RELEASE", EXPERIMENT: "EXPERIMENT", @@ -9510,24 +9529,28 @@ export class SchemaType implements SchemaDef { } }, RRResetInterval: { + name: "RRResetInterval", values: { MONTH: "MONTH", DAY: "DAY" } }, RRTimestampBasis: { + name: "RRTimestampBasis", values: { CREATED_AT: "CREATED_AT", START_TIME: "START_TIME" } }, AccessScope: { + name: "AccessScope", values: { READ_BOOKING: "READ_BOOKING", READ_PROFILE: "READ_PROFILE" } }, RedirectType: { + name: "RedirectType", values: { UserEventType: "UserEventType", TeamEventType: "TeamEventType", @@ -9562,6 +9585,7 @@ export class SchemaType implements SchemaDef { } }, SMSLockState: { + name: "SMSLockState", values: { LOCKED: "LOCKED", UNLOCKED: "UNLOCKED", @@ -9569,6 +9593,7 @@ export class SchemaType implements SchemaDef { } }, AttributeType: { + name: "AttributeType", values: { TEXT: "TEXT", NUMBER: "NUMBER", @@ -9577,6 +9602,7 @@ export class SchemaType implements SchemaDef { } }, AssignmentReasonEnum: { + name: "AssignmentReasonEnum", values: { ROUTING_FORM_ROUTING: "ROUTING_FORM_ROUTING", ROUTING_FORM_ROUTING_FALLBACK: "ROUTING_FORM_ROUTING_FALLBACK", @@ -9587,12 +9613,14 @@ export class SchemaType implements SchemaDef { } }, EventTypeAutoTranslatedField: { + name: "EventTypeAutoTranslatedField", values: { DESCRIPTION: "DESCRIPTION", TITLE: "TITLE" } }, WatchlistType: { + name: "WatchlistType", values: { EMAIL: "EMAIL", DOMAIN: "DOMAIN", @@ -9600,6 +9628,7 @@ export class SchemaType implements SchemaDef { } }, WatchlistSeverity: { + name: "WatchlistSeverity", values: { LOW: "LOW", MEDIUM: "MEDIUM", @@ -9608,29 +9637,34 @@ export class SchemaType implements SchemaDef { } }, BillingPeriod: { + name: "BillingPeriod", values: { MONTHLY: "MONTHLY", ANNUALLY: "ANNUALLY" } }, IncompleteBookingActionType: { + name: "IncompleteBookingActionType", values: { SALESFORCE: "SALESFORCE" } }, FilterSegmentScope: { + name: "FilterSegmentScope", values: { USER: "USER", TEAM: "TEAM" } }, WorkflowContactType: { + name: "WorkflowContactType", values: { PHONE: "PHONE", EMAIL: "EMAIL" } }, RoleType: { + name: "RoleType", values: { SYSTEM: "SYSTEM", CUSTOM: "CUSTOM" diff --git a/tests/e2e/github-repos/formbricks/schema.ts b/tests/e2e/github-repos/formbricks/schema.ts index 935b8320b..f3e478760 100644 --- a/tests/e2e/github-repos/formbricks/schema.ts +++ b/tests/e2e/github-repos/formbricks/schema.ts @@ -2834,6 +2834,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { PipelineTriggers: { + name: "PipelineTriggers", values: { responseCreated: "responseCreated", responseUpdated: "responseUpdated", @@ -2841,6 +2842,7 @@ export class SchemaType implements SchemaDef { } }, WebhookSource: { + name: "WebhookSource", values: { user: "user", zapier: "zapier", @@ -2850,12 +2852,14 @@ export class SchemaType implements SchemaDef { } }, ContactAttributeType: { + name: "ContactAttributeType", values: { default: "default", custom: "custom" } }, SurveyStatus: { + name: "SurveyStatus", values: { draft: "draft", scheduled: "scheduled", @@ -2865,18 +2869,21 @@ export class SchemaType implements SchemaDef { } }, DisplayStatus: { + name: "DisplayStatus", values: { seen: "seen", responded: "responded" } }, SurveyAttributeFilterCondition: { + name: "SurveyAttributeFilterCondition", values: { equals: "equals", notEquals: "notEquals" } }, SurveyType: { + name: "SurveyType", values: { link: "link", web: "web", @@ -2885,6 +2892,7 @@ export class SchemaType implements SchemaDef { } }, displayOptions: { + name: "displayOptions", values: { displayOnce: "displayOnce", displayMultiple: "displayMultiple", @@ -2893,18 +2901,21 @@ export class SchemaType implements SchemaDef { } }, ActionType: { + name: "ActionType", values: { code: "code", noCode: "noCode" } }, EnvironmentType: { + name: "EnvironmentType", values: { production: "production", development: "development" } }, IntegrationType: { + name: "IntegrationType", values: { googleSheets: "googleSheets", notion: "notion", @@ -2913,6 +2924,7 @@ export class SchemaType implements SchemaDef { } }, DataMigrationStatus: { + name: "DataMigrationStatus", values: { pending: "pending", applied: "applied", @@ -2920,6 +2932,7 @@ export class SchemaType implements SchemaDef { } }, WidgetPlacement: { + name: "WidgetPlacement", values: { bottomLeft: "bottomLeft", bottomRight: "bottomRight", @@ -2929,6 +2942,7 @@ export class SchemaType implements SchemaDef { } }, OrganizationRole: { + name: "OrganizationRole", values: { owner: "owner", manager: "manager", @@ -2937,6 +2951,7 @@ export class SchemaType implements SchemaDef { } }, MembershipRole: { + name: "MembershipRole", values: { owner: "owner", admin: "admin", @@ -2946,6 +2961,7 @@ export class SchemaType implements SchemaDef { } }, ApiKeyPermission: { + name: "ApiKeyPermission", values: { read: "read", write: "write", @@ -2953,6 +2969,7 @@ export class SchemaType implements SchemaDef { } }, IdentityProvider: { + name: "IdentityProvider", values: { email: "email", github: "github", @@ -2963,6 +2980,7 @@ export class SchemaType implements SchemaDef { } }, Role: { + name: "Role", values: { project_manager: "project_manager", engineer: "engineer", @@ -2972,6 +2990,7 @@ export class SchemaType implements SchemaDef { } }, Objective: { + name: "Objective", values: { increase_conversion: "increase_conversion", improve_user_retention: "improve_user_retention", @@ -2982,6 +3001,7 @@ export class SchemaType implements SchemaDef { } }, Intention: { + name: "Intention", values: { survey_user_segments: "survey_user_segments", survey_at_specific_point_in_user_journey: "survey_at_specific_point_in_user_journey", @@ -2991,6 +3011,7 @@ export class SchemaType implements SchemaDef { } }, InsightCategory: { + name: "InsightCategory", values: { featureRequest: "featureRequest", complaint: "complaint", @@ -2999,6 +3020,7 @@ export class SchemaType implements SchemaDef { } }, Sentiment: { + name: "Sentiment", values: { positive: "positive", negative: "negative", @@ -3006,12 +3028,14 @@ export class SchemaType implements SchemaDef { } }, TeamUserRole: { + name: "TeamUserRole", values: { admin: "admin", contributor: "contributor" } }, ProjectTeamPermission: { + name: "ProjectTeamPermission", values: { read: "read", readWrite: "readWrite", diff --git a/tests/e2e/github-repos/trigger.dev/schema.ts b/tests/e2e/github-repos/trigger.dev/schema.ts index 9b884d078..12dab9e1b 100644 --- a/tests/e2e/github-repos/trigger.dev/schema.ts +++ b/tests/e2e/github-repos/trigger.dev/schema.ts @@ -6081,18 +6081,21 @@ export class SchemaType implements SchemaDef { } as const; enums = { AuthenticationMethod: { + name: "AuthenticationMethod", values: { GITHUB: "GITHUB", MAGIC_LINK: "MAGIC_LINK" } }, OrgMemberRole: { + name: "OrgMemberRole", values: { ADMIN: "ADMIN", MEMBER: "MEMBER" } }, RuntimeEnvironmentType: { + name: "RuntimeEnvironmentType", values: { PRODUCTION: "PRODUCTION", STAGING: "STAGING", @@ -6101,24 +6104,28 @@ export class SchemaType implements SchemaDef { } }, ProjectVersion: { + name: "ProjectVersion", values: { V2: "V2", V3: "V3" } }, SecretStoreProvider: { + name: "SecretStoreProvider", values: { DATABASE: "DATABASE", AWS_PARAM_STORE: "AWS_PARAM_STORE" } }, TaskTriggerSource: { + name: "TaskTriggerSource", values: { STANDARD: "STANDARD", SCHEDULED: "SCHEDULED" } }, TaskRunStatus: { + name: "TaskRunStatus", values: { DELAYED: "DELAYED", PENDING: "PENDING", @@ -6139,12 +6146,14 @@ export class SchemaType implements SchemaDef { } }, RunEngineVersion: { + name: "RunEngineVersion", values: { V1: "V1", V2: "V2" } }, TaskRunExecutionStatus: { + name: "TaskRunExecutionStatus", values: { RUN_CREATED: "RUN_CREATED", QUEUED: "QUEUED", @@ -6158,12 +6167,14 @@ export class SchemaType implements SchemaDef { } }, TaskRunCheckpointType: { + name: "TaskRunCheckpointType", values: { DOCKER: "DOCKER", KUBERNETES: "KUBERNETES" } }, WaitpointType: { + name: "WaitpointType", values: { RUN: "RUN", DATETIME: "DATETIME", @@ -6172,18 +6183,21 @@ export class SchemaType implements SchemaDef { } }, WaitpointStatus: { + name: "WaitpointStatus", values: { PENDING: "PENDING", COMPLETED: "COMPLETED" } }, WorkerInstanceGroupType: { + name: "WorkerInstanceGroupType", values: { MANAGED: "MANAGED", UNMANAGED: "UNMANAGED" } }, TaskRunAttemptStatus: { + name: "TaskRunAttemptStatus", values: { PENDING: "PENDING", EXECUTING: "EXECUTING", @@ -6194,6 +6208,7 @@ export class SchemaType implements SchemaDef { } }, TaskEventLevel: { + name: "TaskEventLevel", values: { TRACE: "TRACE", DEBUG: "DEBUG", @@ -6204,6 +6219,7 @@ export class SchemaType implements SchemaDef { } }, TaskEventKind: { + name: "TaskEventKind", values: { UNSPECIFIED: "UNSPECIFIED", INTERNAL: "INTERNAL", @@ -6216,6 +6232,7 @@ export class SchemaType implements SchemaDef { } }, TaskEventStatus: { + name: "TaskEventStatus", values: { UNSET: "UNSET", OK: "OK", @@ -6224,18 +6241,21 @@ export class SchemaType implements SchemaDef { } }, TaskQueueType: { + name: "TaskQueueType", values: { VIRTUAL: "VIRTUAL", NAMED: "NAMED" } }, TaskQueueVersion: { + name: "TaskQueueVersion", values: { V1: "V1", V2: "V2" } }, BatchTaskRunStatus: { + name: "BatchTaskRunStatus", values: { PENDING: "PENDING", COMPLETED: "COMPLETED", @@ -6243,6 +6263,7 @@ export class SchemaType implements SchemaDef { } }, BatchTaskRunItemStatus: { + name: "BatchTaskRunItemStatus", values: { PENDING: "PENDING", FAILED: "FAILED", @@ -6251,18 +6272,21 @@ export class SchemaType implements SchemaDef { } }, CheckpointType: { + name: "CheckpointType", values: { DOCKER: "DOCKER", KUBERNETES: "KUBERNETES" } }, CheckpointRestoreEventType: { + name: "CheckpointRestoreEventType", values: { CHECKPOINT: "CHECKPOINT", RESTORE: "RESTORE" } }, WorkerDeploymentType: { + name: "WorkerDeploymentType", values: { MANAGED: "MANAGED", UNMANAGED: "UNMANAGED", @@ -6270,6 +6294,7 @@ export class SchemaType implements SchemaDef { } }, WorkerDeploymentStatus: { + name: "WorkerDeploymentStatus", values: { PENDING: "PENDING", BUILDING: "BUILDING", @@ -6281,17 +6306,20 @@ export class SchemaType implements SchemaDef { } }, ScheduleType: { + name: "ScheduleType", values: { DECLARATIVE: "DECLARATIVE", IMPERATIVE: "IMPERATIVE" } }, ScheduleGeneratorType: { + name: "ScheduleGeneratorType", values: { CRON: "CRON" } }, ProjectAlertChannelType: { + name: "ProjectAlertChannelType", values: { EMAIL: "EMAIL", SLACK: "SLACK", @@ -6299,6 +6327,7 @@ export class SchemaType implements SchemaDef { } }, ProjectAlertType: { + name: "ProjectAlertType", values: { TASK_RUN: "TASK_RUN", TASK_RUN_ATTEMPT: "TASK_RUN_ATTEMPT", @@ -6307,6 +6336,7 @@ export class SchemaType implements SchemaDef { } }, ProjectAlertStatus: { + name: "ProjectAlertStatus", values: { PENDING: "PENDING", SENT: "SENT", @@ -6314,23 +6344,27 @@ export class SchemaType implements SchemaDef { } }, IntegrationService: { + name: "IntegrationService", values: { SLACK: "SLACK" } }, BulkActionType: { + name: "BulkActionType", values: { CANCEL: "CANCEL", REPLAY: "REPLAY" } }, BulkActionStatus: { + name: "BulkActionStatus", values: { PENDING: "PENDING", COMPLETED: "COMPLETED" } }, BulkActionItemStatus: { + name: "BulkActionItemStatus", values: { PENDING: "PENDING", COMPLETED: "COMPLETED", diff --git a/tests/e2e/orm/schemas/basic/schema.ts b/tests/e2e/orm/schemas/basic/schema.ts index 1e559cc79..9d6cb1982 100644 --- a/tests/e2e/orm/schemas/basic/schema.ts +++ b/tests/e2e/orm/schemas/basic/schema.ts @@ -295,6 +295,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { Role: { + name: "Role", values: { ADMIN: "ADMIN", USER: "USER" diff --git a/tests/e2e/orm/schemas/name-mapping/schema.ts b/tests/e2e/orm/schemas/name-mapping/schema.ts index 8e610ffd0..b92cd14b4 100644 --- a/tests/e2e/orm/schemas/name-mapping/schema.ts +++ b/tests/e2e/orm/schemas/name-mapping/schema.ts @@ -90,6 +90,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { Role: { + name: "Role", values: { USER: "USER", ADMIN: "ADMIN", diff --git a/tests/e2e/orm/schemas/procedures/schema.ts b/tests/e2e/orm/schemas/procedures/schema.ts index f5a9044e5..bd2b10018 100644 --- a/tests/e2e/orm/schemas/procedures/schema.ts +++ b/tests/e2e/orm/schemas/procedures/schema.ts @@ -69,6 +69,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { Role: { + name: "Role", values: { ADMIN: "ADMIN", USER: "USER" diff --git a/tests/e2e/orm/schemas/typed-json/schema.ts b/tests/e2e/orm/schemas/typed-json/schema.ts index 11be6f901..d99f97b5d 100644 --- a/tests/e2e/orm/schemas/typed-json/schema.ts +++ b/tests/e2e/orm/schemas/typed-json/schema.ts @@ -94,6 +94,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { Gender: { + name: "Gender", values: { MALE: "MALE", FEMALE: "FEMALE" diff --git a/tests/e2e/orm/schemas/typing/schema.ts b/tests/e2e/orm/schemas/typing/schema.ts index a558c23c6..4c82da67d 100644 --- a/tests/e2e/orm/schemas/typing/schema.ts +++ b/tests/e2e/orm/schemas/typing/schema.ts @@ -328,12 +328,14 @@ export class SchemaType implements SchemaDef { } as const; enums = { Role: { + name: "Role", values: { ADMIN: "ADMIN", USER: "USER" } }, Status: { + name: "Status", values: { ACTIVE: "ACTIVE", INACTIVE: "INACTIVE", diff --git a/tests/regression/package.json b/tests/regression/package.json index d6b14dac5..12f1f3014 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -20,6 +20,7 @@ "@zenstackhq/language": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/sdk": "workspace:*", + "@zenstackhq/plugin-policy": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", "@zenstackhq/vitest-config": "workspace:*", "@types/node": "catalog:" diff --git a/tests/regression/test/issue-204/schema.ts b/tests/regression/test/issue-204/schema.ts index 5b24b3ae7..3c8726b39 100644 --- a/tests/regression/test/issue-204/schema.ts +++ b/tests/regression/test/issue-204/schema.ts @@ -47,6 +47,7 @@ export class SchemaType implements SchemaDef { } as const; enums = { ShirtColor: { + name: "ShirtColor", values: { Black: "Black", White: "White",