@delali/narsil 0.1.5 → 0.1.7

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.
@@ -1,6 +1,6 @@
1
- import { createHNSWIndex, magnitude, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes } from './chunk-GVN2W4VA.mjs';
2
- import { english } from './chunk-WYC635GI.mjs';
3
- import { NarsilError, ErrorCodes } from './chunk-7GKPWGCN.mjs';
1
+ import { createVectorStore, createHNSWIndex, magnitude, createBoundedMaxHeap, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes } from './chunk-VVORJOXR.mjs';
2
+ import { english } from './chunk-I24SGLFS.mjs';
3
+ import { NarsilError, ErrorCodes, createNarsilError } from './chunk-R2C2EQGO.mjs';
4
4
  import { decode, encode } from '@msgpack/msgpack';
5
5
 
6
6
  // src/languages/registry.ts
@@ -43,6 +43,67 @@ function mergeFacets(partitionFacets) {
43
43
  return result;
44
44
  }
45
45
 
46
+ // src/core/bitset.ts
47
+ function createBitSet(capacity) {
48
+ return new Uint32Array(capacity + 31 >>> 5 || 1);
49
+ }
50
+ function bitsetSet(bs, index) {
51
+ const word = index >>> 5;
52
+ if (word < bs.length) {
53
+ bs[word] |= 1 << (index & 31);
54
+ }
55
+ }
56
+ function bitsetHas(bs, index) {
57
+ const word = index >>> 5;
58
+ if (word >= bs.length) return false;
59
+ return (bs[word] & 1 << (index & 31)) !== 0;
60
+ }
61
+ function bitsetAnd(a, b) {
62
+ const len = Math.min(a.length, b.length);
63
+ const result = new Uint32Array(len);
64
+ for (let i = 0; i < len; i++) {
65
+ result[i] = a[i] & b[i];
66
+ }
67
+ return result;
68
+ }
69
+ function bitsetOr(a, b) {
70
+ const longer = a.length >= b.length ? a : b;
71
+ const shorter = a.length >= b.length ? b : a;
72
+ const result = new Uint32Array(longer.length);
73
+ for (let i = 0; i < shorter.length; i++) {
74
+ result[i] = longer[i] | shorter[i];
75
+ }
76
+ for (let i = shorter.length; i < longer.length; i++) {
77
+ result[i] = longer[i];
78
+ }
79
+ return result;
80
+ }
81
+ function bitsetNot(bs, capacity) {
82
+ const result = new Uint32Array(bs.length);
83
+ const fullWords = capacity >>> 5;
84
+ for (let i = 0; i < fullWords; i++) {
85
+ result[i] = ~bs[i];
86
+ }
87
+ const remainder = capacity & 31;
88
+ if (remainder > 0 && fullWords < bs.length) {
89
+ result[fullWords] = ~bs[fullWords] & (1 << remainder) - 1;
90
+ }
91
+ return result;
92
+ }
93
+ function bitsetIsEmpty(bs) {
94
+ for (let i = 0; i < bs.length; i++) {
95
+ if (bs[i] !== 0) return false;
96
+ }
97
+ return true;
98
+ }
99
+ function bitsetFromSet(set, capacity) {
100
+ const bs = createBitSet(capacity);
101
+ for (const id of set) {
102
+ bs[id >>> 5] |= 1 << (id & 31);
103
+ }
104
+ return bs;
105
+ }
106
+
46
107
  // src/core/fuzzy.ts
47
108
  function boundedLevenshtein(a, b, tolerance) {
48
109
  if (tolerance < 0) return { distance: -1, withinTolerance: false };
@@ -95,8 +156,67 @@ function boundedLevenshtein(a, b, tolerance) {
95
156
  // src/core/tokenizer.ts
96
157
  var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{N}_'-]+/u;
97
158
  var DEFAULT_MIN_TOKEN_LENGTH = 1;
159
+ var CACHE_SIZE_FLOOR = 5e4;
160
+ var CACHE_SIZE_CEILING = 2e6;
161
+ var BYTES_PER_ENTRY = 200;
162
+ function computeDefaultCacheSize() {
163
+ try {
164
+ if (typeof process !== "undefined" && typeof process.versions?.node === "string" && typeof window === "undefined") {
165
+ const constrainedMemory = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
166
+ if (constrainedMemory > 0) {
167
+ const budget = Math.floor(constrainedMemory * 0.05 / BYTES_PER_ENTRY);
168
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(budget, CACHE_SIZE_CEILING));
169
+ }
170
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(1e6, CACHE_SIZE_CEILING));
171
+ }
172
+ if (typeof navigator !== "undefined") {
173
+ const mem = navigator.deviceMemory;
174
+ if (typeof mem === "number" && mem > 0) {
175
+ let entries;
176
+ if (mem <= 1) entries = 1e5;
177
+ else if (mem <= 4) entries = 25e4;
178
+ else entries = 5e5;
179
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(entries, CACHE_SIZE_CEILING));
180
+ }
181
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(2e5, CACHE_SIZE_CEILING));
182
+ }
183
+ } catch {
184
+ }
185
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(2e5, CACHE_SIZE_CEILING));
186
+ }
98
187
  var normalizationCache = /* @__PURE__ */ new Map();
99
- var MAX_CACHE_SIZE = 65536;
188
+ var maxCacheSize = computeDefaultCacheSize();
189
+ function configureNormalizationCache(maxSize) {
190
+ if (!Number.isFinite(maxSize)) {
191
+ throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must be a finite number", {
192
+ received: maxSize
193
+ });
194
+ }
195
+ if (maxSize < 0) {
196
+ throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must not be negative", {
197
+ received: maxSize
198
+ });
199
+ }
200
+ if (maxSize === 0) {
201
+ throw createNarsilError(
202
+ ErrorCodes.CONFIG_INVALID,
203
+ "tokenizerCacheSize must be greater than zero; the normalization cache cannot be disabled",
204
+ {
205
+ received: maxSize
206
+ }
207
+ );
208
+ }
209
+ maxCacheSize = Math.max(CACHE_SIZE_FLOOR, Math.min(Math.floor(maxSize), CACHE_SIZE_CEILING));
210
+ if (normalizationCache.size > maxCacheSize) {
211
+ const excess = normalizationCache.size - maxCacheSize;
212
+ let count = 0;
213
+ for (const key of normalizationCache.keys()) {
214
+ if (count >= excess) break;
215
+ normalizationCache.delete(key);
216
+ count++;
217
+ }
218
+ }
219
+ }
100
220
  var cachedLangName = "";
101
221
  var cachedFlags = "";
102
222
  var cachedPrefix = "";
@@ -128,9 +248,14 @@ function transformToken(raw, language, stem, removeDiacritics) {
128
248
  if (stem && language.stemmer) {
129
249
  normalized = language.stemmer(normalized);
130
250
  }
131
- if (normalizationCache.size >= MAX_CACHE_SIZE) {
132
- const firstKey = normalizationCache.keys().next().value;
133
- normalizationCache.delete(firstKey);
251
+ if (normalizationCache.size >= maxCacheSize) {
252
+ const evictCount = Math.max(1, maxCacheSize >>> 2);
253
+ let count = 0;
254
+ for (const key of normalizationCache.keys()) {
255
+ if (count >= evictCount) break;
256
+ normalizationCache.delete(key);
257
+ count++;
258
+ }
134
259
  }
135
260
  normalizationCache.set(cacheKey, normalized);
136
261
  return normalized;
@@ -186,6 +311,53 @@ function tokenize(text, language, options) {
186
311
  }
187
312
  return { tokens, originalTokens };
188
313
  }
314
+ function* tokenizeIterator(text, language, options) {
315
+ const {
316
+ stem = true,
317
+ removeStopWords = true,
318
+ removeDiacritics = false,
319
+ customTokenizer,
320
+ stopWordOverride
321
+ } = options ?? {};
322
+ if (customTokenizer) {
323
+ const customResult = customTokenizer.tokenize(text);
324
+ for (const entry of customResult) {
325
+ yield entry;
326
+ }
327
+ return;
328
+ }
329
+ const normalized = isAscii(text) ? text.toLowerCase() : text.normalize("NFC").toLowerCase();
330
+ const rawParts = splitText(normalized, language);
331
+ const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
332
+ const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
333
+ const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
334
+ let position = 0;
335
+ for (const part of rawParts) {
336
+ if (part.length < minLength) {
337
+ position++;
338
+ continue;
339
+ }
340
+ if (stopWords.has(part)) {
341
+ position++;
342
+ continue;
343
+ }
344
+ const processed = transformToken(part, language, stem, effectiveDiacritics);
345
+ if (processed.length > 0) {
346
+ yield { token: processed, position };
347
+ }
348
+ position++;
349
+ }
350
+ }
351
+ function clearNormalizationCache() {
352
+ normalizationCache.clear();
353
+ }
354
+ function resetNormalizationCache() {
355
+ normalizationCache.clear();
356
+ maxCacheSize = computeDefaultCacheSize();
357
+ }
358
+ function getNormalizationCacheSize() {
359
+ return normalizationCache.size;
360
+ }
189
361
 
190
362
  // src/schema/validator.ts
191
363
  var SCALAR_FIELD_TYPES = /* @__PURE__ */ new Set([
@@ -201,6 +373,7 @@ var SCALAR_FIELD_TYPES = /* @__PURE__ */ new Set([
201
373
  ]);
202
374
  var VECTOR_PATTERN = /^vector\[(\d+)]$/;
203
375
  var RESERVED_ROOT_FIELDS = /* @__PURE__ */ new Set(["id"]);
376
+ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
204
377
  var MAX_NESTING_DEPTH = 4;
205
378
  function isPlainObject(value) {
206
379
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -215,6 +388,12 @@ function validateSchemaFields(schema, depth, prefix) {
215
388
  }
216
389
  for (const [field, type] of Object.entries(schema)) {
217
390
  const path = prefix ? `${prefix}.${field}` : field;
391
+ if (PROTOTYPE_POLLUTION_KEYS.has(field)) {
392
+ throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field name "${field}" is not allowed in a schema`, {
393
+ field,
394
+ path
395
+ });
396
+ }
218
397
  if (depth === 1 && RESERVED_ROOT_FIELDS.has(field)) {
219
398
  throw new NarsilError(
220
399
  ErrorCodes.SCHEMA_INVALID_TYPE,
@@ -481,6 +660,27 @@ function validateDocumentStrict(document, schema) {
481
660
  );
482
661
  }
483
662
  }
663
+ function resolveNestedValue(document, path) {
664
+ if (!path.includes(".")) return document[path];
665
+ const segments = path.split(".");
666
+ let current = document;
667
+ for (const segment of segments) {
668
+ if (current === null || current === void 0 || typeof current !== "object") return void 0;
669
+ current = current[segment];
670
+ }
671
+ return current;
672
+ }
673
+ function validateRequiredFields(document, required) {
674
+ if (required.length === 0) return;
675
+ for (const field of required) {
676
+ const value = resolveNestedValue(document, field);
677
+ if (value === void 0 || value === null) {
678
+ throw new NarsilError(ErrorCodes.DOC_MISSING_REQUIRED_FIELD, `Document is missing required field "${field}"`, {
679
+ field
680
+ });
681
+ }
682
+ }
683
+ }
484
684
  function flattenRecursive(schema, prefix, result) {
485
685
  for (const [field, type] of Object.entries(schema)) {
486
686
  const path = prefix ? `${prefix}.${field}` : field;
@@ -496,6 +696,17 @@ function flattenSchema(schema) {
496
696
  flattenRecursive(schema, "", result);
497
697
  return result;
498
698
  }
699
+ function extractVectorFieldsFromSchema(schema) {
700
+ const flat = flattenSchema(schema);
701
+ const result = /* @__PURE__ */ new Map();
702
+ for (const [fieldPath, fieldType] of Object.entries(flat)) {
703
+ const match = VECTOR_PATTERN.exec(fieldType);
704
+ if (match) {
705
+ result.set(fieldPath, Number.parseInt(match[1], 10));
706
+ }
707
+ }
708
+ return result;
709
+ }
499
710
 
500
711
  // src/search/fulltext.ts
501
712
  function fulltextSearch(partition, params, language, schema, options) {
@@ -519,9 +730,16 @@ function fulltextSearch(partition, params, language, schema, options) {
519
730
  return { scored: [], totalMatched: 0 };
520
731
  }
521
732
  const queryTokens = deduplicateTokens(queryTokenResult.tokens);
522
- const hasPostFilters = params.filters !== void 0 || params.minScore !== void 0 || params.termMatch !== void 0 && params.termMatch !== "any";
733
+ let filterBitset;
734
+ if (params.filters) {
735
+ filterBitset = partition.applyFiltersBitset(params.filters, schema);
736
+ if (bitsetIsEmpty(filterBitset)) {
737
+ return { scored: [], totalMatched: 0 };
738
+ }
739
+ }
740
+ const needsAllResults = params.minScore !== void 0 || params.termMatch !== void 0 && params.termMatch !== "any" || params.sort !== void 0 || params.group !== void 0 || params.pinned !== void 0 || params.searchAfter !== void 0;
523
741
  const requestedLimit = params.limit !== void 0 || params.offset !== void 0 ? (params.limit ?? 10) + (params.offset ?? 0) + 1 : void 0;
524
- const maxResults = requestedLimit !== void 0 && !hasPostFilters ? requestedLimit : void 0;
742
+ const maxResults = requestedLimit !== void 0 && !needsAllResults ? requestedLimit : void 0;
525
743
  const rawResult = partition.searchFulltext({
526
744
  queryTokens,
527
745
  fields: params.fields,
@@ -532,7 +750,8 @@ function fulltextSearch(partition, params, language, schema, options) {
532
750
  bm25Params: options?.bm25Params,
533
751
  globalStats: options?.globalStats,
534
752
  maxResults,
535
- termMatch: params.termMatch
753
+ termMatch: params.termMatch,
754
+ filterBitset
536
755
  });
537
756
  let scored = rawResult.scored;
538
757
  const termMatch = params.termMatch ?? "any";
@@ -543,11 +762,7 @@ function fulltextSearch(partition, params, language, schema, options) {
543
762
  const threshold = params.minScore;
544
763
  scored = scored.filter((doc) => doc.score >= threshold);
545
764
  }
546
- if (params.filters) {
547
- const matchingDocIds = partition.applyFilters(params.filters, schema);
548
- scored = scored.filter((doc) => matchingDocIds.has(doc.docId));
549
- }
550
- const totalMatched = hasPostFilters ? scored.length : rawResult.totalMatched;
765
+ const totalMatched = needsAllResults ? scored.length : rawResult.totalMatched;
551
766
  return { scored, totalMatched };
552
767
  }
553
768
  function validateSearchFields(fields, flatSchema) {
@@ -608,109 +823,6 @@ function queryTermSatisfied(queryToken, indexTokens, tolerance, exact) {
608
823
  return false;
609
824
  }
610
825
 
611
- // src/search/hybrid.ts
612
- function hybridSearch(partition, params, language, schema, options) {
613
- const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
614
- const hasVector = params.vector !== void 0;
615
- if (!hasTerm && !hasVector) {
616
- return { scored: [], totalMatched: 0 };
617
- }
618
- if (hasTerm && !hasVector) {
619
- return fulltextSearch(partition, params, language, schema, options);
620
- }
621
- if (!hasTerm && hasVector) {
622
- return runVectorOnly(partition, params);
623
- }
624
- const textResult = fulltextSearch(partition, params, language, schema, options);
625
- const vectorConfig = params.vector;
626
- const vectorResult = partition.searchVector({
627
- field: vectorConfig.field,
628
- value: vectorConfig.value,
629
- k: params.limit ?? 10,
630
- similarity: vectorConfig.similarity,
631
- metric: vectorConfig.metric,
632
- efSearch: vectorConfig.efSearch
633
- });
634
- const normalizedText = normalizeScores(textResult.scored);
635
- const normalizedVector = normalizeScores(vectorResult.scored);
636
- const textDocMap = /* @__PURE__ */ new Map();
637
- for (const doc of textResult.scored) {
638
- textDocMap.set(doc.docId, doc);
639
- }
640
- const mergedMap = /* @__PURE__ */ new Map();
641
- for (const doc of normalizedText) {
642
- mergedMap.set(doc.docId, {
643
- textScore: doc.score,
644
- vectorScore: 0,
645
- textDoc: textDocMap.get(doc.docId) ?? null
646
- });
647
- }
648
- for (const doc of normalizedVector) {
649
- const existing = mergedMap.get(doc.docId);
650
- if (existing) {
651
- existing.vectorScore = doc.score;
652
- } else {
653
- mergedMap.set(doc.docId, { textScore: 0, vectorScore: doc.score, textDoc: null });
654
- }
655
- }
656
- const alpha = clampAlpha(params.hybrid?.alpha);
657
- const scored = [];
658
- for (const [docId, entry] of mergedMap) {
659
- const combinedScore = alpha * entry.vectorScore + (1 - alpha) * entry.textScore;
660
- scored.push({
661
- docId,
662
- score: combinedScore,
663
- termFrequencies: entry.textDoc?.termFrequencies ?? {},
664
- fieldLengths: entry.textDoc?.fieldLengths ?? {},
665
- idf: entry.textDoc?.idf ?? {}
666
- });
667
- }
668
- scored.sort((a, b) => b.score - a.score || a.docId.localeCompare(b.docId));
669
- let filtered = scored;
670
- if (params.minScore !== void 0 && params.minScore > 0) {
671
- const threshold = params.minScore;
672
- filtered = filtered.filter((doc) => doc.score >= threshold);
673
- }
674
- if (params.filters) {
675
- const matchingDocIds = partition.applyFilters(params.filters, schema);
676
- filtered = filtered.filter((doc) => matchingDocIds.has(doc.docId));
677
- }
678
- return { scored: filtered, totalMatched: filtered.length };
679
- }
680
- function runVectorOnly(partition, params) {
681
- const vectorConfig = params.vector;
682
- const result = partition.searchVector({
683
- field: vectorConfig.field,
684
- value: vectorConfig.value,
685
- k: params.limit ?? 10,
686
- similarity: vectorConfig.similarity,
687
- metric: vectorConfig.metric,
688
- efSearch: vectorConfig.efSearch
689
- });
690
- return result;
691
- }
692
- function normalizeScores(docs) {
693
- if (docs.length === 0) return [];
694
- let min = docs[0].score;
695
- let max = docs[0].score;
696
- for (let i = 1; i < docs.length; i++) {
697
- if (docs[i].score < min) min = docs[i].score;
698
- if (docs[i].score > max) max = docs[i].score;
699
- }
700
- const range = max - min;
701
- if (range === 0) {
702
- return docs.map((d) => ({ docId: d.docId, score: 1 }));
703
- }
704
- return docs.map((d) => ({ docId: d.docId, score: (d.score - min) / range }));
705
- }
706
- function clampAlpha(alpha) {
707
- if (alpha === void 0) return 0.5;
708
- if (!Number.isFinite(alpha)) return 0.5;
709
- if (alpha < 0) return 0;
710
- if (alpha > 1) return 1;
711
- return alpha;
712
- }
713
-
714
826
  // src/partitioning/distributed-scoring.ts
715
827
  function collectGlobalStats(manager) {
716
828
  const aggregate = manager.getAggregateStats();
@@ -791,24 +903,6 @@ function dispatchToAllPartitions(partitions, params, language, schema, options,
791
903
  return Promise.all(promises);
792
904
  }
793
905
  function dispatchSinglePartition(partition, params, language, schema, options) {
794
- const isHybridMode = params.mode === "hybrid";
795
- const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
796
- const hasVector = params.vector !== void 0;
797
- if (isHybridMode || hasTerm && hasVector) {
798
- return hybridSearch(partition, params, language, schema, options);
799
- }
800
- if (params.mode === "vector" || hasVector && !hasTerm) {
801
- if (!params.vector) return { scored: [], totalMatched: 0 };
802
- const vectorConfig = params.vector;
803
- return partition.searchVector({
804
- field: vectorConfig.field,
805
- value: vectorConfig.value,
806
- k: params.limit ?? 10,
807
- similarity: vectorConfig.similarity,
808
- metric: vectorConfig.metric,
809
- efSearch: vectorConfig.efSearch
810
- });
811
- }
812
906
  return fulltextSearch(partition, params, language, schema, options);
813
907
  }
814
908
  function collectAndMergeFacets(outcomes, params, schema) {
@@ -936,190 +1030,6 @@ function heapPop(heap) {
936
1030
  }
937
1031
  return top;
938
1032
  }
939
-
940
- // src/runtime/detect.ts
941
- function detectRuntimeName() {
942
- if ("Bun" in globalThis) {
943
- return "bun";
944
- }
945
- if ("Deno" in globalThis) {
946
- return "deno";
947
- }
948
- const proc = globalThis.process;
949
- if (typeof proc === "object" && proc !== null && typeof proc.versions === "object" && typeof proc.versions?.node === "string") {
950
- return "node";
951
- }
952
- return "browser";
953
- }
954
- function getCpuCount() {
955
- const nav = globalThis.navigator;
956
- if (nav && typeof nav.hardwareConcurrency === "number" && nav.hardwareConcurrency >= 1) {
957
- return nav.hardwareConcurrency;
958
- }
959
- return 1;
960
- }
961
- function detectRuntime() {
962
- const runtime = detectRuntimeName();
963
- return {
964
- runtime,
965
- cpuCount: getCpuCount(),
966
- supportsWorkerThreads: runtime === "node" || runtime === "bun",
967
- supportsWebWorkers: runtime === "browser" || runtime === "deno" || runtime === "bun",
968
- supportsFileSystem: runtime === "node" || runtime === "bun" || runtime === "deno",
969
- supportsIndexedDB: runtime === "browser" || "indexedDB" in globalThis,
970
- supportsBroadcastChannel: "BroadcastChannel" in globalThis
971
- };
972
- }
973
-
974
- // src/vector/promoter.ts
975
- function detectWorkerStrategy() {
976
- const runtime = detectRuntime();
977
- const url = import.meta.url;
978
- const hasBundledWorker = url.endsWith(".mjs") || url.endsWith(".js");
979
- if (runtime.supportsWorkerThreads && hasBundledWorker) return "worker-threads";
980
- if (runtime.supportsWebWorkers && hasBundledWorker) return "web-worker";
981
- return "synchronous";
982
- }
983
- function collectVectorsForWorker(engine) {
984
- const vectors = [];
985
- for (const [, entry] of engine.entries()) {
986
- vectors.push({ docId: entry.docId, values: Array.from(entry.vector) });
987
- }
988
- return vectors;
989
- }
990
- function buildWorkerRequest(engine, hnswConfig) {
991
- return {
992
- type: "build",
993
- vectors: collectVectorsForWorker(engine),
994
- dimension: engine.dimension,
995
- config: hnswConfig ?? {}
996
- };
997
- }
998
- function resolveWorkerUrl() {
999
- const base = import.meta.url;
1000
- const distIndex = base.lastIndexOf("/dist/");
1001
- if (distIndex !== -1) {
1002
- return new URL("vector/hnsw-build-worker.mjs", base.slice(0, distIndex + 6)).href;
1003
- }
1004
- return base.replace(/\/src\/vector\/[^/]+$/, "/dist/vector/hnsw-build-worker.mjs");
1005
- }
1006
- function spawnNodeWorker(request, onComplete, onError) {
1007
- const entryUrl = resolveWorkerUrl();
1008
- let worker = null;
1009
- import('worker_threads').then((wt) => {
1010
- worker = new wt.Worker(new URL(entryUrl));
1011
- worker.on("message", (msg) => {
1012
- const response = msg;
1013
- worker?.terminate();
1014
- if (response.type === "success") {
1015
- onComplete(response.graph);
1016
- } else {
1017
- onError(new Error(response.message));
1018
- }
1019
- });
1020
- worker.on("error", (err) => {
1021
- worker?.terminate();
1022
- onError(err instanceof Error ? err : new Error(String(err)));
1023
- });
1024
- worker.postMessage(request);
1025
- }).catch(onError);
1026
- return {
1027
- terminate() {
1028
- worker?.terminate();
1029
- }
1030
- };
1031
- }
1032
- function spawnWebWorker(request, onComplete, onError) {
1033
- const entryUrl = resolveWorkerUrl();
1034
- const WorkerCtor = globalThis.Worker;
1035
- const worker = new WorkerCtor(entryUrl, { type: "module" });
1036
- worker.onmessage = (event) => {
1037
- const response = event.data;
1038
- worker.terminate();
1039
- if (response.type === "success") {
1040
- onComplete(response.graph);
1041
- } else {
1042
- onError(new Error(response.message));
1043
- }
1044
- };
1045
- worker.onerror = (event) => {
1046
- worker.terminate();
1047
- onError(event.error ?? new Error(event.message ?? "Web Worker error"));
1048
- };
1049
- worker.postMessage(request);
1050
- return {
1051
- terminate() {
1052
- worker.terminate();
1053
- }
1054
- };
1055
- }
1056
- function createVectorPromoter(config) {
1057
- const threshold = config?.promotionThreshold ?? 1e4;
1058
- const hnswConfig = config?.hnswConfig;
1059
- const promoting = /* @__PURE__ */ new Set();
1060
- const strategy = config?.workerStrategy ?? detectWorkerStrategy();
1061
- const pendingWorkers = /* @__PURE__ */ new Set();
1062
- return {
1063
- get strategy() {
1064
- return strategy;
1065
- },
1066
- check(engines) {
1067
- for (const [field, engine] of engines) {
1068
- if (engine.isPromoted || promoting.has(field)) continue;
1069
- if (engine.size < threshold) continue;
1070
- promoting.add(field);
1071
- if (strategy === "worker-threads") {
1072
- const request = buildWorkerRequest(engine, hnswConfig);
1073
- const handle = spawnNodeWorker(
1074
- request,
1075
- (graph) => {
1076
- pendingWorkers.delete(handle);
1077
- if (!engine.isPromoted) {
1078
- engine.deserializeHNSW(graph);
1079
- }
1080
- promoting.delete(field);
1081
- },
1082
- () => {
1083
- pendingWorkers.delete(handle);
1084
- promoting.delete(field);
1085
- }
1086
- );
1087
- pendingWorkers.add(handle);
1088
- } else if (strategy === "web-worker") {
1089
- const request = buildWorkerRequest(engine, hnswConfig);
1090
- const handle = spawnWebWorker(
1091
- request,
1092
- (graph) => {
1093
- pendingWorkers.delete(handle);
1094
- if (!engine.isPromoted) {
1095
- engine.deserializeHNSW(graph);
1096
- }
1097
- promoting.delete(field);
1098
- },
1099
- () => {
1100
- pendingWorkers.delete(handle);
1101
- promoting.delete(field);
1102
- }
1103
- );
1104
- pendingWorkers.add(handle);
1105
- } else {
1106
- try {
1107
- engine.promoteToHNSW(hnswConfig);
1108
- } finally {
1109
- promoting.delete(field);
1110
- }
1111
- }
1112
- }
1113
- },
1114
- shutdown() {
1115
- for (const handle of pendingWorkers) {
1116
- handle.terminate();
1117
- }
1118
- pendingWorkers.clear();
1119
- promoting.clear();
1120
- }
1121
- };
1122
- }
1123
1033
  function encodeRawPayloadV2(wire) {
1124
1034
  return encode(wire);
1125
1035
  }
@@ -1182,6 +1092,13 @@ function deserializePayloadV2(data) {
1182
1092
  efConstruction: data2.hnsw_graph.ef_construction,
1183
1093
  metric: validateHnswMetric(data2.hnsw_graph.metric),
1184
1094
  nodes: data2.hnsw_graph.nodes
1095
+ } : null,
1096
+ sq8: data2.sq8 ? {
1097
+ alpha: data2.sq8.alpha,
1098
+ offset: data2.sq8.offset,
1099
+ quantizedVectors: data2.sq8.quantized_vectors,
1100
+ vectorSums: data2.sq8.vector_sums,
1101
+ vectorSumSqs: data2.sq8.vector_sum_sqs
1185
1102
  } : null
1186
1103
  };
1187
1104
  }
@@ -1214,18 +1131,52 @@ function deserializePayloadV2(data) {
1214
1131
  // src/core/document-store.ts
1215
1132
  function createDocumentStore() {
1216
1133
  const docs = /* @__PURE__ */ new Map();
1134
+ const forwardMap = /* @__PURE__ */ new Map();
1135
+ const reverseMap = [];
1136
+ let nextInternalId = 0;
1137
+ function assignInternalId(docId) {
1138
+ const existing = forwardMap.get(docId);
1139
+ if (existing !== void 0) return existing;
1140
+ const internalId = nextInternalId++;
1141
+ forwardMap.set(docId, internalId);
1142
+ reverseMap[internalId] = docId;
1143
+ return internalId;
1144
+ }
1145
+ function clearMappings() {
1146
+ forwardMap.clear();
1147
+ reverseMap.length = 0;
1148
+ nextInternalId = 0;
1149
+ }
1150
+ const idResolver = {
1151
+ toExternal(internalId) {
1152
+ return reverseMap[internalId];
1153
+ },
1154
+ toInternal(externalId) {
1155
+ return forwardMap.get(externalId);
1156
+ }
1157
+ };
1217
1158
  return {
1218
1159
  store(docId, document, fieldLengths) {
1219
- docs.set(docId, { fields: structuredClone(document), fieldLengths });
1160
+ assignInternalId(docId);
1161
+ docs.set(docId, { fields: document, fieldLengths });
1220
1162
  },
1221
1163
  storeRef(docId, document, fieldLengths) {
1164
+ assignInternalId(docId);
1222
1165
  docs.set(docId, { fields: document, fieldLengths });
1223
1166
  },
1224
1167
  get(docId) {
1225
1168
  return docs.get(docId);
1226
1169
  },
1227
1170
  remove(docId) {
1228
- return docs.delete(docId);
1171
+ const removed = docs.delete(docId);
1172
+ if (removed) {
1173
+ const internalId = forwardMap.get(docId);
1174
+ if (internalId !== void 0) {
1175
+ forwardMap.delete(docId);
1176
+ reverseMap[internalId] = void 0;
1177
+ }
1178
+ }
1179
+ return removed;
1229
1180
  },
1230
1181
  has(docId) {
1231
1182
  return docs.has(docId);
@@ -1238,6 +1189,7 @@ function createDocumentStore() {
1238
1189
  },
1239
1190
  clear() {
1240
1191
  docs.clear();
1192
+ clearMappings();
1241
1193
  },
1242
1194
  serialize() {
1243
1195
  const result = {};
@@ -1248,9 +1200,31 @@ function createDocumentStore() {
1248
1200
  },
1249
1201
  deserialize(data) {
1250
1202
  docs.clear();
1203
+ clearMappings();
1251
1204
  for (const docId of Object.keys(data)) {
1205
+ assignInternalId(docId);
1252
1206
  docs.set(docId, data[docId]);
1253
1207
  }
1208
+ },
1209
+ ensureInternalId(docId) {
1210
+ return assignInternalId(docId);
1211
+ },
1212
+ getInternalId(docId) {
1213
+ return forwardMap.get(docId);
1214
+ },
1215
+ getExternalId(internalId) {
1216
+ return reverseMap[internalId];
1217
+ },
1218
+ *allInternalIds() {
1219
+ for (const internalId of forwardMap.values()) {
1220
+ yield internalId;
1221
+ }
1222
+ },
1223
+ internalIdCapacity() {
1224
+ return nextInternalId;
1225
+ },
1226
+ resolver() {
1227
+ return idResolver;
1254
1228
  }
1255
1229
  };
1256
1230
  }
@@ -1262,6 +1236,8 @@ var COMPACTION_THRESHOLD = 0.3;
1262
1236
  function createInvertedIndex(fieldNameTable) {
1263
1237
  const index = /* @__PURE__ */ new Map();
1264
1238
  const charBuckets = /* @__PURE__ */ new Map();
1239
+ let batchMode = false;
1240
+ const batchDirtyTokens = /* @__PURE__ */ new Set();
1265
1241
  function trackToken(token) {
1266
1242
  if (token.length === 0) return;
1267
1243
  const ch = token[0];
@@ -1345,10 +1321,10 @@ function createInvertedIndex(fieldNameTable) {
1345
1321
  list.length = writeIdx;
1346
1322
  list.deletedDocs.clear();
1347
1323
  }
1348
- function compactDocEntries(list, docId) {
1324
+ function compactDocEntries(list, internalId) {
1349
1325
  let writeIdx = 0;
1350
1326
  for (let i = 0; i < list.length; i++) {
1351
- if (list.docIds[i] !== docId) {
1327
+ if (list.docIds[i] !== internalId) {
1352
1328
  if (writeIdx !== i) {
1353
1329
  list.docIds[writeIdx] = list.docIds[i];
1354
1330
  list.termFrequencies[writeIdx] = list.termFrequencies[i];
@@ -1365,17 +1341,17 @@ function createInvertedIndex(fieldNameTable) {
1365
1341
  list.length = writeIdx;
1366
1342
  }
1367
1343
  return {
1368
- insert(token, docId, termFrequency, fieldNameIndex, positions) {
1344
+ insert(token, internalId, termFrequency, fieldNameIndex, positions) {
1369
1345
  const list = getOrCreateList(token);
1370
- if (list.deletedDocs.size > 0 && list.deletedDocs.has(docId)) {
1371
- compactDocEntries(list, docId);
1372
- list.deletedDocs.delete(docId);
1346
+ if (list.deletedDocs.size > 0 && list.deletedDocs.has(internalId)) {
1347
+ compactDocEntries(list, internalId);
1348
+ list.deletedDocs.delete(internalId);
1373
1349
  }
1374
1350
  if (list.length >= list.termFrequencies.length) {
1375
1351
  growTypedArrays(list);
1376
1352
  }
1377
1353
  const idx = list.length;
1378
- list.docIds.push(docId);
1354
+ list.docIds.push(internalId);
1379
1355
  list.termFrequencies[idx] = termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : termFrequency;
1380
1356
  list.fieldNameIndices[idx] = fieldNameIndex;
1381
1357
  if (positions !== null) {
@@ -1387,25 +1363,45 @@ function createInvertedIndex(fieldNameTable) {
1387
1363
  } else if (list.positions !== null) {
1388
1364
  list.positions.push([]);
1389
1365
  }
1390
- if (!list.docIdSet.has(docId)) {
1391
- list.docIdSet.add(docId);
1366
+ if (!list.docIdSet.has(internalId)) {
1367
+ list.docIdSet.add(internalId);
1392
1368
  }
1393
1369
  list.length++;
1394
1370
  },
1395
- remove(token, docId) {
1371
+ remove(token, internalId) {
1396
1372
  const list = index.get(token);
1397
- if (!list || !list.docIdSet.has(docId)) return;
1398
- list.docIdSet.delete(docId);
1399
- list.deletedDocs.add(docId);
1373
+ if (!list || !list.docIdSet.has(internalId)) return;
1374
+ list.docIdSet.delete(internalId);
1375
+ list.deletedDocs.add(internalId);
1400
1376
  if (list.docIdSet.size === 0) {
1401
1377
  index.delete(token);
1402
1378
  untrackToken(token);
1379
+ if (batchMode) batchDirtyTokens.delete(token);
1380
+ return;
1381
+ }
1382
+ if (batchMode) {
1383
+ batchDirtyTokens.add(token);
1403
1384
  return;
1404
1385
  }
1405
1386
  if (list.deletedDocs.size / list.length > COMPACTION_THRESHOLD) {
1406
1387
  compactList(list);
1407
1388
  }
1408
1389
  },
1390
+ beginBatch() {
1391
+ batchMode = true;
1392
+ batchDirtyTokens.clear();
1393
+ },
1394
+ endBatch() {
1395
+ batchMode = false;
1396
+ for (const token of batchDirtyTokens) {
1397
+ const list = index.get(token);
1398
+ if (!list) continue;
1399
+ if (list.deletedDocs.size / list.length > COMPACTION_THRESHOLD) {
1400
+ compactList(list);
1401
+ }
1402
+ }
1403
+ batchDirtyTokens.clear();
1404
+ },
1409
1405
  lookup(token) {
1410
1406
  return index.get(token);
1411
1407
  },
@@ -1452,14 +1448,16 @@ function createInvertedIndex(fieldNameTable) {
1452
1448
  index.clear();
1453
1449
  charBuckets.clear();
1454
1450
  },
1455
- serialize() {
1451
+ serialize(resolver) {
1456
1452
  const result = /* @__PURE__ */ Object.create(null);
1457
1453
  for (const [token, list] of index) {
1458
1454
  const postings = [];
1459
1455
  for (let i = 0; i < list.length; i++) {
1460
1456
  if (list.deletedDocs.size > 0 && list.deletedDocs.has(list.docIds[i])) continue;
1457
+ const externalId = resolver.toExternal(list.docIds[i]);
1458
+ if (externalId === void 0) continue;
1461
1459
  postings.push({
1462
- docId: list.docIds[i],
1460
+ docId: externalId,
1463
1461
  termFrequency: list.termFrequencies[i],
1464
1462
  fieldName: fieldNameTable.names[list.fieldNameIndices[i]],
1465
1463
  positions: list.positions ? list.positions[i] : []
@@ -1471,7 +1469,7 @@ function createInvertedIndex(fieldNameTable) {
1471
1469
  }
1472
1470
  return result;
1473
1471
  },
1474
- deserialize(data) {
1472
+ deserialize(data, resolver) {
1475
1473
  index.clear();
1476
1474
  charBuckets.clear();
1477
1475
  for (const token of Object.keys(data)) {
@@ -1482,32 +1480,44 @@ function createInvertedIndex(fieldNameTable) {
1482
1480
  const termFrequencies = new Uint16Array(count);
1483
1481
  const fieldNameIndices = new Uint8Array(count);
1484
1482
  let hasPositions = false;
1483
+ let validCount = 0;
1485
1484
  for (let i = 0; i < count; i++) {
1486
1485
  const p = src.postings[i];
1487
- docIds[i] = p.docId;
1488
- termFrequencies[i] = p.termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : p.termFrequency;
1486
+ const internalId = resolver.toInternal(p.docId);
1487
+ if (internalId === void 0) continue;
1488
+ docIds[validCount] = internalId;
1489
+ termFrequencies[validCount] = p.termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : p.termFrequency;
1489
1490
  let fnIndex = fieldNameTable.indexMap.get(p.fieldName);
1490
1491
  if (fnIndex === void 0) {
1491
1492
  fnIndex = fieldNameTable.names.length;
1492
1493
  fieldNameTable.names.push(p.fieldName);
1493
1494
  fieldNameTable.indexMap.set(p.fieldName, fnIndex);
1494
1495
  }
1495
- fieldNameIndices[i] = fnIndex;
1496
- docIdSet.add(p.docId);
1496
+ fieldNameIndices[validCount] = fnIndex;
1497
+ docIdSet.add(internalId);
1497
1498
  if (p.positions.length > 0) hasPositions = true;
1499
+ validCount++;
1498
1500
  }
1501
+ if (validCount === 0) continue;
1502
+ docIds.length = validCount;
1503
+ const finalTF = validCount < count ? termFrequencies.slice(0, validCount) : termFrequencies;
1504
+ const finalFNI = validCount < count ? fieldNameIndices.slice(0, validCount) : fieldNameIndices;
1499
1505
  let positions = null;
1500
1506
  if (hasPositions) {
1501
- positions = new Array(count);
1507
+ positions = new Array(validCount);
1508
+ let vi = 0;
1502
1509
  for (let i = 0; i < count; i++) {
1503
- positions[i] = src.postings[i].positions;
1510
+ const internalId = resolver.toInternal(src.postings[i].docId);
1511
+ if (internalId === void 0) continue;
1512
+ positions[vi] = src.postings[i].positions;
1513
+ vi++;
1504
1514
  }
1505
1515
  }
1506
1516
  index.set(token, {
1507
- length: count,
1517
+ length: validCount,
1508
1518
  docIds,
1509
- termFrequencies,
1510
- fieldNameIndices,
1519
+ termFrequencies: finalTF,
1520
+ fieldNameIndices: finalFNI,
1511
1521
  positions,
1512
1522
  docIdSet,
1513
1523
  deletedDocs: /* @__PURE__ */ new Set()
@@ -1519,16 +1529,55 @@ function createInvertedIndex(fieldNameTable) {
1519
1529
  }
1520
1530
 
1521
1531
  // src/core/statistics.ts
1522
- function collectUniqueTokens(tokens) {
1523
- const unique = /* @__PURE__ */ new Set();
1532
+ function applyUniqueTokens(tokens, docFrequencies, increment) {
1524
1533
  const fields = Object.keys(tokens);
1534
+ if (fields.length === 1) {
1535
+ const fieldTokens = tokens[fields[0]];
1536
+ if (increment === 1) {
1537
+ const seen = /* @__PURE__ */ new Set();
1538
+ for (let j = 0; j < fieldTokens.length; j++) {
1539
+ const tok = fieldTokens[j];
1540
+ if (seen.has(tok)) continue;
1541
+ seen.add(tok);
1542
+ docFrequencies[tok] = (docFrequencies[tok] ?? 0) + 1;
1543
+ }
1544
+ } else {
1545
+ const seen = /* @__PURE__ */ new Set();
1546
+ for (let j = 0; j < fieldTokens.length; j++) {
1547
+ const tok = fieldTokens[j];
1548
+ if (seen.has(tok)) continue;
1549
+ seen.add(tok);
1550
+ if (tok in docFrequencies) {
1551
+ docFrequencies[tok]--;
1552
+ if (docFrequencies[tok] <= 0) {
1553
+ delete docFrequencies[tok];
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ return;
1559
+ }
1560
+ const unique = /* @__PURE__ */ new Set();
1525
1561
  for (let i = 0; i < fields.length; i++) {
1526
1562
  const fieldTokens = tokens[fields[i]];
1527
1563
  for (let j = 0; j < fieldTokens.length; j++) {
1528
1564
  unique.add(fieldTokens[j]);
1529
1565
  }
1530
1566
  }
1531
- return unique;
1567
+ if (increment === 1) {
1568
+ for (const tok of unique) {
1569
+ docFrequencies[tok] = (docFrequencies[tok] ?? 0) + 1;
1570
+ }
1571
+ } else {
1572
+ for (const tok of unique) {
1573
+ if (tok in docFrequencies) {
1574
+ docFrequencies[tok]--;
1575
+ if (docFrequencies[tok] <= 0) {
1576
+ delete docFrequencies[tok];
1577
+ }
1578
+ }
1579
+ }
1580
+ }
1532
1581
  }
1533
1582
  function createPartitionStats() {
1534
1583
  const stats = {
@@ -1543,10 +1592,7 @@ function createPartitionStats() {
1543
1592
  const field = fields[i];
1544
1593
  stats.totalFieldLengths[field] = (stats.totalFieldLengths[field] ?? 0) + fieldLengths[field];
1545
1594
  }
1546
- const uniqueTokens = collectUniqueTokens(tokens);
1547
- for (const token of uniqueTokens) {
1548
- stats.docFrequencies[token] = (stats.docFrequencies[token] ?? 0) + 1;
1549
- }
1595
+ applyUniqueTokens(tokens, stats.docFrequencies, 1);
1550
1596
  stats.recalculateAverages();
1551
1597
  },
1552
1598
  removeDocument(fieldLengths, tokens) {
@@ -1562,15 +1608,7 @@ function createPartitionStats() {
1562
1608
  }
1563
1609
  }
1564
1610
  }
1565
- const uniqueTokens = collectUniqueTokens(tokens);
1566
- for (const token of uniqueTokens) {
1567
- if (token in stats.docFrequencies) {
1568
- stats.docFrequencies[token]--;
1569
- if (stats.docFrequencies[token] <= 0) {
1570
- delete stats.docFrequencies[token];
1571
- }
1572
- }
1573
- }
1611
+ applyUniqueTokens(tokens, stats.docFrequencies, -1);
1574
1612
  stats.recalculateAverages();
1575
1613
  },
1576
1614
  recalculateAverages() {
@@ -1719,12 +1757,12 @@ function createGeoIndex() {
1719
1757
  get entries() {
1720
1758
  return entries;
1721
1759
  },
1722
- insert(docId, lat, lon) {
1723
- entries.push({ lat, lon, docId });
1760
+ insert(internalId, lat, lon) {
1761
+ entries.push({ lat, lon, docId: internalId });
1724
1762
  },
1725
- remove(docId) {
1763
+ remove(internalId) {
1726
1764
  for (let i = entries.length - 1; i >= 0; i--) {
1727
- if (entries[i].docId === docId) {
1765
+ if (entries[i].docId === internalId) {
1728
1766
  entries.splice(i, 1);
1729
1767
  return;
1730
1768
  }
@@ -1769,188 +1807,6 @@ function createGeoIndex() {
1769
1807
  };
1770
1808
  }
1771
1809
 
1772
- // src/vector/brute-force.ts
1773
- function createBruteForceVectorStore(dimension) {
1774
- const vectors = /* @__PURE__ */ new Map();
1775
- return {
1776
- get dimension() {
1777
- return dimension;
1778
- },
1779
- get size() {
1780
- return vectors.size;
1781
- },
1782
- insert(docId, vector) {
1783
- if (vector.length !== dimension) {
1784
- throw new Error(`Vector dimension mismatch: expected ${dimension}, got ${vector.length}`);
1785
- }
1786
- const mag = magnitude(vector);
1787
- vectors.set(docId, { docId, vector, magnitude: mag });
1788
- },
1789
- remove(docId) {
1790
- vectors.delete(docId);
1791
- },
1792
- has(docId) {
1793
- return vectors.has(docId);
1794
- },
1795
- search(query, k, metric, minSimilarity, filterDocIds) {
1796
- if (query.length !== dimension) {
1797
- throw new Error(`Query dimension mismatch: expected ${dimension}, got ${query.length}`);
1798
- }
1799
- const queryMag = magnitude(query);
1800
- const results = [];
1801
- for (const [docId, entry] of vectors) {
1802
- if (filterDocIds && !filterDocIds.has(docId)) continue;
1803
- let score;
1804
- switch (metric) {
1805
- case "cosine":
1806
- score = cosineSimilarityWithMagnitudes(query, entry.vector, queryMag, entry.magnitude);
1807
- break;
1808
- case "dotProduct":
1809
- score = dotProduct(query, entry.vector);
1810
- break;
1811
- case "euclidean": {
1812
- const dist = euclideanDistance(query, entry.vector);
1813
- score = 1 / (1 + dist);
1814
- break;
1815
- }
1816
- }
1817
- if (score >= minSimilarity) {
1818
- results.push({ docId, score });
1819
- }
1820
- }
1821
- results.sort((a, b) => b.score - a.score);
1822
- const topK = results.slice(0, k);
1823
- return topK.map((r) => ({
1824
- docId: r.docId,
1825
- score: r.score,
1826
- termFrequencies: {},
1827
- fieldLengths: {},
1828
- idf: {}
1829
- }));
1830
- },
1831
- clear() {
1832
- vectors.clear();
1833
- },
1834
- entries() {
1835
- return vectors.entries();
1836
- }
1837
- };
1838
- }
1839
-
1840
- // src/search/vector-search.ts
1841
- function createVectorSearchEngine(dimension, defaultHNSWConfig) {
1842
- const bruteForce = createBruteForceVectorStore(dimension);
1843
- const hnswConfig = defaultHNSWConfig;
1844
- let hnsw = null;
1845
- return {
1846
- get dimension() {
1847
- return dimension;
1848
- },
1849
- get size() {
1850
- return bruteForce.size;
1851
- },
1852
- get isPromoted() {
1853
- return hnsw !== null;
1854
- },
1855
- insert(docId, vector) {
1856
- bruteForce.insert(docId, vector);
1857
- if (hnsw) {
1858
- hnsw.insert(docId, vector);
1859
- }
1860
- },
1861
- remove(docId) {
1862
- bruteForce.remove(docId);
1863
- if (hnsw) {
1864
- hnsw.remove(docId);
1865
- }
1866
- },
1867
- has(docId) {
1868
- return bruteForce.has(docId);
1869
- },
1870
- search(query, k, metric, minSimilarity, filterDocIds, efSearch) {
1871
- if (hnsw) {
1872
- return hnsw.search(query, k, metric, minSimilarity, filterDocIds, efSearch);
1873
- }
1874
- return bruteForce.search(query, k, metric, minSimilarity, filterDocIds);
1875
- },
1876
- clear() {
1877
- bruteForce.clear();
1878
- if (hnsw) {
1879
- hnsw.clear();
1880
- hnsw = null;
1881
- }
1882
- },
1883
- entries() {
1884
- return bruteForce.entries();
1885
- },
1886
- promoteToHNSW(overrideConfig) {
1887
- const cfg = overrideConfig ?? hnswConfig;
1888
- const newHnsw = createHNSWIndex(dimension, cfg);
1889
- for (const [, entry] of bruteForce.entries()) {
1890
- newHnsw.insert(entry.docId, entry.vector);
1891
- }
1892
- hnsw = newHnsw;
1893
- },
1894
- demoteToLinear() {
1895
- if (hnsw) {
1896
- hnsw.clear();
1897
- hnsw = null;
1898
- }
1899
- },
1900
- getHNSWIndex() {
1901
- return hnsw;
1902
- },
1903
- getBruteForceStore() {
1904
- return bruteForce;
1905
- },
1906
- estimateMemoryBytes() {
1907
- const count = bruteForce.size;
1908
- if (count === 0) return 0;
1909
- const MAP_OVERHEAD = 64;
1910
- const MAP_ENTRY = 72;
1911
- const VECTOR_ENTRY_OBJ = 32;
1912
- const AVG_DOCID_BYTES = 56;
1913
- const TYPED_ARRAY_HEADER = 64;
1914
- const MAGNITUDE_BYTES = 8;
1915
- const perBruteForceEntry = MAP_ENTRY + VECTOR_ENTRY_OBJ + AVG_DOCID_BYTES + TYPED_ARRAY_HEADER + MAGNITUDE_BYTES;
1916
- let bytes = MAP_OVERHEAD + count * (perBruteForceEntry + dimension * 4);
1917
- if (hnsw) {
1918
- const HNSW_NODE_OBJ = 64;
1919
- const SHARED_REFS = 16;
1920
- const CONN_ARRAY_HEADER = 32;
1921
- const SET_OVERHEAD = 64;
1922
- const SET_ENTRY_COST = 72;
1923
- const m = hnsw.m;
1924
- const avgLayers = m / (m - 1);
1925
- const avgConnsLayer0 = m;
1926
- const avgConnsUpper = Math.ceil(m / 2);
1927
- const connMemPerNode = CONN_ARRAY_HEADER + (SET_OVERHEAD + avgConnsLayer0 * SET_ENTRY_COST) + Math.max(0, avgLayers - 1) * (SET_OVERHEAD + avgConnsUpper * SET_ENTRY_COST);
1928
- const perHnswNode = MAP_ENTRY + HNSW_NODE_OBJ + SHARED_REFS + connMemPerNode;
1929
- bytes += MAP_OVERHEAD + count * perHnswNode;
1930
- }
1931
- return Math.round(bytes);
1932
- },
1933
- serializeHNSW() {
1934
- if (!hnsw) return null;
1935
- return hnsw.serialize();
1936
- },
1937
- deserializeHNSW(data) {
1938
- const cfg = {};
1939
- const restoredHnsw = createHNSWIndex(dimension, {
1940
- m: data.m ?? cfg.m,
1941
- efConstruction: data.efConstruction ?? cfg.efConstruction,
1942
- metric: data.metric ?? cfg.metric
1943
- });
1944
- const vectorMap = /* @__PURE__ */ new Map();
1945
- for (const [, entry] of bruteForce.entries()) {
1946
- vectorMap.set(entry.docId, { vector: entry.vector, mag: entry.magnitude });
1947
- }
1948
- restoredHnsw.deserialize(data, vectorMap);
1949
- hnsw = restoredHnsw;
1950
- }
1951
- };
1952
- }
1953
-
1954
1810
  // src/core/field-index.ts
1955
1811
  function lowerBound(entries, value) {
1956
1812
  let lo = 0;
@@ -1979,6 +1835,13 @@ function collectDocIds(entries, from, to) {
1979
1835
  }
1980
1836
  return result;
1981
1837
  }
1838
+ function collectDocIdsBitset(entries, from, to, capacity) {
1839
+ const bs = createBitSet(capacity);
1840
+ for (let i = from; i < to; i++) {
1841
+ bitsetSet(bs, entries[i].docId);
1842
+ }
1843
+ return bs;
1844
+ }
1982
1845
  function createNumericIndex() {
1983
1846
  let entries = [];
1984
1847
  let sorted = true;
@@ -1989,16 +1852,16 @@ function createNumericIndex() {
1989
1852
  }
1990
1853
  }
1991
1854
  return {
1992
- insert(docId, value) {
1993
- entries.push({ value, docId });
1855
+ insert(internalId, value) {
1856
+ entries.push({ value, docId: internalId });
1994
1857
  sorted = false;
1995
1858
  },
1996
- remove(docId, value) {
1859
+ remove(internalId, value) {
1997
1860
  ensureSorted();
1998
1861
  const start = lowerBound(entries, value);
1999
1862
  const end = upperBound(entries, value);
2000
1863
  for (let i = start; i < end; i++) {
2001
- if (entries[i].docId === docId) {
1864
+ if (entries[i].docId === internalId) {
2002
1865
  entries.splice(i, 1);
2003
1866
  return;
2004
1867
  }
@@ -2040,6 +1903,33 @@ function createNumericIndex() {
2040
1903
  getAllDocIds() {
2041
1904
  return collectDocIds(entries, 0, entries.length);
2042
1905
  },
1906
+ queryEqBitset(value, capacity) {
1907
+ ensureSorted();
1908
+ return collectDocIdsBitset(entries, lowerBound(entries, value), upperBound(entries, value), capacity);
1909
+ },
1910
+ queryGtBitset(value, capacity) {
1911
+ ensureSorted();
1912
+ return collectDocIdsBitset(entries, upperBound(entries, value), entries.length, capacity);
1913
+ },
1914
+ queryGteBitset(value, capacity) {
1915
+ ensureSorted();
1916
+ return collectDocIdsBitset(entries, lowerBound(entries, value), entries.length, capacity);
1917
+ },
1918
+ queryLtBitset(value, capacity) {
1919
+ ensureSorted();
1920
+ return collectDocIdsBitset(entries, 0, lowerBound(entries, value), capacity);
1921
+ },
1922
+ queryLteBitset(value, capacity) {
1923
+ ensureSorted();
1924
+ return collectDocIdsBitset(entries, 0, upperBound(entries, value), capacity);
1925
+ },
1926
+ queryBetweenBitset(min, max, capacity) {
1927
+ ensureSorted();
1928
+ return collectDocIdsBitset(entries, lowerBound(entries, min), upperBound(entries, max), capacity);
1929
+ },
1930
+ getAllDocIdsBitset(capacity) {
1931
+ return collectDocIdsBitset(entries, 0, entries.length, capacity);
1932
+ },
2043
1933
  count() {
2044
1934
  return entries.length;
2045
1935
  },
@@ -2061,13 +1951,13 @@ function createBooleanIndex() {
2061
1951
  const trueDocs = /* @__PURE__ */ new Set();
2062
1952
  const falseDocs = /* @__PURE__ */ new Set();
2063
1953
  return {
2064
- insert(docId, value) {
2065
- if (value) trueDocs.add(docId);
2066
- else falseDocs.add(docId);
1954
+ insert(internalId, value) {
1955
+ if (value) trueDocs.add(internalId);
1956
+ else falseDocs.add(internalId);
2067
1957
  },
2068
- remove(docId, value) {
2069
- if (value) trueDocs.delete(docId);
2070
- else falseDocs.delete(docId);
1958
+ remove(internalId, value) {
1959
+ if (value) trueDocs.delete(internalId);
1960
+ else falseDocs.delete(internalId);
2071
1961
  },
2072
1962
  queryEq(value) {
2073
1963
  return new Set(value ? trueDocs : falseDocs);
@@ -2080,6 +1970,20 @@ function createBooleanIndex() {
2080
1970
  for (const id of falseDocs) all.add(id);
2081
1971
  return all;
2082
1972
  },
1973
+ queryEqBitset(value, capacity) {
1974
+ const source = value ? trueDocs : falseDocs;
1975
+ const bs = createBitSet(capacity);
1976
+ for (const id of source) {
1977
+ bitsetSet(bs, id);
1978
+ }
1979
+ return bs;
1980
+ },
1981
+ getAllDocIdsBitset(capacity) {
1982
+ const bs = createBitSet(capacity);
1983
+ for (const id of trueDocs) bitsetSet(bs, id);
1984
+ for (const id of falseDocs) bitsetSet(bs, id);
1985
+ return bs;
1986
+ },
2083
1987
  count() {
2084
1988
  return trueDocs.size + falseDocs.size;
2085
1989
  },
@@ -2111,18 +2015,18 @@ function createEnumIndex() {
2111
2015
  return result;
2112
2016
  }
2113
2017
  return {
2114
- insert(docId, value) {
2018
+ insert(internalId, value) {
2115
2019
  let docSet = valueMap.get(value);
2116
2020
  if (!docSet) {
2117
2021
  docSet = /* @__PURE__ */ new Set();
2118
2022
  valueMap.set(value, docSet);
2119
2023
  }
2120
- docSet.add(docId);
2024
+ docSet.add(internalId);
2121
2025
  },
2122
- remove(docId, value) {
2026
+ remove(internalId, value) {
2123
2027
  const docSet = valueMap.get(value);
2124
2028
  if (!docSet) return;
2125
- docSet.delete(docId);
2029
+ docSet.delete(internalId);
2126
2030
  if (docSet.size === 0) valueMap.delete(value);
2127
2031
  },
2128
2032
  queryEq(value) {
@@ -2156,6 +2060,31 @@ function createEnumIndex() {
2156
2060
  getAllDocIds() {
2157
2061
  return allDocs();
2158
2062
  },
2063
+ queryEqBitset(value, capacity) {
2064
+ const bs = createBitSet(capacity);
2065
+ const docSet = valueMap.get(value);
2066
+ if (docSet) {
2067
+ for (const id of docSet) bitsetSet(bs, id);
2068
+ }
2069
+ return bs;
2070
+ },
2071
+ queryInBitset(values, capacity) {
2072
+ const bs = createBitSet(capacity);
2073
+ for (const val of values) {
2074
+ const docSet = valueMap.get(val);
2075
+ if (docSet) {
2076
+ for (const id of docSet) bitsetSet(bs, id);
2077
+ }
2078
+ }
2079
+ return bs;
2080
+ },
2081
+ getAllDocIdsBitset(capacity) {
2082
+ const bs = createBitSet(capacity);
2083
+ for (const docSet of valueMap.values()) {
2084
+ for (const id of docSet) bitsetSet(bs, id);
2085
+ }
2086
+ return bs;
2087
+ },
2159
2088
  count() {
2160
2089
  let total = 0;
2161
2090
  for (const docSet of valueMap.values()) total += docSet.size;
@@ -2189,7 +2118,6 @@ function getOrCreateFieldNameIndex(table, fieldName) {
2189
2118
  table.indexMap.set(fieldName, idx);
2190
2119
  return idx;
2191
2120
  }
2192
- var VECTOR_PATTERN2 = /^vector\[(\d+)]$/;
2193
2121
  function getNestedValue(obj, path) {
2194
2122
  const parts = path.split(".");
2195
2123
  let current = obj;
@@ -2221,10 +2149,6 @@ function textFieldsChanged(oldDoc, newDoc, flatSchema) {
2221
2149
  }
2222
2150
  return false;
2223
2151
  }
2224
- function parseVectorDimension(fieldType) {
2225
- const match = VECTOR_PATTERN2.exec(fieldType);
2226
- return match ? Number.parseInt(match[1], 10) : null;
2227
- }
2228
2152
  function tokenizeOptions(options) {
2229
2153
  return {
2230
2154
  stem: true,
@@ -2244,10 +2168,15 @@ function getFieldValueForDoc(docStore, docId, fieldPath) {
2244
2168
  if (!stored) return void 0;
2245
2169
  return getNestedValue(stored.fields, fieldPath);
2246
2170
  }
2247
- function getAllDocIds(docStore) {
2171
+ function getFieldValueByInternalId(docStore, internalId, fieldPath) {
2172
+ const externalId = docStore.getExternalId(internalId);
2173
+ if (externalId === void 0) return void 0;
2174
+ return getFieldValueForDoc(docStore, externalId, fieldPath);
2175
+ }
2176
+ function getAllInternalDocIds(docStore) {
2248
2177
  const ids = /* @__PURE__ */ new Set();
2249
- for (const [id] of docStore.all()) {
2250
- ids.add(id);
2178
+ for (const internalId of docStore.allInternalIds()) {
2179
+ ids.add(internalId);
2251
2180
  }
2252
2181
  return ids;
2253
2182
  }
@@ -2262,11 +2191,6 @@ function ensureFieldIndex(state, fieldPath, fieldType) {
2262
2191
  state.enumIndexes.set(fieldPath, createEnumIndex());
2263
2192
  } else if (fieldType === "geopoint" && !state.geoIndexes.has(fieldPath)) {
2264
2193
  state.geoIndexes.set(fieldPath, createGeoIndex());
2265
- } else {
2266
- const dim = parseVectorDimension(fieldType);
2267
- if (dim !== null && !state.vectorStores.has(fieldPath)) {
2268
- state.vectorStores.set(fieldPath, createVectorSearchEngine(dim));
2269
- }
2270
2194
  }
2271
2195
  }
2272
2196
  function getOrCreateNumericIndex(state, fieldPath) {
@@ -2285,14 +2209,20 @@ function getOrCreateBooleanIndex(state, fieldPath) {
2285
2209
  }
2286
2210
  return idx;
2287
2211
  }
2288
- function indexStringField(state, docId, fieldPath, text, language, options, fieldLengths, tokensByField) {
2289
- const result = tokenize(text, language, tokenizeOptions(options));
2290
- fieldLengths[fieldPath] = result.tokens.length;
2212
+ function resolveInternalId(state, docId) {
2213
+ const internalId = state.docStore.getInternalId(docId);
2214
+ if (internalId === void 0) {
2215
+ throw new Error(`Internal ID not found for document "${docId}"`);
2216
+ }
2217
+ return internalId;
2218
+ }
2219
+ function indexStringField(state, internalId, fieldPath, text, language, options, fieldLengths, tokensByField) {
2291
2220
  const fieldTokenList = [];
2292
2221
  const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2222
+ const opts = tokenizeOptions(options);
2293
2223
  if (state.trackPositions) {
2294
2224
  const tokenFreqs = /* @__PURE__ */ new Map();
2295
- for (const t of result.tokens) {
2225
+ for (const t of tokenizeIterator(text, language, opts)) {
2296
2226
  const existing = tokenFreqs.get(t.token);
2297
2227
  if (existing) {
2298
2228
  existing.count++;
@@ -2303,29 +2233,31 @@ function indexStringField(state, docId, fieldPath, text, language, options, fiel
2303
2233
  fieldTokenList.push(t.token);
2304
2234
  }
2305
2235
  for (const [token, freq] of tokenFreqs) {
2306
- state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
2236
+ state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2307
2237
  }
2308
2238
  } else {
2309
2239
  const tokenCounts = /* @__PURE__ */ new Map();
2310
- for (const t of result.tokens) {
2240
+ for (const t of tokenizeIterator(text, language, opts)) {
2311
2241
  tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2312
2242
  fieldTokenList.push(t.token);
2313
2243
  }
2314
2244
  for (const [token, count] of tokenCounts) {
2315
- state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
2245
+ state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
2316
2246
  }
2317
2247
  }
2248
+ fieldLengths[fieldPath] = fieldTokenList.length;
2318
2249
  tokensByField[fieldPath] = fieldTokenList;
2319
2250
  }
2320
- function indexStringArrayField(state, docId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
2251
+ function indexStringArrayField(state, internalId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
2321
2252
  const fieldTokenList = [];
2322
2253
  const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2254
+ const opts = tokenizeOptions(options);
2323
2255
  if (state.trackPositions) {
2324
2256
  const tokenFreqs = /* @__PURE__ */ new Map();
2325
2257
  let positionOffset = 0;
2326
2258
  for (const item of arr) {
2327
- const result = tokenize(item, language, tokenizeOptions(options));
2328
- for (const t of result.tokens) {
2259
+ let itemTokenCount = 0;
2260
+ for (const t of tokenizeIterator(item, language, opts)) {
2329
2261
  const existing = tokenFreqs.get(t.token);
2330
2262
  if (existing) {
2331
2263
  existing.count++;
@@ -2334,25 +2266,25 @@ function indexStringArrayField(state, docId, fieldPath, arr, language, options,
2334
2266
  tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
2335
2267
  }
2336
2268
  fieldTokenList.push(t.token);
2269
+ itemTokenCount++;
2337
2270
  }
2338
- positionOffset += result.tokens.length;
2271
+ positionOffset += itemTokenCount;
2339
2272
  }
2340
2273
  fieldLengths[fieldPath] = fieldTokenList.length;
2341
2274
  for (const [token, freq] of tokenFreqs) {
2342
- state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
2275
+ state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2343
2276
  }
2344
2277
  } else {
2345
2278
  const tokenCounts = /* @__PURE__ */ new Map();
2346
2279
  for (const item of arr) {
2347
- const result = tokenize(item, language, tokenizeOptions(options));
2348
- for (const t of result.tokens) {
2280
+ for (const t of tokenizeIterator(item, language, opts)) {
2349
2281
  tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2350
2282
  fieldTokenList.push(t.token);
2351
2283
  }
2352
2284
  }
2353
2285
  fieldLengths[fieldPath] = fieldTokenList.length;
2354
2286
  for (const [token, count] of tokenCounts) {
2355
- state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
2287
+ state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
2356
2288
  }
2357
2289
  }
2358
2290
  tokensByField[fieldPath] = fieldTokenList;
@@ -2360,39 +2292,43 @@ function indexStringArrayField(state, docId, fieldPath, arr, language, options,
2360
2292
  function indexDocument(state, docId, document, flatSchema, language, options) {
2361
2293
  const fieldLengths = {};
2362
2294
  const tokensByField = {};
2295
+ const internalId = resolveInternalId(state, docId);
2363
2296
  for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2364
2297
  const value = getNestedValue(document, fieldPath);
2365
2298
  if (value === void 0 || value === null) continue;
2366
2299
  ensureFieldIndex(state, fieldPath, fieldType);
2367
2300
  if (fieldType === "string") {
2368
- indexStringField(state, docId, fieldPath, value, language, options, fieldLengths, tokensByField);
2301
+ indexStringField(state, internalId, fieldPath, value, language, options, fieldLengths, tokensByField);
2369
2302
  } else if (fieldType === "number") {
2370
- getOrCreateNumericIndex(state, fieldPath).insert(docId, value);
2303
+ getOrCreateNumericIndex(state, fieldPath).insert(internalId, value);
2371
2304
  } else if (fieldType === "boolean") {
2372
- getOrCreateBooleanIndex(state, fieldPath).insert(docId, value);
2305
+ getOrCreateBooleanIndex(state, fieldPath).insert(internalId, value);
2373
2306
  } else if (fieldType === "enum") {
2374
- state.enumIndexes.get(fieldPath)?.insert(docId, value);
2307
+ state.enumIndexes.get(fieldPath)?.insert(internalId, value);
2375
2308
  } else if (fieldType === "geopoint") {
2376
2309
  const geo = value;
2377
- state.geoIndexes.get(fieldPath)?.insert(docId, geo.lat, geo.lon);
2310
+ state.geoIndexes.get(fieldPath)?.insert(internalId, geo.lat, geo.lon);
2378
2311
  } else if (fieldType === "string[]") {
2379
- indexStringArrayField(state, docId, fieldPath, value, language, options, fieldLengths, tokensByField);
2312
+ indexStringArrayField(
2313
+ state,
2314
+ internalId,
2315
+ fieldPath,
2316
+ value,
2317
+ language,
2318
+ options,
2319
+ fieldLengths,
2320
+ tokensByField
2321
+ );
2380
2322
  } else if (fieldType === "number[]") {
2381
2323
  const numIdx = getOrCreateNumericIndex(state, fieldPath);
2382
- for (const num of value) numIdx.insert(docId, num);
2324
+ for (const num of value) numIdx.insert(internalId, num);
2383
2325
  } else if (fieldType === "boolean[]") {
2384
2326
  const boolIdx = getOrCreateBooleanIndex(state, fieldPath);
2385
- for (const b of value) boolIdx.insert(docId, b);
2327
+ for (const b of value) boolIdx.insert(internalId, b);
2386
2328
  } else if (fieldType === "enum[]") {
2387
2329
  const enumIdx = state.enumIndexes.get(fieldPath);
2388
2330
  if (enumIdx) {
2389
- for (const e of value) enumIdx.insert(docId, e);
2390
- }
2391
- } else {
2392
- const dim = parseVectorDimension(fieldType);
2393
- if (dim !== null) {
2394
- const vec = value instanceof Float32Array ? value : new Float32Array(value);
2395
- state.vectorStores.get(fieldPath)?.insert(docId, vec);
2331
+ for (const e of value) enumIdx.insert(internalId, e);
2396
2332
  }
2397
2333
  }
2398
2334
  }
@@ -2402,6 +2338,7 @@ function removeFromIndexes(state, docId, storedDoc, flatSchema, language, option
2402
2338
  const fieldLengths = {};
2403
2339
  const tokensByField = {};
2404
2340
  const fields = storedDoc.fields;
2341
+ const internalId = resolveInternalId(state, docId);
2405
2342
  for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2406
2343
  const value = getNestedValue(fields, fieldPath);
2407
2344
  if (value === void 0 || value === null) continue;
@@ -2414,16 +2351,16 @@ function removeFromIndexes(state, docId, storedDoc, flatSchema, language, option
2414
2351
  uniqueTokens.add(t.token);
2415
2352
  fieldTokenList.push(t.token);
2416
2353
  }
2417
- for (const token of uniqueTokens) state.invertedIdx.remove(token, docId);
2354
+ for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2418
2355
  tokensByField[fieldPath] = fieldTokenList;
2419
2356
  } else if (fieldType === "number") {
2420
- state.numericIndexes.get(fieldPath)?.remove(docId, value);
2357
+ state.numericIndexes.get(fieldPath)?.remove(internalId, value);
2421
2358
  } else if (fieldType === "boolean") {
2422
- state.booleanIndexes.get(fieldPath)?.remove(docId, value);
2359
+ state.booleanIndexes.get(fieldPath)?.remove(internalId, value);
2423
2360
  } else if (fieldType === "enum") {
2424
- state.enumIndexes.get(fieldPath)?.remove(docId, value);
2361
+ state.enumIndexes.get(fieldPath)?.remove(internalId, value);
2425
2362
  } else if (fieldType === "geopoint") {
2426
- state.geoIndexes.get(fieldPath)?.remove(docId);
2363
+ state.geoIndexes.get(fieldPath)?.remove(internalId);
2427
2364
  } else if (fieldType === "string[]") {
2428
2365
  const arr = value;
2429
2366
  const uniqueTokens = /* @__PURE__ */ new Set();
@@ -2436,32 +2373,29 @@ function removeFromIndexes(state, docId, storedDoc, flatSchema, language, option
2436
2373
  }
2437
2374
  }
2438
2375
  fieldLengths[fieldPath] = fieldTokenList.length;
2439
- for (const token of uniqueTokens) state.invertedIdx.remove(token, docId);
2376
+ for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2440
2377
  tokensByField[fieldPath] = fieldTokenList;
2441
2378
  } else if (fieldType === "number[]") {
2442
2379
  const numIdx = state.numericIndexes.get(fieldPath);
2443
2380
  if (numIdx) {
2444
- for (const num of value) numIdx.remove(docId, num);
2381
+ for (const num of value) numIdx.remove(internalId, num);
2445
2382
  }
2446
2383
  } else if (fieldType === "boolean[]") {
2447
2384
  const boolIdx = state.booleanIndexes.get(fieldPath);
2448
2385
  if (boolIdx) {
2449
- for (const b of value) boolIdx.remove(docId, b);
2386
+ for (const b of value) boolIdx.remove(internalId, b);
2450
2387
  }
2451
2388
  } else if (fieldType === "enum[]") {
2452
2389
  const enumIdx = state.enumIndexes.get(fieldPath);
2453
2390
  if (enumIdx) {
2454
- for (const e of value) enumIdx.remove(docId, e);
2455
- }
2456
- } else {
2457
- if (parseVectorDimension(fieldType) !== null) {
2458
- state.vectorStores.get(fieldPath)?.remove(docId);
2391
+ for (const e of value) enumIdx.remove(internalId, e);
2459
2392
  }
2460
2393
  }
2461
2394
  }
2462
2395
  return { fieldLengths, tokensByField };
2463
2396
  }
2464
2397
  function updateFieldIndexOnly(state, docId, oldFields, newDoc, flatSchema) {
2398
+ const internalId = resolveInternalId(state, docId);
2465
2399
  for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2466
2400
  if (fieldType === "string" || fieldType === "string[]") continue;
2467
2401
  const oldVal = getNestedValue(oldFields, fieldPath);
@@ -2475,67 +2409,58 @@ function updateFieldIndexOnly(state, docId, oldFields, newDoc, flatSchema) {
2475
2409
  if (fieldType === "number") {
2476
2410
  const numIdx = state.numericIndexes.get(fieldPath);
2477
2411
  if (numIdx) {
2478
- if (oldVal !== void 0 && oldVal !== null) numIdx.remove(docId, oldVal);
2479
- if (newVal !== void 0 && newVal !== null) numIdx.insert(docId, newVal);
2412
+ if (oldVal !== void 0 && oldVal !== null) numIdx.remove(internalId, oldVal);
2413
+ if (newVal !== void 0 && newVal !== null) numIdx.insert(internalId, newVal);
2480
2414
  }
2481
2415
  } else if (fieldType === "boolean") {
2482
2416
  const boolIdx = state.booleanIndexes.get(fieldPath);
2483
2417
  if (boolIdx) {
2484
- if (oldVal !== void 0 && oldVal !== null) boolIdx.remove(docId, oldVal);
2485
- if (newVal !== void 0 && newVal !== null) boolIdx.insert(docId, newVal);
2418
+ if (oldVal !== void 0 && oldVal !== null) boolIdx.remove(internalId, oldVal);
2419
+ if (newVal !== void 0 && newVal !== null) boolIdx.insert(internalId, newVal);
2486
2420
  }
2487
2421
  } else if (fieldType === "enum") {
2488
2422
  const enumIdx = state.enumIndexes.get(fieldPath);
2489
2423
  if (enumIdx) {
2490
- if (oldVal !== void 0 && oldVal !== null) enumIdx.remove(docId, oldVal);
2491
- if (newVal !== void 0 && newVal !== null) enumIdx.insert(docId, newVal);
2424
+ if (oldVal !== void 0 && oldVal !== null) enumIdx.remove(internalId, oldVal);
2425
+ if (newVal !== void 0 && newVal !== null) enumIdx.insert(internalId, newVal);
2492
2426
  }
2493
2427
  } else if (fieldType === "geopoint") {
2494
2428
  const geoIdx = state.geoIndexes.get(fieldPath);
2495
2429
  if (geoIdx) {
2496
- if (oldVal !== void 0 && oldVal !== null) geoIdx.remove(docId);
2430
+ if (oldVal !== void 0 && oldVal !== null) geoIdx.remove(internalId);
2497
2431
  if (newVal !== void 0 && newVal !== null) {
2498
2432
  const geo = newVal;
2499
- geoIdx.insert(docId, geo.lat, geo.lon);
2433
+ geoIdx.insert(internalId, geo.lat, geo.lon);
2500
2434
  }
2501
2435
  }
2502
2436
  } else if (fieldType === "number[]") {
2503
2437
  const numIdx = state.numericIndexes.get(fieldPath);
2504
2438
  if (numIdx) {
2505
2439
  if (oldVal !== void 0 && oldVal !== null) {
2506
- for (const n of oldVal) numIdx.remove(docId, n);
2440
+ for (const n of oldVal) numIdx.remove(internalId, n);
2507
2441
  }
2508
2442
  if (newVal !== void 0 && newVal !== null) {
2509
- for (const n of newVal) numIdx.insert(docId, n);
2443
+ for (const n of newVal) numIdx.insert(internalId, n);
2510
2444
  }
2511
2445
  }
2512
2446
  } else if (fieldType === "boolean[]") {
2513
2447
  const boolIdx = state.booleanIndexes.get(fieldPath);
2514
2448
  if (boolIdx) {
2515
2449
  if (oldVal !== void 0 && oldVal !== null) {
2516
- for (const b of oldVal) boolIdx.remove(docId, b);
2450
+ for (const b of oldVal) boolIdx.remove(internalId, b);
2517
2451
  }
2518
2452
  if (newVal !== void 0 && newVal !== null) {
2519
- for (const b of newVal) boolIdx.insert(docId, b);
2453
+ for (const b of newVal) boolIdx.insert(internalId, b);
2520
2454
  }
2521
2455
  }
2522
2456
  } else if (fieldType === "enum[]") {
2523
2457
  const enumIdx = state.enumIndexes.get(fieldPath);
2524
2458
  if (enumIdx) {
2525
2459
  if (oldVal !== void 0 && oldVal !== null) {
2526
- for (const e of oldVal) enumIdx.remove(docId, e);
2527
- }
2528
- if (newVal !== void 0 && newVal !== null) {
2529
- for (const e of newVal) enumIdx.insert(docId, e);
2460
+ for (const e of oldVal) enumIdx.remove(internalId, e);
2530
2461
  }
2531
- }
2532
- } else if (parseVectorDimension(fieldType) !== null) {
2533
- const vecStore = state.vectorStores.get(fieldPath);
2534
- if (vecStore) {
2535
- vecStore.remove(docId);
2536
2462
  if (newVal !== void 0 && newVal !== null) {
2537
- const vec = newVal instanceof Float32Array ? newVal : new Float32Array(newVal);
2538
- vecStore.insert(docId, vec);
2463
+ for (const e of newVal) enumIdx.insert(internalId, e);
2539
2464
  }
2540
2465
  }
2541
2466
  }
@@ -2543,39 +2468,26 @@ function updateFieldIndexOnly(state, docId, oldFields, newDoc, flatSchema) {
2543
2468
  }
2544
2469
 
2545
2470
  // src/filters/combinators.ts
2546
- function applyAnd(sets) {
2547
- if (sets.length === 0) return /* @__PURE__ */ new Set();
2548
- if (sets.length === 1) return sets[0];
2549
- const sorted = [...sets].sort((a, b) => a.size - b.size);
2550
- let result = sorted[0];
2551
- for (let i = 1; i < sorted.length; i++) {
2552
- const next = sorted[i];
2553
- const intersection = /* @__PURE__ */ new Set();
2554
- for (const item of result) {
2555
- if (next.has(item)) intersection.add(item);
2556
- }
2557
- result = intersection;
2558
- if (result.size === 0) break;
2471
+ function applyAndBitset(bitsets) {
2472
+ if (bitsets.length === 0) return new Uint32Array(0);
2473
+ if (bitsets.length === 1) return bitsets[0];
2474
+ let result = bitsets[0];
2475
+ for (let i = 1; i < bitsets.length; i++) {
2476
+ result = bitsetAnd(result, bitsets[i]);
2559
2477
  }
2560
2478
  return result;
2561
2479
  }
2562
- function applyOr(sets) {
2563
- if (sets.length === 0) return /* @__PURE__ */ new Set();
2564
- if (sets.length === 1) return sets[0];
2565
- const result = /* @__PURE__ */ new Set();
2566
- for (const set of sets) {
2567
- for (const item of set) {
2568
- result.add(item);
2569
- }
2480
+ function applyOrBitset(bitsets) {
2481
+ if (bitsets.length === 0) return new Uint32Array(0);
2482
+ if (bitsets.length === 1) return bitsets[0];
2483
+ let result = bitsets[0];
2484
+ for (let i = 1; i < bitsets.length; i++) {
2485
+ result = bitsetOr(result, bitsets[i]);
2570
2486
  }
2571
2487
  return result;
2572
2488
  }
2573
- function applyNot(universe, excluded) {
2574
- const result = /* @__PURE__ */ new Set();
2575
- for (const item of universe) {
2576
- if (!excluded.has(item)) result.add(item);
2577
- }
2578
- return result;
2489
+ function applyNotBitset(excluded, capacity) {
2490
+ return bitsetNot(excluded, capacity);
2579
2491
  }
2580
2492
 
2581
2493
  // src/filters/operators.ts
@@ -2586,251 +2498,176 @@ function convertToMeters(distance, unit) {
2586
2498
  if (unit === "mi") return distance * METERS_PER_MI;
2587
2499
  return distance;
2588
2500
  }
2589
- function setDifference(a, b) {
2590
- const result = /* @__PURE__ */ new Set();
2591
- for (const item of a) {
2592
- if (!b.has(item)) result.add(item);
2501
+ function resolveBitset(bs) {
2502
+ return typeof bs === "function" ? bs() : bs;
2503
+ }
2504
+ function scanToBitset(docIds, capacity, getValue, predicate) {
2505
+ const resolved = resolveBitset(docIds);
2506
+ const result = createBitSet(capacity);
2507
+ for (let wi = 0; wi < resolved.length; wi++) {
2508
+ let word = resolved[wi];
2509
+ if (word === 0) continue;
2510
+ const base = wi << 5;
2511
+ while (word !== 0) {
2512
+ const tz = Math.clz32(word & -word) ^ 31;
2513
+ const id = base + tz;
2514
+ if (predicate(getValue(id))) {
2515
+ bitsetSet(result, id);
2516
+ }
2517
+ word &= word - 1;
2518
+ }
2593
2519
  }
2594
2520
  return result;
2595
2521
  }
2596
- function applyEq(value, docIds, getValue, fieldIndex) {
2522
+ function applyEqBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2597
2523
  if (fieldIndex?.type === "numeric" && typeof value === "number") {
2598
- return fieldIndex.index.eq(value);
2524
+ return fieldIndex.index.eqBitset(value, capacity);
2599
2525
  }
2600
2526
  if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
2601
- return value ? fieldIndex.index.getTrue() : fieldIndex.index.getFalse();
2527
+ return value ? fieldIndex.index.getTrueBitset(capacity) : fieldIndex.index.getFalseBitset(capacity);
2602
2528
  }
2603
2529
  if (fieldIndex?.type === "enum" && typeof value === "string") {
2604
- return fieldIndex.index.getDocIds(value);
2530
+ return fieldIndex.index.getDocIdsBitset(value, capacity);
2605
2531
  }
2606
- const result = /* @__PURE__ */ new Set();
2607
- for (const docId of docIds) {
2608
- if (getValue(docId) === value) result.add(docId);
2609
- }
2610
- return result;
2532
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === value);
2611
2533
  }
2612
- function applyNe(value, docIds, getValue, fieldIndex) {
2534
+ function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2613
2535
  if (fieldIndex?.type === "numeric" && typeof value === "number") {
2614
- return setDifference(fieldIndex.index.allDocIds(), fieldIndex.index.eq(value));
2536
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
2537
+ const eq = fieldIndex.index.eqBitset(value, capacity);
2538
+ return bitsetAnd(all, bitsetNot(eq, capacity));
2615
2539
  }
2616
2540
  if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
2617
- return value ? fieldIndex.index.getFalse() : fieldIndex.index.getTrue();
2541
+ return value ? fieldIndex.index.getFalseBitset(capacity) : fieldIndex.index.getTrueBitset(capacity);
2618
2542
  }
2619
2543
  if (fieldIndex?.type === "enum" && typeof value === "string") {
2620
- return setDifference(fieldIndex.index.allDocIds(), fieldIndex.index.getDocIds(value));
2621
- }
2622
- const result = /* @__PURE__ */ new Set();
2623
- for (const docId of docIds) {
2624
- const v = getValue(docId);
2625
- if (v !== void 0 && v !== null && v !== value) result.add(docId);
2544
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
2545
+ const eq = fieldIndex.index.getDocIdsBitset(value, capacity);
2546
+ return bitsetAnd(all, bitsetNot(eq, capacity));
2626
2547
  }
2627
- return result;
2548
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null && v !== value);
2628
2549
  }
2629
- function applyGt(value, docIds, getValue, fieldIndex) {
2550
+ function applyGtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2630
2551
  if (fieldIndex?.type === "numeric") {
2631
- return fieldIndex.index.gt(value);
2632
- }
2633
- const result = /* @__PURE__ */ new Set();
2634
- for (const docId of docIds) {
2635
- const v = getValue(docId);
2636
- if (typeof v === "number" && v > value) result.add(docId);
2552
+ return fieldIndex.index.gtBitset(value, capacity);
2637
2553
  }
2638
- return result;
2554
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v > value);
2639
2555
  }
2640
- function applyLt(value, docIds, getValue, fieldIndex) {
2556
+ function applyLtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2641
2557
  if (fieldIndex?.type === "numeric") {
2642
- return fieldIndex.index.lt(value);
2643
- }
2644
- const result = /* @__PURE__ */ new Set();
2645
- for (const docId of docIds) {
2646
- const v = getValue(docId);
2647
- if (typeof v === "number" && v < value) result.add(docId);
2558
+ return fieldIndex.index.ltBitset(value, capacity);
2648
2559
  }
2649
- return result;
2560
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v < value);
2650
2561
  }
2651
- function applyGte(value, docIds, getValue, fieldIndex) {
2562
+ function applyGteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2652
2563
  if (fieldIndex?.type === "numeric") {
2653
- return fieldIndex.index.gte(value);
2564
+ return fieldIndex.index.gteBitset(value, capacity);
2654
2565
  }
2655
- const result = /* @__PURE__ */ new Set();
2656
- for (const docId of docIds) {
2657
- const v = getValue(docId);
2658
- if (typeof v === "number" && v >= value) result.add(docId);
2659
- }
2660
- return result;
2566
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= value);
2661
2567
  }
2662
- function applyLte(value, docIds, getValue, fieldIndex) {
2568
+ function applyLteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
2663
2569
  if (fieldIndex?.type === "numeric") {
2664
- return fieldIndex.index.lte(value);
2665
- }
2666
- const result = /* @__PURE__ */ new Set();
2667
- for (const docId of docIds) {
2668
- const v = getValue(docId);
2669
- if (typeof v === "number" && v <= value) result.add(docId);
2570
+ return fieldIndex.index.lteBitset(value, capacity);
2670
2571
  }
2671
- return result;
2572
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v <= value);
2672
2573
  }
2673
- function applyBetween(range, docIds, getValue, fieldIndex) {
2574
+ function applyBetweenBitset(range, allDocsBitset, capacity, getValue, fieldIndex) {
2674
2575
  if (fieldIndex?.type === "numeric") {
2675
- return fieldIndex.index.between(range[0], range[1]);
2676
- }
2677
- const result = /* @__PURE__ */ new Set();
2678
- for (const docId of docIds) {
2679
- const v = getValue(docId);
2680
- if (typeof v === "number" && v >= range[0] && v <= range[1]) result.add(docId);
2576
+ return fieldIndex.index.betweenBitset(range[0], range[1], capacity);
2681
2577
  }
2682
- return result;
2578
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= range[0] && v <= range[1]);
2683
2579
  }
2684
- function applyIn(values, docIds, getValue, fieldIndex) {
2580
+ function applyInBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2685
2581
  if (fieldIndex?.type === "enum") {
2686
- const result2 = /* @__PURE__ */ new Set();
2687
- for (const val of values) {
2688
- for (const docId of fieldIndex.index.getDocIds(val)) {
2689
- result2.add(docId);
2690
- }
2691
- }
2692
- return result2;
2582
+ return fieldIndex.index.getDocIdsInBitset(values, capacity);
2693
2583
  }
2694
2584
  const valSet = new Set(values);
2695
- const result = /* @__PURE__ */ new Set();
2696
- for (const docId of docIds) {
2697
- const v = getValue(docId);
2698
- if (typeof v === "string" && valSet.has(v)) result.add(docId);
2699
- }
2700
- return result;
2585
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && valSet.has(v));
2701
2586
  }
2702
- function applyNin(values, docIds, getValue, fieldIndex) {
2587
+ function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2703
2588
  if (fieldIndex?.type === "enum") {
2704
- const matched = /* @__PURE__ */ new Set();
2705
- for (const val of values) {
2706
- for (const docId of fieldIndex.index.getDocIds(val)) {
2707
- matched.add(docId);
2708
- }
2709
- }
2710
- return setDifference(fieldIndex.index.allDocIds(), matched);
2589
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
2590
+ const matched = fieldIndex.index.getDocIdsInBitset(values, capacity);
2591
+ return bitsetAnd(all, bitsetNot(matched, capacity));
2711
2592
  }
2712
2593
  const valSet = new Set(values);
2713
- const result = /* @__PURE__ */ new Set();
2714
- for (const docId of docIds) {
2715
- const v = getValue(docId);
2716
- if (v !== void 0 && v !== null && typeof v === "string" && !valSet.has(v)) result.add(docId);
2717
- }
2718
- return result;
2594
+ return scanToBitset(
2595
+ allDocsBitset,
2596
+ capacity,
2597
+ getValue,
2598
+ (v) => v !== void 0 && v !== null && typeof v === "string" && !valSet.has(v)
2599
+ );
2719
2600
  }
2720
- function applyStartsWith(prefix, docIds, getValue) {
2721
- const result = /* @__PURE__ */ new Set();
2722
- for (const docId of docIds) {
2723
- const v = getValue(docId);
2724
- if (typeof v === "string" && v.startsWith(prefix)) result.add(docId);
2725
- }
2726
- return result;
2601
+ function applyStartsWithBitset(prefix, allDocsBitset, capacity, getValue) {
2602
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.startsWith(prefix));
2727
2603
  }
2728
- function applyEndsWith(suffix, docIds, getValue) {
2729
- const result = /* @__PURE__ */ new Set();
2730
- for (const docId of docIds) {
2731
- const v = getValue(docId);
2732
- if (typeof v === "string" && v.endsWith(suffix)) result.add(docId);
2733
- }
2734
- return result;
2604
+ function applyEndsWithBitset(suffix, allDocsBitset, capacity, getValue) {
2605
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.endsWith(suffix));
2735
2606
  }
2736
- function applyContainsAll(values, docIds, getValue) {
2737
- const result = /* @__PURE__ */ new Set();
2738
- for (const docId of docIds) {
2739
- const v = getValue(docId);
2740
- if (!Array.isArray(v)) continue;
2607
+ function applyContainsAllBitset(values, allDocsBitset, capacity, getValue) {
2608
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2609
+ if (!Array.isArray(v)) return false;
2741
2610
  const arr = v;
2742
- let allFound = true;
2743
2611
  for (const val of values) {
2744
- if (!arr.includes(val)) {
2745
- allFound = false;
2746
- break;
2747
- }
2612
+ if (!arr.includes(val)) return false;
2748
2613
  }
2749
- if (allFound) result.add(docId);
2750
- }
2751
- return result;
2614
+ return true;
2615
+ });
2752
2616
  }
2753
- function applyMatchesAny(values, docIds, getValue) {
2617
+ function applyMatchesAnyBitset(values, allDocsBitset, capacity, getValue) {
2754
2618
  const valSet = new Set(values);
2755
- const result = /* @__PURE__ */ new Set();
2756
- for (const docId of docIds) {
2757
- const v = getValue(docId);
2758
- if (!Array.isArray(v)) continue;
2619
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2620
+ if (!Array.isArray(v)) return false;
2759
2621
  for (const item of v) {
2760
- if (valSet.has(item)) {
2761
- result.add(docId);
2762
- break;
2763
- }
2622
+ if (valSet.has(item)) return true;
2764
2623
  }
2765
- }
2766
- return result;
2624
+ return false;
2625
+ });
2767
2626
  }
2768
- function applySize(sizeFilter, docIds, getValue) {
2769
- const result = /* @__PURE__ */ new Set();
2770
- for (const docId of docIds) {
2771
- const v = getValue(docId);
2772
- if (!Array.isArray(v)) continue;
2773
- if (matchesNumericComparison(v.length, sizeFilter)) result.add(docId);
2774
- }
2775
- return result;
2627
+ function applySizeBitset(sizeFilter, allDocsBitset, capacity, getValue) {
2628
+ return scanToBitset(
2629
+ allDocsBitset,
2630
+ capacity,
2631
+ getValue,
2632
+ (v) => Array.isArray(v) && matchesNumericComparison(v.length, sizeFilter)
2633
+ );
2776
2634
  }
2777
- function applyExists(docIds, getValue) {
2778
- const result = /* @__PURE__ */ new Set();
2779
- for (const docId of docIds) {
2780
- const v = getValue(docId);
2781
- if (v !== void 0 && v !== null) result.add(docId);
2782
- }
2783
- return result;
2635
+ function applyExistsBitset(allDocsBitset, capacity, getValue) {
2636
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null);
2784
2637
  }
2785
- function applyNotExists(docIds, getValue) {
2786
- const result = /* @__PURE__ */ new Set();
2787
- for (const docId of docIds) {
2788
- const v = getValue(docId);
2789
- if (v === void 0 || v === null) result.add(docId);
2790
- }
2791
- return result;
2638
+ function applyNotExistsBitset(allDocsBitset, capacity, getValue) {
2639
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === void 0 || v === null);
2792
2640
  }
2793
- function applyIsEmpty(docIds, getValue) {
2794
- const result = /* @__PURE__ */ new Set();
2795
- for (const docId of docIds) {
2796
- const v = getValue(docId);
2797
- if (v === void 0 || v === null) {
2798
- result.add(docId);
2799
- continue;
2800
- }
2801
- if (typeof v === "string" && v === "") {
2802
- result.add(docId);
2803
- continue;
2804
- }
2805
- if (Array.isArray(v) && v.length === 0) {
2806
- result.add(docId);
2807
- }
2808
- }
2809
- return result;
2641
+ function applyIsEmptyBitset(allDocsBitset, capacity, getValue) {
2642
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2643
+ if (v === void 0 || v === null) return true;
2644
+ if (typeof v === "string" && v === "") return true;
2645
+ if (Array.isArray(v) && v.length === 0) return true;
2646
+ return false;
2647
+ });
2810
2648
  }
2811
- function applyIsNotEmpty(docIds, getValue) {
2812
- const result = /* @__PURE__ */ new Set();
2813
- for (const docId of docIds) {
2814
- const v = getValue(docId);
2815
- if (v === void 0 || v === null) continue;
2816
- if (typeof v === "string" && v === "") continue;
2817
- if (Array.isArray(v) && v.length === 0) continue;
2818
- result.add(docId);
2819
- }
2820
- return result;
2649
+ function applyIsNotEmptyBitset(allDocsBitset, capacity, getValue) {
2650
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2651
+ if (v === void 0 || v === null) return false;
2652
+ if (typeof v === "string" && v === "") return false;
2653
+ if (Array.isArray(v) && v.length === 0) return false;
2654
+ return true;
2655
+ });
2821
2656
  }
2822
- function applyGeoRadius(filter, geoIndex) {
2657
+ function applyGeoRadiusBitset(filter, geoIndex, capacity) {
2823
2658
  const distanceMeters = convertToMeters(filter.distance, filter.unit);
2824
- return geoIndex.radiusQuery(
2659
+ const setResult = geoIndex.radiusQuery(
2825
2660
  filter.lat,
2826
2661
  filter.lon,
2827
2662
  distanceMeters,
2828
2663
  filter.inside ?? true,
2829
2664
  filter.highPrecision ?? false
2830
2665
  );
2666
+ return bitsetFromSet(setResult, capacity);
2831
2667
  }
2832
- function applyGeoPolygon(filter, geoIndex) {
2833
- return geoIndex.polygonQuery(filter.points, filter.inside ?? true);
2668
+ function applyGeoPolygonBitset(filter, geoIndex, capacity) {
2669
+ const setResult = geoIndex.polygonQuery(filter.points, filter.inside ?? true);
2670
+ return bitsetFromSet(setResult, capacity);
2834
2671
  }
2835
2672
  function matchesNumericComparison(value, filter) {
2836
2673
  if (filter.eq !== void 0 && value !== filter.eq) return false;
@@ -2845,98 +2682,119 @@ function matchesNumericComparison(value, filter) {
2845
2682
 
2846
2683
  // src/filters/evaluator.ts
2847
2684
  function evaluateFilters(expression, context) {
2848
- const sets = [];
2685
+ const bitsets = [];
2849
2686
  if (expression.fields) {
2850
2687
  for (const [fieldPath, filter] of Object.entries(expression.fields)) {
2851
- sets.push(evaluateFieldFilter(fieldPath, filter, context));
2688
+ bitsets.push(evaluateFieldFilter(fieldPath, filter, context));
2852
2689
  }
2853
2690
  }
2854
2691
  if (expression.and?.length) {
2855
- const andSets = expression.and.map((expr) => evaluateFilters(expr, context));
2856
- sets.push(applyAnd(andSets));
2692
+ const andBitsets = expression.and.map((expr) => evaluateFilters(expr, context));
2693
+ bitsets.push(applyAndBitset(andBitsets));
2857
2694
  }
2858
2695
  if (expression.or?.length) {
2859
- const orSets = expression.or.map((expr) => evaluateFilters(expr, context));
2860
- sets.push(applyOr(orSets));
2696
+ const orBitsets = expression.or.map((expr) => evaluateFilters(expr, context));
2697
+ bitsets.push(applyOrBitset(orBitsets));
2861
2698
  }
2862
2699
  if (expression.not) {
2863
2700
  const excluded = evaluateFilters(expression.not, context);
2864
- sets.push(applyNot(context.allDocIds, excluded));
2701
+ const universe = context.allDocIdsBitset;
2702
+ bitsets.push(applyAndBitset([universe, applyNotBitset(excluded, context.capacity)]));
2865
2703
  }
2866
- if (sets.length === 0) return new Set(context.allDocIds);
2867
- if (sets.length === 1) return sets[0];
2868
- return applyAnd(sets);
2704
+ if (bitsets.length === 0) return context.allDocIdsBitset;
2705
+ if (bitsets.length === 1) return bitsets[0];
2706
+ return applyAndBitset(bitsets);
2869
2707
  }
2870
2708
  function evaluateFieldFilter(fieldPath, filter, context) {
2871
2709
  const fieldIndex = context.fieldIndexes[fieldPath];
2872
- const getValue = (docId) => context.getFieldValue(docId, fieldPath);
2710
+ const getValue = (internalId) => context.getFieldValue(internalId, fieldPath);
2873
2711
  const f = filter;
2874
- const sets = [];
2712
+ const bitsets = [];
2713
+ const { capacity } = context;
2714
+ let allDocsBitset = null;
2715
+ const getAllDocsBitset = () => {
2716
+ if (allDocsBitset === null) {
2717
+ allDocsBitset = context.allDocIdsBitset;
2718
+ }
2719
+ return allDocsBitset;
2720
+ };
2875
2721
  if ("radius" in f && f.radius) {
2876
2722
  const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
2877
- sets.push(applyGeoRadius(filter.radius, geoIndex));
2723
+ bitsets.push(applyGeoRadiusBitset(filter.radius, geoIndex, capacity));
2878
2724
  }
2879
2725
  if ("polygon" in f && f.polygon) {
2880
2726
  const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
2881
- sets.push(applyGeoPolygon(filter.polygon, geoIndex));
2727
+ bitsets.push(applyGeoPolygonBitset(filter.polygon, geoIndex, capacity));
2882
2728
  }
2883
2729
  if ("eq" in f && f.eq !== void 0) {
2884
- sets.push(applyEq(f.eq, context.allDocIds, getValue, fieldIndex));
2730
+ bitsets.push(applyEqBitset(f.eq, getAllDocsBitset, capacity, getValue, fieldIndex));
2885
2731
  }
2886
2732
  if ("ne" in f && f.ne !== void 0) {
2887
- sets.push(applyNe(f.ne, context.allDocIds, getValue, fieldIndex));
2733
+ bitsets.push(applyNeBitset(f.ne, getAllDocsBitset, capacity, getValue, fieldIndex));
2888
2734
  }
2889
2735
  if ("gt" in f && f.gt !== void 0) {
2890
- sets.push(applyGt(f.gt, context.allDocIds, getValue, fieldIndex));
2736
+ bitsets.push(applyGtBitset(f.gt, getAllDocsBitset, capacity, getValue, fieldIndex));
2891
2737
  }
2892
2738
  if ("lt" in f && f.lt !== void 0) {
2893
- sets.push(applyLt(f.lt, context.allDocIds, getValue, fieldIndex));
2739
+ bitsets.push(applyLtBitset(f.lt, getAllDocsBitset, capacity, getValue, fieldIndex));
2894
2740
  }
2895
2741
  if ("gte" in f && f.gte !== void 0) {
2896
- sets.push(applyGte(f.gte, context.allDocIds, getValue, fieldIndex));
2742
+ bitsets.push(applyGteBitset(f.gte, getAllDocsBitset, capacity, getValue, fieldIndex));
2897
2743
  }
2898
2744
  if ("lte" in f && f.lte !== void 0) {
2899
- sets.push(applyLte(f.lte, context.allDocIds, getValue, fieldIndex));
2745
+ bitsets.push(applyLteBitset(f.lte, getAllDocsBitset, capacity, getValue, fieldIndex));
2900
2746
  }
2901
2747
  if ("between" in f && f.between !== void 0) {
2902
- sets.push(applyBetween(f.between, context.allDocIds, getValue, fieldIndex));
2748
+ bitsets.push(applyBetweenBitset(f.between, getAllDocsBitset, capacity, getValue, fieldIndex));
2903
2749
  }
2904
2750
  if ("in" in f && f.in !== void 0) {
2905
- sets.push(applyIn(f.in, context.allDocIds, getValue, fieldIndex));
2751
+ bitsets.push(applyInBitset(f.in, getAllDocsBitset, capacity, getValue, fieldIndex));
2906
2752
  }
2907
2753
  if ("nin" in f && f.nin !== void 0) {
2908
- sets.push(applyNin(f.nin, context.allDocIds, getValue, fieldIndex));
2754
+ bitsets.push(applyNinBitset(f.nin, getAllDocsBitset, capacity, getValue, fieldIndex));
2909
2755
  }
2910
2756
  if ("startsWith" in f && f.startsWith !== void 0) {
2911
- sets.push(applyStartsWith(f.startsWith, context.allDocIds, getValue));
2757
+ bitsets.push(applyStartsWithBitset(f.startsWith, getAllDocsBitset, capacity, getValue));
2912
2758
  }
2913
2759
  if ("endsWith" in f && f.endsWith !== void 0) {
2914
- sets.push(applyEndsWith(f.endsWith, context.allDocIds, getValue));
2760
+ bitsets.push(applyEndsWithBitset(f.endsWith, getAllDocsBitset, capacity, getValue));
2915
2761
  }
2916
2762
  if ("containsAll" in f && f.containsAll !== void 0) {
2917
- sets.push(applyContainsAll(f.containsAll, context.allDocIds, getValue));
2763
+ bitsets.push(
2764
+ applyContainsAllBitset(f.containsAll, getAllDocsBitset, capacity, getValue)
2765
+ );
2918
2766
  }
2919
2767
  if ("matchesAny" in f && f.matchesAny !== void 0) {
2920
- sets.push(applyMatchesAny(f.matchesAny, context.allDocIds, getValue));
2768
+ bitsets.push(
2769
+ applyMatchesAnyBitset(f.matchesAny, getAllDocsBitset, capacity, getValue)
2770
+ );
2921
2771
  }
2922
2772
  if ("size" in f && f.size !== void 0) {
2923
- sets.push(applySize(f.size, context.allDocIds, getValue));
2773
+ bitsets.push(applySizeBitset(f.size, getAllDocsBitset, capacity, getValue));
2924
2774
  }
2925
2775
  if ("exists" in f && f.exists !== void 0) {
2926
- sets.push(f.exists ? applyExists(context.allDocIds, getValue) : applyNotExists(context.allDocIds, getValue));
2776
+ bitsets.push(
2777
+ f.exists ? applyExistsBitset(getAllDocsBitset, capacity, getValue) : applyNotExistsBitset(getAllDocsBitset, capacity, getValue)
2778
+ );
2927
2779
  }
2928
2780
  if ("notExists" in f && f.notExists !== void 0) {
2929
- sets.push(f.notExists ? applyNotExists(context.allDocIds, getValue) : applyExists(context.allDocIds, getValue));
2781
+ bitsets.push(
2782
+ f.notExists ? applyNotExistsBitset(getAllDocsBitset, capacity, getValue) : applyExistsBitset(getAllDocsBitset, capacity, getValue)
2783
+ );
2930
2784
  }
2931
2785
  if ("isEmpty" in f && f.isEmpty !== void 0) {
2932
- sets.push(f.isEmpty ? applyIsEmpty(context.allDocIds, getValue) : applyIsNotEmpty(context.allDocIds, getValue));
2786
+ bitsets.push(
2787
+ f.isEmpty ? applyIsEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue)
2788
+ );
2933
2789
  }
2934
2790
  if ("isNotEmpty" in f && f.isNotEmpty !== void 0) {
2935
- sets.push(f.isNotEmpty ? applyIsNotEmpty(context.allDocIds, getValue) : applyIsEmpty(context.allDocIds, getValue));
2791
+ bitsets.push(
2792
+ f.isNotEmpty ? applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsEmptyBitset(getAllDocsBitset, capacity, getValue)
2793
+ );
2936
2794
  }
2937
- if (sets.length === 0) return new Set(context.allDocIds);
2938
- if (sets.length === 1) return sets[0];
2939
- return applyAnd(sets);
2795
+ if (bitsets.length === 0) return context.allDocIdsBitset;
2796
+ if (bitsets.length === 1) return bitsets[0];
2797
+ return applyAndBitset(bitsets);
2940
2798
  }
2941
2799
  function resolveGeoIndex(fieldPath, fieldIndex) {
2942
2800
  if (fieldIndex?.type !== "geopoint") {
@@ -2984,7 +2842,8 @@ function searchFulltext(state, params) {
2984
2842
  bm25Params,
2985
2843
  globalStats,
2986
2844
  maxResults,
2987
- termMatch
2845
+ termMatch,
2846
+ filterBitset
2988
2847
  } = params;
2989
2848
  if (queryTokens.length === 0) {
2990
2849
  return { scored: [], totalMatched: 0 };
@@ -2997,6 +2856,7 @@ function searchFulltext(state, params) {
2997
2856
  const fieldLengthCache = /* @__PURE__ */ new Map();
2998
2857
  const useIntersection = termMatch === "all" && queryTokens.length > 1;
2999
2858
  const fieldNames = state.fieldNameTable.names;
2859
+ const resolver = state.docStore.resolver();
3000
2860
  if (useIntersection) {
3001
2861
  const resolved = [];
3002
2862
  for (const qt of queryTokens) {
@@ -3020,31 +2880,33 @@ function searchFulltext(state, params) {
3020
2880
  const list = match.postingList;
3021
2881
  const hasDeleted = list.deletedDocs.size > 0;
3022
2882
  for (let pi = 0; pi < list.length; pi++) {
3023
- const docId = list.docIds[pi];
3024
- if (hasDeleted && list.deletedDocs.has(docId)) continue;
3025
- if (tokenIndex > 0 && !docScores.has(docId)) continue;
2883
+ const internalId = list.docIds[pi];
2884
+ if (hasDeleted && list.deletedDocs.has(internalId)) continue;
2885
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
2886
+ if (tokenIndex > 0 && !docScores.has(internalId)) continue;
3026
2887
  const fieldName = fieldNames[list.fieldNameIndices[pi]];
3027
2888
  if (fields && !fields.includes(fieldName)) continue;
3028
2889
  const termFrequency = list.termFrequencies[pi];
3029
2890
  const fieldBoost = boost?.[fieldName] ?? 1;
3030
2891
  const avgLen = avgFieldLengths[fieldName] ?? 1;
3031
- let cachedLengths = fieldLengthCache.get(docId);
2892
+ let cachedLengths = fieldLengthCache.get(internalId);
3032
2893
  if (cachedLengths === void 0) {
3033
- const storedDoc = state.docStore.get(docId);
2894
+ const externalId = resolver.toExternal(internalId);
2895
+ const storedDoc = externalId !== void 0 ? state.docStore.get(externalId) : void 0;
3034
2896
  cachedLengths = storedDoc?.fieldLengths ?? null;
3035
- fieldLengthCache.set(docId, cachedLengths);
2897
+ fieldLengthCache.set(internalId, cachedLengths);
3036
2898
  }
3037
2899
  const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
3038
2900
  let termScore = scoreFn(termFrequency, match.docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
3039
2901
  termScore *= fieldBoost;
3040
- const existing = docScores.get(docId);
2902
+ const existing = docScores.get(internalId);
3041
2903
  if (existing) {
3042
2904
  existing.score += termScore;
3043
2905
  existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
3044
2906
  existing.fieldLengths[fieldName] = actualFieldLength;
3045
2907
  existing.idf[match.token] = match.idf;
3046
2908
  } else {
3047
- docScores.set(docId, {
2909
+ docScores.set(internalId, {
3048
2910
  score: termScore,
3049
2911
  termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
3050
2912
  fieldLengths: { [fieldName]: actualFieldLength },
@@ -3066,30 +2928,32 @@ function searchFulltext(state, params) {
3066
2928
  const list = match.postingList;
3067
2929
  const hasDeleted = list.deletedDocs.size > 0;
3068
2930
  for (let pi = 0; pi < list.length; pi++) {
3069
- const docId = list.docIds[pi];
3070
- if (hasDeleted && list.deletedDocs.has(docId)) continue;
2931
+ const internalId = list.docIds[pi];
2932
+ if (hasDeleted && list.deletedDocs.has(internalId)) continue;
2933
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
3071
2934
  const fieldName = fieldNames[list.fieldNameIndices[pi]];
3072
2935
  if (fields && !fields.includes(fieldName)) continue;
3073
2936
  const termFrequency = list.termFrequencies[pi];
3074
2937
  const fieldBoost = boost?.[fieldName] ?? 1;
3075
2938
  const avgLen = avgFieldLengths[fieldName] ?? 1;
3076
- let cachedLengths = fieldLengthCache.get(docId);
2939
+ let cachedLengths = fieldLengthCache.get(internalId);
3077
2940
  if (cachedLengths === void 0) {
3078
- const storedDoc = state.docStore.get(docId);
2941
+ const externalId = resolver.toExternal(internalId);
2942
+ const storedDoc = externalId !== void 0 ? state.docStore.get(externalId) : void 0;
3079
2943
  cachedLengths = storedDoc?.fieldLengths ?? null;
3080
- fieldLengthCache.set(docId, cachedLengths);
2944
+ fieldLengthCache.set(internalId, cachedLengths);
3081
2945
  }
3082
2946
  const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
3083
2947
  let termScore = scoreFn(termFrequency, docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
3084
2948
  termScore *= fieldBoost;
3085
- const existing = docScores.get(docId);
2949
+ const existing = docScores.get(internalId);
3086
2950
  if (existing) {
3087
2951
  existing.score += termScore;
3088
2952
  existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
3089
2953
  existing.fieldLengths[fieldName] = actualFieldLength;
3090
2954
  existing.idf[match.token] = idf;
3091
2955
  } else {
3092
- docScores.set(docId, {
2956
+ docScores.set(internalId, {
3093
2957
  score: termScore,
3094
2958
  termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
3095
2959
  fieldLengths: { [fieldName]: actualFieldLength },
@@ -3102,33 +2966,35 @@ function searchFulltext(state, params) {
3102
2966
  }
3103
2967
  const totalMatched = docScores.size;
3104
2968
  const k = maxResults !== void 0 && maxResults > 0 ? maxResults : DEFAULT_MAX_RESULTS;
3105
- const scored = topKFromMap(docScores, Math.min(k, totalMatched));
2969
+ const scored = topKFromMap(docScores, Math.min(k, totalMatched), resolver);
3106
2970
  return { scored, totalMatched };
3107
2971
  }
3108
- function topKFromMap(docScores, k) {
2972
+ function topKFromMap(docScores, k, resolver) {
3109
2973
  if (k <= 0) return [];
3110
2974
  const heap = [];
3111
- for (const [docId, data] of docScores) {
2975
+ for (const [internalId, data] of docScores) {
3112
2976
  if (heap.length < k) {
3113
- heap.push({ docId, score: data.score });
2977
+ heap.push({ internalId, score: data.score });
3114
2978
  if (heap.length === k) buildMinHeap(heap);
3115
2979
  } else if (data.score > heap[0].score) {
3116
- heap[0] = { docId, score: data.score };
2980
+ heap[0] = { internalId, score: data.score };
3117
2981
  siftDown(heap, 0);
3118
2982
  }
3119
2983
  }
3120
2984
  heap.sort((a, b) => b.score - a.score);
3121
- const result = new Array(heap.length);
2985
+ const result = [];
3122
2986
  for (let i = 0; i < heap.length; i++) {
3123
- const data = docScores.get(heap[i].docId);
2987
+ const data = docScores.get(heap[i].internalId);
3124
2988
  if (!data) continue;
3125
- result[i] = {
3126
- docId: heap[i].docId,
2989
+ const externalId = resolver.toExternal(heap[i].internalId);
2990
+ if (externalId === void 0) continue;
2991
+ result.push({
2992
+ docId: externalId,
3127
2993
  score: data.score,
3128
2994
  termFrequencies: data.termFrequencies,
3129
2995
  fieldLengths: data.fieldLengths,
3130
2996
  idf: data.idf
3131
- };
2997
+ });
3132
2998
  }
3133
2999
  return result;
3134
3000
  }
@@ -3152,26 +3018,10 @@ function siftDown(heap, idx) {
3152
3018
  idx = smallest;
3153
3019
  }
3154
3020
  }
3155
- function searchVector(state, params) {
3156
- const { field, value, k, similarity = 0, metric = "cosine", filterDocIds, efSearch } = params;
3157
- const vecStore = state.vectorStores.get(field);
3158
- if (!vecStore) {
3159
- return { scored: [], totalMatched: 0 };
3160
- }
3161
- if (value.length !== vecStore.dimension) {
3162
- throw new NarsilError(
3163
- ErrorCodes.SEARCH_INVALID_VECTOR_SIZE,
3164
- `Query vector dimension ${value.length} does not match field "${field}" dimension ${vecStore.dimension}`,
3165
- { field, expected: vecStore.dimension, received: value.length }
3166
- );
3167
- }
3168
- const queryVec = new Float32Array(value);
3169
- const results = vecStore.search(queryVec, k, metric, similarity, filterDocIds, efSearch);
3170
- return { scored: results, totalMatched: results.length };
3171
- }
3172
3021
  function buildFilterContext(state, schema) {
3173
3022
  const flat = getFlatSchema(state, schema);
3174
3023
  const fieldIndexes = {};
3024
+ const capacity = state.docStore.internalIdCapacity();
3175
3025
  for (const [fieldPath, fieldType] of Object.entries(flat)) {
3176
3026
  if (fieldType === "number" || fieldType === "number[]") {
3177
3027
  const numIdx = state.numericIndexes.get(fieldPath);
@@ -3185,7 +3035,14 @@ function buildFilterContext(state, schema) {
3185
3035
  lt: (v) => numIdx.queryLt(v),
3186
3036
  lte: (v) => numIdx.queryLte(v),
3187
3037
  between: (min, max) => numIdx.queryBetween(min, max),
3188
- allDocIds: () => numIdx.getAllDocIds()
3038
+ allDocIds: () => numIdx.getAllDocIds(),
3039
+ eqBitset: (v, cap) => numIdx.queryEqBitset(v, cap),
3040
+ gtBitset: (v, cap) => numIdx.queryGtBitset(v, cap),
3041
+ gteBitset: (v, cap) => numIdx.queryGteBitset(v, cap),
3042
+ ltBitset: (v, cap) => numIdx.queryLtBitset(v, cap),
3043
+ lteBitset: (v, cap) => numIdx.queryLteBitset(v, cap),
3044
+ betweenBitset: (min, max, cap) => numIdx.queryBetweenBitset(min, max, cap),
3045
+ allDocIdsBitset: (cap) => numIdx.getAllDocIdsBitset(cap)
3189
3046
  }
3190
3047
  };
3191
3048
  }
@@ -3197,7 +3054,10 @@ function buildFilterContext(state, schema) {
3197
3054
  index: {
3198
3055
  getTrue: () => boolIdx.queryEq(true),
3199
3056
  getFalse: () => boolIdx.queryEq(false),
3200
- allDocIds: () => boolIdx.getAllDocIds()
3057
+ allDocIds: () => boolIdx.getAllDocIds(),
3058
+ getTrueBitset: (cap) => boolIdx.queryEqBitset(true, cap),
3059
+ getFalseBitset: (cap) => boolIdx.queryEqBitset(false, cap),
3060
+ allDocIdsBitset: (cap) => boolIdx.getAllDocIdsBitset(cap)
3201
3061
  }
3202
3062
  };
3203
3063
  }
@@ -3208,7 +3068,10 @@ function buildFilterContext(state, schema) {
3208
3068
  type: "enum",
3209
3069
  index: {
3210
3070
  getDocIds: (v) => enumIdx.queryEq(v),
3211
- allDocIds: () => enumIdx.getAllDocIds()
3071
+ allDocIds: () => enumIdx.getAllDocIds(),
3072
+ getDocIdsBitset: (v, cap) => enumIdx.queryEqBitset(v, cap),
3073
+ getDocIdsInBitset: (values, cap) => enumIdx.queryInBitset(values, cap),
3074
+ allDocIdsBitset: (cap) => enumIdx.getAllDocIdsBitset(cap)
3212
3075
  }
3213
3076
  };
3214
3077
  }
@@ -3223,18 +3086,47 @@ function buildFilterContext(state, schema) {
3223
3086
  }
3224
3087
  }
3225
3088
  let cachedAllDocIds = null;
3089
+ let cachedAllDocIdsBitset = null;
3226
3090
  return {
3227
3091
  fieldIndexes,
3228
- getFieldValue: (docId, fieldPath) => getFieldValueForDoc(state.docStore, docId, fieldPath),
3092
+ getFieldValue: (internalId, fieldPath) => getFieldValueByInternalId(state.docStore, internalId, fieldPath),
3229
3093
  get allDocIds() {
3230
3094
  if (!cachedAllDocIds) {
3231
- cachedAllDocIds = getAllDocIds(state.docStore);
3095
+ cachedAllDocIds = getAllInternalDocIds(state.docStore);
3232
3096
  }
3233
3097
  return cachedAllDocIds;
3098
+ },
3099
+ capacity,
3100
+ get allDocIdsBitset() {
3101
+ if (!cachedAllDocIdsBitset) {
3102
+ cachedAllDocIdsBitset = bitsetFromSet(this.allDocIds, capacity);
3103
+ }
3104
+ return cachedAllDocIdsBitset;
3234
3105
  }
3235
3106
  };
3236
3107
  }
3237
3108
  function applyPartitionFilters(state, filters, schema) {
3109
+ const context = buildFilterContext(state, schema);
3110
+ const resultBitset = evaluateFilters(filters, context);
3111
+ const resolver = state.docStore.resolver();
3112
+ const externalResult = /* @__PURE__ */ new Set();
3113
+ for (let wi = 0; wi < resultBitset.length; wi++) {
3114
+ let word = resultBitset[wi];
3115
+ if (word === 0) continue;
3116
+ const base = wi << 5;
3117
+ while (word !== 0) {
3118
+ const tz = Math.clz32(word & -word) ^ 31;
3119
+ const internalId = base + tz;
3120
+ const externalId = resolver.toExternal(internalId);
3121
+ if (externalId !== void 0) {
3122
+ externalResult.add(externalId);
3123
+ }
3124
+ word &= word - 1;
3125
+ }
3126
+ }
3127
+ return externalResult;
3128
+ }
3129
+ function applyPartitionFiltersBitset(state, filters, schema) {
3238
3130
  const context = buildFilterContext(state, schema);
3239
3131
  return evaluateFilters(filters, context);
3240
3132
  }
@@ -3304,53 +3196,66 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
3304
3196
  const serializedDocs = /* @__PURE__ */ Object.create(null);
3305
3197
  for (const [docId, stored] of state.docStore.all()) {
3306
3198
  serializedDocs[docId] = {
3307
- fields: structuredClone(stored.fields),
3199
+ fields: stored.fields,
3308
3200
  fieldLengths: { ...stored.fieldLengths }
3309
3201
  };
3310
3202
  }
3311
- const serializedInverted = state.invertedIdx.serialize();
3203
+ const resolver = state.docStore.resolver();
3204
+ const fieldNames = state.fieldNameTable.names;
3312
3205
  const serializedInvertedIndex = /* @__PURE__ */ Object.create(null);
3313
- for (const [token, list] of Object.entries(serializedInverted)) {
3314
- serializedInvertedIndex[token] = {
3315
- docFrequency: list.docFrequency,
3316
- postings: list.postings.map((p) => ({
3317
- docId: p.docId,
3318
- termFrequency: p.termFrequency,
3319
- field: p.fieldName,
3320
- positions: [...p.positions]
3321
- }))
3322
- };
3206
+ for (const token of state.invertedIdx.tokens()) {
3207
+ const list = state.invertedIdx.lookup(token);
3208
+ if (!list) continue;
3209
+ const hasDeleted = list.deletedDocs.size > 0;
3210
+ const postings = [];
3211
+ for (let i = 0; i < list.length; i++) {
3212
+ if (hasDeleted && list.deletedDocs.has(list.docIds[i])) continue;
3213
+ const externalId = resolver.toExternal(list.docIds[i]);
3214
+ if (externalId === void 0) continue;
3215
+ postings.push({
3216
+ docId: externalId,
3217
+ termFrequency: list.termFrequencies[i],
3218
+ field: fieldNames[list.fieldNameIndices[i]],
3219
+ positions: list.positions ? list.positions[i] : []
3220
+ });
3221
+ }
3222
+ if (postings.length > 0) {
3223
+ serializedInvertedIndex[token] = { docFrequency: list.docIdSet.size, postings };
3224
+ }
3323
3225
  }
3324
3226
  const serializedNumeric = {};
3325
3227
  for (const [path, idx] of state.numericIndexes) {
3326
- serializedNumeric[path] = idx.serialize();
3228
+ const raw = idx.serialize();
3229
+ serializedNumeric[path] = raw.map((e) => {
3230
+ const externalId = resolver.toExternal(e.docId);
3231
+ return { value: e.value, docId: externalId ?? "" };
3232
+ });
3327
3233
  }
3328
3234
  const serializedBoolean = {};
3329
3235
  for (const [path, idx] of state.booleanIndexes) {
3330
- serializedBoolean[path] = idx.serialize();
3236
+ const raw = idx.serialize();
3237
+ serializedBoolean[path] = {
3238
+ trueDocs: raw.trueDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== ""),
3239
+ falseDocs: raw.falseDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "")
3240
+ };
3331
3241
  }
3332
3242
  const serializedEnum = {};
3333
3243
  for (const [path, idx] of state.enumIndexes) {
3334
- serializedEnum[path] = idx.serialize();
3244
+ const raw = idx.serialize();
3245
+ const converted = /* @__PURE__ */ Object.create(null);
3246
+ for (const [value, internalIds] of Object.entries(raw)) {
3247
+ converted[value] = internalIds.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "");
3248
+ }
3249
+ serializedEnum[path] = converted;
3335
3250
  }
3336
3251
  const serializedGeo = {};
3337
3252
  for (const [path, idx] of state.geoIndexes) {
3338
- serializedGeo[path] = idx.serialize();
3339
- }
3340
- const serializedVectors = {};
3341
- for (const [path, store] of state.vectorStores) {
3342
- const vectors = [];
3343
- for (const [, entry] of store.entries()) {
3344
- vectors.push({
3345
- docId: entry.docId,
3346
- vector: Array.from(entry.vector)
3347
- });
3348
- }
3349
- serializedVectors[path] = {
3350
- dimension: store.dimension,
3351
- vectors,
3352
- hnswGraph: store.serializeHNSW()
3353
- };
3253
+ const raw = idx.serialize();
3254
+ serializedGeo[path] = raw.map((e) => ({
3255
+ lat: e.lat,
3256
+ lon: e.lon,
3257
+ docId: resolver.toExternal(e.docId) ?? ""
3258
+ }));
3354
3259
  }
3355
3260
  const serializedStats = state.stats.serialize();
3356
3261
  return {
@@ -3369,7 +3274,6 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
3369
3274
  enum: serializedEnum,
3370
3275
  geopoint: serializedGeo
3371
3276
  },
3372
- vectorData: serializedVectors,
3373
3277
  statistics: serializedStats
3374
3278
  };
3375
3279
  }
@@ -3382,10 +3286,11 @@ function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalP
3382
3286
  const wireDocs = /* @__PURE__ */ Object.create(null);
3383
3287
  for (const [docId, stored] of state.docStore.all()) {
3384
3288
  wireDocs[docId] = {
3385
- fields: structuredClone(stored.fields),
3289
+ fields: stored.fields,
3386
3290
  field_lengths: { ...stored.fieldLengths }
3387
3291
  };
3388
3292
  }
3293
+ const resolver = state.docStore.resolver();
3389
3294
  const fieldNames = [...state.fieldNameTable.names];
3390
3295
  const wireEntries = /* @__PURE__ */ Object.create(null);
3391
3296
  for (const token of state.invertedIdx.tokens()) {
@@ -3394,57 +3299,57 @@ function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalP
3394
3299
  const hasDeleted = list.deletedDocs.size > 0;
3395
3300
  const ids = [];
3396
3301
  const tf = [];
3397
- const fi = [];
3302
+ const fi = new Uint8Array(list.length);
3398
3303
  const pos = list.positions ? [] : null;
3304
+ let writeIdx = 0;
3399
3305
  for (let i = 0; i < list.length; i++) {
3400
3306
  if (hasDeleted && list.deletedDocs.has(list.docIds[i])) continue;
3401
- ids.push(list.docIds[i]);
3307
+ const externalId = resolver.toExternal(list.docIds[i]);
3308
+ if (externalId === void 0) continue;
3309
+ ids.push(externalId);
3402
3310
  tf.push(list.termFrequencies[i]);
3403
- fi.push(list.fieldNameIndices[i]);
3311
+ fi[writeIdx] = list.fieldNameIndices[i];
3312
+ writeIdx++;
3404
3313
  if (pos && list.positions) {
3405
3314
  pos.push(list.positions[i]);
3406
3315
  }
3407
3316
  }
3408
3317
  if (ids.length > 0) {
3409
- wireEntries[token] = { df: list.docIdSet.size, ids, tf, fi, pos };
3318
+ const finalFi = writeIdx < list.length ? fi.subarray(0, writeIdx) : fi;
3319
+ wireEntries[token] = { df: list.docIdSet.size, ids, tf, fi: finalFi, pos };
3410
3320
  }
3411
3321
  }
3412
3322
  const wireNumeric = {};
3413
3323
  for (const [path, idx] of state.numericIndexes) {
3414
- wireNumeric[path] = idx.serialize().map((e) => ({ value: e.value, doc_id: e.docId }));
3324
+ wireNumeric[path] = idx.serialize().map((e) => ({
3325
+ value: e.value,
3326
+ doc_id: resolver.toExternal(e.docId) ?? ""
3327
+ }));
3415
3328
  }
3416
3329
  const wireBoolean = {};
3417
3330
  for (const [path, idx] of state.booleanIndexes) {
3418
3331
  const s = idx.serialize();
3419
- wireBoolean[path] = { true_docs: s.trueDocs, false_docs: s.falseDocs };
3332
+ wireBoolean[path] = {
3333
+ true_docs: s.trueDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== ""),
3334
+ false_docs: s.falseDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "")
3335
+ };
3420
3336
  }
3421
3337
  const wireEnum = {};
3422
3338
  for (const [path, idx] of state.enumIndexes) {
3423
- wireEnum[path] = idx.serialize();
3339
+ const raw = idx.serialize();
3340
+ const converted = /* @__PURE__ */ Object.create(null);
3341
+ for (const [value, internalIds] of Object.entries(raw)) {
3342
+ converted[value] = internalIds.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "");
3343
+ }
3344
+ wireEnum[path] = converted;
3424
3345
  }
3425
3346
  const wireGeo = {};
3426
3347
  for (const [path, idx] of state.geoIndexes) {
3427
- wireGeo[path] = idx.serialize().map((e) => ({ lat: e.lat, lon: e.lon, doc_id: e.docId }));
3428
- }
3429
- const wireVectors = {};
3430
- for (const [path, store] of state.vectorStores) {
3431
- const vectors = [];
3432
- for (const [, entry] of store.entries()) {
3433
- vectors.push({ doc_id: entry.docId, vector: Array.from(entry.vector) });
3434
- }
3435
- const hnswData = store.serializeHNSW();
3436
- wireVectors[path] = {
3437
- dimension: store.dimension,
3438
- vectors,
3439
- hnsw_graph: hnswData ? {
3440
- entry_point: hnswData.entryPoint,
3441
- max_layer: hnswData.maxLayer,
3442
- m: hnswData.m,
3443
- ef_construction: hnswData.efConstruction,
3444
- metric: hnswData.metric,
3445
- nodes: hnswData.nodes
3446
- } : null
3447
- };
3348
+ wireGeo[path] = idx.serialize().map((e) => ({
3349
+ lat: e.lat,
3350
+ lon: e.lon,
3351
+ doc_id: resolver.toExternal(e.docId) ?? ""
3352
+ }));
3448
3353
  }
3449
3354
  const serializedStats = state.stats.serialize();
3450
3355
  return {
@@ -3467,7 +3372,6 @@ function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalP
3467
3372
  enum: wireEnum,
3468
3373
  geopoint: wireGeo
3469
3374
  },
3470
- vector_data: wireVectors,
3471
3375
  statistics: {
3472
3376
  total_documents: serializedStats.totalDocuments,
3473
3377
  total_field_lengths: serializedStats.totalFieldLengths,
@@ -3478,6 +3382,12 @@ function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalP
3478
3382
  }
3479
3383
  function deserializePartition(state, data, clearFn, schema) {
3480
3384
  clearFn();
3385
+ const docsData = /* @__PURE__ */ Object.create(null);
3386
+ for (const [docId, doc] of Object.entries(data.documents)) {
3387
+ docsData[docId] = { fields: doc.fields, fieldLengths: doc.fieldLengths };
3388
+ }
3389
+ state.docStore.deserialize(docsData);
3390
+ const resolver = state.docStore.resolver();
3481
3391
  const invertedData = /* @__PURE__ */ Object.create(null);
3482
3392
  for (const [token, list] of Object.entries(data.invertedIndex)) {
3483
3393
  invertedData[token] = {
@@ -3490,42 +3400,42 @@ function deserializePartition(state, data, clearFn, schema) {
3490
3400
  }))
3491
3401
  };
3492
3402
  }
3493
- state.invertedIdx.deserialize(invertedData);
3494
- const docsData = /* @__PURE__ */ Object.create(null);
3495
- for (const [docId, doc] of Object.entries(data.documents)) {
3496
- docsData[docId] = { fields: doc.fields, fieldLengths: doc.fieldLengths };
3497
- }
3498
- state.docStore.deserialize(docsData);
3403
+ state.invertedIdx.deserialize(invertedData, resolver);
3499
3404
  for (const [path, entries] of Object.entries(data.fieldIndexes.numeric)) {
3500
3405
  const idx = createNumericIndex();
3501
- idx.deserialize(entries);
3406
+ const converted = entries.map((e) => {
3407
+ const internalId = resolver.toInternal(e.docId);
3408
+ return { value: e.value, docId: internalId ?? -1 };
3409
+ }).filter((e) => e.docId !== -1);
3410
+ idx.deserialize(converted);
3502
3411
  state.numericIndexes.set(path, idx);
3503
3412
  }
3504
3413
  for (const [path, serialized] of Object.entries(data.fieldIndexes.boolean)) {
3505
3414
  const idx = createBooleanIndex();
3506
- idx.deserialize(serialized);
3415
+ const trueDocs = serialized.trueDocs.map((id) => resolver.toInternal(id)).filter((id) => id !== void 0);
3416
+ const falseDocs = serialized.falseDocs.map((id) => resolver.toInternal(id)).filter((id) => id !== void 0);
3417
+ idx.deserialize({ trueDocs, falseDocs });
3507
3418
  state.booleanIndexes.set(path, idx);
3508
3419
  }
3509
3420
  for (const [path, serialized] of Object.entries(data.fieldIndexes.enum)) {
3510
3421
  const idx = createEnumIndex();
3511
- idx.deserialize(serialized);
3422
+ const converted = /* @__PURE__ */ Object.create(null);
3423
+ for (const [value, docIds] of Object.entries(serialized)) {
3424
+ converted[value] = docIds.map((id) => resolver.toInternal(id)).filter((id) => id !== void 0);
3425
+ }
3426
+ idx.deserialize(converted);
3512
3427
  state.enumIndexes.set(path, idx);
3513
3428
  }
3514
3429
  for (const [path, entries] of Object.entries(data.fieldIndexes.geopoint)) {
3515
3430
  const geoIdx = createGeoIndex();
3516
- geoIdx.deserialize(entries);
3431
+ const converted = entries.map((e) => ({
3432
+ lat: e.lat,
3433
+ lon: e.lon,
3434
+ docId: resolver.toInternal(e.docId) ?? -1
3435
+ })).filter((e) => e.docId !== -1);
3436
+ geoIdx.deserialize(converted);
3517
3437
  state.geoIndexes.set(path, geoIdx);
3518
3438
  }
3519
- for (const [path, vecData] of Object.entries(data.vectorData)) {
3520
- const store = createVectorSearchEngine(vecData.dimension);
3521
- for (const entry of vecData.vectors) {
3522
- store.insert(entry.docId, new Float32Array(entry.vector));
3523
- }
3524
- if (vecData.hnswGraph) {
3525
- store.deserializeHNSW(vecData.hnswGraph);
3526
- }
3527
- state.vectorStores.set(path, store);
3528
- }
3529
3439
  state.stats.deserialize(data.statistics);
3530
3440
  const flatSchema = getFlatSchema(state, schema);
3531
3441
  const serializedFields = data.schema;
@@ -3544,12 +3454,7 @@ function deserializePartition(state, data, clearFn, schema) {
3544
3454
  }
3545
3455
 
3546
3456
  // src/core/partition/index.ts
3547
- function createPartitionIndex(partitionId, trackPositions = true, vectorPromotionConfig) {
3548
- const vectorPromoter = vectorPromotionConfig ? createVectorPromoter({
3549
- promotionThreshold: vectorPromotionConfig.threshold,
3550
- hnswConfig: vectorPromotionConfig.hnswConfig,
3551
- workerStrategy: vectorPromotionConfig.workerStrategy
3552
- }) : null;
3457
+ function createPartitionIndex(partitionId, trackPositions = true) {
3553
3458
  const fieldNameTable = { names: [], indexMap: /* @__PURE__ */ new Map() };
3554
3459
  const state = {
3555
3460
  invertedIdx: createInvertedIndex(fieldNameTable),
@@ -3559,26 +3464,22 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3559
3464
  booleanIndexes: /* @__PURE__ */ new Map(),
3560
3465
  enumIndexes: /* @__PURE__ */ new Map(),
3561
3466
  geoIndexes: /* @__PURE__ */ new Map(),
3562
- vectorStores: /* @__PURE__ */ new Map(),
3563
3467
  fieldNameTable,
3564
3468
  flatSchemaCache: null,
3565
3469
  lastSchemaRef: null,
3566
3470
  trackPositions
3567
3471
  };
3568
3472
  function clearAll() {
3569
- vectorPromoter?.shutdown();
3570
3473
  state.invertedIdx.clear();
3571
3474
  state.docStore.clear();
3572
3475
  for (const idx of state.numericIndexes.values()) idx.clear();
3573
3476
  for (const idx of state.booleanIndexes.values()) idx.clear();
3574
3477
  for (const idx of state.enumIndexes.values()) idx.clear();
3575
3478
  for (const idx of state.geoIndexes.values()) idx.clear();
3576
- for (const store of state.vectorStores.values()) store.clear();
3577
3479
  state.numericIndexes.clear();
3578
3480
  state.booleanIndexes.clear();
3579
3481
  state.enumIndexes.clear();
3580
3482
  state.geoIndexes.clear();
3581
- state.vectorStores.clear();
3582
3483
  state.stats.deserialize({ totalDocuments: 0, totalFieldLengths: {}, averageFieldLengths: {}, docFrequencies: {} });
3583
3484
  state.flatSchemaCache = null;
3584
3485
  state.lastSchemaRef = null;
@@ -3604,6 +3505,7 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3604
3505
  validateDocumentStrict(document, schema);
3605
3506
  }
3606
3507
  }
3508
+ state.docStore.ensureInternalId(docId);
3607
3509
  const flatSchema = getFlatSchema(state, schema);
3608
3510
  const { fieldLengths, tokensByField } = indexDocument(
3609
3511
  state,
@@ -3619,7 +3521,6 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3619
3521
  state.docStore.store(docId, document, fieldLengths);
3620
3522
  }
3621
3523
  state.stats.addDocument(fieldLengths, tokensByField);
3622
- vectorPromoter?.check(state.vectorStores);
3623
3524
  },
3624
3525
  remove(docId, schema, language, options) {
3625
3526
  const stored = state.docStore.get(docId);
@@ -3634,6 +3535,12 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3634
3535
  state.docStore.remove(docId);
3635
3536
  state.stats.removeDocument(fieldLengths, tokensByField);
3636
3537
  },
3538
+ beginBatch() {
3539
+ state.invertedIdx.beginBatch();
3540
+ },
3541
+ endBatch() {
3542
+ state.invertedIdx.endBatch();
3543
+ },
3637
3544
  update(docId, document, schema, language, options) {
3638
3545
  const stored = state.docStore.get(docId);
3639
3546
  if (!stored) {
@@ -3661,6 +3568,7 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3661
3568
  );
3662
3569
  state.stats.removeDocument(oldFieldLengths, oldTokens);
3663
3570
  state.docStore.remove(docId);
3571
+ state.docStore.ensureInternalId(docId);
3664
3572
  const { fieldLengths: newFieldLengths, tokensByField: newTokens } = indexDocument(
3665
3573
  state,
3666
3574
  docId,
@@ -3719,29 +3627,17 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3719
3627
  bytes += docCount * state.booleanIndexes.size * FIELD_ENTRY_OVERHEAD;
3720
3628
  bytes += docCount * state.enumIndexes.size * FIELD_ENTRY_OVERHEAD;
3721
3629
  bytes += docCount * state.geoIndexes.size * FIELD_ENTRY_OVERHEAD;
3722
- for (const store of state.vectorStores.values()) {
3723
- bytes += store.estimateMemoryBytes();
3724
- }
3725
3630
  return bytes;
3726
3631
  },
3727
- vectorFieldCount() {
3728
- return state.vectorStores.size;
3729
- },
3730
- hasPromotedHnsw() {
3731
- for (const store of state.vectorStores.values()) {
3732
- if (store.isPromoted) return true;
3733
- }
3734
- return false;
3735
- },
3736
3632
  searchFulltext(params) {
3737
3633
  return searchFulltext(state, params);
3738
3634
  },
3739
- searchVector(params) {
3740
- return searchVector(state, params);
3741
- },
3742
3635
  applyFilters(filters, schema) {
3743
3636
  return applyPartitionFilters(state, filters, schema);
3744
3637
  },
3638
+ applyFiltersBitset(filters, schema) {
3639
+ return applyPartitionFiltersBitset(state, filters, schema);
3640
+ },
3745
3641
  computeFacets(docIds, config, schema) {
3746
3642
  return computeFacets(state, docIds, config, schema);
3747
3643
  },
@@ -3763,14 +3659,31 @@ function createPartitionIndex(partitionId, trackPositions = true, vectorPromotio
3763
3659
  }
3764
3660
 
3765
3661
  // src/partitioning/manager.ts
3766
- function createPartitionManager(indexName, config, language, router, initialPartitionCount) {
3662
+ function setNestedValue(obj, path, value) {
3663
+ if (!path.includes(".")) {
3664
+ obj[path] = value;
3665
+ return;
3666
+ }
3667
+ const segments = path.split(".");
3668
+ let current = obj;
3669
+ for (let i = 0; i < segments.length - 1; i++) {
3670
+ let next = current[segments[i]];
3671
+ if (next === null || next === void 0 || typeof next !== "object") {
3672
+ next = {};
3673
+ current[segments[i]] = next;
3674
+ }
3675
+ current = next;
3676
+ }
3677
+ current[segments[segments.length - 1]] = value;
3678
+ }
3679
+ function createPartitionManager(indexName, config, language, router, initialPartitionCount, vectorIndexes) {
3767
3680
  const count = initialPartitionCount ?? 1;
3768
3681
  const trackPositions = config.trackPositions ?? true;
3769
- const vectorPromotionConfig = config.vectorPromotion;
3682
+ const vecIndexes = vectorIndexes ?? /* @__PURE__ */ new Map();
3770
3683
  let partitions = [];
3771
3684
  const docPartitionMap = /* @__PURE__ */ new Map();
3772
3685
  for (let i = 0; i < count; i++) {
3773
- partitions.push(createPartitionIndex(i, trackPositions, vectorPromotionConfig));
3686
+ partitions.push(createPartitionIndex(i, trackPositions));
3774
3687
  }
3775
3688
  function validatePartitionId(partitionId) {
3776
3689
  if (partitionId < 0 || partitionId >= partitions.length) {
@@ -3838,7 +3751,7 @@ function createPartitionManager(indexName, config, language, router, initialPart
3838
3751
  { maxPartitions: currentMaxPartitions, currentCount: partitions.length }
3839
3752
  );
3840
3753
  }
3841
- const partition = createPartitionIndex(partitions.length, trackPositions, vectorPromotionConfig);
3754
+ const partition = createPartitionIndex(partitions.length, trackPositions);
3842
3755
  partitions.push(partition);
3843
3756
  return partition;
3844
3757
  },
@@ -3878,6 +3791,16 @@ function createPartitionManager(indexName, config, language, router, initialPart
3878
3791
  partitions[pid].remove(docId, config.schema, language);
3879
3792
  docPartitionMap.delete(docId);
3880
3793
  },
3794
+ beginBatchRemove() {
3795
+ for (let i = 0; i < partitions.length; i++) {
3796
+ partitions[i].beginBatch();
3797
+ }
3798
+ },
3799
+ endBatchRemove() {
3800
+ for (let i = 0; i < partitions.length; i++) {
3801
+ partitions[i].endBatch();
3802
+ }
3803
+ },
3881
3804
  update(docId, document, options) {
3882
3805
  const pid = docPartitionMap.get(docId);
3883
3806
  if (pid === void 0) {
@@ -3889,7 +3812,15 @@ function createPartitionManager(indexName, config, language, router, initialPart
3889
3812
  get(docId) {
3890
3813
  const pid = docPartitionMap.get(docId);
3891
3814
  if (pid === void 0) return void 0;
3892
- return partitions[pid].get(docId);
3815
+ const doc = partitions[pid].get(docId);
3816
+ if (!doc) return void 0;
3817
+ for (const [fieldPath, vecIndex] of vecIndexes) {
3818
+ const vector = vecIndex.getVector(docId);
3819
+ if (vector) {
3820
+ setNestedValue(doc, fieldPath, vector);
3821
+ }
3822
+ }
3823
+ return doc;
3893
3824
  },
3894
3825
  getRef(docId) {
3895
3826
  const pid = docPartitionMap.get(docId);
@@ -3936,16 +3867,26 @@ function createPartitionManager(indexName, config, language, router, initialPart
3936
3867
  for (let i = 0; i < partitions.length; i++) {
3937
3868
  total += partitions[i].estimateMemoryBytes();
3938
3869
  }
3870
+ for (const [, vecIndex] of vecIndexes) {
3871
+ total += vecIndex.estimateMemoryBytes();
3872
+ }
3939
3873
  return total;
3940
3874
  },
3941
3875
  getPartitionStats() {
3942
3876
  return partitions.map((p, i) => ({
3943
3877
  partitionId: i,
3944
3878
  documentCount: p.count(),
3945
- estimatedMemoryBytes: p.estimateMemoryBytes(),
3946
- vectorFieldCount: p.vectorFieldCount(),
3947
- isHnswPromoted: p.hasPromotedHnsw()
3879
+ estimatedMemoryBytes: p.estimateMemoryBytes()
3948
3880
  }));
3881
+ },
3882
+ getVectorIndexes() {
3883
+ return vecIndexes;
3884
+ },
3885
+ resetVectorIndexes(newIndexes) {
3886
+ vecIndexes.clear();
3887
+ for (const [key, value] of newIndexes) {
3888
+ vecIndexes.set(key, value);
3889
+ }
3949
3890
  }
3950
3891
  };
3951
3892
  return manager;
@@ -3993,6 +3934,957 @@ function createPartitionRouter() {
3993
3934
  return { route, routeBatch };
3994
3935
  }
3995
3936
 
3937
+ // src/runtime/detect.ts
3938
+ function detectRuntimeName() {
3939
+ if ("Bun" in globalThis) {
3940
+ return "bun";
3941
+ }
3942
+ if ("Deno" in globalThis) {
3943
+ return "deno";
3944
+ }
3945
+ const proc = globalThis.process;
3946
+ if (typeof proc === "object" && proc !== null && typeof proc.versions === "object" && typeof proc.versions?.node === "string") {
3947
+ return "node";
3948
+ }
3949
+ return "browser";
3950
+ }
3951
+ function getCpuCount() {
3952
+ const nav = globalThis.navigator;
3953
+ if (nav && typeof nav.hardwareConcurrency === "number" && nav.hardwareConcurrency >= 1) {
3954
+ return nav.hardwareConcurrency;
3955
+ }
3956
+ return 1;
3957
+ }
3958
+ function detectRuntime() {
3959
+ const runtime = detectRuntimeName();
3960
+ return {
3961
+ runtime,
3962
+ cpuCount: getCpuCount(),
3963
+ supportsWorkerThreads: runtime === "node" || runtime === "bun",
3964
+ supportsWebWorkers: runtime === "browser" || runtime === "deno" || runtime === "bun",
3965
+ supportsFileSystem: runtime === "node" || runtime === "bun" || runtime === "deno",
3966
+ supportsIndexedDB: runtime === "browser" || "indexedDB" in globalThis,
3967
+ supportsBroadcastChannel: "BroadcastChannel" in globalThis
3968
+ };
3969
+ }
3970
+
3971
+ // src/vector/hnsw-worker-dispatch.ts
3972
+ var BUILD_TIMEOUT_MS = 12e4;
3973
+ function resolveWorkerEntryPoint() {
3974
+ const base = import.meta.url;
3975
+ const distIndex = base.lastIndexOf("/dist/");
3976
+ if (distIndex !== -1) {
3977
+ return new URL("vector/hnsw-build-worker.mjs", base.slice(0, distIndex + 6)).href;
3978
+ }
3979
+ return base.replace(/\/src\/vector\/[^/]+$/, "/dist/vector/hnsw-build-worker.mjs");
3980
+ }
3981
+ async function spawnWorker() {
3982
+ const runtime = detectRuntime();
3983
+ const entryPoint = resolveWorkerEntryPoint();
3984
+ if (runtime.supportsWorkerThreads) {
3985
+ try {
3986
+ const workerThreads = await import('worker_threads');
3987
+ const instance = new workerThreads.Worker(new URL(entryPoint));
3988
+ return instance;
3989
+ } catch {
3990
+ return null;
3991
+ }
3992
+ }
3993
+ if (runtime.supportsWebWorkers) {
3994
+ try {
3995
+ const WorkerCtor = globalThis.Worker;
3996
+ if (typeof WorkerCtor !== "function") return null;
3997
+ return new WorkerCtor(entryPoint, { type: "module" });
3998
+ } catch {
3999
+ return null;
4000
+ }
4001
+ }
4002
+ return null;
4003
+ }
4004
+ function listenForMessage(worker, handler) {
4005
+ if (typeof worker.on === "function") {
4006
+ worker.on("message", handler);
4007
+ return () => {
4008
+ };
4009
+ }
4010
+ if (typeof worker.addEventListener === "function") {
4011
+ const wrappedHandler = (event) => {
4012
+ handler(event.data);
4013
+ };
4014
+ worker.addEventListener("message", wrappedHandler);
4015
+ return () => {
4016
+ worker.removeEventListener?.("message", wrappedHandler);
4017
+ };
4018
+ }
4019
+ return () => {
4020
+ };
4021
+ }
4022
+ async function dispatchWorkerBuild(docIds, vectorData, dimension, config, timeoutMs, skipCopy = false) {
4023
+ let worker;
4024
+ try {
4025
+ worker = await spawnWorker();
4026
+ } catch {
4027
+ return {
4028
+ ok: false,
4029
+ reason: "spawn-error",
4030
+ message: "Failed to spawn HNSW build worker"
4031
+ };
4032
+ }
4033
+ if (worker === null) {
4034
+ return {
4035
+ ok: false,
4036
+ reason: "no-workers",
4037
+ message: "No worker thread support available in the current runtime"
4038
+ };
4039
+ }
4040
+ const effectiveTimeout = timeoutMs ?? BUILD_TIMEOUT_MS;
4041
+ let transferBuffer;
4042
+ if (skipCopy) {
4043
+ transferBuffer = vectorData;
4044
+ } else {
4045
+ transferBuffer = new Float32Array(vectorData.length);
4046
+ transferBuffer.set(vectorData);
4047
+ }
4048
+ const request = {
4049
+ type: "build-binary",
4050
+ docIds,
4051
+ vectorData: transferBuffer,
4052
+ dimension,
4053
+ config
4054
+ };
4055
+ return new Promise((resolve) => {
4056
+ let settled = false;
4057
+ const timeoutId = setTimeout(() => {
4058
+ if (settled) return;
4059
+ settled = true;
4060
+ cleanup();
4061
+ resolve({
4062
+ ok: false,
4063
+ reason: "timeout",
4064
+ message: `HNSW build timed out after ${effectiveTimeout}ms`
4065
+ });
4066
+ }, effectiveTimeout);
4067
+ function cleanup() {
4068
+ clearTimeout(timeoutId);
4069
+ removeListener();
4070
+ try {
4071
+ worker?.terminate();
4072
+ } catch {
4073
+ }
4074
+ }
4075
+ function handleMessage(msg) {
4076
+ if (settled) return;
4077
+ const response = msg;
4078
+ if (response.type === "success") {
4079
+ settled = true;
4080
+ cleanup();
4081
+ resolve({ ok: true, graph: response.graph });
4082
+ return;
4083
+ }
4084
+ if (response.type === "error") {
4085
+ settled = true;
4086
+ cleanup();
4087
+ resolve({
4088
+ ok: false,
4089
+ reason: "build-error",
4090
+ message: response.message
4091
+ });
4092
+ }
4093
+ }
4094
+ const removeListener = listenForMessage(worker, handleMessage);
4095
+ try {
4096
+ worker.postMessage(request, [transferBuffer.buffer]);
4097
+ } catch (err) {
4098
+ if (settled) return;
4099
+ settled = true;
4100
+ cleanup();
4101
+ resolve({
4102
+ ok: false,
4103
+ reason: "spawn-error",
4104
+ message: err instanceof Error ? err.message : "Failed to post message to worker"
4105
+ });
4106
+ }
4107
+ });
4108
+ }
4109
+
4110
+ // src/vector/scalar-quantization.ts
4111
+ var PADDING_FACTOR = 0.01;
4112
+ function createScalarQuantizer(dimensions) {
4113
+ const quantizedVectors = /* @__PURE__ */ new Map();
4114
+ const vectorSums = /* @__PURE__ */ new Map();
4115
+ const vectorSumSqs = /* @__PURE__ */ new Map();
4116
+ const dequantizedMagnitudes = /* @__PURE__ */ new Map();
4117
+ let alpha = 0;
4118
+ let offset = 0;
4119
+ let alphaSquared = 0;
4120
+ let alphaTimesOffset = 0;
4121
+ let dTimesOffsetSquared = 0;
4122
+ let invAlpha = 0;
4123
+ let calibrated = false;
4124
+ function updateDerivedConstants() {
4125
+ alphaSquared = alpha * alpha;
4126
+ alphaTimesOffset = alpha * offset;
4127
+ dTimesOffsetSquared = dimensions * offset * offset;
4128
+ invAlpha = alpha > 0 ? 1 / alpha : 0;
4129
+ }
4130
+ function computeMagnitude(sumSq, sum) {
4131
+ const val = alphaSquared * sumSq + 2 * alphaTimesOffset * sum + dTimesOffsetSquared;
4132
+ return val > 0 ? Math.sqrt(val) : 0;
4133
+ }
4134
+ function quantizeVector(vector) {
4135
+ const result = new Uint8Array(dimensions);
4136
+ for (let d = 0; d < dimensions; d++) {
4137
+ const normalized = (vector[d] - offset) * invAlpha;
4138
+ const scaled = normalized + 0.5;
4139
+ result[d] = scaled < 0 ? 0 : scaled > 255 ? 255 : scaled | 0;
4140
+ }
4141
+ return result;
4142
+ }
4143
+ function computeSumAndSumSq(quantized) {
4144
+ let sum = 0;
4145
+ let sumSq = 0;
4146
+ for (let d = 0; d < dimensions; d++) {
4147
+ const val = quantized[d];
4148
+ sum += val;
4149
+ sumSq += val * val;
4150
+ }
4151
+ return { sum, sumSq };
4152
+ }
4153
+ function storeQuantized(docId, quantized, sum, sumSq) {
4154
+ quantizedVectors.set(docId, quantized);
4155
+ vectorSums.set(docId, sum);
4156
+ vectorSumSqs.set(docId, sumSq);
4157
+ dequantizedMagnitudes.set(docId, computeMagnitude(sumSq, sum));
4158
+ }
4159
+ function calibrateFromVectors(vectors) {
4160
+ let globalMin = Number.POSITIVE_INFINITY;
4161
+ let globalMax = Number.NEGATIVE_INFINITY;
4162
+ let count = 0;
4163
+ for (const vec of vectors) {
4164
+ for (let d = 0; d < dimensions; d++) {
4165
+ const val = vec[d];
4166
+ if (val < globalMin) globalMin = val;
4167
+ if (val > globalMax) globalMax = val;
4168
+ }
4169
+ count++;
4170
+ }
4171
+ if (count === 0) return;
4172
+ const range = globalMax - globalMin;
4173
+ const pad = range * PADDING_FACTOR;
4174
+ globalMin -= pad;
4175
+ globalMax += pad;
4176
+ if (globalMin === globalMax) {
4177
+ globalMin -= 1e-3;
4178
+ globalMax += 1e-3;
4179
+ }
4180
+ alpha = (globalMax - globalMin) / 255;
4181
+ offset = globalMin;
4182
+ updateDerivedConstants();
4183
+ calibrated = true;
4184
+ }
4185
+ function isOutsideBounds(vector) {
4186
+ const currentMin = offset;
4187
+ const currentMax = offset + alpha * 255;
4188
+ for (let d = 0; d < dimensions; d++) {
4189
+ if (vector[d] < currentMin || vector[d] > currentMax) {
4190
+ return true;
4191
+ }
4192
+ }
4193
+ return false;
4194
+ }
4195
+ return {
4196
+ get dimensions() {
4197
+ return dimensions;
4198
+ },
4199
+ get size() {
4200
+ return quantizedVectors.size;
4201
+ },
4202
+ isCalibrated() {
4203
+ return calibrated;
4204
+ },
4205
+ calibrate(vectors) {
4206
+ calibrateFromVectors(vectors);
4207
+ },
4208
+ needsRecalibration(vector) {
4209
+ if (!calibrated) return false;
4210
+ return isOutsideBounds(vector);
4211
+ },
4212
+ recalibrateAll(vectors) {
4213
+ const collected = [];
4214
+ const rawVectors = [];
4215
+ for (const pair of vectors) {
4216
+ collected.push(pair);
4217
+ rawVectors.push(pair[1]);
4218
+ }
4219
+ calibrateFromVectors(rawVectors);
4220
+ quantizedVectors.clear();
4221
+ vectorSums.clear();
4222
+ vectorSumSqs.clear();
4223
+ dequantizedMagnitudes.clear();
4224
+ for (const [docId, vec] of collected) {
4225
+ const quantized = quantizeVector(vec);
4226
+ const { sum, sumSq } = computeSumAndSumSq(quantized);
4227
+ storeQuantized(docId, quantized, sum, sumSq);
4228
+ }
4229
+ },
4230
+ quantize(docId, vector) {
4231
+ if (!calibrated) {
4232
+ calibrateFromVectors([vector]);
4233
+ }
4234
+ const quantized = quantizeVector(vector);
4235
+ const { sum, sumSq } = computeSumAndSumSq(quantized);
4236
+ storeQuantized(docId, quantized, sum, sumSq);
4237
+ },
4238
+ remove(docId) {
4239
+ quantizedVectors.delete(docId);
4240
+ vectorSums.delete(docId);
4241
+ vectorSumSqs.delete(docId);
4242
+ dequantizedMagnitudes.delete(docId);
4243
+ },
4244
+ getQuantized(docId) {
4245
+ return quantizedVectors.get(docId);
4246
+ },
4247
+ prepareQuery(query) {
4248
+ if (!calibrated) return null;
4249
+ const quantized = quantizeVector(query);
4250
+ const { sum, sumSq } = computeSumAndSumSq(quantized);
4251
+ const mag = computeMagnitude(sumSq, sum);
4252
+ return { quantized, sum, sumSq, magnitude: mag };
4253
+ },
4254
+ distanceFromPrepared(prepared, docId, metric) {
4255
+ const vecQ = quantizedVectors.get(docId);
4256
+ if (!vecQ) return Number.POSITIVE_INFINITY;
4257
+ const vecSum = vectorSums.get(docId);
4258
+ const vecSumSq = vectorSumSqs.get(docId);
4259
+ if (vecSum === void 0 || vecSumSq === void 0) return Number.POSITIVE_INFINITY;
4260
+ const qQ = prepared.quantized;
4261
+ const dims = dimensions;
4262
+ if (metric === "euclidean") {
4263
+ let intSqDist = 0;
4264
+ for (let d = 0; d < dims; d++) {
4265
+ const diff = qQ[d] - vecQ[d];
4266
+ intSqDist += diff * diff;
4267
+ }
4268
+ return alpha * Math.sqrt(intSqDist);
4269
+ }
4270
+ let intDot = 0;
4271
+ for (let d = 0; d < dims; d++) {
4272
+ intDot += qQ[d] * vecQ[d];
4273
+ }
4274
+ const realDot = alphaSquared * intDot + alphaTimesOffset * (prepared.sum + vecSum) + dTimesOffsetSquared;
4275
+ if (metric === "dotProduct") {
4276
+ return -realDot;
4277
+ }
4278
+ const vecMag = dequantizedMagnitudes.get(docId);
4279
+ if (!vecMag || vecMag === 0 || prepared.magnitude === 0) return 1;
4280
+ return 1 - realDot / (prepared.magnitude * vecMag);
4281
+ },
4282
+ restoreCalibration(restoredAlpha, restoredOffset) {
4283
+ alpha = restoredAlpha;
4284
+ offset = restoredOffset;
4285
+ updateDerivedConstants();
4286
+ calibrated = true;
4287
+ },
4288
+ restoreEntry(docId, quantized, sum, sumSq) {
4289
+ storeQuantized(docId, quantized, sum, sumSq);
4290
+ },
4291
+ serialize() {
4292
+ const serializedVectors = {};
4293
+ const serializedSums = {};
4294
+ const serializedSumSqs = {};
4295
+ for (const [docId, qv] of quantizedVectors) {
4296
+ serializedVectors[docId] = Array.from(qv);
4297
+ const sum = vectorSums.get(docId);
4298
+ const sumSq = vectorSumSqs.get(docId);
4299
+ if (sum !== void 0) serializedSums[docId] = sum;
4300
+ if (sumSq !== void 0) serializedSumSqs[docId] = sumSq;
4301
+ }
4302
+ return {
4303
+ alpha,
4304
+ offset,
4305
+ quantizedVectors: serializedVectors,
4306
+ vectorSums: serializedSums,
4307
+ vectorSumSqs: serializedSumSqs
4308
+ };
4309
+ },
4310
+ clear() {
4311
+ quantizedVectors.clear();
4312
+ vectorSums.clear();
4313
+ vectorSumSqs.clear();
4314
+ dequantizedMagnitudes.clear();
4315
+ alpha = 0;
4316
+ offset = 0;
4317
+ alphaSquared = 0;
4318
+ alphaTimesOffset = 0;
4319
+ dTimesOffsetSquared = 0;
4320
+ invAlpha = 0;
4321
+ calibrated = false;
4322
+ }
4323
+ };
4324
+ }
4325
+ function deserializeScalarQuantizer(data, dimensions) {
4326
+ const quantizer = createScalarQuantizer(dimensions);
4327
+ if (data.alpha === 0 && data.offset === 0 && Object.keys(data.quantizedVectors).length === 0) {
4328
+ return quantizer;
4329
+ }
4330
+ quantizer.restoreCalibration(data.alpha, data.offset);
4331
+ for (const [docId, values] of Object.entries(data.quantizedVectors)) {
4332
+ const quantized = new Uint8Array(values);
4333
+ const sum = data.vectorSums[docId] ?? 0;
4334
+ const sumSq = data.vectorSumSqs[docId] ?? 0;
4335
+ quantizer.restoreEntry(docId, quantized, sum, sumSq);
4336
+ }
4337
+ return quantizer;
4338
+ }
4339
+
4340
+ // src/vector/vector-index.ts
4341
+ var DEFAULT_PROMOTION_THRESHOLD = 1024;
4342
+ var DEFAULT_FILTER_THRESHOLD = 0.03;
4343
+ var ESTIMATED_MS_PER_TOMBSTONE = 0.05;
4344
+ var ESTIMATED_MS_PER_VECTOR_REBUILD = 0.15;
4345
+ var WORKER_BUILD_SIZE_THRESHOLD = 5e3;
4346
+ function createVectorIndex(fieldName, dimension, config) {
4347
+ if (!Number.isInteger(dimension) || dimension <= 0) {
4348
+ throw new NarsilError(
4349
+ ErrorCodes.VECTOR_DIMENSION_MISMATCH,
4350
+ `Vector dimension must be a positive integer, got ${dimension}`,
4351
+ { dimension }
4352
+ );
4353
+ }
4354
+ const promotionThreshold = config?.threshold ?? DEFAULT_PROMOTION_THRESHOLD;
4355
+ const filterThreshold = Math.max(0, Math.min(1, config?.filterThreshold ?? DEFAULT_FILTER_THRESHOLD));
4356
+ const quantizationMode = config?.quantization ?? "sq8";
4357
+ const rawHnswM = config?.hnswConfig?.m;
4358
+ const hnswConfig = config?.hnswConfig ? { ...config.hnswConfig, m: rawHnswM !== void 0 ? Math.max(rawHnswM, 2) : void 0 } : void 0;
4359
+ const dimensionScale = dimension / 256;
4360
+ const store = createVectorStore();
4361
+ const tombstones = /* @__PURE__ */ new Set();
4362
+ let sq8 = quantizationMode === "sq8" ? createScalarQuantizer(dimension) : null;
4363
+ let hnsw = null;
4364
+ const buffer = /* @__PURE__ */ new Set();
4365
+ let building = false;
4366
+ let buildScheduled = false;
4367
+ let pendingBuild = null;
4368
+ let disposed = false;
4369
+ function liveSize() {
4370
+ return store.size - tombstones.size;
4371
+ }
4372
+ function yieldToEventLoop() {
4373
+ return new Promise((resolve) => setTimeout(resolve, 0));
4374
+ }
4375
+ function mergeResults(hnswResults, bufferResults, k) {
4376
+ const seen = /* @__PURE__ */ new Set();
4377
+ const merged = [];
4378
+ let hi = 0;
4379
+ let bi = 0;
4380
+ while (merged.length < k && (hi < hnswResults.length || bi < bufferResults.length)) {
4381
+ const h = hi < hnswResults.length ? hnswResults[hi] : void 0;
4382
+ const b = bi < bufferResults.length ? bufferResults[bi] : void 0;
4383
+ let pick;
4384
+ if (h && b) {
4385
+ if (h.score > b.score || h.score === b.score && h.docId.localeCompare(b.docId) < 0) {
4386
+ pick = h;
4387
+ hi++;
4388
+ } else {
4389
+ pick = b;
4390
+ bi++;
4391
+ }
4392
+ } else if (h) {
4393
+ pick = h;
4394
+ hi++;
4395
+ } else if (b) {
4396
+ pick = b;
4397
+ bi++;
4398
+ } else {
4399
+ break;
4400
+ }
4401
+ if (seen.has(pick.docId)) continue;
4402
+ seen.add(pick.docId);
4403
+ merged.push(pick);
4404
+ }
4405
+ return merged;
4406
+ }
4407
+ function validateDimension(vector) {
4408
+ if (vector.length !== dimension) {
4409
+ throw new NarsilError(
4410
+ ErrorCodes.VECTOR_DIMENSION_MISMATCH,
4411
+ `Vector dimension mismatch: expected ${dimension}, got ${vector.length}`,
4412
+ { expected: dimension, received: vector.length }
4413
+ );
4414
+ }
4415
+ }
4416
+ function calibrateAndQuantizeAll() {
4417
+ if (!sq8) return;
4418
+ if (store.size === 0) return;
4419
+ function* vectorIterator() {
4420
+ for (const [docId, entry] of store.entries()) {
4421
+ if (tombstones.has(docId)) continue;
4422
+ yield entry.vector;
4423
+ }
4424
+ }
4425
+ sq8.calibrate(vectorIterator());
4426
+ for (const [docId, entry] of store.entries()) {
4427
+ if (tombstones.has(docId)) continue;
4428
+ sq8.quantize(docId, entry.vector);
4429
+ }
4430
+ }
4431
+ function recalibrateFromStore() {
4432
+ if (!sq8) return;
4433
+ function* storeVectors() {
4434
+ for (const [docId, entry] of store.entries()) {
4435
+ if (tombstones.has(docId)) continue;
4436
+ yield [docId, entry.vector];
4437
+ }
4438
+ }
4439
+ sq8.recalibrateAll(storeVectors());
4440
+ }
4441
+ function triggerBuild() {
4442
+ if (building) return;
4443
+ building = true;
4444
+ const liveDocIds = [];
4445
+ for (const [docId] of store.entries()) {
4446
+ if (tombstones.has(docId)) continue;
4447
+ liveDocIds.push(docId);
4448
+ }
4449
+ const bufferSnapshot = new Set(buffer);
4450
+ const buildPromise = (async () => {
4451
+ try {
4452
+ if (liveDocIds.length === 0 || disposed) return;
4453
+ if (sq8) {
4454
+ calibrateAndQuantizeAll();
4455
+ }
4456
+ if (liveDocIds.length > WORKER_BUILD_SIZE_THRESHOLD) {
4457
+ const workerResult = await tryWorkerBuild(liveDocIds, bufferSnapshot);
4458
+ if (workerResult) return;
4459
+ }
4460
+ const newHnsw = createHNSWIndex(dimension, store, hnswConfig, sq8 ?? void 0);
4461
+ const CHUNK_SIZE = 100;
4462
+ for (let i = 0; i < liveDocIds.length; i++) {
4463
+ const docId = liveDocIds[i];
4464
+ if (!store.has(docId) || tombstones.has(docId)) continue;
4465
+ newHnsw.insertNode(docId);
4466
+ if ((i + 1) % CHUNK_SIZE === 0) {
4467
+ if (disposed) return;
4468
+ await yieldToEventLoop();
4469
+ }
4470
+ }
4471
+ if (disposed) return;
4472
+ hnsw = newHnsw;
4473
+ for (const docId of tombstones) {
4474
+ if (newHnsw.has(docId)) {
4475
+ newHnsw.markTombstone(docId);
4476
+ }
4477
+ }
4478
+ for (const docId of bufferSnapshot) {
4479
+ buffer.delete(docId);
4480
+ }
4481
+ } finally {
4482
+ building = false;
4483
+ pendingBuild = null;
4484
+ }
4485
+ })();
4486
+ pendingBuild = buildPromise;
4487
+ }
4488
+ async function tryWorkerBuild(liveDocIds, bufferSnapshot) {
4489
+ const vectorData = new Float32Array(liveDocIds.length * dimension);
4490
+ const validDocIds = [];
4491
+ let offset = 0;
4492
+ for (const docId of liveDocIds) {
4493
+ const entry = store.get(docId);
4494
+ if (!entry || tombstones.has(docId)) continue;
4495
+ vectorData.set(entry.vector, offset);
4496
+ validDocIds.push(docId);
4497
+ offset += dimension;
4498
+ }
4499
+ if (validDocIds.length === 0) return false;
4500
+ const packedData = offset < vectorData.length ? vectorData.subarray(0, offset) : vectorData;
4501
+ const resolvedConfig = {
4502
+ m: hnswConfig?.m,
4503
+ efConstruction: hnswConfig?.efConstruction,
4504
+ metric: hnswConfig?.metric
4505
+ };
4506
+ const timeoutMs = Math.max(1e4, liveDocIds.length * 2);
4507
+ const outcome = await dispatchWorkerBuild(validDocIds, packedData, dimension, resolvedConfig, timeoutMs, true);
4508
+ if (!outcome.ok) return false;
4509
+ const newHnsw = createHNSWIndex(dimension, store, hnswConfig, sq8 ?? void 0);
4510
+ newHnsw.deserialize(outcome.graph);
4511
+ hnsw = newHnsw;
4512
+ for (const docId of tombstones) {
4513
+ if (newHnsw.has(docId)) {
4514
+ newHnsw.markTombstone(docId);
4515
+ }
4516
+ }
4517
+ for (const docId of bufferSnapshot) {
4518
+ buffer.delete(docId);
4519
+ }
4520
+ return true;
4521
+ }
4522
+ async function awaitPendingBuild() {
4523
+ if (pendingBuild) {
4524
+ await pendingBuild;
4525
+ }
4526
+ }
4527
+ function dispose() {
4528
+ disposed = true;
4529
+ }
4530
+ function scheduleBuild() {
4531
+ if (building || buildScheduled || disposed) return;
4532
+ const thresholdMet = !hnsw && liveSize() >= promotionThreshold || hnsw !== null && buffer.size >= promotionThreshold;
4533
+ if (!thresholdMet) return;
4534
+ buildScheduled = true;
4535
+ setTimeout(() => {
4536
+ buildScheduled = false;
4537
+ triggerBuild();
4538
+ }, 0);
4539
+ }
4540
+ function bruteForceSearch(query, k, metric, minSimilarity, candidates) {
4541
+ const queryMag = magnitude(query);
4542
+ const highScoreFirst = (a, b) => b.score - a.score || a.docId.localeCompare(b.docId);
4543
+ const heap = createBoundedMaxHeap(highScoreFirst, k);
4544
+ for (const docId of candidates) {
4545
+ if (tombstones.has(docId)) continue;
4546
+ const entry = store.get(docId);
4547
+ if (!entry) continue;
4548
+ let score;
4549
+ switch (metric) {
4550
+ case "cosine":
4551
+ score = cosineSimilarityWithMagnitudes(query, entry.vector, queryMag, entry.magnitude);
4552
+ break;
4553
+ case "dotProduct":
4554
+ score = dotProduct(query, entry.vector);
4555
+ break;
4556
+ case "euclidean": {
4557
+ const dist = euclideanDistance(query, entry.vector);
4558
+ score = 1 / (1 + dist);
4559
+ break;
4560
+ }
4561
+ }
4562
+ if (score >= minSimilarity) {
4563
+ heap.push({ docId, score });
4564
+ }
4565
+ }
4566
+ return heap.toSortedArray().reverse();
4567
+ }
4568
+ function* allLiveDocIds() {
4569
+ for (const [docId] of store.entries()) {
4570
+ if (tombstones.has(docId)) continue;
4571
+ yield docId;
4572
+ }
4573
+ }
4574
+ function insert(docId, vector) {
4575
+ validateDimension(vector);
4576
+ tombstones.delete(docId);
4577
+ store.insert(docId, vector);
4578
+ buffer.add(docId);
4579
+ }
4580
+ function remove(docId) {
4581
+ if (!store.has(docId)) return;
4582
+ tombstones.add(docId);
4583
+ buffer.delete(docId);
4584
+ if (hnsw) {
4585
+ hnsw.markTombstone(docId);
4586
+ }
4587
+ }
4588
+ function* bufferCandidates(filterDocIds) {
4589
+ if (filterDocIds) {
4590
+ if (buffer.size <= filterDocIds.size) {
4591
+ for (const docId of buffer) {
4592
+ if (filterDocIds.has(docId) && !tombstones.has(docId)) yield docId;
4593
+ }
4594
+ } else {
4595
+ for (const docId of filterDocIds) {
4596
+ if (buffer.has(docId) && !tombstones.has(docId)) yield docId;
4597
+ }
4598
+ }
4599
+ } else {
4600
+ for (const docId of buffer) {
4601
+ if (!tombstones.has(docId)) yield docId;
4602
+ }
4603
+ }
4604
+ }
4605
+ function search(query, k, options) {
4606
+ validateDimension(query);
4607
+ const currentLiveSize = liveSize();
4608
+ if (currentLiveSize === 0) return [];
4609
+ if (k <= 0) return [];
4610
+ if (buffer.size > 0 && !building && !buildScheduled) {
4611
+ scheduleBuild();
4612
+ }
4613
+ const { metric, minSimilarity, filterDocIds, efSearch } = options;
4614
+ if (filterDocIds && filterDocIds.size === 0) return [];
4615
+ if (!hnsw) {
4616
+ const candidates = filterDocIds ?? allLiveDocIds();
4617
+ return bruteForceSearch(query, k, metric, minSimilarity, candidates);
4618
+ }
4619
+ if (buffer.size === 0) {
4620
+ if (filterDocIds) {
4621
+ const hnswLiveSize = hnsw.size;
4622
+ const selectivity = hnswLiveSize > 0 ? filterDocIds.size / hnswLiveSize : 1;
4623
+ if (selectivity < filterThreshold) {
4624
+ return bruteForceSearch(query, k, metric, minSimilarity, filterDocIds);
4625
+ }
4626
+ }
4627
+ const hnswResults2 = hnsw.search(query, k, metric, minSimilarity, filterDocIds, efSearch);
4628
+ return hnswResults2.map((r) => ({ docId: r.docId, score: r.score }));
4629
+ }
4630
+ if (filterDocIds) {
4631
+ const hnswLiveSize = hnsw.size;
4632
+ const selectivity = hnswLiveSize > 0 ? filterDocIds.size / hnswLiveSize : 1;
4633
+ if (selectivity < filterThreshold) {
4634
+ return bruteForceSearch(query, k, metric, minSimilarity, filterDocIds);
4635
+ }
4636
+ }
4637
+ const hnswResults = hnsw.search(query, k, metric, minSimilarity, filterDocIds, efSearch).map((r) => ({ docId: r.docId, score: r.score }));
4638
+ const bufferResults = bruteForceSearch(query, k, metric, minSimilarity, bufferCandidates(filterDocIds));
4639
+ return mergeResults(hnswResults, bufferResults, k);
4640
+ }
4641
+ function getVector(docId) {
4642
+ if (tombstones.has(docId)) return null;
4643
+ const entry = store.get(docId);
4644
+ if (!entry) return null;
4645
+ return new Float32Array(entry.vector);
4646
+ }
4647
+ function has(docId) {
4648
+ return store.has(docId) && !tombstones.has(docId);
4649
+ }
4650
+ function compact() {
4651
+ if (tombstones.size === 0) return;
4652
+ if (hnsw) {
4653
+ hnsw.compactTombstones();
4654
+ }
4655
+ for (const docId of tombstones) {
4656
+ store.remove(docId);
4657
+ buffer.delete(docId);
4658
+ if (sq8) {
4659
+ sq8.remove(docId);
4660
+ }
4661
+ }
4662
+ tombstones.clear();
4663
+ if (sq8?.isCalibrated() && store.size > 0) {
4664
+ recalibrateFromStore();
4665
+ }
4666
+ }
4667
+ async function optimize() {
4668
+ if (pendingBuild) {
4669
+ await pendingBuild;
4670
+ }
4671
+ compact();
4672
+ const live = liveSize();
4673
+ if (live === 0) {
4674
+ if (hnsw) {
4675
+ hnsw.clear();
4676
+ hnsw = null;
4677
+ }
4678
+ buffer.clear();
4679
+ if (sq8) {
4680
+ sq8.clear();
4681
+ }
4682
+ return;
4683
+ }
4684
+ if (sq8) {
4685
+ calibrateAndQuantizeAll();
4686
+ }
4687
+ const newHnsw = createHNSWIndex(dimension, store, hnswConfig, sq8 ?? void 0);
4688
+ const liveDocIds = [];
4689
+ for (const [docId] of store.entries()) {
4690
+ if (tombstones.has(docId)) continue;
4691
+ liveDocIds.push(docId);
4692
+ }
4693
+ const CHUNK_SIZE = 100;
4694
+ for (let i = 0; i < liveDocIds.length; i++) {
4695
+ newHnsw.insertNode(liveDocIds[i]);
4696
+ if ((i + 1) % CHUNK_SIZE === 0) {
4697
+ await yieldToEventLoop();
4698
+ }
4699
+ }
4700
+ hnsw = newHnsw;
4701
+ buffer.clear();
4702
+ if (sq8 && store.size > 0) {
4703
+ recalibrateFromStore();
4704
+ }
4705
+ }
4706
+ function maintenanceStatus() {
4707
+ const storeSize = store.size;
4708
+ const tombstoneRatio = storeSize > 0 ? tombstones.size / storeSize : 0;
4709
+ const graphCount = hnsw ? 1 : 0;
4710
+ const estimatedCompactMs = Math.round(tombstones.size * ESTIMATED_MS_PER_TOMBSTONE * dimensionScale);
4711
+ const estimatedOptimizeMs = Math.round(storeSize * ESTIMATED_MS_PER_VECTOR_REBUILD * dimensionScale);
4712
+ return {
4713
+ tombstoneRatio,
4714
+ graphCount,
4715
+ bufferSize: buffer.size,
4716
+ building,
4717
+ estimatedCompactMs,
4718
+ estimatedOptimizeMs
4719
+ };
4720
+ }
4721
+ function serialize() {
4722
+ const vectors = [];
4723
+ for (const [docId, entry] of store.entries()) {
4724
+ if (tombstones.has(docId)) continue;
4725
+ vectors.push({ docId, vector: Array.from(entry.vector) });
4726
+ }
4727
+ const graphs = [];
4728
+ if (hnsw) {
4729
+ graphs.push(hnsw.serialize());
4730
+ }
4731
+ let sq8Data = null;
4732
+ if (sq8?.isCalibrated() && sq8.size > 0) {
4733
+ sq8Data = sq8.serialize();
4734
+ }
4735
+ return {
4736
+ fieldName,
4737
+ dimension,
4738
+ vectors,
4739
+ graphs,
4740
+ sq8: sq8Data
4741
+ };
4742
+ }
4743
+ function deserialize(payload) {
4744
+ if (payload.dimension !== dimension) {
4745
+ throw new NarsilError(
4746
+ ErrorCodes.VECTOR_DIMENSION_MISMATCH,
4747
+ `Payload dimension ${payload.dimension} does not match index dimension ${dimension}`,
4748
+ { expected: dimension, received: payload.dimension }
4749
+ );
4750
+ }
4751
+ for (const entry of payload.vectors) {
4752
+ if (entry.vector.length !== dimension) {
4753
+ throw new NarsilError(
4754
+ ErrorCodes.VECTOR_DIMENSION_MISMATCH,
4755
+ `Vector for doc "${entry.docId}" has dimension ${entry.vector.length}, expected ${dimension}`,
4756
+ { docId: entry.docId, expected: dimension, received: entry.vector.length }
4757
+ );
4758
+ }
4759
+ }
4760
+ store.clear();
4761
+ tombstones.clear();
4762
+ buffer.clear();
4763
+ if (hnsw) {
4764
+ hnsw.clear();
4765
+ hnsw = null;
4766
+ }
4767
+ if (sq8) {
4768
+ sq8.clear();
4769
+ }
4770
+ for (const entry of payload.vectors) {
4771
+ store.insert(entry.docId, new Float32Array(entry.vector));
4772
+ }
4773
+ if (payload.sq8) {
4774
+ if (quantizationMode === "sq8") {
4775
+ sq8 = deserializeScalarQuantizer(payload.sq8, dimension);
4776
+ }
4777
+ }
4778
+ if (payload.graphs.length > 0) {
4779
+ const graphData = payload.graphs[0];
4780
+ const restoredHnsw = createHNSWIndex(
4781
+ dimension,
4782
+ store,
4783
+ {
4784
+ m: graphData.m ?? hnswConfig?.m,
4785
+ efConstruction: graphData.efConstruction ?? hnswConfig?.efConstruction,
4786
+ metric: graphData.metric ?? hnswConfig?.metric
4787
+ },
4788
+ sq8 ?? void 0
4789
+ );
4790
+ restoredHnsw.deserialize(graphData);
4791
+ hnsw = restoredHnsw;
4792
+ for (let i = 1; i < payload.graphs.length; i++) {
4793
+ const additionalGraph = payload.graphs[i];
4794
+ for (const [nodeDocId] of additionalGraph.nodes) {
4795
+ if (!restoredHnsw.has(nodeDocId) && store.has(nodeDocId)) {
4796
+ restoredHnsw.insertNode(nodeDocId);
4797
+ }
4798
+ }
4799
+ }
4800
+ for (const [docId] of store.entries()) {
4801
+ if (tombstones.has(docId)) continue;
4802
+ if (!restoredHnsw.has(docId)) {
4803
+ buffer.add(docId);
4804
+ }
4805
+ }
4806
+ if (buffer.size > restoredHnsw.size) {
4807
+ hnsw.clear();
4808
+ hnsw = null;
4809
+ buffer.clear();
4810
+ for (const [docId] of store.entries()) {
4811
+ if (tombstones.has(docId)) continue;
4812
+ buffer.add(docId);
4813
+ }
4814
+ }
4815
+ } else {
4816
+ for (const [docId] of store.entries()) {
4817
+ if (tombstones.has(docId)) continue;
4818
+ buffer.add(docId);
4819
+ }
4820
+ }
4821
+ }
4822
+ function estimateMemoryBytes() {
4823
+ const count = store.size;
4824
+ if (count === 0 && tombstones.size === 0 && buffer.size === 0) return 0;
4825
+ let bytes = store.estimateMemory(dimension);
4826
+ const TOMBSTONE_SET_OVERHEAD = 64;
4827
+ const TOMBSTONE_ENTRY_COST = 72;
4828
+ bytes += TOMBSTONE_SET_OVERHEAD + tombstones.size * TOMBSTONE_ENTRY_COST;
4829
+ const BUFFER_SET_OVERHEAD = 64;
4830
+ const BUFFER_ENTRY_COST = 72;
4831
+ bytes += BUFFER_SET_OVERHEAD + buffer.size * BUFFER_ENTRY_COST;
4832
+ if (hnsw) {
4833
+ const HNSW_NODE_OBJ = 48;
4834
+ const MAP_ENTRY = 72;
4835
+ const MAP_OVERHEAD = 64;
4836
+ const CONN_ARRAY_HEADER = 32;
4837
+ const SET_OVERHEAD = 64;
4838
+ const SET_ENTRY_COST = 72;
4839
+ const m = hnsw.m;
4840
+ const avgLayers = m > 1 ? m / (m - 1) : 1;
4841
+ const avgConnsLayer0 = m;
4842
+ const avgConnsUpper = Math.ceil(m / 2);
4843
+ const connMemPerNode = CONN_ARRAY_HEADER + (SET_OVERHEAD + avgConnsLayer0 * SET_ENTRY_COST) + Math.max(0, avgLayers - 1) * (SET_OVERHEAD + avgConnsUpper * SET_ENTRY_COST);
4844
+ const hnswNodeCount = hnsw.size + hnsw.tombstoneCount;
4845
+ const perHnswNode = MAP_ENTRY + HNSW_NODE_OBJ + connMemPerNode;
4846
+ bytes += MAP_OVERHEAD + hnswNodeCount * perHnswNode;
4847
+ }
4848
+ if (sq8?.isCalibrated()) {
4849
+ const sqCount = sq8.size;
4850
+ const MAP_OVERHEAD_SQ = 64;
4851
+ const MAP_ENTRY_SQ = 72;
4852
+ const UINT8_ARRAY_HEADER = 64;
4853
+ const PER_VECTOR_METADATA = 8 * 3;
4854
+ const GLOBAL_CALIBRATION = 8 * 5;
4855
+ bytes += 4 * (MAP_OVERHEAD_SQ + sqCount * MAP_ENTRY_SQ);
4856
+ bytes += sqCount * (UINT8_ARRAY_HEADER + dimension + PER_VECTOR_METADATA);
4857
+ bytes += GLOBAL_CALIBRATION;
4858
+ }
4859
+ return Math.round(bytes);
4860
+ }
4861
+ return {
4862
+ get size() {
4863
+ return liveSize();
4864
+ },
4865
+ get dimension() {
4866
+ return dimension;
4867
+ },
4868
+ get fieldName() {
4869
+ return fieldName;
4870
+ },
4871
+ insert,
4872
+ remove,
4873
+ scheduleBuild,
4874
+ awaitPendingBuild,
4875
+ dispose,
4876
+ search,
4877
+ getVector,
4878
+ has,
4879
+ compact,
4880
+ optimize,
4881
+ maintenanceStatus,
4882
+ estimateMemoryBytes,
4883
+ serialize,
4884
+ deserialize
4885
+ };
4886
+ }
4887
+
3996
4888
  // src/workers/direct-executor.ts
3997
4889
  function createDirectExecutor() {
3998
4890
  const indexes = /* @__PURE__ */ new Map();
@@ -4013,8 +4905,13 @@ function createDirectExecutor() {
4013
4905
  }
4014
4906
  const router = createPartitionRouter();
4015
4907
  const partitionCount = config.partitions?.maxPartitions ?? 1;
4016
- const manager = createPartitionManager(indexName, config, language, router, partitionCount);
4017
- indexes.set(indexName, { manager, config, language });
4908
+ const vectorFields = extractVectorFieldsFromSchema(config.schema);
4909
+ const vectorIndexes = /* @__PURE__ */ new Map();
4910
+ for (const [fieldPath, dim] of vectorFields) {
4911
+ vectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, config.vectorPromotion));
4912
+ }
4913
+ const manager = createPartitionManager(indexName, config, language, router, partitionCount, vectorIndexes);
4914
+ indexes.set(indexName, { manager, config, language, vectorIndexes });
4018
4915
  }
4019
4916
  function dropIndex(indexName) {
4020
4917
  const entry = requireIndex(indexName);
@@ -4098,6 +4995,13 @@ function createDirectExecutor() {
4098
4995
  partition.clear();
4099
4996
  }
4100
4997
  entry.manager.setPartitions(partitions);
4998
+ const vectorFields = extractVectorFieldsFromSchema(entry.config.schema);
4999
+ const newVectorIndexes = /* @__PURE__ */ new Map();
5000
+ for (const [fieldPath, dim] of vectorFields) {
5001
+ newVectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, entry.config.vectorPromotion));
5002
+ }
5003
+ entry.manager.resetVectorIndexes(newVectorIndexes);
5004
+ entry.vectorIndexes = entry.manager.getVectorIndexes();
4101
5005
  return void 0;
4102
5006
  }
4103
5007
  case "serialize": {
@@ -4137,4 +5041,4 @@ function createDirectExecutor() {
4137
5041
  };
4138
5042
  }
4139
5043
 
4140
- export { createDirectExecutor, createPartitionIndex, createPartitionRouter, deserializePayloadV2, detectRuntime, fanOutQuery, fnv1a, getLanguage, kWayMerge, registerLanguage, tokenize, validateSchema };
5044
+ export { clearNormalizationCache, configureNormalizationCache, createDirectExecutor, createPartitionIndex, createPartitionRouter, deserializePayloadV2, detectRuntime, extractVectorFieldsFromSchema, fanOutQuery, flattenSchema, fnv1a, getLanguage, getNestedValue, getNormalizationCacheSize, kWayMerge, registerLanguage, resetNormalizationCache, tokenize, validateRequiredFields, validateSchema };