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