@orpc/json-schema 1.14.6 → 2.0.0-beta.10

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,5 +1,142 @@
1
- import { isObject, toArray, guard } from '@orpc/shared';
2
- import { CompositeSchemaConverter } from '@orpc/openapi';
1
+ import { get, isPlainObject, toArray, tryOrUndefined, 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 mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
63
+ if (!value || typeof value !== "object") {
64
+ return value;
65
+ }
66
+ if (Array.isArray(value)) {
67
+ return value.map((item, index) => mapJsonSchemaRefs(item, map, schemaLevel, [...path, index]));
68
+ }
69
+ const result = {};
70
+ for (const [key, val] of Object.entries(value)) {
71
+ if (key === "$ref" && typeof val === "string") {
72
+ result[key] = map(val, [...path, key]);
73
+ } else if (!schemaLevel) {
74
+ result[key] = mapJsonSchemaRefs(val, map, true, [...path, key]);
75
+ } else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
76
+ result[key] = mapJsonSchemaRefs(val, map, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), [...path, key]);
77
+ } else {
78
+ result[key] = val;
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ function hoistRecursiveRefToDef(schema) {
84
+ if (typeof schema !== "object") {
85
+ return schema;
86
+ }
87
+ let defName;
88
+ const rewritten = mapJsonSchemaRefs(schema, (ref) => {
89
+ if (ref === "#" || ref.startsWith("#/") && !ref.startsWith("#/$defs/") && get(schema, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
90
+ defName ??= findRecursiveJsonSchemaDefName(schema.$defs);
91
+ return `#/$defs/${encodeJsonPointerSegment(defName)}${ref.slice(1)}`;
92
+ }
93
+ return ref;
94
+ });
95
+ if (defName === void 0) {
96
+ return schema;
97
+ }
98
+ const { $defs, ...rest } = rewritten;
99
+ return {
100
+ $ref: `#/$defs/${encodeJsonPointerSegment(defName)}`,
101
+ $defs: {
102
+ ...$defs,
103
+ [defName]: rest
104
+ }
105
+ };
106
+ }
107
+ function resolveJsonSchemaRootLocalRef(schema, $defs) {
108
+ if (typeof schema === "boolean") {
109
+ return schema;
110
+ }
111
+ if (arguments.length === 1) {
112
+ $defs = schema.$defs;
113
+ }
114
+ if (!$defs) {
115
+ return schema;
116
+ }
117
+ if (typeof schema.$ref !== "string" || !schema.$ref.startsWith("#/$defs/")) {
118
+ return schema;
119
+ }
120
+ const resolved = get($defs, schema.$ref.slice("#/$defs/".length).split("/").map(decodeJsonPointerSegment));
121
+ if (resolved === void 0) {
122
+ return schema;
123
+ }
124
+ if (typeof resolved !== "object") {
125
+ return resolved;
126
+ }
127
+ const { $ref: _ref, ...rest } = schema;
128
+ return resolveJsonSchemaRootLocalRef({
129
+ ...rest,
130
+ ...resolved
131
+ });
132
+ }
133
+ function findRecursiveJsonSchemaDefName(defs) {
134
+ let index = 0;
135
+ while (defs?.[`__schema${index}`] !== void 0) {
136
+ index++;
137
+ }
138
+ return `__schema${index}`;
139
+ }
3
140
 
4
141
  var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
5
142
  JsonSchemaXNativeType2["BigInt"] = "bigint";
@@ -13,25 +150,30 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
13
150
 
14
151
  const FLEXIBLE_DATE_FORMAT_REGEX = /^[^-]+-[^-]+-[^-]+$/;
15
152
  class JsonSchemaCoercer {
16
- coerce(schema, value, options = {}) {
17
- const [, coerced] = this.#coerce(schema, value, options);
153
+ coerce([schema, optional], value) {
154
+ if (optional && value === void 0) {
155
+ return value;
156
+ }
157
+ const [, coerced] = this.coerceInternal(schema, schema, value);
18
158
  return coerced;
19
159
  }
20
- #coerce(schema, originalValue, options) {
160
+ coerceInternal(rootSchema, schema, value) {
21
161
  if (typeof schema === "boolean") {
22
- return [schema, originalValue];
162
+ return [schema, value];
23
163
  }
24
164
  if (Array.isArray(schema.type)) {
25
- return this.#coerce({
26
- anyOf: schema.type.map((type) => ({ ...schema, type }))
27
- }, originalValue, options);
165
+ return this.coerceInternal(
166
+ rootSchema,
167
+ { anyOf: schema.type.map((type) => ({ ...schema, type })) },
168
+ value
169
+ );
28
170
  }
29
- let coerced = originalValue;
171
+ let coerced = value;
30
172
  let satisfied = true;
31
173
  if (typeof schema.$ref === "string") {
32
- const refSchema = options?.components?.[schema.$ref];
33
- if (refSchema !== void 0) {
34
- const [subSatisfied, subCoerced] = this.#coerce(refSchema, coerced, options);
174
+ 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
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced);
35
177
  coerced = subCoerced;
36
178
  satisfied = subSatisfied;
37
179
  }
@@ -39,11 +181,11 @@ class JsonSchemaCoercer {
39
181
  const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
40
182
  if (enumValues !== void 0 && !enumValues.includes(coerced)) {
41
183
  if (typeof coerced === "string") {
42
- const numberValue = this.#stringToNumber(coerced);
184
+ const numberValue = stringToNumber(coerced);
43
185
  if (enumValues.includes(numberValue)) {
44
186
  coerced = numberValue;
45
187
  } else {
46
- const booleanValue = this.#stringToBoolean(coerced);
188
+ const booleanValue = stringToBoolean(coerced);
47
189
  if (enumValues.includes(booleanValue)) {
48
190
  coerced = booleanValue;
49
191
  } else {
@@ -54,7 +196,7 @@ class JsonSchemaCoercer {
54
196
  satisfied = false;
55
197
  }
56
198
  }
57
- if (typeof schema.type === "string") {
199
+ if (schema.type) {
58
200
  switch (schema.type) {
59
201
  case "null": {
60
202
  if (coerced !== null) {
@@ -70,7 +212,7 @@ class JsonSchemaCoercer {
70
212
  }
71
213
  case "number": {
72
214
  if (typeof coerced === "string") {
73
- coerced = this.#stringToNumber(coerced);
215
+ coerced = stringToNumber(coerced);
74
216
  }
75
217
  if (typeof coerced !== "number") {
76
218
  satisfied = false;
@@ -79,7 +221,7 @@ class JsonSchemaCoercer {
79
221
  }
80
222
  case "integer": {
81
223
  if (typeof coerced === "string") {
82
- coerced = this.#stringToInteger(coerced);
224
+ coerced = stringToInteger(coerced);
83
225
  }
84
226
  if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
85
227
  satisfied = false;
@@ -88,7 +230,7 @@ class JsonSchemaCoercer {
88
230
  }
89
231
  case "boolean": {
90
232
  if (typeof coerced === "string") {
91
- coerced = this.#stringToBoolean(coerced);
233
+ coerced = stringToBoolean(coerced);
92
234
  }
93
235
  if (typeof coerced !== "boolean") {
94
236
  satisfied = false;
@@ -106,7 +248,7 @@ class JsonSchemaCoercer {
106
248
  satisfied = false;
107
249
  return item;
108
250
  }
109
- const [subSatisfied, subCoerced] = this.#coerce(subSchema, item, options);
251
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
110
252
  if (!subSatisfied) {
111
253
  satisfied = false;
112
254
  }
@@ -130,25 +272,25 @@ class JsonSchemaCoercer {
130
272
  if (Array.isArray(coerced)) {
131
273
  coerced = { ...coerced };
132
274
  }
133
- if (isObject(coerced)) {
275
+ if (isPlainObject(coerced)) {
134
276
  let shouldUseCoercedItems = false;
135
277
  const coercedItems = {};
136
- const patternProperties = Object.entries(schema.patternProperties ?? {}).map(([key, value]) => [new RegExp(key), value]);
278
+ const patternProperties = Object.entries(schema.patternProperties ?? {}).map(([key, value2]) => [new RegExp(key), value2]);
137
279
  for (const key in coerced) {
138
- const value = coerced[key];
280
+ const value2 = coerced[key];
139
281
  const subSchema = schema.properties?.[key] ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
140
- if (value === void 0 && !schema.required?.includes(key)) {
141
- coercedItems[key] = value;
282
+ if (value2 === void 0 && !schema.required?.includes(key)) {
283
+ coercedItems[key] = value2;
142
284
  } else if (subSchema === void 0) {
143
- coercedItems[key] = value;
285
+ coercedItems[key] = value2;
144
286
  satisfied = false;
145
287
  } else {
146
- const [subSatisfied, subCoerced] = this.#coerce(subSchema, value, options);
288
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
147
289
  coercedItems[key] = subCoerced;
148
290
  if (!subSatisfied) {
149
291
  satisfied = false;
150
292
  }
151
- if (subCoerced !== value) {
293
+ if (subCoerced !== value2) {
152
294
  shouldUseCoercedItems = true;
153
295
  }
154
296
  }
@@ -170,7 +312,7 @@ class JsonSchemaCoercer {
170
312
  switch (schema["x-native-type"]) {
171
313
  case JsonSchemaXNativeType.Date: {
172
314
  if (typeof coerced === "string") {
173
- coerced = this.#stringToDate(coerced);
315
+ coerced = stringToDate(coerced);
174
316
  }
175
317
  if (!(coerced instanceof Date)) {
176
318
  satisfied = false;
@@ -180,10 +322,10 @@ class JsonSchemaCoercer {
180
322
  case JsonSchemaXNativeType.BigInt: {
181
323
  switch (typeof coerced) {
182
324
  case "string":
183
- coerced = this.#stringToBigInt(coerced);
325
+ coerced = stringToBigInt(coerced);
184
326
  break;
185
327
  case "number":
186
- coerced = this.#numberToBigInt(coerced);
328
+ coerced = numberToBigInt(coerced);
187
329
  break;
188
330
  }
189
331
  if (typeof coerced !== "bigint") {
@@ -193,7 +335,7 @@ class JsonSchemaCoercer {
193
335
  }
194
336
  case JsonSchemaXNativeType.RegExp: {
195
337
  if (typeof coerced === "string") {
196
- coerced = this.#stringToRegExp(coerced);
338
+ coerced = stringToRegExp(coerced);
197
339
  }
198
340
  if (!(coerced instanceof RegExp)) {
199
341
  satisfied = false;
@@ -202,7 +344,7 @@ class JsonSchemaCoercer {
202
344
  }
203
345
  case JsonSchemaXNativeType.Url: {
204
346
  if (typeof coerced === "string") {
205
- coerced = this.#stringToURL(coerced);
347
+ coerced = stringToURL(coerced);
206
348
  }
207
349
  if (!(coerced instanceof URL)) {
208
350
  satisfied = false;
@@ -211,7 +353,7 @@ class JsonSchemaCoercer {
211
353
  }
212
354
  case JsonSchemaXNativeType.Set: {
213
355
  if (Array.isArray(coerced)) {
214
- coerced = this.#arrayToSet(coerced);
356
+ coerced = arrayToSet(coerced);
215
357
  }
216
358
  if (!(coerced instanceof Set)) {
217
359
  satisfied = false;
@@ -220,7 +362,7 @@ class JsonSchemaCoercer {
220
362
  }
221
363
  case JsonSchemaXNativeType.Map: {
222
364
  if (Array.isArray(coerced)) {
223
- coerced = this.#arrayToMap(coerced);
365
+ coerced = arrayToMap(coerced);
224
366
  }
225
367
  if (!(coerced instanceof Map)) {
226
368
  satisfied = false;
@@ -231,7 +373,7 @@ class JsonSchemaCoercer {
231
373
  }
232
374
  if (schema.allOf) {
233
375
  for (const subSchema of schema.allOf) {
234
- const [subSatisfied, subCoerced] = this.#coerce(subSchema, coerced, options);
376
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
235
377
  coerced = subCoerced;
236
378
  if (!subSatisfied) {
237
379
  satisfied = false;
@@ -242,7 +384,7 @@ class JsonSchemaCoercer {
242
384
  if (schema[key]) {
243
385
  let bestOptions;
244
386
  for (const subSchema of schema[key]) {
245
- const [subSatisfied, subCoerced] = this.#coerce(subSchema, coerced, options);
387
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
246
388
  if (subSatisfied) {
247
389
  if (!bestOptions || subCoerced === coerced) {
248
390
  bestOptions = { coerced: subCoerced, satisfied: subSatisfied };
@@ -257,107 +399,599 @@ class JsonSchemaCoercer {
257
399
  }
258
400
  }
259
401
  if (typeof schema.not !== "undefined") {
260
- const [notSatisfied] = this.#coerce(schema.not, coerced, options);
402
+ const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced);
261
403
  if (notSatisfied) {
262
404
  satisfied = false;
263
405
  }
264
406
  }
265
407
  return [satisfied, coerced];
266
408
  }
267
- #stringToNumber(value) {
268
- const num = Number.parseFloat(value);
269
- if (Number.isNaN(num) || num !== Number(value)) {
270
- return value;
409
+ }
410
+ function stringToNumber(value) {
411
+ const num = Number.parseFloat(value);
412
+ if (Number.isNaN(num) || num !== Number(value)) {
413
+ return value;
414
+ }
415
+ return num;
416
+ }
417
+ function stringToInteger(value) {
418
+ const num = Number.parseInt(value);
419
+ if (Number.isNaN(num) || num !== Number(value)) {
420
+ return value;
421
+ }
422
+ return num;
423
+ }
424
+ function stringToBoolean(value) {
425
+ const lower = value.toLowerCase();
426
+ if (lower === "false" || lower === "off") {
427
+ return false;
428
+ }
429
+ if (lower === "true" || lower === "on") {
430
+ return true;
431
+ }
432
+ return value;
433
+ }
434
+ function stringToBigInt(value) {
435
+ return tryOrUndefined(() => BigInt(value)) ?? value;
436
+ }
437
+ function numberToBigInt(value) {
438
+ return tryOrUndefined(() => BigInt(value)) ?? value;
439
+ }
440
+ function stringToDate(value) {
441
+ const date = new Date(value);
442
+ if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
443
+ return value;
444
+ }
445
+ return date;
446
+ }
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
+ function stringToURL(value) {
456
+ return tryOrUndefined(() => new URL(value)) ?? value;
457
+ }
458
+ function arrayToSet(value) {
459
+ const set = new Set(value);
460
+ if (set.size !== value.length) {
461
+ return value;
462
+ }
463
+ return set;
464
+ }
465
+ function arrayToMap(value) {
466
+ if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
467
+ return value;
468
+ }
469
+ const result = new Map(value);
470
+ if (result.size !== value.length) {
471
+ return value;
472
+ }
473
+ return result;
474
+ }
475
+
476
+ function isJsonPrimitiveSchema(schema) {
477
+ if (typeof schema === "boolean") {
478
+ return false;
479
+ }
480
+ if (typeof schema.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(schema.type)) {
481
+ return true;
482
+ }
483
+ if (schema.const !== void 0) {
484
+ return true;
485
+ }
486
+ if (schema.enum !== void 0) {
487
+ return true;
488
+ }
489
+ return false;
490
+ }
491
+ function isJsonFileSchema(schema) {
492
+ return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
493
+ }
494
+ function isJsonObjectSchema(schema) {
495
+ return typeof schema !== "boolean" && schema.type === "object";
496
+ }
497
+ function isJsonArraySchema(schema) {
498
+ return typeof schema !== "boolean" && schema.type === "array";
499
+ }
500
+ function isUnconstrainedSchema(schema) {
501
+ if (typeof schema === "boolean") {
502
+ return schema;
503
+ }
504
+ if (Object.keys(schema).every((k) => !JSON_SCHEMA_LOGIC_KEYWORDS.has(k))) {
505
+ return true;
506
+ }
507
+ return false;
508
+ }
509
+ function ensureJsonSchemaObject(schema) {
510
+ if (typeof schema === "boolean") {
511
+ return schema ? {} : { not: {} };
512
+ }
513
+ return schema;
514
+ }
515
+
516
+ function combineJsonSchemasWithComposition(keyword, schemas) {
517
+ if (schemas.length <= 1) {
518
+ return schemas[0] ?? true;
519
+ }
520
+ const mergedDefs = {};
521
+ const compositionBranches = [];
522
+ for (let i = 0; i < schemas.length; i++) {
523
+ const schema = schemas[i];
524
+ if (typeof schema === "boolean") {
525
+ compositionBranches.push(schema);
526
+ continue;
271
527
  }
272
- return num;
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
+ ));
273
579
  }
274
- #stringToInteger(value) {
275
- const num = Number.parseInt(value);
276
- if (Number.isNaN(num) || num !== Number(value)) {
277
- return value;
580
+ const result = { [keyword]: compositionBranches };
581
+ if (Object.keys(mergedDefs).length > 0) {
582
+ result.$defs = mergedDefs;
583
+ }
584
+ return result;
585
+ }
586
+ function combineJsonObjectSchemaEntries(entries) {
587
+ const properties = {};
588
+ const required = [];
589
+ const mergedDefs = {};
590
+ for (const [name, propertySchema, optional] of entries) {
591
+ if (!optional) {
592
+ required.push(name);
278
593
  }
279
- return num;
594
+ if (typeof propertySchema === "boolean") {
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
+ });
280
648
  }
281
- #stringToBoolean(value) {
282
- const lower = value.toLowerCase();
283
- if (lower === "false" || lower === "off") {
284
- return false;
649
+ const schema = {
650
+ type: "object",
651
+ properties
652
+ };
653
+ if (required.length > 0) {
654
+ schema.required = required;
655
+ }
656
+ if (Object.keys(mergedDefs).length > 0) {
657
+ schema.$defs = mergedDefs;
658
+ }
659
+ return schema;
660
+ }
661
+ function extractJsonObjectSchemaEntries(schema) {
662
+ schema = hoistRecursiveRefToDef(schema);
663
+ if (typeof schema !== "object") {
664
+ return void 0;
665
+ }
666
+ const result = extractJsonObjectSchemaEntriesInternal(omit(schema, ["$defs"]), schema.$defs, /* @__PURE__ */ new Set());
667
+ if (!result.objectLike) {
668
+ return void 0;
669
+ }
670
+ return result.entries.map(([n, s, ...r]) => [n, withRootDefs(s, schema.$defs), ...r]);
671
+ }
672
+ function extractJsonObjectSchemaEntriesInternal(schema, $defs, resolvingRefs) {
673
+ if (typeof schema !== "object") {
674
+ return { entries: [], objectLike: false };
675
+ }
676
+ if (typeof schema.$ref === "string") {
677
+ if (resolvingRefs.has(schema.$ref)) {
678
+ return { entries: [], objectLike: true };
285
679
  }
286
- if (lower === "true" || lower === "on") {
287
- return true;
680
+ const resolved = resolveJsonSchemaRootLocalRef(schema, $defs);
681
+ if (resolved !== schema) {
682
+ return extractJsonObjectSchemaEntriesInternal(resolved, $defs, new Set(resolvingRefs).add(schema.$ref));
288
683
  }
289
- return value;
290
684
  }
291
- #stringToBigInt(value) {
292
- return guard(() => BigInt(value)) ?? value;
685
+ const sources = [];
686
+ if (schema.properties) {
687
+ sources.push({
688
+ entries: Object.entries(schema.properties).map(([name, propertySchema]) => {
689
+ return [
690
+ name,
691
+ propertySchema,
692
+ !schema.required?.includes(name)
693
+ ];
694
+ }),
695
+ source: "direct"
696
+ });
293
697
  }
294
- #numberToBigInt(value) {
295
- return guard(() => BigInt(value)) ?? value;
698
+ let objectLike = schema.type === "object" || schema.properties !== void 0 || schema.required !== void 0 || schema.additionalProperties !== void 0;
699
+ for (const keyword of ["anyOf", "oneOf", "allOf"]) {
700
+ const branches = schema[keyword];
701
+ if (branches === void 0) {
702
+ continue;
703
+ }
704
+ const branchResults = branches.map((branch) => extractJsonObjectSchemaEntriesInternal(branch, $defs, resolvingRefs));
705
+ if (branchResults.some((result) => !result.objectLike)) {
706
+ return { entries: [], objectLike: false };
707
+ }
708
+ const entriesByName = /* @__PURE__ */ new Map();
709
+ for (const result of branchResults) {
710
+ for (const entry of result.entries) {
711
+ const entries = entriesByName.get(entry[0]);
712
+ if (entries) {
713
+ entries.push(entry);
714
+ } else {
715
+ entriesByName.set(entry[0], [entry]);
716
+ }
717
+ }
718
+ }
719
+ objectLike = true;
720
+ sources.push({
721
+ entries: Array.from(entriesByName.entries()).map(([name, entries]) => {
722
+ const schemas = deduplicateJsonSchemas(entries.map((entry) => entry[1]));
723
+ const required = keyword === "allOf" ? entries.some((entry) => !entry[2]) : branchResults.every((result) => {
724
+ const entry = result.entries.find((item) => item[0] === name);
725
+ return entry !== void 0 && !entry[2];
726
+ });
727
+ return [
728
+ name,
729
+ schemas.length === 1 ? schemas[0] : keyword === "allOf" ? { allOf: schemas } : { anyOf: schemas },
730
+ !required
731
+ ];
732
+ }),
733
+ source: keyword
734
+ });
296
735
  }
297
- #stringToDate(value) {
298
- const date = new Date(value);
299
- if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
300
- return value;
736
+ const sourceEntries = /* @__PURE__ */ new Map();
737
+ for (const { entries, source } of sources) {
738
+ for (const entry of entries) {
739
+ const existing = sourceEntries.get(entry[0]);
740
+ if (existing) {
741
+ existing[source] = entry;
742
+ } else {
743
+ sourceEntries.set(entry[0], { [source]: entry });
744
+ }
301
745
  }
302
- return date;
303
746
  }
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;
747
+ return {
748
+ entries: Array.from(sourceEntries.entries()).map(([name, entries]) => {
749
+ const schemas = deduplicateJsonSchemas([
750
+ entries.direct?.[1],
751
+ entries.allOf?.[1],
752
+ entries.anyOf?.[1],
753
+ entries.oneOf?.[1]
754
+ ].filter((schema2) => schema2 !== void 0));
755
+ return [
756
+ name,
757
+ schemas.length === 1 ? schemas[0] : { allOf: schemas },
758
+ [entries.direct, entries.allOf, entries.anyOf, entries.oneOf].every((entry) => entry === void 0 || entry[2])
759
+ ];
760
+ }),
761
+ objectLike
762
+ };
763
+ }
764
+ function flattenJsonUnionSchema(schema) {
765
+ return deduplicateJsonSchemas(flattenJsonUnionSchemaInternal(schema, /* @__PURE__ */ new Set()));
766
+ }
767
+ function flattenJsonUnionSchemaInternal(schema, resolvingRefs) {
768
+ if (typeof schema !== "object") {
769
+ return [schema];
770
+ }
771
+ if (typeof schema.$ref === "string") {
772
+ if (resolvingRefs.has(schema.$ref)) {
773
+ return [];
774
+ }
775
+ const resolved = resolveJsonSchemaRootLocalRef(schema);
776
+ if (resolved !== schema) {
777
+ const result = flattenJsonUnionSchemaInternal(resolved, resolvingRefs.add(schema.$ref));
778
+ if (result.length > 1) {
779
+ return result;
780
+ }
309
781
  }
310
- return value;
311
782
  }
312
- #stringToURL(value) {
313
- return guard(() => new URL(value)) ?? value;
783
+ const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
784
+ const entries = Object.entries(rest).filter(([, val]) => val !== void 0);
785
+ for (const keyword of ["anyOf", "oneOf"]) {
786
+ if (schema[keyword]) {
787
+ return schema[keyword].flatMap((s) => {
788
+ s = ensureJsonSchemaObject(s);
789
+ const mergedSchema = {
790
+ ...s,
791
+ ...Object.fromEntries(entries.filter(([key]) => s[key] === void 0)),
792
+ $defs: schema.$defs
793
+ };
794
+ const conflicts = entries.filter(([key]) => s[key] !== void 0);
795
+ if (conflicts.length) {
796
+ mergedSchema.allOf = [...toArray(mergedSchema.allOf), Object.fromEntries(conflicts)];
797
+ }
798
+ return flattenJsonUnionSchemaInternal(mergedSchema, resolvingRefs);
799
+ });
800
+ }
314
801
  }
315
- #arrayToSet(value) {
316
- const set = new Set(value);
317
- if (set.size !== value.length) {
318
- return value;
802
+ return [schema];
803
+ }
804
+ function matchArrayableJsonSchema(schema) {
805
+ const schemas = flattenJsonUnionSchema(schema);
806
+ if (schemas.length !== 2) {
807
+ return void 0;
808
+ }
809
+ const arraySchema = schemas.find(isJsonArraySchema);
810
+ if (arraySchema === void 0) {
811
+ return void 0;
812
+ }
813
+ const items1 = arraySchema.items ?? true;
814
+ const items2 = schemas.find((s) => s !== arraySchema);
815
+ const logicItem1 = Object.fromEntries(
816
+ Object.entries(ensureJsonSchemaObject(items1)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
817
+ );
818
+ const logicItem2 = Object.fromEntries(
819
+ Object.entries(ensureJsonSchemaObject(items2)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
820
+ );
821
+ if (!isDeepEqual(logicItem1, logicItem2)) {
822
+ return void 0;
823
+ }
824
+ return [items2, arraySchema];
825
+ }
826
+ function deduplicateJsonSchemas(schemas) {
827
+ const result = [];
828
+ for (const schema of schemas) {
829
+ if (result.some((i) => isDeepEqual(i, schema))) {
830
+ continue;
319
831
  }
320
- return set;
832
+ result.push(schema);
321
833
  }
322
- #arrayToMap(value) {
323
- if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
324
- return value;
834
+ return result;
835
+ }
836
+ function withRootDefs(schema, $defs) {
837
+ if (typeof schema === "boolean" || !$defs) {
838
+ return schema;
839
+ }
840
+ return { ...schema, $defs };
841
+ }
842
+
843
+ class DelegatingJsonSchemaConverter {
844
+ constructor(converters = []) {
845
+ this.converters = converters;
846
+ }
847
+ async convert(schema, direction) {
848
+ for (const converter of this.converters) {
849
+ if (await converter.condition(schema, direction)) {
850
+ return converter.convert(schema, direction);
851
+ }
325
852
  }
326
- const result = new Map(value);
327
- if (result.size !== value.length) {
328
- return value;
853
+ const optional = !(await schema?.["~standard"].validate(void 0))?.issues?.length;
854
+ if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
855
+ try {
856
+ return [
857
+ schema["~standard"].jsonSchema[direction](),
858
+ optional
859
+ ];
860
+ } catch {
861
+ }
862
+ }
863
+ return [{}, optional];
864
+ }
865
+ }
866
+
867
+ class StandardJsonSchemaConverter {
868
+ condition(schema, _direction) {
869
+ return Boolean(
870
+ 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"
871
+ );
872
+ }
873
+ convert(schema, direction) {
874
+ return this.convertInternal(schema, direction);
875
+ }
876
+ convertInternal(schema, direction) {
877
+ try {
878
+ const jsonSchema = direction === "input" ? schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : schema["~standard"].jsonSchema.output({ target: "draft-2020-12" });
879
+ let optional = false;
880
+ try {
881
+ const result = schema["~standard"].validate(void 0);
882
+ if (!(result instanceof Promise) && !result.issues) {
883
+ optional = direction === "input" ? true : result.value === void 0;
884
+ }
885
+ } catch {
886
+ }
887
+ return [jsonSchema, optional];
888
+ } catch {
889
+ return [{}, true];
329
890
  }
330
- return result;
331
891
  }
332
892
  }
333
893
 
334
- class SmartCoercionPlugin {
894
+ class SmartCoercionHandlerPlugin {
895
+ name = "~smart-coercion";
335
896
  converter;
336
897
  coercer;
337
898
  cache = /* @__PURE__ */ new WeakMap();
338
899
  constructor(options = {}) {
339
- this.converter = new CompositeSchemaConverter(toArray(options.schemaConverters));
900
+ this.converter = new DelegatingJsonSchemaConverter([
901
+ ...toArray(options.converters),
902
+ new StandardJsonSchemaConverter()
903
+ ]);
340
904
  this.coercer = new JsonSchemaCoercer();
341
905
  }
342
906
  init(options) {
343
- options.clientInterceptors ??= [];
344
- options.clientInterceptors.unshift(async (options2) => {
345
- const inputSchema = options2.procedure["~orpc"].inputSchema;
346
- if (!inputSchema) {
347
- return options2.next();
907
+ return {
908
+ ...options,
909
+ clientInterceptors: [
910
+ async ({ next, input, ...interceptorOptions }) => {
911
+ const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
912
+ if (!inputSchemas) {
913
+ return next();
914
+ }
915
+ const coercedInput = await this.coerceValue(inputSchemas, input);
916
+ return next({ ...interceptorOptions, input: coercedInput });
917
+ },
918
+ ...toArray(options.clientInterceptors)
919
+ ]
920
+ };
921
+ }
922
+ async coerceValue(schemas, value) {
923
+ for (const schema of schemas) {
924
+ let converted = this.cache.get(schema);
925
+ if (!converted) {
926
+ converted = await this.converter.convert(schema, "input");
927
+ this.cache.set(schema, converted);
348
928
  }
349
- const coercedInput = await this.#coerce(inputSchema, options2.input);
350
- return options2.next({ ...options2, input: coercedInput });
351
- });
929
+ value = this.coercer.coerce(converted, value);
930
+ }
931
+ return value;
352
932
  }
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);
933
+ }
934
+
935
+ class SmartCoercionLinkPlugin {
936
+ constructor(contract, options = {}) {
937
+ this.contract = contract;
938
+ this.converter = new DelegatingJsonSchemaConverter([
939
+ ...toArray(options.converters),
940
+ new StandardJsonSchemaConverter()
941
+ ]);
942
+ this.coercer = new JsonSchemaCoercer();
943
+ }
944
+ name = "~smart-coercion";
945
+ /**
946
+ * Output and error values should be coerced before validation.
947
+ */
948
+ after = ["~response-validation"];
949
+ converter;
950
+ coercer;
951
+ cache = /* @__PURE__ */ new WeakMap();
952
+ init(options) {
953
+ return {
954
+ ...options,
955
+ interceptors: [
956
+ ...toArray(options.interceptors),
957
+ async ({ next, path }) => {
958
+ const procedure = getProcedureContractOrThrow(this.contract, path);
959
+ try {
960
+ const output = await next();
961
+ const outputSchemas = procedure["~orpc"].outputSchemas;
962
+ if (!outputSchemas) {
963
+ return output;
964
+ }
965
+ const coercedOutput = await this.coerceValue(outputSchemas, output);
966
+ return coercedOutput;
967
+ } catch (error) {
968
+ if (!(error instanceof ORPCError) || !error.defined) {
969
+ throw error;
970
+ }
971
+ const errorMap = procedure["~orpc"].errorMap;
972
+ const dataSchema = errorMap[error.code]?.data;
973
+ if (!dataSchema) {
974
+ throw error;
975
+ }
976
+ const cloned = cloneORPCError(error);
977
+ cloned.data = await this.coerceValue([dataSchema], cloned.data);
978
+ throw cloned;
979
+ }
980
+ }
981
+ ]
982
+ };
983
+ }
984
+ async coerceValue(schemas, value) {
985
+ for (const schema of schemas) {
986
+ let converted = this.cache.get(schema);
987
+ if (!converted) {
988
+ converted = await this.converter.convert(schema, "output");
989
+ this.cache.set(schema, converted);
990
+ }
991
+ value = this.coercer.coerce(converted, value);
358
992
  }
359
- return this.coercer.coerce(jsonSchema, value);
993
+ return value;
360
994
  }
361
995
  }
362
996
 
363
- export { JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionPlugin };
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 };