functionalscript 0.29.1 → 0.31.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/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/bun/module.f.d.ts +2 -2
- package/fs/ci/bun/module.f.js +7 -14
- package/fs/ci/common/module.f.d.ts +3 -1
- package/fs/ci/common/module.f.js +9 -2
- package/fs/ci/config/module.f.d.ts +7 -5
- package/fs/ci/config/module.f.js +11 -5
- package/fs/ci/deno/module.f.d.ts +1 -1
- package/fs/ci/deno/module.f.js +7 -5
- package/fs/ci/module.f.d.ts +6 -3
- package/fs/ci/module.f.js +24 -54
- package/fs/ci/node/module.f.d.ts +3 -3
- package/fs/ci/node/module.f.js +30 -26
- package/fs/ci/playwright/module.f.js +1 -1
- package/fs/ci/proof.f.d.ts +6 -7
- package/fs/ci/proof.f.js +63 -36
- package/fs/ci/rust/module.f.d.ts +2 -1
- package/fs/ci/rust/module.f.js +33 -16
- 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 +166 -135
- 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,9 +6,24 @@ 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';
|
|
@@ -16,10 +31,18 @@ import { concat } from "../../path/module.f.js";
|
|
|
16
31
|
import { normalize } from "../../path/module.f.js";
|
|
17
32
|
import {} from "../module.f.js";
|
|
18
33
|
import { asyncRun } from "../module.js";
|
|
34
|
+
import { memoryOperationMap } from "./memory/module.js";
|
|
19
35
|
import {} from "./module.f.js";
|
|
20
36
|
import { asBase, asNominal } from "../../types/nominal/module.f.js";
|
|
21
37
|
import { error, ok } from "../../types/result/module.f.js";
|
|
22
38
|
import { fromVec, listToVec, toVec } from "../../types/uint8array/module.f.js";
|
|
39
|
+
/**
|
|
40
|
+
* Narrowed structural view of `node:http`'s `createServer`. The official types
|
|
41
|
+
* declare `method`/`url` optional and header values as
|
|
42
|
+
* `string | string[] | undefined`, while the effect-level `RequestListener`
|
|
43
|
+
* requires them present; this local view keeps the narrowing in one place.
|
|
44
|
+
*/
|
|
45
|
+
const createServer = http.createServer;
|
|
23
46
|
const tc = async (f) => {
|
|
24
47
|
try {
|
|
25
48
|
return ok(await f());
|
|
@@ -35,97 +58,34 @@ const collect = async (v) => {
|
|
|
35
58
|
}
|
|
36
59
|
return result;
|
|
37
60
|
};
|
|
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
|
-
};
|
|
61
|
+
const { mkdir, readFile, readdir, writeFile, rm, access } = fs.promises;
|
|
62
|
+
const { exec } = childProcess;
|
|
63
|
+
const prefix = 'file:///';
|
|
64
|
+
const asyncImport = (v) => {
|
|
65
|
+
const s0 = v.includes(':') || v.startsWith('/') ? v : concat(process.cwd())(v);
|
|
66
|
+
const s1 = s0.startsWith(prefix) ? s0 : `${prefix}${s0}`;
|
|
67
|
+
return import(__rewriteRelativeImportExtension(s1));
|
|
103
68
|
};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
69
|
+
const sandbox = async (f) => {
|
|
70
|
+
let result;
|
|
71
|
+
let after;
|
|
72
|
+
const before = performance.now();
|
|
73
|
+
try {
|
|
74
|
+
let p = f();
|
|
75
|
+
after = performance.now();
|
|
76
|
+
if (p instanceof Promise) {
|
|
77
|
+
p = await p;
|
|
78
|
+
after = performance.now();
|
|
115
79
|
}
|
|
116
|
-
|
|
80
|
+
result = ok(p);
|
|
117
81
|
}
|
|
118
|
-
|
|
119
|
-
|
|
82
|
+
catch (e) {
|
|
83
|
+
after = performance.now();
|
|
84
|
+
result = error(e);
|
|
120
85
|
}
|
|
86
|
+
return { result, duration: after - before };
|
|
121
87
|
};
|
|
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:///';
|
|
88
|
+
const awaitPromise = async (p) => [p instanceof Promise ? await p : p];
|
|
129
89
|
const { now } = Date;
|
|
130
90
|
/** Maps `WriteConsoles` names to the corresponding Node.js writable streams. */
|
|
131
91
|
const streams = {
|
|
@@ -151,67 +111,138 @@ const writeAll = async (stream, data) => {
|
|
|
151
111
|
await once(stream, 'drain');
|
|
152
112
|
}
|
|
153
113
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Resolves `true` once stdin reaches EOF, or `false` as soon as more data is
|
|
116
|
+
* readable. Both listeners are removed the moment either fires, so a
|
|
117
|
+
* long-running server that idles between messages never accumulates leftover
|
|
118
|
+
* `'readable'`/`'end'` listeners (which would eventually trip
|
|
119
|
+
* `MaxListenersExceededWarning`).
|
|
120
|
+
*/
|
|
121
|
+
const waitReadableOrEnd = (stdin) => new Promise(resolve => {
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
stdin.removeListener('readable', onReadable);
|
|
124
|
+
stdin.removeListener('end', onEnd);
|
|
125
|
+
};
|
|
126
|
+
const onReadable = () => { cleanup(); resolve(false); };
|
|
127
|
+
const onEnd = () => { cleanup(); resolve(true); };
|
|
128
|
+
stdin.once('readable', onReadable);
|
|
129
|
+
stdin.once('end', onEnd);
|
|
130
|
+
});
|
|
131
|
+
/**
|
|
132
|
+
* Reads one byte from `process.stdin`, or `null` at EOF.
|
|
133
|
+
*
|
|
134
|
+
* `read(1)` returns `null` both at end-of-stream and when no byte is buffered
|
|
135
|
+
* yet, so the two are told apart by waiting on `'readable'` (more data) vs
|
|
136
|
+
* `'end'` (EOF). The line framing lives in the pure `readLine` combinator; this
|
|
137
|
+
* interpreter is deliberately just "next byte".
|
|
138
|
+
*/
|
|
139
|
+
const readStdinByte = async () => {
|
|
140
|
+
const stdin = process.stdin;
|
|
141
|
+
while (true) {
|
|
142
|
+
const chunk = stdin.read(1);
|
|
143
|
+
if (chunk !== null) {
|
|
144
|
+
return chunk[0];
|
|
145
|
+
}
|
|
146
|
+
if (stdin.readableEnded) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (await waitReadableOrEnd(stdin)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
161
152
|
}
|
|
162
153
|
};
|
|
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);
|
|
154
|
+
const runNodeEffect = asyncRun({
|
|
155
|
+
...memoryOperationMap(),
|
|
156
|
+
all: async (...effects) => await Promise.all(effects.map(runNodeEffect)),
|
|
157
|
+
fetch: async (url) => tc(async () => {
|
|
158
|
+
const response = await fetch(url);
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`Fetch error: ${response.status} ${response.statusText}`);
|
|
182
161
|
}
|
|
162
|
+
return toVec(new Uint8Array(await response.arrayBuffer()));
|
|
163
|
+
}),
|
|
164
|
+
mkdir: (...p) => tc(async () => { await mkdir(...p); }),
|
|
165
|
+
readFile: path => tc(async () => toVec(await readFile(path))),
|
|
166
|
+
readdir: (path, r) => tc(async () => (await readdir(path, { ...r, withFileTypes: true }))
|
|
167
|
+
.map(v => ({
|
|
168
|
+
name: v.name,
|
|
169
|
+
parentPath: normalize(v.parentPath),
|
|
170
|
+
isFile: v.isFile()
|
|
171
|
+
}))),
|
|
172
|
+
writeFile: (path, data) => tc(() => writeFile(path, fromVec(data))),
|
|
173
|
+
rm: path => tc(() => rm(path)),
|
|
174
|
+
access: path => tc(() => access(path)),
|
|
175
|
+
import: path => tc(() => asyncImport(path)),
|
|
176
|
+
exec: (command, stdin) => new Promise(resolve => {
|
|
177
|
+
const child = exec(command, (e, stdout, stderr) => resolve(e !== null ? ['error', e] : ok({ stdout, stderr })));
|
|
178
|
+
child.stdin?.end(stdin);
|
|
179
|
+
}),
|
|
180
|
+
createServer: async (requestListener) => {
|
|
181
|
+
const erl = requestListener;
|
|
182
|
+
const nodeRl = async (req, res) => {
|
|
183
|
+
const reqBody = await collect(req);
|
|
184
|
+
const { method, url, headers } = req;
|
|
185
|
+
const { status, headers: outHeaders, body: outBody } = await runNodeEffect(erl({
|
|
186
|
+
method,
|
|
187
|
+
url,
|
|
188
|
+
headers,
|
|
189
|
+
body: listToVec(reqBody)
|
|
190
|
+
}));
|
|
191
|
+
res
|
|
192
|
+
.writeHead(status, outHeaders)
|
|
193
|
+
.end(fromVec(outBody));
|
|
194
|
+
};
|
|
195
|
+
const server = asNominal(createServer(nodeRl));
|
|
196
|
+
return server;
|
|
183
197
|
},
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
198
|
+
listen: async (server, port) => {
|
|
199
|
+
const s = asBase(server);
|
|
200
|
+
s.listen(port);
|
|
201
|
+
},
|
|
202
|
+
forever: () => new Promise(() => { }),
|
|
203
|
+
now: async () => now(),
|
|
204
|
+
sandbox,
|
|
205
|
+
await: awaitPromise,
|
|
206
|
+
write: (stream, data) => writeAll(streams[stream], fromVec(data)),
|
|
207
|
+
read: readStdinByte,
|
|
208
|
+
test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => runNodeEffect(test(t))),
|
|
209
|
+
});
|
|
210
|
+
const isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
|
|
211
|
+
const pwTest = isPlaywright
|
|
212
|
+
? (await import('@playwright/test')).test
|
|
213
|
+
: undefined;
|
|
214
|
+
const inlineTest = async (name, { expectFailure }, fn) => {
|
|
215
|
+
if (expectFailure) {
|
|
191
216
|
try {
|
|
192
|
-
|
|
193
|
-
after = performance.now();
|
|
194
|
-
if (p instanceof Promise) {
|
|
195
|
-
p = await p;
|
|
196
|
-
after = performance.now();
|
|
197
|
-
}
|
|
198
|
-
result = ok(p);
|
|
217
|
+
await fn(inlineContext);
|
|
199
218
|
}
|
|
200
|
-
catch
|
|
201
|
-
|
|
202
|
-
result = error(e);
|
|
219
|
+
catch {
|
|
220
|
+
return;
|
|
203
221
|
}
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
throw new Error(`expected to throw: ${name}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
await fn(inlineContext);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const inlineContext = { test: inlineTest };
|
|
229
|
+
const wrapInlineTest = (register) => ({
|
|
230
|
+
test: (name, opts, fn) => register(name, () => inlineTest(name, opts, fn))
|
|
231
|
+
});
|
|
232
|
+
const bunTestContext = wrapInlineTest(testContext.test);
|
|
233
|
+
const playwrightTestContext = wrapInlineTest(pwTest);
|
|
234
|
+
const options = {
|
|
235
|
+
args: process.argv.slice(2),
|
|
236
|
+
env: process.env,
|
|
237
|
+
home: os.homedir(),
|
|
238
|
+
std: { stdout: process.stdout, stderr: process.stderr },
|
|
208
239
|
testContext,
|
|
209
240
|
bunTestContext,
|
|
210
241
|
playwrightTestContext,
|
|
211
242
|
engine: isPlaywright ? 'playwright' : 'Bun' in globalThis ? 'bun' : 'node',
|
|
212
243
|
};
|
|
213
244
|
/**
|
|
214
|
-
* Runs a `NodeProgram` against the real Node
|
|
245
|
+
* Runs a `NodeProgram` against the real Node globals and process arguments,
|
|
215
246
|
* resolving to its exit code **without** terminating the process.
|
|
216
247
|
*
|
|
217
248
|
* Use this when the caller must stay alive afterwards — e.g. when proofs are
|
|
@@ -219,7 +250,7 @@ const io = {
|
|
|
219
250
|
* that owns the process lifecycle. For a standalone CLI entry point that should
|
|
220
251
|
* exit with the program's code, use {@link run} instead.
|
|
221
252
|
*/
|
|
222
|
-
export const runEffect =
|
|
253
|
+
export const runEffect = program => runNodeEffect(program(options));
|
|
223
254
|
/**
|
|
224
255
|
* CLI entry point: runs a `NodeProgram` via {@link runEffect}, then calls
|
|
225
256
|
* `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>;
|