functionalscript 0.45.0 → 0.46.1

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 (296) hide show
  1. package/README.md +5 -3
  2. package/fjs/asn.1/module.f.mjs +8 -1
  3. package/fjs/asn.1/proof.f.d.mts +10 -0
  4. package/fjs/asn.1/proof.f.mjs +16 -0
  5. package/fjs/basen/base128/module.f.mjs +22 -5
  6. package/fjs/bnf/data/module.f.d.mts +32 -6
  7. package/fjs/bnf/data/module.f.mjs +112 -7
  8. package/fjs/bnf/data/proof.f.d.mts +2 -0
  9. package/fjs/bnf/data/proof.f.mjs +74 -3
  10. package/fjs/bnf/data/types.d.ts +20 -2
  11. package/fjs/bnf/descent/module.f.d.mts +28 -14
  12. package/fjs/bnf/descent/module.f.mjs +122 -77
  13. package/fjs/bnf/descent/proof.f.d.mts +2 -0
  14. package/fjs/bnf/descent/proof.f.mjs +117 -31
  15. package/fjs/bnf/descent/types.d.ts +12 -14
  16. package/fjs/bnf/ll1/module.f.d.mts +64 -22
  17. package/fjs/bnf/ll1/module.f.mjs +214 -154
  18. package/fjs/bnf/ll1/proof.f.d.mts +15 -2
  19. package/fjs/bnf/ll1/proof.f.mjs +323 -149
  20. package/fjs/bnf/ll1/types.d.ts +16 -24
  21. package/fjs/bnf/matcher/module.f.d.mts +66 -0
  22. package/fjs/bnf/matcher/module.f.mjs +81 -0
  23. package/fjs/bnf/matcher/proof.f.d.mts +10 -0
  24. package/fjs/bnf/matcher/proof.f.mjs +79 -0
  25. package/fjs/bnf/matcher/types.d.ts +54 -0
  26. package/fjs/bnf/testlib.f.d.mts +31 -0
  27. package/fjs/bnf/testlib.f.mjs +80 -0
  28. package/fjs/cas/cli/module.f.d.mts +1 -1
  29. package/fjs/cas/cli/module.f.mjs +14 -20
  30. package/fjs/cas/cli/proof.f.d.mts +1 -3
  31. package/fjs/cas/cli/proof.f.mjs +44 -33
  32. package/fjs/cas/evo/module.f.d.mts +64 -18
  33. package/fjs/cas/evo/module.f.mjs +148 -70
  34. package/fjs/cas/evo/proof.f.d.mts +10 -1
  35. package/fjs/cas/evo/proof.f.mjs +305 -223
  36. package/fjs/cas/evo/types.d.ts +45 -9
  37. package/fjs/cas/module.f.d.mts +18 -32
  38. package/fjs/cas/module.f.mjs +129 -128
  39. package/fjs/cas/proof.f.d.mts +11 -8
  40. package/fjs/cas/proof.f.mjs +259 -147
  41. package/fjs/cas/types.d.ts +24 -10
  42. package/fjs/ci/config/module.f.d.mts +2 -2
  43. package/fjs/ci/config/module.f.mjs +2 -2
  44. package/fjs/ci/module.f.d.mts +5 -5
  45. package/fjs/ci/module.f.mjs +8 -7
  46. package/fjs/ci/nix/module.f.d.mts +7 -5
  47. package/fjs/ci/nix/module.f.mjs +13 -12
  48. package/fjs/ci/nix/proof.f.mjs +2 -2
  49. package/fjs/ci/proof.f.mjs +9 -5
  50. package/fjs/cli/module.f.d.mts +4 -6
  51. package/fjs/cli/module.f.mjs +4 -8
  52. package/fjs/cli/proof.f.mjs +17 -16
  53. package/fjs/cli/types.d.ts +2 -3
  54. package/fjs/common/monoid/types.d.ts +1 -1
  55. package/fjs/crypto/hmac/module.f.mjs +2 -2
  56. package/fjs/crypto/sha2/module.f.mjs +3 -1
  57. package/fjs/crypto/sha2/proof.f.d.mts +1 -0
  58. package/fjs/crypto/sha2/proof.f.mjs +24 -0
  59. package/fjs/crypto/sha2/types.d.ts +11 -0
  60. package/fjs/crypto/sign/module.f.mjs +2 -2
  61. package/fjs/dev/module.f.d.mts +13 -4
  62. package/fjs/dev/module.f.mjs +56 -27
  63. package/fjs/dev/update/module.f.d.mts +9 -4
  64. package/fjs/dev/update/module.f.mjs +14 -10
  65. package/fjs/dev/update/proof.f.d.mts +1 -3
  66. package/fjs/dev/update/proof.f.mjs +10 -5
  67. package/fjs/djs/module.f.d.mts +13 -5
  68. package/fjs/djs/module.f.mjs +31 -16
  69. package/fjs/djs/parser/module.f.d.mts +13 -3
  70. package/fjs/djs/parser/module.f.mjs +117 -16
  71. package/fjs/djs/parser/proof.f.d.mts +5 -1
  72. package/fjs/djs/parser/proof.f.mjs +274 -12
  73. package/fjs/djs/parser/types.d.ts +7 -1
  74. package/fjs/djs/proof.f.d.mts +18 -2
  75. package/fjs/djs/proof.f.mjs +187 -12
  76. package/fjs/djs/serializer/module.f.d.mts +25 -8
  77. package/fjs/djs/serializer/module.f.mjs +61 -15
  78. package/fjs/djs/serializer/proof.f.d.mts +5 -0
  79. package/fjs/djs/serializer/proof.f.mjs +23 -1
  80. package/fjs/djs/tokenizer/module.f.d.mts +17 -5
  81. package/fjs/djs/tokenizer/module.f.mjs +99 -60
  82. package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
  83. package/fjs/djs/tokenizer/proof.f.mjs +74 -67
  84. package/fjs/djs/transpiler/module.f.d.mts +12 -7
  85. package/fjs/djs/transpiler/module.f.mjs +82 -56
  86. package/fjs/djs/transpiler/types.d.ts +7 -3
  87. package/fjs/djs/types.d.ts +7 -1
  88. package/fjs/effects/list/module.f.d.mts +14 -11
  89. package/fjs/effects/list/module.f.mjs +13 -11
  90. package/fjs/effects/list/types.d.ts +27 -7
  91. package/fjs/effects/memory/module.f.d.mts +2 -1
  92. package/fjs/effects/memory/module.f.mjs +3 -4
  93. package/fjs/effects/memory/proof.f.mjs +13 -7
  94. package/fjs/effects/memory/types.d.ts +4 -3
  95. package/fjs/effects/mock/module.f.d.mts +20 -4
  96. package/fjs/effects/mock/module.f.mjs +37 -5
  97. package/fjs/effects/mock/types.d.ts +10 -1
  98. package/fjs/effects/module.d.mts +4 -2
  99. package/fjs/effects/module.f.d.mts +466 -280
  100. package/fjs/effects/module.f.mjs +537 -299
  101. package/fjs/effects/module.mjs +2 -1
  102. package/fjs/effects/node/memory/module.d.mts +4 -2
  103. package/fjs/effects/node/memory/module.mjs +6 -3
  104. package/fjs/effects/node/memory/proof.mjs +10 -4
  105. package/fjs/effects/node/module.d.mts +4 -3
  106. package/fjs/effects/node/module.f.d.mts +168 -33
  107. package/fjs/effects/node/module.f.mjs +256 -52
  108. package/fjs/effects/node/module.mjs +57 -34
  109. package/fjs/effects/node/proof.f.d.mts +28 -2
  110. package/fjs/effects/node/proof.f.mjs +161 -42
  111. package/fjs/effects/node/types.d.ts +106 -18
  112. package/fjs/effects/node/virtual/module.f.d.mts +18 -4
  113. package/fjs/effects/node/virtual/module.f.mjs +110 -68
  114. package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
  115. package/fjs/effects/node/virtual/proof.f.mjs +190 -9
  116. package/fjs/effects/proof.f.d.mts +69 -37
  117. package/fjs/effects/proof.f.mjs +410 -130
  118. package/fjs/effects/types.d.ts +161 -33
  119. package/fjs/emergent_testing/module.f.d.mts +19 -12
  120. package/fjs/emergent_testing/module.f.mjs +93 -33
  121. package/fjs/emergent_testing/proof.f.d.mts +21 -7
  122. package/fjs/emergent_testing/proof.f.mjs +166 -32
  123. package/fjs/emergent_testing/types.d.ts +22 -4
  124. package/fjs/fsm/module.f.d.mts +14 -4
  125. package/fjs/fsm/module.f.mjs +54 -37
  126. package/fjs/fsm/proof.f.d.mts +2 -0
  127. package/fjs/fsm/proof.f.mjs +83 -114
  128. package/fjs/js/keywords/module.f.d.mts +52 -0
  129. package/fjs/js/keywords/module.f.mjs +72 -0
  130. package/fjs/js/keywords/proof.f.d.mts +3 -0
  131. package/fjs/js/keywords/proof.f.mjs +13 -0
  132. package/fjs/js/tokenizer/module.f.d.mts +26 -6
  133. package/fjs/js/tokenizer/module.f.mjs +145 -159
  134. package/fjs/js/tokenizer/proof.f.d.mts +1 -0
  135. package/fjs/js/tokenizer/proof.f.mjs +54 -24
  136. package/fjs/js/tokenizer/types.d.ts +33 -24
  137. package/fjs/mcp/cas/module.f.d.mts +1 -6
  138. package/fjs/mcp/cas/module.f.mjs +55 -51
  139. package/fjs/mcp/cas/proof.f.d.mts +15 -0
  140. package/fjs/mcp/cas/proof.f.mjs +174 -0
  141. package/fjs/mcp/evo/module.f.d.mts +19 -10
  142. package/fjs/mcp/evo/module.f.mjs +48 -27
  143. package/fjs/mcp/evo/proof.f.d.mts +6 -1
  144. package/fjs/mcp/evo/proof.f.mjs +115 -31
  145. package/fjs/mcp/module.f.d.mts +4 -4
  146. package/fjs/mcp/module.f.mjs +6 -6
  147. package/fjs/mcp/proof.f.d.mts +5 -3
  148. package/fjs/mcp/proof.f.mjs +112 -46
  149. package/fjs/media/html/module.f.mjs +1 -1
  150. package/fjs/media/json/extended/module.f.d.mts +82 -0
  151. package/fjs/media/json/extended/module.f.mjs +153 -0
  152. package/fjs/media/json/extended/proof.f.d.mts +42 -0
  153. package/fjs/media/json/extended/proof.f.mjs +127 -0
  154. package/fjs/media/json/extended/types.d.ts +23 -0
  155. package/fjs/media/json/module.f.d.mts +8 -2
  156. package/fjs/media/json/module.f.mjs +43 -41
  157. package/fjs/media/json/number/module.f.d.mts +59 -0
  158. package/fjs/media/json/number/module.f.mjs +136 -0
  159. package/fjs/media/json/number/proof.f.d.mts +24 -0
  160. package/fjs/media/json/number/proof.f.mjs +86 -0
  161. package/fjs/media/json/number/types.d.ts +28 -0
  162. package/fjs/media/json/parser/module.f.d.mts +25 -13
  163. package/fjs/media/json/parser/module.f.mjs +114 -70
  164. package/fjs/media/json/parser/proof.f.d.mts +5 -0
  165. package/fjs/media/json/parser/proof.f.mjs +31 -1
  166. package/fjs/media/json/parser/types.d.ts +33 -14
  167. package/fjs/media/json/rtti/module.f.d.mts +1 -1
  168. package/fjs/media/json/rtti/module.f.mjs +1 -1
  169. package/fjs/media/json/rtti/proof.f.mjs +9 -9
  170. package/fjs/media/json/schema/module.f.mjs +3 -13
  171. package/fjs/media/json/schema/proof.f.d.mts +0 -1
  172. package/fjs/media/json/schema/proof.f.mjs +1 -2
  173. package/fjs/media/json/serializer/module.f.d.mts +32 -1
  174. package/fjs/media/json/serializer/module.f.mjs +64 -2
  175. package/fjs/media/json/tokenizer/module.f.mjs +7 -3
  176. package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
  177. package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
  178. package/fjs/media/json/types.d.ts +36 -10
  179. package/fjs/media/lock/module.f.d.mts +100 -0
  180. package/fjs/media/lock/module.f.mjs +125 -0
  181. package/fjs/media/lock/proof.f.d.mts +33 -0
  182. package/fjs/media/lock/proof.f.mjs +196 -0
  183. package/fjs/media/lock/types.d.ts +15 -0
  184. package/fjs/media/module.f.d.mts +6 -5
  185. package/fjs/media/module.f.mjs +8 -7
  186. package/fjs/media/note/module.f.d.mts +121 -0
  187. package/fjs/media/note/module.f.mjs +131 -0
  188. package/fjs/media/note/proof.f.d.mts +29 -0
  189. package/fjs/media/note/proof.f.mjs +150 -0
  190. package/fjs/media/note/types.d.ts +10 -0
  191. package/fjs/media/proof.f.d.mts +4 -1
  192. package/fjs/media/proof.f.mjs +40 -21
  193. package/fjs/media/revision/module.f.d.mts +78 -7
  194. package/fjs/media/revision/module.f.mjs +119 -12
  195. package/fjs/media/revision/proof.f.d.mts +10 -0
  196. package/fjs/media/revision/proof.f.mjs +88 -0
  197. package/fjs/media/revision/types.d.ts +34 -5
  198. package/fjs/media/type/module.f.d.mts +33 -15
  199. package/fjs/media/type/module.f.mjs +35 -29
  200. package/fjs/media/type/proof.f.d.mts +2 -1
  201. package/fjs/media/type/proof.f.mjs +30 -10
  202. package/fjs/module.f.mjs +29 -8
  203. package/fjs/nanvm/proof.f.mjs +3 -3
  204. package/fjs/nanvm/rust/module.f.mjs +1 -1
  205. package/fjs/nanvm/update/module.f.d.mts +4 -4
  206. package/fjs/nanvm/update/module.f.mjs +8 -9
  207. package/fjs/nanvm/update/proof.f.mjs +4 -3
  208. package/fjs/proof.f.d.mts +3 -3
  209. package/fjs/proof.f.mjs +36 -10
  210. package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
  211. package/fjs/protocol/json_rpc/module.f.mjs +3 -3
  212. package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
  213. package/fjs/protocol/mcp/module.f.d.mts +10 -13
  214. package/fjs/protocol/mcp/module.f.mjs +74 -58
  215. package/fjs/protocol/mcp/proof.f.d.mts +13 -4
  216. package/fjs/protocol/mcp/proof.f.mjs +193 -61
  217. package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
  218. package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
  219. package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
  220. package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
  221. package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
  222. package/fjs/protocol/mcp/types.d.ts +17 -6
  223. package/fjs/sul/id/module.f.d.mts +0 -1
  224. package/fjs/sul/id/module.f.mjs +2 -3
  225. package/fjs/sul/level/hash/module.f.mjs +2 -1
  226. package/fjs/sul/level/hash/proof.f.mjs +2 -2
  227. package/fjs/sul/module.f.mjs +18 -13
  228. package/fjs/text/code_point/module.f.d.mts +8 -0
  229. package/fjs/text/code_point/module.f.mjs +8 -1
  230. package/fjs/text/code_point/proof.f.d.mts +1 -0
  231. package/fjs/text/code_point/proof.f.mjs +11 -0
  232. package/fjs/text/sgr/module.f.d.mts +4 -6
  233. package/fjs/text/sgr/module.f.mjs +6 -7
  234. package/fjs/text/utf16/module.f.mjs +7 -2
  235. package/fjs/text/utf16/proof.f.mjs +3 -3
  236. package/fjs/text/utf8/module.f.mjs +4 -2
  237. package/fjs/types/array/module.f.mjs +14 -2
  238. package/fjs/types/bit_vec/module.f.d.mts +0 -2
  239. package/fjs/types/bit_vec/module.f.mjs +46 -32
  240. package/fjs/types/bit_vec/proof.f.mjs +2 -2
  241. package/fjs/types/btree/remove/module.f.mjs +1 -1
  242. package/fjs/types/btree/set/module.f.mjs +9 -11
  243. package/fjs/types/btree/set/proof.f.mjs +12 -0
  244. package/fjs/types/byte_set/module.f.d.mts +11 -4
  245. package/fjs/types/byte_set/module.f.mjs +14 -6
  246. package/fjs/types/byte_set/proof.f.mjs +7 -7
  247. package/fjs/types/function/compare/module.f.mjs +10 -3
  248. package/fjs/types/list/module.f.d.mts +1 -1
  249. package/fjs/types/list/module.f.mjs +1 -1
  250. package/fjs/types/nullable/module.f.d.mts +18 -4
  251. package/fjs/types/nullable/module.f.mjs +21 -4
  252. package/fjs/types/nullable/proof.f.d.mts +4 -0
  253. package/fjs/types/nullable/proof.f.mjs +15 -0
  254. package/fjs/types/object/module.f.d.mts +12 -2
  255. package/fjs/types/object/module.f.mjs +11 -1
  256. package/fjs/types/patricia_trie/module.f.mjs +26 -11
  257. package/fjs/types/result/module.f.d.mts +3 -3
  258. package/fjs/types/result/module.f.mjs +3 -3
  259. package/fjs/types/rtti/common/module.f.d.mts +25 -28
  260. package/fjs/types/rtti/common/module.f.mjs +36 -32
  261. package/fjs/types/rtti/common/proof.f.mjs +5 -5
  262. package/fjs/types/rtti/data/module.f.d.mts +14 -0
  263. package/fjs/types/rtti/data/module.f.mjs +25 -2
  264. package/fjs/types/rtti/data/proof.f.d.mts +1 -0
  265. package/fjs/types/rtti/data/proof.f.mjs +25 -2
  266. package/fjs/types/rtti/parse/module.f.d.mts +24 -14
  267. package/fjs/types/rtti/parse/module.f.mjs +37 -28
  268. package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
  269. package/fjs/types/rtti/parse/proof.f.mjs +33 -14
  270. package/fjs/types/rtti/proof.f.mjs +3 -1
  271. package/fjs/types/rtti/ts/module.f.mjs +13 -17
  272. package/fjs/types/rtti/ts/types.d.ts +14 -1
  273. package/fjs/types/rtti/validate/module.f.d.mts +81 -19
  274. package/fjs/types/rtti/validate/module.f.mjs +107 -61
  275. package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
  276. package/fjs/types/rtti/validate/proof.f.mjs +186 -197
  277. package/fjs/types/sorted_set/module.f.d.mts +18 -0
  278. package/fjs/types/sorted_set/module.f.mjs +22 -0
  279. package/fjs/types/sorted_set/proof.f.d.mts +1 -0
  280. package/fjs/types/sorted_set/proof.f.mjs +16 -1
  281. package/fjs/types/uint8array/module.f.d.mts +1 -1
  282. package/fjs/types/uint8array/module.f.mjs +1 -1
  283. package/fjs/website/module.f.d.mts +3 -3
  284. package/fjs/website/module.f.mjs +4 -7
  285. package/fjs/website/proof.f.mjs +2 -1
  286. package/package.json +2 -2
  287. package/fjs/dev/package_json/module.f.d.mts +0 -39
  288. package/fjs/dev/package_json/module.f.mjs +0 -40
  289. package/fjs/dev/package_json/proof.f.d.mts +0 -6
  290. package/fjs/dev/package_json/proof.f.mjs +0 -32
  291. package/fjs/effects/eff/module.f.d.mts +0 -20
  292. package/fjs/effects/eff/module.f.mjs +0 -70
  293. package/fjs/effects/eff/proof.f.d.mts +0 -15
  294. package/fjs/effects/eff/proof.f.mjs +0 -69
  295. package/fjs/effects/eff/types.d.ts +0 -71
  296. package/fjs/types/rtti/validate/types.d.ts +0 -6
@@ -1,14 +1,17 @@
1
1
  /**
2
- * @import { ValidationError } from '../common/types.ts'
2
+ * @import { ValidationError, ValidateE } from '../common/types.ts'
3
+ * @import { Type } from '../types.ts'
3
4
  * @import { Equal } from '../../ts/types.ts'
4
- * @import { Ts } from '../ts/types.ts'
5
+ * @import { Ts, Unknown } from '../ts/types.ts'
5
6
  * @import { Unknown as DjsUnknown } from '../../../djs/types.ts'
6
7
  * @import { Assert } from '../../../asserts/types.ts'
7
8
  */
8
9
 
9
10
  import { validate } from './module.f.mjs'
11
+ import { parse } from '../parse/module.f.mjs'
10
12
  import { boolean, number, string, bigint, unknown, array, record, or, option } from '../module.f.mjs'
11
- import { assert, assertEq } from '../../../asserts/module.f.mjs'
13
+ import { unwrap } from '../../result/module.f.mjs'
14
+ import { assert, assertEq, assertStructurallySame } from '../../../asserts/module.f.mjs'
12
15
 
13
16
  /** @type {(r: readonly [string, unknown]) => void} */
14
17
  const assertOk = ([k]) => { assertEq(k, 'ok', 'expected ok') }
@@ -21,13 +24,115 @@ const assertErrorPath = expected =>
21
24
  r => {
22
25
  assert(r[0] === 'error', 'expected error')
23
26
  const e = /** @type {ValidationError} */ (r[1])
24
- if (e.path.length !== expected.length) { throw `path length ${e.path.length} != ${expected.length}` }
25
- for (let i = 0; i < expected.length; i++) {
26
- if (e.path[i] !== expected[i]) { throw `path[${i}] ${e.path[i]} != ${expected[i]}` }
27
- }
27
+ assertStructurallySame(e.path, expected, 'unexpected error path')
28
28
  }
29
29
 
30
+ /** Both readers with their payload type erased, so a table can hold rows of mixed schemas. */
31
+
32
+ /** @type {(t: Type) => ValidateE} */
33
+ const v = t => /** @type {any} */ (validate(t))
34
+
35
+ /** @type {(t: Type) => ValidateE} */
36
+ const p = t => /** @type {any} */ (parse(t))
37
+
30
38
  export const proof = {
39
+ // ── the three properties this module exists for ──────────────────────────
40
+ //
41
+ // `parse` rebuilds: it materializes a declared-but-absent member as
42
+ // `undefined`, drops an undeclared one, and returns a new object. For a
43
+ // caller whose value is a document whose bytes are its identity — a
44
+ // content-addressed store, a signed payload — each of those is a
45
+ // different document. `validate` answers the same question about the
46
+ // value it was handed and hands it back.
47
+ verbatim: {
48
+ // An absent optional member stays absent. `'b' in out` is the
49
+ // assertion, not `out.b === undefined`: `parse` satisfies the latter.
50
+ absentOptionalStaysAbsent: () => {
51
+ const schema = { a: number, b: option(string) }
52
+ const input = { a: 1 }
53
+ const out = unwrap(validate(schema)(input))
54
+ assert(!('b' in out), 'an absent optional member must stay absent')
55
+ // The contrast that motivates the module.
56
+ assert('b' in unwrap(parse(schema)(input)), 'parse materializes it')
57
+ },
58
+ // An undeclared member survives. `parse` accepts it too — structs and
59
+ // tuples are open — but does not carry it into what it builds.
60
+ undeclaredMemberSurvives: () => {
61
+ const schema = { a: number }
62
+ const struct = { a: 1, b: 'extra' }
63
+ assertStructurallySame(unwrap(validate(schema)(struct)), { a: 1, b: 'extra' })
64
+ assert(!('b' in unwrap(parse(schema)(struct))), 'parse drops it')
65
+ // The same on the other kind: a longer array keeps its tail.
66
+ const tuple = [1, 'extra']
67
+ assertStructurallySame(unwrap(validate([number])(tuple)), [1, 'extra'])
68
+ },
69
+ // On success the result *is* the argument. This is the property the
70
+ // other two follow from, and the mirror of `../parse/proof.f.mjs`'s
71
+ // `freshArray` / `freshRecord`.
72
+ referenceIdentity: () => {
73
+ /** @type {(t: Type, value: Unknown) => void} */
74
+ const same = (t, value) => {
75
+ const r = v(t)(value)
76
+ assert(r[0] === 'ok', 'expected ok')
77
+ assert(Object.is(r[1], value), 'expected the original value')
78
+ }
79
+ const arr = [1, 2, 3]
80
+ same(array(number), arr)
81
+ same([number, number, number], arr)
82
+ same(unknown, arr)
83
+ const obj = { a: 1, b: 2 }
84
+ same(record(number), obj)
85
+ same({ a: number }, obj)
86
+ same(or(string, record(number)), obj)
87
+ const nested = { xs: [{ a: 1 }] }
88
+ same({ xs: array({ a: number }) }, nested)
89
+ assert(Object.is(unwrap(validate({ xs: array({ a: number }) })(nested)).xs, nested.xs),
90
+ 'nested containers are not rebuilt either')
91
+ },
92
+ },
93
+ // 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.
96
+ 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
+ for (const [t, value] of rows) {
127
+ const rv = v(t)(value)
128
+ const rp = p(t)(value)
129
+ assertEq(rv[0], rp[0], 'validate and parse must agree on acceptance')
130
+ if (rv[0] === 'error') {
131
+ assert(rp[0] === 'error', 'expected error')
132
+ assertStructurallySame(rv[1], rp[1], 'the two readers report the same error')
133
+ }
134
+ }
135
+ },
31
136
  boolean: {
32
137
  ok: () => {
33
138
  /** @typedef {Assert<Equal<Ts<typeof boolean>, boolean>>} _RoundTrip */
@@ -83,98 +188,99 @@ export const proof = {
83
188
  },
84
189
  const: {
85
190
  null: {
86
- ok: () => {
87
- /** @typedef {Assert<Equal<Ts<null>, null>>} _RoundTrip */
88
- assertOk(validate(null)(null))
89
- },
191
+ ok: () => assertOk(validate(null)(null)),
90
192
  error: () => {
91
193
  assertError(validate(null)(undefined))
92
194
  assertError(validate(null)(0))
93
195
  },
94
196
  },
95
197
  undefined: {
96
- ok: () => {
97
- /** @typedef {Assert<Equal<Ts<undefined>, undefined>>} _RoundTrip */
98
- assertOk(validate(undefined)(undefined))
99
- },
198
+ ok: () => assertOk(validate(undefined)(undefined)),
100
199
  error: () => assertError(validate(undefined)(null)),
101
200
  },
102
201
  number: {
103
- ok: () => {
104
- /** @typedef {Assert<Equal<Ts<42>, 42>>} _RoundTrip */
105
- assertOk(validate(/** @type {const} */ (42))(42))
106
- },
202
+ ok: () => assertOk(validate(/** @type {const} */ (42))(42)),
107
203
  error: () => assertError(validate(/** @type {const} */ (42))(43)),
108
204
  },
109
205
  nan: {
110
- ok: () => assertOk(validate(/** @type {number} */ (NaN))(NaN)),
206
+ ok: () => assertOk(validate(NaN)(NaN)),
111
207
  error: () => {
112
- assertError(validate(/** @type {number} */ (NaN))(0))
208
+ assertError(validate(NaN)(0))
113
209
  assertError(validate(/** @type {const} */ (0))(NaN))
114
210
  assertError(validate(/** @type {const} */ (42))(NaN))
115
211
  },
116
212
  },
117
213
  infinity: {
118
214
  ok: () => {
119
- assertOk(validate(/** @type {number} */ (Infinity))(Infinity))
120
- assertOk(validate(/** @type {number} */ (-Infinity))(-Infinity))
215
+ assertOk(validate(Infinity)(Infinity))
216
+ assertOk(validate(-Infinity)(-Infinity))
121
217
  },
122
218
  error: () => {
123
- assertError(validate(/** @type {number} */ (Infinity))(-Infinity))
124
- assertError(validate(/** @type {number} */ (Infinity))(0))
219
+ assertError(validate(Infinity)(-Infinity))
220
+ assertError(validate(Infinity)(0))
125
221
  },
126
222
  },
127
223
  signedZero: {
128
224
  // `Object.is` distinguishes +0 and -0; `===` treats them equal.
129
225
  distinct: () => {
130
226
  assertError(validate(/** @type {const} */ (0))(-0))
131
- assertError(validate(/** @type {number} */ (-0))(0))
227
+ assertError(validate(-0)(0))
132
228
  },
133
229
  self: () => {
134
230
  assertOk(validate(/** @type {const} */ (0))(0))
135
- assertOk(validate(/** @type {number} */ (-0))(-0))
231
+ assertOk(validate(-0)(-0))
136
232
  },
137
233
  },
138
234
  string: {
139
- ok: () => {
140
- /** @typedef {Assert<Equal<Ts<'hello'>, 'hello'>>} _RoundTrip */
141
- assertOk(validate(/** @type {const} */ ('hello'))('hello'))
142
- },
235
+ ok: () => assertOk(validate(/** @type {const} */ ('hello'))('hello')),
143
236
  error: () => assertError(validate(/** @type {const} */ ('hello'))('world')),
144
237
  },
145
238
  bigint: {
146
- ok: () => {
147
- /** @typedef {Assert<Equal<Ts<7n>, 7n>>} _RoundTrip */
148
- assertOk(validate(/** @type {const} */ (7n))(7n))
149
- },
239
+ ok: () => assertOk(validate(/** @type {const} */ (7n))(7n)),
150
240
  error: () => assertError(validate(/** @type {const} */ (7n))(8n)),
151
241
  },
152
242
  boolean: {
153
- ok: () => {
154
- /** @typedef {Assert<Equal<Ts<true>, true>>} _RoundTrip */
155
- assertOk(validate(/** @type {const} */ (true))(true))
156
- },
243
+ ok: () => assertOk(validate(/** @type {const} */ (true))(true)),
157
244
  error: () => assertError(validate(/** @type {const} */ (true))(false)),
158
245
  },
159
246
  tuple: {
160
247
  ok: () => {
161
248
  const t = /** @type {const} */ ([42, 'hello'])
162
- /** @typedef {Assert<Equal<Ts<typeof t>, readonly[42, 'hello']>>} _RoundTrip */
163
- assertOk(validate(t)([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: () => {
258
+ const long = [42, 1, 2, 3]
259
+ assert(Object.is(unwrap(validate(/** @type {const} */ ([42]))(long)), long),
260
+ 'the longer array comes back as it went in')
261
+ },
262
+ // An absent member reads as `undefined`, so a position is required
263
+ // exactly when its set excludes `undefined` — and nothing is
264
+ // filled in, so the array keeps its length.
265
+ shortArrayKeepsItsLength: () => {
266
+ const short = [42]
267
+ const out = unwrap(validate([number, option(string)])(short))
268
+ assert(Object.is(out, short), 'expected the original array')
269
+ assertEq(short.length, 1, 'no gap is filled')
164
270
  },
165
- extraItems: () => assertOk(validate(/** @type {const} */ ([42]))([42, 'extra'])),
271
+ empty: () => assertOk(validate(/** @type {const} */ ([]))([])),
166
272
  error: () => {
167
273
  assertError(validate(/** @type {const} */ ([42]))([99]))
168
274
  assertError(validate(/** @type {const} */ ([42]))({}))
275
+ // `42` excludes `undefined`, so position 0 is required.
276
+ assertError(validate(/** @type {const} */ ([42]))([]))
169
277
  },
170
278
  },
171
279
  struct: {
172
280
  ok: () => {
173
281
  const t = /** @type {const} */ ({ a: 42, b: 'hello' })
174
- /** @typedef {Assert<Equal<Ts<typeof t>, { readonly a: 42, readonly b: 'hello' }>>} _RoundTrip */
175
- assertOk(validate(t)({ a: 42, b: 'hello' }))
282
+ assertStructurallySame(unwrap(validate(t)({ a: 42, b: 'hello' })), { a: 42, b: 'hello' })
176
283
  },
177
- extraKeys: () => assertOk(validate(/** @type {const} */ ({ a: /** @type {const} */ (42) }))({ a: 42, b: 'extra' })),
178
284
  error: () => {
179
285
  assertError(validate(/** @type {const} */ ({ a: 42 }))({ a: 99 }))
180
286
  assertError(validate(/** @type {const} */ ({ a: 42 }))([]))
@@ -182,31 +288,30 @@ export const proof = {
182
288
  },
183
289
  },
184
290
  array: {
185
- empty: () => assertOk(validate(array(number))([])),
186
- ok: () => {
187
- const t = array(number)
188
- /** @typedef {Assert<Equal<Ts<typeof t>, readonly number[]>>} _RoundTrip */
189
- assertOk(validate(array(number))([1, 2, 3]))
291
+ empty: () => {
292
+ const input = /** @type {readonly number[]} */ ([])
293
+ assert(Object.is(unwrap(validate(array(number))(input)), input), 'expected the original array')
190
294
  },
295
+ ok: () => assertStructurallySame(unwrap(validate(array(number))([1, 2, 3])), [1, 2, 3]),
191
296
  error: () => {
192
297
  assertError(validate(array(number))([1, 'two', 3]))
193
298
  assertError(validate(array(number))({}))
194
299
  assertError(validate(array(number))(null))
195
300
  },
196
301
  nested: () => {
197
- const t = array(array(boolean))
198
- /** @typedef {Assert<Equal<Ts<typeof t>, readonly (readonly boolean[])[]>>} _RoundTrip */
199
302
  assertOk(validate(array(array(boolean)))([[true, false], [false]]))
200
303
  assertError(validate(array(array(boolean)))([[true, 42]]))
201
304
  },
202
305
  },
203
306
  record: {
204
- empty: () => assertOk(validate(record(number))({})),
205
- ok: () => {
206
- const t = record(string)
207
- /** @typedef {Assert<Equal<Ts<typeof t>, { readonly[K in string]?: string }>>} _RoundTrip */
208
- assertOk(validate(t)({ a: 'hello', b: 'world' }))
307
+ empty: () => {
308
+ const input = /** @type {{ readonly[K in string]?: number }} */ ({})
309
+ assert(Object.is(unwrap(validate(record(number))(input)), input), 'expected the original record')
209
310
  },
311
+ ok: () => assertStructurallySame(
312
+ unwrap(validate(record(string))({ a: 'hello', b: 'world' })),
313
+ { a: 'hello', b: 'world' },
314
+ ),
210
315
  error: () => {
211
316
  assertError(validate(record(number))({ a: 1, b: 'two' }))
212
317
  assertError(validate(record(number))(null))
@@ -216,7 +321,6 @@ export const proof = {
216
321
  constThunk: {
217
322
  primitive: () => {
218
323
  const t = () => /** @type {const} */ (['const', 7n])
219
- /** @typedef {Assert<Equal<Ts<typeof t>, 7n>>} _RoundTrip */
220
324
  assertOk(validate(t)(7n))
221
325
  assertError(validate(t)(8n))
222
326
  },
@@ -224,8 +328,7 @@ export const proof = {
224
328
  or: {
225
329
  consts: {
226
330
  ok: () => {
227
- const t = or(.../** @type {const} */ ([false,42, 'hello']))
228
- /** @typedef {Assert<Equal<Ts<typeof t>, false | 42 | 'hello'>>} _RoundTrip */
331
+ const t = or(.../** @type {const} */ ([false, 42, 'hello']))
229
332
  assertOk(validate(t)(false))
230
333
  assertOk(validate(t)(42))
231
334
  assertOk(validate(t)('hello'))
@@ -241,7 +344,6 @@ export const proof = {
241
344
  thunks: {
242
345
  ok: () => {
243
346
  const t = or(number, string)
244
- /** @typedef {Assert<Equal<Ts<typeof t>, number | string>>} _RoundTrip */
245
347
  assertOk(validate(t)(42))
246
348
  assertOk(validate(t)('hello'))
247
349
  },
@@ -251,24 +353,20 @@ export const proof = {
251
353
  assertError(validate(t)(null))
252
354
  },
253
355
  },
254
- mixed: {
255
- ok: () => {
256
- const t = or(/** @type {const} */ (42), string)
257
- /** @typedef {Assert<Equal<Ts<typeof t>, 42 | string>>} _RoundTrip */
258
- assertOk(validate(t)(42))
259
- assertOk(validate(t)('hello'))
260
- },
261
- error: () => {
262
- const t = or(/** @type {const} */ (42), string)
263
- assertError(validate(t)(43))
264
- assertError(validate(t)(null))
265
- },
356
+ // The first matching variant wins, and it returns the value itself —
357
+ // so, unlike `parse`, which variant matched is not observable in the
358
+ // result. `parse` here returns a length-1 array; `validate` returns
359
+ // the length-3 one it was given.
360
+ firstMatchWins: () => {
361
+ const t = or(/** @type {const} */ ([number]), array(number))
362
+ const input = [1, 2, 3]
363
+ assert(Object.is(unwrap(validate(t)(input)), input), 'expected the original array')
364
+ assertStructurallySame(unwrap(parse(t)(input)), [1])
266
365
  },
267
366
  },
268
367
  option: {
269
368
  ok: () => {
270
369
  const t = option(number)
271
- /** @typedef {Assert<Equal<Ts<typeof t>, number | undefined>>} _RoundTrip */
272
370
  assertOk(validate(t)(42))
273
371
  assertOk(validate(t)(undefined))
274
372
  },
@@ -281,11 +379,7 @@ export const proof = {
281
379
  path: {
282
380
  rootMismatch: () => assertErrorPath([])(validate(number)('not a number')),
283
381
  arrayIndex: () => assertErrorPath(['1'])(validate(array(number))([1, 'two', 3])),
284
- recordKey: () => {
285
- const r = validate(record(number))({ a: 1, b: 'two', c: 3 })
286
- // record iteration order matches Object.entries; 'b' is the failing key
287
- assertErrorPath(['b'])(r)
288
- },
382
+ recordKey: () => assertErrorPath(['b'])(validate(record(number))({ a: 1, b: 'two', c: 3 })),
289
383
  nestedArray: () => assertErrorPath(['0', '1'])(
290
384
  validate(array(array(number)))([[1, 'x'], [2, 3]])
291
385
  ),
@@ -297,13 +391,11 @@ export const proof = {
297
391
  ),
298
392
  deepStruct: () => {
299
393
  const schema = /** @type {const} */ ({ user: { name: string, age: number } })
300
- const r = validate(schema)({ user: { name: 'A', age: 'old' } })
301
- assertErrorPath(['user', 'age'])(r)
394
+ assertErrorPath(['user', 'age'])(validate(schema)({ user: { name: 'A', age: 'old' } }))
302
395
  },
303
396
  recursiveSchema: () => {
304
397
  /** @typedef {readonly _A[]} _A */
305
398
  const list = () => /** @type {const} */ (['array', list])
306
- // [[[42]]] — innermost element 42 is a number, not an array
307
399
  const r = validate(list)([/** @type {_A} */ (/** @type {unknown} */ ([[42]]))])
308
400
  assertErrorPath(['0', '0', '0'])(r)
309
401
  },
@@ -312,126 +404,23 @@ export const proof = {
312
404
  recursive: {
313
405
  arrayOfArrays: () => {
314
406
  /** @typedef {readonly _A[]} _A */
315
- // self-referential schema: an array whose elements are also arrays of the same type
316
407
  const list = () => /** @type {const} */ (['array', list])
317
- /** @typedef {Assert<Equal<_A, Ts<typeof list>>>} _RoundTripA */
318
- const v = validate(list)
319
- assertOk(v([]))
320
- assertOk(v([[], []]))
321
- assertOk(v([[[], []], []]))
322
- assertError(v([42]))
323
- assertError(v(null))
408
+ /** @typedef {Assert<Equal<_A, Ts<typeof list>>>} _ListRoundTrip */
409
+ const x = validate(list)
410
+ assertOk(x([]))
411
+ assertOk(x([[], []]))
412
+ assertOk(x([[[], []], []]))
413
+ assertError(x([42]))
414
+ assertError(x(null))
324
415
  },
325
416
  recordOfRecords: () => {
326
417
  const tree = () => /** @type {const} */ (['record', tree])
327
418
  /** @typedef {{ readonly[K in string]?: _A }} _A */
328
- /** @typedef {Assert<Equal<_A, Ts<typeof tree>>>} _RoundTrip */
329
- const v = validate(tree)
330
- assertOk(v({}))
331
- assertOk(v({ a: {}, b: { c: {} } }))
332
- assertError(v({ a: 42 }))
419
+ /** @typedef {Assert<Equal<_A, Ts<typeof tree>>>} _TreeRoundTrip */
420
+ const x = validate(tree)
421
+ assertOk(x({}))
422
+ assertOk(x({ a: {}, b: { c: {} } }))
423
+ assertError(x({ a: 42 }))
333
424
  },
334
425
  },
335
- funcParam: () => {
336
- const paramSet0 = /** @type {const} */ (['hello', bigint])
337
- const paramSet1 = /** @type {const} */ (['goodbye', string, 43])
338
-
339
- const paramSet01 = or(paramSet0, paramSet1)
340
-
341
- /** @typedef {Ts<typeof paramSet0>} _Param0 */
342
- /** @typedef {Ts<typeof paramSet1>} _Param1 */
343
- /** @typedef {Ts<typeof paramSet01>} _Param01 */
344
-
345
- const v0 = validate(paramSet0)
346
- const v1 = validate(paramSet1)
347
-
348
- /** @template T @typedef {(...args: _Param0) => T} _F0 */
349
- /** @template T @typedef {(...args: _Param1) => T} _F1 */
350
-
351
- /**
352
- * @template T
353
- * @param {_F0<T>} f0
354
- * @param {_F1<T>} f1
355
- * @returns {(...args: _Param01) => T}
356
- */
357
- const func = (f0, f1) => (...args) => {
358
- {
359
- const [t, r] = v0(args)
360
- if (t === 'ok') {
361
- return f0(...r)
362
- }
363
- }
364
- {
365
- const [t, r] = v1(args)
366
- if (t === 'ok') {
367
- return f1(...r)
368
- }
369
- }
370
- throw 'unreachable: args did not match any parameter set'
371
- }
372
-
373
- /** @type {(a: 'hello', b: bigint) => number} */
374
- const f0 = (a, b) => 42
375
- /** @type {(a: 'goodbye', b: string, c: 43) => number} */
376
- const f1 = (a, b, c) => 13
377
-
378
- /** @type {(...args: _Param01) => number} */
379
- const x = func(f0, f1)
380
-
381
- return () => {
382
- assertEq(x('hello', 42n), 42)
383
- assertEq(x('goodbye', 'world', 43), 13)
384
- }
385
- },
386
- funcObj: () => {
387
- const param0 = { a: 'hello', b: bigint }
388
- const param1 = { a: 'goodbye', b: string, c: 43 }
389
-
390
- const param01 = or(param0, param1)
391
-
392
- /** @typedef {Ts<typeof param0>} _Param0 */
393
- /** @typedef {Ts<typeof param1>} _Param1 */
394
- /** @typedef {Ts<typeof param01>} _Param01 */
395
-
396
- const v0 = validate(param0)
397
- const v1 = validate(param1)
398
-
399
- /** @template T @typedef {(args: _Param0) => T} _F0 */
400
- /** @template T @typedef {(args: _Param1) => T} _F1 */
401
-
402
- /**
403
- * @template T
404
- * @param {_F0<T>} f0
405
- * @param {_F1<T>} f1
406
- * @returns {(args: _Param01) => T}
407
- */
408
- const func = (f0, f1) => args => {
409
- {
410
- const [t, r] = v0(args)
411
- if (t === 'ok') {
412
- return f0(r)
413
- }
414
- }
415
- {
416
- const [t, r] = v1(args)
417
- if (t === 'ok') {
418
- return f1(r)
419
- }
420
- }
421
- throw 'unreachable: args did not match any parameter set'
422
- }
423
-
424
- /** @type {(args: _Param0) => number} */
425
- const f0 = args => Number(args.b) + args.a.length
426
- /** @type {(args: _Param1) => number} */
427
- const f1 = args => args.c
428
-
429
- /** @type {(args: _Param01) => number} */
430
- const x = func(f0, f1)
431
-
432
- return () => {
433
- assertEq(x({ a: 'hello', b: 42n }), 47)
434
- assertEq(x({ a: 'goodbye', b: 'world', c: 43 }), 43)
435
- }
436
- }
437
426
  }
@@ -52,3 +52,21 @@ export declare const has: <T>(
52
52
  * @returns {(value: T) => (set: SortedSet<T>) => boolean}
53
53
  */
54
54
  cmp: Cmp<T>) => (value: T) => (set: SortedSet<T>) => boolean;
55
+ /**
56
+ * A canonical key for a set of strings: equal sets always produce the same
57
+ * key, and unequal sets never produce the same one. Useful wherever a set has
58
+ * to index a `StringMap` — the subset construction in `fjs/fsm`, say.
59
+ *
60
+ * Each element is written length-prefixed (`3:abc`) and the parts are
61
+ * concatenated. Joining on a separator would **not** be canonical: elements
62
+ * are arbitrary strings, so `['a,b']` and `['a', 'b']` would both produce
63
+ * `a,b` and two different sets would share one key. A length prefix is
64
+ * readable back — take the digits, the `:`, then exactly that many code
65
+ * units — so the encoding is injective without an escaping table.
66
+ *
67
+ * The key is canonical for the *set* only because `SortedSet` is sorted:
68
+ * element order is fixed by the set's own invariant, not by this function.
69
+ *
70
+ * @type {(set: SortedSet<string>) => string}
71
+ */
72
+ export declare const toKey: (set: SortedSet<string>) => string;
@@ -56,3 +56,25 @@ export const has =
56
56
  * @returns {(value: T) => (set: SortedSet<T>) => boolean}
57
57
  */
58
58
  cmp => value => set => find(cmp)(value)(set) === value
59
+
60
+ /** @type {(s: string) => string} */
61
+ const lengthPrefixed = s => `${s.length}:${s}`
62
+
63
+ /**
64
+ * A canonical key for a set of strings: equal sets always produce the same
65
+ * key, and unequal sets never produce the same one. Useful wherever a set has
66
+ * to index a `StringMap` — the subset construction in `fjs/fsm`, say.
67
+ *
68
+ * Each element is written length-prefixed (`3:abc`) and the parts are
69
+ * concatenated. Joining on a separator would **not** be canonical: elements
70
+ * are arbitrary strings, so `['a,b']` and `['a', 'b']` would both produce
71
+ * `a,b` and two different sets would share one key. A length prefix is
72
+ * readable back — take the digits, the `:`, then exactly that many code
73
+ * units — so the encoding is injective without an escaping table.
74
+ *
75
+ * The key is canonical for the *set* only because `SortedSet` is sorted:
76
+ * element order is fixed by the set's own invariant, not by this function.
77
+ *
78
+ * @type {(set: SortedSet<string>) => string}
79
+ */
80
+ export const toKey = set => set.map(lengthPrefixed).join('')
@@ -2,6 +2,7 @@
2
2
  * @import { Unknown } from '../../media/json/types.ts'
3
3
  */
4
4
  export declare const proof: {
5
+ toKey: (() => void)[];
5
6
  example: () => void;
6
7
  union: (() => void)[];
7
8
  intersect: (() => void)[];
@@ -2,7 +2,7 @@
2
2
  * @import { Unknown } from '../../media/json/types.ts'
3
3
  */
4
4
 
5
- import { has, intersect, union } from './module.f.mjs'
5
+ import { has, intersect, toKey, union } from './module.f.mjs'
6
6
  import { stringify } from '../../media/json/module.f.mjs'
7
7
  import { sort } from '../object/module.f.mjs'
8
8
  import { toArray, countdown, length } from '../list/module.f.mjs'
@@ -16,6 +16,21 @@ const str = a => stringify(sort)(a)
16
16
  const reverseCmp = flip(cmp)
17
17
 
18
18
  export const proof = {
19
+ toKey: [
20
+ () => assertEq(toKey([]), ''),
21
+ () => assertEq(toKey(['']), '0:'),
22
+ () => assertEq(toKey(['a']), '1:a'),
23
+ () => assertEq(toKey(['a', 'b']), '1:a1:b'),
24
+ // The reason the key is length-prefixed rather than separator-joined:
25
+ // these two sets are different and must not share a key, but any
26
+ // fixed separator that can occur in an element collapses them.
27
+ () => assert(toKey(['a,b']) !== toKey(['a', 'b']), 'comma'),
28
+ () => assert(toKey(['a:b']) !== toKey(['a', 'b']), 'colon'),
29
+ () => assert(toKey(['a\nb']) !== toKey(['a', 'b']), 'newline'),
30
+ // A prefix of one element must not be confused with a shorter one.
31
+ () => assert(toKey(['ab']) !== toKey(['a']), 'prefix'),
32
+ () => assert(toKey(['']) !== toKey([]), 'empty element vs empty set'),
33
+ ],
19
34
  example: () => {
20
35
  const cmp = (/** @type {number} */ a) => (/** @type {number} */ b) => a < b ? -1 : a > b ? 1 : 0
21
36
 
@@ -19,7 +19,7 @@ import type { List } from '../list/types.ts';
19
19
  *
20
20
  * Throws if the result would exceed `maxLength`. The bound is not precomputed:
21
21
  * `tryU8ListToVec` attempts the real conversion and reports `null` when it does
22
- * not fit (AGENTS.md §5.6).
22
+ * not fit (`DESIGN.md` §6).
23
23
  *
24
24
  * @type {(input: List<Uint8Array>) => Vec}
25
25
  */
@@ -29,7 +29,7 @@ const m = map(fromArrayLike)
29
29
  *
30
30
  * Throws if the result would exceed `maxLength`. The bound is not precomputed:
31
31
  * `tryU8ListToVec` attempts the real conversion and reports `null` when it does
32
- * not fit (AGENTS.md §5.6).
32
+ * not fit (`DESIGN.md` §6).
33
33
  *
34
34
  * @type {(input: List<Uint8Array>) => Vec}
35
35
  */