quicktype-core 23.3.21 → 23.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,10 @@ import { type ClassProperty, type EnumType, ObjectType, type Type } from "../../
8
8
  import type { typeScriptZodOptions } from "./language";
9
9
  export declare class TypeScriptZodRenderer extends ConvenienceRenderer {
10
10
  protected readonly _options: OptionValues<typeof typeScriptZodOptions>;
11
+ /** TypeRefs of object types that participate in a reference cycle.
12
+ * These must be emitted as z.lazy() schemas with an explicit type
13
+ * annotation, since zod cannot infer recursive types. */
14
+ private _recursiveTypeRefs;
11
15
  constructor(targetLanguage: TargetLanguage, renderContext: RenderContext, _options: OptionValues<typeof typeScriptZodOptions>);
12
16
  protected forbiddenNamesForGlobalNamespace(): string[];
13
17
  protected nameStyle(original: string, upper: boolean): string;
@@ -19,7 +23,23 @@ export declare class TypeScriptZodRenderer extends ConvenienceRenderer {
19
23
  protected emitImports(): void;
20
24
  protected typeMapTypeForProperty(p: ClassProperty): Sourcelike;
21
25
  protected typeMapTypeFor(t: Type, required?: boolean): Sourcelike;
26
+ /** TypeScript type for `t`, used to annotate recursive schemas for
27
+ * which zod cannot infer the type.
28
+ *
29
+ * This intentionally duplicates neither `typeMapTypeFor` above nor
30
+ * `sourceFor` in the plain TypeScript renderer
31
+ * (`TypeScriptFlowBaseRenderer`): it must mirror exactly what
32
+ * `z.infer` would derive from the schemas emitted by
33
+ * `typeMapTypeFor` (e.g. `z.coerce.date()` implies `Date`), while
34
+ * `sourceFor` is shaped by ts/flow-specific options like
35
+ * `preferConstValues` and `declareUnions` that don't exist here. */
36
+ protected underlyingTypeFor(t: Type): Sourcelike;
37
+ protected isRecursive(t: ObjectType): boolean;
22
38
  protected emitObject(name: Name, t: ObjectType): void;
39
+ /** Emit a recursive object type. zod cannot infer recursive types, so
40
+ * the type is declared explicitly and the schema is wrapped in
41
+ * z.lazy() with a z.ZodType annotation. */
42
+ protected emitLazyObject(name: Name, t: ObjectType): void;
23
43
  protected emitEnum(e: EnumType, enumName: Name): void;
24
44
  /** Static function that extracts underlying type refs for types that form part of the
25
45
  * definition of the passed type - used to ensure that these appear in generated source
@@ -14,6 +14,10 @@ class TypeScriptZodRenderer extends ConvenienceRenderer_1.ConvenienceRenderer {
14
14
  constructor(targetLanguage, renderContext, _options) {
15
15
  super(targetLanguage, renderContext);
16
16
  this._options = _options;
17
+ /** TypeRefs of object types that participate in a reference cycle.
18
+ * These must be emitted as z.lazy() schemas with an explicit type
19
+ * annotation, since zod cannot infer recursive types. */
20
+ this._recursiveTypeRefs = new Set();
17
21
  }
18
22
  forbiddenNamesForGlobalNamespace() {
19
23
  return ["Class", "Date", "Object", "String", "Array", "JSON", "Error"];
@@ -72,7 +76,46 @@ class TypeScriptZodRenderer extends ConvenienceRenderer_1.ConvenienceRenderer {
72
76
  }
73
77
  return match;
74
78
  }
79
+ /** TypeScript type for `t`, used to annotate recursive schemas for
80
+ * which zod cannot infer the type.
81
+ *
82
+ * This intentionally duplicates neither `typeMapTypeFor` above nor
83
+ * `sourceFor` in the plain TypeScript renderer
84
+ * (`TypeScriptFlowBaseRenderer`): it must mirror exactly what
85
+ * `z.infer` would derive from the schemas emitted by
86
+ * `typeMapTypeFor` (e.g. `z.coerce.date()` implies `Date`), while
87
+ * `sourceFor` is shaped by ts/flow-specific options like
88
+ * `preferConstValues` and `declareUnions` that don't exist here. */
89
+ underlyingTypeFor(t) {
90
+ if (["class", "object", "enum"].includes(t.kind)) {
91
+ return this.nameForNamedType(t);
92
+ }
93
+ return (0, TypeUtils_1.matchType)(t, (_anyType) => "any", (_nullType) => "null", (_boolType) => "boolean", (_integerType) => "number", (_doubleType) => "number", (_stringType) => "string", (arrayType) => [
94
+ "Array<",
95
+ this.underlyingTypeFor(arrayType.items),
96
+ ">",
97
+ ], (_classType) => (0, Support_1.panic)("Should already be handled."), (mapType) => [
98
+ "Record<string, ",
99
+ this.underlyingTypeFor(mapType.values),
100
+ ">",
101
+ ], (_enumType) => (0, Support_1.panic)("Should already be handled."), (unionType) => {
102
+ const children = Array.from(unionType.getChildren()).map((type) => this.underlyingTypeFor(type));
103
+ return (0, collection_utils_1.arrayIntercalate)(" | ", children);
104
+ }, (_transformedStringType) => {
105
+ if (_transformedStringType.kind === "date-time") {
106
+ return "Date";
107
+ }
108
+ return "string";
109
+ });
110
+ }
111
+ isRecursive(t) {
112
+ return this._recursiveTypeRefs.has(t.typeRef);
113
+ }
75
114
  emitObject(name, t) {
115
+ if (this.isRecursive(t)) {
116
+ this.emitLazyObject(name, t);
117
+ return;
118
+ }
76
119
  this.ensureBlankLine();
77
120
  this.emitLine("\nexport const ", name, "Schema = ", "z.object({");
78
121
  this.indent(() => {
@@ -85,6 +128,35 @@ class TypeScriptZodRenderer extends ConvenienceRenderer_1.ConvenienceRenderer {
85
128
  this.emitLine("export type ", name, " = z.infer<typeof ", name, "Schema>;");
86
129
  }
87
130
  }
131
+ /** Emit a recursive object type. zod cannot infer recursive types, so
132
+ * the type is declared explicitly and the schema is wrapped in
133
+ * z.lazy() with a z.ZodType annotation. */
134
+ emitLazyObject(name, t) {
135
+ this.ensureBlankLine();
136
+ if (this._options.justSchema) {
137
+ this.emitLine("\nexport const ", name, "Schema: z.ZodType<any> = z.lazy(() =>");
138
+ }
139
+ else {
140
+ this.emitLine("\nexport type ", name, " = {");
141
+ this.indent(() => {
142
+ this.forEachClassProperty(t, "none", (_, jsonName, property) => {
143
+ this.emitLine(`"${(0, Strings_1.utf16StringEscape)(jsonName)}"`, property.isOptional ? "?" : "", ": ", this.underlyingTypeFor(property.type), ";");
144
+ });
145
+ });
146
+ this.emitLine("};");
147
+ this.emitLine("export const ", name, "Schema: z.ZodType<", name, "> = z.lazy(() =>");
148
+ }
149
+ this.indent(() => {
150
+ this.emitLine("z.object({");
151
+ this.indent(() => {
152
+ this.forEachClassProperty(t, "none", (_, jsonName, property) => {
153
+ this.emitLine(`"${(0, Strings_1.utf16StringEscape)(jsonName)}"`, ": ", this.typeMapTypeForProperty(property), ",");
154
+ });
155
+ });
156
+ this.emitLine("})");
157
+ });
158
+ this.emitLine(");");
159
+ }
88
160
  emitEnum(e, enumName) {
89
161
  this.ensureBlankLine();
90
162
  this.emitDescription(this.descriptionForType(e));
@@ -164,10 +236,44 @@ class TypeScriptZodRenderer extends ConvenienceRenderer_1.ConvenienceRenderer {
164
236
  });
165
237
  mapChildTypeRefs.push(childTypeRefs);
166
238
  });
239
+ // Find types that participate in a reference cycle: types from
240
+ // which we can get back to the same type by following child type
241
+ // references. Those can never be topologically ordered.
242
+ const indexForTypeRef = new Map();
243
+ mapTypeRef.forEach((typeRef, index) => indexForTypeRef.set(typeRef, index));
244
+ this._recursiveTypeRefs = new Set();
245
+ mapType.forEach((_, startIndex) => {
246
+ const visited = new Set();
247
+ const stack = [...mapChildTypeRefs[startIndex]];
248
+ while (stack.length > 0) {
249
+ const childRef = stack.pop();
250
+ const childIndex = indexForTypeRef.get(childRef);
251
+ if (childIndex === undefined)
252
+ continue;
253
+ if (childIndex === startIndex) {
254
+ this._recursiveTypeRefs.add(mapTypeRef[startIndex]);
255
+ break;
256
+ }
257
+ if (visited.has(childIndex))
258
+ continue;
259
+ visited.add(childIndex);
260
+ stack.push(...mapChildTypeRefs[childIndex]);
261
+ }
262
+ });
167
263
  // Items to process on this pass
168
264
  let indices = [];
169
265
  mapType.forEach((_, index) => {
170
- indices.push(index);
266
+ if (this._recursiveTypeRefs.has(mapTypeRef[index])) {
267
+ // Recursive types are emitted first: they are wrapped in
268
+ // z.lazy(), so all their references to other schemas are
269
+ // deferred until parse time and don't constrain the
270
+ // output order, while other schemas can reference them
271
+ // directly.
272
+ order.push(index);
273
+ }
274
+ else {
275
+ indices.push(index);
276
+ }
171
277
  });
172
278
  // items to process on the next pass
173
279
  let deferredIndices = [];
@@ -214,8 +320,9 @@ class TypeScriptZodRenderer extends ConvenienceRenderer_1.ConvenienceRenderer {
214
320
  deferredIndices = [];
215
321
  passNum++;
216
322
  if (passNum > MAX_PASSES) {
217
- // giving up
218
- order.push(...deferredIndices);
323
+ // giving up: emit the stuck items anyway rather than
324
+ // silently dropping them
325
+ order.push(...indices);
219
326
  console.warn("Exceeded maximum number of passes when determining output order, output may contain forward references");
220
327
  }
221
328
  } while (indices.length > 0 && passNum <= MAX_PASSES);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quicktype-core",
3
- "version": "23.3.21",
3
+ "version": "23.3.22",
4
4
  "description": "The quicktype engine as a library",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",