@elaraai/east 1.0.79 → 1.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/comparison.d.ts.map +1 -1
- package/dist/src/comparison.js +4 -5
- package/dist/src/comparison.js.map +1 -1
- package/dist/src/compile/builtins/array.d.ts +59 -0
- package/dist/src/compile/builtins/array.d.ts.map +1 -0
- package/dist/src/compile/builtins/array.js +656 -0
- package/dist/src/compile/builtins/array.js.map +1 -0
- package/dist/src/compile/builtins/blob.d.ts +18 -0
- package/dist/src/compile/builtins/blob.d.ts.map +1 -0
- package/dist/src/compile/builtins/blob.js +150 -0
- package/dist/src/compile/builtins/blob.js.map +1 -0
- package/dist/src/compile/builtins/compare.d.ts +18 -0
- package/dist/src/compile/builtins/compare.d.ts.map +1 -0
- package/dist/src/compile/builtins/compare.js +52 -0
- package/dist/src/compile/builtins/compare.js.map +1 -0
- package/dist/src/compile/builtins/datetime.d.ts +21 -0
- package/dist/src/compile/builtins/datetime.d.ts.map +1 -0
- package/dist/src/compile/builtins/datetime.js +46 -0
- package/dist/src/compile/builtins/datetime.js.map +1 -0
- package/dist/src/compile/builtins/dict.d.ts +46 -0
- package/dist/src/compile/builtins/dict.d.ts.map +1 -0
- package/dist/src/compile/builtins/dict.js +551 -0
- package/dist/src/compile/builtins/dict.js.map +1 -0
- package/dist/src/compile/builtins/index.d.ts +9 -0
- package/dist/src/compile/builtins/index.d.ts.map +1 -0
- package/dist/src/compile/builtins/index.js +28 -0
- package/dist/src/compile/builtins/index.js.map +1 -0
- package/dist/src/compile/builtins/matrix.d.ts +31 -0
- package/dist/src/compile/builtins/matrix.d.ts.map +1 -0
- package/dist/src/compile/builtins/matrix.js +310 -0
- package/dist/src/compile/builtins/matrix.js.map +1 -0
- package/dist/src/compile/builtins/primitives.d.ts +36 -0
- package/dist/src/compile/builtins/primitives.d.ts.map +1 -0
- package/dist/src/compile/builtins/primitives.js +67 -0
- package/dist/src/compile/builtins/primitives.js.map +1 -0
- package/dist/src/compile/builtins/ref.d.ts +11 -0
- package/dist/src/compile/builtins/ref.d.ts.map +1 -0
- package/dist/src/compile/builtins/ref.js +30 -0
- package/dist/src/compile/builtins/ref.js.map +1 -0
- package/dist/src/compile/builtins/set.d.ts +41 -0
- package/dist/src/compile/builtins/set.d.ts.map +1 -0
- package/dist/src/compile/builtins/set.js +385 -0
- package/dist/src/compile/builtins/set.js.map +1 -0
- package/dist/src/compile/builtins/sparse.d.ts +19 -0
- package/dist/src/compile/builtins/sparse.d.ts.map +1 -0
- package/dist/src/compile/builtins/sparse.js +143 -0
- package/dist/src/compile/builtins/sparse.js.map +1 -0
- package/dist/src/compile/builtins/string.d.ts +34 -0
- package/dist/src/compile/builtins/string.d.ts.map +1 -0
- package/dist/src/compile/builtins/string.js +238 -0
- package/dist/src/compile/builtins/string.js.map +1 -0
- package/dist/src/compile/builtins/vector.d.ts +44 -0
- package/dist/src/compile/builtins/vector.d.ts.map +1 -0
- package/dist/src/compile/builtins/vector.js +463 -0
- package/dist/src/compile/builtins/vector.js.map +1 -0
- package/dist/src/compile/constructors.d.ts +13 -0
- package/dist/src/compile/constructors.d.ts.map +1 -0
- package/dist/src/compile/constructors.js +179 -0
- package/dist/src/compile/constructors.js.map +1 -0
- package/dist/src/compile/control.d.ts +13 -0
- package/dist/src/compile/control.d.ts.map +1 -0
- package/dist/src/compile/control.js +513 -0
- package/dist/src/compile/control.js.map +1 -0
- package/dist/src/compile/functions.d.ts +13 -0
- package/dist/src/compile/functions.d.ts.map +1 -0
- package/dist/src/compile/functions.js +492 -0
- package/dist/src/compile/functions.js.map +1 -0
- package/dist/src/compile/ir.d.ts +19 -0
- package/dist/src/compile/ir.d.ts.map +1 -0
- package/dist/src/compile/ir.js +193 -0
- package/dist/src/compile/ir.js.map +1 -0
- package/dist/src/compile/runtime.d.ts +78 -0
- package/dist/src/compile/runtime.d.ts.map +1 -0
- package/dist/src/compile/runtime.js +111 -0
- package/dist/src/compile/runtime.js.map +1 -0
- package/dist/src/compile/type_parameters.d.ts +4 -0
- package/dist/src/compile/type_parameters.d.ts.map +1 -0
- package/dist/src/compile/type_parameters.js +55 -0
- package/dist/src/compile/type_parameters.js.map +1 -0
- package/dist/src/compile/typed_arrays.d.ts +28 -0
- package/dist/src/compile/typed_arrays.d.ts.map +1 -0
- package/dist/src/compile/typed_arrays.js +77 -0
- package/dist/src/compile/typed_arrays.js.map +1 -0
- package/dist/src/compile.d.ts +6 -41
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +8 -4415
- package/dist/src/compile.js.map +1 -1
- package/dist/src/compile.open_beast.spec.js +5 -4
- package/dist/src/compile.open_beast.spec.js.map +1 -1
- package/dist/src/containers/sortedmap.d.ts +2 -1
- package/dist/src/containers/sortedmap.d.ts.map +1 -1
- package/dist/src/containers/sortedmap.js +4 -2
- package/dist/src/containers/sortedmap.js.map +1 -1
- package/dist/src/containers/sortedset.d.ts +2 -1
- package/dist/src/containers/sortedset.d.ts.map +1 -1
- package/dist/src/containers/sortedset.js +4 -2
- package/dist/src/containers/sortedset.js.map +1 -1
- package/dist/src/error.d.ts +15 -0
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js +16 -0
- package/dist/src/error.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/patch/index.d.ts +1 -1
- package/dist/src/patch/index.d.ts.map +1 -1
- package/dist/src/patch/index.js +1 -1
- package/dist/src/patch/index.js.map +1 -1
- package/dist/src/patch/type_of_patch.d.ts +25 -1
- package/dist/src/patch/type_of_patch.d.ts.map +1 -1
- package/dist/src/patch/type_of_patch.js +33 -10
- package/dist/src/patch/type_of_patch.js.map +1 -1
- package/dist/src/platform.spec.js +19 -1
- package/dist/src/platform.spec.js.map +1 -1
- package/dist/src/runner_protocol.d.ts +316 -0
- package/dist/src/runner_protocol.d.ts.map +1 -0
- package/dist/src/runner_protocol.js +135 -0
- package/dist/src/runner_protocol.js.map +1 -0
- package/dist/src/serialization/beast2/index.d.ts +21 -5
- package/dist/src/serialization/beast2/index.d.ts.map +1 -1
- package/dist/src/serialization/beast2/index.js +12 -2
- package/dist/src/serialization/beast2/index.js.map +1 -1
- package/dist/src/serialization/beast2/index.spec.js +84 -3
- package/dist/src/serialization/beast2/index.spec.js.map +1 -1
- package/dist/src/serialization/beast2/shared.d.ts +32 -3
- package/dist/src/serialization/beast2/shared.d.ts.map +1 -1
- package/dist/src/serialization/beast2/shared.js +135 -10
- package/dist/src/serialization/beast2/shared.js.map +1 -1
- package/dist/src/serialization/beast2/v4/container.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v4/container.js +5 -3
- package/dist/src/serialization/beast2/v4/container.js.map +1 -1
- package/dist/src/serialization/beast2/v5/aliasing.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/aliasing.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/aliasing.spec.js +75 -0
- package/dist/src/serialization/beast2/v5/aliasing.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/boundary.d.ts +176 -0
- package/dist/src/serialization/beast2/v5/boundary.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/boundary.js +297 -0
- package/dist/src/serialization/beast2/v5/boundary.js.map +1 -0
- package/dist/src/serialization/beast2/v5/boundary.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/boundary.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/boundary.spec.js +479 -0
- package/dist/src/serialization/beast2/v5/boundary.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/codec.d.ts +6 -5
- package/dist/src/serialization/beast2/v5/codec.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/codec.js +18 -3
- package/dist/src/serialization/beast2/v5/codec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/deflate.d.ts +24 -2
- package/dist/src/serialization/beast2/v5/deflate.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/deflate.js +47 -12
- package/dist/src/serialization/beast2/v5/deflate.js.map +1 -1
- package/dist/src/serialization/beast2/v5/deflate.spec.js +15 -1
- package/dist/src/serialization/beast2/v5/deflate.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/elements.d.ts +64 -0
- package/dist/src/serialization/beast2/v5/elements.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/elements.js +320 -0
- package/dist/src/serialization/beast2/v5/elements.js.map +1 -0
- package/dist/src/serialization/beast2/v5/elements.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/elements.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/elements.spec.js +161 -0
- package/dist/src/serialization/beast2/v5/elements.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/frame-pool.d.ts +62 -21
- package/dist/src/serialization/beast2/v5/frame-pool.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/frame-pool.js +138 -27
- package/dist/src/serialization/beast2/v5/frame-pool.js.map +1 -1
- package/dist/src/serialization/beast2/v5/frame-pool.spec.js +198 -71
- package/dist/src/serialization/beast2/v5/frame-pool.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/frame-worker.js +25 -15
- package/dist/src/serialization/beast2/v5/frame-worker.js.map +1 -1
- package/dist/src/serialization/beast2/v5/frames.d.ts +22 -12
- package/dist/src/serialization/beast2/v5/frames.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/frames.js +52 -27
- package/dist/src/serialization/beast2/v5/frames.js.map +1 -1
- package/dist/src/serialization/beast2/v5/geometry.d.ts +38 -1
- package/dist/src/serialization/beast2/v5/geometry.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/geometry.js +73 -1
- package/dist/src/serialization/beast2/v5/geometry.js.map +1 -1
- package/dist/src/serialization/beast2/v5/geometry.spec.js +34 -23
- package/dist/src/serialization/beast2/v5/geometry.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/index.spec.js +32 -28
- package/dist/src/serialization/beast2/v5/index.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.d.ts +42 -1
- package/dist/src/serialization/beast2/v5/lazy.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.js +143 -28
- package/dist/src/serialization/beast2/v5/lazy.js.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.spec.js +102 -24
- package/dist/src/serialization/beast2/v5/lazy.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/manifest-writer.d.ts +110 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.js +118 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.js.map +1 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.spec.js +207 -0
- package/dist/src/serialization/beast2/v5/manifest-writer.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/manifest.d.ts +241 -0
- package/dist/src/serialization/beast2/v5/manifest.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/manifest.js +233 -0
- package/dist/src/serialization/beast2/v5/manifest.js.map +1 -0
- package/dist/src/serialization/beast2/v5/merge.d.ts +93 -0
- package/dist/src/serialization/beast2/v5/merge.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/merge.js +202 -0
- package/dist/src/serialization/beast2/v5/merge.js.map +1 -0
- package/dist/src/serialization/beast2/v5/merge.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/merge.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/merge.spec.js +118 -0
- package/dist/src/serialization/beast2/v5/merge.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/recut.d.ts +143 -0
- package/dist/src/serialization/beast2/v5/recut.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/recut.js +365 -0
- package/dist/src/serialization/beast2/v5/recut.js.map +1 -0
- package/dist/src/serialization/beast2/v5/recut.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/recut.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/recut.spec.js +215 -0
- package/dist/src/serialization/beast2/v5/recut.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/runs.d.ts +148 -0
- package/dist/src/serialization/beast2/v5/runs.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/runs.js +260 -0
- package/dist/src/serialization/beast2/v5/runs.js.map +1 -0
- package/dist/src/serialization/beast2/v5/runs.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/runs.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/runs.spec.js +182 -0
- package/dist/src/serialization/beast2/v5/runs.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/sha256.d.ts +19 -0
- package/dist/src/serialization/beast2/v5/sha256.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/sha256.js +103 -0
- package/dist/src/serialization/beast2/v5/sha256.js.map +1 -0
- package/dist/src/serialization/beast2/v5/sha256.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/sha256.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/sha256.spec.js +42 -0
- package/dist/src/serialization/beast2/v5/sha256.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/stream.d.ts +273 -86
- package/dist/src/serialization/beast2/v5/stream.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/stream.js +576 -244
- package/dist/src/serialization/beast2/v5/stream.js.map +1 -1
- package/dist/src/serialization/beast2/v5/sync-reader.spec.js +24 -10
- package/dist/src/serialization/beast2/v5/sync-reader.spec.js.map +1 -1
- package/dist/src/serialization/index.d.ts +1 -1
- package/dist/src/serialization/index.d.ts.map +1 -1
- package/dist/src/serialization/index.js +1 -1
- package/dist/src/serialization/index.js.map +1 -1
- package/dist/src/serialization/json.d.ts +28 -0
- package/dist/src/serialization/json.d.ts.map +1 -1
- package/dist/src/serialization/json.js +141 -30
- package/dist/src/serialization/json.js.map +1 -1
- package/dist/src/serialization/json.spec.js +47 -2
- package/dist/src/serialization/json.spec.js.map +1 -1
- package/dist/src/serialization/json_schema.d.ts +15 -11
- package/dist/src/serialization/json_schema.d.ts.map +1 -1
- package/dist/src/serialization/json_schema.js +21 -36
- package/dist/src/serialization/json_schema.js.map +1 -1
- package/dist/src/serialization/json_schema.spec.js +26 -32
- package/dist/src/serialization/json_schema.spec.js.map +1 -1
- package/dist/src/serialization/json_schema_to_type.d.ts +10 -5
- package/dist/src/serialization/json_schema_to_type.d.ts.map +1 -1
- package/dist/src/serialization/json_schema_to_type.js +13 -6
- package/dist/src/serialization/json_schema_to_type.js.map +1 -1
- package/dist/src/serialization/json_schema_to_type.spec.js +19 -0
- package/dist/src/serialization/json_schema_to_type.spec.js.map +1 -1
- package/dist/src/string_order.d.ts +36 -0
- package/dist/src/string_order.d.ts.map +1 -0
- package/dist/src/string_order.js +64 -0
- package/dist/src/string_order.js.map +1 -0
- package/dist/src/string_order.spec.d.ts +6 -0
- package/dist/src/string_order.spec.d.ts.map +1 -0
- package/dist/src/string_order.spec.js +79 -0
- package/dist/src/string_order.spec.js.map +1 -0
- package/dist/src/type_of_type.d.ts +17 -0
- package/dist/src/type_of_type.d.ts.map +1 -1
- package/dist/src/type_of_type.js +66 -0
- package/dist/src/type_of_type.js.map +1 -1
- package/dist/src/type_of_type.to_etv.spec.js +69 -1
- package/dist/src/type_of_type.to_etv.spec.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Beast2 v5 streaming and paging APIs.
|
|
7
7
|
*
|
|
8
|
+
* - {@link Beast2ElementWriter} — the canonical writer: elements in, segments
|
|
9
|
+
* out where the content-defined cut rule places them, so a collection's
|
|
10
|
+
* bytes are a function of its value alone — as one blob, or as the
|
|
11
|
+
* standalone segment blobs a manifest names. Every writer of a stored
|
|
12
|
+
* collection writes through it.
|
|
13
|
+
* - {@link encodeBeast2PagedFor} — one whole value through the element writer.
|
|
8
14
|
* - {@link Beast2Writer} — append-only streaming writer: each `write(batch)`
|
|
9
15
|
* becomes one root segment (one frame), so writer memory is O(batch), never
|
|
10
|
-
* the whole collection.
|
|
16
|
+
* the whole collection. The geometry is the caller's.
|
|
11
17
|
* - {@link encodeBeast2SegmentsFor} — in-memory convenience over the writer.
|
|
12
18
|
* - {@link iterBeast2SegmentsFor} — sequential segment iterator: yields one
|
|
13
19
|
* decoded collection per root segment with O(segment) decoded memory.
|
|
@@ -15,9 +21,10 @@
|
|
|
15
21
|
* `elementCount`, per-segment decode via footer + index seeks.
|
|
16
22
|
*
|
|
17
23
|
* Streaming roots are collections (Array/Set/Dict). Writers default to
|
|
18
|
-
* self-contained
|
|
19
|
-
* pageable and parallel-decodable
|
|
20
|
-
* whole-stream aliasing at the cost
|
|
24
|
+
* self-contained output — aliasing scoped per root element — so segments are
|
|
25
|
+
* pageable and parallel-decodable and an element's bytes depend on the element
|
|
26
|
+
* alone; pass `selfContained: false` to keep whole-stream aliasing at the cost
|
|
27
|
+
* of random access.
|
|
21
28
|
*/
|
|
22
29
|
import { EastTypeValueType, isTypeValueEqual } from "../../../type_of_type.js";
|
|
23
30
|
import { printFor } from "../../east.js";
|
|
@@ -29,9 +36,11 @@ import { SortedMap } from "../../../containers/sortedmap.js";
|
|
|
29
36
|
import { buildPlatformContext } from "../shared.js";
|
|
30
37
|
import { writeTypeSection, readTypeSection, asTypeValue } from "./type-section.js";
|
|
31
38
|
import { FrameReader, openFramePrefix, writeFrame } from "./frames.js";
|
|
32
|
-
import {
|
|
39
|
+
import { framePool } from "./frame-pool.js";
|
|
33
40
|
import { MAGIC_BYTES_V5, TAG_NEW, createV5EncodeContext, buildV5Encoder, buildV5Decoder, writeSourceMapSectionV5, readSourceMapSectionV5, writeIndexAndFooter, readIndex, isSegmentedRoot, } from "./codec.js";
|
|
34
41
|
import { TAG_OR_TERMINATOR_FRAME, bytesReader, isBeast2SyncRangeReader, readExact, readU64LE, readBeast2ExtentsSync } from "./range.js";
|
|
42
|
+
import { SegmentCutter, decodeBeast2FenceFor } from "./boundary.js";
|
|
43
|
+
import { isBeast2ManifestSource } from "./manifest.js";
|
|
35
44
|
/** Validates that a root type is streamable and returns its kind. */
|
|
36
45
|
function checkSegmented(typeValue) {
|
|
37
46
|
if (!isSegmentedRoot(typeValue)) {
|
|
@@ -50,9 +59,120 @@ function orderCmpFor(typeValue, kind) {
|
|
|
50
59
|
* frame pool: workers cost tens of milliseconds to start, and a value this
|
|
51
60
|
* small deflates faster than that on one thread. */
|
|
52
61
|
const POOL_MIN_LOGICAL_BYTES = 8 * 1024 * 1024;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// Framing
|
|
64
|
+
// =============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Frames segments' logical bytes — inline, or on the frame pool — and lands
|
|
67
|
+
* each frame in the order its segment was submitted, with what the submitter
|
|
68
|
+
* said about it. Both writers frame through one of these.
|
|
69
|
+
*/
|
|
70
|
+
class FramePipeline {
|
|
71
|
+
codec;
|
|
72
|
+
parallel;
|
|
73
|
+
land;
|
|
74
|
+
/** `undefined` until decided; `null` when frames are written inline. */
|
|
75
|
+
pool;
|
|
76
|
+
logicalWritten = 0;
|
|
77
|
+
/** Frames submitted to the pool and not yet landed, in order. */
|
|
78
|
+
inflight = [];
|
|
79
|
+
constructor(codec, parallel, land) {
|
|
80
|
+
this.codec = codec;
|
|
81
|
+
this.parallel = parallel;
|
|
82
|
+
this.land = land;
|
|
83
|
+
}
|
|
84
|
+
/** Frames one segment, or hands it to the pool; its frame lands once every
|
|
85
|
+
* frame submitted before it has. */
|
|
86
|
+
submit(logical, info) {
|
|
87
|
+
this.logicalWritten += logical.length;
|
|
88
|
+
const pool = this.poolFor();
|
|
89
|
+
if (pool === null) {
|
|
90
|
+
const frame = new BufferWriter();
|
|
91
|
+
writeFrame(frame, logical, this.codec);
|
|
92
|
+
this.land(frame.toUint8Array(), info);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Back-pressure: at most two frames per worker in flight, so the
|
|
96
|
+
// writer's memory stays O(workers x segment).
|
|
97
|
+
while (this.inflight.length >= pool.workers * 2)
|
|
98
|
+
this.appendFrames(1);
|
|
99
|
+
this.inflight.push({ frame: pool.submit(logical, this.codec), info });
|
|
100
|
+
this.appendFrames(0);
|
|
101
|
+
}
|
|
102
|
+
/** Lands every in-flight frame. */
|
|
103
|
+
settle() {
|
|
104
|
+
this.appendFrames(Infinity);
|
|
105
|
+
}
|
|
106
|
+
/** The pool to frame on — decided once a parallel writer has produced
|
|
107
|
+
* enough bytes to be worth it. While frames are in flight the writer keeps
|
|
108
|
+
* the pool they are on, so frames cannot interleave out of order; with none
|
|
109
|
+
* in flight it follows the process's current pool — a new one after an idle
|
|
110
|
+
* pool retired its workers, `null` once a pool has lost a worker — so a
|
|
111
|
+
* writer never submits to terminated workers. */
|
|
112
|
+
poolFor() {
|
|
113
|
+
if (this.pool === undefined) {
|
|
114
|
+
if (!this.parallel || this.logicalWritten < POOL_MIN_LOGICAL_BYTES)
|
|
115
|
+
return null;
|
|
116
|
+
this.pool = framePool();
|
|
117
|
+
}
|
|
118
|
+
else if (this.pool !== null && this.inflight.length === 0) {
|
|
119
|
+
this.pool = framePool();
|
|
120
|
+
}
|
|
121
|
+
return this.pool;
|
|
122
|
+
}
|
|
123
|
+
/** Lands the done frames at the head of the in-flight queue, in order;
|
|
124
|
+
* blocks until at least `minimum` have landed (`Infinity` settles the
|
|
125
|
+
* queue). */
|
|
126
|
+
appendFrames(minimum) {
|
|
127
|
+
let appended = 0;
|
|
128
|
+
while (this.inflight.length > 0) {
|
|
129
|
+
const head = this.inflight[0];
|
|
130
|
+
if (appended >= minimum && !head.frame.ready())
|
|
131
|
+
return;
|
|
132
|
+
const bytes = head.frame.take();
|
|
133
|
+
this.inflight.shift();
|
|
134
|
+
this.land(bytes, head.info);
|
|
135
|
+
appended++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The header a collection blob of `typeValue` starts with: magic, type
|
|
141
|
+
* section, source-map section, then the root tag as its own frame so every
|
|
142
|
+
* indexed segment frame is pure. A caller-provided prefix (splice tooling) is
|
|
143
|
+
* the header instead — after verifying its wire type IS the declared type,
|
|
144
|
+
* since a mismatched prefix would write a blob whose header lies about its
|
|
145
|
+
* contents.
|
|
146
|
+
*/
|
|
147
|
+
function headerFor(typeValue, sourceMap, headerPrefix) {
|
|
148
|
+
if (headerPrefix !== undefined) {
|
|
149
|
+
verifyV5Magic(headerPrefix);
|
|
150
|
+
const prefixReader = new BufferReader(headerPrefix, MAGIC_BYTES_V5.length);
|
|
151
|
+
const { rootType } = readTypeSection(prefixReader);
|
|
152
|
+
if (!isTypeValueEqual(rootType, typeValue)) {
|
|
153
|
+
const printType = printFor(EastTypeValueType);
|
|
154
|
+
throw new TypeError(`beast2 v5: headerPrefix declares wire type ${printType(rootType)}, not the writer's ${printType(typeValue)} — the prefix must come from a blob of the same wire type`);
|
|
155
|
+
}
|
|
156
|
+
return headerPrefix;
|
|
157
|
+
}
|
|
158
|
+
const head = new BufferWriter();
|
|
159
|
+
head.writeBytes(MAGIC_BYTES_V5);
|
|
160
|
+
writeTypeSection(typeValue, head);
|
|
161
|
+
writeSourceMapSectionV5(sourceMap, head);
|
|
162
|
+
writeFrame(head, new Uint8Array([TAG_NEW]), "none");
|
|
163
|
+
return head.toUint8Array();
|
|
164
|
+
}
|
|
165
|
+
/** A segment as a standalone blob: the collection's header, the segment's
|
|
166
|
+
* frame, the terminator, and an index naming the one segment — byte for byte
|
|
167
|
+
* what carving it out of the whole blob gives. */
|
|
168
|
+
function segmentBlob(head, frame, count) {
|
|
169
|
+
const blob = new BufferWriter(head.length + frame.length + TAG_OR_TERMINATOR_FRAME.length + 32);
|
|
170
|
+
blob.writeBytes(head);
|
|
171
|
+
blob.writeBytes(frame);
|
|
172
|
+
blob.writeBytes(TAG_OR_TERMINATOR_FRAME);
|
|
173
|
+
writeIndexAndFooter(blob, [{ offset: head.length, count }], true);
|
|
174
|
+
return blob.toUint8Array();
|
|
175
|
+
}
|
|
56
176
|
// =============================================================================
|
|
57
177
|
// Streaming writer
|
|
58
178
|
// =============================================================================
|
|
@@ -66,6 +186,10 @@ const POOL_DEMOTE_MIN_SEGMENTS = 8;
|
|
|
66
186
|
* {@link finish} appends the terminator (plus index and footer by default),
|
|
67
187
|
* so the byte stream is append-only end to end.
|
|
68
188
|
*
|
|
189
|
+
* Where the segments fall is the caller's choice here. A collection that is
|
|
190
|
+
* stored is written through {@link Beast2ElementWriter} instead, which cuts
|
|
191
|
+
* where the content-defined rule says, so equal values store as equal bytes.
|
|
192
|
+
*
|
|
69
193
|
* @example
|
|
70
194
|
* ```ts
|
|
71
195
|
* const chunks: Uint8Array[] = [];
|
|
@@ -82,7 +206,6 @@ export class Beast2Writer {
|
|
|
82
206
|
segments = 0;
|
|
83
207
|
kind;
|
|
84
208
|
sink;
|
|
85
|
-
codec;
|
|
86
209
|
selfContained;
|
|
87
210
|
withIndex;
|
|
88
211
|
ctx;
|
|
@@ -93,15 +216,9 @@ export class Beast2Writer {
|
|
|
93
216
|
hasLast = false;
|
|
94
217
|
bytesWritten = 0;
|
|
95
218
|
finished = false;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
logicalWritten = 0;
|
|
100
|
-
/** Frames submitted to the pool and not yet on the sink, in order. */
|
|
101
|
-
inflight = [];
|
|
102
|
-
inflightLogical = 0;
|
|
103
|
-
/** {@link settle} calls that found frames in flight. */
|
|
104
|
-
settlesInflight = 0;
|
|
219
|
+
/** Frames each segment and puts it on the sink as it lands, recording its
|
|
220
|
+
* index entry there. */
|
|
221
|
+
frames;
|
|
105
222
|
/**
|
|
106
223
|
* @param type - the collection type this stream holds (Array/Set/Dict)
|
|
107
224
|
* @param sink - receives output bytes as they are produced
|
|
@@ -113,19 +230,29 @@ export class Beast2Writer {
|
|
|
113
230
|
const typeValue = asTypeValue(type);
|
|
114
231
|
this.kind = checkSegmented(typeValue);
|
|
115
232
|
this.sink = sink;
|
|
116
|
-
this.codec = options?.codec ?? "deflate";
|
|
117
233
|
this.selfContained = options?.selfContained ?? true;
|
|
118
234
|
this.withIndex = options?.index ?? true;
|
|
119
|
-
this.parallel = options?.parallel ?? false;
|
|
120
235
|
this.orderCmp = orderCmpFor(typeValue, this.kind);
|
|
236
|
+
this.frames = new FramePipeline(options?.codec ?? "deflate", options?.parallel ?? false, (frame, count) => {
|
|
237
|
+
this.index.push({ offset: this.bytesWritten, count });
|
|
238
|
+
this.emit(frame);
|
|
239
|
+
});
|
|
121
240
|
const sourceMap = options?.sourceMap ?? null;
|
|
122
241
|
this.ctx = createV5EncodeContext(sourceMap, this.selfContained);
|
|
242
|
+
// A self-contained stream starts every root element with an empty identity
|
|
243
|
+
// map, so no REF reaches past the element it sits in: sharing inside an
|
|
244
|
+
// element is kept, sharing between elements is written out again.
|
|
245
|
+
const scoped = this.selfContained;
|
|
123
246
|
const typeCtx = new Map();
|
|
124
247
|
if (this.kind === "Dict") {
|
|
125
248
|
const key = buildV5Encoder(typeValue.value.key, typeCtx);
|
|
126
249
|
const val = buildV5Encoder(typeValue.value.value, typeCtx);
|
|
127
250
|
this.encodeElems = (value, logical) => {
|
|
128
251
|
for (const [k, v] of value) {
|
|
252
|
+
if (scoped) {
|
|
253
|
+
this.ctx.containerIndex.clear();
|
|
254
|
+
this.ctx.segmentBaseDef = this.ctx.containerCount;
|
|
255
|
+
}
|
|
129
256
|
key(k, logical, this.ctx);
|
|
130
257
|
val(v, logical, this.ctx);
|
|
131
258
|
}
|
|
@@ -134,39 +261,22 @@ export class Beast2Writer {
|
|
|
134
261
|
else {
|
|
135
262
|
const elem = buildV5Encoder(typeValue.value, typeCtx);
|
|
136
263
|
this.encodeElems = (value, logical) => {
|
|
137
|
-
for (const item of value)
|
|
264
|
+
for (const item of value) {
|
|
265
|
+
if (scoped) {
|
|
266
|
+
this.ctx.containerIndex.clear();
|
|
267
|
+
this.ctx.segmentBaseDef = this.ctx.containerCount;
|
|
268
|
+
}
|
|
138
269
|
elem(item, logical, this.ctx);
|
|
270
|
+
}
|
|
139
271
|
};
|
|
140
272
|
}
|
|
141
|
-
|
|
142
|
-
// as its own frame so every indexed segment frame is pure. A caller-
|
|
143
|
-
// provided prefix (splice tooling) is emitted verbatim instead — after
|
|
144
|
-
// verifying its wire type IS the declared type, since a mismatched
|
|
145
|
-
// prefix would write a blob whose header lies about its contents.
|
|
146
|
-
if (options?.headerPrefix !== undefined) {
|
|
147
|
-
verifyV5Magic(options.headerPrefix);
|
|
148
|
-
const prefixReader = new BufferReader(options.headerPrefix, MAGIC_BYTES_V5.length);
|
|
149
|
-
const { rootType } = readTypeSection(prefixReader);
|
|
150
|
-
if (!isTypeValueEqual(rootType, typeValue)) {
|
|
151
|
-
const printType = printFor(EastTypeValueType);
|
|
152
|
-
throw new TypeError(`beast2 v5: headerPrefix declares wire type ${printType(rootType)}, not the writer's ${printType(typeValue)} — the prefix must come from a blob of the same wire type`);
|
|
153
|
-
}
|
|
154
|
-
this.ctx.containerCount = 1;
|
|
155
|
-
this.ctx.segmentBaseDef = 1;
|
|
156
|
-
this.emit(options.headerPrefix);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const head = new BufferWriter();
|
|
160
|
-
head.writeBytes(MAGIC_BYTES_V5);
|
|
161
|
-
writeTypeSection(typeValue, head);
|
|
162
|
-
writeSourceMapSectionV5(sourceMap, head);
|
|
163
|
-
writeFrame(head, new Uint8Array([TAG_NEW]), "none");
|
|
273
|
+
const head = headerFor(typeValue, sourceMap, options?.headerPrefix);
|
|
164
274
|
// The root container consumes definition 0 (no root object exists on the
|
|
165
275
|
// encode side — batches are independent values, so nothing can alias it);
|
|
166
276
|
// segments scope from definition 1 to match the decoder's numbering.
|
|
167
277
|
this.ctx.containerCount = 1;
|
|
168
278
|
this.ctx.segmentBaseDef = 1;
|
|
169
|
-
this.emit(head
|
|
279
|
+
this.emit(head);
|
|
170
280
|
}
|
|
171
281
|
/**
|
|
172
282
|
* Encodes one batch as one root segment.
|
|
@@ -195,106 +305,53 @@ export class Beast2Writer {
|
|
|
195
305
|
return;
|
|
196
306
|
if (this.orderCmp)
|
|
197
307
|
this.checkAscent(batch);
|
|
198
|
-
if (this.selfContained) {
|
|
199
|
-
this.ctx.containerIndex.clear();
|
|
200
|
-
this.ctx.segmentBaseDef = this.ctx.containerCount;
|
|
201
|
-
}
|
|
202
308
|
const logical = new BufferWriter();
|
|
203
309
|
logical.writeVarint(count);
|
|
204
310
|
this.encodeElems(batch, logical);
|
|
205
|
-
|
|
206
|
-
this.segments++;
|
|
207
|
-
this.logicalWritten += logicalBytes.length;
|
|
208
|
-
const pool = this.poolFor();
|
|
209
|
-
if (pool === null) {
|
|
210
|
-
const frame = new BufferWriter();
|
|
211
|
-
writeFrame(frame, logicalBytes, this.codec);
|
|
212
|
-
this.index.push({ offset: this.bytesWritten, count });
|
|
213
|
-
this.emit(frame.toUint8Array());
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
// A caller whose batch decisions keep landing between the bounds settles
|
|
217
|
-
// on nearly every segment, which makes the pool pure overhead. Framing
|
|
218
|
-
// strategy never changes a byte, so demote: drain in order, then frame
|
|
219
|
-
// inline for the rest of the stream.
|
|
220
|
-
if (this.segments >= POOL_DEMOTE_MIN_SEGMENTS && this.settlesInflight * 2 >= this.segments) {
|
|
221
|
-
this.appendFrames(Infinity);
|
|
222
|
-
this.pool = null;
|
|
223
|
-
const frame = new BufferWriter();
|
|
224
|
-
writeFrame(frame, logicalBytes, this.codec);
|
|
225
|
-
this.index.push({ offset: this.bytesWritten, count });
|
|
226
|
-
this.emit(frame.toUint8Array());
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
// Back-pressure: at most two frames per worker in flight, so the
|
|
230
|
-
// writer's memory stays O(workers x segment).
|
|
231
|
-
while (this.inflight.length >= pool.workers * 2)
|
|
232
|
-
this.appendFrames(1);
|
|
233
|
-
this.inflight.push({ frame: pool.submit(logicalBytes, this.codec), count });
|
|
234
|
-
this.inflightLogical += logicalBytes.length;
|
|
235
|
-
this.appendFrames(0);
|
|
311
|
+
this.frameSegment(count, logical.toUint8Array());
|
|
236
312
|
}
|
|
237
313
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
314
|
+
* Writes one root segment from elements that are already encoded — each in
|
|
315
|
+
* its canonical bytes, with aliasing scoped to itself — which is how
|
|
316
|
+
* {@link Beast2ElementWriter} writes the segments it cuts.
|
|
240
317
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* decision that is monotone in the byte count — the paged encoders' batch
|
|
245
|
-
* refinement — is exact when it agrees at both bounds; otherwise
|
|
246
|
-
* {@link settle} first.
|
|
318
|
+
* The elements are copied before this returns, so the caller may reuse the
|
|
319
|
+
* buffer. Their order is the caller's to keep: nothing here decodes them to
|
|
320
|
+
* check a Set or Dict's ascent.
|
|
247
321
|
*
|
|
248
|
-
* @
|
|
322
|
+
* @param count - the elements (pairs, for a Dict) the bytes hold; a
|
|
323
|
+
* zero-count segment is skipped
|
|
324
|
+
* @param elements - the elements' bytes, back to back
|
|
325
|
+
* @throws {Error} When called after {@link finish}, or — for a parallel
|
|
326
|
+
* writer — when a frame worker failed (see {@link write}).
|
|
249
327
|
*/
|
|
250
|
-
|
|
251
|
-
this.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
settle() {
|
|
260
|
-
if (this.inflight.length > 0)
|
|
261
|
-
this.settlesInflight++;
|
|
262
|
-
this.appendFrames(Infinity);
|
|
328
|
+
writeEncodedSegment(count, elements) {
|
|
329
|
+
if (this.finished)
|
|
330
|
+
throw new Error("write() after finish()");
|
|
331
|
+
if (count === 0)
|
|
332
|
+
return;
|
|
333
|
+
const logical = new BufferWriter(elements.length + 10);
|
|
334
|
+
logical.writeVarint(count);
|
|
335
|
+
logical.writeBytes(elements);
|
|
336
|
+
this.frameSegment(count, logical.toUint8Array());
|
|
263
337
|
}
|
|
264
|
-
/**
|
|
265
|
-
*
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
* writer never submits to terminated workers. A writer demoted to inline
|
|
270
|
-
* framing stays inline. */
|
|
271
|
-
poolFor() {
|
|
272
|
-
if (this.pool === undefined) {
|
|
273
|
-
if (!this.parallel || this.logicalWritten < POOL_MIN_LOGICAL_BYTES)
|
|
274
|
-
return null;
|
|
275
|
-
this.pool = framePool();
|
|
276
|
-
}
|
|
277
|
-
else if (this.pool !== null && this.inflight.length === 0) {
|
|
278
|
-
this.pool = framePool();
|
|
279
|
-
}
|
|
280
|
-
return this.pool;
|
|
338
|
+
/** Frames one segment's logical bytes — inline, or on the frame pool — and
|
|
339
|
+
* records its index entry as its frame lands. */
|
|
340
|
+
frameSegment(count, logicalBytes) {
|
|
341
|
+
this.segments++;
|
|
342
|
+
this.frames.submit(logicalBytes, count);
|
|
281
343
|
}
|
|
282
|
-
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this.inflightLogical -= head.frame.logicalLength;
|
|
294
|
-
this.index.push({ offset: this.bytesWritten, count: head.count });
|
|
295
|
-
this.emit(bytes);
|
|
296
|
-
appended++;
|
|
297
|
-
}
|
|
344
|
+
/**
|
|
345
|
+
* Waits for every in-flight frame and passes it to the sink, so the sink
|
|
346
|
+
* holds every segment written so far. A writer about to go quiet then holds
|
|
347
|
+
* nothing on the pool, which lets an idle pool retire its workers. A no-op
|
|
348
|
+
* for a serial writer.
|
|
349
|
+
*
|
|
350
|
+
* @throws {Error} For a parallel writer, when an in-flight frame's worker
|
|
351
|
+
* failed or stopped responding (see {@link write}).
|
|
352
|
+
*/
|
|
353
|
+
settle() {
|
|
354
|
+
this.frames.settle();
|
|
298
355
|
}
|
|
299
356
|
/**
|
|
300
357
|
* Terminates the stream: writes the terminator frame and, unless disabled,
|
|
@@ -306,7 +363,7 @@ export class Beast2Writer {
|
|
|
306
363
|
finish() {
|
|
307
364
|
if (this.finished)
|
|
308
365
|
return;
|
|
309
|
-
this.
|
|
366
|
+
this.frames.settle();
|
|
310
367
|
this.finished = true;
|
|
311
368
|
const tail = new BufferWriter();
|
|
312
369
|
writeFrame(tail, new Uint8Array([0x00]), "none");
|
|
@@ -386,30 +443,291 @@ export function encodeBeast2SegmentsFor(type, options) {
|
|
|
386
443
|
return out;
|
|
387
444
|
};
|
|
388
445
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Builds an encoder of a collection's root elements, one at a time, each in
|
|
448
|
+
* its canonical bytes: aliasing is scoped to the element, so no REF reaches a
|
|
449
|
+
* neighbour and the element's bytes depend on it alone.
|
|
450
|
+
*
|
|
451
|
+
* @param typeValue - the collection type (Array/Set/Dict)
|
|
452
|
+
* @param sourceMap - the stream's source map, for function values
|
|
453
|
+
* @returns a function appending one element's bytes to a writer and returning
|
|
454
|
+
* the length of its key: a Set element's whole length, a Dict pair's key, 0
|
|
455
|
+
* for an Array element, which has none
|
|
456
|
+
* @throws {TypeError} When `typeValue` is not an Array, Set or Dict type.
|
|
457
|
+
* @internal
|
|
458
|
+
*/
|
|
459
|
+
export function elementEncoderFor(typeValue, sourceMap) {
|
|
460
|
+
const kind = checkSegmented(typeValue);
|
|
461
|
+
const ctx = createV5EncodeContext(sourceMap, true);
|
|
462
|
+
// The root container is definition 0, so elements define from 1.
|
|
463
|
+
ctx.containerCount = 1;
|
|
464
|
+
const typeCtx = new Map();
|
|
465
|
+
if (kind === "Dict") {
|
|
466
|
+
const key = buildV5Encoder(typeValue.value.key, typeCtx);
|
|
467
|
+
const value = buildV5Encoder(typeValue.value.value, typeCtx);
|
|
468
|
+
return (element, writer) => {
|
|
469
|
+
ctx.containerIndex.clear();
|
|
470
|
+
ctx.segmentBaseDef = ctx.containerCount;
|
|
471
|
+
const start = writer.size;
|
|
472
|
+
key(element[0], writer, ctx);
|
|
473
|
+
const keyLength = writer.size - start;
|
|
474
|
+
value(element[1], writer, ctx);
|
|
475
|
+
return keyLength;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const elem = buildV5Encoder(typeValue.value, typeCtx);
|
|
479
|
+
const keyed = kind === "Set";
|
|
480
|
+
return (element, writer) => {
|
|
481
|
+
ctx.containerIndex.clear();
|
|
482
|
+
ctx.segmentBaseDef = ctx.containerCount;
|
|
483
|
+
const start = writer.size;
|
|
484
|
+
elem(element, writer, ctx);
|
|
485
|
+
return keyed ? writer.size - start : 0;
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* The canonical writer of a collection: elements go in one at a time, in
|
|
490
|
+
* canonical order, and segments come out wherever the content-defined cut rule
|
|
491
|
+
* ({@link SegmentCutter}) places them — as one blob, or as the standalone
|
|
492
|
+
* segment blobs a manifest names, one at a time to a {@link Beast2SegmentSink}.
|
|
493
|
+
*
|
|
494
|
+
* Each element is encoded as it arrives, with aliasing scoped to itself, so
|
|
495
|
+
* its bytes depend on the element alone — and the cut rule reads nothing but
|
|
496
|
+
* those bytes. The blob is therefore a function of the value: whichever
|
|
497
|
+
* process writes a collection, in whichever runtime, and however its elements
|
|
498
|
+
* were produced, it writes the same bytes. That is what lets a
|
|
499
|
+
* content-addressed store keep an equal value once and diff two values by
|
|
500
|
+
* segment.
|
|
501
|
+
*
|
|
502
|
+
* Memory is one open segment. Elements that are already encoded — sorted runs,
|
|
503
|
+
* a merge, a re-cut — go in through {@link addEncoded}, without a decode.
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```ts
|
|
507
|
+
* const chunks: Uint8Array[] = [];
|
|
508
|
+
* const type = DictType(StringType, IntegerType);
|
|
509
|
+
* const writer = new Beast2ElementWriter(type, (b) => chunks.push(b));
|
|
510
|
+
* writer.add(["a", 1n]);
|
|
511
|
+
* writer.add(["b", 2n]);
|
|
512
|
+
* writer.finish();
|
|
513
|
+
* decodeBeast2For(type)(Buffer.concat(chunks)); // Map { "a" => 1n, "b" => 2n }
|
|
514
|
+
*
|
|
515
|
+
* const segments: Beast2Segment[] = [];
|
|
516
|
+
* const split = new Beast2ElementWriter(type, { segment: (s) => segments.push(s) });
|
|
517
|
+
* split.add(["a", 1n]);
|
|
518
|
+
* split.finish();
|
|
519
|
+
* segments[0].blob; // segment 0 as a standalone blob, under split.header
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
export class Beast2ElementWriter {
|
|
523
|
+
kind;
|
|
524
|
+
/** The writer of the one blob, when that is the output. */
|
|
525
|
+
blob;
|
|
526
|
+
/** The framing of the segment blobs, when those are the output. */
|
|
527
|
+
frames;
|
|
528
|
+
head;
|
|
529
|
+
encodeElement;
|
|
530
|
+
orderCmp;
|
|
531
|
+
cutter = new SegmentCutter();
|
|
532
|
+
/** The open segment's elements, back to back. */
|
|
533
|
+
open = new BufferWriter();
|
|
534
|
+
/** Elements in the open segment. */
|
|
535
|
+
count = 0;
|
|
536
|
+
/** The length of the open segment's first key — its fence. */
|
|
537
|
+
firstKeyLength = 0;
|
|
538
|
+
written = 0;
|
|
539
|
+
lastKey;
|
|
540
|
+
hasLast = false;
|
|
541
|
+
finished = false;
|
|
542
|
+
/**
|
|
543
|
+
* @param type - the collection type (Array/Set/Dict)
|
|
544
|
+
* @param sink - receives the blob's bytes as they are produced; or, as a
|
|
545
|
+
* {@link Beast2SegmentSink}, each segment as a standalone blob
|
|
546
|
+
* @param options - codec, source map, header prefix and parallel framing
|
|
547
|
+
* @throws {TypeError} When `type` is not an Array, Set or Dict type, or
|
|
548
|
+
* when `options.headerPrefix` is not a v5 header of exactly `type`.
|
|
549
|
+
*/
|
|
550
|
+
constructor(type, sink, options) {
|
|
551
|
+
const typeValue = asTypeValue(type);
|
|
552
|
+
this.kind = checkSegmented(typeValue);
|
|
553
|
+
this.orderCmp = orderCmpFor(typeValue, this.kind);
|
|
554
|
+
this.encodeElement = elementEncoderFor(typeValue, options?.sourceMap ?? null);
|
|
555
|
+
const head = headerFor(typeValue, options?.sourceMap ?? null, options?.headerPrefix);
|
|
556
|
+
this.head = head;
|
|
557
|
+
if (typeof sink === "function") {
|
|
558
|
+
this.blob = new Beast2Writer(typeValue, sink, options);
|
|
559
|
+
this.frames = null;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
this.blob = null;
|
|
563
|
+
this.frames = new FramePipeline(options?.codec ?? "deflate", options?.parallel ?? false, (frame, facts) => sink.segment({ ...facts, blob: segmentBlob(head, frame, facts.count) }));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/** Segments written so far; the open segment is not among them until the
|
|
567
|
+
* cut rule closes it or {@link finish} does. */
|
|
568
|
+
get segments() {
|
|
569
|
+
return this.written;
|
|
570
|
+
}
|
|
571
|
+
/** The header every segment is written under — magic, type section,
|
|
572
|
+
* source-map section and root tag — which a manifest names as its header. */
|
|
573
|
+
get header() {
|
|
574
|
+
return this.head;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Encodes one element and appends it to the collection.
|
|
578
|
+
*
|
|
579
|
+
* @param element - an Array or Set element, or a Dict's `[key, value]` pair
|
|
580
|
+
* @throws {Error} When called after {@link finish}, when a Set element or
|
|
581
|
+
* Dict key does not ascend strictly from the last in East order, or when
|
|
582
|
+
* the element cannot be encoded — which leaves the writer as it was.
|
|
583
|
+
*/
|
|
584
|
+
add(element) {
|
|
585
|
+
if (this.finished)
|
|
586
|
+
throw new Error("add() after finish()");
|
|
587
|
+
let key;
|
|
588
|
+
if (this.orderCmp !== null) {
|
|
589
|
+
key = this.kind === "Dict" ? element[0] : element;
|
|
590
|
+
if (this.hasLast && this.orderCmp(this.lastKey, key) >= 0) {
|
|
591
|
+
throw new Error(`beast2 v5: ${this.kind} ${this.kind === "Dict" ? "keys" : "elements"} must arrive strictly ascending in East order — ` +
|
|
592
|
+
`the blob holds the canonical value; sort them first, or write arrival order as an Array`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const start = this.open.size;
|
|
596
|
+
let keyLength;
|
|
597
|
+
try {
|
|
598
|
+
keyLength = this.encodeElement(element, this.open);
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
const kept = this.open.toUint8Array().slice(0, start);
|
|
602
|
+
this.open.pop();
|
|
603
|
+
this.open.writeBytes(kept);
|
|
604
|
+
throw err;
|
|
605
|
+
}
|
|
606
|
+
if (this.orderCmp !== null) {
|
|
607
|
+
this.lastKey = key;
|
|
608
|
+
this.hasLast = true;
|
|
609
|
+
}
|
|
610
|
+
this.place(start, keyLength);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Appends one element that is already in its canonical bytes — encoded
|
|
614
|
+
* with aliasing scoped to itself, as {@link add} encodes one — without
|
|
615
|
+
* decoding it.
|
|
616
|
+
*
|
|
617
|
+
* Order is the caller's to keep: a Set or Dict's elements must arrive
|
|
618
|
+
* strictly ascending, and nothing here decodes them to check.
|
|
619
|
+
*
|
|
620
|
+
* @param element - the element's canonical bytes (a Dict pair's key, then
|
|
621
|
+
* its value)
|
|
622
|
+
* @param keyLength - the length of the key at the front of `element`: a Set
|
|
623
|
+
* element's whole length, a Dict pair's key; ignored for an Array
|
|
624
|
+
* @throws {Error} When called after {@link finish}.
|
|
625
|
+
*/
|
|
626
|
+
addEncoded(element, keyLength) {
|
|
627
|
+
if (this.finished)
|
|
628
|
+
throw new Error("add() after finish()");
|
|
629
|
+
const start = this.open.size;
|
|
630
|
+
this.open.writeBytes(element);
|
|
631
|
+
this.place(start, keyLength);
|
|
632
|
+
}
|
|
633
|
+
/** Accounts for the element just appended at `start`, and when the cut rule
|
|
634
|
+
* starts a segment at it, writes out the segment that closes. */
|
|
635
|
+
place(start, keyLength) {
|
|
636
|
+
const bytes = this.open.toUint8Array();
|
|
637
|
+
const element = bytes.subarray(start);
|
|
638
|
+
// An Array element has no key, so the rule hashes it whole.
|
|
639
|
+
const hashed = this.kind === "Array" ? element : element.subarray(0, keyLength);
|
|
640
|
+
if (this.cutter.startsSegment(element.length, hashed)) {
|
|
641
|
+
this.writeSegment(bytes.subarray(0, start));
|
|
642
|
+
const carried = element.slice();
|
|
643
|
+
this.open.pop();
|
|
644
|
+
this.open.writeBytes(carried);
|
|
645
|
+
this.count = 0;
|
|
646
|
+
}
|
|
647
|
+
if (this.count === 0)
|
|
648
|
+
this.firstKeyLength = keyLength;
|
|
649
|
+
this.count++;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Waits for every in-flight frame and passes it to the sink, so the sink
|
|
653
|
+
* holds every segment written so far. A no-op for a serial writer.
|
|
654
|
+
*
|
|
655
|
+
* @throws {Error} For a parallel writer, when an in-flight frame's worker
|
|
656
|
+
* failed or stopped responding (see {@link Beast2Writer.write}).
|
|
657
|
+
*/
|
|
658
|
+
settle() {
|
|
659
|
+
if (this.blob !== null)
|
|
660
|
+
this.blob.settle();
|
|
661
|
+
else
|
|
662
|
+
this.frames.settle();
|
|
663
|
+
}
|
|
664
|
+
/** Elements in the open segment.
|
|
665
|
+
* @internal */
|
|
666
|
+
get openCount() {
|
|
667
|
+
return this.cutter.openCount;
|
|
668
|
+
}
|
|
669
|
+
/** Logical bytes in the open segment.
|
|
670
|
+
* @internal */
|
|
671
|
+
get openBytes() {
|
|
672
|
+
return this.cutter.openBytes;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Ends the open segment here, for a caller that knows the cut rule starts
|
|
676
|
+
* one at what comes next without handing it over: writes the open segment,
|
|
677
|
+
* and makes the next element the first of a segment.
|
|
678
|
+
*
|
|
679
|
+
* @throws {Error} When called after {@link finish}.
|
|
680
|
+
* @internal
|
|
681
|
+
*/
|
|
682
|
+
closeSegment() {
|
|
683
|
+
if (this.finished)
|
|
684
|
+
throw new Error("add() after finish()");
|
|
685
|
+
this.writeSegment(this.open.toUint8Array());
|
|
686
|
+
this.open.pop();
|
|
687
|
+
this.count = 0;
|
|
688
|
+
this.cutter = new SegmentCutter();
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Writes the open segment and — for a blob — the terminator, index and
|
|
692
|
+
* footer. Idempotent.
|
|
693
|
+
*
|
|
694
|
+
* @throws {Error} For a parallel writer, when an in-flight frame's worker
|
|
695
|
+
* failed or stopped responding (see {@link Beast2Writer.write}).
|
|
696
|
+
*/
|
|
697
|
+
finish() {
|
|
698
|
+
if (this.finished)
|
|
699
|
+
return;
|
|
700
|
+
this.finished = true;
|
|
701
|
+
this.writeSegment(this.open.toUint8Array());
|
|
702
|
+
if (this.blob !== null)
|
|
703
|
+
this.blob.finish();
|
|
704
|
+
else
|
|
705
|
+
this.frames.settle();
|
|
706
|
+
}
|
|
707
|
+
/** Writes the open segment, whose elements are `elements`, unless it is
|
|
708
|
+
* empty. */
|
|
709
|
+
writeSegment(elements) {
|
|
710
|
+
if (this.count === 0)
|
|
711
|
+
return;
|
|
712
|
+
this.written++;
|
|
713
|
+
if (this.blob !== null) {
|
|
714
|
+
this.blob.writeEncodedSegment(this.count, elements);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const logical = new BufferWriter(elements.length + 10);
|
|
718
|
+
logical.writeVarint(this.count);
|
|
719
|
+
logical.writeBytes(elements);
|
|
720
|
+
this.frames.submit(logical.toUint8Array(), {
|
|
721
|
+
count: this.count,
|
|
722
|
+
fence: this.kind === "Array" ? new Uint8Array(0) : elements.slice(0, this.firstKeyLength),
|
|
723
|
+
logicalBytes: elements.length,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
409
727
|
/**
|
|
410
728
|
* Builds a curried paged encoder: `encode(value)` writes one whole collection
|
|
411
|
-
* value as a segmented, self-contained, indexed v5 blob
|
|
412
|
-
*
|
|
729
|
+
* value as a segmented, self-contained, indexed v5 blob, cut by the
|
|
730
|
+
* content-defined rule through {@link Beast2ElementWriter}.
|
|
413
731
|
*
|
|
414
732
|
* The write-side sibling of {@link openBeast2PagesFor}: a blob written this
|
|
415
733
|
* way supports random access ({@link Beast2Pages.segment} /
|
|
@@ -420,7 +738,7 @@ const PAGED_PROBE_BATCH = BEAST2_PAGED_PROBE_BATCH;
|
|
|
420
738
|
* bytes), so content-addressed stores hash the two forms differently.
|
|
421
739
|
*
|
|
422
740
|
* @param type - the collection type (Array/Set/Dict)
|
|
423
|
-
* @param options -
|
|
741
|
+
* @param options - codec and source map options
|
|
424
742
|
* @returns a function encoding a collection value to an indexed v5 blob
|
|
425
743
|
* @throws {TypeError} When `type` is not an Array, Set or Dict type.
|
|
426
744
|
*/
|
|
@@ -428,20 +746,10 @@ export function encodeBeast2PagedFor(type, options) {
|
|
|
428
746
|
const typeValue = asTypeValue(type);
|
|
429
747
|
const kind = checkSegmented(typeValue);
|
|
430
748
|
const cmp = orderCmpFor(typeValue, kind);
|
|
431
|
-
const batchCap = Math.max(1, Math.floor(options?.batchSize ?? BEAST2_PAGED_BATCH_DEFAULT));
|
|
432
|
-
const targetBytes = Math.max(1, Math.floor(options?.targetSegmentBytes ?? BEAST2_PAGED_TARGET_BYTES_DEFAULT));
|
|
433
|
-
const writerOptions = {
|
|
434
|
-
...(options?.codec !== undefined && { codec: options.codec }),
|
|
435
|
-
...(options?.sourceMap !== undefined && { sourceMap: options.sourceMap }),
|
|
436
|
-
};
|
|
437
749
|
return (value) => {
|
|
438
|
-
const makeBatch = kind === "Array" ? (items) => items
|
|
439
|
-
: kind === "Set" ? (items) => new Set(items)
|
|
440
|
-
: (items) => new Map(items);
|
|
441
750
|
// Canonical source order: SortedSet/SortedMap iterate in East order
|
|
442
|
-
// already; a plain Set/Map (insertion order) is sorted first
|
|
443
|
-
|
|
444
|
-
const iterable = kind === "Dict"
|
|
751
|
+
// already; a plain Set/Map (insertion order) is sorted first.
|
|
752
|
+
const elements = kind === "Dict"
|
|
445
753
|
? (value instanceof SortedMap
|
|
446
754
|
? value.entries()
|
|
447
755
|
: [...value.entries()].sort((a, b) => cmp(a[0], b[0])))
|
|
@@ -450,71 +758,14 @@ export function encodeBeast2PagedFor(type, options) {
|
|
|
450
758
|
? value
|
|
451
759
|
: [...value].sort(cmp))
|
|
452
760
|
: value;
|
|
453
|
-
// Byte-adaptive batching: a throwaway scratch encode of the first few
|
|
454
|
-
// elements measures the average wire size, and batches then target
|
|
455
|
-
// `targetSegmentBytes` (never above the element cap). Re-encoding the
|
|
456
|
-
// probe costs a handful of elements; the real stream starts with
|
|
457
|
-
// full-size, right-sized segments. Batching is a pure function of the
|
|
458
|
-
// value, so the bytes stay deterministic for content-addressing.
|
|
459
|
-
const items = iterable[Symbol.iterator]();
|
|
460
|
-
const probe = [];
|
|
461
|
-
while (probe.length < PAGED_PROBE_BATCH) {
|
|
462
|
-
const n = items.next();
|
|
463
|
-
if (n.done)
|
|
464
|
-
break;
|
|
465
|
-
probe.push(n.value);
|
|
466
|
-
}
|
|
467
|
-
let nextBatch = batchCap;
|
|
468
|
-
if (probe.length > 0) {
|
|
469
|
-
let scratchBytes = 0;
|
|
470
|
-
let scratchHeader = 0;
|
|
471
|
-
const scratch = new Beast2Writer(typeValue, (b) => { scratchBytes += b.length; }, writerOptions);
|
|
472
|
-
scratchHeader = scratchBytes;
|
|
473
|
-
scratch.write(makeBatch(probe));
|
|
474
|
-
const avg = Math.max(1, (scratchBytes - scratchHeader) / probe.length);
|
|
475
|
-
nextBatch = Math.max(1, Math.min(batchCap, Math.floor(targetBytes / avg)));
|
|
476
|
-
}
|
|
477
761
|
const chunks = [];
|
|
478
|
-
let
|
|
762
|
+
let total = 0;
|
|
479
763
|
// Frames deflate on worker threads where the runtime has them (#763).
|
|
480
|
-
const writer = new
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}, { ...writerOptions, parallel: true });
|
|
484
|
-
const headerBytes = bodyBytes;
|
|
485
|
-
let written = 0;
|
|
486
|
-
let batch = [];
|
|
487
|
-
const refine = (body) => Math.max(1, Math.min(batchCap, Math.floor(targetBytes / Math.max(1, body / written))));
|
|
488
|
-
const flush = () => {
|
|
489
|
-
if (batch.length === 0)
|
|
490
|
-
return;
|
|
491
|
-
writer.write(makeBatch(batch));
|
|
492
|
-
written += batch.length;
|
|
493
|
-
batch = [];
|
|
494
|
-
// Refine toward the target as real output accumulates (drifting data).
|
|
495
|
-
// With frames still deflating, only bounds on that output are known;
|
|
496
|
-
// the refinement is monotone in it, so agreeing bounds ARE the serial
|
|
497
|
-
// decision, and disagreeing ones wait for the frames. Either way the
|
|
498
|
-
// segmentation — and every byte — is what a serial writer produces.
|
|
499
|
-
const { lo, hi } = writer.emittedBounds();
|
|
500
|
-
nextBatch = refine(lo - headerBytes);
|
|
501
|
-
if (nextBatch !== refine(hi - headerBytes)) {
|
|
502
|
-
writer.settle();
|
|
503
|
-
nextBatch = refine(writer.emittedBounds().lo - headerBytes);
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
const pump = (item) => {
|
|
507
|
-
batch.push(item);
|
|
508
|
-
if (batch.length >= nextBatch)
|
|
509
|
-
flush();
|
|
510
|
-
};
|
|
511
|
-
for (const p of probe)
|
|
512
|
-
pump(p);
|
|
513
|
-
for (let n = items.next(); !n.done; n = items.next())
|
|
514
|
-
pump(n.value);
|
|
515
|
-
flush();
|
|
764
|
+
const writer = new Beast2ElementWriter(typeValue, (b) => { chunks.push(b); total += b.length; }, { ...options, parallel: true });
|
|
765
|
+
for (const element of elements)
|
|
766
|
+
writer.add(element);
|
|
516
767
|
writer.finish();
|
|
517
|
-
const out = new Uint8Array(
|
|
768
|
+
const out = new Uint8Array(total);
|
|
518
769
|
let pos = 0;
|
|
519
770
|
for (const c of chunks) {
|
|
520
771
|
out.set(c, pos);
|
|
@@ -555,7 +806,11 @@ function openSegmented(data, typeValue) {
|
|
|
555
806
|
* decoded element/key must exceed `order.prev`. Passing one state across
|
|
556
807
|
* consecutive segments extends the check over the segment boundary; a fresh
|
|
557
808
|
* state validates a single segment in isolation. Violations are corruption
|
|
558
|
-
* (the wire must hold the canonical value), never data to repair.
|
|
809
|
+
* (the wire must hold the canonical value), never data to repair.
|
|
810
|
+
*
|
|
811
|
+
* A Set/Dict segment is a SortedSet/SortedMap under the East comparator, as a
|
|
812
|
+
* whole-value decode is: a plain JS Set or Map compares keys by SameValueZero,
|
|
813
|
+
* which reads a `-0` key as `0` and merges it with a `0` beside it. */
|
|
559
814
|
function buildSegmentDecoder(typeValue, kind) {
|
|
560
815
|
const typeCtx = new Map();
|
|
561
816
|
const cmp = orderCmpFor(typeValue, kind);
|
|
@@ -563,7 +818,7 @@ function buildSegmentDecoder(typeValue, kind) {
|
|
|
563
818
|
const key = buildV5Decoder(typeValue.value.key, typeCtx);
|
|
564
819
|
const val = buildV5Decoder(typeValue.value.value, typeCtx);
|
|
565
820
|
return (reader, ctx, n, order) => {
|
|
566
|
-
const map = new
|
|
821
|
+
const map = new SortedMap(undefined, cmp);
|
|
567
822
|
for (let i = 0; i < n; i++) {
|
|
568
823
|
const k = key(reader, ctx);
|
|
569
824
|
if (order) {
|
|
@@ -584,7 +839,7 @@ function buildSegmentDecoder(typeValue, kind) {
|
|
|
584
839
|
const elem = buildV5Decoder(typeValue.value, typeCtx);
|
|
585
840
|
if (kind === "Set") {
|
|
586
841
|
return (reader, ctx, n, order) => {
|
|
587
|
-
const set = new
|
|
842
|
+
const set = new SortedSet(undefined, cmp);
|
|
588
843
|
for (let i = 0; i < n; i++) {
|
|
589
844
|
const item = elem(reader, ctx);
|
|
590
845
|
if (order) {
|
|
@@ -709,11 +964,16 @@ export class Beast2Pages {
|
|
|
709
964
|
/** Whether segments are independently decodable. */
|
|
710
965
|
selfContained;
|
|
711
966
|
source;
|
|
967
|
+
/** The manifest behind this reader, when the segments are separate blobs
|
|
968
|
+
* rather than runs of one. */
|
|
969
|
+
manifestSource = null;
|
|
712
970
|
/** Wire offset of the terminator frame — where the last segment's frame ends. */
|
|
713
971
|
segmentsEnd;
|
|
714
972
|
indexData;
|
|
715
973
|
kind;
|
|
716
974
|
typeValue;
|
|
975
|
+
/** The stream's source map. For a manifest it starts empty and is replaced
|
|
976
|
+
* by the first segment's own header map, which every segment shares. */
|
|
717
977
|
sourceMap;
|
|
718
978
|
decodeSegment;
|
|
719
979
|
platform;
|
|
@@ -722,6 +982,8 @@ export class Beast2Pages {
|
|
|
722
982
|
/** First key/element of each segment, in segment order (Set/Dict only). */
|
|
723
983
|
fences = null;
|
|
724
984
|
fenceDec = null;
|
|
985
|
+
/** The decoder of a manifest's stored fence bytes, built on first use. */
|
|
986
|
+
manifestFenceDec = null;
|
|
725
987
|
/** Decoded segments kept hot for the element and keyed read paths, keyed
|
|
726
988
|
* by segment index in LRU order (mirrors east-c's `B2V5_PAGES_LRU`).
|
|
727
989
|
* Only {@link element} and {@link get} route through it — the public
|
|
@@ -735,7 +997,25 @@ export class Beast2Pages {
|
|
|
735
997
|
let kind;
|
|
736
998
|
let sourceMap;
|
|
737
999
|
let index;
|
|
738
|
-
if (
|
|
1000
|
+
if (isBeast2ManifestSource(source)) {
|
|
1001
|
+
// A manifest names its segments; there is no one blob to take geometry
|
|
1002
|
+
// from. Counts and fences come from the entries, and a segment is its
|
|
1003
|
+
// own blob — so no offset in this index is ever read, and the header
|
|
1004
|
+
// (with it the source map) is parsed from the first segment opened.
|
|
1005
|
+
kind = checkSegmented(typeValue);
|
|
1006
|
+
sourceMap = new SourceMap();
|
|
1007
|
+
const entries = source.manifest.entries;
|
|
1008
|
+
index = {
|
|
1009
|
+
selfContained: true,
|
|
1010
|
+
offsets: entries.map(() => 0),
|
|
1011
|
+
counts: entries.map((e) => Number(e.count)),
|
|
1012
|
+
totalCount: entries.reduce((sum, e) => sum + Number(e.count), 0),
|
|
1013
|
+
};
|
|
1014
|
+
this.manifestSource = source;
|
|
1015
|
+
this.segmentsEnd = 0;
|
|
1016
|
+
this.source = bytesReader(new Uint8Array(0));
|
|
1017
|
+
}
|
|
1018
|
+
else if (!isBeast2SyncRangeReader(source)) {
|
|
739
1019
|
({ kind, sourceMap } = openSegmented(source, typeValue));
|
|
740
1020
|
const whole = readIndex(source);
|
|
741
1021
|
if (!whole) {
|
|
@@ -780,12 +1060,31 @@ export class Beast2Pages {
|
|
|
780
1060
|
/** Reads segment `i`'s frame — exactly its wire bytes, from its index
|
|
781
1061
|
* offset to the next segment's (or the terminator) — and opens its
|
|
782
1062
|
* logical chunk. The only place segment bytes are fetched, so a ranged
|
|
783
|
-
* source touches one frame per decode
|
|
1063
|
+
* source touches one frame per decode, and a manifest source touches one
|
|
1064
|
+
* segment blob. */
|
|
784
1065
|
frameReader(i) {
|
|
1066
|
+
if (this.manifestSource !== null)
|
|
1067
|
+
return this.manifestFrameReader(i);
|
|
785
1068
|
const start = this.indexData.offsets[i];
|
|
786
1069
|
const end = i + 1 < this.indexData.offsets.length ? this.indexData.offsets[i + 1] : this.segmentsEnd;
|
|
787
1070
|
return new FrameReader(readExact(this.source, start, end - start), 0).next();
|
|
788
1071
|
}
|
|
1072
|
+
/** Segment `i`'s frame out of its own standalone blob: the blob's geometry
|
|
1073
|
+
* names exactly one frame, and its header carries the source map every
|
|
1074
|
+
* segment of this collection shares. */
|
|
1075
|
+
manifestFrameReader(i) {
|
|
1076
|
+
const blob = this.manifestSource.segment(i);
|
|
1077
|
+
const reader = isBeast2SyncRangeReader(blob) ? blob : bytesReader(blob);
|
|
1078
|
+
const extents = readBeast2ExtentsSync(reader);
|
|
1079
|
+
if (extents.offsets.length !== 1) {
|
|
1080
|
+
throw new Error(`beast2 v5: manifest entry ${i} holds ${extents.offsets.length} segments, not one`);
|
|
1081
|
+
}
|
|
1082
|
+
if (this.sourceMap.size <= 1n) {
|
|
1083
|
+
this.sourceMap = openSegmented(extents.head, this.typeValue).sourceMap;
|
|
1084
|
+
}
|
|
1085
|
+
const start = extents.offsets[0];
|
|
1086
|
+
return new FrameReader(readExact(reader, start, extents.segmentsEnd - start), 0).next();
|
|
1087
|
+
}
|
|
789
1088
|
/**
|
|
790
1089
|
* Decodes one segment by index.
|
|
791
1090
|
*
|
|
@@ -863,6 +1162,15 @@ export class Beast2Pages {
|
|
|
863
1162
|
* final attempt reads the frame whole, so corruption is still reported
|
|
864
1163
|
* with the frame's own error. */
|
|
865
1164
|
firstKey(i) {
|
|
1165
|
+
// A manifest stores every fence already, so a bisect over one reads no
|
|
1166
|
+
// segment bytes at all — the whole point of carrying them.
|
|
1167
|
+
if (this.manifestSource !== null) {
|
|
1168
|
+
if (!this.manifestFenceDec) {
|
|
1169
|
+
const keyType = this.kind === "Dict" ? this.typeValue.value.key : this.typeValue.value;
|
|
1170
|
+
this.manifestFenceDec = decodeBeast2FenceFor(keyType, this.platform);
|
|
1171
|
+
}
|
|
1172
|
+
return this.manifestFenceDec(this.manifestSource.manifest.entries[i].fence);
|
|
1173
|
+
}
|
|
866
1174
|
if (!this.fenceDec) {
|
|
867
1175
|
const keyType = this.kind === "Dict" ? this.typeValue.value.key : this.typeValue.value;
|
|
868
1176
|
this.fenceDec = buildV5Decoder(keyType);
|
|
@@ -983,7 +1291,7 @@ export class Beast2Pages {
|
|
|
983
1291
|
* Rows address stream order — for Array roots the element order, for
|
|
984
1292
|
* Set/Dict roots the canonical East (key) order, since segments are
|
|
985
1293
|
* disjoint ascending ranges. Returns a collection value of the root kind
|
|
986
|
-
* holding the window (an array, `
|
|
1294
|
+
* holding the window (an array, `SortedSet`, or `SortedMap` in that order).
|
|
987
1295
|
*
|
|
988
1296
|
* Clamps like `Array.prototype.slice`: a window past the end returns the
|
|
989
1297
|
* available tail (or an empty collection), never throws for being short.
|
|
@@ -999,7 +1307,8 @@ export class Beast2Pages {
|
|
|
999
1307
|
if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 0) {
|
|
1000
1308
|
throw new Error(`beast2 v5: slice(${offset}, ${limit}) — offset and limit must be non-negative integers`);
|
|
1001
1309
|
}
|
|
1002
|
-
const empty = () => this.kind === "Array" ? []
|
|
1310
|
+
const empty = () => this.kind === "Array" ? []
|
|
1311
|
+
: this.kind === "Set" ? new SortedSet(undefined, this.orderCmp) : new SortedMap(undefined, this.orderCmp);
|
|
1003
1312
|
if (limit === 0 || offset >= this.elementCount)
|
|
1004
1313
|
return empty();
|
|
1005
1314
|
if (this.kind === "Array") {
|
|
@@ -1021,7 +1330,7 @@ export class Beast2Pages {
|
|
|
1021
1330
|
// against the fence of the first untouched segment.
|
|
1022
1331
|
const fences = this.verifyFences();
|
|
1023
1332
|
const isSet = this.kind === "Set";
|
|
1024
|
-
const out = (
|
|
1333
|
+
const out = empty();
|
|
1025
1334
|
let taken = 0;
|
|
1026
1335
|
let { seg, base } = this.rowSegment(offset);
|
|
1027
1336
|
const order = { prev: undefined, has: false };
|
|
@@ -1092,6 +1401,28 @@ export class Beast2Pages {
|
|
|
1092
1401
|
}
|
|
1093
1402
|
return lo;
|
|
1094
1403
|
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Decodes one segment of a Set or Dict for a walk in canonical order
|
|
1406
|
+
* (Set/Dict roots only), as east-c's paged loops read one: the fences are
|
|
1407
|
+
* probed and verified to ascend strictly first, once per reader, and the
|
|
1408
|
+
* segment's last key is checked to fall below the next segment's fence. A
|
|
1409
|
+
* walk from segment 0 over a blob that violates the canonical-order contract
|
|
1410
|
+
* therefore fails at the first segment it reads: before any element when the
|
|
1411
|
+
* fences are out of order, and before the elements of the first segment
|
|
1412
|
+
* that overlaps the next.
|
|
1413
|
+
*
|
|
1414
|
+
* @param i - zero-based segment index
|
|
1415
|
+
* @returns the segment's decoded collection
|
|
1416
|
+
* @throws {Error} When the root is an Array, `i` is out of range, the blob
|
|
1417
|
+
* is not self-contained, or the blob violates the canonical-order
|
|
1418
|
+
* contract.
|
|
1419
|
+
*/
|
|
1420
|
+
segmentDisjoint(i) {
|
|
1421
|
+
if (this.kind === "Array") {
|
|
1422
|
+
throw new Error(`beast2 v5: segmentDisjoint() addresses Set and Dict roots; this blob holds Array — use segment()`);
|
|
1423
|
+
}
|
|
1424
|
+
return this.decodeDisjoint(i, { prev: undefined, has: false }, this.verifyFences());
|
|
1425
|
+
}
|
|
1095
1426
|
/**
|
|
1096
1427
|
* Looks up one Set element or Dict value by key (Set/Dict roots only):
|
|
1097
1428
|
* binary-searches the verified segment fences for the only segment whose
|
|
@@ -1133,9 +1464,10 @@ export class Beast2Pages {
|
|
|
1133
1464
|
* Builds a curried pages opener: `open(source)` parses the header, footer and
|
|
1134
1465
|
* index once and returns a {@link Beast2Pages} for random access.
|
|
1135
1466
|
*
|
|
1136
|
-
* `source` is the whole blob,
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1467
|
+
* `source` is the whole blob, a {@link Beast2SyncRangeReader} over it — then
|
|
1468
|
+
* only the tail, the head and the segments actually read are ever fetched —
|
|
1469
|
+
* or a {@link Beast2ManifestSource}, whose segments are separate blobs and
|
|
1470
|
+
* whose fences are already decoded, so a keyed read touches exactly one.
|
|
1139
1471
|
*
|
|
1140
1472
|
* @param type - the collection type (Array/Set/Dict)
|
|
1141
1473
|
* @param options - decode options (platform functions for decoded functions)
|