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
@@ -8,11 +8,13 @@ import {
8
8
  array,
9
9
  bigint,
10
10
  boolean,
11
+ open,
11
12
  never as neverRtti,
12
13
  number,
13
14
  option,
14
15
  or,
15
16
  record,
17
+ rest,
16
18
  string,
17
19
  unknown as unknownRtti,
18
20
  } from '../module.f.mjs'
@@ -114,10 +116,34 @@ const identityRec = f => f
114
116
  /** @type {_Rec} */
115
117
  const anon = identityRec(() => ['array', anon])
116
118
 
119
+ /** A cycle through a closed tuple: `_ClosedNode = [number, readonly _ClosedNode[]]`. */
120
+ /** @typedef {() => readonly ['const', readonly [typeof number, _ClosedChildren]]} _ClosedNode */
121
+ /** @typedef {() => readonly ['array', _ClosedNode]} _ClosedChildren */
122
+ /** @type {_ClosedNode} */
123
+ const closedNode = () => ['const', [number, closedChildren]]
124
+ /** @type {_ClosedChildren} */
125
+ const closedChildren = () => ['array', closedNode]
126
+
127
+ /** A cycle through a struct's stated `rest`. */
128
+ /** @typedef {() => readonly ['rest', { readonly a: typeof number }, _NestedRest]} _NestedRest */
129
+ /** @type {_NestedRest} */
130
+ const nestedRest = () => ['rest', { a: number }, nestedRest]
131
+
117
132
  const tupleNumber = /** @type {const} */ ([number])
118
133
  const tupleString = /** @type {const} */ ([string])
134
+ const tupleNumberNumber = /** @type {const} */ ([number, number])
119
135
  const emptyTuple = /** @type {const} */ ([])
120
136
 
137
+ /**
138
+ * The exact-length array patterns — what a bare, closed `Tuple` converts to,
139
+ * and what an `open` one never reaches. Written as data as well so the
140
+ * `rest`-less arm of every array operation is exercised from both ends.
141
+ */
142
+ /** @type {Data} */
143
+ const exactlyOneNumber = [{}, { array: [{ prefix: [{ number: true }] }] }]
144
+ /** @type {Data} */
145
+ const exactlyTwoNumbers = [{}, { array: [{ prefix: [{ number: true }, { number: true }] }] }]
146
+
121
147
  export const proof = {
122
148
  withoutUnits: [
123
149
  // Removing the last unit bit removes the key: an empty kind is an
@@ -176,16 +202,83 @@ export const proof = {
176
202
  assertData(toData(record(unknownRtti)))([{}, { object: true }])
177
203
  assertData(toData(array(neverRtti)))([{}, { array: [{ prefix: [] }] }])
178
204
  assertData(toData(record(neverRtti)))([{}, { object: [{ props: {}, rest: {} }] }])
179
- assertData(toData(emptyTuple))([{}, { array: [{ prefix: [] }] }])
180
- assertData(toData(/** @type {const} */ ([number, 42])))(
181
- [{}, { array: [{ prefix: [{ number: true }, { number: [42] }] }] }])
182
- assertData(toData({}))([{}, { object: true }])
183
- assertData(toData({ b: string, a: number }))(
205
+ // both const containers are closed, so each carries the `rest`
206
+ // that says so `never`, on either kind. `open` is what supplies
207
+ // the `unknown` one, which the two kinds spell differently: a
208
+ // `rest: unknown` past a tuple's prefix, and no `rest` at all on a
209
+ // struct. An open tuple declaring nothing is therefore every array
210
+ // — and so is one whose every position restates that `rest`, which
211
+ // is trimmed away so that one set keeps one spelling
212
+ assertData(toData(open(emptyTuple)))([{}, { array: true }])
213
+ assertData(toData(open(/** @type {const} */ ([unknownRtti]))))([{}, { array: true }])
214
+ assertData(toData(open(/** @type {const} */ ([unknownRtti, unknownRtti]))))([{}, { array: true }])
215
+ assertData(toData(open(/** @type {const} */ ([number, unknownRtti]))))(toData(open(tupleNumber)))
216
+ assertData(toData(open(/** @type {const} */ ([number, 42]))))(
217
+ [{}, { array: [{ prefix: [{ number: true }, { number: [42] }], rest: unknown }] }])
218
+ assertData(toData(open({})))([{}, { object: true }])
219
+ assertData(toData(open({ b: string, a: number })))(
184
220
  [{}, { object: [{ props: { a: { number: true }, b: { string: true } } }] }])
185
- assertData(toData({ a: unknownRtti }))([{}, { object: true }])
221
+ assertData(toData(open({ a: unknownRtti })))([{}, { object: true }])
186
222
  assertData(toData(/** @type {const} */ ([neverRtti])))([{}, never])
187
223
  assertData(toData({ a: neverRtti }))([{}, never])
188
224
  },
225
+ // `rest` needs no new concept underneath: it is the schema-form
226
+ // spelling of a `rest` the data form already carries — `never` for the
227
+ // bare, closed container, `R` for a stated one — and `open` is the
228
+ // `unknown` one.
229
+ rest: () => {
230
+ assertData(toData(tupleNumber))(exactlyOneNumber)
231
+ assertData(toData(tupleNumberNumber))(exactlyTwoNumbers)
232
+ assertData(toData(emptyTuple))([{}, { array: [{ prefix: [] }] }])
233
+ assertData(toData({ a: number }))(
234
+ [{}, { object: [{ props: { a: { number: true } }, rest: never }] }])
235
+ assertData(toData({}))([{}, { object: [{ props: {}, rest: never }] }])
236
+ // the bare form and an explicit empty rest are one set
237
+ assertData(toData(tupleNumber))(toData(rest(tupleNumber, neverRtti)))
238
+ // a stated rest is that rest
239
+ assertData(toData(rest(tupleNumber, string)))(
240
+ [{}, { array: [{ prefix: [{ number: true }], rest: { string: true } }] }])
241
+ assertData(toData(rest({ a: number }, string)))(
242
+ [{}, { object: [{ props: { a: { number: true } }, rest: { string: true } }] }])
243
+ // an unconstrained rest is `open`, and normalizes the same way on
244
+ // both kinds
245
+ assertData(toData(rest(tupleNumber, unknownRtti)))(toData(open(tupleNumber)))
246
+ assertData(toData(rest({ a: number }, unknownRtti)))(toData(open({ a: number })))
247
+ // a `never` member empties the whole pattern, whatever the rest
248
+ assertData(toData(/** @type {const} */ ([neverRtti])))([{}, never])
249
+ assertData(toData({ a: neverRtti }))([{}, never])
250
+ // an unconstrained key is dropped only once the rest is gone: with
251
+ // one present, "anything at `a`" says strictly more than leaving
252
+ // `a` out, which `open({ a: unknown })` alone does not
253
+ assertData(toData(open({ a: unknownRtti })))([{}, { object: true }])
254
+ assertData(toData({ a: unknownRtti }))(
255
+ [{}, { object: [{ props: { a: unknown }, rest: never }] }])
256
+ // the same container bare and opened are two sets, so the
257
+ // by-identity memo for const containers must not answer for both
258
+ assert(!equal(toData(/** @type {const} */ ([tupleNumber, open(tupleNumber)])))(
259
+ toData(/** @type {const} */ ([tupleNumber, tupleNumber]))))
260
+ },
261
+ // A cycle closes through a stated `rest` the same way it closes
262
+ // through a bare container: the enclosing thunk is what becomes the
263
+ // named rule.
264
+ restRecursion: () => {
265
+ assertData(toData(closedNode))([
266
+ {
267
+ closedNode: {
268
+ array: [{
269
+ prefix: [
270
+ { number: true },
271
+ { array: [{ prefix: [], rest: 'closedNode' }] },
272
+ ],
273
+ }],
274
+ },
275
+ },
276
+ 'closedNode',
277
+ ])
278
+ // and a cycle whose recursive position is the rest itself
279
+ assertData(toData(nestedRest))(
280
+ [{ nestedRest: { object: [{ props: { a: { number: true } }, rest: 'nestedRest' }] } }, 'nestedRest'])
281
+ },
189
282
  constSchemas: () => {
190
283
  assertData(toData(() => /** @type {const} */ (['const', 42])))([{}, { number: [42] }])
191
284
  assertData(toData(() => /** @type {const} */ (['const', null])))([{}, { unit: unitBit(null) }])
@@ -242,11 +335,16 @@ export const proof = {
242
335
  ])
243
336
  assertData(toData({ p: list }))([
244
337
  { list: { array: [{ prefix: [], rest: 'list' }] } },
245
- { object: [{ props: { p: 'list' } }] },
338
+ { object: [{ props: { p: 'list' }, rest: never }] },
246
339
  ])
247
340
  assertData(toData(a2))([
248
341
  {
249
- a2: { array: [{ prefix: [], rest: { array: [{ prefix: ['a2', 'b2'] }] } }] },
342
+ a2: {
343
+ array: [{
344
+ prefix: [],
345
+ rest: { array: [{ prefix: ['a2', 'b2'] }] },
346
+ }],
347
+ },
250
348
  b2: { array: [{ prefix: [], rest: 'b2' }] },
251
349
  },
252
350
  'a2',
@@ -312,24 +410,43 @@ export const proof = {
312
410
  assertData(toData(anon))([{ '': { array: [{ prefix: [], rest: '' }] } }, ''])
313
411
  },
314
412
  collapse: () => {
315
- // readonly [number] readonly number[] the tuple pattern is dropped
316
- assertData(toData(or(tupleNumber, array(number))))(toData(array(number)))
317
- // the empty tuple is a member of every uniform array set
318
- assertData(toData(or(emptyTuple, array(number))))(toData(array(number)))
319
- assertData(toData(or(array(number), emptyTuple)))(toData(array(number)))
320
- // kept when neither pattern subsumes the other
321
- assertData(toData(or(tupleNumber, tupleString)))(
322
- [{}, { array: [{ prefix: [{ string: true }] }, { prefix: [{ number: true }] }] }])
413
+ // a longer tuple pattern is included in a shorter one when both
414
+ // are open — the array counterpart of a wider struct in a narrower
415
+ // one. Closed, neither includes the other: they differ in length.
416
+ assertData(toData(or(open(tupleNumberNumber), open(tupleNumber))))(toData(open(tupleNumber)))
417
+ // the empty-array pattern is a member of every uniform array set
418
+ assertData(toData(or(array(neverRtti), array(number))))(toData(array(number)))
419
+ assertData(toData(or(array(number), array(neverRtti))))(toData(array(number)))
420
+ // an open tuple declaring nothing absorbs the whole kind
421
+ assertData(toData(or(open(emptyTuple), array(number))))(toData(array(unknownRtti)))
422
+ // kept when neither pattern subsumes the other: an open `[number]`
423
+ // admits `[1, 'x']`, which `readonly number[]` does not, and the
424
+ // uniform set admits `[]`, which the tuple does not
425
+ assertData(toData(or(open(tupleNumber), array(number))))([{}, {
426
+ array: [
427
+ { prefix: [], rest: { number: true } },
428
+ { prefix: [{ number: true }], rest: unknown },
429
+ ],
430
+ }])
431
+ assertData(toData(or(open(tupleNumber), open(tupleString))))([{}, {
432
+ array: [
433
+ { prefix: [{ string: true }], rest: unknown },
434
+ { prefix: [{ number: true }], rest: unknown },
435
+ ],
436
+ }])
323
437
  // the collapse recurses into inline positions
324
- assertData(toData(array(or(tupleNumber, array(number)))))(toData(array(array(number))))
325
- assertData(toData(record(or(tupleNumber, array(number)))))(toData(record(array(number))))
326
- assertData(toData({ a: or(tupleNumber, array(number)) }))(toData({ a: array(number) }))
438
+ assertData(toData(array(or(open(tupleNumberNumber), open(tupleNumber)))))(
439
+ toData(array(open(tupleNumber))))
440
+ assertData(toData(record(or(open(tupleNumberNumber), open(tupleNumber)))))(
441
+ toData(record(open(tupleNumber))))
442
+ assertData(toData({ a: or(open(tupleNumberNumber), open(tupleNumber)) }))(
443
+ toData({ a: open(tupleNumber) }))
327
444
  // collapsing innards can make two patterns identical — deduplicated
328
- assertData(toData(or(array(or(tupleNumber, array(number))), array(array(number)))))(
329
- toData(array(array(number))))
445
+ assertData(toData(or(array(or(open(tupleNumberNumber), open(tupleNumber))), array(open(tupleNumber)))))(
446
+ toData(array(open(tupleNumber))))
330
447
  // subsumed object patterns are dropped, `true` absorbs patterns
331
448
  assertData(toData(or({ a: 42 }, { a: number })))(toData({ a: number }))
332
- assertData(toData(or({ a: number }, {})))(toData({}))
449
+ assertData(toData(or(open({ a: number }), open({}))))(toData(open({})))
333
450
  },
334
451
  serializable: () => {
335
452
  const d = toData(or(string, array(number), null))
@@ -363,6 +480,16 @@ export const proof = {
363
480
  assert(cmp(toData(number))(toData(list)) < 0)
364
481
  assert(cmp(toData(list))(toData(number)) > 0)
365
482
  },
483
+ // Two spellings of one set must not compare unequal, and two sets must
484
+ // not compare equal — the data form is content-addressed.
485
+ rest: () => {
486
+ assertEq(cmp(toData(tupleNumber))(toData(rest(tupleNumber, neverRtti))), 0)
487
+ assertEq(cmp(toData(rest(tupleNumber, unknownRtti)))(toData(open(tupleNumber))), 0)
488
+ assert(cmp(toData(tupleNumber))(toData(open(tupleNumber))) !== 0)
489
+ assert(cmp(toData({ a: number }))(toData(open({ a: number }))) !== 0)
490
+ assert(cmp(toData(tupleNumber))(toData(rest(tupleNumber, string))) !== 0)
491
+ assert(equal(toData(tupleNumber))(exactlyOneNumber))
492
+ },
366
493
  restOrder: () => {
367
494
  // a pattern without a rest sorts before one with a rest
368
495
  /** @type {Data} */
@@ -383,7 +510,7 @@ export const proof = {
383
510
  assertData(toData(or(record(number), record(string))))(
384
511
  [{}, { object: [{ props: {}, rest: { string: true } }, { props: {}, rest: { number: true } }] }])
385
512
  // fewer props sort before more props
386
- assertData(toData(or({ a: number }, record(number))))(
513
+ assertData(toData(or(open({ a: number }), record(number))))(
387
514
  [{}, { object: [{ props: {}, rest: { number: true } }, { props: { a: { number: true } } }] }])
388
515
  },
389
516
  rules: () => {
@@ -412,37 +539,110 @@ export const proof = {
412
539
  assert(subset(toData(or(number, string)))(toData(unknownRtti)))
413
540
  },
414
541
  arrays: () => {
415
- assert(subset(toData(tupleNumber))(toData(array(number))))
416
- assert(!subset(toData(array(number)))(toData(tupleNumber)))
542
+ // a longer open tuple is included in a shorter one, and neither is
543
+ // included in the uniform set: an open `[number]` admits `[1, 'x']`
544
+ assert(subset(toData(open(tupleNumberNumber)))(toData(open(tupleNumber))))
545
+ assert(!subset(toData(open(tupleNumber)))(toData(open(tupleNumberNumber))))
546
+ assert(!subset(toData(open(tupleNumber)))(toData(array(number))))
547
+ assert(!subset(toData(array(number)))(toData(open(tupleNumber))))
548
+ // the exact-length pattern — the bare, closed tuple — is the one
549
+ // the uniform set contains
550
+ assert(subset(exactlyOneNumber)(toData(array(number))))
551
+ assert(subset(exactlyOneNumber)(toData(open(tupleNumber))))
552
+ // and, admitting one length only, contains just its own
553
+ assert(subset(exactlyOneNumber)(exactlyOneNumber))
554
+ assert(!subset(exactlyOneNumber)(exactlyTwoNumbers))
555
+ assert(!subset(toData(open(tupleNumber)))(exactlyOneNumber))
417
556
  assert(subset(toData(array(number)))(toData(unknownRtti)))
418
557
  assert(!subset(toData(array(unknownRtti)))(toData(array(number))))
419
558
  assert(subset(toData(array(number)))(toData(array(or(number, string)))))
420
- assert(!subset(toData(tupleNumber))(toData(tupleString)))
421
- assert(!subset(toData(tupleNumber))(toData(/** @type {const} */ ([number, number]))))
559
+ assert(!subset(toData(open(tupleNumber)))(toData(open(tupleString))))
422
560
  },
423
561
  arrayRest: () => {
424
562
  /** @type {Data} */
425
563
  const oneOrMoreNumbers = [{}, { array: [{ prefix: [{ number: true }], rest: { number: true } }] }]
564
+ // a position restating a `rest` that excludes `undefined` is not
565
+ // redundant — it is what makes this "one or more" rather than the
566
+ // uniform set, so nothing trims it away
426
567
  assert(subset(oneOrMoreNumbers)(toData(array(number))))
427
568
  assert(!subset(toData(array(number)))(oneOrMoreNumbers))
428
- assert(subset(toData(/** @type {const} */ ([number, number])))(oneOrMoreNumbers))
429
- assert(!subset(toData(emptyTuple))(oneOrMoreNumbers))
569
+ assert(subset(exactlyTwoNumbers)(oneOrMoreNumbers))
570
+ // the open tuple of two numbers admits a third element of any kind
571
+ assert(!subset(toData(open(tupleNumberNumber)))(oneOrMoreNumbers))
572
+ // an open tuple declaring nothing is the whole kind
573
+ assert(!subset(toData(open(emptyTuple)))(oneOrMoreNumbers))
430
574
  /** @type {Data} */
431
575
  const oneNumberThenStrings = [{}, { array: [{ prefix: [{ number: true }], rest: { string: true } }] }]
432
576
  assert(!subset(oneOrMoreNumbers)(oneNumberThenStrings))
433
577
  },
578
+ // The closed default makes a `rest`-less array pattern and an object
579
+ // pattern with an empty `rest` the *ordinary* output of the thunk
580
+ // form, where hand-written data used to be the only way to reach
581
+ // either. Both directions of each.
582
+ rest: () => {
583
+ // closed is included in open, never the other way round
584
+ assert(subset(toData(tupleNumber))(toData(open(tupleNumber))))
585
+ assert(!subset(toData(open(tupleNumber)))(toData(tupleNumber)))
586
+ assert(subset(toData({ a: number }))(toData(open({ a: number }))))
587
+ assert(!subset(toData(open({ a: number })))(toData({ a: number })))
588
+ // an exact length is its own, and contains no other
589
+ assert(subset(toData(tupleNumber))(toData(tupleNumber)))
590
+ assert(!subset(toData(tupleNumber))(toData(tupleNumberNumber)))
591
+ assert(!subset(toData(tupleNumberNumber))(toData(tupleNumber)))
592
+ // a stated rest widens it, and an unconstrained one is openness
593
+ assert(subset(toData(tupleNumber))(toData(rest(tupleNumber, string))))
594
+ assert(!subset(toData(rest(tupleNumber, string)))(toData(tupleNumber)))
595
+ assert(subset(toData(rest(tupleNumber, string)))(toData(rest(tupleNumber, unknownRtti))))
596
+ assert(subset(toData({ a: number }))(toData(rest({ a: number }, string))))
597
+ assert(!subset(toData(rest({ a: number }, string)))(toData({ a: number })))
598
+ assert(subset(toData(rest({ a: number }, number)))(toData(record(number))))
599
+ assert(!subset(toData(record(number)))(toData(rest({ a: number }, number))))
600
+ // and the members are still compared pointwise
601
+ assert(subset(toData(/** @type {const} */ ([42])))(toData(tupleNumber)))
602
+ assert(!subset(toData(tupleNumber))(toData(tupleString)))
603
+ assert(subset(toData(closedNode))(toData(closedNode)))
604
+ },
605
+ // A key present holding `undefined` and a key absent are two different
606
+ // objects, and the two sides of a pattern read them differently: a
607
+ // declared key constrains the value *read* at it, so absence passes
608
+ // when its set holds `undefined`; an undeclared key is checked as an
609
+ // *entry*, so a present `undefined` must belong to `rest` itself.
610
+ //
611
+ // One "read set" of `rest ∪ undefined` folded the two together, which
612
+ // stayed sound only while a struct's `rest` was `unknown` and failed
613
+ // the trailing rest check. A bare struct now supplies `never` there,
614
+ // so the fold is reachable from every schema and answered `true` for a
615
+ // non-inclusion.
616
+ presenceIsNotAbsence: () => {
617
+ const p = toData({ a: option(number) })
618
+ const q = toData(record(number))
619
+ assert(!subset(p)(q))
620
+ // the witness, and the acceptance that makes it one
621
+ assertEq(validate(p)({ a: undefined })[0], 'ok')
622
+ assertEq(validate(q)({ a: undefined })[0], 'error')
623
+ // both halves of the per-key check are load-bearing, and neither
624
+ // implies the other: this pair agrees on every present value and
625
+ // differs only on whether the key may be missing
626
+ assert(!subset(toData(record(number)))(toData(rest({ a: number }, number))))
627
+ assertEq(validate(toData(record(number)))({})[0], 'ok')
628
+ assertEq(validate(toData(rest({ a: number }, number)))({})[0], 'error')
629
+ // and the open-struct spelling, which was sound before, still is
630
+ assert(!subset(toData({ a: option(number) }))(toData(record(number))))
631
+ },
434
632
  objects: () => {
435
- assert(subset(toData({ a: number }))(toData({})))
633
+ assert(subset(toData(open({ a: number })))(toData(open({}))))
436
634
  assert(subset(toData({ a: 42 }))(toData({ a: number })))
437
635
  assert(!subset(toData({ a: number }))(toData({ a: 42 })))
438
- assert(subset(toData({ a: number, b: string }))(toData({ a: number })))
636
+ assert(subset(toData(open({ a: number, b: string })))(toData(open({ a: number }))))
439
637
  assert(subset(toData(record(number)))(toData(record(or(number, string)))))
440
638
  assert(!subset(toData(record(or(number, string))))(toData(record(number))))
441
639
  // a record's keys may be absent, a required key excludes that
442
- assert(!subset(toData(record(number)))(toData({ a: number })))
443
- assert(subset(toData(record(number)))(toData({ a: option(number) })))
444
- // a struct leaves undeclared keys unconstrained, a record does not
445
- assert(!subset(toData({ a: number }))(toData(record(number))))
640
+ assert(!subset(toData(record(number)))(toData(open({ a: number }))))
641
+ assert(subset(toData(record(number)))(toData(open({ a: option(number) }))))
642
+ // an open struct leaves undeclared keys unconstrained, a record
643
+ // does not — while a closed one names them all, so it is included
644
+ assert(!subset(toData(open({ a: number })))(toData(record(number))))
645
+ assert(subset(toData({ a: number }))(toData(record(number))))
446
646
  // a key inherited from Object.prototype is not a declared prop
447
647
  assert(!subset(toData({ a: number }))(toData({ toString: number })))
448
648
  assert(subset(toData({ toString: /** @type {const} */ (42) }))(toData({ toString: number })))
@@ -517,10 +717,43 @@ export const proof = {
517
717
  assertEq(
518
718
  JSON.stringify(v([1, 'x'])),
519
719
  '["error",{"path":["1"],"message":"unexpected value"}]')
520
- const vt = validate(toData(/** @type {const} */ ([number, string])))
720
+ // an open tuple admits a longer array, and a position is required
721
+ // exactly when its set excludes `undefined`, so a shorter one is a
722
+ // member whenever the positions past its end admit absence
723
+ const vt = validate(toData(open(/** @type {const} */ ([number, string]))))
521
724
  assertEq(vt([1, 'a'])[0], 'ok')
522
- assertEq(vt([1])[0], 'error')
523
- assertEq(vt([1, 'a', 2])[0], 'error')
725
+ assertEq(vt([1, 'a', 2])[0], 'ok')
726
+ assertEq(
727
+ JSON.stringify(vt([1])),
728
+ '["error",{"path":["1"],"message":"unexpected value"}]')
729
+ const vo = validate(toData(open(/** @type {const} */ ([number, option(string)]))))
730
+ assertEq(vo([1])[0], 'ok')
731
+ assertEq(vo([1, 'a'])[0], 'ok')
732
+ assertEq(vo([])[0], 'error')
733
+ // the exact-length pattern admits neither, and counts a hole past
734
+ // the prefix: not an entry, but the array is still that long
735
+ const vx = validate(exactlyOneNumber)
736
+ assertEq(vx([1])[0], 'ok')
737
+ assertEq(vx([1, 2])[0], 'error')
738
+ assertEq(vx([])[0], 'error')
739
+ assertEq(vx([1, , ])[0], 'error')
740
+ assertEq(validate(toData(array(neverRtti)))([, ])[0], 'error')
741
+ assertEq(validate(toData(array(neverRtti)))([])[0], 'ok')
742
+ assertEq(validate(toData(array(neverRtti)))([1])[0], 'error')
743
+ // an enumerable non-index key is an entry the prefix has not
744
+ // spoken for, like an index past it: the `rest` answers it, and
745
+ // an exact-length pattern, having no `rest`, rejects it
746
+ assertEq(validate(toData(array(number)))(Object.assign([1], { foo: 2 }))[0], 'ok')
747
+ assertEq(validate(toData(array(number)))(Object.assign([1], { foo: 'x' }))[0], 'error')
748
+ assertEq(validate(toData(array(neverRtti)))(Object.assign([], { foo: 'x' }))[0], 'error')
749
+ assertEq(validate(exactlyOneNumber)(Object.assign([1], { foo: 1 }))[0], 'error')
750
+ // only the canonical spelling of a non-negative integer is a
751
+ // position; every other key is a property of the array object,
752
+ // whatever `Number` makes of it
753
+ for (const k of ['-1', '01', '1.5', ' 1', '1e0']) {
754
+ assertEq(validate(toData(array(number)))(Object.assign([1], { [k]: 'x' }))[0], 'error')
755
+ assertEq(validate(exactlyTwoNumbers)(Object.assign([1, 2], { [k]: 2 }))[0], 'error')
756
+ }
524
757
  /** @type {Data} */
525
758
  const onePlus = [{}, { array: [{ prefix: [{ number: true }], rest: { string: true } }] }]
526
759
  const vp = validate(onePlus)
@@ -536,7 +769,7 @@ export const proof = {
536
769
  '["error",{"path":[],"message":"no match"}]')
537
770
  },
538
771
  objects: () => {
539
- const v = validate(toData({ a: number, b: option(string) }))
772
+ const v = validate(toData(open({ a: number, b: option(string) })))
540
773
  assertEq(v({ a: 1 })[0], 'ok')
541
774
  assertEq(v({ a: 1, b: 's' })[0], 'ok')
542
775
  assertEq(v({ a: 1, extra: true })[0], 'ok')
@@ -19,14 +19,21 @@ export type KindSet<T> = true | readonly T[];
19
19
  /**
20
20
  * A set of arrays: a tuple with an optional rest.
21
21
  *
22
- * - `prefix` constrains the value set of each leading position.
23
- * - `rest` present: the array may be any length `>= prefix.length`, and every
24
- * element past the prefix belongs to `rest`.
25
- * - `rest` absent: the array length is exactly `prefix.length`.
22
+ * - `prefix` constrains, per leading position, the value *read* at that
23
+ * position a position past the array's end reads as `undefined`, so a
24
+ * position is required exactly when its set excludes `undefined`. This is
25
+ * the array half of the rule {@link ObjectSet} states for keys.
26
+ * - `rest` present: the value at every position past the prefix belongs to
27
+ * `rest`.
28
+ * - `rest` absent: there is nothing past the prefix.
26
29
  *
27
- * A tuple schema is `{ prefix }`; a uniform array schema is
28
- * `{ prefix: [], rest }`. Both are points of the same kind, so
29
- * `readonly [number] readonly number[]` is a plain pattern inclusion.
30
+ * A bare tuple schema is `{ prefix }` alone the exact-length set, since
31
+ * tuples are closed (see "Structs and tuples are closed" in `../README.md`)
32
+ * an `open` one is `{ prefix, rest: unknown }`, and a uniform array schema is
33
+ * `{ prefix: [], rest }`.
34
+ * All are points of the same kind, so a longer tuple pattern included in a
35
+ * shorter one is a plain pattern inclusion, the array counterpart of a wider
36
+ * struct included in a narrower one.
30
37
  */
31
38
  export type ArraySet = {
32
39
  readonly prefix: readonly Node[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The rtti readers against values **FunctionalScript cannot build**.
3
+ *
4
+ * A `proof.mjs` rather than a `proof.f.mjs`, and deliberately: the fixtures
5
+ * here need in-place mutation — `Object.setPrototypeOf` to give an array a
6
+ * prototype that supplies an index, `Object.assign` to put a key past the
7
+ * index range on one — which [`../../AGENTS.md`](../../AGENTS.md) §3.1 forbids
8
+ * in authored FunctionalScript. `shouldLoad` in
9
+ * [`../../dev/module.f.mjs`](../../dev/module.f.mjs) makes a plain
10
+ * `proof.mjs` the opt-in home for exactly that, so the mutation stays out of
11
+ * the `.f.mjs` proofs rather than being smuggled through them.
12
+ *
13
+ * The values are worth pinning even though the subset cannot express them: a
14
+ * caller in ordinary JavaScript can hand one to an exported reader, and both
15
+ * are cases where reading a member by *entry* and reading it by *index*
16
+ * disagree — which is what these readers had wrong.
17
+ *
18
+ * @module
19
+ *
20
+ * @import { Type } from './types.ts'
21
+ * @import { ValidateE } from './common/types.ts'
22
+ * @import { Unknown } from './ts/types.ts'
23
+ */
24
+ export declare const proof: {
25
+ undeclaredMembers: {
26
+ inheritedIndexIsAMember: () => void;
27
+ shadowedIndexIsTheOwnOne: () => void;
28
+ beyondTheIndexRangeIsAMember: () => void;
29
+ };
30
+ readersAgree: () => void;
31
+ inheritedIndexMeetsTheRest: () => void;
32
+ beyondTheIndexRangeIsAMember: () => void;
33
+ };