diff --git a/packages/runtime/package.json b/packages/runtime/package.json index b00d326a..ba09c9a8 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -99,6 +99,7 @@ "@zenstackhq/testtools": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", "@zenstackhq/vitest-config": "workspace:*", - "tsx": "^4.19.2" + "tsx": "^4.19.2", + "zod": "~3.25.0" } } diff --git a/packages/runtime/src/client/crud-types.ts b/packages/runtime/src/client/crud-types.ts index 96c57be5..73e5ed31 100644 --- a/packages/runtime/src/client/crud-types.ts +++ b/packages/runtime/src/client/crud-types.ts @@ -247,8 +247,8 @@ type EnumFilter, Nullable e }; type ArrayFilter = { - equals?: MapScalarType[]; - has?: MapScalarType; + equals?: MapScalarType[] | null; + has?: MapScalarType | null; hasEvery?: MapScalarType[]; hasSome?: MapScalarType[]; isEmpty?: boolean; diff --git a/packages/runtime/src/client/crud/validator.ts b/packages/runtime/src/client/crud/validator.ts index eb9ecf21..6d5f7ebf 100644 --- a/packages/runtime/src/client/crud/validator.ts +++ b/packages/runtime/src/client/crud/validator.ts @@ -233,10 +233,10 @@ export class InputValidator { } else { return match(type) .with('String', () => z.string()) - .with('Int', () => z.int()) + .with('Int', () => z.number().int()) .with('Float', () => z.number()) .with('Boolean', () => z.boolean()) - .with('BigInt', () => z.union([z.int(), z.bigint()])) + .with('BigInt', () => z.union([z.number().int(), z.bigint()])) .with('Decimal', () => z.union([z.number(), z.instanceof(Decimal), z.string()])) .with('DateTime', () => z.union([z.date(), z.string().datetime()])) .with('Bytes', () => z.instanceof(Uint8Array)) @@ -252,20 +252,22 @@ export class InputValidator { } const typeDef = this.schema.typeDefs?.[type]; invariant(typeDef, `Type definition "${type}" not found in schema`); - schema = z.looseObject( - Object.fromEntries( - Object.entries(typeDef.fields).map(([field, def]) => { - let fieldSchema = this.makePrimitiveSchema(def.type); - if (def.array) { - fieldSchema = fieldSchema.array(); - } - if (def.optional) { - fieldSchema = fieldSchema.optional(); - } - return [field, fieldSchema]; - }), - ), - ); + schema = z + .object( + Object.fromEntries( + Object.entries(typeDef.fields).map(([field, def]) => { + let fieldSchema = this.makePrimitiveSchema(def.type); + if (def.array) { + fieldSchema = fieldSchema.array(); + } + if (def.optional) { + fieldSchema = fieldSchema.optional(); + } + return [field, fieldSchema]; + }), + ), + ) + .passthrough(); this.schemaCache.set(key, schema); return schema; } @@ -469,7 +471,7 @@ export class InputValidator { private makeDateTimeFilterSchema(optional: boolean, withAggregations: boolean): ZodType { return this.makeCommonPrimitiveFilterSchema( - z.union([z.iso.datetime(), z.date()]), + z.union([z.string().datetime(), z.date()]), optional, () => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations)), withAggregations ? ['_count', '_min', '_max'] : undefined, @@ -519,7 +521,7 @@ export class InputValidator { gte: baseSchema.optional(), not: makeThis().optional(), ...(withAggregations?.includes('_count') - ? { _count: this.makeNumberFilterSchema(z.int(), false, false).optional() } + ? { _count: this.makeNumberFilterSchema(z.number().int(), false, false).optional() } : {}), ...(withAggregations?.includes('_avg') ? { _avg: commonAggSchema() } : {}), ...(withAggregations?.includes('_sum') ? { _sum: commonAggSchema() } : {}), @@ -1020,7 +1022,7 @@ export class InputValidator { return z.strictObject({ where: this.makeWhereSchema(model, false).optional(), data: this.makeUpdateDataSchema(model, [], true), - limit: z.int().nonnegative().optional(), + limit: z.number().int().nonnegative().optional(), }); } @@ -1158,7 +1160,7 @@ export class InputValidator { return z .object({ where: this.makeWhereSchema(model, false).optional(), - limit: z.int().nonnegative().optional(), + limit: z.number().int().nonnegative().optional(), }) .optional(); @@ -1255,10 +1257,10 @@ export class InputValidator { const modelDef = requireModel(this.schema, model); const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); - let schema = z.strictObject({ + let schema: z.ZodSchema = z.strictObject({ where: this.makeWhereSchema(model, false).optional(), orderBy: this.orArray(this.makeOrderBySchema(model, false, true), true).optional(), - by: this.orArray(z.enum(nonRelationFields), true), + by: this.orArray(z.enum(nonRelationFields as [string, ...string[]]), true), having: this.makeHavingSchema(model).optional(), skip: this.makeSkipSchema().optional(), take: this.makeTakeSchema().optional(), @@ -1340,11 +1342,11 @@ export class InputValidator { // #region Helpers private makeSkipSchema() { - return z.int().nonnegative(); + return z.number().int().nonnegative(); } private makeTakeSchema() { - return z.int(); + return z.number().int(); } private refineForSelectIncludeMutuallyExclusive(schema: ZodType) { diff --git a/packages/runtime/test/client-api/mixin.test.ts b/packages/runtime/test/client-api/mixin.test.ts index ffbdbf2f..9f85d30a 100644 --- a/packages/runtime/test/client-api/mixin.test.ts +++ b/packages/runtime/test/client-api/mixin.test.ts @@ -51,7 +51,7 @@ model Bar with CommonFields { description: 'Bar', }, }), - ).rejects.toThrow('Invalid input'); + ).rejects.toThrow(/invalid/i); await expect( client.bar.create({ diff --git a/packages/runtime/test/client-api/typed-json-fields.test.ts b/packages/runtime/test/client-api/typed-json-fields.test.ts index 53ca43e0..4ea57c7e 100644 --- a/packages/runtime/test/client-api/typed-json-fields.test.ts +++ b/packages/runtime/test/client-api/typed-json-fields.test.ts @@ -127,7 +127,7 @@ model User { }, }, }), - ).rejects.toThrow('Invalid input'); + ).rejects.toThrow(/invalid/i); }); it('works with find', async () => { @@ -215,7 +215,7 @@ model User { }, }, }), - ).rejects.toThrow('Invalid input'); + ).rejects.toThrow(/invalid/i); }); }, ); diff --git a/packages/zod/package.json b/packages/zod/package.json index b6ff5943..de0ef2a2 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -30,7 +30,8 @@ }, "devDependencies": { "@zenstackhq/eslint-config": "workspace:*", - "@zenstackhq/typescript-config": "workspace:*" + "@zenstackhq/typescript-config": "workspace:*", + "zod": "~3.25.0" }, "peerDependencies": { "zod": "catalog:" diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts index a7fa63e6..a74ad446 100644 --- a/packages/zod/src/index.ts +++ b/packages/zod/src/index.ts @@ -28,6 +28,6 @@ function makeScalarSchema(fieldDef: FieldDef): ZodType { .with('String', () => z.string()) .with(P.union('Int', 'BigInt', 'Float', 'Decimal'), () => z.number()) .with('Boolean', () => z.boolean()) - .with('DateTime', () => z.iso.datetime()) + .with('DateTime', () => z.string().datetime()) .otherwise(() => z.unknown()); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42a9470e..70b5b1db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,9 +33,6 @@ catalogs: typescript: specifier: ^5.8.0 version: 5.8.3 - zod: - specifier: ^4.0.0 - version: 4.0.5 importers: @@ -290,9 +287,6 @@ importers: uuid: specifier: ^11.0.5 version: 11.0.5 - zod: - specifier: 'catalog:' - version: 4.0.5 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -324,6 +318,9 @@ importers: tsx: specifier: ^4.19.2 version: 4.19.2 + zod: + specifier: ~3.25.0 + version: 3.25.76 packages/sdk: dependencies: @@ -427,9 +424,6 @@ importers: ts-pattern: specifier: 'catalog:' version: 5.7.1 - zod: - specifier: 'catalog:' - version: 4.0.5 devDependencies: '@zenstackhq/eslint-config': specifier: workspace:* @@ -437,6 +431,9 @@ importers: '@zenstackhq/typescript-config': specifier: workspace:* version: link:../typescript-config + zod: + specifier: ~3.25.0 + version: 3.25.76 samples/blog: dependencies: @@ -2666,8 +2663,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@4.0.5: - resolution: {integrity: sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: @@ -4727,4 +4724,4 @@ snapshots: yocto-queue@0.1.0: {} - zod@4.0.5: {} + zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 80826124..11ea3a73 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,7 +4,7 @@ packages: - tests/** catalog: kysely: ^0.27.6 - zod: ^4.0.0 + zod: ^3.25.0 || ^4.0.0 prisma: ^6.10.0 langium: 3.5.0 langium-cli: 3.5.0