@graphty/graph-format 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +216 -43
- package/dist/graph-format.d.ts +1 -0
- package/dist/graph-format.d.ts.map +1 -0
- package/dist/graph-format.js +15798 -0
- package/dist/graph-format.js.map +1 -0
- package/dist/src/builder/arena.d.ts +85 -0
- package/dist/src/builder/arena.d.ts.map +1 -0
- package/dist/src/builder/arena.js +154 -0
- package/dist/src/builder/arena.js.map +1 -0
- package/dist/src/builder/compact.d.ts +515 -0
- package/dist/src/builder/compact.d.ts.map +1 -0
- package/dist/src/builder/compact.js +1553 -0
- package/dist/src/builder/compact.js.map +1 -0
- package/dist/src/builder/counting-sort.d.ts +96 -0
- package/dist/src/builder/counting-sort.d.ts.map +1 -0
- package/dist/src/builder/counting-sort.js +430 -0
- package/dist/src/builder/counting-sort.js.map +1 -0
- package/dist/src/builder/freeze.d.ts +72 -0
- package/dist/src/builder/freeze.d.ts.map +1 -0
- package/dist/src/builder/freeze.js +548 -0
- package/dist/src/builder/freeze.js.map +1 -0
- package/dist/src/builder/graph-builder.d.ts +602 -0
- package/dist/src/builder/graph-builder.d.ts.map +1 -0
- package/dist/src/builder/graph-builder.js +1940 -0
- package/dist/src/builder/graph-builder.js.map +1 -0
- package/dist/src/builder/options.d.ts +16 -0
- package/dist/src/builder/options.d.ts.map +1 -0
- package/dist/src/builder/options.js +21 -0
- package/dist/src/builder/options.js.map +1 -0
- package/dist/src/columns/bitmap.d.ts +96 -0
- package/dist/src/columns/bitmap.d.ts.map +1 -0
- package/dist/src/columns/bitmap.js +184 -0
- package/dist/src/columns/bitmap.js.map +1 -0
- package/dist/src/columns/column.d.ts +220 -0
- package/dist/src/columns/column.d.ts.map +1 -0
- package/dist/src/columns/column.js +2109 -0
- package/dist/src/columns/column.js.map +1 -0
- package/dist/src/columns/dictionary.d.ts +60 -0
- package/dist/src/columns/dictionary.d.ts.map +1 -0
- package/dist/src/columns/dictionary.js +102 -0
- package/dist/src/columns/dictionary.js.map +1 -0
- package/dist/src/columns/growable.d.ts +198 -0
- package/dist/src/columns/growable.d.ts.map +1 -0
- package/dist/src/columns/growable.js +296 -0
- package/dist/src/columns/growable.js.map +1 -0
- package/dist/src/columns/infer.d.ts +121 -0
- package/dist/src/columns/infer.d.ts.map +1 -0
- package/dist/src/columns/infer.js +268 -0
- package/dist/src/columns/infer.js.map +1 -0
- package/dist/src/columns/remap.d.ts +143 -0
- package/dist/src/columns/remap.d.ts.map +1 -0
- package/dist/src/columns/remap.js +588 -0
- package/dist/src/columns/remap.js.map +1 -0
- package/dist/src/columns/table.d.ts +184 -0
- package/dist/src/columns/table.d.ts.map +1 -0
- package/dist/src/columns/table.js +485 -0
- package/dist/src/columns/table.js.map +1 -0
- package/dist/src/constants.d.ts +34 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +79 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +76 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +32 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/ids/edge-id-index.d.ts +77 -0
- package/dist/src/ids/edge-id-index.d.ts.map +1 -0
- package/dist/src/ids/edge-id-index.js +155 -0
- package/dist/src/ids/edge-id-index.js.map +1 -0
- package/dist/src/ids/node-id-map.d.ts +280 -0
- package/dist/src/ids/node-id-map.d.ts.map +1 -0
- package/dist/src/ids/node-id-map.js +1139 -0
- package/dist/src/ids/node-id-map.js.map +1 -0
- package/dist/src/ids/string-store.d.ts +219 -0
- package/dist/src/ids/string-store.d.ts.map +1 -0
- package/dist/src/ids/string-store.js +526 -0
- package/dist/src/ids/string-store.js.map +1 -0
- package/dist/src/index.d.ts +30 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +29 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/populate/from-csr.d.ts +61 -0
- package/dist/src/populate/from-csr.d.ts.map +1 -0
- package/dist/src/populate/from-csr.js +706 -0
- package/dist/src/populate/from-csr.js.map +1 -0
- package/dist/src/populate/from-edge-arrays.d.ts +51 -0
- package/dist/src/populate/from-edge-arrays.d.ts.map +1 -0
- package/dist/src/populate/from-edge-arrays.js +217 -0
- package/dist/src/populate/from-edge-arrays.js.map +1 -0
- package/dist/src/populate/from-records.d.ts +51 -0
- package/dist/src/populate/from-records.d.ts.map +1 -0
- package/dist/src/populate/from-records.js +447 -0
- package/dist/src/populate/from-records.js.map +1 -0
- package/dist/src/snapshot/derived.d.ts +211 -0
- package/dist/src/snapshot/derived.d.ts.map +1 -0
- package/dist/src/snapshot/derived.js +1254 -0
- package/dist/src/snapshot/derived.js.map +1 -0
- package/dist/src/snapshot/graph-meta.d.ts +23 -0
- package/dist/src/snapshot/graph-meta.d.ts.map +1 -0
- package/dist/src/snapshot/graph-meta.js +159 -0
- package/dist/src/snapshot/graph-meta.js.map +1 -0
- package/dist/src/snapshot/graph-snapshot.d.ts +509 -0
- package/dist/src/snapshot/graph-snapshot.d.ts.map +1 -0
- package/dist/src/snapshot/graph-snapshot.js +1247 -0
- package/dist/src/snapshot/graph-snapshot.js.map +1 -0
- package/dist/src/snapshot/hash.d.ts +90 -0
- package/dist/src/snapshot/hash.d.ts.map +1 -0
- package/dist/src/snapshot/hash.js +228 -0
- package/dist/src/snapshot/hash.js.map +1 -0
- package/dist/src/snapshot/queries.d.ts +74 -0
- package/dist/src/snapshot/queries.d.ts.map +1 -0
- package/dist/src/snapshot/queries.js +131 -0
- package/dist/src/snapshot/queries.js.map +1 -0
- package/dist/src/snapshot/validate.d.ts +192 -0
- package/dist/src/snapshot/validate.d.ts.map +1 -0
- package/dist/src/snapshot/validate.js +990 -0
- package/dist/src/snapshot/validate.js.map +1 -0
- package/dist/src/snapshot/views.d.ts +274 -0
- package/dist/src/snapshot/views.d.ts.map +1 -0
- package/dist/src/snapshot/views.js +856 -0
- package/dist/src/snapshot/views.js.map +1 -0
- package/dist/src/types/builder.d.ts +456 -0
- package/dist/src/types/builder.d.ts.map +1 -0
- package/dist/src/types/builder.js +11 -0
- package/dist/src/types/builder.js.map +1 -0
- package/dist/src/types/columns.d.ts +615 -0
- package/dist/src/types/columns.d.ts.map +1 -0
- package/dist/src/types/columns.js +14 -0
- package/dist/src/types/columns.js.map +1 -0
- package/dist/src/types/index.d.ts +14 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +11 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/internal.d.ts +172 -0
- package/dist/src/types/internal.d.ts.map +1 -0
- package/dist/src/types/internal.js +13 -0
- package/dist/src/types/internal.js.map +1 -0
- package/dist/src/types/snapshot.d.ts +801 -0
- package/dist/src/types/snapshot.d.ts.map +1 -0
- package/dist/src/types/snapshot.js +14 -0
- package/dist/src/types/snapshot.js.map +1 -0
- package/dist/src/types/wire.d.ts +196 -0
- package/dist/src/types/wire.d.ts.map +1 -0
- package/dist/src/types/wire.js +8 -0
- package/dist/src/types/wire.js.map +1 -0
- package/dist/src/util/mask.d.ts +55 -0
- package/dist/src/util/mask.d.ts.map +1 -0
- package/dist/src/util/mask.js +71 -0
- package/dist/src/util/mask.js.map +1 -0
- package/dist/src/util/options.d.ts +22 -0
- package/dist/src/util/options.d.ts.map +1 -0
- package/dist/src/util/options.js +49 -0
- package/dist/src/util/options.js.map +1 -0
- package/dist/src/util/shared-buffers.d.ts +42 -0
- package/dist/src/util/shared-buffers.d.ts.map +1 -0
- package/dist/src/util/shared-buffers.js +52 -0
- package/dist/src/util/shared-buffers.js.map +1 -0
- package/dist/src/util/typed-array.d.ts +205 -0
- package/dist/src/util/typed-array.d.ts.map +1 -0
- package/dist/src/util/typed-array.js +289 -0
- package/dist/src/util/typed-array.js.map +1 -0
- package/dist/src/wire/bytes.d.ts +69 -0
- package/dist/src/wire/bytes.d.ts.map +1 -0
- package/dist/src/wire/bytes.js +361 -0
- package/dist/src/wire/bytes.js.map +1 -0
- package/dist/src/wire/carried-views.d.ts +111 -0
- package/dist/src/wire/carried-views.d.ts.map +1 -0
- package/dist/src/wire/carried-views.js +286 -0
- package/dist/src/wire/carried-views.js.map +1 -0
- package/dist/src/wire/from-wire.d.ts +144 -0
- package/dist/src/wire/from-wire.d.ts.map +1 -0
- package/dist/src/wire/from-wire.js +1425 -0
- package/dist/src/wire/from-wire.js.map +1 -0
- package/dist/src/wire/to-wire.d.ts +171 -0
- package/dist/src/wire/to-wire.d.ts.map +1 -0
- package/dist/src/wire/to-wire.js +652 -0
- package/dist/src/wire/to-wire.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +72 -7
- package/src/builder/arena.ts +192 -0
- package/src/builder/compact.ts +1811 -0
- package/src/builder/counting-sort.ts +506 -0
- package/src/builder/freeze.ts +689 -0
- package/src/builder/graph-builder.ts +2264 -0
- package/src/builder/options.ts +29 -0
- package/src/columns/bitmap.ts +196 -0
- package/src/columns/column.ts +2574 -0
- package/src/columns/dictionary.ts +114 -0
- package/src/columns/growable.ts +350 -0
- package/src/columns/infer.ts +285 -0
- package/src/columns/remap.ts +663 -0
- package/src/columns/table.ts +568 -0
- package/src/constants.ts +89 -0
- package/src/errors.ts +115 -0
- package/src/ids/edge-id-index.ts +179 -0
- package/src/ids/node-id-map.ts +1340 -0
- package/src/ids/string-store.ts +589 -0
- package/src/index.ts +139 -0
- package/src/lib-resizable-array-buffer.d.ts +34 -0
- package/src/populate/from-csr.ts +802 -0
- package/src/populate/from-edge-arrays.ts +257 -0
- package/src/populate/from-records.ts +536 -0
- package/src/snapshot/derived.ts +1486 -0
- package/src/snapshot/graph-meta.ts +167 -0
- package/src/snapshot/graph-snapshot.ts +1503 -0
- package/src/snapshot/hash.ts +241 -0
- package/src/snapshot/queries.ts +136 -0
- package/src/snapshot/validate.ts +1108 -0
- package/src/snapshot/views.ts +943 -0
- package/src/types/builder.ts +480 -0
- package/src/types/columns.ts +727 -0
- package/src/types/index.ts +14 -0
- package/src/types/internal.ts +195 -0
- package/src/types/snapshot.ts +886 -0
- package/src/types/wire.ts +207 -0
- package/src/util/mask.ts +89 -0
- package/src/util/options.ts +57 -0
- package/src/util/shared-buffers.ts +56 -0
- package/src/util/typed-array.ts +371 -0
- package/src/wire/bytes.ts +440 -0
- package/src/wire/carried-views.ts +327 -0
- package/src/wire/from-wire.ts +1690 -0
- package/src/wire/to-wire.ts +755 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GSNP byte container (design section 9.2): `toBytes()` writes one contiguous buffer,
|
|
3
|
+
* `toByteChunks()` the same bytes as a sequence (header + manifest, then every 256-padded segment
|
|
4
|
+
* in manifest order), `fromBytes()` adopts a container's buffer region as the arena and
|
|
5
|
+
* `fromByteChunks()` adopts each chunk's buffer per segment. The snapshot's `toBytes` /
|
|
6
|
+
* `toByteChunks` methods call the functions of this module directly.
|
|
7
|
+
*
|
|
8
|
+
* Layout, all integers little-endian:
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* offset size field
|
|
12
|
+
* 0 4 magic "GSNP"
|
|
13
|
+
* 4 2 wire major (u16)
|
|
14
|
+
* 6 2 wire minor (u16)
|
|
15
|
+
* 8 4 endianness probe: 0x01020304 written through a host-order Uint32Array
|
|
16
|
+
* 12 4 manifest byte length L (u32)
|
|
17
|
+
* 16 L manifest: UTF-8 JSON of the WireManifest, every reference into buffer 0 with a
|
|
18
|
+
* region-relative byteOffset
|
|
19
|
+
* 16+L pad zero padding to the next multiple of 256
|
|
20
|
+
* B ... buffer region: each array at a 256-aligned offset in manifest order, core arrays
|
|
21
|
+
* first so the region's prefix is the arena; every segment padded to 256 bytes
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The reader refuses a bad magic, an unexpected probe, a truncated header or manifest, a manifest
|
|
25
|
+
* whose versions disagree with the header (E_BAD_SERIALIZATION), an unknown wire major
|
|
26
|
+
* (E_UNSUPPORTED_VERSION) and everything the manifest reader of from-wire.ts refuses.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { ALIGNMENT, CONTAINER_MAGIC, ENDIAN_PROBE, IS_LITTLE_ENDIAN, WIRE_MAJOR, WIRE_MINOR } from "../constants.js";
|
|
30
|
+
import { GraphFormatError } from "../errors.js";
|
|
31
|
+
import { type GraphSnapshot } from "../snapshot/graph-snapshot.js";
|
|
32
|
+
import { type FromWireOptions, type ToBytesOptions, type TypedArrayData, type U8 } from "../types/index.js";
|
|
33
|
+
import { alignUp, padTo4 } from "../util/typed-array.js";
|
|
34
|
+
import {
|
|
35
|
+
badWire,
|
|
36
|
+
bufferRegion,
|
|
37
|
+
checkManifestVersions,
|
|
38
|
+
decodeManifest,
|
|
39
|
+
isSharedArrayBuffer,
|
|
40
|
+
type LocatedRange,
|
|
41
|
+
parseGuardedJson,
|
|
42
|
+
resolveFromWireOptions,
|
|
43
|
+
type WireRegion,
|
|
44
|
+
} from "./from-wire.js";
|
|
45
|
+
import { collectWire, type ContainerSegment, encodeManifest, WireLayout } from "./to-wire.js";
|
|
46
|
+
|
|
47
|
+
// ============================================================ constants
|
|
48
|
+
|
|
49
|
+
/** Bytes before the manifest: magic, major, minor, probe and manifest length. */
|
|
50
|
+
export const CONTAINER_HEADER_BYTES = 16;
|
|
51
|
+
|
|
52
|
+
const fatalDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
53
|
+
|
|
54
|
+
// ============================================================ writing
|
|
55
|
+
|
|
56
|
+
/** Everything a container write needs, computed once per call. */
|
|
57
|
+
interface ContainerPlan {
|
|
58
|
+
/** The manifest's UTF-8 bytes. */
|
|
59
|
+
readonly manifestBytes: Uint8Array;
|
|
60
|
+
/** B: the start of the buffer region (header + manifest padded to 256). */
|
|
61
|
+
readonly regionStart: number;
|
|
62
|
+
/** The placed arrays in region order. */
|
|
63
|
+
readonly segments: readonly ContainerSegment[];
|
|
64
|
+
/** The padded region length. */
|
|
65
|
+
readonly regionLength: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Lay a snapshot out for the container.
|
|
70
|
+
* @param snapshot - the snapshot
|
|
71
|
+
* @param options - the views to carry
|
|
72
|
+
* @returns the plan
|
|
73
|
+
*/
|
|
74
|
+
function planContainer(snapshot: GraphSnapshot, options: ToBytesOptions): ContainerPlan {
|
|
75
|
+
const layout = new WireLayout("container");
|
|
76
|
+
const { manifest } = collectWire(snapshot, layout, options.includeViews, true);
|
|
77
|
+
const manifestBytes = encodeManifest(manifest);
|
|
78
|
+
return {
|
|
79
|
+
manifestBytes,
|
|
80
|
+
regionStart: alignUp(CONTAINER_HEADER_BYTES + manifestBytes.byteLength, ALIGNMENT),
|
|
81
|
+
segments: layout.segments,
|
|
82
|
+
regionLength: layout.regionLength,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write the 16-byte header into a fresh buffer (design section 9.2).
|
|
88
|
+
* @param out - a Uint8Array at offset 0 of its buffer with at least 16 bytes
|
|
89
|
+
* @param manifestLength - L
|
|
90
|
+
*/
|
|
91
|
+
function writeHeader(out: U8, manifestLength: number): void {
|
|
92
|
+
out.set(CONTAINER_MAGIC, 0);
|
|
93
|
+
const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
94
|
+
view.setUint16(4, WIRE_MAJOR, true);
|
|
95
|
+
view.setUint16(6, WIRE_MINOR, true);
|
|
96
|
+
new Uint32Array(out.buffer, out.byteOffset + 8, 1)[0] = ENDIAN_PROBE;
|
|
97
|
+
view.setUint32(12, manifestLength, true);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The bytes of a typed array as a Uint8Array view.
|
|
102
|
+
* @param view - the array
|
|
103
|
+
* @returns the byte view
|
|
104
|
+
*/
|
|
105
|
+
function bytesOf(view: TypedArrayData): Uint8Array {
|
|
106
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The GSNP container of a snapshot as one contiguous buffer (design section 9.2).
|
|
111
|
+
* @param snapshot - the snapshot
|
|
112
|
+
* @param options - the cached views to carry
|
|
113
|
+
* @returns the container bytes at offset 0 of a fresh ArrayBuffer; E_UNSUPPORTED on a big-endian host
|
|
114
|
+
*/
|
|
115
|
+
export function toBytes(snapshot: GraphSnapshot, options: ToBytesOptions = {}): U8 {
|
|
116
|
+
const plan = planContainer(snapshot, options);
|
|
117
|
+
const out = new Uint8Array(plan.regionStart + plan.regionLength);
|
|
118
|
+
writeHeader(out, plan.manifestBytes.byteLength);
|
|
119
|
+
out.set(plan.manifestBytes, CONTAINER_HEADER_BYTES);
|
|
120
|
+
for (const segment of plan.segments) {
|
|
121
|
+
out.set(bytesOf(segment.view), plan.regionStart + segment.ref.byteOffset);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Yield the chunks of a plan: the padded header + manifest, then every non-empty segment padded to
|
|
128
|
+
* 256 bytes.
|
|
129
|
+
* @param plan - the plan
|
|
130
|
+
* @yields the chunks in order
|
|
131
|
+
*/
|
|
132
|
+
function* chunksOf(plan: ContainerPlan): Generator<U8, void, undefined> {
|
|
133
|
+
const head = new Uint8Array(plan.regionStart);
|
|
134
|
+
writeHeader(head, plan.manifestBytes.byteLength);
|
|
135
|
+
head.set(plan.manifestBytes, CONTAINER_HEADER_BYTES);
|
|
136
|
+
yield head;
|
|
137
|
+
for (const segment of plan.segments) {
|
|
138
|
+
if (segment.ref.byteLength === 0) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const chunk = new Uint8Array(alignUp(segment.ref.byteLength, ALIGNMENT));
|
|
142
|
+
chunk.set(bytesOf(segment.view));
|
|
143
|
+
yield chunk;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The GSNP container as a sequence of chunks whose concatenation equals `toBytes()` (design section
|
|
149
|
+
* 9.2): the header + manifest padded to 256, then each non-empty segment padded to 256, in manifest
|
|
150
|
+
* order. The layout is computed when this function is called; the chunk copies are made lazily.
|
|
151
|
+
* @param snapshot - the snapshot
|
|
152
|
+
* @param options - the cached views to carry
|
|
153
|
+
* @returns the chunks; E_UNSUPPORTED on a big-endian host
|
|
154
|
+
*/
|
|
155
|
+
export function toByteChunks(snapshot: GraphSnapshot, options: ToBytesOptions = {}): Iterable<U8> {
|
|
156
|
+
return chunksOf(planContainer(snapshot, options));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================================ reading
|
|
160
|
+
|
|
161
|
+
/** The fields of a container header. */
|
|
162
|
+
interface ContainerHeader {
|
|
163
|
+
/** The wire major. */
|
|
164
|
+
readonly major: number;
|
|
165
|
+
/** The wire minor. */
|
|
166
|
+
readonly minor: number;
|
|
167
|
+
/** L, the manifest byte length. */
|
|
168
|
+
readonly manifestLength: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check and read the 16-byte header (design section 9.2).
|
|
173
|
+
* @param head - at least the first 16 bytes of the container
|
|
174
|
+
* @param totalLength - the container's total byte length
|
|
175
|
+
* @returns the header fields
|
|
176
|
+
*/
|
|
177
|
+
function readHeader(head: Uint8Array, totalLength: number): ContainerHeader {
|
|
178
|
+
if (totalLength < CONTAINER_HEADER_BYTES) {
|
|
179
|
+
throw badWire("header", `truncated: ${totalLength} bytes, expected at least ${CONTAINER_HEADER_BYTES}`, {
|
|
180
|
+
byteLength: totalLength,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
for (let i = 0; i < CONTAINER_MAGIC.length; i++) {
|
|
184
|
+
if (head[i] !== CONTAINER_MAGIC[i]) {
|
|
185
|
+
throw badWire("magic", "not a GSNP container", { found: Array.from(head.subarray(0, 4)) });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const view = new DataView(head.buffer, head.byteOffset, head.byteLength);
|
|
189
|
+
const major = view.getUint16(4, true);
|
|
190
|
+
const minor = view.getUint16(6, true);
|
|
191
|
+
if (major !== WIRE_MAJOR) {
|
|
192
|
+
throw new GraphFormatError(
|
|
193
|
+
"E_UNSUPPORTED_VERSION",
|
|
194
|
+
`wire major ${major} is not supported (reader: ${WIRE_MAJOR})`,
|
|
195
|
+
{
|
|
196
|
+
kind: "wire",
|
|
197
|
+
found: major,
|
|
198
|
+
supported: WIRE_MAJOR,
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const probe = view.getUint32(8, IS_LITTLE_ENDIAN);
|
|
203
|
+
if (probe !== ENDIAN_PROBE) {
|
|
204
|
+
throw badWire(
|
|
205
|
+
"endianness",
|
|
206
|
+
`probe reads 0x${probe.toString(16)}; the container was written on another endianness`,
|
|
207
|
+
{
|
|
208
|
+
found: probe,
|
|
209
|
+
expected: ENDIAN_PROBE,
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const manifestLength = view.getUint32(12, true);
|
|
214
|
+
if (CONTAINER_HEADER_BYTES + manifestLength > totalLength) {
|
|
215
|
+
throw badWire(
|
|
216
|
+
"manifest",
|
|
217
|
+
`truncated: ${manifestLength} manifest bytes declared, ${totalLength} bytes in total`,
|
|
218
|
+
{
|
|
219
|
+
manifestLength,
|
|
220
|
+
byteLength: totalLength,
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return { major, minor, manifestLength };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Parse the manifest bytes: a fatal UTF-8 decode, the guarding JSON reviver, then the version check
|
|
229
|
+
* against the header.
|
|
230
|
+
* @param bytes - the manifest bytes
|
|
231
|
+
* @param header - the header the manifest must agree with
|
|
232
|
+
* @returns the manifest object
|
|
233
|
+
*/
|
|
234
|
+
function readManifest(bytes: Uint8Array, header: ContainerHeader): Record<string, unknown> {
|
|
235
|
+
let text: string;
|
|
236
|
+
try {
|
|
237
|
+
text = fatalDecoder.decode(bytes);
|
|
238
|
+
} catch {
|
|
239
|
+
throw badWire("manifest", "not valid UTF-8");
|
|
240
|
+
}
|
|
241
|
+
const manifest: unknown = parseGuardedJson(text, "manifest");
|
|
242
|
+
if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
|
|
243
|
+
throw badWire("manifest", "expected an object");
|
|
244
|
+
}
|
|
245
|
+
const record = manifest as Record<string, unknown>;
|
|
246
|
+
const [major, minor] = checkManifestVersions(record);
|
|
247
|
+
if (major !== header.major || minor !== header.minor) {
|
|
248
|
+
throw badWire(
|
|
249
|
+
"wire",
|
|
250
|
+
`manifest says [${major}, ${minor}], the header says [${header.major}, ${header.minor}]`,
|
|
251
|
+
{
|
|
252
|
+
found: [major, minor],
|
|
253
|
+
header: [header.major, header.minor],
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return record;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Rebuild a snapshot from a GSNP container (design section 9.2). Defaults: `validate: "full"` and
|
|
262
|
+
* `copy: false`. The buffer region is adopted as the arena when the container is a plain
|
|
263
|
+
* ArrayBuffer viewed at a byteOffset that is a multiple of 8 (so f64 segments align); a
|
|
264
|
+
* SharedArrayBuffer, an odd offset or `copy: true` take the copy path into a fresh 256-aligned
|
|
265
|
+
* buffer.
|
|
266
|
+
* @param bytes - the container: a Uint8Array, an ArrayBuffer or a SharedArrayBuffer
|
|
267
|
+
* @param options - validation level, copy and unknown-column policy
|
|
268
|
+
* @returns the snapshot; E_BAD_SERIALIZATION / E_UNSUPPORTED_VERSION / E_UNSUPPORTED / E_INVALID_SNAPSHOT on bad input
|
|
269
|
+
*/
|
|
270
|
+
export function fromBytes(bytes: Uint8Array | ArrayBufferLike, options?: FromWireOptions): GraphSnapshot {
|
|
271
|
+
const resolved = resolveFromWireOptions(options, "full");
|
|
272
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
273
|
+
const header = readHeader(view, view.byteLength);
|
|
274
|
+
const manifest = readManifest(
|
|
275
|
+
view.subarray(CONTAINER_HEADER_BYTES, CONTAINER_HEADER_BYTES + header.manifestLength),
|
|
276
|
+
header,
|
|
277
|
+
);
|
|
278
|
+
const regionStart = alignUp(CONTAINER_HEADER_BYTES + header.manifestLength, ALIGNMENT);
|
|
279
|
+
const regionLength = Math.max(0, view.byteLength - regionStart);
|
|
280
|
+
const { buffer } = view;
|
|
281
|
+
let region: WireRegion;
|
|
282
|
+
if (!resolved.copy && !isSharedArrayBuffer(buffer) && view.byteOffset % 8 === 0) {
|
|
283
|
+
region = bufferRegion(buffer, view.byteOffset + regionStart, regionLength);
|
|
284
|
+
} else {
|
|
285
|
+
const copy = new Uint8Array(alignUp(regionLength, ALIGNMENT));
|
|
286
|
+
if (regionLength > 0) {
|
|
287
|
+
copy.set(view.subarray(regionStart));
|
|
288
|
+
}
|
|
289
|
+
region = bufferRegion(copy.buffer, 0, regionLength);
|
|
290
|
+
}
|
|
291
|
+
return decodeManifest(manifest, [region], resolved, true);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* A byte stream made of chunks, addressed by absolute offset: ranges inside one chunk are located
|
|
296
|
+
* for adoption, ranges spanning chunks are copied.
|
|
297
|
+
*/
|
|
298
|
+
class ChunkedBytes {
|
|
299
|
+
private readonly chunks: readonly Uint8Array[];
|
|
300
|
+
private readonly starts: number[];
|
|
301
|
+
/** The total byte length. */
|
|
302
|
+
readonly byteLength: number;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Wrap the chunks.
|
|
306
|
+
* @param chunks - the chunks in order
|
|
307
|
+
*/
|
|
308
|
+
constructor(chunks: readonly Uint8Array[]) {
|
|
309
|
+
this.chunks = chunks;
|
|
310
|
+
this.starts = [];
|
|
311
|
+
let total = 0;
|
|
312
|
+
for (const chunk of chunks) {
|
|
313
|
+
this.starts.push(total);
|
|
314
|
+
total += chunk.byteLength;
|
|
315
|
+
}
|
|
316
|
+
this.byteLength = total;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The chunk index holding an absolute offset (the last chunk starting at or before it).
|
|
321
|
+
* @param offset - the absolute offset
|
|
322
|
+
* @returns the index, or -1 when there is no chunk
|
|
323
|
+
*/
|
|
324
|
+
private chunkAt(offset: number): number {
|
|
325
|
+
let lo = 0;
|
|
326
|
+
let hi = this.starts.length - 1;
|
|
327
|
+
let found = -1;
|
|
328
|
+
while (lo <= hi) {
|
|
329
|
+
const mid = (lo + hi) >>> 1;
|
|
330
|
+
if (this.starts[mid] <= offset) {
|
|
331
|
+
found = mid;
|
|
332
|
+
lo = mid + 1;
|
|
333
|
+
} else {
|
|
334
|
+
hi = mid - 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return found;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Find a range inside one chunk over a plain ArrayBuffer.
|
|
342
|
+
* @param offset - the absolute start
|
|
343
|
+
* @param length - the byte length
|
|
344
|
+
* @returns the buffer and absolute offset, or null when the range spans chunks or a SharedArrayBuffer
|
|
345
|
+
*/
|
|
346
|
+
locate(offset: number, length: number): LocatedRange | null {
|
|
347
|
+
const i = this.chunkAt(offset);
|
|
348
|
+
if (i < 0) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const chunk = this.chunks[i];
|
|
352
|
+
const start = this.starts[i];
|
|
353
|
+
if (offset + length > start + chunk.byteLength || isSharedArrayBuffer(chunk.buffer)) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return { buffer: chunk.buffer, byteOffset: chunk.byteOffset + (offset - start) };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Copy a range out of the stream.
|
|
361
|
+
* @param offset - the absolute start
|
|
362
|
+
* @param length - the byte length
|
|
363
|
+
* @returns a fresh Uint8Array of `length` bytes at offset 0 of a padded buffer
|
|
364
|
+
*/
|
|
365
|
+
read(offset: number, length: number): U8 {
|
|
366
|
+
if (offset + length > this.byteLength) {
|
|
367
|
+
throw badWire("chunks", `range [${offset}, ${offset + length}) exceeds the ${this.byteLength} bytes given`);
|
|
368
|
+
}
|
|
369
|
+
const out = new Uint8Array(new ArrayBuffer(padTo4(length)), 0, length);
|
|
370
|
+
let written = 0;
|
|
371
|
+
let i = this.chunkAt(offset);
|
|
372
|
+
while (written < length && i < this.chunks.length) {
|
|
373
|
+
const chunk = this.chunks[i];
|
|
374
|
+
const start = this.starts[i];
|
|
375
|
+
const from = offset + written - start;
|
|
376
|
+
const take = Math.min(length - written, chunk.byteLength - from);
|
|
377
|
+
out.set(chunk.subarray(from, from + take), written);
|
|
378
|
+
written += take;
|
|
379
|
+
i++;
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A view when the range lies inside one chunk, a copy otherwise.
|
|
386
|
+
* @param offset - the absolute start
|
|
387
|
+
* @param length - the byte length
|
|
388
|
+
* @returns the bytes
|
|
389
|
+
*/
|
|
390
|
+
span(offset: number, length: number): Uint8Array {
|
|
391
|
+
const i = this.chunkAt(offset);
|
|
392
|
+
if (i >= 0) {
|
|
393
|
+
const chunk = this.chunks[i];
|
|
394
|
+
const start = this.starts[i];
|
|
395
|
+
if (offset + length <= start + chunk.byteLength) {
|
|
396
|
+
return chunk.subarray(offset - start, offset - start + length);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return this.read(offset, length);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The stream from `base` onward as a decoder region.
|
|
404
|
+
* @param base - the absolute offset of the region's start
|
|
405
|
+
* @returns the region
|
|
406
|
+
*/
|
|
407
|
+
region(base: number): WireRegion {
|
|
408
|
+
return {
|
|
409
|
+
byteLength: Math.max(0, this.byteLength - base),
|
|
410
|
+
locate: (byteOffset: number, byteLength: number): LocatedRange | null =>
|
|
411
|
+
this.locate(base + byteOffset, byteLength),
|
|
412
|
+
read: (byteOffset: number, byteLength: number): U8 => this.read(base + byteOffset, byteLength),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Rebuild a snapshot from the chunks `toByteChunks()` produced (or any split of a container's
|
|
419
|
+
* bytes): each segment is adopted as a view into the chunk that holds it (a segment spanning two
|
|
420
|
+
* chunks, or one in a SharedArrayBuffer, is copied) and the snapshot has `arena === null` (design
|
|
421
|
+
* section 9.2). Defaults: `validate: "full"`, `copy: false`.
|
|
422
|
+
* @param chunks - the chunks in order
|
|
423
|
+
* @param options - validation level, copy and unknown-column policy
|
|
424
|
+
* @returns the snapshot
|
|
425
|
+
*/
|
|
426
|
+
export function fromByteChunks(chunks: Iterable<Uint8Array>, options?: FromWireOptions): GraphSnapshot {
|
|
427
|
+
const resolved = resolveFromWireOptions(options, "full");
|
|
428
|
+
const list: Uint8Array[] = [];
|
|
429
|
+
for (const chunk of chunks) {
|
|
430
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
431
|
+
throw badWire(`chunks[${list.length}]`, "expected a Uint8Array");
|
|
432
|
+
}
|
|
433
|
+
list.push(resolved.copy ? chunk.slice() : chunk);
|
|
434
|
+
}
|
|
435
|
+
const bytes = new ChunkedBytes(list);
|
|
436
|
+
const header = readHeader(bytes.span(0, Math.min(CONTAINER_HEADER_BYTES, bytes.byteLength)), bytes.byteLength);
|
|
437
|
+
const manifest = readManifest(bytes.span(CONTAINER_HEADER_BYTES, header.manifestLength), header);
|
|
438
|
+
const regionStart = alignUp(CONTAINER_HEADER_BYTES + header.manifestLength, ALIGNMENT);
|
|
439
|
+
return decodeManifest(manifest, [bytes.region(regionStart)], resolved, false);
|
|
440
|
+
}
|