functionalscript 0.46.0 → 0.47.0

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 (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +120 -0
  157. package/fjs/types/rtti/validate/module.f.mjs +351 -0
  158. package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
  159. package/fjs/types/rtti/validate/proof.f.mjs +781 -0
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. package/package.json +2 -3
@@ -25,7 +25,7 @@
25
25
  import { assert, assertNotNullish } from '../../../asserts/module.f.mjs'
26
26
  import { at, definedEntries, definedValues } from '../../object/module.f.mjs'
27
27
  import { ok } from '../../result/module.f.mjs'
28
- import { eachEntry, isArray, verror } from '../common/module.f.mjs'
28
+ import { eachEntry, isArray, undeclaredMembers, verror } from '../common/module.f.mjs'
29
29
 
30
30
  /**
31
31
  * The unit kind's enumeration: bit `1 << i` of a {@link UnionSet}'s `unit`
@@ -286,33 +286,76 @@ const isNever = n => typeof n !== 'string' && cmpUnion(n, never) === 0
286
286
  /** @type {(n: Node) => boolean} */
287
287
  const isTop = n => typeof n !== 'string' && cmpUnion(n, unknown) === 0
288
288
 
289
+ /**
290
+ * The prefix with its redundant tail removed: a last position stating exactly
291
+ * the `rest` says nothing the `rest` does not already say, *provided* the
292
+ * `rest` admits `undefined`.
293
+ *
294
+ * Every array carrying a value at that position is read against the same set
295
+ * either way, so the two spellings can only differ on the arrays with nothing
296
+ * there — one that ends before it, and one holding a hole at it. Both read
297
+ * `undefined`, which the `rest` alone imposes nothing on, so dropping the
298
+ * position widens the set unless the `rest` admits `undefined` too. That is
299
+ * why `{ prefix: [number], rest: number }` keeps its position and stays "one
300
+ * or more numbers": `[]` and `[ , 1]` belong to `{ prefix: [], rest: number }`
301
+ * and not to it.
302
+ *
303
+ * This is what keeps one set to one spelling: the open tuples `[]` and
304
+ * `[unknown]` are both every array and have to produce one `Node`.
305
+ *
306
+ * A referenced `rest` is left alone — reading its unit bits would need the
307
+ * rule set, and the form already declines to see through a reference (see
308
+ * `./README.md`).
309
+ *
310
+ * @type {(prefix: readonly Node[], rest: Node) => readonly Node[]}
311
+ */
312
+ const trimPrefix = (prefix, rest) =>
313
+ typeof rest === 'string' || ((rest.unit ?? 0) & unitBit(undefined)) === 0
314
+ ? prefix
315
+ : prefix.slice(0, prefix.findLastIndex(n => cmpNode(n, rest) !== 0) + 1)
316
+
289
317
  /**
290
318
  * Canonical array-kind singleton. A syntactically empty position makes the
291
- * whole pattern empty; an empty `rest` is the same set as no `rest`; an
292
- * unconstrained `rest` with no prefix is every array.
319
+ * whole pattern empty (a position past the array's end reads as `undefined`,
320
+ * which the empty set excludes, so no length escapes it); an empty `rest`
321
+ * admits nothing past the prefix, which is what no `rest` already says; a
322
+ * prefix restating its `rest` is {@link trimPrefix}'d away; an unconstrained
323
+ * `rest` with nothing left before it is every array.
324
+ *
325
+ * Every array set is stated with a `rest` — `never` for a bare tuple,
326
+ * `unknown` for an `open` one, the element set for a uniform array — so this
327
+ * takes one rather than an optional one; the absent `rest` is what it
328
+ * normalizes an empty one *to*.
293
329
  *
294
- * @type {(prefix: readonly Node[], rest: Node | undefined) => UnionSet}
330
+ * @type {(prefix: readonly Node[], rest: Node) => UnionSet}
295
331
  */
296
332
  const arraySet = (prefix, rest) => {
297
333
  if (prefix.some(isNever)) { return never }
298
- const r = rest !== undefined && isNever(rest) ? undefined : rest
299
- return prefix.length === 0 && r !== undefined && isTop(r)
300
- ? { array: true }
301
- : { array: [r === undefined ? { prefix } : { prefix, rest: r }] }
334
+ if (isNever(rest)) { return { array: [{ prefix }] } }
335
+ const p = trimPrefix(prefix, rest)
336
+ return p.length === 0 && isTop(rest) ? { array: true } : { array: [{ prefix: p, rest }] }
302
337
  }
303
338
 
304
339
  /**
305
- * Canonical object-kind singleton. An unconstrained key is dropped (reading
306
- * it yields some value regardless); a syntactically empty key set makes the
307
- * whole pattern empty; an unconstrained `rest` is the same set as no `rest`;
308
- * with nothing left, the pattern is every object.
340
+ * Canonical object-kind singleton. An unconstrained `rest` is the same set as
341
+ * no `rest`; an unconstrained key is then dropped too; a syntactically empty
342
+ * key set makes the whole pattern empty; with nothing left, the pattern is
343
+ * every object.
344
+ *
345
+ * A key is dropped only once the `rest` is gone, and that order is the whole
346
+ * rule: an undeclared key may be absent, or must belong to `rest`, which
347
+ * leaves it unconstrained exactly when there is no `rest` — so with one
348
+ * present a key saying "anything" says strictly more than leaving it out.
349
+ * A bare struct's empty `rest` is where the two part company —
350
+ * `{ props: { a: unknown }, rest: never }` admits `{ a: 1 }` and
351
+ * `{ props: {}, rest: never }` admits only `{}`.
309
352
  *
310
353
  * @type {(props: readonly (readonly [string, Node])[], rest: Node | undefined) => UnionSet}
311
354
  */
312
355
  const objectSet = (props, rest) => {
313
- const constrained = props.filter(([, v]) => !isTop(v))
314
- if (constrained.some(([, v]) => isNever(v))) { return never }
315
356
  const r = rest !== undefined && isTop(rest) ? undefined : rest
357
+ const constrained = r === undefined ? props.filter(([, v]) => !isTop(v)) : props
358
+ if (constrained.some(([, v]) => isNever(v))) { return never }
316
359
  if (constrained.length === 0 && r === undefined) { return { object: true } }
317
360
  /** @type {StringMap<Node>} */
318
361
  const sorted = Object.fromEntries(constrained.toSorted(([ak], [bk]) => cmpString(ak, bk)))
@@ -367,12 +410,22 @@ const kindSubset = le => (a, b) => {
367
410
  return a.every(x => b.some(y => le(x, y)))
368
411
  }
369
412
 
370
- /** @type {(ctx: _Ctx) => (assumed: _Assumed) => (p: ArraySet, q: ArraySet) => boolean} */
413
+ /**
414
+ * Only the *longest* array each side admits is tested here — `pn` without a
415
+ * `rest`, unbounded with one. The shortest needs no test of its own: a
416
+ * position `q` insists on (one whose set excludes `undefined`) is a position
417
+ * `p` insists on too as soon as the pointwise check below passes, since
418
+ * otherwise `undefined` would be a member of `p.prefix[i]` and not of
419
+ * `q.prefix[i]`. Sound, and incomplete in the way `subset` is elsewhere: a
420
+ * `p` shorter than `q` is answered `false` even when every position past its
421
+ * end is one `q` admits as absent.
422
+ *
423
+ * @type {(ctx: _Ctx) => (assumed: _Assumed) => (p: ArraySet, q: ArraySet) => boolean}
424
+ */
371
425
  const arraySetSubset = ctx => assumed => (p, q) => {
372
426
  const le = nodeSubset(ctx)(assumed)
373
427
  const pn = p.prefix.length
374
428
  const qn = q.prefix.length
375
- // `p` admits lengths `{pn}` or `[pn, ∞)`; they must fit `q`'s admitted lengths
376
429
  const lengthOk = q.rest !== undefined
377
430
  ? qn <= pn
378
431
  : p.rest === undefined && qn === pn
@@ -387,38 +440,65 @@ const arraySetSubset = ctx => assumed => (p, q) => {
387
440
  const keyed = n => [n, typeof n === 'string' ? `r:${n}` : undefined]
388
441
 
389
442
  /**
390
- * The set of values *read* at key `k` from objects of the pattern: the
391
- * declared set, else — since the key may also be absent, reading
392
- * `undefined` — the `rest` set plus `undefined`, else anything. A read-set
393
- * synthesized from a referenced rest keeps that rule's identity (`u:`), so
394
- * the coinductive memo closes cycles through it.
443
+ * The set of values the pattern admits at key `k` when the key is **present**:
444
+ * the declared set, else the `rest`, else anything.
445
+ *
446
+ * Presence is the whole point of splitting this from {@link objectMayOmit}. An
447
+ * absent key and a key present holding `undefined` are not the same object, and
448
+ * the two sides of a pattern read them differently: a *declared* key constrains
449
+ * the value read at it, so absence reads `undefined` and passes when the set
450
+ * holds it, whereas an *undeclared* key is checked as an entry, so a present
451
+ * `undefined` must belong to `rest` itself (see {@link objectSetValidate}).
452
+ * Folding the two into one "read set" of `rest ∪ undefined` made
453
+ * `{ a: option(number) }` a subset of `record(number)`, which admits
454
+ * `{ a: undefined }` on the left and rejects it on the right.
395
455
  *
396
- * @type {(rules: RuleSet) => (pattern: ObjectSet) => (k: string) => _Keyed}
456
+ * @type {(pattern: ObjectSet) => (k: string) => _Keyed}
397
457
  */
398
- const objectReadSet = rules => pattern => k => {
458
+ const objectPresentSet = pattern => k => {
399
459
  const n = at(k)(pattern.props)
400
460
  if (n !== null) { return keyed(n) }
401
461
  const { rest } = pattern
402
- return rest === undefined
403
- ? [unknown, 't']
404
- : [
405
- merge(resolve(rules)(rest), { unit: unitBit(undefined) }),
406
- typeof rest === 'string' ? `u:${rest}` : undefined,
407
- ]
462
+ return rest === undefined ? [unknown, 't'] : keyed(rest)
463
+ }
464
+
465
+ /**
466
+ * Whether the pattern admits an object carrying no `k` at all: an undeclared
467
+ * key may always be missing, and a declared one exactly when its set holds
468
+ * `undefined`, since an absent property reads as `undefined`.
469
+ *
470
+ * This is the half of the old read-set that the `∪ undefined` stood for, now
471
+ * asked as its own question — a local unit-bit test, so it needs no memo.
472
+ *
473
+ * @type {(rules: RuleSet) => (pattern: ObjectSet) => (k: string) => boolean}
474
+ */
475
+ const objectMayOmit = rules => pattern => k => {
476
+ const n = at(k)(pattern.props)
477
+ return n === null || ((resolve(rules)(n).unit ?? 0) & unitBit(undefined)) !== 0
408
478
  }
409
479
 
410
480
  /** @type {(list: readonly string[]) => readonly string[]} */
411
481
  const dedup = list => list.filter((n, i) => list.indexOf(n) === i)
412
482
 
413
- /** @type {(ctx: _Ctx) => (assumed: _Assumed) => (p: ObjectSet, q: ObjectSet) => boolean} */
483
+ /**
484
+ * Every key either side declares is checked twice — what `p` may hold there
485
+ * must be something `q` holds there, and `p` may leave it out only where `q`
486
+ * lets it. Neither question implies the other: the first alone lets `p` admit
487
+ * an object `q` rejects for a *missing* key, the second alone for a *present*
488
+ * one.
489
+ *
490
+ * @type {(ctx: _Ctx) => (assumed: _Assumed) => (p: ObjectSet, q: ObjectSet) => boolean}
491
+ */
414
492
  const objectSetSubset = ctx => assumed => (p, q) => {
415
493
  const le = keyedSubset(ctx)(assumed)
416
494
  const keys = dedup([
417
495
  ...definedEntries(p.props).map(([k]) => k),
418
496
  ...definedEntries(q.props).map(([k]) => k),
419
497
  ])
420
- return keys.every(k => le(objectReadSet(ctx[0])(p)(k), objectReadSet(ctx[1])(q)(k)))
421
- // values at the keys declared by neither side
498
+ return keys.every(k =>
499
+ le(objectPresentSet(p)(k), objectPresentSet(q)(k))
500
+ && (!objectMayOmit(ctx[0])(p)(k) || objectMayOmit(ctx[1])(q)(k)))
501
+ // values at the keys declared by neither side, which both may omit
422
502
  && (q.rest === undefined || nodeSubset(ctx)(assumed)(p.rest ?? unknown, q.rest))
423
503
  }
424
504
 
@@ -459,6 +539,23 @@ const nodeSubset = ctx => assumed => (a, b) =>
459
539
  export const subset = ([aRules, aNode]) => ([bRules, bNode]) =>
460
540
  nodeSubset([aRules, bRules])({})(aNode, bNode)
461
541
 
542
+ /**
543
+ * Whether `a` and `b` denote the same set: {@link subset} both ways.
544
+ *
545
+ * Weaker as a test than {@link equal} and stronger as an answer. `equal`
546
+ * compares canonical *structure*, so it reports two α-equivalent recursive
547
+ * definitions — the same shape under different rule names — as different;
548
+ * `subset` resolves references coinductively and sees through the renaming
549
+ * (see `./README.md`). It inherits `subset`'s incompleteness in exchange: an
550
+ * equality that holds only by distributing a union, or through a non-syntactic
551
+ * empty set, is answered `false`. That direction is the safe one — a `false`
552
+ * here never merges two memberships — so a caller may treat a `true` as
553
+ * conclusive and a `false` as "not established".
554
+ *
555
+ * @type {(a: Data) => (b: Data) => boolean}
556
+ */
557
+ export const equivalent = a => b => subset(a)(b) && subset(b)(a)
558
+
462
559
  // ── coverage collapse ────────────────────────────────────────────────────────
463
560
 
464
561
  /**
@@ -685,12 +782,24 @@ const primitiveUnion = p => {
685
782
  const containerMemo = (state, c) => {
686
783
  const done = assoc(state.done, c)
687
784
  if (done !== undefined) { return [state, done] }
688
- const [state1, u] = containerUnion(state, c)
785
+ const [state1, u] = containerUnion(state, c, never)
689
786
  return [{ ...state1, done: [...state1.done, [c, u]] }, u]
690
787
  }
691
788
 
692
- /** @type {(state: _State, c: ConstObject) => readonly [_State, UnionSet]} */
693
- const containerUnion = (state, c) => {
789
+ /**
790
+ * The union of a const container, with `rest` the set every member it does not
791
+ * declare belongs to.
792
+ *
793
+ * Used bare, both kinds are **closed** — the declared members and no others —
794
+ * so the default maps onto the `rest` that says so, `never` on either kind.
795
+ * `rest`/`open` is what supplies one of its own; `unknown` is the open form,
796
+ * which the two kinds spell differently in the canonical data (a tuple's
797
+ * `rest: unknown`, a struct's absent `rest`) and {@link arraySet} /
798
+ * {@link objectSet} normalize to.
799
+ *
800
+ * @type {(state: _State, c: ConstObject, rest: Node) => readonly [_State, UnionSet]}
801
+ */
802
+ const containerUnion = (state, c, rest) => {
694
803
  let s = state
695
804
  if (c instanceof Array) {
696
805
  /** @type {readonly Node[]} */
@@ -700,7 +809,7 @@ const containerUnion = (state, c) => {
700
809
  s = s1
701
810
  prefix = [...prefix, n]
702
811
  }
703
- return [s, arraySet(prefix, undefined)]
812
+ return [s, arraySet(prefix, rest)]
704
813
  }
705
814
  /** @type {readonly (readonly [string, Node])[]} */
706
815
  let props = []
@@ -709,7 +818,22 @@ const containerUnion = (state, c) => {
709
818
  s = s1
710
819
  props = [...props, [k, n]]
711
820
  }
712
- return [s, objectSet(props, undefined)]
821
+ return [s, objectSet(props, rest)]
822
+ }
823
+
824
+ /**
825
+ * The union of a `rest` schema. The container is walked here rather than
826
+ * through {@link containerMemo}: that memo is keyed by the container's
827
+ * identity alone, and the same object with two different rests is two
828
+ * different sets. The enclosing thunk is memoized by {@link convertThunk}
829
+ * either way, so nothing is recomputed and a cycle through a `rest` still
830
+ * closes.
831
+ *
832
+ * @type {(state: _State, c: ConstObject, r: Type) => readonly [_State, UnionSet]}
833
+ */
834
+ const restUnion = (state, c, r) => {
835
+ const [state1, restNode] = nodeOf(state)(r)
836
+ return containerUnion(state1, c, restNode)
713
837
  }
714
838
 
715
839
  /** @type {(state: _State, c: Const) => readonly [_State, UnionSet]} */
@@ -784,6 +908,11 @@ const thunkUnion = (state, t) => {
784
908
  const [state1, value] = nodeOf(state)(rest[0])
785
909
  return [state1, objectSet([], value)]
786
910
  }
911
+ case 'rest': {
912
+ const [c, r] = rest
913
+ assert(typeof c === 'object' && c !== null, c)
914
+ return restUnion(state, c, r)
915
+ }
787
916
  default: { return orUnion(state, t, rest) }
788
917
  }
789
918
  }
@@ -987,21 +1116,44 @@ const patternsValidate = (k, item, value) => {
987
1116
  return verror('no match')
988
1117
  }
989
1118
 
990
- /** @type {(p: ArraySet, i: number) => Node} */
991
- const atIndex = (p, i) => i < p.prefix.length ? p.prefix[i] : assertNotNullish(p.rest)
992
-
993
- /** @type {(rules: RuleSet) => (p: ArraySet) => (value: readonly Unknown[]) => ResultE} */
1119
+ /**
1120
+ * The declared positions are checked by reading the value at each — a
1121
+ * position past the end reads as `undefined`, so a position is required
1122
+ * exactly when its set excludes `undefined`, and no minimum length is tested
1123
+ * for. What is left over is tested against `rest`, or, with no `rest`, must
1124
+ * not be there at all. Same shape as {@link objectSetValidate}, one kind
1125
+ * over.
1126
+ *
1127
+ * `undeclaredMembers` is what the schema-form readers walk too, so "what is
1128
+ * left over" is one rule rather than two that happen to coincide — including
1129
+ * an index the prototype supplies, which an own-entry filter here answered
1130
+ * `ok` for while the rendered tail claimed the `rest`'s type over it.
1131
+ *
1132
+ * @type {(rules: RuleSet) => (p: ArraySet) => (value: readonly Unknown[]) => ResultE}
1133
+ */
994
1134
  const arraySetValidate = rules => p => value => {
995
1135
  const pn = p.prefix.length
996
- if (p.rest === undefined ? value.length !== pn : value.length < pn) {
997
- return verror('unexpected value')
998
- }
999
- const r = eachEntry(
1000
- Object.entries(value),
1001
- (k, v) => nodeValidate(rules)(atIndex(p, Number(k)))(v),
1136
+ const { rest } = p
1137
+ const declared = eachEntry(
1138
+ Object.entries(p.prefix),
1139
+ (k, n) => nodeValidate(rules)(n)(value[Number(k)]),
1002
1140
  undefined,
1003
1141
  noAccumulate,
1004
1142
  )
1143
+ if (declared[0] === 'error') { return declared }
1144
+ const extra = undeclaredMembers(p.prefix.map((_, i) => String(i)), value)
1145
+ if (rest === undefined) {
1146
+ // Nothing past the prefix, by length as well as by entry: a hole past
1147
+ // it is not an entry, but the array is still that long, and this is
1148
+ // the set `Ts<>` renders as a tuple of exactly `pn` positions and JSON
1149
+ // Schema as `items: false`. A *shorter* array is another matter — the
1150
+ // declared loop above has already held every position it left unfilled
1151
+ // to a set admitting `undefined`.
1152
+ return extra.length === 0 && value.length <= pn
1153
+ ? ok(value)
1154
+ : verror('unexpected value')
1155
+ }
1156
+ const r = eachEntry(extra, (_k, v) => nodeValidate(rules)(rest)(v), undefined, noAccumulate)
1005
1157
  return r[0] === 'error' ? r : ok(value)
1006
1158
  }
1007
1159
 
@@ -1042,6 +1194,48 @@ const unionValidate = rules => u => value => {
1042
1194
  /** @type {(rules: RuleSet) => (n: Node) => (value: Unknown) => ResultE} */
1043
1195
  const nodeValidate = rules => n => unionValidate(rules)(resolve(rules)(n))
1044
1196
 
1197
+ /**
1198
+ * Whether `r`, as the rest of the container `c`, admits nothing — so that
1199
+ * `rest(c, r)` and the bare, closed `c` denote one set.
1200
+ *
1201
+ * This is what the schema-form readers bound an array's length by, and it is
1202
+ * asked *here* on purpose: `arraySet` is where an empty rest normalizes away,
1203
+ * so the readers agree with this form by asking it rather than by re-deriving
1204
+ * the same rule and drifting. Stated as **making no difference to the
1205
+ * canonical form** — the conversion of `rest(c, r)` denoting the same set as
1206
+ * the conversion of `c` — and not as a judgement about `r` on its own, nor as
1207
+ * a reading of whether the conversion kept a `rest` key. Five cases fix it
1208
+ * between them, and only this equality satisfies all five:
1209
+ *
1210
+ * - `never`, `or()` and `[or()]` all convert `rest(c, r)` to `c`'s own
1211
+ * conversion, so all three are empty. Keying on the exported `never` alone
1212
+ * would pass a `never`-only proof with the other two spellings still
1213
+ * disagreeing.
1214
+ * - `const r = () => ['rest', [r], never]` has no finite inhabitant, yet the
1215
+ * conversion keeps `rest: "r"`, so it is *not* empty here. A reader
1216
+ * recognizing that emptiness would start rejecting what this form accepts.
1217
+ * - `const a = () => ['or', b]; const b = () => ['or', a]` rules out the
1218
+ * rest's *own* canonical data as the test: {@link toData} of `a` is `never`,
1219
+ * and the conversion still keeps `rest: "a"`.
1220
+ * - `unknown` rules out "the conversion kept no `rest` key" as the test:
1221
+ * `toData(rest([], unknown))` is `{ array: true }` — the whole kind, with no
1222
+ * `rest` key — because a top rest collapses the pattern rather than being
1223
+ * dropped from it.
1224
+ * - Two separately constructed copies of one recursive rule, one in `c` and
1225
+ * one in `r`, rule out {@link equal} as the comparison: converting the rest
1226
+ * reserves the name, so `c`'s rule is named `r0` where converting `c` alone
1227
+ * names it `r`, and `equal` compares recursive definitions by rule name.
1228
+ *
1229
+ * {@link equivalent} answers all five. It is incomplete, and in the direction
1230
+ * that costs nothing here: an unrecognized empty rest leaves the length
1231
+ * unbounded, which is what a *kept* `rest` key means in this form anyway, so
1232
+ * the readers still agree with it.
1233
+ *
1234
+ * @type {(c: ConstObject, r: Type) => boolean}
1235
+ */
1236
+ export const emptyRest = (c, r) =>
1237
+ equivalent(toData(/** @type {Type} */ (() => ['rest', c, r])))(toData(c))
1238
+
1045
1239
  /**
1046
1240
  * Data-driven validation — the counterpart of `../validate` that consumes a
1047
1241
  * {@link Data} produced by {@link toData} instead of walking the thunk
@@ -30,6 +30,11 @@ export type _Odd = () => readonly ['const', {
30
30
  readonly next: Or<readonly [_Even, undefined]>;
31
31
  }];
32
32
  export type _Rec = () => readonly ['array', _Rec];
33
+ export type _ClosedNode = () => readonly ['const', readonly [typeof number, _ClosedChildren]];
34
+ export type _ClosedChildren = () => readonly ['array', _ClosedNode];
35
+ export type _NestedRest = () => readonly ['rest', {
36
+ readonly a: typeof number;
37
+ }, _NestedRest];
33
38
  export declare const proof: {
34
39
  withoutUnits: (() => void)[];
35
40
  unitBits: () => void;
@@ -37,6 +42,8 @@ export declare const proof: {
37
42
  primitives: () => void;
38
43
  tag0: () => void;
39
44
  containers: () => void;
45
+ rest: () => void;
46
+ restRecursion: () => void;
40
47
  constSchemas: () => void;
41
48
  or: () => void;
42
49
  orCanonicalIdentity: () => void;
@@ -50,6 +57,7 @@ export declare const proof: {
50
57
  };
51
58
  cmp: {
52
59
  totalOrder: () => void;
60
+ rest: () => void;
53
61
  restOrder: () => void;
54
62
  rules: () => void;
55
63
  };
@@ -57,6 +65,8 @@ export declare const proof: {
57
65
  literals: () => void;
58
66
  arrays: () => void;
59
67
  arrayRest: () => void;
68
+ rest: () => void;
69
+ presenceIsNotAbsence: () => void;
60
70
  objects: () => void;
61
71
  recursion: () => void;
62
72
  mixedObjectRecursion: () => void;