sweet-search 0.0.1 → 2.3.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 (161) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +23 -0
  3. package/core/cli.js +51 -0
  4. package/core/config.js +27 -0
  5. package/core/embedding/embedding-cache.js +467 -0
  6. package/core/embedding/embedding-local-model.js +845 -0
  7. package/core/embedding/embedding-remote.js +492 -0
  8. package/core/embedding/embedding-service.js +712 -0
  9. package/core/embedding/embedding-telemetry.js +219 -0
  10. package/core/embedding/index.js +40 -0
  11. package/core/graph/community-detector.js +294 -0
  12. package/core/graph/graph-expansion.js +839 -0
  13. package/core/graph/graph-extractor.js +2304 -0
  14. package/core/graph/graph-search.js +2148 -0
  15. package/core/graph/hcgs-generator.js +666 -0
  16. package/core/graph/index.js +16 -0
  17. package/core/graph/leiden-algorithm.js +547 -0
  18. package/core/graph/relationship-resolver.js +366 -0
  19. package/core/graph/repo-map.js +408 -0
  20. package/core/graph/summary-manager.js +549 -0
  21. package/core/indexing/artifact-builder.js +1054 -0
  22. package/core/indexing/ast-chunker.js +709 -0
  23. package/core/indexing/chunking/chunk-builder.js +170 -0
  24. package/core/indexing/chunking/markdown-chunker.js +503 -0
  25. package/core/indexing/chunking/plaintext-chunker.js +104 -0
  26. package/core/indexing/dedup/dedup-phase.js +159 -0
  27. package/core/indexing/dedup/exemplar-selector.js +65 -0
  28. package/core/indexing/document-chunker.js +56 -0
  29. package/core/indexing/incremental-parser.js +390 -0
  30. package/core/indexing/incremental-tracker.js +761 -0
  31. package/core/indexing/index-codebase-v21.js +472 -0
  32. package/core/indexing/index-maintainer.mjs +1674 -0
  33. package/core/indexing/index.js +90 -0
  34. package/core/indexing/indexer-ann.js +1077 -0
  35. package/core/indexing/indexer-build.js +742 -0
  36. package/core/indexing/indexer-phases.js +800 -0
  37. package/core/indexing/indexer-pool.js +764 -0
  38. package/core/indexing/indexer-sparse-gram.js +98 -0
  39. package/core/indexing/indexer-utils.js +536 -0
  40. package/core/indexing/indexer-worker.js +148 -0
  41. package/core/indexing/li-skip-policy.js +225 -0
  42. package/core/indexing/merkle-tracker.js +244 -0
  43. package/core/indexing/model-pool.js +166 -0
  44. package/core/infrastructure/code-graph-repository.js +120 -0
  45. package/core/infrastructure/codebase-repository.js +131 -0
  46. package/core/infrastructure/config/dedup.js +54 -0
  47. package/core/infrastructure/config/embedding.js +298 -0
  48. package/core/infrastructure/config/graph.js +80 -0
  49. package/core/infrastructure/config/index.js +82 -0
  50. package/core/infrastructure/config/indexing.js +8 -0
  51. package/core/infrastructure/config/platform.js +254 -0
  52. package/core/infrastructure/config/ranking.js +221 -0
  53. package/core/infrastructure/config/search.js +396 -0
  54. package/core/infrastructure/config/translation.js +89 -0
  55. package/core/infrastructure/config/vector-store.js +114 -0
  56. package/core/infrastructure/constants.js +86 -0
  57. package/core/infrastructure/coreml-cascade.js +909 -0
  58. package/core/infrastructure/coreml-cascade.json +46 -0
  59. package/core/infrastructure/coreml-provider.js +81 -0
  60. package/core/infrastructure/db-utils.js +69 -0
  61. package/core/infrastructure/dedup-hashing.js +83 -0
  62. package/core/infrastructure/hardware-capability.js +332 -0
  63. package/core/infrastructure/index.js +104 -0
  64. package/core/infrastructure/language-patterns/maps.js +121 -0
  65. package/core/infrastructure/language-patterns/registry-core.js +323 -0
  66. package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
  67. package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
  68. package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
  69. package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
  70. package/core/infrastructure/language-patterns/registry.js +19 -0
  71. package/core/infrastructure/language-patterns.js +141 -0
  72. package/core/infrastructure/llm-provider.js +733 -0
  73. package/core/infrastructure/manifest.json +46 -0
  74. package/core/infrastructure/maxsim.wasm +0 -0
  75. package/core/infrastructure/model-fetcher.js +423 -0
  76. package/core/infrastructure/model-registry.js +214 -0
  77. package/core/infrastructure/native-inference.js +587 -0
  78. package/core/infrastructure/native-resolver.js +187 -0
  79. package/core/infrastructure/native-sparse-gram.js +257 -0
  80. package/core/infrastructure/native-tokenizer.js +160 -0
  81. package/core/infrastructure/onnx-mutex.js +45 -0
  82. package/core/infrastructure/onnx-session-utils.js +261 -0
  83. package/core/infrastructure/ort-pipeline.js +111 -0
  84. package/core/infrastructure/project-detector.js +102 -0
  85. package/core/infrastructure/quantization.js +410 -0
  86. package/core/infrastructure/simd-distance.js +502 -0
  87. package/core/infrastructure/simd-distance.wasm +0 -0
  88. package/core/infrastructure/tree-sitter-provider.js +665 -0
  89. package/core/infrastructure/webgpu-maxsim.js +222 -0
  90. package/core/query/index.js +35 -0
  91. package/core/query/intent-detector.js +201 -0
  92. package/core/query/intent-router.js +156 -0
  93. package/core/query/query-router-catboost.js +222 -0
  94. package/core/query/query-router-ml.js +266 -0
  95. package/core/query/query-router.js +213 -0
  96. package/core/ranking/cascaded-scorer.js +379 -0
  97. package/core/ranking/flashrank.js +810 -0
  98. package/core/ranking/index.js +49 -0
  99. package/core/ranking/late-interaction-index.js +2383 -0
  100. package/core/ranking/late-interaction-model.js +812 -0
  101. package/core/ranking/local-reranker.js +374 -0
  102. package/core/ranking/mmr.js +379 -0
  103. package/core/ranking/quality-scorer.js +363 -0
  104. package/core/search/context-expander.js +1167 -0
  105. package/core/search/dedup/sibling-expander.js +327 -0
  106. package/core/search/index.js +16 -0
  107. package/core/search/search-boost.js +259 -0
  108. package/core/search/search-cli.js +544 -0
  109. package/core/search/search-format.js +282 -0
  110. package/core/search/search-fusion.js +327 -0
  111. package/core/search/search-hybrid.js +204 -0
  112. package/core/search/search-pattern-chunks.js +337 -0
  113. package/core/search/search-pattern-planner.js +439 -0
  114. package/core/search/search-pattern-prefilter.js +412 -0
  115. package/core/search/search-pattern-ripgrep.js +663 -0
  116. package/core/search/search-pattern.js +463 -0
  117. package/core/search/search-postprocess.js +452 -0
  118. package/core/search/search-semantic.js +706 -0
  119. package/core/search/search-server.js +554 -0
  120. package/core/search/session-daemon-prewarm.mjs +164 -0
  121. package/core/search/session-warmup.js +595 -0
  122. package/core/search/sweet-search.js +632 -0
  123. package/core/search/warmup-metrics.js +532 -0
  124. package/core/start-server.js +6 -0
  125. package/core/training/query-router/features/extractor.js +762 -0
  126. package/core/training/query-router/features/multilingual-patterns.js +431 -0
  127. package/core/training/query-router/features/text-segmenter.js +303 -0
  128. package/core/training/query-router/features/unicode-utils.js +383 -0
  129. package/core/training/query-router/output/v45_router_d4.js +11521 -0
  130. package/core/training/query-router/output/v46_router_d4.js +11498 -0
  131. package/core/vector-store/binary-heap.js +227 -0
  132. package/core/vector-store/binary-hnsw-index.js +1004 -0
  133. package/core/vector-store/float-vector-store.js +234 -0
  134. package/core/vector-store/hnsw-index.js +580 -0
  135. package/core/vector-store/index.js +39 -0
  136. package/core/vector-store/seismic-index.js +498 -0
  137. package/core/vocabulary/index.js +84 -0
  138. package/core/vocabulary/vocab-constants.js +20 -0
  139. package/core/vocabulary/vocab-miner-extractors.js +375 -0
  140. package/core/vocabulary/vocab-miner-nl.js +404 -0
  141. package/core/vocabulary/vocab-miner-utils.js +146 -0
  142. package/core/vocabulary/vocab-miner.js +574 -0
  143. package/core/vocabulary/vocab-prewarm-cli.js +110 -0
  144. package/core/vocabulary/vocab-ranker.js +492 -0
  145. package/core/vocabulary/vocab-warmer.js +523 -0
  146. package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
  147. package/core/vocabulary/vocabulary-utils.js +704 -0
  148. package/crates/wasm-router/pkg/package.json +13 -0
  149. package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
  150. package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
  151. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
  152. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
  153. package/mcp/config-gen.js +121 -0
  154. package/mcp/server.js +335 -0
  155. package/mcp/tool-handlers.js +476 -0
  156. package/package.json +131 -9
  157. package/scripts/benchmark-harness.js +794 -0
  158. package/scripts/init.js +1058 -0
  159. package/scripts/smoke-test.js +435 -0
  160. package/scripts/uninstall.js +478 -0
  161. package/scripts/verify-runtime.js +176 -0
@@ -0,0 +1,502 @@
1
+ /**
2
+ * WASM-accelerated distance functions with JS fallback (Fix 6).
3
+ *
4
+ * Three-tier MaxSim acceleration:
5
+ * Tier 1: Native Rust + Rayon (60x) — parallel across CPU cores, NEON/AVX2 SIMD
6
+ * Tier 2: WASM SIMD f32x4 (16x) — portable, single-threaded
7
+ * Tier 3: JS fallback (3.5x) — norm-cached flat-buffer scoring
8
+ *
9
+ * Also provides WASM-accelerated:
10
+ * - hamming_distance, int8_dot, int8_norm_sq (simd-distance.wasm)
11
+ *
12
+ * Build: node scripts/build-wasm.js (WASM)
13
+ * cd crates/sweet-search-native && npx napi build --release --platform (native)
14
+ * cd crates/wasm-maxsim && RUSTFLAGS="-C target-feature=+simd128" cargo build --target wasm32-unknown-unknown --release (Rust WASM)
15
+ */
16
+
17
+ import { fileURLToPath } from 'url';
18
+ import { dirname, join } from 'path';
19
+ import { readFileSync, existsSync } from 'fs';
20
+ import { createRequire } from 'module';
21
+ import { resolveNativeAddon } from './native-resolver.js';
22
+
23
+ const DATA_OFFSET = 0; // SIMD popcount needs no LUT
24
+
25
+ let wasmExports = null;
26
+ let wasmMem = null;
27
+ let initDone = false;
28
+
29
+ // Rust-compiled MaxSim WASM (LLVM-optimized f32x4 SIMD + fused dequant)
30
+ let maxsimExports = null;
31
+ let maxsimMem = null;
32
+
33
+ // Native Rust + Rayon addon (Tier 1 — fastest)
34
+ let nativeMaxsim = null;
35
+
36
+ // =============================================================================
37
+ // INITIALIZATION
38
+ // =============================================================================
39
+
40
+ let initPromise = null;
41
+
42
+ async function initWasm() {
43
+ if (initDone) return true;
44
+ if (initPromise) return initPromise;
45
+
46
+ initPromise = (async () => {
47
+ try {
48
+ const __dirname = dirname(fileURLToPath(import.meta.url));
49
+ const require = createRequire(import.meta.url);
50
+
51
+ // Tier 1: Try native Rust addon (rayon parallel + NEON/AVX2 SIMD)
52
+ try {
53
+ const addonPath = resolveNativeAddon();
54
+ if (addonPath) {
55
+ nativeMaxsim = require(addonPath);
56
+ }
57
+ } catch {
58
+ // Native not available — fall through to WASM
59
+ }
60
+
61
+ // Tier 2a: Load hand-assembled SIMD distance WASM
62
+ const wasmPath = join(__dirname, 'simd-distance.wasm');
63
+ if (existsSync(wasmPath)) {
64
+ const wasmBuffer = readFileSync(wasmPath);
65
+ const { instance } = await WebAssembly.instantiate(wasmBuffer);
66
+ wasmExports = instance.exports;
67
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
68
+ }
69
+
70
+ // Tier 2b: Load Rust-compiled MaxSim WASM (LLVM-optimized, f32x4 SIMD)
71
+ const maxsimPath = join(__dirname, 'maxsim.wasm');
72
+ if (existsSync(maxsimPath)) {
73
+ const maxsimBuffer = readFileSync(maxsimPath);
74
+ const { instance } = await WebAssembly.instantiate(maxsimBuffer);
75
+ maxsimExports = instance.exports;
76
+ maxsimMem = new Uint8Array(instance.exports.memory.buffer);
77
+ }
78
+
79
+ initDone = true;
80
+
81
+ if (nativeMaxsim) {
82
+ console.log('[MaxSim] Tier 1: Native Rust + Rayon (parallel SIMD)');
83
+ } else if (maxsimExports || wasmExports?.maxsim_f32) {
84
+ console.log('[MaxSim] Tier 2: WASM SIMD f32x4');
85
+ } else {
86
+ console.log('[MaxSim] Tier 3: JS fallback');
87
+ }
88
+
89
+ return true;
90
+ } catch {
91
+ initPromise = null;
92
+ return false;
93
+ }
94
+ })();
95
+
96
+ return initPromise;
97
+ }
98
+
99
+ // Eager non-blocking init
100
+ initWasm().catch(() => {});
101
+
102
+ // =============================================================================
103
+ // POPCOUNT LUT (JS fallback)
104
+ // =============================================================================
105
+
106
+ const POPCOUNT_LUT = new Uint8Array(256);
107
+ for (let i = 0; i < 256; i++) {
108
+ POPCOUNT_LUT[i] = (i & 1) + POPCOUNT_LUT[i >> 1];
109
+ }
110
+
111
+ // =============================================================================
112
+ // HAMMING DISTANCE
113
+ // =============================================================================
114
+
115
+ export function wasmHammingDistance(a, b) {
116
+ if (wasmExports) {
117
+ const aPtr = DATA_OFFSET;
118
+ const bPtr = DATA_OFFSET + a.length;
119
+ // Re-acquire view in case memory grew
120
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
121
+ wasmMem.set(a, aPtr);
122
+ wasmMem.set(b, bPtr);
123
+ return wasmExports.hamming_distance(aPtr, bPtr, a.length);
124
+ }
125
+ // JS fallback
126
+ let distance = 0;
127
+ for (let i = 0; i < a.length; i++) {
128
+ distance += POPCOUNT_LUT[a[i] ^ b[i]];
129
+ }
130
+ return distance;
131
+ }
132
+
133
+ // =============================================================================
134
+ // INT8 COSINE SIMILARITY
135
+ // =============================================================================
136
+
137
+ export function wasmInt8Cosine(a, b) {
138
+ if (wasmExports) {
139
+ const aPtr = DATA_OFFSET;
140
+ const bPtr = DATA_OFFSET + a.length;
141
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
142
+ wasmMem.set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength), aPtr);
143
+ wasmMem.set(new Uint8Array(b.buffer, b.byteOffset, b.byteLength), bPtr);
144
+
145
+ const dot = wasmExports.int8_dot(aPtr, bPtr, a.length);
146
+ const normA = wasmExports.int8_norm_sq(aPtr, a.length);
147
+ const normB = wasmExports.int8_norm_sq(bPtr, b.length);
148
+
149
+ const na = Math.sqrt(normA);
150
+ const nb = Math.sqrt(normB);
151
+ if (na === 0 || nb === 0) return 0;
152
+ return dot / (na * nb);
153
+ }
154
+ // JS fallback
155
+ let dot = 0, normA = 0, normB = 0;
156
+ for (let i = 0; i < a.length; i++) {
157
+ dot += a[i] * b[i];
158
+ normA += a[i] * a[i];
159
+ normB += b[i] * b[i];
160
+ }
161
+ const na = Math.sqrt(normA);
162
+ const nb = Math.sqrt(normB);
163
+ if (na === 0 || nb === 0) return 0;
164
+ return dot / (na * nb);
165
+ }
166
+
167
+ // =============================================================================
168
+ // INT8 BATCHED DOT PRODUCT (Phase 1: copy query once, score slab)
169
+ // =============================================================================
170
+
171
+ /**
172
+ * Batch int8 dot product: score multiple candidate vectors against one query.
173
+ * Copies query once to WASM memory, candidates as contiguous slab.
174
+ * Returns raw int8 dot products (not normalized).
175
+ *
176
+ * For normalized int8 vectors (from L2-normalized floats), the raw dot
177
+ * approximates cosine * 127². Divide by 16129 for cosine-scale scores.
178
+ *
179
+ * @param {Int8Array} query - Query int8 vector
180
+ * @param {Int8Array[]} candidates - Array of candidate int8 vectors
181
+ * @returns {Int32Array} Raw dot products
182
+ */
183
+ export function wasmInt8BatchDot(query, candidates) {
184
+ const dim = query.length;
185
+ const count = candidates.length;
186
+ if (count === 0) return new Int32Array(0);
187
+
188
+ if (wasmExports && wasmExports.int8_batch_dot) {
189
+ const queryPtr = DATA_OFFSET;
190
+ const slabPtr = DATA_OFFSET + dim;
191
+ // Scores buffer: 4 bytes per candidate, placed after the slab
192
+ const scoresPtr = slabPtr + count * dim;
193
+ // Align scores pointer to 4-byte boundary
194
+ const alignedScoresPtr = (scoresPtr + 3) & ~3;
195
+ const needed = alignedScoresPtr + count * 4;
196
+
197
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
198
+ if (needed > wasmMem.length) {
199
+ return _jsInt8BatchDot(query, candidates);
200
+ }
201
+
202
+ // Copy query once
203
+ wasmMem.set(new Uint8Array(query.buffer, query.byteOffset, query.byteLength), queryPtr);
204
+
205
+ // Copy all candidates as contiguous slab
206
+ for (let i = 0; i < count; i++) {
207
+ const v = candidates[i];
208
+ wasmMem.set(new Uint8Array(v.buffer, v.byteOffset, v.byteLength), slabPtr + i * dim);
209
+ }
210
+
211
+ // Single WASM call: scores all candidates internally, writes to output buffer
212
+ wasmExports.int8_batch_dot(queryPtr, slabPtr, count, dim, alignedScoresPtr);
213
+
214
+ // Read scores from WASM memory
215
+ return new Int32Array(wasmExports.memory.buffer, alignedScoresPtr, count);
216
+ }
217
+
218
+ return _jsInt8BatchDot(query, candidates);
219
+ }
220
+
221
+ function _jsInt8BatchDot(query, candidates) {
222
+ const dim = query.length;
223
+ const scores = new Int32Array(candidates.length);
224
+ for (let c = 0; c < candidates.length; c++) {
225
+ let dot = 0;
226
+ const v = candidates[c];
227
+ for (let i = 0; i < dim; i++) dot += query[i] * v[i];
228
+ scores[c] = dot;
229
+ }
230
+ return scores;
231
+ }
232
+
233
+ // =============================================================================
234
+ // FLOAT32 BATCHED DOT PRODUCT (Phase 2: Stage 2.5 float rescore)
235
+ // =============================================================================
236
+
237
+ /**
238
+ * Batch float32 dot product: score multiple float vectors against one query.
239
+ * Pure JS implementation — V8 JIT is competitive with WASM for float math.
240
+ *
241
+ * @param {Float32Array} query - Query float vector
242
+ * @param {Float32Array[]} candidates - Array of candidate float vectors
243
+ * @returns {Float64Array} Dot product scores
244
+ */
245
+ export function float32BatchDot(query, candidates) {
246
+ const dim = query.length;
247
+ const scores = new Float64Array(candidates.length);
248
+ for (let c = 0; c < candidates.length; c++) {
249
+ const v = candidates[c];
250
+ if (v.length !== dim) {
251
+ throw new Error(`float32BatchDot dimension mismatch: query=${dim}, candidate[${c}]=${v.length}`);
252
+ }
253
+ let dot = 0;
254
+ for (let i = 0; i < dim; i++) dot += query[i] * v[i];
255
+ scores[c] = dot;
256
+ }
257
+ return scores;
258
+ }
259
+
260
+ // For L2-normalized vectors, dot product = cosine similarity.
261
+ // Use float32BatchDot directly — no separate cosine wrapper needed.
262
+
263
+ // =============================================================================
264
+ // INT8 DOT PRODUCT (raw, for asymmetric distance)
265
+ // =============================================================================
266
+
267
+ export function wasmInt8Dot(a, b) {
268
+ if (wasmExports) {
269
+ const aPtr = DATA_OFFSET;
270
+ const bPtr = DATA_OFFSET + a.length;
271
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
272
+ wasmMem.set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength), aPtr);
273
+ wasmMem.set(new Uint8Array(b.buffer, b.byteOffset, b.byteLength), bPtr);
274
+ return wasmExports.int8_dot(aPtr, bPtr, a.length);
275
+ }
276
+ // JS fallback
277
+ let dot = 0;
278
+ for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
279
+ return dot;
280
+ }
281
+
282
+ // =============================================================================
283
+ // STATUS
284
+ // =============================================================================
285
+
286
+ // =============================================================================
287
+ // ASYMMETRIC DISTANCE (1-bit doc × int4 query)
288
+ // =============================================================================
289
+
290
+ export function wasmAsymmetricDistance(docBinary, queryInt4, queryNormScaled) {
291
+ if (wasmExports) {
292
+ const docPtr = DATA_OFFSET;
293
+ const queryPtr = DATA_OFFSET + docBinary.length;
294
+ wasmMem = new Uint8Array(wasmExports.memory.buffer);
295
+ wasmMem.set(docBinary, docPtr);
296
+ wasmMem.set(new Uint8Array(queryInt4.buffer, queryInt4.byteOffset, queryInt4.byteLength), queryPtr);
297
+ return wasmExports.asymmetric_distance(docPtr, queryPtr, queryInt4.length, queryNormScaled);
298
+ }
299
+ // JS fallback
300
+ let approxDot = 0;
301
+ const dim = queryInt4.length;
302
+ for (let byteIdx = 0; byteIdx < docBinary.length; byteIdx++) {
303
+ let byte = docBinary[byteIdx];
304
+ const baseIdx = byteIdx * 8;
305
+ for (let bit = 7; bit >= 0; bit--) {
306
+ const idx = baseIdx + (7 - bit);
307
+ if (idx >= dim) break;
308
+ if (byte & (1 << bit)) approxDot += queryInt4[idx];
309
+ else approxDot -= queryInt4[idx];
310
+ }
311
+ }
312
+ return queryNormScaled - 2 * approxDot;
313
+ }
314
+
315
+ // =============================================================================
316
+ // WASM MAXSIM F32
317
+ // =============================================================================
318
+
319
+ /**
320
+ * Compute MaxSim score entirely in WASM f32 arithmetic.
321
+ *
322
+ * Copies query and doc float32 tokens to WASM memory, calls the WASM
323
+ * maxsim_f32 kernel, returns the score. ~10-30x faster than JS for
324
+ * large Q×D×dim workloads.
325
+ *
326
+ * Falls back to null if WASM is unavailable or data exceeds memory.
327
+ *
328
+ * @param {Float32Array} queryFlat - Q × dim float32 (contiguous)
329
+ * @param {Float32Array} docFlat - D × dim float32 (contiguous)
330
+ * @param {number} numQ - Number of query tokens
331
+ * @param {number} numD - Number of document tokens
332
+ * @param {number} dim - Token dimension
333
+ * @returns {number|null} MaxSim score, or null if WASM unavailable
334
+ */
335
+ export function wasmMaxSimF32(queryFlat, docFlat, numQ, numD, dim) {
336
+ // Use Rust-compiled WASM only (handles arbitrary dim via scalar tail)
337
+ if (!maxsimExports?.maxsim_f32) return null;
338
+ const exports = maxsimExports;
339
+
340
+ const mem = maxsimExports
341
+ ? new Uint8Array(maxsimExports.memory.buffer)
342
+ : (wasmMem = new Uint8Array(wasmExports.memory.buffer));
343
+
344
+ const qBytes = numQ * dim * 4;
345
+ const dBytes = numD * dim * 4;
346
+
347
+ if (qBytes + dBytes + 1024 > mem.length) return null;
348
+
349
+ const qPtr = DATA_OFFSET;
350
+ const dPtr = DATA_OFFSET + qBytes;
351
+
352
+ mem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
353
+ mem.set(new Uint8Array(docFlat.buffer, docFlat.byteOffset, dBytes), dPtr);
354
+
355
+ return exports.maxsim_f32(qPtr, dPtr, numQ, numD, dim);
356
+ }
357
+
358
+ /**
359
+ * Compute MaxSim with fused int8 dequantization in WASM.
360
+ *
361
+ * Skips JS-side dequantization entirely: copies raw int8 tokens (4x smaller)
362
+ * to WASM memory and dequantizes inline during scoring.
363
+ *
364
+ * @param {Float32Array} queryFlat - Q × dim float32 (contiguous)
365
+ * @param {Int8Array} docInt8 - Raw int8 document tokens (D × dim)
366
+ * @param {number} numQ - Number of query tokens
367
+ * @param {number} numD - Number of document tokens
368
+ * @param {number} dim - Token dimension
369
+ * @param {number} min - Quantization min
370
+ * @param {number} scale - Quantization scale
371
+ * @returns {number|null} MaxSim score, or null if unavailable
372
+ */
373
+ export function wasmMaxSimDequant(queryFlat, docInt8, numQ, numD, dim, min, scale) {
374
+ if (!maxsimExports?.maxsim_dequant) return null;
375
+
376
+ const qBytes = numQ * dim * 4; // float32
377
+ const dBytes = numD * dim; // int8 (4x smaller!)
378
+
379
+ const mem = new Uint8Array(maxsimExports.memory.buffer);
380
+ if (qBytes + dBytes + 1024 > mem.length) return null;
381
+
382
+ const qPtr = DATA_OFFSET;
383
+ const dPtr = DATA_OFFSET + qBytes;
384
+
385
+ // Copy query (float32)
386
+ mem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
387
+ // Copy doc int8 (4x smaller copy than float32!)
388
+ mem.set(new Uint8Array(docInt8.buffer, docInt8.byteOffset, dBytes), dPtr);
389
+
390
+ return maxsimExports.maxsim_dequant(qPtr, dPtr, numQ, numD, dim, min, scale);
391
+ }
392
+
393
+ // Shared layout: copy query + doc + per-token params into WASM memory, return pointers.
394
+ // Returns null if data doesn't fit in WASM memory.
395
+ function _wasmPerTokenLayout(queryFlat, docData, minArray, scaleArray, tokenNorms, numQ, numD, dim, dBytes) {
396
+ const qBytes = numQ * dim * 4;
397
+ const paramBytes = numD * 4 * 3;
398
+ if (!maxsimMem || maxsimMem.buffer !== maxsimExports.memory.buffer) {
399
+ maxsimMem = new Uint8Array(maxsimExports.memory.buffer);
400
+ }
401
+ if (qBytes + dBytes + paramBytes + 1024 > maxsimMem.length) return null;
402
+
403
+ const qPtr = DATA_OFFSET;
404
+ const dPtr = qPtr + qBytes;
405
+ const minPtr = dPtr + dBytes;
406
+ const scalePtr = minPtr + numD * 4;
407
+ const normPtr = scalePtr + numD * 4;
408
+
409
+ maxsimMem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
410
+ maxsimMem.set(new Uint8Array(docData.buffer, docData.byteOffset, dBytes), dPtr);
411
+ maxsimMem.set(new Uint8Array(minArray.buffer, minArray.byteOffset, numD * 4), minPtr);
412
+ maxsimMem.set(new Uint8Array(scaleArray.buffer, scaleArray.byteOffset, numD * 4), scalePtr);
413
+ maxsimMem.set(new Uint8Array(tokenNorms.buffer, tokenNorms.byteOffset, numD * 4), normPtr);
414
+
415
+ return { qPtr, dPtr, minPtr, scalePtr, normPtr };
416
+ }
417
+
418
+ export function wasmMaxSimDequantPerToken(queryFlat, docInt8, minArray, scaleArray, tokenNorms, numQ, numD, dim) {
419
+ if (!maxsimExports?.maxsim_dequant_pertoken) return null;
420
+ const ptrs = _wasmPerTokenLayout(queryFlat, docInt8, minArray, scaleArray, tokenNorms, numQ, numD, dim, numD * dim);
421
+ if (!ptrs) return null;
422
+ return maxsimExports.maxsim_dequant_pertoken(ptrs.qPtr, ptrs.dPtr, ptrs.minPtr, ptrs.scalePtr, ptrs.normPtr, numQ, numD, dim);
423
+ }
424
+
425
+ export function wasmMaxSimDequant4Bit(queryFlat, docPacked, minArray, scaleArray, tokenNorms, numQ, numD, dim) {
426
+ if (!maxsimExports?.maxsim_dequant_4bit) return null;
427
+ const ptrs = _wasmPerTokenLayout(queryFlat, docPacked, minArray, scaleArray, tokenNorms, numQ, numD, dim, numD * Math.ceil(dim / 2));
428
+ if (!ptrs) return null;
429
+ return maxsimExports.maxsim_dequant_4bit(ptrs.qPtr, ptrs.dPtr, ptrs.minPtr, ptrs.scalePtr, ptrs.normPtr, numQ, numD, dim);
430
+ }
431
+
432
+ // =============================================================================
433
+ // NATIVE MAXSIM BATCH (Tier 1 — rayon parallel)
434
+ // =============================================================================
435
+
436
+ /**
437
+ * Score all candidates in parallel using the native Rust + Rayon addon.
438
+ *
439
+ * @param {Float32Array} queryFlat - Q × dim float32
440
+ * @param {number} numQ
441
+ * @param {number} dim
442
+ * @param {Array<{tokens: Int8Array|Buffer, numTokens: number, dim: number, min: number, scale: number}>} candidates
443
+ * @returns {number[]|null} Array of MaxSim scores, or null if native unavailable
444
+ */
445
+ export function nativeMaxSimBatch(queryFlat, numQ, dim, candidates) {
446
+ if (!nativeMaxsim) return null;
447
+ return nativeMaxsim.maxsimScoreBatch(queryFlat, numQ, dim, candidates);
448
+ }
449
+
450
+ /**
451
+ * Native batch scoring with per-token min/scale and pre-stored norms (Phase 4).
452
+ * @param {Float32Array} queryFlat
453
+ * @param {number} numQ
454
+ * @param {number} dim
455
+ * @param {Array<{tokens: Buffer, numTokens: number, dim: number, minArray: Float32Array, scaleArray: Float32Array, tokenNorms: Float32Array}>} candidates
456
+ * @returns {number[]|null}
457
+ */
458
+ export function nativeMaxSimBatchPerToken(queryFlat, numQ, dim, candidates) {
459
+ if (!nativeMaxsim?.maxsimScoreBatchPertoken) return null;
460
+ return nativeMaxsim.maxsimScoreBatchPertoken(queryFlat, numQ, dim, candidates);
461
+ }
462
+
463
+ /**
464
+ * Native batch scoring with 4-bit nibble-packed tokens (Phase 4).
465
+ * @param {Float32Array} queryFlat
466
+ * @param {number} numQ
467
+ * @param {number} dim
468
+ * @param {Array<{tokens: Buffer, numTokens: number, dim: number, minArray: Float32Array, scaleArray: Float32Array, tokenNorms: Float32Array}>} candidates
469
+ * @returns {number[]|null}
470
+ */
471
+ export function nativeMaxSimBatch4Bit(queryFlat, numQ, dim, candidates) {
472
+ if (!nativeMaxsim?.maxsimScoreBatch4Bit) return null;
473
+ return nativeMaxsim.maxsimScoreBatch4Bit(queryFlat, numQ, dim, candidates);
474
+ }
475
+
476
+ export function isNativePerTokenAvailable() {
477
+ return !!nativeMaxsim?.maxsimScoreBatchPertoken;
478
+ }
479
+
480
+ export function isNative4BitAvailable() {
481
+ return !!nativeMaxsim?.maxsimScoreBatch4Bit;
482
+ }
483
+
484
+ export function isWasmAvailable() {
485
+ return !!wasmExports;
486
+ }
487
+
488
+ export function isMaxSimWasmAvailable() {
489
+ return !!maxsimExports;
490
+ }
491
+
492
+ export function isNativeMaxSimAvailable() {
493
+ return !!nativeMaxsim;
494
+ }
495
+
496
+ export function getMaxSimTier() {
497
+ if (nativeMaxsim) return 'native';
498
+ if (maxsimExports || wasmExports?.maxsim_f32) return 'wasm';
499
+ return 'js';
500
+ }
501
+
502
+ export { initWasm };