functionalscript 0.30.0 → 0.31.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/fs/asn.1/module.f.js +18 -22
- package/fs/asn.1/proof.f.d.ts +5 -0
- package/fs/asn.1/proof.f.js +38 -2
- package/fs/base64/module.f.d.ts +9 -0
- package/fs/base64/module.f.js +60 -0
- package/fs/base64/proof.f.d.ts +11 -0
- package/fs/base64/proof.f.js +117 -0
- package/fs/base_n/module.f.d.ts +44 -0
- package/fs/base_n/module.f.js +54 -0
- package/fs/base_n/proof.f.d.ts +10 -0
- package/fs/base_n/proof.f.js +86 -0
- package/fs/bnf/data/module.f.d.ts +4 -9
- package/fs/bnf/data/module.f.js +4 -3
- package/fs/bnf/module.f.d.ts +3 -4
- package/fs/bnf/module.f.js +3 -2
- package/fs/bnf/proof.f.d.ts +9 -0
- package/fs/bnf/proof.f.js +98 -1
- package/fs/cas/mcp/module.f.d.ts +40 -0
- package/fs/cas/mcp/module.f.js +223 -0
- package/fs/cas/mcp/proof.f.d.ts +30 -0
- package/fs/cas/mcp/proof.f.js +310 -0
- package/fs/cas/module.f.d.ts +9 -3
- package/fs/cas/module.f.js +104 -69
- package/fs/cas/proof.f.d.ts +2 -0
- package/fs/cas/proof.f.js +30 -1
- package/fs/cbase32/module.f.d.ts +1 -1
- package/fs/cbase32/module.f.js +19 -40
- package/fs/cbase32/proof.f.d.ts +1 -0
- package/fs/cbase32/proof.f.js +15 -0
- package/fs/ci/config/module.f.d.ts +6 -6
- package/fs/ci/config/module.f.js +6 -6
- package/fs/ci/deno/module.f.js +2 -2
- package/fs/ci/module.f.d.ts +5 -0
- package/fs/ci/module.f.js +2 -3
- package/fs/ci/proof.f.d.ts +1 -0
- package/fs/ci/proof.f.js +9 -3
- package/fs/cli/module.f.d.ts +3 -1
- package/fs/cli/module.f.js +22 -12
- package/fs/cli/proof.f.d.ts +5 -0
- package/fs/cli/proof.f.js +76 -0
- package/fs/crypto/hmac/proof.f.d.ts +1 -0
- package/fs/crypto/hmac/proof.f.js +8 -0
- package/fs/crypto/secp/proof.f.d.ts +1 -0
- package/fs/crypto/secp/proof.f.js +10 -0
- package/fs/crypto/sign/module.f.js +1 -4
- package/fs/dev/module.f.d.ts +5 -3
- package/fs/dev/module.f.js +13 -2
- package/fs/dev/package_json/proof.f.d.ts +1 -0
- package/fs/dev/package_json/proof.f.js +5 -0
- package/fs/dev/proof.f.d.ts +1 -0
- package/fs/dev/proof.f.js +12 -1
- package/fs/djs/ast/module.f.d.ts +1 -1
- package/fs/djs/ast/proof.f.d.ts +5 -0
- package/fs/djs/ast/proof.f.js +17 -0
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +2 -3
- package/fs/djs/serializer/module.f.js +24 -11
- package/fs/djs/transpiler/module.f.js +3 -4
- package/fs/effects/memory/module.f.d.ts +37 -0
- package/fs/effects/memory/module.f.js +24 -0
- package/fs/effects/memory/proof.f.d.ts +6 -0
- package/fs/effects/memory/proof.f.js +64 -0
- package/fs/effects/mock/module.f.d.ts +7 -2
- package/fs/effects/mock/module.f.js +12 -8
- package/fs/effects/module.d.ts +1 -1
- package/fs/effects/module.f.d.ts +41 -0
- package/fs/effects/module.f.js +25 -0
- package/fs/effects/module.js +6 -7
- package/fs/effects/node/memory/module.d.ts +20 -0
- package/fs/effects/node/memory/module.js +44 -0
- package/fs/effects/node/memory/proof.d.ts +10 -0
- package/fs/effects/node/memory/proof.js +35 -0
- package/fs/effects/node/module.d.ts +1 -1
- package/fs/effects/node/module.f.d.ts +54 -8
- package/fs/effects/node/module.f.js +55 -4
- package/fs/effects/node/module.js +167 -137
- package/fs/effects/node/proof.f.d.ts +10 -0
- package/fs/effects/node/proof.f.js +88 -21
- package/fs/effects/node/virtual/module.f.d.ts +6 -0
- package/fs/effects/node/virtual/module.f.js +48 -16
- package/fs/effects/node/virtual/proof.f.d.ts +17 -0
- package/fs/effects/node/virtual/proof.f.js +79 -0
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +54 -34
- package/fs/emergent_testing/module.f.d.ts +6 -1
- package/fs/emergent_testing/module.f.js +8 -3
- package/fs/emergent_testing/proof.f.d.ts +11 -0
- package/fs/emergent_testing/proof.f.js +52 -16
- package/fs/fjs/module.f.js +2 -2
- package/fs/fjs/proof.f.d.ts +8 -0
- package/fs/fjs/proof.f.js +39 -0
- package/fs/fsc/module.f.d.ts +5 -0
- package/fs/fsc/module.f.js +17 -27
- package/fs/fsc/proof.f.d.ts +8 -0
- package/fs/fsc/proof.f.js +48 -2
- package/fs/fsm/module.f.d.ts +2 -3
- package/fs/fsm/module.f.js +1 -1
- package/fs/html/module.f.d.ts +2 -3
- package/fs/html/module.f.js +7 -6
- package/fs/html/proof.f.d.ts +1 -0
- package/fs/html/proof.f.js +7 -0
- package/fs/js/tokenizer/module.f.js +1 -1
- package/fs/js/tokenizer/proof.f.js +7 -0
- package/fs/json/module.f.d.ts +9 -2
- package/fs/json/module.f.js +9 -7
- package/fs/json/parser/module.f.js +4 -6
- package/fs/json/parser/proof.f.js +19 -10
- package/fs/json/proof.f.d.ts +2 -1
- package/fs/json/proof.f.js +33 -6
- package/fs/json/rpc/module.f.d.ts +5 -0
- package/fs/json/rpc/module.f.js +16 -11
- package/fs/json/schema/module.f.d.ts +15 -14
- package/fs/json/schema/proof.f.d.ts +3 -0
- package/fs/json/schema/proof.f.js +16 -1
- package/fs/mcp/module.f.d.ts +103 -5
- package/fs/mcp/module.f.js +139 -2
- package/fs/mcp/proof.f.d.ts +34 -0
- package/fs/mcp/proof.f.js +207 -0
- package/fs/mcp/stdio/module.f.d.ts +39 -0
- package/fs/mcp/stdio/module.f.js +54 -0
- package/fs/mcp/stdio/proof.f.d.ts +10 -0
- package/fs/mcp/stdio/proof.f.js +92 -0
- package/fs/mime/module.f.d.ts +39 -0
- package/fs/mime/module.f.js +78 -0
- package/fs/mime/proof.f.d.ts +15 -0
- package/fs/mime/proof.f.js +30 -0
- package/fs/path/module.f.d.ts +8 -0
- package/fs/path/module.f.js +10 -3
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/text/sgr/proof.f.js +30 -2
- package/fs/text/utf8/module.f.d.ts +7 -0
- package/fs/text/utf8/module.f.js +85 -31
- package/fs/text/utf8/proof.f.d.ts +1 -0
- package/fs/text/utf8/proof.f.js +60 -1
- package/fs/types/bigfloat/module.f.d.ts +3 -0
- package/fs/types/bigfloat/module.f.js +30 -31
- package/fs/types/bigint/module.f.d.ts +24 -2
- package/fs/types/bigint/module.f.js +30 -2
- package/fs/types/bigint/proof.f.d.ts +7 -0
- package/fs/types/bigint/proof.f.js +47 -16
- package/fs/types/bit_vec/module.f.d.ts +18 -0
- package/fs/types/bit_vec/module.f.js +18 -0
- package/fs/types/bit_vec/proof.f.d.ts +1 -0
- package/fs/types/bit_vec/proof.f.js +13 -1
- package/fs/types/function/operator/proof.f.d.ts +2 -0
- package/fs/types/function/operator/proof.f.js +20 -2
- package/fs/types/list/module.f.js +1 -4
- package/fs/types/list/proof.f.d.ts +1 -1
- package/fs/types/list/proof.f.js +14 -6
- package/fs/types/map/proof.f.d.ts +1 -0
- package/fs/types/map/proof.f.js +10 -1
- package/fs/types/nibble_set/module.f.d.ts +13 -0
- package/fs/types/nibble_set/module.f.js +16 -0
- package/fs/types/object/module.f.d.ts +12 -8
- package/fs/types/object/module.f.js +9 -2
- package/fs/types/object/proof.f.d.ts +14 -0
- package/fs/types/object/proof.f.js +4 -0
- package/fs/types/ordered_map/proof.f.d.ts +1 -0
- package/fs/types/ordered_map/proof.f.js +39 -79
- package/fs/types/phantom/module.f.d.ts +22 -0
- package/fs/types/phantom/module.f.js +11 -0
- package/fs/types/prime_field/proof.f.d.ts +7 -0
- package/fs/types/prime_field/proof.f.js +63 -122
- package/fs/types/rtti/common/module.f.d.ts +4 -4
- package/fs/types/rtti/common/module.f.js +1 -1
- package/fs/types/rtti/module.f.d.ts +2 -3
- package/fs/types/rtti/parse/module.f.js +0 -1
- package/fs/types/rtti/ts/module.f.d.ts +26 -32
- package/fs/types/rtti/ts/module.f.js +2 -2
- package/fs/types/rtti/ts/proof.f.js +3 -3
- package/fs/types/rtti/validate/module.f.js +0 -1
- package/fs/types/sorted_list/module.f.d.ts +6 -0
- package/fs/types/sorted_list/module.f.js +11 -0
- package/fs/types/sorted_set/module.f.js +2 -8
- package/fs/types/ts/module.f.d.ts +1 -1
- package/fs/types/ts/module.f.js +3 -1
- package/fs/types/ts/proof.f.js +3 -2
- package/fs/website/proof.f.d.ts +1 -0
- package/fs/website/proof.f.js +8 -1
- package/package.json +3 -3
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js effect operations: filesystem (`mkdir`, `readFile`, `readdir`,
|
|
3
|
-
* `writeFile`, `rm`, `access
|
|
3
|
+
* `writeFile`, `rm`, `access`, plus the `readUtf8File`/`writeUtf8File` text
|
|
4
|
+
* helpers), networking (`fetch`, `createServer`, `listen`),
|
|
4
5
|
* subprocess `exec`, `log`/`error` (wrappers over `write`), `import_`, `now`,
|
|
5
6
|
* `sandbox`, `forever`, and `all`/`both` parallelism; defines the
|
|
6
7
|
* `NodeOp`/`NodeProgram` types used by the Node runner.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
|
-
import { utf8 } from "../../text/module.f.js";
|
|
11
|
+
import { utf8, utf8ToString } from "../../text/module.f.js";
|
|
12
|
+
import { toCodePointList } from "../../text/utf8/module.f.js";
|
|
13
|
+
import { codePointListToString } from "../../text/utf16/module.f.js";
|
|
14
|
+
import { reverse } from "../../types/list/module.f.js";
|
|
15
|
+
import { ok } from "../../types/result/module.f.js";
|
|
11
16
|
import { do_, pure } from "../module.f.js";
|
|
12
|
-
|
|
17
|
+
/**
|
|
18
|
+
* True if `e` is a "file or directory does not exist" (`ENOENT`) error.
|
|
19
|
+
*
|
|
20
|
+
* Node's filesystem rejections are `Error`s carrying `code: 'ENOENT'`; the
|
|
21
|
+
* virtual interpreter mirrors that shape for absent paths. Lets callers swallow
|
|
22
|
+
* only the missing-path case (e.g. a fresh store) while propagating genuine
|
|
23
|
+
* failures (permissions, corruption) rather than masking them.
|
|
24
|
+
*/
|
|
25
|
+
export const isNotFound = (e) => typeof e === 'object' && e !== null && e.code === 'ENOENT';
|
|
13
26
|
/**
|
|
14
27
|
* To run the operation `O` should be known by the runner/engine.
|
|
15
28
|
* This is the reason why we merge `O` with `All` in the resulted `Effect`.
|
|
@@ -17,13 +30,23 @@ const doAll = do_('all');
|
|
|
17
30
|
* @param a
|
|
18
31
|
* @returns
|
|
19
32
|
*/
|
|
20
|
-
export const all = (
|
|
33
|
+
export const all = do_('all');
|
|
21
34
|
export const both = (a) => (b) => all(a, b);
|
|
22
35
|
export const fetch = do_('fetch');
|
|
23
36
|
export const mkdir = do_('mkdir');
|
|
24
37
|
export const readFile = do_('readFile');
|
|
38
|
+
/**
|
|
39
|
+
* Reads a file as UTF-8 text.
|
|
40
|
+
*
|
|
41
|
+
* Preserves the `IoResult` instead of unwrapping so callers can pattern-match
|
|
42
|
+
* on errors (e.g. convert them into domain-specific errors) or `unwrap` at the
|
|
43
|
+
* call site.
|
|
44
|
+
*/
|
|
45
|
+
export const readUtf8File = (path) => readFile(path).step(r => pure(r[0] === 'ok' ? ok(utf8ToString(r[1])) : r));
|
|
25
46
|
export const readdir = do_('readdir');
|
|
26
47
|
export const writeFile = do_('writeFile');
|
|
48
|
+
/** Writes a string to `path` as UTF-8 bytes. */
|
|
49
|
+
export const writeUtf8File = (path, content) => writeFile(path, utf8(content));
|
|
27
50
|
export const rm = do_('rm');
|
|
28
51
|
export const exec = do_('exec');
|
|
29
52
|
export const access = do_('access');
|
|
@@ -42,6 +65,34 @@ const writeString = (stream) => (s) => write(stream, utf8(s + '\n'));
|
|
|
42
65
|
export const log = writeString('stdout');
|
|
43
66
|
/** Writes a line to `stderr`. Replaces the retired `Error` effect. */
|
|
44
67
|
export const error = writeString('stderr');
|
|
68
|
+
/** Emits a `Read` effect, yielding the next input byte or `null` at EOF. */
|
|
69
|
+
export const read = do_('read');
|
|
70
|
+
/** Decodes accumulated UTF-8 bytes (MSB-first, already in order) into a string. */
|
|
71
|
+
const utf8ListToString = (bytes) => codePointListToString(toCodePointList(bytes));
|
|
72
|
+
/** The line-feed byte (`\n`) that terminates a line. */
|
|
73
|
+
const lf = 0x0a;
|
|
74
|
+
/**
|
|
75
|
+
* Reads one line from `stream` as a pure combinator over the byte-level
|
|
76
|
+
* {@link read}: accumulates bytes until a `\n` terminator or EOF, then
|
|
77
|
+
* UTF-8-decodes them. The terminator is consumed but excluded from the result.
|
|
78
|
+
*
|
|
79
|
+
* Reading a single byte per step means a line never over-reads past its
|
|
80
|
+
* terminator, so no leftover-byte buffer has to survive between calls — each
|
|
81
|
+
* `readLine` is self-contained. Yields `null` only at EOF with nothing
|
|
82
|
+
* buffered; a final line lacking a trailing newline is returned in full.
|
|
83
|
+
*
|
|
84
|
+
* Bytes accumulate into a cons-list by prepending (O(1) per byte) and are
|
|
85
|
+
* reversed and decoded once at the terminator, so a large line costs O(n)
|
|
86
|
+
* rather than the O(n²) of copying a growing array on every byte.
|
|
87
|
+
*/
|
|
88
|
+
export const readLine = (stream) => {
|
|
89
|
+
const loop = (acc) => read(stream).step(b => b === null
|
|
90
|
+
? pure(acc === null ? null : utf8ListToString(reverse(acc)))
|
|
91
|
+
: b === lf
|
|
92
|
+
? pure(utf8ListToString(reverse(acc)))
|
|
93
|
+
: loop({ first: b, tail: acc }));
|
|
94
|
+
return loop(null);
|
|
95
|
+
};
|
|
45
96
|
export const now = do_('now');
|
|
46
97
|
/**
|
|
47
98
|
* Runs a plain synchronous function in an isolated, measured environment.
|
|
@@ -6,20 +6,42 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Node.js effect runner: interprets `Effect<NodeOp, T>` directly against the
|
|
11
|
+
* Node globals and built-in modules (`fs`, `http`, `child_process`, `process`,
|
|
12
|
+
* `fetch`, …).
|
|
13
|
+
*
|
|
14
|
+
* There is deliberately no injectable IO seam here. Effectful programs are
|
|
15
|
+
* tested against the in-memory interpreters in `fs/effects/mock` and
|
|
16
|
+
* `fs/effects/node/virtual`, which interpret the same operations without
|
|
17
|
+
* touching the OS, so a handler-table indirection in this module would have
|
|
18
|
+
* exactly one instance and no consumer — the handlers reference the Node
|
|
19
|
+
* globals directly instead.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
9
23
|
import http from 'node:http';
|
|
10
24
|
import childProcess from 'node:child_process';
|
|
11
25
|
import fs from 'node:fs';
|
|
26
|
+
import os from 'node:os';
|
|
12
27
|
import process from 'node:process';
|
|
13
28
|
import { once } from 'node:events';
|
|
14
29
|
import * as testContext from 'node:test';
|
|
15
|
-
import { concat } from "../../path/module.f.js";
|
|
16
|
-
import { normalize } from "../../path/module.f.js";
|
|
30
|
+
import { concat, normalize, toPosix } from "../../path/module.f.js";
|
|
17
31
|
import {} from "../module.f.js";
|
|
18
32
|
import { asyncRun } from "../module.js";
|
|
33
|
+
import { memoryOperationMap } from "./memory/module.js";
|
|
19
34
|
import {} from "./module.f.js";
|
|
20
35
|
import { asBase, asNominal } from "../../types/nominal/module.f.js";
|
|
21
36
|
import { error, ok } from "../../types/result/module.f.js";
|
|
22
37
|
import { fromVec, listToVec, toVec } from "../../types/uint8array/module.f.js";
|
|
38
|
+
/**
|
|
39
|
+
* Narrowed structural view of `node:http`'s `createServer`. The official types
|
|
40
|
+
* declare `method`/`url` optional and header values as
|
|
41
|
+
* `string | string[] | undefined`, while the effect-level `RequestListener`
|
|
42
|
+
* requires them present; this local view keeps the narrowing in one place.
|
|
43
|
+
*/
|
|
44
|
+
const createServer = http.createServer;
|
|
23
45
|
const tc = async (f) => {
|
|
24
46
|
try {
|
|
25
47
|
return ok(await f());
|
|
@@ -35,97 +57,34 @@ const collect = async (v) => {
|
|
|
35
57
|
}
|
|
36
58
|
return result;
|
|
37
59
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
return toVec(new Uint8Array(await response.arrayBuffer()));
|
|
47
|
-
}),
|
|
48
|
-
mkdir: (...p) => tc(async () => { await mkdir(...p); }),
|
|
49
|
-
readFile: path => tc(async () => toVec(await readFile(path))),
|
|
50
|
-
readdir: (path, r) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
|
|
51
|
-
.map(v => ({
|
|
52
|
-
name: v.name,
|
|
53
|
-
parentPath: normalize(v.parentPath),
|
|
54
|
-
isFile: v.isFile()
|
|
55
|
-
}))),
|
|
56
|
-
writeFile: (path, data) => tc(() => writeFile(path, fromVec(data))),
|
|
57
|
-
rm: path => tc(() => rm(path)),
|
|
58
|
-
access: path => tc(() => access(path)),
|
|
59
|
-
import: path => tc(() => asyncImport(path)),
|
|
60
|
-
exec: (command, stdin) => new Promise(resolve => {
|
|
61
|
-
const child = childProcess.exec(command, (e, stdout, stderr) => resolve(e !== null ? ['error', e] : ok({ stdout, stderr })));
|
|
62
|
-
child.stdin?.end(stdin);
|
|
63
|
-
}),
|
|
64
|
-
createServer: async (requestListener) => {
|
|
65
|
-
const erl = requestListener;
|
|
66
|
-
const nodeRl = async (req, res) => {
|
|
67
|
-
const reqBody = await collect(req);
|
|
68
|
-
const { method, url, headers } = req;
|
|
69
|
-
const { status, headers: outHeaders, body: outBody } = await result(erl({
|
|
70
|
-
method,
|
|
71
|
-
url,
|
|
72
|
-
headers,
|
|
73
|
-
body: listToVec(reqBody)
|
|
74
|
-
}));
|
|
75
|
-
res
|
|
76
|
-
.writeHead(status, outHeaders)
|
|
77
|
-
.end(fromVec(outBody));
|
|
78
|
-
};
|
|
79
|
-
const server = asNominal(createServer(nodeRl));
|
|
80
|
-
return server;
|
|
81
|
-
},
|
|
82
|
-
listen: async (server, port) => {
|
|
83
|
-
const s = asBase(server);
|
|
84
|
-
s.listen(port);
|
|
85
|
-
},
|
|
86
|
-
forever: () => new Promise(() => { }),
|
|
87
|
-
now: async () => ioNow(),
|
|
88
|
-
sandbox,
|
|
89
|
-
await: awaitPromise,
|
|
90
|
-
write,
|
|
91
|
-
test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => result(test(t))),
|
|
92
|
-
});
|
|
93
|
-
return result;
|
|
94
|
-
};
|
|
95
|
-
const runProgram = (io) => {
|
|
96
|
-
const { process: { env, stdout, stderr }, testContext, bunTestContext, playwrightTestContext, engine } = io;
|
|
97
|
-
const std = { stdout, stderr };
|
|
98
|
-
const f = fromIo(io);
|
|
99
|
-
return args => {
|
|
100
|
-
const options = { args, env, std, testContext, bunTestContext, playwrightTestContext, engine };
|
|
101
|
-
return program => f(program(options));
|
|
102
|
-
};
|
|
60
|
+
const { mkdir, readFile, readdir, writeFile, rm, access } = fs.promises;
|
|
61
|
+
const { exec } = childProcess;
|
|
62
|
+
const prefix = 'file:///';
|
|
63
|
+
const asyncImport = (v) => {
|
|
64
|
+
const s0 = v.includes(':') || v.startsWith('/') ? v : concat(process.cwd())(v);
|
|
65
|
+
const s1 = s0.startsWith(prefix) ? s0 : `${prefix}${s0}`;
|
|
66
|
+
return import(__rewriteRelativeImportExtension(s1));
|
|
103
67
|
};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
68
|
+
const sandbox = async (f) => {
|
|
69
|
+
let result;
|
|
70
|
+
let after;
|
|
71
|
+
const before = performance.now();
|
|
72
|
+
try {
|
|
73
|
+
let p = f();
|
|
74
|
+
after = performance.now();
|
|
75
|
+
if (p instanceof Promise) {
|
|
76
|
+
p = await p;
|
|
77
|
+
after = performance.now();
|
|
115
78
|
}
|
|
116
|
-
|
|
79
|
+
result = ok(p);
|
|
117
80
|
}
|
|
118
|
-
|
|
119
|
-
|
|
81
|
+
catch (e) {
|
|
82
|
+
after = performance.now();
|
|
83
|
+
result = error(e);
|
|
120
84
|
}
|
|
85
|
+
return { result, duration: after - before };
|
|
121
86
|
};
|
|
122
|
-
const
|
|
123
|
-
const wrapInlineTest = (register) => ({
|
|
124
|
-
test: (name, opts, fn) => register(name, () => inlineTest(name, opts, fn))
|
|
125
|
-
});
|
|
126
|
-
const bunTestContext = wrapInlineTest(testContext.test);
|
|
127
|
-
const playwrightTestContext = wrapInlineTest(pwTest);
|
|
128
|
-
const prefix = 'file:///';
|
|
87
|
+
const awaitPromise = async (p) => [p instanceof Promise ? await p : p];
|
|
129
88
|
const { now } = Date;
|
|
130
89
|
/** Maps `WriteConsoles` names to the corresponding Node.js writable streams. */
|
|
131
90
|
const streams = {
|
|
@@ -151,67 +110,138 @@ const writeAll = async (stream, data) => {
|
|
|
151
110
|
await once(stream, 'drain');
|
|
152
111
|
}
|
|
153
112
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Resolves `true` once stdin reaches EOF, or `false` as soon as more data is
|
|
115
|
+
* readable. Both listeners are removed the moment either fires, so a
|
|
116
|
+
* long-running server that idles between messages never accumulates leftover
|
|
117
|
+
* `'readable'`/`'end'` listeners (which would eventually trip
|
|
118
|
+
* `MaxListenersExceededWarning`).
|
|
119
|
+
*/
|
|
120
|
+
const waitReadableOrEnd = (stdin) => new Promise(resolve => {
|
|
121
|
+
const cleanup = () => {
|
|
122
|
+
stdin.removeListener('readable', onReadable);
|
|
123
|
+
stdin.removeListener('end', onEnd);
|
|
124
|
+
};
|
|
125
|
+
const onReadable = () => { cleanup(); resolve(false); };
|
|
126
|
+
const onEnd = () => { cleanup(); resolve(true); };
|
|
127
|
+
stdin.once('readable', onReadable);
|
|
128
|
+
stdin.once('end', onEnd);
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Reads one byte from `process.stdin`, or `null` at EOF.
|
|
132
|
+
*
|
|
133
|
+
* `read(1)` returns `null` both at end-of-stream and when no byte is buffered
|
|
134
|
+
* yet, so the two are told apart by waiting on `'readable'` (more data) vs
|
|
135
|
+
* `'end'` (EOF). The line framing lives in the pure `readLine` combinator; this
|
|
136
|
+
* interpreter is deliberately just "next byte".
|
|
137
|
+
*/
|
|
138
|
+
const readStdinByte = async () => {
|
|
139
|
+
const stdin = process.stdin;
|
|
140
|
+
while (true) {
|
|
141
|
+
const chunk = stdin.read(1);
|
|
142
|
+
if (chunk !== null) {
|
|
143
|
+
return chunk[0];
|
|
144
|
+
}
|
|
145
|
+
if (stdin.readableEnded) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
if (await waitReadableOrEnd(stdin)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
161
151
|
}
|
|
162
152
|
};
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const s1 = s0.startsWith(prefix) ? s0 : `${prefix}${s0}`;
|
|
171
|
-
return asyncImport(s1);
|
|
172
|
-
},
|
|
173
|
-
performance,
|
|
174
|
-
fetch,
|
|
175
|
-
tryCatch,
|
|
176
|
-
asyncTryCatch: async (f) => {
|
|
177
|
-
try {
|
|
178
|
-
return ok(await f());
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
return error(e);
|
|
153
|
+
const runNodeEffect = asyncRun({
|
|
154
|
+
...memoryOperationMap(),
|
|
155
|
+
all: async (...effects) => await Promise.all(effects.map(runNodeEffect)),
|
|
156
|
+
fetch: async (url) => tc(async () => {
|
|
157
|
+
const response = await fetch(url);
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw new Error(`Fetch error: ${response.status} ${response.statusText}`);
|
|
182
160
|
}
|
|
161
|
+
return toVec(new Uint8Array(await response.arrayBuffer()));
|
|
162
|
+
}),
|
|
163
|
+
mkdir: (...p) => tc(async () => { await mkdir(...p); }),
|
|
164
|
+
readFile: path => tc(async () => toVec(await readFile(path))),
|
|
165
|
+
readdir: (path, r) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
|
|
166
|
+
.map(v => ({
|
|
167
|
+
name: v.name,
|
|
168
|
+
parentPath: normalize(v.parentPath),
|
|
169
|
+
isFile: v.isFile()
|
|
170
|
+
}))),
|
|
171
|
+
writeFile: (path, data) => tc(() => writeFile(path, fromVec(data))),
|
|
172
|
+
rm: path => tc(() => rm(path)),
|
|
173
|
+
access: path => tc(() => access(path)),
|
|
174
|
+
import: path => tc(() => asyncImport(path)),
|
|
175
|
+
exec: (command, stdin) => new Promise(resolve => {
|
|
176
|
+
const child = exec(command, (e, stdout, stderr) => resolve(e !== null ? ['error', e] : ok({ stdout, stderr })));
|
|
177
|
+
child.stdin?.end(stdin);
|
|
178
|
+
}),
|
|
179
|
+
createServer: async (requestListener) => {
|
|
180
|
+
const erl = requestListener;
|
|
181
|
+
const nodeRl = async (req, res) => {
|
|
182
|
+
const reqBody = await collect(req);
|
|
183
|
+
const { method, url, headers } = req;
|
|
184
|
+
const { status, headers: outHeaders, body: outBody } = await runNodeEffect(erl({
|
|
185
|
+
method,
|
|
186
|
+
url,
|
|
187
|
+
headers,
|
|
188
|
+
body: listToVec(reqBody)
|
|
189
|
+
}));
|
|
190
|
+
res
|
|
191
|
+
.writeHead(status, outHeaders)
|
|
192
|
+
.end(fromVec(outBody));
|
|
193
|
+
};
|
|
194
|
+
const server = asNominal(createServer(nodeRl));
|
|
195
|
+
return server;
|
|
183
196
|
},
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
listen: async (server, port) => {
|
|
198
|
+
const s = asBase(server);
|
|
199
|
+
s.listen(port);
|
|
200
|
+
},
|
|
201
|
+
forever: () => new Promise(() => { }),
|
|
202
|
+
now: async () => now(),
|
|
203
|
+
sandbox,
|
|
204
|
+
await: awaitPromise,
|
|
205
|
+
write: (stream, data) => writeAll(streams[stream], fromVec(data)),
|
|
206
|
+
read: readStdinByte,
|
|
207
|
+
test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => runNodeEffect(test(t))),
|
|
208
|
+
});
|
|
209
|
+
const isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
|
|
210
|
+
const pwTest = isPlaywright
|
|
211
|
+
? (await import('@playwright/test')).test
|
|
212
|
+
: undefined;
|
|
213
|
+
const inlineTest = async (name, { expectFailure }, fn) => {
|
|
214
|
+
if (expectFailure) {
|
|
191
215
|
try {
|
|
192
|
-
|
|
193
|
-
after = performance.now();
|
|
194
|
-
if (p instanceof Promise) {
|
|
195
|
-
p = await p;
|
|
196
|
-
after = performance.now();
|
|
197
|
-
}
|
|
198
|
-
result = ok(p);
|
|
216
|
+
await fn(inlineContext);
|
|
199
217
|
}
|
|
200
|
-
catch
|
|
201
|
-
|
|
202
|
-
result = error(e);
|
|
218
|
+
catch {
|
|
219
|
+
return;
|
|
203
220
|
}
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
221
|
+
throw new Error(`expected to throw: ${name}`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
await fn(inlineContext);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const inlineContext = { test: inlineTest };
|
|
228
|
+
const wrapInlineTest = (register) => ({
|
|
229
|
+
test: (name, opts, fn) => register(name, () => inlineTest(name, opts, fn))
|
|
230
|
+
});
|
|
231
|
+
const bunTestContext = wrapInlineTest(testContext.test);
|
|
232
|
+
const playwrightTestContext = wrapInlineTest(pwTest);
|
|
233
|
+
const options = {
|
|
234
|
+
args: process.argv.slice(2),
|
|
235
|
+
env: process.env,
|
|
236
|
+
home: toPosix(os.homedir()),
|
|
237
|
+
std: { stdout: process.stdout, stderr: process.stderr },
|
|
208
238
|
testContext,
|
|
209
239
|
bunTestContext,
|
|
210
240
|
playwrightTestContext,
|
|
211
241
|
engine: isPlaywright ? 'playwright' : 'Bun' in globalThis ? 'bun' : 'node',
|
|
212
242
|
};
|
|
213
243
|
/**
|
|
214
|
-
* Runs a `NodeProgram` against the real Node
|
|
244
|
+
* Runs a `NodeProgram` against the real Node globals and process arguments,
|
|
215
245
|
* resolving to its exit code **without** terminating the process.
|
|
216
246
|
*
|
|
217
247
|
* Use this when the caller must stay alive afterwards — e.g. when proofs are
|
|
@@ -219,7 +249,7 @@ const io = {
|
|
|
219
249
|
* that owns the process lifecycle. For a standalone CLI entry point that should
|
|
220
250
|
* exit with the program's code, use {@link run} instead.
|
|
221
251
|
*/
|
|
222
|
-
export const runEffect =
|
|
252
|
+
export const runEffect = program => runNodeEffect(program(options));
|
|
223
253
|
/**
|
|
224
254
|
* CLI entry point: runs a `NodeProgram` via {@link runEffect}, then calls
|
|
225
255
|
* `process.exit` with its exit code. The `Promise<never>` return type reflects
|
|
@@ -12,6 +12,10 @@ export declare const proof: {
|
|
|
12
12
|
noSuchFile: () => void;
|
|
13
13
|
nestedPath: () => void;
|
|
14
14
|
};
|
|
15
|
+
readUtf8File: {
|
|
16
|
+
ok: () => void;
|
|
17
|
+
noSuchFile: () => void;
|
|
18
|
+
};
|
|
15
19
|
readdir: {
|
|
16
20
|
one: () => void;
|
|
17
21
|
nonRecursive: () => void;
|
|
@@ -24,15 +28,21 @@ export declare const proof: {
|
|
|
24
28
|
nestedPath: () => void;
|
|
25
29
|
directory: () => void;
|
|
26
30
|
};
|
|
31
|
+
writeUtf8File: () => void;
|
|
27
32
|
rm: {
|
|
28
33
|
one: () => void;
|
|
29
34
|
nested: () => void;
|
|
30
35
|
noSuchFile: () => void;
|
|
31
36
|
isDirectory: () => void;
|
|
32
37
|
};
|
|
38
|
+
both: () => void;
|
|
33
39
|
now: () => void;
|
|
34
40
|
sandbox: {
|
|
35
41
|
ok: () => void;
|
|
36
42
|
error: () => void;
|
|
37
43
|
};
|
|
44
|
+
memory: {
|
|
45
|
+
createAndRead: () => void;
|
|
46
|
+
createAndWrite: () => void;
|
|
47
|
+
};
|
|
38
48
|
};
|
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
import { empty, isVec, uint, vec8 } from "../../types/bit_vec/module.f.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { utf8, utf8ToString } from "../../text/module.f.js";
|
|
3
|
+
import { decode, pure } from "../module.f.js";
|
|
4
|
+
import { both, fetch, mkdir, now, readdir, readFile, readUtf8File, rm, sandbox, writeFile, writeUtf8File } from "./module.f.js";
|
|
5
|
+
import { create as memCreate, read as memRead, write as memWrite } from "../memory/module.f.js";
|
|
4
6
|
import { emptyState, virtual } from "./virtual/module.f.js";
|
|
5
7
|
export const proof = {
|
|
6
8
|
map: () => {
|
|
7
|
-
|
|
9
|
+
const e = readFile('hello').step(([k, v]) => {
|
|
8
10
|
if (k === 'error') {
|
|
9
11
|
throw v;
|
|
10
12
|
}
|
|
11
13
|
return pure(uint(v) * 2n);
|
|
12
14
|
});
|
|
13
15
|
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
if (p[0] !== 'hello') {
|
|
28
|
-
throw p;
|
|
29
|
-
}
|
|
30
|
-
e = cont(['ok', vec8(0x15n)]);
|
|
16
|
+
let d = decode(e);
|
|
17
|
+
while (!d.done) {
|
|
18
|
+
if (d.command !== 'readFile') {
|
|
19
|
+
throw d.command;
|
|
20
|
+
}
|
|
21
|
+
if (d.payload[0] !== 'hello') {
|
|
22
|
+
throw d.payload;
|
|
23
|
+
}
|
|
24
|
+
d = decode(d.continuation(['ok', vec8(0x15n)]));
|
|
25
|
+
}
|
|
26
|
+
if (d.result !== 0x2an) {
|
|
27
|
+
throw d.result;
|
|
31
28
|
}
|
|
32
29
|
},
|
|
33
30
|
fetch: () => {
|
|
@@ -127,11 +124,31 @@ export const proof = {
|
|
|
127
124
|
if (t !== 'error') {
|
|
128
125
|
throw result;
|
|
129
126
|
}
|
|
130
|
-
if (result !== '
|
|
127
|
+
if (result.code !== 'ENOENT') {
|
|
131
128
|
throw result;
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
131
|
},
|
|
132
|
+
readUtf8File: {
|
|
133
|
+
ok: () => {
|
|
134
|
+
const [_, [t, result]] = virtual({
|
|
135
|
+
...emptyState,
|
|
136
|
+
root: { hello: utf8('Hello, world!') },
|
|
137
|
+
})(readUtf8File('hello'));
|
|
138
|
+
if (t !== 'ok') {
|
|
139
|
+
throw result;
|
|
140
|
+
}
|
|
141
|
+
if (result !== 'Hello, world!') {
|
|
142
|
+
throw result;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
noSuchFile: () => {
|
|
146
|
+
const [_, [t, result]] = virtual(emptyState)(readUtf8File('hello'));
|
|
147
|
+
if (t !== 'error') {
|
|
148
|
+
throw result;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
},
|
|
135
152
|
readdir: {
|
|
136
153
|
one: () => {
|
|
137
154
|
const [_, [t, result]] = virtual({
|
|
@@ -272,6 +289,19 @@ export const proof = {
|
|
|
272
289
|
}
|
|
273
290
|
},
|
|
274
291
|
},
|
|
292
|
+
writeUtf8File: () => {
|
|
293
|
+
const [state, [t, result]] = virtual(emptyState)(writeUtf8File('hello', 'Hello, world!'));
|
|
294
|
+
if (t !== 'ok') {
|
|
295
|
+
throw result;
|
|
296
|
+
}
|
|
297
|
+
const file = state.root.hello;
|
|
298
|
+
if (!isVec(file)) {
|
|
299
|
+
throw file;
|
|
300
|
+
}
|
|
301
|
+
if (utf8ToString(file) !== 'Hello, world!') {
|
|
302
|
+
throw file;
|
|
303
|
+
}
|
|
304
|
+
},
|
|
275
305
|
rm: {
|
|
276
306
|
one: () => {
|
|
277
307
|
const [state, [t, result]] = virtual({
|
|
@@ -326,6 +356,27 @@ export const proof = {
|
|
|
326
356
|
}
|
|
327
357
|
},
|
|
328
358
|
},
|
|
359
|
+
both: () => {
|
|
360
|
+
const [_, results] = virtual({
|
|
361
|
+
...emptyState,
|
|
362
|
+
root: {
|
|
363
|
+
a: vec8(0x2an),
|
|
364
|
+
b: vec8(0x15n),
|
|
365
|
+
},
|
|
366
|
+
})(both(readFile('a'))(readFile('b')));
|
|
367
|
+
if (results[0][0] !== 'ok') {
|
|
368
|
+
throw results[0];
|
|
369
|
+
}
|
|
370
|
+
if (results[1][0] !== 'ok') {
|
|
371
|
+
throw results[1];
|
|
372
|
+
}
|
|
373
|
+
if (uint(results[0][1]) !== 0x2an) {
|
|
374
|
+
throw results[0][1];
|
|
375
|
+
}
|
|
376
|
+
if (uint(results[1][1]) !== 0x15n) {
|
|
377
|
+
throw results[1][1];
|
|
378
|
+
}
|
|
379
|
+
},
|
|
329
380
|
now: () => {
|
|
330
381
|
const [_, result] = virtual({ ...emptyState, epochNs: 1_000_000 })(now());
|
|
331
382
|
if (result !== 1_000_000) {
|
|
@@ -359,4 +410,20 @@ export const proof = {
|
|
|
359
410
|
}
|
|
360
411
|
},
|
|
361
412
|
},
|
|
413
|
+
memory: {
|
|
414
|
+
createAndRead: () => {
|
|
415
|
+
const effect = memCreate(42).step(key => memRead(key));
|
|
416
|
+
const [_, value] = virtual(emptyState)(effect);
|
|
417
|
+
if (value !== 42) {
|
|
418
|
+
throw value;
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
createAndWrite: () => {
|
|
422
|
+
const effect = memCreate(1).step(key => memWrite(key, 99).step(() => memRead(key)));
|
|
423
|
+
const [_, value] = virtual(emptyState)(effect);
|
|
424
|
+
if (value !== 99) {
|
|
425
|
+
throw value;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
},
|
|
362
429
|
};
|
|
@@ -17,11 +17,17 @@ export type Dir = {
|
|
|
17
17
|
export type State = {
|
|
18
18
|
stdout: string;
|
|
19
19
|
stderr: string;
|
|
20
|
+
/** Remaining stdin bytes; each `read` pops the first, `null` at EOF. */
|
|
21
|
+
stdin: readonly number[];
|
|
20
22
|
root: Dir;
|
|
21
23
|
internet: {
|
|
22
24
|
readonly [url: string]: Vec;
|
|
23
25
|
};
|
|
24
26
|
epochNs: number;
|
|
27
|
+
memoryNext: number;
|
|
28
|
+
memoryValues: {
|
|
29
|
+
readonly [key: string]: unknown;
|
|
30
|
+
};
|
|
25
31
|
};
|
|
26
32
|
export declare const emptyState: State;
|
|
27
33
|
export declare const virtual: RunInstance<NodeOp, State>;
|