@emagjby/strata-js 0.3.0 → 0.3.2

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 ADDED
@@ -0,0 +1,180 @@
1
+ # @emagjby/strata-js
2
+
3
+ Deterministic Strata implementation for JavaScript.
4
+
5
+ Strata is a deterministic binary data format with canonical encoding: identical logical values must produce identical bytes (and therefore identical hashes) across all implementations.
6
+
7
+ This package is a **parity implementation** with the Rust reference implementation. If Rust and JavaScript ever disagree, **that is a bug**.
8
+
9
+ - Canonical encoding: one value → one byte representation
10
+ - Hashing: stable hash over canonical bytes (BLAKE3-256)
11
+ - Strata Text (`.st`): human authoring format that compiles to canonical Strata Core Binary (`.scb`)
12
+
13
+ Docs: https://strata.emagjby.com/docs
14
+
15
+ ## Install
16
+
17
+ Library:
18
+
19
+ ```bash
20
+ npm install @emagjby/strata-js
21
+ ```
22
+
23
+ CLI:
24
+
25
+ ```bash
26
+ npm install -g @emagjby/strata-js
27
+ ```
28
+
29
+ ## Quickstart (Library)
30
+
31
+ ### Parse Strata Text (`.st`) → encode (`.scb`) → hash
32
+
33
+ ```js
34
+ import { parse, encodeValue, hashValueHex } from "@emagjby/strata-js";
35
+
36
+ const source = `{
37
+ "name": "alice",
38
+ "active": true,
39
+ "count": 3
40
+ }`;
41
+
42
+ const value = parse(source);
43
+ const scb = encodeValue(value); // Uint8Array canonical bytes
44
+
45
+ // Hashing is defined over canonical bytes.
46
+ console.log(hashValueHex(value));
47
+ ```
48
+
49
+ ### Decode `.scb` bytes back into a Strata value
50
+
51
+ ```js
52
+ import { decodeValue, encodeValue } from "@emagjby/strata-js";
53
+
54
+ // Given canonical bytes (or bytes captured from storage/wire)
55
+ const originalScb = new Uint8Array([0x00]); // example only
56
+
57
+ const value = decodeValue(originalScb);
58
+ const roundtrippedScb = encodeValue(value);
59
+
60
+ // Strata guarantees: decode(encode(value)) == value
61
+ // The reverse is intentionally NOT guaranteed.
62
+ console.log(roundtrippedScb);
63
+ ```
64
+
65
+ ### Hash a value (canonical re-encode)
66
+
67
+ If you already have a JS `Value` and want a canonical hash:
68
+
69
+ ```js
70
+ import { hashValueHex, parse } from "@emagjby/strata-js";
71
+
72
+ const value = parse("[1, 2, 3]");
73
+ console.log(hashValueHex(value));
74
+ ```
75
+
76
+ ## CLI
77
+
78
+ The `strata-js` CLI mirrors the Rust CLI.
79
+
80
+ Commands:
81
+
82
+ - `compile` – compile `.st` → canonical `.scb`
83
+ - `decode` – decode `.scb` into a stable inspection format
84
+ - `hash` – compute deterministic hash (BLAKE3-256)
85
+ - `fmt` – parse `.st` and print a structured inspection format
86
+
87
+ ### Compile
88
+
89
+ ```bash
90
+ strata-js compile input.st output.scb
91
+ ```
92
+
93
+ ### Hash
94
+
95
+ ```bash
96
+ strata-js hash input.st
97
+ strata-js hash input.scb
98
+ ```
99
+
100
+ Behavior:
101
+
102
+ - If input is `.st`, it is parsed and canonically encoded first
103
+ - If input is `.scb`, bytes are hashed directly
104
+ - Output is lowercase hex
105
+
106
+ ### Decode
107
+
108
+ ```bash
109
+ strata-js decode input.scb
110
+ ```
111
+
112
+ ### Fmt
113
+
114
+ ```bash
115
+ strata-js fmt input.st
116
+ ```
117
+
118
+ ### Exit codes
119
+
120
+ - `0` success
121
+ - `1` invalid input
122
+ - `2` I/O failure
123
+ - `100` internal error
124
+
125
+ ## JavaScript value model (important)
126
+
127
+ Strata values in JS are **not JSON** and are intentionally strict.
128
+
129
+ - Integers are `bigint` (JS `number` MUST NOT be used)
130
+ - Bytes are `Uint8Array`
131
+ - Maps are `ReadonlyMap<string, Value>`
132
+
133
+ If you need to bridge into JSON:
134
+
135
+ - `bigint` cannot be directly serialized by `JSON.stringify`
136
+ - You must choose an explicit representation (commonly decimal strings)
137
+
138
+ Type shape (abridged):
139
+
140
+ - `{ kind: "null" }`
141
+ - `{ kind: "bool", value: boolean }`
142
+ - `{ kind: "int", value: bigint }`
143
+ - `{ kind: "string", value: string }`
144
+ - `{ kind: "bytes", value: Uint8Array }`
145
+ - `{ kind: "list", value: readonly Value[] }`
146
+ - `{ kind: "map", value: ReadonlyMap<string, Value> }`
147
+
148
+ ## Determinism notes
149
+
150
+ - Hashing is defined over **canonical encoded bytes**, not decoded structures.
151
+ - Decoding exists to inspect reality; encoding defines canonical truth.
152
+ - Map keys are canonically ordered by UTF-8 byte order during encoding.
153
+
154
+ If your system needs “mostly the same bytes”, Strata is not the tool. If it needs _exactly the same bytes_, it is.
155
+
156
+ ## Golden vectors
157
+
158
+ Strata correctness is enforced via golden vectors (authored in `.st`) and cross-implementation tests.
159
+
160
+ Golden vectors are not examples; they are law:
161
+
162
+ - If this package disagrees with a golden vector, **the implementation is wrong**.
163
+ - Vectors are not adjusted to match buggy behavior.
164
+
165
+ ## What Strata does NOT do
166
+
167
+ Strata intentionally does not provide:
168
+
169
+ - Schemas or validation rules
170
+ - Optional fields or default values
171
+ - Backward-compatible schema evolution
172
+ - Floating point numbers
173
+ - Streaming/framing rules (transport concerns are external)
174
+ - Compression, encryption, or authentication
175
+
176
+ ## Links
177
+
178
+ - Documentation: https://strata.emagjby.com/docs
179
+ - Repo: https://github.com/Emagjby/Strata
180
+ - Strata JS package source: https://github.com/Emagjby/Strata/tree/main/strata-js
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { decodeValue } from "./decode.js";
7
7
  import { hashBytes } from "./hash.js";
8
8
  import { DecodeError } from "./decode_error.js";
9
9
  import { ParseError } from "./parse_error.js";
10
+ import { inspectValue } from "./inspect.js";
10
11
  function exitOk() {
11
12
  process.exit(0);
12
13
  }
@@ -41,7 +42,7 @@ function cmdCompile(input, output) {
41
42
  function cmdDecode(input) {
42
43
  const encoded = fs.readFileSync(input);
43
44
  const value = decodeValue(encoded);
44
- console.log(JSON.stringify(value, null, 2));
45
+ console.log(JSON.stringify(inspectValue(value), null, 2));
45
46
  }
46
47
  function cmdHash(input) {
47
48
  let encoded;
@@ -59,7 +60,7 @@ function cmdHash(input) {
59
60
  function cmdFmt(input) {
60
61
  const source = fs.readFileSync(input, "utf8");
61
62
  const parsedValue = parse(source);
62
- console.log(JSON.stringify(parsedValue, null, 2));
63
+ console.log(JSON.stringify(inspectValue(parsedValue), null, 2));
63
64
  }
64
65
  function main() {
65
66
  const [, , cmd, ...args] = process.argv;
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Value } from "./value.js";
2
+ export declare function inspectValue(value: Value): any;
3
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CA0B9C"}
@@ -0,0 +1,23 @@
1
+ export function inspectValue(value) {
2
+ switch (value.kind) {
3
+ case "null":
4
+ return null;
5
+ case "int":
6
+ return value.value.toString();
7
+ case "bool":
8
+ case "string":
9
+ return value.value;
10
+ case "bytes":
11
+ return Array.from(value.value);
12
+ case "list":
13
+ return value.value.map(inspectValue);
14
+ case "map": {
15
+ const obj = {};
16
+ for (const [k, v] of value.value.entries()) {
17
+ obj[k] = inspectValue(v);
18
+ }
19
+ return obj;
20
+ }
21
+ }
22
+ }
23
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,KAAY;IACrC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,MAAM;YACP,OAAO,IAAI,CAAC;QAEhB,KAAK,KAAK;YACN,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAElC,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ;YACT,OAAO,KAAK,CAAC,KAAK,CAAC;QAEvB,KAAK,OAAO;YACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAEnC,KAAK,MAAM;YACP,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAEzC,KAAK,KAAK,CAAC,CAAC,CAAC;YACT,MAAM,GAAG,GAAwB,EAAE,CAAC;YACpC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBACzC,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;YACD,OAAO,GAAG,CAAC;QACf,CAAC;IACL,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,35 +1,30 @@
1
1
  {
2
- "name": "@emagjby/strata-js",
3
- "version": "0.3.0",
4
- "type": "module",
5
-
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": "./dist/index.js"
10
- },
11
-
12
- "bin": {
13
- "strata-js": "./dist/cli.js"
14
- },
15
-
16
- "files": [
17
- "dist"
18
- ],
19
-
20
- "scripts": {
21
- "build": "tsc",
22
- "test": "tsx --test",
23
- "check": "tsc --noEmit && npm run test",
24
- "prepublishOnly": "npm run build"
25
- },
26
-
27
- "devDependencies": {
28
- "@types/node": "^25.0.3",
29
- "tsx": "^4.21.0",
30
- "typescript": "^5.9.3"
31
- },
32
- "dependencies": {
33
- "@noble/hashes": "^2.0.1"
34
- }
2
+ "name": "@emagjby/strata-js",
3
+ "version": "0.3.2",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "bin": {
11
+ "strata-js": "./dist/cli.js"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "tsx --test",
19
+ "check": "tsc --noEmit && npm run test",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.0.3",
24
+ "tsx": "^4.21.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "dependencies": {
28
+ "@noble/hashes": "^2.0.1"
29
+ }
35
30
  }
@@ -1,28 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { decodeValue } from "../decode.js";
4
- import { encodeValue } from "../encode.js";
5
- import { V } from "../value_factory.js";
6
- import { DecodeError } from "../decode_error.js";
7
- test("decode simple int", () => {
8
- const bytes = new Uint8Array([0x10, 0x01]);
9
- const v = decodeValue(bytes);
10
- assert.equal(v.kind, "int");
11
- assert.equal(v.value, 1n);
12
- });
13
- test("encode -> decode roundtrip", () => {
14
- const value = V.map([
15
- ["a", V.int(1n)],
16
- ["b", V.bool(true)],
17
- ["c", V.list([V.null(), V.string("hi")])],
18
- ]);
19
- const encoded = encodeValue(value);
20
- const decoded = decodeValue(encoded);
21
- assert.deepEqual(decoded, value);
22
- });
23
- test("invalid tag throws DecodeError", () => {
24
- const bytes = new Uint8Array([0xff]);
25
- assert.throws(() => decodeValue(bytes), (err) => err instanceof DecodeError &&
26
- err.kind.type === "InvalidTag" &&
27
- err.offset === 0);
28
- });
@@ -1,46 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { encodeValue } from "../encode.js";
6
- import { V } from "../value_factory.js";
7
- const VECTORS_DIR = join(process.cwd(), "..", "vectors");
8
- function readHex(path) {
9
- const text = readFileSync(path, "utf-8").trim();
10
- assert.equal(text.length % 2, 0, "Hex string must have even length");
11
- const out = new Uint8Array(text.length / 2);
12
- for (let i = 0; i < text.length; i += 2) {
13
- out[i / 2] = Number.parseInt(text.slice(i, i + 2), 16);
14
- }
15
- return out;
16
- }
17
- test("encode matches v1/01-basic.scb.hex", () => {
18
- const config = V.map([
19
- ["enabled", V.bool(true)],
20
- ["retries", V.int(3n)],
21
- ["name", V.string("strata")],
22
- ["empty", V.null()],
23
- ]);
24
- const root = V.map([["config", config]]);
25
- const expected = readHex(join(VECTORS_DIR, "v1", "01-basic.scb.hex"));
26
- const actual = encodeValue(root);
27
- assert.deepEqual([...actual], [...expected]);
28
- });
29
- test("encode matches v1/03-bigint-bytes.scb.hex", () => {
30
- const avatar = new Uint8Array([
31
- 0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0,
32
- 0xc5, 0x5a, 0xd0, 0x15,
33
- ]);
34
- const profile = V.map([
35
- ["id", V.int(9007199254740993n)],
36
- ["avatar_hash", V.bytes(avatar)],
37
- [
38
- "tags",
39
- V.list([V.string("logistics"), V.string("state"), V.string("integrity")]),
40
- ],
41
- ]);
42
- const root = V.map([["profile", profile]]);
43
- const expected = readHex(join(VECTORS_DIR, "v1", "03-bigint-bytes.scb.hex"));
44
- const actual = encodeValue(root);
45
- assert.deepEqual([...actual], [...expected]);
46
- });
@@ -1,51 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { V } from "../value_factory.js";
6
- import { hashValueHex } from "../hash.js";
7
- const VECTORS_DIR = join(process.cwd(), "..", "vectors");
8
- function readText(path) {
9
- return readFileSync(path, "utf8").trim();
10
- }
11
- test("hash matches v1/01-basic.hash.hex", () => {
12
- const config = V.map([
13
- ["enabled", V.bool(true)],
14
- ["retries", V.int(3n)],
15
- ["name", V.string("strata")],
16
- ["empty", V.null()],
17
- ]);
18
- const root = V.map([["config", config]]);
19
- const expected = readText(join(VECTORS_DIR, "v1", "01-basic.hash.hex"));
20
- const actual = hashValueHex(root);
21
- assert.equal(actual, expected);
22
- });
23
- test("hash matches v1/02-map-order.hash.hex", () => {
24
- const data = V.map([
25
- ["z", V.int(1n)],
26
- ["a", V.int(2n)],
27
- ["m", V.int(3n)],
28
- ]);
29
- const root = V.map([["data", data]]);
30
- const expected = readText(join(VECTORS_DIR, "v1", "02-map-order.hash.hex"));
31
- const actual = hashValueHex(root);
32
- assert.equal(actual, expected);
33
- });
34
- test("hash matches v1/03-bigint-bytes.hash.hex", () => {
35
- const avatar = new Uint8Array([
36
- 0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0,
37
- 0xc5, 0x5a, 0xd0, 0x15,
38
- ]);
39
- const profile = V.map([
40
- ["id", V.int(9007199254740993n)],
41
- ["avatar_hash", V.bytes(avatar)],
42
- [
43
- "tags",
44
- V.list([V.string("logistics"), V.string("state"), V.string("integrity")]),
45
- ],
46
- ]);
47
- const root = V.map([["profile", profile]]);
48
- const expected = readText(join(VECTORS_DIR, "v1", "03-bigint-bytes.hash.hex"));
49
- const actual = hashValueHex(root);
50
- assert.equal(actual, expected);
51
- });
@@ -1,23 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { V } from "../value_factory.js";
4
- test("can represent all core value types", () => {
5
- const v = V.map([
6
- ["null", V.null()],
7
- ["bool", V.bool(true)],
8
- ["int", V.int(42n)],
9
- ["string", V.string("strata")],
10
- ["bytes", V.bytes(new Uint8Array([0xde, 0xad]))],
11
- ["list", V.list([V.int(1n), V.int(2n), V.int(3n)])],
12
- ]);
13
- assert.equal(v.kind, "map");
14
- });
15
- test("rejects number for int", () => {
16
- // @ts-expect-error
17
- assert.throws(() => V.int(42));
18
- });
19
- test("supports BigInt beyond JS safe integer range", () => {
20
- const big = 9007199254740993n;
21
- const v = V.int(big);
22
- assert.equal(v.value, big);
23
- });
@@ -1,37 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { encodeULEB128, decodeULEB128, encodeSLEB128, decodeSLEB128, } from "../varint.js";
4
- test("ULEB128 basic values", () => {
5
- assert.deepEqual(encodeULEB128(0n), Uint8Array.from([0x00]));
6
- assert.deepEqual(encodeULEB128(1n), Uint8Array.from([0x01]));
7
- assert.deepEqual(encodeULEB128(127n), Uint8Array.from([0x7f]));
8
- assert.deepEqual(encodeULEB128(128n), Uint8Array.from([0x80, 0x01]));
9
- });
10
- test("ULEB128 round trip", () => {
11
- const values = [0n, 1n, 127n, 128n, 1024n, 9007199254740993n];
12
- for (const value of values) {
13
- const encoded = encodeULEB128(value);
14
- const decoded = decodeULEB128(encoded);
15
- assert.equal(decoded.value, value);
16
- assert.equal(decoded.nextOffset, encoded.length);
17
- }
18
- });
19
- test("SLEB128 basic values", () => {
20
- const values = [
21
- 0n,
22
- 1n,
23
- -1n,
24
- 63n,
25
- -64n,
26
- 127n,
27
- -128n,
28
- 9007199254740993n,
29
- -9007199254740993n,
30
- ];
31
- for (const value of values) {
32
- const encoded = encodeSLEB128(value);
33
- const decoded = decodeSLEB128(encoded);
34
- assert.equal(decoded.value, value);
35
- assert.equal(decoded.nextOffset, encoded.length);
36
- }
37
- });
@@ -1,75 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { encodeValue } from "../encode.js";
6
- import { decodeValue } from "../decode.js";
7
- import { hashValueHex } from "../hash.js";
8
- import { parse } from "../parser.js";
9
- import { DecodeError } from "../decode_error.js";
10
- const VECTORS_DIR = join(process.cwd(), "..", "vectors");
11
- function readHex(path) {
12
- const text = readFileSync(path, "utf8").trim();
13
- assert.ok(text.length % 2 === 0, "Hex string must have even length");
14
- const out = new Uint8Array(text.length / 2);
15
- for (let i = 0; i < text.length; i += 2) {
16
- out[i / 2] = Number.parseInt(text.slice(i, i + 2), 16);
17
- }
18
- return out;
19
- }
20
- function readText(path) {
21
- return readFileSync(path, "utf8");
22
- }
23
- function readJson(path) {
24
- return JSON.parse(readFileSync(path, "utf8"));
25
- }
26
- function runPositiveVector(relPath) {
27
- const base = join(VECTORS_DIR, relPath);
28
- const stPath = base + ".st";
29
- const scbHexPath = base + ".scb.hex";
30
- const hashHexPath = base + ".hash.hex";
31
- const source = readText(stPath);
32
- const parsedValue = parse(source);
33
- const encoded = encodeValue(parsedValue);
34
- const expectedScb = readHex(scbHexPath);
35
- assert.deepEqual([...encoded], [...expectedScb], `SCB mismatch for vector ${relPath}`);
36
- //hash
37
- const actualHash = hashValueHex(parsedValue);
38
- const expectedHash = readText(hashHexPath).trim();
39
- assert.equal(actualHash, expectedHash, `Hash mismatch for vector ${relPath}`);
40
- }
41
- function runNegativeDecodeVector(relPath) {
42
- const base = join(VECTORS_DIR, relPath);
43
- const hexPath = base + ".hex";
44
- const errorPath = base + ".error.json";
45
- const bytes = readHex(hexPath);
46
- const expectedError = readJson(errorPath);
47
- let thrown;
48
- try {
49
- decodeValue(bytes);
50
- }
51
- catch (err) {
52
- thrown = err;
53
- }
54
- assert.ok(thrown instanceof DecodeError, `Expected DecodeError for ${relPath}`);
55
- const err = thrown;
56
- if (expectedError.kind === "InvalidTag") {
57
- assert.equal(err.kind.type, "InvalidTag");
58
- }
59
- else {
60
- assert.equal(err.kind.type, expectedError.kind);
61
- }
62
- assert.equal(err.offset, expectedError.offset, `offset mismatch for ${relPath}`);
63
- }
64
- // v1
65
- test("vector v1/01-basic", () => runPositiveVector("v1/01-basic"));
66
- test("vector v1/02-map-order", () => runPositiveVector("v1/02-map-order"));
67
- test("vector v1/03-bigint-bytes", () => runPositiveVector("v1/03-bigint-bytes"));
68
- // v2
69
- test("vector v2/01-decode-roundtrip", () => runPositiveVector("v2/01-decode-roundtrip"));
70
- test("vector v2/02-noncanonical-map-order", () => runPositiveVector("v2/02-noncanonical-map-order"));
71
- test("vector v2/03-nested-structure", () => runPositiveVector("v2/03-nested-structure"));
72
- test("neg v2.1/01-invalid-tag", () => runNegativeDecodeVector("v2.1/neg-01-invalid-tag"));
73
- test("neg v2.1/02-truncated-string", () => runNegativeDecodeVector("v2.1/neg-02-truncated-string"));
74
- test("neg v2.1/03-varint-overflow", () => runNegativeDecodeVector("v2.1/neg-03-varint-overflow"));
75
- test("neg v2.1/04-invalid-utf8", () => runNegativeDecodeVector("v2.1/neg-04-invalid-utf8"));
@@ -1,17 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { readFileSync, readdirSync } from "node:fs";
4
- import { join } from "node:path";
5
- const VECTORS_DIR = join(process.cwd(), "..", "vectors");
6
- test("JS can see vectors directory", () => {
7
- const entries = readdirSync(VECTORS_DIR);
8
- assert.ok(entries.includes("v1"));
9
- assert.ok(entries.includes("v2"));
10
- assert.ok(entries.includes("v2.1"));
11
- });
12
- test("JS can read a known vector file", () => {
13
- const path = join(VECTORS_DIR, "v1", "01-basic.st");
14
- const content = readFileSync(path, "utf-8");
15
- assert.ok(content.includes("config"));
16
- assert.ok(content.includes("enabled"));
17
- });