@orpc/json-schema 2.0.0-beta.4 → 2.0.0-beta.41

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