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
package/fjs/effects/types.d.ts
CHANGED
|
@@ -3,19 +3,102 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
import type { Ok, Error, Result } from '../types/result/types.ts';
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* A command name paired with the signature a runner implements it at.
|
|
9
|
+
*
|
|
10
|
+
* **The return type is a {@link Result}, and that is a rule rather than a
|
|
11
|
+
* convention every operation happens to follow.** A runner may decline any
|
|
12
|
+
* command it was not given a handler for — `partialMatch` answers
|
|
13
|
+
* `error(notImplemented(command))` through the command's own output — so an
|
|
14
|
+
* operation whose return admitted no error would be a hole in that mechanism:
|
|
15
|
+
* there would be nowhere to put the refusal. Every *host* operation already
|
|
16
|
+
* returned `OpResult<…>` or `IoResult<…>` when this constraint was added, and
|
|
17
|
+
* so did four of the six declared inside proofs — through a bare `Result`
|
|
18
|
+
* rather than either alias, since those two are node conveniences. The two in
|
|
19
|
+
* `./proof.f.mjs` returned a bare `number`, and the commit that added the rule
|
|
20
|
+
* rewrote them.
|
|
21
|
+
*
|
|
22
|
+
* It is also the latch the whole representation now rests on. An operation
|
|
23
|
+
* *cannot* be declared infallible, so every {@link Effect} built from one has a
|
|
24
|
+
* `Result` to carry — which is what lets {@link Effect} name its error channel
|
|
25
|
+
* rather than leaving it inside an opaque payload.
|
|
12
26
|
*/
|
|
13
|
-
export type
|
|
27
|
+
export type Operation = readonly [string, (..._: readonly never[]) => Result<unknown, unknown>];
|
|
14
28
|
/**
|
|
15
|
-
*
|
|
29
|
+
* The runner cannot dispatch this operation and has **not** started it.
|
|
30
|
+
*
|
|
31
|
+
* It is ordinary recoverable effect data rather than a fatal runner condition:
|
|
32
|
+
* the program receives control back and decides what an incompatible runner
|
|
33
|
+
* means for it — recover, choose a fallback operation, or treat it as fatal and
|
|
34
|
+
* panic itself (`throw`, e.g. via `unwrap`). Escalation belongs to the program,
|
|
35
|
+
* so the missing-handler path answers with `error(notImplemented)` rather than
|
|
36
|
+
* panicking on the program's behalf.
|
|
37
|
+
*
|
|
38
|
+
* **It identifies the operation by command name only.** An operation's payload
|
|
39
|
+
* may hold functions — `createServer`'s listener, `sandbox`'s thunk, `test`'s
|
|
40
|
+
* body — so carrying it would break the serializability the asserts in
|
|
41
|
+
* the asserts at the bottom of this file pin against the JSON data model.
|
|
42
|
+
*
|
|
43
|
+
* **It is not a security boundary.** A runner keeps its authority over
|
|
44
|
+
* execution through a separate, out-of-band mechanism: it may interrupt or
|
|
45
|
+
* terminate a program that is malicious, over budget, or violating host policy,
|
|
46
|
+
* and nothing in the error channel obliges it to hand control back. A
|
|
47
|
+
* capability the runner merely lacks is answered with this error; a refusal to
|
|
48
|
+
* continue is an interruption, never dressed up as `NotImplemented`.
|
|
49
|
+
*
|
|
50
|
+
* **It is why there is no infallible effect.** Every {@link Do} node is
|
|
51
|
+
* dispatched by a runner that may decline the command, so an effect that
|
|
52
|
+
* declared no error channel would be describing today's runner rather than the
|
|
53
|
+
* computation. That observation is what collapsed the representation into this
|
|
54
|
+
* one type — see {@link Effect}.
|
|
55
|
+
*/
|
|
56
|
+
export type NotImplemented = readonly ['notImplemented', string];
|
|
57
|
+
/**
|
|
58
|
+
* An `Effect<O, T, E>` is the raw value: a {@link Pure} thunk yielding
|
|
59
|
+
* `Result<T, E>`, or a {@link Do} node describing a command to perform. It is
|
|
60
|
+
* plain data — compose effects with the combinators in
|
|
61
|
+
* [`./module.f.mjs`](./module.f.mjs), which are eager wherever the head
|
|
62
|
+
* is `Pure`.
|
|
63
|
+
*
|
|
64
|
+
* **The error channel is part of the representation, not a wrapper over it.**
|
|
65
|
+
* This used to be two types: a `RawEffect<O, T>` with an opaque payload, and an
|
|
66
|
+
* `Effect<O, T, E>` alias for `RawEffect<O, Result<T, E>>`. The division was
|
|
67
|
+
* described as *composition* against *representation*, and it was defended on
|
|
68
|
+
* the grounds that the representation is generic over its payload by
|
|
69
|
+
* definition — the runners, {@link match}, `runPure`, `do_`. That is true and
|
|
70
|
+
* it is not a reason for two names: {@link Operation} requires a `Result`
|
|
71
|
+
* return, so the payload a runner is generic over is *always* `Result<T, E>`
|
|
72
|
+
* already. Naming its two halves costs nothing and lets every signature in the
|
|
73
|
+
* system say which half it means.
|
|
16
74
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
75
|
+
* **`E` defaults to {@link NotImplemented}**, the one error every operation can
|
|
76
|
+
* answer with, so the common case is written `Effect<Sandbox, T>`. An
|
|
77
|
+
* operation's own failures extend the channel — `Effect<ReadFile, Vec,
|
|
78
|
+
* IoChannel>`, that alias being the node standard of
|
|
79
|
+
* `NotImplemented | IoError`.
|
|
80
|
+
*
|
|
81
|
+
* **`Effect<O, T, never>` is a claim, not an absence.** It says this code
|
|
82
|
+
* absorbs its own failures *here* — an MCP handler turning one into a JSON-RPC
|
|
83
|
+
* error response, a decoder turning one into `null`, a test body turning one
|
|
84
|
+
* into a panic. A reader who thinks the absorption is wrong has something to
|
|
85
|
+
* point at, which is what the opaque payload could not offer.
|
|
86
|
+
*
|
|
87
|
+
* The channel is where **short-circuiting** lives, not specifically where a
|
|
88
|
+
* runner's refusal lives. A parse failure, a domain verdict, or a non-zero exit
|
|
89
|
+
* code belongs in it for the same reason {@link NotImplemented} does: `step`
|
|
90
|
+
* should stop the chain and carry it out.
|
|
91
|
+
*
|
|
92
|
+
* It widens and does not narrow, in both channels and in `O` — the asserts in
|
|
93
|
+
* the asserts at the bottom of this file pin each direction, so an unhandled error
|
|
94
|
+
* type is a compile error rather than a value nobody looked at.
|
|
95
|
+
*/
|
|
96
|
+
export type Effect<O extends Operation, T, E = NotImplemented> = Pure<T, E> | Do<O, T, E>;
|
|
97
|
+
/**
|
|
98
|
+
* A pure effect: an *already-computed* `Result<T, E>` behind a thunk.
|
|
99
|
+
*
|
|
100
|
+
* The thunk is a **discriminator, not a suspension**. {@link Effect} is a union
|
|
101
|
+
* with no tag field, so telling its two cases apart needs a runtime test, and
|
|
19
102
|
* `typeof e === 'function'` is it — wrapping the value in a function is what
|
|
20
103
|
* makes that test work. Deferral is not what the thunk is for. A `Pure` never
|
|
21
104
|
* holds work that has yet to happen; everything that *does* something is a
|
|
@@ -26,19 +109,19 @@ export type Effect<O extends Operation, T> = Pure<T> | Do<O, T>;
|
|
|
26
109
|
* - **The thunk must be pure and total.** Work hidden behind it is an effect
|
|
27
110
|
* that no runner ever sees and no `OperationMap` can interpret or mock.
|
|
28
111
|
* - **It may be called more than once.** Nothing memoizes it. The same effect
|
|
29
|
-
* can be decoded repeatedly
|
|
30
|
-
*
|
|
112
|
+
* can be decoded repeatedly, and under the first rule that costs nothing and
|
|
113
|
+
* changes nothing.
|
|
31
114
|
*
|
|
32
115
|
* A `lazy` constructor (`<T>(t: () => T): Effect<never, T> => t`) once existed
|
|
33
116
|
* to advertise the thunk as a suspension. It was the identity function, and it
|
|
34
117
|
* promised a deferral this representation does not keep; it has been removed.
|
|
35
118
|
* Reintroducing it would reintroduce the contradiction, not fix one.
|
|
36
119
|
*/
|
|
37
|
-
export type Pure<T> = () => T
|
|
120
|
+
export type Pure<T, E> = () => Result<T, E>;
|
|
38
121
|
export type Pr<O extends Operation, K extends O[0]> = O extends readonly [K, (...args: infer P) => infer R] ? readonly [P, R] : never;
|
|
39
122
|
/**
|
|
40
|
-
* A
|
|
41
|
-
* the effect.
|
|
123
|
+
* A {@link Do} node's continuation: given the command's output, produce the
|
|
124
|
+
* rest of the effect.
|
|
42
125
|
*
|
|
43
126
|
* The `out O` annotation asserts a covariance TypeScript cannot derive through
|
|
44
127
|
* the conditional `Pr` type: the command's output sits in the *contravariant*
|
|
@@ -55,7 +138,7 @@ export type Pr<O extends Operation, K extends O[0]> = O extends readonly [K, (..
|
|
|
55
138
|
* the continuation representation must re-check this argument before keeping the
|
|
56
139
|
* annotation.
|
|
57
140
|
*/
|
|
58
|
-
export type Cont<out O extends Operation, T> = (_: Pr<O, O[0]>[1]) => Effect<O, T>;
|
|
141
|
+
export type Cont<out O extends Operation, T, E> = (_: Pr<O, O[0]>[1]) => Effect<O, T, E>;
|
|
59
142
|
/**
|
|
60
143
|
* A `Do` node: the command to perform, its payload, and the continuation to
|
|
61
144
|
* resume with the command's output. Its runtime value is exactly this record,
|
|
@@ -64,7 +147,7 @@ export type Cont<out O extends Operation, T> = (_: Pr<O, O[0]>[1]) => Effect<O,
|
|
|
64
147
|
*
|
|
65
148
|
* It must be an object rather than a tuple, and that is not a style choice:
|
|
66
149
|
* only object / function / mapped-type aliases may carry a variance annotation
|
|
67
|
-
* (`TS2637` forbids `out` on a tuple), and the
|
|
150
|
+
* (`TS2637` forbids `out` on a tuple), and the {@link Effect} union must be
|
|
68
151
|
* covariant in `O` end to end. `command` and `payload` are indexed/conditional
|
|
69
152
|
* types over `O` that TypeScript will not widen generically on their own —
|
|
70
153
|
* annotating only {@link Cont} is not enough — so the whole node carries
|
|
@@ -78,27 +161,25 @@ export type Cont<out O extends Operation, T> = (_: Pr<O, O[0]>[1]) => Effect<O,
|
|
|
78
161
|
* tuple's price without being a tuple. Named fields make the node
|
|
79
162
|
* self-describing at every read and leave no layout to memorize.
|
|
80
163
|
*/
|
|
81
|
-
export type Do<out O extends Operation, T> = {
|
|
164
|
+
export type Do<out O extends Operation, T, E> = {
|
|
82
165
|
readonly command: O[0];
|
|
83
166
|
readonly payload: Pr<O, O[0]>[0];
|
|
84
|
-
readonly continuation: Cont<O, T>;
|
|
167
|
+
readonly continuation: Cont<O, T, E>;
|
|
85
168
|
};
|
|
169
|
+
export type Param<O extends Operation> = F<O>[0];
|
|
170
|
+
export type Return<O extends Operation> = F<O>[1];
|
|
86
171
|
/**
|
|
87
|
-
*
|
|
88
|
-
* far, newest first. `History<O, readonly[C, B, A]>` is three links deep, with
|
|
89
|
-
* `A` bound earliest.
|
|
172
|
+
* The success half of a {@link Result} type, and {@link ErrOf} the error half.
|
|
90
173
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* `
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* Heterogeneous by design: each element has its own type, so this is not a
|
|
97
|
-
* `List` and nothing that folds or maps a list applies to it.
|
|
174
|
+
* These read an operation's *declared* return apart so that {@link Func} can
|
|
175
|
+
* state the effect `do_` builds from it. {@link Operation} guarantees the
|
|
176
|
+
* return is a `Result`, so neither is ever the `never` its fallback branch
|
|
177
|
+
* names; and both distribute over the union, which is what makes
|
|
178
|
+
* `Result<T, E>` decompose back into exactly `T` and `E`.
|
|
98
179
|
*/
|
|
99
|
-
export type
|
|
100
|
-
|
|
101
|
-
export type
|
|
180
|
+
export type OkOf<R> = R extends Ok<infer T> ? T : never;
|
|
181
|
+
/** The error half of a {@link Result} type — see {@link OkOf}. */
|
|
182
|
+
export type ErrOf<R> = R extends Error<infer E> ? E : never;
|
|
102
183
|
/**
|
|
103
184
|
* An operation map whose entries take a command's payload and return some
|
|
104
185
|
* output `R`. Generalizes `ToAsyncOperationMap` (`R = Promise<…>`) and the
|
|
@@ -107,9 +188,56 @@ export type Return<O extends Operation> = F<O>[1];
|
|
|
107
188
|
export type OperationMap<O extends Operation, R> = {
|
|
108
189
|
readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => R;
|
|
109
190
|
};
|
|
110
|
-
|
|
191
|
+
/**
|
|
192
|
+
* An {@link OperationMap} a runner may leave holes in: every handler it *does*
|
|
193
|
+
* provide has the same type, and any of them may be absent.
|
|
194
|
+
*
|
|
195
|
+
* Partiality is opt-in, and deliberately not the default. A total map means a
|
|
196
|
+
* runner that forgets a handler is a compile error, which is what should happen
|
|
197
|
+
* to the Node runner; this type is for a runner that is *meant* to lack
|
|
198
|
+
* operations — a virtual filesystem with no subprocesses, a mock that answers
|
|
199
|
+
* the three commands its proof issues. An absent handler here is an answer
|
|
200
|
+
* (`error(notImplemented)`), not an oversight.
|
|
201
|
+
*/
|
|
202
|
+
export type PartialOperationMap<O extends Operation, R> = {
|
|
203
|
+
readonly [K in O[0]]?: (...payload: Pr<O, K>[0]) => R;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* The runtime witness of `O`'s command set.
|
|
207
|
+
*
|
|
208
|
+
* A {@link PartialOperationMap} cannot say, at runtime, whether a command it
|
|
209
|
+
* has no handler for is one the operation set declares — types are erased, so
|
|
210
|
+
* an omitted `readFile` and a garbled `readFilee` reach the interpreter as the
|
|
211
|
+
* same missing lookup. They are not the same thing: the first is a capability
|
|
212
|
+
* this runner lacks and a program may recover from, the second is a `Do` node
|
|
213
|
+
* whose `command` was never the one its type claimed. Telling them apart needs
|
|
214
|
+
* `O`'s commands as data.
|
|
215
|
+
*
|
|
216
|
+
* Declare it as a record rather than an array — a `Record<O[0], null>` is
|
|
217
|
+
* checked for *completeness*, so a command added to `O` and forgotten here is a
|
|
218
|
+
* compile error, whereas an array literal only has its members checked and
|
|
219
|
+
* drifts silently.
|
|
220
|
+
*/
|
|
221
|
+
export type CommandSet<O extends Operation> = Readonly<Record<O[0], null>>;
|
|
222
|
+
/** `O`'s commands, in the form {@link match} tests membership against. */
|
|
223
|
+
export type Commands<O extends Operation> = readonly O[0][];
|
|
224
|
+
/**
|
|
225
|
+
* What one decoding step of an effect yields: the finished `Result`, or the
|
|
226
|
+
* operation's output `R` paired with the continuation to resume with.
|
|
227
|
+
*
|
|
228
|
+
* The `done` payload is the whole `Result`, not its `ok` half. An interpreter
|
|
229
|
+
* is the one reader that must not short-circuit — a runner hands a failed
|
|
230
|
+
* command's `error` back *through* the continuation, so the two channels only
|
|
231
|
+
* separate above this layer.
|
|
232
|
+
*/
|
|
233
|
+
export type MatchResult<O extends Operation, T, E, R> = readonly ['done', Result<T, E>] | readonly ['cont', R, Cont<O, T, E>];
|
|
111
234
|
export type ToAsyncOperationMap<O extends Operation> = {
|
|
112
235
|
readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => Promise<Pr<O, K>[1]>;
|
|
113
236
|
};
|
|
114
237
|
export type F<O extends Operation> = Pr<O, O[0]>;
|
|
115
|
-
|
|
238
|
+
/**
|
|
239
|
+
* The effect-returning function `do_` builds for a single operation: its
|
|
240
|
+
* payload in, an {@link Effect} whose two channels are the halves of the
|
|
241
|
+
* operation's declared `Result` out.
|
|
242
|
+
*/
|
|
243
|
+
export type Func<O extends Operation> = (..._: Param<O>) => Effect<O, OkOf<Return<O>>, ErrOf<Return<O>>>;
|
|
@@ -10,15 +10,17 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*
|
|
13
|
-
* @import {
|
|
13
|
+
* @import { Operation } from '../effects/types.ts'
|
|
14
|
+
* @import { Effect, NotImplemented } from '../effects/types.ts'
|
|
14
15
|
* @import { LoadModuleOperations, ModuleMap } from '../dev/types.ts'
|
|
15
16
|
* @import { TestFn, TestEntry, TestSet, Path, Reporter, _TestState, _TestAndPath } from './types.ts'
|
|
16
|
-
* @import { All, Await, Env, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write, WriteConsoles } from '../effects/node/types.ts'
|
|
17
|
+
* @import { All, Await, Env, IoChannel, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write, WriteConsoles } from '../effects/node/types.ts'
|
|
17
18
|
*/
|
|
18
|
-
import type {
|
|
19
|
+
import type { Operation } from '../effects/types.ts';
|
|
20
|
+
import type { Effect, NotImplemented } from '../effects/types.ts';
|
|
19
21
|
import type { LoadModuleOperations, ModuleMap } from '../dev/types.ts';
|
|
20
22
|
import type { TestEntry, TestSet, Path, Reporter, _TestAndPath } from './types.ts';
|
|
21
|
-
import type { All, Await, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write } from '../effects/node/types.ts';
|
|
23
|
+
import type { All, Await, IoChannel, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write } from '../effects/node/types.ts';
|
|
22
24
|
/**
|
|
23
25
|
* Converts an arbitrary JS value into a `TestSet`.
|
|
24
26
|
*
|
|
@@ -54,9 +56,9 @@ export declare const collectTests: (path: Path, throws: boolean, v: unknown) =>
|
|
|
54
56
|
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
55
57
|
* be declared upfront and the framework drives execution.
|
|
56
58
|
*
|
|
57
|
-
* @type {(ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void>}
|
|
59
|
+
* @type {(ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void, NotImplemented>}
|
|
58
60
|
*/
|
|
59
|
-
export declare const registerModule: (ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void>;
|
|
61
|
+
export declare const registerModule: (ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void, NotImplemented>;
|
|
60
62
|
/**
|
|
61
63
|
* Runs all test modules in `moduleMap` whose names pass `isTest`, accumulates
|
|
62
64
|
* pass/fail/time via `reporter`, and returns an exit code (0 = all passed,
|
|
@@ -64,19 +66,24 @@ export declare const registerModule: (ctx: TestContext, k: string, v: unknown, s
|
|
|
64
66
|
*
|
|
65
67
|
* @template {Operation} O
|
|
66
68
|
* @param {Reporter<O>} reporter
|
|
67
|
-
* @returns {(moduleMap: ModuleMap) => Effect<O | All, number>}
|
|
69
|
+
* @returns {(moduleMap: ModuleMap) => Effect<O | All, number, IoChannel>}
|
|
68
70
|
*/
|
|
69
|
-
export declare const runModuleMap: <O extends Operation>(reporter: Reporter<O>) => (moduleMap: ModuleMap) => Effect<O | All, number>;
|
|
71
|
+
export declare const runModuleMap: <O extends Operation>(reporter: Reporter<O>) => (moduleMap: ModuleMap) => Effect<O | All, number, IoChannel>;
|
|
70
72
|
/**
|
|
71
73
|
* Discovers all test modules via `loadModuleMap`, then runs them through
|
|
72
74
|
* `runModuleMap`. The composed effect is a `NodeProgram` entry point for the
|
|
73
75
|
* `fjs t` test runner.
|
|
74
76
|
*
|
|
77
|
+
* The chain leaves the error channel here, because this is where a `Program` ends
|
|
78
|
+
* and a `Program`'s answer is an exit code rather than a `Result`. A run that
|
|
79
|
+
* could not report its own results is a failed run, so it exits `1` with the
|
|
80
|
+
* reason on `stderr` instead of unwinding as a panic.
|
|
81
|
+
*
|
|
75
82
|
* @template {Operation} O
|
|
76
83
|
* @param {Reporter<O>} reporter
|
|
77
|
-
* @returns {Program<O | All | LoadModuleOperations>}
|
|
84
|
+
* @returns {Program<O | All | LoadModuleOperations | Write>}
|
|
78
85
|
*/
|
|
79
|
-
export declare const testAll: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations>;
|
|
86
|
+
export declare const testAll: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations | Write>;
|
|
80
87
|
/** Returns `true` if `s` is a non-negative decimal integer without a leading zero.
|
|
81
88
|
*
|
|
82
89
|
* @type {(s: string) => boolean}
|
|
@@ -125,9 +132,9 @@ export declare const ghEscape: (s: string) => string;
|
|
|
125
132
|
* Default `Reporter.test` implementation: sandboxes `fn` once and inverts the
|
|
126
133
|
* result when `throws` is `true` (caught error → pass, clean return → fail).
|
|
127
134
|
*
|
|
128
|
-
* @type {(file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown
|
|
135
|
+
* @type {(file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown>, NotImplemented>}
|
|
129
136
|
*/
|
|
130
|
-
export declare const defaultTest: (file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown
|
|
137
|
+
export declare const defaultTest: (file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown>, NotImplemented>;
|
|
131
138
|
/**
|
|
132
139
|
* The terminal/GitHub reporter used by `fjs t`. Output goes through
|
|
133
140
|
* `csiWrite`, so ANSI styles are stripped on non-TTY streams. When
|
|
@@ -10,15 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*
|
|
13
|
-
* @import {
|
|
13
|
+
* @import { Operation } from '../effects/types.ts'
|
|
14
|
+
* @import { Effect, NotImplemented } from '../effects/types.ts'
|
|
14
15
|
* @import { LoadModuleOperations, ModuleMap } from '../dev/types.ts'
|
|
15
16
|
* @import { TestFn, TestEntry, TestSet, Path, Reporter, _TestState, _TestAndPath } from './types.ts'
|
|
16
|
-
* @import { All, Await, Env, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write, WriteConsoles } from '../effects/node/types.ts'
|
|
17
|
+
* @import { All, Await, Env, IoChannel, NodeProgram, NodeProgramOptions, Program, Sandbox, SandboxResult, Test, TestContext, Write, WriteConsoles } from '../effects/node/types.ts'
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { reset, fgGreen, fgRed, bold, csiWrite } from '../text/sgr/module.f.mjs'
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
21
|
+
import { allOk, awaitIfPromise, errorExit, errorMessage, errorSummary, exitStep, sandbox, test } from '../effects/node/module.f.mjs'
|
|
22
|
+
import {
|
|
23
|
+
catchStep, history, historyStep, mapStep, pureError, pureOk, resultStep, step,
|
|
24
|
+
} from '../effects/module.f.mjs'
|
|
22
25
|
import { loadModuleMap } from '../dev/module.f.mjs'
|
|
23
26
|
import { invert } from '../types/result/module.f.mjs'
|
|
24
27
|
import { definedEntries } from '../types/object/module.f.mjs'
|
|
@@ -104,10 +107,10 @@ export const collectTests = (path, throws, v) => {
|
|
|
104
107
|
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
105
108
|
* be declared upfront and the framework drives execution.
|
|
106
109
|
*
|
|
107
|
-
* @type {(ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void>}
|
|
110
|
+
* @type {(ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void, NotImplemented>}
|
|
108
111
|
*/
|
|
109
112
|
export const registerModule = (ctx, k, v, star) => {
|
|
110
|
-
/** @type {(ctx: TestContext, entry: _TestAndPath) => Effect<Test | All | Await, void>} */
|
|
113
|
+
/** @type {(ctx: TestContext, entry: _TestAndPath) => Effect<Test | All | Await, void, NotImplemented>} */
|
|
111
114
|
const registerOne = (ctx, [path, { fn, throws }]) => {
|
|
112
115
|
// `star` (non-empty for Bun and for Node below the 26 baseline) signals
|
|
113
116
|
// that all sub-tests run inline inside this single registration, so an
|
|
@@ -117,22 +120,38 @@ export const registerModule = (ctx, k, v, star) => {
|
|
|
117
120
|
// extra suffix is needed.
|
|
118
121
|
const base = fmtImport(k, path)
|
|
119
122
|
const name = throws ? base : `${base}${star}`
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
// The registered callback panics on failure, deliberately. `Test` hands
|
|
124
|
+
// it to an external framework (node `--test`, Bun, Deno) that takes a
|
|
125
|
+
// body which either returns or throws: there is no channel to answer a
|
|
126
|
+
// failure through, so propagating it here would only discard it one
|
|
127
|
+
// level up.
|
|
128
|
+
// A throw is what that framework *does* understand — it reports the
|
|
129
|
+
// test as failed, which is the outcome a caller wants anyway. The `test`
|
|
130
|
+
// operation's own result is propagated normally, just below.
|
|
131
|
+
//
|
|
132
|
+
// `catchStep` rather than `unwrapStep`, so the absorption is visible in
|
|
133
|
+
// the type: the body answers `Effect<…, void, never>`, and the `never`
|
|
134
|
+
// is *because* this handler panics. `errorSummary` still names what is
|
|
135
|
+
// being panicked on and pins it, so if the body's channel ever widens
|
|
136
|
+
// past the node one, the compiler asks here rather than turning a new
|
|
137
|
+
// recoverable failure into a crash.
|
|
138
|
+
/** @type {(t: TestContext) => Effect<Test | All | Await, void, never>} */
|
|
139
|
+
const body = t =>
|
|
140
|
+
catchStep(step(awaitIfPromise(fn()), resolved => {
|
|
122
141
|
if (throws) {
|
|
123
|
-
return
|
|
142
|
+
return pureOk(undefined)
|
|
124
143
|
}
|
|
125
144
|
const sub = collectTests([...path, null], false, resolved)
|
|
126
145
|
if (sub.length === 0) {
|
|
127
|
-
return
|
|
146
|
+
return pureOk(undefined)
|
|
128
147
|
}
|
|
129
|
-
return mapStep(
|
|
130
|
-
})
|
|
131
|
-
)
|
|
148
|
+
return mapStep(allOk(...sub.map(e => registerOne(t, e))), () => undefined)
|
|
149
|
+
}), e => { throw errorSummary(e) })
|
|
150
|
+
return test(ctx, name, throws, body)
|
|
132
151
|
}
|
|
133
152
|
const tests = collectTests([], false, v)
|
|
134
|
-
if (tests.length === 0) { return
|
|
135
|
-
return mapStep(
|
|
153
|
+
if (tests.length === 0) { return pureOk(undefined) }
|
|
154
|
+
return mapStep(allOk(...tests.map(e => registerOne(ctx, e))), () => undefined)
|
|
136
155
|
}
|
|
137
156
|
|
|
138
157
|
/** @type {(a: _TestState, b: _TestState) => _TestState} */
|
|
@@ -145,10 +164,10 @@ const zero = { time: 0, pass: 0, fail: 0 }
|
|
|
145
164
|
/**
|
|
146
165
|
* @template {Operation} O
|
|
147
166
|
* @param {Reporter<O>} reporter
|
|
148
|
-
* @returns {(k: string, v: unknown) => (ts: _TestState) => Effect<O | All, _TestState>}
|
|
167
|
+
* @returns {(k: string, v: unknown) => (ts: _TestState) => Effect<O | All, _TestState, IoChannel>}
|
|
149
168
|
*/
|
|
150
169
|
const runModule = ({ result, test }) => (k, v) => ts => {
|
|
151
|
-
/** @type {(entry: _TestAndPath) => Effect<O | All, _TestState>} */
|
|
170
|
+
/** @type {(entry: _TestAndPath) => Effect<O | All, _TestState, IoChannel>} */
|
|
152
171
|
const one = ([testPath, set]) => {
|
|
153
172
|
// The sandbox result is still needed after it has been reported, so the
|
|
154
173
|
// reporting call is captured rather than nested inside its own step.
|
|
@@ -160,22 +179,22 @@ const runModule = ({ result, test }) => (k, v) => ts => {
|
|
|
160
179
|
([, sr]) => {
|
|
161
180
|
const { result: [s, r], duration } = sr
|
|
162
181
|
if (s !== 'ok') {
|
|
163
|
-
return
|
|
182
|
+
return pureOk(addFail(duration)(zero))
|
|
164
183
|
}
|
|
165
184
|
if (set.throws) {
|
|
166
|
-
return
|
|
185
|
+
return pureOk(addPass(duration)(zero))
|
|
167
186
|
}
|
|
168
187
|
// Walk return-value sub-tree; null marks the call boundary so
|
|
169
188
|
// paths render as e.g. `outer().inner`. throws resets to false.
|
|
170
|
-
return
|
|
189
|
+
return mapStep(
|
|
171
190
|
walk([...testPath, null], false, r),
|
|
172
|
-
sub =>
|
|
191
|
+
sub => mergeState(addPass(duration)(zero), sub))
|
|
173
192
|
})
|
|
174
193
|
}
|
|
175
|
-
/** @type {(path: Path, throws: boolean, v: unknown) => Effect<O | All, _TestState>} */
|
|
194
|
+
/** @type {(path: Path, throws: boolean, v: unknown) => Effect<O | All, _TestState, IoChannel>} */
|
|
176
195
|
const walk = (path, throws, v) => {
|
|
177
196
|
const effects = collectTests(path, throws, v).map(one)
|
|
178
|
-
return mapStep(
|
|
197
|
+
return mapStep(allOk(...effects), states => states.reduce(mergeState, zero))
|
|
179
198
|
}
|
|
180
199
|
return mapStep(walk([], false, v), delta => mergeState(ts, delta))
|
|
181
200
|
}
|
|
@@ -192,13 +211,13 @@ const proofEntries = moduleMap =>
|
|
|
192
211
|
*
|
|
193
212
|
* @template {Operation} O
|
|
194
213
|
* @param {Reporter<O>} reporter
|
|
195
|
-
* @returns {(moduleMap: ModuleMap) => Effect<O | All, number>}
|
|
214
|
+
* @returns {(moduleMap: ModuleMap) => Effect<O | All, number, IoChannel>}
|
|
196
215
|
*/
|
|
197
216
|
export const runModuleMap = reporter => moduleMap => {
|
|
198
217
|
const { summary } = reporter
|
|
199
218
|
const modules = proofEntries(moduleMap)
|
|
200
219
|
const total = mapStep(
|
|
201
|
-
|
|
220
|
+
allOk(...modules.map(([k, v]) => runModule(reporter)(k, v)(zero))),
|
|
202
221
|
m => m.reduce(mergeState, zero))
|
|
203
222
|
// The totals are still needed after the summary has been printed, so they
|
|
204
223
|
// are carried forward in a history rather than closed over by a nested
|
|
@@ -209,28 +228,57 @@ export const runModuleMap = reporter => moduleMap => {
|
|
|
209
228
|
return mapStep(reported, ([, ts]) => ts.fail !== 0 ? 1 : 0)
|
|
210
229
|
}
|
|
211
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Ends a run with the exit code it computed, reporting a channel failure on
|
|
233
|
+
* `stderr` as exit `1`.
|
|
234
|
+
*
|
|
235
|
+
* Not `exitStep`, which answers `0` for every success: this chain's success
|
|
236
|
+
* value **is** the exit code, `1` when a test failed. The two policies differ
|
|
237
|
+
* only in what an `ok` means, and conflating them would report a failing suite
|
|
238
|
+
* as a passing run.
|
|
239
|
+
*
|
|
240
|
+
* A non-zero code therefore leaves through the *error* branch, which is what it
|
|
241
|
+
* means — a suite with failures is a failed program — and is why a caller
|
|
242
|
+
* cannot chain past it by accident.
|
|
243
|
+
*
|
|
244
|
+
* @type {<O extends Operation>(e: Effect<O, number, IoChannel>) => Effect<O | Write, 0, number>}
|
|
245
|
+
*/
|
|
246
|
+
const exitCodeStep = e =>
|
|
247
|
+
resultStep(e, r => {
|
|
248
|
+
/** @type {Effect<Write, 0, number>} */
|
|
249
|
+
const code = r[0] === 'error'
|
|
250
|
+
? errorExit(errorMessage(r[1]))
|
|
251
|
+
: r[1] === 0 ? pureOk(0) : pureError(r[1])
|
|
252
|
+
return code
|
|
253
|
+
})
|
|
254
|
+
|
|
212
255
|
/**
|
|
213
256
|
* Discovers all test modules via `loadModuleMap`, then runs them through
|
|
214
257
|
* `runModuleMap`. The composed effect is a `NodeProgram` entry point for the
|
|
215
258
|
* `fjs t` test runner.
|
|
216
259
|
*
|
|
260
|
+
* The chain leaves the error channel here, because this is where a `Program` ends
|
|
261
|
+
* and a `Program`'s answer is an exit code rather than a `Result`. A run that
|
|
262
|
+
* could not report its own results is a failed run, so it exits `1` with the
|
|
263
|
+
* reason on `stderr` instead of unwinding as a panic.
|
|
264
|
+
*
|
|
217
265
|
* @template {Operation} O
|
|
218
266
|
* @param {Reporter<O>} reporter
|
|
219
|
-
* @returns {Program<O | All | LoadModuleOperations>}
|
|
267
|
+
* @returns {Program<O | All | LoadModuleOperations | Write>}
|
|
220
268
|
*/
|
|
221
269
|
export const testAll = reporter => options =>
|
|
222
|
-
step(loadModuleMap(options.env), runModuleMap(reporter))
|
|
270
|
+
exitCodeStep(step(loadModuleMap(options.env), runModuleMap(reporter)))
|
|
223
271
|
|
|
224
272
|
/**
|
|
225
273
|
* Registers all modules in `moduleMap` that export a `proof` property with
|
|
226
274
|
* `ctx`. Delegates to `registerModule` for each matching entry.
|
|
227
275
|
*
|
|
228
|
-
* @type {(ctx: TestContext, star: string) => (moduleMap: ModuleMap) => Effect<Test | All | Await, void>}
|
|
276
|
+
* @type {(ctx: TestContext, star: string) => (moduleMap: ModuleMap) => Effect<Test | All | Await, void, NotImplemented>}
|
|
229
277
|
*/
|
|
230
278
|
const registerModuleMap = (ctx, star) => moduleMap => {
|
|
231
279
|
const modules = proofEntries(moduleMap)
|
|
232
|
-
if (modules.length === 0) { return
|
|
233
|
-
return mapStep(
|
|
280
|
+
if (modules.length === 0) { return pureOk(undefined) }
|
|
281
|
+
return mapStep(allOk(...modules.map(([k, v]) => registerModule(ctx, k, v, star))), () => undefined)
|
|
234
282
|
}
|
|
235
283
|
|
|
236
284
|
/** @type {(c: string) => boolean} */
|
|
@@ -316,7 +364,7 @@ export const ghEscape = s =>
|
|
|
316
364
|
* Default `Reporter.test` implementation: sandboxes `fn` once and inverts the
|
|
317
365
|
* result when `throws` is `true` (caught error → pass, clean return → fail).
|
|
318
366
|
*
|
|
319
|
-
* @type {(file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown
|
|
367
|
+
* @type {(file: string, path: Path, entry: TestEntry) => Effect<Sandbox, SandboxResult<unknown>, NotImplemented>}
|
|
320
368
|
*/
|
|
321
369
|
export const defaultTest = (file, path, { fn, throws }) =>
|
|
322
370
|
mapStep(sandbox(fn), r => throws ? { ...r, result: invert(r.result) } : r)
|
|
@@ -336,7 +384,12 @@ const fmtResultLine = (file, path, color, label, duration) =>
|
|
|
336
384
|
*/
|
|
337
385
|
export const defaultReporter = options => {
|
|
338
386
|
const write = csiWrite(options)
|
|
339
|
-
|
|
387
|
+
// A reporter that cannot emit its own output has no fallback to choose —
|
|
388
|
+
// there is nowhere left to report the failure — but it does not have to
|
|
389
|
+
// decide that here: the failure travels to the program's tail, which ends
|
|
390
|
+
// the run with the reason on `stderr` and exit `1`. That is the same
|
|
391
|
+
// outcome a panic produced, minus the stack trace.
|
|
392
|
+
/** @type {(w: WriteConsoles) => (s: string) => Effect<Write, void, NotImplemented>} */
|
|
340
393
|
const line = w => {
|
|
341
394
|
const x = write(w)
|
|
342
395
|
return s => x(s + '\n')
|
|
@@ -351,6 +404,9 @@ export const defaultReporter = options => {
|
|
|
351
404
|
? csiLog(fmtResultLine(file, path, fgGreen, 'ok', duration) + (throws ? ' # EXPECTED TO THROW' : ''))
|
|
352
405
|
: isGitHub
|
|
353
406
|
? csiError(`::error file=${file},line=1,title=${ghEscape(fmtImport(file, path))}::${ghEscape(String(v))}`)
|
|
407
|
+
// `step`, so the detail line is attempted only when the
|
|
408
|
+
// header line was written: two halves of one report, and
|
|
409
|
+
// half of it is worse than none.
|
|
354
410
|
: step(
|
|
355
411
|
csiError(fmtResultLine(file, path, fgRed, 'error', duration)),
|
|
356
412
|
() => csiError(`${fgRed}${v}${reset}`)),
|
|
@@ -384,5 +440,9 @@ export const register = o => {
|
|
|
384
440
|
const star = o.inlineTestContext ? ' ...' : ''
|
|
385
441
|
const ctx = o.engine === 'bun' ? o.bunTestContext : o.testContext
|
|
386
442
|
const registered = step(loadModuleMap(o.env), registerModuleMap(ctx, star))
|
|
387
|
-
|
|
443
|
+
// `exitStep`, not `mapStep(…, () => 0)`: registering is the whole job here,
|
|
444
|
+
// so "registered nothing, exit 0" is the answer this used to give and the
|
|
445
|
+
// one it must not. A success has no code of its own, which is exactly the
|
|
446
|
+
// shape `exitStep` is for.
|
|
447
|
+
return exitStep(registered)
|
|
388
448
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
* @import {
|
|
2
|
+
* @import { RunInstance } from '../effects/mock/types.ts'
|
|
3
|
+
* @import { Effect, NotImplemented } from '../effects/types.ts'
|
|
4
|
+
* @import { NodeProgramOptions, OpResult, Sandbox, Write } from '../effects/node/types.ts'
|
|
4
5
|
* @import { JsModule } from '../effects/node/virtual/types.ts'
|
|
5
6
|
* @import { Reporter } from './types.ts'
|
|
6
|
-
* @import { All, Await, Test, TestContext } from '../effects/node/types.ts'
|
|
7
|
+
* @import { All, Await, Import, Readdir, Test, TestContext } from '../effects/node/types.ts'
|
|
7
8
|
* @import { Ts } from '../types/rtti/ts/types.ts'
|
|
8
9
|
*/
|
|
10
|
+
import type { RunInstance } from '../effects/mock/types.ts';
|
|
9
11
|
import type { Effect } from '../effects/types.ts';
|
|
10
|
-
import type { Sandbox, Write } from '../effects/node/types.ts';
|
|
12
|
+
import type { OpResult, Sandbox, Write } from '../effects/node/types.ts';
|
|
11
13
|
import type { Reporter } from './types.ts';
|
|
12
|
-
import type { All, Await, Test, TestContext } from '../effects/node/types.ts';
|
|
14
|
+
import type { All, Await, Import, Readdir, Test, TestContext } from '../effects/node/types.ts';
|
|
13
15
|
import type { Ts } from '../types/rtti/ts/types.ts';
|
|
14
16
|
/**
|
|
15
17
|
* The mock reporter's stdout lines. A schema rather than a hand-written type:
|
|
@@ -39,13 +41,19 @@ export declare const defaultReporterOutput: () => void;
|
|
|
39
41
|
export declare const defaultReporterOutputLargeDuration: () => void;
|
|
40
42
|
export declare const defaultReporterFailOutput: () => void;
|
|
41
43
|
export declare const githubReporterOutput: () => void;
|
|
44
|
+
export type _FailOps = All | Import | Readdir | Sandbox | Write;
|
|
45
|
+
/** @typedef {All | Import | Readdir | Sandbox | Write} _FailOps */
|
|
46
|
+
export declare const reporterWriteFailure: () => void;
|
|
42
47
|
export type _RegisterMockState = readonly string[];
|
|
43
48
|
export type _RegisterMockOps = Test | All | Await;
|
|
44
|
-
export type _RegisterRunner =
|
|
45
|
-
export type _RegisterTestOp = (runner: _RegisterRunner, ctx: TestContext, name: string, expectFailure: boolean, fn: (t: TestContext) => Effect<_RegisterMockOps, void>) => (s: _RegisterMockState) => readonly [_RegisterMockState, void];
|
|
49
|
+
export type _RegisterRunner = RunInstance<_RegisterMockOps, _RegisterMockState>;
|
|
50
|
+
export type _RegisterTestOp = (runner: _RegisterRunner, ctx: TestContext, name: string, expectFailure: boolean, fn: (t: TestContext) => Effect<_RegisterMockOps, void, never>) => (s: _RegisterMockState) => readonly [_RegisterMockState, OpResult<void>];
|
|
46
51
|
export declare const registerSuffixes: () => void;
|
|
52
|
+
declare const registerBodyPanicsOnUndispatchableEffect: () => void;
|
|
47
53
|
export declare const registerThrowsWithoutThrowing: () => void;
|
|
48
54
|
export declare const registerEmptyProof: () => void;
|
|
55
|
+
export declare const registerEmptyModuleMap: () => void;
|
|
56
|
+
export declare const registerSelectsContextAndStar: () => void;
|
|
49
57
|
export declare const helpers: {
|
|
50
58
|
isInteger: () => void;
|
|
51
59
|
isIdentifier: () => void;
|
|
@@ -61,6 +69,9 @@ export declare const helpers: {
|
|
|
61
69
|
};
|
|
62
70
|
declare const defaultReporterExpectedToThrow: () => void;
|
|
63
71
|
export declare const proof: {
|
|
72
|
+
throw: {
|
|
73
|
+
registerBodyPanicsOnUndispatchableEffect: typeof registerBodyPanicsOnUndispatchableEffect;
|
|
74
|
+
};
|
|
64
75
|
flat: typeof flat;
|
|
65
76
|
nested: typeof nested;
|
|
66
77
|
throwKey: typeof throwKey;
|
|
@@ -76,9 +87,12 @@ export declare const proof: {
|
|
|
76
87
|
defaultReporterOutputLargeDuration: typeof defaultReporterOutputLargeDuration;
|
|
77
88
|
defaultReporterFailOutput: typeof defaultReporterFailOutput;
|
|
78
89
|
githubReporterOutput: typeof githubReporterOutput;
|
|
90
|
+
reporterWriteFailure: typeof reporterWriteFailure;
|
|
79
91
|
registerSuffixes: typeof registerSuffixes;
|
|
80
92
|
registerThrowsWithoutThrowing: typeof registerThrowsWithoutThrowing;
|
|
81
93
|
registerEmptyProof: typeof registerEmptyProof;
|
|
94
|
+
registerEmptyModuleMap: typeof registerEmptyModuleMap;
|
|
95
|
+
registerSelectsContextAndStar: typeof registerSelectsContextAndStar;
|
|
82
96
|
defaultReporterExpectedToThrow: typeof defaultReporterExpectedToThrow;
|
|
83
97
|
helpers: {
|
|
84
98
|
isInteger: () => void;
|