effect 4.0.0-rc.113 → 4.0.0-rc.115

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 (76) hide show
  1. package/dist/Effect.d.ts +268 -7
  2. package/dist/Effect.d.ts.map +1 -1
  3. package/dist/Effect.js +33 -2
  4. package/dist/Effect.js.map +1 -1
  5. package/dist/Effectable.d.ts +1 -2
  6. package/dist/Effectable.d.ts.map +1 -1
  7. package/dist/Effectable.js.map +1 -1
  8. package/dist/FileSystem.d.ts +3 -4
  9. package/dist/FileSystem.d.ts.map +1 -1
  10. package/dist/FileSystem.js.map +1 -1
  11. package/dist/Match.d.ts +1 -14
  12. package/dist/Match.d.ts.map +1 -1
  13. package/dist/Schema.d.ts +4 -4
  14. package/dist/Schema.d.ts.map +1 -1
  15. package/dist/SchemaAST.d.ts.map +1 -1
  16. package/dist/SchemaAST.js +43 -23
  17. package/dist/SchemaAST.js.map +1 -1
  18. package/dist/SchemaParser.d.ts.map +1 -1
  19. package/dist/SchemaParser.js +3 -2
  20. package/dist/SchemaParser.js.map +1 -1
  21. package/dist/SchemaRepresentation.d.ts +39 -24
  22. package/dist/SchemaRepresentation.d.ts.map +1 -1
  23. package/dist/SchemaRepresentation.js +37 -23
  24. package/dist/SchemaRepresentation.js.map +1 -1
  25. package/dist/internal/arbitrary/schema.js.map +1 -1
  26. package/dist/internal/effect.js +16 -8
  27. package/dist/internal/effect.js.map +1 -1
  28. package/dist/internal/schema/fromJsonSchemaDocument.js +221 -230
  29. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  30. package/dist/internal/schema/make.js +8 -3
  31. package/dist/internal/schema/make.js.map +1 -1
  32. package/dist/internal/schema/toCodeDocument.js +4 -4
  33. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  34. package/dist/internal/schema/toEquivalence.js +2 -2
  35. package/dist/internal/schema/toEquivalence.js.map +1 -1
  36. package/dist/internal/schema/toFormatter.js +2 -2
  37. package/dist/internal/schema/toFormatter.js.map +1 -1
  38. package/dist/unstable/cli/Prompt.d.ts +6 -2
  39. package/dist/unstable/cli/Prompt.d.ts.map +1 -1
  40. package/dist/unstable/cli/Prompt.js +8 -7
  41. package/dist/unstable/cli/Prompt.js.map +1 -1
  42. package/dist/unstable/cli/internal/command.d.ts +1 -6
  43. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  44. package/dist/unstable/cli/internal/command.js +2 -0
  45. package/dist/unstable/cli/internal/command.js.map +1 -1
  46. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  47. package/dist/unstable/http/HttpEffect.js +6 -2
  48. package/dist/unstable/http/HttpEffect.js.map +1 -1
  49. package/dist/unstable/http/HttpServerResponse.d.ts +13 -2
  50. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  51. package/dist/unstable/http/HttpServerResponse.js +19 -5
  52. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  53. package/dist/unstable/persistence/Persistence.d.ts.map +1 -1
  54. package/dist/unstable/persistence/Persistence.js +2 -10
  55. package/dist/unstable/persistence/Persistence.js.map +1 -1
  56. package/package.json +2 -2
  57. package/src/Effect.ts +271 -7
  58. package/src/Effectable.ts +2 -2
  59. package/src/FileSystem.ts +3 -4
  60. package/src/Match.ts +1 -1
  61. package/src/Schema.ts +4 -7
  62. package/src/SchemaAST.ts +48 -25
  63. package/src/SchemaParser.ts +3 -2
  64. package/src/SchemaRepresentation.ts +39 -24
  65. package/src/internal/arbitrary/schema.ts +16 -16
  66. package/src/internal/effect.ts +34 -11
  67. package/src/internal/schema/fromJsonSchemaDocument.ts +294 -268
  68. package/src/internal/schema/make.ts +9 -4
  69. package/src/internal/schema/toCodeDocument.ts +4 -4
  70. package/src/internal/schema/toEquivalence.ts +2 -2
  71. package/src/internal/schema/toFormatter.ts +2 -2
  72. package/src/unstable/cli/Prompt.ts +22 -11
  73. package/src/unstable/cli/internal/command.ts +2 -0
  74. package/src/unstable/http/HttpEffect.ts +5 -2
  75. package/src/unstable/http/HttpServerResponse.ts +21 -5
  76. package/src/unstable/persistence/Persistence.ts +6 -16
@@ -16,16 +16,23 @@ const string = {
16
16
  _tag: "String",
17
17
  checks: []
18
18
  };
19
- function makeJsonLiteral(input) {
19
+ const unsupportedStructuredValue = "Only primitive values are supported in \"const\" and \"enum\".";
20
+ function isObject(input) {
21
+ return typeof input === "object" && input !== null && !Array.isArray(input);
22
+ }
23
+ function makeJsonLiteral(input, path) {
20
24
  if (input === null) return {
21
25
  _tag: "Null",
22
26
  checks: []
23
27
  };
24
- return typeof input === "string" || typeof input === "number" || typeof input === "boolean" ? {
25
- _tag: "Literal",
26
- literal: input,
27
- checks: []
28
- } : undefined;
28
+ if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
29
+ return {
30
+ _tag: "Literal",
31
+ literal: input,
32
+ checks: []
33
+ };
34
+ }
35
+ if (typeof input === "object") throw errorWithPath(unsupportedStructuredValue, path);
29
36
  }
30
37
  const jsonSchemaValueTypes = ["null", "string", "number", "boolean", "object", "array"];
31
38
  const jsonSchemaTypeSpecificKeys = ["minLength", "maxLength", "pattern", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "properties", "required", "additionalProperties", "patternProperties", "propertyNames", "minProperties", "maxProperties", "items", "prefixItems", "minItems", "maxItems", "uniqueItems"];
@@ -84,66 +91,77 @@ function jsonDeclaration(annotations) {
84
91
  typeParameters: []
85
92
  };
86
93
  }
87
- function unknownJsonSchemas(representation) {
88
- switch (representation._tag) {
89
- case "Unknown":
90
- return jsonDeclaration(representation.annotations);
91
- case "Suspend":
92
- return {
93
- ...representation,
94
- thunk: unknownJsonSchemas(representation.thunk)
95
- };
96
- case "Arrays":
97
- return {
98
- ...representation,
99
- elements: representation.elements.map(element => ({
100
- ...element,
101
- type: unknownJsonSchemas(element.type)
102
- })),
103
- rest: representation.rest.map(unknownJsonSchemas)
104
- };
105
- case "Objects":
106
- return {
107
- ...representation,
108
- propertySignatures: representation.propertySignatures.map(property => ({
109
- ...property,
110
- type: unknownJsonSchemas(property.type)
111
- })),
112
- indexSignatures: representation.indexSignatures.map(indexSignature => ({
113
- parameter: unknownJsonSchemas(indexSignature.parameter),
114
- type: unknownJsonSchemas(indexSignature.type)
115
- })),
116
- checks: representation.checks.map(unknownJsonSchemaCheck)
117
- };
118
- case "Union":
119
- return {
120
- ...representation,
121
- types: representation.types.map(unknownJsonSchemas)
122
- };
123
- default:
124
- return representation;
125
- }
126
- }
127
- function unknownJsonSchemaCheck(check) {
128
- const representation = check.representation;
129
- const schemas = representation?.schemas;
130
- if (representation === undefined || schemas === undefined) {
131
- return check;
132
- }
133
- return {
134
- ...check,
135
- representation: {
136
- ...representation,
137
- schemas: schemas.map(unknownJsonSchemas)
138
- }
139
- };
140
- }
141
94
  function translateJsonSchemaMultiDocument(document, options, singleRoot = false) {
142
95
  const definitionCache = new Map();
143
96
  const reachableDefinitions = new Map();
144
97
  const objectScopesByProperties = new WeakMap();
145
98
  const annotatedReferences = [];
146
99
  const resolvingChoices = new Set();
100
+ const finalReferences = new Set();
101
+ let inNestedResource = false;
102
+ function finalize(representation) {
103
+ switch (representation._tag) {
104
+ case "Unknown":
105
+ return jsonDeclaration(representation.annotations);
106
+ case "Reference":
107
+ finalReferences.add(representation.$ref);
108
+ return representation;
109
+ case "Suspend":
110
+ return {
111
+ ...representation,
112
+ thunk: finalize(representation.thunk)
113
+ };
114
+ case "Arrays":
115
+ return {
116
+ ...representation,
117
+ elements: representation.elements.map(element => ({
118
+ ...element,
119
+ type: finalize(element.type)
120
+ })),
121
+ rest: representation.rest.map(finalize)
122
+ };
123
+ case "Objects":
124
+ // Intersections may reduce open patterns to fixed properties, so reject only
125
+ // if an open pattern remains in the final representation.
126
+ const [scopes, path] = objectScopesByProperties.get(representation.propertySignatures);
127
+ if (representation.indexSignatures[0]?.parameter === string && scopes.some(scope => scope.patterns.length > 0)) {
128
+ throw errorWithPath("Cannot import open \"patternProperties\": unmatched keys cannot be typed correctly.", path);
129
+ }
130
+ return {
131
+ ...representation,
132
+ propertySignatures: representation.propertySignatures.map(property => ({
133
+ ...property,
134
+ type: finalize(property.type)
135
+ })),
136
+ indexSignatures: representation.indexSignatures.map(indexSignature => ({
137
+ parameter: finalize(indexSignature.parameter),
138
+ type: finalize(indexSignature.type)
139
+ })),
140
+ checks: representation.checks.map(finalizeCheck)
141
+ };
142
+ case "Union":
143
+ return {
144
+ ...representation,
145
+ types: representation.types.map(finalize)
146
+ };
147
+ default:
148
+ return representation;
149
+ }
150
+ }
151
+ function finalizeCheck(check) {
152
+ const representation = check.representation;
153
+ const schemas = representation?.schemas;
154
+ if (representation === undefined || schemas === undefined) {
155
+ return check;
156
+ }
157
+ return {
158
+ ...check,
159
+ representation: {
160
+ ...representation,
161
+ schemas: schemas.map(finalize)
162
+ }
163
+ };
164
+ }
147
165
  function annotate(representation, annotations) {
148
166
  if (annotations === undefined) return representation;
149
167
  if (representation._tag === "Reference") {
@@ -166,29 +184,26 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
166
184
  const cached = definitionCache.get(key);
167
185
  if (cached !== undefined) {
168
186
  if (cached === null) {
169
- throw errorWithPath(recursiveReferenceError ?? `Invalid reference ${key}`, [...path, "$ref"]);
187
+ throw errorWithPath(recursiveReferenceError ?? `Definition ${JSON.stringify(key)} is a circular alias.`, [...path, "$ref"]);
170
188
  }
171
189
  return cached;
172
190
  }
173
- if (!Object.hasOwn(document.definitions, key)) {
174
- throw errorWithPath(`Invalid reference ${key}`, [...path, "$ref"]);
175
- }
176
191
  definitionCache.set(key, null);
177
- const representation = recur(document.definitions[key], ["definitions", key]);
192
+ const representation = translateSchema(document.definitions[key], ["definitions", key]);
178
193
  definitionCache.set(key, representation);
179
194
  return representation;
180
195
  }
181
- function resolveReference(reference, path, options, seen = new Set()) {
196
+ function resolveReference(reference, path, recursiveReferenceError, seen = new Set()) {
182
197
  if (seen.has(reference.$ref)) {
183
- throw errorWithPath(`Invalid reference ${reference.$ref}`, [...path, "$ref"]);
198
+ throw errorWithPath(`Definition ${JSON.stringify(reference.$ref)} is a circular alias.`, [...path, "$ref"]);
184
199
  }
185
200
  seen.add(reference.$ref);
186
- const representation = translateDefinition(reference.$ref, path, options?.recursiveReferenceError);
201
+ const representation = translateDefinition(reference.$ref, path, recursiveReferenceError);
187
202
  if (representation._tag === "Reference") {
188
- return resolveReference(representation, path, options, seen);
203
+ return resolveReference(representation, path, recursiveReferenceError, seen);
189
204
  }
190
205
  if (representation._tag === "Suspend" && representation.thunk._tag === "Reference") {
191
- return annotate(resolveReference(representation.thunk, path, options, seen), representation.annotations);
206
+ return annotate(resolveReference(representation.thunk, path, recursiveReferenceError, seen), representation.annotations);
192
207
  }
193
208
  return representation;
194
209
  }
@@ -200,35 +215,31 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
200
215
  ...right
201
216
  };
202
217
  }
203
- function combinedAnnotations(representation, left, right) {
218
+ function annotateIntersection(representation, left, right) {
204
219
  return annotate(representation, mergeAnnotations(left._tag === "Reference" ? undefined : left.annotations, right._tag === "Reference" ? undefined : right.annotations));
205
220
  }
206
- function asChecks(checks, annotations) {
207
- if (checks.length === 0) return undefined;
208
- if (annotations === undefined) return checks;
209
- if (checks.length === 1 && checks[0].annotations === undefined) {
210
- return [{
211
- ...checks[0],
212
- annotations
213
- }];
214
- }
215
- return [{
216
- _tag: "FilterGroup",
217
- checks: checks,
218
- annotations
219
- }];
220
- }
221
- function combineChecks(left, right, annotations, deduplicate = []) {
221
+ function intersectChecks(left, right, deduplicate = []) {
222
+ let checks = right.checks;
222
223
  for (const id of deduplicate) {
223
- if (left.some(check => checkId(check) === id)) {
224
- right = right.filter(check => checkId(check) !== id);
224
+ if (left.checks.some(check => check._tag === "Filter" && check.representation?.id === id)) {
225
+ checks = checks.filter(check => check._tag !== "Filter" || check.representation?.id !== id);
225
226
  }
226
227
  }
227
- const checks = asChecks(right, annotations);
228
- return checks === undefined ? undefined : [...left, ...checks];
229
- }
230
- function checkId(check) {
231
- return check._tag === "Filter" ? check.representation?.id : undefined;
228
+ if (checks.length === 0) {
229
+ const annotations = mergeAnnotations(left.annotations, right.annotations);
230
+ return [left.checks, annotations];
231
+ }
232
+ if (right.annotations !== undefined) {
233
+ checks = checks.length === 1 && checks[0].annotations === undefined ? [{
234
+ ...checks[0],
235
+ annotations: right.annotations
236
+ }] : [{
237
+ _tag: "FilterGroup",
238
+ checks: checks,
239
+ annotations: right.annotations
240
+ }];
241
+ }
242
+ return [[...left.checks, ...checks], left.annotations];
232
243
  }
233
244
  function satisfiesPrimitiveCheck(check, value) {
234
245
  if (check._tag === "FilterGroup") {
@@ -266,11 +277,11 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
266
277
  }
267
278
  return representation.checks.every(check => satisfiesPrimitiveCheck(check, value));
268
279
  }
269
- function combinePrimitiveWithLiteral(primitive, literal) {
280
+ function intersectPrimitiveWithLiteral(primitive, literal) {
270
281
  const satisfies = primitive._tag === "Boolean" ? typeof literal.literal === "boolean" : satisfiesLiteral(primitive, literal);
271
- return satisfies ? combinedAnnotations(literal, primitive, literal) : never;
282
+ return satisfies ? annotateIntersection(literal, primitive, literal) : never;
272
283
  }
273
- function combineArrays(left, right, path) {
284
+ function intersectArrays(left, right, path) {
274
285
  const elements = [];
275
286
  const length = Math.max(left.elements.length, right.elements.length);
276
287
  for (let index = 0; index < length; index++) {
@@ -285,7 +296,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
285
296
  rest: []
286
297
  } : undefined;
287
298
  }
288
- const type = combine(leftType, rightType, [...path, "elements", index, "type"]);
299
+ const type = intersect(leftType, rightType, [...path, "elements", index, "type"]);
289
300
  if (type._tag === "Never") {
290
301
  return isOptional ? {
291
302
  elements,
@@ -305,16 +316,16 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
305
316
  rest: []
306
317
  };
307
318
  }
308
- const rest = combine(leftRest, rightRest, [...path, "rest", 0]);
319
+ const rest = intersect(leftRest, rightRest, [...path, "rest", 0]);
309
320
  return {
310
321
  elements,
311
322
  rest: rest._tag === "Never" ? [] : [rest]
312
323
  };
313
324
  }
314
- function combineTypes(types, path) {
325
+ function intersectAll(types, path) {
315
326
  let out = types[0] ?? unknown;
316
327
  for (let index = 1; index < types.length; index++) {
317
- out = combine(out, types[index], [...path, index]);
328
+ out = intersect(out, types[index], [...path, index]);
318
329
  }
319
330
  return out;
320
331
  }
@@ -327,8 +338,9 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
327
338
  hasFiniteKeyDomain ||= scope.additionalProperties._tag === "Never" && scope.patterns.length === 0;
328
339
  requiresFiniteKeyDomain ||= scope.additionalProperties._tag === "Never" || scope.additionalProperties._tag !== "Unknown" && (scope.hasProperties || scope.patterns.length > 0);
329
340
  }
330
- if (!hasFiniteKeyDomain && requiresFiniteKeyDomain) {
331
- throw errorWithPath("Unsupported object keyword scopes", path);
341
+ const closedPattern = scopes.length === 1 && names.size === 0 && scopes[0].additionalProperties._tag === "Never" && scopes[0].patterns.length === 1 ? scopes[0].patterns[0] : undefined;
342
+ if (!hasFiniteKeyDomain && requiresFiniteKeyDomain && closedPattern === undefined) {
343
+ throw errorWithPath(scopes.some(scope => scope.additionalProperties._tag === "Never") ? "Cannot import this closed patterned object: only one pattern without properties is supported." : "Cannot combine typed \"additionalProperties\" with other property schemas: Effect index signatures also check excluded keys.", path);
332
344
  }
333
345
  const properties = [];
334
346
  for (const name of names) {
@@ -349,7 +361,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
349
361
  }
350
362
  if (property?.type === undefined && !matches) types.push(scope.additionalProperties);
351
363
  }
352
- const type = combineTypes(types, [...path, "properties", name]);
364
+ const type = intersectAll(types, [...path, "properties", name]);
353
365
  if (!isOptional && type._tag === "Never") return never;
354
366
  properties.push({
355
367
  name,
@@ -358,46 +370,25 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
358
370
  isMutable: false
359
371
  });
360
372
  }
361
- const indexSignatures = [];
362
- if (!hasFiniteKeyDomain) {
363
- const additionalProperties = combineTypes(scopes.map(scope => scope.additionalProperties), [...path, "additionalProperties"]);
364
- const patterns = new Map();
365
- for (const scope of scopes) {
366
- for (const pattern of scope.patterns) {
367
- const previous = patterns.get(pattern.source);
368
- patterns.set(pattern.source, previous === undefined ? pattern : {
369
- ...pattern,
370
- type: combine(previous.type, pattern.type, [...path, "patternProperties", pattern.source])
371
- });
372
- }
373
- }
374
- for (const {
375
- parameter,
376
- type
377
- } of patterns.values()) {
378
- indexSignatures.push({
379
- parameter,
380
- type: combine(type, additionalProperties, [...path, "indexSignatures"])
381
- });
382
- }
383
- indexSignatures.push({
384
- parameter: string,
385
- type: additionalProperties
386
- });
387
- } else if (properties.length === 0) {
388
- indexSignatures.push({
389
- parameter: string,
390
- type: never
391
- });
392
- }
393
- objectScopesByProperties.set(properties, scopes);
394
- return {
373
+ const indexSignatures = closedPattern !== undefined ? [{
374
+ parameter: closedPattern.parameter,
375
+ type: closedPattern.type
376
+ }] : !hasFiniteKeyDomain ? [{
377
+ parameter: string,
378
+ type: intersectAll(scopes.map(scope => scope.additionalProperties), [...path, "additionalProperties"])
379
+ }] : properties.length === 0 ? [{
380
+ parameter: string,
381
+ type: never
382
+ }] : [];
383
+ const representation = {
395
384
  _tag: "Objects",
396
385
  propertySignatures: properties,
397
386
  indexSignatures,
398
387
  checks,
399
388
  annotations
400
389
  };
390
+ objectScopesByProperties.set(properties, [scopes, path]);
391
+ return representation;
401
392
  }
402
393
  const jsonRootTags = ["Null", "String", "Number", "Boolean", "Objects", "Arrays"];
403
394
  const allMasks = (1 << jsonRootTags.length) - 1;
@@ -452,7 +443,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
452
443
  case "Arrays":
453
444
  return representation.elements.some(element => hasChoices(element.type)) || representation.rest.some(type => hasChoices(type));
454
445
  case "Objects":
455
- return representation.propertySignatures.some(property => hasChoices(property.type)) || representation.indexSignatures.some(indexSignature => hasChoices(indexSignature.parameter) || hasChoices(indexSignature.type)) || representation.checks.some(checkHasChoices);
446
+ return representation.propertySignatures.some(property => hasChoices(property.type)) || representation.indexSignatures.some(indexSignature => hasChoices(indexSignature.parameter) || hasChoices(indexSignature.type)) || objectScopesByProperties.get(representation.propertySignatures)[0].some(scope => scope.patterns.some(pattern => hasChoices(pattern.type))) || representation.checks.some(checkHasChoices);
456
447
  default:
457
448
  return false;
458
449
  }
@@ -474,7 +465,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
474
465
  const literal = representation.literal;
475
466
  return typeof literal === "string" || typeof literal === "number" || typeof literal === "boolean" ? literal : undefined;
476
467
  }
477
- function combineLiteralUnions(left, right, path) {
468
+ function intersectLiteralUnions(left, right, path) {
478
469
  if ((left.options?.mode ?? "anyOf") !== "anyOf" || (right.options?.mode ?? "anyOf") !== "anyOf") {
479
470
  return undefined;
480
471
  }
@@ -491,11 +482,11 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
491
482
  const value = literalValue(representation);
492
483
  if (value === undefined) return undefined;
493
484
  const match = rightByValue.get(value);
494
- if (match !== undefined) types.push(combine(representation, annotate(match, right.annotations), path));
485
+ if (match !== undefined) types.push(intersect(representation, annotate(match, right.annotations), path));
495
486
  }
496
487
  return makeUnion(left, types);
497
488
  }
498
- function combineUnionWithType(union, type, path, unionOnLeft) {
489
+ function intersectUnionWithType(union, type, path, unionOnLeft) {
499
490
  const mask = rootMask(type);
500
491
  const members = union.types.map((member, index) => ({
501
492
  index,
@@ -506,9 +497,9 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
506
497
  }
507
498
  if (members.length === 1 && union.checks.length === 0) {
508
499
  const member = annotate(members[0].type, union.annotations);
509
- return unionOnLeft ? combine(member, type, [...path, "types", members[0].index]) : combine(type, member, [...path, "types", members[0].index]);
500
+ return unionOnLeft ? intersect(member, type, [...path, "types", members[0].index]) : intersect(type, member, [...path, "types", members[0].index]);
510
501
  }
511
- return makeUnion(union, members.map(member => unionOnLeft ? combine(member.type, type, [...path, "types", member.index]) : combine(type, member.type, [...path, "types", member.index])).filter(type => type._tag !== "Never"));
502
+ return makeUnion(union, members.map(member => unionOnLeft ? intersect(member.type, type, [...path, "types", member.index]) : intersect(type, member.type, [...path, "types", member.index])).filter(type => type._tag !== "Never"));
512
503
  }
513
504
  function isTypePartition(union) {
514
505
  let mask = 0;
@@ -520,9 +511,9 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
520
511
  return true;
521
512
  }
522
513
  function unsupportedIntersection(path) {
523
- throw errorWithPath("Unsupported intersection of overlapping unions", path);
514
+ throw errorWithPath("Cannot intersect these \"anyOf\" or \"oneOf\" alternatives without expanding their branches.", path);
524
515
  }
525
- function combinePartition(partition, other, path, partitionOnLeft) {
516
+ function intersectPartition(partition, other, path, partitionOnLeft) {
526
517
  const partitionTypes = partition.types;
527
518
  const otherTypes = other.types;
528
519
  for (const type of otherTypes) {
@@ -534,114 +525,112 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
534
525
  const types = [];
535
526
  for (let index = 0; index < partitionTypes.length; index++) {
536
527
  const member = partitionTypes[index];
537
- const type = combineUnionWithType(other, member, [...path, "types", index], !partitionOnLeft);
528
+ const type = intersectUnionWithType(other, member, [...path, "types", index], !partitionOnLeft);
538
529
  if (type._tag !== "Never") types.push(type);
539
530
  }
540
531
  return makeUnion(partition, types);
541
532
  }
542
- function combineUnions(left, right, path) {
543
- const literals = combineLiteralUnions(left, right, path);
533
+ function intersectUnions(left, right, path) {
534
+ const literals = intersectLiteralUnions(left, right, path);
544
535
  if (literals !== undefined) return literals;
545
- if (isTypePartition(left)) return combinePartition(left, right, path, true);
546
- if (isTypePartition(right)) return combinePartition(right, left, path, false);
536
+ if (isTypePartition(left)) return intersectPartition(left, right, path, true);
537
+ if (isTypePartition(right)) return intersectPartition(right, left, path, false);
547
538
  return unsupportedIntersection(path);
548
539
  }
549
- function combine(left, right, path) {
540
+ function intersect(left, right, path) {
550
541
  if (left._tag === "Never") return left;
551
542
  if (right._tag === "Never") return right;
552
- if (left._tag === "Unknown") return combinedAnnotations(right, left, right);
553
- if (right._tag === "Unknown") return combinedAnnotations(left, left, right);
554
- if (left._tag === "Reference") return combine(resolveReference(left, path), right, path);
555
- if (right._tag === "Reference") return combine(left, resolveReference(right, path), path);
543
+ if (left._tag === "Unknown") return annotateIntersection(right, left, right);
544
+ if (right._tag === "Unknown") return annotateIntersection(left, left, right);
545
+ if (left._tag === "Reference") return intersect(resolveReference(left, path), right, path);
546
+ if (right._tag === "Reference") return intersect(left, resolveReference(right, path), path);
556
547
  if (left._tag === "Suspend") {
557
- return annotate(combine(left.thunk, right, path), left.annotations);
548
+ return annotate(intersect(left.thunk, right, path), left.annotations);
558
549
  }
559
550
  if (right._tag === "Suspend") {
560
- return annotate(combine(left, right.thunk, path), right.annotations);
551
+ return annotate(intersect(left, right.thunk, path), right.annotations);
561
552
  }
562
- if (left._tag === "Union" && right._tag === "Union") return combineUnions(left, right, path);
563
- if (left._tag === "Union") return combineUnionWithType(left, right, path, true);
564
- if (right._tag === "Union") return combineUnionWithType(right, left, path, false);
553
+ if (left._tag === "Union" && right._tag === "Union") return intersectUnions(left, right, path);
554
+ if (left._tag === "Union") return intersectUnionWithType(left, right, path, true);
555
+ if (right._tag === "Union") return intersectUnionWithType(right, left, path, false);
565
556
  switch (left._tag) {
566
557
  case "Null":
567
- return right._tag === "Null" ? combinedAnnotations({
558
+ return right._tag === "Null" ? annotateIntersection({
568
559
  _tag: "Null",
569
560
  checks: [...left.checks, ...right.checks]
570
561
  }, left, right) : never;
571
562
  case "String":
572
563
  if (right._tag === "Literal") {
573
- return combinePrimitiveWithLiteral(left, right);
564
+ return intersectPrimitiveWithLiteral(left, right);
574
565
  }
575
566
  if (right._tag !== "String") return never;
576
- const stringChecks = combineChecks(left.checks, right.checks, right.annotations);
567
+ const [stringChecks, stringAnnotations] = intersectChecks(left, right);
577
568
  return annotate({
578
569
  _tag: "String",
579
- checks: stringChecks ?? left.checks
580
- }, mergeAnnotations(left.annotations, stringChecks === undefined ? right.annotations : undefined));
570
+ checks: stringChecks
571
+ }, stringAnnotations);
581
572
  case "Number":
582
573
  if (right._tag === "Literal") {
583
- return combinePrimitiveWithLiteral(left, right);
574
+ return intersectPrimitiveWithLiteral(left, right);
584
575
  }
585
576
  if (right._tag !== "Number") return never;
586
- const numberChecks = combineChecks(left.checks, right.checks, right.annotations, ["effect/schema/isFinite", "effect/schema/isInt"]);
577
+ const [numberChecks, numberAnnotations] = intersectChecks(left, right, ["effect/schema/isFinite", "effect/schema/isInt"]);
587
578
  return annotate({
588
579
  _tag: "Number",
589
- checks: numberChecks ?? left.checks
590
- }, mergeAnnotations(left.annotations, numberChecks === undefined ? right.annotations : undefined));
580
+ checks: numberChecks
581
+ }, numberAnnotations);
591
582
  case "Boolean":
592
583
  if (right._tag === "Literal") {
593
- return combinePrimitiveWithLiteral(left, right);
584
+ return intersectPrimitiveWithLiteral(left, right);
594
585
  }
595
- return right._tag === "Boolean" ? combinedAnnotations({
586
+ return right._tag === "Boolean" ? annotateIntersection({
596
587
  _tag: "Boolean",
597
588
  checks: [...left.checks, ...right.checks]
598
589
  }, left, right) : never;
599
590
  case "Literal":
600
591
  if (right._tag === "Literal") {
601
- return left.literal === right.literal ? combinedAnnotations({
592
+ return left.literal === right.literal ? annotateIntersection({
602
593
  ...left,
603
594
  checks: [...left.checks, ...right.checks]
604
595
  }, left, right) : never;
605
596
  }
606
597
  if ((right._tag === "String" || right._tag === "Number") && satisfiesLiteral(right, left) || right._tag === "Boolean" && typeof left.literal === "boolean") {
607
- return combinedAnnotations(left, left, right);
598
+ return annotateIntersection(left, left, right);
608
599
  }
609
600
  return never;
610
601
  case "Arrays":
611
602
  {
612
603
  if (right._tag !== "Arrays") return never;
613
- const arrays = combineArrays(left, right, path);
604
+ const arrays = intersectArrays(left, right, path);
614
605
  if (arrays === undefined) return never;
615
- const arrayChecks = combineChecks(left.checks, right.checks, right.annotations, ["effect/schema/isUnique"]);
606
+ const [checks, annotations] = intersectChecks(left, right, ["effect/schema/isUnique"]);
616
607
  return annotate({
617
608
  _tag: "Arrays",
618
- elements: arrays.elements,
619
- rest: arrays.rest,
620
- checks: arrayChecks ?? left.checks
621
- }, mergeAnnotations(left.annotations, arrayChecks === undefined ? right.annotations : undefined));
609
+ ...arrays,
610
+ checks
611
+ }, annotations);
622
612
  }
623
613
  case "Objects":
624
614
  {
625
615
  if (right._tag !== "Objects") return never;
626
- const objectChecks = combineChecks(left.checks, right.checks, right.annotations);
627
- const scopes = [...objectScopesByProperties.get(left.propertySignatures), ...objectScopesByProperties.get(right.propertySignatures)];
628
- return lowerObject(scopes, objectChecks ?? left.checks, mergeAnnotations(left.annotations, objectChecks === undefined ? right.annotations : undefined), path);
616
+ const [checks, annotations] = intersectChecks(left, right);
617
+ const scopes = [...objectScopesByProperties.get(left.propertySignatures)[0], ...objectScopesByProperties.get(right.propertySignatures)[0]];
618
+ return lowerObject(scopes, checks, annotations, path);
629
619
  }
630
620
  }
631
621
  }
632
- function recur(input, path) {
622
+ function translateSchema(input, path, isRoot = false) {
633
623
  if (input === false) {
634
624
  return never;
635
625
  }
636
- if (typeof input !== "object" || input === null || Array.isArray(input)) return unknown;
626
+ if (!isObject(input)) return unknown;
637
627
  const schema = options?.onEnter === undefined ? input : options.onEnter(input);
638
628
  if (schema === undefined) {
639
629
  return unknown;
640
630
  }
641
- const enumIndex = Array.isArray(schema.enum) ? schema.enum.findIndex(value => typeof value === "object" && value !== null) : -1;
642
- if (enumIndex !== -1) {
643
- throw errorWithPath(`Unsupported structured JSON Schema value for "enum"`, [...path, "enum", enumIndex]);
644
- }
631
+ if (isObject(schema.not) && Object.keys(schema.not).length === 0) return annotate(never, jsonSchemaAnnotations(schema));
632
+ const wasInNestedResource = inNestedResource;
633
+ inNestedResource ||= !isRoot && typeof schema.$id === "string";
645
634
  for (const keyword of Object.keys(schema)) {
646
635
  if (keyword === "if" && !Object.hasOwn(schema, "then") && !Object.hasOwn(schema, "else")) continue;
647
636
  switch (keyword) {
@@ -653,41 +642,40 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
653
642
  case "not":
654
643
  case "unevaluatedItems":
655
644
  case "unevaluatedProperties":
656
- throw errorWithPath(`Unsupported JSON Schema keyword "${keyword}"`, [...path, keyword]);
645
+ throw errorWithPath(`Cannot import JSON Schema keyword "${keyword}": Effect has no equivalent constraint.`, [...path, keyword]);
657
646
  }
658
647
  }
659
- let representation = on(schema, path);
648
+ let representation = translateType(schema, path);
660
649
  if (Object.hasOwn(schema, "const")) {
661
- const literal = makeJsonLiteral(schema.const);
662
- if (literal === undefined && typeof schema.const === "object") {
663
- throw errorWithPath(`Unsupported structured JSON Schema value for "const"`, [...path, "const"]);
664
- }
665
- if (literal !== undefined) representation = combine(representation, literal, [...path, "const"]);
650
+ const constPath = [...path, "const"];
651
+ const literal = makeJsonLiteral(schema.const, constPath);
652
+ if (literal !== undefined) representation = intersect(representation, literal, constPath);
666
653
  }
667
654
  if (Array.isArray(schema.enum)) {
668
- const types = schema.enum.map(value => makeJsonLiteral(value) ?? unknown);
669
- representation = combine(representation, types.length === 1 ? types[0] : {
655
+ const types = schema.enum.map((value, index) => makeJsonLiteral(value, [...path, "enum", index]) ?? unknown);
656
+ representation = intersect(representation, types.length === 1 ? types[0] : {
670
657
  _tag: "Union",
671
658
  types,
672
659
  checks: []
673
660
  }, [...path, "enum"]);
674
661
  }
675
662
  if (typeof schema.$ref === "string") {
663
+ if (inNestedResource) {
664
+ throw errorWithPath("Cannot resolve $ref under a nested \"$id\". Resolve or flatten it first.", [...path, "$ref"]);
665
+ }
676
666
  const $ref = JsonSchema.getReferenceKey(schema.$ref);
677
667
  if ($ref === undefined) {
678
- throw errorWithPath(`Unsupported reference ${JSON.stringify(schema.$ref)}`, [...path, "$ref"]);
668
+ throw errorWithPath(`Unsupported $ref ${JSON.stringify(schema.$ref)}. Use "#/$defs/Name".`, [...path, "$ref"]);
679
669
  }
680
670
  if (!Object.hasOwn(document.definitions, $ref)) {
681
- throw errorWithPath(`Invalid reference ${JSON.stringify(schema.$ref)}`, [...path, "$ref"]);
671
+ throw errorWithPath(`Missing definition ${JSON.stringify($ref)} for $ref ${JSON.stringify(schema.$ref)}.`, [...path, "$ref"]);
682
672
  }
683
673
  if (!reachableDefinitions.has($ref)) reachableDefinitions.set($ref, path);
684
674
  const reference = {
685
675
  _tag: "Reference",
686
676
  $ref
687
677
  };
688
- representation = representation._tag === "Unknown" ? reference : representation._tag === "Never" ? never : combine(resolveReference(reference, path, {
689
- recursiveReferenceError: `Unsupported assertion siblings on recursive reference ${reference.$ref}`
690
- }), representation, path);
678
+ representation = representation._tag === "Unknown" ? reference : representation._tag === "Never" ? never : intersect(resolveReference(reference, path, `Recursive $ref ${JSON.stringify(reference.$ref)} cannot have sibling constraints.`), representation, path);
691
679
  }
692
680
  const annotations = jsonSchemaAnnotations(schema);
693
681
  if (annotations !== undefined && representation._tag === "Reference") {
@@ -699,7 +687,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
699
687
  representation = annotate(representation, annotations);
700
688
  if (Array.isArray(schema.allOf)) {
701
689
  for (let index = 0; index < schema.allOf.length; index++) {
702
- representation = combine(representation, recur(schema.allOf[index], [...path, "allOf", index]), [...path, "allOf", index]);
690
+ representation = intersect(representation, translateSchema(schema.allOf[index], [...path, "allOf", index]), [...path, "allOf", index]);
703
691
  }
704
692
  }
705
693
  for (const mode of ["anyOf", "oneOf"]) {
@@ -707,7 +695,7 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
707
695
  if (Array.isArray(members)) {
708
696
  const union = {
709
697
  _tag: "Union",
710
- types: members.map((member, index) => recur(member, [...path, mode, index])),
698
+ types: members.map((member, index) => translateSchema(member, [...path, mode, index])),
711
699
  ...(mode === "oneOf" ? {
712
700
  options: {
713
701
  mode
@@ -715,17 +703,18 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
715
703
  } : {}),
716
704
  checks: []
717
705
  };
718
- representation = combine(union, representation, [...path, mode]);
706
+ representation = intersect(union, representation, [...path, mode]);
719
707
  }
720
708
  }
709
+ inNestedResource = wasInNestedResource;
721
710
  return representation;
722
711
  }
723
- function on(schema, path) {
712
+ function translateType(schema, path) {
724
713
  const types = Array.isArray(schema.type) && schema.type.every(isImportedJsonSchemaType) ? schema.type : !isImportedJsonSchemaType(schema.type) && hasTypeSpecificKeywords(schema) ? jsonSchemaValueTypes : undefined;
725
714
  if (types !== undefined) {
726
715
  return {
727
716
  _tag: "Union",
728
- types: types.map(type => on({
717
+ types: types.map(type => translateType({
729
718
  ...schema,
730
719
  type
731
720
  }, path)),
@@ -761,14 +750,14 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
761
750
  const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
762
751
  const elements = prefixItems?.map((element, index) => ({
763
752
  isOptional: index + 1 > minItems,
764
- type: recur(element, [...path, "prefixItems", index])
753
+ type: translateSchema(element, [...path, "prefixItems", index])
765
754
  })) ?? [];
766
755
  const isTupleClosed = schema.items === false || schema.items === undefined && prefixItems !== undefined && schema.maxItems === prefixItems.length;
767
756
  const isMaxItemsRedundant = isTupleClosed && typeof schema.maxItems === "number" && schema.maxItems >= elements.length;
768
757
  return {
769
758
  _tag: "Arrays",
770
759
  elements,
771
- rest: isTupleClosed ? [] : [schema.items === undefined ? unknown : recur(schema.items, [...path, "items"])],
760
+ rest: isTupleClosed ? [] : [schema.items === undefined ? unknown : translateSchema(schema.items, [...path, "items"])],
772
761
  checks: collectArrayChecks(schema, isMaxItemsRedundant)
773
762
  };
774
763
  }
@@ -781,17 +770,17 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
781
770
  return unknown;
782
771
  }
783
772
  }
784
- function importPatternChecks(pattern, path) {
773
+ function importPatternCheck(pattern, path) {
785
774
  switch (options?.patterns ?? "error") {
786
775
  case "error":
787
- throw errorWithPath(`Pattern encountered while patterns is set to "error"`, path);
776
+ throw errorWithPath("Patterns may block validation and are disabled. Use patterns: \"apply\" for trusted schemas or \"ignore\" to discard them.", path);
788
777
  case "ignore":
789
- return [];
778
+ return undefined;
790
779
  case "apply":
791
- return [jsonSchemaFilter("effect/schema/isPattern", {
780
+ return jsonSchemaFilter("effect/schema/isPattern", {
792
781
  source: pattern,
793
782
  flags: ""
794
- })];
783
+ });
795
784
  }
796
785
  }
797
786
  function collectStringChecks(schema, path) {
@@ -799,7 +788,8 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
799
788
  addNumberCheck(checks, schema.minLength, "effect/schema/isMinLength", "minLength");
800
789
  addNumberCheck(checks, schema.maxLength, "effect/schema/isMaxLength", "maxLength");
801
790
  if (typeof schema.pattern === "string") {
802
- checks.push(...importPatternChecks(schema.pattern, [...path, "pattern"]));
791
+ const check = importPatternCheck(schema.pattern, [...path, "pattern"]);
792
+ if (check !== undefined) checks.push(check);
803
793
  }
804
794
  return checks;
805
795
  }
@@ -826,20 +816,20 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
826
816
  return checks;
827
817
  }
828
818
  function collectObjectScope(schema, path) {
829
- const sourceProperties = typeof schema.properties === "object" && schema.properties !== null && !Array.isArray(schema.properties) ? schema.properties : {};
830
- const required = Array.isArray(schema.required) ? schema.required.filter(key => typeof key === "string") : [];
819
+ const sourceProperties = isObject(schema.properties) ? schema.properties : {};
820
+ const required = new Set(Array.isArray(schema.required) ? schema.required.filter(key => typeof key === "string") : []);
831
821
  const propertyNames = Object.keys(sourceProperties);
832
822
  const keys = new Set([...propertyNames, ...required]);
833
823
  const properties = new Map(Array.from(keys, name => [name, {
834
- type: Object.hasOwn(sourceProperties, name) ? recur(sourceProperties[name], [...path, "properties", name]) : undefined,
835
- isOptional: !required.includes(name)
824
+ type: Object.hasOwn(sourceProperties, name) ? translateSchema(sourceProperties[name], [...path, "properties", name]) : undefined,
825
+ isOptional: !required.has(name)
836
826
  }]));
837
827
  const hasProperties = propertyNames.length > 0;
838
828
  const patterns = [];
839
- if (typeof schema.patternProperties === "object" && schema.patternProperties !== null && !Array.isArray(schema.patternProperties)) {
829
+ if (isObject(schema.patternProperties)) {
840
830
  for (const [pattern, value] of Object.entries(schema.patternProperties)) {
841
- const checks = importPatternChecks(pattern, [...path, "patternProperties", pattern]);
842
- if (checks.length === 0) return {
831
+ const check = importPatternCheck(pattern, [...path, "patternProperties", pattern]);
832
+ if (check === undefined) return {
843
833
  properties,
844
834
  hasProperties,
845
835
  patterns: [],
@@ -849,13 +839,13 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
849
839
  source: pattern,
850
840
  parameter: {
851
841
  _tag: "String",
852
- checks
842
+ checks: [check]
853
843
  },
854
- type: recur(value, [...path, "patternProperties", pattern])
844
+ type: translateSchema(value, [...path, "patternProperties", pattern])
855
845
  });
856
846
  }
857
847
  }
858
- const additionalProperties = schema.additionalProperties === false ? never : typeof schema.additionalProperties === "object" && schema.additionalProperties !== null ? recur(schema.additionalProperties, [...path, "additionalProperties"]) : unknown;
848
+ const additionalProperties = schema.additionalProperties === false ? never : isObject(schema.additionalProperties) ? translateSchema(schema.additionalProperties, [...path, "additionalProperties"]) : unknown;
859
849
  return {
860
850
  properties,
861
851
  hasProperties,
@@ -869,14 +859,15 @@ function translateJsonSchemaMultiDocument(document, options, singleRoot = false)
869
859
  addNumberCheck(checks, schema.maxProperties, "effect/schema/isMaxProperties", "maxProperties");
870
860
  if (schema.propertyNames !== undefined) {
871
861
  const propertyNamesPath = [...path, "propertyNames"];
872
- checks.push(jsonSchemaFilter("effect/schema/isPropertyNames", null, [combine(string, recur(schema.propertyNames, propertyNamesPath), propertyNamesPath)]));
862
+ checks.push(jsonSchemaFilter("effect/schema/isPropertyNames", null, [intersect(string, translateSchema(schema.propertyNames, propertyNamesPath), propertyNamesPath)]));
873
863
  }
874
864
  return checks;
875
865
  }
876
866
  const references = {};
877
- const representations = document.schemas.map((schema, index) => unknownJsonSchemas(recur(schema, singleRoot ? ["schema"] : ["schemas", index])));
878
- for (const [key, path] of reachableDefinitions) {
879
- InternalRecord.assignProperty(references, key, unknownJsonSchemas(translateDefinition(key, path)));
867
+ const representations = document.schemas.map((schema, index) => finalize(translateSchema(schema, singleRoot ? ["schema"] : ["schemas", index], true)));
868
+ // Definitions expanded away by intersections do not need a standalone output schema.
869
+ for (const key of finalReferences) {
870
+ InternalRecord.assignProperty(references, key, finalize(translateDefinition(key, reachableDefinitions.get(key))));
880
871
  }
881
872
  for (const {
882
873
  reference,