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.
Files changed (181) hide show
  1. package/fs/asn.1/module.f.js +18 -22
  2. package/fs/asn.1/proof.f.d.ts +5 -0
  3. package/fs/asn.1/proof.f.js +38 -2
  4. package/fs/base64/module.f.d.ts +9 -0
  5. package/fs/base64/module.f.js +60 -0
  6. package/fs/base64/proof.f.d.ts +11 -0
  7. package/fs/base64/proof.f.js +117 -0
  8. package/fs/base_n/module.f.d.ts +44 -0
  9. package/fs/base_n/module.f.js +54 -0
  10. package/fs/base_n/proof.f.d.ts +10 -0
  11. package/fs/base_n/proof.f.js +86 -0
  12. package/fs/bnf/data/module.f.d.ts +4 -9
  13. package/fs/bnf/data/module.f.js +4 -3
  14. package/fs/bnf/module.f.d.ts +3 -4
  15. package/fs/bnf/module.f.js +3 -2
  16. package/fs/bnf/proof.f.d.ts +9 -0
  17. package/fs/bnf/proof.f.js +98 -1
  18. package/fs/cas/mcp/module.f.d.ts +40 -0
  19. package/fs/cas/mcp/module.f.js +223 -0
  20. package/fs/cas/mcp/proof.f.d.ts +30 -0
  21. package/fs/cas/mcp/proof.f.js +310 -0
  22. package/fs/cas/module.f.d.ts +9 -3
  23. package/fs/cas/module.f.js +104 -69
  24. package/fs/cas/proof.f.d.ts +2 -0
  25. package/fs/cas/proof.f.js +30 -1
  26. package/fs/cbase32/module.f.d.ts +1 -1
  27. package/fs/cbase32/module.f.js +19 -40
  28. package/fs/cbase32/proof.f.d.ts +1 -0
  29. package/fs/cbase32/proof.f.js +15 -0
  30. package/fs/ci/config/module.f.d.ts +6 -6
  31. package/fs/ci/config/module.f.js +6 -6
  32. package/fs/ci/deno/module.f.js +2 -2
  33. package/fs/ci/module.f.d.ts +5 -0
  34. package/fs/ci/module.f.js +2 -3
  35. package/fs/ci/proof.f.d.ts +1 -0
  36. package/fs/ci/proof.f.js +9 -3
  37. package/fs/cli/module.f.d.ts +3 -1
  38. package/fs/cli/module.f.js +22 -12
  39. package/fs/cli/proof.f.d.ts +5 -0
  40. package/fs/cli/proof.f.js +76 -0
  41. package/fs/crypto/hmac/proof.f.d.ts +1 -0
  42. package/fs/crypto/hmac/proof.f.js +8 -0
  43. package/fs/crypto/secp/proof.f.d.ts +1 -0
  44. package/fs/crypto/secp/proof.f.js +10 -0
  45. package/fs/crypto/sign/module.f.js +1 -4
  46. package/fs/dev/module.f.d.ts +5 -3
  47. package/fs/dev/module.f.js +13 -2
  48. package/fs/dev/package_json/proof.f.d.ts +1 -0
  49. package/fs/dev/package_json/proof.f.js +5 -0
  50. package/fs/dev/proof.f.d.ts +1 -0
  51. package/fs/dev/proof.f.js +12 -1
  52. package/fs/djs/ast/module.f.d.ts +1 -1
  53. package/fs/djs/ast/proof.f.d.ts +5 -0
  54. package/fs/djs/ast/proof.f.js +17 -0
  55. package/fs/djs/module.f.d.ts +1 -1
  56. package/fs/djs/module.f.js +2 -3
  57. package/fs/djs/serializer/module.f.js +24 -11
  58. package/fs/djs/transpiler/module.f.js +3 -4
  59. package/fs/effects/memory/module.f.d.ts +37 -0
  60. package/fs/effects/memory/module.f.js +24 -0
  61. package/fs/effects/memory/proof.f.d.ts +6 -0
  62. package/fs/effects/memory/proof.f.js +64 -0
  63. package/fs/effects/mock/module.f.d.ts +7 -2
  64. package/fs/effects/mock/module.f.js +12 -8
  65. package/fs/effects/module.d.ts +1 -1
  66. package/fs/effects/module.f.d.ts +41 -0
  67. package/fs/effects/module.f.js +25 -0
  68. package/fs/effects/module.js +6 -7
  69. package/fs/effects/node/memory/module.d.ts +20 -0
  70. package/fs/effects/node/memory/module.js +44 -0
  71. package/fs/effects/node/memory/proof.d.ts +10 -0
  72. package/fs/effects/node/memory/proof.js +35 -0
  73. package/fs/effects/node/module.d.ts +1 -1
  74. package/fs/effects/node/module.f.d.ts +54 -8
  75. package/fs/effects/node/module.f.js +55 -4
  76. package/fs/effects/node/module.js +167 -137
  77. package/fs/effects/node/proof.f.d.ts +10 -0
  78. package/fs/effects/node/proof.f.js +88 -21
  79. package/fs/effects/node/virtual/module.f.d.ts +6 -0
  80. package/fs/effects/node/virtual/module.f.js +48 -16
  81. package/fs/effects/node/virtual/proof.f.d.ts +17 -0
  82. package/fs/effects/node/virtual/proof.f.js +79 -0
  83. package/fs/effects/proof.f.d.ts +5 -0
  84. package/fs/effects/proof.f.js +54 -34
  85. package/fs/emergent_testing/module.f.d.ts +6 -1
  86. package/fs/emergent_testing/module.f.js +8 -3
  87. package/fs/emergent_testing/proof.f.d.ts +11 -0
  88. package/fs/emergent_testing/proof.f.js +52 -16
  89. package/fs/fjs/module.f.js +2 -2
  90. package/fs/fjs/proof.f.d.ts +8 -0
  91. package/fs/fjs/proof.f.js +39 -0
  92. package/fs/fsc/module.f.d.ts +5 -0
  93. package/fs/fsc/module.f.js +17 -27
  94. package/fs/fsc/proof.f.d.ts +8 -0
  95. package/fs/fsc/proof.f.js +48 -2
  96. package/fs/fsm/module.f.d.ts +2 -3
  97. package/fs/fsm/module.f.js +1 -1
  98. package/fs/html/module.f.d.ts +2 -3
  99. package/fs/html/module.f.js +7 -6
  100. package/fs/html/proof.f.d.ts +1 -0
  101. package/fs/html/proof.f.js +7 -0
  102. package/fs/js/tokenizer/module.f.js +1 -1
  103. package/fs/js/tokenizer/proof.f.js +7 -0
  104. package/fs/json/module.f.d.ts +9 -2
  105. package/fs/json/module.f.js +9 -7
  106. package/fs/json/parser/module.f.js +4 -6
  107. package/fs/json/parser/proof.f.js +19 -10
  108. package/fs/json/proof.f.d.ts +2 -1
  109. package/fs/json/proof.f.js +33 -6
  110. package/fs/json/rpc/module.f.d.ts +5 -0
  111. package/fs/json/rpc/module.f.js +16 -11
  112. package/fs/json/schema/module.f.d.ts +15 -14
  113. package/fs/json/schema/proof.f.d.ts +3 -0
  114. package/fs/json/schema/proof.f.js +16 -1
  115. package/fs/mcp/module.f.d.ts +103 -5
  116. package/fs/mcp/module.f.js +139 -2
  117. package/fs/mcp/proof.f.d.ts +34 -0
  118. package/fs/mcp/proof.f.js +207 -0
  119. package/fs/mcp/stdio/module.f.d.ts +39 -0
  120. package/fs/mcp/stdio/module.f.js +54 -0
  121. package/fs/mcp/stdio/proof.f.d.ts +10 -0
  122. package/fs/mcp/stdio/proof.f.js +92 -0
  123. package/fs/mime/module.f.d.ts +39 -0
  124. package/fs/mime/module.f.js +78 -0
  125. package/fs/mime/proof.f.d.ts +15 -0
  126. package/fs/mime/proof.f.js +30 -0
  127. package/fs/path/module.f.d.ts +8 -0
  128. package/fs/path/module.f.js +10 -3
  129. package/fs/path/proof.f.d.ts +1 -0
  130. package/fs/path/proof.f.js +40 -2
  131. package/fs/text/sgr/proof.f.js +30 -2
  132. package/fs/text/utf8/module.f.d.ts +7 -0
  133. package/fs/text/utf8/module.f.js +85 -31
  134. package/fs/text/utf8/proof.f.d.ts +1 -0
  135. package/fs/text/utf8/proof.f.js +60 -1
  136. package/fs/types/bigfloat/module.f.d.ts +3 -0
  137. package/fs/types/bigfloat/module.f.js +30 -31
  138. package/fs/types/bigint/module.f.d.ts +24 -2
  139. package/fs/types/bigint/module.f.js +30 -2
  140. package/fs/types/bigint/proof.f.d.ts +7 -0
  141. package/fs/types/bigint/proof.f.js +47 -16
  142. package/fs/types/bit_vec/module.f.d.ts +18 -0
  143. package/fs/types/bit_vec/module.f.js +18 -0
  144. package/fs/types/bit_vec/proof.f.d.ts +1 -0
  145. package/fs/types/bit_vec/proof.f.js +13 -1
  146. package/fs/types/function/operator/proof.f.d.ts +2 -0
  147. package/fs/types/function/operator/proof.f.js +20 -2
  148. package/fs/types/list/module.f.js +1 -4
  149. package/fs/types/list/proof.f.d.ts +1 -1
  150. package/fs/types/list/proof.f.js +14 -6
  151. package/fs/types/map/proof.f.d.ts +1 -0
  152. package/fs/types/map/proof.f.js +10 -1
  153. package/fs/types/nibble_set/module.f.d.ts +13 -0
  154. package/fs/types/nibble_set/module.f.js +16 -0
  155. package/fs/types/object/module.f.d.ts +12 -8
  156. package/fs/types/object/module.f.js +9 -2
  157. package/fs/types/object/proof.f.d.ts +14 -0
  158. package/fs/types/object/proof.f.js +4 -0
  159. package/fs/types/ordered_map/proof.f.d.ts +1 -0
  160. package/fs/types/ordered_map/proof.f.js +39 -79
  161. package/fs/types/phantom/module.f.d.ts +22 -0
  162. package/fs/types/phantom/module.f.js +11 -0
  163. package/fs/types/prime_field/proof.f.d.ts +7 -0
  164. package/fs/types/prime_field/proof.f.js +63 -122
  165. package/fs/types/rtti/common/module.f.d.ts +4 -4
  166. package/fs/types/rtti/common/module.f.js +1 -1
  167. package/fs/types/rtti/module.f.d.ts +2 -3
  168. package/fs/types/rtti/parse/module.f.js +0 -1
  169. package/fs/types/rtti/ts/module.f.d.ts +26 -32
  170. package/fs/types/rtti/ts/module.f.js +2 -2
  171. package/fs/types/rtti/ts/proof.f.js +3 -3
  172. package/fs/types/rtti/validate/module.f.js +0 -1
  173. package/fs/types/sorted_list/module.f.d.ts +6 -0
  174. package/fs/types/sorted_list/module.f.js +11 -0
  175. package/fs/types/sorted_set/module.f.js +2 -8
  176. package/fs/types/ts/module.f.d.ts +1 -1
  177. package/fs/types/ts/module.f.js +3 -1
  178. package/fs/types/ts/proof.f.js +3 -2
  179. package/fs/website/proof.f.d.ts +1 -0
  180. package/fs/website/proof.f.js +8 -1
  181. 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`), networking (`fetch`, `createServer`, `listen`),
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
- const doAll = do_('all');
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 = (...a) => doAll(...a);
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 fromIo = ({ fs: { promises: { mkdir, readFile, readdir, writeFile, rm, access } }, fetch, http: { createServer }, childProcess, asyncImport, now: ioNow, sandbox, write, await: awaitPromise, }) => {
39
- const result = asyncRun({
40
- all: async (...effects) => await Promise.all(effects.map(result)),
41
- fetch: async (url) => tc(async () => {
42
- const response = await fetch(url);
43
- if (!response.ok) {
44
- throw new Error(`Fetch error: ${response.status} ${response.statusText}`);
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 isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
105
- const pwTest = isPlaywright
106
- ? (await import('@playwright/test')).test
107
- : undefined;
108
- const inlineTest = async (name, { expectFailure }, fn) => {
109
- if (expectFailure) {
110
- try {
111
- await fn(inlineContext);
112
- }
113
- catch {
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
- throw new Error(`expected to throw: ${name}`);
79
+ result = ok(p);
117
80
  }
118
- else {
119
- await fn(inlineContext);
81
+ catch (e) {
82
+ after = performance.now();
83
+ result = error(e);
120
84
  }
85
+ return { result, duration: after - before };
121
86
  };
122
- const inlineContext = { test: inlineTest };
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
- const asyncImport = (v) => import(__rewriteRelativeImportExtension(v));
155
- const tryCatch = f => {
156
- try {
157
- return ok(f());
158
- }
159
- catch (e) {
160
- return error(e);
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 awaitPromise = async (p) => [p instanceof Promise ? await p : p];
164
- const io = {
165
- console,
166
- fs,
167
- process,
168
- asyncImport: (v) => {
169
- const s0 = v.includes(':') || v.startsWith('/') ? v : concat(process.cwd())(v);
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
- http,
185
- childProcess,
186
- now,
187
- sandbox: async (f) => {
188
- let result;
189
- let after;
190
- const before = performance.now();
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
- let p = f();
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 (e) {
201
- after = performance.now();
202
- result = error(e);
218
+ catch {
219
+ return;
203
220
  }
204
- return { result, duration: after - before };
205
- },
206
- write: (stream, data) => writeAll(streams[stream], fromVec(data)),
207
- await: awaitPromise,
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 `io` and process arguments,
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 = runProgram(io)(io.process.argv.slice(2));
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 { pure } from "../module.f.js";
3
- import { fetch, mkdir, now, readdir, readFile, rm, sandbox, writeFile } from "./module.f.js";
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
- let e = readFile('hello').step(([k, v]) => {
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
- while (true) {
15
- const { value } = e;
16
- if (value.length === 1) {
17
- const [result] = value;
18
- if (result !== 0x2an) {
19
- throw result;
20
- }
21
- return;
22
- }
23
- const [cmd, p, cont] = value;
24
- if (cmd !== 'readFile') {
25
- throw cmd;
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 !== 'no such file') {
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>;