@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,371 @@
1
+ /**
2
+ * Low-level typed-array helpers shared by every module of @graphty/graph-format (design sections 5.7,
3
+ * 6.2, 9.5, 10.2 and 10.3): rounding and alignment arithmetic, arena segment layout, the 4-byte
4
+ * padding rules of I10, the u8 adoption predicate, the wire buffer-range check, per-dtype
5
+ * typed-array constructors, and the growth primitive of the builder staging (doubling into a
6
+ * resizable ArrayBuffer when the engine has one, allocate-and-copy doubling otherwise).
7
+ *
8
+ * Nothing here knows about graphs; everything is arithmetic over byte lengths and typed arrays.
9
+ */
10
+
11
+ import { ALIGNMENT } from "../constants.js";
12
+ import { GraphFormatError } from "../errors.js";
13
+ import { type Dtype, type TypedArrayData, type U8, type U32 } from "../types/index.js";
14
+
15
+ // ============================================================ resizable ArrayBuffer detection
16
+
17
+ /**
18
+ * Probe once whether this engine supports resizable ArrayBuffer (ES2024: Node 20+, Chrome 111+,
19
+ * Safari 16.4+, Firefox 128+). The probe constructs a tiny resizable buffer and grows it; any throw or
20
+ * missing member means "not supported", in which case staging grows by allocate-and-copy (design
21
+ * section 6.2).
22
+ * @returns true when `new ArrayBuffer(n, { maxByteLength })` and `resize()` work
23
+ */
24
+ function detectResizableArrayBuffer(): boolean {
25
+ try {
26
+ const probe = new ArrayBuffer(0, { maxByteLength: 64 });
27
+ if (!probe.resizable || typeof probe.resize !== "function") {
28
+ return false;
29
+ }
30
+ probe.resize(64);
31
+ return probe.byteLength === 64 && probe.maxByteLength === 64;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Whether resizable ArrayBuffer is available, detected once at module load (design section 6.2). The
39
+ * growth helpers take it as their default; tests pass an explicit value to exercise both paths.
40
+ */
41
+ export const HAS_RESIZABLE_ARRAY_BUFFER: boolean = detectResizableArrayBuffer();
42
+
43
+ // ============================================================ staging growth constants
44
+
45
+ /** Staging capacities are rounded up to this many elements (design section 6.2). */
46
+ export const STAGING_ELEMENT_GRANULE = 16;
47
+
48
+ /** Staging backing buffers are a multiple of this many bytes (design section 6.2). */
49
+ export const STAGING_BYTE_GRANULE = 64;
50
+
51
+ /**
52
+ * Virtual address space reserved (`maxByteLength`) for a fresh resizable staging buffer: 256 MiB, so
53
+ * a staging array grows in place without copying up to 64M u32 elements. Reservations are
54
+ * address-space only (pages are committed as the buffer grows), and a buffer that outgrows its
55
+ * reservation is reallocated with a doubled one, so the constant bounds the number of copies rather
56
+ * than the size of anything.
57
+ */
58
+ export const STAGING_RESERVE_BYTES = 256 * 1024 * 1024;
59
+
60
+ // ============================================================ rounding and alignment
61
+
62
+ /**
63
+ * Round `value` up to the next multiple of `multiple`. Written with `%`, not bit masks, because arc
64
+ * counts and byte lengths exceed 2^31 (invariant I3, design section 10.6).
65
+ * @param value - a non-negative integer
66
+ * @param multiple - a positive integer
67
+ * @returns the smallest multiple of `multiple` that is >= value
68
+ */
69
+ export function roundUp(value: number, multiple: number): number {
70
+ const remainder = value % multiple;
71
+ return remainder === 0 ? value : value + multiple - remainder;
72
+ }
73
+
74
+ /**
75
+ * Pad a byte length to the next multiple of 4: the size of the padded u32 view of a u8 column and
76
+ * the rule every GPU-bound array satisfies (design sections 5.7 and 10.2).
77
+ * @param byteLength - a non-negative byte count
78
+ * @returns roundUp(byteLength, 4)
79
+ */
80
+ export function padTo4(byteLength: number): number {
81
+ return roundUp(byteLength, 4);
82
+ }
83
+
84
+ /**
85
+ * The number of u32 words that span a byte range: ceil(byteLength / 4) (design section 10.2).
86
+ * @param byteLength - a non-negative byte count
87
+ * @returns the word count of the padded u32 view
88
+ */
89
+ export function paddedWordCount(byteLength: number): number {
90
+ return Math.ceil(byteLength / 4);
91
+ }
92
+
93
+ /**
94
+ * Align a byte offset up to the next arena segment boundary (design section 10.3; 256 bytes, the
95
+ * WebGPU minStorageBufferOffsetAlignment default).
96
+ * @param byteOffset - a non-negative byte offset
97
+ * @param alignment - the alignment in bytes; defaults to ALIGNMENT (256)
98
+ * @returns roundUp(byteOffset, alignment)
99
+ */
100
+ export function alignUp(byteOffset: number, alignment: number = ALIGNMENT): number {
101
+ return roundUp(byteOffset, alignment);
102
+ }
103
+
104
+ // ============================================================ arena segment layout
105
+
106
+ /**
107
+ * The result of laying out consecutive segments at aligned offsets (design section 10.3).
108
+ */
109
+ interface SegmentLayout {
110
+ /**
111
+ * One entry per input length: the segment's byte offset relative to the start of the arena, or null for
112
+ * a zero-length segment, which occupies nothing.
113
+ */
114
+ readonly offsets: readonly (number | null)[];
115
+ /** One entry per input length: offset + length, or null for a zero-length segment. */
116
+ readonly ends: readonly (number | null)[];
117
+ /** The end of the last non-empty segment; 0 when every segment is empty. No trailing padding is added. */
118
+ readonly byteLength: number;
119
+ /** The bytes spent on inter-segment padding. */
120
+ readonly padding: number;
121
+ }
122
+
123
+ /**
124
+ * Lay out segments back to back, each non-empty one starting at a multiple of `alignment`, in the
125
+ * given order (design section 10.3: hot to cold). Zero-length segments get a null offset and occupy
126
+ * nothing, which is how an absent `weights`, a zero-arc `colIdx` and an identity permutation stay out
127
+ * of the arena. For the worked example of section 10.3 (400,004 / 8,000,000 / 8,000,000 / 8,000,000 /
128
+ * 4,000,000) the offsets are 0 / 400,128 / 8,400,128 / 16,400,128 / 24,400,128 and the total is
129
+ * 28,400,128 with 124 bytes of padding.
130
+ * @param byteLengths - the unpadded byte length of every segment in arena order (0 = absent)
131
+ * @param alignment - the segment alignment in bytes; defaults to ALIGNMENT (256)
132
+ * @returns the offsets, ends, total byte length and padding of the layout
133
+ */
134
+ export function layoutSegments(byteLengths: readonly number[], alignment: number = ALIGNMENT): SegmentLayout {
135
+ const offsets: (number | null)[] = [];
136
+ const ends: (number | null)[] = [];
137
+ let cursor = 0;
138
+ let padding = 0;
139
+ for (const byteLength of byteLengths) {
140
+ if (byteLength === 0) {
141
+ offsets.push(null);
142
+ ends.push(null);
143
+ continue;
144
+ }
145
+ const offset = alignUp(cursor, alignment);
146
+ padding += offset - cursor;
147
+ offsets.push(offset);
148
+ cursor = offset + byteLength;
149
+ ends.push(cursor);
150
+ }
151
+ return { offsets, ends, byteLength: cursor, padding };
152
+ }
153
+
154
+ // ============================================================ 4-byte rules and adoption (I10, 5.7)
155
+
156
+ /**
157
+ * The I10 predicate for a GPU-bound array: a view whose byteOffset and byteLength are both multiples
158
+ * of 4 (design section 10.2).
159
+ * @param view - any ArrayBufferView
160
+ * @returns true when both byteOffset and byteLength are multiples of 4
161
+ */
162
+ export function isFourByteAligned(view: ArrayBufferView): boolean {
163
+ return view.byteOffset % 4 === 0 && view.byteLength % 4 === 0;
164
+ }
165
+
166
+ /**
167
+ * The "plain ArrayBuffer" half of invariant I10 (decision D-SAB, design section 9.4): the view's
168
+ * buffer is an `ArrayBuffer` that is neither a `SharedArrayBuffer` nor resizable, so its bytes are a
169
+ * `BufferSource` for `GPUQueue.writeBuffer`, transferable, and of fixed length for the life of the
170
+ * snapshot (invariant I17).
171
+ * @param view - any ArrayBufferView
172
+ * @returns true when the buffer is a plain, fixed-length ArrayBuffer
173
+ */
174
+ export function isOverPlainBuffer(view: ArrayBufferView): boolean {
175
+ const { buffer } = view;
176
+ return buffer instanceof ArrayBuffer && !buffer.resizable;
177
+ }
178
+
179
+ /**
180
+ * The u8 adoption predicate of design section 5.7: "a zero-copy padded Uint32Array view is
181
+ * constructible", i.e. `byteOffset % 4 === 0 && byteOffset + roundUp(byteLength, 4) <=
182
+ * buffer.byteLength`.
183
+ * @param data - the u8 array a column would adopt
184
+ * @returns true when paddedU32View(data) can be built without copying
185
+ */
186
+ export function canViewAsPaddedU32(data: U8): boolean {
187
+ return data.byteOffset % 4 === 0 && data.byteOffset + padTo4(data.byteLength) <= data.buffer.byteLength;
188
+ }
189
+
190
+ /**
191
+ * The zero-copy Uint32Array of ceil(byteLength / 4) words over a u8 array's own byte range (design
192
+ * sections 5.7 and 10.2); the trailing lanes of the last word are whatever the buffer holds there.
193
+ * @param data - a u8 array satisfying canViewAsPaddedU32()
194
+ * @returns the padded u32 view; E_COLUMN_ALIGNMENT when the view is not constructible
195
+ */
196
+ export function paddedU32View(data: U8): U32 {
197
+ if (!canViewAsPaddedU32(data)) {
198
+ throw new GraphFormatError(
199
+ "E_COLUMN_ALIGNMENT",
200
+ `u8 array at byteOffset ${data.byteOffset} with byteLength ${data.byteLength} has no zero-copy padded u32 view`,
201
+ { byteOffset: data.byteOffset, byteLength: data.byteLength, bufferByteLength: data.buffer.byteLength },
202
+ );
203
+ }
204
+ return new Uint32Array(data.buffer, data.byteOffset, paddedWordCount(data.byteLength));
205
+ }
206
+
207
+ /**
208
+ * Copy a u8 array into a fresh store over which the padded u32 view is constructible: a new
209
+ * ArrayBuffer of roundUp(length, 4) bytes viewed at offset 0 with the original length (design section
210
+ * 5.7, the `adopt: "copy"` path). The padding bytes are zero.
211
+ * @param data - the u8 array to copy
212
+ * @returns a new U8 of the same length whose store satisfies canViewAsPaddedU32()
213
+ */
214
+ export function copyToPaddedStore(data: U8): U8 {
215
+ const store = new Uint8Array(new ArrayBuffer(padTo4(data.byteLength)), 0, data.byteLength);
216
+ store.set(data);
217
+ return store;
218
+ }
219
+
220
+ // ============================================================ per-dtype typed arrays
221
+
222
+ /**
223
+ * The dtypes whose storage is one flat typed array: the five numeric dtypes plus bool (packed u32
224
+ * words) and dict (u32 codes). string, list and json have offsets, children or JS values instead.
225
+ */
226
+ export type TypedDtype = Exclude<Dtype, "string" | "list" | "json">;
227
+
228
+ /**
229
+ * A typed-array class as the helpers use it: BYTES_PER_ELEMENT plus the (buffer, byteOffset, length)
230
+ * constructor. Every concrete typed-array constructor satisfies it with T fixed to its
231
+ * ArrayBuffer-parameterised instance type.
232
+ */
233
+ export interface TypedArrayCtor<T extends TypedArrayData> {
234
+ /** Bytes per element. */
235
+ readonly BYTES_PER_ELEMENT: number;
236
+ /**
237
+ * Construct a view of `length` elements over `buffer` starting at `byteOffset`.
238
+ * @param buffer - the backing buffer
239
+ * @param byteOffset - the start of the view in bytes
240
+ * @param length - the element count of the view
241
+ */
242
+ new (buffer: ArrayBuffer, byteOffset: number, length: number): T;
243
+ }
244
+
245
+ /**
246
+ * Throw E_UNSUPPORTED for a dtype value outside the union; the `default` branch of every dtype switch
247
+ * (design section 13.4) calls this so an unknown dtype coming from untrusted data fails loudly. Always
248
+ * throws.
249
+ * @param dtype - the value that reached the default branch
250
+ */
251
+ export function unsupportedDtype(dtype: never): never {
252
+ throw new GraphFormatError("E_UNSUPPORTED", `unsupported dtype ${String(dtype)}`, { dtype });
253
+ }
254
+
255
+ /**
256
+ * The typed-array class that stores a flat dtype (design section 5.1): Float32Array for f32,
257
+ * Float64Array for f64, Int32Array for i32, Uint8Array for u8, and Uint32Array for u32, for the packed
258
+ * words of bool and for the codes of dict.
259
+ * @param dtype - a flat dtype
260
+ * @returns the constructor; E_UNSUPPORTED for an unknown dtype
261
+ */
262
+ export function typedArrayCtorOf(dtype: TypedDtype): TypedArrayCtor<TypedArrayData> {
263
+ switch (dtype) {
264
+ case "f32":
265
+ return Float32Array;
266
+ case "f64":
267
+ return Float64Array;
268
+ case "i32":
269
+ return Int32Array;
270
+ case "u8":
271
+ return Uint8Array;
272
+ case "u32":
273
+ case "bool":
274
+ case "dict":
275
+ return Uint32Array;
276
+ default:
277
+ return unsupportedDtype(dtype);
278
+ }
279
+ }
280
+
281
+ // ============================================================ staging growth
282
+
283
+ /**
284
+ * The next staging capacity, in elements, that holds at least `needed`: doubling from `current`,
285
+ * rounded up to STAGING_ELEMENT_GRANULE (design section 6.2). A capacity of 0 grows to the granule.
286
+ * @param current - the present capacity in elements
287
+ * @param needed - the minimum capacity required
288
+ * @returns the new capacity, >= needed, >= 2 * current, a multiple of 16
289
+ */
290
+ export function growCapacity(current: number, needed: number): number {
291
+ return roundUp(Math.max(needed, current * 2, STAGING_ELEMENT_GRANULE), STAGING_ELEMENT_GRANULE);
292
+ }
293
+
294
+ /**
295
+ * The byte length of a staging backing buffer for `capacity` elements of `bytesPerElement` bytes each:
296
+ * rounded up to STAGING_BYTE_GRANULE (design section 6.2). Because every element size divides 64 the
297
+ * result is always a whole number of elements.
298
+ * @param capacity - the capacity in elements
299
+ * @param bytesPerElement - the element size in bytes (1, 4 or 8)
300
+ * @returns the buffer byte length, a multiple of 64
301
+ */
302
+ export function stagingByteLength(capacity: number, bytesPerElement: number): number {
303
+ return roundUp(capacity * bytesPerElement, STAGING_BYTE_GRANULE);
304
+ }
305
+
306
+ /**
307
+ * Allocate a staging buffer of `byteLength` bytes. With `resizable` true the buffer is created with a
308
+ * `maxByteLength` of at least `reserveBytes` (and at least `byteLength`) so it can later grow in place;
309
+ * should the engine refuse that reservation with a RangeError (an address-space limit, not a missing
310
+ * capability) the buffer is created resizable with `maxByteLength === byteLength`, which simply means
311
+ * its next growth reallocates. With `resizable` false a plain fixed-length buffer is returned.
312
+ * @param byteLength - the initial byte length
313
+ * @param resizable - whether to create a resizable buffer; defaults to HAS_RESIZABLE_ARRAY_BUFFER
314
+ * @param reserveBytes - the address space to reserve for in-place growth; defaults to STAGING_RESERVE_BYTES
315
+ * @returns the new buffer
316
+ */
317
+ export function allocateStagingBuffer(
318
+ byteLength: number,
319
+ resizable: boolean = HAS_RESIZABLE_ARRAY_BUFFER,
320
+ reserveBytes: number = STAGING_RESERVE_BYTES,
321
+ ): ArrayBuffer {
322
+ if (!resizable) {
323
+ return new ArrayBuffer(byteLength);
324
+ }
325
+ const maxByteLength = Math.max(byteLength, reserveBytes);
326
+ try {
327
+ return new ArrayBuffer(byteLength, { maxByteLength });
328
+ } catch (err) {
329
+ if (err instanceof RangeError && maxByteLength > byteLength) {
330
+ return new ArrayBuffer(byteLength, { maxByteLength: byteLength });
331
+ }
332
+ throw err;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Grow a staging array so it holds at least `minLength` elements, preserving its contents (design
338
+ * section 6.2). When the array is the leading view of a resizable buffer with room left, the buffer is
339
+ * resized in place and no bytes move; otherwise a new buffer is allocated (resizable per `resizable`,
340
+ * with a reservation of at least twice the old one so repeated overflow stays amortised) and the
341
+ * contents copied. Either way the returned array is a fresh view object of exactly the new capacity;
342
+ * the caller replaces its reference. Elements beyond the old length are zero on the copy path and on a
343
+ * freshly resized region.
344
+ * @param array - the current staging array, viewed from byteOffset 0 of its buffer
345
+ * @param ctor - the array's constructor
346
+ * @param minLength - the minimum element count required
347
+ * @param resizable - whether new buffers are resizable; defaults to HAS_RESIZABLE_ARRAY_BUFFER
348
+ * @returns a view of growCapacity(array.length, minLength) elements holding the old contents
349
+ */
350
+ export function growTypedArray<T extends TypedArrayData>(
351
+ array: T,
352
+ ctor: TypedArrayCtor<T>,
353
+ minLength: number,
354
+ resizable: boolean = HAS_RESIZABLE_ARRAY_BUFFER,
355
+ ): T {
356
+ if (minLength <= array.length) {
357
+ return array;
358
+ }
359
+ const bytesPerElement = ctor.BYTES_PER_ELEMENT;
360
+ const byteLength = stagingByteLength(growCapacity(array.length, minLength), bytesPerElement);
361
+ const capacity = byteLength / bytesPerElement;
362
+ const { buffer } = array;
363
+ if (buffer.resizable && array.byteOffset === 0 && byteLength <= buffer.maxByteLength) {
364
+ buffer.resize(byteLength);
365
+ return new ctor(buffer, 0, capacity);
366
+ }
367
+ const reserve = Math.max(buffer.maxByteLength * 2, STAGING_RESERVE_BYTES);
368
+ const next = new ctor(allocateStagingBuffer(byteLength, resizable, reserve), 0, capacity);
369
+ next.set(array);
370
+ return next;
371
+ }