effect 4.0.0-beta.50 → 4.0.0-beta.51

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 (81) hide show
  1. package/dist/BigDecimal.d.ts.map +1 -1
  2. package/dist/BigDecimal.js +18 -14
  3. package/dist/BigDecimal.js.map +1 -1
  4. package/dist/BigInt.d.ts.map +1 -1
  5. package/dist/BigInt.js +4 -4
  6. package/dist/BigInt.js.map +1 -1
  7. package/dist/Brand.d.ts +2 -4
  8. package/dist/Brand.d.ts.map +1 -1
  9. package/dist/Brand.js.map +1 -1
  10. package/dist/Duration.js +1 -1
  11. package/dist/Duration.js.map +1 -1
  12. package/dist/Schema.d.ts +77 -9
  13. package/dist/Schema.d.ts.map +1 -1
  14. package/dist/Schema.js +37 -7
  15. package/dist/Schema.js.map +1 -1
  16. package/dist/SchemaAST.d.ts +6 -0
  17. package/dist/SchemaAST.d.ts.map +1 -1
  18. package/dist/SchemaAST.js +216 -229
  19. package/dist/SchemaAST.js.map +1 -1
  20. package/dist/SchemaGetter.d.ts +3 -5
  21. package/dist/SchemaGetter.d.ts.map +1 -1
  22. package/dist/SchemaGetter.js +3 -2
  23. package/dist/SchemaGetter.js.map +1 -1
  24. package/dist/SchemaIssue.d.ts.map +1 -1
  25. package/dist/SchemaIssue.js +29 -11
  26. package/dist/SchemaIssue.js.map +1 -1
  27. package/dist/SchemaParser.js +14 -2
  28. package/dist/SchemaParser.js.map +1 -1
  29. package/dist/internal/effect.js +142 -65
  30. package/dist/internal/effect.js.map +1 -1
  31. package/dist/unstable/cluster/Runners.d.ts.map +1 -1
  32. package/dist/unstable/cluster/Runners.js +3 -2
  33. package/dist/unstable/cluster/Runners.js.map +1 -1
  34. package/dist/unstable/cluster/SqlMessageStorage.d.ts.map +1 -1
  35. package/dist/unstable/cluster/SqlMessageStorage.js +1 -0
  36. package/dist/unstable/cluster/SqlMessageStorage.js.map +1 -1
  37. package/dist/unstable/cluster/SqlRunnerStorage.d.ts.map +1 -1
  38. package/dist/unstable/cluster/SqlRunnerStorage.js +6 -6
  39. package/dist/unstable/cluster/SqlRunnerStorage.js.map +1 -1
  40. package/dist/unstable/eventlog/SqlEventJournal.d.ts.map +1 -1
  41. package/dist/unstable/eventlog/SqlEventJournal.js +9 -8
  42. package/dist/unstable/eventlog/SqlEventJournal.js.map +1 -1
  43. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -1
  44. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js +6 -5
  45. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js.map +1 -1
  46. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts.map +1 -1
  47. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js +6 -5
  48. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js.map +1 -1
  49. package/dist/unstable/httpapi/OpenApi.d.ts +1 -10
  50. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  51. package/dist/unstable/httpapi/OpenApi.js +2 -11
  52. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  53. package/dist/unstable/observability/OtlpMetrics.js +1 -1
  54. package/dist/unstable/observability/OtlpMetrics.js.map +1 -1
  55. package/dist/unstable/observability/internal/protobuf.js +4 -4
  56. package/dist/unstable/observability/internal/protobuf.js.map +1 -1
  57. package/dist/unstable/rpc/RpcSerialization.d.ts +11 -0
  58. package/dist/unstable/rpc/RpcSerialization.d.ts.map +1 -1
  59. package/dist/unstable/rpc/RpcSerialization.js +14 -9
  60. package/dist/unstable/rpc/RpcSerialization.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/BigDecimal.ts +20 -16
  63. package/src/BigInt.ts +4 -4
  64. package/src/Brand.ts +2 -4
  65. package/src/Duration.ts +1 -1
  66. package/src/Schema.ts +87 -11
  67. package/src/SchemaAST.ts +315 -267
  68. package/src/SchemaGetter.ts +4 -6
  69. package/src/SchemaIssue.ts +28 -15
  70. package/src/SchemaParser.ts +8 -2
  71. package/src/internal/effect.ts +196 -69
  72. package/src/unstable/cluster/Runners.ts +8 -5
  73. package/src/unstable/cluster/SqlMessageStorage.ts +1 -0
  74. package/src/unstable/cluster/SqlRunnerStorage.ts +12 -6
  75. package/src/unstable/eventlog/SqlEventJournal.ts +10 -2
  76. package/src/unstable/eventlog/SqlEventLogServerEncrypted.ts +8 -3
  77. package/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +9 -3
  78. package/src/unstable/httpapi/OpenApi.ts +2 -14
  79. package/src/unstable/observability/OtlpMetrics.ts +1 -1
  80. package/src/unstable/observability/internal/protobuf.ts +4 -4
  81. package/src/unstable/rpc/RpcSerialization.ts +41 -36
package/src/SchemaAST.ts CHANGED
@@ -77,10 +77,10 @@ import * as Arr from "./Array.ts"
77
77
  import * as Cause from "./Cause.ts"
78
78
  import type * as Combiner from "./Combiner.ts"
79
79
  import * as Effect from "./Effect.ts"
80
- import type * as Exit from "./Exit.ts"
80
+ import * as Exit from "./Exit.ts"
81
81
  import { format, formatPropertyKey } from "./Formatter.ts"
82
82
  import { memoize } from "./Function.ts"
83
- import { effectIsExit } from "./internal/effect.ts"
83
+ import { effectIsExit, iterateEager } from "./internal/effect.ts"
84
84
  import * as internalRecord from "./internal/record.ts"
85
85
  import * as InternalAnnotations from "./internal/schema/annotations.ts"
86
86
  import * as Option from "./Option.ts"
@@ -440,6 +440,13 @@ export interface ParseOptions {
440
440
  * transformations.
441
441
  */
442
442
  readonly disableChecks?: boolean | undefined
443
+
444
+ /**
445
+ * The maximum number of async effects to run concurrently.
446
+ *
447
+ * Defaults to 1.
448
+ */
449
+ readonly concurrency?: number | "unbounded" | undefined
443
450
  }
444
451
 
445
452
  /** @internal */
@@ -1375,6 +1382,19 @@ export class Arrays extends Base {
1375
1382
  const elements = ast.elements.map((ast) => ({ ast, parser: recur(ast) }))
1376
1383
  const rest = ast.rest.map((ast) => ({ ast, parser: recur(ast) }))
1377
1384
  const elementLen = elements.length
1385
+
1386
+ const [head, ...tail] = rest
1387
+ const tailLen = tail.length
1388
+
1389
+ function getParser(tailThreshold: number, index: number): { readonly ast: AST; readonly parser: Parser.Parser } {
1390
+ if (index < elementLen) {
1391
+ return elements[index]
1392
+ } else if (index >= tailThreshold) {
1393
+ return tail[index - tailThreshold]
1394
+ }
1395
+ return head
1396
+ }
1397
+
1378
1398
  return Effect.fnUntracedEager(function*(oinput, options) {
1379
1399
  if (oinput._tag === "None") {
1380
1400
  return oinput
@@ -1386,132 +1406,42 @@ export class Arrays extends Base {
1386
1406
  return yield* Effect.fail(new Issue.InvalidType(ast, oinput))
1387
1407
  }
1388
1408
 
1389
- const output: Array<unknown> = []
1390
- let issues: Arr.NonEmptyArray<Issue.Issue> | undefined
1391
- const errorsAllOption = options.errors === "all"
1392
-
1393
- let i = 0
1394
- // ---------------------------------------------
1395
- // handle elements
1396
- // ---------------------------------------------
1397
- for (; i < elementLen; i++) {
1398
- const e = elements[i]
1399
- const value = i < input.length ? Option.some(input[i]) : Option.none()
1400
- const eff = e.parser(value, options)
1401
- const exit = effectIsExit(eff) ? eff : yield* Effect.exit(eff)
1402
- if (exit._tag === "Failure") {
1403
- const issueElement = Cause.findError(exit.cause)
1404
- if (Result.isFailure(issueElement)) {
1405
- return yield* exit
1406
- }
1407
- const issue = new Issue.Pointer([i], issueElement.success)
1408
- if (errorsAllOption) {
1409
- if (issues) issues.push(issue)
1410
- else issues = [issue]
1411
- } else {
1412
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1413
- }
1414
- } else if (exit.value._tag === "Some") {
1415
- output[i] = exit.value.value
1416
- } else if (!isOptional(e.ast)) {
1417
- const issue = new Issue.Pointer([i], new Issue.MissingKey(e.ast.context?.annotations))
1418
- if (errorsAllOption) {
1419
- if (issues) issues.push(issue)
1420
- else issues = [issue]
1421
- } else {
1422
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1423
- }
1424
- }
1409
+ const len = input.length
1410
+ const state = {
1411
+ ast,
1412
+ getParser,
1413
+ oinput,
1414
+ len,
1415
+ tailThreshold: resolveTailThreshold(len, elementLen, tailLen),
1416
+ output: new globalThis.Array(len),
1417
+ issues: undefined as Arr.NonEmptyArray<Issue.Issue> | undefined,
1418
+ options
1425
1419
  }
1420
+ const concurrency = resolveConcurrency(options?.concurrency)
1421
+ const eff = parseArray(state, input, {
1422
+ concurrency: concurrency?.concurrency,
1423
+ end: ast.rest.length === 0 ? elementLen : Math.max(len, elementLen + tailLen)
1424
+ })
1425
+ if (eff) yield* eff
1426
+
1426
1427
  // ---------------------------------------------
1427
- // handle rest element
1428
+ // handle excess indexes
1428
1429
  // ---------------------------------------------
1429
- const len = input.length
1430
- if (ast.rest.length > 0) {
1431
- const [head, ...tail] = rest
1432
- const keyAnnotations = head.ast.context?.annotations
1433
- for (; i < len - tail.length; i++) {
1434
- const eff = head.parser(Option.some(input[i]), options)
1435
- const exit = effectIsExit(eff) ? eff : yield* Effect.exit(eff)
1436
- if (exit._tag === "Failure") {
1437
- const issueRest = Cause.findError(exit.cause)
1438
- if (Result.isFailure(issueRest)) {
1439
- return yield* exit
1440
- }
1441
- const issue = new Issue.Pointer([i], issueRest.success)
1442
- if (errorsAllOption) {
1443
- if (issues) issues.push(issue)
1444
- else issues = [issue]
1445
- } else {
1446
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1447
- }
1448
- } else if (exit.value._tag === "Some") {
1449
- output[i] = exit.value.value
1450
- } else {
1451
- const issue = new Issue.Pointer([i], new Issue.MissingKey(keyAnnotations))
1452
- if (errorsAllOption) {
1453
- if (issues) issues.push(issue)
1454
- else issues = [issue]
1455
- } else {
1456
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1457
- }
1458
- }
1459
- }
1460
- // ---------------------------------------------
1461
- // handle post rest elements
1462
- // ---------------------------------------------
1463
- for (let j = 0; j < tail.length; j++) {
1464
- const index = i + j
1465
- if (len < index) {
1466
- continue
1467
- } else {
1468
- const tailj = tail[j]
1469
- const keyAnnotations = tailj.ast.context?.annotations
1470
- const eff = tailj.parser(Option.some(input[index]), options)
1471
- const exit = effectIsExit(eff) ? eff : yield* Effect.exit(eff)
1472
- if (exit._tag === "Failure") {
1473
- const issueRest = Cause.findError(exit.cause)
1474
- if (Result.isFailure(issueRest)) {
1475
- return yield* exit
1476
- }
1477
- const issue = new Issue.Pointer([index], issueRest.success)
1478
- if (errorsAllOption) {
1479
- if (issues) issues.push(issue)
1480
- else issues = [issue]
1481
- } else {
1482
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1483
- }
1484
- } else if (exit.value._tag === "Some") {
1485
- output[index] = exit.value.value
1486
- } else {
1487
- const issue = new Issue.Pointer([index], new Issue.MissingKey(keyAnnotations))
1488
- if (errorsAllOption) {
1489
- if (issues) issues.push(issue)
1490
- else issues = [issue]
1491
- } else {
1492
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1493
- }
1494
- }
1495
- }
1496
- }
1497
- } else {
1498
- // ---------------------------------------------
1499
- // handle excess indexes
1500
- // ---------------------------------------------
1430
+ if (ast.rest.length === 0 && len > elementLen) {
1501
1431
  for (let i = elementLen; i <= len - 1; i++) {
1502
1432
  const issue = new Issue.Pointer([i], new Issue.UnexpectedKey(ast, input[i]))
1503
- if (errorsAllOption) {
1504
- if (issues) issues.push(issue)
1505
- else issues = [issue]
1433
+ if (options.errors === "all") {
1434
+ if (state.issues) state.issues.push(issue)
1435
+ else state.issues = [issue]
1506
1436
  } else {
1507
1437
  return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1508
1438
  }
1509
1439
  }
1510
1440
  }
1511
- if (issues) {
1512
- return yield* Effect.fail(new Issue.Composite(ast, oinput, issues))
1441
+ if (state.issues) {
1442
+ return yield* Effect.fail(new Issue.Composite(ast, oinput, state.issues))
1513
1443
  }
1514
- return Option.some(output)
1444
+ return Option.some(state.output)
1515
1445
  })
1516
1446
  }
1517
1447
  /** @internal */
@@ -1527,6 +1457,74 @@ export class Arrays extends Base {
1527
1457
  return "array"
1528
1458
  }
1529
1459
  }
1460
+ const parseArray = iterateEager<{
1461
+ readonly ast: AST
1462
+ readonly oinput: Option.Option<unknown>
1463
+ readonly len: number
1464
+ readonly getParser: (tailThreshold: number, index: number) => { readonly ast: AST; readonly parser: Parser.Parser }
1465
+ readonly tailThreshold: number
1466
+ readonly options: ParseOptions
1467
+ readonly output: Array<unknown>
1468
+ issues: Array<Issue.Issue> | undefined
1469
+ }, unknown>()({
1470
+ onItem(s, item, i) {
1471
+ const value = i < s.len ? Option.some(item) : Option.none()
1472
+ return s.getParser(s.tailThreshold, i).parser(value, s.options)
1473
+ },
1474
+ step(s, _, exit, i) {
1475
+ if (exit._tag === "Failure") {
1476
+ return wrapPropertyKeyIssue(s, s.ast, i, exit)
1477
+ } else if (exit.value._tag === "Some") {
1478
+ s.output[i] = exit.value.value
1479
+ } else {
1480
+ const p = s.getParser(s.tailThreshold, i)
1481
+ if (isOptional(p.ast)) return
1482
+ const issue = new Issue.Pointer([i], new Issue.MissingKey(p.ast.context?.annotations))
1483
+ if (s.options.errors === "all") {
1484
+ if (s.issues) s.issues.push(issue)
1485
+ else s.issues = [issue]
1486
+ } else {
1487
+ return Exit.fail(new Issue.Composite(s.ast, s.oinput, [issue]))
1488
+ }
1489
+ }
1490
+ }
1491
+ })
1492
+
1493
+ function resolveTailThreshold(
1494
+ inputLen: number,
1495
+ elementLen: number,
1496
+ tailLen: number
1497
+ ) {
1498
+ return Math.max(elementLen, inputLen - tailLen)
1499
+ }
1500
+
1501
+ const resolveConcurrency = (value: number | "unbounded" | undefined) => {
1502
+ value = value === "unbounded" ? Infinity : value ?? 1
1503
+ return value > 1 ? { concurrency: value } : undefined
1504
+ }
1505
+
1506
+ const wrapPropertyKeyIssue = (
1507
+ s: {
1508
+ readonly oinput: Option.Option<unknown>
1509
+ readonly options: ParseOptions
1510
+ issues: Array<Issue.Issue> | undefined
1511
+ },
1512
+ ast: AST,
1513
+ key: PropertyKey,
1514
+ exit: Exit.Failure<any, Issue.Issue>
1515
+ ) => {
1516
+ const issueResult = Cause.findError(exit.cause)
1517
+ if (Result.isFailure(issueResult)) {
1518
+ return exit
1519
+ }
1520
+ const issue = new Issue.Pointer([key], issueResult.success)
1521
+ if (s.options.errors === "all") {
1522
+ if (s.issues) s.issues.push(issue)
1523
+ else s.issues = [issue]
1524
+ } else {
1525
+ return Exit.fail(new Issue.Composite(ast, s.oinput, [issue]))
1526
+ }
1527
+ }
1530
1528
 
1531
1529
  /**
1532
1530
  * floating point or integer, with optional exponent
@@ -1720,12 +1718,11 @@ export class Objects extends Base {
1720
1718
  const expectedKeys: Array<PropertyKey> = []
1721
1719
  const expectedKeysSet = new Set<PropertyKey>()
1722
1720
  const properties: Array<{
1723
- readonly ps: PropertySignature
1721
+ readonly ps: PropertySignature | IndexSignature
1724
1722
  readonly parser: Parser.Parser
1725
1723
  readonly name: PropertyKey
1726
1724
  readonly type: AST
1727
1725
  }> = []
1728
- const propertyCount = ast.propertySignatures.length
1729
1726
  for (const ps of ast.propertySignatures) {
1730
1727
  expectedKeys.push(ps.name)
1731
1728
  expectedKeysSet.add(ps.name)
@@ -1743,6 +1740,54 @@ export class Objects extends Base {
1743
1740
  if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
1744
1741
  return fromRefinement(ast, Predicate.isNotNullish)
1745
1742
  }
1743
+
1744
+ const parseIndexes = indexCount > 0 ?
1745
+ iterateEager<{
1746
+ readonly oinput: Option.Option<unknown>
1747
+ readonly input: Record<PropertyKey, unknown>
1748
+ readonly options: ParseOptions
1749
+ readonly out: Record<PropertyKey, unknown>
1750
+ issues: Array<Issue.Issue> | undefined
1751
+ }, [key: PropertyKey, is: IndexSignature]>()({
1752
+ onItem: Effect.fnUntracedEager(function*(
1753
+ s,
1754
+ [key, is]
1755
+ ) {
1756
+ const parserKey = recur(indexSignatureParameterFromString(is.parameter))
1757
+ const effKey = parserKey(Option.some(key), s.options)
1758
+ const exitKey = (effectIsExit(effKey) ? effKey : yield* Effect.exit(effKey)) as Exit.Exit<
1759
+ Option.Option<PropertyKey>,
1760
+ Issue.Issue
1761
+ >
1762
+ if (exitKey._tag === "Failure") {
1763
+ const eff = wrapPropertyKeyIssue(s, ast, key, exitKey)
1764
+ if (eff) yield* eff
1765
+ return
1766
+ }
1767
+
1768
+ const value: Option.Option<unknown> = Option.some(s.input[key])
1769
+ const parserValue = recur(is.type)
1770
+ const effValue = parserValue(value, s.options)
1771
+ const exitValue = effectIsExit(effValue) ? effValue : yield* Effect.exit(effValue)
1772
+ if (exitValue._tag === "Failure") {
1773
+ const eff = wrapPropertyKeyIssue(s, ast, key, exitValue)
1774
+ if (eff) yield* eff
1775
+ return
1776
+ } else if (exitKey.value._tag === "Some" && exitValue.value._tag === "Some") {
1777
+ const k2 = exitKey.value.value
1778
+ const v2 = exitValue.value.value
1779
+ if (is.merge && is.merge.decode && Object.hasOwn(s.out, k2)) {
1780
+ const [k, v] = is.merge.decode.combine([k2, s.out[k2]], [k2, v2])
1781
+ internalRecord.set(s.out, k, v)
1782
+ } else {
1783
+ internalRecord.set(s.out, k2, v2)
1784
+ }
1785
+ }
1786
+ }),
1787
+ step: (_s, _, exit: Exit.Exit<void, Issue.Issue>) => exit._tag === "Failure" ? exit : undefined
1788
+ }) :
1789
+ undefined
1790
+
1746
1791
  return Effect.fnUntracedEager(function*(oinput, options) {
1747
1792
  if (oinput._tag === "None") {
1748
1793
  return oinput
@@ -1755,7 +1800,14 @@ export class Objects extends Base {
1755
1800
  }
1756
1801
 
1757
1802
  const out: Record<PropertyKey, unknown> = {}
1758
- let issues: Arr.NonEmptyArray<Issue.Issue> | undefined
1803
+ const state = {
1804
+ ast,
1805
+ oinput,
1806
+ input,
1807
+ out,
1808
+ issues: undefined as Arr.NonEmptyArray<Issue.Issue> | undefined,
1809
+ options
1810
+ }
1759
1811
  const errorsAllOption = options.errors === "all"
1760
1812
  const onExcessPropertyError = options.onExcessProperty === "error"
1761
1813
  const onExcessPropertyPreserve = options.onExcessProperty === "preserve"
@@ -1773,10 +1825,10 @@ export class Objects extends Base {
1773
1825
  if (onExcessPropertyError) {
1774
1826
  const issue = new Issue.Pointer([key], new Issue.UnexpectedKey(ast, input[key]))
1775
1827
  if (errorsAllOption) {
1776
- if (issues) {
1777
- issues.push(issue)
1828
+ if (state.issues) {
1829
+ state.issues.push(issue)
1778
1830
  } else {
1779
- issues = [issue]
1831
+ state.issues = [issue]
1780
1832
  }
1781
1833
  continue
1782
1834
  } else {
@@ -1790,111 +1842,33 @@ export class Objects extends Base {
1790
1842
  }
1791
1843
  }
1792
1844
 
1845
+ const concurrency = resolveConcurrency(options?.concurrency)
1846
+
1793
1847
  // ---------------------------------------------
1794
1848
  // handle property signatures
1795
1849
  // ---------------------------------------------
1796
- for (let i = 0; i < propertyCount; i++) {
1797
- const p = properties[i]
1798
- const value: Option.Option<unknown> = Object.hasOwn(input, p.name)
1799
- ? Option.some(input[p.name])
1800
- : Option.none()
1801
- const eff = p.parser(value, options)
1802
- const exit = effectIsExit(eff) ? eff : yield* Effect.exit(eff)
1803
- if (exit._tag === "Failure") {
1804
- const issueProp = Cause.findError(exit.cause)
1805
- if (Result.isFailure(issueProp)) {
1806
- return yield* exit
1807
- }
1808
- const issue = new Issue.Pointer([p.name], issueProp.success)
1809
- if (errorsAllOption) {
1810
- if (issues) issues.push(issue)
1811
- else issues = [issue]
1812
- continue
1813
- } else {
1814
- return yield* Effect.fail(new Issue.Composite(ast, oinput, [issue]))
1815
- }
1816
- } else if (exit.value._tag === "Some") {
1817
- internalRecord.set(out, p.name, exit.value.value)
1818
- } else if (!isOptional(p.type)) {
1819
- const issue = new Issue.Pointer([p.name], new Issue.MissingKey(p.type.context?.annotations))
1820
- if (errorsAllOption) {
1821
- if (issues) issues.push(issue)
1822
- else issues = [issue]
1823
- continue
1824
- } else {
1825
- return yield* Effect.fail(
1826
- new Issue.Composite(ast, oinput, [issue])
1827
- )
1828
- }
1829
- }
1830
- }
1850
+ const eff = parseProperties(state, properties, concurrency)
1851
+ if (eff) yield* eff
1831
1852
 
1832
1853
  // ---------------------------------------------
1833
1854
  // handle index signatures
1834
1855
  // ---------------------------------------------
1835
- if (indexCount > 0) {
1856
+ if (parseIndexes) {
1857
+ const keyPairs = Arr.empty<[PropertyKey, IndexSignature]>()
1836
1858
  for (let i = 0; i < indexCount; i++) {
1837
1859
  const is = ast.indexSignatures[i]
1838
1860
  const keys = getIndexSignatureKeys(input, is.parameter)
1839
1861
  for (let j = 0; j < keys.length; j++) {
1840
1862
  const key = keys[j]
1841
- const parserKey = recur(indexSignatureParameterFromString(is.parameter))
1842
- const effKey = parserKey(Option.some(key), options)
1843
- const exitKey = (effectIsExit(effKey) ? effKey : yield* Effect.exit(effKey)) as Exit.Exit<
1844
- Option.Option<PropertyKey>,
1845
- Issue.Issue
1846
- >
1847
- if (exitKey._tag === "Failure") {
1848
- const issueKey = Cause.findError(exitKey.cause)
1849
- if (Result.isFailure(issueKey)) {
1850
- return yield* exitKey
1851
- }
1852
- const issue = new Issue.Pointer([key], issueKey.success)
1853
- if (errorsAllOption) {
1854
- if (issues) issues.push(issue)
1855
- else issues = [issue]
1856
- continue
1857
- }
1858
- return yield* Effect.fail(
1859
- new Issue.Composite(ast, oinput, [issue])
1860
- )
1861
- }
1862
-
1863
- const value: Option.Option<unknown> = Option.some(input[key])
1864
- const parserValue = recur(is.type)
1865
- const effValue = parserValue(value, options)
1866
- const exitValue = effectIsExit(effValue) ? effValue : yield* Effect.exit(effValue)
1867
- if (exitValue._tag === "Failure") {
1868
- const issueValue = Cause.findError(exitValue.cause)
1869
- if (Result.isFailure(issueValue)) {
1870
- return yield* exitValue
1871
- }
1872
- const issue = new Issue.Pointer([key], issueValue.success)
1873
- if (errorsAllOption) {
1874
- if (issues) issues.push(issue)
1875
- else issues = [issue]
1876
- continue
1877
- } else {
1878
- return yield* Effect.fail(
1879
- new Issue.Composite(ast, oinput, [issue])
1880
- )
1881
- }
1882
- } else if (exitKey.value._tag === "Some" && exitValue.value._tag === "Some") {
1883
- const k2 = exitKey.value.value
1884
- const v2 = exitValue.value.value
1885
- if (is.merge && is.merge.decode && Object.hasOwn(out, k2)) {
1886
- const [k, v] = is.merge.decode.combine([k2, out[k2]], [k2, v2])
1887
- internalRecord.set(out, k, v)
1888
- } else {
1889
- internalRecord.set(out, k2, v2)
1890
- }
1891
- }
1863
+ keyPairs.push([key, is])
1892
1864
  }
1893
1865
  }
1866
+ const eff = parseIndexes(state, keyPairs, concurrency)
1867
+ if (eff) yield* eff
1894
1868
  }
1895
1869
 
1896
- if (issues) {
1897
- return yield* Effect.fail(new Issue.Composite(ast, oinput, issues))
1870
+ if (state.issues) {
1871
+ return yield* Effect.fail(new Issue.Composite(ast, oinput, state.issues))
1898
1872
  }
1899
1873
  if (options.propertyOrder === "original") {
1900
1874
  // preserve input keys order
@@ -1947,6 +1921,56 @@ export class Objects extends Base {
1947
1921
  }
1948
1922
  }
1949
1923
 
1924
+ type ParsedProperty = {
1925
+ readonly ps: PropertySignature | IndexSignature
1926
+ readonly parser: Parser.Parser
1927
+ readonly name: PropertyKey
1928
+ readonly type: AST
1929
+ }
1930
+
1931
+ const parseProperties = iterateEager<{
1932
+ readonly ast: AST
1933
+ readonly oinput: Option.Option<unknown>
1934
+ readonly input: Record<PropertyKey, unknown>
1935
+ readonly options: ParseOptions
1936
+ readonly out: Record<PropertyKey, unknown>
1937
+ issues: Array<Issue.Issue> | undefined
1938
+ }, ParsedProperty>()({
1939
+ onItem(
1940
+ s: {
1941
+ readonly oinput: Option.Option<unknown>
1942
+ readonly input: Record<PropertyKey, unknown>
1943
+ readonly options: ParseOptions
1944
+ readonly out: Record<PropertyKey, unknown>
1945
+ issues: Array<Issue.Issue> | undefined
1946
+ },
1947
+ p
1948
+ ) {
1949
+ const value: Option.Option<unknown> = Object.hasOwn(s.input, p.name)
1950
+ ? Option.some(s.input[p.name])
1951
+ : Option.none()
1952
+ return p.parser(value, s.options)
1953
+ },
1954
+ step(s, p, exit) {
1955
+ if (exit._tag === "Failure") {
1956
+ return wrapPropertyKeyIssue(s, s.ast, p.name, exit)
1957
+ } else if (exit.value._tag === "Some") {
1958
+ internalRecord.set(s.out, p.name, exit.value.value)
1959
+ } else if (!isOptional(p.type)) {
1960
+ const issue = new Issue.Pointer([p.name], new Issue.MissingKey(p.type.context?.annotations))
1961
+ if (s.options.errors === "all") {
1962
+ if (s.issues) s.issues.push(issue)
1963
+ else s.issues = [issue]
1964
+ return
1965
+ } else {
1966
+ return Exit.fail(
1967
+ new Issue.Composite(s.ast, s.oinput, [issue])
1968
+ )
1969
+ }
1970
+ }
1971
+ }
1972
+ })
1973
+
1950
1974
  function mergeChecks(checks: Checks | undefined, b: AST): Checks | undefined {
1951
1975
  if (!checks) {
1952
1976
  return b.checks
@@ -2252,54 +2276,33 @@ export class Union<A extends AST = AST> extends Base {
2252
2276
  getParser(recur: (ast: AST) => Parser.Parser): Parser.Parser {
2253
2277
  // oxlint-disable-next-line @typescript-eslint/no-this-alias
2254
2278
  const ast = this
2255
- return Effect.fnUntracedEager(function*(oinput, options) {
2279
+
2280
+ return (oinput, options) => {
2256
2281
  if (oinput._tag === "None") {
2257
- return oinput
2282
+ return Effect.succeed(oinput)
2258
2283
  }
2259
2284
  const input = oinput.value
2260
- const oneOf = ast.mode === "oneOf"
2261
2285
  const candidates = getCandidates(input, ast.types)
2262
- let issues: Arr.NonEmptyArray<Issue.Issue> | undefined
2263
2286
 
2264
- const tracking: {
2265
- out: Option.Option<unknown> | undefined
2266
- successes: Array<AST>
2267
- } = {
2287
+ const state = {
2288
+ ast,
2289
+ recur,
2290
+ oinput,
2291
+ input,
2268
2292
  out: undefined,
2269
- successes: []
2270
- }
2271
- for (let i = 0; i < candidates.length; i++) {
2272
- const candidate = candidates[i]
2273
- const parser = recur(candidate)
2274
- const eff = parser(oinput, options)
2275
- const exit = effectIsExit(eff) ? eff : yield* Effect.exit(eff)
2276
- if (exit._tag === "Failure") {
2277
- const issueResult = Cause.findError(exit.cause)
2278
- if (Result.isFailure(issueResult)) {
2279
- return yield* exit
2280
- }
2281
- if (issues) issues.push(issueResult.success)
2282
- else issues = [issueResult.success]
2283
- continue
2284
- } else {
2285
- if (tracking.out && oneOf) {
2286
- tracking.successes.push(candidate)
2287
- return yield* Effect.fail(new Issue.OneOf(ast, input, tracking.successes))
2288
- }
2289
- tracking.out = exit.value
2290
- tracking.successes.push(candidate)
2291
- if (!oneOf) {
2292
- break
2293
- }
2294
- }
2293
+ successes: [],
2294
+ issues: undefined as Arr.NonEmptyArray<Issue.Issue> | undefined,
2295
+ options
2295
2296
  }
2296
-
2297
- if (tracking.out) {
2298
- return tracking.out
2299
- } else {
2300
- return yield* Effect.fail(new Issue.AnyOf(ast, input, issues ?? []))
2297
+ const concurrency = resolveConcurrency(options?.concurrency)
2298
+ const eff = parseUnion(state, candidates, concurrency)
2299
+ if (!eff) {
2300
+ return state.out ? Effect.succeed(state.out) : Effect.fail(new Issue.AnyOf(ast, input, state.issues ?? []))
2301
2301
  }
2302
- })
2302
+ return Effect.flatMap(eff, (_) => {
2303
+ return state.out ? Effect.succeed(state.out) : Effect.fail(new Issue.AnyOf(ast, input, state.issues ?? []))
2304
+ })
2305
+ }
2303
2306
  }
2304
2307
  /** @internal */
2305
2308
  recur(recur: (ast: AST) => AST) {
@@ -2347,6 +2350,42 @@ export class Union<A extends AST = AST> extends Base {
2347
2350
  }
2348
2351
  }
2349
2352
 
2353
+ const parseUnion = iterateEager<{
2354
+ readonly recur: (ast: AST) => Parser.Parser
2355
+ readonly ast: Union
2356
+ readonly oinput: Option.Option<unknown>
2357
+ readonly input: unknown
2358
+ readonly options: ParseOptions
2359
+ out: Option.Option<unknown> | undefined
2360
+ successes: Array<AST>
2361
+ issues: Array<Issue.Issue> | undefined
2362
+ }, AST>()({
2363
+ onItem(s, ast) {
2364
+ const parser = s.recur(ast)
2365
+ return parser(s.oinput, s.options)
2366
+ },
2367
+ step(s, candidate, exit) {
2368
+ if (exit._tag === "Failure") {
2369
+ const issueResult = Cause.findError(exit.cause)
2370
+ if (Result.isFailure(issueResult)) {
2371
+ return exit
2372
+ }
2373
+ if (s.issues) s.issues.push(issueResult.success)
2374
+ else s.issues = [issueResult.success]
2375
+ } else {
2376
+ if (s.out && s.ast.mode === "oneOf") {
2377
+ s.successes.push(candidate)
2378
+ return Exit.fail(new Issue.OneOf(s.ast, s.input, s.successes))
2379
+ }
2380
+ s.out = exit.value
2381
+ s.successes.push(candidate)
2382
+ if (s.ast.mode === "anyOf") {
2383
+ return Exit.void
2384
+ }
2385
+ }
2386
+ }
2387
+ })
2388
+
2350
2389
  const nonFiniteLiterals = new Union([
2351
2390
  new Literal("Infinity"),
2352
2391
  new Literal("-Infinity"),
@@ -2550,19 +2589,12 @@ export type Check<T> = Filter<T> | FilterGroup<T>
2550
2589
 
2551
2590
  /** @internal */
2552
2591
  export function makeFilter<T>(
2553
- filter: (
2554
- input: T,
2555
- ast: AST,
2556
- options: ParseOptions
2557
- ) => undefined | boolean | string | Issue.Issue | {
2558
- readonly path: ReadonlyArray<PropertyKey>
2559
- readonly message: string
2560
- },
2592
+ filter: (input: T, ast: AST, options: ParseOptions) => Schema.FilterOutput,
2561
2593
  annotations?: Schema.Annotations.Filter | undefined,
2562
2594
  aborted: boolean = false
2563
2595
  ): Filter<T> {
2564
2596
  return new Filter(
2565
- (input, ast, options) => Issue.make(input, filter(input, ast, options)),
2597
+ (input, ast, options) => Issue.make(input, ast, filter(input, ast, options)),
2566
2598
  annotations,
2567
2599
  aborted
2568
2600
  )
@@ -3372,7 +3404,14 @@ export const resolveDescription: (ast: AST) => string | undefined = InternalAnno
3372
3404
  * @internal
3373
3405
  */
3374
3406
  export function isJson(u: unknown): u is Schema.Json {
3375
- const seen = new Set<unknown>()
3407
+ // `onPath` is the current recursion stack: nodes between the root and the
3408
+ // one being visited. A hit here means we looped back to an ancestor — a
3409
+ // real cycle, not a DAG — so the value is not JSON.
3410
+ const onPath = new Set<unknown>()
3411
+ // `validated` memoizes subtrees we've already fully checked. Without it, a
3412
+ // diamond-shaped DAG (same node reached through multiple parents) would be
3413
+ // re-traversed once per parent, which is exponential in the nesting depth.
3414
+ const validated = new Set<unknown>()
3376
3415
  return recur(u)
3377
3416
 
3378
3417
  function recur(u: unknown): boolean {
@@ -3385,14 +3424,23 @@ export function isJson(u: unknown): u is Schema.Json {
3385
3424
  if (typeof u !== "object" || u === undefined) {
3386
3425
  return false
3387
3426
  }
3388
- if (seen.has(u)) {
3427
+ if (onPath.has(u)) {
3389
3428
  return false
3390
3429
  }
3391
- seen.add(u)
3392
- if (Array.isArray(u)) {
3393
- return u.every(recur)
3430
+ if (validated.has(u)) {
3431
+ return true
3394
3432
  }
3395
- return Object.keys(u).every((key) => recur((u as Record<string, unknown>)[key]))
3433
+ onPath.add(u)
3434
+ const ok = Array.isArray(u)
3435
+ ? u.every(recur)
3436
+ : Object.keys(u).every((key) => recur((u as Record<string, unknown>)[key]))
3437
+ // Pop on exit so siblings reaching the same node via a different path
3438
+ // don't see it as an ancestor (that would reject valid DAGs).
3439
+ onPath.delete(u)
3440
+ if (ok) {
3441
+ validated.add(u)
3442
+ }
3443
+ return ok
3396
3444
  }
3397
3445
  }
3398
3446