@hviana/sema 0.1.5 → 0.1.6

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 (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -1,299 +0,0 @@
1
- import { Prng } from "./prng.js";
2
- function nextPow2(n) {
3
- let p = 1;
4
- while (p < n) {
5
- p <<= 1;
6
- }
7
- return p;
8
- }
9
- /** Set-bit count for every byte value, for counting code bits during the byte scan. */
10
- const POPCOUNT8 = new Uint8Array(256);
11
- for (let i = 1; i < 256; i++) {
12
- POPCOUNT8[i] = POPCOUNT8[i >> 1] + (i & 1);
13
- }
14
- /**
15
- * 1-bit RaBitQ quantizer (cosine) -- the ONLY representation of a vector kept by
16
- * the index. A D-dimensional vector collapses to ceil(D/32) 32-bit words of sign
17
- * bits, e.g. a 256-d vector goes from 256*4 = 1024 bytes to 32 bytes of code.
18
- *
19
- * Each vector is centered by an optional centroid, normalised, rotated by a
20
- * fast random orthogonal transform (random sign flips + Walsh-Hadamard,
21
- * O(D log D)) and reduced to one sign bit per padded dimension. The random
22
- * rotation makes the quantisation error essentially uniform across vectors, so
23
- * the cosine estimate needs only a single fixed scale (`cosFactor`) rather than
24
- * any per-vector correction.
25
- *
26
- * Two estimators are provided:
27
- * - `estimate` : full-precision query vs stored code (accurate)
28
- * - `codeDistanceBytes` : stored code vs stored code (Hamming based; used while
29
- * building the graph, where neither side is full precision)
30
- *
31
- * Reference: Gao & Long, "RaBitQ: Quantizing High-Dimensional Vectors with a
32
- * Theoretical Error Bound for Approximate Nearest Neighbor Search", SIGMOD 2024.
33
- */
34
- export class RaBitQuantizer {
35
- dim;
36
- paddedDim;
37
- codeWords;
38
- queryBits;
39
- rounds;
40
- seed;
41
- centroid;
42
- sqrtD;
43
- maxQInt;
44
- signs;
45
- scratch;
46
- /** Fixed cosine inner-product scale (= 1 / E[L1] of a rotated unit vector). */
47
- cosFactor;
48
- // byte-LUT machinery for the query/code estimator
49
- nbytes;
50
- /** coordinate index sitting at byte position p, bit k -> bitCoord[p*8 + k]. */
51
- bitCoord;
52
- /** true when the largest possible LUT entry overflows a Uint8. */
53
- lutWide;
54
- constructor(dim, opts = {}) {
55
- this.dim = dim;
56
- this.paddedDim = nextPow2(dim);
57
- this.codeWords = Math.ceil(this.paddedDim / 32);
58
- this.queryBits = opts.queryBits ?? 8;
59
- this.rounds = opts.rounds ?? 3;
60
- this.seed = (opts.seed ?? 0x1234abcd) >>> 0;
61
- this.sqrtD = Math.sqrt(this.paddedDim);
62
- this.cosFactor = Math.sqrt(Math.PI / (2 * this.paddedDim));
63
- this.maxQInt = (1 << this.queryBits) - 1;
64
- this.centroid = new Float64Array(dim);
65
- if (opts.centroid) {
66
- for (let i = 0; i < dim; i++) {
67
- this.centroid[i] = opts.centroid[i] ?? 0;
68
- }
69
- }
70
- const prng = new Prng(this.seed);
71
- this.signs = [];
72
- for (let r = 0; r < this.rounds; r++) {
73
- const s = new Float64Array(this.paddedDim);
74
- for (let i = 0; i < this.paddedDim; i++) {
75
- s[i] = prng.next() < 0.5 ? -1 : 1;
76
- }
77
- this.signs.push(s);
78
- }
79
- this.scratch = new Float64Array(this.paddedDim);
80
- // Map (code-byte position, bit-in-byte) -> coordinate, honouring the host
81
- // byte order so the Uint8 view of the (Uint32) code buffer is interpreted
82
- // correctly on both little- and big-endian platforms.
83
- this.nbytes = this.paddedDim >>> 3;
84
- const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
85
- this.bitCoord = new Int32Array(this.nbytes * 8);
86
- for (let p = 0; p < this.nbytes; p++) {
87
- const word = p >>> 2;
88
- const localByte = littleEndian ? p & 3 : 3 - (p & 3);
89
- const bitBase = word * 32 + localByte * 8;
90
- for (let k = 0; k < 8; k++) {
91
- this.bitCoord[p * 8 + k] = bitBase + k;
92
- }
93
- }
94
- this.lutWide = 8 * this.maxQInt > 255;
95
- }
96
- /** In-place fast Walsh-Hadamard transform; `a.length` must be a power of two. */
97
- fwht(a) {
98
- const n = a.length;
99
- for (let len = 1; len < n; len <<= 1) {
100
- const span = len << 1;
101
- for (let i = 0; i < n; i += span) {
102
- for (let j = i; j < i + len; j++) {
103
- const u = a[j];
104
- const v = a[j + len];
105
- a[j] = u + v;
106
- a[j + len] = u - v;
107
- }
108
- }
109
- }
110
- }
111
- /** Apply the orthogonal rotation in place (a.length === paddedDim). */
112
- rotate(a) {
113
- const n = this.paddedDim;
114
- const inv = 1 / this.sqrtD;
115
- for (let r = 0; r < this.rounds; r++) {
116
- const s = this.signs[r];
117
- for (let i = 0; i < n; i++) {
118
- a[i] *= s[i];
119
- }
120
- this.fwht(a);
121
- for (let i = 0; i < n; i++) {
122
- a[i] *= inv;
123
- }
124
- }
125
- }
126
- /** Encode a raw vector into its 1-bit sign code (the whole representation). */
127
- encode(vec) {
128
- const dim = this.dim;
129
- const pd = this.paddedDim;
130
- const buf = this.scratch;
131
- let sq = 0;
132
- for (let i = 0; i < dim; i++) {
133
- const v = vec[i] - this.centroid[i];
134
- buf[i] = v;
135
- sq += v * v;
136
- }
137
- for (let i = dim; i < pd; i++) {
138
- buf[i] = 0;
139
- }
140
- const code = new Uint32Array(this.codeWords);
141
- if (sq === 0) {
142
- return code;
143
- }
144
- const invNorm = 1 / Math.sqrt(sq);
145
- for (let i = 0; i < dim; i++) {
146
- buf[i] *= invNorm; // unit residual; padded dims stay 0
147
- }
148
- this.rotate(buf);
149
- for (let i = 0; i < pd; i++) {
150
- if (buf[i] > 0) {
151
- code[i >>> 5] |= 1 << (i & 31);
152
- }
153
- }
154
- return code;
155
- }
156
- /** Pre-process a full-precision query into the structure consumed by `estimate`. */
157
- prepareQuery(vec) {
158
- const dim = this.dim;
159
- const pd = this.paddedDim;
160
- const nb = this.nbytes;
161
- const buf = this.scratch;
162
- let sq = 0;
163
- for (let i = 0; i < dim; i++) {
164
- const v = vec[i] - this.centroid[i];
165
- buf[i] = v;
166
- sq += v * v;
167
- }
168
- for (let i = dim; i < pd; i++) {
169
- buf[i] = 0;
170
- }
171
- const qNorm = Math.sqrt(sq);
172
- const qlut = this.lutWide
173
- ? new Uint16Array(nb * 256)
174
- : new Uint8Array(nb * 256);
175
- if (qNorm === 0) {
176
- return { vmin: 0, delta: 0, sumQInt: 0, qlut, nbytes: nb, zero: true };
177
- }
178
- const invNorm = 1 / qNorm;
179
- for (let i = 0; i < dim; i++) {
180
- buf[i] *= invNorm;
181
- }
182
- this.rotate(buf);
183
- let vmin = Infinity;
184
- let vmax = -Infinity;
185
- for (let i = 0; i < pd; i++) {
186
- const x = buf[i];
187
- if (x < vmin) {
188
- vmin = x;
189
- }
190
- if (x > vmax) {
191
- vmax = x;
192
- }
193
- }
194
- const range = vmax - vmin;
195
- const delta = range > 0 ? range / this.maxQInt : 0;
196
- const invDelta = delta > 0 ? 1 / delta : 0;
197
- // Quantise each (rotated) query coordinate to queryBits bits.
198
- const qint = buf; // reuse: write the integer code back over the float buffer
199
- let sumQInt = 0;
200
- for (let i = 0; i < pd; i++) {
201
- let q = delta > 0 ? Math.round((buf[i] - vmin) * invDelta) : 0;
202
- if (q < 0) {
203
- q = 0;
204
- } else if (q > this.maxQInt) {
205
- q = this.maxQInt;
206
- }
207
- qint[i] = q;
208
- sumQInt += q;
209
- }
210
- // Build the byte LUT: qlut[p*256 + v] = sum of qint at coords whose bit is
211
- // set in v, grown incrementally as qlut[..(v with lowest set bit cleared)..]
212
- // plus the contribution of that lowest set bit.
213
- const bitCoord = this.bitCoord;
214
- for (let p = 0; p < nb; p++) {
215
- const base = p << 8;
216
- const cb = p << 3;
217
- for (let v = 1; v < 256; v++) {
218
- const low = v & -v;
219
- const k = 31 - Math.clz32(low);
220
- qlut[base + v] = qlut[base + (v & (v - 1))] + qint[bitCoord[cb + k]];
221
- }
222
- }
223
- return { vmin, delta, sumQInt, qlut, nbytes: nb, zero: false };
224
- }
225
- /**
226
- * Estimate the cosine distance (1 - cosine) between a stored code and a
227
- * full-precision query, reading the code's bytes against the query's byte LUT.
228
- * The code's set-bit count is tallied in the same byte scan, so nothing beyond
229
- * the code itself is needed.
230
- *
231
- * `codeBytes` is a Uint8 view of the packed code buffer and `byteOffset` is the
232
- * code's start byte (id * paddedDim/8).
233
- */
234
- estimate(codeBytes, byteOffset, q) {
235
- if (q.zero) {
236
- return 1;
237
- }
238
- const nb = q.nbytes;
239
- const lut = q.qlut;
240
- let dot = 0;
241
- let popcount = 0;
242
- for (let p = 0; p < nb; p++) {
243
- const b = codeBytes[byteOffset + p];
244
- dot += lut[(p << 8) + b];
245
- popcount += POPCOUNT8[b];
246
- }
247
- // A = sum_i sign_i * q_rot_i, recovered from the quantised query.
248
- const A = q.vmin * (2 * popcount - this.paddedDim) +
249
- q.delta * (2 * dot - q.sumQInt);
250
- return 1 - this.cosFactor * A;
251
- }
252
- /**
253
- * Cosine distance (1 - cosine, in [0, 2]) between two packed codes, computed
254
- * directly from the BLOB bytes via their sign-bit Hamming distance — no word
255
- * reinterpretation, so it is endianness-agnostic. Identical codes score 0.
256
- *
257
- * This is the distance used to build the graph (code vs code) and to answer a
258
- * query given an already-quantized code. It is coarser than `estimate`, where
259
- * one side is full precision.
260
- */
261
- codeDistanceBytes(a, b) {
262
- const nb = this.nbytes;
263
- let ham = 0;
264
- // 32-bit-word Hamming: this is the hot arithmetic of graph construction
265
- // (every candidate/prune comparison), so fold 4 bytes into one word and
266
- // popcount it — ~4× fewer loop iterations than a byte-LUT scan, with no
267
- // allocation and no dependence on the buffers' alignment or endianness
268
- // (both sides are composed identically, so XOR is order-agnostic).
269
- let p = 0;
270
- for (const n4 = nb & ~3; p < n4; p += 4) {
271
- let x = (a[p] ^ b[p]) |
272
- ((a[p + 1] ^ b[p + 1]) << 8) |
273
- ((a[p + 2] ^ b[p + 2]) << 16) |
274
- ((a[p + 3] ^ b[p + 3]) << 24);
275
- x -= (x >>> 1) & 0x55555555;
276
- x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
277
- x = (x + (x >>> 4)) & 0x0f0f0f0f;
278
- ham += Math.imul(x, 0x01010101) >>> 24;
279
- }
280
- for (; p < nb; p++) {
281
- ham += POPCOUNT8[a[p] ^ b[p]];
282
- }
283
- return (2 * ham) / this.paddedDim;
284
- }
285
- /** Pack a code (codeWords 32-bit words) into its little-endian BLOB bytes. */
286
- codeToBytes(code) {
287
- const u = new Uint32Array(this.codeWords);
288
- for (let i = 0; i < this.codeWords; i++) {
289
- u[i] = code[i];
290
- }
291
- return new Uint8Array(u.buffer, 0, u.byteLength);
292
- }
293
- /** Reinterpret a code BLOB as a copy of codeWords 32-bit words. */
294
- bytesToCode(bytes) {
295
- const u = new Uint32Array(this.codeWords);
296
- new Uint8Array(u.buffer).set(bytes);
297
- return u;
298
- }
299
- }
@@ -1,162 +0,0 @@
1
- import { DatabaseSync } from "node:sqlite";
2
- /**
3
- * The on-disk structure of the index, in SQLite. This is NOT a serialization of
4
- * an in-memory graph: it IS the graph. Every code and every adjacency list lives
5
- * in these tables and is read/written on demand, so process memory stays flat as
6
- * the collection grows -- only the working set of the current operation, plus
7
- * SQLite's fixed-size page cache, is ever resident.
8
- *
9
- * Tables
10
- * meta one row of configuration + mutable global state (entry point, counts,
11
- * level-RNG state). The quantizer's rotation is fully determined by
12
- * {dim, rounds, seed, centroid}, so reopening a database reproduces it
13
- * exactly without storing any matrix.
14
- * nodes one row per inserted vector: id (rowid), external id, top level,
15
- * tombstone flag, and the 1-bit code as a BLOB. Indexed by rowid (code
16
- * lookup) and by a partial unique index on the external id (live nodes).
17
- * links one row per (node, layer) holding that node's neighbour ids packed as
18
- * a BLOB. WITHOUT ROWID makes (node, layer) the clustering key, so a
19
- * neighbour-list fetch -- the hot path of search -- is a single B-tree
20
- * descent landing on the inline BLOB.
21
- */
22
- export interface StoreConfig {
23
- dim: number;
24
- m: number;
25
- efConstruction: number;
26
- efSearch: number;
27
- queryBits: number;
28
- rotationRounds: number;
29
- seed: number;
30
- centroid: Float64Array | null;
31
- codeWords: number;
32
- paddedDim: number;
33
- }
34
- export interface GlobalState {
35
- entryPoint: number;
36
- maxLevel: number;
37
- live: number;
38
- total: number;
39
- rng: number;
40
- }
41
- /** A node as the graph algorithms need it: code bytes, tombstone, external id. */
42
- export interface NodeRec {
43
- code: Uint8Array;
44
- deleted: number;
45
- ext: number | null;
46
- }
47
- export declare class Store {
48
- readonly db: DatabaseSync;
49
- /**
50
- * Number of row reads served from the database (node and neighbour-list
51
- * fetches). This counts *storage* accesses only, so it is unaffected by any
52
- * caching layer above it -- the honest measure of how the engine scales.
53
- */
54
- reads: number;
55
- private sInsNode;
56
- private sNode;
57
- private sIdByExt;
58
- private sTombstone;
59
- private sNbrs;
60
- private sSetNbrs;
61
- private sState;
62
- private readonly cacheBudgetBytes;
63
- /** Whether the shared memory budget is non-zero — consumers that trade RAM
64
- * for speed (the graph's visited-tag array) key off this so the
65
- * `cacheSizeMb: 0` flat-memory mode stays exactly flat. */
66
- get cacheEnabled(): boolean;
67
- private slab;
68
- private slabHot;
69
- private nbrSlab;
70
- private slabsDerived;
71
- /**
72
- * @param dbPath path to the SQLite file (":memory:" for transient).
73
- * @param cacheSizeMb the ONE memory knob (MiB). It sizes BOTH SQLite's page
74
- * cache AND the immutable-code LRU above (whose entry-capacity is derived
75
- * from this budget and the code size — no second knob). Both are pure
76
- * speed enhancements: correctness and the per-operation storage-read
77
- * count do not depend on them. Pass 0 to run with essentially no cache,
78
- * which is how the tests run so a poor access pattern can never hide
79
- * behind a warm cache.
80
- */
81
- constructor(dbPath: string, cacheSizeMb?: number);
82
- private tableExists;
83
- /** (Re)compile the hot statements against the current schema. */
84
- private prepareAll;
85
- /** Load the persisted configuration, or null for a fresh database. */
86
- loadConfig(): StoreConfig | null;
87
- /** Initialise the meta row for a new database. */
88
- initConfig(c: StoreConfig): void;
89
- loadState(): GlobalState;
90
- saveState(s: GlobalState): void;
91
- setEfSearch(ef: number): void;
92
- setQueryBits(bits: number): void;
93
- resetReads(): void;
94
- /** Stream every live external id in id order, in bounded-memory batches. */
95
- liveExts(batch?: number): IterableIterator<number>;
96
- /** Size the two slabs from the shared memory budget and the actual code
97
- * size (known on the first record cached): total slots = budget / bytes
98
- * per slot, the upper-layer slab taking half. A slot is the code bytes
99
- * plus 17 B of typed-array bookkeeping (key + ext + deleted). */
100
- private deriveSlabs;
101
- /** Record a node in the slab cache. Upper-layer nodes (level ≥ 1) go to
102
- * the pinned section layer-0 traffic never touches; a colliding slot is
103
- * simply overwritten (direct-mapped eviction — no bookkeeping). */
104
- private cacheRec;
105
- /** Insert a node, returning its assigned id (rowid). */
106
- addNode(ext: number | null, level: number, code: Uint8Array): number;
107
- /** Fetch a node's code (copied), tombstone flag and external id, or null.
108
- * Served from the slab cache when present; a miss reads SQLite, counts
109
- * one `reads` (the cache-independent scalability witness), and caches the
110
- * result. The cache is a latency layer ONLY — `reads` does not count hits, so
111
- * the index's storage-read scaling is identical with the cache off. */
112
- getNode(id: number): NodeRec | null;
113
- idByExt(ext: number): number | null;
114
- private readonly batchStmts;
115
- private static readonly BATCH_MAX;
116
- private batchStmt;
117
- /** Fetch several nodes into `out` (skipping ids already present). Serves
118
- * from the code LRU first; the misses are read with one IN query per
119
- * chunk. Ids with no row are simply absent from `out`. Each id that
120
- * reaches SQLite counts one `reads`, exactly like a getNode miss. */
121
- getNodesInto(ids: number[], out: Map<number, NodeRec>, count?: number): void;
122
- /** Pre-fill the code and neighbour-list caches with ONE sequential table
123
- * scan each, up to their existing budget-derived caps. A cold session
124
- * otherwise warms the caches through hundreds of thousands of RANDOM point
125
- * reads spread across its first minutes of inserts/queries; a sequential
126
- * scan streams the same rows at C speed in seconds. A pure latency
127
- * optimisation with the exact same caps and coherence rules as demand
128
- * filling — nothing about results, `reads` discipline, or memory ceilings
129
- * changes. Two passes over `nodes` so the upper-layer pinned section is
130
- * filled before layer-0 rows compete for the LRU. Returns rows warmed. */
131
- warmCache(): number;
132
- tombstone(id: number): void;
133
- /** A node's neighbour ids at a layer, or null if it has no list there. */
134
- getNeighbors(node: number, layer: number): Uint32Array | null;
135
- setNeighbors(node: number, layer: number, ids: number[]): void;
136
- begin(): void;
137
- commit(): void;
138
- rollback(): void;
139
- /** Encode a neighbour id list to the delta-varint blob format. */
140
- private encodeNbrs;
141
- /** Decode a delta-varint neighbour blob into a number[]. */
142
- private decodeNbrs;
143
- /** Tombstone-splice compaction. `M`/`Mmax0` cap a rewritten list on upper
144
- * layers / layer 0. Returns the new global state scalars (internal ids are
145
- * preserved, so `entry` survives unless it was itself dead). The caller
146
- * persists state and vacuums. */
147
- spliceCompact(M: number, Mmax0: number, entry: number): {
148
- entry: number;
149
- maxLevel: number;
150
- live: number;
151
- };
152
- /** Stream live external ids with internal id > `after`, in internal-id
153
- * order, in bounded batches. Internal ids are assigned monotonically and
154
- * PRESERVED by {@link spliceCompact}, so a caller can use the largest
155
- * internal id it has seen as a durable incremental watermark. */
156
- liveExtsSince(after: number, batch?: number): IterableIterator<{
157
- ext: number;
158
- internal: number;
159
- }>;
160
- vacuum(): void;
161
- close(): void;
162
- }