@orpc/zod 1.14.11 → 1.14.12

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.
package/dist/index.mjs CHANGED
@@ -1,99 +1,871 @@
1
- import { JsonSchemaXNativeType, JsonSchemaFormat, encodeJsonPointerSegment } from '@orpc/json-schema';
2
- import { registry, toJSONSchema, globalRegistry } from 'zod/v4/core';
1
+ import { custom, ZodFirstPartyTypeKind } from 'zod/v3';
2
+ import wcmatch from 'wildcard-match';
3
+ import { isObject, NullProtoObj, guard, toArray } from '@orpc/shared';
4
+ import { JsonSchemaXNativeType } from '@orpc/json-schema';
5
+ import { JSONSchemaFormat } from '@orpc/openapi';
6
+ import escapeStringRegexp from 'escape-string-regexp';
3
7
 
4
- const JSON_SCHEMA_REGISTRY = registry();
5
- const JSON_SCHEMA_INPUT_REGISTRY = registry();
6
- const JSON_SCHEMA_OUTPUT_REGISTRY = registry();
8
+ const CUSTOM_JSON_SCHEMA_SYMBOL = Symbol("ORPC_CUSTOM_JSON_SCHEMA");
9
+ const CUSTOM_JSON_SCHEMA_INPUT_SYMBOL = Symbol("ORPC_CUSTOM_JSON_SCHEMA_INPUT");
10
+ const CUSTOM_JSON_SCHEMA_OUTPUT_SYMBOL = Symbol("ORPC_CUSTOM_JSON_SCHEMA_OUTPUT");
11
+ function getCustomJsonSchema(def, options) {
12
+ if (options.strategy === "input" && CUSTOM_JSON_SCHEMA_INPUT_SYMBOL in def) {
13
+ return def[CUSTOM_JSON_SCHEMA_INPUT_SYMBOL];
14
+ }
15
+ if (options.strategy === "output" && CUSTOM_JSON_SCHEMA_OUTPUT_SYMBOL in def) {
16
+ return def[CUSTOM_JSON_SCHEMA_OUTPUT_SYMBOL];
17
+ }
18
+ if (CUSTOM_JSON_SCHEMA_SYMBOL in def) {
19
+ return def[CUSTOM_JSON_SCHEMA_SYMBOL];
20
+ }
21
+ return void 0;
22
+ }
23
+ function customJsonSchema(schema, custom, options = {}) {
24
+ const SYMBOL = options.strategy === "input" ? CUSTOM_JSON_SCHEMA_INPUT_SYMBOL : options.strategy === "output" ? CUSTOM_JSON_SCHEMA_OUTPUT_SYMBOL : CUSTOM_JSON_SCHEMA_SYMBOL;
25
+ const This = schema.constructor;
26
+ const newSchema = new This({
27
+ ...schema._def,
28
+ [SYMBOL]: custom
29
+ });
30
+ return newSchema;
31
+ }
32
+
33
+ const CUSTOM_ZOD_DEF_SYMBOL = Symbol("ORPC_CUSTOM_ZOD_DEF");
34
+ function setCustomZodDef(def, custom) {
35
+ Object.assign(def, { [CUSTOM_ZOD_DEF_SYMBOL]: custom });
36
+ }
37
+ function getCustomZodDef(def) {
38
+ return def[CUSTOM_ZOD_DEF_SYMBOL];
39
+ }
40
+ function composeParams(defaultMessage, params) {
41
+ return (val) => {
42
+ const message = defaultMessage(val);
43
+ if (!params) {
44
+ return {
45
+ message
46
+ };
47
+ }
48
+ if (typeof params === "function") {
49
+ return {
50
+ message,
51
+ ...params(val)
52
+ };
53
+ }
54
+ if (typeof params === "object") {
55
+ return {
56
+ message,
57
+ ...params
58
+ };
59
+ }
60
+ return {
61
+ message: params
62
+ };
63
+ };
64
+ }
65
+
66
+ function blob(params) {
67
+ const schema = custom(
68
+ (val) => val instanceof Blob,
69
+ composeParams(
70
+ () => "Input is not a blob",
71
+ params
72
+ )
73
+ );
74
+ setCustomZodDef(schema._def, { type: "blob" });
75
+ return schema;
76
+ }
77
+
78
+ function file(params) {
79
+ const schema = custom(
80
+ (val) => val instanceof File,
81
+ composeParams(
82
+ () => "Input is not a file",
83
+ params
84
+ )
85
+ );
86
+ setCustomZodDef(schema._def, { type: "file" });
87
+ return Object.assign(schema, {
88
+ type: (mimeType, params2) => {
89
+ const isMatch = wcmatch(mimeType);
90
+ const refinedSchema = schema.refine(
91
+ (val) => isMatch(val.type.split(";")[0]),
92
+ composeParams(
93
+ (val) => `Expected a file of type ${mimeType} but got a file of type ${val.type || "unknown"}`,
94
+ params2
95
+ )
96
+ );
97
+ setCustomZodDef(refinedSchema._def, { type: "file", mimeType });
98
+ return refinedSchema;
99
+ }
100
+ });
101
+ }
102
+
103
+ function regexp(params) {
104
+ const schema = custom(
105
+ (val) => val instanceof RegExp,
106
+ composeParams(
107
+ () => "Input is not a regexp",
108
+ params
109
+ )
110
+ );
111
+ setCustomZodDef(schema._def, { type: "regexp" });
112
+ return schema;
113
+ }
114
+
115
+ function url(params) {
116
+ const schema = custom(
117
+ (val) => val instanceof URL,
118
+ composeParams(
119
+ () => "Input is not a URL",
120
+ params
121
+ )
122
+ );
123
+ setCustomZodDef(schema._def, { type: "url" });
124
+ return schema;
125
+ }
126
+
127
+ class ZodSmartCoercionPlugin {
128
+ init(options) {
129
+ options.clientInterceptors ??= [];
130
+ options.clientInterceptors.unshift((options2) => {
131
+ const inputSchema = options2.procedure["~orpc"].inputSchema;
132
+ if (!inputSchema || inputSchema["~standard"].vendor !== "zod" || "_zod" in inputSchema) {
133
+ return options2.next();
134
+ }
135
+ const coercedInput = zodCoerceInternal(inputSchema, options2.input);
136
+ return options2.next({ ...options2, input: coercedInput });
137
+ });
138
+ }
139
+ }
140
+ function zodCoerceInternal(schema, value) {
141
+ const customZodDef = getCustomZodDef(schema._def);
142
+ switch (customZodDef?.type) {
143
+ case "regexp": {
144
+ if (typeof value === "string") {
145
+ return safeToRegExp(value);
146
+ }
147
+ return value;
148
+ }
149
+ case "url": {
150
+ if (typeof value === "string") {
151
+ return safeToURL(value);
152
+ }
153
+ return value;
154
+ }
155
+ }
156
+ const typeName = schema._def.typeName;
157
+ switch (typeName) {
158
+ case ZodFirstPartyTypeKind.ZodNumber: {
159
+ if (typeof value === "string") {
160
+ return safeToNumber(value);
161
+ }
162
+ return value;
163
+ }
164
+ case ZodFirstPartyTypeKind.ZodBigInt: {
165
+ if (typeof value === "string") {
166
+ return safeToBigInt(value);
167
+ }
168
+ return value;
169
+ }
170
+ case ZodFirstPartyTypeKind.ZodBoolean: {
171
+ if (typeof value === "string") {
172
+ return safeToBoolean(value);
173
+ }
174
+ return value;
175
+ }
176
+ case ZodFirstPartyTypeKind.ZodDate: {
177
+ if (typeof value === "string") {
178
+ return safeToDate(value);
179
+ }
180
+ return value;
181
+ }
182
+ case ZodFirstPartyTypeKind.ZodLiteral: {
183
+ const schema_ = schema;
184
+ const expectedValue = schema_._def.value;
185
+ if (typeof value === "string" && typeof expectedValue !== "string") {
186
+ if (typeof expectedValue === "bigint") {
187
+ return safeToBigInt(value);
188
+ } else if (typeof expectedValue === "number") {
189
+ return safeToNumber(value);
190
+ } else if (typeof expectedValue === "boolean") {
191
+ return safeToBoolean(value);
192
+ }
193
+ }
194
+ return value;
195
+ }
196
+ case ZodFirstPartyTypeKind.ZodNativeEnum: {
197
+ const schema_ = schema;
198
+ if (Object.values(schema_._def.values).includes(value)) {
199
+ return value;
200
+ }
201
+ if (typeof value === "string") {
202
+ for (const expectedValue of Object.values(schema_._def.values)) {
203
+ if (expectedValue.toString() === value) {
204
+ return expectedValue;
205
+ }
206
+ }
207
+ }
208
+ return value;
209
+ }
210
+ case ZodFirstPartyTypeKind.ZodObject: {
211
+ const schema_ = schema;
212
+ if (isObject(value)) {
213
+ const newObj = new NullProtoObj();
214
+ const shape = schema_.shape;
215
+ const keys = /* @__PURE__ */ new Set([
216
+ ...Object.keys(value),
217
+ ...Object.keys(shape)
218
+ ]);
219
+ for (const k of keys) {
220
+ newObj[k] = zodCoerceInternal(
221
+ (Object.hasOwn(shape, k) ? shape[k] : void 0) ?? schema_._def.catchall,
222
+ value[k]
223
+ );
224
+ }
225
+ return newObj;
226
+ }
227
+ return value;
228
+ }
229
+ case ZodFirstPartyTypeKind.ZodRecord: {
230
+ const schema_ = schema;
231
+ if (isObject(value)) {
232
+ const newObj = new NullProtoObj();
233
+ for (const [k, v] of Object.entries(value)) {
234
+ const key = zodCoerceInternal(schema_._def.keyType, k);
235
+ const val = zodCoerceInternal(schema_._def.valueType, v);
236
+ newObj[key] = val;
237
+ }
238
+ return newObj;
239
+ }
240
+ return value;
241
+ }
242
+ case ZodFirstPartyTypeKind.ZodArray: {
243
+ const schema_ = schema;
244
+ if (Array.isArray(value)) {
245
+ return value.map((v) => zodCoerceInternal(schema_._def.type, v));
246
+ }
247
+ return value;
248
+ }
249
+ case ZodFirstPartyTypeKind.ZodTuple: {
250
+ const schema_ = schema;
251
+ if (Array.isArray(value)) {
252
+ return value.map((v, i) => {
253
+ const s = schema_._def.items[i] ?? schema_._def.rest;
254
+ return s ? zodCoerceInternal(s, v) : v;
255
+ });
256
+ }
257
+ return value;
258
+ }
259
+ case ZodFirstPartyTypeKind.ZodSet: {
260
+ const schema_ = schema;
261
+ if (Array.isArray(value)) {
262
+ return new Set(
263
+ value.map((v) => zodCoerceInternal(schema_._def.valueType, v))
264
+ );
265
+ }
266
+ return value;
267
+ }
268
+ case ZodFirstPartyTypeKind.ZodMap: {
269
+ const schema_ = schema;
270
+ if (Array.isArray(value) && value.every((i) => Array.isArray(i) && i.length === 2)) {
271
+ return new Map(
272
+ value.map(([k, v]) => [
273
+ zodCoerceInternal(schema_._def.keyType, k),
274
+ zodCoerceInternal(schema_._def.valueType, v)
275
+ ])
276
+ );
277
+ }
278
+ return value;
279
+ }
280
+ case ZodFirstPartyTypeKind.ZodUnion:
281
+ case ZodFirstPartyTypeKind.ZodDiscriminatedUnion: {
282
+ const schema_ = schema;
283
+ if (schema_.safeParse(value).success) {
284
+ return value;
285
+ }
286
+ const results = [];
287
+ for (const s of schema_._def.options) {
288
+ const newValue = zodCoerceInternal(s, value);
289
+ const result = schema_.safeParse(newValue);
290
+ if (result.success) {
291
+ return newValue;
292
+ }
293
+ results.push([newValue, result.error.issues.length]);
294
+ }
295
+ if (results.length === 0) {
296
+ return value;
297
+ }
298
+ return results.sort((a, b) => a[1] - b[1])[0][0];
299
+ }
300
+ case ZodFirstPartyTypeKind.ZodIntersection: {
301
+ const schema_ = schema;
302
+ return zodCoerceInternal(
303
+ schema_._def.right,
304
+ zodCoerceInternal(schema_._def.left, value)
305
+ );
306
+ }
307
+ case ZodFirstPartyTypeKind.ZodReadonly: {
308
+ const schema_ = schema;
309
+ return zodCoerceInternal(schema_._def.innerType, value);
310
+ }
311
+ case ZodFirstPartyTypeKind.ZodPipeline: {
312
+ const schema_ = schema;
313
+ return zodCoerceInternal(schema_._def.in, value);
314
+ }
315
+ case ZodFirstPartyTypeKind.ZodEffects: {
316
+ const schema_ = schema;
317
+ return zodCoerceInternal(schema_._def.schema, value);
318
+ }
319
+ case ZodFirstPartyTypeKind.ZodBranded: {
320
+ const schema_ = schema;
321
+ return zodCoerceInternal(schema_._def.type, value);
322
+ }
323
+ case ZodFirstPartyTypeKind.ZodCatch: {
324
+ const schema_ = schema;
325
+ return zodCoerceInternal(schema_._def.innerType, value);
326
+ }
327
+ case ZodFirstPartyTypeKind.ZodDefault: {
328
+ const schema_ = schema;
329
+ return zodCoerceInternal(schema_._def.innerType, value);
330
+ }
331
+ case ZodFirstPartyTypeKind.ZodNullable: {
332
+ if (value === null) {
333
+ return null;
334
+ }
335
+ const schema_ = schema;
336
+ return zodCoerceInternal(schema_._def.innerType, value);
337
+ }
338
+ case ZodFirstPartyTypeKind.ZodOptional: {
339
+ if (value === void 0) {
340
+ return void 0;
341
+ }
342
+ const schema_ = schema;
343
+ return zodCoerceInternal(schema_._def.innerType, value);
344
+ }
345
+ case ZodFirstPartyTypeKind.ZodLazy: {
346
+ const schema_ = schema;
347
+ if (value !== void 0) {
348
+ return zodCoerceInternal(schema_._def.getter(), value);
349
+ }
350
+ return value;
351
+ }
352
+ }
353
+ return value;
354
+ }
355
+ function safeToBigInt(value) {
356
+ return guard(() => BigInt(value)) ?? value;
357
+ }
358
+ function safeToNumber(value) {
359
+ const num = Number(value);
360
+ return Number.isNaN(num) || num.toString() !== value ? value : num;
361
+ }
362
+ function safeToBoolean(value) {
363
+ const lower = value.toLowerCase();
364
+ if (lower === "false" || lower === "off" || lower === "f") {
365
+ return false;
366
+ }
367
+ if (lower === "true" || lower === "on" || lower === "t") {
368
+ return true;
369
+ }
370
+ return value;
371
+ }
372
+ function safeToRegExp(value) {
373
+ if (value.startsWith("/")) {
374
+ const match = value.match(/^\/(.*)\/([a-z]*)$/);
375
+ if (match) {
376
+ const [, pattern, flags] = match;
377
+ return new RegExp(pattern, flags);
378
+ }
379
+ }
380
+ return value;
381
+ }
382
+ function safeToURL(value) {
383
+ return guard(() => new URL(value)) ?? value;
384
+ }
385
+ function safeToDate(value) {
386
+ const date = new Date(value);
387
+ if (!Number.isNaN(date.getTime()) && date.toISOString().startsWith(value)) {
388
+ return date;
389
+ }
390
+ return value;
391
+ }
7
392
 
8
393
  class ZodToJsonSchemaConverter {
394
+ maxLazyDepth;
395
+ maxStructureDepth;
396
+ unsupportedJsonSchema;
397
+ anyJsonSchema;
9
398
  constructor(options = {}) {
10
- this.options = options;
11
- }
12
- condition(schema, _direction) {
13
- return schema?.["~standard"].vendor === "zod";
14
- }
15
- convert(schema, direction) {
16
- const zodSchema = schema;
17
- const jsonSchema = this.convertZod(zodSchema, direction);
18
- let optional = false;
19
- try {
20
- const result = zodSchema["~standard"].validate(void 0);
21
- if (!(result instanceof Promise) && !result.issues) {
22
- optional = direction === "input" ? true : result.value === void 0;
23
- }
24
- } catch {
25
- }
26
- return [jsonSchema, optional];
27
- }
28
- convertZod(schema, direction) {
29
- const jsonSchema = toJSONSchema(schema, {
30
- unrepresentable: "any",
31
- ...this.options,
32
- target: "draft-2020-12",
33
- io: direction,
34
- override: (ctx) => {
35
- const def = ctx.zodSchema._zod.def;
36
- if (def.type === "bigint") {
37
- ctx.jsonSchema.type = "string";
38
- ctx.jsonSchema.pattern = "^-?[0-9]+$";
39
- ctx.jsonSchema["x-native-type"] = JsonSchemaXNativeType.BigInt;
40
- } else if (def.type === "date") {
41
- ctx.jsonSchema.type = "string";
42
- ctx.jsonSchema.format = JsonSchemaFormat.DateTime;
43
- ctx.jsonSchema["x-native-type"] = JsonSchemaXNativeType.Date;
44
- } else if (def.type === "set") {
45
- ctx.jsonSchema.type = "array";
46
- ctx.jsonSchema.uniqueItems = true;
47
- ctx.jsonSchema.items = this.convertZod(def.valueType, direction);
48
- ctx.jsonSchema["x-native-type"] = JsonSchemaXNativeType.Set;
49
- } else if (def.type === "map") {
50
- ctx.jsonSchema.type = "array";
51
- ctx.jsonSchema.items = {
399
+ this.maxLazyDepth = options.maxLazyDepth ?? 3;
400
+ this.maxStructureDepth = options.maxStructureDepth ?? 10;
401
+ this.unsupportedJsonSchema = options.unsupportedJsonSchema ?? { not: {} };
402
+ this.anyJsonSchema = options.anyJsonSchema ?? {};
403
+ }
404
+ condition(schema) {
405
+ return schema !== void 0 && schema["~standard"].vendor === "zod" && !("_zod" in schema);
406
+ }
407
+ convert(schema, options, lazyDepth = 0, isHandledCustomJSONSchema = false, isHandledZodDescription = false, structureDepth = 0) {
408
+ const def = schema._def;
409
+ if (structureDepth > this.maxStructureDepth) {
410
+ return [false, this.anyJsonSchema];
411
+ }
412
+ if (!options.minStructureDepthForRef || options.minStructureDepthForRef <= structureDepth) {
413
+ const components = toArray(options.components);
414
+ for (const component of components) {
415
+ if (component.schema === schema && component.allowedStrategies.includes(options.strategy)) {
416
+ return [component.required, { $ref: component.ref }];
417
+ }
418
+ }
419
+ }
420
+ if (!isHandledZodDescription && "description" in def && typeof def.description === "string") {
421
+ const [required, json] = this.convert(
422
+ schema,
423
+ options,
424
+ lazyDepth,
425
+ isHandledCustomJSONSchema,
426
+ true,
427
+ structureDepth
428
+ );
429
+ return [required, { ...json, description: def.description }];
430
+ }
431
+ if (!isHandledCustomJSONSchema) {
432
+ const customJSONSchema = getCustomJsonSchema(def, options);
433
+ if (customJSONSchema) {
434
+ const [required, json] = this.convert(
435
+ schema,
436
+ options,
437
+ lazyDepth,
438
+ true,
439
+ isHandledZodDescription,
440
+ structureDepth
441
+ );
442
+ return [required, { ...json, ...customJSONSchema }];
443
+ }
444
+ }
445
+ const customSchema = this.#handleCustomZodDef(def);
446
+ if (customSchema) {
447
+ return [true, customSchema];
448
+ }
449
+ const typeName = this.#getZodTypeName(def);
450
+ switch (typeName) {
451
+ case ZodFirstPartyTypeKind.ZodString: {
452
+ const schema_ = schema;
453
+ const json = { type: "string" };
454
+ for (const check of schema_._def.checks) {
455
+ switch (check.kind) {
456
+ case "base64":
457
+ json.contentEncoding = "base64";
458
+ break;
459
+ case "cuid":
460
+ json.pattern = "^[0-9A-HJKMNP-TV-Z]{26}$";
461
+ break;
462
+ case "email":
463
+ json.format = JSONSchemaFormat.Email;
464
+ break;
465
+ case "url":
466
+ json.format = JSONSchemaFormat.URI;
467
+ break;
468
+ case "uuid":
469
+ json.format = JSONSchemaFormat.UUID;
470
+ break;
471
+ case "regex":
472
+ json.pattern = check.regex.source;
473
+ break;
474
+ case "min":
475
+ json.minLength = check.value;
476
+ break;
477
+ case "max":
478
+ json.maxLength = check.value;
479
+ break;
480
+ case "length":
481
+ json.minLength = check.value;
482
+ json.maxLength = check.value;
483
+ break;
484
+ case "includes":
485
+ json.pattern = escapeStringRegexp(check.value);
486
+ break;
487
+ case "startsWith":
488
+ json.pattern = `^${escapeStringRegexp(check.value)}`;
489
+ break;
490
+ case "endsWith":
491
+ json.pattern = `${escapeStringRegexp(check.value)}$`;
492
+ break;
493
+ case "emoji":
494
+ json.pattern = "^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";
495
+ break;
496
+ case "nanoid":
497
+ json.pattern = "^[a-zA-Z0-9_-]{21}$";
498
+ break;
499
+ case "cuid2":
500
+ json.pattern = "^[0-9a-z]+$";
501
+ break;
502
+ case "ulid":
503
+ json.pattern = "^[0-9A-HJKMNP-TV-Z]{26}$";
504
+ break;
505
+ case "datetime":
506
+ json.format = JSONSchemaFormat.DateTime;
507
+ break;
508
+ case "date":
509
+ json.format = JSONSchemaFormat.Date;
510
+ break;
511
+ case "time":
512
+ json.format = JSONSchemaFormat.Time;
513
+ break;
514
+ case "duration":
515
+ json.format = JSONSchemaFormat.Duration;
516
+ break;
517
+ case "ip": {
518
+ if (check.version === "v4") {
519
+ json.format = JSONSchemaFormat.IPv4;
520
+ } else if (check.version === "v6") {
521
+ json.format = JSONSchemaFormat.IPv6;
522
+ } else {
523
+ json.anyOf = [
524
+ { format: JSONSchemaFormat.IPv4 },
525
+ { format: JSONSchemaFormat.IPv6 }
526
+ ];
527
+ }
528
+ break;
529
+ }
530
+ case "jwt":
531
+ json.pattern = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$";
532
+ break;
533
+ case "base64url":
534
+ json.pattern = "^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$";
535
+ break;
536
+ default: {
537
+ check.kind;
538
+ }
539
+ }
540
+ }
541
+ return [true, json];
542
+ }
543
+ case ZodFirstPartyTypeKind.ZodNumber: {
544
+ const schema_ = schema;
545
+ const json = { type: "number" };
546
+ for (const check of schema_._def.checks) {
547
+ switch (check.kind) {
548
+ case "int":
549
+ json.type = "integer";
550
+ break;
551
+ case "min":
552
+ json.minimum = check.value;
553
+ break;
554
+ case "max":
555
+ json.maximum = check.value;
556
+ break;
557
+ case "multipleOf":
558
+ json.multipleOf = check.value;
559
+ break;
560
+ default: {
561
+ check.kind;
562
+ }
563
+ }
564
+ }
565
+ return [true, json];
566
+ }
567
+ case ZodFirstPartyTypeKind.ZodBigInt: {
568
+ const json = {
569
+ "type": "string",
570
+ "pattern": "^-?[0-9]+$",
571
+ "x-native-type": JsonSchemaXNativeType.BigInt
572
+ };
573
+ return [true, json];
574
+ }
575
+ case ZodFirstPartyTypeKind.ZodNaN: {
576
+ return options.strategy === "input" ? [true, this.unsupportedJsonSchema] : [true, { type: "null" }];
577
+ }
578
+ case ZodFirstPartyTypeKind.ZodBoolean: {
579
+ return [true, { type: "boolean" }];
580
+ }
581
+ case ZodFirstPartyTypeKind.ZodDate: {
582
+ const schema2 = {
583
+ "type": "string",
584
+ "format": JSONSchemaFormat.DateTime,
585
+ "x-native-type": JsonSchemaXNativeType.Date
586
+ };
587
+ return [true, schema2];
588
+ }
589
+ case ZodFirstPartyTypeKind.ZodNull: {
590
+ return [true, { type: "null" }];
591
+ }
592
+ case ZodFirstPartyTypeKind.ZodLiteral: {
593
+ const schema_ = schema;
594
+ if (schema_._def.value === void 0) {
595
+ return [false, this.unsupportedJsonSchema];
596
+ }
597
+ return [true, { const: schema_._def.value }];
598
+ }
599
+ case ZodFirstPartyTypeKind.ZodVoid:
600
+ case ZodFirstPartyTypeKind.ZodUndefined: {
601
+ return [false, this.unsupportedJsonSchema];
602
+ }
603
+ case ZodFirstPartyTypeKind.ZodUnknown:
604
+ case ZodFirstPartyTypeKind.ZodAny: {
605
+ return [false, this.anyJsonSchema];
606
+ }
607
+ case ZodFirstPartyTypeKind.ZodEnum: {
608
+ const schema_ = schema;
609
+ const values = schema_._def.values;
610
+ const json = { enum: values, type: "string" };
611
+ return [true, json];
612
+ }
613
+ case ZodFirstPartyTypeKind.ZodNativeEnum: {
614
+ const schema_ = schema;
615
+ const values = getEnumValues(schema_._def.values);
616
+ const json = { enum: values };
617
+ if (values.every((v) => typeof v === "string")) {
618
+ json.type = "string";
619
+ } else if (values.every((v) => Number.isFinite(v))) {
620
+ json.type = "number";
621
+ }
622
+ return [true, json];
623
+ }
624
+ case ZodFirstPartyTypeKind.ZodArray: {
625
+ const schema_ = schema;
626
+ const def2 = schema_._def;
627
+ const json = { type: "array" };
628
+ const [itemRequired, itemJson] = this.convert(def2.type, options, lazyDepth, false, false, structureDepth + 1);
629
+ json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
630
+ if (def2.exactLength) {
631
+ json.maxItems = def2.exactLength.value;
632
+ json.minItems = def2.exactLength.value;
633
+ }
634
+ if (def2.minLength) {
635
+ json.minItems = def2.minLength.value;
636
+ }
637
+ if (def2.maxLength) {
638
+ json.maxItems = def2.maxLength.value;
639
+ }
640
+ return [true, json];
641
+ }
642
+ case ZodFirstPartyTypeKind.ZodTuple: {
643
+ const schema_ = schema;
644
+ const prefixItems = [];
645
+ const json = { type: "array" };
646
+ for (const item of schema_._def.items) {
647
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1);
648
+ prefixItems.push(
649
+ this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy)
650
+ );
651
+ }
652
+ if (prefixItems?.length) {
653
+ json.prefixItems = prefixItems;
654
+ }
655
+ if (schema_._def.rest) {
656
+ const [itemRequired, itemJson] = this.convert(schema_._def.rest, options, lazyDepth, false, false, structureDepth + 1);
657
+ json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
658
+ }
659
+ return [true, json];
660
+ }
661
+ case ZodFirstPartyTypeKind.ZodObject: {
662
+ const schema_ = schema;
663
+ const json = { type: "object" };
664
+ const properties = {};
665
+ const required = [];
666
+ for (const [key, value] of Object.entries(schema_.shape)) {
667
+ const [itemRequired, itemJson] = this.convert(value, options, lazyDepth, false, false, structureDepth + 1);
668
+ properties[key] = itemJson;
669
+ if (itemRequired) {
670
+ required.push(key);
671
+ }
672
+ }
673
+ if (Object.keys(properties).length) {
674
+ json.properties = properties;
675
+ }
676
+ if (required.length) {
677
+ json.required = required;
678
+ }
679
+ const catchAllTypeName = this.#getZodTypeName(schema_._def.catchall._def);
680
+ if (catchAllTypeName === ZodFirstPartyTypeKind.ZodNever) {
681
+ if (schema_._def.unknownKeys === "strict") {
682
+ json.additionalProperties = false;
683
+ }
684
+ } else {
685
+ const [_, addJson] = this.convert(schema_._def.catchall, options, lazyDepth, false, false, structureDepth + 1);
686
+ json.additionalProperties = addJson;
687
+ }
688
+ return [true, json];
689
+ }
690
+ case ZodFirstPartyTypeKind.ZodRecord: {
691
+ const schema_ = schema;
692
+ const json = { type: "object" };
693
+ const [__, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false, structureDepth + 1);
694
+ if (Object.entries(keyJson).some(([k, v]) => k !== "type" || v !== "string")) {
695
+ json.propertyNames = keyJson;
696
+ }
697
+ const [_, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
698
+ json.additionalProperties = itemJson;
699
+ return [true, json];
700
+ }
701
+ case ZodFirstPartyTypeKind.ZodSet: {
702
+ const schema_ = schema;
703
+ const json = {
704
+ "type": "array",
705
+ "uniqueItems": true,
706
+ "x-native-type": JsonSchemaXNativeType.Set
707
+ };
708
+ const [itemRequired, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
709
+ json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
710
+ return [true, json];
711
+ }
712
+ case ZodFirstPartyTypeKind.ZodMap: {
713
+ const schema_ = schema;
714
+ const [keyRequired, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false, structureDepth + 1);
715
+ const [valueRequired, valueJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
716
+ const json = {
717
+ "type": "array",
718
+ "items": {
52
719
  type: "array",
53
720
  prefixItems: [
54
- this.convertZod(def.keyType, direction),
55
- this.convertZod(def.valueType, direction)
721
+ this.#toArrayItemJsonSchema(keyRequired, keyJson, options.strategy),
722
+ this.#toArrayItemJsonSchema(valueRequired, valueJson, options.strategy)
56
723
  ],
57
724
  maxItems: 2,
58
725
  minItems: 2
59
- };
60
- ctx.jsonSchema["x-native-type"] = JsonSchemaXNativeType.Map;
726
+ },
727
+ "x-native-type": JsonSchemaXNativeType.Map
728
+ };
729
+ return [true, json];
730
+ }
731
+ case ZodFirstPartyTypeKind.ZodUnion:
732
+ case ZodFirstPartyTypeKind.ZodDiscriminatedUnion: {
733
+ const schema_ = schema;
734
+ const anyOf = [];
735
+ let required = true;
736
+ for (const item of schema_._def.options) {
737
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1);
738
+ if (!itemRequired) {
739
+ required = false;
740
+ if (itemJson !== this.unsupportedJsonSchema) {
741
+ anyOf.push(itemJson);
742
+ }
743
+ } else {
744
+ anyOf.push(itemJson);
745
+ }
61
746
  }
62
- const customJsonSchema = this.getCustomJsonSchema(ctx.zodSchema, direction);
63
- if (customJsonSchema) {
64
- Object.assign(ctx.jsonSchema, customJsonSchema);
747
+ return [required, { anyOf }];
748
+ }
749
+ case ZodFirstPartyTypeKind.ZodIntersection: {
750
+ const schema_ = schema;
751
+ const allOf = [];
752
+ let required = false;
753
+ for (const item of [schema_._def.left, schema_._def.right]) {
754
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1);
755
+ allOf.push(itemJson);
756
+ if (itemRequired) {
757
+ required = true;
758
+ }
65
759
  }
66
- this.options.override?.(ctx);
760
+ return [required, { allOf }];
67
761
  }
68
- });
69
- const { $schema, ...rest } = jsonSchema;
70
- const registry = this.options.metadata ?? globalRegistry;
71
- const { id } = registry.get(schema) || {};
72
- if (typeof id === "string" && rest.$ref === void 0) {
73
- const { $defs = {}, ...restWithoutDefs } = rest;
74
- let defName = id;
75
- let index = 0;
76
- while (defName in $defs) {
77
- defName = `${defName}__${index++}`;
762
+ case ZodFirstPartyTypeKind.ZodLazy: {
763
+ const currentLazyDepth = lazyDepth + 1;
764
+ if (currentLazyDepth > this.maxLazyDepth) {
765
+ return [false, this.anyJsonSchema];
766
+ }
767
+ const schema_ = schema;
768
+ return this.convert(schema_._def.getter(), options, currentLazyDepth, false, false, structureDepth);
78
769
  }
79
- return {
80
- $ref: `#/$defs/${encodeJsonPointerSegment(defName)}`,
81
- $defs: {
82
- ...$defs,
83
- [defName]: restWithoutDefs
770
+ case ZodFirstPartyTypeKind.ZodOptional: {
771
+ const schema_ = schema;
772
+ const [_, inner] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
773
+ return [false, inner];
774
+ }
775
+ case ZodFirstPartyTypeKind.ZodReadonly: {
776
+ const schema_ = schema;
777
+ const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
778
+ return [required, { ...json, readOnly: true }];
779
+ }
780
+ case ZodFirstPartyTypeKind.ZodDefault: {
781
+ const schema_ = schema;
782
+ const [_, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
783
+ return [false, { default: schema_._def.defaultValue(), ...json }];
784
+ }
785
+ case ZodFirstPartyTypeKind.ZodEffects: {
786
+ const schema_ = schema;
787
+ if (schema_._def.effect.type === "transform" && options.strategy === "output") {
788
+ return [false, this.anyJsonSchema];
84
789
  }
85
- };
790
+ return this.convert(schema_._def.schema, options, lazyDepth, false, false, structureDepth);
791
+ }
792
+ case ZodFirstPartyTypeKind.ZodCatch: {
793
+ const schema_ = schema;
794
+ return this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
795
+ }
796
+ case ZodFirstPartyTypeKind.ZodBranded: {
797
+ const schema_ = schema;
798
+ return this.convert(schema_._def.type, options, lazyDepth, false, false, structureDepth);
799
+ }
800
+ case ZodFirstPartyTypeKind.ZodPipeline: {
801
+ const schema_ = schema;
802
+ return this.convert(
803
+ options.strategy === "input" ? schema_._def.in : schema_._def.out,
804
+ options,
805
+ lazyDepth,
806
+ false,
807
+ false,
808
+ structureDepth
809
+ );
810
+ }
811
+ case ZodFirstPartyTypeKind.ZodNullable: {
812
+ const schema_ = schema;
813
+ const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
814
+ return [required, { anyOf: [json, { type: "null" }] }];
815
+ }
86
816
  }
87
- return rest;
817
+ return [true, this.unsupportedJsonSchema];
88
818
  }
89
- getCustomJsonSchema(schema, direction) {
90
- const general = JSON_SCHEMA_REGISTRY.get(schema);
91
- const directional = direction === "input" ? JSON_SCHEMA_INPUT_REGISTRY.get(schema) : JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
92
- if (general === void 0 && directional === void 0) {
819
+ #handleCustomZodDef(def) {
820
+ const customZodDef = getCustomZodDef(def);
821
+ if (!customZodDef) {
93
822
  return void 0;
94
823
  }
95
- return { ...general, ...directional };
824
+ switch (customZodDef.type) {
825
+ case "blob": {
826
+ return { type: "string", contentMediaType: "*/*" };
827
+ }
828
+ case "file": {
829
+ return { type: "string", contentMediaType: customZodDef.mimeType ?? "*/*" };
830
+ }
831
+ case "regexp": {
832
+ return {
833
+ "type": "string",
834
+ "pattern": "^\\/(.*)\\/([a-z]*)$",
835
+ "x-native-type": JsonSchemaXNativeType.RegExp
836
+ };
837
+ }
838
+ case "url": {
839
+ return {
840
+ "type": "string",
841
+ "format": JSONSchemaFormat.URI,
842
+ "x-native-type": JsonSchemaXNativeType.Url
843
+ };
844
+ }
845
+ }
846
+ }
847
+ #getZodTypeName(def) {
848
+ return def.typeName;
849
+ }
850
+ #toArrayItemJsonSchema(required, schema, strategy) {
851
+ if (required) {
852
+ return schema;
853
+ }
854
+ return strategy === "input" ? { anyOf: [schema, this.unsupportedJsonSchema] } : { anyOf: [schema, { type: "null" }] };
96
855
  }
97
856
  }
857
+ function getEnumValues(entries) {
858
+ const numericValues = Object.values(entries).filter((v) => typeof v === "number");
859
+ const values = Object.entries(entries).filter(([k, _]) => !numericValues.includes(+k)).map(([_, v]) => v);
860
+ return values;
861
+ }
862
+
863
+ const oz = {
864
+ file,
865
+ blob,
866
+ url,
867
+ regexp,
868
+ openapi: customJsonSchema
869
+ };
98
870
 
99
- export { JSON_SCHEMA_INPUT_REGISTRY, JSON_SCHEMA_OUTPUT_REGISTRY, JSON_SCHEMA_REGISTRY, ZodToJsonSchemaConverter };
871
+ export { ZodSmartCoercionPlugin, ZodToJsonSchemaConverter, blob, composeParams, customJsonSchema, file, getCustomJsonSchema, getCustomZodDef, oz, regexp, setCustomZodDef, url };