@orpc/json-schema 2.0.0-beta.20 → 2.0.0-beta.22

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.
Files changed (3) hide show
  1. package/README.md +5 -5
  2. package/dist/index.mjs +107 -76
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -109,11 +109,10 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
109
109
  <td align="center"><a href="https://github.com/valerii15298?ref=orpc" target="_blank" rel="noopener" title="Valerii Petryniak"><img src="https://avatars.githubusercontent.com/u/44531564?u=88ac74d9bacd20401518441907acad21063cd397&amp;v=4" width="139" alt="Valerii Petryniak"/><br />Valerii Petryniak</a></td>
110
110
  <td align="center"><a href="https://github.com/letstri?ref=orpc" target="_blank" rel="noopener" title="Valerii Strilets"><img src="https://avatars.githubusercontent.com/u/13253748?u=c7b10399ccc8f8081e24db94ec32cd9858e86ac3&amp;v=4" width="139" alt="Valerii Strilets"/><br />Valerii Strilets</a></td>
111
111
  <td align="center"><a href="https://github.com/K-Mistele?ref=orpc" target="_blank" rel="noopener" title="Kyle Mistele"><img src="https://avatars.githubusercontent.com/u/18430555?u=3afebeb81de666e35aaac3ed46f14159d7603ffb&amp;v=4" width="139" alt="Kyle Mistele"/><br />Kyle Mistele</a></td>
112
- <td align="center"><a href="https://github.com/R44VC0RP?ref=orpc" target="_blank" rel="noopener" title="Ryan Vogel"><img src="https://avatars.githubusercontent.com/u/89211796?u=1857347b9787d8d8a7ea5bfc333f96be92d5a683&amp;v=4" width="139" alt="Ryan Vogel"/><br />Ryan Vogel</a></td>
113
112
  <td align="center"><a href="https://github.com/christ12938?ref=orpc" target="_blank" rel="noopener" title="christ12938"><img src="https://avatars.githubusercontent.com/u/25758598?v=4" width="139" alt="christ12938"/><br />christ12938</a></td>
113
+ <td align="center"><a href="https://github.com/Ryanjso?ref=orpc" target="_blank" rel="noopener" title="Ryan Soderberg"><img src="https://avatars.githubusercontent.com/u/39172778?u=5ed913c31d57e7221b75784abcad48c7ebddde27&amp;v=4" width="139" alt="Ryan Soderberg"/><br />Ryan Soderberg</a></td>
114
114
  </tr>
115
115
  <tr>
116
- <td align="center"><a href="https://github.com/Ryanjso?ref=orpc" target="_blank" rel="noopener" title="Ryan Soderberg"><img src="https://avatars.githubusercontent.com/u/39172778?u=5ed913c31d57e7221b75784abcad48c7ebddde27&amp;v=4" width="139" alt="Ryan Soderberg"/><br />Ryan Soderberg</a></td>
117
116
  <td align="center"><a href="https://github.com/itigoore01?ref=orpc" target="_blank" rel="noopener" title="shota"><img src="https://avatars.githubusercontent.com/u/11831107?u=c976a6dc7e055eb026304c46c99100ed22b0c8e0&amp;v=4" width="139" alt="shota"/><br />shota</a></td>
118
117
  </tr>
119
118
  </table>
@@ -123,24 +122,23 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
123
122
  <table>
124
123
  <tr>
125
124
  <td align="center"><a href="https://github.com/rhinodavid?ref=orpc" target="_blank" rel="noopener" title="David Walsh"><img src="https://avatars.githubusercontent.com/u/5778036?u=b5521f07d2f88c3db2a0dae62b5f2f8357214af0&amp;v=4" width="119" alt="David Walsh"/><br />David Walsh</a></td>
126
- <td align="center"><a href="https://github.com/Nic13Gamer?ref=orpc" target="_blank" rel="noopener" title="Nicholas"><img src="https://avatars.githubusercontent.com/u/54724556?u=56a7ab430ce7a80d648ab6eba051d454a818ed0b&amp;v=4" width="119" alt="Nicholas"/><br />Nicholas</a></td>
127
125
  <td align="center"><a href="https://github.com/Robbe95?ref=orpc" target="_blank" rel="noopener" title="Robbe Vaes"><img src="https://avatars.githubusercontent.com/u/44748019?u=e0232402c045ad4eac7cbd217f1f47e083103b89&amp;v=4" width="119" alt="Robbe Vaes"/><br />Robbe Vaes</a></td>
128
126
  <td align="center"><a href="https://github.com/aidansunbury?ref=orpc" target="_blank" rel="noopener" title="Aidan Sunbury"><img src="https://avatars.githubusercontent.com/u/64103161?v=4" width="119" alt="Aidan Sunbury"/><br />Aidan Sunbury</a></td>
129
127
  <td align="center"><a href="https://github.com/soonoo?ref=orpc" target="_blank" rel="noopener" title="soonoo"><img src="https://avatars.githubusercontent.com/u/5436405?u=5d0b4aa955c87e30e6bda7f0cccae5402da99528&amp;v=4" width="119" alt="soonoo"/><br />soonoo</a></td>
130
128
  <td align="center"><a href="https://github.com/kporten?ref=orpc" target="_blank" rel="noopener" title="Kevin Porten"><img src="https://avatars.githubusercontent.com/u/1839345?u=dc2263d5cfe0d927ce1a0be04a1d55dd6b55405c&amp;v=4" width="119" alt="Kevin Porten"/><br />Kevin Porten</a></td>
131
129
  <td align="center"><a href="https://github.com/pumpkinlink?ref=orpc" target="_blank" rel="noopener" title="Denis"><img src="https://avatars.githubusercontent.com/u/11864620?u=5f47bbe6c65d0f6f5cf011021490238e4b0593d0&amp;v=4" width="119" alt="Denis"/><br />Denis</a></td>
130
+ <td align="center"><a href="https://github.com/christopher-kapic?ref=orpc" target="_blank" rel="noopener" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
132
131
  </tr>
133
132
  <tr>
134
- <td align="center"><a href="https://github.com/christopher-kapic?ref=orpc" target="_blank" rel="noopener" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
135
133
  <td align="center"><a href="https://github.com/thomasballinger?ref=orpc" target="_blank" rel="noopener" title="Tom Ballinger"><img src="https://avatars.githubusercontent.com/u/458879?u=4b045ac75d721b6ac2b42a74d7d37f61f0414031&amp;v=4" width="119" alt="Tom Ballinger"/><br />Tom Ballinger</a></td>
136
134
  <td align="center"><a href="https://github.com/SSam0419?ref=orpc" target="_blank" rel="noopener" title="Sam"><img src="https://avatars.githubusercontent.com/u/102863520?u=3c89611f549d5070be232eb4532f690c8f2e7a65&amp;v=4" width="119" alt="Sam"/><br />Sam</a></td>
137
135
  <td align="center"><a href="https://github.com/Titoine?ref=orpc" target="_blank" rel="noopener" title="Titoine"><img src="https://avatars.githubusercontent.com/u/3514286?u=1bb1e86b0c99c8a1121372e56d51a177eea12191&amp;v=4" width="119" alt="Titoine"/><br />Titoine</a></td>
138
136
  <td align="center"><a href="https://github.com/Mnigos?ref=orpc" target="_blank" rel="noopener" title="Igor Makowski"><img src="https://avatars.githubusercontent.com/u/56691628?u=ee8c879478f7c151b9156aef6c74243fa3e247a8&amp;v=4" width="119" alt="Igor Makowski"/><br />Igor Makowski</a></td>
139
137
  <td align="center"><a href="https://github.com/hanayashiki?ref=orpc" target="_blank" rel="noopener" title="hanayashiki"><img src="https://avatars.githubusercontent.com/u/26056783?u=06c3b9205a16fd41a871e82da1cc2a09306d53f5&amp;v=4" width="119" alt="hanayashiki"/><br />hanayashiki</a></td>
140
138
  <td align="center"><a href="https://github.com/ldub?ref=orpc" target="_blank" rel="noopener" title="Lev Dubinets"><img src="https://avatars.githubusercontent.com/u/3114081?u=f547f5d5012cab54851f1b1ad72d10e537f78fc2&amp;v=4" width="119" alt="Lev Dubinets"/><br />Lev Dubinets</a></td>
139
+ <td align="center"><a href="https://github.com/mr-kelly?ref=orpc" target="_blank" rel="noopener" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
141
140
  </tr>
142
141
  <tr>
143
- <td align="center"><a href="https://github.com/mr-kelly?ref=orpc" target="_blank" rel="noopener" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
144
142
  <td align="center"><a href="https://github.com/piscis?ref=orpc" target="_blank" rel="noopener" title="Alex"><img src="https://avatars.githubusercontent.com/u/326163?u=b245f368bd940cf51d08c0b6bf55f8257f359437&amp;v=4" width="119" alt="Alex"/><br />Alex</a></td>
145
143
  <td align="center"><a href="https://github.com/finom?ref=orpc" target="_blank" rel="noopener" title="Andrey Gubanov"><img src="https://avatars.githubusercontent.com/u/1082083?u=29e91400dbd4a9c217048a8f59562c4f740498e6&amp;v=4" width="119" alt="Andrey Gubanov"/><br />Andrey Gubanov</a></td>
146
144
  </tr>
@@ -173,6 +171,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
173
171
  <a href="https://github.com/nguyenlc1993?ref=orpc" target="_blank" rel="noopener" title="Lê Cao Nguyên"><img src="https://avatars.githubusercontent.com/u/13871971?u=83c8b69d9e35b589c4e1f066cc113b1d9461386f&amp;v=4" width="32" height="32" alt="Lê Cao Nguyên" /></a>
174
172
  <a href="https://github.com/wobsoriano?ref=orpc" target="_blank" rel="noopener" title="Robert Soriano"><img src="https://avatars.githubusercontent.com/u/13049130?u=6d72104182e7c9ed25934815313fb69107332111&amp;v=4" width="32" height="32" alt="Robert Soriano" /></a>
175
173
  <a href="https://github.com/andrewpeters9?ref=orpc" target="_blank" rel="noopener" title="Andrew Peters"><img src="https://avatars.githubusercontent.com/u/36251325?v=4" width="32" height="32" alt="Andrew Peters" /></a>
174
+ <a href="https://github.com/R44VC0RP?ref=orpc" target="_blank" rel="noopener" title="Ryan Vogel"><img src="https://avatars.githubusercontent.com/u/89211796?u=1857347b9787d8d8a7ea5bfc333f96be92d5a683&amp;v=4" width="32" height="32" alt="Ryan Vogel" /></a>
176
175
  <a href="https://github.com/SKostyukovich?ref=orpc" target="_blank" rel="noopener" title="SKostyukovich"><img src="https://avatars.githubusercontent.com/u/10700067?v=4" width="32" height="32" alt="SKostyukovich" /></a>
177
176
  <a href="https://github.com/peter-adam-dy?ref=orpc" target="_blank" rel="noopener" title="Peter Adam"><img src="https://avatars.githubusercontent.com/u/132129459?u=4f3dbbb3b443990b56acb7d6a5d11ed2c555f6db&amp;v=4" width="32" height="32" alt="Peter Adam" /></a>
178
177
  <a href="https://github.com/FabworksHQ?ref=orpc" target="_blank" rel="noopener" title="Fabworks"><img src="https://avatars.githubusercontent.com/u/160179500?v=4" width="32" height="32" alt="Fabworks" /></a>
@@ -185,6 +184,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
185
184
  <a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="natt"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="32" height="32" alt="natt" /></a>
186
185
  <a href="https://github.com/ChromeGG?ref=orpc" target="_blank" rel="noopener" title="Adam Tkaczyk"><img src="https://avatars.githubusercontent.com/u/39050595?u=a58ca6042a6950e94e6e92442db76ef584279bc0&amp;v=4" width="32" height="32" alt="Adam Tkaczyk" /></a>
187
186
  <a href="https://github.com/plancraft?ref=orpc" target="_blank" rel="noopener" title="plancraft"><img src="https://avatars.githubusercontent.com/u/46482287?v=4" width="32" height="32" alt="plancraft" /></a>
187
+ <a href="https://github.com/Nic13Gamer?ref=orpc" target="_blank" rel="noopener" title="Nicholas"><img src="https://avatars.githubusercontent.com/u/54724556?u=56a7ab430ce7a80d648ab6eba051d454a818ed0b&amp;v=4" width="32" height="32" alt="Nicholas" /></a>
188
188
  </p>
189
189
 
190
190
  ## References
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { get, isPlainObject, toArray, tryOrUndefined, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
1
+ import { get, isPlainObject, tryOrUndefined, toArray, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
2
2
  export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
3
3
  import { ORPCError, cloneORPCError } from '@orpc/client';
4
4
  import { getProcedureContractOrThrow } from '@orpc/contract';
@@ -169,7 +169,12 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
169
169
  return JsonSchemaXNativeType2;
170
170
  })(JsonSchemaXNativeType || {});
171
171
 
172
- const FLEXIBLE_DATE_FORMAT_REGEX = /^[^-]+-[^-]+-[^-]+$/;
172
+ const UNSATISFIED = 0;
173
+ const LOOSELY_SATISFIED = 1;
174
+ const SATISFIED = 2;
175
+ function minSatisfaction(a, b) {
176
+ return a < b ? a : b;
177
+ }
173
178
  class JsonSchemaCoercer {
174
179
  coerce([schema, optional], value) {
175
180
  if (optional && value === void 0) {
@@ -178,25 +183,29 @@ class JsonSchemaCoercer {
178
183
  const [, coerced] = this.coerceInternal(schema, schema, value);
179
184
  return coerced;
180
185
  }
181
- coerceInternal(rootSchema, schema, value) {
186
+ coerceInternal(rootSchema, schema, value, appliedRefs) {
182
187
  if (typeof schema === "boolean") {
183
- return [schema, value];
188
+ return [schema ? SATISFIED : UNSATISFIED, value];
184
189
  }
185
190
  if (Array.isArray(schema.type)) {
186
191
  return this.coerceInternal(
187
192
  rootSchema,
188
193
  { anyOf: schema.type.map((type) => ({ ...schema, type })) },
189
- value
194
+ value,
195
+ appliedRefs
190
196
  );
191
197
  }
192
198
  let coerced = value;
193
- let satisfied = true;
199
+ let satisfied = SATISFIED;
194
200
  if (typeof schema.$ref === "string") {
195
201
  const resolved = schema.$ref.startsWith("#/") ? get(rootSchema, schema.$ref.slice("#/".length).split("/").map(decodeJsonPointerSegment)) : schema.$ref === "#" ? rootSchema : void 0;
196
- if (resolved !== void 0) {
197
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced);
202
+ if (resolved !== void 0 && !appliedRefs?.has(resolved)) {
203
+ appliedRefs ??= /* @__PURE__ */ new Set();
204
+ appliedRefs.add(resolved);
205
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced, appliedRefs);
206
+ appliedRefs.delete(resolved);
198
207
  coerced = subCoerced;
199
- satisfied = subSatisfied;
208
+ satisfied = minSatisfaction(satisfied, subSatisfied);
200
209
  }
201
210
  }
202
211
  const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
@@ -210,24 +219,24 @@ class JsonSchemaCoercer {
210
219
  if (enumValues.includes(booleanValue)) {
211
220
  coerced = booleanValue;
212
221
  } else {
213
- satisfied = false;
222
+ satisfied = UNSATISFIED;
214
223
  }
215
224
  }
216
225
  } else {
217
- satisfied = false;
226
+ satisfied = UNSATISFIED;
218
227
  }
219
228
  }
220
229
  if (schema.type) {
221
230
  switch (schema.type) {
222
231
  case "null": {
223
232
  if (coerced !== null) {
224
- satisfied = false;
233
+ satisfied = UNSATISFIED;
225
234
  }
226
235
  break;
227
236
  }
228
237
  case "string": {
229
238
  if (typeof coerced !== "string") {
230
- satisfied = false;
239
+ satisfied = UNSATISFIED;
231
240
  }
232
241
  break;
233
242
  }
@@ -236,7 +245,7 @@ class JsonSchemaCoercer {
236
245
  coerced = stringToNumber(coerced);
237
246
  }
238
247
  if (typeof coerced !== "number") {
239
- satisfied = false;
248
+ satisfied = UNSATISFIED;
240
249
  }
241
250
  break;
242
251
  }
@@ -245,7 +254,7 @@ class JsonSchemaCoercer {
245
254
  coerced = stringToInteger(coerced);
246
255
  }
247
256
  if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
248
- satisfied = false;
257
+ satisfied = UNSATISFIED;
249
258
  }
250
259
  break;
251
260
  }
@@ -254,7 +263,7 @@ class JsonSchemaCoercer {
254
263
  coerced = stringToBoolean(coerced);
255
264
  }
256
265
  if (typeof coerced !== "boolean") {
257
- satisfied = false;
266
+ satisfied = UNSATISFIED;
258
267
  }
259
268
  break;
260
269
  }
@@ -266,26 +275,24 @@ class JsonSchemaCoercer {
266
275
  const coercedItems = coerced.map((item, i) => {
267
276
  const subSchema = prefixItemSchemas[i] ?? itemSchema;
268
277
  if (subSchema === void 0) {
269
- satisfied = false;
278
+ satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
270
279
  return item;
271
280
  }
272
281
  const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
273
- if (!subSatisfied) {
274
- satisfied = false;
275
- }
282
+ satisfied = minSatisfaction(satisfied, subSatisfied);
276
283
  if (subCoerced !== item) {
277
284
  shouldUseCoercedItems = true;
278
285
  }
279
286
  return subCoerced;
280
287
  });
281
288
  if (coercedItems.length < prefixItemSchemas.length) {
282
- satisfied = false;
289
+ satisfied = UNSATISFIED;
283
290
  }
284
291
  if (shouldUseCoercedItems) {
285
292
  coerced = coercedItems;
286
293
  }
287
294
  } else {
288
- satisfied = false;
295
+ satisfied = UNSATISFIED;
289
296
  }
290
297
  break;
291
298
  }
@@ -295,35 +302,36 @@ class JsonSchemaCoercer {
295
302
  }
296
303
  if (isPlainObject(coerced)) {
297
304
  let shouldUseCoercedItems = false;
298
- const coercedItems = {};
299
- const patternProperties = Object.entries(schema.patternProperties ?? {}).map(([key, value2]) => [new RegExp(key), value2]);
305
+ const coercedItems = { ...coerced };
306
+ const patternProperties = Object.entries(schema.patternProperties ?? {}).flatMap(([key, value2]) => {
307
+ const pattern = tryOrUndefined(() => new RegExp(key));
308
+ return pattern ? [[pattern, value2]] : [];
309
+ });
310
+ const propertySchemas = schema.properties;
300
311
  for (const key in coerced) {
301
312
  const value2 = coerced[key];
302
- const subSchema = schema.properties?.[key] ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
303
- if (value2 === void 0 && !schema.required?.includes(key)) {
304
- coercedItems[key] = value2;
305
- } else if (subSchema === void 0) {
306
- coercedItems[key] = value2;
307
- satisfied = false;
308
- } else {
309
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
310
- coercedItems[key] = subCoerced;
311
- if (!subSatisfied) {
312
- satisfied = false;
313
- }
314
- if (subCoerced !== value2) {
315
- shouldUseCoercedItems = true;
313
+ const subSchema = (propertySchemas !== void 0 && Object.hasOwn(propertySchemas, key) ? propertySchemas[key] : void 0) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
314
+ if (value2 !== void 0 || schema.required?.includes(key)) {
315
+ if (subSchema === void 0) {
316
+ satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
317
+ } else {
318
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
319
+ coercedItems[key] = subCoerced;
320
+ satisfied = minSatisfaction(satisfied, subSatisfied);
321
+ if (subCoerced !== value2) {
322
+ shouldUseCoercedItems = true;
323
+ }
316
324
  }
317
325
  }
318
326
  }
319
327
  if (schema.required?.some((key) => !Object.hasOwn(coercedItems, key))) {
320
- satisfied = false;
328
+ satisfied = UNSATISFIED;
321
329
  }
322
330
  if (shouldUseCoercedItems) {
323
331
  coerced = coercedItems;
324
332
  }
325
333
  } else {
326
- satisfied = false;
334
+ satisfied = UNSATISFIED;
327
335
  }
328
336
  break;
329
337
  }
@@ -336,7 +344,7 @@ class JsonSchemaCoercer {
336
344
  coerced = stringToDate(coerced);
337
345
  }
338
346
  if (!(coerced instanceof Date)) {
339
- satisfied = false;
347
+ satisfied = UNSATISFIED;
340
348
  }
341
349
  break;
342
350
  }
@@ -350,7 +358,7 @@ class JsonSchemaCoercer {
350
358
  break;
351
359
  }
352
360
  if (typeof coerced !== "bigint") {
353
- satisfied = false;
361
+ satisfied = UNSATISFIED;
354
362
  }
355
363
  break;
356
364
  }
@@ -359,7 +367,7 @@ class JsonSchemaCoercer {
359
367
  coerced = stringToRegExp(coerced);
360
368
  }
361
369
  if (!(coerced instanceof RegExp)) {
362
- satisfied = false;
370
+ satisfied = UNSATISFIED;
363
371
  }
364
372
  break;
365
373
  }
@@ -368,7 +376,7 @@ class JsonSchemaCoercer {
368
376
  coerced = stringToURL(coerced);
369
377
  }
370
378
  if (!(coerced instanceof URL)) {
371
- satisfied = false;
379
+ satisfied = UNSATISFIED;
372
380
  }
373
381
  break;
374
382
  }
@@ -377,7 +385,7 @@ class JsonSchemaCoercer {
377
385
  coerced = arrayToSet(coerced);
378
386
  }
379
387
  if (!(coerced instanceof Set)) {
380
- satisfied = false;
388
+ satisfied = UNSATISFIED;
381
389
  }
382
390
  break;
383
391
  }
@@ -386,7 +394,7 @@ class JsonSchemaCoercer {
386
394
  coerced = arrayToMap(coerced);
387
395
  }
388
396
  if (!(coerced instanceof Map)) {
389
- satisfied = false;
397
+ satisfied = UNSATISFIED;
390
398
  }
391
399
  break;
392
400
  }
@@ -394,54 +402,78 @@ class JsonSchemaCoercer {
394
402
  }
395
403
  if (schema.allOf) {
396
404
  for (const subSchema of schema.allOf) {
397
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
405
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
398
406
  coerced = subCoerced;
399
- if (!subSatisfied) {
400
- satisfied = false;
401
- }
407
+ satisfied = minSatisfaction(satisfied, subSatisfied);
402
408
  }
403
409
  }
404
410
  for (const key of ["anyOf", "oneOf"]) {
405
411
  if (schema[key]) {
406
- let bestOptions;
412
+ let best;
407
413
  for (const subSchema of schema[key]) {
408
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
409
- if (subSatisfied) {
410
- if (!bestOptions || subCoerced === coerced) {
411
- bestOptions = { coerced: subCoerced, satisfied: subSatisfied };
412
- }
413
- if (subCoerced === coerced) {
414
- break;
415
- }
414
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
415
+ if (subSatisfied === UNSATISFIED) {
416
+ continue;
417
+ }
418
+ if (subCoerced === coerced) {
419
+ best = { satisfied: subSatisfied, value: subCoerced };
420
+ break;
416
421
  }
422
+ if (!best || subSatisfied > best.satisfied) {
423
+ best = { satisfied: subSatisfied, value: subCoerced };
424
+ }
425
+ }
426
+ if (best) {
427
+ coerced = best.value;
428
+ satisfied = minSatisfaction(satisfied, best.satisfied);
429
+ } else {
430
+ satisfied = UNSATISFIED;
417
431
  }
418
- coerced = bestOptions ? bestOptions.coerced : coerced;
419
- satisfied = bestOptions ? bestOptions.satisfied : false;
420
432
  }
421
433
  }
422
434
  if (typeof schema.not !== "undefined") {
423
- const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced);
424
- if (notSatisfied) {
425
- satisfied = false;
435
+ const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced, appliedRefs);
436
+ if (notSatisfied !== UNSATISFIED) {
437
+ satisfied = UNSATISFIED;
426
438
  }
427
439
  }
428
440
  return [satisfied, coerced];
429
441
  }
430
442
  }
443
+ const NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
431
444
  function stringToNumber(value) {
432
- const num = Number.parseFloat(value);
433
- if (Number.isNaN(num) || num !== Number(value)) {
445
+ if (!NUMBER_PATTERN.test(value)) {
446
+ return value;
447
+ }
448
+ const num = Number(value);
449
+ if (num < Number.MIN_SAFE_INTEGER || num > Number.MAX_SAFE_INTEGER) {
434
450
  return value;
435
451
  }
436
452
  return num;
437
453
  }
454
+ const INTEGER_PATTERN = /^-?(?:0|[1-9]\d*)$/;
438
455
  function stringToInteger(value) {
439
- const num = Number.parseInt(value);
440
- if (Number.isNaN(num) || num !== Number(value)) {
456
+ if (!INTEGER_PATTERN.test(value)) {
457
+ return value;
458
+ }
459
+ const num = Number(value);
460
+ if (!Number.isSafeInteger(num)) {
441
461
  return value;
442
462
  }
443
463
  return num;
444
464
  }
465
+ function stringToBigInt(value) {
466
+ if (!INTEGER_PATTERN.test(value)) {
467
+ return value;
468
+ }
469
+ return BigInt(value);
470
+ }
471
+ function numberToBigInt(value) {
472
+ if (!Number.isInteger(value)) {
473
+ return value;
474
+ }
475
+ return BigInt(value);
476
+ }
445
477
  function stringToBoolean(value) {
446
478
  const lower = value.toLowerCase();
447
479
  if (lower === "false" || lower === "off") {
@@ -452,21 +484,20 @@ function stringToBoolean(value) {
452
484
  }
453
485
  return value;
454
486
  }
455
- function stringToBigInt(value) {
456
- return tryOrUndefined(() => BigInt(value)) ?? value;
457
- }
458
- function numberToBigInt(value) {
459
- return tryOrUndefined(() => BigInt(value)) ?? value;
460
- }
487
+ const DATE_TIME_PATTERN = /^[+-]?\d{4,6}-\d{1,2}-\d{1,2}(?:[T ].*)?$/;
461
488
  function stringToDate(value) {
489
+ if (!DATE_TIME_PATTERN.test(value)) {
490
+ return value;
491
+ }
462
492
  const date = new Date(value);
463
- if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
493
+ if (Number.isNaN(date.getTime())) {
464
494
  return value;
465
495
  }
466
496
  return date;
467
497
  }
498
+ const REGEXP_PATTERN = /^\/([\s\S]*)\/([a-z]*)$/;
468
499
  function stringToRegExp(value) {
469
- const match = value.match(/^\/(.*)\/([a-z]*)$/);
500
+ const match = value.match(REGEXP_PATTERN);
470
501
  if (match) {
471
502
  const [, pattern, flags] = match;
472
503
  return tryOrUndefined(() => new RegExp(pattern, flags)) ?? value;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/json-schema",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.20",
4
+ "version": "2.0.0-beta.22",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -27,10 +27,10 @@
27
27
  "dependencies": {
28
28
  "@standard-schema/spec": "^1.1.0",
29
29
  "json-schema-typed": "^8.0.2",
30
- "@orpc/client": "2.0.0-beta.20",
31
- "@orpc/contract": "2.0.0-beta.20",
32
- "@orpc/shared": "2.0.0-beta.20",
33
- "@orpc/server": "2.0.0-beta.20"
30
+ "@orpc/client": "2.0.0-beta.22",
31
+ "@orpc/shared": "2.0.0-beta.22",
32
+ "@orpc/contract": "2.0.0-beta.22",
33
+ "@orpc/server": "2.0.0-beta.22"
34
34
  },
35
35
  "devDependencies": {
36
36
  "zod": "^4.4.3"