@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.
Files changed (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -43
  3. package/dist/graph-format.d.ts +1 -0
  4. package/dist/graph-format.d.ts.map +1 -0
  5. package/dist/graph-format.js +15798 -0
  6. package/dist/graph-format.js.map +1 -0
  7. package/dist/src/builder/arena.d.ts +85 -0
  8. package/dist/src/builder/arena.d.ts.map +1 -0
  9. package/dist/src/builder/arena.js +154 -0
  10. package/dist/src/builder/arena.js.map +1 -0
  11. package/dist/src/builder/compact.d.ts +515 -0
  12. package/dist/src/builder/compact.d.ts.map +1 -0
  13. package/dist/src/builder/compact.js +1553 -0
  14. package/dist/src/builder/compact.js.map +1 -0
  15. package/dist/src/builder/counting-sort.d.ts +96 -0
  16. package/dist/src/builder/counting-sort.d.ts.map +1 -0
  17. package/dist/src/builder/counting-sort.js +430 -0
  18. package/dist/src/builder/counting-sort.js.map +1 -0
  19. package/dist/src/builder/freeze.d.ts +72 -0
  20. package/dist/src/builder/freeze.d.ts.map +1 -0
  21. package/dist/src/builder/freeze.js +548 -0
  22. package/dist/src/builder/freeze.js.map +1 -0
  23. package/dist/src/builder/graph-builder.d.ts +602 -0
  24. package/dist/src/builder/graph-builder.d.ts.map +1 -0
  25. package/dist/src/builder/graph-builder.js +1940 -0
  26. package/dist/src/builder/graph-builder.js.map +1 -0
  27. package/dist/src/builder/options.d.ts +16 -0
  28. package/dist/src/builder/options.d.ts.map +1 -0
  29. package/dist/src/builder/options.js +21 -0
  30. package/dist/src/builder/options.js.map +1 -0
  31. package/dist/src/columns/bitmap.d.ts +96 -0
  32. package/dist/src/columns/bitmap.d.ts.map +1 -0
  33. package/dist/src/columns/bitmap.js +184 -0
  34. package/dist/src/columns/bitmap.js.map +1 -0
  35. package/dist/src/columns/column.d.ts +220 -0
  36. package/dist/src/columns/column.d.ts.map +1 -0
  37. package/dist/src/columns/column.js +2109 -0
  38. package/dist/src/columns/column.js.map +1 -0
  39. package/dist/src/columns/dictionary.d.ts +60 -0
  40. package/dist/src/columns/dictionary.d.ts.map +1 -0
  41. package/dist/src/columns/dictionary.js +102 -0
  42. package/dist/src/columns/dictionary.js.map +1 -0
  43. package/dist/src/columns/growable.d.ts +198 -0
  44. package/dist/src/columns/growable.d.ts.map +1 -0
  45. package/dist/src/columns/growable.js +296 -0
  46. package/dist/src/columns/growable.js.map +1 -0
  47. package/dist/src/columns/infer.d.ts +121 -0
  48. package/dist/src/columns/infer.d.ts.map +1 -0
  49. package/dist/src/columns/infer.js +268 -0
  50. package/dist/src/columns/infer.js.map +1 -0
  51. package/dist/src/columns/remap.d.ts +143 -0
  52. package/dist/src/columns/remap.d.ts.map +1 -0
  53. package/dist/src/columns/remap.js +588 -0
  54. package/dist/src/columns/remap.js.map +1 -0
  55. package/dist/src/columns/table.d.ts +184 -0
  56. package/dist/src/columns/table.d.ts.map +1 -0
  57. package/dist/src/columns/table.js +485 -0
  58. package/dist/src/columns/table.js.map +1 -0
  59. package/dist/src/constants.d.ts +34 -0
  60. package/dist/src/constants.d.ts.map +1 -0
  61. package/dist/src/constants.js +79 -0
  62. package/dist/src/constants.js.map +1 -0
  63. package/dist/src/errors.d.ts +76 -0
  64. package/dist/src/errors.d.ts.map +1 -0
  65. package/dist/src/errors.js +32 -0
  66. package/dist/src/errors.js.map +1 -0
  67. package/dist/src/ids/edge-id-index.d.ts +77 -0
  68. package/dist/src/ids/edge-id-index.d.ts.map +1 -0
  69. package/dist/src/ids/edge-id-index.js +155 -0
  70. package/dist/src/ids/edge-id-index.js.map +1 -0
  71. package/dist/src/ids/node-id-map.d.ts +280 -0
  72. package/dist/src/ids/node-id-map.d.ts.map +1 -0
  73. package/dist/src/ids/node-id-map.js +1139 -0
  74. package/dist/src/ids/node-id-map.js.map +1 -0
  75. package/dist/src/ids/string-store.d.ts +219 -0
  76. package/dist/src/ids/string-store.d.ts.map +1 -0
  77. package/dist/src/ids/string-store.js +526 -0
  78. package/dist/src/ids/string-store.js.map +1 -0
  79. package/dist/src/index.d.ts +30 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +29 -0
  82. package/dist/src/index.js.map +1 -0
  83. package/dist/src/populate/from-csr.d.ts +61 -0
  84. package/dist/src/populate/from-csr.d.ts.map +1 -0
  85. package/dist/src/populate/from-csr.js +706 -0
  86. package/dist/src/populate/from-csr.js.map +1 -0
  87. package/dist/src/populate/from-edge-arrays.d.ts +51 -0
  88. package/dist/src/populate/from-edge-arrays.d.ts.map +1 -0
  89. package/dist/src/populate/from-edge-arrays.js +217 -0
  90. package/dist/src/populate/from-edge-arrays.js.map +1 -0
  91. package/dist/src/populate/from-records.d.ts +51 -0
  92. package/dist/src/populate/from-records.d.ts.map +1 -0
  93. package/dist/src/populate/from-records.js +447 -0
  94. package/dist/src/populate/from-records.js.map +1 -0
  95. package/dist/src/snapshot/derived.d.ts +211 -0
  96. package/dist/src/snapshot/derived.d.ts.map +1 -0
  97. package/dist/src/snapshot/derived.js +1254 -0
  98. package/dist/src/snapshot/derived.js.map +1 -0
  99. package/dist/src/snapshot/graph-meta.d.ts +23 -0
  100. package/dist/src/snapshot/graph-meta.d.ts.map +1 -0
  101. package/dist/src/snapshot/graph-meta.js +159 -0
  102. package/dist/src/snapshot/graph-meta.js.map +1 -0
  103. package/dist/src/snapshot/graph-snapshot.d.ts +509 -0
  104. package/dist/src/snapshot/graph-snapshot.d.ts.map +1 -0
  105. package/dist/src/snapshot/graph-snapshot.js +1247 -0
  106. package/dist/src/snapshot/graph-snapshot.js.map +1 -0
  107. package/dist/src/snapshot/hash.d.ts +90 -0
  108. package/dist/src/snapshot/hash.d.ts.map +1 -0
  109. package/dist/src/snapshot/hash.js +228 -0
  110. package/dist/src/snapshot/hash.js.map +1 -0
  111. package/dist/src/snapshot/queries.d.ts +74 -0
  112. package/dist/src/snapshot/queries.d.ts.map +1 -0
  113. package/dist/src/snapshot/queries.js +131 -0
  114. package/dist/src/snapshot/queries.js.map +1 -0
  115. package/dist/src/snapshot/validate.d.ts +192 -0
  116. package/dist/src/snapshot/validate.d.ts.map +1 -0
  117. package/dist/src/snapshot/validate.js +990 -0
  118. package/dist/src/snapshot/validate.js.map +1 -0
  119. package/dist/src/snapshot/views.d.ts +274 -0
  120. package/dist/src/snapshot/views.d.ts.map +1 -0
  121. package/dist/src/snapshot/views.js +856 -0
  122. package/dist/src/snapshot/views.js.map +1 -0
  123. package/dist/src/types/builder.d.ts +456 -0
  124. package/dist/src/types/builder.d.ts.map +1 -0
  125. package/dist/src/types/builder.js +11 -0
  126. package/dist/src/types/builder.js.map +1 -0
  127. package/dist/src/types/columns.d.ts +615 -0
  128. package/dist/src/types/columns.d.ts.map +1 -0
  129. package/dist/src/types/columns.js +14 -0
  130. package/dist/src/types/columns.js.map +1 -0
  131. package/dist/src/types/index.d.ts +14 -0
  132. package/dist/src/types/index.d.ts.map +1 -0
  133. package/dist/src/types/index.js +11 -0
  134. package/dist/src/types/index.js.map +1 -0
  135. package/dist/src/types/internal.d.ts +172 -0
  136. package/dist/src/types/internal.d.ts.map +1 -0
  137. package/dist/src/types/internal.js +13 -0
  138. package/dist/src/types/internal.js.map +1 -0
  139. package/dist/src/types/snapshot.d.ts +801 -0
  140. package/dist/src/types/snapshot.d.ts.map +1 -0
  141. package/dist/src/types/snapshot.js +14 -0
  142. package/dist/src/types/snapshot.js.map +1 -0
  143. package/dist/src/types/wire.d.ts +196 -0
  144. package/dist/src/types/wire.d.ts.map +1 -0
  145. package/dist/src/types/wire.js +8 -0
  146. package/dist/src/types/wire.js.map +1 -0
  147. package/dist/src/util/mask.d.ts +55 -0
  148. package/dist/src/util/mask.d.ts.map +1 -0
  149. package/dist/src/util/mask.js +71 -0
  150. package/dist/src/util/mask.js.map +1 -0
  151. package/dist/src/util/options.d.ts +22 -0
  152. package/dist/src/util/options.d.ts.map +1 -0
  153. package/dist/src/util/options.js +49 -0
  154. package/dist/src/util/options.js.map +1 -0
  155. package/dist/src/util/shared-buffers.d.ts +42 -0
  156. package/dist/src/util/shared-buffers.d.ts.map +1 -0
  157. package/dist/src/util/shared-buffers.js +52 -0
  158. package/dist/src/util/shared-buffers.js.map +1 -0
  159. package/dist/src/util/typed-array.d.ts +205 -0
  160. package/dist/src/util/typed-array.d.ts.map +1 -0
  161. package/dist/src/util/typed-array.js +289 -0
  162. package/dist/src/util/typed-array.js.map +1 -0
  163. package/dist/src/wire/bytes.d.ts +69 -0
  164. package/dist/src/wire/bytes.d.ts.map +1 -0
  165. package/dist/src/wire/bytes.js +361 -0
  166. package/dist/src/wire/bytes.js.map +1 -0
  167. package/dist/src/wire/carried-views.d.ts +111 -0
  168. package/dist/src/wire/carried-views.d.ts.map +1 -0
  169. package/dist/src/wire/carried-views.js +286 -0
  170. package/dist/src/wire/carried-views.js.map +1 -0
  171. package/dist/src/wire/from-wire.d.ts +144 -0
  172. package/dist/src/wire/from-wire.d.ts.map +1 -0
  173. package/dist/src/wire/from-wire.js +1425 -0
  174. package/dist/src/wire/from-wire.js.map +1 -0
  175. package/dist/src/wire/to-wire.d.ts +171 -0
  176. package/dist/src/wire/to-wire.d.ts.map +1 -0
  177. package/dist/src/wire/to-wire.js +652 -0
  178. package/dist/src/wire/to-wire.js.map +1 -0
  179. package/dist/tsconfig.build.tsbuildinfo +1 -0
  180. package/package.json +72 -7
  181. package/src/builder/arena.ts +192 -0
  182. package/src/builder/compact.ts +1811 -0
  183. package/src/builder/counting-sort.ts +506 -0
  184. package/src/builder/freeze.ts +689 -0
  185. package/src/builder/graph-builder.ts +2264 -0
  186. package/src/builder/options.ts +29 -0
  187. package/src/columns/bitmap.ts +196 -0
  188. package/src/columns/column.ts +2574 -0
  189. package/src/columns/dictionary.ts +114 -0
  190. package/src/columns/growable.ts +350 -0
  191. package/src/columns/infer.ts +285 -0
  192. package/src/columns/remap.ts +663 -0
  193. package/src/columns/table.ts +568 -0
  194. package/src/constants.ts +89 -0
  195. package/src/errors.ts +115 -0
  196. package/src/ids/edge-id-index.ts +179 -0
  197. package/src/ids/node-id-map.ts +1340 -0
  198. package/src/ids/string-store.ts +589 -0
  199. package/src/index.ts +139 -0
  200. package/src/lib-resizable-array-buffer.d.ts +34 -0
  201. package/src/populate/from-csr.ts +802 -0
  202. package/src/populate/from-edge-arrays.ts +257 -0
  203. package/src/populate/from-records.ts +536 -0
  204. package/src/snapshot/derived.ts +1486 -0
  205. package/src/snapshot/graph-meta.ts +167 -0
  206. package/src/snapshot/graph-snapshot.ts +1503 -0
  207. package/src/snapshot/hash.ts +241 -0
  208. package/src/snapshot/queries.ts +136 -0
  209. package/src/snapshot/validate.ts +1108 -0
  210. package/src/snapshot/views.ts +943 -0
  211. package/src/types/builder.ts +480 -0
  212. package/src/types/columns.ts +727 -0
  213. package/src/types/index.ts +14 -0
  214. package/src/types/internal.ts +195 -0
  215. package/src/types/snapshot.ts +886 -0
  216. package/src/types/wire.ts +207 -0
  217. package/src/util/mask.ts +89 -0
  218. package/src/util/options.ts +57 -0
  219. package/src/util/shared-buffers.ts +56 -0
  220. package/src/util/typed-array.ts +371 -0
  221. package/src/wire/bytes.ts +440 -0
  222. package/src/wire/carried-views.ts +327 -0
  223. package/src/wire/from-wire.ts +1690 -0
  224. 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
+ }