Skip to content

Commit 090be2c

Browse files
Azzerty23claude
andauthored
fix(zod): json type compatibility between inferred zod types and @zenstackhq/orm types (#2641)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8ddbfde commit 090be2c

10 files changed

Lines changed: 526 additions & 52 deletions

File tree

packages/orm/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"build": "tsc --noEmit && tsdown",
1919
"watch": "tsdown --watch",
2020
"lint": "eslint src --ext ts",
21-
"pack": "pnpm pack"
21+
"pack": "pnpm pack",
22+
"test:generate": "tsx ../../scripts/test-generate.ts . --generate-models"
2223
},
2324
"keywords": [],
2425
"files": [

packages/orm/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];

packages/orm/test/schema/schema.ts

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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 { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema";
9+
export class SchemaType implements SchemaDef {
10+
provider = {
11+
type: "postgresql"
12+
} as const;
13+
models = {
14+
User: {
15+
name: "User",
16+
fields: {
17+
id: {
18+
name: "id",
19+
type: "String",
20+
id: true,
21+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[],
22+
default: ExpressionUtils.call("cuid") as FieldDefault
23+
},
24+
email: {
25+
name: "email",
26+
type: "String",
27+
attributes: [{ name: "@email" }, { name: "@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("The user's email address") }] }] as readonly AttributeApplication[]
28+
},
29+
username: {
30+
name: "username",
31+
type: "String",
32+
attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(3) }, { name: "max", value: ExpressionUtils.literal(50) }] }] as readonly AttributeApplication[]
33+
},
34+
website: {
35+
name: "website",
36+
type: "String",
37+
optional: true,
38+
attributes: [{ name: "@url" }] as readonly AttributeApplication[]
39+
},
40+
code: {
41+
name: "code",
42+
type: "String",
43+
attributes: [{ name: "@startsWith", args: [{ name: "text", value: ExpressionUtils.literal("USR") }] }] as readonly AttributeApplication[]
44+
},
45+
age: {
46+
name: "age",
47+
type: "Int",
48+
attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }, { name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] as readonly AttributeApplication[]
49+
},
50+
score: {
51+
name: "score",
52+
type: "Float",
53+
attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0.0) }] }, { name: "@lt", args: [{ name: "value", value: ExpressionUtils.literal(100.0) }] }] as readonly AttributeApplication[]
54+
},
55+
bigNum: {
56+
name: "bigNum",
57+
type: "BigInt",
58+
attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[]
59+
},
60+
balance: {
61+
name: "balance",
62+
type: "Decimal",
63+
attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[]
64+
},
65+
active: {
66+
name: "active",
67+
type: "Boolean"
68+
},
69+
birthdate: {
70+
name: "birthdate",
71+
type: "DateTime",
72+
optional: true
73+
},
74+
avatar: {
75+
name: "avatar",
76+
type: "Bytes",
77+
optional: true
78+
},
79+
metadata: {
80+
name: "metadata",
81+
type: "Json",
82+
optional: true
83+
},
84+
status: {
85+
name: "status",
86+
type: "Status"
87+
},
88+
address: {
89+
name: "address",
90+
type: "Address",
91+
optional: true,
92+
attributes: [{ name: "@json" }] as readonly AttributeApplication[]
93+
},
94+
posts: {
95+
name: "posts",
96+
type: "Post",
97+
array: true,
98+
relation: { opposite: "author" }
99+
}
100+
},
101+
attributes: [
102+
{ name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("age"), ">=", ExpressionUtils.literal(18)) }, { name: "message", value: ExpressionUtils.literal("Must be adult") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("age")]) }] },
103+
{ name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A user of the system") }] }
104+
] as readonly AttributeApplication[],
105+
idFields: ["id"],
106+
uniqueFields: {
107+
id: { type: "String" }
108+
}
109+
},
110+
Post: {
111+
name: "Post",
112+
fields: {
113+
id: {
114+
name: "id",
115+
type: "String",
116+
id: true,
117+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[],
118+
default: ExpressionUtils.call("cuid") as FieldDefault
119+
},
120+
title: {
121+
name: "title",
122+
type: "String"
123+
},
124+
published: {
125+
name: "published",
126+
type: "Boolean"
127+
},
128+
tags: {
129+
name: "tags",
130+
type: "String",
131+
array: true
132+
},
133+
author: {
134+
name: "author",
135+
type: "User",
136+
optional: true,
137+
attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array("String", [ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array("String", [ExpressionUtils.field("id")]) }] }] as readonly AttributeApplication[],
138+
relation: { opposite: "posts", fields: ["authorId"], references: ["id"] }
139+
},
140+
authorId: {
141+
name: "authorId",
142+
type: "String",
143+
optional: true,
144+
foreignKeyFor: [
145+
"author"
146+
] as readonly string[]
147+
}
148+
},
149+
idFields: ["id"],
150+
uniqueFields: {
151+
id: { type: "String" }
152+
}
153+
},
154+
Product: {
155+
name: "Product",
156+
fields: {
157+
id: {
158+
name: "id",
159+
type: "String",
160+
id: true,
161+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[],
162+
default: ExpressionUtils.call("cuid") as FieldDefault
163+
},
164+
name: {
165+
name: "name",
166+
type: "String"
167+
},
168+
price: {
169+
name: "price",
170+
type: "Float"
171+
},
172+
discount: {
173+
name: "discount",
174+
type: "Float",
175+
attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[],
176+
default: 0 as FieldDefault
177+
},
178+
finalPrice: {
179+
name: "finalPrice",
180+
type: "Float",
181+
attributes: [{ name: "@computed" }] as readonly AttributeApplication[],
182+
computed: true
183+
}
184+
},
185+
idFields: ["id"],
186+
uniqueFields: {
187+
id: { type: "String" }
188+
},
189+
computedFields: {
190+
finalPrice(_context: {
191+
modelAlias: string;
192+
}): number {
193+
throw new Error("This is a stub for computed field");
194+
}
195+
}
196+
},
197+
Asset: {
198+
name: "Asset",
199+
fields: {
200+
id: {
201+
name: "id",
202+
type: "Int",
203+
id: true,
204+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[],
205+
default: ExpressionUtils.call("autoincrement") as FieldDefault
206+
},
207+
createdAt: {
208+
name: "createdAt",
209+
type: "DateTime",
210+
attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[],
211+
default: ExpressionUtils.call("now") as FieldDefault
212+
},
213+
assetType: {
214+
name: "assetType",
215+
type: "String",
216+
isDiscriminator: true
217+
}
218+
},
219+
attributes: [
220+
{ name: "@@delegate", args: [{ name: "discriminator", value: ExpressionUtils.field("assetType") }] }
221+
] as readonly AttributeApplication[],
222+
idFields: ["id"],
223+
uniqueFields: {
224+
id: { type: "Int" }
225+
},
226+
isDelegate: true,
227+
subModels: ["Video", "Image"]
228+
},
229+
Video: {
230+
name: "Video",
231+
baseModel: "Asset",
232+
fields: {
233+
id: {
234+
name: "id",
235+
type: "Int",
236+
id: true,
237+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[],
238+
default: ExpressionUtils.call("autoincrement") as FieldDefault
239+
},
240+
createdAt: {
241+
name: "createdAt",
242+
type: "DateTime",
243+
originModel: "Asset",
244+
attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[],
245+
default: ExpressionUtils.call("now") as FieldDefault
246+
},
247+
assetType: {
248+
name: "assetType",
249+
type: "String",
250+
originModel: "Asset",
251+
isDiscriminator: true
252+
},
253+
duration: {
254+
name: "duration",
255+
type: "Int"
256+
},
257+
url: {
258+
name: "url",
259+
type: "String"
260+
}
261+
},
262+
idFields: ["id"],
263+
uniqueFields: {
264+
id: { type: "Int" }
265+
}
266+
},
267+
Image: {
268+
name: "Image",
269+
baseModel: "Asset",
270+
fields: {
271+
id: {
272+
name: "id",
273+
type: "Int",
274+
id: true,
275+
attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[],
276+
default: ExpressionUtils.call("autoincrement") as FieldDefault
277+
},
278+
createdAt: {
279+
name: "createdAt",
280+
type: "DateTime",
281+
originModel: "Asset",
282+
attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[],
283+
default: ExpressionUtils.call("now") as FieldDefault
284+
},
285+
assetType: {
286+
name: "assetType",
287+
type: "String",
288+
originModel: "Asset",
289+
isDiscriminator: true
290+
},
291+
format: {
292+
name: "format",
293+
type: "String"
294+
},
295+
width: {
296+
name: "width",
297+
type: "Int"
298+
}
299+
},
300+
idFields: ["id"],
301+
uniqueFields: {
302+
id: { type: "Int" }
303+
}
304+
}
305+
} as const;
306+
typeDefs = {
307+
Address: {
308+
name: "Address",
309+
fields: {
310+
residents: {
311+
name: "residents",
312+
type: "String",
313+
array: true
314+
},
315+
street: {
316+
name: "street",
317+
type: "String",
318+
attributes: [{ name: "@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("Street address line") }] }] as readonly AttributeApplication[]
319+
},
320+
city: {
321+
name: "city",
322+
type: "String",
323+
attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }] }] as readonly AttributeApplication[]
324+
},
325+
zip: {
326+
name: "zip",
327+
type: "String",
328+
optional: true
329+
}
330+
},
331+
attributes: [
332+
{ name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] },
333+
{ name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] }
334+
] as readonly AttributeApplication[]
335+
}
336+
} as const;
337+
enums = {
338+
Status: {
339+
name: "Status",
340+
values: {
341+
ACTIVE: "ACTIVE",
342+
INACTIVE: "INACTIVE",
343+
PENDING: "PENDING"
344+
},
345+
attributes: [
346+
{ name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] }
347+
] as readonly AttributeApplication[]
348+
}
349+
} as const;
350+
authType = "User" as const;
351+
plugins = {};
352+
}
353+
export const schema = new SchemaType();

0 commit comments

Comments
 (0)