functionalscript 0.46.1 → 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 +42 -25
  157. package/fjs/types/rtti/validate/module.f.mjs +187 -53
  158. package/fjs/types/rtti/validate/proof.f.d.mts +28 -2
  159. package/fjs/types/rtti/validate/proof.f.mjs +430 -75
  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
@@ -18,6 +18,12 @@
18
18
  * - `eachEntry`: the container entry loop (array/record/tuple/struct). Callers
19
19
  * choose what (if anything) to accumulate, so a caller that only needs
20
20
  * pass/fail pays no allocation per entry.
21
+ * - `tupleSchemaEntries`/`structSchemaEntries`: what a container schema
22
+ * declares, per kind — the entry list its readers walk.
23
+ * - `undeclaredMembers`: the other half of the loop — the members a container
24
+ * schema does not name, which a bare schema rejects and a `rest` one holds
25
+ * to its rest. The data form walks it too, so the two readers state one
26
+ * rule.
21
27
  * - `orVisit`: the shared `or` handler — try each variant's recursive walker,
22
28
  * return the first match.
23
29
  *
@@ -27,16 +33,16 @@
27
33
  * @module
28
34
  *
29
35
  * @import { Primitive, Unknown } from '../ts/types.ts'
30
- * @import { Const, Info0, Primitive0, Tag1, Tuple, Type } from '../types.ts'
36
+ * @import { Const, Info0, Primitive0, Struct, Tag1, Tuple, Type } from '../types.ts'
31
37
  * @import { Error, Result as CommonResult } from '../../result/types.ts'
32
38
  * @import { StringMap } from '../../object/types.ts'
33
- * @import { Validate, Visitor, IsContainer, Container, ResultE, ValidateE, ValidationError } from './types.ts'
39
+ * @import { Validate, Visitor, IsContainer, Container, ResultE, SchemaEntries, ValidateE, ValidationError } from './types.ts'
34
40
  */
35
41
  import type { Primitive, Unknown } from '../ts/types.ts';
36
- import type { Info0, Primitive0, Type } from '../types.ts';
42
+ import type { Info0, Primitive0, Struct, Tuple, Type } from '../types.ts';
37
43
  import type { Error, Result as CommonResult } from '../../result/types.ts';
38
44
  import type { StringMap } from '../../object/types.ts';
39
- import type { Validate, Visitor, IsContainer, ResultE, ValidateE, ValidationError } from './types.ts';
45
+ import type { Validate, Visitor, IsContainer, ResultE, SchemaEntries, ValidateE, ValidationError } from './types.ts';
40
46
  /** Builds an error result with empty path and the given message. */
41
47
  /** @type {(message: string) => Error<ValidationError>} */
42
48
  export declare const verror: (message: string) => Error<ValidationError>;
@@ -85,6 +91,73 @@ export declare const isObject: IsContainer<StringMap<Unknown>>;
85
91
  * instead and pays no allocation per entry.
86
92
  */
87
93
  export declare const eachEntry: <V, R, A>(entries: ReadonlyArray<readonly [string, V]>, item: (k: string, v: V) => CommonResult<R, ValidationError>, init: A, accumulate: (acc: A, k: string, value: R) => A) => CommonResult<A, ValidationError>;
94
+ /**
95
+ * What a `Tuple` schema declares, read by **length**.
96
+ *
97
+ * `Array.from` yields `undefined` for a hole and preserves the schema's
98
+ * length, so a hole is a declared position whose schema is `undefined` — which
99
+ * is a `Const` schema in its own right, and exactly what reading index `0` of
100
+ * `new Array(1)` gives. That is the reading `../data/module.f.mjs`'s
101
+ * `containerUnion` has always had, so the canonical data form stays fixed.
102
+ *
103
+ * `Array.from` walks the iterator, which is the *same* walk `containerUnion`
104
+ * makes, and that is the point rather than an accident: the two agree by
105
+ * construction. It holds even for a schema carrying an overridden
106
+ * `Symbol.iterator`, where reading indices here would disagree with the data
107
+ * form all over again — verified: such a schema is read as `number` by the
108
+ * entry reading and as `string` by `containerUnion`. Reading *both* by index
109
+ * is defensible, but it changes the canonical, content-addressed data form and
110
+ * belongs with that decision, not here. FunctionalScript cannot build such a
111
+ * schema in the first place: it has no symbols and no mutation, so the case is
112
+ * reachable only from plain JavaScript, which is also why no proof can pin it.
113
+ *
114
+ * `Object.entries` skips holes, which is why it is not used here: it would
115
+ * make `new Array(1)` and `[]` the same schema while `[undefined]` stayed
116
+ * different from both. It also yields a non-index own property, which is no
117
+ * position either — `getItem` reads a tuple by index, so such a key was
118
+ * declared and then matched against `value[NaN]`. `Array.from` answers
119
+ * positions only. On a plain dense array the two agree exactly.
120
+ *
121
+ * @type {SchemaEntries<Tuple>}
122
+ */
123
+ export declare const tupleSchemaEntries: SchemaEntries<Tuple>;
124
+ /**
125
+ * What a `Struct` schema declares: its enumerable own keys. A struct has no
126
+ * holes, so there is nothing for this to disagree with.
127
+ *
128
+ * @type {SchemaEntries<Struct>}
129
+ */
130
+ export declare const structSchemaEntries: SchemaEntries<Struct>;
131
+ /**
132
+ * The members of `value` that `declared` does not name — every one the
133
+ * schema's `rest` has to answer for, as `[key, value]` pairs.
134
+ *
135
+ * `declared` is a container schema's own key list, so for a struct these are
136
+ * its undeclared own keys, and for a tuple they are the positions past the
137
+ * prefix together with every own key that is no position at all.
138
+ *
139
+ * **A tuple's positions are read, not enumerated.** `length` is what says how
140
+ * far an array reaches, and every index below it that *reads* a value is a
141
+ * member the `rest` must answer for — including one supplied by the prototype,
142
+ * which no own-entry walk sees ({@link readIndices} is the walk). Filtering
143
+ * `Object.entries` alone accepted `[42, , ]` carrying an inherited `1: 99`
144
+ * against `rest([42], string)`, handing back an array whose index 1 reads a
145
+ * number the rendered tail types as `string`. A genuinely absent index is
146
+ * skipped instead: a hole is no member, so it meets no `rest` — the same rule
147
+ * the struct kind states by walking own keys.
148
+ *
149
+ * An index at or above `length` is a different matter and is not answered
150
+ * here: it is readable through the prototype and no walk bounded by the value
151
+ * reaches it. See "Beyond `length`" in `../README.md`.
152
+ *
153
+ * Passing an empty `declared` asks for every member, which is what the uniform
154
+ * `array`/`record` readers want — so they share this walk rather than reaching
155
+ * for `Object.entries` and disagreeing with the data form on an inherited
156
+ * index.
157
+ *
158
+ * @type {(declared: readonly string[], value: ReadonlyArray<Unknown> | StringMap<Unknown>) => ReadonlyArray<readonly [string, Unknown]>}
159
+ */
160
+ export declare const undeclaredMembers: (declared: readonly string[], value: ReadonlyArray<Unknown> | StringMap<Unknown>) => ReadonlyArray<readonly [string, Unknown]>;
88
161
  /**
89
162
  * First variant in `variants` that `recurse` accepts, else `verror('no match')`.
90
163
  *
@@ -103,7 +176,7 @@ export declare const orVisit: (recurse: (t: Type) => ValidateE) => (variants: re
103
176
  *
104
177
  * - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
105
178
  * routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
106
- * or a `Tag0` primitive).
179
+ * `'rest'`, or a `Tag0` primitive).
107
180
  * - `Const` schemas (primitives, tuples, structs) are routed directly to
108
181
  * `tuple`, `struct`, or `constPrimitive`.
109
182
  */
@@ -18,6 +18,12 @@
18
18
  * - `eachEntry`: the container entry loop (array/record/tuple/struct). Callers
19
19
  * choose what (if anything) to accumulate, so a caller that only needs
20
20
  * pass/fail pays no allocation per entry.
21
+ * - `tupleSchemaEntries`/`structSchemaEntries`: what a container schema
22
+ * declares, per kind — the entry list its readers walk.
23
+ * - `undeclaredMembers`: the other half of the loop — the members a container
24
+ * schema does not name, which a bare schema rejects and a `rest` one holds
25
+ * to its rest. The data form walks it too, so the two readers state one
26
+ * rule.
21
27
  * - `orVisit`: the shared `or` handler — try each variant's recursive walker,
22
28
  * return the first match.
23
29
  *
@@ -27,10 +33,10 @@
27
33
  * @module
28
34
  *
29
35
  * @import { Primitive, Unknown } from '../ts/types.ts'
30
- * @import { Const, Info0, Primitive0, Tag1, Tuple, Type } from '../types.ts'
36
+ * @import { Const, Info0, Primitive0, Struct, Tag1, Tuple, Type } from '../types.ts'
31
37
  * @import { Error, Result as CommonResult } from '../../result/types.ts'
32
38
  * @import { StringMap } from '../../object/types.ts'
33
- * @import { Validate, Visitor, IsContainer, Container, ResultE, ValidateE, ValidationError } from './types.ts'
39
+ * @import { Validate, Visitor, IsContainer, Container, ResultE, SchemaEntries, ValidateE, ValidationError } from './types.ts'
34
40
  */
35
41
 
36
42
  import { assert } from '../../../asserts/module.f.mjs'
@@ -128,6 +134,157 @@ export const eachEntry =
128
134
  return ok(acc)
129
135
  }
130
136
 
137
+ /**
138
+ * What a `Tuple` schema declares, read by **length**.
139
+ *
140
+ * `Array.from` yields `undefined` for a hole and preserves the schema's
141
+ * length, so a hole is a declared position whose schema is `undefined` — which
142
+ * is a `Const` schema in its own right, and exactly what reading index `0` of
143
+ * `new Array(1)` gives. That is the reading `../data/module.f.mjs`'s
144
+ * `containerUnion` has always had, so the canonical data form stays fixed.
145
+ *
146
+ * `Array.from` walks the iterator, which is the *same* walk `containerUnion`
147
+ * makes, and that is the point rather than an accident: the two agree by
148
+ * construction. It holds even for a schema carrying an overridden
149
+ * `Symbol.iterator`, where reading indices here would disagree with the data
150
+ * form all over again — verified: such a schema is read as `number` by the
151
+ * entry reading and as `string` by `containerUnion`. Reading *both* by index
152
+ * is defensible, but it changes the canonical, content-addressed data form and
153
+ * belongs with that decision, not here. FunctionalScript cannot build such a
154
+ * schema in the first place: it has no symbols and no mutation, so the case is
155
+ * reachable only from plain JavaScript, which is also why no proof can pin it.
156
+ *
157
+ * `Object.entries` skips holes, which is why it is not used here: it would
158
+ * make `new Array(1)` and `[]` the same schema while `[undefined]` stayed
159
+ * different from both. It also yields a non-index own property, which is no
160
+ * position either — `getItem` reads a tuple by index, so such a key was
161
+ * declared and then matched against `value[NaN]`. `Array.from` answers
162
+ * positions only. On a plain dense array the two agree exactly.
163
+ *
164
+ * @type {SchemaEntries<Tuple>}
165
+ */
166
+ export const tupleSchemaEntries = rtti =>
167
+ Array.from(rtti, (t, i) => [String(i), t])
168
+
169
+ /**
170
+ * What a `Struct` schema declares: its enumerable own keys. A struct has no
171
+ * holes, so there is nothing for this to disagree with.
172
+ *
173
+ * @type {SchemaEntries<Struct>}
174
+ */
175
+ export const structSchemaEntries = rtti =>
176
+ Object.entries(rtti)
177
+
178
+ /**
179
+ * The position `k` names, or `undefined` when `k` names no position at all.
180
+ *
181
+ * Only the canonical spelling of a non-negative integer is an index: `'-1'`,
182
+ * `'01'`, `'1.5'` and `' 1'` are ordinary properties of an array object,
183
+ * however `Number` maps them. Round-tripping the number back through `String`
184
+ * is what rejects every non-canonical spelling at once, rather than one at a
185
+ * time.
186
+ *
187
+ * And only one **below `2 ** 32 - 1`**, which is where the language draws the
188
+ * line rather than a bound chosen here: assigning `a['4294967295']` creates an
189
+ * ordinary enumerable property and leaves `a.length` alone. Reading such a key
190
+ * as an index put it past every `length`-bounded walk *and* past the non-index
191
+ * filter, so it was no member on either path and an undeclared property rode
192
+ * through a closed container.
193
+ *
194
+ * @type {(k: string) => number | undefined}
195
+ */
196
+ const arrayIndex = k => {
197
+ const i = Number(k)
198
+ return Number.isInteger(i) && i >= 0 && i < 2 ** 32 - 1 && String(i) === k ? i : undefined
199
+ }
200
+
201
+ /**
202
+ * Every index below `length` at which `value` reads something, ascending.
203
+ *
204
+ * Bounded by what the value and its prototypes **carry** rather than by
205
+ * `length`: an index that reads a value is an own property of the array or of
206
+ * something on its prototype chain, so enumerating those names finds every one
207
+ * without materializing the range. Walking `0 … length - 1` instead turned a
208
+ * `new Array(2 ** 32 - 1)` — which carries one own property, `length` — into
209
+ * billions of iterations before any check could reject it.
210
+ *
211
+ * The own names are **already the answer** for all but a pathological value:
212
+ * `[[OwnPropertyKeys]]` yields integer indices ascending and without repeats,
213
+ * so an ordinary array pays one linear pass and no sort. Only an index the
214
+ * chain supplies and the value does not is merged in, and that set is empty
215
+ * unless someone has put an index on a prototype — which is why the dedup it
216
+ * needs may be quadratic without costing an ordinary array anything. Deduping
217
+ * the whole list instead made every `array(t)` read quadratic in its length:
218
+ * 829 ms at 40 000 elements against 3 ms at 1 000.
219
+ *
220
+ * No `in` test: a name reached this way is a property of the value or of
221
+ * something it inherits from, so the array reads at it by construction.
222
+ *
223
+ * @type {(value: ReadonlyArray<Unknown>) => readonly number[]}
224
+ */
225
+ const readIndices = value => {
226
+ const { length } = value
227
+ /** @type {(names: readonly string[]) => readonly number[]} */
228
+ const indices = names => names.flatMap(k => {
229
+ const i = arrayIndex(k)
230
+ return i !== undefined && i < length ? [i] : []
231
+ })
232
+ const own = indices(Object.getOwnPropertyNames(value))
233
+ /** @type {readonly number[]} */
234
+ let chain = []
235
+ for (let o = Object.getPrototypeOf(value); o !== null; o = Object.getPrototypeOf(o)) {
236
+ chain = [...chain, ...indices(Object.getOwnPropertyNames(o))]
237
+ }
238
+ if (chain.length === 0) { return own }
239
+ const inherited = chain
240
+ .filter(i => !Object.hasOwn(value, i))
241
+ .filter((i, at, a) => a.indexOf(i) === at)
242
+ return inherited.length === 0 ? own : [...own, ...inherited].toSorted((a, b) => a - b)
243
+ }
244
+
245
+ /**
246
+ * The members of `value` that `declared` does not name — every one the
247
+ * schema's `rest` has to answer for, as `[key, value]` pairs.
248
+ *
249
+ * `declared` is a container schema's own key list, so for a struct these are
250
+ * its undeclared own keys, and for a tuple they are the positions past the
251
+ * prefix together with every own key that is no position at all.
252
+ *
253
+ * **A tuple's positions are read, not enumerated.** `length` is what says how
254
+ * far an array reaches, and every index below it that *reads* a value is a
255
+ * member the `rest` must answer for — including one supplied by the prototype,
256
+ * which no own-entry walk sees ({@link readIndices} is the walk). Filtering
257
+ * `Object.entries` alone accepted `[42, , ]` carrying an inherited `1: 99`
258
+ * against `rest([42], string)`, handing back an array whose index 1 reads a
259
+ * number the rendered tail types as `string`. A genuinely absent index is
260
+ * skipped instead: a hole is no member, so it meets no `rest` — the same rule
261
+ * the struct kind states by walking own keys.
262
+ *
263
+ * An index at or above `length` is a different matter and is not answered
264
+ * here: it is readable through the prototype and no walk bounded by the value
265
+ * reaches it. See "Beyond `length`" in `../README.md`.
266
+ *
267
+ * Passing an empty `declared` asks for every member, which is what the uniform
268
+ * `array`/`record` readers want — so they share this walk rather than reaching
269
+ * for `Object.entries` and disagreeing with the data form on an inherited
270
+ * index.
271
+ *
272
+ * @type {(declared: readonly string[], value: ReadonlyArray<Unknown> | StringMap<Unknown>) => ReadonlyArray<readonly [string, Unknown]>}
273
+ */
274
+ export const undeclaredMembers = (declared, value) => {
275
+ /** @type {(k: string) => boolean} */
276
+ const undeclared = k => !declared.some(d => d === k)
277
+ if (!commonIsArray(value)) {
278
+ return Object.entries(value).filter(([k]) => undeclared(k))
279
+ }
280
+ return [
281
+ ...readIndices(value)
282
+ .filter(i => undeclared(String(i)))
283
+ .map(i => /** @type {const} */ ([String(i), value[i]])),
284
+ ...Object.entries(value).filter(([k]) => arrayIndex(k) === undefined && undeclared(k)),
285
+ ]
286
+ }
287
+
131
288
  /**
132
289
  * First variant in `variants` that `recurse` accepts, else `verror('no match')`.
133
290
  *
@@ -157,7 +314,7 @@ export const orVisit =
157
314
  *
158
315
  * - `Thunk` schemas are evaluated once to read the `Info` descriptor, then
159
316
  * routed by tag (`'const'`, `'array'`, `'record'`, `'unknown'`, `'or'`,
160
- * or a `Tag0` primitive).
317
+ * `'rest'`, or a `Tag0` primitive).
161
318
  * - `Const` schemas (primitives, tuples, structs) are routed directly to
162
319
  * `tuple`, `struct`, or `constPrimitive`.
163
320
  */
@@ -182,6 +339,14 @@ export const visit =
182
339
  case 'record': return v.record(value[0])
183
340
  case 'unknown': return v.unknown()
184
341
  case 'or': return v.or(value)
342
+ case 'rest': {
343
+ const [c, r] = value
344
+ // `rest`'s container is a `ConstObject`, which is exactly
345
+ // the non-null objects among the `Const`s — a `Thunk` is a
346
+ // function, and every other `Const` is a primitive.
347
+ assert(typeof c === 'object' && c !== null, c)
348
+ return v.rest(c, r)
349
+ }
185
350
  }
186
351
  return v.primitive0(tag)
187
352
  }
@@ -9,5 +9,19 @@ export declare const proof: {
9
9
  noAccumulate: () => void;
10
10
  firstErrorWins: () => void;
11
11
  shortCircuits: () => void;
12
+ undeclared: {
13
+ struct: () => void;
14
+ tuple: () => void;
15
+ none: () => void;
16
+ holeIsNoMember: () => void;
17
+ lengthDoesNotBoundTheWalk: () => void;
18
+ };
19
+ schemaEntries: {
20
+ tuple: () => void;
21
+ tupleHole: () => void;
22
+ tupleHoleIsTheDenseReading: () => void;
23
+ struct: () => void;
24
+ empty: () => void;
25
+ };
12
26
  pathPrefixed: () => void;
13
27
  };
@@ -3,9 +3,9 @@
3
3
  * @import { ValidationError } from './types.ts'
4
4
  */
5
5
 
6
- import { eachEntry } from './module.f.mjs'
6
+ import { eachEntry, structSchemaEntries, tupleSchemaEntries, undeclaredMembers } from './module.f.mjs'
7
7
  import { error, ok } from '../../result/module.f.mjs'
8
- import { assert, assertEq } from '../../../asserts/module.f.mjs'
8
+ import { assert, assertEq, assertStructurallySame } from '../../../asserts/module.f.mjs'
9
9
 
10
10
  /** @typedef {ReadonlyArray<readonly [string, number]>} _Entries */
11
11
 
@@ -54,6 +54,65 @@ export const proof = {
54
54
  assert(r[0] === 'error')
55
55
  assertEq(calls, 1)
56
56
  },
57
+ // The other half of a container's loop: the members a bare schema rejects
58
+ // and a `rest` one holds to its rest. One function answers both kinds — a
59
+ // struct's undeclared own keys, and an array's positions past the prefix
60
+ // together with the own keys that are no position at all.
61
+ undeclared: {
62
+ struct: () => {
63
+ const r = undeclaredMembers(['a'], { a: 1, b: 2 })
64
+ assertStructurallySame(r, [['b', 2]])
65
+ },
66
+ // The positions come first, in index order, then the keys that name
67
+ // none — so the reported error path is the leftmost failing member.
68
+ tuple: () => {
69
+ const r = undeclaredMembers(['0'], Object.assign([1, 2], { foo: 3, '01': 4 }))
70
+ assertStructurallySame(r, [['1', 2], ['foo', 3], ['01', 4]])
71
+ },
72
+ none: () => assertEq(undeclaredMembers(['a'], { a: 1 }).length, 0),
73
+ // A hole is no member, so it meets no `rest` — which is why the array
74
+ // kind also answers with its `length`; see `fits` in
75
+ // `../parse/module.f.mjs`.
76
+ holeIsNoMember: () => assertEq(undeclaredMembers(['0'], [1, , 3]).length, 1),
77
+ // The walk is bounded by what the value and its prototypes carry, not
78
+ // by `length` — this one carries a single own property, `length`, so
79
+ // it answers at once. Materializing the range instead exhausted memory
80
+ // long before any check could reject the value.
81
+ lengthDoesNotBoundTheWalk: () =>
82
+ assertEq(undeclaredMembers([], new Array(2 ** 32 - 1)).length, 0),
83
+ // An index the prototype supplies is a member too, and a canonical
84
+ // numeric key past the index range is one by the non-index half. Both
85
+ // need in-place mutation to build, so they are pinned in
86
+ // `../host.proof.mjs` — see its module doc.
87
+ },
88
+ // What a container schema declares, per kind. A tuple is read by length,
89
+ // so a hole is a declared position whose schema is `undefined` — the same
90
+ // reading `../data/module.f.mjs` has, and the reason the two kinds need
91
+ // different entry readers at all.
92
+ schemaEntries: {
93
+ tuple: () => assertStructurallySame(
94
+ tupleSchemaEntries([1, 'a']),
95
+ [['0', 1], ['1', 'a']],
96
+ ),
97
+ // `Object.entries` would answer `[]` here, and `[['0', undefined]]` for
98
+ // `[undefined]` — two schemas that denote the same set, read as two.
99
+ tupleHole: () => assertStructurallySame(
100
+ tupleSchemaEntries(new Array(1)),
101
+ [['0', undefined]],
102
+ ),
103
+ tupleHoleIsTheDenseReading: () => assertStructurallySame(
104
+ tupleSchemaEntries(new Array(1)),
105
+ tupleSchemaEntries([undefined]),
106
+ ),
107
+ struct: () => assertStructurallySame(
108
+ structSchemaEntries({ a: 1, b: 'x' }),
109
+ [['a', 1], ['b', 'x']],
110
+ ),
111
+ empty: () => {
112
+ assertEq(tupleSchemaEntries([]).length, 0)
113
+ assertEq(structSchemaEntries({}).length, 0)
114
+ },
115
+ },
57
116
  pathPrefixed: () => {
58
117
  /** @type {(k: string, v: number) => Result<number, ValidationError>} */
59
118
  const nested = (k, v) =>
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
  import type { Primitive, Unknown } from '../ts/types.ts';
7
- import type { Primitive0, Struct, Tag1, Tuple, Type } from '../types.ts';
7
+ import type { ConstObject, Primitive0, Struct, Tag1, Tuple, Type } from '../types.ts';
8
8
  import type { Result as CommonResult } from '../../result/types.ts';
9
9
  import type { Ts } from '../ts/types.ts';
10
10
  import type { StringMap } from '../../object/types.ts';
@@ -26,6 +26,13 @@ export type Validate<T extends Type> = (value: Unknown) => Result<T>;
26
26
  export type Visitor<R> = {
27
27
  readonly tuple: (rtti: Tuple) => R;
28
28
  readonly struct: (rtti: Struct) => R;
29
+ /**
30
+ * A container with a stated rest: the declared members of `rtti`, plus any
31
+ * number of members belonging to `r`. The bare `tuple`/`struct` handlers
32
+ * are the same thing with `r` of `never`, and are kept apart because the
33
+ * common case pays no rest walk.
34
+ */
35
+ readonly rest: (rtti: ConstObject, r: Type) => R;
29
36
  readonly array: (item: Type) => R;
30
37
  readonly record: (item: Type) => R;
31
38
  readonly or: (variants: readonly Type[]) => R;
@@ -33,6 +40,21 @@ export type Visitor<R> = {
33
40
  readonly primitive0: (tag: Primitive0) => R;
34
41
  readonly unknown: () => R;
35
42
  };
43
+ /**
44
+ * Reads what a container schema declares, as `[key, Type]` pairs — one per
45
+ * container kind, since a `Tuple` is read by length and a `Struct` by
46
+ * enumerable key. See `tupleSchemaEntries` in `./module.f.mjs` for why the two
47
+ * readings are not interchangeable on a sparse array.
48
+ */
49
+ export type SchemaEntries<S extends ConstObject> = (rtti: S) => ReadonlyArray<readonly [string, Type]>;
50
+ /**
51
+ * Whether a container `value` reaches no further than the `declared` members
52
+ * of its schema. The array kind is the only one with an answer of its own:
53
+ * `length` says how far an array reaches whether or not anything is there, so
54
+ * a hole past the prefix is caught here and nowhere else. An object has no
55
+ * such measure, and always fits.
56
+ */
57
+ export type Fits<C extends Unknown> = (value: C, declared: number) => boolean;
36
58
  /** Type guard narrowing `Unknown` to a specific container type `C`. */
37
59
  export type IsContainer<C extends Unknown> = (value: Unknown) => value is C;
38
60
  /** Maps a `Tag1` to its runtime container type. */
@@ -21,7 +21,7 @@
21
21
  * @import { StringMap } from '../../object/types.ts'
22
22
  * @import { ArraySet, Data, KindSet, Node, ObjectSet, RuleSet, UnionSet } from './types.ts'
23
23
  */
24
- import type { Const, Type } from '../types.ts';
24
+ import type { Const, ConstObject, Type } from '../types.ts';
25
25
  import type { Primitive, Unknown } from '../ts/types.ts';
26
26
  import type { ResultE } from '../common/types.ts';
27
27
  import type { StringMap } from '../../object/types.ts';
@@ -95,6 +95,22 @@ export type _Keyed = readonly [Node, string | undefined];
95
95
  * @type {(a: Data) => (b: Data) => boolean}
96
96
  */
97
97
  export declare const subset: (a: Data) => (b: Data) => boolean;
98
+ /**
99
+ * Whether `a` and `b` denote the same set: {@link subset} both ways.
100
+ *
101
+ * Weaker as a test than {@link equal} and stronger as an answer. `equal`
102
+ * compares canonical *structure*, so it reports two α-equivalent recursive
103
+ * definitions — the same shape under different rule names — as different;
104
+ * `subset` resolves references coinductively and sees through the renaming
105
+ * (see `./README.md`). It inherits `subset`'s incompleteness in exchange: an
106
+ * equality that holds only by distributing a union, or through a non-syntactic
107
+ * empty set, is answered `false`. That direction is the safe one — a `false`
108
+ * here never merges two memberships — so a caller may treat a `true` as
109
+ * conclusive and a `false` as "not established".
110
+ *
111
+ * @type {(a: Data) => (b: Data) => boolean}
112
+ */
113
+ export declare const equivalent: (a: Data) => (b: Data) => boolean;
98
114
  export type _NodeMap = (n: Node) => Node;
99
115
  export type _Thunk = Exclude<Type, Const>;
100
116
  export type _Key = Exclude<Type, Primitive>;
@@ -123,6 +139,46 @@ export type _State = {
123
139
  * @type {(t: Type) => Data}
124
140
  */
125
141
  export declare const toData: (t: Type) => Data;
142
+ /**
143
+ * Whether `r`, as the rest of the container `c`, admits nothing — so that
144
+ * `rest(c, r)` and the bare, closed `c` denote one set.
145
+ *
146
+ * This is what the schema-form readers bound an array's length by, and it is
147
+ * asked *here* on purpose: `arraySet` is where an empty rest normalizes away,
148
+ * so the readers agree with this form by asking it rather than by re-deriving
149
+ * the same rule and drifting. Stated as **making no difference to the
150
+ * canonical form** — the conversion of `rest(c, r)` denoting the same set as
151
+ * the conversion of `c` — and not as a judgement about `r` on its own, nor as
152
+ * a reading of whether the conversion kept a `rest` key. Five cases fix it
153
+ * between them, and only this equality satisfies all five:
154
+ *
155
+ * - `never`, `or()` and `[or()]` all convert `rest(c, r)` to `c`'s own
156
+ * conversion, so all three are empty. Keying on the exported `never` alone
157
+ * would pass a `never`-only proof with the other two spellings still
158
+ * disagreeing.
159
+ * - `const r = () => ['rest', [r], never]` has no finite inhabitant, yet the
160
+ * conversion keeps `rest: "r"`, so it is *not* empty here. A reader
161
+ * recognizing that emptiness would start rejecting what this form accepts.
162
+ * - `const a = () => ['or', b]; const b = () => ['or', a]` rules out the
163
+ * rest's *own* canonical data as the test: {@link toData} of `a` is `never`,
164
+ * and the conversion still keeps `rest: "a"`.
165
+ * - `unknown` rules out "the conversion kept no `rest` key" as the test:
166
+ * `toData(rest([], unknown))` is `{ array: true }` — the whole kind, with no
167
+ * `rest` key — because a top rest collapses the pattern rather than being
168
+ * dropped from it.
169
+ * - Two separately constructed copies of one recursive rule, one in `c` and
170
+ * one in `r`, rule out {@link equal} as the comparison: converting the rest
171
+ * reserves the name, so `c`'s rule is named `r0` where converting `c` alone
172
+ * names it `r`, and `equal` compares recursive definitions by rule name.
173
+ *
174
+ * {@link equivalent} answers all five. It is incomplete, and in the direction
175
+ * that costs nothing here: an unrecognized empty rest leaves the length
176
+ * unbounded, which is what a *kept* `rest` key means in this form anyway, so
177
+ * the readers still agree with it.
178
+ *
179
+ * @type {(c: ConstObject, r: Type) => boolean}
180
+ */
181
+ export declare const emptyRest: (c: ConstObject, r: Type) => boolean;
126
182
  /**
127
183
  * Data-driven validation — the counterpart of `../validate` that consumes a
128
184
  * {@link Data} produced by {@link toData} instead of walking the thunk