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

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,601 @@ 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;
271
- }
272
- return num;
409
+ }
410
+ function stringToNumber(value) {
411
+ const num = Number.parseFloat(value);
412
+ if (Number.isNaN(num) || num !== Number(value)) {
413
+ return value;
273
414
  }
274
- #stringToInteger(value) {
275
- const num = Number.parseInt(value);
276
- if (Number.isNaN(num) || num !== Number(value)) {
277
- return value;
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 isJsonFileSchema(schema) {
477
+ return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
478
+ }
479
+ function isJsonObjectSchema(schema) {
480
+ return typeof schema !== "boolean" && schema.type === "object";
481
+ }
482
+ function isJsonArraySchema(schema) {
483
+ return typeof schema !== "boolean" && schema.type === "array";
484
+ }
485
+ function isUnconstrainedSchema(schema) {
486
+ if (typeof schema === "boolean") {
487
+ return schema;
488
+ }
489
+ if (Object.keys(schema).every((k) => !JSON_SCHEMA_LOGIC_KEYWORDS.has(k))) {
490
+ return true;
491
+ }
492
+ return false;
493
+ }
494
+ function ensureJsonSchemaObject(schema) {
495
+ if (typeof schema === "boolean") {
496
+ return schema ? {} : { not: {} };
497
+ }
498
+ return schema;
499
+ }
500
+
501
+ function combineJsonSchemasWithComposition(keyword, schemas) {
502
+ if (schemas.length <= 1) {
503
+ return schemas[0] ?? true;
504
+ }
505
+ const mergedDefs = {};
506
+ const compositionBranches = [];
507
+ for (let i = 0; i < schemas.length; i++) {
508
+ const schema = schemas[i];
509
+ if (typeof schema === "boolean") {
510
+ compositionBranches.push(schema);
511
+ continue;
278
512
  }
279
- return num;
513
+ const { $defs, ...rest } = schema;
514
+ const renameMap = {};
515
+ const promotedNames = /* @__PURE__ */ new Set();
516
+ if ($defs) {
517
+ for (const [name, def] of Object.entries($defs)) {
518
+ if (def === void 0)
519
+ continue;
520
+ promotedNames.add(name);
521
+ if (name in mergedDefs) {
522
+ if (isDeepEqual(mergedDefs[name], def)) {
523
+ continue;
524
+ }
525
+ let counter = 2;
526
+ let newName = `${name}${counter}`;
527
+ while (newName in mergedDefs) {
528
+ counter++;
529
+ newName = `${name}${counter}`;
530
+ }
531
+ mergedDefs[newName] = def;
532
+ renameMap[name] = newName;
533
+ } else {
534
+ mergedDefs[name] = def;
535
+ }
536
+ }
537
+ }
538
+ compositionBranches.push(mapJsonSchemaRefs(
539
+ rest,
540
+ (ref) => {
541
+ if (ref === "#") {
542
+ return `#/${keyword}/${i}`;
543
+ }
544
+ if (ref.startsWith("#/$defs/")) {
545
+ const afterPrefix = ref.slice("#/$defs/".length);
546
+ const slashIdx = afterPrefix.indexOf("/");
547
+ const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
548
+ const rest2 = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
549
+ const defName = decodeJsonPointerSegment(encodedSegment);
550
+ if (!promotedNames.has(defName)) {
551
+ return ref;
552
+ }
553
+ if (defName in renameMap) {
554
+ return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${rest2}`;
555
+ }
556
+ return ref;
557
+ }
558
+ if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
559
+ return `#/${keyword}/${i}/${ref.slice(2)}`;
560
+ }
561
+ return ref;
562
+ }
563
+ ));
564
+ }
565
+ const result = { [keyword]: compositionBranches };
566
+ if (Object.keys(mergedDefs).length > 0) {
567
+ result.$defs = mergedDefs;
280
568
  }
281
- #stringToBoolean(value) {
282
- const lower = value.toLowerCase();
283
- if (lower === "false" || lower === "off") {
569
+ return result;
570
+ }
571
+ function isJsonPrimitiveSchema(schema) {
572
+ return flattenJsonUnionSchema(schema).every((s) => {
573
+ if (typeof s === "boolean") {
284
574
  return false;
285
575
  }
286
- if (lower === "true" || lower === "on") {
576
+ if (typeof s.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(s.type)) {
287
577
  return true;
288
578
  }
289
- return value;
579
+ if (s.const !== void 0) {
580
+ return true;
581
+ }
582
+ if (s.enum !== void 0) {
583
+ return true;
584
+ }
585
+ return false;
586
+ });
587
+ }
588
+ function combineJsonObjectSchemaEntries(entries) {
589
+ const properties = {};
590
+ const required = [];
591
+ const mergedDefs = {};
592
+ for (const [name, propertySchema, optional] of entries) {
593
+ if (!optional) {
594
+ required.push(name);
595
+ }
596
+ if (typeof propertySchema === "boolean") {
597
+ properties[name] = propertySchema;
598
+ continue;
599
+ }
600
+ const { $defs, ...rest } = propertySchema;
601
+ const renameMap = {};
602
+ const promotedNames = /* @__PURE__ */ new Set();
603
+ if ($defs) {
604
+ for (const [defName, def] of Object.entries($defs)) {
605
+ if (def === void 0) {
606
+ continue;
607
+ }
608
+ promotedNames.add(defName);
609
+ if (defName in mergedDefs) {
610
+ if (isDeepEqual(mergedDefs[defName], def)) {
611
+ continue;
612
+ }
613
+ let counter = 2;
614
+ let newName = `${defName}${counter}`;
615
+ while (newName in mergedDefs) {
616
+ counter++;
617
+ newName = `${defName}${counter}`;
618
+ }
619
+ mergedDefs[newName] = def;
620
+ renameMap[defName] = newName;
621
+ } else {
622
+ mergedDefs[defName] = def;
623
+ }
624
+ }
625
+ }
626
+ const propertyPathPrefix = `#/properties/${encodeJsonPointerSegment(name)}`;
627
+ properties[name] = mapJsonSchemaRefs(rest, (ref) => {
628
+ if (ref.startsWith("#/$defs/")) {
629
+ const afterPrefix = ref.slice("#/$defs/".length);
630
+ const slashIdx = afterPrefix.indexOf("/");
631
+ const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
632
+ const refRest = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
633
+ const defName = decodeJsonPointerSegment(encodedSegment);
634
+ if (!promotedNames.has(defName)) {
635
+ return ref;
636
+ }
637
+ if (defName in renameMap) {
638
+ return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${refRest}`;
639
+ }
640
+ return ref;
641
+ }
642
+ if (ref === "#") {
643
+ return propertyPathPrefix;
644
+ }
645
+ if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
646
+ return `${propertyPathPrefix}/${ref.slice(2)}`;
647
+ }
648
+ return ref;
649
+ });
290
650
  }
291
- #stringToBigInt(value) {
292
- return guard(() => BigInt(value)) ?? value;
651
+ const schema = {
652
+ type: "object",
653
+ properties
654
+ };
655
+ if (required.length > 0) {
656
+ schema.required = required;
293
657
  }
294
- #numberToBigInt(value) {
295
- return guard(() => BigInt(value)) ?? value;
658
+ if (Object.keys(mergedDefs).length > 0) {
659
+ schema.$defs = mergedDefs;
296
660
  }
297
- #stringToDate(value) {
298
- const date = new Date(value);
299
- if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
300
- return value;
661
+ return schema;
662
+ }
663
+ function extractJsonObjectSchemaEntries(schema) {
664
+ schema = hoistRecursiveRefToDef(schema);
665
+ if (typeof schema !== "object") {
666
+ return void 0;
667
+ }
668
+ const result = extractJsonObjectSchemaEntriesInternal(omit(schema, ["$defs"]), schema.$defs, /* @__PURE__ */ new Set());
669
+ if (!result.objectLike) {
670
+ return void 0;
671
+ }
672
+ return result.entries.map(([n, s, ...r]) => [n, withRootDefs(s, schema.$defs), ...r]);
673
+ }
674
+ function extractJsonObjectSchemaEntriesInternal(schema, $defs, resolvingRefs) {
675
+ if (typeof schema !== "object") {
676
+ return { entries: [], objectLike: false };
677
+ }
678
+ if (typeof schema.$ref === "string") {
679
+ if (resolvingRefs.has(schema.$ref)) {
680
+ return { entries: [], objectLike: true };
681
+ }
682
+ const resolved = resolveJsonSchemaRootLocalRef(schema, $defs);
683
+ if (resolved !== schema) {
684
+ return extractJsonObjectSchemaEntriesInternal(resolved, $defs, new Set(resolvingRefs).add(schema.$ref));
685
+ }
686
+ }
687
+ const sources = [];
688
+ if (schema.properties) {
689
+ sources.push({
690
+ entries: Object.entries(schema.properties).map(([name, propertySchema]) => {
691
+ return [
692
+ name,
693
+ propertySchema,
694
+ !schema.required?.includes(name)
695
+ ];
696
+ }),
697
+ source: "direct"
698
+ });
699
+ }
700
+ let objectLike = schema.type === "object" || schema.properties !== void 0 || schema.required !== void 0 || schema.additionalProperties !== void 0;
701
+ for (const keyword of ["anyOf", "oneOf", "allOf"]) {
702
+ const branches = schema[keyword];
703
+ if (branches === void 0) {
704
+ continue;
301
705
  }
302
- return date;
706
+ const branchResults = branches.map((branch) => extractJsonObjectSchemaEntriesInternal(branch, $defs, resolvingRefs));
707
+ if (branchResults.some((result) => !result.objectLike)) {
708
+ return { entries: [], objectLike: false };
709
+ }
710
+ const entriesByName = /* @__PURE__ */ new Map();
711
+ for (const result of branchResults) {
712
+ for (const entry of result.entries) {
713
+ const entries = entriesByName.get(entry[0]);
714
+ if (entries) {
715
+ entries.push(entry);
716
+ } else {
717
+ entriesByName.set(entry[0], [entry]);
718
+ }
719
+ }
720
+ }
721
+ objectLike = true;
722
+ sources.push({
723
+ entries: Array.from(entriesByName.entries()).map(([name, entries]) => {
724
+ const schemas = deduplicateJsonSchemas(entries.map((entry) => entry[1]));
725
+ const required = keyword === "allOf" ? entries.some((entry) => !entry[2]) : branchResults.every((result) => {
726
+ const entry = result.entries.find((item) => item[0] === name);
727
+ return entry !== void 0 && !entry[2];
728
+ });
729
+ return [
730
+ name,
731
+ schemas.length === 1 ? schemas[0] : keyword === "allOf" ? { allOf: schemas } : { anyOf: schemas },
732
+ !required
733
+ ];
734
+ }),
735
+ source: keyword
736
+ });
303
737
  }
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;
738
+ const sourceEntries = /* @__PURE__ */ new Map();
739
+ for (const { entries, source } of sources) {
740
+ for (const entry of entries) {
741
+ const existing = sourceEntries.get(entry[0]);
742
+ if (existing) {
743
+ existing[source] = entry;
744
+ } else {
745
+ sourceEntries.set(entry[0], { [source]: entry });
746
+ }
309
747
  }
310
- return value;
311
748
  }
312
- #stringToURL(value) {
313
- return guard(() => new URL(value)) ?? value;
749
+ return {
750
+ entries: Array.from(sourceEntries.entries()).map(([name, entries]) => {
751
+ const schemas = deduplicateJsonSchemas([
752
+ entries.direct?.[1],
753
+ entries.allOf?.[1],
754
+ entries.anyOf?.[1],
755
+ entries.oneOf?.[1]
756
+ ].filter((schema2) => schema2 !== void 0));
757
+ return [
758
+ name,
759
+ schemas.length === 1 ? schemas[0] : { allOf: schemas },
760
+ [entries.direct, entries.allOf, entries.anyOf, entries.oneOf].every((entry) => entry === void 0 || entry[2])
761
+ ];
762
+ }),
763
+ objectLike
764
+ };
765
+ }
766
+ function flattenJsonUnionSchema(schema) {
767
+ return deduplicateJsonSchemas(flattenJsonUnionSchemaInternal(schema, /* @__PURE__ */ new Set()));
768
+ }
769
+ function flattenJsonUnionSchemaInternal(schema, resolvingRefs) {
770
+ if (typeof schema !== "object") {
771
+ return [schema];
314
772
  }
315
- #arrayToSet(value) {
316
- const set = new Set(value);
317
- if (set.size !== value.length) {
318
- return value;
773
+ if (typeof schema.$ref === "string") {
774
+ if (resolvingRefs.has(schema.$ref)) {
775
+ return [];
776
+ }
777
+ const resolved = resolveJsonSchemaRootLocalRef(schema);
778
+ if (resolved !== schema) {
779
+ const result = flattenJsonUnionSchemaInternal(resolved, resolvingRefs.add(schema.$ref));
780
+ if (result.length > 1) {
781
+ return result;
782
+ }
319
783
  }
320
- return set;
321
784
  }
322
- #arrayToMap(value) {
323
- if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
324
- return value;
785
+ const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
786
+ const entries = Object.entries(rest).filter(([, val]) => val !== void 0);
787
+ for (const keyword of ["anyOf", "oneOf"]) {
788
+ if (schema[keyword]) {
789
+ return schema[keyword].flatMap((s) => {
790
+ s = ensureJsonSchemaObject(s);
791
+ const mergedSchema = {
792
+ ...s,
793
+ ...Object.fromEntries(entries.filter(([key]) => s[key] === void 0)),
794
+ $defs: schema.$defs
795
+ };
796
+ const conflicts = entries.filter(([key]) => s[key] !== void 0);
797
+ if (conflicts.length) {
798
+ mergedSchema.allOf = [...toArray(mergedSchema.allOf), Object.fromEntries(conflicts)];
799
+ }
800
+ return flattenJsonUnionSchemaInternal(mergedSchema, resolvingRefs);
801
+ });
325
802
  }
326
- const result = new Map(value);
327
- if (result.size !== value.length) {
328
- return value;
803
+ }
804
+ return [schema];
805
+ }
806
+ function matchArrayableJsonSchema(schema) {
807
+ const schemas = flattenJsonUnionSchema(schema);
808
+ if (schemas.length !== 2) {
809
+ return void 0;
810
+ }
811
+ const arraySchema = schemas.find(isJsonArraySchema);
812
+ if (arraySchema === void 0) {
813
+ return void 0;
814
+ }
815
+ const items1 = arraySchema.items ?? true;
816
+ const items2 = schemas.find((s) => s !== arraySchema);
817
+ const logicItem1 = Object.fromEntries(
818
+ Object.entries(ensureJsonSchemaObject(items1)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
819
+ );
820
+ const logicItem2 = Object.fromEntries(
821
+ Object.entries(ensureJsonSchemaObject(items2)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
822
+ );
823
+ if (!isDeepEqual(logicItem1, logicItem2)) {
824
+ return void 0;
825
+ }
826
+ return [items2, arraySchema];
827
+ }
828
+ function deduplicateJsonSchemas(schemas) {
829
+ const result = [];
830
+ for (const schema of schemas) {
831
+ if (result.some((i) => isDeepEqual(i, schema))) {
832
+ continue;
833
+ }
834
+ result.push(schema);
835
+ }
836
+ return result;
837
+ }
838
+ function withRootDefs(schema, $defs) {
839
+ if (typeof schema === "boolean" || !$defs) {
840
+ return schema;
841
+ }
842
+ return { ...schema, $defs };
843
+ }
844
+
845
+ class DelegatingJsonSchemaConverter {
846
+ constructor(converters = []) {
847
+ this.converters = converters;
848
+ }
849
+ async convert(schema, direction) {
850
+ for (const converter of this.converters) {
851
+ if (await converter.condition(schema, direction)) {
852
+ return converter.convert(schema, direction);
853
+ }
854
+ }
855
+ const optional = !(await schema?.["~standard"].validate(void 0))?.issues?.length;
856
+ if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
857
+ try {
858
+ return [
859
+ schema["~standard"].jsonSchema[direction](),
860
+ optional
861
+ ];
862
+ } catch {
863
+ }
864
+ }
865
+ return [{}, optional];
866
+ }
867
+ }
868
+
869
+ class StandardJsonSchemaConverter {
870
+ condition(schema, _direction) {
871
+ return Boolean(
872
+ 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"
873
+ );
874
+ }
875
+ convert(schema, direction) {
876
+ return this.convertInternal(schema, direction);
877
+ }
878
+ convertInternal(schema, direction) {
879
+ try {
880
+ const jsonSchema = direction === "input" ? schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : schema["~standard"].jsonSchema.output({ target: "draft-2020-12" });
881
+ let optional = false;
882
+ try {
883
+ const result = schema["~standard"].validate(void 0);
884
+ if (!(result instanceof Promise) && !result.issues) {
885
+ optional = direction === "input" ? true : result.value === void 0;
886
+ }
887
+ } catch {
888
+ }
889
+ return [jsonSchema, optional];
890
+ } catch {
891
+ return [{}, true];
329
892
  }
330
- return result;
331
893
  }
332
894
  }
333
895
 
334
- class SmartCoercionPlugin {
896
+ class SmartCoercionHandlerPlugin {
897
+ name = "~smart-coercion";
335
898
  converter;
336
899
  coercer;
337
900
  cache = /* @__PURE__ */ new WeakMap();
338
901
  constructor(options = {}) {
339
- this.converter = new CompositeSchemaConverter(toArray(options.schemaConverters));
902
+ this.converter = new DelegatingJsonSchemaConverter([
903
+ ...toArray(options.converters),
904
+ new StandardJsonSchemaConverter()
905
+ ]);
340
906
  this.coercer = new JsonSchemaCoercer();
341
907
  }
342
908
  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();
909
+ return {
910
+ ...options,
911
+ clientInterceptors: [
912
+ async ({ next, input, ...interceptorOptions }) => {
913
+ const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
914
+ if (!inputSchemas) {
915
+ return next();
916
+ }
917
+ const coercedInput = await this.coerceValue(inputSchemas, input);
918
+ return next({ ...interceptorOptions, input: coercedInput });
919
+ },
920
+ ...toArray(options.clientInterceptors)
921
+ ]
922
+ };
923
+ }
924
+ async coerceValue(schemas, value) {
925
+ for (const schema of schemas) {
926
+ let converted = this.cache.get(schema);
927
+ if (!converted) {
928
+ converted = await this.converter.convert(schema, "input");
929
+ this.cache.set(schema, converted);
348
930
  }
349
- const coercedInput = await this.#coerce(inputSchema, options2.input);
350
- return options2.next({ ...options2, input: coercedInput });
351
- });
931
+ value = this.coercer.coerce(converted, value);
932
+ }
933
+ return value;
352
934
  }
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);
935
+ }
936
+
937
+ class SmartCoercionLinkPlugin {
938
+ constructor(contract, options = {}) {
939
+ this.contract = contract;
940
+ this.converter = new DelegatingJsonSchemaConverter([
941
+ ...toArray(options.converters),
942
+ new StandardJsonSchemaConverter()
943
+ ]);
944
+ this.coercer = new JsonSchemaCoercer();
945
+ }
946
+ name = "~smart-coercion";
947
+ /**
948
+ * Output and error values should be coerced before validation.
949
+ */
950
+ after = ["~response-validation"];
951
+ converter;
952
+ coercer;
953
+ cache = /* @__PURE__ */ new WeakMap();
954
+ init(options) {
955
+ return {
956
+ ...options,
957
+ interceptors: [
958
+ ...toArray(options.interceptors),
959
+ async ({ next, path }) => {
960
+ const procedure = getProcedureContractOrThrow(this.contract, path);
961
+ try {
962
+ const output = await next();
963
+ const outputSchemas = procedure["~orpc"].outputSchemas;
964
+ if (!outputSchemas) {
965
+ return output;
966
+ }
967
+ const coercedOutput = await this.coerceValue(outputSchemas, output);
968
+ return coercedOutput;
969
+ } catch (error) {
970
+ if (!(error instanceof ORPCError) || !error.defined) {
971
+ throw error;
972
+ }
973
+ const errorMap = procedure["~orpc"].errorMap;
974
+ const dataSchema = errorMap[error.code]?.data;
975
+ if (!dataSchema) {
976
+ throw error;
977
+ }
978
+ const cloned = cloneORPCError(error);
979
+ cloned.data = await this.coerceValue([dataSchema], cloned.data);
980
+ throw cloned;
981
+ }
982
+ }
983
+ ]
984
+ };
985
+ }
986
+ async coerceValue(schemas, value) {
987
+ for (const schema of schemas) {
988
+ let converted = this.cache.get(schema);
989
+ if (!converted) {
990
+ converted = await this.converter.convert(schema, "output");
991
+ this.cache.set(schema, converted);
992
+ }
993
+ value = this.coercer.coerce(converted, value);
358
994
  }
359
- return this.coercer.coerce(jsonSchema, value);
995
+ return value;
360
996
  }
361
997
  }
362
998
 
363
- export { JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionPlugin };
999
+ export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef };