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.
- package/README.md +1 -1
- package/fs/asserts/module.f.d.ts +1 -1
- package/fs/asserts/module.f.js +4 -1
- package/fs/bnf/data/module.f.d.ts +0 -103
- package/fs/bnf/data/module.f.js +10 -246
- package/fs/bnf/data/proof.f.d.ts +0 -7
- package/fs/bnf/data/proof.f.js +3 -636
- package/fs/bnf/descent/module.f.d.ts +56 -0
- package/fs/bnf/descent/module.f.js +119 -0
- package/fs/bnf/descent/proof.f.d.ts +5 -0
- package/fs/bnf/descent/proof.f.js +296 -0
- package/fs/bnf/ll1/module.f.d.ts +72 -0
- package/fs/bnf/ll1/module.f.js +153 -0
- package/fs/bnf/ll1/proof.f.d.ts +9 -0
- package/fs/bnf/ll1/proof.f.js +358 -0
- package/fs/cas/cli/module.f.d.ts +6 -0
- package/fs/cas/cli/module.f.js +56 -0
- package/fs/cas/cli/proof.f.d.ts +14 -0
- package/fs/cas/cli/proof.f.js +149 -0
- package/fs/cas/mcp/module.f.d.ts +9 -17
- package/fs/cas/mcp/module.f.js +117 -83
- package/fs/cas/mcp/proof.f.d.ts +5 -1
- package/fs/cas/mcp/proof.f.js +79 -32
- package/fs/cas/module.f.d.ts +33 -27
- package/fs/cas/module.f.js +151 -126
- package/fs/cas/proof.f.d.ts +16 -13
- package/fs/cas/proof.f.js +255 -130
- package/fs/ci/config/module.f.d.ts +15 -15
- package/fs/ci/config/module.f.js +15 -15
- package/fs/ci/proof.f.js +8 -6
- package/fs/cli/proof.f.js +2 -12
- package/fs/djs/parser/module.f.d.ts +0 -5
- package/fs/djs/proof.f.js +5 -6
- package/fs/djs/serializer/module.f.d.ts +1 -3
- package/fs/djs/tokenizer-new/module.f.js +3 -2
- package/fs/djs/tokenizer-new/proof.f.d.ts +3 -0
- package/fs/djs/tokenizer-new/proof.f.js +458 -296
- package/fs/djs/transpiler/proof.f.js +10 -10
- package/fs/effects/list/module.f.d.ts +31 -0
- package/fs/effects/list/module.f.js +16 -0
- package/fs/effects/module.f.d.ts +11 -5
- package/fs/effects/module.f.js +16 -6
- package/fs/effects/node/module.f.d.ts +29 -3
- package/fs/effects/node/module.f.js +35 -1
- package/fs/effects/node/module.js +32 -18
- package/fs/effects/node/proof.f.js +36 -36
- package/fs/effects/node/virtual/module.f.d.ts +16 -2
- package/fs/effects/node/virtual/module.f.js +129 -20
- package/fs/effects/node/virtual/proof.f.d.ts +4 -0
- package/fs/effects/node/virtual/proof.f.js +47 -14
- package/fs/effects/proof.f.d.ts +4 -0
- package/fs/effects/proof.f.js +18 -1
- package/fs/emergent_testing/proof.f.js +3 -11
- package/fs/fjs/module.f.js +2 -8
- package/fs/fjs/proof.f.js +2 -11
- package/fs/html/module.f.js +6 -8
- package/fs/js/tokenizer/module.f.d.ts +5 -0
- package/fs/js/tokenizer/module.f.js +13 -0
- package/fs/json/serializer/module.f.js +0 -1
- package/fs/mcp/module.f.d.ts +1 -1
- package/fs/mime/module.f.d.ts +101 -5
- package/fs/mime/module.f.js +159 -6
- package/fs/mime/proof.f.d.ts +25 -0
- package/fs/mime/proof.f.js +193 -3
- package/fs/text/code_point/module.f.d.ts +39 -2
- package/fs/text/code_point/module.f.js +74 -2
- package/fs/text/code_point/proof.f.d.ts +8 -0
- package/fs/text/code_point/proof.f.js +72 -0
- package/fs/text/sgr/proof.f.js +2 -11
- package/fs/text/utf16/module.f.js +4 -26
- package/fs/text/utf8/module.f.d.ts +27 -0
- package/fs/text/utf8/module.f.js +8 -14
- package/fs/types/bigint/module.f.d.ts +8 -15
- package/fs/types/bigint/module.f.js +9 -22
- package/fs/types/bigint/proof.f.d.ts +0 -2
- package/fs/types/bigint/proof.f.js +6 -16
- package/fs/types/function/operator/module.f.d.ts +26 -0
- package/fs/types/list/module.f.d.ts +3 -0
- package/fs/types/result/module.d.ts +2 -4
- package/fs/types/result/module.js +9 -3
- package/package.json +3 -3
- package/issues/demo/data/data.f.js +0 -12
- package/issues/demo/data/shared.f.js +0 -3
- package/issues/demo/fs/app.js +0 -4
- package/issues/demo/fs/math.f.js +0 -4
- package/issues/demo/sample/proof.f.js +0 -13
- /package/{issues → todo}/031-json.f.d.ts +0 -0
- /package/{issues → todo}/031-json.f.js +0 -0
- /package/{issues → todo}/proof.f.d.ts +0 -0
- /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 {
|
|
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
|
-
|
|
71
|
-
if (!isVec(file)) {
|
|
70
|
+
if (!Array.isArray(file)) {
|
|
72
71
|
return error(`'${path[0]}' is not a file`);
|
|
73
72
|
}
|
|
74
|
-
|
|
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
|
-
|
|
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 ||
|
|
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 (
|
|
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 (!
|
|
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
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
};
|
package/fs/effects/proof.f.d.ts
CHANGED
package/fs/effects/proof.f.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 });
|
package/fs/fjs/module.f.js
CHANGED
|
@@ -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 = {
|
package/fs/html/module.f.js
CHANGED
|
@@ -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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
case greaterThanSign: return '>';
|
|
55
|
-
default: return fromCharCode(code);
|
|
56
|
-
}
|
|
49
|
+
const escapeTable = {
|
|
50
|
+
[quotationMark]: '"',
|
|
51
|
+
[ampersand]: '&',
|
|
52
|
+
[lessThanSign]: '<',
|
|
53
|
+
[greaterThanSign]: '>',
|
|
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
|
+
};
|
package/fs/mcp/module.f.d.ts
CHANGED
|
@@ -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:
|
|
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>;
|