quicktype-core 23.3.21 → 23.3.23
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
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -11,16 +11,67 @@ const TypeUtils_1 = require("../Type/TypeUtils");
|
|
|
11
11
|
const UnifyClasses_1 = require("../UnifyClasses");
|
|
12
12
|
function flattenUnions(graph, stringTypeMapping, conflateNumbers, makeObjectTypes, debugPrintReconstitution) {
|
|
13
13
|
let needsRepeat = false;
|
|
14
|
+
// During flattening, recursive unions can appear again while their replacement
|
|
15
|
+
// is still being built. For example, flattening `A | M` can later ask for
|
|
16
|
+
// `A | (A | M)` through an array item or object property. Those are the same
|
|
17
|
+
// union by associativity/idempotence, so remember each in-progress flattened
|
|
18
|
+
// union by a normalized key of its non-union member refs. Looking up that key
|
|
19
|
+
// lets recursive occurrences reuse the replacement's forwarding ref instead
|
|
20
|
+
// of allocating another finite unrolling of the same cycle.
|
|
21
|
+
const unionKeyToRef = new Map();
|
|
22
|
+
function addUnionKeyAtoms(t, atoms, seen) {
|
|
23
|
+
const index = t.index;
|
|
24
|
+
if (seen.has(index))
|
|
25
|
+
return;
|
|
26
|
+
seen.add(index);
|
|
27
|
+
if (t instanceof Type_1.UnionType) {
|
|
28
|
+
for (const m of t.members) {
|
|
29
|
+
addUnionKeyAtoms(m, atoms, seen);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
atoms.add((0, TypeRef_1.typeRefIndex)(t.typeRef));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function unionKeyForTypes(types) {
|
|
37
|
+
const atoms = new Set();
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
for (const t of types) {
|
|
40
|
+
addUnionKeyAtoms(t, atoms, seen);
|
|
41
|
+
}
|
|
42
|
+
return Array.from(atoms)
|
|
43
|
+
.sort((a, b) => a - b)
|
|
44
|
+
.join(",");
|
|
45
|
+
}
|
|
14
46
|
function replace(types, builder, forwardingRef) {
|
|
15
|
-
|
|
47
|
+
unionKeyToRef.set(unionKeyForTypes(types), forwardingRef);
|
|
48
|
+
let unionBuilder;
|
|
49
|
+
const unifyTypeRefs = (trefs) => {
|
|
16
50
|
(0, Support_1.assert)(trefs.length > 0, "Must have at least one type to build union");
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
51
|
+
const maybeReconstituted = builder.lookupTypeRefs(trefs, undefined, false);
|
|
52
|
+
if (maybeReconstituted !== undefined) {
|
|
53
|
+
return maybeReconstituted;
|
|
54
|
+
}
|
|
55
|
+
const typesToUnify = new Set(trefs.map((tref) => (0, TypeRef_1.derefTypeRef)(tref, graph)));
|
|
56
|
+
if ((0, collection_utils_1.iterableSome)(typesToUnify, (t) => t instanceof Type_1.IntersectionType)) {
|
|
57
|
+
trefs = trefs.map((tref) => builder.reconstituteType((0, TypeRef_1.derefTypeRef)(tref, graph)));
|
|
58
|
+
if (trefs.length === 1) {
|
|
59
|
+
return trefs[0];
|
|
60
|
+
}
|
|
61
|
+
needsRepeat = true;
|
|
62
|
+
return builder.getUnionType(TypeAttributes_1.emptyTypeAttributes, new Set(trefs));
|
|
63
|
+
}
|
|
64
|
+
const key = unionKeyForTypes(typesToUnify);
|
|
65
|
+
const maybeRef = unionKeyToRef.get(key);
|
|
66
|
+
if (maybeRef !== undefined) {
|
|
67
|
+
return maybeRef;
|
|
20
68
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
return builder.withForwardingRef(undefined, (nestedForwardingRef) => {
|
|
70
|
+
unionKeyToRef.set(key, nestedForwardingRef);
|
|
71
|
+
return (0, UnifyClasses_1.unifyTypes)(typesToUnify, TypeAttributes_1.emptyTypeAttributes, builder, unionBuilder, conflateNumbers, nestedForwardingRef);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
unionBuilder = new UnifyClasses_1.UnifyUnionBuilder(builder, makeObjectTypes, true, unifyTypeRefs);
|
|
24
75
|
return (0, UnifyClasses_1.unifyTypes)(types, TypeAttributes_1.emptyTypeAttributes, builder, unionBuilder, conflateNumbers, forwardingRef);
|
|
25
76
|
}
|
|
26
77
|
const allUnions = (0, collection_utils_1.setFilter)(graph.allTypesUnordered(), (t) => t instanceof Type_1.UnionType);
|