@zakkster/lite-bake-stream 1.4.0 → 1.4.1
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/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/FileIngest.js +26 -13
- package/src/MultiReader.js +1 -1
- package/src/PreserveReader.js +1 -1
- package/src/PreserveTokenizer.js +1 -1
- package/src/PreserveWriter.js +1 -1
- package/src/RangeReader.js +1 -1
- package/src/Reader.js +1 -1
- package/src/Split.js +1 -1
- package/src/StringTable.js +1 -1
- package/src/Tokenizer.js +1 -1
- package/src/Writer.js +1 -1
- package/src/index.js +1 -1
- package/types/FileIngest.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [1.4.1] -- 2026-09-02
|
|
6
|
+
|
|
7
|
+
M8 -- the four reserved torture cells become real; BS-30 and BS-31 close. No tier prints "reserved" any more. Suite: 461 tests / 461 pass / 0 fail / 0 todo (unchanged -- no named-suite edits). Torture: 44/44 fast (~1.3 s wall), 47/47 full, arrayBuffers growth 0. BREAK matrix: ten runs (`=1`, eight registry ids, one unknown id) all exit non-zero; the unknown id is refused at import. Inventory gate: 54 thrown codes / 0 unpinned. Falsifiability: with baseline src stashed, exactly one new assertion fails (the onProgress-identity pin), both tiers. Test-only except one hot-path reuse in `src/FileIngest.js` (below).
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- BS-31 (hot path): `ingestStream`'s per-chunk `onProgress` state object is now allocated ONCE and mutated in place, removing a per-chunk object literal from the ingest read loop. The state object is a reused ephemeral view -- valid only during the callback; copy it if retained. Contract documented in `src/FileIngest.js`, `types/FileIngest.d.ts`, and `llms.txt`. Falsifiability: with baseline src stashed, exactly the new onProgress-identity pin fails (baseline allocates a fresh object per chunk); every other new cell passes on both trees.
|
|
12
|
+
- Torture cells t0/t1/t2/t4 filled in. t0: the re-chunk determinism law (byte-identical container across whole/string/1/7/64/4096-byte chunkings x both modes on a shared degenerate corpus) plus mergeContainers/compileInParts/MultiReader query-equivalence, findShards superset+point-exact, and splitNDJSON reassembly. t1: degenerate-value and refusal pins over the shared corpus (`test/torture/fixtures/degenerate.mjs`, reused by t0/t2 and MQ2). t2: every-byte-position split-invariance fuzz over both tokenizers. t4: API-abuse decided-policy pins plus the R5 adversarial doors.
|
|
13
|
+
- BREAK selector: `BAKE_TORTURE_BREAK` now accepts a registry id (t6, g, i, j, k, l, m, n) to arm exactly one break site -- each control proves its own exit-non-zero path -- alongside legacy `=1` (arm all, halt at first). An unknown value fails closed at import. Registry documented in `test/torture/harness.mjs`.
|
|
14
|
+
- Demo (repo-only, never shipped in the tarball): refreshed from its pre-1.0 state to the current release. The synthetic fixture now exercises the v1.4 inference-window semantics (~8% null string cells read back `""` with the lane still U32; ~8% absent numeric cells read `0`); schema-mode ingest uses `framing: 'auto'` so a dropped top-level `[...]` array works; error handling matches on stable codes (`W_NESTED_UNSUPPORTED`) instead of message text; the onProgress reuse contract is stated where the demo destructures the state object; both demo files are ASCII-clean. Every library call the demo makes was verified headless against `src/` (24 checks green, incl. both handled error paths).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- BS-30: the code-inventory gate (`test/torture/inventory.mjs`, run from t8). It scans `src/*.js` for every thrown stable code (constructed-error and `_err`/`raise*` families), asserts each is pinned by at least one test, and prints its collected-code count (54). The six previously-open doors are pinned in t3/t4 (`R_UNSUPPORTED_VERSION`, `R_UNSUPPORTED_ENDIAN`, `R_ADAPTER_SHORT_READ`, `W_ROW_STRIDE_ZERO` x2 sites, `E_RECORD_TOO_LARGE`, `E_UNSUPPORTED_FRAMING`); t1 pins the two remaining unpinned degenerate codes (`E_INVALID_HEX`, `E_TRAILING_INPUT`). A t9 control feeds the scanner a synthetic unpinned code to prove the gate can flag one.
|
|
19
|
+
- BS-31: a t7 mid-stream-failure witness -- a ReadableStream whose pull throws makes `ingestStream` reject with the source error (no half-built Reader), and the lite-leak tracker returns to size 0. t6 gates the ingest sync core (feed + reused-progress mutation + `onProgress`) at zero allocation; the async await-read loop is covered by the t7 witness (runOpsGate rejects async fns).
|
|
20
|
+
- t9 controls (i)-(n): one in-run rehearsal per newly-real cell plus the inventory gate, so a plain `npm run torture` proves every new gate can fail.
|
|
21
|
+
|
|
22
|
+
### Findings (ledgered, no ride-along fix per session non-goals)
|
|
23
|
+
|
|
24
|
+
- `Reader.get(1.5)` returns garbage (a fractional-byte-offset read) instead of a decided policy (throw / no-op / undefined) -- t4 records it, does not pin it as correct; the t9 (l) control proves the gate would catch a garbage return.
|
|
25
|
+
- `RangeReader.syncRange` without a prior `prefetchRange` throws the shared `R_TRUNCATED`; the ROADMAP calls for a distinct code. t4 records it, does not pin the shared code.
|
|
26
|
+
- `RangeReader` never length-validates an adapter's `fetch()` return: an adapter that returns fewer bytes than requested surfaces a raw downstream `RangeError`, not `R_ADAPTER_SHORT_READ` (whose two real sites are the Mock adapter's own past-size check and `HTTPRangeAdapter`'s 200-not-206 check). Ledgered as a reader-guard gap.
|
|
27
|
+
|
|
5
28
|
## [1.4.0] -- 2026-09-01
|
|
6
29
|
|
|
7
30
|
M4 -- the sample window does what its name says, and null stops eating lanes. Two inference-window bugs fixed together (BS-07, BS-20). Suite: 461 tests / 461 pass / 0 fail / 0 todo. Torture: 44/44 fast, 47/47 full (incl. the 64 MB structural scenario), arrayBuffers growth 0. Mutation measurement: the 8-test manifest failed exactly and the 5 canaries passed on baseline src. Findings probes: 19 NOT-REPRODUCED / 0 REPRODUCED / 0 PARTIAL -- the ledger's first all-clear; the probe file is retired.
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
> Streaming byte-level JSON compiler for [`@zakkster/lite-bake`](https://github.com/PeshoVurtoleta/lite-bake). Zero-GC, tree-shakeable, gigabyte-scale.
|
|
15
15
|
|
|
16
|
-
**Status:** v1.4.
|
|
16
|
+
**Status:** v1.4.1. LBK1 format frozen at `format_version: 1`. Qualified on an 8 GB soak with zero GC and byte-exact preservation across 590 million cells.
|
|
17
17
|
|
|
18
18
|
## Two modes, one API
|
|
19
19
|
|
package/llms.txt
CHANGED
|
@@ -8,13 +8,13 @@ Ingest gigabyte-scale JSON (top-level array or NDJSON) into the `lite-bake` LBK1
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
v1.4.
|
|
11
|
+
v1.4.1 — stable. LBK1 format frozen at `format_version: 1`. Qualified on an 8 GB soak (M1 MacBook Pro): 98.37M rows, 4.89 GB container, zero major GC, zero minor GC, 499 KB total heap allocation, 590.21M cells verified byte-exact, zero mismatches. Tokenizer benches at 222-237 MB/s (~55% of JSON.parse, with no object graph allocated).
|
|
12
12
|
|
|
13
13
|
Public API follows semver from 1.0.0. Future additions (I64 lane, columnar payload mode, container-level string table) land via the format's forward-compat seams -- `min_reader_version` on ShardEntry, reserved FieldDescriptor flags, the `metadata_off` block wrapper -- without a format_version bump.
|
|
14
14
|
|
|
15
15
|
See SPEC.md for the LBK1 container format, section 3.6 for zone maps, section 4.3 for the reserved field flags.
|
|
16
16
|
|
|
17
|
-
## Public API (v1.4.
|
|
17
|
+
## Public API (v1.4.1)
|
|
18
18
|
|
|
19
19
|
Two ingest modes share one top-level API:
|
|
20
20
|
|
|
@@ -41,7 +41,7 @@ Shared:
|
|
|
41
41
|
- `ingestStream`, `ingestFile` (`/file-ingest`): browser helpers piping a `ReadableStream<Uint8Array>` through the pipeline. `preserve` option dispatches to the right writer.
|
|
42
42
|
|
|
43
43
|
Error classes with stable `code`: `TokenizerError`, `WriterError`, `ReaderError`, `RangeReaderError`, `MultiReaderError`, `SplitError`, `PreserveTokenizerError`, `PreserveWriterError`, `PreserveReaderError`.
|
|
44
|
-
- `ingestStream`, `ingestFile` (`/file-ingest`): browser helpers piping a `ReadableStream<Uint8Array>` (e.g. `File.stream()`) through the Tokenizer + Writer, returning a Reader. Per-chunk `onProgress` callback.
|
|
44
|
+
- `ingestStream`, `ingestFile` (`/file-ingest`): browser helpers piping a `ReadableStream<Uint8Array>` (e.g. `File.stream()`) through the Tokenizer + Writer, returning a Reader. Per-chunk `onProgress` callback; its state object is reused across calls (mutated in place, zero per-chunk allocation) -- copy it if retained past the callback.
|
|
45
45
|
- `TokenizerError`, `WriterError`, `ReaderError`, `RangeReaderError`: thrown on parse/write/read errors with stable `code`.
|
|
46
46
|
- `VERSION` const per subpath.
|
|
47
47
|
|
package/package.json
CHANGED
package/src/FileIngest.js
CHANGED
|
@@ -27,7 +27,7 @@ import { PreserveWriter } from './PreserveWriter.js';
|
|
|
27
27
|
import { PreserveReader } from './PreserveReader.js';
|
|
28
28
|
import { checkOpts } from './Opts.js';
|
|
29
29
|
|
|
30
|
-
export const VERSION = '1.4.
|
|
30
|
+
export const VERSION = '1.4.1';
|
|
31
31
|
|
|
32
32
|
const U32_MAX = 4294967295;
|
|
33
33
|
const INGEST_OPTS = {
|
|
@@ -49,7 +49,12 @@ function raiseIngest(code, msg) { throw new WriterError(code, msg); }
|
|
|
49
49
|
// sampleBytes }. Preserve: { targetShardBytes, maxRecordBytes }.
|
|
50
50
|
// onProgress(state): called after each chunk with { bytesIngested,
|
|
51
51
|
// totalBytes, rowsWritten, shardsCommitted, chunkBytes,
|
|
52
|
-
// chunkCount, elapsedMs }
|
|
52
|
+
// chunkCount, elapsedMs }. The state object is REUSED
|
|
53
|
+
// across calls -- one object is mutated in place per chunk
|
|
54
|
+
// (zero per-chunk allocation), so it is valid only during
|
|
55
|
+
// the callback; copy it if you retain it past the call.
|
|
56
|
+
// Same ephemeral-view contract as the tokenizer's byte
|
|
57
|
+
// ranges.
|
|
53
58
|
// totalBytes: optional total for progress percentage. Pass file.size
|
|
54
59
|
// when ingesting a File; omit for indeterminate streams.
|
|
55
60
|
export async function ingestStream(readableStream, opts) {
|
|
@@ -73,10 +78,20 @@ export async function ingestStream(readableStream, opts) {
|
|
|
73
78
|
: new Tokenizer(writer, { framing });
|
|
74
79
|
|
|
75
80
|
const reader = readableStream.getReader();
|
|
76
|
-
const
|
|
81
|
+
const nowFn = (globalThis.performance && performance.now)
|
|
82
|
+
? () => performance.now() : () => Date.now();
|
|
83
|
+
const t0 = nowFn();
|
|
77
84
|
let bytesIngested = 0;
|
|
78
85
|
let chunkCount = 0;
|
|
79
86
|
|
|
87
|
+
// One progress object, hoisted and mutated per chunk -- the ingest loop
|
|
88
|
+
// allocates nothing per chunk. Documented as a reused ephemeral view above.
|
|
89
|
+
const progress = onProgress ? {
|
|
90
|
+
bytesIngested: 0, totalBytes,
|
|
91
|
+
rowsWritten: 0, shardsCommitted: 0,
|
|
92
|
+
chunkBytes: 0, chunkCount: 0, elapsedMs: 0,
|
|
93
|
+
} : null;
|
|
94
|
+
|
|
80
95
|
try {
|
|
81
96
|
while (true) {
|
|
82
97
|
const { value, done } = await reader.read();
|
|
@@ -87,16 +102,14 @@ export async function ingestStream(readableStream, opts) {
|
|
|
87
102
|
tokenizer.feed(value);
|
|
88
103
|
bytesIngested += value.length;
|
|
89
104
|
chunkCount++;
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
elapsedMs: now - t0,
|
|
99
|
-
});
|
|
105
|
+
if (progress) {
|
|
106
|
+
progress.bytesIngested = bytesIngested;
|
|
107
|
+
progress.rowsWritten = writer.totalRows;
|
|
108
|
+
progress.shardsCommitted = writer.shardCount;
|
|
109
|
+
progress.chunkBytes = value.length;
|
|
110
|
+
progress.chunkCount = chunkCount;
|
|
111
|
+
progress.elapsedMs = nowFn() - t0;
|
|
112
|
+
onProgress(progress);
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
} finally {
|
package/src/MultiReader.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// M_ROW_OUT_OF_RANGE - rowIdx >= totalRows
|
|
26
26
|
// M_TOO_MANY_ROWS - cumulative row count exceeds Number.MAX_SAFE_INTEGER
|
|
27
27
|
|
|
28
|
-
export const VERSION = '1.4.
|
|
28
|
+
export const VERSION = '1.4.1';
|
|
29
29
|
|
|
30
30
|
export class MultiReaderError extends Error {
|
|
31
31
|
constructor(code, msg) { super(msg); this.code = code; this.name = 'MultiReaderError'; }
|
package/src/PreserveReader.js
CHANGED
package/src/PreserveTokenizer.js
CHANGED
package/src/PreserveWriter.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { checkOpts } from './Opts.js';
|
|
20
20
|
|
|
21
|
-
export const VERSION = '1.4.
|
|
21
|
+
export const VERSION = '1.4.1';
|
|
22
22
|
|
|
23
23
|
export class PreserveWriterError extends Error {
|
|
24
24
|
constructor(code, msg) { super(msg); this.code = code; this.name = 'PreserveWriterError'; }
|
package/src/RangeReader.js
CHANGED
package/src/Reader.js
CHANGED
package/src/Split.js
CHANGED
|
@@ -37,7 +37,7 @@ import { Reader, ReaderError } from './Reader.js';
|
|
|
37
37
|
import { StringTable } from './StringTable.js';
|
|
38
38
|
import { checkOpts } from './Opts.js';
|
|
39
39
|
|
|
40
|
-
export const VERSION = '1.4.
|
|
40
|
+
export const VERSION = '1.4.1';
|
|
41
41
|
|
|
42
42
|
const LF = 0x0A;
|
|
43
43
|
const CONTAINER_HEADER_BYTES = 48;
|
package/src/StringTable.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// and at every reset(), so an absent U32 row cell (which is 0) decodes as ""
|
|
21
21
|
// rather than aliasing the shard's first-interned string (SPEC 3.3, SPEC 7).
|
|
22
22
|
|
|
23
|
-
export const VERSION = '1.4.
|
|
23
|
+
export const VERSION = '1.4.1';
|
|
24
24
|
|
|
25
25
|
const EMPTY_SLOT = 0xFFFFFFFF; // MUST be unsigned; typed-array reads are unsigned
|
|
26
26
|
const INITIAL_BLOB_BYTES = 64 * 1024;
|
package/src/Tokenizer.js
CHANGED
package/src/Writer.js
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
import { StringTable } from './StringTable.js';
|
|
44
44
|
import { checkOpts } from './Opts.js';
|
|
45
45
|
|
|
46
|
-
export const VERSION = '1.4.
|
|
46
|
+
export const VERSION = '1.4.1';
|
|
47
47
|
|
|
48
48
|
const U32_MAX = 4294967295;
|
|
49
49
|
// Post-finalize sentinel for _recordDepth. Chosen = 2 so every post-finalize
|
package/src/index.js
CHANGED
package/types/FileIngest.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ import type { WriterOptions } from './Writer.d.ts';
|
|
|
6
6
|
|
|
7
7
|
export const VERSION: string;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Progress state passed to onProgress. The SAME object is reused across every
|
|
11
|
+
* chunk callback (mutated in place, zero per-chunk allocation): it is valid only
|
|
12
|
+
* during the callback -- copy it if you retain it past the call.
|
|
13
|
+
*/
|
|
9
14
|
export interface IngestProgress {
|
|
10
15
|
bytesIngested: number;
|
|
11
16
|
totalBytes: number;
|