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
@@ -2,12 +2,14 @@
2
2
  * @import { ValidationError } from '../common/types.ts'
3
3
  * @import { Equal } from '../../ts/types.ts'
4
4
  * @import { Ts } from '../ts/types.ts'
5
+ * @import { Parse } from './types.ts'
5
6
  * @import { Unknown as DjsUnknown } from '../../../djs/types.ts'
6
7
  * @import { Assert } from '../../../asserts/types.ts'
8
+ * @import { Phantom } from '../../phantom/types.ts'
7
9
  */
8
10
 
9
11
  import { parse } from './module.f.mjs'
10
- import { boolean, number, string, bigint, unknown, array, record, or, option } from '../module.f.mjs'
12
+ import { boolean, number, string, bigint, unknown, array, open, record, rest, or, option } from '../module.f.mjs'
11
13
  import {
12
14
  assert,
13
15
  assertEq,
@@ -30,6 +32,23 @@ const unwrap = r => {
30
32
  return /** @type {T} */ (r[1])
31
33
  }
32
34
 
35
+ /** A container that contains itself: `[number, node?]`. */
36
+ /** @typedef {readonly [number, _Node | undefined]} _Node */
37
+
38
+ const _node = () => /** @type {const} */ (['const', [number, option(_node)]])
39
+
40
+ /** @type {Phantom<typeof _node, _Node>} */
41
+ const node = _node
42
+
43
+ /** A struct whose every undeclared key holds another one of these. */
44
+ /** @typedef {() => readonly ['rest', { readonly a: typeof number }, _Nest]} _Nest */
45
+
46
+ /** @type {_Nest} */
47
+ const _nest = () => ['rest', { a: number }, _nest]
48
+
49
+ /** @type {Phantom<_Nest, { readonly a: number }>} */
50
+ const nest = _nest
51
+
33
52
  /** @type {(expected: readonly string[]) => (r: readonly [string, unknown]) => void} */
34
53
  const assertErrorPath = expected =>
35
54
  r => {
@@ -105,15 +124,15 @@ export const proof = {
105
124
  error: () => assertError(parse(undefined)(null)),
106
125
  },
107
126
  number: {
108
- ok: () => assertOk(parse(/** @type {const} */ (42))(42)),
109
- error: () => assertError(parse(/** @type {const} */ (42))(43)),
127
+ ok: () => assertOk(parse(42)(42)),
128
+ error: () => assertError(parse(42)(43)),
110
129
  },
111
130
  nan: {
112
131
  ok: () => assertOk(parse(NaN)(NaN)),
113
132
  error: () => {
114
133
  assertError(parse(NaN)(0))
115
- assertError(parse(/** @type {const} */ (0))(NaN))
116
- assertError(parse(/** @type {const} */ (42))(NaN))
134
+ assertError(parse(0)(NaN))
135
+ assertError(parse(42)(NaN))
117
136
  },
118
137
  },
119
138
  infinity: {
@@ -129,44 +148,39 @@ export const proof = {
129
148
  signedZero: {
130
149
  // `Object.is` distinguishes +0 and -0; `===` treats them equal.
131
150
  distinct: () => {
132
- assertError(parse(/** @type {const} */ (0))(-0))
151
+ assertError(parse(0)(-0))
133
152
  assertError(parse(-0)(0))
134
153
  },
135
154
  self: () => {
136
- assertOk(parse(/** @type {const} */ (0))(0))
155
+ assertOk(parse(0)(0))
137
156
  assertOk(parse(-0)(-0))
138
157
  },
139
158
  },
140
159
  string: {
141
- ok: () => assertOk(parse(/** @type {const} */ ('hello'))('hello')),
142
- error: () => assertError(parse(/** @type {const} */ ('hello'))('world')),
160
+ ok: () => assertOk(parse('hello')('hello')),
161
+ error: () => assertError(parse('hello')('world')),
143
162
  },
144
163
  bigint: {
145
- ok: () => assertOk(parse(/** @type {const} */ (7n))(7n)),
146
- error: () => assertError(parse(/** @type {const} */ (7n))(8n)),
164
+ ok: () => assertOk(parse(7n)(7n)),
165
+ error: () => assertError(parse(7n)(8n)),
147
166
  },
148
167
  boolean: {
149
- ok: () => assertOk(parse(/** @type {const} */ (true))(true)),
150
- error: () => assertError(parse(/** @type {const} */ (true))(false)),
168
+ ok: () => assertOk(parse(true)(true)),
169
+ error: () => assertError(parse(true)(false)),
151
170
  },
152
171
  tuple: {
153
172
  ok: () => {
154
- const t = /** @type {const} */ ([42, 'hello'])
155
- const r = parse(t)([42, 'hello'])
173
+ const r = parse([42, 'hello'])([42, 'hello'])
156
174
  assertStructurallySame(unwrap(r), [42, 'hello'])
157
175
  },
158
- // A tuple is OPEN: a longer array is accepted, and the extras are
159
- // absent from what `parse` builds. This is deliberate — see
160
- // "Structs and tuples are open" in ../README.md. Do not add a
161
- // length check here on the strength of `Ts<readonly [42]>` being
162
- // an exact tuple; that mapping is exact only because TypeScript
163
- // could not express the open one (see ../ts/types.ts `TupleTs`),
164
- // and reading it as a design decision is what produced #1622.
165
- extraItemsAcceptedAndDropped: () => {
166
- const r = parse(/** @type {const} */ ([42]))([42, 'extra'])
167
- assertStructurallySame(unwrap(r), [42])
168
- const long = parse(/** @type {const} */ ([42]))([42, 1, 2, 3])
169
- assertStructurallySame(unwrap(long), [42])
176
+ // A tuple is CLOSED: a longer array is not one of its values —
177
+ // see "Structs and tuples are closed" in ../README.md. Under
178
+ // `open` it is accepted, and the extras are absent from what
179
+ // `parse` builds.
180
+ extraItemsRejected: () => {
181
+ assertError(parse([42])([42, 'extra']))
182
+ assertStructurallySame(unwrap(parse(open([42]))([42, 'extra'])), [42])
183
+ assertStructurallySame(unwrap(parse(open([42]))([42, 1, 2, 3])), [42])
170
184
  },
171
185
  // An absent member reads as `undefined`, so a position is required
172
186
  // exactly when its set excludes `undefined` — the same rule the
@@ -176,28 +190,32 @@ export const proof = {
176
190
  assertStructurallySame(unwrap(r), [42, undefined])
177
191
  },
178
192
  error: () => {
179
- assertError(parse(/** @type {const} */ ([42]))([99]))
180
- assertError(parse(/** @type {const} */ ([42]))({}))
193
+ assertError(parse([42])([99]))
194
+ assertError(parse([42])({}))
181
195
  // `42` excludes `undefined`, so position 0 is required.
182
- assertError(parse(/** @type {const} */ ([42]))([]))
196
+ assertError(parse([42])([]))
183
197
  },
184
198
  },
185
199
  struct: {
200
+ // `parse` takes a `const` type parameter, so a struct literal keeps
201
+ // its literal members without an `@type {const}` cast at the call
202
+ // site: this is a reader for `{ a: 42, b: 'hello' }`, not for
203
+ // `{ a: number, b: string }`. Dropping the modifier is what makes
204
+ // the assertion fail.
186
205
  ok: () => {
187
- const t = /** @type {const} */ ({ a: 42, b: 'hello' })
188
- const r = parse(t)({ a: 42, b: 'hello' })
189
- assertStructurallySame(unwrap(r), { a: 42, b: 'hello' })
206
+ const p = parse({ a: 42, b: 'hello' })
207
+ /** @typedef {Assert<Equal<typeof p, Parse<{ readonly a: 42, readonly b: 'hello' }>>>} _ConstParameter */
208
+ assertStructurallySame(unwrap(p({ a: 42, b: 'hello' })), { a: 42, b: 'hello' })
190
209
  },
191
- // A struct is OPEN, on the same terms as a tuple: undeclared
192
- // properties are accepted and absent from what `parse` builds.
193
- // See "Structs and tuples are open" in ../README.md.
194
- extraKeysAcceptedAndDropped: () => {
195
- const r = parse(/** @type {const} */ ({ a: /** @type {const} */ (42) }))({ a: 42, b: 'extra' })
196
- assertStructurallySame(unwrap(r), { a: 42 })
210
+ // A struct is CLOSED, on the same terms as a tuple. See "Structs
211
+ // and tuples are closed" in ../README.md.
212
+ extraKeysRejected: () => {
213
+ assertError(parse({ a: 42 })({ a: 42, b: 'extra' }))
214
+ assertStructurallySame(unwrap(parse(open({ a: 42 }))({ a: 42, b: 'extra' })), { a: 42 })
197
215
  },
198
216
  error: () => {
199
- assertError(parse(/** @type {const} */ ({ a: 42 }))({ a: 99 }))
200
- assertError(parse(/** @type {const} */ ({ a: 42 }))([]))
217
+ assertError(parse({ a: 42 })({ a: 99 }))
218
+ assertError(parse({ a: 42 })([]))
201
219
  },
202
220
  },
203
221
  },
@@ -262,13 +280,13 @@ export const proof = {
262
280
  or: {
263
281
  consts: {
264
282
  ok: () => {
265
- const t = or(.../** @type {const} */ ([false, 42, 'hello']))
283
+ const t = or(false, 42, 'hello')
266
284
  assertOk(parse(t)(false))
267
285
  assertOk(parse(t)(42))
268
286
  assertOk(parse(t)('hello'))
269
287
  },
270
288
  error: () => {
271
- const t = or(.../** @type {const} */ ([false, 42, 'hello']))
289
+ const t = or(false, 42, 'hello')
272
290
  assertError(parse(t)(true))
273
291
  assertError(parse(t)(43))
274
292
  assertError(parse(t)('world'))
@@ -289,10 +307,11 @@ export const proof = {
289
307
  },
290
308
  // First matching variant wins; the freshly-constructed value comes from that variant.
291
309
  firstMatchWins: () => {
292
- const t = or(/** @type {const} */ ([number]), array(number))
310
+ const t = or(open([number]), array(number))
293
311
  /** @type {readonly number[]} */
294
312
  const out = unwrap(parse(t)([1, 2, 3]))
295
- // The const tuple `[number]` matches first and returns a length-1 result.
313
+ // The open tuple `open([number])` matches first and returns a
314
+ // length-1 result; the closed `[number]` would not match at all.
296
315
  assertStructurallySame(out, [1])
297
316
  },
298
317
  },
@@ -319,10 +338,10 @@ export const proof = {
319
338
  parse(array(array(number)))([[1, 'x'], [2, 3]])
320
339
  ),
321
340
  tupleIndex: () => assertErrorPath(['1'])(
322
- parse(/** @type {const} */ ([number, number]))([1, 'two'])
341
+ parse([number, number])([1, 'two'])
323
342
  ),
324
343
  structKey: () => assertErrorPath(['b'])(
325
- parse(/** @type {const} */ ({ a: number, b: number }))({ a: 1, b: 'two' })
344
+ parse({ a: number, b: number })({ a: 1, b: 'two' })
326
345
  ),
327
346
  deepStruct: () => {
328
347
  const schema = /** @type {const} */ ({ user: { name: string, age: number } })
@@ -359,4 +378,109 @@ export const proof = {
359
378
  assertError(v({ a: 42 }))
360
379
  },
361
380
  },
381
+ // A bare container is closed, and a stated rest widens acceptance without
382
+ // changing construction at all: the result carries the declared members
383
+ // either way. `../validate/proof.f.mjs` runs the acceptance half of this
384
+ // through all three readers.
385
+ closed: {
386
+ tuple: {
387
+ exact: () => {
388
+ const p = parse([number, string])
389
+ /** @typedef {Assert<Equal<typeof p, Parse<readonly [typeof number, typeof string]>>>} _ConstParameter */
390
+ assertStructurallySame(unwrap(p([1, 'a'])), [1, 'a'])
391
+ },
392
+ // The whole point of the default: `open` is what accepts this.
393
+ extraRejected: () => {
394
+ assertError(parse([number])([1, 2]))
395
+ assertStructurallySame(unwrap(parse(open([number]))([1, 2])), [1])
396
+ },
397
+ // A hole past the prefix is no member, so length is what catches it.
398
+ holePastThePrefixRejected: () => assertError(parse([number])([1, ,])),
399
+ // Nor is a key that is no position at all.
400
+ nonIndexKeyRejected: () =>
401
+ assertError(parse([number])(Object.assign([1], { foo: 2 }))),
402
+ // The rule for a missing member is unchanged: an absent position
403
+ // reads as `undefined`.
404
+ shortArray: () => {
405
+ assertError(parse([number])([]))
406
+ assertStructurallySame(unwrap(parse([number, option(string)])([1])), [1, undefined])
407
+ },
408
+ empty: () => {
409
+ assertStructurallySame(unwrap(parse([])([])), [])
410
+ assertError(parse([])([1]))
411
+ },
412
+ notAnArray: () => assertError(parse([number])({})),
413
+ },
414
+ struct: {
415
+ exact: () => assertStructurallySame(unwrap(parse({ a: number })({ a: 1 })), { a: 1 }),
416
+ extraRejected: () => {
417
+ assertError(parse({ a: number })({ a: 1, b: 2 }))
418
+ assertOk(parse(open({ a: number }))({ a: 1, b: 2 }))
419
+ },
420
+ // A key declared as `unknown` is a member the schema *has*, so a
421
+ // closed struct still admits it — the canonical form may not drop
422
+ // it the way an `open` struct's is dropped.
423
+ unknownMemberIsStillDeclared: () => {
424
+ assertOk(parse({ a: unknown })({ a: 1 }))
425
+ assertError(parse({ a: unknown })({ a: 1, b: 2 }))
426
+ },
427
+ empty: () => {
428
+ assertStructurallySame(unwrap(parse({})({})), {})
429
+ assertError(parse({})({ a: 1 }))
430
+ },
431
+ notAnObject: () => assertError(parse({ a: number })([])),
432
+ },
433
+ // With a `rest`, an undeclared member is legal when it belongs to it —
434
+ // and still not carried into what `parse` builds. `rest` says what an
435
+ // undeclared member must be, not that the reader should keep it.
436
+ rest: {
437
+ checkedAndDropped: () => {
438
+ assertStructurallySame(unwrap(parse(rest({ a: number }, string))({ a: 1, b: 'x' })), { a: 1 })
439
+ assertStructurallySame(unwrap(parse(rest([number], string))([1, 'x', 'y'])), [1])
440
+ },
441
+ rejected: () => {
442
+ assertErrorPath(['b'])(parse(rest({ a: number }, string))({ a: 1, b: 2 }))
443
+ assertErrorPath(['1'])(parse(rest([number], string))([1, 2]))
444
+ },
445
+ // An unconstrained rest is `open`.
446
+ unknownRestIsOpen: () => assertOk(parse(rest([number], unknown))([1, 'x'])),
447
+ // An empty one is the bare form: the length bound comes back.
448
+ emptyRestBoundsTheLength: () => {
449
+ assertError(parse(rest([number], or()))([1, ,]))
450
+ assertStructurallySame(unwrap(parse(rest([number], or()))([1])), [1])
451
+ },
452
+ },
453
+ // The declared members are read the same way whatever the rest is,
454
+ // error paths included.
455
+ path: () => {
456
+ assertErrorPath(['1'])(parse([number, number])([1, 'two']))
457
+ assertErrorPath(['a', '0'])(parse({ a: [number] })({ a: ['x'] }))
458
+ },
459
+ // A cycle through a container terminates. The schema is
460
+ // `Phantom`-wrapped for the usual reason (`Ts<>`'s structural walk
461
+ // would not terminate over a recursive container — see
462
+ // `../ts/types.ts`); it is the *value* half under test here.
463
+ recursive: () => {
464
+ const p = parse(node)
465
+ assertStructurallySame(unwrap(p([1])), [1, undefined])
466
+ assertStructurallySame(unwrap(p([1, [2]])), [1, [2, undefined]])
467
+ assertError(p([1, [2], 3]))
468
+ },
469
+ // A cycle through the `rest` itself: every key other than `a` holds
470
+ // another one of these.
471
+ recursiveRest: () => {
472
+ const p = parse(nest)
473
+ assertStructurallySame(unwrap(p({ a: 1, b: { a: 2 } })), { a: 1 })
474
+ assertError(p({ a: 1, b: { a: 'x' } }))
475
+ assertError(p({ a: 1, b: 2 }))
476
+ },
477
+ },
478
+ arrayOptional: () => {
479
+ const a = /** @type {const} */([number, option(string)])
480
+ const v = parse(a)
481
+ assertOk(v([5]))
482
+ assertError(v(["n"]))
483
+ assertOk(v([6, "3"]))
484
+ assertError(v([6, 9]))
485
+ }
362
486
  }
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * @import { StringMap } from '../object/types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../ts/types.ts'
5
+ * @import { Or, Rest, Type1, Unknown } from './types.ts'
3
6
  */
4
7
  import type { StringMap } from '../object/types.ts';
5
8
  export type _Tests = StringMap<readonly unknown[]>;
9
+ declare const constInference: () => void;
6
10
  export declare const proof: {
11
+ constInference: typeof constInference;
7
12
  typeof: {
8
13
  [k: string]: (() => void)[];
9
14
  };
10
15
  };
16
+ export {};
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * @import { StringMap } from '../object/types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../ts/types.ts'
5
+ * @import { Or, Rest, Type1, Unknown } from './types.ts'
3
6
  */
4
7
 
5
- import { assertNotNullish } from '../../asserts/module.f.mjs'
8
+ import { assertNotNullish, assertStructurallySame } from '../../asserts/module.f.mjs'
9
+ import { array, number, open, option, or, record, rest, string, unknown } from './module.f.mjs'
6
10
 
7
11
  /** @typedef {StringMap<readonly unknown[]>} _Tests */
8
12
 
@@ -17,7 +21,44 @@ const tests = {
17
21
  function: [() => undefined]
18
22
  }
19
23
 
24
+ // `or`, `option`, `array`, `record`, `rest` and `open` take `const` type
25
+ // parameters, so a literal written at the call site stays a literal: `or(42, string)`
26
+ // describes `42 | string`, not `number | string`. Without the modifier a caller
27
+ // has to pin every literal with an `@type {const}` cast, and the assertions
28
+ // below are what fail if one of the modifiers is dropped. Each is paired with
29
+ // the thunk's own output, so the schema a call *builds* is checked next to the
30
+ // type it is given.
31
+ const constInference = () => {
32
+ const orConst = or(42, string)
33
+ /** @typedef {Assert<Equal<typeof orConst, Or<readonly [42, typeof string]>>>} _OrConst */
34
+ assertStructurallySame(orConst(), ['or', 42, string])
35
+
36
+ const optionConst = option([42, string])
37
+ /** @typedef {Assert<Equal<typeof optionConst, Or<readonly [readonly [42, typeof string], undefined]>>>} _OptionConst */
38
+ assertStructurallySame(optionConst(), ['or', [42, string], undefined])
39
+
40
+ const arrayConst = array('hello')
41
+ /** @typedef {Assert<Equal<typeof arrayConst, Type1<'array', 'hello'>>>} _ArrayConst */
42
+ assertStructurallySame(arrayConst(), ['array', 'hello'])
43
+
44
+ const recordConst = record({ a: number })
45
+ /** @typedef {Assert<Equal<typeof recordConst, Type1<'record', { readonly a: typeof number }>>>} _RecordConst */
46
+ assertStructurallySame(recordConst(), ['record', { a: number }])
47
+
48
+ const restConst = rest({ a: 42 }, string)
49
+ /** @typedef {Assert<Equal<typeof restConst, Rest<{ readonly a: 42 }, typeof string>>>} _RestConst */
50
+ assertStructurallySame(restConst(), ['rest', { a: 42 }, string])
51
+
52
+ // `open` needs the modifier of its own — it is not `rest` partially
53
+ // applied, so dropping it there widens `[42, string]` to `Type[]` while
54
+ // every assertion above still passes.
55
+ const openConst = open([42, string])
56
+ /** @typedef {Assert<Equal<typeof openConst, Rest<readonly [42, typeof string], Unknown>>>} _OpenConst */
57
+ assertStructurallySame(openConst(), ['rest', [42, string], unknown])
58
+ }
59
+
20
60
  export const proof = {
61
+ constInference,
21
62
  typeof: Object.fromEntries(Object.entries(tests).map(([k, a]) => [k, assertNotNullish(a).map(v => () => {
22
63
  if (typeof v !== k) { throw `typeof ${v} !== ${k}` }
23
64
  })])),
@@ -64,9 +64,14 @@ export declare const dataToTs: (mut?: true) => (data: Data) => readonly [readonl
64
64
  * definitions the expression references; a schema with no reference cycles
65
65
  * needs none.
66
66
  *
67
- * **Note:** the `unknown` schema produces the string `'unknown'`
68
- * (TypeScript's built-in), whereas `Ts<>` maps it to `DjsUnknown` from
69
- * `djs/module.f.ts`.
67
+ * **Two notes where this and `Ts<>` differ.** The `unknown` schema produces
68
+ * the string `'unknown'` (TypeScript's built-in), whereas `Ts<>` maps it to
69
+ * `DjsUnknown` from `djs/module.f.ts`. And this printer recognizes an empty
70
+ * rest **semantically** — the data form has already normalized one away — so
71
+ * `rest([42], [or()])` prints the exact `readonly[42]`, where `Ts<>` keeps a
72
+ * tail it cannot see through (`RestTs` in `./types.ts` says why, and in which
73
+ * direction). Both print the same thing for every rest a schema states
74
+ * directly.
70
75
  *
71
76
  * @example
72
77
  * ```js
@@ -78,6 +83,7 @@ export declare const dataToTs: (mut?: true) => (data: Data) => readonly [readonl
78
83
  * toTs(42) // '42'
79
84
  * toTs('hello') // '"hello"'
80
85
  * toTs([boolean, number]) // 'readonly[boolean,number]'
86
+ * toTs(open([boolean, number])) // 'readonly[boolean,number,...readonly(unknown)[]]'
81
87
  * toTs({ x: string }) // '{readonly"x":string}'
82
88
  *
83
89
  * const list = () => ['array', list]
@@ -22,7 +22,7 @@ import { assertNotNullish } from '../../../asserts/module.f.mjs'
22
22
  import { reservedWords, strictModeReservedWords } from '../../../js/keywords/module.f.mjs'
23
23
  import { at, definedEntries } from '../../object/module.f.mjs'
24
24
  import { primitive, union, printer as tsPrinter } from '../../ts/module.f.mjs'
25
- import { cmp, toData, unitBit, unknown as top } from '../data/module.f.mjs'
25
+ import { cmp, never as bottom, toData, unitBit, unknown as top } from '../data/module.f.mjs'
26
26
 
27
27
  const nullBit = unitBit(null)
28
28
  const undefinedBit = unitBit(undefined)
@@ -145,28 +145,69 @@ const unitToTs = bits => [
145
145
  /**
146
146
  * A tuple prints its prefix, an array its element type, and a
147
147
  * tuple-with-rest combines them with a rest element:
148
- * `readonly[A,...readonly(R)[]]`.
148
+ * `readonly[A,...readonly(R|undefined)[]]`.
149
+ *
150
+ * A position the array may simply end before prints optional — the trailing
151
+ * run whose sets admit `undefined`, which is exactly what the array may stop
152
+ * at, arrays being contiguous. It mirrors the optional key `objectSetToTs`
153
+ * prints, and keeps the union rather than stripping `undefined` from it, as
154
+ * that one does.
155
+ *
156
+ * The **tail** admits `undefined` on top of what the `rest` states, because a
157
+ * hole past the prefix is no member: the readers check each present member
158
+ * against the `rest` and skip an absent one, so `rest([42], string)` accepts
159
+ * `[42, , ]` and index 1 reads `undefined`. `Ts<>` renders the same tail for
160
+ * the same reason (see `RestTs` in `./types.ts`). The open case pays nothing —
161
+ * `unknown` already admits `undefined` — and an empty rest never reaches here,
162
+ * the data form having normalized it away into the exact-length pattern.
163
+ *
164
+ * A pattern with **no prefix** is the uniform array, which `Ts<>` renders as
165
+ * `ArrayTs` — `readonly Ts<R>[]` — and this mirrors. A hole is no member there
166
+ * either, so that rendering has the same gap one element wider; it is
167
+ * `ArrayTs`'s to close, not this printer's, and closing it here alone would
168
+ * put the two renderers at odds on the commonest schema there is.
149
169
  *
150
170
  * @type {(ctx: _Ctx) => (p: ArraySet) => string}
151
171
  */
152
172
  const arraySetToTs = ctx => p => {
153
- const items = p.prefix.map(nodeToTs(ctx))
173
+ const required = p.prefix.findLastIndex(n => !admitsUndefined(ctx)(n)) + 1
174
+ const items = p.prefix.map((n, i) => {
175
+ const ts = nodeToTs(ctx)(n)
176
+ return i < required ? ts : `(${ts})?`
177
+ })
154
178
  const { rest } = p
155
179
  if (rest === undefined) { return ctx.ts.tuple(items) }
156
- const restTs = ctx.ts.array(nodeToTs(ctx)(rest))
157
- return items.length === 0 ? restTs : ctx.ts.tuple([...items, `...${restTs}`])
180
+ const restTs = nodeToTs(ctx)(rest)
181
+ if (items.length === 0) { return ctx.ts.array(restTs) }
182
+ const tail = ctx.ts.array(admitsUndefined(ctx)(rest)
183
+ ? restTs
184
+ : union(dedup([primitive(undefined), restTs])))
185
+ return ctx.ts.tuple([...items, `...${tail}`])
158
186
  }
159
187
 
160
188
  /**
161
- * Whether the node's value set admits `undefined` its unit bit, read
162
- * through a reference (own-property only) if needed.
189
+ * The node's own union, read through a reference (own-property only) if
190
+ * needed.
191
+ *
192
+ * @type {(ctx: _Ctx) => (n: Node) => UnionSet}
193
+ */
194
+ const resolveNode = ctx => n =>
195
+ typeof n === 'string' ? assertNotNullish(at(n)(ctx.rules)) : n
196
+
197
+ /**
198
+ * Whether the node's value set admits `undefined` — its unit bit.
163
199
  *
164
200
  * @type {(ctx: _Ctx) => (n: Node) => boolean}
165
201
  */
166
- const admitsUndefined = ctx => n => {
167
- const u = typeof n === 'string' ? assertNotNullish(at(n)(ctx.rules)) : n
168
- return ((u.unit ?? 0) & undefinedBit) !== 0
169
- }
202
+ const admitsUndefined = ctx => n =>
203
+ ((resolveNode(ctx)(n).unit ?? 0) & undefinedBit) !== 0
204
+
205
+ /**
206
+ * Whether the node's value set is empty.
207
+ *
208
+ * @type {(ctx: _Ctx) => (n: Node) => boolean}
209
+ */
210
+ const isNever = ctx => n => cmp([{}, resolveNode(ctx)(n)])([{}, bottom]) === 0
170
211
 
171
212
  /** @type {(list: readonly string[]) => readonly string[]} */
172
213
  const dedup = list => list.filter((s, i) => list.indexOf(s) === i)
@@ -179,6 +220,12 @@ const dedup = list => list.filter((s, i) => list.indexOf(s) === i)
179
220
  * declared keys too, so the index type widens to the union of the rest and
180
221
  * the declared value types — the closest expressible supertype.
181
222
  *
223
+ * An **empty** rest — what a bare, closed struct carries — prints as the
224
+ * fields alone. TypeScript object types are structurally open, so "and no
225
+ * other key" has no spelling there and the index signature would say the
226
+ * opposite of what is meant; the fields are the closest expressible
227
+ * supertype, exactly as `Ts<>` renders them.
228
+ *
182
229
  * @type {(ctx: _Ctx) => (p: ObjectSet) => string}
183
230
  */
184
231
  const objectSetToTs = ctx => p => {
@@ -188,7 +235,7 @@ const objectSetToTs = ctx => p => {
188
235
  return admitsUndefined(ctx)(v) ? [k, ts, true] : [k, ts]
189
236
  })
190
237
  const { rest } = p
191
- if (rest === undefined) { return ctx.ts.struct(fields) }
238
+ if (rest === undefined || isNever(ctx)(rest)) { return ctx.ts.struct(fields) }
192
239
  const restTs = ctx.ts.record(union(dedup([...fields.map(([, v]) => v), nodeToTs(ctx)(rest)])))
193
240
  return fields.length === 0 ? restTs : `${ctx.ts.struct(fields)}&${restTs}`
194
241
  }
@@ -257,9 +304,14 @@ export const dataToTs = mut => ([rules, entry]) => {
257
304
  * definitions the expression references; a schema with no reference cycles
258
305
  * needs none.
259
306
  *
260
- * **Note:** the `unknown` schema produces the string `'unknown'`
261
- * (TypeScript's built-in), whereas `Ts<>` maps it to `DjsUnknown` from
262
- * `djs/module.f.ts`.
307
+ * **Two notes where this and `Ts<>` differ.** The `unknown` schema produces
308
+ * the string `'unknown'` (TypeScript's built-in), whereas `Ts<>` maps it to
309
+ * `DjsUnknown` from `djs/module.f.ts`. And this printer recognizes an empty
310
+ * rest **semantically** — the data form has already normalized one away — so
311
+ * `rest([42], [or()])` prints the exact `readonly[42]`, where `Ts<>` keeps a
312
+ * tail it cannot see through (`RestTs` in `./types.ts` says why, and in which
313
+ * direction). Both print the same thing for every rest a schema states
314
+ * directly.
263
315
  *
264
316
  * @example
265
317
  * ```js
@@ -271,6 +323,7 @@ export const dataToTs = mut => ([rules, entry]) => {
271
323
  * toTs(42) // '42'
272
324
  * toTs('hello') // '"hello"'
273
325
  * toTs([boolean, number]) // 'readonly[boolean,number]'
326
+ * toTs(open([boolean, number])) // 'readonly[boolean,number,...readonly(unknown)[]]'
274
327
  * toTs({ x: string }) // '{readonly"x":string}'
275
328
  *
276
329
  * const list = () => ['array', list]
@@ -1,8 +1,30 @@
1
1
  /**
2
- * @import { Type } from '../types.ts'
2
+ * @import { Or, Type } from '../types.ts'
3
3
  * @import { Data } from '../data/types.ts'
4
+ * @import { Ts, TupleTs } from './types.ts'
5
+ * @import { Assert } from '../../../asserts/types.ts'
6
+ * @import { Equal } from '../../ts/types.ts'
4
7
  */
5
- import { number, string } from '../module.f.mjs';
8
+ import type { Or } from '../types.ts';
9
+ import type { Ts, TupleTs } from './types.ts';
10
+ import type { Assert } from '../../../asserts/types.ts';
11
+ import type { Equal } from '../../ts/types.ts';
12
+ import { boolean, number, string, bigint } from '../module.f.mjs';
13
+ export type _NonFixedLength = Assert<Equal<Ts<readonly (typeof number | typeof bigint)[]>, readonly (number | bigint)[]>>;
14
+ export type _OptionBoolean = Or<readonly [typeof boolean, undefined]>;
15
+ export type _OptionString = Or<readonly [typeof string, undefined]>;
16
+ export type _VariadicSchema = readonly [...(typeof number)[], _OptionString];
17
+ export type _VariadicPrefixRejectsMixedPrefix = Assert<readonly [1, 'x', 2] extends Ts<_VariadicSchema> ? false : true>;
18
+ export type _VariadicPrefixAdmitsItsOwnShape = Assert<readonly [1, 2, 'x'] extends Ts<_VariadicSchema> ? true : false>;
19
+ export type _RestTuple = Assert<Equal<Ts<readonly [typeof number, ...(typeof string)[]]>, readonly [number, ...string[]]>>;
20
+ export type _OptionalMember = Assert<Equal<Ts<readonly [typeof number, (typeof string)?]>, readonly [number, string?]>>;
21
+ export type _BranchA = readonly [typeof number, _OptionString];
22
+ export type _BranchB = readonly [typeof string, _OptionBoolean, _OptionNumber];
23
+ export type _OptionNumber = Or<readonly [typeof number, undefined]>;
24
+ export type _UnionKeepsBranchCorrelation = Assert<readonly [1, true] extends TupleTs<_BranchA | _BranchB> ? false : true>;
25
+ export type _UnionAdmitsItsOwnBranches = Assert<readonly [1, 'x'] extends TupleTs<_BranchA | _BranchB> ? true : false>;
26
+ export type _OptionalTail = Assert<Equal<Ts<readonly [typeof number, typeof bigint, _OptionBoolean, _OptionString]>, readonly [number, bigint, (boolean | undefined)?, (string | undefined)?]>>;
27
+ export type _InteriorStaysRequired = Assert<Equal<Ts<readonly [_OptionString, typeof number]>, readonly [string | undefined, number]>>;
6
28
  export type _List = () => readonly ['array', _List];
7
29
  export type _Tree = () => readonly ['or', typeof number, _Forest];
8
30
  export type _Forest = () => readonly ['array', _Tree];
@@ -42,7 +64,10 @@ export declare const proof: {
42
64
  bigint: () => void;
43
65
  emptyTuple: () => void;
44
66
  tuple: () => void;
67
+ optionalTuplePosition: () => void;
68
+ allOptionalTuple: () => void;
45
69
  emptyStruct: () => void;
70
+ unknownProp: () => void;
46
71
  struct: () => void;
47
72
  nestedStruct: () => void;
48
73
  quotedKey: () => void;
@@ -59,7 +84,21 @@ export declare const proof: {
59
84
  thunks: () => void;
60
85
  mixed: () => void;
61
86
  };
87
+ open: {
88
+ emptyTuple: () => void;
89
+ unconstrainedTuple: () => void;
90
+ tuple: () => void;
91
+ emptyStruct: () => void;
92
+ struct: () => void;
93
+ unknownProp: () => void;
94
+ tupleRest: () => void;
95
+ structRest: () => void;
96
+ openIsAnUnconstrainedRest: () => void;
97
+ emptyRestIsTheBareForm: () => void;
98
+ mut: () => void;
99
+ };
62
100
  never: () => void;
101
+ arrayOfNever: () => void;
63
102
  option: () => void;
64
103
  normalization: {
65
104
  booleanFromConsts: () => void;
@@ -86,6 +125,7 @@ export declare const proof: {
86
125
  };
87
126
  data: {
88
127
  tupleWithRest: () => void;
128
+ exactLengthTuple: () => void;
89
129
  structWithRest: () => void;
90
130
  optionalByReference: () => void;
91
131
  wholeKinds: () => void;