@orpc/json-schema 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,163 +1,5 @@
1
- import { get, isPlainObject, tryOrUndefined, toArray, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
2
- export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
3
- import { ORPCError, cloneORPCError } from '@orpc/client';
4
- import { getProcedureContractOrThrow } from '@orpc/contract';
5
-
6
- const JSON_SCHEMA_LOGIC_KEYWORDS = /* @__PURE__ */ new Set([
7
- "$dynamicRef",
8
- "$ref",
9
- "additionalItems",
10
- "additionalProperties",
11
- "allOf",
12
- "anyOf",
13
- "const",
14
- "contains",
15
- "contentEncoding",
16
- "contentMediaType",
17
- "contentSchema",
18
- "dependencies",
19
- "dependentRequired",
20
- "dependentSchemas",
21
- "else",
22
- "enum",
23
- "exclusiveMaximum",
24
- "exclusiveMinimum",
25
- "format",
26
- "if",
27
- "items",
28
- "maxContains",
29
- "maximum",
30
- "maxItems",
31
- "maxLength",
32
- "maxProperties",
33
- "minContains",
34
- "minimum",
35
- "minItems",
36
- "minLength",
37
- "minProperties",
38
- "multipleOf",
39
- "not",
40
- "oneOf",
41
- "pattern",
42
- "patternProperties",
43
- "prefixItems",
44
- "properties",
45
- "propertyNames",
46
- "required",
47
- "then",
48
- "type",
49
- "unevaluatedItems",
50
- "unevaluatedProperties",
51
- "uniqueItems"
52
- ]);
53
- const JSON_SCHEMA_PRIMITIVE_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
54
- const JSON_SCHEMA_RECORD_KEYWORDS = /* @__PURE__ */ new Set(["properties", "patternProperties", "dependentSchemas", "dependencies", "$defs"]);
55
-
56
- function encodeJsonPointerSegment(segment) {
57
- return segment.replaceAll("~", "~0").replaceAll("/", "~1");
58
- }
59
- function decodeJsonPointerSegment(segment) {
60
- return segment.replaceAll("~1", "/").replaceAll("~0", "~");
61
- }
62
- function visitJsonSchemaRefs(value, visit, schemaLevel = true, seen = /* @__PURE__ */ new Set()) {
63
- if (!value || typeof value !== "object" || seen.has(value)) {
64
- return;
65
- }
66
- seen.add(value);
67
- if (Array.isArray(value)) {
68
- for (const item of value) {
69
- visitJsonSchemaRefs(item, visit, schemaLevel, seen);
70
- }
71
- return;
72
- }
73
- for (const [key, val] of Object.entries(value)) {
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
- }
83
- function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
84
- if (!value || typeof value !== "object") {
85
- return value;
86
- }
87
- if (Array.isArray(value)) {
88
- return value.map((item, index) => mapJsonSchemaRefs(item, map, schemaLevel, [...path, index]));
89
- }
90
- const result = {};
91
- for (const [key, val] of Object.entries(value)) {
92
- if (key === "$ref" && typeof val === "string") {
93
- result[key] = map(val, [...path, key]);
94
- } else if (!schemaLevel) {
95
- result[key] = mapJsonSchemaRefs(val, map, true, [...path, key]);
96
- } else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
97
- result[key] = mapJsonSchemaRefs(val, map, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), [...path, key]);
98
- } else {
99
- result[key] = val;
100
- }
101
- }
102
- return result;
103
- }
104
- function hoistRecursiveRefToDef(schema) {
105
- if (typeof schema !== "object") {
106
- return schema;
107
- }
108
- let defName;
109
- const rewritten = mapJsonSchemaRefs(schema, (ref) => {
110
- if (ref === "#" || ref.startsWith("#/") && !ref.startsWith("#/$defs/") && get(schema, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
111
- defName ??= findRecursiveJsonSchemaDefName(schema.$defs);
112
- return `#/$defs/${encodeJsonPointerSegment(defName)}${ref.slice(1)}`;
113
- }
114
- return ref;
115
- });
116
- if (defName === void 0) {
117
- return schema;
118
- }
119
- const { $defs, ...rest } = rewritten;
120
- return {
121
- $ref: `#/$defs/${encodeJsonPointerSegment(defName)}`,
122
- $defs: {
123
- ...$defs,
124
- [defName]: rest
125
- }
126
- };
127
- }
128
- function resolveJsonSchemaRootLocalRef(schema, $defs) {
129
- if (typeof schema === "boolean") {
130
- return schema;
131
- }
132
- if (arguments.length === 1) {
133
- $defs = schema.$defs;
134
- }
135
- if (!$defs) {
136
- return schema;
137
- }
138
- if (typeof schema.$ref !== "string" || !schema.$ref.startsWith("#/$defs/")) {
139
- return schema;
140
- }
141
- const resolved = get($defs, schema.$ref.slice("#/$defs/".length).split("/").map(decodeJsonPointerSegment));
142
- if (resolved === void 0) {
143
- return schema;
144
- }
145
- if (typeof resolved !== "object") {
146
- return resolved;
147
- }
148
- const { $ref: _ref, ...rest } = schema;
149
- return resolveJsonSchemaRootLocalRef({
150
- ...rest,
151
- ...resolved
152
- });
153
- }
154
- function findRecursiveJsonSchemaDefName(defs) {
155
- let index = 0;
156
- while (defs?.[`__schema${index}`] !== void 0) {
157
- index++;
158
- }
159
- return `__schema${index}`;
160
- }
1
+ import { isObject, NullProtoObj, toArray, guard } from '@orpc/shared';
2
+ import { CompositeSchemaConverter } from '@orpc/openapi';
161
3
 
162
4
  var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
163
5
  JsonSchemaXNativeType2["BigInt"] = "bigint";
@@ -169,101 +11,87 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
169
11
  return JsonSchemaXNativeType2;
170
12
  })(JsonSchemaXNativeType || {});
171
13
 
172
- const UNSATISFIED = 0;
173
- const LOOSELY_SATISFIED = 1;
174
- const SATISFIED = 2;
175
- function minSatisfaction(a, b) {
176
- return a < b ? a : b;
177
- }
14
+ const FLEXIBLE_DATE_FORMAT_REGEX = /^[^-]+-[^-]+-[^-]+$/;
178
15
  class JsonSchemaCoercer {
179
- coerce([schema, optional], value) {
180
- if (optional && value === void 0) {
181
- return value;
182
- }
183
- const [, coerced] = this.coerceInternal(schema, schema, value);
16
+ coerce(schema, value, options = {}) {
17
+ const [, coerced] = this.#coerce(schema, value, options);
184
18
  return coerced;
185
19
  }
186
- coerceInternal(rootSchema, schema, value, appliedRefs) {
20
+ #coerce(schema, originalValue, options) {
187
21
  if (typeof schema === "boolean") {
188
- return [schema ? SATISFIED : UNSATISFIED, value];
22
+ return [schema, originalValue];
189
23
  }
190
24
  if (Array.isArray(schema.type)) {
191
- return this.coerceInternal(
192
- rootSchema,
193
- { anyOf: schema.type.map((type) => ({ ...schema, type })) },
194
- value,
195
- appliedRefs
196
- );
25
+ return this.#coerce({
26
+ anyOf: schema.type.map((type) => ({ ...schema, type }))
27
+ }, originalValue, options);
197
28
  }
198
- let coerced = value;
199
- let satisfied = SATISFIED;
29
+ let coerced = originalValue;
30
+ let satisfied = true;
200
31
  if (typeof schema.$ref === "string") {
201
- const resolved = schema.$ref.startsWith("#/") ? get(rootSchema, schema.$ref.slice("#/".length).split("/").map(decodeJsonPointerSegment)) : schema.$ref === "#" ? rootSchema : void 0;
202
- if (resolved !== void 0 && !appliedRefs?.has(resolved)) {
203
- appliedRefs ??= /* @__PURE__ */ new Set();
204
- appliedRefs.add(resolved);
205
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced, appliedRefs);
206
- appliedRefs.delete(resolved);
32
+ const refSchema = options?.components?.[schema.$ref];
33
+ if (refSchema !== void 0) {
34
+ const [subSatisfied, subCoerced] = this.#coerce(refSchema, coerced, options);
207
35
  coerced = subCoerced;
208
- satisfied = minSatisfaction(satisfied, subSatisfied);
36
+ satisfied = subSatisfied;
209
37
  }
210
38
  }
211
39
  const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
212
40
  if (enumValues !== void 0 && !enumValues.includes(coerced)) {
213
41
  if (typeof coerced === "string") {
214
- const numberValue = stringToNumber(coerced);
42
+ const numberValue = this.#stringToNumber(coerced);
215
43
  if (enumValues.includes(numberValue)) {
216
44
  coerced = numberValue;
217
45
  } else {
218
- const booleanValue = stringToBoolean(coerced);
46
+ const booleanValue = this.#stringToBoolean(coerced);
219
47
  if (enumValues.includes(booleanValue)) {
220
48
  coerced = booleanValue;
221
49
  } else {
222
- satisfied = UNSATISFIED;
50
+ satisfied = false;
223
51
  }
224
52
  }
225
53
  } else {
226
- satisfied = UNSATISFIED;
54
+ satisfied = false;
227
55
  }
228
56
  }
229
- if (schema.type) {
57
+ if (typeof schema.type === "string") {
230
58
  switch (schema.type) {
231
59
  case "null": {
232
60
  if (coerced !== null) {
233
- satisfied = UNSATISFIED;
61
+ satisfied = false;
234
62
  }
235
63
  break;
236
64
  }
237
65
  case "string": {
238
66
  if (typeof coerced !== "string") {
239
- satisfied = UNSATISFIED;
67
+ satisfied = false;
240
68
  }
241
69
  break;
242
70
  }
243
71
  case "number": {
244
72
  if (typeof coerced === "string") {
245
- coerced = stringToNumber(coerced);
73
+ coerced = this.#stringToNumber(coerced);
246
74
  }
247
75
  if (typeof coerced !== "number") {
248
- satisfied = UNSATISFIED;
76
+ satisfied = false;
249
77
  }
250
78
  break;
251
79
  }
252
80
  case "integer": {
253
81
  if (typeof coerced === "string") {
254
- coerced = stringToInteger(coerced);
82
+ coerced = this.#stringToInteger(coerced);
255
83
  }
256
84
  if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
257
- satisfied = UNSATISFIED;
85
+ satisfied = false;
258
86
  }
259
87
  break;
260
88
  }
261
89
  case "boolean": {
262
90
  if (typeof coerced === "string") {
263
- coerced = stringToBoolean(coerced);
91
+ coerced = this.#stringToBoolean(coerced);
264
92
  }
265
93
  if (typeof coerced !== "boolean") {
266
- satisfied = UNSATISFIED;
94
+ satisfied = false;
267
95
  }
268
96
  break;
269
97
  }
@@ -275,24 +103,26 @@ class JsonSchemaCoercer {
275
103
  const coercedItems = coerced.map((item, i) => {
276
104
  const subSchema = prefixItemSchemas[i] ?? itemSchema;
277
105
  if (subSchema === void 0) {
278
- satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
106
+ satisfied = false;
279
107
  return item;
280
108
  }
281
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
282
- satisfied = minSatisfaction(satisfied, subSatisfied);
109
+ const [subSatisfied, subCoerced] = this.#coerce(subSchema, item, options);
110
+ if (!subSatisfied) {
111
+ satisfied = false;
112
+ }
283
113
  if (subCoerced !== item) {
284
114
  shouldUseCoercedItems = true;
285
115
  }
286
116
  return subCoerced;
287
117
  });
288
118
  if (coercedItems.length < prefixItemSchemas.length) {
289
- satisfied = UNSATISFIED;
119
+ satisfied = false;
290
120
  }
291
121
  if (shouldUseCoercedItems) {
292
122
  coerced = coercedItems;
293
123
  }
294
124
  } else {
295
- satisfied = UNSATISFIED;
125
+ satisfied = false;
296
126
  }
297
127
  break;
298
128
  }
@@ -300,38 +130,37 @@ class JsonSchemaCoercer {
300
130
  if (Array.isArray(coerced)) {
301
131
  coerced = { ...coerced };
302
132
  }
303
- if (isPlainObject(coerced)) {
133
+ if (isObject(coerced)) {
304
134
  let shouldUseCoercedItems = false;
305
- const coercedItems = { ...coerced };
306
- const patternProperties = Object.entries(schema.patternProperties ?? {}).flatMap(([key, value2]) => {
307
- const pattern = tryOrUndefined(() => new RegExp(key));
308
- return pattern ? [[pattern, value2]] : [];
309
- });
310
- const propertySchemas = schema.properties;
135
+ const coercedItems = new NullProtoObj();
136
+ const patternProperties = Object.entries(schema.patternProperties ?? {}).map(([key, value]) => [new RegExp(key), value]);
311
137
  for (const key in coerced) {
312
- const value2 = coerced[key];
313
- const subSchema = (propertySchemas !== void 0 && Object.hasOwn(propertySchemas, key) ? propertySchemas[key] : void 0) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
314
- if (value2 !== void 0 || schema.required?.includes(key)) {
315
- if (subSchema === void 0) {
316
- satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
317
- } else {
318
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
319
- coercedItems[key] = subCoerced;
320
- satisfied = minSatisfaction(satisfied, subSatisfied);
321
- if (subCoerced !== value2) {
322
- shouldUseCoercedItems = true;
323
- }
138
+ const value = coerced[key];
139
+ const subSchema = (schema.properties !== void 0 && Object.hasOwn(schema.properties, key) ? schema.properties[key] : void 0) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
140
+ if (value === void 0 && !schema.required?.includes(key)) {
141
+ coercedItems[key] = value;
142
+ } else if (subSchema === void 0) {
143
+ coercedItems[key] = value;
144
+ satisfied = false;
145
+ } else {
146
+ const [subSatisfied, subCoerced] = this.#coerce(subSchema, value, options);
147
+ coercedItems[key] = subCoerced;
148
+ if (!subSatisfied) {
149
+ satisfied = false;
150
+ }
151
+ if (subCoerced !== value) {
152
+ shouldUseCoercedItems = true;
324
153
  }
325
154
  }
326
155
  }
327
156
  if (schema.required?.some((key) => !Object.hasOwn(coercedItems, key))) {
328
- satisfied = UNSATISFIED;
157
+ satisfied = false;
329
158
  }
330
159
  if (shouldUseCoercedItems) {
331
160
  coerced = coercedItems;
332
161
  }
333
162
  } else {
334
- satisfied = UNSATISFIED;
163
+ satisfied = false;
335
164
  }
336
165
  break;
337
166
  }
@@ -341,60 +170,60 @@ class JsonSchemaCoercer {
341
170
  switch (schema["x-native-type"]) {
342
171
  case JsonSchemaXNativeType.Date: {
343
172
  if (typeof coerced === "string") {
344
- coerced = stringToDate(coerced);
173
+ coerced = this.#stringToDate(coerced);
345
174
  }
346
175
  if (!(coerced instanceof Date)) {
347
- satisfied = UNSATISFIED;
176
+ satisfied = false;
348
177
  }
349
178
  break;
350
179
  }
351
180
  case JsonSchemaXNativeType.BigInt: {
352
181
  switch (typeof coerced) {
353
182
  case "string":
354
- coerced = stringToBigInt(coerced);
183
+ coerced = this.#stringToBigInt(coerced);
355
184
  break;
356
185
  case "number":
357
- coerced = numberToBigInt(coerced);
186
+ coerced = this.#numberToBigInt(coerced);
358
187
  break;
359
188
  }
360
189
  if (typeof coerced !== "bigint") {
361
- satisfied = UNSATISFIED;
190
+ satisfied = false;
362
191
  }
363
192
  break;
364
193
  }
365
194
  case JsonSchemaXNativeType.RegExp: {
366
195
  if (typeof coerced === "string") {
367
- coerced = stringToRegExp(coerced);
196
+ coerced = this.#stringToRegExp(coerced);
368
197
  }
369
198
  if (!(coerced instanceof RegExp)) {
370
- satisfied = UNSATISFIED;
199
+ satisfied = false;
371
200
  }
372
201
  break;
373
202
  }
374
203
  case JsonSchemaXNativeType.Url: {
375
204
  if (typeof coerced === "string") {
376
- coerced = stringToURL(coerced);
205
+ coerced = this.#stringToURL(coerced);
377
206
  }
378
207
  if (!(coerced instanceof URL)) {
379
- satisfied = UNSATISFIED;
208
+ satisfied = false;
380
209
  }
381
210
  break;
382
211
  }
383
212
  case JsonSchemaXNativeType.Set: {
384
213
  if (Array.isArray(coerced)) {
385
- coerced = arrayToSet(coerced);
214
+ coerced = this.#arrayToSet(coerced);
386
215
  }
387
216
  if (!(coerced instanceof Set)) {
388
- satisfied = UNSATISFIED;
217
+ satisfied = false;
389
218
  }
390
219
  break;
391
220
  }
392
221
  case JsonSchemaXNativeType.Map: {
393
222
  if (Array.isArray(coerced)) {
394
- coerced = arrayToMap(coerced);
223
+ coerced = this.#arrayToMap(coerced);
395
224
  }
396
225
  if (!(coerced instanceof Map)) {
397
- satisfied = UNSATISFIED;
226
+ satisfied = false;
398
227
  }
399
228
  break;
400
229
  }
@@ -402,649 +231,133 @@ class JsonSchemaCoercer {
402
231
  }
403
232
  if (schema.allOf) {
404
233
  for (const subSchema of schema.allOf) {
405
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
234
+ const [subSatisfied, subCoerced] = this.#coerce(subSchema, coerced, options);
406
235
  coerced = subCoerced;
407
- satisfied = minSatisfaction(satisfied, subSatisfied);
236
+ if (!subSatisfied) {
237
+ satisfied = false;
238
+ }
408
239
  }
409
240
  }
410
241
  for (const key of ["anyOf", "oneOf"]) {
411
242
  if (schema[key]) {
412
- let best;
243
+ let bestOptions;
413
244
  for (const subSchema of schema[key]) {
414
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
415
- if (subSatisfied === UNSATISFIED) {
416
- continue;
417
- }
418
- if (subCoerced === coerced) {
419
- best = { satisfied: subSatisfied, value: subCoerced };
420
- break;
421
- }
422
- if (!best || subSatisfied > best.satisfied) {
423
- best = { satisfied: subSatisfied, value: subCoerced };
245
+ const [subSatisfied, subCoerced] = this.#coerce(subSchema, coerced, options);
246
+ if (subSatisfied) {
247
+ if (!bestOptions || subCoerced === coerced) {
248
+ bestOptions = { coerced: subCoerced, satisfied: subSatisfied };
249
+ }
250
+ if (subCoerced === coerced) {
251
+ break;
252
+ }
424
253
  }
425
254
  }
426
- if (best) {
427
- coerced = best.value;
428
- satisfied = minSatisfaction(satisfied, best.satisfied);
429
- } else {
430
- satisfied = UNSATISFIED;
431
- }
255
+ coerced = bestOptions ? bestOptions.coerced : coerced;
256
+ satisfied = bestOptions ? bestOptions.satisfied : false;
432
257
  }
433
258
  }
434
259
  if (typeof schema.not !== "undefined") {
435
- const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced, appliedRefs);
436
- if (notSatisfied !== UNSATISFIED) {
437
- satisfied = UNSATISFIED;
260
+ const [notSatisfied] = this.#coerce(schema.not, coerced, options);
261
+ if (notSatisfied) {
262
+ satisfied = false;
438
263
  }
439
264
  }
440
265
  return [satisfied, coerced];
441
266
  }
442
- }
443
- const NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
444
- function stringToNumber(value) {
445
- if (!NUMBER_PATTERN.test(value)) {
446
- return value;
447
- }
448
- const num = Number(value);
449
- if (num < Number.MIN_SAFE_INTEGER || num > Number.MAX_SAFE_INTEGER) {
450
- return value;
451
- }
452
- return num;
453
- }
454
- const INTEGER_PATTERN = /^-?(?:0|[1-9]\d*)$/;
455
- function stringToInteger(value) {
456
- if (!INTEGER_PATTERN.test(value)) {
457
- return value;
458
- }
459
- const num = Number(value);
460
- if (!Number.isSafeInteger(num)) {
461
- return value;
462
- }
463
- return num;
464
- }
465
- function stringToBigInt(value) {
466
- if (!INTEGER_PATTERN.test(value)) {
467
- return value;
468
- }
469
- return BigInt(value);
470
- }
471
- function numberToBigInt(value) {
472
- if (!Number.isInteger(value)) {
473
- return value;
474
- }
475
- return BigInt(value);
476
- }
477
- function stringToBoolean(value) {
478
- const lower = value.toLowerCase();
479
- if (lower === "false" || lower === "off") {
480
- return false;
481
- }
482
- if (lower === "true" || lower === "on") {
483
- return true;
484
- }
485
- return value;
486
- }
487
- const DATE_TIME_PATTERN = /^[+-]?\d{4,6}-\d{1,2}-\d{1,2}(?:[T ].*)?$/;
488
- function stringToDate(value) {
489
- if (!DATE_TIME_PATTERN.test(value)) {
490
- return value;
491
- }
492
- const date = new Date(value);
493
- if (Number.isNaN(date.getTime())) {
494
- return value;
495
- }
496
- return date;
497
- }
498
- const REGEXP_PATTERN = /^\/([\s\S]*)\/([a-z]*)$/;
499
- function stringToRegExp(value) {
500
- const match = value.match(REGEXP_PATTERN);
501
- if (match) {
502
- const [, pattern, flags] = match;
503
- return tryOrUndefined(() => new RegExp(pattern, flags)) ?? value;
504
- }
505
- return value;
506
- }
507
- function stringToURL(value) {
508
- return tryOrUndefined(() => new URL(value)) ?? value;
509
- }
510
- function arrayToSet(value) {
511
- const set = new Set(value);
512
- if (set.size !== value.length) {
513
- return value;
514
- }
515
- return set;
516
- }
517
- function arrayToMap(value) {
518
- if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
519
- return value;
520
- }
521
- const result = new Map(value);
522
- if (result.size !== value.length) {
523
- return value;
524
- }
525
- return result;
526
- }
527
-
528
- function isJsonPrimitiveSchema(schema) {
529
- if (typeof schema === "boolean") {
530
- return false;
531
- }
532
- if (typeof schema.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(schema.type)) {
533
- return true;
534
- }
535
- if (schema.const !== void 0) {
536
- return true;
537
- }
538
- if (schema.enum !== void 0) {
539
- return true;
540
- }
541
- return false;
542
- }
543
- function isJsonFileSchema(schema) {
544
- return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
545
- }
546
- function isJsonObjectSchema(schema) {
547
- return typeof schema !== "boolean" && schema.type === "object";
548
- }
549
- function isJsonArraySchema(schema) {
550
- return typeof schema !== "boolean" && schema.type === "array";
551
- }
552
- function isUnconstrainedSchema(schema) {
553
- if (typeof schema === "boolean") {
554
- return schema;
555
- }
556
- if (Object.keys(schema).every((k) => !JSON_SCHEMA_LOGIC_KEYWORDS.has(k))) {
557
- return true;
558
- }
559
- return false;
560
- }
561
- function ensureJsonSchemaObject(schema) {
562
- if (typeof schema === "boolean") {
563
- return schema ? {} : { not: {} };
564
- }
565
- return schema;
566
- }
567
-
568
- function combineJsonSchemasWithComposition(keyword, schemas) {
569
- if (schemas.length <= 1) {
570
- return schemas[0] ?? true;
571
- }
572
- const mergedDefs = {};
573
- const compositionBranches = [];
574
- for (let i = 0; i < schemas.length; i++) {
575
- const schema = schemas[i];
576
- if (typeof schema === "boolean") {
577
- compositionBranches.push(schema);
578
- continue;
579
- }
580
- const { $defs, ...rest } = schema;
581
- const renameMap = {};
582
- const promotedNames = /* @__PURE__ */ new Set();
583
- if ($defs) {
584
- for (const [name, def] of Object.entries($defs)) {
585
- if (def === void 0)
586
- continue;
587
- promotedNames.add(name);
588
- if (name in mergedDefs) {
589
- if (isDeepEqual(mergedDefs[name], def)) {
590
- continue;
591
- }
592
- let counter = 2;
593
- let newName = `${name}${counter}`;
594
- while (newName in mergedDefs) {
595
- counter++;
596
- newName = `${name}${counter}`;
597
- }
598
- mergedDefs[newName] = def;
599
- renameMap[name] = newName;
600
- } else {
601
- mergedDefs[name] = def;
602
- }
603
- }
604
- }
605
- compositionBranches.push(mapJsonSchemaRefs(
606
- rest,
607
- (ref) => {
608
- if (ref === "#") {
609
- return `#/${keyword}/${i}`;
610
- }
611
- if (ref.startsWith("#/$defs/")) {
612
- const afterPrefix = ref.slice("#/$defs/".length);
613
- const slashIdx = afterPrefix.indexOf("/");
614
- const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
615
- const rest2 = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
616
- const defName = decodeJsonPointerSegment(encodedSegment);
617
- if (!promotedNames.has(defName)) {
618
- return ref;
619
- }
620
- if (defName in renameMap) {
621
- return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${rest2}`;
622
- }
623
- return ref;
624
- }
625
- if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
626
- return `#/${keyword}/${i}/${ref.slice(2)}`;
627
- }
628
- return ref;
629
- }
630
- ));
631
- }
632
- const result = { [keyword]: compositionBranches };
633
- if (Object.keys(mergedDefs).length > 0) {
634
- result.$defs = mergedDefs;
635
- }
636
- return result;
637
- }
638
- function combineJsonObjectSchemaEntries(entries) {
639
- const properties = {};
640
- const required = [];
641
- const mergedDefs = {};
642
- for (const [name, propertySchema, optional] of entries) {
643
- if (!optional) {
644
- required.push(name);
645
- }
646
- if (typeof propertySchema === "boolean") {
647
- properties[name] = propertySchema;
648
- continue;
649
- }
650
- const { $defs, ...rest } = propertySchema;
651
- const renameMap = {};
652
- const promotedNames = /* @__PURE__ */ new Set();
653
- if ($defs) {
654
- for (const [defName, def] of Object.entries($defs)) {
655
- if (def === void 0) {
656
- continue;
657
- }
658
- promotedNames.add(defName);
659
- if (defName in mergedDefs) {
660
- if (isDeepEqual(mergedDefs[defName], def)) {
661
- continue;
662
- }
663
- let counter = 2;
664
- let newName = `${defName}${counter}`;
665
- while (newName in mergedDefs) {
666
- counter++;
667
- newName = `${defName}${counter}`;
668
- }
669
- mergedDefs[newName] = def;
670
- renameMap[defName] = newName;
671
- } else {
672
- mergedDefs[defName] = def;
673
- }
674
- }
267
+ #stringToNumber(value) {
268
+ const num = Number.parseFloat(value);
269
+ if (Number.isNaN(num) || num !== Number(value)) {
270
+ return value;
675
271
  }
676
- const propertyPathPrefix = `#/properties/${encodeJsonPointerSegment(name)}`;
677
- properties[name] = mapJsonSchemaRefs(rest, (ref) => {
678
- if (ref.startsWith("#/$defs/")) {
679
- const afterPrefix = ref.slice("#/$defs/".length);
680
- const slashIdx = afterPrefix.indexOf("/");
681
- const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
682
- const refRest = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
683
- const defName = decodeJsonPointerSegment(encodedSegment);
684
- if (!promotedNames.has(defName)) {
685
- return ref;
686
- }
687
- if (defName in renameMap) {
688
- return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${refRest}`;
689
- }
690
- return ref;
691
- }
692
- if (ref === "#") {
693
- return propertyPathPrefix;
694
- }
695
- if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
696
- return `${propertyPathPrefix}/${ref.slice(2)}`;
697
- }
698
- return ref;
699
- });
272
+ return num;
700
273
  }
701
- const schema = {
702
- type: "object",
703
- properties
704
- };
705
- if (required.length > 0) {
706
- schema.required = required;
707
- }
708
- if (Object.keys(mergedDefs).length > 0) {
709
- schema.$defs = mergedDefs;
710
- }
711
- return schema;
712
- }
713
- function extractJsonObjectSchemaEntries(schema) {
714
- schema = hoistRecursiveRefToDef(schema);
715
- if (typeof schema !== "object") {
716
- return void 0;
717
- }
718
- const result = extractJsonObjectSchemaEntriesInternal(omit(schema, ["$defs"]), schema.$defs, /* @__PURE__ */ new Set());
719
- if (!result.objectLike) {
720
- return void 0;
721
- }
722
- return result.entries.map(([n, s, ...r]) => [n, withRootDefs(s, schema.$defs), ...r]);
723
- }
724
- function extractJsonObjectSchemaEntriesInternal(schema, $defs, resolvingRefs) {
725
- if (typeof schema !== "object") {
726
- return { entries: [], objectLike: false };
727
- }
728
- if (typeof schema.$ref === "string") {
729
- if (resolvingRefs.has(schema.$ref)) {
730
- return { entries: [], objectLike: true };
731
- }
732
- const resolved = resolveJsonSchemaRootLocalRef(schema, $defs);
733
- if (resolved !== schema) {
734
- return extractJsonObjectSchemaEntriesInternal(resolved, $defs, new Set(resolvingRefs).add(schema.$ref));
274
+ #stringToInteger(value) {
275
+ const num = Number.parseInt(value);
276
+ if (Number.isNaN(num) || num !== Number(value)) {
277
+ return value;
735
278
  }
279
+ return num;
736
280
  }
737
- const sources = [];
738
- if (schema.properties) {
739
- sources.push({
740
- entries: Object.entries(schema.properties).map(([name, propertySchema]) => {
741
- return [
742
- name,
743
- propertySchema,
744
- !schema.required?.includes(name)
745
- ];
746
- }),
747
- source: "direct"
748
- });
749
- }
750
- let objectLike = schema.type === "object" || schema.properties !== void 0 || schema.required !== void 0 || schema.additionalProperties !== void 0;
751
- for (const keyword of ["anyOf", "oneOf", "allOf"]) {
752
- const branches = schema[keyword];
753
- if (branches === void 0) {
754
- continue;
755
- }
756
- const branchResults = branches.map((branch) => extractJsonObjectSchemaEntriesInternal(branch, $defs, resolvingRefs));
757
- if (branchResults.some((result) => !result.objectLike)) {
758
- return { entries: [], objectLike: false };
281
+ #stringToBoolean(value) {
282
+ const lower = value.toLowerCase();
283
+ if (lower === "false" || lower === "off") {
284
+ return false;
759
285
  }
760
- const entriesByName = /* @__PURE__ */ new Map();
761
- for (const result of branchResults) {
762
- for (const entry of result.entries) {
763
- const entries = entriesByName.get(entry[0]);
764
- if (entries) {
765
- entries.push(entry);
766
- } else {
767
- entriesByName.set(entry[0], [entry]);
768
- }
769
- }
286
+ if (lower === "true" || lower === "on") {
287
+ return true;
770
288
  }
771
- objectLike = true;
772
- sources.push({
773
- entries: Array.from(entriesByName.entries()).map(([name, entries]) => {
774
- const schemas = deduplicateJsonSchemas(entries.map((entry) => entry[1]));
775
- const required = keyword === "allOf" ? entries.some((entry) => !entry[2]) : branchResults.every((result) => {
776
- const entry = result.entries.find((item) => item[0] === name);
777
- return entry !== void 0 && !entry[2];
778
- });
779
- return [
780
- name,
781
- schemas.length === 1 ? schemas[0] : keyword === "allOf" ? { allOf: schemas } : { anyOf: schemas },
782
- !required
783
- ];
784
- }),
785
- source: keyword
786
- });
289
+ return value;
787
290
  }
788
- const sourceEntries = /* @__PURE__ */ new Map();
789
- for (const { entries, source } of sources) {
790
- for (const entry of entries) {
791
- const existing = sourceEntries.get(entry[0]);
792
- if (existing) {
793
- existing[source] = entry;
794
- } else {
795
- sourceEntries.set(entry[0], { [source]: entry });
796
- }
797
- }
291
+ #stringToBigInt(value) {
292
+ return guard(() => BigInt(value)) ?? value;
798
293
  }
799
- return {
800
- entries: Array.from(sourceEntries.entries()).map(([name, entries]) => {
801
- const schemas = deduplicateJsonSchemas([
802
- entries.direct?.[1],
803
- entries.allOf?.[1],
804
- entries.anyOf?.[1],
805
- entries.oneOf?.[1]
806
- ].filter((schema2) => schema2 !== void 0));
807
- return [
808
- name,
809
- schemas.length === 1 ? schemas[0] : { allOf: schemas },
810
- [entries.direct, entries.allOf, entries.anyOf, entries.oneOf].every((entry) => entry === void 0 || entry[2])
811
- ];
812
- }),
813
- objectLike
814
- };
815
- }
816
- function flattenJsonUnionSchema(schema) {
817
- return deduplicateJsonSchemas(flattenJsonUnionSchemaInternal(schema, /* @__PURE__ */ new Set()));
818
- }
819
- function flattenJsonUnionSchemaInternal(schema, resolvingRefs) {
820
- if (typeof schema !== "object") {
821
- return [schema];
294
+ #numberToBigInt(value) {
295
+ return guard(() => BigInt(value)) ?? value;
822
296
  }
823
- if (typeof schema.$ref === "string") {
824
- if (resolvingRefs.has(schema.$ref)) {
825
- return [];
826
- }
827
- const resolved = resolveJsonSchemaRootLocalRef(schema);
828
- if (resolved !== schema) {
829
- const result = flattenJsonUnionSchemaInternal(resolved, resolvingRefs.add(schema.$ref));
830
- if (result.length > 1) {
831
- return result;
832
- }
297
+ #stringToDate(value) {
298
+ const date = new Date(value);
299
+ if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
300
+ return value;
833
301
  }
302
+ return date;
834
303
  }
835
- const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
836
- const entries = Object.entries(rest).filter(([, val]) => val !== void 0);
837
- for (const keyword of ["anyOf", "oneOf"]) {
838
- if (schema[keyword]) {
839
- return schema[keyword].flatMap((s) => {
840
- s = ensureJsonSchemaObject(s);
841
- const mergedSchema = {
842
- ...s,
843
- ...Object.fromEntries(entries.filter(([key]) => s[key] === void 0)),
844
- $defs: schema.$defs
845
- };
846
- const conflicts = entries.filter(([key]) => s[key] !== void 0);
847
- if (conflicts.length) {
848
- mergedSchema.allOf = [...toArray(mergedSchema.allOf), Object.fromEntries(conflicts)];
849
- }
850
- return flattenJsonUnionSchemaInternal(mergedSchema, resolvingRefs);
851
- });
304
+ #stringToRegExp(value) {
305
+ const match = value.match(/^\/(.*)\/([a-z]*)$/);
306
+ if (match) {
307
+ const [, pattern, flags] = match;
308
+ return guard(() => new RegExp(pattern, flags)) ?? value;
852
309
  }
310
+ return value;
853
311
  }
854
- return [schema];
855
- }
856
- function matchArrayableJsonSchema(schema) {
857
- const schemas = flattenJsonUnionSchema(schema);
858
- if (schemas.length !== 2) {
859
- return void 0;
860
- }
861
- const arraySchema = schemas.find(isJsonArraySchema);
862
- if (arraySchema === void 0) {
863
- return void 0;
864
- }
865
- const items1 = arraySchema.items ?? true;
866
- const items2 = schemas.find((s) => s !== arraySchema);
867
- const logicItem1 = Object.fromEntries(
868
- Object.entries(ensureJsonSchemaObject(items1)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
869
- );
870
- const logicItem2 = Object.fromEntries(
871
- Object.entries(ensureJsonSchemaObject(items2)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
872
- );
873
- if (!isDeepEqual(logicItem1, logicItem2)) {
874
- return void 0;
312
+ #stringToURL(value) {
313
+ return guard(() => new URL(value)) ?? value;
875
314
  }
876
- return [items2, arraySchema];
877
- }
878
- function deduplicateJsonSchemas(schemas) {
879
- const result = [];
880
- for (const schema of schemas) {
881
- if (result.some((i) => isDeepEqual(i, schema))) {
882
- continue;
315
+ #arrayToSet(value) {
316
+ const set = new Set(value);
317
+ if (set.size !== value.length) {
318
+ return value;
883
319
  }
884
- result.push(schema);
885
- }
886
- return result;
887
- }
888
- function withRootDefs(schema, $defs) {
889
- if (typeof schema === "boolean" || !$defs) {
890
- return schema;
320
+ return set;
891
321
  }
892
- return { ...schema, $defs };
893
- }
894
-
895
- class DelegatingJsonSchemaConverter {
896
- constructor(converters = []) {
897
- this.converters = converters;
898
- }
899
- convert(schema, direction) {
900
- for (const converter of this.converters) {
901
- if (converter.condition(schema, direction)) {
902
- return converter.convert(schema, direction);
903
- }
904
- }
905
- const result = schema?.["~standard"].validate(void 0);
906
- const optional = result instanceof Promise ? false : !result?.issues?.length;
907
- if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
908
- try {
909
- return [
910
- schema["~standard"].jsonSchema[direction](),
911
- optional
912
- ];
913
- } catch {
914
- }
322
+ #arrayToMap(value) {
323
+ if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
324
+ return value;
915
325
  }
916
- return [{}, optional];
917
- }
918
- }
919
-
920
- class StandardJsonSchemaConverter {
921
- condition(schema, _direction) {
922
- return Boolean(
923
- schema && "jsonSchema" in schema["~standard"] && isTypescriptObject(schema["~standard"].jsonSchema) && "input" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.input === "function" && "output" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.output === "function"
924
- );
925
- }
926
- convert(schema, direction) {
927
- return this.convertInternal(schema, direction);
928
- }
929
- convertInternal(schema, direction) {
930
- try {
931
- const jsonSchema = direction === "input" ? schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : schema["~standard"].jsonSchema.output({ target: "draft-2020-12" });
932
- let optional = false;
933
- try {
934
- const result = schema["~standard"].validate(void 0);
935
- if (!(result instanceof Promise) && !result.issues) {
936
- optional = direction === "input" ? true : result.value === void 0;
937
- }
938
- } catch {
939
- }
940
- return [jsonSchema, optional];
941
- } catch {
942
- return [{}, true];
326
+ const result = new Map(value);
327
+ if (result.size !== value.length) {
328
+ return value;
943
329
  }
330
+ return result;
944
331
  }
945
332
  }
946
333
 
947
- class SmartCoercionHandlerPlugin {
948
- name = "~smart-coercion";
334
+ class SmartCoercionPlugin {
949
335
  converter;
950
336
  coercer;
951
337
  cache = /* @__PURE__ */ new WeakMap();
952
338
  constructor(options = {}) {
953
- this.converter = new DelegatingJsonSchemaConverter([
954
- ...toArray(options.converters),
955
- new StandardJsonSchemaConverter()
956
- ]);
339
+ this.converter = new CompositeSchemaConverter(toArray(options.schemaConverters));
957
340
  this.coercer = new JsonSchemaCoercer();
958
341
  }
959
342
  init(options) {
960
- return {
961
- ...options,
962
- clientInterceptors: [
963
- ({ next, input, ...interceptorOptions }) => {
964
- const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
965
- if (!inputSchemas) {
966
- return next();
967
- }
968
- const coercedInput = this.coerceValue(inputSchemas, input);
969
- return next({ ...interceptorOptions, input: coercedInput });
970
- },
971
- ...toArray(options.clientInterceptors)
972
- ]
973
- };
974
- }
975
- coerceValue(schemas, value) {
976
- for (const schema of schemas) {
977
- let converted = this.cache.get(schema);
978
- if (!converted) {
979
- converted = this.converter.convert(schema, "input");
980
- this.cache.set(schema, converted);
343
+ options.clientInterceptors ??= [];
344
+ options.clientInterceptors.unshift(async (options2) => {
345
+ const inputSchema = options2.procedure["~orpc"].inputSchema;
346
+ if (!inputSchema) {
347
+ return options2.next();
981
348
  }
982
- value = this.coercer.coerce(converted, value);
983
- }
984
- return value;
985
- }
986
- }
987
-
988
- class SmartCoercionLinkPlugin {
989
- constructor(contract, options = {}) {
990
- this.contract = contract;
991
- this.converter = new DelegatingJsonSchemaConverter([
992
- ...toArray(options.converters),
993
- new StandardJsonSchemaConverter()
994
- ]);
995
- this.coercer = new JsonSchemaCoercer();
996
- }
997
- name = "~smart-coercion";
998
- /**
999
- * Output and error values should be coerced before validation.
1000
- */
1001
- after = ["~response-validation"];
1002
- converter;
1003
- coercer;
1004
- cache = /* @__PURE__ */ new WeakMap();
1005
- init(options) {
1006
- return {
1007
- ...options,
1008
- interceptors: [
1009
- ...toArray(options.interceptors),
1010
- async ({ next, path }) => {
1011
- const procedure = getProcedureContractOrThrow(this.contract, path);
1012
- try {
1013
- const output = await next();
1014
- const outputSchemas = procedure["~orpc"].outputSchemas;
1015
- if (!outputSchemas) {
1016
- return output;
1017
- }
1018
- const coercedOutput = this.coerceValue(outputSchemas, output);
1019
- return coercedOutput;
1020
- } catch (error) {
1021
- if (!(error instanceof ORPCError) || !error.defined) {
1022
- throw error;
1023
- }
1024
- const errorMap = procedure["~orpc"].errorMap;
1025
- const dataSchema = errorMap[error.code]?.data;
1026
- if (!dataSchema) {
1027
- throw error;
1028
- }
1029
- const cloned = cloneORPCError(error);
1030
- cloned.data = this.coerceValue([dataSchema], cloned.data);
1031
- throw cloned;
1032
- }
1033
- }
1034
- ]
1035
- };
349
+ const coercedInput = await this.#coerce(inputSchema, options2.input);
350
+ return options2.next({ ...options2, input: coercedInput });
351
+ });
1036
352
  }
1037
- coerceValue(schemas, value) {
1038
- for (const schema of schemas) {
1039
- let converted = this.cache.get(schema);
1040
- if (!converted) {
1041
- converted = this.converter.convert(schema, "output");
1042
- this.cache.set(schema, converted);
1043
- }
1044
- value = this.coercer.coerce(converted, value);
353
+ async #coerce(schema, value) {
354
+ let jsonSchema = this.cache.get(schema);
355
+ if (!jsonSchema) {
356
+ jsonSchema = (await this.converter.convert(schema, { strategy: "input" }))[1];
357
+ this.cache.set(schema, jsonSchema);
1045
358
  }
1046
- return value;
359
+ return this.coercer.coerce(jsonSchema, value);
1047
360
  }
1048
361
  }
1049
362
 
1050
- export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
363
+ export { JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionPlugin };