functionalscript 0.32.4 → 0.34.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.
Files changed (90) hide show
  1. package/README.md +1 -1
  2. package/fs/asserts/module.f.d.ts +1 -1
  3. package/fs/asserts/module.f.js +4 -1
  4. package/fs/bnf/data/module.f.d.ts +0 -103
  5. package/fs/bnf/data/module.f.js +10 -246
  6. package/fs/bnf/data/proof.f.d.ts +0 -7
  7. package/fs/bnf/data/proof.f.js +3 -636
  8. package/fs/bnf/descent/module.f.d.ts +56 -0
  9. package/fs/bnf/descent/module.f.js +119 -0
  10. package/fs/bnf/descent/proof.f.d.ts +5 -0
  11. package/fs/bnf/descent/proof.f.js +296 -0
  12. package/fs/bnf/ll1/module.f.d.ts +72 -0
  13. package/fs/bnf/ll1/module.f.js +153 -0
  14. package/fs/bnf/ll1/proof.f.d.ts +9 -0
  15. package/fs/bnf/ll1/proof.f.js +358 -0
  16. package/fs/cas/cli/module.f.d.ts +6 -0
  17. package/fs/cas/cli/module.f.js +56 -0
  18. package/fs/cas/cli/proof.f.d.ts +14 -0
  19. package/fs/cas/cli/proof.f.js +149 -0
  20. package/fs/cas/mcp/module.f.d.ts +9 -17
  21. package/fs/cas/mcp/module.f.js +117 -83
  22. package/fs/cas/mcp/proof.f.d.ts +5 -1
  23. package/fs/cas/mcp/proof.f.js +79 -32
  24. package/fs/cas/module.f.d.ts +33 -27
  25. package/fs/cas/module.f.js +151 -126
  26. package/fs/cas/proof.f.d.ts +16 -13
  27. package/fs/cas/proof.f.js +255 -130
  28. package/fs/ci/config/module.f.d.ts +15 -15
  29. package/fs/ci/config/module.f.js +15 -15
  30. package/fs/ci/proof.f.js +8 -6
  31. package/fs/cli/proof.f.js +2 -12
  32. package/fs/djs/parser/module.f.d.ts +0 -5
  33. package/fs/djs/proof.f.js +5 -6
  34. package/fs/djs/serializer/module.f.d.ts +1 -3
  35. package/fs/djs/tokenizer-new/module.f.js +3 -2
  36. package/fs/djs/tokenizer-new/proof.f.d.ts +3 -0
  37. package/fs/djs/tokenizer-new/proof.f.js +458 -296
  38. package/fs/djs/transpiler/proof.f.js +10 -10
  39. package/fs/effects/list/module.f.d.ts +31 -0
  40. package/fs/effects/list/module.f.js +16 -0
  41. package/fs/effects/module.f.d.ts +11 -5
  42. package/fs/effects/module.f.js +16 -6
  43. package/fs/effects/node/module.f.d.ts +29 -3
  44. package/fs/effects/node/module.f.js +35 -1
  45. package/fs/effects/node/module.js +32 -18
  46. package/fs/effects/node/proof.f.js +36 -36
  47. package/fs/effects/node/virtual/module.f.d.ts +16 -2
  48. package/fs/effects/node/virtual/module.f.js +129 -20
  49. package/fs/effects/node/virtual/proof.f.d.ts +4 -0
  50. package/fs/effects/node/virtual/proof.f.js +47 -14
  51. package/fs/effects/proof.f.d.ts +4 -0
  52. package/fs/effects/proof.f.js +18 -1
  53. package/fs/emergent_testing/proof.f.js +3 -11
  54. package/fs/fjs/module.f.js +2 -8
  55. package/fs/fjs/proof.f.js +2 -11
  56. package/fs/html/module.f.js +6 -8
  57. package/fs/js/tokenizer/module.f.d.ts +5 -0
  58. package/fs/js/tokenizer/module.f.js +13 -0
  59. package/fs/json/serializer/module.f.js +0 -1
  60. package/fs/mcp/module.f.d.ts +1 -1
  61. package/fs/mime/module.f.d.ts +101 -5
  62. package/fs/mime/module.f.js +159 -6
  63. package/fs/mime/proof.f.d.ts +25 -0
  64. package/fs/mime/proof.f.js +193 -3
  65. package/fs/text/code_point/module.f.d.ts +39 -2
  66. package/fs/text/code_point/module.f.js +74 -2
  67. package/fs/text/code_point/proof.f.d.ts +8 -0
  68. package/fs/text/code_point/proof.f.js +72 -0
  69. package/fs/text/sgr/proof.f.js +2 -11
  70. package/fs/text/utf16/module.f.js +4 -26
  71. package/fs/text/utf8/module.f.d.ts +27 -0
  72. package/fs/text/utf8/module.f.js +8 -14
  73. package/fs/types/bigint/module.f.d.ts +8 -15
  74. package/fs/types/bigint/module.f.js +9 -22
  75. package/fs/types/bigint/proof.f.d.ts +0 -2
  76. package/fs/types/bigint/proof.f.js +6 -16
  77. package/fs/types/function/operator/module.f.d.ts +26 -0
  78. package/fs/types/list/module.f.d.ts +3 -0
  79. package/fs/types/result/module.d.ts +2 -4
  80. package/fs/types/result/module.js +9 -3
  81. package/package.json +3 -3
  82. package/issues/demo/data/data.f.js +0 -12
  83. package/issues/demo/data/shared.f.js +0 -3
  84. package/issues/demo/fs/app.js +0 -4
  85. package/issues/demo/fs/math.f.js +0 -4
  86. package/issues/demo/sample/proof.f.js +0 -13
  87. /package/{issues → todo}/031-json.f.d.ts +0 -0
  88. /package/{issues → todo}/031-json.f.js +0 -0
  89. /package/{issues → todo}/proof.f.d.ts +0 -0
  90. /package/{issues → todo}/proof.f.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { type Vec } from '../../../types/bit_vec/module.f.ts';
2
2
  import { type RunInstance } from '../../mock/module.f.ts';
3
- import type { Module, NodeOp } from '../module.f.ts';
3
+ import type { Module, NodeOp, NodeProgramOptions } from '../module.f.ts';
4
4
  /**
5
5
  * In-memory JS module entry. When `import_` is called on the path, the
6
6
  * function is invoked and its return value is the module value (with a
@@ -10,7 +10,7 @@ import type { Module, NodeOp } from '../module.f.ts';
10
10
  * each import for closures/state.
11
11
  */
12
12
  export type JsModule = () => Module;
13
- export type Entity = Vec | Dir | JsModule;
13
+ export type Entity = readonly Vec[] | Dir | JsModule;
14
14
  export type Dir = {
15
15
  readonly [name in string]?: Entity;
16
16
  };
@@ -33,3 +33,17 @@ export type State = {
33
33
  };
34
34
  export declare const emptyState: State;
35
35
  export declare const virtual: RunInstance<NodeOp, State>;
36
+ /**
37
+ * Safe, inert defaults for every {@link NodeProgramOptions} field, intended for
38
+ * proof files that need to call a program without owning the full literal.
39
+ *
40
+ * Proofs spread-override only what their test cares about:
41
+ *
42
+ * ```ts
43
+ * const opts: NodeProgramOptions = { ...defaultNodeProgramOptions, args }
44
+ * ```
45
+ *
46
+ * Future additions to `NodeProgramOptions` only need a default added here,
47
+ * keeping unrelated proof files from churning.
48
+ */
49
+ export declare const defaultNodeProgramOptions: NodeProgramOptions;
@@ -6,7 +6,7 @@
6
6
  import { todo } from "../../../asserts/module.f.js";
7
7
  import { join, parse } from "../../../path/module.f.js";
8
8
  import { utf8ToString } from "../../../text/module.f.js";
9
- import { isVec, length, maxLengthBytes, msb, vec } from "../../../types/bit_vec/module.f.js";
9
+ import { empty, length, maxLengthBytes, msb, vec } from "../../../types/bit_vec/module.f.js";
10
10
  import { error, ok } from "../../../types/result/module.f.js";
11
11
  import { run } from "../../mock/module.f.js";
12
12
  import { asBase, asNominal } from "../../memory/module.f.js";
@@ -28,7 +28,7 @@ const operation = (op) => {
28
28
  }
29
29
  const [first, ...rest] = path;
30
30
  const subDir = dir[first];
31
- if (typeof subDir !== 'object') {
31
+ if (typeof subDir !== 'object' || Array.isArray(subDir)) {
32
32
  return op(dir, path);
33
33
  }
34
34
  const [newSubDir, r] = f(subDir, rest);
@@ -67,11 +67,23 @@ const readFile = readOperation((dir, path) => {
67
67
  if (file === undefined) {
68
68
  return enoent;
69
69
  }
70
- // exists but is a directory — a real error, not a missing path
71
- if (!isVec(file)) {
70
+ if (!Array.isArray(file)) {
72
71
  return error(`'${path[0]}' is not a file`);
73
72
  }
74
- return ok(file);
73
+ const chunks = file;
74
+ const capBits = maxLengthBytes * 8n;
75
+ let result = empty;
76
+ for (const chunk of chunks) {
77
+ const chunkLen = length(chunk);
78
+ if (chunkLen === 0n) {
79
+ continue;
80
+ }
81
+ if (length(result) + chunkLen > capBits) {
82
+ return error(`File size exceeds maximum allowed size of ${maxLengthBytes} bytes`);
83
+ }
84
+ result = msb.concat(result)(chunk);
85
+ }
86
+ return ok(result);
75
87
  });
76
88
  const import_ = readOperation((dir, path) => {
77
89
  if (path.length !== 1) {
@@ -90,11 +102,10 @@ const writeFile = (payload) => operation((dir, path) => {
90
102
  }
91
103
  const [name] = path;
92
104
  const file = dir[name];
93
- // fail if the file is a directory
94
- if (file !== undefined && !isVec(file)) {
105
+ if (file !== undefined && !Array.isArray(file)) {
95
106
  return [dir, writeFileError];
96
107
  }
97
- dir = { ...dir, [name]: payload };
108
+ dir = { ...dir, [name]: [payload] };
98
109
  return [dir, okVoid];
99
110
  });
100
111
  const invalidPath = error('invalid path');
@@ -109,7 +120,7 @@ const readdir = (base, recursive) => readOperation((dir, path) => {
109
120
  if (content === undefined) {
110
121
  continue;
111
122
  }
112
- const isFile = typeof content !== 'object';
123
+ const isFile = Array.isArray(content) || typeof content !== 'object';
113
124
  result = [...result, { name, parentPath, isFile }];
114
125
  if (!isFile && recursive) {
115
126
  result = [...result, ...f(join(parentPath, name), content)];
@@ -137,7 +148,7 @@ const rm = operation((dir, path) => {
137
148
  if (entry === undefined) {
138
149
  return [dir, error('no such file')];
139
150
  }
140
- if (typeof entry === 'object') {
151
+ if (!Array.isArray(entry) && typeof entry === 'object') {
141
152
  return [dir, error('is a directory')];
142
153
  }
143
154
  const { [name]: _, ...rest } = dir;
@@ -158,7 +169,7 @@ const extractEntity = (dir, path) => {
158
169
  }
159
170
  const [first, ...rest] = path;
160
171
  const sub = dir[first];
161
- if (sub === undefined || isVec(sub) || typeof sub === 'function') {
172
+ if (sub === undefined || Array.isArray(sub) || typeof sub === 'function') {
162
173
  return [dir, enoent];
163
174
  }
164
175
  const [newSub, result] = extractEntity(sub, rest);
@@ -175,8 +186,8 @@ const insertEntityAt = (dir, path, entity) => {
175
186
  const [name] = path;
176
187
  const existing = dir[name];
177
188
  if (existing !== undefined) {
178
- const entityIsDir = typeof entity === 'object';
179
- const existingIsDir = typeof existing === 'object';
189
+ const entityIsDir = !Array.isArray(entity) && typeof entity === 'object';
190
+ const existingIsDir = !Array.isArray(existing) && typeof existing === 'object';
180
191
  if (entityIsDir && !existingIsDir) {
181
192
  return [dir, error(`cannot overwrite file '${name}' with a directory`)];
182
193
  }
@@ -198,7 +209,7 @@ const insertEntityAt = (dir, path, entity) => {
198
209
  if (sub === undefined) {
199
210
  return [dir, enoent];
200
211
  }
201
- if (isVec(sub) || typeof sub === 'function') {
212
+ if (Array.isArray(sub) || typeof sub === 'function') {
202
213
  return [dir, error('not a directory')];
203
214
  }
204
215
  const [newSub, result] = insertEntityAt(sub, rest, entity);
@@ -238,7 +249,7 @@ const readBytesOp = (path, offset, size) => readOperation((dir, p) => {
238
249
  if (file === undefined) {
239
250
  return enoent;
240
251
  }
241
- if (!isVec(file)) {
252
+ if (!Array.isArray(file)) {
242
253
  return error(`'${p[0]}' is not a file`);
243
254
  }
244
255
  if (!Number.isInteger(offset)) {
@@ -256,12 +267,83 @@ const readBytesOp = (path, offset, size) => readOperation((dir, p) => {
256
267
  if (BigInt(size) > maxLengthBytes) {
257
268
  return error(`Chunk size ${size} exceeds maximum allowed size of ${maxLengthBytes} bytes`);
258
269
  }
259
- const offsetBits = BigInt(offset) * 8n;
260
- const sizeBits = BigInt(size) * 8n;
261
- const remaining = msb.removeFront(offsetBits)(file);
262
- const actualSizeBits = sizeBits < length(remaining) ? sizeBits : length(remaining);
263
- return ok(vec(actualSizeBits)(msb.front(actualSizeBits)(remaining)));
270
+ const chunks = file;
271
+ let toSkip = BigInt(offset) * 8n;
272
+ let toRead = BigInt(size) * 8n;
273
+ let result = empty;
274
+ for (const chunk of chunks) {
275
+ if (toRead === 0n) {
276
+ break;
277
+ }
278
+ const chunkBits = length(chunk);
279
+ if (toSkip >= chunkBits) {
280
+ toSkip -= chunkBits;
281
+ continue;
282
+ }
283
+ const skipInChunk = toSkip;
284
+ const availableBits = chunkBits - skipInChunk;
285
+ const takeBits = availableBits <= toRead ? availableBits : toRead;
286
+ const taken = vec(takeBits)(msb.front(takeBits)(msb.removeFront(skipInChunk)(chunk)));
287
+ result = msb.concat(result)(taken);
288
+ toSkip = 0n;
289
+ toRead -= takeBits;
290
+ }
291
+ return ok(result);
292
+ })(path);
293
+ /** Total byte size of a chunk-list file (each chunk is byte-aligned). */
294
+ const fileSizeBytes = (chunks) => chunks.reduce((acc, c) => acc + Number(length(c) / 8n), 0);
295
+ /** Absent-path error for an already-existing exclusive create, mirroring `EEXIST`. */
296
+ const eexist = error({ code: 'EEXIST' });
297
+ const createExclusive = operation((dir, path) => {
298
+ if (path.length !== 1) {
299
+ return [dir, invalidPath];
300
+ }
301
+ const [name] = path;
302
+ // O_EXCL: fail if the name is already taken; otherwise create an empty file.
303
+ if (dir[name] !== undefined) {
304
+ return [dir, eexist];
305
+ }
306
+ return [{ ...dir, [name]: [] }, okVoid];
307
+ });
308
+ // The lock-free upload only ever writes sequentially at the current end of the
309
+ // staging file (`offset === size`), so the virtual model implements that append
310
+ // case exactly: it never creates (a missing file is `ENOENT`), never overwrites
311
+ // existing bytes, and never leaves a hole — matching the effect's contract for
312
+ // the one access pattern its callers use.
313
+ const writeBytesOp = (path, offset, data) => operation((dir, p) => {
314
+ if (p.length !== 1) {
315
+ return [dir, enoent];
316
+ }
317
+ const [name] = p;
318
+ const file = dir[name];
319
+ if (file === undefined) {
320
+ return [dir, enoent];
321
+ } // writeBytes never creates
322
+ if (!Array.isArray(file)) {
323
+ return [dir, error(`'${name}' is not a file`)];
324
+ }
325
+ if (!Number.isInteger(offset) || offset < 0) {
326
+ return [dir, error(`Offset ${offset} is invalid`)];
327
+ }
328
+ const chunks = file;
329
+ if (offset !== fileSizeBytes(chunks)) {
330
+ return [dir, error(`writeBytes offset ${offset} must equal the file size (append-only)`)];
331
+ }
332
+ return [{ ...dir, [name]: [...chunks, data] }, okVoid];
264
333
  })(path);
334
+ const statOp = readOperation((dir, path) => {
335
+ if (path.length !== 1) {
336
+ return enoent;
337
+ }
338
+ const file = dir[path[0]];
339
+ if (file === undefined) {
340
+ return enoent;
341
+ }
342
+ if (!Array.isArray(file)) {
343
+ return error(`'${path[0]}' is not a file`);
344
+ }
345
+ return ok({ size: fileSizeBytes(file) });
346
+ });
265
347
  const map = {
266
348
  all: (...a) => state => {
267
349
  let e = [];
@@ -302,6 +384,9 @@ const map = {
302
384
  rm,
303
385
  rename,
304
386
  readBytes: readBytesOp,
387
+ createExclusive,
388
+ writeBytes: writeBytesOp,
389
+ stat: statOp,
305
390
  randomInt: () => state => [{ ...state, randomNext: state.randomNext + 1 }, state.randomNext],
306
391
  exec: todo,
307
392
  createServer: todo,
@@ -330,3 +415,27 @@ const map = {
330
415
  },
331
416
  };
332
417
  export const virtual = run(map);
418
+ const testContext = { test: todo };
419
+ /**
420
+ * Safe, inert defaults for every {@link NodeProgramOptions} field, intended for
421
+ * proof files that need to call a program without owning the full literal.
422
+ *
423
+ * Proofs spread-override only what their test cares about:
424
+ *
425
+ * ```ts
426
+ * const opts: NodeProgramOptions = { ...defaultNodeProgramOptions, args }
427
+ * ```
428
+ *
429
+ * Future additions to `NodeProgramOptions` only need a default added here,
430
+ * keeping unrelated proof files from churning.
431
+ */
432
+ export const defaultNodeProgramOptions = {
433
+ args: [],
434
+ env: {},
435
+ home: '.',
436
+ std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
437
+ testContext,
438
+ bunTestContext: testContext,
439
+ playwrightTestContext: testContext,
440
+ engine: 'node',
441
+ };
@@ -19,9 +19,13 @@ export declare const proof: {
19
19
  renameOntoOwnAncestor: () => void;
20
20
  renameNonEmptyDirOverEmptyDir: () => void;
21
21
  renameEmptyDirOverNonEmptyDir: () => void;
22
+ renameFileOntoDirectory: () => void;
23
+ readFileTooLarge: () => void;
22
24
  readBytesNegativeSize: () => void;
23
25
  readBytesZeroSize: () => void;
24
26
  readBytesNegativeOffset: () => void;
25
27
  readBytesFractionalSize: () => void;
26
28
  readBytesFractionalOffset: () => void;
29
+ readBytesAcrossChunkBoundary: () => void;
30
+ largeFileReadBytes: () => void;
27
31
  };
@@ -1,11 +1,11 @@
1
1
  import { assertEq } from "../../../asserts/module.f.js";
2
2
  import { awaitIfPromise, fetch, rm, writeFile, readFile, readdir, import_, rename, readBytes } from "../module.f.js";
3
- import { vec8 } from "../../../types/bit_vec/module.f.js";
3
+ import { maxLengthBytes, vec, vec8 } from "../../../types/bit_vec/module.f.js";
4
4
  import { emptyState, virtual } from "./module.f.js";
5
5
  export const proof = {
6
6
  rm: {
7
7
  success: () => {
8
- const root = { 'a.txt': vec8(0x42n) };
8
+ const root = { 'a.txt': [vec8(0x42n)] };
9
9
  const [, result] = virtual({ ...emptyState, root })(rm('a.txt'));
10
10
  assertEq(result[0], 'ok');
11
11
  },
@@ -27,7 +27,7 @@ export const proof = {
27
27
  assertEq(result[0], 'error');
28
28
  },
29
29
  readdirRecursive: () => {
30
- const file = vec8(0x42n);
30
+ const file = [vec8(0x42n)];
31
31
  const sub = { 'file.txt': file };
32
32
  const outer = { 'sub': sub };
33
33
  const root = { 'mydir': outer };
@@ -65,7 +65,7 @@ export const proof = {
65
65
  },
66
66
  importNonModule: () => {
67
67
  // import_ on a Vec (not a JsModule) covers typeof entry !== 'function' branch
68
- const root = { 'module.f.ts': vec8(0x42n) };
68
+ const root = { 'module.f.ts': [vec8(0x42n)] };
69
69
  const [, result] = virtual({ ...emptyState, root })(import_('module.f.ts'));
70
70
  assertEq(result[0], 'error');
71
71
  },
@@ -78,62 +78,95 @@ export const proof = {
78
78
  },
79
79
  renameSamePath: () => {
80
80
  // rename('a', 'a') should succeed as a no-op, not reject
81
- const root = { 'a': vec8(0x42n) };
81
+ const root = { 'a': [vec8(0x42n)] };
82
82
  const [, result] = virtual({ ...emptyState, root })(rename('a', 'a'));
83
83
  assertEq(result[0], 'ok');
84
84
  },
85
85
  renameIntoOwnSubtree: () => {
86
86
  // rename('a', 'a/b') should fail (dst inside src's subtree)
87
- const root = { 'a': { 'b': vec8(0x42n) } };
87
+ const root = { 'a': { 'b': [vec8(0x42n)] } };
88
88
  const [, result] = virtual({ ...emptyState, root })(rename('a', 'a/b'));
89
89
  assertEq(result[0], 'error');
90
90
  },
91
91
  renameOntoOwnAncestor: () => {
92
92
  // rename('a/b', 'a') should fail (src inside dst's subtree)
93
- const root = { 'a': { 'b': vec8(0x42n) } };
93
+ const root = { 'a': { 'b': [vec8(0x42n)] } };
94
94
  const [, result] = virtual({ ...emptyState, root })(rename('a/b', 'a'));
95
95
  assertEq(result[0], 'error');
96
96
  },
97
97
  renameNonEmptyDirOverEmptyDir: () => {
98
98
  // rename a directory onto an empty directory should succeed
99
- const root = { 'src': { 'file': vec8(0x42n) }, 'dst': {} };
99
+ const root = { 'src': { 'file': [vec8(0x42n)] }, 'dst': {} };
100
100
  const [, result] = virtual({ ...emptyState, root })(rename('src', 'dst'));
101
101
  assertEq(result[0], 'ok');
102
102
  },
103
103
  renameEmptyDirOverNonEmptyDir: () => {
104
104
  // rename an empty directory onto a non-empty directory should fail
105
- const root = { 'src': {}, 'dst': { 'file': vec8(0x42n) } };
105
+ const root = { 'src': {}, 'dst': { 'file': [vec8(0x42n)] } };
106
106
  const [, result] = virtual({ ...emptyState, root })(rename('src', 'dst'));
107
107
  assertEq(result[0], 'error');
108
108
  },
109
+ renameFileOntoDirectory: () => {
110
+ // rename a file to a path that is already an existing directory should fail
111
+ const root = { 'myfile': [vec8(0x42n)], 'mydir': {} };
112
+ const [, result] = virtual({ ...emptyState, root })(rename('myfile', 'mydir'));
113
+ assertEq(result[0], 'error');
114
+ },
115
+ readFileTooLarge: () => {
116
+ // a file stored as two max-size chunks exceeds the limit; readFile must return an error
117
+ const chunk0 = vec(maxLengthBytes * 8n)(0n);
118
+ const chunk1 = vec(1n)(1n);
119
+ const root = { 'big': [chunk0, chunk1] };
120
+ const [, result] = virtual({ ...emptyState, root })(readFile('big'));
121
+ assertEq(result[0], 'error');
122
+ },
109
123
  readBytesNegativeSize: () => {
110
124
  // readBytes with negative size should fail
111
- const root = { 'file': vec8(0x42n) };
125
+ const root = { 'file': [vec8(0x42n)] };
112
126
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, -1));
113
127
  assertEq(result[0], 'error');
114
128
  },
115
129
  readBytesZeroSize: () => {
116
130
  // readBytes with zero size should succeed and return empty vec
117
- const root = { 'file': vec8(0x42n) };
131
+ const root = { 'file': [vec8(0x42n)] };
118
132
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, 0));
119
133
  assertEq(result[0], 'ok');
120
134
  },
121
135
  readBytesNegativeOffset: () => {
122
136
  // readBytes with negative offset should fail
123
- const root = { 'file': vec8(0x42n) };
137
+ const root = { 'file': [vec8(0x42n)] };
124
138
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', -1, 1));
125
139
  assertEq(result[0], 'error');
126
140
  },
127
141
  readBytesFractionalSize: () => {
128
142
  // readBytes with fractional size should fail rather than throw RangeError
129
- const root = { 'file': vec8(0x42n) };
143
+ const root = { 'file': [vec8(0x42n)] };
130
144
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0, 1.5));
131
145
  assertEq(result[0], 'error');
132
146
  },
133
147
  readBytesFractionalOffset: () => {
134
148
  // readBytes with fractional offset should fail rather than throw RangeError
135
- const root = { 'file': vec8(0x42n) };
149
+ const root = { 'file': [vec8(0x42n)] };
136
150
  const [, result] = virtual({ ...emptyState, root })(readBytes('file', 0.5, 1));
137
151
  assertEq(result[0], 'error');
138
152
  },
153
+ readBytesAcrossChunkBoundary: () => {
154
+ // Two 128 KiB chunks; read 2 bytes spanning the boundary (last byte of chunk 0, first of chunk 1).
155
+ const chunkSize = Number(maxLengthBytes);
156
+ const chunk0 = vec(maxLengthBytes * 8n)(0xaan);
157
+ const chunk1 = vec(maxLengthBytes * 8n)(0xbbn);
158
+ const root = { 'big': [chunk0, chunk1] };
159
+ const [, result] = virtual({ ...emptyState, root })(readBytes('big', chunkSize - 1, 2));
160
+ assertEq(result[0], 'ok');
161
+ },
162
+ largeFileReadBytes: () => {
163
+ // A file stored as two 128 KiB chunks is larger than maxLengthBytes.
164
+ // readBytes within the second chunk (offset = 128 KiB, size = 1) should succeed.
165
+ const chunkSize = Number(maxLengthBytes);
166
+ const chunk0 = vec(maxLengthBytes * 8n)(0n);
167
+ const chunk1 = vec(maxLengthBytes * 8n)(0xffn);
168
+ const root = { 'large': [chunk0, chunk1] };
169
+ const [, result] = virtual({ ...emptyState, root })(readBytes('large', chunkSize, 1));
170
+ assertEq(result[0], 'ok');
171
+ },
139
172
  };
@@ -1,4 +1,8 @@
1
1
  export declare const proof: {
2
+ lazy: {
3
+ value: () => void;
4
+ deferred: () => void;
5
+ };
2
6
  foldStep: {
3
7
  empty: () => void;
4
8
  threadsState: () => void;
@@ -1,4 +1,4 @@
1
- import { decode, do_, foldStep, forEachStep, match, pure } from "./module.f.js";
1
+ import { decode, do_, foldStep, forEachStep, lazy, match, pure } from "./module.f.js";
2
2
  const assertPure = (e, expected) => {
3
3
  const d = decode(e);
4
4
  if (!d.done) {
@@ -10,6 +10,23 @@ const assertPure = (e, expected) => {
10
10
  };
11
11
  const next = match({ add: (a, b) => a + b });
12
12
  export const proof = {
13
+ lazy: {
14
+ value: () => {
15
+ assertPure(lazy(() => 42), 42);
16
+ },
17
+ deferred: () => {
18
+ // The thunk runs only when the effect is decoded, not when `lazy` is called.
19
+ let evaluated = false;
20
+ const e = lazy(() => { evaluated = true; return 7; });
21
+ if (evaluated) {
22
+ throw 'lazy must not evaluate eagerly';
23
+ }
24
+ assertPure(e, 7);
25
+ if (!evaluated) {
26
+ throw 'decode must force the thunk';
27
+ }
28
+ },
29
+ },
13
30
  foldStep: {
14
31
  empty: () => {
15
32
  const e = foldStep((x) => (s) => pure(s + x))(10)([]);
@@ -1,8 +1,7 @@
1
- import { pure } from "../effects/module.f.js";
2
1
  import { log } from "../effects/node/module.f.js";
3
- import { emptyState } from "../effects/node/virtual/module.f.js";
2
+ import { defaultNodeProgramOptions, emptyState } from "../effects/node/virtual/module.f.js";
4
3
  import { virtual } from "../effects/node/virtual/module.f.js";
5
- import { assert, assertEq, todo } from "../asserts/module.f.js";
4
+ import { assert, assertEq } from "../asserts/module.f.js";
6
5
  import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, registerModule, parseTestSet, defaultTest, } from "./module.f.js";
7
6
  import { run as mockRun } from "../effects/mock/module.f.js";
8
7
  import { shouldLoad } from "../dev/module.f.js";
@@ -13,16 +12,9 @@ const makeReporter = () => ({
13
12
  summary: (pass, fail, time) => writeEvent(['summary', pass, fail, time]),
14
13
  test: defaultTest,
15
14
  });
16
- const noopTestContext = { test: todo };
17
15
  const options = (initCwd, github = false) => ({
18
- args: [],
16
+ ...defaultNodeProgramOptions,
19
17
  env: { INIT_CWD: initCwd, ...(github ? { GITHUB_ACTIONS: 'true' } : {}) },
20
- home: '.',
21
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
22
- testContext: noopTestContext,
23
- bunTestContext: noopTestContext,
24
- playwrightTestContext: noopTestContext,
25
- engine: 'node',
26
18
  });
27
19
  const ok0 = () => ({ result: ['ok', undefined], duration: 0 });
28
20
  const fail0 = () => ({ result: ['error', 'oops'], duration: 0 });
@@ -5,14 +5,11 @@
5
5
  */
6
6
  import { compile } from "../djs/module.f.js";
7
7
  import { main as testMain } from "../emergent_testing/module.f.js";
8
- import { commands as casCommands } from "../cas/module.f.js";
8
+ import { commands as casCommands } from "../cas/cli/module.f.js";
9
9
  import { main as ciMain } from "../ci/module.f.js";
10
10
  import { import_ } from "../effects/node/module.f.js";
11
11
  import { dispatch } from "../cli/module.f.js";
12
12
  import { casMcpServer } from "../cas/mcp/module.f.js";
13
- import { cas, fileKvStore, toPath } from "../cas/module.f.js";
14
- import { sha256 } from "../crypto/sha2/module.f.js";
15
- import { join } from "../path/module.f.js";
16
13
  import { pure } from "../effects/module.f.js";
17
14
  const commands = [
18
15
  {
@@ -33,10 +30,7 @@ const commands = [
33
30
  {
34
31
  names: ['mcp', 'm'],
35
32
  description: 'Run an MCP server over stdio exposing the CAS as tools',
36
- handler: ({ home }) => {
37
- const c = cas(sha256)(fileKvStore(home));
38
- return casMcpServer(c, home, hash => join(home, toPath(hash))).step(() => pure(0));
39
- },
33
+ handler: ({ home }) => casMcpServer(home).step(() => pure(0)),
40
34
  },
41
35
  {
42
36
  names: ['ci', 'i'],
package/fs/fjs/proof.f.js CHANGED
@@ -1,17 +1,8 @@
1
1
  import { assert, assertEq } from "../asserts/module.f.js";
2
2
  import { pure } from "../effects/module.f.js";
3
- import { emptyState, virtual } from "../effects/node/virtual/module.f.js";
3
+ import { defaultNodeProgramOptions, emptyState, virtual } from "../effects/node/virtual/module.f.js";
4
4
  import { main } from "./module.f.js";
5
- const makeOptions = (args) => ({
6
- args,
7
- env: {},
8
- home: '.',
9
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
10
- testContext: { test: async () => { } },
11
- bunTestContext: { test: async () => { } },
12
- playwrightTestContext: { test: async () => { } },
13
- engine: 'node',
14
- });
5
+ const makeOptions = (args) => ({ ...defaultNodeProgramOptions, args });
15
6
  const run = (root) => (args) => virtual({ ...emptyState, root })(main(makeOptions(args)));
16
7
  const appMain = ({ args }) => pure(args.length);
17
8
  export const proof = {
@@ -46,15 +46,13 @@ const rawText = [
46
46
  /**
47
47
  * https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html
48
48
  */
49
- const escapeCharCode = (code) => {
50
- switch (code) {
51
- case quotationMark: return '&quot;';
52
- case ampersand: return '&amp;';
53
- case lessThanSign: return '&lt;';
54
- case greaterThanSign: return '&gt;';
55
- default: return fromCharCode(code);
56
- }
49
+ const escapeTable = {
50
+ [quotationMark]: '&quot;',
51
+ [ampersand]: '&amp;',
52
+ [lessThanSign]: '&lt;',
53
+ [greaterThanSign]: '&gt;',
57
54
  };
55
+ const escapeCharCode = (code) => escapeTable[code] ?? fromCharCode(code);
58
56
  const escape = compose(stringToList)(map(escapeCharCode));
59
57
  const node = (n) => typeof n === 'string' ? escape(n) : element(n);
60
58
  const nodes = flatMap(node);
@@ -95,4 +95,9 @@ export type JsTokenWithMetadata = {
95
95
  type ErrorMessage = '" are missing' | 'unescaped character' | 'invalid hex value' | 'unexpected character' | 'invalid number' | 'invalid token' | '*\/ expected' | 'unterminated string literal' | 'eof';
96
96
  export declare const isKeywordToken: (token: JsToken) => boolean;
97
97
  export declare const tokenize: (input: List<number>) => (path: string) => List<JsTokenWithMetadata>;
98
+ export declare const proof: {
99
+ throw: {
100
+ unionConflict: () => void;
101
+ };
102
+ };
98
103
  export {};
@@ -27,6 +27,7 @@ leftSquareBracket, reverseSolidus, rightSquareBracket, lowLine,
27
27
  latinSmallLetterRange, latinSmallLetterA, latinSmallLetterB, latinSmallLetterE, latinSmallLetterF, latinSmallLetterN, latinSmallLetterR, latinSmallLetterT, latinSmallLetterU,
28
28
  //
29
29
  leftCurlyBracket, rightCurlyBracket, dollarSign } from "../../text/ascii/module.f.js";
30
+ import { todo } from "../../asserts/module.f.js";
30
31
  const { fromCharCode } = String;
31
32
  const rangeOneNine = range('19');
32
33
  const rangeSetNewLine = [
@@ -519,3 +520,15 @@ export const tokenize = input => path => {
519
520
  const scan = scanTokenize({ state: { kind: 'initial' }, metadata: { path, line: 1, column: 1 } });
520
521
  return flat(scan(flat([input /*satisfies List<CharCodeOrEof>*/, [null]])));
521
522
  };
523
+ export const proof = {
524
+ throw: {
525
+ // union throws when two distinct non-default handlers are merged for the same range;
526
+ // this path is unreachable through the public API (no overlapping ranges in practice).
527
+ unionConflict: () => {
528
+ const def = (_) => todo;
529
+ const a = (_) => todo;
530
+ const b = (_) => todo;
531
+ union(def)(a)(b);
532
+ }
533
+ }
534
+ };
@@ -4,7 +4,6 @@
4
4
  * @module
5
5
  */
6
6
  import { flat, reduce, empty } from "../../types/list/module.f.js";
7
- import {} from "../../types/object/module.f.js";
8
7
  import {} from "../../types/function/operator/module.f.js";
9
8
  const jsonStringify = JSON.stringify;
10
9
  /**
@@ -229,4 +229,4 @@ export type McpConfig = {
229
229
  * - `tools/list` params (an optional pagination `cursor`) are validated and passed
230
230
  * to the handler; invalid params → -32602.
231
231
  */
232
- export declare const mcpStep: <O extends Operation>({ protocolVersion, capabilities, serverInfo, }: McpConfig) => (handlers: McpHandlers<O>) => (stateKey: Key<McpSessionState>) => (value: Unknown) => Effect<MemOp | O, Response | null>;
232
+ export declare const mcpStep: ({ protocolVersion, capabilities, serverInfo, }: McpConfig) => <O extends Operation>(handlers: McpHandlers<O>) => (stateKey: Key<McpSessionState>) => (value: Unknown) => Effect<MemOp | O, Response | null>;