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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import { ValidationError, ValidateE } from '../common/types.ts'
2
+ * @import { ValidationError, ValidateE, Validate } from '../common/types.ts'
3
3
  * @import { Type } from '../types.ts'
4
4
  * @import { Equal } from '../../ts/types.ts'
5
5
  * @import { Ts, Unknown } from '../ts/types.ts'
@@ -9,7 +9,8 @@
9
9
 
10
10
  import { validate } from './module.f.mjs'
11
11
  import { parse } from '../parse/module.f.mjs'
12
- import { boolean, number, string, bigint, unknown, array, record, or, option } from '../module.f.mjs'
12
+ import { toData, validate as dataValidate } from '../data/module.f.mjs'
13
+ import { boolean, number, string, bigint, unknown, array, never, open, record, rest, or, option } from '../module.f.mjs'
13
14
  import { unwrap } from '../../result/module.f.mjs'
14
15
  import { assert, assertEq, assertStructurallySame } from '../../../asserts/module.f.mjs'
15
16
 
@@ -35,6 +36,170 @@ const v = t => /** @type {any} */ (validate(t))
35
36
  /** @type {(t: Type) => ValidateE} */
36
37
  const p = t => /** @type {any} */ (parse(t))
37
38
 
39
+ /** The data form's reader, over the same erased signature. */
40
+ /** @type {(t: Type) => ValidateE} */
41
+ const d = t => dataValidate(toData(t))
42
+
43
+ /**
44
+ * A rest that is its own container, so nothing about it is inline: the
45
+ * conversion keeps `rest: "recursiveRest"` rather than recognizing that no
46
+ * finite array inhabits it, and every reader accepts a hole past the prefix
47
+ * accordingly. It is one of the two rests {@link emptyRests} must *not*
48
+ * recognize.
49
+ *
50
+ * @typedef {() => readonly ['rest', readonly [_RecursiveRest], typeof never]} _RecursiveRest
51
+ */
52
+
53
+ /** @type {_RecursiveRest} */
54
+ const recursiveRest = () => ['rest', [recursiveRest], never]
55
+
56
+ /**
57
+ * The other one: a pure `or` cycle. `toData(orCycleA)` **is** `never`, yet as a
58
+ * rest it converts to a reference and stays, so a test on the rest's own
59
+ * canonical data would answer the opposite of the criterion.
60
+ *
61
+ * @typedef {() => readonly ['or', _OrCycleB]} _OrCycleA
62
+ * @typedef {() => readonly ['or', _OrCycleA]} _OrCycleB
63
+ */
64
+
65
+ /** @type {_OrCycleA} */
66
+ const orCycleA = () => ['or', orCycleB]
67
+
68
+ /** @type {_OrCycleB} */
69
+ const orCycleB = () => ['or', orCycleA]
70
+
71
+ /**
72
+ * Two separately constructed copies of one recursive rule. Converting a rest
73
+ * reserves its rule name first, so the container's copy is named `r0` where
74
+ * converting the container alone names it `r` — which is what rules `equal`
75
+ * out as the comparison behind {@link emptyRests}.
76
+ *
77
+ * @typedef {() => readonly ['or', undefined, () => readonly ['array', _SelfList]]} _SelfList
78
+ */
79
+
80
+ /** @type {_SelfList} */
81
+ const selfList0 = () => ['or', undefined, array(selfList0)]
82
+
83
+ /** @type {_SelfList} */
84
+ const selfList1 = () => ['or', undefined, array(selfList1)]
85
+
86
+ /**
87
+ * The acceptance table. Rows cover both container kinds, the closed default
88
+ * and a stated rest on both, the short-array rule, primitives, `or`, and
89
+ * misses — every reader of a schema has to answer them the same way.
90
+ *
91
+ * @type {readonly (readonly [Type, Unknown])[]}
92
+ */
93
+ const rows = [
94
+ [number, 42],
95
+ [number, '42'],
96
+ [string, 42],
97
+ [boolean, false],
98
+ [bigint, 7n],
99
+ [unknown, { a: [1, 'x'] }],
100
+ [/** @type {const} */ (42), 42],
101
+ [/** @type {const} */ (42), 43],
102
+ [array(number), [1, 2, 3]],
103
+ [array(number), [1, 'two']],
104
+ [array(number), {}],
105
+ // an enumerable non-index key is an entry every reader walks, so it is
106
+ // held to the element type like any other — and a key is an index only in
107
+ // the canonical spelling, whatever `Number` makes of it
108
+ [array(number), Object.assign([1], { foo: 2 })],
109
+ [array(number), Object.assign([1], { foo: 'x' })],
110
+ [array(number), Object.assign([1], { '-1': 'x' })],
111
+ [array(number), Object.assign([1], { '01': 'x' })],
112
+ // an empty element set is the empty array, not "any number of holes": the
113
+ // data form normalizes such a rest away, which leaves the exact-length
114
+ // pattern, and the thunk readers bound the length to match
115
+ [array(or()), []],
116
+ [array(or()), new Array(1)],
117
+ [array(number), [, ,]],
118
+ [record(number), { a: 1 }],
119
+ [record(number), { a: 'one' }],
120
+ [record(number), []],
121
+ // the closed default, on both kinds
122
+ [[/** @type {const} */ (42)], [42, 'extra']],
123
+ [[/** @type {const} */ (42)], [42]],
124
+ [[/** @type {const} */ (42)], [42, undefined]],
125
+ [[/** @type {const} */ (42)], [42, ,]],
126
+ [[/** @type {const} */ (42)], Object.assign([42], { foo: 1 })],
127
+ [[/** @type {const} */ (42)], []],
128
+ [{ a: /** @type {const} */ (42) }, { a: 42, b: 'x' }],
129
+ [{ a: /** @type {const} */ (42) }, { a: 42 }],
130
+ // a key declared `unknown` is a member the schema has, so the canonical
131
+ // form must not drop it the way an `open` struct's is dropped
132
+ [{ a: unknown }, { a: 1 }],
133
+ [{ a: unknown }, { a: 1, b: 2 }],
134
+ // and the same rows under `open`, which is the form that admits them
135
+ [open([/** @type {const} */ (42)]), [42, 'extra']],
136
+ [open({ a: /** @type {const} */ (42) }), { a: 42, b: 'x' }],
137
+ [open([]), [1]],
138
+ [open({}), { a: 1 }],
139
+ // closedness is about *undeclared* members and leaves the short-array rule
140
+ // alone
141
+ [[number, option(string)], [42]],
142
+ // the rule is per position, not "the last one": every trailing position
143
+ // whose set admits `undefined` may be absent, so an array may stop at the
144
+ // last required one
145
+ [[number, bigint, option(string), option(null)], [2, 4n]],
146
+ [[number, bigint, option(string), option(null)], [2, 4n, 'x']],
147
+ [[number, bigint, option(string), option(null)], [2, 4n, 'x', null]],
148
+ [[number, bigint, option(string), option(null)], [2]],
149
+ [[number, bigint, option(string), option(null)], [2, 4n, 5]],
150
+ [{ a: number, b: option(string) }, { a: 1 }],
151
+ [{ a: number }, { a: 'one' }],
152
+ // a hole in a tuple schema is a declared position whose schema is
153
+ // `undefined`, so the schema's length is what it declares — the reading
154
+ // the data form has always had, and the one `Object.entries` lost
155
+ [new Array(1), [1, 2, 3]],
156
+ [new Array(1), new Array(1)],
157
+ [new Array(1), [undefined]],
158
+ [new Array(1), [1]],
159
+ [new Array(1), []],
160
+ [[, number], [9, 5]],
161
+ [[, number], [undefined, 5]],
162
+ // and a non-index enumerable own property is no position at all: a tuple
163
+ // schema is read by index, so `foo` declares nothing — which leaves a
164
+ // value's own `foo` an undeclared member like any other
165
+ [Object.assign([number], { foo: string }), [1]],
166
+ [Object.assign([number], { foo: string }), Object.assign([1], { foo: 'x' })],
167
+ [open(Object.assign([number], { foo: string })), Object.assign([1], { foo: 'x' })],
168
+ // a stated rest: what an undeclared member must be
169
+ [rest([number], string), [1, 'x', 'y']],
170
+ [rest([number], string), [1, 2]],
171
+ // a hole past the prefix is no member, so it meets no rest — which is what
172
+ // the `| undefined` in the rendered tail says
173
+ [rest([number], string), [1, ,]],
174
+ // An index the prototype supplies, and a key past the index range, are
175
+ // members too — both need in-place mutation to build, so their rows run
176
+ // through the same three readers in `../host.proof.mjs`.
177
+ [rest({ a: number }, string), { a: 1, b: 'x' }],
178
+ [rest({ a: number }, string), { a: 1, b: 2 }],
179
+ // a stated rest with nothing to answer for: the struct kind has no length,
180
+ // so it fits whatever the rest is
181
+ [rest({ a: number }, string), { a: 1 }],
182
+ // an unconstrained rest is `open`
183
+ [rest([number], unknown), [1, 'x']],
184
+ [rest({ a: number }, unknown), { a: 1, b: 'x' }],
185
+ // an empty one is the bare form, so the length is bounded again
186
+ [rest([number], never), [1, ,]],
187
+ [rest([number], or()), [1, ,]],
188
+ [rest([number], [or()]), [1, ,]],
189
+ [rest([number], [or()]), [1, 2]],
190
+ // …and a rest the conversion keeps is not empty, however few values it
191
+ // has: these two are the pair that tells the criterion from an emptiness
192
+ // analysis
193
+ [rest([number], recursiveRest), [1, ,]],
194
+ [rest([number], orCycleA), [1, ,]],
195
+ [rest([selfList0], [selfList1, never]), [undefined, ,]],
196
+ [or(number, string), true],
197
+ [or(number, string), 'hello'],
198
+ [option(number), undefined],
199
+ [option(number), null],
200
+ [{ user: { name: string, age: number } }, { user: { name: 'A', age: 'old' } }],
201
+ ]
202
+
38
203
  export const proof = {
39
204
  // ── the three properties this module exists for ──────────────────────────
40
205
  //
@@ -55,16 +220,17 @@ export const proof = {
55
220
  // The contrast that motivates the module.
56
221
  assert('b' in unwrap(parse(schema)(input)), 'parse materializes it')
57
222
  },
58
- // An undeclared member survives. `parse` accepts it too structs and
59
- // tuples are open but does not carry it into what it builds.
223
+ // An undeclared member survives where the schema admits one at all.
224
+ // `parse` accepts the same values and does not carry the member into
225
+ // what it builds.
60
226
  undeclaredMemberSurvives: () => {
61
- const schema = { a: number }
227
+ const schema = open({ a: number })
62
228
  const struct = { a: 1, b: 'extra' }
63
229
  assertStructurallySame(unwrap(validate(schema)(struct)), { a: 1, b: 'extra' })
64
230
  assert(!('b' in unwrap(parse(schema)(struct))), 'parse drops it')
65
231
  // The same on the other kind: a longer array keeps its tail.
66
232
  const tuple = [1, 'extra']
67
- assertStructurallySame(unwrap(validate([number])(tuple)), [1, 'extra'])
233
+ assertStructurallySame(unwrap(validate(open([number]))(tuple)), [1, 'extra'])
68
234
  },
69
235
  // On success the result *is* the argument. This is the property the
70
236
  // other two follow from, and the mirror of `../parse/proof.f.mjs`'s
@@ -82,7 +248,7 @@ export const proof = {
82
248
  same(unknown, arr)
83
249
  const obj = { a: 1, b: 2 }
84
250
  same(record(number), obj)
85
- same({ a: number }, obj)
251
+ same({ a: number, b: number }, obj)
86
252
  same(or(string, record(number)), obj)
87
253
  const nested = { xs: [{ a: 1 }] }
88
254
  same({ xs: array({ a: number }) }, nested)
@@ -91,38 +257,8 @@ export const proof = {
91
257
  },
92
258
  },
93
259
  // Acceptance is `parse`'s, exactly: the two readers differ in what a
94
- // success carries and in nothing else. Rows cover both container kinds,
95
- // openness on both, the short-array rule, primitives, `or`, and misses.
260
+ // success carries and in nothing else.
96
261
  sameAcceptanceAsParse: () => {
97
- /** @type {readonly (readonly [Type, Unknown])[]} */
98
- const rows = [
99
- [number, 42],
100
- [number, '42'],
101
- [string, 42],
102
- [boolean, false],
103
- [bigint, 7n],
104
- [unknown, { a: [1, 'x'] }],
105
- [/** @type {const} */ (42), 42],
106
- [/** @type {const} */ (42), 43],
107
- [array(number), [1, 2, 3]],
108
- [array(number), [1, 'two']],
109
- [array(number), {}],
110
- [record(number), { a: 1 }],
111
- [record(number), { a: 'one' }],
112
- [record(number), []],
113
- // the four openness rows
114
- [[/** @type {const} */ (42)], [42, 'extra']],
115
- [{ a: /** @type {const} */ (42) }, { a: 42, b: 'x' }],
116
- [[number, option(string)], [42]],
117
- [[/** @type {const} */ (42)], []],
118
- [{ a: number, b: option(string) }, { a: 1 }],
119
- [{ a: number }, { a: 'one' }],
120
- [or(number, string), true],
121
- [or(number, string), 'hello'],
122
- [option(number), undefined],
123
- [option(number), null],
124
- [{ user: { name: string, age: number } }, { user: { name: 'A', age: 'old' } }],
125
- ]
126
262
  for (const [t, value] of rows) {
127
263
  const rv = v(t)(value)
128
264
  const rp = p(t)(value)
@@ -133,6 +269,130 @@ export const proof = {
133
269
  }
134
270
  }
135
271
  },
272
+ // The same table against the third reader, the data form's — the one that
273
+ // consumes `toData` output rather than the thunk graph. A schema denotes
274
+ // one set of values, so a conversion that changed which values a schema
275
+ // admits would be a bug in `toData`, and this is where it shows up: an
276
+ // open tuple whose data form was exact-length passed every row above and
277
+ // still disagreed here. Acceptance only: the data form reaches a value
278
+ // through the canonical union rather than through the schema's syntax, so
279
+ // it reports a miss as its own kind-wise failure rather than repeating
280
+ // `or`'s `no match`.
281
+ sameAcceptanceInTheDataForm: () => {
282
+ for (const [t, value] of rows) {
283
+ assertEq(d(t)(value)[0], p(t)(value)[0], 'the data form must accept what `parse` accepts')
284
+ }
285
+ },
286
+ // The two proofs above compare the readers with one another, so a
287
+ // regression all three shared would pass them both. The table's rows for
288
+ // a schema with several optional positions therefore carry their expected
289
+ // answer here as well.
290
+ //
291
+ // The rule is *per position*, not "the last one": a position is required
292
+ // exactly when its set excludes `undefined`, independently of the others.
293
+ // A dense prefix alone would only show that an optional *suffix* may be
294
+ // truncated, so the cases below also omit position 2 while position 3 is
295
+ // present, and omit a required position with everything after it present.
296
+ //
297
+ // Every case runs against the `open` form too. A stated rest is a separate
298
+ // reader on all three — `restContainerValidate`/`restContainerParse`, and
299
+ // its own conversion in the data form — and it widens *which values are
300
+ // members*, not which positions are required, so it must answer these
301
+ // identically. The one case where opening does change the answer is at the
302
+ // end.
303
+ optionalPositions: () => {
304
+ const t = /** @type {const} */ ([number, bigint, option(string), option(null)])
305
+ /** @type {(rtti: Type) => (check: (r: readonly [string, unknown]) => void) => (value: Unknown) => void} */
306
+ const every = rtti =>
307
+ check =>
308
+ value => {
309
+ for (const read of [v, p, d]) { check(read(rtti)(value)) }
310
+ }
311
+ for (const rtti of [t, open(t)]) {
312
+ const accepted = every(rtti)(assertOk)
313
+ const rejected = every(rtti)(assertError)
314
+ accepted([2, 4n]) // stops at the last required position
315
+ accepted([2, 4n, 'x']) // the first optional present
316
+ accepted([2, 4n, 'x', null]) // both present
317
+ // Omission is independent, not just truncation: an absent member
318
+ // reads as `undefined` wherever it sits, so position 2 may be
319
+ // missing while position 3 is present. A hole and an explicit
320
+ // `undefined` are the same value, so both spellings are accepted.
321
+ accepted([2, 4n, , null]) //< a hole at position 2
322
+ accepted([2, 4n, undefined, null]) //< the same value, spelled densely
323
+ rejected([2]) // `bigint` excludes `undefined`
324
+ rejected([2, 4n, 5]) // an optional that is present is still checked
325
+ // The mirror of the two rows above: `bigint` excludes `undefined`,
326
+ // so omitting position 1 fails however much of the rest is present.
327
+ rejected([2, , 'x', null]) //< a hole at position 1
328
+ }
329
+ // What opening does change: an element past the declared positions is
330
+ // a member of the open set and not of the bare, closed one.
331
+ const extra = /** @type {const} */ ([2, 4n, 'x', null, 'extra'])
332
+ every(t)(assertError)(extra)
333
+ every(open(t))(assertOk)(extra)
334
+ // How stopping short composes with running long: the two rules are
335
+ // independent, so the open form's accepted lengths run from the last
336
+ // required position upwards without a gap or a cap — 2, 3, 4, 5, and
337
+ // on. The bare form caps the top at the declared count and leaves the
338
+ // bottom where it is.
339
+ every(open(t))(assertOk)([2, 4n, 'x', null, 'a', 'b'])
340
+ },
341
+ // An interior omittable position may be absent with a *required* position
342
+ // after it, which is the sharpest witness that absence is per position
343
+ // rather than truncation: `optionalPositions`'s hole is followed by
344
+ // another omittable position, so it cannot say this. Absence is also
345
+ // positional, not a shift — `[, 5]` holds `5` at position 1 and is
346
+ // accepted, while `[5]` holds it at position 0 and is not.
347
+ interiorOptionBeforeRequired: () => {
348
+ const t = /** @type {const} */ ([option(string), number])
349
+ /** @type {(rtti: Type) => (check: (r: readonly [string, unknown]) => void) => (value: Unknown) => void} */
350
+ const every = rtti =>
351
+ check =>
352
+ value => {
353
+ for (const read of [v, p, d]) { check(read(rtti)(value)) }
354
+ }
355
+ // Open too, for the reason `optionalPositions` runs both: a stated
356
+ // rest is its own reader on all three, and this schema is not one of
357
+ // the shapes the trailing-option cases there already put through it.
358
+ for (const rtti of [t, open(t)]) {
359
+ every(rtti)(assertOk)([, 5]) //< a hole at position 0
360
+ every(rtti)(assertOk)([undefined, 5]) //< the same value, spelled densely
361
+ every(rtti)(assertOk)(['x', 5])
362
+ every(rtti)(assertError)([5]) //< `number` at position 1 is required
363
+ }
364
+ // And the one answer opening changes here as well.
365
+ const extra = /** @type {const} */ (['x', 5, 'extra'])
366
+ every(t)(assertError)(extra)
367
+ every(open(t))(assertOk)(extra)
368
+ },
369
+ // The two tables above pin that the three readers *agree*; these pin what
370
+ // they agree on, which is what the changelog entry claims.
371
+ sparseTuple: {
372
+ holeIsDeclaredUndefined: () => {
373
+ assertError(validate([, number])([9, 5]))
374
+ assertOk(validate([, number])([undefined, 5]))
375
+ assertError(validate(new Array(1))([1, 2, 3]))
376
+ assertOk(validate(new Array(1))([undefined]))
377
+ },
378
+ // A hole is a position, so a sparse schema is as long as it looks:
379
+ // `declared.length` is the schema's length, not its key count.
380
+ arityIsTheSchemaLength: () => {
381
+ assertOk(validate(new Array(1))([undefined]))
382
+ assertError(validate(new Array(1))([1]))
383
+ },
384
+ // A tuple schema is read by index, so `foo` declares no position — it
385
+ // is not matched against anything, and on the value side it is an
386
+ // undeclared member like any other, which the closed form rejects and
387
+ // `open` admits.
388
+ nonIndexPropertyIsNotDeclared: () => {
389
+ const schema = Object.assign([number], { foo: string })
390
+ assertOk(validate(schema)([1]))
391
+ assertError(validate(schema)(Object.assign([1], { foo: 'x' })))
392
+ assertOk(validate(open(schema))(Object.assign([1], { foo: 'x' })))
393
+ assertError(validate(schema)(['x']))
394
+ },
395
+ },
136
396
  boolean: {
137
397
  ok: () => {
138
398
  /** @typedef {Assert<Equal<Ts<typeof boolean>, boolean>>} _RoundTrip */
@@ -199,15 +459,15 @@ export const proof = {
199
459
  error: () => assertError(validate(undefined)(null)),
200
460
  },
201
461
  number: {
202
- ok: () => assertOk(validate(/** @type {const} */ (42))(42)),
203
- error: () => assertError(validate(/** @type {const} */ (42))(43)),
462
+ ok: () => assertOk(validate(42)(42)),
463
+ error: () => assertError(validate(42)(43)),
204
464
  },
205
465
  nan: {
206
466
  ok: () => assertOk(validate(NaN)(NaN)),
207
467
  error: () => {
208
468
  assertError(validate(NaN)(0))
209
- assertError(validate(/** @type {const} */ (0))(NaN))
210
- assertError(validate(/** @type {const} */ (42))(NaN))
469
+ assertError(validate(0)(NaN))
470
+ assertError(validate(42)(NaN))
211
471
  },
212
472
  },
213
473
  infinity: {
@@ -223,40 +483,36 @@ export const proof = {
223
483
  signedZero: {
224
484
  // `Object.is` distinguishes +0 and -0; `===` treats them equal.
225
485
  distinct: () => {
226
- assertError(validate(/** @type {const} */ (0))(-0))
486
+ assertError(validate(0)(-0))
227
487
  assertError(validate(-0)(0))
228
488
  },
229
489
  self: () => {
230
- assertOk(validate(/** @type {const} */ (0))(0))
490
+ assertOk(validate(0)(0))
231
491
  assertOk(validate(-0)(-0))
232
492
  },
233
493
  },
234
494
  string: {
235
- ok: () => assertOk(validate(/** @type {const} */ ('hello'))('hello')),
236
- error: () => assertError(validate(/** @type {const} */ ('hello'))('world')),
495
+ ok: () => assertOk(validate('hello')('hello')),
496
+ error: () => assertError(validate('hello')('world')),
237
497
  },
238
498
  bigint: {
239
- ok: () => assertOk(validate(/** @type {const} */ (7n))(7n)),
240
- error: () => assertError(validate(/** @type {const} */ (7n))(8n)),
499
+ ok: () => assertOk(validate(7n)(7n)),
500
+ error: () => assertError(validate(7n)(8n)),
241
501
  },
242
502
  boolean: {
243
- ok: () => assertOk(validate(/** @type {const} */ (true))(true)),
244
- error: () => assertError(validate(/** @type {const} */ (true))(false)),
503
+ ok: () => assertOk(validate(true)(true)),
504
+ error: () => assertError(validate(true)(false)),
245
505
  },
246
506
  tuple: {
247
- ok: () => {
248
- const t = /** @type {const} */ ([42, 'hello'])
249
- assertStructurallySame(unwrap(validate(t)([42, 'hello'])), [42, 'hello'])
250
- },
251
- // A tuple is OPEN, and the extras are still there afterwards. This
252
- // is deliberate — see "Structs and tuples are open" in
253
- // ../README.md. Do not restore #1622's length check on the
254
- // strength of `Ts<readonly [42]>` being an exact tuple; that
255
- // mapping is exact only because TypeScript could not express the
256
- // open one (see ../ts/types.ts `TupleTs`).
257
- extraItemsAcceptedAndKept: () => {
507
+ ok: () => assertStructurallySame(
508
+ unwrap(validate([42, 'hello'])([42, 'hello'])), [42, 'hello']),
509
+ // A tuple is CLOSED, so a longer array is not one of its values —
510
+ // see "Structs and tuples are closed" in ../README.md. Under
511
+ // `open` it is, and it comes back as it went in.
512
+ extraItemsRejected: () => {
258
513
  const long = [42, 1, 2, 3]
259
- assert(Object.is(unwrap(validate(/** @type {const} */ ([42]))(long)), long),
514
+ assertError(validate([42])(long))
515
+ assert(Object.is(unwrap(validate(open([42]))(long)), long),
260
516
  'the longer array comes back as it went in')
261
517
  },
262
518
  // An absent member reads as `undefined`, so a position is required
@@ -268,22 +524,28 @@ export const proof = {
268
524
  assert(Object.is(out, short), 'expected the original array')
269
525
  assertEq(short.length, 1, 'no gap is filled')
270
526
  },
271
- empty: () => assertOk(validate(/** @type {const} */ ([]))([])),
527
+ empty: () => assertOk(validate([])([])),
272
528
  error: () => {
273
- assertError(validate(/** @type {const} */ ([42]))([99]))
274
- assertError(validate(/** @type {const} */ ([42]))({}))
529
+ assertError(validate([42])([99]))
530
+ assertError(validate([42])({}))
275
531
  // `42` excludes `undefined`, so position 0 is required.
276
- assertError(validate(/** @type {const} */ ([42]))([]))
532
+ assertError(validate([42])([]))
277
533
  },
278
534
  },
279
535
  struct: {
536
+ // `validate` takes a `const` type parameter, so a struct literal
537
+ // keeps its literal members without an `@type {const}` cast at the
538
+ // call site: this is a reader for `{ a: 42, b: 'hello' }`, not for
539
+ // `{ a: number, b: string }`. Dropping the modifier is what makes
540
+ // the assertion fail.
280
541
  ok: () => {
281
- const t = /** @type {const} */ ({ a: 42, b: 'hello' })
282
- assertStructurallySame(unwrap(validate(t)({ a: 42, b: 'hello' })), { a: 42, b: 'hello' })
542
+ const v = validate({ a: 42, b: 'hello' })
543
+ /** @typedef {Assert<Equal<typeof v, Validate<{ readonly a: 42, readonly b: 'hello' }>>>} _ConstParameter */
544
+ assertStructurallySame(unwrap(v({ a: 42, b: 'hello' })), { a: 42, b: 'hello' })
283
545
  },
284
546
  error: () => {
285
- assertError(validate(/** @type {const} */ ({ a: 42 }))({ a: 99 }))
286
- assertError(validate(/** @type {const} */ ({ a: 42 }))([]))
547
+ assertError(validate({ a: 42 })({ a: 99 }))
548
+ assertError(validate({ a: 42 })([]))
287
549
  },
288
550
  },
289
551
  },
@@ -328,13 +590,13 @@ export const proof = {
328
590
  or: {
329
591
  consts: {
330
592
  ok: () => {
331
- const t = or(.../** @type {const} */ ([false, 42, 'hello']))
593
+ const t = or(false, 42, 'hello')
332
594
  assertOk(validate(t)(false))
333
595
  assertOk(validate(t)(42))
334
596
  assertOk(validate(t)('hello'))
335
597
  },
336
598
  error: () => {
337
- const t = or(.../** @type {const} */ ([false, 42, 'hello']))
599
+ const t = or(false, 42, 'hello')
338
600
  assertError(validate(t)(true))
339
601
  assertError(validate(t)(43))
340
602
  assertError(validate(t)('world'))
@@ -358,7 +620,7 @@ export const proof = {
358
620
  // result. `parse` here returns a length-1 array; `validate` returns
359
621
  // the length-3 one it was given.
360
622
  firstMatchWins: () => {
361
- const t = or(/** @type {const} */ ([number]), array(number))
623
+ const t = or(open([number]), array(number))
362
624
  const input = [1, 2, 3]
363
625
  assert(Object.is(unwrap(validate(t)(input)), input), 'expected the original array')
364
626
  assertStructurallySame(unwrap(parse(t)(input)), [1])
@@ -384,10 +646,10 @@ export const proof = {
384
646
  validate(array(array(number)))([[1, 'x'], [2, 3]])
385
647
  ),
386
648
  tupleIndex: () => assertErrorPath(['1'])(
387
- validate(/** @type {const} */ ([number, number]))([1, 'two'])
649
+ validate([number, number])([1, 'two'])
388
650
  ),
389
651
  structKey: () => assertErrorPath(['b'])(
390
- validate(/** @type {const} */ ({ a: number, b: number }))({ a: 1, b: 'two' })
652
+ validate({ a: number, b: number })({ a: 1, b: 'two' })
391
653
  ),
392
654
  deepStruct: () => {
393
655
  const schema = /** @type {const} */ ({ user: { name: string, age: number } })
@@ -423,4 +685,97 @@ export const proof = {
423
685
  assertError(x({ a: 42 }))
424
686
  },
425
687
  },
688
+ // A stated rest widens acceptance and nothing else: a success still carries
689
+ // the very value it was given. The acceptance half is in the table above,
690
+ // run through all three readers; what is left to pin here is that
691
+ // `validate` stays verbatim on that form too.
692
+ rest: {
693
+ verbatim: () => {
694
+ const value = [1, 'x', 'y']
695
+ assert(Object.is(unwrap(validate(rest([number], string))(value)), value),
696
+ 'the value comes back as it went in')
697
+ const struct = { a: 1, b: 'x' }
698
+ assert(Object.is(unwrap(validate(rest({ a: number }, string))(struct)), struct),
699
+ 'and so does an object with rest-matching keys')
700
+ },
701
+ // An absent optional member still stays absent — a container's rest
702
+ // says nothing about a member it declares.
703
+ absentOptionalStaysAbsent: () => {
704
+ const out = unwrap(validate({ a: number, b: option(string) })({ a: 1 }))
705
+ assert(!('b' in out), 'an absent optional member must stay absent')
706
+ },
707
+ path: () => {
708
+ assertErrorPath(['1'])(validate([number, number])([1, 'two']))
709
+ assertErrorPath(['b'])(validate(rest({ a: number }, string))({ a: 1, b: 2 }))
710
+ // The rejection of an undeclared member is about the container, so
711
+ // it is reported at the container.
712
+ assertErrorPath([])(validate({ a: number })({ a: 1, b: 2 }))
713
+ },
714
+ notAContainer: () => {
715
+ assertError(validate(rest([number], string))({}))
716
+ assertError(validate(rest({ a: number }, string))([]))
717
+ },
718
+ },
719
+ // The three-reader table pins that the readers *agree* on the empty-rest
720
+ // criterion; these assert the verdict outright, since a row alone passes
721
+ // whenever all three move together. Each spelling is named rather than
722
+ // covered by "an independently constructed empty rest": an implementation
723
+ // recognizing empty unions but not `[or()]` passes the `or()` row while
724
+ // keeping that spelling's disagreement, and one keyed on the exported
725
+ // `never`'s identity passes the converse.
726
+ emptyRests: {
727
+ dropped: () => {
728
+ for (const r of [never, or(), [or()]]) {
729
+ assertError(validate(rest([number], r))([42, ,]))
730
+ }
731
+ // The name collision: converting the rest reserves `r`, so the
732
+ // container's own rule is named `r0` — which `equal` reads as a
733
+ // different schema and `subset` both ways does not.
734
+ assertError(v(rest([selfList0], [selfList1, never]))([undefined, ,]))
735
+ },
736
+ kept: () => {
737
+ // A rest the conversion keeps is not empty however few values it
738
+ // has: `recursiveRest` catches an emptiness analysis that reaches
739
+ // container cycles, `orCycleA` one that tests the rest's own
740
+ // canonical data, and `unknown` one that reads the absence of a
741
+ // `rest` key as elimination.
742
+ // `v` rather than `validate`: `Ts<>` walks a recursive schema
743
+ // structurally, and these two exist to be recursive.
744
+ assertOk(v(rest([number], recursiveRest))([42, ,]))
745
+ assertOk(v(rest([number], orCycleA))([42, ,]))
746
+ assertOk(validate(open([]))([1]))
747
+ },
748
+ },
749
+ // The two ways an array can reach past a closed prefix, told apart. The
750
+ // acceptance table pins that the three readers agree on both; these assert
751
+ // the verdict, since a row alone passes whenever all three move together.
752
+ // The hole is rejected by *length* — it is no member, so the member check
753
+ // alone would let it through — and the explicit `undefined` by the member
754
+ // check, since it is a member and the schema declares no position for it.
755
+ // A closed tuple therefore has exactly one spelling per value.
756
+ beyondAClosedPrefix: () => {
757
+ for (const read of [v, p, d]) {
758
+ assertError(read([/** @type {const} */ (42)])([42, ,]))
759
+ assertError(read([/** @type {const} */ (42)])([42, undefined]))
760
+ assertOk(read([/** @type {const} */ (42)])([42]))
761
+ }
762
+ },
763
+ // The walk is bounded by what the value and its prototypes carry rather
764
+ // than by `length`: a sparse array as long as the index space allows
765
+ // answers at once, where materializing the range exhausted memory first.
766
+ // The verdicts are the ordinary ones — a bare tuple is too short for it,
767
+ // a rest with nothing present past the prefix admits it.
768
+ lengthDoesNotBoundTheWalk: () => {
769
+ const big = new Array(2 ** 32 - 1)
770
+ assertError(v([option(string)])(big))
771
+ assertOk(v(rest([], string))(big))
772
+ },
773
+ arrayOptional: () => {
774
+ const a = /** @type {const} */([number, option(string)])
775
+ const v = validate(a)
776
+ assertOk(v([5]))
777
+ assertError(v(["n"]))
778
+ assertOk(v([6, "3"]))
779
+ assertError(v([6, 9]))
780
+ }
426
781
  }
@@ -5,6 +5,10 @@
5
5
  * @module
6
6
  */
7
7
  export type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
8
+ export type And<A extends boolean, B extends boolean> = [
9
+ A,
10
+ B
11
+ ] extends [true, true] ? true : false;
8
12
  /**
9
13
  * A `struct` field: the key, its type expression, and — when the third
10
14
  * element is `true` — an optional-key marker (`"key"?: type`).