@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,990 @@
1
+ /**
2
+ * `validate()` (design sections 3.2, 9.5 and 11.4): the invariants I1-I13 as one function each, the
3
+ * two levels "structure" (O(n + m + columns): lengths, ranges, counts, alignment, column length
4
+ * rules) and "full" (O(m log d): sortedness, orientation, pairing, NaN, flag recomputation, id
5
+ * bijection, unique columns), and the flag predicates of design section 3.8 shared with the derived
6
+ * graph core builder so that every flag has exactly one definition.
7
+ *
8
+ * Every check throws `E_INVALID_SNAPSHOT` on the first violation with `details.invariant` naming the
9
+ * number ("I4") and the location (`row`, `arc`, `edge`, `column`, `table`, `flag`, `reason`). The
10
+ * functions take the public `GraphSnapshot` contract, so test/invariants.test.ts runs the same
11
+ * helpers over hand-written fixtures. Checksum comparison needs the snapshot's private records and
12
+ * lives in graph-snapshot.ts.
13
+ */
14
+ import { bitmapCount, bitmapGet } from "../columns/bitmap.js";
15
+ import { verifyUniqueColumn } from "../columns/table.js";
16
+ import { ALIGNMENT, INVALID_INDEX, MAX_COUNT } from "../constants.js";
17
+ import { GraphFormatError } from "../errors.js";
18
+ import { isFourByteAligned, isOverPlainBuffer } from "../util/typed-array.js";
19
+ // ============================================================ errors
20
+ /**
21
+ * Build the E_INVALID_SNAPSHOT error of one violated invariant.
22
+ * @param invariant - the invariant number, e.g. "I4"
23
+ * @param message - a plain-ASCII description naming the location
24
+ * @param details - the location (row, arc, edge, column, ...); `invariant` is added
25
+ * @returns the error
26
+ */
27
+ export function invariantViolation(invariant, message, details = {}) {
28
+ return new GraphFormatError("E_INVALID_SNAPSHOT", `invariant ${invariant} violated: ${message}`, {
29
+ invariant,
30
+ ...details,
31
+ });
32
+ }
33
+ /**
34
+ * Whether some row holds two arcs with equal colIdx (parallel edges); adjacent by invariant I4.
35
+ * @param rowPtr - the row offsets
36
+ * @param colIdx - the sorted targets
37
+ * @param nodeCount - the node count
38
+ * @returns true for a multigraph
39
+ */
40
+ function hasParallelArcs(rowPtr, colIdx, nodeCount) {
41
+ for (let u = 0; u < nodeCount; u++) {
42
+ const end = rowPtr[u + 1];
43
+ for (let a = rowPtr[u] + 1; a < end; a++) {
44
+ if (colIdx[a] === colIdx[a - 1]) {
45
+ return true;
46
+ }
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ /**
52
+ * The number of arcs a with colIdx[a] === row(a).
53
+ * @param rowPtr - the row offsets
54
+ * @param colIdx - the sorted targets
55
+ * @param nodeCount - the node count
56
+ * @returns the loop arc count
57
+ */
58
+ export function countLoopArcs(rowPtr, colIdx, nodeCount) {
59
+ let loops = 0;
60
+ for (let u = 0; u < nodeCount; u++) {
61
+ const end = rowPtr[u + 1];
62
+ for (let a = rowPtr[u]; a < end; a++) {
63
+ if (colIdx[a] === u) {
64
+ loops++;
65
+ }
66
+ }
67
+ }
68
+ return loops;
69
+ }
70
+ /**
71
+ * Whether a permutation array is the identity.
72
+ * @param perm - the array
73
+ * @returns true when perm[i] === i for every i
74
+ */
75
+ export function isIdentity(perm) {
76
+ for (let i = 0; i < perm.length; i++) {
77
+ if (perm[i] !== i) {
78
+ return false;
79
+ }
80
+ }
81
+ return true;
82
+ }
83
+ /**
84
+ * Compute every flag of design section 3.8 from the arrays (invariant I9: never guessed).
85
+ * @param core - the arrays and counts the predicates read
86
+ * @returns the truthful flags
87
+ */
88
+ export function computeFlags(core) {
89
+ const { directed, nodeCount, rowPtr, colIdx, weights, arcToEdge } = core;
90
+ let allWeightsOne = true;
91
+ let nonNegativeWeights = true;
92
+ let finiteWeights = true;
93
+ if (weights !== null) {
94
+ for (let a = 0; a < weights.length; a++) {
95
+ const w = weights[a];
96
+ if (w !== 1) {
97
+ allWeightsOne = false;
98
+ }
99
+ if (!(w >= 0)) {
100
+ nonNegativeWeights = false;
101
+ }
102
+ if (!Number.isFinite(w)) {
103
+ finiteWeights = false;
104
+ }
105
+ }
106
+ }
107
+ return {
108
+ multigraph: hasParallelArcs(rowPtr, colIdx, nodeCount),
109
+ hasSelfLoops: core.selfLoopCount > 0,
110
+ arcToEdgeIsIdentity: directed && (arcToEdge === null || isIdentity(arcToEdge)),
111
+ weighted: weights !== null,
112
+ allWeightsOne,
113
+ nonNegativeWeights,
114
+ finiteWeights,
115
+ };
116
+ }
117
+ // ============================================================ I1 - I3: structure
118
+ /**
119
+ * I1: rowPtr has nodeCount + 1 entries, starts at 0, is non-decreasing and ends at arcCount ===
120
+ * colIdx.length.
121
+ * @param s - the snapshot
122
+ */
123
+ export function checkI1(s) {
124
+ const { rowPtr, colIdx, nodeCount, arcCount } = s;
125
+ if (rowPtr.length !== nodeCount + 1) {
126
+ throw invariantViolation("I1", `rowPtr has ${rowPtr.length} entries, expected ${nodeCount + 1}`, {
127
+ expected: nodeCount + 1,
128
+ found: rowPtr.length,
129
+ });
130
+ }
131
+ if (rowPtr[0] !== 0) {
132
+ throw invariantViolation("I1", `rowPtr[0] is ${rowPtr[0]}, expected 0`, { row: 0, found: rowPtr[0] });
133
+ }
134
+ for (let u = 0; u < nodeCount; u++) {
135
+ if (rowPtr[u + 1] < rowPtr[u]) {
136
+ throw invariantViolation("I1", `rowPtr decreases at row ${u}: ${rowPtr[u]} -> ${rowPtr[u + 1]}`, {
137
+ row: u,
138
+ });
139
+ }
140
+ }
141
+ if (rowPtr[nodeCount] !== arcCount || colIdx.length !== arcCount) {
142
+ throw invariantViolation("I1", `rowPtr[${nodeCount}] = ${rowPtr[nodeCount]}, colIdx.length = ${colIdx.length}, arcCount = ${arcCount}`, { row: nodeCount, found: rowPtr[nodeCount], expected: arcCount, colIdxLength: colIdx.length });
143
+ }
144
+ }
145
+ /**
146
+ * I2: every colIdx entry is below nodeCount (so INVALID_INDEX never appears).
147
+ * @param s - the snapshot
148
+ */
149
+ export function checkI2(s) {
150
+ const { colIdx, nodeCount } = s;
151
+ for (let a = 0; a < colIdx.length; a++) {
152
+ if (colIdx[a] >= nodeCount) {
153
+ throw invariantViolation("I2", `colIdx[${a}] = ${colIdx[a]} is not below nodeCount ${nodeCount}`, {
154
+ arc: a,
155
+ found: colIdx[a],
156
+ });
157
+ }
158
+ }
159
+ }
160
+ /**
161
+ * I3: the counts are non-negative integers at most MAX_COUNT, edgeCount <= arcCount and selfLoopCount
162
+ * <= edgeCount.
163
+ * @param s - the snapshot
164
+ */
165
+ export function checkI3(s) {
166
+ const counts = [
167
+ ["nodeCount", s.nodeCount],
168
+ ["edgeCount", s.edgeCount],
169
+ ["arcCount", s.arcCount],
170
+ ["selfLoopCount", s.selfLoopCount],
171
+ ];
172
+ for (const [name, value] of counts) {
173
+ if (!Number.isInteger(value) || value < 0 || value > MAX_COUNT) {
174
+ throw invariantViolation("I3", `${name} = ${value} is not an integer in [0, MAX_COUNT]`, {
175
+ count: name,
176
+ found: value,
177
+ });
178
+ }
179
+ }
180
+ if (s.edgeCount > s.arcCount) {
181
+ throw invariantViolation("I3", `edgeCount ${s.edgeCount} exceeds arcCount ${s.arcCount}`, {
182
+ edgeCount: s.edgeCount,
183
+ arcCount: s.arcCount,
184
+ });
185
+ }
186
+ if (s.selfLoopCount > s.edgeCount) {
187
+ throw invariantViolation("I3", `selfLoopCount ${s.selfLoopCount} exceeds edgeCount ${s.edgeCount}`, {
188
+ selfLoopCount: s.selfLoopCount,
189
+ edgeCount: s.edgeCount,
190
+ });
191
+ }
192
+ }
193
+ // ============================================================ I4 - I8: rows, permutations, pairing, weights
194
+ /**
195
+ * I4: within every row colIdx is non-decreasing and arcs with equal colIdx are ordered by ascending
196
+ * arcToEdge. Full level.
197
+ * @param s - the snapshot
198
+ */
199
+ export function checkI4(s) {
200
+ const { rowPtr, colIdx, nodeCount } = s;
201
+ const identity = s.flags.arcToEdgeIsIdentity;
202
+ const arcToEdge = identity ? null : s.arcToEdge;
203
+ for (let u = 0; u < nodeCount; u++) {
204
+ const end = rowPtr[u + 1];
205
+ for (let a = rowPtr[u] + 1; a < end; a++) {
206
+ if (colIdx[a] < colIdx[a - 1]) {
207
+ throw invariantViolation("I4", `row ${u} is not sorted at arc ${a}`, { row: u, arc: a });
208
+ }
209
+ if (colIdx[a] === colIdx[a - 1] && arcToEdge !== null && arcToEdge[a] <= arcToEdge[a - 1]) {
210
+ throw invariantViolation("I4", `parallel arcs ${a - 1} and ${a} in row ${u} are not in edge order`, {
211
+ row: u,
212
+ arc: a,
213
+ });
214
+ }
215
+ }
216
+ }
217
+ }
218
+ /**
219
+ * I5 (structure part): arcToEdge has arcCount entries below edgeCount and edgeToArc has edgeCount
220
+ * entries below arcCount; an identity flag requires arcCount === edgeCount and a directed snapshot.
221
+ * @param s - the snapshot
222
+ */
223
+ export function checkI5Ranges(s) {
224
+ const { arcCount, edgeCount } = s;
225
+ if (s.flags.arcToEdgeIsIdentity) {
226
+ if (!s.directed || arcCount !== edgeCount) {
227
+ throw invariantViolation("I5", "identity permutation claimed on a snapshot where it cannot hold", {
228
+ directed: s.directed,
229
+ arcCount,
230
+ edgeCount,
231
+ });
232
+ }
233
+ return;
234
+ }
235
+ const { arcToEdge, edgeToArc } = s;
236
+ if (arcToEdge.length !== arcCount) {
237
+ throw invariantViolation("I5", `arcToEdge has ${arcToEdge.length} entries, expected ${arcCount}`, {
238
+ expected: arcCount,
239
+ found: arcToEdge.length,
240
+ });
241
+ }
242
+ if (edgeToArc.length !== edgeCount) {
243
+ throw invariantViolation("I5", `edgeToArc has ${edgeToArc.length} entries, expected ${edgeCount}`, {
244
+ expected: edgeCount,
245
+ found: edgeToArc.length,
246
+ });
247
+ }
248
+ for (let a = 0; a < arcCount; a++) {
249
+ if (arcToEdge[a] >= edgeCount) {
250
+ throw invariantViolation("I5", `arcToEdge[${a}] = ${arcToEdge[a]} is not below edgeCount ${edgeCount}`, {
251
+ arc: a,
252
+ found: arcToEdge[a],
253
+ });
254
+ }
255
+ }
256
+ for (let e = 0; e < edgeCount; e++) {
257
+ if (edgeToArc[e] >= arcCount) {
258
+ throw invariantViolation("I5", `edgeToArc[${e}] = ${edgeToArc[e]} is not below arcCount ${arcCount}`, {
259
+ edge: e,
260
+ found: edgeToArc[e],
261
+ });
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * I5 (orientation part): arcToEdge[edgeToArc[e]] === e for every edge. Full level.
267
+ * @param s - the snapshot
268
+ */
269
+ export function checkI5Orientation(s) {
270
+ if (s.flags.arcToEdgeIsIdentity) {
271
+ return;
272
+ }
273
+ const { arcToEdge, edgeToArc, edgeCount } = s;
274
+ for (let e = 0; e < edgeCount; e++) {
275
+ if (arcToEdge[edgeToArc[e]] !== e) {
276
+ throw invariantViolation("I5", `arcToEdge[edgeToArc[${e}]] = ${arcToEdge[edgeToArc[e]]}, expected ${e}`, {
277
+ edge: e,
278
+ arc: edgeToArc[e],
279
+ });
280
+ }
281
+ }
282
+ }
283
+ /**
284
+ * I6: a directed snapshot has arcCount === edgeCount (structure) and arcToEdge is a permutation of
285
+ * 0..edgeCount-1 (full: every edge exactly once).
286
+ * @param s - the snapshot
287
+ * @param full - whether to run the permutation check
288
+ */
289
+ export function checkI6(s, full) {
290
+ if (!s.directed) {
291
+ return;
292
+ }
293
+ const { arcCount, edgeCount } = s;
294
+ if (arcCount !== edgeCount) {
295
+ throw invariantViolation("I6", `directed snapshot has arcCount ${arcCount} but edgeCount ${edgeCount}`, {
296
+ arcCount,
297
+ edgeCount,
298
+ });
299
+ }
300
+ if (!full || s.flags.arcToEdgeIsIdentity) {
301
+ return;
302
+ }
303
+ const { arcToEdge } = s;
304
+ const seen = new Uint8Array(edgeCount);
305
+ for (let a = 0; a < arcCount; a++) {
306
+ const e = arcToEdge[a];
307
+ if (e >= edgeCount || seen[e] === 1) {
308
+ throw invariantViolation("I6", `edge ${e} appears more than once in arcToEdge (arc ${a})`, {
309
+ arc: a,
310
+ edge: e,
311
+ });
312
+ }
313
+ seen[e] = 1;
314
+ }
315
+ }
316
+ /**
317
+ * I7 (structure part): an undirected snapshot has arcCount === 2 * edgeCount - selfLoopCount.
318
+ * @param s - the snapshot
319
+ */
320
+ export function checkI7Counts(s) {
321
+ if (s.directed) {
322
+ return;
323
+ }
324
+ const expected = 2 * s.edgeCount - s.selfLoopCount;
325
+ if (s.arcCount !== expected) {
326
+ throw invariantViolation("I7", `undirected snapshot has arcCount ${s.arcCount}, expected 2 * ${s.edgeCount} - ${s.selfLoopCount} = ${expected}`, { arcCount: s.arcCount, expected });
327
+ }
328
+ }
329
+ /**
330
+ * I7 (pairing part): every arc u -> v with u !== v has exactly one mate v -> u with the same logical
331
+ * edge and weight, a self-loop edge has exactly one arc, and every edge appears once (loop) or twice
332
+ * (mates). The lockstep walk of design section 6.4. Full level.
333
+ * @param s - the snapshot
334
+ */
335
+ export function checkI7Pairing(s) {
336
+ if (s.directed) {
337
+ return;
338
+ }
339
+ const { nodeCount, arcCount, edgeCount, rowPtr, colIdx, weights, arcToEdge } = s;
340
+ const occurrences = new Uint8Array(edgeCount);
341
+ for (let a = 0; a < arcCount; a++) {
342
+ const e = arcToEdge[a];
343
+ if (occurrences[e] === 2) {
344
+ throw invariantViolation("I7", `edge ${e} appears more than twice in arcToEdge (arc ${a})`, {
345
+ arc: a,
346
+ edge: e,
347
+ });
348
+ }
349
+ occurrences[e]++;
350
+ }
351
+ const cursor = rowPtr.slice(0, nodeCount);
352
+ for (let u = 0; u < nodeCount; u++) {
353
+ const end = rowPtr[u + 1];
354
+ let a = cursor[u];
355
+ if (a < rowPtr[u] || a > end) {
356
+ throw invariantViolation("I7", `row ${u} was over-consumed by mates from earlier rows`, { row: u });
357
+ }
358
+ while (a < end) {
359
+ const v = colIdx[a];
360
+ let g = a + 1;
361
+ while (g < end && colIdx[g] === v) {
362
+ g++;
363
+ }
364
+ const k = g - a;
365
+ if (v === u) {
366
+ for (let i = a; i < g; i++) {
367
+ if (occurrences[arcToEdge[i]] !== 1) {
368
+ throw invariantViolation("I7", `self-loop edge ${arcToEdge[i]} has more than one arc`, {
369
+ row: u,
370
+ arc: i,
371
+ edge: arcToEdge[i],
372
+ });
373
+ }
374
+ }
375
+ }
376
+ else {
377
+ if (v < u) {
378
+ throw invariantViolation("I7", `arc ${a} in row ${u} targets ${v} but row ${v} holds no mate`, {
379
+ row: u,
380
+ arc: a,
381
+ target: v,
382
+ });
383
+ }
384
+ const b = cursor[v];
385
+ const rowEnd = rowPtr[v + 1];
386
+ for (let i = 0; i < k; i++) {
387
+ const arc = a + i;
388
+ const mate = b + i;
389
+ if (mate >= rowEnd || colIdx[mate] !== u) {
390
+ throw invariantViolation("I7", `arc ${arc} in row ${u} targeting ${v} has no mate in row ${v}`, {
391
+ row: u,
392
+ arc,
393
+ target: v,
394
+ });
395
+ }
396
+ if (arcToEdge[mate] !== arcToEdge[arc]) {
397
+ throw invariantViolation("I7", `arcs ${arc} and ${mate} should share a logical edge but hold ${arcToEdge[arc]} and ${arcToEdge[mate]}`, { row: u, arc, mate });
398
+ }
399
+ if (occurrences[arcToEdge[arc]] !== 2) {
400
+ throw invariantViolation("I7", `edge ${arcToEdge[arc]} of arc ${arc} does not have two arcs`, {
401
+ row: u,
402
+ arc,
403
+ edge: arcToEdge[arc],
404
+ });
405
+ }
406
+ if (weights !== null && weights[arc] !== weights[mate]) {
407
+ throw invariantViolation("I7", `arcs ${arc} and ${mate} carry different weights ${weights[arc]} and ${weights[mate]}`, { row: u, arc, mate });
408
+ }
409
+ }
410
+ if (b + k < rowEnd && colIdx[b + k] === u) {
411
+ throw invariantViolation("I7", `row ${v} holds more arcs to ${u} than row ${u} holds to ${v}`, {
412
+ row: v,
413
+ arc: b + k,
414
+ target: u,
415
+ });
416
+ }
417
+ cursor[v] = b + k;
418
+ }
419
+ a = g;
420
+ }
421
+ cursor[u] = end;
422
+ }
423
+ }
424
+ /**
425
+ * I8 (length part): weights is null or has arcCount entries.
426
+ * @param s - the snapshot
427
+ */
428
+ export function checkI8Length(s) {
429
+ const { weights, arcCount } = s;
430
+ if (weights !== null && weights.length !== arcCount) {
431
+ throw invariantViolation("I8", `weights has ${weights.length} entries, expected ${arcCount}`, {
432
+ expected: arcCount,
433
+ found: weights.length,
434
+ });
435
+ }
436
+ }
437
+ /**
438
+ * I8 (value part): no weight is NaN. Full level.
439
+ * @param s - the snapshot
440
+ */
441
+ export function checkI8NaN(s) {
442
+ const { weights } = s;
443
+ if (weights === null) {
444
+ return;
445
+ }
446
+ for (let a = 0; a < weights.length; a++) {
447
+ if (Number.isNaN(weights[a])) {
448
+ throw invariantViolation("I8", `weights[${a}] is NaN`, { arc: a });
449
+ }
450
+ }
451
+ }
452
+ // ============================================================ I9 - I13: flags, alignment, ids, columns
453
+ /**
454
+ * I9: every flag equals its predicate over the arrays, and selfLoopCount equals the number of loop
455
+ * arcs. Full level.
456
+ * @param s - the snapshot
457
+ */
458
+ export function checkI9(s) {
459
+ const loopArcs = countLoopArcs(s.rowPtr, s.colIdx, s.nodeCount);
460
+ if (loopArcs !== s.selfLoopCount) {
461
+ throw invariantViolation("I9", `selfLoopCount is ${s.selfLoopCount} but ${loopArcs} loop arcs exist`, {
462
+ reason: "selfLoopCount",
463
+ expected: loopArcs,
464
+ found: s.selfLoopCount,
465
+ });
466
+ }
467
+ const recomputed = computeFlags({
468
+ directed: s.directed,
469
+ nodeCount: s.nodeCount,
470
+ rowPtr: s.rowPtr,
471
+ colIdx: s.colIdx,
472
+ weights: s.weights,
473
+ arcToEdge: s.flags.arcToEdgeIsIdentity ? null : s.arcToEdge,
474
+ selfLoopCount: s.selfLoopCount,
475
+ });
476
+ const names = [
477
+ "multigraph",
478
+ "hasSelfLoops",
479
+ "arcToEdgeIsIdentity",
480
+ "weighted",
481
+ "allWeightsOne",
482
+ "nonNegativeWeights",
483
+ "finiteWeights",
484
+ ];
485
+ for (const name of names) {
486
+ if (s.flags[name] !== recomputed[name]) {
487
+ throw invariantViolation("I9", `flag ${name} is ${s.flags[name]} but the arrays say ${recomputed[name]}`, {
488
+ flag: name,
489
+ found: s.flags[name],
490
+ expected: recomputed[name],
491
+ });
492
+ }
493
+ }
494
+ }
495
+ /**
496
+ * Check one core array against its arena segment (design section 10.3).
497
+ * @param arena - the arena
498
+ * @param name - the core array name
499
+ * @param array - the array, or null when absent
500
+ */
501
+ function checkSegment(arena, name, array) {
502
+ const segment = arena.segments[name];
503
+ if (segment === null) {
504
+ return;
505
+ }
506
+ if (array === null) {
507
+ throw invariantViolation("I10", `arena names a segment for absent core array ${name}`, { array: name });
508
+ }
509
+ const relative = segment.byteOffset - arena.byteOffset;
510
+ if (relative < 0 || relative % ALIGNMENT !== 0 || relative + segment.byteLength > arena.byteLength) {
511
+ throw invariantViolation("I10", `arena segment ${name} at ${segment.byteOffset} is not 256-aligned inside the arena`, {
512
+ array: name,
513
+ byteOffset: segment.byteOffset,
514
+ });
515
+ }
516
+ if (array.buffer !== arena.buffer ||
517
+ array.byteOffset !== segment.byteOffset ||
518
+ array.byteLength !== segment.byteLength) {
519
+ throw invariantViolation("I10", `core array ${name} is not the view its arena segment describes`, {
520
+ array: name,
521
+ byteOffset: array.byteOffset,
522
+ byteLength: array.byteLength,
523
+ });
524
+ }
525
+ }
526
+ /**
527
+ * I10: every core array is 4-byte aligned over a plain ArrayBuffer and, when an arena exists, every
528
+ * array with a non-null segment is the 256-aligned view the segment describes and hotByteLength is the
529
+ * end of the weights (or colIdx, or rowPtr) segment.
530
+ * @param s - the snapshot
531
+ */
532
+ export function checkI10(s) {
533
+ const identity = s.flags.arcToEdgeIsIdentity;
534
+ const arrays = [
535
+ ["rowPtr", s.rowPtr],
536
+ ["colIdx", s.colIdx],
537
+ ["weights", s.weights],
538
+ ["arcToEdge", identity ? null : s.arcToEdge],
539
+ ["edgeToArc", identity ? null : s.edgeToArc],
540
+ ];
541
+ for (const [name, array] of arrays) {
542
+ if (array === null) {
543
+ continue;
544
+ }
545
+ if (!isFourByteAligned(array)) {
546
+ throw invariantViolation("I10", `core array ${name} is not 4-byte aligned`, {
547
+ array: name,
548
+ byteOffset: array.byteOffset,
549
+ byteLength: array.byteLength,
550
+ });
551
+ }
552
+ if (!isOverPlainBuffer(array)) {
553
+ throw invariantViolation("I10", `core array ${name} is not a view over a plain ArrayBuffer`, {
554
+ array: name,
555
+ reason: "buffer",
556
+ });
557
+ }
558
+ }
559
+ const { arena } = s;
560
+ if (arena === null) {
561
+ return;
562
+ }
563
+ const { alignment } = arena;
564
+ if (alignment !== ALIGNMENT) {
565
+ throw invariantViolation("I10", `arena alignment is ${alignment}, expected ${ALIGNMENT}`, {
566
+ found: alignment,
567
+ });
568
+ }
569
+ for (const [name, array] of arrays) {
570
+ checkSegment(arena, name, array);
571
+ }
572
+ let hotEnd = 0;
573
+ for (const name of ["rowPtr", "colIdx", "weights"]) {
574
+ const segment = arena.segments[name];
575
+ if (segment !== null) {
576
+ hotEnd = segment.byteOffset + segment.byteLength - arena.byteOffset;
577
+ }
578
+ }
579
+ if (arena.hotByteLength !== hotEnd) {
580
+ throw invariantViolation("I10", `arena.hotByteLength is ${arena.hotByteLength}, expected ${hotEnd}`, {
581
+ reason: "hotByteLength",
582
+ found: arena.hotByteLength,
583
+ expected: hotEnd,
584
+ });
585
+ }
586
+ }
587
+ /**
588
+ * I11 (size part): the id map has nodeCount ids.
589
+ * @param s - the snapshot
590
+ */
591
+ export function checkI11Size(s) {
592
+ if (s.ids.size !== s.nodeCount) {
593
+ throw invariantViolation("I11", `ids.size is ${s.ids.size}, expected nodeCount ${s.nodeCount}`, {
594
+ expected: s.nodeCount,
595
+ found: s.ids.size,
596
+ });
597
+ }
598
+ }
599
+ /**
600
+ * I11 (bijection part): ids.indexOf(ids.idOf(i)) === i for every i and no id is NaN. Full level.
601
+ * @param s - the snapshot
602
+ */
603
+ export function checkI11Bijection(s) {
604
+ const { ids, nodeCount } = s;
605
+ for (let i = 0; i < nodeCount; i++) {
606
+ const id = ids.idOf(i);
607
+ if (typeof id === "number" && Number.isNaN(id)) {
608
+ throw invariantViolation("I11", `id of node ${i} is NaN`, { index: i });
609
+ }
610
+ const back = ids.indexOf(id);
611
+ if (back !== i) {
612
+ throw invariantViolation("I11", `ids.indexOf(ids.idOf(${i})) is ${back}`, { index: i, found: back });
613
+ }
614
+ }
615
+ }
616
+ /**
617
+ * The E_INVALID_SNAPSHOT error of one column length-rule violation (I12).
618
+ * @param table - the table name for the message
619
+ * @param column - the column
620
+ * @param message - what is wrong
621
+ * @param details - extra location details
622
+ * @returns the error
623
+ */
624
+ function columnViolation(table, column, message, details = {}) {
625
+ return invariantViolation("I12", `column "${column.meta.name}" of table ${table}: ${message}`, {
626
+ table,
627
+ column: column.meta.name,
628
+ ...details,
629
+ });
630
+ }
631
+ /**
632
+ * Check that offsets are rows + 1 long, non-decreasing and end at `end`.
633
+ * @param table - the table name for the message
634
+ * @param column - the column
635
+ * @param offsets - the offsets
636
+ * @param rows - the row count
637
+ * @param end - the expected last offset
638
+ */
639
+ function checkOffsets(table, column, offsets, rows, end) {
640
+ if (offsets.length !== rows + 1) {
641
+ throw columnViolation(table, column, `offsets has ${offsets.length} entries, expected ${rows + 1}`, {
642
+ expected: rows + 1,
643
+ found: offsets.length,
644
+ });
645
+ }
646
+ if (offsets[0] !== 0) {
647
+ throw columnViolation(table, column, `offsets[0] is ${offsets[0]}`, { row: 0 });
648
+ }
649
+ for (let r = 0; r < rows; r++) {
650
+ if (offsets[r + 1] < offsets[r]) {
651
+ throw columnViolation(table, column, `offsets decrease at row ${r}`, { row: r });
652
+ }
653
+ }
654
+ if (offsets[rows] !== end) {
655
+ throw columnViolation(table, column, `offsets[${rows}] is ${offsets[rows]}, expected ${end}`, {
656
+ row: rows,
657
+ found: offsets[rows],
658
+ expected: end,
659
+ });
660
+ }
661
+ }
662
+ /**
663
+ * Check the values of a refersTo u32 array: every entry below `bound` or INVALID_INDEX; when
664
+ * `rowValidity` is given an INVALID_INDEX entry requires its row to be unset.
665
+ * @param table - the table name for the message
666
+ * @param column - the column
667
+ * @param values - the u32 values
668
+ * @param components - values per row
669
+ * @param bound - the referenced space's row count
670
+ * @param rowValidity - the validity bitmap of the rows (null = all set), or undefined to skip the unset rule
671
+ */
672
+ function checkReferences(table, column, values, components, bound, rowValidity) {
673
+ for (let i = 0; i < values.length; i++) {
674
+ const value = values[i];
675
+ if (value === INVALID_INDEX) {
676
+ const row = Math.floor(i / components);
677
+ if (rowValidity !== undefined && (rowValidity === null || bitmapGet(rowValidity, row))) {
678
+ throw columnViolation(table, column, `row ${row} is set but holds INVALID_INDEX`, { row });
679
+ }
680
+ continue;
681
+ }
682
+ if (value >= bound) {
683
+ throw columnViolation(table, column, `value ${value} at ${i} is not below ${bound}`, {
684
+ row: Math.floor(i / components),
685
+ found: value,
686
+ expected: bound,
687
+ });
688
+ }
689
+ }
690
+ }
691
+ /**
692
+ * The refersTo rules of invariant I12 for one column: a u32 column's values (or a list column's u32
693
+ * child items) are below `bound` or INVALID_INDEX, and a scalar INVALID_INDEX sits in an unset row.
694
+ * @param table - the table name for the message
695
+ * @param column - a column whose meta.refersTo is set
696
+ * @param bound - the referenced space's row count
697
+ */
698
+ function checkColumnReferences(table, column, bound) {
699
+ switch (column.dtype) {
700
+ case "u32":
701
+ checkReferences(table, column, column.data, column.meta.components, bound, column.validity);
702
+ return;
703
+ case "list": {
704
+ const { child } = column;
705
+ if (child.dtype !== "u32") {
706
+ throw columnViolation(table, column, `refersTo list requires a u32 child, found ${child.dtype}`);
707
+ }
708
+ checkReferences(table, column, child.data, 1, bound, undefined);
709
+ return;
710
+ }
711
+ default:
712
+ throw columnViolation(table, column, `refersTo requires dtype u32, found ${column.dtype}`);
713
+ }
714
+ }
715
+ /**
716
+ * The typed arrays a column exposes to a GPU consumer (data, validity, codes, list child data): each
717
+ * must be a view over a plain ArrayBuffer (I10, decision D-SAB).
718
+ * @param column - the column
719
+ * @returns the arrays with their names
720
+ */
721
+ function columnArrays(column) {
722
+ switch (column.dtype) {
723
+ case "f32":
724
+ case "f64":
725
+ case "i32":
726
+ case "u32":
727
+ case "u8":
728
+ case "bool":
729
+ return [
730
+ ["data", column.data],
731
+ ["validity", column.validity],
732
+ ];
733
+ case "dict":
734
+ return [
735
+ ["codes", column.codes],
736
+ ["validity", column.validity],
737
+ ];
738
+ case "string":
739
+ return [
740
+ ["offsets", column.offsets],
741
+ ["utf8", column.utf8],
742
+ ["validity", column.validity],
743
+ ];
744
+ case "list":
745
+ return [
746
+ ["offsets", column.offsets],
747
+ ["validity", column.validity],
748
+ ...columnArrays(column.child).map(([name, array]) => [
749
+ `child.${name}`,
750
+ array,
751
+ ]),
752
+ ];
753
+ case "json":
754
+ return [["validity", column.validity]];
755
+ default: {
756
+ const unknown = column;
757
+ throw invariantViolation("I12", `unknown dtype ${unknown.dtype}`);
758
+ }
759
+ }
760
+ }
761
+ /**
762
+ * The length rules of design section 5.7 for one column, plus dictionary code ranges, refersTo
763
+ * ranges, the recorded nullCount (recomputed from the bitmap, design section 9.5) and the plain
764
+ * ArrayBuffer rule of I10 for every typed array the column exposes (I12).
765
+ * @param table - the table name for the message
766
+ * @param column - the column
767
+ * @param rows - the table's row count
768
+ * @param s - the snapshot (for the referenced row counts)
769
+ */
770
+ export function checkColumnRules(table, column, rows, s) {
771
+ if (column.length !== rows) {
772
+ throw columnViolation(table, column, `has ${column.length} rows, expected ${rows}`, {
773
+ expected: rows,
774
+ found: column.length,
775
+ });
776
+ }
777
+ const { validity, meta } = column;
778
+ const validityWords = Math.ceil(rows / 32);
779
+ if (validity !== null && validity.length !== validityWords) {
780
+ throw columnViolation(table, column, `validity has ${validity.length} words, expected ${validityWords}`, {
781
+ expected: validityWords,
782
+ found: validity.length,
783
+ });
784
+ }
785
+ const nullCount = validity === null ? 0 : rows - bitmapCount(validity, rows);
786
+ if (column.nullCount !== nullCount) {
787
+ throw columnViolation(table, column, `nullCount is ${column.nullCount}, the validity bitmap says ${nullCount}`, {
788
+ expected: nullCount,
789
+ found: column.nullCount,
790
+ reason: "nullCount",
791
+ });
792
+ }
793
+ for (const [name, array] of columnArrays(column)) {
794
+ if (array !== null && !isOverPlainBuffer(array)) {
795
+ throw columnViolation(table, column, `${name} is not a view over a plain ArrayBuffer`, {
796
+ array: name,
797
+ reason: "buffer",
798
+ });
799
+ }
800
+ }
801
+ const refBound = meta.refersTo === "node" ? s.nodeCount : s.edgeCount;
802
+ const { dtype } = column;
803
+ switch (dtype) {
804
+ case "f32":
805
+ case "f64":
806
+ case "i32":
807
+ case "u32":
808
+ case "u8": {
809
+ const expected = rows * meta.components;
810
+ if (column.data.length !== expected) {
811
+ throw columnViolation(table, column, `data has ${column.data.length} values, expected ${expected}`, {
812
+ expected,
813
+ found: column.data.length,
814
+ });
815
+ }
816
+ if (meta.refersTo !== null) {
817
+ checkColumnReferences(table, column, refBound);
818
+ }
819
+ break;
820
+ }
821
+ case "bool":
822
+ if (column.data.length !== validityWords) {
823
+ throw columnViolation(table, column, `bool data has ${column.data.length} words, expected ${validityWords}`, {
824
+ expected: validityWords,
825
+ found: column.data.length,
826
+ });
827
+ }
828
+ break;
829
+ case "dict": {
830
+ if (column.codes.length !== rows) {
831
+ throw columnViolation(table, column, `codes has ${column.codes.length} entries, expected ${rows}`, {
832
+ expected: rows,
833
+ found: column.codes.length,
834
+ });
835
+ }
836
+ const size = column.dictionary.length;
837
+ for (let r = 0; r < rows; r++) {
838
+ if (column.codes[r] >= size && (validity === null || bitmapGet(validity, r))) {
839
+ throw columnViolation(table, column, `code ${column.codes[r]} at row ${r} is not below ${size}`, {
840
+ row: r,
841
+ found: column.codes[r],
842
+ });
843
+ }
844
+ }
845
+ break;
846
+ }
847
+ case "string":
848
+ checkOffsets(table, column, column.offsets, rows, column.utf8.length);
849
+ break;
850
+ case "list": {
851
+ const { child } = column;
852
+ checkOffsets(table, column, column.offsets, rows, child.length);
853
+ if (child.validity !== null) {
854
+ throw columnViolation(table, column, "list child column has a validity bitmap");
855
+ }
856
+ if (meta.refersTo !== null) {
857
+ checkColumnReferences(table, column, refBound);
858
+ }
859
+ break;
860
+ }
861
+ case "json":
862
+ if (column.values.length !== rows) {
863
+ throw columnViolation(table, column, `values has ${column.values.length} entries, expected ${rows}`, {
864
+ expected: rows,
865
+ found: column.values.length,
866
+ });
867
+ }
868
+ break;
869
+ default: {
870
+ const unknown = dtype;
871
+ throw columnViolation(table, column, `unknown dtype ${String(unknown)}`);
872
+ }
873
+ }
874
+ }
875
+ /**
876
+ * Check every column of a table against its row count.
877
+ * @param name - the table name for the message
878
+ * @param table - the table
879
+ * @param rows - the expected row count
880
+ * @param s - the snapshot
881
+ */
882
+ function checkTable(name, table, rows, s) {
883
+ for (const column of table) {
884
+ checkColumnRules(name, column, rows, s);
885
+ }
886
+ }
887
+ /**
888
+ * I12: the node, edge and graph tables have nodeCount, edgeCount and 1 rows, every column (extension
889
+ * tables included) obeys its length rules, dictionary codes are in range and refersTo values are in
890
+ * range or INVALID_INDEX with the row unset.
891
+ * @param s - the snapshot
892
+ */
893
+ export function checkI12(s) {
894
+ const tables = [
895
+ ["nodes", s.nodes, s.nodeCount],
896
+ ["edges", s.edges, s.edgeCount],
897
+ ["graph", s.graph, 1],
898
+ ];
899
+ for (const [name, table, rows] of tables) {
900
+ if (table.rowCount !== rows) {
901
+ throw invariantViolation("I12", `table ${name} has ${table.rowCount} rows, expected ${rows}`, {
902
+ table: name,
903
+ expected: rows,
904
+ found: table.rowCount,
905
+ });
906
+ }
907
+ checkTable(name, table, rows, s);
908
+ }
909
+ for (const [name, table] of s.extensions) {
910
+ checkTable(`extensions[${name}]`, table, table.rowCount, s);
911
+ }
912
+ }
913
+ /**
914
+ * I13: edge attribute columns are indexed by logical edge: the edge table has edgeCount rows.
915
+ * @param s - the snapshot
916
+ */
917
+ export function checkI13(s) {
918
+ if (s.edges.rowCount !== s.edgeCount) {
919
+ throw invariantViolation("I13", `edge table has ${s.edges.rowCount} rows, expected edgeCount ${s.edgeCount}`, {
920
+ expected: s.edgeCount,
921
+ found: s.edges.rowCount,
922
+ });
923
+ }
924
+ }
925
+ // ============================================================ levels
926
+ /**
927
+ * The "structure" level of design section 9.5: I1-I3, I5 lengths and ranges, I6 / I7 counts, I8
928
+ * length, I10, I11 size, I12 lengths and ranges, I13. O(n + m + columns).
929
+ * @param s - the snapshot
930
+ */
931
+ export function validateStructure(s) {
932
+ checkI3(s);
933
+ checkI1(s);
934
+ checkI2(s);
935
+ checkI5Ranges(s);
936
+ checkI6(s, false);
937
+ checkI7Counts(s);
938
+ checkI8Length(s);
939
+ checkI10(s);
940
+ checkI11Size(s);
941
+ checkI13(s);
942
+ checkI12(s);
943
+ }
944
+ /**
945
+ * The "full" level of design section 9.5: structure plus I4, I5 orientation, I6 permutation, I8 NaN
946
+ * (before the pairing walk, so a NaN weight is reported as I8 rather than as an unequal mate), I7
947
+ * pairing, I9 flags, I11 bijection and unique columns. O(m log d).
948
+ * @param s - the snapshot
949
+ */
950
+ export function validateFull(s) {
951
+ validateStructure(s);
952
+ checkI4(s);
953
+ checkI5Orientation(s);
954
+ checkI6(s, true);
955
+ checkI8NaN(s);
956
+ checkI7Pairing(s);
957
+ checkI9(s);
958
+ checkI11Bijection(s);
959
+ const tables = [
960
+ ["nodes", s.nodes],
961
+ ["edges", s.edges],
962
+ ["graph", s.graph],
963
+ ...[...s.extensions].map(([name, table]) => [`extensions[${name}]`, table]),
964
+ ];
965
+ for (const [name, table] of tables) {
966
+ for (const column of table) {
967
+ checkUniqueColumn(name, column);
968
+ }
969
+ }
970
+ }
971
+ /**
972
+ * The `unique` rule of a column as a validation failure (design sections 9.5 and 11.4): the
973
+ * freeze-time codes E_DUPLICATE_ID / E_DUPLICATE_EDGE_ID of `verifyUniqueColumn` become
974
+ * E_INVALID_SNAPSHOT with `details.invariant` "I12", `details.reason` "unique" and the original
975
+ * code in `details.cause`, so a reader of untrusted input sees one error family.
976
+ * @param table - the table name for the message
977
+ * @param column - the column
978
+ */
979
+ export function checkUniqueColumn(table, column) {
980
+ try {
981
+ verifyUniqueColumn(column);
982
+ }
983
+ catch (err) {
984
+ if (err instanceof GraphFormatError && (err.code === "E_DUPLICATE_ID" || err.code === "E_DUPLICATE_EDGE_ID")) {
985
+ throw columnViolation(table, column, err.message, { ...err.details, reason: "unique", cause: err.code });
986
+ }
987
+ throw err;
988
+ }
989
+ }
990
+ //# sourceMappingURL=validate.js.map