@gabrielbryk/json-schema-to-zod 2.7.2

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.
Files changed (99) hide show
  1. package/.prettierrc.json +3 -0
  2. package/CONTRIBUTING.md +9 -0
  3. package/LICENSE +15 -0
  4. package/README.md +157 -0
  5. package/createIndex.ts +32 -0
  6. package/dist/cjs/Types.js +2 -0
  7. package/dist/cjs/cli.js +70 -0
  8. package/dist/cjs/index.js +44 -0
  9. package/dist/cjs/jsonSchemaToZod.js +78 -0
  10. package/dist/cjs/package.json +1 -0
  11. package/dist/cjs/parsers/parseAllOf.js +40 -0
  12. package/dist/cjs/parsers/parseAnyOf.js +18 -0
  13. package/dist/cjs/parsers/parseArray.js +71 -0
  14. package/dist/cjs/parsers/parseBoolean.js +7 -0
  15. package/dist/cjs/parsers/parseConst.js +7 -0
  16. package/dist/cjs/parsers/parseDefault.js +8 -0
  17. package/dist/cjs/parsers/parseEnum.js +21 -0
  18. package/dist/cjs/parsers/parseIfThenElse.js +34 -0
  19. package/dist/cjs/parsers/parseMultipleType.js +10 -0
  20. package/dist/cjs/parsers/parseNot.js +12 -0
  21. package/dist/cjs/parsers/parseNull.js +7 -0
  22. package/dist/cjs/parsers/parseNullable.js +12 -0
  23. package/dist/cjs/parsers/parseNumber.js +74 -0
  24. package/dist/cjs/parsers/parseObject.js +286 -0
  25. package/dist/cjs/parsers/parseOneOf.js +53 -0
  26. package/dist/cjs/parsers/parseSchema.js +285 -0
  27. package/dist/cjs/parsers/parseSimpleDiscriminatedOneOf.js +29 -0
  28. package/dist/cjs/parsers/parseString.js +77 -0
  29. package/dist/cjs/utils/anyOrUnknown.js +14 -0
  30. package/dist/cjs/utils/cliTools.js +108 -0
  31. package/dist/cjs/utils/half.js +7 -0
  32. package/dist/cjs/utils/jsdocs.js +20 -0
  33. package/dist/cjs/utils/omit.js +10 -0
  34. package/dist/cjs/utils/withMessage.js +22 -0
  35. package/dist/cjs/zodToJsonSchema.js +89 -0
  36. package/dist/esm/Types.js +1 -0
  37. package/dist/esm/cli.js +68 -0
  38. package/dist/esm/index.js +28 -0
  39. package/dist/esm/jsonSchemaToZod.js +74 -0
  40. package/dist/esm/package.json +1 -0
  41. package/dist/esm/parsers/parseAllOf.js +37 -0
  42. package/dist/esm/parsers/parseAnyOf.js +14 -0
  43. package/dist/esm/parsers/parseArray.js +67 -0
  44. package/dist/esm/parsers/parseBoolean.js +3 -0
  45. package/dist/esm/parsers/parseConst.js +3 -0
  46. package/dist/esm/parsers/parseDefault.js +4 -0
  47. package/dist/esm/parsers/parseEnum.js +17 -0
  48. package/dist/esm/parsers/parseIfThenElse.js +30 -0
  49. package/dist/esm/parsers/parseMultipleType.js +6 -0
  50. package/dist/esm/parsers/parseNot.js +8 -0
  51. package/dist/esm/parsers/parseNull.js +3 -0
  52. package/dist/esm/parsers/parseNullable.js +8 -0
  53. package/dist/esm/parsers/parseNumber.js +70 -0
  54. package/dist/esm/parsers/parseObject.js +283 -0
  55. package/dist/esm/parsers/parseOneOf.js +49 -0
  56. package/dist/esm/parsers/parseSchema.js +281 -0
  57. package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +25 -0
  58. package/dist/esm/parsers/parseString.js +73 -0
  59. package/dist/esm/utils/anyOrUnknown.js +10 -0
  60. package/dist/esm/utils/cliTools.js +102 -0
  61. package/dist/esm/utils/half.js +3 -0
  62. package/dist/esm/utils/jsdocs.js +15 -0
  63. package/dist/esm/utils/omit.js +6 -0
  64. package/dist/esm/utils/withMessage.js +19 -0
  65. package/dist/esm/zodToJsonSchema.js +86 -0
  66. package/dist/types/Types.d.ts +125 -0
  67. package/dist/types/cli.d.ts +2 -0
  68. package/dist/types/index.d.ts +28 -0
  69. package/dist/types/jsonSchemaToZod.d.ts +2 -0
  70. package/dist/types/parsers/parseAllOf.d.ts +4 -0
  71. package/dist/types/parsers/parseAnyOf.d.ts +4 -0
  72. package/dist/types/parsers/parseArray.d.ts +4 -0
  73. package/dist/types/parsers/parseBoolean.d.ts +4 -0
  74. package/dist/types/parsers/parseConst.d.ts +4 -0
  75. package/dist/types/parsers/parseDefault.d.ts +2 -0
  76. package/dist/types/parsers/parseEnum.d.ts +4 -0
  77. package/dist/types/parsers/parseIfThenElse.d.ts +6 -0
  78. package/dist/types/parsers/parseMultipleType.d.ts +4 -0
  79. package/dist/types/parsers/parseNot.d.ts +4 -0
  80. package/dist/types/parsers/parseNull.d.ts +4 -0
  81. package/dist/types/parsers/parseNullable.d.ts +7 -0
  82. package/dist/types/parsers/parseNumber.d.ts +4 -0
  83. package/dist/types/parsers/parseObject.d.ts +4 -0
  84. package/dist/types/parsers/parseOneOf.d.ts +4 -0
  85. package/dist/types/parsers/parseSchema.d.ts +50 -0
  86. package/dist/types/parsers/parseSimpleDiscriminatedOneOf.d.ts +2 -0
  87. package/dist/types/parsers/parseString.d.ts +4 -0
  88. package/dist/types/utils/anyOrUnknown.d.ts +9 -0
  89. package/dist/types/utils/cliTools.d.ts +28 -0
  90. package/dist/types/utils/half.d.ts +1 -0
  91. package/dist/types/utils/jsdocs.d.ts +3 -0
  92. package/dist/types/utils/omit.d.ts +1 -0
  93. package/dist/types/utils/withMessage.d.ts +10 -0
  94. package/dist/types/zodToJsonSchema.d.ts +26 -0
  95. package/jest.config.js +4 -0
  96. package/package.json +83 -0
  97. package/postcjs.js +1 -0
  98. package/postesm.js +1 -0
  99. package/scripts/generateWorkflowSchema.ts +82 -0
@@ -0,0 +1,283 @@
1
+ import { parseAnyOf } from "./parseAnyOf.js";
2
+ import { parseOneOf } from "./parseOneOf.js";
3
+ import { its, parseSchema } from "./parseSchema.js";
4
+ import { parseAllOf } from "./parseAllOf.js";
5
+ import { parseIfThenElse } from "./parseIfThenElse.js";
6
+ import { addJsdocs } from "../utils/jsdocs.js";
7
+ import { anyOrUnknown } from "../utils/anyOrUnknown.js";
8
+ export function parseObject(objectSchema, refs) {
9
+ let properties = undefined;
10
+ if (objectSchema.properties) {
11
+ if (!Object.keys(objectSchema.properties).length) {
12
+ properties = "z.object({})";
13
+ }
14
+ else {
15
+ properties = "z.object({ ";
16
+ properties += Object.keys(objectSchema.properties)
17
+ .map((key) => {
18
+ const propSchema = objectSchema.properties[key];
19
+ const parsedProp = parseSchema(propSchema, {
20
+ ...refs,
21
+ path: [...refs.path, "properties", key],
22
+ });
23
+ const hasDefault = typeof propSchema === "object" && propSchema.default !== undefined;
24
+ const required = Array.isArray(objectSchema.required)
25
+ ? objectSchema.required.includes(key)
26
+ : typeof propSchema === "object" && propSchema.required === true;
27
+ const optional = !hasDefault && !required;
28
+ const valueWithOptional = optional
29
+ ? `${parsedProp}.optional()`
30
+ : parsedProp;
31
+ let result = shouldUseGetter(valueWithOptional, refs)
32
+ ? `get ${JSON.stringify(key)}(){ return ${valueWithOptional} }`
33
+ : `${JSON.stringify(key)}: ${valueWithOptional}`;
34
+ if (refs.withJsdocs && typeof propSchema === "object") {
35
+ result = addJsdocs(propSchema, result);
36
+ }
37
+ return result;
38
+ })
39
+ .join(", ");
40
+ properties += " })";
41
+ }
42
+ }
43
+ const additionalProperties = objectSchema.additionalProperties !== undefined
44
+ ? parseSchema(objectSchema.additionalProperties, {
45
+ ...refs,
46
+ path: [...refs.path, "additionalProperties"],
47
+ })
48
+ : undefined;
49
+ const unevaluated = objectSchema.unevaluatedProperties;
50
+ let patternProperties = undefined;
51
+ if (objectSchema.patternProperties) {
52
+ const parsedPatternProperties = Object.fromEntries(Object.entries(objectSchema.patternProperties).map(([key, value]) => {
53
+ return [
54
+ key,
55
+ parseSchema(value, {
56
+ ...refs,
57
+ path: [...refs.path, "patternProperties", key],
58
+ }),
59
+ ];
60
+ }, {}));
61
+ patternProperties = "";
62
+ if (properties) {
63
+ if (additionalProperties) {
64
+ patternProperties += `.catchall(z.union([${[
65
+ ...Object.values(parsedPatternProperties),
66
+ additionalProperties,
67
+ ].join(", ")}]))`;
68
+ }
69
+ else if (Object.keys(parsedPatternProperties).length > 1) {
70
+ patternProperties += `.catchall(z.union([${Object.values(parsedPatternProperties).join(", ")}]))`;
71
+ }
72
+ else {
73
+ patternProperties += `.catchall(${Object.values(parsedPatternProperties)})`;
74
+ }
75
+ }
76
+ else {
77
+ if (additionalProperties) {
78
+ patternProperties += `z.record(z.string(), z.union([${[
79
+ ...Object.values(parsedPatternProperties),
80
+ additionalProperties,
81
+ ].join(", ")}]))`;
82
+ }
83
+ else if (Object.keys(parsedPatternProperties).length > 1) {
84
+ patternProperties += `z.record(z.string(), z.union([${Object.values(parsedPatternProperties).join(", ")}]))`;
85
+ }
86
+ else {
87
+ patternProperties += `z.record(z.string(), ${Object.values(parsedPatternProperties)})`;
88
+ }
89
+ }
90
+ patternProperties += ".superRefine((value, ctx) => {\n";
91
+ patternProperties += "for (const key in value) {\n";
92
+ if (additionalProperties) {
93
+ if (objectSchema.properties) {
94
+ patternProperties += `let evaluated = [${Object.keys(objectSchema.properties)
95
+ .map((key) => JSON.stringify(key))
96
+ .join(", ")}].includes(key)\n`;
97
+ }
98
+ else {
99
+ patternProperties += `let evaluated = false\n`;
100
+ }
101
+ }
102
+ for (const key in objectSchema.patternProperties) {
103
+ patternProperties +=
104
+ "if (key.match(new RegExp(" + JSON.stringify(key) + "))) {\n";
105
+ if (additionalProperties) {
106
+ patternProperties += "evaluated = true\n";
107
+ }
108
+ patternProperties +=
109
+ "const result = " +
110
+ parsedPatternProperties[key] +
111
+ ".safeParse(value[key])\n";
112
+ patternProperties += "if (!result.success) {\n";
113
+ patternProperties += `ctx.addIssue({
114
+ path: [key],
115
+ code: 'custom',
116
+ message: \`Invalid input: Key matching regex /\${key}/ must match schema\`,
117
+ params: {
118
+ issues: result.error.issues
119
+ }
120
+ })\n`;
121
+ patternProperties += "}\n";
122
+ patternProperties += "}\n";
123
+ }
124
+ if (additionalProperties) {
125
+ patternProperties += "if (!evaluated) {\n";
126
+ patternProperties +=
127
+ "const result = " + additionalProperties + ".safeParse(value[key])\n";
128
+ patternProperties += "if (!result.success) {\n";
129
+ patternProperties += `ctx.addIssue({
130
+ path: [key],
131
+ code: 'custom',
132
+ message: \`Invalid input: must match catchall schema\`,
133
+ params: {
134
+ issues: result.error.issues
135
+ }
136
+ })\n`;
137
+ patternProperties += "}\n";
138
+ patternProperties += "}\n";
139
+ }
140
+ patternProperties += "}\n";
141
+ patternProperties += "})";
142
+ // Store original patternProperties in meta for JSON Schema round-trip
143
+ if (refs.preserveJsonSchemaForRoundTrip) {
144
+ const patternPropsJson = JSON.stringify(Object.fromEntries(Object.entries(objectSchema.patternProperties).map(([pattern, schema]) => [
145
+ pattern,
146
+ schema
147
+ ])));
148
+ patternProperties += `.meta({ __jsonSchema: { patternProperties: ${patternPropsJson} } })`;
149
+ }
150
+ }
151
+ // Check if there will be an .and() call that adds properties from oneOf/anyOf/allOf
152
+ // In that case, we should NOT use .strict() because it will reject the additional keys
153
+ // before the union gets a chance to validate them.
154
+ const hasCompositionKeywords = its.an.anyOf(objectSchema) || its.a.oneOf(objectSchema) || its.an.allOf(objectSchema);
155
+ let output = properties
156
+ ? patternProperties
157
+ ? properties + patternProperties
158
+ : additionalProperties
159
+ ? additionalProperties === "z.never()"
160
+ // Don't use .strict() if there are composition keywords that add properties
161
+ ? hasCompositionKeywords
162
+ ? properties
163
+ : properties + ".strict()"
164
+ : properties + `.catchall(${additionalProperties})`
165
+ : properties
166
+ : patternProperties
167
+ ? patternProperties
168
+ : additionalProperties
169
+ ? `z.record(z.string(), ${additionalProperties})`
170
+ : `z.record(z.string(), ${anyOrUnknown(refs)})`;
171
+ if (unevaluated === false && properties && !hasCompositionKeywords) {
172
+ output += ".strict()";
173
+ }
174
+ else if (unevaluated && typeof unevaluated !== 'boolean') {
175
+ const unevaluatedSchema = parseSchema(unevaluated, {
176
+ ...refs,
177
+ path: [...refs.path, "unevaluatedProperties"],
178
+ });
179
+ const knownKeys = objectSchema.properties ? Object.keys(objectSchema.properties) : [];
180
+ const patterns = objectSchema.patternProperties
181
+ ? Object.keys(objectSchema.patternProperties).map((p) => new RegExp(p))
182
+ : [];
183
+ output += `.superRefine((value, ctx) => {
184
+ for (const key in value) {
185
+ const isKnown = ${JSON.stringify(knownKeys)}.includes(key);
186
+ const matchesPattern = ${patterns.length ? "[" + patterns.map((r) => r.toString()).join(",") + "]" : "[]"}.some((r) => r.test(key));
187
+ if (!isKnown && !matchesPattern) {
188
+ const result = ${unevaluatedSchema}.safeParse(value[key]);
189
+ if (!result.success) {
190
+ ctx.addIssue({ code: "custom", path: [key], message: "Invalid unevaluated property", params: { issues: result.error.issues } });
191
+ }
192
+ }
193
+ }
194
+ })`;
195
+ }
196
+ if (its.an.anyOf(objectSchema)) {
197
+ output += `.and(${parseAnyOf({
198
+ ...objectSchema,
199
+ anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
200
+ !x.type &&
201
+ (x.properties || x.additionalProperties || x.patternProperties)
202
+ ? { ...x, type: "object" }
203
+ : x),
204
+ }, refs)})`;
205
+ }
206
+ if (its.a.oneOf(objectSchema)) {
207
+ output += `.and(${parseOneOf({
208
+ ...objectSchema,
209
+ oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
210
+ !x.type &&
211
+ (x.properties || x.additionalProperties || x.patternProperties)
212
+ ? { ...x, type: "object" }
213
+ : x),
214
+ }, refs)})`;
215
+ }
216
+ if (its.an.allOf(objectSchema)) {
217
+ output += `.and(${parseAllOf({
218
+ ...objectSchema,
219
+ allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
220
+ !x.type &&
221
+ (x.properties || x.additionalProperties || x.patternProperties)
222
+ ? { ...x, type: "object" }
223
+ : x),
224
+ }, refs)})`;
225
+ }
226
+ // Handle if/then/else conditionals on object schemas
227
+ if (its.a.conditional(objectSchema)) {
228
+ output += `.and(${parseIfThenElse(objectSchema, refs)})`;
229
+ }
230
+ // propertyNames
231
+ if (objectSchema.propertyNames) {
232
+ const normalizedPropNames = typeof objectSchema.propertyNames === "object" &&
233
+ !objectSchema.propertyNames.type &&
234
+ objectSchema.propertyNames.pattern
235
+ ? { ...objectSchema.propertyNames, type: "string" }
236
+ : objectSchema.propertyNames;
237
+ const propNameSchema = parseSchema(normalizedPropNames, {
238
+ ...refs,
239
+ path: [...refs.path, "propertyNames"],
240
+ });
241
+ output += `.superRefine((value, ctx) => {
242
+ for (const key in value) {
243
+ const result = ${propNameSchema}.safeParse(key);
244
+ if (!result.success) {
245
+ ctx.addIssue({
246
+ path: [key],
247
+ code: "custom",
248
+ message: "Invalid property name",
249
+ params: { issues: result.error.issues }
250
+ });
251
+ }
252
+ }
253
+ })`;
254
+ }
255
+ // dependentSchemas
256
+ if (objectSchema.dependentSchemas && typeof objectSchema.dependentSchemas === "object") {
257
+ const entries = Object.entries(objectSchema.dependentSchemas);
258
+ if (entries.length) {
259
+ output += `.superRefine((obj, ctx) => {
260
+ ${entries
261
+ .map(([key, schema], idx) => {
262
+ const parsed = parseSchema(schema, { ...refs, path: [...refs.path, "dependentSchemas", key] });
263
+ return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(key)})) {
264
+ const result = ${parsed}.safeParse(obj);
265
+ if (!result.success) {
266
+ ctx.addIssue({ code: "custom", message: "Dependent schema failed", path: [], params: { issues: result.error.issues } });
267
+ }
268
+ }`;
269
+ })
270
+ .join("\n ")}
271
+ })`;
272
+ }
273
+ }
274
+ return output;
275
+ }
276
+ const shouldUseGetter = (parsed, refs) => {
277
+ if (!parsed)
278
+ return false;
279
+ if (refs.currentSchemaName && parsed.includes(refs.currentSchemaName)) {
280
+ return true;
281
+ }
282
+ return Boolean(refs.inProgress && refs.inProgress.has(parsed));
283
+ };
@@ -0,0 +1,49 @@
1
+ import { parseSchema } from "./parseSchema.js";
2
+ import { anyOrUnknown } from "../utils/anyOrUnknown.js";
3
+ export const parseOneOf = (schema, refs) => {
4
+ if (!schema.oneOf.length) {
5
+ return anyOrUnknown(refs);
6
+ }
7
+ if (schema.oneOf.length === 1) {
8
+ return parseSchema(schema.oneOf[0], {
9
+ ...refs,
10
+ path: [...refs.path, "oneOf", 0],
11
+ });
12
+ }
13
+ // Generate parsed schemas for each oneOf option
14
+ const parsedSchemas = schema.oneOf.map((s, i) => parseSchema(s, {
15
+ ...refs,
16
+ path: [...refs.path, "oneOf", i],
17
+ }));
18
+ // JSON Schema oneOf = exactly one must match (exclusive OR)
19
+ // Zod union = at least one must match (inclusive OR)
20
+ //
21
+ // By default, use simple z.union() which provides "at least one must match".
22
+ // This is more practical for most use cases, as strict oneOf enforcement
23
+ // often fails when schemas have overlapping base types.
24
+ //
25
+ // If strictOneOf is enabled, add superRefine to enforce "exactly one" constraint.
26
+ if (refs.strictOneOf) {
27
+ return `z.union([${parsedSchemas.join(", ")}]).superRefine((x, ctx) => {
28
+ const schemas = [${parsedSchemas.join(", ")}];
29
+ const errors = schemas.reduce<z.ZodError[]>(
30
+ (errors, schema) =>
31
+ ((result) =>
32
+ result.error ? [...errors, result.error] : errors)(
33
+ schema.safeParse(x),
34
+ ),
35
+ [],
36
+ );
37
+ if (schemas.length - errors.length !== 1) {
38
+ ctx.addIssue({
39
+ path: [],
40
+ code: "invalid_union",
41
+ errors: errors.map(e => e.issues),
42
+ message: "Invalid input: Should pass single schema",
43
+ });
44
+ }
45
+ })`;
46
+ }
47
+ // Default: use simple z.union() (at least one must match)
48
+ return `z.union([${parsedSchemas.join(", ")}])`;
49
+ };
@@ -0,0 +1,281 @@
1
+ import { parseAnyOf } from "./parseAnyOf.js";
2
+ import { parseBoolean } from "./parseBoolean.js";
3
+ import { parseDefault } from "./parseDefault.js";
4
+ import { parseMultipleType } from "./parseMultipleType.js";
5
+ import { parseNot } from "./parseNot.js";
6
+ import { parseNull } from "./parseNull.js";
7
+ import { parseAllOf } from "./parseAllOf.js";
8
+ import { parseArray } from "./parseArray.js";
9
+ import { parseConst } from "./parseConst.js";
10
+ import { parseEnum } from "./parseEnum.js";
11
+ import { parseIfThenElse } from "./parseIfThenElse.js";
12
+ import { parseNumber } from "./parseNumber.js";
13
+ import { parseObject } from "./parseObject.js";
14
+ import { parseString } from "./parseString.js";
15
+ import { parseOneOf } from "./parseOneOf.js";
16
+ import { parseSimpleDiscriminatedOneOf } from "./parseSimpleDiscriminatedOneOf.js";
17
+ import { parseNullable } from "./parseNullable.js";
18
+ import { anyOrUnknown } from "../utils/anyOrUnknown.js";
19
+ export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
20
+ // Ensure ref bookkeeping exists so $ref declarations and getter-based recursion work
21
+ refs.root = refs.root ?? schema;
22
+ refs.declarations = refs.declarations ?? new Map();
23
+ refs.inProgress = refs.inProgress ?? new Set();
24
+ refs.refNameByPointer = refs.refNameByPointer ?? new Map();
25
+ refs.usedNames = refs.usedNames ?? new Set();
26
+ if (typeof schema !== "object")
27
+ return schema ? anyOrUnknown(refs) : "z.never()";
28
+ if (refs.parserOverride) {
29
+ const custom = refs.parserOverride(schema, refs);
30
+ if (typeof custom === "string") {
31
+ return custom;
32
+ }
33
+ }
34
+ let seen = refs.seen.get(schema);
35
+ if (seen) {
36
+ if (seen.r !== undefined) {
37
+ return seen.r;
38
+ }
39
+ if (refs.depth === undefined || seen.n >= refs.depth) {
40
+ return anyOrUnknown(refs);
41
+ }
42
+ seen.n += 1;
43
+ }
44
+ else {
45
+ seen = { r: undefined, n: 0 };
46
+ refs.seen.set(schema, seen);
47
+ }
48
+ if (its.a.ref(schema)) {
49
+ const parsedRef = parseRef(schema, refs);
50
+ seen.r = parsedRef;
51
+ return parsedRef;
52
+ }
53
+ let parsed = selectParser(schema, refs);
54
+ if (!blockMeta) {
55
+ if (!refs.withoutDescribes) {
56
+ parsed = addDescribes(schema, parsed, refs);
57
+ }
58
+ if (!refs.withoutDefaults) {
59
+ parsed = addDefaults(schema, parsed);
60
+ }
61
+ parsed = addAnnotations(schema, parsed);
62
+ }
63
+ seen.r = parsed;
64
+ return parsed;
65
+ };
66
+ const parseRef = (schema, refs) => {
67
+ const resolved = resolveRef(refs.root, schema.$ref);
68
+ if (!resolved) {
69
+ return anyOrUnknown(refs);
70
+ }
71
+ const { schema: target, path } = resolved;
72
+ const refName = getOrCreateRefName(schema.$ref, path, refs);
73
+ if (!refs.declarations.has(refName) && !refs.inProgress.has(refName)) {
74
+ refs.inProgress.add(refName);
75
+ const declaration = parseSchema(target, {
76
+ ...refs,
77
+ path,
78
+ currentSchemaName: refName,
79
+ root: refs.root,
80
+ });
81
+ refs.inProgress.delete(refName);
82
+ refs.declarations.set(refName, declaration);
83
+ }
84
+ return refName;
85
+ };
86
+ const addDescribes = (schema, parsed, refs) => {
87
+ // Use .meta() for richer metadata when withMeta is enabled
88
+ if (refs?.withMeta) {
89
+ const meta = {};
90
+ if (schema.$id)
91
+ meta.id = schema.$id;
92
+ if (schema.title)
93
+ meta.title = schema.title;
94
+ if (schema.description)
95
+ meta.description = schema.description;
96
+ if (schema.examples)
97
+ meta.examples = schema.examples;
98
+ if (schema.deprecated)
99
+ meta.deprecated = schema.deprecated;
100
+ if (Object.keys(meta).length > 0) {
101
+ parsed += `.meta(${JSON.stringify(meta)})`;
102
+ }
103
+ }
104
+ else if (schema.description) {
105
+ parsed += `.describe(${JSON.stringify(schema.description)})`;
106
+ }
107
+ return parsed;
108
+ };
109
+ const resolveRef = (root, ref) => {
110
+ if (!root || !ref.startsWith("#/"))
111
+ return undefined;
112
+ const rawSegments = ref
113
+ .slice(2)
114
+ .split("/")
115
+ .filter((segment) => segment.length > 0)
116
+ .map(decodePointerSegment);
117
+ let current = root;
118
+ for (const segment of rawSegments) {
119
+ if (typeof current !== "object" || current === null)
120
+ return undefined;
121
+ current = current[segment];
122
+ }
123
+ return { schema: current, path: rawSegments };
124
+ };
125
+ const decodePointerSegment = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
126
+ const getOrCreateRefName = (pointer, path, refs) => {
127
+ if (refs.refNameByPointer?.has(pointer)) {
128
+ return refs.refNameByPointer.get(pointer);
129
+ }
130
+ const preferred = buildNameFromPath(path, refs.usedNames);
131
+ refs.refNameByPointer?.set(pointer, preferred);
132
+ refs.usedNames?.add(preferred);
133
+ return preferred;
134
+ };
135
+ const buildNameFromPath = (path, used) => {
136
+ const filtered = path.filter((segment) => segment !== "$defs" && segment !== "definitions" && segment !== "properties");
137
+ const base = filtered.length
138
+ ? filtered
139
+ .map((segment) => typeof segment === "number"
140
+ ? `Ref${segment}`
141
+ : segment
142
+ .replace(/[^a-zA-Z0-9_$]/g, " ")
143
+ .split(" ")
144
+ .filter(Boolean)
145
+ .map(capitalize)
146
+ .join(""))
147
+ .join("")
148
+ : "Ref";
149
+ const sanitized = sanitizeIdentifier(base || "Ref");
150
+ if (!used || !used.has(sanitized))
151
+ return sanitized;
152
+ let counter = 2;
153
+ let candidate = `${sanitized}${counter}`;
154
+ while (used.has(candidate)) {
155
+ counter += 1;
156
+ candidate = `${sanitized}${counter}`;
157
+ }
158
+ return candidate;
159
+ };
160
+ const sanitizeIdentifier = (value) => {
161
+ const cleaned = value.replace(/^[^a-zA-Z_$]+/, "").replace(/[^a-zA-Z0-9_$]/g, "");
162
+ return cleaned || "Ref";
163
+ };
164
+ const capitalize = (value) => value.length ? value[0].toUpperCase() + value.slice(1) : value;
165
+ const addDefaults = (schema, parsed) => {
166
+ if (schema.default !== undefined) {
167
+ parsed += `.default(${JSON.stringify(schema.default)})`;
168
+ }
169
+ return parsed;
170
+ };
171
+ const addAnnotations = (schema, parsed) => {
172
+ if (schema.readOnly) {
173
+ parsed += ".readonly()";
174
+ }
175
+ return parsed;
176
+ };
177
+ const selectParser = (schema, refs) => {
178
+ if (its.a.nullable(schema)) {
179
+ return parseNullable(schema, refs);
180
+ }
181
+ else if (its.an.object(schema)) {
182
+ return parseObject(schema, refs);
183
+ }
184
+ else if (its.an.array(schema)) {
185
+ return parseArray(schema, refs);
186
+ }
187
+ else if (its.an.anyOf(schema)) {
188
+ return parseAnyOf(schema, refs);
189
+ }
190
+ else if (its.an.allOf(schema)) {
191
+ return parseAllOf(schema, refs);
192
+ }
193
+ else if (its.a.simpleDiscriminatedOneOf(schema)) {
194
+ return parseSimpleDiscriminatedOneOf(schema, refs);
195
+ }
196
+ else if (its.a.oneOf(schema)) {
197
+ return parseOneOf(schema, refs);
198
+ }
199
+ else if (its.a.not(schema)) {
200
+ return parseNot(schema, refs);
201
+ }
202
+ else if (its.an.enum(schema)) {
203
+ return parseEnum(schema); //<-- needs to come before primitives
204
+ }
205
+ else if (its.a.const(schema)) {
206
+ return parseConst(schema);
207
+ }
208
+ else if (its.a.multipleType(schema)) {
209
+ return parseMultipleType(schema, refs);
210
+ }
211
+ else if (its.a.primitive(schema, "string")) {
212
+ return parseString(schema);
213
+ }
214
+ else if (its.a.primitive(schema, "number") ||
215
+ its.a.primitive(schema, "integer")) {
216
+ return parseNumber(schema);
217
+ }
218
+ else if (its.a.primitive(schema, "boolean")) {
219
+ return parseBoolean(schema);
220
+ }
221
+ else if (its.a.primitive(schema, "null")) {
222
+ return parseNull(schema);
223
+ }
224
+ else if (its.a.conditional(schema)) {
225
+ return parseIfThenElse(schema, refs);
226
+ }
227
+ else {
228
+ return parseDefault(schema, refs);
229
+ }
230
+ };
231
+ export const its = {
232
+ an: {
233
+ object: (x) => x.type === "object",
234
+ array: (x) => x.type === "array",
235
+ anyOf: (x) => x.anyOf !== undefined,
236
+ allOf: (x) => x.allOf !== undefined,
237
+ enum: (x) => x.enum !== undefined,
238
+ },
239
+ a: {
240
+ nullable: (x) => x.nullable === true,
241
+ multipleType: (x) => Array.isArray(x.type),
242
+ not: (x) => x.not !== undefined,
243
+ ref: (x) => typeof x.$ref === "string",
244
+ const: (x) => x.const !== undefined,
245
+ primitive: (x, p) => x.type === p,
246
+ conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
247
+ simpleDiscriminatedOneOf: (x) => {
248
+ if (!x.oneOf ||
249
+ !Array.isArray(x.oneOf) ||
250
+ x.oneOf.length === 0 ||
251
+ !x.discriminator ||
252
+ typeof x.discriminator !== "object" ||
253
+ !("propertyName" in x.discriminator) ||
254
+ typeof x.discriminator.propertyName !== "string") {
255
+ return false;
256
+ }
257
+ const discriminatorProp = x.discriminator.propertyName;
258
+ return x.oneOf.every((schema) => {
259
+ if (!schema ||
260
+ typeof schema !== "object" ||
261
+ schema.type !== "object" ||
262
+ !schema.properties ||
263
+ typeof schema.properties !== "object" ||
264
+ !(discriminatorProp in schema.properties)) {
265
+ return false;
266
+ }
267
+ const property = schema.properties[discriminatorProp];
268
+ return (property &&
269
+ typeof property === "object" &&
270
+ (property.type === undefined || property.type === "string") &&
271
+ // Ensure discriminator has a constant value (const or single-value enum)
272
+ (property.const !== undefined ||
273
+ (property.enum && Array.isArray(property.enum) && property.enum.length === 1)) &&
274
+ // Ensure discriminator property is required
275
+ Array.isArray(schema.required) &&
276
+ schema.required.includes(discriminatorProp));
277
+ });
278
+ },
279
+ oneOf: (x) => x.oneOf !== undefined,
280
+ },
281
+ };
@@ -0,0 +1,25 @@
1
+ import { parseSchema } from "./parseSchema.js";
2
+ import { anyOrUnknown } from "../utils/anyOrUnknown.js";
3
+ export const parseSimpleDiscriminatedOneOf = (schema, refs) => {
4
+ const discriminator = schema.discriminator.propertyName;
5
+ const entries = schema.oneOf.map((option, i) => {
6
+ const opt = option;
7
+ const discriminatorSchema = opt.properties[discriminator];
8
+ const value = discriminatorSchema.const ??
9
+ (discriminatorSchema.enum && discriminatorSchema.enum[0]);
10
+ const parsed = parseSchema(option, {
11
+ ...refs,
12
+ path: [...refs.path, "oneOf", i],
13
+ });
14
+ const key = typeof value === "string" ? JSON.stringify(value) : JSON.stringify(String(value));
15
+ return `${key}: ${parsed}`;
16
+ });
17
+ return schema.oneOf.length
18
+ ? schema.oneOf.length === 1
19
+ ? parseSchema(schema.oneOf[0], {
20
+ ...refs,
21
+ path: [...refs.path, "oneOf", 0],
22
+ })
23
+ : `z.discriminatedUnion("${discriminator}", { ${entries.join(", ")} })`
24
+ : anyOrUnknown(refs);
25
+ };