@orpc/json-schema 2.0.0-beta.4 → 2.0.0-beta.40
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 +97 -94
- package/dist/index.d.mts +78 -10
- package/dist/index.d.ts +78 -10
- package/dist/index.mjs +230 -226
- package/package.json +19 -10
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { get, isPlainObject,
|
|
2
|
-
export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
|
|
1
|
+
import { get, isPlainObject, tryOrUndefined, getOwn, toArray, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
|
|
3
2
|
import { ORPCError, cloneORPCError } from '@orpc/client';
|
|
4
3
|
import { getProcedureContractOrThrow } from '@orpc/contract';
|
|
5
4
|
|
|
@@ -59,6 +58,28 @@ function encodeJsonPointerSegment(segment) {
|
|
|
59
58
|
function decodeJsonPointerSegment(segment) {
|
|
60
59
|
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
61
60
|
}
|
|
61
|
+
function visitJsonSchemaRefs(value, visit, schemaLevel = true, seen = /* @__PURE__ */ new Set()) {
|
|
62
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
seen.add(value);
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
for (const item of value) {
|
|
68
|
+
visitJsonSchemaRefs(item, visit, schemaLevel, seen);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const key of Object.keys(value)) {
|
|
73
|
+
const val = value[key];
|
|
74
|
+
if (key === "$ref" && typeof val === "string") {
|
|
75
|
+
visit(val);
|
|
76
|
+
} else if (!schemaLevel) {
|
|
77
|
+
visitJsonSchemaRefs(val, visit, true, seen);
|
|
78
|
+
} else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
|
|
79
|
+
visitJsonSchemaRefs(val, visit, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), seen);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
62
83
|
function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
|
|
63
84
|
if (!value || typeof value !== "object") {
|
|
64
85
|
return value;
|
|
@@ -67,7 +88,8 @@ function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
|
|
|
67
88
|
return value.map((item, index) => mapJsonSchemaRefs(item, map, schemaLevel, [...path, index]));
|
|
68
89
|
}
|
|
69
90
|
const result = {};
|
|
70
|
-
for (const
|
|
91
|
+
for (const key of Object.keys(value)) {
|
|
92
|
+
const val = value[key];
|
|
71
93
|
if (key === "$ref" && typeof val === "string") {
|
|
72
94
|
result[key] = map(val, [...path, key]);
|
|
73
95
|
} else if (!schemaLevel) {
|
|
@@ -138,9 +160,40 @@ function findRecursiveJsonSchemaDefName(defs) {
|
|
|
138
160
|
return `__schema${index}`;
|
|
139
161
|
}
|
|
140
162
|
|
|
163
|
+
var JsonSchemaType = /* @__PURE__ */ ((JsonSchemaType2) => {
|
|
164
|
+
JsonSchemaType2["Array"] = "array";
|
|
165
|
+
JsonSchemaType2["Boolean"] = "boolean";
|
|
166
|
+
JsonSchemaType2["Integer"] = "integer";
|
|
167
|
+
JsonSchemaType2["Null"] = "null";
|
|
168
|
+
JsonSchemaType2["Number"] = "number";
|
|
169
|
+
JsonSchemaType2["Object"] = "object";
|
|
170
|
+
JsonSchemaType2["String"] = "string";
|
|
171
|
+
return JsonSchemaType2;
|
|
172
|
+
})(JsonSchemaType || {});
|
|
173
|
+
var JsonSchemaFormat = /* @__PURE__ */ ((JsonSchemaFormat2) => {
|
|
174
|
+
JsonSchemaFormat2["Date"] = "date";
|
|
175
|
+
JsonSchemaFormat2["DateTime"] = "date-time";
|
|
176
|
+
JsonSchemaFormat2["Duration"] = "duration";
|
|
177
|
+
JsonSchemaFormat2["Email"] = "email";
|
|
178
|
+
JsonSchemaFormat2["Hostname"] = "hostname";
|
|
179
|
+
JsonSchemaFormat2["IDNEmail"] = "idn-email";
|
|
180
|
+
JsonSchemaFormat2["IDNHostname"] = "idn-hostname";
|
|
181
|
+
JsonSchemaFormat2["IPv4"] = "ipv4";
|
|
182
|
+
JsonSchemaFormat2["IPv6"] = "ipv6";
|
|
183
|
+
JsonSchemaFormat2["IRI"] = "iri";
|
|
184
|
+
JsonSchemaFormat2["IRIReference"] = "iri-reference";
|
|
185
|
+
JsonSchemaFormat2["JSONPointer"] = "json-pointer";
|
|
186
|
+
JsonSchemaFormat2["RegEx"] = "regex";
|
|
187
|
+
JsonSchemaFormat2["RelativeJSONPointer"] = "relative-json-pointer";
|
|
188
|
+
JsonSchemaFormat2["Time"] = "time";
|
|
189
|
+
JsonSchemaFormat2["URI"] = "uri";
|
|
190
|
+
JsonSchemaFormat2["URIReference"] = "uri-reference";
|
|
191
|
+
JsonSchemaFormat2["URITemplate"] = "uri-template";
|
|
192
|
+
JsonSchemaFormat2["UUID"] = "uuid";
|
|
193
|
+
return JsonSchemaFormat2;
|
|
194
|
+
})(JsonSchemaFormat || {});
|
|
141
195
|
var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
|
|
142
196
|
JsonSchemaXNativeType2["BigInt"] = "bigint";
|
|
143
|
-
JsonSchemaXNativeType2["RegExp"] = "regexp";
|
|
144
197
|
JsonSchemaXNativeType2["Date"] = "date";
|
|
145
198
|
JsonSchemaXNativeType2["Url"] = "url";
|
|
146
199
|
JsonSchemaXNativeType2["Set"] = "set";
|
|
@@ -148,7 +201,12 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
|
|
|
148
201
|
return JsonSchemaXNativeType2;
|
|
149
202
|
})(JsonSchemaXNativeType || {});
|
|
150
203
|
|
|
151
|
-
const
|
|
204
|
+
const UNSATISFIED = 0;
|
|
205
|
+
const LOOSELY_SATISFIED = 1;
|
|
206
|
+
const SATISFIED = 2;
|
|
207
|
+
function minSatisfaction(a, b) {
|
|
208
|
+
return a < b ? a : b;
|
|
209
|
+
}
|
|
152
210
|
class JsonSchemaCoercer {
|
|
153
211
|
coerce([schema, optional], value) {
|
|
154
212
|
if (optional && value === void 0) {
|
|
@@ -157,25 +215,29 @@ class JsonSchemaCoercer {
|
|
|
157
215
|
const [, coerced] = this.coerceInternal(schema, schema, value);
|
|
158
216
|
return coerced;
|
|
159
217
|
}
|
|
160
|
-
coerceInternal(rootSchema, schema, value) {
|
|
218
|
+
coerceInternal(rootSchema, schema, value, appliedRefs) {
|
|
161
219
|
if (typeof schema === "boolean") {
|
|
162
|
-
return [schema, value];
|
|
220
|
+
return [schema ? SATISFIED : UNSATISFIED, value];
|
|
163
221
|
}
|
|
164
222
|
if (Array.isArray(schema.type)) {
|
|
165
223
|
return this.coerceInternal(
|
|
166
224
|
rootSchema,
|
|
167
225
|
{ anyOf: schema.type.map((type) => ({ ...schema, type })) },
|
|
168
|
-
value
|
|
226
|
+
value,
|
|
227
|
+
appliedRefs
|
|
169
228
|
);
|
|
170
229
|
}
|
|
171
230
|
let coerced = value;
|
|
172
|
-
let satisfied =
|
|
231
|
+
let satisfied = SATISFIED;
|
|
173
232
|
if (typeof schema.$ref === "string") {
|
|
174
233
|
const resolved = schema.$ref.startsWith("#/") ? get(rootSchema, schema.$ref.slice("#/".length).split("/").map(decodeJsonPointerSegment)) : schema.$ref === "#" ? rootSchema : void 0;
|
|
175
|
-
if (resolved !== void 0) {
|
|
176
|
-
|
|
234
|
+
if (resolved !== void 0 && !appliedRefs?.has(resolved)) {
|
|
235
|
+
appliedRefs ??= /* @__PURE__ */ new Set();
|
|
236
|
+
appliedRefs.add(resolved);
|
|
237
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced, appliedRefs);
|
|
238
|
+
appliedRefs.delete(resolved);
|
|
177
239
|
coerced = subCoerced;
|
|
178
|
-
satisfied = subSatisfied;
|
|
240
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
179
241
|
}
|
|
180
242
|
}
|
|
181
243
|
const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
|
|
@@ -189,24 +251,24 @@ class JsonSchemaCoercer {
|
|
|
189
251
|
if (enumValues.includes(booleanValue)) {
|
|
190
252
|
coerced = booleanValue;
|
|
191
253
|
} else {
|
|
192
|
-
satisfied =
|
|
254
|
+
satisfied = UNSATISFIED;
|
|
193
255
|
}
|
|
194
256
|
}
|
|
195
257
|
} else {
|
|
196
|
-
satisfied =
|
|
258
|
+
satisfied = UNSATISFIED;
|
|
197
259
|
}
|
|
198
260
|
}
|
|
199
261
|
if (schema.type) {
|
|
200
262
|
switch (schema.type) {
|
|
201
263
|
case "null": {
|
|
202
264
|
if (coerced !== null) {
|
|
203
|
-
satisfied =
|
|
265
|
+
satisfied = UNSATISFIED;
|
|
204
266
|
}
|
|
205
267
|
break;
|
|
206
268
|
}
|
|
207
269
|
case "string": {
|
|
208
270
|
if (typeof coerced !== "string") {
|
|
209
|
-
satisfied =
|
|
271
|
+
satisfied = UNSATISFIED;
|
|
210
272
|
}
|
|
211
273
|
break;
|
|
212
274
|
}
|
|
@@ -215,7 +277,7 @@ class JsonSchemaCoercer {
|
|
|
215
277
|
coerced = stringToNumber(coerced);
|
|
216
278
|
}
|
|
217
279
|
if (typeof coerced !== "number") {
|
|
218
|
-
satisfied =
|
|
280
|
+
satisfied = UNSATISFIED;
|
|
219
281
|
}
|
|
220
282
|
break;
|
|
221
283
|
}
|
|
@@ -224,7 +286,7 @@ class JsonSchemaCoercer {
|
|
|
224
286
|
coerced = stringToInteger(coerced);
|
|
225
287
|
}
|
|
226
288
|
if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
|
|
227
|
-
satisfied =
|
|
289
|
+
satisfied = UNSATISFIED;
|
|
228
290
|
}
|
|
229
291
|
break;
|
|
230
292
|
}
|
|
@@ -233,7 +295,7 @@ class JsonSchemaCoercer {
|
|
|
233
295
|
coerced = stringToBoolean(coerced);
|
|
234
296
|
}
|
|
235
297
|
if (typeof coerced !== "boolean") {
|
|
236
|
-
satisfied =
|
|
298
|
+
satisfied = UNSATISFIED;
|
|
237
299
|
}
|
|
238
300
|
break;
|
|
239
301
|
}
|
|
@@ -245,26 +307,24 @@ class JsonSchemaCoercer {
|
|
|
245
307
|
const coercedItems = coerced.map((item, i) => {
|
|
246
308
|
const subSchema = prefixItemSchemas[i] ?? itemSchema;
|
|
247
309
|
if (subSchema === void 0) {
|
|
248
|
-
satisfied =
|
|
310
|
+
satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
|
|
249
311
|
return item;
|
|
250
312
|
}
|
|
251
313
|
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
|
|
252
|
-
|
|
253
|
-
satisfied = false;
|
|
254
|
-
}
|
|
314
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
255
315
|
if (subCoerced !== item) {
|
|
256
316
|
shouldUseCoercedItems = true;
|
|
257
317
|
}
|
|
258
318
|
return subCoerced;
|
|
259
319
|
});
|
|
260
320
|
if (coercedItems.length < prefixItemSchemas.length) {
|
|
261
|
-
satisfied =
|
|
321
|
+
satisfied = UNSATISFIED;
|
|
262
322
|
}
|
|
263
323
|
if (shouldUseCoercedItems) {
|
|
264
324
|
coerced = coercedItems;
|
|
265
325
|
}
|
|
266
326
|
} else {
|
|
267
|
-
satisfied =
|
|
327
|
+
satisfied = UNSATISFIED;
|
|
268
328
|
}
|
|
269
329
|
break;
|
|
270
330
|
}
|
|
@@ -274,35 +334,36 @@ class JsonSchemaCoercer {
|
|
|
274
334
|
}
|
|
275
335
|
if (isPlainObject(coerced)) {
|
|
276
336
|
let shouldUseCoercedItems = false;
|
|
277
|
-
const coercedItems = {};
|
|
278
|
-
const patternProperties = Object.entries(schema.patternProperties ?? {}).
|
|
279
|
-
|
|
337
|
+
const coercedItems = { ...coerced };
|
|
338
|
+
const patternProperties = Object.entries(schema.patternProperties ?? {}).flatMap(([key, value2]) => {
|
|
339
|
+
const pattern = tryOrUndefined(() => new RegExp(key));
|
|
340
|
+
return pattern ? [[pattern, value2]] : [];
|
|
341
|
+
});
|
|
342
|
+
const propertySchemas = schema.properties ?? {};
|
|
343
|
+
for (const key of Object.keys(coerced)) {
|
|
280
344
|
const value2 = coerced[key];
|
|
281
|
-
const subSchema =
|
|
282
|
-
if (value2
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
if (subCoerced !== value2) {
|
|
294
|
-
shouldUseCoercedItems = true;
|
|
345
|
+
const subSchema = getOwn(propertySchemas, key) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
|
|
346
|
+
if (value2 !== void 0 || schema.required?.includes(key)) {
|
|
347
|
+
if (subSchema === void 0) {
|
|
348
|
+
satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
|
|
349
|
+
} else {
|
|
350
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
|
|
351
|
+
coercedItems[key] = subCoerced;
|
|
352
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
353
|
+
if (subCoerced !== value2) {
|
|
354
|
+
shouldUseCoercedItems = true;
|
|
355
|
+
}
|
|
295
356
|
}
|
|
296
357
|
}
|
|
297
358
|
}
|
|
298
359
|
if (schema.required?.some((key) => !Object.hasOwn(coercedItems, key))) {
|
|
299
|
-
satisfied =
|
|
360
|
+
satisfied = UNSATISFIED;
|
|
300
361
|
}
|
|
301
362
|
if (shouldUseCoercedItems) {
|
|
302
363
|
coerced = coercedItems;
|
|
303
364
|
}
|
|
304
365
|
} else {
|
|
305
|
-
satisfied =
|
|
366
|
+
satisfied = UNSATISFIED;
|
|
306
367
|
}
|
|
307
368
|
break;
|
|
308
369
|
}
|
|
@@ -315,7 +376,7 @@ class JsonSchemaCoercer {
|
|
|
315
376
|
coerced = stringToDate(coerced);
|
|
316
377
|
}
|
|
317
378
|
if (!(coerced instanceof Date)) {
|
|
318
|
-
satisfied =
|
|
379
|
+
satisfied = UNSATISFIED;
|
|
319
380
|
}
|
|
320
381
|
break;
|
|
321
382
|
}
|
|
@@ -329,16 +390,7 @@ class JsonSchemaCoercer {
|
|
|
329
390
|
break;
|
|
330
391
|
}
|
|
331
392
|
if (typeof coerced !== "bigint") {
|
|
332
|
-
satisfied =
|
|
333
|
-
}
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
case JsonSchemaXNativeType.RegExp: {
|
|
337
|
-
if (typeof coerced === "string") {
|
|
338
|
-
coerced = stringToRegExp(coerced);
|
|
339
|
-
}
|
|
340
|
-
if (!(coerced instanceof RegExp)) {
|
|
341
|
-
satisfied = false;
|
|
393
|
+
satisfied = UNSATISFIED;
|
|
342
394
|
}
|
|
343
395
|
break;
|
|
344
396
|
}
|
|
@@ -347,7 +399,7 @@ class JsonSchemaCoercer {
|
|
|
347
399
|
coerced = stringToURL(coerced);
|
|
348
400
|
}
|
|
349
401
|
if (!(coerced instanceof URL)) {
|
|
350
|
-
satisfied =
|
|
402
|
+
satisfied = UNSATISFIED;
|
|
351
403
|
}
|
|
352
404
|
break;
|
|
353
405
|
}
|
|
@@ -356,7 +408,7 @@ class JsonSchemaCoercer {
|
|
|
356
408
|
coerced = arrayToSet(coerced);
|
|
357
409
|
}
|
|
358
410
|
if (!(coerced instanceof Set)) {
|
|
359
|
-
satisfied =
|
|
411
|
+
satisfied = UNSATISFIED;
|
|
360
412
|
}
|
|
361
413
|
break;
|
|
362
414
|
}
|
|
@@ -365,7 +417,7 @@ class JsonSchemaCoercer {
|
|
|
365
417
|
coerced = arrayToMap(coerced);
|
|
366
418
|
}
|
|
367
419
|
if (!(coerced instanceof Map)) {
|
|
368
|
-
satisfied =
|
|
420
|
+
satisfied = UNSATISFIED;
|
|
369
421
|
}
|
|
370
422
|
break;
|
|
371
423
|
}
|
|
@@ -373,54 +425,78 @@ class JsonSchemaCoercer {
|
|
|
373
425
|
}
|
|
374
426
|
if (schema.allOf) {
|
|
375
427
|
for (const subSchema of schema.allOf) {
|
|
376
|
-
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
|
|
428
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
|
|
377
429
|
coerced = subCoerced;
|
|
378
|
-
|
|
379
|
-
satisfied = false;
|
|
380
|
-
}
|
|
430
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
381
431
|
}
|
|
382
432
|
}
|
|
383
433
|
for (const key of ["anyOf", "oneOf"]) {
|
|
384
434
|
if (schema[key]) {
|
|
385
|
-
let
|
|
435
|
+
let best;
|
|
386
436
|
for (const subSchema of schema[key]) {
|
|
387
|
-
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
|
|
388
|
-
if (subSatisfied) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
437
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
|
|
438
|
+
if (subSatisfied === UNSATISFIED) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (subCoerced === coerced) {
|
|
442
|
+
best = { satisfied: subSatisfied, value: subCoerced };
|
|
443
|
+
break;
|
|
395
444
|
}
|
|
445
|
+
if (!best || subSatisfied > best.satisfied) {
|
|
446
|
+
best = { satisfied: subSatisfied, value: subCoerced };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (best) {
|
|
450
|
+
coerced = best.value;
|
|
451
|
+
satisfied = minSatisfaction(satisfied, best.satisfied);
|
|
452
|
+
} else {
|
|
453
|
+
satisfied = UNSATISFIED;
|
|
396
454
|
}
|
|
397
|
-
coerced = bestOptions ? bestOptions.coerced : coerced;
|
|
398
|
-
satisfied = bestOptions ? bestOptions.satisfied : false;
|
|
399
455
|
}
|
|
400
456
|
}
|
|
401
457
|
if (typeof schema.not !== "undefined") {
|
|
402
|
-
const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced);
|
|
403
|
-
if (notSatisfied) {
|
|
404
|
-
satisfied =
|
|
458
|
+
const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced, appliedRefs);
|
|
459
|
+
if (notSatisfied !== UNSATISFIED) {
|
|
460
|
+
satisfied = UNSATISFIED;
|
|
405
461
|
}
|
|
406
462
|
}
|
|
407
463
|
return [satisfied, coerced];
|
|
408
464
|
}
|
|
409
465
|
}
|
|
466
|
+
const NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
410
467
|
function stringToNumber(value) {
|
|
411
|
-
|
|
412
|
-
|
|
468
|
+
if (!NUMBER_PATTERN.test(value)) {
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
const num = Number(value);
|
|
472
|
+
if (num < Number.MIN_SAFE_INTEGER || num > Number.MAX_SAFE_INTEGER) {
|
|
413
473
|
return value;
|
|
414
474
|
}
|
|
415
475
|
return num;
|
|
416
476
|
}
|
|
477
|
+
const INTEGER_PATTERN = /^-?(?:0|[1-9]\d*)$/;
|
|
417
478
|
function stringToInteger(value) {
|
|
418
|
-
|
|
419
|
-
|
|
479
|
+
if (!INTEGER_PATTERN.test(value)) {
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
const num = Number(value);
|
|
483
|
+
if (!Number.isSafeInteger(num)) {
|
|
420
484
|
return value;
|
|
421
485
|
}
|
|
422
486
|
return num;
|
|
423
487
|
}
|
|
488
|
+
function stringToBigInt(value) {
|
|
489
|
+
if (!INTEGER_PATTERN.test(value)) {
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
return BigInt(value);
|
|
493
|
+
}
|
|
494
|
+
function numberToBigInt(value) {
|
|
495
|
+
if (!Number.isInteger(value)) {
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
return BigInt(value);
|
|
499
|
+
}
|
|
424
500
|
function stringToBoolean(value) {
|
|
425
501
|
const lower = value.toLowerCase();
|
|
426
502
|
if (lower === "false" || lower === "off") {
|
|
@@ -431,27 +507,17 @@ function stringToBoolean(value) {
|
|
|
431
507
|
}
|
|
432
508
|
return value;
|
|
433
509
|
}
|
|
434
|
-
|
|
435
|
-
return tryOrUndefined(() => BigInt(value)) ?? value;
|
|
436
|
-
}
|
|
437
|
-
function numberToBigInt(value) {
|
|
438
|
-
return tryOrUndefined(() => BigInt(value)) ?? value;
|
|
439
|
-
}
|
|
510
|
+
const DATE_TIME_PATTERN = /^[+-]?\d{4,6}-\d{1,2}-\d{1,2}(?:[T ].*)?$/;
|
|
440
511
|
function stringToDate(value) {
|
|
512
|
+
if (!DATE_TIME_PATTERN.test(value)) {
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
441
515
|
const date = new Date(value);
|
|
442
|
-
if (Number.isNaN(date.getTime())
|
|
516
|
+
if (Number.isNaN(date.getTime())) {
|
|
443
517
|
return value;
|
|
444
518
|
}
|
|
445
519
|
return date;
|
|
446
520
|
}
|
|
447
|
-
function stringToRegExp(value) {
|
|
448
|
-
const match = value.match(/^\/(.*)\/([a-z]*)$/);
|
|
449
|
-
if (match) {
|
|
450
|
-
const [, pattern, flags] = match;
|
|
451
|
-
return tryOrUndefined(() => new RegExp(pattern, flags)) ?? value;
|
|
452
|
-
}
|
|
453
|
-
return value;
|
|
454
|
-
}
|
|
455
521
|
function stringToURL(value) {
|
|
456
522
|
return tryOrUndefined(() => new URL(value)) ?? value;
|
|
457
523
|
}
|
|
@@ -513,148 +579,85 @@ function ensureJsonSchemaObject(schema) {
|
|
|
513
579
|
return schema;
|
|
514
580
|
}
|
|
515
581
|
|
|
582
|
+
function promoteJsonSchemaDefs(schema, mergedDefs, basePath) {
|
|
583
|
+
const { $defs, ...rootBody } = schema;
|
|
584
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
585
|
+
const mapRef = (ref) => {
|
|
586
|
+
if (ref === "#") {
|
|
587
|
+
return basePath;
|
|
588
|
+
}
|
|
589
|
+
if (ref.startsWith("#/$defs/")) {
|
|
590
|
+
const [encodedName, ...segments] = ref.slice("#/$defs/".length).split("/");
|
|
591
|
+
const newName = renameMap.get(decodeJsonPointerSegment(encodedName));
|
|
592
|
+
return newName === void 0 ? ref : ["#/$defs", encodeJsonPointerSegment(newName), ...segments].join("/");
|
|
593
|
+
}
|
|
594
|
+
if (ref.startsWith("#/") && get(rootBody, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
595
|
+
return `${basePath}/${ref.slice(2)}`;
|
|
596
|
+
}
|
|
597
|
+
return ref;
|
|
598
|
+
};
|
|
599
|
+
const defs = Object.entries($defs ?? {}).filter(([, def]) => def !== void 0);
|
|
600
|
+
if (defs.length > 0) {
|
|
601
|
+
const promoted = /* @__PURE__ */ new Map();
|
|
602
|
+
const reserved = /* @__PURE__ */ new Set([...mergedDefs.keys(), ...defs.map(([name]) => name)]);
|
|
603
|
+
for (let changed = true; changed; ) {
|
|
604
|
+
changed = false;
|
|
605
|
+
for (const [name, def] of defs) {
|
|
606
|
+
const body = mapJsonSchemaRefs(def, mapRef);
|
|
607
|
+
promoted.set(name, body);
|
|
608
|
+
if (renameMap.has(name) || !mergedDefs.has(name) || isDeepEqual(mergedDefs.get(name), body)) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
let counter = 2;
|
|
612
|
+
while (reserved.has(`${name}${counter}`)) {
|
|
613
|
+
counter++;
|
|
614
|
+
}
|
|
615
|
+
reserved.add(`${name}${counter}`);
|
|
616
|
+
renameMap.set(name, `${name}${counter}`);
|
|
617
|
+
changed = true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
for (const [name, body] of promoted) {
|
|
621
|
+
mergedDefs.set(renameMap.get(name) ?? name, body);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return mapJsonSchemaRefs(rootBody, mapRef);
|
|
625
|
+
}
|
|
516
626
|
function combineJsonSchemasWithComposition(keyword, schemas) {
|
|
517
627
|
if (schemas.length <= 1) {
|
|
518
628
|
return schemas[0] ?? true;
|
|
519
629
|
}
|
|
520
|
-
const mergedDefs =
|
|
630
|
+
const mergedDefs = /* @__PURE__ */ new Map();
|
|
521
631
|
const compositionBranches = [];
|
|
522
632
|
for (let i = 0; i < schemas.length; i++) {
|
|
523
633
|
const schema = schemas[i];
|
|
524
|
-
|
|
525
|
-
compositionBranches.push(schema);
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
const { $defs, ...rest } = schema;
|
|
529
|
-
const renameMap = {};
|
|
530
|
-
const promotedNames = /* @__PURE__ */ new Set();
|
|
531
|
-
if ($defs) {
|
|
532
|
-
for (const [name, def] of Object.entries($defs)) {
|
|
533
|
-
if (def === void 0)
|
|
534
|
-
continue;
|
|
535
|
-
promotedNames.add(name);
|
|
536
|
-
if (name in mergedDefs) {
|
|
537
|
-
if (isDeepEqual(mergedDefs[name], def)) {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
let counter = 2;
|
|
541
|
-
let newName = `${name}${counter}`;
|
|
542
|
-
while (newName in mergedDefs) {
|
|
543
|
-
counter++;
|
|
544
|
-
newName = `${name}${counter}`;
|
|
545
|
-
}
|
|
546
|
-
mergedDefs[newName] = def;
|
|
547
|
-
renameMap[name] = newName;
|
|
548
|
-
} else {
|
|
549
|
-
mergedDefs[name] = def;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
compositionBranches.push(mapJsonSchemaRefs(
|
|
554
|
-
rest,
|
|
555
|
-
(ref) => {
|
|
556
|
-
if (ref === "#") {
|
|
557
|
-
return `#/${keyword}/${i}`;
|
|
558
|
-
}
|
|
559
|
-
if (ref.startsWith("#/$defs/")) {
|
|
560
|
-
const afterPrefix = ref.slice("#/$defs/".length);
|
|
561
|
-
const slashIdx = afterPrefix.indexOf("/");
|
|
562
|
-
const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
|
|
563
|
-
const rest2 = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
|
|
564
|
-
const defName = decodeJsonPointerSegment(encodedSegment);
|
|
565
|
-
if (!promotedNames.has(defName)) {
|
|
566
|
-
return ref;
|
|
567
|
-
}
|
|
568
|
-
if (defName in renameMap) {
|
|
569
|
-
return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${rest2}`;
|
|
570
|
-
}
|
|
571
|
-
return ref;
|
|
572
|
-
}
|
|
573
|
-
if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
574
|
-
return `#/${keyword}/${i}/${ref.slice(2)}`;
|
|
575
|
-
}
|
|
576
|
-
return ref;
|
|
577
|
-
}
|
|
578
|
-
));
|
|
634
|
+
compositionBranches.push(typeof schema === "boolean" ? schema : promoteJsonSchemaDefs(schema, mergedDefs, `#/${keyword}/${i}`));
|
|
579
635
|
}
|
|
580
636
|
const result = { [keyword]: compositionBranches };
|
|
581
|
-
if (
|
|
582
|
-
result.$defs = mergedDefs;
|
|
637
|
+
if (mergedDefs.size > 0) {
|
|
638
|
+
result.$defs = Object.fromEntries(mergedDefs);
|
|
583
639
|
}
|
|
584
640
|
return result;
|
|
585
641
|
}
|
|
586
642
|
function combineJsonObjectSchemaEntries(entries) {
|
|
587
|
-
const properties =
|
|
643
|
+
const properties = /* @__PURE__ */ new Map();
|
|
588
644
|
const required = [];
|
|
589
|
-
const mergedDefs =
|
|
645
|
+
const mergedDefs = /* @__PURE__ */ new Map();
|
|
590
646
|
for (const [name, propertySchema, optional] of entries) {
|
|
591
647
|
if (!optional) {
|
|
592
648
|
required.push(name);
|
|
593
649
|
}
|
|
594
|
-
|
|
595
|
-
properties[name] = propertySchema;
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
const { $defs, ...rest } = propertySchema;
|
|
599
|
-
const renameMap = {};
|
|
600
|
-
const promotedNames = /* @__PURE__ */ new Set();
|
|
601
|
-
if ($defs) {
|
|
602
|
-
for (const [defName, def] of Object.entries($defs)) {
|
|
603
|
-
if (def === void 0) {
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
promotedNames.add(defName);
|
|
607
|
-
if (defName in mergedDefs) {
|
|
608
|
-
if (isDeepEqual(mergedDefs[defName], def)) {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
let counter = 2;
|
|
612
|
-
let newName = `${defName}${counter}`;
|
|
613
|
-
while (newName in mergedDefs) {
|
|
614
|
-
counter++;
|
|
615
|
-
newName = `${defName}${counter}`;
|
|
616
|
-
}
|
|
617
|
-
mergedDefs[newName] = def;
|
|
618
|
-
renameMap[defName] = newName;
|
|
619
|
-
} else {
|
|
620
|
-
mergedDefs[defName] = def;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const propertyPathPrefix = `#/properties/${encodeJsonPointerSegment(name)}`;
|
|
625
|
-
properties[name] = mapJsonSchemaRefs(rest, (ref) => {
|
|
626
|
-
if (ref.startsWith("#/$defs/")) {
|
|
627
|
-
const afterPrefix = ref.slice("#/$defs/".length);
|
|
628
|
-
const slashIdx = afterPrefix.indexOf("/");
|
|
629
|
-
const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
|
|
630
|
-
const refRest = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
|
|
631
|
-
const defName = decodeJsonPointerSegment(encodedSegment);
|
|
632
|
-
if (!promotedNames.has(defName)) {
|
|
633
|
-
return ref;
|
|
634
|
-
}
|
|
635
|
-
if (defName in renameMap) {
|
|
636
|
-
return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${refRest}`;
|
|
637
|
-
}
|
|
638
|
-
return ref;
|
|
639
|
-
}
|
|
640
|
-
if (ref === "#") {
|
|
641
|
-
return propertyPathPrefix;
|
|
642
|
-
}
|
|
643
|
-
if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
644
|
-
return `${propertyPathPrefix}/${ref.slice(2)}`;
|
|
645
|
-
}
|
|
646
|
-
return ref;
|
|
647
|
-
});
|
|
650
|
+
properties.set(name, typeof propertySchema === "boolean" ? propertySchema : promoteJsonSchemaDefs(propertySchema, mergedDefs, `#/properties/${encodeJsonPointerSegment(name)}`));
|
|
648
651
|
}
|
|
649
652
|
const schema = {
|
|
650
653
|
type: "object",
|
|
651
|
-
properties
|
|
654
|
+
properties: Object.fromEntries(properties)
|
|
652
655
|
};
|
|
653
656
|
if (required.length > 0) {
|
|
654
657
|
schema.required = required;
|
|
655
658
|
}
|
|
656
|
-
if (
|
|
657
|
-
schema.$defs = mergedDefs;
|
|
659
|
+
if (mergedDefs.size > 0) {
|
|
660
|
+
schema.$defs = Object.fromEntries(mergedDefs);
|
|
658
661
|
}
|
|
659
662
|
return schema;
|
|
660
663
|
}
|
|
@@ -774,7 +777,7 @@ function flattenJsonUnionSchemaInternal(schema, resolvingRefs) {
|
|
|
774
777
|
}
|
|
775
778
|
const resolved = resolveJsonSchemaRootLocalRef(schema);
|
|
776
779
|
if (resolved !== schema) {
|
|
777
|
-
const result = flattenJsonUnionSchemaInternal(resolved, resolvingRefs.add(schema.$ref));
|
|
780
|
+
const result = flattenJsonUnionSchemaInternal(resolved, new Set(resolvingRefs).add(schema.$ref));
|
|
778
781
|
if (result.length > 1) {
|
|
779
782
|
return result;
|
|
780
783
|
}
|
|
@@ -844,13 +847,14 @@ class DelegatingJsonSchemaConverter {
|
|
|
844
847
|
constructor(converters = []) {
|
|
845
848
|
this.converters = converters;
|
|
846
849
|
}
|
|
847
|
-
|
|
850
|
+
convert(schema, direction) {
|
|
848
851
|
for (const converter of this.converters) {
|
|
849
|
-
if (
|
|
852
|
+
if (converter.condition(schema, direction)) {
|
|
850
853
|
return converter.convert(schema, direction);
|
|
851
854
|
}
|
|
852
855
|
}
|
|
853
|
-
const
|
|
856
|
+
const result = schema?.["~standard"].validate(void 0);
|
|
857
|
+
const optional = result instanceof Promise ? false : !result?.issues?.length;
|
|
854
858
|
if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
|
|
855
859
|
try {
|
|
856
860
|
return [
|
|
@@ -907,23 +911,23 @@ class SmartCoercionHandlerPlugin {
|
|
|
907
911
|
return {
|
|
908
912
|
...options,
|
|
909
913
|
clientInterceptors: [
|
|
910
|
-
|
|
914
|
+
({ next, input, ...interceptorOptions }) => {
|
|
911
915
|
const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
|
|
912
916
|
if (!inputSchemas) {
|
|
913
917
|
return next();
|
|
914
918
|
}
|
|
915
|
-
const coercedInput =
|
|
919
|
+
const coercedInput = this.coerceValue(inputSchemas, input);
|
|
916
920
|
return next({ ...interceptorOptions, input: coercedInput });
|
|
917
921
|
},
|
|
918
922
|
...toArray(options.clientInterceptors)
|
|
919
923
|
]
|
|
920
924
|
};
|
|
921
925
|
}
|
|
922
|
-
|
|
926
|
+
coerceValue(schemas, value) {
|
|
923
927
|
for (const schema of schemas) {
|
|
924
928
|
let converted = this.cache.get(schema);
|
|
925
929
|
if (!converted) {
|
|
926
|
-
converted =
|
|
930
|
+
converted = this.converter.convert(schema, "input");
|
|
927
931
|
this.cache.set(schema, converted);
|
|
928
932
|
}
|
|
929
933
|
value = this.coercer.coerce(converted, value);
|
|
@@ -962,30 +966,30 @@ class SmartCoercionLinkPlugin {
|
|
|
962
966
|
if (!outputSchemas) {
|
|
963
967
|
return output;
|
|
964
968
|
}
|
|
965
|
-
const coercedOutput =
|
|
969
|
+
const coercedOutput = this.coerceValue(outputSchemas, output);
|
|
966
970
|
return coercedOutput;
|
|
967
971
|
} catch (error) {
|
|
968
972
|
if (!(error instanceof ORPCError) || !error.defined) {
|
|
969
973
|
throw error;
|
|
970
974
|
}
|
|
971
975
|
const errorMap = procedure["~orpc"].errorMap;
|
|
972
|
-
const dataSchema = errorMap
|
|
976
|
+
const dataSchema = getOwn(errorMap, error.code)?.data;
|
|
973
977
|
if (!dataSchema) {
|
|
974
978
|
throw error;
|
|
975
979
|
}
|
|
976
980
|
const cloned = cloneORPCError(error);
|
|
977
|
-
cloned.data =
|
|
981
|
+
cloned.data = this.coerceValue([dataSchema], cloned.data);
|
|
978
982
|
throw cloned;
|
|
979
983
|
}
|
|
980
984
|
}
|
|
981
985
|
]
|
|
982
986
|
};
|
|
983
987
|
}
|
|
984
|
-
|
|
988
|
+
coerceValue(schemas, value) {
|
|
985
989
|
for (const schema of schemas) {
|
|
986
990
|
let converted = this.cache.get(schema);
|
|
987
991
|
if (!converted) {
|
|
988
|
-
converted =
|
|
992
|
+
converted = this.converter.convert(schema, "output");
|
|
989
993
|
this.cache.set(schema, converted);
|
|
990
994
|
}
|
|
991
995
|
value = this.coercer.coerce(converted, value);
|
|
@@ -994,4 +998,4 @@ class SmartCoercionLinkPlugin {
|
|
|
994
998
|
}
|
|
995
999
|
}
|
|
996
1000
|
|
|
997
|
-
export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef };
|
|
1001
|
+
export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaFormat, JsonSchemaType, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
|