zod 4.2.0-canary.20251213T203150 → 4.2.0-canary.20251215T071855

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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/v4/classic/external.ts +2 -1
  3. package/src/v4/classic/from-json-schema.ts +527 -0
  4. package/src/v4/classic/schemas.ts +43 -0
  5. package/src/v4/classic/tests/from-json-schema.test.ts +537 -0
  6. package/src/v4/classic/tests/record.test.ts +37 -0
  7. package/src/v4/classic/tests/union.test.ts +38 -0
  8. package/src/v4/core/api.ts +15 -0
  9. package/src/v4/core/errors.ts +12 -1
  10. package/src/v4/core/json-schema-processors.ts +5 -5
  11. package/src/v4/core/schemas.ts +99 -10
  12. package/src/v4/mini/external.ts +1 -1
  13. package/src/v4/mini/schemas.ts +39 -0
  14. package/v4/classic/external.cjs +5 -2
  15. package/v4/classic/external.d.cts +3 -1
  16. package/v4/classic/external.d.ts +3 -1
  17. package/v4/classic/external.js +3 -1
  18. package/v4/classic/from-json-schema.cjs +503 -0
  19. package/v4/classic/from-json-schema.d.cts +10 -0
  20. package/v4/classic/from-json-schema.d.ts +10 -0
  21. package/v4/classic/from-json-schema.js +477 -0
  22. package/v4/classic/schemas.cjs +30 -2
  23. package/v4/classic/schemas.d.cts +10 -0
  24. package/v4/classic/schemas.d.ts +10 -0
  25. package/v4/classic/schemas.js +26 -0
  26. package/v4/core/api.cjs +9 -0
  27. package/v4/core/api.d.cts +2 -0
  28. package/v4/core/api.d.ts +2 -0
  29. package/v4/core/api.js +8 -0
  30. package/v4/core/errors.d.cts +10 -1
  31. package/v4/core/errors.d.ts +10 -1
  32. package/v4/core/json-schema-processors.cjs +5 -5
  33. package/v4/core/json-schema-processors.js +5 -5
  34. package/v4/core/schemas.cjs +76 -11
  35. package/v4/core/schemas.d.cts +9 -0
  36. package/v4/core/schemas.d.ts +9 -0
  37. package/v4/core/schemas.js +74 -9
  38. package/v4/mini/external.cjs +3 -2
  39. package/v4/mini/external.d.cts +2 -1
  40. package/v4/mini/external.d.ts +2 -1
  41. package/v4/mini/external.js +2 -1
  42. package/v4/mini/schemas.cjs +28 -2
  43. package/v4/mini/schemas.d.cts +8 -0
  44. package/v4/mini/schemas.d.ts +8 -0
  45. package/v4/mini/schemas.js +24 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.2.0-canary.20251213T203150",
3
+ "version": "4.2.0-canary.20251215T071855",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -25,11 +25,12 @@ export {
25
25
  prettifyError,
26
26
  formatError,
27
27
  flattenError,
28
- toJSONSchema,
29
28
  TimePrecision,
30
29
  util,
31
30
  NEVER,
32
31
  } from "../core/index.js";
32
+ export { toJSONSchema } from "../core/json-schema-processors.js";
33
+ export { fromJSONSchema } from "./from-json-schema.js";
33
34
 
34
35
  export * as locales from "../locales/index.js";
35
36
 
@@ -0,0 +1,527 @@
1
+ import type * as JSONSchema from "../core/json-schema.js";
2
+ import * as z from "../index.js";
3
+ import type { ZodNumber, ZodString, ZodType } from "./schemas.js";
4
+
5
+ type JSONSchemaVersion = "draft-2020-12" | "draft-7" | "draft-4" | "openapi-3.0";
6
+
7
+ interface FromJSONSchemaParams {
8
+ defaultTarget?: JSONSchemaVersion;
9
+ }
10
+
11
+ interface ConversionContext {
12
+ version: JSONSchemaVersion;
13
+ defs: Record<string, JSONSchema.JSONSchema>;
14
+ refs: Map<string, ZodType>;
15
+ processing: Set<string>;
16
+ rootSchema: JSONSchema.JSONSchema;
17
+ }
18
+
19
+ function detectVersion(schema: JSONSchema.JSONSchema, defaultTarget?: JSONSchemaVersion): JSONSchemaVersion {
20
+ const $schema = schema.$schema;
21
+
22
+ if ($schema === "https://json-schema.org/draft/2020-12/schema") {
23
+ return "draft-2020-12";
24
+ }
25
+ if ($schema === "http://json-schema.org/draft-07/schema#") {
26
+ return "draft-7";
27
+ }
28
+ if ($schema === "http://json-schema.org/draft-04/schema#") {
29
+ return "draft-4";
30
+ }
31
+
32
+ // Use defaultTarget if provided, otherwise default to draft-2020-12
33
+ return defaultTarget ?? "draft-2020-12";
34
+ }
35
+
36
+ function resolveRef(ref: string, ctx: ConversionContext): JSONSchema.JSONSchema {
37
+ if (!ref.startsWith("#")) {
38
+ throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
39
+ }
40
+
41
+ const path = ref.slice(1).split("/").filter(Boolean);
42
+
43
+ // Handle root reference "#"
44
+ if (path.length === 0) {
45
+ return ctx.rootSchema;
46
+ }
47
+
48
+ const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
49
+
50
+ if (path[0] === defsKey) {
51
+ const key = path[1];
52
+ if (!key || !ctx.defs[key]) {
53
+ throw new Error(`Reference not found: ${ref}`);
54
+ }
55
+ return ctx.defs[key]!;
56
+ }
57
+
58
+ throw new Error(`Reference not found: ${ref}`);
59
+ }
60
+
61
+ function convertBaseSchema(schema: JSONSchema.JSONSchema, ctx: ConversionContext): ZodType {
62
+ // Handle unsupported features
63
+ if (schema.not !== undefined) {
64
+ // Special case: { not: {} } represents never
65
+ if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
66
+ return z.never();
67
+ }
68
+ throw new Error("not is not supported in Zod (except { not: {} } for never)");
69
+ }
70
+ if (schema.unevaluatedItems !== undefined) {
71
+ throw new Error("unevaluatedItems is not supported");
72
+ }
73
+ if (schema.unevaluatedProperties !== undefined) {
74
+ throw new Error("unevaluatedProperties is not supported");
75
+ }
76
+ if (schema.if !== undefined || schema.then !== undefined || schema.else !== undefined) {
77
+ throw new Error("Conditional schemas (if/then/else) are not supported");
78
+ }
79
+ if (schema.dependentSchemas !== undefined || schema.dependentRequired !== undefined) {
80
+ throw new Error("dependentSchemas and dependentRequired are not supported");
81
+ }
82
+
83
+ // Handle $ref
84
+ if (schema.$ref) {
85
+ const refPath = schema.$ref;
86
+ if (ctx.refs.has(refPath)) {
87
+ return ctx.refs.get(refPath)!;
88
+ }
89
+
90
+ if (ctx.processing.has(refPath)) {
91
+ // Circular reference - use lazy
92
+ return z.lazy(() => {
93
+ if (!ctx.refs.has(refPath)) {
94
+ throw new Error(`Circular reference not resolved: ${refPath}`);
95
+ }
96
+ return ctx.refs.get(refPath)!;
97
+ });
98
+ }
99
+
100
+ ctx.processing.add(refPath);
101
+ const resolved = resolveRef(refPath, ctx);
102
+ const zodSchema = convertSchema(resolved, ctx);
103
+ ctx.refs.set(refPath, zodSchema);
104
+ ctx.processing.delete(refPath);
105
+ return zodSchema;
106
+ }
107
+
108
+ // Handle enum
109
+ if (schema.enum !== undefined) {
110
+ const enumValues = schema.enum;
111
+
112
+ // Special case: OpenAPI 3.0 null representation { type: "string", nullable: true, enum: [null] }
113
+ if (
114
+ ctx.version === "openapi-3.0" &&
115
+ schema.nullable === true &&
116
+ enumValues.length === 1 &&
117
+ enumValues[0] === null
118
+ ) {
119
+ return z.null();
120
+ }
121
+
122
+ if (enumValues.length === 0) {
123
+ return z.never();
124
+ }
125
+ if (enumValues.length === 1) {
126
+ return z.literal(enumValues[0]!);
127
+ }
128
+ // Check if all values are strings
129
+ if (enumValues.every((v) => typeof v === "string")) {
130
+ return z.enum(enumValues as [string, ...string[]]);
131
+ }
132
+ // Mixed types - use union of literals
133
+ const literalSchemas = enumValues.map((v) => z.literal(v));
134
+ if (literalSchemas.length < 2) {
135
+ return literalSchemas[0]!;
136
+ }
137
+ return z.union([literalSchemas[0]!, literalSchemas[1]!, ...literalSchemas.slice(2)] as [
138
+ ZodType,
139
+ ZodType,
140
+ ...ZodType[],
141
+ ]);
142
+ }
143
+
144
+ // Handle const
145
+ if (schema.const !== undefined) {
146
+ return z.literal(schema.const);
147
+ }
148
+
149
+ // Handle type
150
+ const type = schema.type;
151
+
152
+ if (Array.isArray(type)) {
153
+ // Expand type array into anyOf union
154
+ const typeSchemas = type.map((t) => {
155
+ const typeSchema: JSONSchema.JSONSchema = { ...schema, type: t };
156
+ return convertBaseSchema(typeSchema, ctx);
157
+ });
158
+ if (typeSchemas.length === 0) {
159
+ return z.never();
160
+ }
161
+ if (typeSchemas.length === 1) {
162
+ return typeSchemas[0]!;
163
+ }
164
+ return z.union(typeSchemas as [ZodType, ZodType, ...ZodType[]]);
165
+ }
166
+
167
+ if (!type) {
168
+ // No type specified - empty schema (any)
169
+ return z.any();
170
+ }
171
+
172
+ let zodSchema: ZodType;
173
+
174
+ switch (type) {
175
+ case "string": {
176
+ let stringSchema: ZodString = z.string();
177
+
178
+ // Apply format using .check() with Zod format functions
179
+ if (schema.format) {
180
+ const format = schema.format;
181
+ // Map common formats to Zod check functions
182
+ if (format === "email") {
183
+ stringSchema = stringSchema.check(z.email());
184
+ } else if (format === "uri" || format === "uri-reference") {
185
+ stringSchema = stringSchema.check(z.url());
186
+ } else if (format === "uuid" || format === "guid") {
187
+ stringSchema = stringSchema.check(z.uuid());
188
+ } else if (format === "date-time") {
189
+ stringSchema = stringSchema.check(z.iso.datetime());
190
+ } else if (format === "date") {
191
+ stringSchema = stringSchema.check(z.iso.date());
192
+ } else if (format === "time") {
193
+ stringSchema = stringSchema.check(z.iso.time());
194
+ } else if (format === "duration") {
195
+ stringSchema = stringSchema.check(z.iso.duration());
196
+ } else if (format === "ipv4") {
197
+ stringSchema = stringSchema.check(z.ipv4());
198
+ } else if (format === "ipv6") {
199
+ stringSchema = stringSchema.check(z.ipv6());
200
+ } else if (format === "mac") {
201
+ stringSchema = stringSchema.check(z.mac());
202
+ } else if (format === "cidr") {
203
+ stringSchema = stringSchema.check(z.cidrv4());
204
+ } else if (format === "cidr-v6") {
205
+ stringSchema = stringSchema.check(z.cidrv6());
206
+ } else if (format === "base64") {
207
+ stringSchema = stringSchema.check(z.base64());
208
+ } else if (format === "base64url") {
209
+ stringSchema = stringSchema.check(z.base64url());
210
+ } else if (format === "e164") {
211
+ stringSchema = stringSchema.check(z.e164());
212
+ } else if (format === "jwt") {
213
+ stringSchema = stringSchema.check(z.jwt());
214
+ } else if (format === "emoji") {
215
+ stringSchema = stringSchema.check(z.emoji());
216
+ } else if (format === "nanoid") {
217
+ stringSchema = stringSchema.check(z.nanoid());
218
+ } else if (format === "cuid") {
219
+ stringSchema = stringSchema.check(z.cuid());
220
+ } else if (format === "cuid2") {
221
+ stringSchema = stringSchema.check(z.cuid2());
222
+ } else if (format === "ulid") {
223
+ stringSchema = stringSchema.check(z.ulid());
224
+ } else if (format === "xid") {
225
+ stringSchema = stringSchema.check(z.xid());
226
+ } else if (format === "ksuid") {
227
+ stringSchema = stringSchema.check(z.ksuid());
228
+ }
229
+ // Note: json-string format is not currently supported by Zod
230
+ // Custom formats are ignored - keep as plain string
231
+ }
232
+
233
+ // Apply constraints
234
+ if (typeof schema.minLength === "number") {
235
+ stringSchema = stringSchema.min(schema.minLength);
236
+ }
237
+ if (typeof schema.maxLength === "number") {
238
+ stringSchema = stringSchema.max(schema.maxLength);
239
+ }
240
+ if (schema.pattern) {
241
+ // JSON Schema patterns are not implicitly anchored (match anywhere in string)
242
+ stringSchema = stringSchema.regex(new RegExp(schema.pattern));
243
+ }
244
+
245
+ zodSchema = stringSchema;
246
+ break;
247
+ }
248
+
249
+ case "number":
250
+ case "integer": {
251
+ let numberSchema: ZodNumber = type === "integer" ? z.number().int() : z.number();
252
+
253
+ // Apply constraints
254
+ if (typeof schema.minimum === "number") {
255
+ numberSchema = numberSchema.min(schema.minimum);
256
+ }
257
+ if (typeof schema.maximum === "number") {
258
+ numberSchema = numberSchema.max(schema.maximum);
259
+ }
260
+ if (typeof schema.exclusiveMinimum === "number") {
261
+ numberSchema = numberSchema.gt(schema.exclusiveMinimum);
262
+ } else if (schema.exclusiveMinimum === true && typeof schema.minimum === "number") {
263
+ numberSchema = numberSchema.gt(schema.minimum);
264
+ }
265
+ if (typeof schema.exclusiveMaximum === "number") {
266
+ numberSchema = numberSchema.lt(schema.exclusiveMaximum);
267
+ } else if (schema.exclusiveMaximum === true && typeof schema.maximum === "number") {
268
+ numberSchema = numberSchema.lt(schema.maximum);
269
+ }
270
+ if (typeof schema.multipleOf === "number") {
271
+ numberSchema = numberSchema.multipleOf(schema.multipleOf);
272
+ }
273
+
274
+ zodSchema = numberSchema;
275
+ break;
276
+ }
277
+
278
+ case "boolean": {
279
+ zodSchema = z.boolean();
280
+ break;
281
+ }
282
+
283
+ case "null": {
284
+ zodSchema = z.null();
285
+ break;
286
+ }
287
+
288
+ case "object": {
289
+ const shape: Record<string, ZodType> = {};
290
+ const properties = schema.properties || {};
291
+ const requiredSet = new Set(schema.required || []);
292
+
293
+ // Convert properties - mark optional ones
294
+ for (const [key, propSchema] of Object.entries(properties)) {
295
+ const propZodSchema = convertSchema(propSchema as JSONSchema.JSONSchema, ctx);
296
+ // If not in required array, make it optional
297
+ shape[key] = requiredSet.has(key) ? propZodSchema : propZodSchema.optional();
298
+ }
299
+
300
+ // Handle propertyNames
301
+ if (schema.propertyNames) {
302
+ const keySchema = convertSchema(schema.propertyNames, ctx) as ZodString;
303
+ const valueSchema =
304
+ schema.additionalProperties && typeof schema.additionalProperties === "object"
305
+ ? convertSchema(schema.additionalProperties as JSONSchema.JSONSchema, ctx)
306
+ : z.any();
307
+
308
+ // Case A: No properties (pure record)
309
+ if (Object.keys(shape).length === 0) {
310
+ zodSchema = z.record(keySchema, valueSchema);
311
+ break;
312
+ }
313
+
314
+ // Case B: With properties (intersection of object and looseRecord)
315
+ const objectSchema = z.object(shape).passthrough();
316
+ const recordSchema = z.looseRecord(keySchema, valueSchema);
317
+ zodSchema = z.intersection(objectSchema, recordSchema);
318
+ break;
319
+ }
320
+
321
+ // Handle patternProperties
322
+ if (schema.patternProperties) {
323
+ // patternProperties: keys matching pattern must satisfy corresponding schema
324
+ // Use loose records so non-matching keys pass through
325
+ const patternProps = schema.patternProperties;
326
+ const patternKeys = Object.keys(patternProps);
327
+ const looseRecords: ZodType[] = [];
328
+
329
+ for (const pattern of patternKeys) {
330
+ const patternValue = convertSchema(patternProps[pattern] as JSONSchema.JSONSchema, ctx);
331
+ const keySchema = z.string().regex(new RegExp(pattern));
332
+ looseRecords.push(z.looseRecord(keySchema, patternValue));
333
+ }
334
+
335
+ // Build intersection: object schema + all pattern property records
336
+ const schemasToIntersect: ZodType[] = [];
337
+ if (Object.keys(shape).length > 0) {
338
+ // Use passthrough so patternProperties can validate additional keys
339
+ schemasToIntersect.push(z.object(shape).passthrough());
340
+ }
341
+ schemasToIntersect.push(...looseRecords);
342
+
343
+ if (schemasToIntersect.length === 0) {
344
+ zodSchema = z.object({}).passthrough();
345
+ } else if (schemasToIntersect.length === 1) {
346
+ zodSchema = schemasToIntersect[0]!;
347
+ } else {
348
+ // Chain intersections: (A & B) & C & D ...
349
+ let result = z.intersection(schemasToIntersect[0]!, schemasToIntersect[1]!);
350
+ for (let i = 2; i < schemasToIntersect.length; i++) {
351
+ result = z.intersection(result, schemasToIntersect[i]!);
352
+ }
353
+ zodSchema = result;
354
+ }
355
+ break;
356
+ }
357
+
358
+ // Handle additionalProperties
359
+ // In JSON Schema, additionalProperties defaults to true (allow any extra properties)
360
+ // In Zod, objects strip unknown keys by default, so we need to handle this explicitly
361
+ const objectSchema = z.object(shape);
362
+ if (schema.additionalProperties === false) {
363
+ // Strict mode - no extra properties allowed
364
+ zodSchema = objectSchema.strict();
365
+ } else if (typeof schema.additionalProperties === "object") {
366
+ // Extra properties must match the specified schema
367
+ zodSchema = objectSchema.catchall(convertSchema(schema.additionalProperties as JSONSchema.JSONSchema, ctx));
368
+ } else {
369
+ // additionalProperties is true or undefined - allow any extra properties (passthrough)
370
+ zodSchema = objectSchema.passthrough();
371
+ }
372
+ break;
373
+ }
374
+
375
+ case "array": {
376
+ // TODO: uniqueItems is not supported
377
+ // TODO: contains/minContains/maxContains are not supported
378
+ // Check if this is a tuple (prefixItems or items as array)
379
+ const prefixItems = schema.prefixItems;
380
+ const items = schema.items;
381
+
382
+ if (prefixItems && Array.isArray(prefixItems)) {
383
+ // Tuple with prefixItems (draft-2020-12)
384
+ const tupleItems = prefixItems.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
385
+ const rest =
386
+ items && typeof items === "object" && !Array.isArray(items)
387
+ ? convertSchema(items as JSONSchema.JSONSchema, ctx)
388
+ : undefined;
389
+ if (rest) {
390
+ zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]).rest(rest);
391
+ } else {
392
+ zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]);
393
+ }
394
+ // Apply minItems/maxItems constraints to tuples
395
+ if (typeof schema.minItems === "number") {
396
+ zodSchema = (zodSchema as any).check(z.minLength(schema.minItems));
397
+ }
398
+ if (typeof schema.maxItems === "number") {
399
+ zodSchema = (zodSchema as any).check(z.maxLength(schema.maxItems));
400
+ }
401
+ } else if (Array.isArray(items)) {
402
+ // Tuple with items array (draft-7)
403
+ const tupleItems = items.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
404
+ const rest =
405
+ schema.additionalItems && typeof schema.additionalItems === "object"
406
+ ? convertSchema(schema.additionalItems as JSONSchema.JSONSchema, ctx)
407
+ : undefined; // additionalItems: false means no rest, handled by default tuple behavior
408
+ if (rest) {
409
+ zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]).rest(rest);
410
+ } else {
411
+ zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]);
412
+ }
413
+ // Apply minItems/maxItems constraints to tuples
414
+ if (typeof schema.minItems === "number") {
415
+ zodSchema = (zodSchema as any).check(z.minLength(schema.minItems));
416
+ }
417
+ if (typeof schema.maxItems === "number") {
418
+ zodSchema = (zodSchema as any).check(z.maxLength(schema.maxItems));
419
+ }
420
+ } else if (items !== undefined) {
421
+ // Regular array
422
+ const element = convertSchema(items as JSONSchema.JSONSchema, ctx);
423
+ let arraySchema = z.array(element);
424
+
425
+ // Apply constraints
426
+ if (typeof schema.minItems === "number") {
427
+ arraySchema = (arraySchema as any).min(schema.minItems);
428
+ }
429
+ if (typeof schema.maxItems === "number") {
430
+ arraySchema = (arraySchema as any).max(schema.maxItems);
431
+ }
432
+
433
+ zodSchema = arraySchema;
434
+ } else {
435
+ // No items specified - array of any
436
+ zodSchema = z.array(z.any());
437
+ }
438
+ break;
439
+ }
440
+
441
+ default:
442
+ throw new Error(`Unsupported type: ${type}`);
443
+ }
444
+
445
+ // Apply metadata
446
+ if (schema.description) {
447
+ zodSchema = zodSchema.describe(schema.description);
448
+ }
449
+ if (schema.default !== undefined) {
450
+ zodSchema = (zodSchema as any).default(schema.default);
451
+ }
452
+
453
+ return zodSchema;
454
+ }
455
+
456
+ function convertSchema(schema: JSONSchema.JSONSchema | boolean, ctx: ConversionContext): ZodType {
457
+ if (typeof schema === "boolean") {
458
+ return schema ? z.any() : z.never();
459
+ }
460
+
461
+ // Convert base schema first (ignoring composition keywords)
462
+ let baseSchema = convertBaseSchema(schema, ctx);
463
+ const hasExplicitType = schema.type || schema.enum !== undefined || schema.const !== undefined;
464
+
465
+ // Process composition keywords LAST (they can appear together)
466
+ // Handle anyOf - wrap base schema with union
467
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
468
+ const options = schema.anyOf.map((s) => convertSchema(s, ctx));
469
+ const anyOfUnion = z.union(options as [ZodType, ZodType, ...ZodType[]]);
470
+ baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
471
+ }
472
+
473
+ // Handle oneOf - exclusive union (exactly one must match)
474
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
475
+ const options = schema.oneOf.map((s) => convertSchema(s, ctx));
476
+ const oneOfUnion = z.xor(options as [ZodType, ZodType, ...ZodType[]]);
477
+ baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
478
+ }
479
+
480
+ // Handle allOf - wrap base schema with intersection
481
+ if (schema.allOf && Array.isArray(schema.allOf)) {
482
+ if (schema.allOf.length === 0) {
483
+ baseSchema = hasExplicitType ? baseSchema : z.any();
484
+ } else {
485
+ let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0]!, ctx);
486
+ const startIdx = hasExplicitType ? 0 : 1;
487
+ for (let i = startIdx; i < schema.allOf.length; i++) {
488
+ result = z.intersection(result, convertSchema(schema.allOf[i]!, ctx));
489
+ }
490
+ baseSchema = result;
491
+ }
492
+ }
493
+
494
+ // Handle nullable (OpenAPI 3.0)
495
+ if (schema.nullable === true && ctx.version === "openapi-3.0") {
496
+ baseSchema = z.nullable(baseSchema);
497
+ }
498
+
499
+ // Handle readOnly
500
+ if (schema.readOnly === true) {
501
+ baseSchema = z.readonly(baseSchema);
502
+ }
503
+
504
+ return baseSchema;
505
+ }
506
+
507
+ /**
508
+ * Converts a JSON Schema to a Zod schema. This function should be considered semi-experimental. It's behavior is liable to change. */
509
+ export function fromJSONSchema(schema: JSONSchema.JSONSchema | boolean, params?: FromJSONSchemaParams): ZodType {
510
+ // Handle boolean schemas
511
+ if (typeof schema === "boolean") {
512
+ return schema ? z.any() : z.never();
513
+ }
514
+
515
+ const version = detectVersion(schema, params?.defaultTarget);
516
+ const defs = (schema.$defs || schema.definitions || {}) as Record<string, JSONSchema.JSONSchema>;
517
+
518
+ const ctx: ConversionContext = {
519
+ version,
520
+ defs,
521
+ refs: new Map(),
522
+ processing: new Set(),
523
+ rootSchema: schema,
524
+ };
525
+
526
+ return convertSchema(schema, ctx);
527
+ }
@@ -1334,6 +1334,35 @@ export function union<const T extends readonly core.SomeType[]>(
1334
1334
  }) as any;
1335
1335
  }
1336
1336
 
1337
+ // ZodXor
1338
+ export interface ZodXor<T extends readonly core.SomeType[] = readonly core.$ZodType[]>
1339
+ extends _ZodType<core.$ZodXorInternals<T>>,
1340
+ core.$ZodXor<T> {
1341
+ "~standard": ZodStandardSchemaWithJSON<this>;
1342
+ options: T;
1343
+ }
1344
+ export const ZodXor: core.$constructor<ZodXor> = /*@__PURE__*/ core.$constructor("ZodXor", (inst, def) => {
1345
+ ZodUnion.init(inst, def);
1346
+ core.$ZodXor.init(inst, def);
1347
+ inst._zod.processJSONSchema = (ctx, json, params) => processors.unionProcessor(inst, ctx, json, params);
1348
+ inst.options = def.options;
1349
+ });
1350
+
1351
+ /** Creates an exclusive union (XOR) where exactly one option must match.
1352
+ * Unlike regular unions that succeed when any option matches, xor fails if
1353
+ * zero or more than one option matches the input. */
1354
+ export function xor<const T extends readonly core.SomeType[]>(
1355
+ options: T,
1356
+ params?: string | core.$ZodXorParams
1357
+ ): ZodXor<T> {
1358
+ return new ZodXor({
1359
+ type: "union",
1360
+ options: options as any as core.$ZodType[],
1361
+ inclusive: false,
1362
+ ...util.normalizeParams(params),
1363
+ }) as any;
1364
+ }
1365
+
1337
1366
  // ZodDiscriminatedUnion
1338
1367
  export interface ZodDiscriminatedUnion<
1339
1368
  Options extends readonly core.SomeType[] = readonly core.$ZodType[],
@@ -1488,6 +1517,20 @@ export function partialRecord<Key extends core.$ZodRecordKey, Value extends core
1488
1517
  }) as any;
1489
1518
  }
1490
1519
 
1520
+ export function looseRecord<Key extends core.$ZodRecordKey, Value extends core.SomeType>(
1521
+ keyType: Key,
1522
+ valueType: Value,
1523
+ params?: string | core.$ZodRecordParams
1524
+ ): ZodRecord<Key, Value> {
1525
+ return new ZodRecord({
1526
+ type: "record",
1527
+ keyType,
1528
+ valueType: valueType as any as core.$ZodType,
1529
+ mode: "loose",
1530
+ ...util.normalizeParams(params),
1531
+ }) as any;
1532
+ }
1533
+
1491
1534
  // ZodMap
1492
1535
  export interface ZodMap<Key extends core.SomeType = core.$ZodType, Value extends core.SomeType = core.$ZodType>
1493
1536
  extends _ZodType<core.$ZodMapInternals<Key, Value>>,