Skip to content

Commit 3815277

Browse files
Azzerty23claude
andcommitted
fix(zod): align Json and TypeDef zod types with @zenstackhq/orm types
Fixes #2639 — `JsonValue` from `@zenstackhq/orm` was not assignable to the union type previously inferred by the zod factory for Json fields. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a31a32e commit 3815277

7 files changed

Lines changed: 84 additions & 51 deletions

File tree

packages/zod/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@zenstackhq/tsdown-config": "workspace:*",
5151
"@zenstackhq/typescript-config": "workspace:*",
5252
"@zenstackhq/vitest-config": "workspace:*",
53+
"@zenstackhq/orm": "workspace:*",
5354
"zod": "^4.1.0"
5455
},
5556
"peerDependencies": {

packages/zod/src/types.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TypeDefResult, JsonValue } from '@zenstackhq/orm';
12
import type {
23
FieldHasDefault,
34
FieldIsArray,
@@ -132,16 +133,10 @@ type MapFieldTypeToZod<Schema extends SchemaDef, FieldType> = FieldType extends
132133
: FieldType extends GetEnums<Schema>
133134
? EnumZodType<Schema, FieldType>
134135
: FieldType extends GetTypeDefs<Schema>
135-
? z.ZodObject<GetTypeDefFieldsShape<Schema, FieldType>, z.core.$strict>
136+
? z.ZodType<TypeDefResult<Schema, FieldType>>
136137
: z.ZodUnknown;
137138

138-
type JsonZodType =
139-
| z.ZodObject<Record<string, z.ZodType>, z.core.$loose>
140-
| z.ZodArray<z.ZodType>
141-
| z.ZodString
142-
| z.ZodNumber
143-
| z.ZodBoolean
144-
| z.ZodNull;
139+
type JsonZodType = z.ZodType<JsonValue>;
145140

146141
type EnumZodType<Schema extends SchemaDef, EnumName extends GetEnums<Schema>> = z.ZodEnum<{
147142
[Key in keyof GetEnum<Schema, EnumName>]: GetEnum<Schema, EnumName>[Key];

packages/zod/test/factory.test.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { describe, expect, expectTypeOf, it } from 'vitest';
33
import { createSchemaFactory } from '../src/index';
44
import { schema } from './schema/schema';
55
import z from 'zod';
6+
import type { User as ModelUser } from './schema/models';
7+
import type { JsonValue } from '@zenstackhq/orm';
68

79
const factory = createSchemaFactory(schema);
810

@@ -69,9 +71,7 @@ describe('SchemaFactory - makeModelSchema', () => {
6971

7072
// optional Json
7173
expectTypeOf<User>().toHaveProperty('metadata');
72-
expectTypeOf<User['metadata']>().toEqualTypeOf<
73-
string | number | boolean | null | Record<string, unknown> | unknown[] | undefined
74-
>();
74+
expectTypeOf<User['metadata']>().toEqualTypeOf<JsonValue | null | undefined>();
7575

7676
// required enum
7777
expectTypeOf<User['status']>().toEqualTypeOf<'ACTIVE' | 'INACTIVE' | 'PENDING'>();
@@ -194,6 +194,22 @@ describe('SchemaFactory - makeModelSchema', () => {
194194
expect(userSchema.safeParse({ ...validUser, metadata: { key: 'value' } }).success).toBe(true);
195195
expect(userSchema.safeParse({ ...validUser, metadata: [1, 2, 3] }).success).toBe(true);
196196
expect(userSchema.safeParse({ ...validUser, metadata: 42 }).success).toBe(true);
197+
expect(userSchema.safeParse({ ...validUser, metadata: null }).success).toBe(true);
198+
});
199+
200+
it('infers zod type compatible with ORM model type (except optionality)', () => {
201+
// ORM model results use `T | null` for optional fields; the Zod schema
202+
// uses `T | null | undefined` to also accept missing fields in input
203+
// objects. The useful property is that any ORM model value is valid
204+
// input for the Zod schema.
205+
const userSchema = factory.makeModelSchema('User');
206+
type ZodUser = z.infer<typeof userSchema>;
207+
expectTypeOf<ModelUser>().toExtend<ZodUser>();
208+
209+
// or with required
210+
const _userSchemaRequired = userSchema.required();
211+
type ZodUserRequired = z.infer<typeof _userSchemaRequired>;
212+
expectTypeOf<ZodUserRequired>().toMatchTypeOf<ModelUser>();
197213
});
198214

199215
it('rejects invalid Json values', () => {

packages/zod/test/schema/models.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//////////////////////////////////////////////////////////////////////////////////////////////
2+
// DO NOT MODIFY THIS FILE //
3+
// This file is automatically generated by ZenStack CLI and should not be manually updated. //
4+
//////////////////////////////////////////////////////////////////////////////////////////////
5+
6+
/* eslint-disable */
7+
8+
import { schema as $schema, type SchemaType as $Schema } from "./schema";
9+
import type { ModelResult as $ModelResult, TypeDefResult as $TypeDefResult } from "@zenstackhq/orm";
10+
export type User = $ModelResult<$Schema, "User">;
11+
export type Post = $ModelResult<$Schema, "Post">;
12+
export type Product = $ModelResult<$Schema, "Product">;
13+
export type Asset = $ModelResult<$Schema, "Asset">;
14+
export type Video = $ModelResult<$Schema, "Video">;
15+
export type Image = $ModelResult<$Schema, "Image">;
16+
export type Address = $TypeDefResult<$Schema, "Address">;
17+
export const Status = $schema.enums.Status.values;
18+
export type Status = (typeof Status)[keyof typeof Status];

0 commit comments

Comments
 (0)