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.
- package/README.md +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/rtti/validate/module.f.d.mts +81 -19
- package/fjs/types/rtti/validate/module.f.mjs +107 -61
- package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
- package/fjs/types/rtti/validate/proof.f.mjs +186 -197
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- 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 {
|
|
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
|
-
|
|
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(
|
|
206
|
+
ok: () => assertOk(validate(NaN)(NaN)),
|
|
111
207
|
error: () => {
|
|
112
|
-
assertError(validate(
|
|
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(
|
|
120
|
-
assertOk(validate(
|
|
215
|
+
assertOk(validate(Infinity)(Infinity))
|
|
216
|
+
assertOk(validate(-Infinity)(-Infinity))
|
|
121
217
|
},
|
|
122
218
|
error: () => {
|
|
123
|
-
assertError(validate(
|
|
124
|
-
assertError(validate(
|
|
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(
|
|
227
|
+
assertError(validate(-0)(0))
|
|
132
228
|
},
|
|
133
229
|
self: () => {
|
|
134
230
|
assertOk(validate(/** @type {const} */ (0))(0))
|
|
135
|
-
assertOk(validate(
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: () =>
|
|
186
|
-
|
|
187
|
-
|
|
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: () =>
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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>>>}
|
|
318
|
-
const
|
|
319
|
-
assertOk(
|
|
320
|
-
assertOk(
|
|
321
|
-
assertOk(
|
|
322
|
-
assertError(
|
|
323
|
-
assertError(
|
|
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>>>}
|
|
329
|
-
const
|
|
330
|
-
assertOk(
|
|
331
|
-
assertOk(
|
|
332
|
-
assertError(
|
|
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,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 (
|
|
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 (
|
|
32
|
+
* not fit (`DESIGN.md` §6).
|
|
33
33
|
*
|
|
34
34
|
* @type {(input: List<Uint8Array>) => Vec}
|
|
35
35
|
*/
|