functionalscript 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/module.f.d.mts +0 -41
- package/fjs/types/rtti/validate/module.f.mjs +0 -171
- package/fjs/types/rtti/validate/proof.f.d.mts +0 -125
- package/fjs/types/rtti/validate/proof.f.mjs +0 -437
- package/fjs/types/rtti/validate/types.d.ts +0 -6
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*
|
|
13
13
|
* @import { Vec } from '../../types/bit_vec/types.ts'
|
|
14
|
-
* @import {
|
|
14
|
+
* @import { Result } from '../../types/result/types.ts'
|
|
15
|
+
* @import { Commands, CommandSet, Effect, Func, NotImplemented, Operation } from '../types.ts'
|
|
15
16
|
* @import { List } from '../list/types.ts'
|
|
16
|
-
* @import { All, Access, Await, Console, CreateExclusive, CreateServer, Dirent, Engine, Env, Exec, ExecResult, Fetch, FileStat, Forever, Fs, Headers, Http, IncomingMessage, Import,
|
|
17
|
+
* @import { All, Access, Await, Console, CreateExclusive, CreateServer, Dirent, Engine, Env, Exec, ExecResult, Fetch, FileStat, Forever, Fs, Headers, Http, IncomingMessage, Import, IoChannel, IoError, IoErrorInfo, Listen, MakeDirectoryOptions, Mkdir, Module, Now, NodeOp, NodeProgramOptions, RandomInt, Read, ReadBytes, ReadConsoles, ReadFile, Readdir, ReaddirOptions, RequestListener, Rename, Rm, Sandbox, SandboxResult, Server, ServerResponse, Stat, Test, TestContext, TestFn, Write, WriteBytes, WriteConsoles, WriteFile, _UtfList, _WriteLoop } from './types.ts'
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { utf8, utf8ToString } from '../../text/module.f.mjs'
|
|
@@ -21,37 +22,153 @@ import { toCodePointList } from '../../text/utf8/module.f.mjs'
|
|
|
21
22
|
import { codePointListToString } from '../../text/utf16/module.f.mjs'
|
|
22
23
|
import { reverse } from '../../types/list/module.f.mjs'
|
|
23
24
|
import { length } from '../../types/bit_vec/module.f.mjs'
|
|
24
|
-
import {
|
|
25
|
-
import { do_,
|
|
25
|
+
import { error as resultError, ok as resultOk, unwrap } from '../../types/result/module.f.mjs'
|
|
26
|
+
import { do_, pure } from '../module.f.mjs'
|
|
27
|
+
import {
|
|
28
|
+
mapStep as ioMapStep, pureError, pureOk, resultMapStep, resultStep, step as ioStep,
|
|
29
|
+
} from '../module.f.mjs'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Builds a normalized host error. The constructor exists so the shape is
|
|
33
|
+
* written once: every runner reports its failures through it, and a consumer
|
|
34
|
+
* matching on `'ioError'` knows what the payload holds.
|
|
35
|
+
*
|
|
36
|
+
* @type {(info: IoErrorInfo) => IoError}
|
|
37
|
+
*/
|
|
38
|
+
export const ioError = info => ['ioError', info]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes a **thrown** value into an {@link IoError}: the OS error code when
|
|
42
|
+
* the host attached a string one, and a message that is the `Error`'s own or
|
|
43
|
+
* the value's string form.
|
|
44
|
+
*
|
|
45
|
+
* This is the boundary where an impure runner's `catch` becomes ordinary effect
|
|
46
|
+
* data. Nothing past it sees the thrown object, which is the point — a stack, a
|
|
47
|
+
* `cause`, and arbitrary own properties do not survive a wire hop, and a
|
|
48
|
+
* program that branched on them would be reading the host's implementation
|
|
49
|
+
* rather than the operation's contract.
|
|
50
|
+
*
|
|
51
|
+
* @type {(e: unknown) => IoError}
|
|
52
|
+
*/
|
|
53
|
+
export const toIoError = e => {
|
|
54
|
+
const message = e instanceof Error ? e.message : String(e)
|
|
55
|
+
if (typeof e !== 'object' || e === null || !('code' in e) || typeof e.code !== 'string') {
|
|
56
|
+
return ioError({ message })
|
|
57
|
+
}
|
|
58
|
+
return ioError({ code: e.code, message })
|
|
59
|
+
}
|
|
26
60
|
|
|
27
61
|
/**
|
|
28
62
|
* True if `e` is a "file or directory does not exist" (`ENOENT`) error.
|
|
29
63
|
*
|
|
30
|
-
* Node's filesystem rejections are `Error`s carrying `code: 'ENOENT'
|
|
31
|
-
*
|
|
32
|
-
* only the missing-path case (e.g. a fresh
|
|
33
|
-
* failures (permissions, corruption) rather
|
|
64
|
+
* Node's filesystem rejections are `Error`s carrying `code: 'ENOENT'`, which
|
|
65
|
+
* {@link toIoError} keeps; the virtual interpreter reports the same code for
|
|
66
|
+
* absent paths. Lets callers swallow only the missing-path case (e.g. a fresh
|
|
67
|
+
* store) while propagating genuine failures (permissions, corruption) rather
|
|
68
|
+
* than masking them.
|
|
34
69
|
*
|
|
35
|
-
* @
|
|
70
|
+
* A {@link NotImplemented} is never "not found": a runner that cannot perform
|
|
71
|
+
* the operation has not looked for the path at all, so the two must not
|
|
72
|
+
* collapse into one benign branch — which is exactly what a bare `unknown`
|
|
73
|
+
* error channel used to allow.
|
|
74
|
+
*
|
|
75
|
+
* @type {(e: IoChannel) => boolean}
|
|
36
76
|
*/
|
|
37
|
-
export const isNotFound =
|
|
38
|
-
|
|
77
|
+
export const isNotFound = ([tag, payload]) =>
|
|
78
|
+
tag === 'ioError' && payload.code === 'ENOENT'
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `NodeOp`'s commands as data, so a runner that implements only part of them
|
|
82
|
+
* can still tell an operation it lacks from a `Do` node whose `command` was
|
|
83
|
+
* never a `NodeOp` at all — see `CommandSet` in `../types.ts` for why the
|
|
84
|
+
* distinction needs the set at runtime.
|
|
85
|
+
*
|
|
86
|
+
* Declared as a record because `CommandSet<NodeOp>` is checked for
|
|
87
|
+
* *completeness*: adding a command to `NodeOp` and forgetting it here is a
|
|
88
|
+
* compile error, where an array literal would only have its members checked and
|
|
89
|
+
* would drift silently.
|
|
90
|
+
*
|
|
91
|
+
* @type {CommandSet<NodeOp>}
|
|
92
|
+
*/
|
|
93
|
+
const nodeCommandSet = {
|
|
94
|
+
access: null, all: null, await: null, createExclusive: null,
|
|
95
|
+
createServer: null, exec: null, fetch: null, forever: null,
|
|
96
|
+
import: null, listen: null, memCreate: null, memRead: null,
|
|
97
|
+
memWrite: null, mkdir: null, now: null, randomInt: null,
|
|
98
|
+
read: null, readBytes: null, readFile: null, readdir: null,
|
|
99
|
+
rename: null, rm: null, sandbox: null, stat: null,
|
|
100
|
+
test: null, write: null, writeBytes: null, writeFile: null,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The commands of {@link nodeCommandSet}, in the form a partial runner tests
|
|
105
|
+
* membership against. The cast is the one `Object.keys` always needs: it
|
|
106
|
+
* answers `string[]` for a record whose keys the type system knows exactly.
|
|
107
|
+
*
|
|
108
|
+
* @type {Commands<NodeOp>}
|
|
109
|
+
*/
|
|
110
|
+
export const nodeCommands = /** @type {Commands<NodeOp>} */ (Object.keys(nodeCommandSet))
|
|
39
111
|
|
|
40
112
|
// all
|
|
41
113
|
|
|
42
114
|
/**
|
|
43
115
|
* To run the operation `O` should be known by the runner/engine.
|
|
44
|
-
* This is the reason why we merge `O` with `All` in the
|
|
116
|
+
* This is the reason why we merge `O` with `All` in the resulting effect.
|
|
45
117
|
*/
|
|
46
118
|
export const all =
|
|
47
|
-
|
|
48
|
-
|
|
119
|
+
// `Func` cannot express a variadic generic operation, so the declared type
|
|
120
|
+
// is written out here and `do_`'s is set aside.
|
|
121
|
+
/** @type {<O extends Operation, T, E>(...a: readonly Effect<O, T, E>[]) => Effect<O | All, readonly Result<T, E>[], NotImplemented>} */
|
|
122
|
+
(/** @type {unknown} */ (do_('all')))
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Collapses a list of results into a result of the list, keeping the **first**
|
|
126
|
+
* error in list order and discarding the later ones.
|
|
127
|
+
*
|
|
128
|
+
* Keeping one is what makes this a `Result` rather than a report: the callers
|
|
129
|
+
* that need it are chains, and a chain has one error channel. A site that wants
|
|
130
|
+
* every failure wants a different return type and should not reach for this.
|
|
131
|
+
*
|
|
132
|
+
* @type {<T, E>(list: readonly Result<T, E>[]) => Result<readonly T[], E>}
|
|
133
|
+
*/
|
|
134
|
+
const okList = list => {
|
|
135
|
+
for (const r of list) {
|
|
136
|
+
if (r[0] === 'error') { return r }
|
|
137
|
+
}
|
|
138
|
+
return resultOk(list.map(unwrap))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* {@link all} in the `ok` channel: collects the values when every effect
|
|
143
|
+
* succeeded, and answers with the first failure otherwise.
|
|
144
|
+
*
|
|
145
|
+
* `all` alone cannot serve a fallible chain. Its envelope is the runner's
|
|
146
|
+
* (`OpResult`, saying whether the *operation* could be dispatched), so handing
|
|
147
|
+
* it `Effect`s nests one `Result` inside another and the caller receives
|
|
148
|
+
* `readonly Result<T, E>[]`. That has to be collapsed before the chain can
|
|
149
|
+
* `step` again, and a continuation that forgets to is the value-discarding
|
|
150
|
+
* hazard this migration exists to remove — one level in, where it is harder to
|
|
151
|
+
* see.
|
|
152
|
+
*
|
|
153
|
+
* **Every effect still runs.** The short-circuit is in the *result*, not in the
|
|
154
|
+
* execution: `all` performs them concurrently and this reads the answers once
|
|
155
|
+
* they are all in, so a failure does not cancel its siblings the way it stops
|
|
156
|
+
* the sequential `forEachStep` in `./module.f.mjs`. The error channel
|
|
157
|
+
* unions the runner's
|
|
158
|
+
* `NotImplemented` with the effects' own `E` for the same reason every other
|
|
159
|
+
* step does — either can be what went wrong.
|
|
160
|
+
*
|
|
161
|
+
* @type {<O extends Operation, T, E>(...a: readonly Effect<O, T, E>[]) => Effect<O | All, readonly T[], NotImplemented | E>}
|
|
162
|
+
*/
|
|
163
|
+
export const allOk = (...a) =>
|
|
164
|
+
ioStep(all(...a), rs => pure(okList(rs)))
|
|
49
165
|
|
|
50
166
|
/**
|
|
51
167
|
* @template {Operation} O0
|
|
52
168
|
* @template T0
|
|
53
|
-
* @
|
|
54
|
-
* @
|
|
169
|
+
* @template E0
|
|
170
|
+
* @param {Effect<O0, T0, E0>} a
|
|
171
|
+
* @returns {<O1 extends Operation, T1, E1>(b: Effect<O1, T1, E1>) => Effect<O0 | O1 | All, readonly[Result<T0, E0>, Result<T1, E1>], NotImplemented>}
|
|
55
172
|
*/
|
|
56
173
|
export const both = a => b =>
|
|
57
174
|
/** @type {any} */ (all)(a, b)
|
|
@@ -74,14 +191,14 @@ export const readFile = do_('readFile')
|
|
|
74
191
|
/**
|
|
75
192
|
* Reads a file as UTF-8 text.
|
|
76
193
|
*
|
|
77
|
-
* Preserves the
|
|
78
|
-
* on
|
|
79
|
-
* call site.
|
|
194
|
+
* Preserves the error channel instead of unwrapping so callers can
|
|
195
|
+
* pattern-match on it (e.g. convert a failure into a domain-specific error) or
|
|
196
|
+
* `unwrap` at the call site.
|
|
80
197
|
*
|
|
81
|
-
* @type {(path: string) => Effect<ReadFile,
|
|
198
|
+
* @type {(path: string) => Effect<ReadFile, string, IoChannel>}
|
|
82
199
|
*/
|
|
83
200
|
export const readUtf8File = path =>
|
|
84
|
-
|
|
201
|
+
ioMapStep(readFile(path), utf8ToString)
|
|
85
202
|
|
|
86
203
|
// readdir
|
|
87
204
|
|
|
@@ -96,7 +213,7 @@ export const writeFile = do_('writeFile')
|
|
|
96
213
|
/**
|
|
97
214
|
* Writes a string to `path` as UTF-8 bytes.
|
|
98
215
|
*
|
|
99
|
-
* @type {(path: string, content: string) => Effect<WriteFile,
|
|
216
|
+
* @type {(path: string, content: string) => Effect<WriteFile, void, IoChannel>}
|
|
100
217
|
*/
|
|
101
218
|
export const writeUtf8File = (path, content) =>
|
|
102
219
|
writeFile(path, utf8(content))
|
|
@@ -145,21 +262,18 @@ export const writeBytes = do_('writeBytes')
|
|
|
145
262
|
const writeLoop = path => {
|
|
146
263
|
/** @type {_WriteLoop} */
|
|
147
264
|
const f = (offset, e) =>
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
const { first: [t, v], tail } = r
|
|
153
|
-
if (t === 'error') {
|
|
154
|
-
return pure(resultError(v))
|
|
265
|
+
ioStep(e, node => {
|
|
266
|
+
if (node === undefined) {
|
|
267
|
+
return pureOk(undefined)
|
|
155
268
|
}
|
|
269
|
+
const { first: v, tail } = node
|
|
156
270
|
const lenV = length(v)
|
|
157
271
|
if ((lenV & 0b111n) !== 0n) {
|
|
158
|
-
return
|
|
272
|
+
return pureError(ioError({ message: 'invalid buffer size' }))
|
|
159
273
|
}
|
|
160
|
-
return
|
|
274
|
+
return ioStep(
|
|
161
275
|
writeBytes(path, offset, v),
|
|
162
|
-
|
|
276
|
+
() => f(offset + Number(lenV >> 3n), tail))
|
|
163
277
|
})
|
|
164
278
|
return f
|
|
165
279
|
}
|
|
@@ -167,13 +281,13 @@ const writeLoop = path => {
|
|
|
167
281
|
/**
|
|
168
282
|
* @template {Operation} O
|
|
169
283
|
* @param {string} path
|
|
170
|
-
* @param {List<O,
|
|
171
|
-
* @returns {Effect<O | WriteBytes | CreateExclusive,
|
|
284
|
+
* @param {List<O, Vec, IoChannel>} e
|
|
285
|
+
* @returns {Effect<O | WriteBytes | CreateExclusive, void, IoChannel>}
|
|
172
286
|
*/
|
|
173
287
|
export const writeFromStream = (path, e) =>
|
|
174
|
-
|
|
288
|
+
ioStep(
|
|
175
289
|
createExclusive(path),
|
|
176
|
-
|
|
290
|
+
() => writeLoop(path)(0, e))
|
|
177
291
|
|
|
178
292
|
// stat
|
|
179
293
|
|
|
@@ -204,27 +318,31 @@ export const import_ = do_('import')
|
|
|
204
318
|
// write
|
|
205
319
|
|
|
206
320
|
/** Emits a `Write` effect to the given named stream. */
|
|
207
|
-
|
|
321
|
+
/** @type {Func<Write>} */
|
|
322
|
+
export const write = do_('write')
|
|
208
323
|
|
|
209
324
|
/**
|
|
210
325
|
* Encodes `s + '\n'` as UTF-8 and emits a `Write` effect to `stream`.
|
|
211
326
|
* Shared implementation for `log` and `error`.
|
|
212
327
|
*
|
|
213
|
-
* @type {(stream: WriteConsoles) =>
|
|
328
|
+
* @type {(stream: WriteConsoles) => Console}
|
|
214
329
|
*/
|
|
215
330
|
const writeString = stream => s =>
|
|
216
331
|
write(stream, utf8(s + '\n'))
|
|
217
332
|
|
|
218
333
|
/** Writes a line to `stdout`. Replaces the retired `Log` effect. */
|
|
219
|
-
|
|
334
|
+
/** @type {Console} */
|
|
335
|
+
export const log = writeString('stdout')
|
|
220
336
|
|
|
221
337
|
/** Writes a line to `stderr`. Replaces the retired `Error` effect. */
|
|
222
|
-
|
|
338
|
+
/** @type {Console} */
|
|
339
|
+
export const error = writeString('stderr')
|
|
223
340
|
|
|
224
341
|
// read
|
|
225
342
|
|
|
226
343
|
/** Emits a `Read` effect, yielding the next input byte or `null` at EOF. */
|
|
227
|
-
|
|
344
|
+
/** @type {Func<Read>} */
|
|
345
|
+
export const read = do_('read')
|
|
228
346
|
|
|
229
347
|
/** @type {(bytes: _UtfList) => string} */
|
|
230
348
|
const utf8ListToString = bytes =>
|
|
@@ -247,17 +365,20 @@ const lf = 0x0a
|
|
|
247
365
|
* reversed and decoded once at the terminator, so a large line costs O(n)
|
|
248
366
|
* rather than the O(n²) of copying a growing array on every byte.
|
|
249
367
|
*
|
|
250
|
-
*
|
|
368
|
+
* A failed `read` — a runner without the operation — propagates: the line is
|
|
369
|
+
* not silently truncated into a `null` that a caller would read as EOF.
|
|
370
|
+
*
|
|
371
|
+
* @type {(stream: ReadConsoles) => Effect<Read, string | null, NotImplemented>}
|
|
251
372
|
*/
|
|
252
373
|
export const readLine = stream => {
|
|
253
|
-
/** @type {(acc: _UtfList) => Effect<Read, string | null>} */
|
|
374
|
+
/** @type {(acc: _UtfList) => Effect<Read, string | null, NotImplemented>} */
|
|
254
375
|
const loop = acc =>
|
|
255
|
-
|
|
376
|
+
ioStep(
|
|
256
377
|
read(stream),
|
|
257
378
|
b => b === null
|
|
258
|
-
?
|
|
379
|
+
? pureOk(acc === null ? null : utf8ListToString(reverse(acc)))
|
|
259
380
|
: b === lf
|
|
260
|
-
?
|
|
381
|
+
? pureOk(utf8ListToString(reverse(acc)))
|
|
261
382
|
: loop({ first: b, tail: acc })
|
|
262
383
|
)
|
|
263
384
|
return loop(null)
|
|
@@ -295,9 +416,9 @@ export const sandbox = do_('sandbox')
|
|
|
295
416
|
/** @type {Func<Await>} */
|
|
296
417
|
const awaitPromise = do_('await')
|
|
297
418
|
|
|
298
|
-
/** @type {(p: unknown) => Effect<Await, unknown>} */
|
|
419
|
+
/** @type {(p: unknown) => Effect<Await, unknown, NotImplemented>} */
|
|
299
420
|
export const awaitIfPromise = p =>
|
|
300
|
-
|
|
421
|
+
ioMapStep(awaitPromise(p), ([x]) => x)
|
|
301
422
|
|
|
302
423
|
// Test registration
|
|
303
424
|
|
|
@@ -307,14 +428,97 @@ export const test = do_('test')
|
|
|
307
428
|
// Node
|
|
308
429
|
|
|
309
430
|
/**
|
|
310
|
-
* Writes an error line to `stderr` and
|
|
431
|
+
* Writes an error line to `stderr` and fails with exit code `1`. The canonical
|
|
311
432
|
* "fail with a message" program for a `NodeProgram`. For non-`1` exit codes,
|
|
312
|
-
* compose `
|
|
433
|
+
* compose `resultMapStep(error(s), () => resultError(n))` directly.
|
|
434
|
+
*
|
|
435
|
+
* **It never succeeds, and the type says so.** `E` is `number` and `T` is
|
|
436
|
+
* `never`, so `step`'s continuation takes a `never` and can never run. That is
|
|
437
|
+
* a continuation nobody reaches, not a compile error: writing one still type-
|
|
438
|
+
* checks, because a function accepting `never` accepts anything. What the type
|
|
439
|
+
* buys is that no *value* can be invented for the success branch, so nothing
|
|
440
|
+
* downstream can proceed as if this had succeeded.
|
|
441
|
+
*
|
|
442
|
+
* **The write's own outcome is deliberately discarded**, which is why this is
|
|
443
|
+
* `resultMapStep` rather than `mapStep`. The program is already failing and
|
|
444
|
+
* the exit code is `1` whether or not `stderr` accepted the bytes; propagating
|
|
445
|
+
* here would hand every caller a "failed to report a failure" branch with no
|
|
446
|
+
* better answer available to it than the one taken here.
|
|
313
447
|
*
|
|
314
|
-
* @type {(s: string) => Effect<Write, number>}
|
|
448
|
+
* @type {(s: string) => Effect<Write, never, number>}
|
|
315
449
|
*/
|
|
316
450
|
export const errorExit = s =>
|
|
317
|
-
|
|
451
|
+
resultMapStep(error(s), () => resultError(1))
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The exit code a {@link Program} answered, from whichever branch it came.
|
|
455
|
+
*
|
|
456
|
+
* `Result<0, number>` puts a number at `[1]` on both sides — `ok(0)` for
|
|
457
|
+
* success, `error(n)` for failure — so reading the code never asks which
|
|
458
|
+
* branch produced it, while a caller that cares *whether* it failed still asks
|
|
459
|
+
* `[0]`. That is why the success type is the literal `0` rather than `void`.
|
|
460
|
+
*
|
|
461
|
+
* A non-zero code belongs in the `error` branch and `0` in the `ok` branch; the
|
|
462
|
+
* type cannot say so, since there is no "non-zero number", and nothing depends
|
|
463
|
+
* on it — this reads `[1]` either way.
|
|
464
|
+
*
|
|
465
|
+
* @type {(r: Result<0, number>) => number}
|
|
466
|
+
*/
|
|
467
|
+
export const exitCode = ([, code]) => code
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Renders a channel error as a human line: an {@link IoError}'s own message, or
|
|
471
|
+
* the command name a runner could not dispatch.
|
|
472
|
+
*
|
|
473
|
+
* @type {(e: IoChannel) => string}
|
|
474
|
+
*/
|
|
475
|
+
export const errorMessage = ([tag, payload]) =>
|
|
476
|
+
tag === 'notImplemented' ? `operation not implemented: ${payload}` : payload.message
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Renders a channel error for a **remote** caller: the command name for a
|
|
480
|
+
* {@link NotImplemented}, the OS error code for an `IoError`, and nothing else.
|
|
481
|
+
*
|
|
482
|
+
* {@link errorMessage} is for the operator of the program, who is entitled to
|
|
483
|
+
* the host's own words — including the path that failed. A protocol client is
|
|
484
|
+
* not, and the difference is not stylistic: `payload.message` is where the
|
|
485
|
+
* host puts the absolute path it could not read, so answering an MCP tool call
|
|
486
|
+
* with it publishes the server's filesystem layout to whoever is on the other
|
|
487
|
+
* end. The code (`ENOENT`, `EACCES`) says *what* went wrong without saying
|
|
488
|
+
* *where*, which is the part a client can act on anyway.
|
|
489
|
+
*
|
|
490
|
+
* A host that attached no code leaves nothing safe to forward, so the answer is
|
|
491
|
+
* the bare kind. That is deliberate: guessing which part of a free-text message
|
|
492
|
+
* is path-free is exactly the mistake this exists to prevent.
|
|
493
|
+
*
|
|
494
|
+
* @type {(e: IoChannel) => string}
|
|
495
|
+
*/
|
|
496
|
+
export const errorSummary = ([tag, payload]) =>
|
|
497
|
+
tag === 'notImplemented'
|
|
498
|
+
? `operation not implemented: ${payload}`
|
|
499
|
+
: payload.code === undefined ? 'io error' : `io error: ${payload.code}`
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Ends a program with an exit code that reflects `e`: `ok` yields `0`, and a
|
|
503
|
+
* failure is reported on `stderr` and yields `1` ({@link errorExit}).
|
|
504
|
+
*
|
|
505
|
+
* This is the exit-code policy a `NodeProgram` needs at the end of its chain,
|
|
506
|
+
* and the reason a program does not have to invent one per command. It is the
|
|
507
|
+
* counterpart of {@link isNotFound} at the other end of the channel: where that
|
|
508
|
+
* one asks which failure this is, this one stops asking and reports.
|
|
509
|
+
*
|
|
510
|
+
* @type {<O extends Operation, T>(e: Effect<O, T, IoChannel>) => Effect<O | Write, 0, number>}
|
|
511
|
+
*/
|
|
512
|
+
export const exitStep = e =>
|
|
513
|
+
resultStep(e, r => {
|
|
514
|
+
// Bound rather than returned inline: the two branches are
|
|
515
|
+
// `Effect<Write, never, number>` and `Effect<never, 0, never>`, both
|
|
516
|
+
// assignable to this, but `step` infers its continuation's type from
|
|
517
|
+
// the union and picks neither.
|
|
518
|
+
/** @type {Effect<Write, 0, number>} */
|
|
519
|
+
const code = r[0] === 'error' ? errorExit(errorMessage(r[1])) : pureOk(0)
|
|
520
|
+
return code
|
|
521
|
+
})
|
|
318
522
|
|
|
319
523
|
/** @type {(version: string) => readonly number[]} */
|
|
320
524
|
const versionParts = version =>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Node.js effect runner: interprets `Effect<NodeOp, T>` directly against the
|
|
2
|
+
* Node.js effect runner: interprets `Effect<NodeOp, T, E>` directly against the
|
|
3
3
|
* Node globals and built-in modules (`fs`, `http`, `child_process`, `process`,
|
|
4
4
|
* `fetch`, …).
|
|
5
5
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @module
|
|
14
14
|
*
|
|
15
15
|
* @import { Effect } from '../types.ts'
|
|
16
|
-
* @import { Server as EffectServer, Headers, Module, NodeOp, RequestListener as Erl, NodeProgram, NodeProgramOptions, WriteConsoles, TestContext, TestFn, } from './types.ts'
|
|
16
|
+
* @import { IoResult, Server as EffectServer, Headers, Module, NodeOp, RequestListener as Erl, NodeProgram, NodeProgramOptions, WriteConsoles, TestContext, TestFn, } from './types.ts'
|
|
17
17
|
* @import { Result } from '../../types/result/types.ts'
|
|
18
18
|
* @import { StringMap } from '../../types/object/types.ts'
|
|
19
19
|
*/
|
|
@@ -30,9 +30,9 @@ import * as testContext from 'node:test'
|
|
|
30
30
|
import { concat, normalize, toPosix } from '../../path/module.f.mjs'
|
|
31
31
|
import { asyncRun } from '../module.mjs'
|
|
32
32
|
import { memoryOperationMap } from './memory/module.mjs'
|
|
33
|
-
import { usesInlineTestContext } from './module.f.mjs'
|
|
33
|
+
import { exitCode, toIoError, usesInlineTestContext } from './module.f.mjs'
|
|
34
34
|
import { asBase, asNominal } from '../../types/nominal/module.f.mjs'
|
|
35
|
-
import { error, ok } from '../../types/result/module.f.mjs'
|
|
35
|
+
import { error, ok, unwrap } from '../../types/result/module.f.mjs'
|
|
36
36
|
import { asyncTryCatch } from '../../types/result/module.mjs'
|
|
37
37
|
import { fromVec, listToVec, toVec } from '../../types/uint8array/module.f.mjs'
|
|
38
38
|
import { maxLengthBytes } from '../../types/bit_vec/module.f.mjs'
|
|
@@ -67,7 +67,23 @@ import { maxLengthBytes } from '../../types/bit_vec/module.f.mjs'
|
|
|
67
67
|
*/
|
|
68
68
|
const createServer = http.createServer
|
|
69
69
|
|
|
70
|
-
/** @typedef {<T>(effect: Effect<NodeOp, T>) => Promise<T
|
|
70
|
+
/** @typedef {<T, E>(effect: Effect<NodeOp, T, E>) => Promise<Result<T, E>>} _EffectToPromise */
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Performs host IO, reporting a thrown failure as an {@link IoResult} error.
|
|
74
|
+
*
|
|
75
|
+
* Every filesystem, network, and subprocess handler below goes through it, so
|
|
76
|
+
* the `catch` that turns an exception into effect data — and the normalization
|
|
77
|
+
* that keeps the channel serializable — happens in exactly one place.
|
|
78
|
+
*
|
|
79
|
+
* @template T
|
|
80
|
+
* @param {() => Promise<T>} f
|
|
81
|
+
* @returns {Promise<IoResult<T>>}
|
|
82
|
+
*/
|
|
83
|
+
const io = async f => {
|
|
84
|
+
const r = await asyncTryCatch(f)
|
|
85
|
+
return r[0] === 'ok' ? r : error(toIoError(r[1]))
|
|
86
|
+
}
|
|
71
87
|
|
|
72
88
|
/**
|
|
73
89
|
* @template T
|
|
@@ -115,7 +131,7 @@ const sandbox = async f => {
|
|
|
115
131
|
p = await p
|
|
116
132
|
after = performance.now()
|
|
117
133
|
}
|
|
118
|
-
result = ok(
|
|
134
|
+
result = ok(p)
|
|
119
135
|
} catch (e) {
|
|
120
136
|
after = performance.now()
|
|
121
137
|
result = error(e)
|
|
@@ -190,7 +206,7 @@ const waitReadableOrEnd = stdin =>
|
|
|
190
206
|
const readStdinByte = async () => {
|
|
191
207
|
const stdin = process.stdin
|
|
192
208
|
while (true) {
|
|
193
|
-
const chunk =
|
|
209
|
+
const chunk = stdin.read(1)
|
|
194
210
|
if (chunk !== null) {
|
|
195
211
|
return chunk[0]
|
|
196
212
|
}
|
|
@@ -210,16 +226,16 @@ const { randomInt } = crypto
|
|
|
210
226
|
/** @type {_EffectToPromise} */
|
|
211
227
|
const runNodeEffect = asyncRun({
|
|
212
228
|
...memoryOperationMap(),
|
|
213
|
-
all: async (...effects) => await Promise.all(effects.map(runNodeEffect)),
|
|
214
|
-
fetch:
|
|
229
|
+
all: async (...effects) => ok(await Promise.all(effects.map(runNodeEffect))),
|
|
230
|
+
fetch: url => io(async () => {
|
|
215
231
|
const response = await fetch(url)
|
|
216
232
|
if (!response.ok) {
|
|
217
233
|
throw new Error(`Fetch error: ${response.status} ${response.statusText}`)
|
|
218
234
|
}
|
|
219
235
|
return toVec(new Uint8Array(await response.arrayBuffer()))
|
|
220
236
|
}),
|
|
221
|
-
mkdir: (path, options) =>
|
|
222
|
-
readFile: path =>
|
|
237
|
+
mkdir: (path, options) => io(async () => { await mkdir(path, options) }),
|
|
238
|
+
readFile: path => io(async () => {
|
|
223
239
|
const fileStats = await stat(path)
|
|
224
240
|
// if the file is too big, toVec should fail anyway but in this case we don't want to load the file.
|
|
225
241
|
if (fileStats.size > maxFileSizeBytes) {
|
|
@@ -227,7 +243,7 @@ const runNodeEffect = asyncRun({
|
|
|
227
243
|
}
|
|
228
244
|
return toVec(await readFile(path))
|
|
229
245
|
}),
|
|
230
|
-
readdir: (path, r) =>
|
|
246
|
+
readdir: (path, r) => io(async () =>
|
|
231
247
|
(await readdir(path, { ...r, withFileTypes: true }))
|
|
232
248
|
.map(v => ({
|
|
233
249
|
name: v.name,
|
|
@@ -235,10 +251,10 @@ const runNodeEffect = asyncRun({
|
|
|
235
251
|
isFile: v.isFile()
|
|
236
252
|
}))
|
|
237
253
|
),
|
|
238
|
-
writeFile: (path, data) =>
|
|
239
|
-
rm: path =>
|
|
240
|
-
rename: (src, dst) =>
|
|
241
|
-
readBytes: (path, offset, size) =>
|
|
254
|
+
writeFile: (path, data) => io(() => writeFile(path, fromVec(data))),
|
|
255
|
+
rm: path => io(() => rm(path)),
|
|
256
|
+
rename: (src, dst) => io(() => rename(src, dst)),
|
|
257
|
+
readBytes: (path, offset, size) => io(async () => {
|
|
242
258
|
if (offset < 0) {
|
|
243
259
|
throw new Error(`Offset ${offset} is negative`)
|
|
244
260
|
}
|
|
@@ -254,13 +270,13 @@ const runNodeEffect = asyncRun({
|
|
|
254
270
|
await fh.close()
|
|
255
271
|
}
|
|
256
272
|
}),
|
|
257
|
-
randomInt: async () => randomInt(randomMax),
|
|
258
|
-
access: path =>
|
|
259
|
-
createExclusive: path =>
|
|
273
|
+
randomInt: async () => ok(randomInt(randomMax)),
|
|
274
|
+
access: path => io(() => access(path)),
|
|
275
|
+
createExclusive: path => io(async () => {
|
|
260
276
|
const fh = await open(path, 'wx')
|
|
261
277
|
await fh.close()
|
|
262
278
|
}),
|
|
263
|
-
writeBytes: (path, offset, data) =>
|
|
279
|
+
writeBytes: (path, offset, data) => io(async () => {
|
|
264
280
|
const fh = await open(path, 'r+')
|
|
265
281
|
try {
|
|
266
282
|
const buffer = fromVec(data)
|
|
@@ -275,11 +291,11 @@ const runNodeEffect = asyncRun({
|
|
|
275
291
|
await fh.close()
|
|
276
292
|
}
|
|
277
293
|
}),
|
|
278
|
-
stat: path =>
|
|
279
|
-
import: path =>
|
|
294
|
+
stat: path => io(async () => ({ size: (await stat(path)).size })),
|
|
295
|
+
import: path => io(() => asyncImport(path)),
|
|
280
296
|
exec: (command, stdin) => new Promise(resolve => {
|
|
281
297
|
const child = exec(command, (e, stdout, stderr) =>
|
|
282
|
-
resolve(e !== null ?
|
|
298
|
+
resolve(e !== null ? error(toIoError(e)) : ok({ stdout, stderr }))
|
|
283
299
|
)
|
|
284
300
|
child.stdin?.end(stdin)
|
|
285
301
|
}),
|
|
@@ -289,30 +305,37 @@ const runNodeEffect = asyncRun({
|
|
|
289
305
|
const nodeRl = async (req, res) => {
|
|
290
306
|
const reqBody = await collect(req)
|
|
291
307
|
const { method, url, headers } = req
|
|
292
|
-
|
|
308
|
+
// `RequestListener` answers `Effect<…, ServerResponse, never>` —
|
|
309
|
+
// the response frame is where a listener puts its failures — so
|
|
310
|
+
// this unwrap is total.
|
|
311
|
+
const { status, headers: outHeaders, body: outBody } = unwrap(await runNodeEffect(erl({
|
|
293
312
|
method,
|
|
294
313
|
url,
|
|
295
314
|
headers,
|
|
296
315
|
body: listToVec(reqBody)
|
|
297
|
-
}))
|
|
316
|
+
})))
|
|
298
317
|
res
|
|
299
318
|
.writeHead(status, outHeaders)
|
|
300
319
|
.end(fromVec(outBody))
|
|
301
320
|
}
|
|
302
|
-
return /** @satisfies {EffectServer} */ (asNominal(createServer(nodeRl)))
|
|
321
|
+
return ok(/** @satisfies {EffectServer} */ (asNominal(createServer(nodeRl))))
|
|
303
322
|
},
|
|
304
323
|
listen: async (server, port) => {
|
|
305
324
|
const s = /** @type {_Server} */ (asBase(server))
|
|
306
325
|
s.listen(port)
|
|
326
|
+
return ok(undefined)
|
|
307
327
|
},
|
|
308
328
|
forever: () => new Promise(() => {}),
|
|
309
|
-
now: async () => now(),
|
|
310
|
-
sandbox,
|
|
311
|
-
await: awaitPromise,
|
|
312
|
-
write: (stream, data) => writeAll(streams[stream], fromVec(data)),
|
|
313
|
-
read: readStdinByte,
|
|
329
|
+
now: async () => ok(now()),
|
|
330
|
+
sandbox: async f => ok(await sandbox(f)),
|
|
331
|
+
await: async p => ok(await awaitPromise(p)),
|
|
332
|
+
write: async (stream, data) => ok(await writeAll(streams[stream], fromVec(data))),
|
|
333
|
+
read: async () => ok(await readStdinByte()),
|
|
314
334
|
test: async (ctx, name, expectFailure, test) =>
|
|
315
|
-
|
|
335
|
+
// The body's answer is `ok(undefined)` — a `Test` callback absorbs its
|
|
336
|
+
// own failures by panicking, which is the only signal these frameworks
|
|
337
|
+
// read — so it is awaited and discarded rather than returned.
|
|
338
|
+
ok(await ctx.test(name, { expectFailure }, async t => { await runNodeEffect(test(t)) })),
|
|
316
339
|
})
|
|
317
340
|
|
|
318
341
|
/** @type {TestFn} */
|
|
@@ -365,8 +388,8 @@ const options = {
|
|
|
365
388
|
* the program's code, use {@link run} instead.
|
|
366
389
|
* @type {(p: NodeProgram) => Promise<number>}
|
|
367
390
|
*/
|
|
368
|
-
export const runEffect = program =>
|
|
369
|
-
runNodeEffect(program(options))
|
|
391
|
+
export const runEffect = async program =>
|
|
392
|
+
exitCode(await runNodeEffect(program(options)))
|
|
370
393
|
|
|
371
394
|
/**
|
|
372
395
|
* CLI entry point: runs a `NodeProgram` via {@link runEffect}, then calls
|