@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.
- package/README.md +130 -2
- package/dist/adapters/filesystem.d.ts +1 -1
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/adapters/memory.d.ts +1 -1
- package/dist/{adapters-XODT2F3h.d.ts → adapters-BbRkoiu9.d.ts} +7 -1
- package/dist/{chunk-WYC635GI.mjs → chunk-I24SGLFS.mjs} +5 -6
- package/dist/{chunk-TFWBALU4.mjs → chunk-KHL4RPC3.mjs} +1957 -1053
- package/dist/{chunk-7GKPWGCN.mjs → chunk-R2C2EQGO.mjs} +12 -2
- package/dist/{chunk-GVN2W4VA.mjs → chunk-VVORJOXR.mjs} +273 -84
- package/dist/direct-executor-LBJLGMJ2.mjs +4 -0
- package/dist/embeddings/openai.d.ts +13 -0
- package/dist/embeddings/openai.mjs +248 -0
- package/dist/index.browser.mjs +7887 -5557
- package/dist/index.d.ts +83 -49
- package/dist/index.mjs +1886 -702
- package/dist/invalidation/broadcast-channel.d.ts +1 -1
- package/dist/invalidation/filesystem.d.ts +1 -1
- package/dist/invalidation/noop.d.ts +1 -1
- package/dist/languages/english.mjs +1 -1
- package/dist/vector/hnsw-build-worker.d.ts +9 -2
- package/dist/vector/hnsw-build-worker.mjs +54 -10
- package/dist/workers/entry.mjs +1 -1
- package/package.json +10 -3
- package/dist/direct-executor-6TSG7GWH.mjs +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createHNSWIndex, magnitude, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes } from './chunk-
|
|
2
|
-
import { english } from './chunk-
|
|
3
|
-
import { NarsilError, ErrorCodes } from './chunk-
|
|
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
|
|
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 >=
|
|
132
|
-
const
|
|
133
|
-
|
|
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
|
-
|
|
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 && !
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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] !==
|
|
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,
|
|
1344
|
+
insert(token, internalId, termFrequency, fieldNameIndex, positions) {
|
|
1369
1345
|
const list = getOrCreateList(token);
|
|
1370
|
-
if (list.deletedDocs.size > 0 && list.deletedDocs.has(
|
|
1371
|
-
compactDocEntries(list,
|
|
1372
|
-
list.deletedDocs.delete(
|
|
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(
|
|
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(
|
|
1391
|
-
list.docIdSet.add(
|
|
1366
|
+
if (!list.docIdSet.has(internalId)) {
|
|
1367
|
+
list.docIdSet.add(internalId);
|
|
1392
1368
|
}
|
|
1393
1369
|
list.length++;
|
|
1394
1370
|
},
|
|
1395
|
-
remove(token,
|
|
1371
|
+
remove(token, internalId) {
|
|
1396
1372
|
const list = index.get(token);
|
|
1397
|
-
if (!list || !list.docIdSet.has(
|
|
1398
|
-
list.docIdSet.delete(
|
|
1399
|
-
list.deletedDocs.add(
|
|
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:
|
|
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
|
-
|
|
1488
|
-
|
|
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[
|
|
1496
|
-
docIdSet.add(
|
|
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(
|
|
1507
|
+
positions = new Array(validCount);
|
|
1508
|
+
let vi = 0;
|
|
1502
1509
|
for (let i = 0; i < count; i++) {
|
|
1503
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1723
|
-
entries.push({ lat, lon, docId });
|
|
1760
|
+
insert(internalId, lat, lon) {
|
|
1761
|
+
entries.push({ lat, lon, docId: internalId });
|
|
1724
1762
|
},
|
|
1725
|
-
remove(
|
|
1763
|
+
remove(internalId) {
|
|
1726
1764
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1727
|
-
if (entries[i].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(
|
|
1993
|
-
entries.push({ value, docId });
|
|
1855
|
+
insert(internalId, value) {
|
|
1856
|
+
entries.push({ value, docId: internalId });
|
|
1994
1857
|
sorted = false;
|
|
1995
1858
|
},
|
|
1996
|
-
remove(
|
|
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 ===
|
|
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(
|
|
2065
|
-
if (value) trueDocs.add(
|
|
2066
|
-
else falseDocs.add(
|
|
1954
|
+
insert(internalId, value) {
|
|
1955
|
+
if (value) trueDocs.add(internalId);
|
|
1956
|
+
else falseDocs.add(internalId);
|
|
2067
1957
|
},
|
|
2068
|
-
remove(
|
|
2069
|
-
if (value) trueDocs.delete(
|
|
2070
|
-
else falseDocs.delete(
|
|
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(
|
|
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(
|
|
2024
|
+
docSet.add(internalId);
|
|
2121
2025
|
},
|
|
2122
|
-
remove(
|
|
2026
|
+
remove(internalId, value) {
|
|
2123
2027
|
const docSet = valueMap.get(value);
|
|
2124
2028
|
if (!docSet) return;
|
|
2125
|
-
docSet.delete(
|
|
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
|
|
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
|
|
2250
|
-
ids.add(
|
|
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
|
|
2289
|
-
const
|
|
2290
|
-
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
2328
|
-
for (const t of
|
|
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 +=
|
|
2271
|
+
positionOffset += itemTokenCount;
|
|
2339
2272
|
}
|
|
2340
2273
|
fieldLengths[fieldPath] = fieldTokenList.length;
|
|
2341
2274
|
for (const [token, freq] of tokenFreqs) {
|
|
2342
|
-
state.invertedIdx.insert(token,
|
|
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
|
|
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,
|
|
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,
|
|
2301
|
+
indexStringField(state, internalId, fieldPath, value, language, options, fieldLengths, tokensByField);
|
|
2369
2302
|
} else if (fieldType === "number") {
|
|
2370
|
-
getOrCreateNumericIndex(state, fieldPath).insert(
|
|
2303
|
+
getOrCreateNumericIndex(state, fieldPath).insert(internalId, value);
|
|
2371
2304
|
} else if (fieldType === "boolean") {
|
|
2372
|
-
getOrCreateBooleanIndex(state, fieldPath).insert(
|
|
2305
|
+
getOrCreateBooleanIndex(state, fieldPath).insert(internalId, value);
|
|
2373
2306
|
} else if (fieldType === "enum") {
|
|
2374
|
-
state.enumIndexes.get(fieldPath)?.insert(
|
|
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(
|
|
2310
|
+
state.geoIndexes.get(fieldPath)?.insert(internalId, geo.lat, geo.lon);
|
|
2378
2311
|
} else if (fieldType === "string[]") {
|
|
2379
|
-
indexStringArrayField(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
2357
|
+
state.numericIndexes.get(fieldPath)?.remove(internalId, value);
|
|
2421
2358
|
} else if (fieldType === "boolean") {
|
|
2422
|
-
state.booleanIndexes.get(fieldPath)?.remove(
|
|
2359
|
+
state.booleanIndexes.get(fieldPath)?.remove(internalId, value);
|
|
2423
2360
|
} else if (fieldType === "enum") {
|
|
2424
|
-
state.enumIndexes.get(fieldPath)?.remove(
|
|
2361
|
+
state.enumIndexes.get(fieldPath)?.remove(internalId, value);
|
|
2425
2362
|
} else if (fieldType === "geopoint") {
|
|
2426
|
-
state.geoIndexes.get(fieldPath)?.remove(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2479
|
-
if (newVal !== void 0 && newVal !== null) numIdx.insert(
|
|
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(
|
|
2485
|
-
if (newVal !== void 0 && newVal !== null) boolIdx.insert(
|
|
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(
|
|
2491
|
-
if (newVal !== void 0 && newVal !== null) enumIdx.insert(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
2547
|
-
if (
|
|
2548
|
-
if (
|
|
2549
|
-
|
|
2550
|
-
let
|
|
2551
|
-
|
|
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
|
|
2563
|
-
if (
|
|
2564
|
-
if (
|
|
2565
|
-
|
|
2566
|
-
for (
|
|
2567
|
-
|
|
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
|
|
2574
|
-
|
|
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
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
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
|
|
2522
|
+
function applyEqBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2597
2523
|
if (fieldIndex?.type === "numeric" && typeof value === "number") {
|
|
2598
|
-
return fieldIndex.index.
|
|
2524
|
+
return fieldIndex.index.eqBitset(value, capacity);
|
|
2599
2525
|
}
|
|
2600
2526
|
if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
|
|
2601
|
-
return value ? fieldIndex.index.
|
|
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.
|
|
2530
|
+
return fieldIndex.index.getDocIdsBitset(value, capacity);
|
|
2605
2531
|
}
|
|
2606
|
-
|
|
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
|
|
2534
|
+
function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2613
2535
|
if (fieldIndex?.type === "numeric" && typeof value === "number") {
|
|
2614
|
-
|
|
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.
|
|
2541
|
+
return value ? fieldIndex.index.getFalseBitset(capacity) : fieldIndex.index.getTrueBitset(capacity);
|
|
2618
2542
|
}
|
|
2619
2543
|
if (fieldIndex?.type === "enum" && typeof value === "string") {
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
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
|
|
2548
|
+
return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null && v !== value);
|
|
2628
2549
|
}
|
|
2629
|
-
function
|
|
2550
|
+
function applyGtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2630
2551
|
if (fieldIndex?.type === "numeric") {
|
|
2631
|
-
return fieldIndex.index.
|
|
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
|
|
2554
|
+
return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v > value);
|
|
2639
2555
|
}
|
|
2640
|
-
function
|
|
2556
|
+
function applyLtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2641
2557
|
if (fieldIndex?.type === "numeric") {
|
|
2642
|
-
return fieldIndex.index.
|
|
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
|
|
2560
|
+
return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v < value);
|
|
2650
2561
|
}
|
|
2651
|
-
function
|
|
2562
|
+
function applyGteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2652
2563
|
if (fieldIndex?.type === "numeric") {
|
|
2653
|
-
return fieldIndex.index.
|
|
2564
|
+
return fieldIndex.index.gteBitset(value, capacity);
|
|
2654
2565
|
}
|
|
2655
|
-
|
|
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
|
|
2568
|
+
function applyLteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2663
2569
|
if (fieldIndex?.type === "numeric") {
|
|
2664
|
-
return fieldIndex.index.
|
|
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
|
|
2572
|
+
return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v <= value);
|
|
2672
2573
|
}
|
|
2673
|
-
function
|
|
2574
|
+
function applyBetweenBitset(range, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2674
2575
|
if (fieldIndex?.type === "numeric") {
|
|
2675
|
-
return fieldIndex.index.
|
|
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
|
|
2578
|
+
return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= range[0] && v <= range[1]);
|
|
2683
2579
|
}
|
|
2684
|
-
function
|
|
2580
|
+
function applyInBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2685
2581
|
if (fieldIndex?.type === "enum") {
|
|
2686
|
-
|
|
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
|
-
|
|
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
|
|
2587
|
+
function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
|
|
2703
2588
|
if (fieldIndex?.type === "enum") {
|
|
2704
|
-
const
|
|
2705
|
-
|
|
2706
|
-
|
|
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
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
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
|
|
2721
|
-
|
|
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
|
|
2729
|
-
|
|
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
|
|
2737
|
-
|
|
2738
|
-
|
|
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
|
-
|
|
2750
|
-
}
|
|
2751
|
-
return result;
|
|
2614
|
+
return true;
|
|
2615
|
+
});
|
|
2752
2616
|
}
|
|
2753
|
-
function
|
|
2617
|
+
function applyMatchesAnyBitset(values, allDocsBitset, capacity, getValue) {
|
|
2754
2618
|
const valSet = new Set(values);
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
2624
|
+
return false;
|
|
2625
|
+
});
|
|
2767
2626
|
}
|
|
2768
|
-
function
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
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
|
|
2778
|
-
|
|
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
|
|
2786
|
-
|
|
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
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
if (v
|
|
2798
|
-
|
|
2799
|
-
|
|
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
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
if (v
|
|
2816
|
-
|
|
2817
|
-
|
|
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
|
|
2657
|
+
function applyGeoRadiusBitset(filter, geoIndex, capacity) {
|
|
2823
2658
|
const distanceMeters = convertToMeters(filter.distance, filter.unit);
|
|
2824
|
-
|
|
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
|
|
2833
|
-
|
|
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
|
|
2685
|
+
const bitsets = [];
|
|
2849
2686
|
if (expression.fields) {
|
|
2850
2687
|
for (const [fieldPath, filter] of Object.entries(expression.fields)) {
|
|
2851
|
-
|
|
2688
|
+
bitsets.push(evaluateFieldFilter(fieldPath, filter, context));
|
|
2852
2689
|
}
|
|
2853
2690
|
}
|
|
2854
2691
|
if (expression.and?.length) {
|
|
2855
|
-
const
|
|
2856
|
-
|
|
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
|
|
2860
|
-
|
|
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
|
-
|
|
2701
|
+
const universe = context.allDocIdsBitset;
|
|
2702
|
+
bitsets.push(applyAndBitset([universe, applyNotBitset(excluded, context.capacity)]));
|
|
2865
2703
|
}
|
|
2866
|
-
if (
|
|
2867
|
-
if (
|
|
2868
|
-
return
|
|
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 = (
|
|
2710
|
+
const getValue = (internalId) => context.getFieldValue(internalId, fieldPath);
|
|
2873
2711
|
const f = filter;
|
|
2874
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
2727
|
+
bitsets.push(applyGeoPolygonBitset(filter.polygon, geoIndex, capacity));
|
|
2882
2728
|
}
|
|
2883
2729
|
if ("eq" in f && f.eq !== void 0) {
|
|
2884
|
-
|
|
2730
|
+
bitsets.push(applyEqBitset(f.eq, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2885
2731
|
}
|
|
2886
2732
|
if ("ne" in f && f.ne !== void 0) {
|
|
2887
|
-
|
|
2733
|
+
bitsets.push(applyNeBitset(f.ne, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2888
2734
|
}
|
|
2889
2735
|
if ("gt" in f && f.gt !== void 0) {
|
|
2890
|
-
|
|
2736
|
+
bitsets.push(applyGtBitset(f.gt, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2891
2737
|
}
|
|
2892
2738
|
if ("lt" in f && f.lt !== void 0) {
|
|
2893
|
-
|
|
2739
|
+
bitsets.push(applyLtBitset(f.lt, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2894
2740
|
}
|
|
2895
2741
|
if ("gte" in f && f.gte !== void 0) {
|
|
2896
|
-
|
|
2742
|
+
bitsets.push(applyGteBitset(f.gte, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2897
2743
|
}
|
|
2898
2744
|
if ("lte" in f && f.lte !== void 0) {
|
|
2899
|
-
|
|
2745
|
+
bitsets.push(applyLteBitset(f.lte, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2900
2746
|
}
|
|
2901
2747
|
if ("between" in f && f.between !== void 0) {
|
|
2902
|
-
|
|
2748
|
+
bitsets.push(applyBetweenBitset(f.between, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2903
2749
|
}
|
|
2904
2750
|
if ("in" in f && f.in !== void 0) {
|
|
2905
|
-
|
|
2751
|
+
bitsets.push(applyInBitset(f.in, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2906
2752
|
}
|
|
2907
2753
|
if ("nin" in f && f.nin !== void 0) {
|
|
2908
|
-
|
|
2754
|
+
bitsets.push(applyNinBitset(f.nin, getAllDocsBitset, capacity, getValue, fieldIndex));
|
|
2909
2755
|
}
|
|
2910
2756
|
if ("startsWith" in f && f.startsWith !== void 0) {
|
|
2911
|
-
|
|
2757
|
+
bitsets.push(applyStartsWithBitset(f.startsWith, getAllDocsBitset, capacity, getValue));
|
|
2912
2758
|
}
|
|
2913
2759
|
if ("endsWith" in f && f.endsWith !== void 0) {
|
|
2914
|
-
|
|
2760
|
+
bitsets.push(applyEndsWithBitset(f.endsWith, getAllDocsBitset, capacity, getValue));
|
|
2915
2761
|
}
|
|
2916
2762
|
if ("containsAll" in f && f.containsAll !== void 0) {
|
|
2917
|
-
|
|
2763
|
+
bitsets.push(
|
|
2764
|
+
applyContainsAllBitset(f.containsAll, getAllDocsBitset, capacity, getValue)
|
|
2765
|
+
);
|
|
2918
2766
|
}
|
|
2919
2767
|
if ("matchesAny" in f && f.matchesAny !== void 0) {
|
|
2920
|
-
|
|
2768
|
+
bitsets.push(
|
|
2769
|
+
applyMatchesAnyBitset(f.matchesAny, getAllDocsBitset, capacity, getValue)
|
|
2770
|
+
);
|
|
2921
2771
|
}
|
|
2922
2772
|
if ("size" in f && f.size !== void 0) {
|
|
2923
|
-
|
|
2773
|
+
bitsets.push(applySizeBitset(f.size, getAllDocsBitset, capacity, getValue));
|
|
2924
2774
|
}
|
|
2925
2775
|
if ("exists" in f && f.exists !== void 0) {
|
|
2926
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2791
|
+
bitsets.push(
|
|
2792
|
+
f.isNotEmpty ? applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsEmptyBitset(getAllDocsBitset, capacity, getValue)
|
|
2793
|
+
);
|
|
2936
2794
|
}
|
|
2937
|
-
if (
|
|
2938
|
-
if (
|
|
2939
|
-
return
|
|
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
|
|
3024
|
-
if (hasDeleted && list.deletedDocs.has(
|
|
3025
|
-
if (
|
|
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(
|
|
2892
|
+
let cachedLengths = fieldLengthCache.get(internalId);
|
|
3032
2893
|
if (cachedLengths === void 0) {
|
|
3033
|
-
const
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3070
|
-
if (hasDeleted && list.deletedDocs.has(
|
|
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(
|
|
2939
|
+
let cachedLengths = fieldLengthCache.get(internalId);
|
|
3077
2940
|
if (cachedLengths === void 0) {
|
|
3078
|
-
const
|
|
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(
|
|
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(
|
|
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(
|
|
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 [
|
|
2975
|
+
for (const [internalId, data] of docScores) {
|
|
3112
2976
|
if (heap.length < k) {
|
|
3113
|
-
heap.push({
|
|
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] = {
|
|
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 =
|
|
2985
|
+
const result = [];
|
|
3122
2986
|
for (let i = 0; i < heap.length; i++) {
|
|
3123
|
-
const data = docScores.get(heap[i].
|
|
2987
|
+
const data = docScores.get(heap[i].internalId);
|
|
3124
2988
|
if (!data) continue;
|
|
3125
|
-
|
|
3126
|
-
|
|
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: (
|
|
3092
|
+
getFieldValue: (internalId, fieldPath) => getFieldValueByInternalId(state.docStore, internalId, fieldPath),
|
|
3229
3093
|
get allDocIds() {
|
|
3230
3094
|
if (!cachedAllDocIds) {
|
|
3231
|
-
cachedAllDocIds =
|
|
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:
|
|
3199
|
+
fields: stored.fields,
|
|
3308
3200
|
fieldLengths: { ...stored.fieldLengths }
|
|
3309
3201
|
};
|
|
3310
3202
|
}
|
|
3311
|
-
const
|
|
3203
|
+
const resolver = state.docStore.resolver();
|
|
3204
|
+
const fieldNames = state.fieldNameTable.names;
|
|
3312
3205
|
const serializedInvertedIndex = /* @__PURE__ */ Object.create(null);
|
|
3313
|
-
for (const
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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) => ({
|
|
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] = {
|
|
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
|
-
|
|
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) => ({
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
4017
|
-
|
|
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 };
|