functionalscript 0.29.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fs/asn.1/module.f.js +18 -22
- package/fs/asn.1/proof.f.d.ts +5 -0
- package/fs/asn.1/proof.f.js +38 -2
- package/fs/base64/module.f.d.ts +9 -0
- package/fs/base64/module.f.js +60 -0
- package/fs/base64/proof.f.d.ts +11 -0
- package/fs/base64/proof.f.js +117 -0
- package/fs/base_n/module.f.d.ts +44 -0
- package/fs/base_n/module.f.js +54 -0
- package/fs/base_n/proof.f.d.ts +10 -0
- package/fs/base_n/proof.f.js +86 -0
- package/fs/bnf/data/module.f.d.ts +4 -9
- package/fs/bnf/data/module.f.js +4 -3
- package/fs/bnf/module.f.d.ts +3 -4
- package/fs/bnf/module.f.js +3 -2
- package/fs/bnf/proof.f.d.ts +9 -0
- package/fs/bnf/proof.f.js +98 -1
- package/fs/cas/mcp/module.f.d.ts +40 -0
- package/fs/cas/mcp/module.f.js +223 -0
- package/fs/cas/mcp/proof.f.d.ts +30 -0
- package/fs/cas/mcp/proof.f.js +310 -0
- package/fs/cas/module.f.d.ts +9 -3
- package/fs/cas/module.f.js +104 -69
- package/fs/cas/proof.f.d.ts +2 -0
- package/fs/cas/proof.f.js +30 -1
- package/fs/cbase32/module.f.d.ts +1 -1
- package/fs/cbase32/module.f.js +19 -40
- package/fs/cbase32/proof.f.d.ts +1 -0
- package/fs/cbase32/proof.f.js +15 -0
- package/fs/ci/bun/module.f.d.ts +2 -2
- package/fs/ci/bun/module.f.js +7 -14
- package/fs/ci/common/module.f.d.ts +3 -1
- package/fs/ci/common/module.f.js +9 -2
- package/fs/ci/config/module.f.d.ts +7 -5
- package/fs/ci/config/module.f.js +11 -5
- package/fs/ci/deno/module.f.d.ts +1 -1
- package/fs/ci/deno/module.f.js +7 -5
- package/fs/ci/module.f.d.ts +6 -3
- package/fs/ci/module.f.js +24 -54
- package/fs/ci/node/module.f.d.ts +3 -3
- package/fs/ci/node/module.f.js +30 -26
- package/fs/ci/playwright/module.f.js +1 -1
- package/fs/ci/proof.f.d.ts +6 -7
- package/fs/ci/proof.f.js +63 -36
- package/fs/ci/rust/module.f.d.ts +2 -1
- package/fs/ci/rust/module.f.js +33 -16
- package/fs/cli/module.f.d.ts +3 -1
- package/fs/cli/module.f.js +22 -12
- package/fs/cli/proof.f.d.ts +5 -0
- package/fs/cli/proof.f.js +76 -0
- package/fs/crypto/hmac/proof.f.d.ts +1 -0
- package/fs/crypto/hmac/proof.f.js +8 -0
- package/fs/crypto/secp/proof.f.d.ts +1 -0
- package/fs/crypto/secp/proof.f.js +10 -0
- package/fs/crypto/sign/module.f.js +1 -4
- package/fs/dev/module.f.d.ts +5 -3
- package/fs/dev/module.f.js +13 -2
- package/fs/dev/package_json/proof.f.d.ts +1 -0
- package/fs/dev/package_json/proof.f.js +5 -0
- package/fs/dev/proof.f.d.ts +1 -0
- package/fs/dev/proof.f.js +12 -1
- package/fs/djs/ast/module.f.d.ts +1 -1
- package/fs/djs/ast/proof.f.d.ts +5 -0
- package/fs/djs/ast/proof.f.js +17 -0
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +2 -3
- package/fs/djs/serializer/module.f.js +24 -11
- package/fs/djs/transpiler/module.f.js +3 -4
- package/fs/effects/memory/module.f.d.ts +37 -0
- package/fs/effects/memory/module.f.js +24 -0
- package/fs/effects/memory/proof.f.d.ts +6 -0
- package/fs/effects/memory/proof.f.js +64 -0
- package/fs/effects/mock/module.f.d.ts +7 -2
- package/fs/effects/mock/module.f.js +12 -8
- package/fs/effects/module.d.ts +1 -1
- package/fs/effects/module.f.d.ts +41 -0
- package/fs/effects/module.f.js +25 -0
- package/fs/effects/module.js +6 -7
- package/fs/effects/node/memory/module.d.ts +20 -0
- package/fs/effects/node/memory/module.js +44 -0
- package/fs/effects/node/memory/proof.d.ts +10 -0
- package/fs/effects/node/memory/proof.js +35 -0
- package/fs/effects/node/module.d.ts +1 -1
- package/fs/effects/node/module.f.d.ts +54 -8
- package/fs/effects/node/module.f.js +55 -4
- package/fs/effects/node/module.js +166 -135
- package/fs/effects/node/proof.f.d.ts +10 -0
- package/fs/effects/node/proof.f.js +88 -21
- package/fs/effects/node/virtual/module.f.d.ts +6 -0
- package/fs/effects/node/virtual/module.f.js +48 -16
- package/fs/effects/node/virtual/proof.f.d.ts +17 -0
- package/fs/effects/node/virtual/proof.f.js +79 -0
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +54 -34
- package/fs/emergent_testing/module.f.d.ts +6 -1
- package/fs/emergent_testing/module.f.js +8 -3
- package/fs/emergent_testing/proof.f.d.ts +11 -0
- package/fs/emergent_testing/proof.f.js +52 -16
- package/fs/fjs/module.f.js +2 -2
- package/fs/fjs/proof.f.d.ts +8 -0
- package/fs/fjs/proof.f.js +39 -0
- package/fs/fsc/module.f.d.ts +5 -0
- package/fs/fsc/module.f.js +17 -27
- package/fs/fsc/proof.f.d.ts +8 -0
- package/fs/fsc/proof.f.js +48 -2
- package/fs/fsm/module.f.d.ts +2 -3
- package/fs/fsm/module.f.js +1 -1
- package/fs/html/module.f.d.ts +2 -3
- package/fs/html/module.f.js +7 -6
- package/fs/html/proof.f.d.ts +1 -0
- package/fs/html/proof.f.js +7 -0
- package/fs/js/tokenizer/module.f.js +1 -1
- package/fs/js/tokenizer/proof.f.js +7 -0
- package/fs/json/module.f.d.ts +9 -2
- package/fs/json/module.f.js +9 -7
- package/fs/json/parser/module.f.js +4 -6
- package/fs/json/parser/proof.f.js +19 -10
- package/fs/json/proof.f.d.ts +2 -1
- package/fs/json/proof.f.js +33 -6
- package/fs/json/rpc/module.f.d.ts +5 -0
- package/fs/json/rpc/module.f.js +16 -11
- package/fs/json/schema/module.f.d.ts +15 -14
- package/fs/json/schema/proof.f.d.ts +3 -0
- package/fs/json/schema/proof.f.js +16 -1
- package/fs/mcp/module.f.d.ts +103 -5
- package/fs/mcp/module.f.js +139 -2
- package/fs/mcp/proof.f.d.ts +34 -0
- package/fs/mcp/proof.f.js +207 -0
- package/fs/mcp/stdio/module.f.d.ts +39 -0
- package/fs/mcp/stdio/module.f.js +54 -0
- package/fs/mcp/stdio/proof.f.d.ts +10 -0
- package/fs/mcp/stdio/proof.f.js +92 -0
- package/fs/mime/module.f.d.ts +39 -0
- package/fs/mime/module.f.js +78 -0
- package/fs/mime/proof.f.d.ts +15 -0
- package/fs/mime/proof.f.js +30 -0
- package/fs/path/module.f.d.ts +8 -0
- package/fs/path/module.f.js +10 -3
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/text/sgr/proof.f.js +30 -2
- package/fs/text/utf8/module.f.d.ts +7 -0
- package/fs/text/utf8/module.f.js +85 -31
- package/fs/text/utf8/proof.f.d.ts +1 -0
- package/fs/text/utf8/proof.f.js +60 -1
- package/fs/types/bigfloat/module.f.d.ts +3 -0
- package/fs/types/bigfloat/module.f.js +30 -31
- package/fs/types/bigint/module.f.d.ts +24 -2
- package/fs/types/bigint/module.f.js +30 -2
- package/fs/types/bigint/proof.f.d.ts +7 -0
- package/fs/types/bigint/proof.f.js +47 -16
- package/fs/types/bit_vec/module.f.d.ts +18 -0
- package/fs/types/bit_vec/module.f.js +18 -0
- package/fs/types/bit_vec/proof.f.d.ts +1 -0
- package/fs/types/bit_vec/proof.f.js +13 -1
- package/fs/types/function/operator/proof.f.d.ts +2 -0
- package/fs/types/function/operator/proof.f.js +20 -2
- package/fs/types/list/module.f.js +1 -4
- package/fs/types/list/proof.f.d.ts +1 -1
- package/fs/types/list/proof.f.js +14 -6
- package/fs/types/map/proof.f.d.ts +1 -0
- package/fs/types/map/proof.f.js +10 -1
- package/fs/types/nibble_set/module.f.d.ts +13 -0
- package/fs/types/nibble_set/module.f.js +16 -0
- package/fs/types/object/module.f.d.ts +12 -8
- package/fs/types/object/module.f.js +9 -2
- package/fs/types/object/proof.f.d.ts +14 -0
- package/fs/types/object/proof.f.js +4 -0
- package/fs/types/ordered_map/proof.f.d.ts +1 -0
- package/fs/types/ordered_map/proof.f.js +39 -79
- package/fs/types/phantom/module.f.d.ts +22 -0
- package/fs/types/phantom/module.f.js +11 -0
- package/fs/types/prime_field/proof.f.d.ts +7 -0
- package/fs/types/prime_field/proof.f.js +63 -122
- package/fs/types/rtti/common/module.f.d.ts +4 -4
- package/fs/types/rtti/common/module.f.js +1 -1
- package/fs/types/rtti/module.f.d.ts +2 -3
- package/fs/types/rtti/parse/module.f.js +0 -1
- package/fs/types/rtti/ts/module.f.d.ts +26 -32
- package/fs/types/rtti/ts/module.f.js +2 -2
- package/fs/types/rtti/ts/proof.f.js +3 -3
- package/fs/types/rtti/validate/module.f.js +0 -1
- package/fs/types/sorted_list/module.f.d.ts +6 -0
- package/fs/types/sorted_list/module.f.js +11 -0
- package/fs/types/sorted_set/module.f.js +2 -8
- package/fs/types/ts/module.f.d.ts +1 -1
- package/fs/types/ts/module.f.js +3 -1
- package/fs/types/ts/proof.f.js +3 -2
- package/fs/website/proof.f.d.ts +1 -0
- package/fs/website/proof.f.js +8 -1
- package/package.json +3 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio transport for JSON-RPC / MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* `stdioTransport` wraps a step function — the `mcpStep`-shaped
|
|
5
|
+
* `(value) => Effect<O, Response | null>` from `fs/mcp/module.f.ts` — in the
|
|
6
|
+
* canonical read → parse → dispatch → write loop, expressed as a recursive
|
|
7
|
+
* effect so it stays in the pure effect model and is fully testable against a
|
|
8
|
+
* mock stdin / stdout (see `fs/effects/node/virtual`) with no real process.
|
|
9
|
+
*
|
|
10
|
+
* The loop only consumes `Read` (stdin, one byte at a time, via the pure
|
|
11
|
+
* `readLine` combinator) and emits `Write` (stdout) on top of whatever `O` the
|
|
12
|
+
* step needs (e.g. `MemOp`); it is transport-agnostic and carries no filesystem
|
|
13
|
+
* dependency.
|
|
14
|
+
*
|
|
15
|
+
* Edge cases, matching the issue's spec:
|
|
16
|
+
* - `readLine` yields `null` (EOF) → the loop returns, a clean shutdown.
|
|
17
|
+
* - a malformed JSON line → a JSON-RPC parse-error response (`-32700`, `id`
|
|
18
|
+
* `null`) is written rather than silently discarded, per JSON-RPC 2.0 §5.
|
|
19
|
+
* - the step yields `null` (a notification needing no reply) → nothing is
|
|
20
|
+
* written and the loop continues.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { type Effect, type Operation } from '../../effects/module.f.ts';
|
|
25
|
+
import { type Read, type Write } from '../../effects/node/module.f.ts';
|
|
26
|
+
import { type Unknown } from '../../json/module.f.ts';
|
|
27
|
+
import { type Response } from '../../json/rpc/module.f.ts';
|
|
28
|
+
/**
|
|
29
|
+
* A transport step: maps one parsed JSON-RPC message to a response, or `null`
|
|
30
|
+
* for a notification that needs no reply. The shape of `mcpStep(config)(handlers)(key)`.
|
|
31
|
+
*/
|
|
32
|
+
export type Step<O extends Operation> = (value: Unknown) => Effect<O, Response | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Drives the read-parse-dispatch-write loop for `step` over stdin/stdout.
|
|
35
|
+
*
|
|
36
|
+
* Recurses after each handled line; terminates (resolving to `void`) when
|
|
37
|
+
* `readLine` reports EOF.
|
|
38
|
+
*/
|
|
39
|
+
export declare const stdioTransport: <O extends Operation>(step: Step<O>) => Effect<Read | Write | O, void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio transport for JSON-RPC / MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* `stdioTransport` wraps a step function — the `mcpStep`-shaped
|
|
5
|
+
* `(value) => Effect<O, Response | null>` from `fs/mcp/module.f.ts` — in the
|
|
6
|
+
* canonical read → parse → dispatch → write loop, expressed as a recursive
|
|
7
|
+
* effect so it stays in the pure effect model and is fully testable against a
|
|
8
|
+
* mock stdin / stdout (see `fs/effects/node/virtual`) with no real process.
|
|
9
|
+
*
|
|
10
|
+
* The loop only consumes `Read` (stdin, one byte at a time, via the pure
|
|
11
|
+
* `readLine` combinator) and emits `Write` (stdout) on top of whatever `O` the
|
|
12
|
+
* step needs (e.g. `MemOp`); it is transport-agnostic and carries no filesystem
|
|
13
|
+
* dependency.
|
|
14
|
+
*
|
|
15
|
+
* Edge cases, matching the issue's spec:
|
|
16
|
+
* - `readLine` yields `null` (EOF) → the loop returns, a clean shutdown.
|
|
17
|
+
* - a malformed JSON line → a JSON-RPC parse-error response (`-32700`, `id`
|
|
18
|
+
* `null`) is written rather than silently discarded, per JSON-RPC 2.0 §5.
|
|
19
|
+
* - the step yields `null` (a notification needing no reply) → nothing is
|
|
20
|
+
* written and the loop continues.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { pure } from "../../effects/module.f.js";
|
|
25
|
+
import { readLine, write } from "../../effects/node/module.f.js";
|
|
26
|
+
import { utf8 } from "../../text/module.f.js";
|
|
27
|
+
import { stringToList } from "../../text/utf16/module.f.js";
|
|
28
|
+
import { stringify } from "../../json/module.f.js";
|
|
29
|
+
import { tokenize } from "../../json/tokenizer/module.f.js";
|
|
30
|
+
import { parse } from "../../json/parser/module.f.js";
|
|
31
|
+
import { sort } from "../../types/object/module.f.js";
|
|
32
|
+
import { jsonrpc, parseError } from "../../json/rpc/module.f.js";
|
|
33
|
+
const stringifyJson = stringify(sort);
|
|
34
|
+
/** The parse-error response (`-32700`, `id: null`) for a malformed input line. */
|
|
35
|
+
const parseErrorResponse = { jsonrpc, error: parseError, id: null };
|
|
36
|
+
/** Encodes a response as a newline-terminated UTF-8 line and writes it to `stdout`. */
|
|
37
|
+
const writeResponse = (resp) => write('stdout', utf8(stringifyJson(resp) + '\n'));
|
|
38
|
+
/** Writes `resp` when present, otherwise does nothing (notification). */
|
|
39
|
+
const writeMaybe = (resp) => resp === null ? pure(undefined) : writeResponse(resp);
|
|
40
|
+
/**
|
|
41
|
+
* Drives the read-parse-dispatch-write loop for `step` over stdin/stdout.
|
|
42
|
+
*
|
|
43
|
+
* Recurses after each handled line; terminates (resolving to `void`) when
|
|
44
|
+
* `readLine` reports EOF.
|
|
45
|
+
*/
|
|
46
|
+
export const stdioTransport = (step) => readLine('stdin').step(line => line === null
|
|
47
|
+
? pure(undefined)
|
|
48
|
+
: handleLine(step)(line));
|
|
49
|
+
const handleLine = (step) => (line) => {
|
|
50
|
+
const [t, value] = parse(tokenize(stringToList(line)));
|
|
51
|
+
return (t === 'error'
|
|
52
|
+
? writeResponse(parseErrorResponse)
|
|
53
|
+
: step(value).step(writeMaybe)).step(() => stdioTransport(step));
|
|
54
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
eofImmediately: () => void;
|
|
3
|
+
requestWritesResponse: () => void;
|
|
4
|
+
requestWithoutTrailingNewline: () => void;
|
|
5
|
+
notificationWritesNothing: () => void;
|
|
6
|
+
malformedJsonWritesParseError: () => void;
|
|
7
|
+
trailingCommaWritesParseError: () => void;
|
|
8
|
+
undefinedFieldOmitted: () => void;
|
|
9
|
+
multipleLines: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { assertEq } from "../../asserts/module.f.js";
|
|
2
|
+
import { pure } from "../../effects/module.f.js";
|
|
3
|
+
import { emptyState, virtual } from "../../effects/node/virtual/module.f.js";
|
|
4
|
+
import { stringify } from "../../json/module.f.js";
|
|
5
|
+
import { utf8 } from "../../text/module.f.js";
|
|
6
|
+
import { fromVec } from "../../types/uint8array/module.f.js";
|
|
7
|
+
import { sort } from "../../types/object/module.f.js";
|
|
8
|
+
import { jsonrpc, parseError } from "../../json/rpc/module.f.js";
|
|
9
|
+
import { stdioTransport } from "./module.f.js";
|
|
10
|
+
const stringifyJson = stringify(sort);
|
|
11
|
+
// Extracts the request `id` (a request has one; a notification does not).
|
|
12
|
+
const idOf = (value) => value !== null && typeof value === 'object' && !(value instanceof Array)
|
|
13
|
+
? value.id
|
|
14
|
+
: undefined;
|
|
15
|
+
// A step that mirrors the mcpStep contract closely enough to drive the loop:
|
|
16
|
+
// a message carrying an `id` (a request) gets an echo success response; a
|
|
17
|
+
// message without an `id` (a notification) yields `null` — no reply.
|
|
18
|
+
const echoStep = (value) => {
|
|
19
|
+
const id = idOf(value);
|
|
20
|
+
return pure(id === undefined
|
|
21
|
+
? null
|
|
22
|
+
: { jsonrpc, result: { ok: true }, id });
|
|
23
|
+
};
|
|
24
|
+
// UTF-8 bytes of `s` as a plain array — the virtual stdin byte stream.
|
|
25
|
+
const toBytes = (s) => [...fromVec(utf8(s))];
|
|
26
|
+
// Run the transport with `step` over `input` fed to stdin one byte at a time;
|
|
27
|
+
// return the final state. `input` is raw text so tests control newline framing.
|
|
28
|
+
const runStep = (step) => (input) => virtual({ ...emptyState, stdin: toBytes(input) })(stdioTransport(step))[0];
|
|
29
|
+
const run = runStep(echoStep);
|
|
30
|
+
const okResponse = (id) => stringifyJson({ jsonrpc, result: { ok: true }, id }) + '\n';
|
|
31
|
+
const parseErrorLine = stringifyJson({ jsonrpc, error: parseError, id: null }) + '\n';
|
|
32
|
+
const ping = (id) => `{"jsonrpc":"2.0","method":"ping","id":${id}}`;
|
|
33
|
+
const notification = '{"jsonrpc":"2.0","method":"notifications/initialized"}';
|
|
34
|
+
export const proof = {
|
|
35
|
+
// EOF on the very first read: clean shutdown, nothing written, no further reads.
|
|
36
|
+
eofImmediately: () => {
|
|
37
|
+
const state = run('');
|
|
38
|
+
assertEq(state.stdout, '');
|
|
39
|
+
assertEq(state.stdin.length, 0);
|
|
40
|
+
},
|
|
41
|
+
// A newline-terminated request is parsed, dispatched, and its response
|
|
42
|
+
// written; stdin is fully drained before the terminating EOF.
|
|
43
|
+
requestWritesResponse: () => {
|
|
44
|
+
const state = run(ping(1) + '\n');
|
|
45
|
+
assertEq(state.stdout, okResponse(1));
|
|
46
|
+
assertEq(state.stdin.length, 0);
|
|
47
|
+
},
|
|
48
|
+
// A final line lacking a trailing newline is still flushed and dispatched
|
|
49
|
+
// (covers `readLine`'s EOF-with-buffered-bytes branch).
|
|
50
|
+
requestWithoutTrailingNewline: () => {
|
|
51
|
+
const state = run(ping(2));
|
|
52
|
+
assertEq(state.stdout, okResponse(2));
|
|
53
|
+
},
|
|
54
|
+
// A notification (no `id`) yields a `null` step result → nothing is written.
|
|
55
|
+
notificationWritesNothing: () => {
|
|
56
|
+
const state = run(notification + '\n');
|
|
57
|
+
assertEq(state.stdout, '');
|
|
58
|
+
},
|
|
59
|
+
// A malformed JSON line produces a JSON-RPC parse-error response (-32700),
|
|
60
|
+
// not a silent discard.
|
|
61
|
+
malformedJsonWritesParseError: () => {
|
|
62
|
+
const state = run('not json\n');
|
|
63
|
+
assertEq(state.stdout, parseErrorLine);
|
|
64
|
+
},
|
|
65
|
+
// A request that is not strict JSON (trailing comma) must be rejected with
|
|
66
|
+
// a parse error, never dispatched to the step.
|
|
67
|
+
trailingCommaWritesParseError: () => {
|
|
68
|
+
const state = run('{"jsonrpc":"2.0","method":"ping","id":1,}\n');
|
|
69
|
+
assertEq(state.stdout, parseErrorLine);
|
|
70
|
+
},
|
|
71
|
+
// A response with an optional field explicitly `undefined` serializes like
|
|
72
|
+
// JSON.stringify — the field is omitted, not a thrown TypeError that would
|
|
73
|
+
// abort the loop.
|
|
74
|
+
undefinedFieldOmitted: () => {
|
|
75
|
+
const step = (value) => {
|
|
76
|
+
const id = idOf(value);
|
|
77
|
+
return pure(id === undefined
|
|
78
|
+
? null
|
|
79
|
+
: { jsonrpc, result: { ok: true, nextCursor: undefined }, id });
|
|
80
|
+
};
|
|
81
|
+
const state = runStep(step)(ping(1) + '\n');
|
|
82
|
+
assertEq(state.stdout, okResponse(1));
|
|
83
|
+
},
|
|
84
|
+
// A multi-line session interleaving all cases: request, notification, and
|
|
85
|
+
// malformed line, ending with an unterminated request. Order is preserved
|
|
86
|
+
// and the notification contributes nothing.
|
|
87
|
+
multipleLines: () => {
|
|
88
|
+
const state = run([ping(1), notification, 'not json', ping(2)].join('\n'));
|
|
89
|
+
assertEq(state.stdout, okResponse(1) + parseErrorLine + okResponse(2));
|
|
90
|
+
assertEq(state.stdin.length, 0);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic-byte MIME type detection.
|
|
3
|
+
*
|
|
4
|
+
* A pure table lookup over the leading bytes of a `Vec`: no I/O, no
|
|
5
|
+
* dependencies beyond `fs/types/bit_vec`. `detect` returns a MIME type string
|
|
6
|
+
* for the container formats whose signatures it knows, or `null` for anything
|
|
7
|
+
* else — text, unknown binary, or a `Vec` too short to match.
|
|
8
|
+
*
|
|
9
|
+
* The CAS store is type-agnostic and keeps raw bytes only, so type is never
|
|
10
|
+
* stored; it is recovered on read by sniffing the content. Callers decide what
|
|
11
|
+
* `null` means: the CAS MCP adapter falls back to a plain text result.
|
|
12
|
+
*
|
|
13
|
+
* ## Recognised signatures
|
|
14
|
+
*
|
|
15
|
+
* | MIME type | Leading bytes |
|
|
16
|
+
* |-------------------|----------------------------------------|
|
|
17
|
+
* | `image/png` | `89 50 4E 47 0D 0A 1A 0A` |
|
|
18
|
+
* | `image/jpeg` | `FF D8 FF` |
|
|
19
|
+
* | `image/gif` | `47 49 46 38 37 61` / `…39 61` (`"GIF87a"` / `"GIF89a"`) |
|
|
20
|
+
* | `image/webp` | `52 49 46 46 .. .. .. .. 57 45 42 50` (`"RIFF"…"WEBP"`) |
|
|
21
|
+
* | `application/pdf` | `25 50 44 46 2D` (`"%PDF-"`) |
|
|
22
|
+
* | `application/zip` | `50 4B 03 04` / `05 06` / `07 08` (`"PK"` entry, empty, or spanned) |
|
|
23
|
+
*
|
|
24
|
+
* WebP is the one signature with a gap: the four-byte little-endian file size
|
|
25
|
+
* sits between the `RIFF` and `WEBP` markers, so it is matched as a prefix plus
|
|
26
|
+
* a second marker at byte offset 8 rather than a single contiguous run.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { type Vec } from '../types/bit_vec/module.f.ts';
|
|
31
|
+
import type { Nullable } from '../types/nullable/module.f.ts';
|
|
32
|
+
/**
|
|
33
|
+
* Detects the MIME type of `bytes` from its leading magic-byte signature.
|
|
34
|
+
*
|
|
35
|
+
* @returns the MIME type string for a recognised format, or `null` when the
|
|
36
|
+
* leading bytes match no known signature (including any `Vec` shorter than
|
|
37
|
+
* the signature it might otherwise match).
|
|
38
|
+
*/
|
|
39
|
+
export declare const detect: (bytes: Vec) => Nullable<string>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic-byte MIME type detection.
|
|
3
|
+
*
|
|
4
|
+
* A pure table lookup over the leading bytes of a `Vec`: no I/O, no
|
|
5
|
+
* dependencies beyond `fs/types/bit_vec`. `detect` returns a MIME type string
|
|
6
|
+
* for the container formats whose signatures it knows, or `null` for anything
|
|
7
|
+
* else — text, unknown binary, or a `Vec` too short to match.
|
|
8
|
+
*
|
|
9
|
+
* The CAS store is type-agnostic and keeps raw bytes only, so type is never
|
|
10
|
+
* stored; it is recovered on read by sniffing the content. Callers decide what
|
|
11
|
+
* `null` means: the CAS MCP adapter falls back to a plain text result.
|
|
12
|
+
*
|
|
13
|
+
* ## Recognised signatures
|
|
14
|
+
*
|
|
15
|
+
* | MIME type | Leading bytes |
|
|
16
|
+
* |-------------------|----------------------------------------|
|
|
17
|
+
* | `image/png` | `89 50 4E 47 0D 0A 1A 0A` |
|
|
18
|
+
* | `image/jpeg` | `FF D8 FF` |
|
|
19
|
+
* | `image/gif` | `47 49 46 38 37 61` / `…39 61` (`"GIF87a"` / `"GIF89a"`) |
|
|
20
|
+
* | `image/webp` | `52 49 46 46 .. .. .. .. 57 45 42 50` (`"RIFF"…"WEBP"`) |
|
|
21
|
+
* | `application/pdf` | `25 50 44 46 2D` (`"%PDF-"`) |
|
|
22
|
+
* | `application/zip` | `50 4B 03 04` / `05 06` / `07 08` (`"PK"` entry, empty, or spanned) |
|
|
23
|
+
*
|
|
24
|
+
* WebP is the one signature with a gap: the four-byte little-endian file size
|
|
25
|
+
* sits between the `RIFF` and `WEBP` markers, so it is matched as a prefix plus
|
|
26
|
+
* a second marker at byte offset 8 rather than a single contiguous run.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { msb, fromSentinel, length } from "../types/bit_vec/module.f.js";
|
|
31
|
+
const { startsWith, removeFront } = msb;
|
|
32
|
+
// Each signature is written as a hex literal whose leading `1` nibble is a
|
|
33
|
+
// sentinel marking the start of the byte run (so leading zero bytes survive)
|
|
34
|
+
// and fixing the length — see `bit_vec` `fromSentinel`.
|
|
35
|
+
const sig = fromSentinel;
|
|
36
|
+
/**
|
|
37
|
+
* Contiguous magic-byte signatures, checked in order; the first prefix match
|
|
38
|
+
* wins. Ordering is irrelevant here — no signature is a prefix of another.
|
|
39
|
+
*/
|
|
40
|
+
const table = [
|
|
41
|
+
[sig(0x189504e470d0a1a0an), 'image/png'],
|
|
42
|
+
[sig(0x1ffd8ffn), 'image/jpeg'],
|
|
43
|
+
// Match the full GIF version headers ("GIF87a" / "GIF89a"), not just "GIF8",
|
|
44
|
+
// so opaque bytes that merely start with "GIF8" are not mistyped.
|
|
45
|
+
[sig(0x1474946383761n), 'image/gif'],
|
|
46
|
+
[sig(0x1474946383961n), 'image/gif'],
|
|
47
|
+
[sig(0x1255044462dn), 'application/pdf'],
|
|
48
|
+
// ZIP has three "PK" local-header variants: a normal entry, an empty
|
|
49
|
+
// archive (end-of-central-directory only), and a spanned archive.
|
|
50
|
+
[sig(0x1504b0304n), 'application/zip'],
|
|
51
|
+
[sig(0x1504b0506n), 'application/zip'],
|
|
52
|
+
[sig(0x1504b0708n), 'application/zip'],
|
|
53
|
+
];
|
|
54
|
+
// WebP: "RIFF" at offset 0, "WEBP" at offset 8 (the 4 bytes between are the
|
|
55
|
+
// file size). 12 bytes = 96 bits is the minimum to carry both markers.
|
|
56
|
+
const riff = sig(0x152494646n);
|
|
57
|
+
const webp = sig(0x157454250n);
|
|
58
|
+
const isWebp = (bytes) => length(bytes) >= 96n
|
|
59
|
+
&& startsWith(riff)(bytes)
|
|
60
|
+
&& startsWith(webp)(removeFront(64n)(bytes));
|
|
61
|
+
/**
|
|
62
|
+
* Detects the MIME type of `bytes` from its leading magic-byte signature.
|
|
63
|
+
*
|
|
64
|
+
* @returns the MIME type string for a recognised format, or `null` when the
|
|
65
|
+
* leading bytes match no known signature (including any `Vec` shorter than
|
|
66
|
+
* the signature it might otherwise match).
|
|
67
|
+
*/
|
|
68
|
+
export const detect = (bytes) => {
|
|
69
|
+
if (isWebp(bytes)) {
|
|
70
|
+
return 'image/webp';
|
|
71
|
+
}
|
|
72
|
+
for (const [s, m] of table) {
|
|
73
|
+
if (startsWith(s)(bytes)) {
|
|
74
|
+
return m;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
png: () => void;
|
|
3
|
+
jpeg: () => void;
|
|
4
|
+
gif89a: () => void;
|
|
5
|
+
gif87a: () => void;
|
|
6
|
+
gif8NotGif: () => void;
|
|
7
|
+
pdf: () => void;
|
|
8
|
+
zip: () => void;
|
|
9
|
+
emptyZip: () => void;
|
|
10
|
+
webp: () => void;
|
|
11
|
+
riffNotWebp: () => void;
|
|
12
|
+
textIsNull: () => void;
|
|
13
|
+
shortIsNull: () => void;
|
|
14
|
+
emptyIsNull: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { assertEq } from "../asserts/module.f.js";
|
|
2
|
+
import { msb, u8ListToVec, empty } from "../types/bit_vec/module.f.js";
|
|
3
|
+
import { detect } from "./module.f.js";
|
|
4
|
+
// Builds a big-endian `Vec` from a list of byte values — mirrors how the CAS
|
|
5
|
+
// store would hold the leading bytes of a stored blob.
|
|
6
|
+
const bytes = (...b) => u8ListToVec(msb)(b);
|
|
7
|
+
export const proof = {
|
|
8
|
+
png: () => assertEq(detect(bytes(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a)), 'image/png'),
|
|
9
|
+
jpeg: () => assertEq(detect(bytes(0xff, 0xd8, 0xff, 0xe0)), 'image/jpeg'),
|
|
10
|
+
gif89a: () => assertEq(detect(bytes(0x47, 0x49, 0x46, 0x38, 0x39, 0x61)), 'image/gif'),
|
|
11
|
+
gif87a: () => assertEq(detect(bytes(0x47, 0x49, 0x46, 0x38, 0x37, 0x61)), 'image/gif'),
|
|
12
|
+
// "GIF8" alone, without a valid version suffix, is not a GIF.
|
|
13
|
+
gif8NotGif: () => assertEq(detect(bytes(0x47, 0x49, 0x46, 0x38, 0x30, 0x30)), null),
|
|
14
|
+
pdf: () =>
|
|
15
|
+
// "%PDF-1.4"
|
|
16
|
+
assertEq(detect(bytes(0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x34)), 'application/pdf'),
|
|
17
|
+
zip: () => assertEq(detect(bytes(0x50, 0x4b, 0x03, 0x04, 0x14, 0x00)), 'application/zip'),
|
|
18
|
+
// An empty ZIP archive starts with the end-of-central-directory record.
|
|
19
|
+
emptyZip: () => assertEq(detect(bytes(0x50, 0x4b, 0x05, 0x06, 0x00, 0x00)), 'application/zip'),
|
|
20
|
+
webp: () =>
|
|
21
|
+
// "RIFF" + 4-byte size + "WEBP"
|
|
22
|
+
assertEq(detect(bytes(0x52, 0x49, 0x46, 0x46, 0x1a, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50)), 'image/webp'),
|
|
23
|
+
// "RIFF…" without the "WEBP" marker (e.g. a WAV) is not WebP.
|
|
24
|
+
riffNotWebp: () => assertEq(detect(bytes(0x52, 0x49, 0x46, 0x46, 0x1a, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45)), null),
|
|
25
|
+
// Plain ASCII text matches no signature.
|
|
26
|
+
textIsNull: () => assertEq(detect(bytes(0x68, 0x65, 0x6c, 0x6c, 0x6f)), null),
|
|
27
|
+
// A prefix shorter than any signature falls through to null, not a partial match.
|
|
28
|
+
shortIsNull: () => assertEq(detect(bytes(0x89, 0x50)), null),
|
|
29
|
+
emptyIsNull: () => assertEq(detect(empty), null),
|
|
30
|
+
};
|
package/fs/path/module.f.d.ts
CHANGED
|
@@ -24,6 +24,14 @@ export declare const normalize: Unary<string, string>;
|
|
|
24
24
|
* Concatenates two path fragments and returns a normalized path.
|
|
25
25
|
*/
|
|
26
26
|
export declare const concat: Reduce<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Joins path segments with single POSIX `/` separators, without
|
|
29
|
+
* normalization. Unlike {@link concat}, the result is not parsed/collapsed,
|
|
30
|
+
* so absolute roots and `.`/`..` segments are preserved verbatim. Use this
|
|
31
|
+
* for building paths from already-clean segments (directory walks, store
|
|
32
|
+
* layouts); use {@link concat} when normalization is desired.
|
|
33
|
+
*/
|
|
34
|
+
export declare const join: (...list: readonly string[]) => string;
|
|
27
35
|
/**
|
|
28
36
|
* Returns `path` relative to `base` with a `./` prefix, or `path` unchanged
|
|
29
37
|
* if it does not start with `base` or `base` is empty.
|
package/fs/path/module.f.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { fold, last, take, length, concat as listConcat, toArray } from "../types/list/module.f.js";
|
|
2
|
-
import { join } from "../types/string/module.f.js";
|
|
3
|
-
import { concat as stringConcat } from "../types/string/module.f.js";
|
|
2
|
+
import { join as listJoin, concat as stringConcat } from "../types/string/module.f.js";
|
|
4
3
|
const foldNormalizeOp = input => state => {
|
|
5
4
|
switch (input) {
|
|
6
5
|
case '':
|
|
@@ -41,7 +40,7 @@ export const parse = (path) => {
|
|
|
41
40
|
*/
|
|
42
41
|
export const normalize = path => {
|
|
43
42
|
const foldResult = parse(path);
|
|
44
|
-
return
|
|
43
|
+
return listJoin('/')(foldResult);
|
|
45
44
|
};
|
|
46
45
|
/**
|
|
47
46
|
* Concatenates two path fragments and returns a normalized path.
|
|
@@ -50,6 +49,14 @@ export const concat = a => b => {
|
|
|
50
49
|
const s = stringConcat([a, '/', b]);
|
|
51
50
|
return normalize(s);
|
|
52
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Joins path segments with single POSIX `/` separators, without
|
|
54
|
+
* normalization. Unlike {@link concat}, the result is not parsed/collapsed,
|
|
55
|
+
* so absolute roots and `.`/`..` segments are preserved verbatim. Use this
|
|
56
|
+
* for building paths from already-clean segments (directory walks, store
|
|
57
|
+
* layouts); use {@link concat} when normalization is desired.
|
|
58
|
+
*/
|
|
59
|
+
export const join = (...list) => list.join('/');
|
|
53
60
|
/**
|
|
54
61
|
* Returns `path` relative to `base` with a `./` prefix, or `path` unchanged
|
|
55
62
|
* if it does not start with `base` or `base` is empty.
|
package/fs/path/proof.f.d.ts
CHANGED
package/fs/path/proof.f.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { concat, normalize, relativize, toPosix } from "./module.f.js";
|
|
1
|
+
import { concat, join, normalize, relativize, toPosix } from "./module.f.js";
|
|
2
2
|
const normalizeTest = [
|
|
3
3
|
() => {
|
|
4
4
|
const norm = normalize("dir/file.json");
|
|
@@ -45,6 +45,44 @@ const concatTest = [
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
];
|
|
48
|
+
const joinTest = [
|
|
49
|
+
() => {
|
|
50
|
+
const r = join('a', 'b');
|
|
51
|
+
if (r !== 'a/b') {
|
|
52
|
+
throw r;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
() => {
|
|
56
|
+
const r = join('/abs/root', 'x');
|
|
57
|
+
if (r !== '/abs/root/x') {
|
|
58
|
+
throw r;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
() => {
|
|
62
|
+
const r = join('a', 'b', 'c', 'd');
|
|
63
|
+
if (r !== 'a/b/c/d') {
|
|
64
|
+
throw r;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
() => {
|
|
68
|
+
const r = join('', 'x');
|
|
69
|
+
if (r !== '/x') {
|
|
70
|
+
throw r;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
() => {
|
|
74
|
+
const r = join();
|
|
75
|
+
if (r !== '') {
|
|
76
|
+
throw r;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
() => {
|
|
80
|
+
const r = join('only');
|
|
81
|
+
if (r !== 'only') {
|
|
82
|
+
throw r;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
];
|
|
48
86
|
const relativizeTest = [
|
|
49
87
|
() => {
|
|
50
88
|
const r = relativize('/repo', '/repo/fs/a.ts');
|
|
@@ -91,4 +129,4 @@ const toPosixTest = [
|
|
|
91
129
|
}
|
|
92
130
|
},
|
|
93
131
|
];
|
|
94
|
-
export const proof = { normalizeTest, concatTest, relativizeTest, toPosixTest };
|
|
132
|
+
export const proof = { normalizeTest, concatTest, joinTest, relativizeTest, toPosixTest };
|
package/fs/text/sgr/proof.f.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { fgRed, createConsoleText, backspace } from "./module.f.js";
|
|
1
|
+
import { fgRed, reset, createConsoleText, backspace, csiWrite } from "./module.f.js";
|
|
2
|
+
import { virtual, emptyState } from "../../effects/node/virtual/module.f.js";
|
|
3
|
+
const makeOptions = (isTTY) => ({
|
|
4
|
+
args: [],
|
|
5
|
+
env: {},
|
|
6
|
+
home: '.',
|
|
7
|
+
std: { stdout: { isTTY }, stderr: { isTTY } },
|
|
8
|
+
testContext: { test: async () => { } },
|
|
9
|
+
bunTestContext: { test: async () => { } },
|
|
10
|
+
playwrightTestContext: { test: async () => { } },
|
|
11
|
+
engine: 'node',
|
|
12
|
+
});
|
|
2
13
|
export const proof = [
|
|
3
14
|
() => {
|
|
4
15
|
if (fgRed !== '\x1b[31m') {
|
|
@@ -19,5 +30,22 @@ export const proof = [
|
|
|
19
30
|
if (output[1] !== expected) {
|
|
20
31
|
throw output[1];
|
|
21
32
|
}
|
|
22
|
-
}
|
|
33
|
+
},
|
|
34
|
+
() => {
|
|
35
|
+
// csiWrite with isTTY=false strips ANSI SGR sequences
|
|
36
|
+
const writeFn = csiWrite(makeOptions(false))('stdout');
|
|
37
|
+
const [state] = virtual(emptyState)(writeFn(fgRed + 'hello' + reset));
|
|
38
|
+
if (state.stdout !== 'hello') {
|
|
39
|
+
throw ['expected ANSI stripped', state.stdout];
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
() => {
|
|
43
|
+
// csiWrite with isTTY=true preserves ANSI SGR sequences
|
|
44
|
+
const writeFn = csiWrite(makeOptions(true))('stdout');
|
|
45
|
+
const [state] = virtual(emptyState)(writeFn(fgRed + 'hello' + reset));
|
|
46
|
+
const expected = fgRed + 'hello' + reset;
|
|
47
|
+
if (state.stdout !== expected) {
|
|
48
|
+
throw ['expected ANSI preserved', state.stdout];
|
|
49
|
+
}
|
|
50
|
+
},
|
|
23
51
|
];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type List, type Thunk } from '../../types/list/module.f.ts';
|
|
7
7
|
import type { Array1, Array2, Array3 } from '../../types/array/module.f.ts';
|
|
8
|
+
import { type Vec } from '../../types/bit_vec/module.f.ts';
|
|
8
9
|
/**
|
|
9
10
|
* An unsigned 8-bit integer, represents a single byte.
|
|
10
11
|
*/
|
|
@@ -41,3 +42,9 @@ export declare const fromCodePointList: (input: List<number>) => Thunk<U8>;
|
|
|
41
42
|
* @returns A list of Unicode code points or error codes.
|
|
42
43
|
*/
|
|
43
44
|
export declare const toCodePointList: (input: List<U8>) => List<I32>;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the decoded string if `v` is valid UTF-8, or `null` otherwise.
|
|
47
|
+
* Rejects non-octet Vecs, invalid byte sequences, surrogates, and out-of-range
|
|
48
|
+
* code points.
|
|
49
|
+
*/
|
|
50
|
+
export declare const fromVec: (v: Vec) => string | null;
|