functionalscript 0.35.2 → 0.37.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 +1 -1
- package/fs/asserts/proof.f.js +1 -1
- package/fs/base_n/module.f.js +3 -3
- package/fs/base_n/proof.f.js +3 -3
- package/fs/{base128 → basen/base128}/module.f.d.ts +1 -1
- package/fs/{base128 → basen/base128}/module.f.js +1 -1
- package/fs/{base128 → basen/base128}/proof.f.js +3 -3
- package/fs/{base64 → basen/base64}/module.f.d.ts +2 -2
- package/fs/basen/base64/module.f.js +71 -0
- package/fs/{base64 → basen/base64}/proof.f.d.ts +2 -0
- package/fs/{base64 → basen/base64}/proof.f.js +17 -2
- package/fs/{cbase32 → basen/cbase32}/module.f.d.ts +2 -2
- package/fs/{cbase32 → basen/cbase32}/module.f.js +2 -2
- package/fs/{cbase32 → basen/cbase32}/proof.f.js +1 -1
- package/fs/bnf/data/module.f.js +4 -4
- package/fs/bnf/data/proof.f.js +6 -6
- package/fs/bnf/descent/module.f.js +6 -6
- package/fs/bnf/descent/proof.f.js +6 -6
- package/fs/bnf/ll1/module.f.js +9 -9
- package/fs/bnf/ll1/proof.f.js +6 -6
- package/fs/bnf/module.f.js +4 -4
- package/fs/bnf/proof.f.js +2 -2
- package/fs/bnf/testlib.f.js +1 -1
- package/fs/cas/cli/module.f.js +7 -7
- package/fs/cas/cli/proof.f.js +8 -8
- package/fs/cas/mcp/module.f.d.ts +8 -4
- package/fs/cas/mcp/module.f.js +124 -72
- package/fs/cas/mcp/proof.f.d.ts +3 -8
- package/fs/cas/mcp/proof.f.js +122 -186
- package/fs/cas/module.f.js +12 -19
- package/fs/cas/proof.f.d.ts +4 -2
- package/fs/cas/proof.f.js +10 -10
- package/fs/ci/bun/module.f.js +2 -2
- package/fs/ci/common/module.f.d.ts +2 -2
- package/fs/ci/common/module.f.js +4 -4
- package/fs/ci/config/module.f.d.ts +20 -21
- package/fs/ci/config/module.f.js +8 -10
- package/fs/ci/deno/module.f.js +8 -4
- package/fs/ci/module.f.js +9 -9
- package/fs/ci/node/module.f.d.ts +1 -1
- package/fs/ci/node/module.f.js +3 -5
- package/fs/ci/playwright/module.f.js +3 -3
- package/fs/ci/proof.f.js +14 -14
- package/fs/ci/rust/module.f.js +2 -2
- package/fs/cli/module.f.js +3 -3
- package/fs/cli/proof.f.js +3 -3
- package/fs/{types → common}/monoid/module.f.d.ts +1 -1
- package/fs/crypto/hmac/module.f.js +2 -2
- package/fs/crypto/hmac/proof.f.js +5 -5
- package/fs/crypto/pow/module.f.js +3 -3
- package/fs/crypto/pow/proof.f.js +4 -4
- package/fs/crypto/secp/module.f.js +2 -2
- package/fs/crypto/secp/proof.f.js +3 -3
- package/fs/crypto/sha2/module.f.js +2 -2
- package/fs/crypto/sha2/proof.f.js +6 -6
- package/fs/crypto/sign/module.f.js +4 -4
- package/fs/crypto/vdf/module.f.js +1 -1
- package/fs/crypto/vdf/proof.f.js +1 -1
- package/fs/dev/module.f.js +6 -6
- package/fs/dev/package_json/module.f.js +6 -6
- package/fs/dev/package_json/proof.f.js +3 -3
- package/fs/dev/proof.f.js +2 -2
- package/fs/djs/ast/module.f.js +2 -2
- package/fs/djs/ast/proof.f.js +4 -4
- package/fs/djs/examples/m.f.d.ts +1 -1
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +5 -5
- package/fs/djs/parser/module.f.js +4 -4
- package/fs/djs/parser/proof.f.js +19 -7
- package/fs/djs/proof.f.js +3 -3
- package/fs/djs/serializer/module.f.js +6 -6
- package/fs/djs/serializer/proof.f.js +4 -4
- package/fs/djs/tokenizer/module.f.js +3 -3
- package/fs/djs/tokenizer/proof.f.js +5 -5
- package/fs/djs/tokenizer-new/proof.f.js +6 -6
- package/fs/djs/transpiler/module.f.d.ts +5 -0
- package/fs/djs/transpiler/module.f.js +21 -11
- package/fs/djs/transpiler/proof.f.js +5 -5
- package/fs/effects/memory/module.f.js +4 -4
- package/fs/effects/memory/proof.f.js +3 -3
- package/fs/effects/module.f.d.ts +15 -0
- package/fs/effects/module.f.js +15 -1
- package/fs/effects/node/memory/module.js +2 -2
- package/fs/effects/node/memory/proof.js +3 -3
- package/fs/effects/node/module.f.js +10 -20
- package/fs/effects/node/module.js +13 -11
- package/fs/effects/node/proof.f.d.ts +3 -0
- package/fs/effects/node/proof.f.js +19 -1
- package/fs/effects/node/virtual/module.f.js +7 -8
- package/fs/effects/node/virtual/proof.f.d.ts +1 -0
- package/fs/effects/node/virtual/proof.f.js +11 -4
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +22 -1
- package/fs/emergent_testing/all.test.js +2 -2
- package/fs/emergent_testing/example.f.d.ts +1 -1
- package/fs/emergent_testing/module.f.js +6 -6
- package/fs/emergent_testing/proof.f.d.ts +19 -17
- package/fs/emergent_testing/proof.f.js +7 -7
- package/fs/emergent_testing/scenarios/all.js +1 -1
- package/fs/fsc/bnf.f.js +2 -2
- package/fs/fsc/json.f.js +1 -1
- package/fs/fsc/module.f.d.ts +1 -0
- package/fs/fsc/module.f.js +11 -7
- package/fs/fsc/proof.f.js +4 -4
- package/fs/fsm/module.f.js +9 -9
- package/fs/fsm/proof.f.js +7 -7
- package/fs/js/tokenizer/module.f.d.ts +1 -1
- package/fs/js/tokenizer/module.f.js +14 -8
- package/fs/js/tokenizer/proof.f.js +64 -5
- package/fs/mcp/module.f.d.ts +13 -13
- package/fs/mcp/module.f.js +8 -8
- package/fs/mcp/proof.f.js +5 -5
- package/fs/mcp/stdio/module.f.d.ts +2 -2
- package/fs/mcp/stdio/module.f.js +10 -10
- package/fs/mcp/stdio/proof.f.js +10 -10
- package/fs/{html → media/html}/module.f.d.ts +3 -3
- package/fs/{html → media/html}/module.f.js +9 -9
- package/fs/media/json/module.f.d.ts +50 -0
- package/fs/{json → media/json}/module.f.js +7 -7
- package/fs/{json → media/json}/parser/module.f.d.ts +2 -2
- package/fs/{json → media/json}/parser/module.f.js +7 -7
- package/fs/{json → media/json}/parser/proof.f.js +24 -6
- package/fs/{json → media/json}/proof.f.js +4 -4
- package/fs/media/json/rpc/module.f.d.ts +114 -0
- package/fs/{json → media/json}/rpc/module.f.js +3 -3
- package/fs/{json → media/json}/rpc/proof.f.js +4 -4
- package/fs/media/json/schema/module.f.d.ts +78 -0
- package/fs/{json → media/json}/schema/module.f.js +2 -2
- package/fs/{json → media/json}/schema/proof.f.js +3 -3
- package/fs/{json → media/json}/serializer/module.f.d.ts +1 -1
- package/fs/{json → media/json}/serializer/module.f.js +2 -2
- package/fs/{json → media/json}/serializer/proof.f.js +2 -2
- package/fs/{json → media/json}/tokenizer/module.f.d.ts +2 -2
- package/fs/{json → media/json}/tokenizer/module.f.js +4 -4
- package/fs/{json → media/json}/tokenizer/proof.f.js +5 -5
- package/fs/media/module.f.d.ts +37 -0
- package/fs/media/module.f.js +24 -0
- package/fs/media/proof.f.d.ts +9 -0
- package/fs/media/proof.f.js +57 -0
- package/fs/media/revision/module.f.d.ts +51 -0
- package/fs/media/revision/module.f.js +110 -0
- package/fs/media/revision/proof.f.d.ts +29 -0
- package/fs/media/revision/proof.f.js +119 -0
- package/fs/{mime → media/type}/module.f.d.ts +6 -6
- package/fs/{mime → media/type}/module.f.js +6 -6
- package/fs/{mime → media/type}/proof.f.js +6 -6
- package/fs/module.f.d.ts +2 -0
- package/fs/{fjs/module.f.js → module.f.js} +8 -8
- package/fs/module.js +4 -0
- package/fs/path/module.f.d.ts +6 -0
- package/fs/path/module.f.js +8 -2
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/{fjs/proof.f.d.ts → proof.f.d.ts} +1 -0
- package/fs/{fjs/proof.f.js → proof.f.js} +10 -4
- package/fs/sul/id/module.f.js +9 -9
- package/fs/sul/id/proof.f.js +5 -5
- package/fs/sul/level/hash/module.f.js +3 -3
- package/fs/sul/level/hash/proof.f.js +3 -3
- package/fs/sul/level/literal/module.f.js +5 -5
- package/fs/sul/level/literal/proof.f.js +3 -3
- package/fs/sul/module.f.js +3 -3
- package/fs/sul/proof.f.js +3 -3
- package/fs/text/ascii/proof.f.js +3 -3
- package/fs/text/code_point/module.f.js +2 -2
- package/fs/text/code_point/proof.f.js +1 -1
- package/fs/text/module.f.js +5 -5
- package/fs/text/proof.f.js +4 -4
- package/fs/text/sgr/module.f.js +2 -2
- package/fs/text/sgr/proof.f.js +2 -2
- package/fs/text/utf16/module.f.js +5 -5
- package/fs/text/utf16/proof.f.js +4 -4
- package/fs/text/utf8/module.f.d.ts +6 -0
- package/fs/text/utf8/module.f.js +37 -19
- package/fs/text/utf8/proof.f.js +58 -5
- package/fs/types/array/module.f.js +1 -1
- package/fs/types/array/proof.f.js +3 -3
- package/fs/types/bigfloat/module.f.js +1 -1
- package/fs/types/bigfloat/proof.f.js +1 -1
- package/fs/types/bigint/module.f.js +2 -2
- package/fs/types/bigint/proof.f.js +3 -3
- package/fs/types/bit_vec/module.f.js +8 -8
- package/fs/types/bit_vec/proof.f.js +5 -5
- package/fs/types/btree/find/module.f.js +1 -1
- package/fs/types/btree/find/proof.f.d.ts +3 -1
- package/fs/types/btree/find/proof.f.js +7 -7
- package/fs/types/btree/module.f.js +2 -2
- package/fs/types/btree/proof.f.d.ts +11 -5
- package/fs/types/btree/proof.f.js +7 -7
- package/fs/types/btree/remove/module.f.js +4 -4
- package/fs/types/btree/remove/proof.f.d.ts +5 -2
- package/fs/types/btree/remove/proof.f.js +5 -5
- package/fs/types/btree/set/module.f.js +3 -3
- package/fs/types/btree/set/proof.f.js +4 -4
- package/fs/types/byte_set/module.f.js +2 -2
- package/fs/types/byte_set/proof.f.js +4 -4
- package/fs/types/function/compare/proof.f.js +1 -1
- package/fs/types/function/operator/proof.f.d.ts +21 -10
- package/fs/types/function/operator/proof.f.js +1 -1
- package/fs/types/function/proof.f.js +1 -1
- package/fs/types/list/module.f.js +2 -2
- package/fs/types/list/proof.f.d.ts +15 -7
- package/fs/types/list/proof.f.js +4 -4
- package/fs/types/map/proof.f.js +2 -2
- package/fs/types/nibble_set/proof.f.js +2 -2
- package/fs/types/nullable/module.f.js +2 -2
- package/fs/types/nullable/proof.f.js +1 -1
- package/fs/types/number/module.f.js +5 -5
- package/fs/types/number/proof.f.js +1 -1
- package/fs/types/object/module.f.js +4 -4
- package/fs/types/object/proof.f.js +1 -1
- package/fs/types/ordered_map/module.f.js +6 -6
- package/fs/types/ordered_map/proof.f.js +3 -3
- package/fs/types/patricia_trie/proof.f.d.ts +11 -5
- package/fs/types/patricia_trie/proof.f.js +2 -2
- package/fs/types/prime_field/module.f.js +2 -2
- package/fs/types/prime_field/proof.f.js +2 -2
- package/fs/types/range/proof.f.js +1 -1
- package/fs/types/range_map/module.f.js +4 -4
- package/fs/types/range_map/proof.f.js +7 -7
- package/fs/types/result/module.f.d.ts +4 -0
- package/fs/types/result/module.f.js +4 -0
- package/fs/types/result/module.js +1 -1
- package/fs/types/result/proof.f.d.ts +9 -3
- package/fs/types/result/proof.f.js +12 -2
- package/fs/types/rtti/common/module.f.js +4 -4
- package/fs/types/rtti/module.f.d.ts +2 -2
- package/fs/types/rtti/module.f.js +1 -1
- package/fs/types/rtti/parse/module.f.js +5 -5
- package/fs/types/rtti/parse/proof.f.js +2 -2
- package/fs/types/rtti/ts/module.f.js +1 -1
- package/fs/types/rtti/ts/proof.f.js +2 -2
- package/fs/types/rtti/validate/module.f.js +4 -4
- package/fs/types/rtti/validate/proof.f.js +2 -2
- package/fs/types/sorted_list/module.f.js +3 -3
- package/fs/types/sorted_list/proof.f.js +6 -6
- package/fs/types/sorted_set/module.f.js +1 -1
- package/fs/types/sorted_set/proof.f.js +6 -6
- package/fs/types/string/module.f.js +4 -4
- package/fs/types/string/proof.f.js +2 -2
- package/fs/types/string_set/module.f.js +6 -6
- package/fs/types/string_set/proof.f.js +1 -1
- package/fs/types/ts/proof.f.d.ts +18 -18
- package/fs/types/ts/proof.f.js +1 -1
- package/fs/types/uint8array/module.f.js +4 -4
- package/fs/types/uint8array/proof.f.js +4 -4
- package/fs/website/module.f.js +3 -3
- package/fs/website/proof.f.js +3 -3
- package/package.json +8 -8
- package/fs/base64/module.f.js +0 -60
- package/fs/fjs/module.f.d.ts +0 -2
- package/fs/fjs/module.js +0 -4
- package/fs/json/module.f.d.ts +0 -50
- package/fs/json/rpc/module.f.d.ts +0 -114
- package/fs/json/schema/module.f.d.ts +0 -77
- /package/fs/{base128 → basen/base128}/proof.f.d.ts +0 -0
- /package/fs/{cbase32 → basen/cbase32}/proof.f.d.ts +0 -0
- /package/fs/{types → common}/monoid/module.f.js +0 -0
- /package/fs/{types → common}/monoid/proof.f.d.ts +0 -0
- /package/fs/{types → common}/monoid/proof.f.js +0 -0
- /package/fs/{html → media/html}/proof.f.d.ts +0 -0
- /package/fs/{html → media/html}/proof.f.js +0 -0
- /package/fs/{json → media/json}/parser/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/rpc/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/schema/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/serializer/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/tokenizer/proof.f.d.ts +0 -0
- /package/fs/{mime → media/type}/proof.f.d.ts +0 -0
- /package/fs/{fjs/module.d.ts → module.d.ts} +0 -0
package/fs/cas/mcp/proof.f.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { assert, assertEq } from
|
|
2
|
-
import { pure } from
|
|
3
|
-
import { run } from
|
|
4
|
-
import { asBase, asNominal, create } from
|
|
5
|
-
import { msb, u8ListToVec, vec8, repeat, length, maxLengthBytes } from
|
|
6
|
-
import { vecToCBase32 } from
|
|
7
|
-
import { encode as base64Encode } from
|
|
8
|
-
import { utf8 } from
|
|
9
|
-
import {} from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
1
|
+
import { assert, assertEq } from '../../asserts/module.f.js';
|
|
2
|
+
import { pure } from '../../effects/module.f.js';
|
|
3
|
+
import { run } from '../../effects/mock/module.f.js';
|
|
4
|
+
import { asBase, asNominal, create } from '../../effects/memory/module.f.js';
|
|
5
|
+
import { msb, u8ListToVec, vec8, repeat, length, maxLengthBytes } from '../../types/bit_vec/module.f.js';
|
|
6
|
+
import { vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
7
|
+
import { encode as base64Encode } from '../../basen/base64/module.f.js';
|
|
8
|
+
import { utf8 } from '../../text/module.f.js';
|
|
9
|
+
import { fileCas } from '../module.f.js';
|
|
10
|
+
import { dialect as revisionDialect, mediaType as revisionMediaType } from '../../media/revision/module.f.js';
|
|
11
|
+
import { sha256 } from '../../crypto/sha2/module.f.js';
|
|
12
|
+
import { nonEmpty, empty as elEmpty } from '../../effects/list/module.f.js';
|
|
13
|
+
import { mcpStep, uninitializedState, } from '../../mcp/module.f.js';
|
|
14
|
+
import { emptyState, virtual } from '../../effects/node/virtual/module.f.js';
|
|
15
|
+
import { casConfig, casMcpHandlers } from './module.f.js';
|
|
16
|
+
import { ok as resultOk } from '../../types/result/module.f.js';
|
|
17
|
+
import { stdioTransport } from '../../mcp/stdio/module.f.js';
|
|
18
|
+
import { fromVec } from '../../types/uint8array/module.f.js';
|
|
16
19
|
const initialTestState = { memory: { next: 0, values: {} } };
|
|
17
20
|
const mock = {
|
|
18
21
|
memCreate: value => state => {
|
|
@@ -59,6 +62,19 @@ const runSessionVirtual = (root, home = '/home/user') => (msgs) => {
|
|
|
59
62
|
});
|
|
60
63
|
return virtual({ ...emptyState, root })(effect)[1];
|
|
61
64
|
};
|
|
65
|
+
// Seeds a blob directly into the virtual store via `c.write`, bypassing the
|
|
66
|
+
// MCP `cas_add` tool entirely — used by tests that need a stored blob larger
|
|
67
|
+
// than `cas_add`'s inline limit, now that the removed `type:'url'` path is no
|
|
68
|
+
// longer available to stage one through MCP. Returns the resulting root
|
|
69
|
+
// `Dir` (with the new shard written) and the blob's cBase32 hash so a
|
|
70
|
+
// subsequent `runSessionVirtual` call can read it back via `cas_get`.
|
|
71
|
+
const seedBlob = (root, home = '/home/user') => (chunks) => {
|
|
72
|
+
const c = fileCas(sha256)(home);
|
|
73
|
+
const stream = chunks.reduceRight((tail, chunk) => nonEmpty(resultOk(chunk), tail), elEmpty());
|
|
74
|
+
const [state, result] = virtual({ ...emptyState, root })(c.write(stream));
|
|
75
|
+
assert(result[0] === 'ok', result);
|
|
76
|
+
return [state.root, vecToCBase32(result[1])];
|
|
77
|
+
};
|
|
62
78
|
// ── Messages ────────────────────────────────────────────────────────────────────
|
|
63
79
|
const init = { jsonrpc: '2.0', method: 'initialize', id: 1,
|
|
64
80
|
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'c', version: '0' } } };
|
|
@@ -89,14 +105,18 @@ const item0 = (resp) => resultOf(resp).content[0];
|
|
|
89
105
|
const textOf = (resp) => item0(resp).text;
|
|
90
106
|
// A plain text sample for text add→get round-trips.
|
|
91
107
|
const textSample = 'hello, world!';
|
|
108
|
+
// A valid `vnd.fjs.revision` blob: zero parents, so `subject` must be (and is,
|
|
109
|
+
// per `fs/media/revision/proof.f.ts`) a valid cbase32 hash on its own.
|
|
110
|
+
const revisionSample = `{"dialect":"${revisionDialect}","subject":"8","parents":[]}`;
|
|
92
111
|
// A base64-encoded binary payload for binary add→get round-trips.
|
|
93
112
|
const binarySample = base64Encode(vec8(0x2an));
|
|
94
113
|
// A base64 blob whose leading bytes are the PNG magic-byte signature, so
|
|
95
|
-
// `cas_get` detects its type and returns base64 with
|
|
114
|
+
// `cas_get` detects its type and returns base64 with mimeType image/png.
|
|
96
115
|
const pngSample = base64Encode(u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x01]));
|
|
97
116
|
// Returns the RFC 4648 base64 encoding of `n` zero bytes, computed directly
|
|
98
|
-
// without bigint arithmetic —
|
|
99
|
-
//
|
|
117
|
+
// without bigint arithmetic — independent of `base64.encode` so these tests
|
|
118
|
+
// don't rely on the correctness of the function they're indirectly exercising
|
|
119
|
+
// (`cas_add` decodes the string via `base64.decode`). Handles every n%3
|
|
100
120
|
// residue: rem=0 → no padding, rem=1 → 'AA==', rem=2 → 'AAA='.
|
|
101
121
|
const base64OfA = (n) => {
|
|
102
122
|
const groups = Number(n / 3n);
|
|
@@ -117,24 +137,18 @@ const base64OfA = (n) => {
|
|
|
117
137
|
// exactly `maxLengthBytes` (the largest single `Vec` the runtime allows), so the
|
|
118
138
|
// two-chunk blob spans two read chunks without any one `Vec` exceeding the cap.
|
|
119
139
|
const largeMultiChunkBlobMeta = (chunk0, chunk1, expectedType, expectedMime) => () => {
|
|
120
|
-
const root = {
|
|
121
|
-
const [
|
|
122
|
-
init, initialized,
|
|
123
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/big', type: 'url' }),
|
|
124
|
-
]).slice(2);
|
|
125
|
-
assert(!resultOf(addResp).isError);
|
|
126
|
-
const hash = textOf(addResp);
|
|
127
|
-
const [, metaResp] = runSessionVirtual(root)([
|
|
140
|
+
const [root, hash] = seedBlob({})([chunk0, chunk1]);
|
|
141
|
+
const [metaResp] = runSessionVirtual(root)([
|
|
128
142
|
init, initialized,
|
|
129
|
-
call(2, '
|
|
130
|
-
call(3, 'cas_get', { hash }),
|
|
143
|
+
call(2, 'cas_get', { hash }),
|
|
131
144
|
]).slice(2);
|
|
132
145
|
assert(!resultOf(metaResp).isError);
|
|
133
146
|
const meta = JSON.parse(textOf(metaResp));
|
|
134
147
|
assertEq(meta.type, expectedType);
|
|
135
|
-
assertEq(meta.
|
|
148
|
+
assertEq(meta.mimeType, expectedMime);
|
|
136
149
|
assertEq(meta.length, Number((length(chunk0) + length(chunk1)) / 8n));
|
|
137
|
-
assertEq(meta.
|
|
150
|
+
assertEq(meta.text, undefined);
|
|
151
|
+
assertEq(meta.blob, undefined);
|
|
138
152
|
};
|
|
139
153
|
// A full `maxLengthBytes`-long chunk of repeated ASCII 'a' — valid UTF-8.
|
|
140
154
|
const asciiChunk = repeat(maxLengthBytes)(vec8(0x61n));
|
|
@@ -145,8 +159,7 @@ const binaryChunk = repeat(maxLengthBytes)(vec8(0xffn));
|
|
|
145
159
|
// 100,000 raw bytes of 0xFF: comfortably under `maxLengthBytes` (so `cas_get`'s
|
|
146
160
|
// own `length > maxLengthBytes` guard does not fire), but base64 inflates it to
|
|
147
161
|
// ceil(100_000/3)*4 = 133,336 chars — already past `maxLength` (131,072 bytes)
|
|
148
|
-
// before the JSON-RPC envelope adds anything.
|
|
149
|
-
// separate `base64Encode`-near-`maxLength` padding bug (see `base64OfA` above).
|
|
162
|
+
// before the JSON-RPC envelope adds anything.
|
|
150
163
|
const oversizedBase64Chunk = repeat(100000n)(vec8(0xffn));
|
|
151
164
|
// 90,000 raw bytes: base64 inflates to 120,000 chars, leaving ~11 KiB of margin
|
|
152
165
|
// under `maxLength` once the envelope is added — the paired "still succeeds" case.
|
|
@@ -170,16 +183,10 @@ export const proof = {
|
|
|
170
183
|
// "too large" error, not "no such hash" — the blob exists, it just can't be
|
|
171
184
|
// buffered inline. The metadata-only path (above) still returns its size/type.
|
|
172
185
|
getContentLargeBlobTooLargeError: () => {
|
|
173
|
-
const root = {
|
|
174
|
-
const [
|
|
186
|
+
const [root, hash] = seedBlob({})([asciiChunk, asciiChunk]);
|
|
187
|
+
const [getResp] = runSessionVirtual(root)([
|
|
175
188
|
init, initialized,
|
|
176
|
-
call(2, '
|
|
177
|
-
]).slice(2);
|
|
178
|
-
const hash = textOf(addResp);
|
|
179
|
-
const [, getResp] = runSessionVirtual(root)([
|
|
180
|
-
init, initialized,
|
|
181
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/big', type: 'url' }),
|
|
182
|
-
call(3, 'cas_get', { hash, content: true }),
|
|
189
|
+
call(2, 'cas_get', { hash, content: true }),
|
|
183
190
|
]).slice(2);
|
|
184
191
|
assertEq(resultOf(getResp).isError, true);
|
|
185
192
|
const text = textOf(getResp);
|
|
@@ -205,34 +212,24 @@ export const proof = {
|
|
|
205
212
|
// This test originally timed out under `bun test`'s native 5s per-test
|
|
206
213
|
// limit (12-14s observed in CI on PR #1201) — the cost was in
|
|
207
214
|
// `base64Encode`, quadratic before the `baseN.vecToString` fix (see
|
|
208
|
-
// `fs/base64/proof.f.ts` `encodeLargeVecIsSlow`). Now well under budget on
|
|
215
|
+
// `fs/basen/base64/proof.f.ts` `encodeLargeVecIsSlow`). Now well under budget on
|
|
209
216
|
// both engines.
|
|
210
217
|
getContentBase64InflationOverflowWritesInternalError: () => {
|
|
211
|
-
const root = {
|
|
212
|
-
const [
|
|
213
|
-
call(2, '
|
|
214
|
-
]);
|
|
215
|
-
const hash = textOf(addResp);
|
|
216
|
-
const [, getResp] = runStdio(root)([
|
|
217
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/big', type: 'url' }),
|
|
218
|
-
call(3, 'cas_get', { hash, content: true }),
|
|
218
|
+
const [root, hash] = seedBlob({})([oversizedBase64Chunk]);
|
|
219
|
+
const [getResp] = runStdio(root)([
|
|
220
|
+
call(2, 'cas_get', { hash, content: true }),
|
|
219
221
|
]);
|
|
220
222
|
const err = getResp;
|
|
221
223
|
assertEq(err.error?.code, -32603);
|
|
222
|
-
assertEq(err.id,
|
|
224
|
+
assertEq(err.id, 2);
|
|
223
225
|
},
|
|
224
226
|
// The paired boundary case: a blob whose base64 inflation leaves enough
|
|
225
227
|
// margin under `maxLength` for the envelope — confirms the fallback above
|
|
226
228
|
// only triggers on genuine overflow, not on every `content:true` binary read.
|
|
227
229
|
getContentBase64NearBoundarySucceeds: () => {
|
|
228
|
-
const root = {
|
|
229
|
-
const [
|
|
230
|
-
call(2, '
|
|
231
|
-
]);
|
|
232
|
-
const hash = textOf(addResp);
|
|
233
|
-
const [, getResp] = runStdio(root)([
|
|
234
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/big', type: 'url' }),
|
|
235
|
-
call(3, 'cas_get', { hash, content: true }),
|
|
230
|
+
const [root, hash] = seedBlob({})([boundaryBase64Chunk]);
|
|
231
|
+
const [getResp] = runStdio(root)([
|
|
232
|
+
call(2, 'cas_get', { hash, content: true }),
|
|
236
233
|
]);
|
|
237
234
|
assert(!resultOf(getResp).isError);
|
|
238
235
|
const result = JSON.parse(textOf(getResp));
|
|
@@ -243,25 +240,21 @@ export const proof = {
|
|
|
243
240
|
// (33,000 bytes) well under `maxLengthBytes`, but each quote costs 2 bytes on
|
|
244
241
|
// the inner CAS-JSON escape and 4 bytes once the outer JSON-RPC envelope
|
|
245
242
|
// re-escapes that backslash-quote pair — no `cas_get`-level size guard could
|
|
246
|
-
// see this coming, only the transport's real encode attempt can.
|
|
247
|
-
//
|
|
248
|
-
// `cas_get`'s response-side escaping without
|
|
249
|
-
// inline `content` argument on the request
|
|
243
|
+
// see this coming, only the transport's real encode attempt can. Seeded
|
|
244
|
+
// directly into the store via `seedBlob` (not an inlined `cas_add` JSON
|
|
245
|
+
// string) so the test exercises `cas_get`'s response-side escaping without
|
|
246
|
+
// also paying for parsing a huge inline `content` argument on the request
|
|
247
|
+
// side. Confirms `writeResponse`'s
|
|
250
248
|
// `tryUtf8` fallback (not a crash) handles double-escaping overflow, again
|
|
251
249
|
// preserving the request's `id`.
|
|
252
250
|
getContentDoubleEscapedOverflowWritesInternalError: () => {
|
|
253
|
-
const root = {
|
|
254
|
-
const [
|
|
255
|
-
call(2, '
|
|
256
|
-
]);
|
|
257
|
-
const hash = textOf(addResp);
|
|
258
|
-
const [, getResp] = runStdio(root)([
|
|
259
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/q', type: 'url' }),
|
|
260
|
-
call(3, 'cas_get', { hash, content: true }),
|
|
251
|
+
const [root, hash] = seedBlob({})([quotesChunk]);
|
|
252
|
+
const [getResp] = runStdio(root)([
|
|
253
|
+
call(2, 'cas_get', { hash, content: true }),
|
|
261
254
|
]);
|
|
262
255
|
const err = getResp;
|
|
263
256
|
assertEq(err.error?.code, -32603);
|
|
264
|
-
assertEq(err.id,
|
|
257
|
+
assertEq(err.id, 2);
|
|
265
258
|
},
|
|
266
259
|
toolsListAdvertisesThreeTools: () => {
|
|
267
260
|
const [resp] = runSessionVirtual({})([init, initialized, list(2)]).slice(2);
|
|
@@ -283,10 +276,10 @@ export const proof = {
|
|
|
283
276
|
const [, getResp] = session(call(2, 'cas_add', { content: textSample }), call(3, 'cas_get', { hash }));
|
|
284
277
|
assert(!resultOf(getResp).isError);
|
|
285
278
|
const result = JSON.parse(textOf(getResp));
|
|
286
|
-
assertEq(result.
|
|
279
|
+
assertEq(result.mimeType, 'text/plain');
|
|
287
280
|
assertEq(result.type, 'text');
|
|
288
281
|
assertEq(result.length, textSample.length);
|
|
289
|
-
assertEq(result.
|
|
282
|
+
assertEq(result.text, undefined);
|
|
290
283
|
},
|
|
291
284
|
// cas_get with content:true returns inline content.
|
|
292
285
|
textAddGetContentRoundTrips: () => {
|
|
@@ -296,8 +289,8 @@ export const proof = {
|
|
|
296
289
|
assert(!resultOf(getResp).isError);
|
|
297
290
|
const result = JSON.parse(textOf(getResp));
|
|
298
291
|
assertEq(result.type, 'text');
|
|
299
|
-
assertEq(result.
|
|
300
|
-
assertEq(result.
|
|
292
|
+
assertEq(result.mimeType, 'text/plain');
|
|
293
|
+
assertEq(result.text, textSample);
|
|
301
294
|
},
|
|
302
295
|
// Binary add→get round-trip: store as base64, retrieve metadata without content.
|
|
303
296
|
binaryAddGetMetaRoundTrips: () => {
|
|
@@ -306,9 +299,9 @@ export const proof = {
|
|
|
306
299
|
const [, getResp] = session(call(2, 'cas_add', { content: binarySample, type: 'base64' }), call(3, 'cas_get', { hash }));
|
|
307
300
|
assert(!resultOf(getResp).isError);
|
|
308
301
|
const result = JSON.parse(textOf(getResp));
|
|
309
|
-
assertEq(result.
|
|
302
|
+
assertEq(result.text, undefined);
|
|
310
303
|
assertEq(result.type, 'text');
|
|
311
|
-
assertEq(result.
|
|
304
|
+
assertEq(result.mimeType, 'text/plain');
|
|
312
305
|
},
|
|
313
306
|
// Binary add→get with content:true returns inline base64 content.
|
|
314
307
|
binaryAddGetContentRoundTrips: () => {
|
|
@@ -318,7 +311,7 @@ export const proof = {
|
|
|
318
311
|
assert(!resultOf(getResp).isError);
|
|
319
312
|
const result = JSON.parse(textOf(getResp));
|
|
320
313
|
assertEq(result.type, 'text');
|
|
321
|
-
assertEq(result.
|
|
314
|
+
assertEq(result.text, '*');
|
|
322
315
|
},
|
|
323
316
|
addGetRoundTrips: () => {
|
|
324
317
|
const [addResp] = session(call(2, 'cas_add', { content: textSample }));
|
|
@@ -326,7 +319,7 @@ export const proof = {
|
|
|
326
319
|
const [, getResp] = session(call(2, 'cas_add', { content: textSample }), call(3, 'cas_get', { hash, content: true }));
|
|
327
320
|
assert(!resultOf(getResp).isError);
|
|
328
321
|
const result = JSON.parse(textOf(getResp));
|
|
329
|
-
assertEq(result.
|
|
322
|
+
assertEq(result.text, textSample);
|
|
330
323
|
},
|
|
331
324
|
// Text content (no magic-byte match, valid UTF-8) comes back with type:'text' when content requested.
|
|
332
325
|
getUntypedReturnsText: () => {
|
|
@@ -336,9 +329,9 @@ export const proof = {
|
|
|
336
329
|
assertEq(item0(getResp) === null ? null : item0(getResp).type, 'text');
|
|
337
330
|
const result = JSON.parse(textOf(getResp));
|
|
338
331
|
assertEq(result.type, 'text');
|
|
339
|
-
assertEq(result.
|
|
332
|
+
assertEq(result.mimeType, 'text/plain');
|
|
340
333
|
},
|
|
341
|
-
// Bytes with a recognised PNG signature come back as base64 with
|
|
334
|
+
// Bytes with a recognised PNG signature come back as base64 with mimeType image/png when content requested.
|
|
342
335
|
getTypedReturnsBinaryJson: () => {
|
|
343
336
|
const [addResp] = session(call(2, 'cas_add', { content: pngSample, type: 'base64' }));
|
|
344
337
|
const hash = textOf(addResp);
|
|
@@ -347,8 +340,33 @@ export const proof = {
|
|
|
347
340
|
assertEq(item0(getResp).type, 'text');
|
|
348
341
|
const result = JSON.parse(textOf(getResp));
|
|
349
342
|
assertEq(result.type, 'base64');
|
|
350
|
-
assertEq(result.
|
|
351
|
-
assertEq(result.
|
|
343
|
+
assertEq(result.mimeType, 'image/png');
|
|
344
|
+
assertEq(result.blob, pngSample);
|
|
345
|
+
},
|
|
346
|
+
// A stored `vnd.fjs.revision` blob is recognized by metadata-only
|
|
347
|
+
// `cas_get` (content:false, the default) — the CAS read path routes the
|
|
348
|
+
// bounded, whole-blob-text case through the dialect-aware detector rather
|
|
349
|
+
// than only reporting the streaming text/plain verdict.
|
|
350
|
+
getMetaRecognizesRevisionDialect: () => {
|
|
351
|
+
const [addResp] = session(call(2, 'cas_add', { content: revisionSample }));
|
|
352
|
+
const hash = textOf(addResp);
|
|
353
|
+
const [, metaResp] = session(call(2, 'cas_add', { content: revisionSample }), call(3, 'cas_get', { hash }));
|
|
354
|
+
assert(!resultOf(metaResp).isError);
|
|
355
|
+
const meta = JSON.parse(textOf(metaResp));
|
|
356
|
+
assertEq(meta.mimeType, revisionMediaType);
|
|
357
|
+
assertEq(meta.type, 'text');
|
|
358
|
+
},
|
|
359
|
+
// Same dialect recognition on the `content: true` path, alongside the
|
|
360
|
+
// inline text payload.
|
|
361
|
+
getContentRecognizesRevisionDialect: () => {
|
|
362
|
+
const [addResp] = session(call(2, 'cas_add', { content: revisionSample }));
|
|
363
|
+
const hash = textOf(addResp);
|
|
364
|
+
const [, getResp] = session(call(2, 'cas_add', { content: revisionSample }), call(3, 'cas_get', { hash, content: true }));
|
|
365
|
+
assert(!resultOf(getResp).isError);
|
|
366
|
+
const result = JSON.parse(textOf(getResp));
|
|
367
|
+
assertEq(result.mimeType, revisionMediaType);
|
|
368
|
+
assertEq(result.type, 'text');
|
|
369
|
+
assertEq(result.text, revisionSample);
|
|
352
370
|
},
|
|
353
371
|
listEnumeratesStoredHashes: () => {
|
|
354
372
|
const [addResp, listResp] = session(call(2, 'cas_add', { content: textSample }), call(3, 'cas_list', {}));
|
|
@@ -370,6 +388,7 @@ export const proof = {
|
|
|
370
388
|
const [resp] = session(call(2, 'cas_add', { content: 'A'.repeat(174_764), type: 'base64' }));
|
|
371
389
|
assertEq(resultOf(resp).isError, true);
|
|
372
390
|
assert(textOf(resp).includes('too large or malformed'));
|
|
391
|
+
assert(textOf(resp).includes('cas add'));
|
|
373
392
|
},
|
|
374
393
|
// One ASCII byte past maxLengthBytes — tryUtf8 returns null and cas_add
|
|
375
394
|
// surfaces an error.
|
|
@@ -377,6 +396,7 @@ export const proof = {
|
|
|
377
396
|
const [resp] = session(call(2, 'cas_add', { content: 'a'.repeat(Number(maxLengthBytes) + 1) }));
|
|
378
397
|
assertEq(resultOf(resp).isError, true);
|
|
379
398
|
assert(textOf(resp).includes('too large or malformed'));
|
|
399
|
+
assert(textOf(resp).includes('cas add'));
|
|
380
400
|
},
|
|
381
401
|
// Exactly maxLengthBytes (131,072) ASCII bytes — tryUtf8 returns a non-null
|
|
382
402
|
// Vec (len === maxLength, not over), so cas_add stores cleanly.
|
|
@@ -385,15 +405,14 @@ export const proof = {
|
|
|
385
405
|
assert(!resultOf(resp).isError);
|
|
386
406
|
assert(textOf(resp).length > 0);
|
|
387
407
|
},
|
|
388
|
-
// base64 of exactly maxLengthBytes —
|
|
389
|
-
//
|
|
390
|
-
// maxLength
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
addBase64AtLimitIsError: () => {
|
|
408
|
+
// base64 of exactly maxLengthBytes — base64Decode trims the encoding's
|
|
409
|
+
// padding bits from the last character before measuring the result
|
|
410
|
+
// against maxLength, so a payload landing exactly at the limit decodes
|
|
411
|
+
// and stores cleanly.
|
|
412
|
+
addBase64AtLimitSucceeds: () => {
|
|
394
413
|
const [resp] = session(call(2, 'cas_add', { content: base64OfA(maxLengthBytes), type: 'base64' }));
|
|
395
|
-
|
|
396
|
-
assert(textOf(resp).
|
|
414
|
+
assert(!resultOf(resp).isError);
|
|
415
|
+
assert(textOf(resp).length > 0);
|
|
397
416
|
},
|
|
398
417
|
getUnterminatedHashIsError: () => {
|
|
399
418
|
const [resp] = session(call(2, 'cas_get', { hash: '0' }));
|
|
@@ -425,66 +444,17 @@ export const proof = {
|
|
|
425
444
|
assert(!('error' in resp));
|
|
426
445
|
assert('result' in resp);
|
|
427
446
|
},
|
|
428
|
-
// cas_add with type:'url' streams a file from /home/user/cas_upload/ into CAS.
|
|
429
|
-
addUrlStoresFileAndReturnsHash: () => {
|
|
430
|
-
const fileContent = utf8('hello from file');
|
|
431
|
-
const root = { 'home': { 'user': { 'cas_upload': { 'hello.txt': [fileContent] } } } };
|
|
432
|
-
const [addUrlResp] = runSessionVirtual(root)([
|
|
433
|
-
init, initialized,
|
|
434
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/hello.txt', type: 'url' }),
|
|
435
|
-
]).slice(2);
|
|
436
|
-
assert(!resultOf(addUrlResp).isError);
|
|
437
|
-
assert(textOf(addUrlResp).length > 0);
|
|
438
|
-
},
|
|
439
|
-
addUrlRoundTrips: () => {
|
|
440
|
-
const fileContent = utf8('round-trip content');
|
|
441
|
-
const root = { 'home': { 'user': { 'cas_upload': { 'rt.txt': [fileContent] } } } };
|
|
442
|
-
// First pass: add to get the hash (deterministic for same content).
|
|
443
|
-
const [addResp] = runSessionVirtual(root)([
|
|
444
|
-
init, initialized,
|
|
445
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/rt.txt', type: 'url' }),
|
|
446
|
-
]).slice(2);
|
|
447
|
-
const hash = textOf(addResp);
|
|
448
|
-
// Second pass: add again + get in one session (file re-present in fresh virtual state).
|
|
449
|
-
const [, getResp] = runSessionVirtual(root)([
|
|
450
|
-
init, initialized,
|
|
451
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/rt.txt', type: 'url' }),
|
|
452
|
-
call(3, 'cas_get', { hash, content: true }),
|
|
453
|
-
]).slice(2);
|
|
454
|
-
assert(!resultOf(getResp).isError);
|
|
455
|
-
const result = JSON.parse(textOf(getResp));
|
|
456
|
-
assertEq(result.type, 'text');
|
|
457
|
-
assertEq(result.content, 'round-trip content');
|
|
458
|
-
},
|
|
459
|
-
addUrlMissingFileIsError: () => {
|
|
460
|
-
const [resp] = runSessionVirtual({})([
|
|
461
|
-
init, initialized,
|
|
462
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/nonexistent.txt', type: 'url' }),
|
|
463
|
-
]).slice(2);
|
|
464
|
-
assertEq(resultOf(resp).isError, true);
|
|
465
|
-
},
|
|
466
447
|
// cas_get without content:true returns only metadata.
|
|
467
448
|
getMetaReturnsLengthAndMimeType: () => {
|
|
468
|
-
const
|
|
469
|
-
const root = { 'home': { 'user': { 'cas_upload': { 'f': [fileContent] } } } };
|
|
470
|
-
// First pass: add to get the hash.
|
|
471
|
-
const [addResp] = runSessionVirtual(root)([
|
|
472
|
-
init, initialized,
|
|
473
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/f', type: 'url' }),
|
|
474
|
-
]).slice(2);
|
|
449
|
+
const [addResp] = session(call(2, 'cas_add', { content: 'text content' }));
|
|
475
450
|
const hash = textOf(addResp);
|
|
476
|
-
|
|
477
|
-
const [, metaResp] = runSessionVirtual(root)([
|
|
478
|
-
init, initialized,
|
|
479
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/f', type: 'url' }),
|
|
480
|
-
call(3, 'cas_get', { hash }),
|
|
481
|
-
]).slice(2);
|
|
451
|
+
const [, metaResp] = session(call(2, 'cas_add', { content: 'text content' }), call(3, 'cas_get', { hash }));
|
|
482
452
|
assert(!resultOf(metaResp).isError);
|
|
483
453
|
const meta = JSON.parse(textOf(metaResp));
|
|
484
|
-
assertEq(meta.
|
|
454
|
+
assertEq(meta.mimeType, 'text/plain');
|
|
485
455
|
assertEq(meta.type, 'text');
|
|
486
456
|
assertEq(meta.length, 12);
|
|
487
|
-
assertEq(meta.
|
|
457
|
+
assertEq(meta.text, undefined);
|
|
488
458
|
},
|
|
489
459
|
getMetaBinaryBlob: () => {
|
|
490
460
|
const [addResp] = session(call(2, 'cas_add', { content: pngSample, type: 'base64' }));
|
|
@@ -492,10 +462,10 @@ export const proof = {
|
|
|
492
462
|
const [, metaResp] = session(call(2, 'cas_add', { content: pngSample, type: 'base64' }), call(3, 'cas_get', { hash }));
|
|
493
463
|
assert(!resultOf(metaResp).isError);
|
|
494
464
|
const meta = JSON.parse(textOf(metaResp));
|
|
495
|
-
assertEq(meta.
|
|
465
|
+
assertEq(meta.mimeType, 'image/png');
|
|
496
466
|
assertEq(meta.type, 'base64');
|
|
497
467
|
assertEq(meta.length, 10);
|
|
498
|
-
assertEq(meta.
|
|
468
|
+
assertEq(meta.blob, undefined);
|
|
499
469
|
},
|
|
500
470
|
getMetaOctetStreamForUnknownBinary: () => {
|
|
501
471
|
const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01]);
|
|
@@ -505,9 +475,9 @@ export const proof = {
|
|
|
505
475
|
const [, metaResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }), call(3, 'cas_get', { hash }));
|
|
506
476
|
assert(!resultOf(metaResp).isError);
|
|
507
477
|
const meta = JSON.parse(textOf(metaResp));
|
|
508
|
-
assertEq(meta.
|
|
478
|
+
assertEq(meta.mimeType, 'application/octet-stream');
|
|
509
479
|
assertEq(meta.type, 'base64');
|
|
510
|
-
assertEq(meta.
|
|
480
|
+
assertEq(meta.blob, undefined);
|
|
511
481
|
},
|
|
512
482
|
// A NUL-bearing blob is valid UTF-8 yet binary: cas_get must report
|
|
513
483
|
// base64/octet-stream, not text/plain.
|
|
@@ -519,7 +489,7 @@ export const proof = {
|
|
|
519
489
|
const [, metaResp] = session(call(2, 'cas_add', { content: nulB64, type: 'base64' }), call(3, 'cas_get', { hash }));
|
|
520
490
|
assert(!resultOf(metaResp).isError);
|
|
521
491
|
const meta = JSON.parse(textOf(metaResp));
|
|
522
|
-
assertEq(meta.
|
|
492
|
+
assertEq(meta.mimeType, 'application/octet-stream');
|
|
523
493
|
assertEq(meta.type, 'base64');
|
|
524
494
|
},
|
|
525
495
|
getMetaMissingHashIsError: () => {
|
|
@@ -531,40 +501,6 @@ export const proof = {
|
|
|
531
501
|
const [resp] = session(call(2, 'cas_get', { hash: 'bad!' }));
|
|
532
502
|
assertEq(resultOf(resp).isError, true);
|
|
533
503
|
},
|
|
534
|
-
// cas_add type:'url' with a subdirectory path flattens slashes to '-' in staging.
|
|
535
|
-
addUrlFromSubdirectorySucceeds: () => {
|
|
536
|
-
const fileContent = utf8('nested file content');
|
|
537
|
-
const root = { 'home': { 'user': { 'cas_upload': { 'subdir': { 'file.txt': [fileContent] } } } } };
|
|
538
|
-
const [resp] = runSessionVirtual(root)([
|
|
539
|
-
init, initialized,
|
|
540
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/subdir/file.txt', type: 'url' }),
|
|
541
|
-
]).slice(2);
|
|
542
|
-
assert(!resultOf(resp).isError);
|
|
543
|
-
assert(textOf(resp).length > 0);
|
|
544
|
-
},
|
|
545
|
-
// cas_add with type:'url' accepts paths within /home/user/cas_upload/
|
|
546
|
-
addUrlFromApprovedDirectorySucceeds: () => {
|
|
547
|
-
const fileContent = utf8('approved file');
|
|
548
|
-
const root = { 'home': { 'user': { 'cas_upload': { 'test.txt': [fileContent] } } } };
|
|
549
|
-
const [resp] = runSessionVirtual(root)([
|
|
550
|
-
init, initialized,
|
|
551
|
-
call(2, 'cas_add', { content: '/home/user/cas_upload/test.txt', type: 'url' }),
|
|
552
|
-
]).slice(2);
|
|
553
|
-
assert(!resultOf(resp).isError);
|
|
554
|
-
assert(textOf(resp).length > 0);
|
|
555
|
-
},
|
|
556
|
-
// cas_add with type:'url' rejects paths outside /home/user/cas_upload/
|
|
557
|
-
addUrlFromRandomDirectoryIsRejected: () => {
|
|
558
|
-
const [resp] = session(call(2, 'cas_add', { content: '/tmp/secret.txt', type: 'url' }));
|
|
559
|
-
assert(resultOf(resp).isError === true);
|
|
560
|
-
assert(textOf(resp).includes('/home/user/cas_upload/'));
|
|
561
|
-
},
|
|
562
|
-
// cas_add with type:'url' rejects path traversal attempts with ..
|
|
563
|
-
addUrlWithPathTraversalIsRejected: () => {
|
|
564
|
-
const [resp] = session(call(2, 'cas_add', { content: '/home/user/cas_upload/../../etc/passwd', type: 'url' }));
|
|
565
|
-
assert(resultOf(resp).isError === true);
|
|
566
|
-
assert(textOf(resp).includes('/home/user/cas_upload/'));
|
|
567
|
-
},
|
|
568
504
|
// cas_get with content:true on octet-stream (no magic bytes, not UTF-8) returns inline base64.
|
|
569
505
|
getOctetStreamWithContentIncludesBase64: () => {
|
|
570
506
|
const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01]);
|
|
@@ -574,8 +510,8 @@ export const proof = {
|
|
|
574
510
|
const [, getResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }), call(3, 'cas_get', { hash, content: true }));
|
|
575
511
|
assert(!resultOf(getResp).isError);
|
|
576
512
|
const result = JSON.parse(textOf(getResp));
|
|
577
|
-
assertEq(result.
|
|
513
|
+
assertEq(result.mimeType, 'application/octet-stream');
|
|
578
514
|
assertEq(result.type, 'base64');
|
|
579
|
-
assertEq(result.
|
|
515
|
+
assertEq(result.blob, binaryB64);
|
|
580
516
|
},
|
|
581
517
|
};
|
package/fs/cas/module.f.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import { sha256 } from
|
|
7
|
-
import { join, normalize, parse } from
|
|
8
|
-
import { empty, length, maxLengthBytes, msb, vec } from
|
|
9
|
-
import { cBase32ToVec, vecToCBase32 } from
|
|
10
|
-
import { foldStep, forEachStep, pure } from
|
|
11
|
-
import { access, createExclusive, isNotFound, mkdir, now, randomInt, readBytes, readdir, rename, rm, stat, writeBytes, } from
|
|
12
|
-
import { toOption } from
|
|
13
|
-
import { error, ok, unwrap } from
|
|
14
|
-
import { splitAt } from
|
|
15
|
-
import { nonEmpty, empty as elEmpty } from
|
|
6
|
+
import { sha256 } from '../crypto/sha2/module.f.js';
|
|
7
|
+
import { join, normalize, parse } from '../path/module.f.js';
|
|
8
|
+
import { empty, length, maxLengthBytes, msb, vec } from '../types/bit_vec/module.f.js';
|
|
9
|
+
import { cBase32ToVec, vecToCBase32 } from '../basen/cbase32/module.f.js';
|
|
10
|
+
import { foldStep, forEachStep, okStep, pure } from '../effects/module.f.js';
|
|
11
|
+
import { access, createExclusive, isNotFound, mkdir, now, randomInt, readBytes, readdir, rename, rm, stat, writeBytes, } from '../effects/node/module.f.js';
|
|
12
|
+
import { toOption } from '../types/nullable/module.f.js';
|
|
13
|
+
import { error, ok, unwrap } from '../types/result/module.f.js';
|
|
14
|
+
import { splitAt } from '../types/string/module.f.js';
|
|
15
|
+
import { nonEmpty, empty as elEmpty } from '../effects/list/module.f.js';
|
|
16
16
|
const split2 = splitAt(2);
|
|
17
17
|
const prefix = '.cas';
|
|
18
18
|
/** Converts a content key to its sharded relative CAS file path. */
|
|
@@ -141,9 +141,7 @@ export const fileCas = (sha2) => (path) => {
|
|
|
141
141
|
.step(t0 => {
|
|
142
142
|
const path0 = join(stageDir, stageName(t0 + leaseDelta, rndStr));
|
|
143
143
|
return createExclusive(path0)
|
|
144
|
-
.step((
|
|
145
|
-
? pure(error(e))
|
|
146
|
-
: loop(sha2.init, 0, path0)(payload));
|
|
144
|
+
.step(okStep(() => loop(sha2.init, 0, path0)(payload)));
|
|
147
145
|
}));
|
|
148
146
|
}));
|
|
149
147
|
},
|
|
@@ -200,10 +198,5 @@ cas.write(streamFile(path));
|
|
|
200
198
|
export const casUpload = (home) => (fileName) => {
|
|
201
199
|
const src = join(home, 'cas_upload', fileName);
|
|
202
200
|
const c = fileCas(sha256)(home);
|
|
203
|
-
return casAddFile(c)(src).step((
|
|
204
|
-
if (result[0] === 'error') {
|
|
205
|
-
return pure(result);
|
|
206
|
-
}
|
|
207
|
-
return rm(src).step(() => pure(result));
|
|
208
|
-
});
|
|
201
|
+
return casAddFile(c)(src).step(okStep(v => rm(src).step(() => pure(ok(v)))));
|
|
209
202
|
};
|
package/fs/cas/proof.f.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { type FileCasOperation } from './module.f.ts';
|
|
|
2
2
|
import { type Effect } from '../effects/module.f.ts';
|
|
3
3
|
import { type ReadFile, type WriteFile, type Rm, type Mkdir } from '../effects/node/module.f.ts';
|
|
4
4
|
type TestOp = FileCasOperation | WriteFile | ReadFile | Rm | Mkdir;
|
|
5
|
+
declare const testAddBigFile: () => Effect<TestOp, void>;
|
|
6
|
+
declare const testAddAndGetBigFile: () => Effect<TestOp, void>;
|
|
5
7
|
export declare const proof: {
|
|
6
|
-
addBigFile:
|
|
7
|
-
addAndGetBigFile:
|
|
8
|
+
addBigFile: typeof testAddBigFile;
|
|
9
|
+
addAndGetBigFile: typeof testAddAndGetBigFile;
|
|
8
10
|
casWriteRead: () => void;
|
|
9
11
|
casReadMissingShard: () => void;
|
|
10
12
|
casWriteMultiChunk: () => void;
|
package/fs/cas/proof.f.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { length, maxLength, msb, vec, vec8 } from
|
|
2
|
-
import { cBase32ToVec, vecToCBase32 } from
|
|
3
|
-
import { computeSync, sha256 } from
|
|
4
|
-
import { fileCas, casAddFile, casUpload } from
|
|
5
|
-
import { pure } from
|
|
6
|
-
import { mkdir, writeFile, rm, readFile, access } from
|
|
7
|
-
import { error, ok } from
|
|
8
|
-
import { emptyState, virtual } from
|
|
9
|
-
import { join } from
|
|
10
|
-
import { nonEmpty, empty } from
|
|
1
|
+
import { length, maxLength, msb, vec, vec8 } from '../types/bit_vec/module.f.js';
|
|
2
|
+
import { cBase32ToVec, vecToCBase32 } from '../basen/cbase32/module.f.js';
|
|
3
|
+
import { computeSync, sha256 } from '../crypto/sha2/module.f.js';
|
|
4
|
+
import { fileCas, casAddFile, casUpload } from './module.f.js';
|
|
5
|
+
import { pure } from '../effects/module.f.js';
|
|
6
|
+
import { mkdir, writeFile, rm, readFile, access } from '../effects/node/module.f.js';
|
|
7
|
+
import { error, ok } from '../types/result/module.f.js';
|
|
8
|
+
import { emptyState, virtual } from '../effects/node/virtual/module.f.js';
|
|
9
|
+
import { join } from '../path/module.f.js';
|
|
10
|
+
import { nonEmpty, empty } from '../effects/list/module.f.js';
|
|
11
11
|
const testDir = './test-cas-cli';
|
|
12
12
|
// Create a 128 KiB big file content (at the max Vec size limit)
|
|
13
13
|
// This tests the boundary where files are at the chunk size limit
|
package/fs/ci/bun/module.f.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
|
-
import { bun } from
|
|
8
|
-
import { clean, install, test, uses } from
|
|
7
|
+
import { bun } from '../config/module.f.js';
|
|
8
|
+
import { clean, install, test, uses } from '../common/module.f.js';
|
|
9
9
|
export const bunSteps = (version) => clean([
|
|
10
10
|
install(uses('oven-sh/setup-bun', { 'bun-version': bun })),
|
|
11
11
|
install({ run: `bun install -g functionalscript@${version}` }),
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { actions, images } from '../config/module.f.ts';
|
|
9
9
|
import { type Ts } from '../../types/rtti/ts/module.f.ts';
|
|
10
|
-
export declare const os: readonly [
|
|
10
|
+
export declare const os: readonly ['ubuntu', 'macos', 'windows'];
|
|
11
11
|
export type Os = typeof os[number];
|
|
12
|
-
export declare const architecture: readonly [
|
|
12
|
+
export declare const architecture: readonly ['intel', 'arm'];
|
|
13
13
|
export type Architecture = typeof architecture[number];
|
|
14
14
|
export type Image = typeof images[Os][Architecture];
|
|
15
15
|
export declare const stepSchema: {
|