@delali/narsil 0.1.1 → 0.1.3
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 +39 -0
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/{chunk-JKIBUPAY.mjs → chunk-7GKPWGCN.mjs} +2 -1
- package/dist/{chunk-5HXMVMSM.mjs → chunk-CHNP2QCL.mjs} +1177 -181
- package/dist/chunk-GVN2W4VA.mjs +570 -0
- package/dist/chunk-HRABJLPB.mjs +64 -0
- package/dist/direct-executor-FF5KPZM4.mjs +4 -0
- package/dist/index.browser.mjs +7114 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.mjs +1371 -415
- package/dist/vector/hnsw-build-worker.d.ts +37 -0
- package/dist/vector/hnsw-build-worker.mjs +81 -0
- package/dist/workers/entry.mjs +3 -32
- package/package.json +31 -11
- package/dist/chunk-N37VMC62.mjs +0 -18
- package/dist/direct-executor-MQD7JRD4.mjs +0 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { createHNSWIndex, magnitude, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes } from './chunk-GVN2W4VA.mjs';
|
|
1
2
|
import { english } from './chunk-WYC635GI.mjs';
|
|
2
|
-
import { NarsilError, ErrorCodes } from './chunk-
|
|
3
|
+
import { NarsilError, ErrorCodes } from './chunk-7GKPWGCN.mjs';
|
|
4
|
+
import { decode, encode } from '@msgpack/msgpack';
|
|
3
5
|
|
|
4
6
|
// src/languages/registry.ts
|
|
5
7
|
var languages = /* @__PURE__ */ new Map();
|
|
@@ -95,6 +97,9 @@ var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{N}_'-]+/u;
|
|
|
95
97
|
var DEFAULT_MIN_TOKEN_LENGTH = 1;
|
|
96
98
|
var normalizationCache = /* @__PURE__ */ new Map();
|
|
97
99
|
var MAX_CACHE_SIZE = 65536;
|
|
100
|
+
var cachedLangName = "";
|
|
101
|
+
var cachedFlags = "";
|
|
102
|
+
var cachedPrefix = "";
|
|
98
103
|
function stripDiacritics(text) {
|
|
99
104
|
return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
100
105
|
}
|
|
@@ -103,9 +108,17 @@ function resolveStopWords(language, override) {
|
|
|
103
108
|
if (override instanceof Set) return override;
|
|
104
109
|
return override(language.stopWords);
|
|
105
110
|
}
|
|
111
|
+
function getCachePrefix(language, flags) {
|
|
112
|
+
if (language.name === cachedLangName && flags === cachedFlags) return cachedPrefix;
|
|
113
|
+
cachedLangName = language.name;
|
|
114
|
+
cachedFlags = flags;
|
|
115
|
+
cachedPrefix = `${language.name}:${flags}:`;
|
|
116
|
+
return cachedPrefix;
|
|
117
|
+
}
|
|
106
118
|
function transformToken(raw, language, stem, removeDiacritics) {
|
|
107
119
|
const flags = (stem ? "s" : "") + (removeDiacritics ? "d" : "");
|
|
108
|
-
const
|
|
120
|
+
const prefix = getCachePrefix(language, flags);
|
|
121
|
+
const cacheKey = prefix + raw;
|
|
109
122
|
const cached = normalizationCache.get(cacheKey);
|
|
110
123
|
if (cached !== void 0) return cached;
|
|
111
124
|
let normalized = raw;
|
|
@@ -115,20 +128,23 @@ function transformToken(raw, language, stem, removeDiacritics) {
|
|
|
115
128
|
if (stem && language.stemmer) {
|
|
116
129
|
normalized = language.stemmer(normalized);
|
|
117
130
|
}
|
|
118
|
-
cacheAndReturn(cacheKey, normalized);
|
|
119
|
-
return normalized;
|
|
120
|
-
}
|
|
121
|
-
function cacheAndReturn(key, value) {
|
|
122
131
|
if (normalizationCache.size >= MAX_CACHE_SIZE) {
|
|
123
132
|
const firstKey = normalizationCache.keys().next().value;
|
|
124
133
|
normalizationCache.delete(firstKey);
|
|
125
134
|
}
|
|
126
|
-
normalizationCache.set(
|
|
135
|
+
normalizationCache.set(cacheKey, normalized);
|
|
136
|
+
return normalized;
|
|
127
137
|
}
|
|
128
138
|
function splitText(text, language) {
|
|
129
139
|
const pattern = language.tokenizer?.splitPattern ?? DEFAULT_SPLIT_PATTERN;
|
|
130
140
|
return text.split(pattern);
|
|
131
141
|
}
|
|
142
|
+
function isAscii(text) {
|
|
143
|
+
for (let i = 0; i < text.length; i++) {
|
|
144
|
+
if (text.charCodeAt(i) > 127) return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
132
148
|
function tokenize(text, language, options) {
|
|
133
149
|
const {
|
|
134
150
|
stem = true,
|
|
@@ -144,7 +160,7 @@ function tokenize(text, language, options) {
|
|
|
144
160
|
originalTokens: customResult.map((t) => t.token)
|
|
145
161
|
};
|
|
146
162
|
}
|
|
147
|
-
const normalized = text.normalize("NFC").toLowerCase();
|
|
163
|
+
const normalized = isAscii(text) ? text.toLowerCase() : text.normalize("NFC").toLowerCase();
|
|
148
164
|
const rawParts = splitText(normalized, language);
|
|
149
165
|
const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
|
|
150
166
|
const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
|
|
@@ -440,6 +456,31 @@ function validateDocument(document, schema) {
|
|
|
440
456
|
}
|
|
441
457
|
validateDocumentFields(document, schema, "");
|
|
442
458
|
}
|
|
459
|
+
function collectExtraFields(doc, schema, prefix, extras) {
|
|
460
|
+
for (const key of Object.keys(doc)) {
|
|
461
|
+
if (key === "id") continue;
|
|
462
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
463
|
+
if (!(key in schema)) {
|
|
464
|
+
extras.push(path);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const schemaType = schema[key];
|
|
468
|
+
if (isPlainObject(schemaType) && isPlainObject(doc[key])) {
|
|
469
|
+
collectExtraFields(doc[key], schemaType, path, extras);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function validateDocumentStrict(document, schema) {
|
|
474
|
+
const extras = [];
|
|
475
|
+
collectExtraFields(document, schema, "", extras);
|
|
476
|
+
if (extras.length > 0) {
|
|
477
|
+
throw new NarsilError(
|
|
478
|
+
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
479
|
+
`Document contains fields not defined in schema: ${extras.join(", ")}`,
|
|
480
|
+
{ extraFields: extras }
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
443
484
|
function flattenRecursive(schema, prefix, result) {
|
|
444
485
|
for (const [field, type] of Object.entries(schema)) {
|
|
445
486
|
const path = prefix ? `${prefix}.${field}` : field;
|
|
@@ -478,6 +519,9 @@ function fulltextSearch(partition, params, language, schema, options) {
|
|
|
478
519
|
return { scored: [], totalMatched: 0 };
|
|
479
520
|
}
|
|
480
521
|
const queryTokens = deduplicateTokens(queryTokenResult.tokens);
|
|
522
|
+
const hasPostFilters = params.filters !== void 0 || params.minScore !== void 0 || params.termMatch !== void 0 && params.termMatch !== "any";
|
|
523
|
+
const requestedLimit = params.limit !== void 0 || params.offset !== void 0 ? (params.limit ?? 10) + (params.offset ?? 0) + 1 : void 0;
|
|
524
|
+
const maxResults = requestedLimit !== void 0 && !hasPostFilters ? requestedLimit : void 0;
|
|
481
525
|
const rawResult = partition.searchFulltext({
|
|
482
526
|
queryTokens,
|
|
483
527
|
fields: params.fields,
|
|
@@ -486,7 +530,9 @@ function fulltextSearch(partition, params, language, schema, options) {
|
|
|
486
530
|
prefixLength: params.prefixLength ?? 2,
|
|
487
531
|
exact: params.exact ?? false,
|
|
488
532
|
bm25Params: options?.bm25Params,
|
|
489
|
-
globalStats: options?.globalStats
|
|
533
|
+
globalStats: options?.globalStats,
|
|
534
|
+
maxResults,
|
|
535
|
+
termMatch: params.termMatch
|
|
490
536
|
});
|
|
491
537
|
let scored = rawResult.scored;
|
|
492
538
|
const termMatch = params.termMatch ?? "any";
|
|
@@ -501,7 +547,8 @@ function fulltextSearch(partition, params, language, schema, options) {
|
|
|
501
547
|
const matchingDocIds = partition.applyFilters(params.filters, schema);
|
|
502
548
|
scored = scored.filter((doc) => matchingDocIds.has(doc.docId));
|
|
503
549
|
}
|
|
504
|
-
|
|
550
|
+
const totalMatched = hasPostFilters ? scored.length : rawResult.totalMatched;
|
|
551
|
+
return { scored, totalMatched };
|
|
505
552
|
}
|
|
506
553
|
function validateSearchFields(fields, flatSchema) {
|
|
507
554
|
for (const field of fields) {
|
|
@@ -581,7 +628,8 @@ function hybridSearch(partition, params, language, schema, options) {
|
|
|
581
628
|
value: vectorConfig.value,
|
|
582
629
|
k: params.limit ?? 10,
|
|
583
630
|
similarity: vectorConfig.similarity,
|
|
584
|
-
metric: vectorConfig.metric
|
|
631
|
+
metric: vectorConfig.metric,
|
|
632
|
+
efSearch: vectorConfig.efSearch
|
|
585
633
|
});
|
|
586
634
|
const normalizedText = normalizeScores(textResult.scored);
|
|
587
635
|
const normalizedVector = normalizeScores(vectorResult.scored);
|
|
@@ -636,7 +684,8 @@ function runVectorOnly(partition, params) {
|
|
|
636
684
|
value: vectorConfig.value,
|
|
637
685
|
k: params.limit ?? 10,
|
|
638
686
|
similarity: vectorConfig.similarity,
|
|
639
|
-
metric: vectorConfig.metric
|
|
687
|
+
metric: vectorConfig.metric,
|
|
688
|
+
efSearch: vectorConfig.efSearch
|
|
640
689
|
});
|
|
641
690
|
return result;
|
|
642
691
|
}
|
|
@@ -680,8 +729,8 @@ function collectGlobalStats(manager) {
|
|
|
680
729
|
}
|
|
681
730
|
|
|
682
731
|
// src/partitioning/fan-out.ts
|
|
683
|
-
function fanOutQuery(manager, params, language, schema, config, searchOptions) {
|
|
684
|
-
const partitions = manager.getAllPartitions();
|
|
732
|
+
async function fanOutQuery(manager, params, language, schema, config, searchOptions) {
|
|
733
|
+
const partitions = config.partitionIds ? config.partitionIds.map((id) => manager.partitionAt(id)).filter((p) => p !== void 0) : manager.getAllPartitions();
|
|
685
734
|
if (partitions.length === 0) {
|
|
686
735
|
return { scored: [], totalMatched: 0 };
|
|
687
736
|
}
|
|
@@ -693,7 +742,16 @@ function fanOutQuery(manager, params, language, schema, config, searchOptions) {
|
|
|
693
742
|
globalStats = config.globalStats;
|
|
694
743
|
}
|
|
695
744
|
const options = buildSearchOptions(searchOptions, globalStats);
|
|
696
|
-
|
|
745
|
+
if (partitions.length === 1 && !config.dispatcher) {
|
|
746
|
+
const result = dispatchSinglePartition(partitions[0], params, language, schema, options);
|
|
747
|
+
let facets2;
|
|
748
|
+
if (params.facets) {
|
|
749
|
+
const matchingDocIds = new Set(result.scored.map((doc) => doc.docId));
|
|
750
|
+
facets2 = partitions[0].computeFacets(matchingDocIds, params.facets, schema);
|
|
751
|
+
}
|
|
752
|
+
return { scored: result.scored, totalMatched: result.totalMatched, facets: facets2 };
|
|
753
|
+
}
|
|
754
|
+
const outcomes = await dispatchToAllPartitions(partitions, params, language, schema, options, config.dispatcher);
|
|
697
755
|
const allScoredArrays = outcomes.map((o) => o.result.scored);
|
|
698
756
|
const merged = kWayMerge(allScoredArrays);
|
|
699
757
|
let totalMatched = 0;
|
|
@@ -718,13 +776,19 @@ function buildSearchOptions(base, globalStats) {
|
|
|
718
776
|
globalStats: globalStats ?? base?.globalStats
|
|
719
777
|
};
|
|
720
778
|
}
|
|
721
|
-
function dispatchToAllPartitions(partitions, params, language, schema, options) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
779
|
+
function dispatchToAllPartitions(partitions, params, language, schema, options, dispatcher) {
|
|
780
|
+
if (!dispatcher) {
|
|
781
|
+
const outcomes = [];
|
|
782
|
+
for (const partition of partitions) {
|
|
783
|
+
const result = dispatchSinglePartition(partition, params, language, schema, options);
|
|
784
|
+
outcomes.push({ result, partition });
|
|
785
|
+
}
|
|
786
|
+
return outcomes;
|
|
726
787
|
}
|
|
727
|
-
|
|
788
|
+
const promises = partitions.map(
|
|
789
|
+
(partition) => dispatcher.dispatch(partition, params, language, schema, options).then((result) => ({ result, partition }))
|
|
790
|
+
);
|
|
791
|
+
return Promise.all(promises);
|
|
728
792
|
}
|
|
729
793
|
function dispatchSinglePartition(partition, params, language, schema, options) {
|
|
730
794
|
const isHybridMode = params.mode === "hybrid";
|
|
@@ -741,7 +805,8 @@ function dispatchSinglePartition(partition, params, language, schema, options) {
|
|
|
741
805
|
value: vectorConfig.value,
|
|
742
806
|
k: params.limit ?? 10,
|
|
743
807
|
similarity: vectorConfig.similarity,
|
|
744
|
-
metric: vectorConfig.metric
|
|
808
|
+
metric: vectorConfig.metric,
|
|
809
|
+
efSearch: vectorConfig.efSearch
|
|
745
810
|
});
|
|
746
811
|
}
|
|
747
812
|
return fulltextSearch(partition, params, language, schema, options);
|
|
@@ -872,6 +937,283 @@ function heapPop(heap) {
|
|
|
872
937
|
return top;
|
|
873
938
|
}
|
|
874
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
|
+
if (base.endsWith(".mjs") || base.endsWith(".js")) {
|
|
1001
|
+
const distIndex = base.lastIndexOf("/dist/");
|
|
1002
|
+
if (distIndex !== -1) {
|
|
1003
|
+
return new URL("vector/hnsw-build-worker.mjs", base.slice(0, distIndex + 6)).href;
|
|
1004
|
+
}
|
|
1005
|
+
return new URL("./hnsw-build-worker.mjs", base).href;
|
|
1006
|
+
}
|
|
1007
|
+
return base.replace(/\/src\/vector\/[^/]+$/, "/dist/vector/hnsw-build-worker.mjs");
|
|
1008
|
+
}
|
|
1009
|
+
function spawnNodeWorker(request, onComplete, onError) {
|
|
1010
|
+
const entryUrl = resolveWorkerUrl();
|
|
1011
|
+
let worker = null;
|
|
1012
|
+
import('worker_threads').then((wt) => {
|
|
1013
|
+
worker = new wt.Worker(new URL(entryUrl));
|
|
1014
|
+
worker.on("message", (msg) => {
|
|
1015
|
+
const response = msg;
|
|
1016
|
+
worker?.terminate();
|
|
1017
|
+
if (response.type === "success") {
|
|
1018
|
+
onComplete(response.graph);
|
|
1019
|
+
} else {
|
|
1020
|
+
onError(new Error(response.message));
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
worker.on("error", (err) => {
|
|
1024
|
+
worker?.terminate();
|
|
1025
|
+
onError(err instanceof Error ? err : new Error(String(err)));
|
|
1026
|
+
});
|
|
1027
|
+
worker.postMessage(request);
|
|
1028
|
+
}).catch(onError);
|
|
1029
|
+
return {
|
|
1030
|
+
terminate() {
|
|
1031
|
+
worker?.terminate();
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function spawnWebWorker(request, onComplete, onError) {
|
|
1036
|
+
const entryUrl = resolveWorkerUrl();
|
|
1037
|
+
const WorkerCtor = globalThis.Worker;
|
|
1038
|
+
const worker = new WorkerCtor(entryUrl, { type: "module" });
|
|
1039
|
+
worker.onmessage = (event) => {
|
|
1040
|
+
const response = event.data;
|
|
1041
|
+
worker.terminate();
|
|
1042
|
+
if (response.type === "success") {
|
|
1043
|
+
onComplete(response.graph);
|
|
1044
|
+
} else {
|
|
1045
|
+
onError(new Error(response.message));
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
worker.onerror = (event) => {
|
|
1049
|
+
worker.terminate();
|
|
1050
|
+
onError(event.error ?? new Error(event.message ?? "Web Worker error"));
|
|
1051
|
+
};
|
|
1052
|
+
worker.postMessage(request);
|
|
1053
|
+
return {
|
|
1054
|
+
terminate() {
|
|
1055
|
+
worker.terminate();
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function createVectorPromoter(config) {
|
|
1060
|
+
const threshold = config?.promotionThreshold ?? 1e4;
|
|
1061
|
+
const hnswConfig = config?.hnswConfig;
|
|
1062
|
+
const promoting = /* @__PURE__ */ new Set();
|
|
1063
|
+
const strategy = config?.workerStrategy ?? detectWorkerStrategy();
|
|
1064
|
+
const pendingWorkers = /* @__PURE__ */ new Set();
|
|
1065
|
+
return {
|
|
1066
|
+
get strategy() {
|
|
1067
|
+
return strategy;
|
|
1068
|
+
},
|
|
1069
|
+
check(engines) {
|
|
1070
|
+
for (const [field, engine] of engines) {
|
|
1071
|
+
if (engine.isPromoted || promoting.has(field)) continue;
|
|
1072
|
+
if (engine.size < threshold) continue;
|
|
1073
|
+
promoting.add(field);
|
|
1074
|
+
if (strategy === "worker-threads") {
|
|
1075
|
+
const request = buildWorkerRequest(engine, hnswConfig);
|
|
1076
|
+
const handle = spawnNodeWorker(
|
|
1077
|
+
request,
|
|
1078
|
+
(graph) => {
|
|
1079
|
+
pendingWorkers.delete(handle);
|
|
1080
|
+
if (!engine.isPromoted) {
|
|
1081
|
+
engine.deserializeHNSW(graph);
|
|
1082
|
+
}
|
|
1083
|
+
promoting.delete(field);
|
|
1084
|
+
},
|
|
1085
|
+
() => {
|
|
1086
|
+
pendingWorkers.delete(handle);
|
|
1087
|
+
promoting.delete(field);
|
|
1088
|
+
}
|
|
1089
|
+
);
|
|
1090
|
+
pendingWorkers.add(handle);
|
|
1091
|
+
} else if (strategy === "web-worker") {
|
|
1092
|
+
const request = buildWorkerRequest(engine, hnswConfig);
|
|
1093
|
+
const handle = spawnWebWorker(
|
|
1094
|
+
request,
|
|
1095
|
+
(graph) => {
|
|
1096
|
+
pendingWorkers.delete(handle);
|
|
1097
|
+
if (!engine.isPromoted) {
|
|
1098
|
+
engine.deserializeHNSW(graph);
|
|
1099
|
+
}
|
|
1100
|
+
promoting.delete(field);
|
|
1101
|
+
},
|
|
1102
|
+
() => {
|
|
1103
|
+
pendingWorkers.delete(handle);
|
|
1104
|
+
promoting.delete(field);
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
pendingWorkers.add(handle);
|
|
1108
|
+
} else {
|
|
1109
|
+
try {
|
|
1110
|
+
engine.promoteToHNSW(hnswConfig);
|
|
1111
|
+
} finally {
|
|
1112
|
+
promoting.delete(field);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
shutdown() {
|
|
1118
|
+
for (const handle of pendingWorkers) {
|
|
1119
|
+
handle.terminate();
|
|
1120
|
+
}
|
|
1121
|
+
pendingWorkers.clear();
|
|
1122
|
+
promoting.clear();
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function encodeRawPayloadV2(wire) {
|
|
1127
|
+
return encode(wire);
|
|
1128
|
+
}
|
|
1129
|
+
var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
|
|
1130
|
+
function validateHnswMetric(value) {
|
|
1131
|
+
if (value === void 0 || value === null) return void 0;
|
|
1132
|
+
if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
|
|
1133
|
+
return value;
|
|
1134
|
+
}
|
|
1135
|
+
return void 0;
|
|
1136
|
+
}
|
|
1137
|
+
function deserializePayloadV2(data) {
|
|
1138
|
+
const raw = decode(data);
|
|
1139
|
+
const documents = {};
|
|
1140
|
+
for (const [docId, doc] of Object.entries(raw.documents ?? {})) {
|
|
1141
|
+
documents[docId] = {
|
|
1142
|
+
fields: doc.fields,
|
|
1143
|
+
fieldLengths: doc.field_lengths ?? {}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
const fieldNames = raw.inverted_index?.field_names ?? [];
|
|
1147
|
+
const invertedIndex = {};
|
|
1148
|
+
for (const [token, list] of Object.entries(raw.inverted_index?.entries ?? {})) {
|
|
1149
|
+
const count = list.ids.length;
|
|
1150
|
+
const postings = new Array(count);
|
|
1151
|
+
for (let i = 0; i < count; i++) {
|
|
1152
|
+
postings[i] = {
|
|
1153
|
+
docId: list.ids[i],
|
|
1154
|
+
termFrequency: list.tf[i],
|
|
1155
|
+
field: fieldNames[list.fi[i]] ?? "",
|
|
1156
|
+
positions: list.pos ? list.pos[i] : []
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
invertedIndex[token] = {
|
|
1160
|
+
docFrequency: list.df,
|
|
1161
|
+
postings
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const numeric = {};
|
|
1165
|
+
for (const [field, entries] of Object.entries(raw.field_indexes?.numeric ?? {})) {
|
|
1166
|
+
numeric[field] = entries.map((e) => ({ value: e.value, docId: e.doc_id }));
|
|
1167
|
+
}
|
|
1168
|
+
const boolean = {};
|
|
1169
|
+
for (const [field, idx] of Object.entries(raw.field_indexes?.boolean ?? {})) {
|
|
1170
|
+
boolean[field] = { trueDocs: idx.true_docs ?? [], falseDocs: idx.false_docs ?? [] };
|
|
1171
|
+
}
|
|
1172
|
+
const geopoint = {};
|
|
1173
|
+
for (const [field, entries] of Object.entries(raw.field_indexes?.geopoint ?? {})) {
|
|
1174
|
+
geopoint[field] = entries.map((e) => ({ lat: e.lat, lon: e.lon, docId: e.doc_id }));
|
|
1175
|
+
}
|
|
1176
|
+
const vectorData = {};
|
|
1177
|
+
for (const [field, data2] of Object.entries(raw.vector_data ?? {})) {
|
|
1178
|
+
vectorData[field] = {
|
|
1179
|
+
dimension: data2.dimension,
|
|
1180
|
+
vectors: (data2.vectors ?? []).map((v) => ({ docId: v.doc_id, vector: v.vector })),
|
|
1181
|
+
hnswGraph: data2.hnsw_graph ? {
|
|
1182
|
+
entryPoint: data2.hnsw_graph.entry_point,
|
|
1183
|
+
maxLayer: data2.hnsw_graph.max_layer,
|
|
1184
|
+
m: data2.hnsw_graph.m,
|
|
1185
|
+
efConstruction: data2.hnsw_graph.ef_construction,
|
|
1186
|
+
metric: validateHnswMetric(data2.hnsw_graph.metric),
|
|
1187
|
+
nodes: data2.hnsw_graph.nodes
|
|
1188
|
+
} : null
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
return {
|
|
1192
|
+
indexName: raw.index_name,
|
|
1193
|
+
partitionId: raw.partition_id,
|
|
1194
|
+
totalPartitions: raw.total_partitions,
|
|
1195
|
+
language: raw.language ?? "english",
|
|
1196
|
+
schema: raw.schema ?? {},
|
|
1197
|
+
docCount: raw.doc_count ?? 0,
|
|
1198
|
+
avgDocLength: raw.avg_doc_length ?? 0,
|
|
1199
|
+
documents,
|
|
1200
|
+
invertedIndex,
|
|
1201
|
+
fieldIndexes: {
|
|
1202
|
+
numeric,
|
|
1203
|
+
boolean,
|
|
1204
|
+
enum: raw.field_indexes?.enum ?? {},
|
|
1205
|
+
geopoint
|
|
1206
|
+
},
|
|
1207
|
+
vectorData,
|
|
1208
|
+
statistics: {
|
|
1209
|
+
totalDocuments: raw.statistics?.total_documents ?? 0,
|
|
1210
|
+
totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
|
|
1211
|
+
averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
|
|
1212
|
+
docFrequencies: raw.statistics?.doc_frequencies ?? {}
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
875
1217
|
// src/core/document-store.ts
|
|
876
1218
|
function createDocumentStore() {
|
|
877
1219
|
const docs = /* @__PURE__ */ new Map();
|
|
@@ -879,6 +1221,9 @@ function createDocumentStore() {
|
|
|
879
1221
|
store(docId, document, fieldLengths) {
|
|
880
1222
|
docs.set(docId, { fields: structuredClone(document), fieldLengths });
|
|
881
1223
|
},
|
|
1224
|
+
storeRef(docId, document, fieldLengths) {
|
|
1225
|
+
docs.set(docId, { fields: document, fieldLengths });
|
|
1226
|
+
},
|
|
882
1227
|
get(docId) {
|
|
883
1228
|
return docs.get(docId);
|
|
884
1229
|
},
|
|
@@ -914,7 +1259,10 @@ function createDocumentStore() {
|
|
|
914
1259
|
}
|
|
915
1260
|
|
|
916
1261
|
// src/core/inverted-index.ts
|
|
917
|
-
|
|
1262
|
+
var INITIAL_CAPACITY = 8;
|
|
1263
|
+
var MAX_TERM_FREQUENCY = 65535;
|
|
1264
|
+
var COMPACTION_THRESHOLD = 0.3;
|
|
1265
|
+
function createInvertedIndex(fieldNameTable) {
|
|
918
1266
|
const index = /* @__PURE__ */ new Map();
|
|
919
1267
|
const charBuckets = /* @__PURE__ */ new Map();
|
|
920
1268
|
function trackToken(token) {
|
|
@@ -953,33 +1301,112 @@ function createInvertedIndex() {
|
|
|
953
1301
|
}
|
|
954
1302
|
return filtered;
|
|
955
1303
|
}
|
|
1304
|
+
function getOrCreateList(token) {
|
|
1305
|
+
let list = index.get(token);
|
|
1306
|
+
if (!list) {
|
|
1307
|
+
list = {
|
|
1308
|
+
length: 0,
|
|
1309
|
+
docIds: [],
|
|
1310
|
+
termFrequencies: new Uint16Array(INITIAL_CAPACITY),
|
|
1311
|
+
fieldNameIndices: new Uint8Array(INITIAL_CAPACITY),
|
|
1312
|
+
positions: null,
|
|
1313
|
+
docIdSet: /* @__PURE__ */ new Set(),
|
|
1314
|
+
deletedDocs: /* @__PURE__ */ new Set()
|
|
1315
|
+
};
|
|
1316
|
+
index.set(token, list);
|
|
1317
|
+
trackToken(token);
|
|
1318
|
+
}
|
|
1319
|
+
return list;
|
|
1320
|
+
}
|
|
1321
|
+
function growTypedArrays(list) {
|
|
1322
|
+
const newCap = list.termFrequencies.length * 2;
|
|
1323
|
+
const newTF = new Uint16Array(newCap);
|
|
1324
|
+
newTF.set(list.termFrequencies);
|
|
1325
|
+
list.termFrequencies = newTF;
|
|
1326
|
+
const newFNI = new Uint8Array(newCap);
|
|
1327
|
+
newFNI.set(list.fieldNameIndices);
|
|
1328
|
+
list.fieldNameIndices = newFNI;
|
|
1329
|
+
}
|
|
1330
|
+
function compactList(list) {
|
|
1331
|
+
if (list.deletedDocs.size === 0) return;
|
|
1332
|
+
let writeIdx = 0;
|
|
1333
|
+
for (let i = 0; i < list.length; i++) {
|
|
1334
|
+
if (!list.deletedDocs.has(list.docIds[i])) {
|
|
1335
|
+
if (writeIdx !== i) {
|
|
1336
|
+
list.docIds[writeIdx] = list.docIds[i];
|
|
1337
|
+
list.termFrequencies[writeIdx] = list.termFrequencies[i];
|
|
1338
|
+
list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
|
|
1339
|
+
if (list.positions) {
|
|
1340
|
+
list.positions[writeIdx] = list.positions[i];
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
writeIdx++;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
list.docIds.length = writeIdx;
|
|
1347
|
+
if (list.positions) list.positions.length = writeIdx;
|
|
1348
|
+
list.length = writeIdx;
|
|
1349
|
+
list.deletedDocs.clear();
|
|
1350
|
+
}
|
|
1351
|
+
function compactDocEntries(list, docId) {
|
|
1352
|
+
let writeIdx = 0;
|
|
1353
|
+
for (let i = 0; i < list.length; i++) {
|
|
1354
|
+
if (list.docIds[i] !== docId) {
|
|
1355
|
+
if (writeIdx !== i) {
|
|
1356
|
+
list.docIds[writeIdx] = list.docIds[i];
|
|
1357
|
+
list.termFrequencies[writeIdx] = list.termFrequencies[i];
|
|
1358
|
+
list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
|
|
1359
|
+
if (list.positions) {
|
|
1360
|
+
list.positions[writeIdx] = list.positions[i];
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
writeIdx++;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
list.docIds.length = writeIdx;
|
|
1367
|
+
if (list.positions) list.positions.length = writeIdx;
|
|
1368
|
+
list.length = writeIdx;
|
|
1369
|
+
}
|
|
956
1370
|
return {
|
|
957
|
-
insert(token,
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
list
|
|
961
|
-
|
|
962
|
-
trackToken(token);
|
|
1371
|
+
insert(token, docId, termFrequency, fieldNameIndex, positions) {
|
|
1372
|
+
const list = getOrCreateList(token);
|
|
1373
|
+
if (list.deletedDocs.size > 0 && list.deletedDocs.has(docId)) {
|
|
1374
|
+
compactDocEntries(list, docId);
|
|
1375
|
+
list.deletedDocs.delete(docId);
|
|
963
1376
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1377
|
+
if (list.length >= list.termFrequencies.length) {
|
|
1378
|
+
growTypedArrays(list);
|
|
1379
|
+
}
|
|
1380
|
+
const idx = list.length;
|
|
1381
|
+
list.docIds.push(docId);
|
|
1382
|
+
list.termFrequencies[idx] = termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : termFrequency;
|
|
1383
|
+
list.fieldNameIndices[idx] = fieldNameIndex;
|
|
1384
|
+
if (positions !== null) {
|
|
1385
|
+
if (!list.positions) {
|
|
1386
|
+
list.positions = [];
|
|
1387
|
+
for (let j = 0; j < idx; j++) list.positions.push([]);
|
|
1388
|
+
}
|
|
1389
|
+
list.positions.push(positions);
|
|
1390
|
+
} else if (list.positions !== null) {
|
|
1391
|
+
list.positions.push([]);
|
|
1392
|
+
}
|
|
1393
|
+
if (!list.docIdSet.has(docId)) {
|
|
1394
|
+
list.docIdSet.add(docId);
|
|
968
1395
|
}
|
|
969
|
-
|
|
970
|
-
list.postings.push(entry);
|
|
971
|
-
if (isNewDoc) list.docFrequency++;
|
|
1396
|
+
list.length++;
|
|
972
1397
|
},
|
|
973
1398
|
remove(token, docId) {
|
|
974
1399
|
const list = index.get(token);
|
|
975
|
-
if (!list) return;
|
|
976
|
-
|
|
977
|
-
list.
|
|
978
|
-
|
|
979
|
-
if (removed > 0) list.docFrequency--;
|
|
980
|
-
if (list.postings.length === 0) {
|
|
1400
|
+
if (!list || !list.docIdSet.has(docId)) return;
|
|
1401
|
+
list.docIdSet.delete(docId);
|
|
1402
|
+
list.deletedDocs.add(docId);
|
|
1403
|
+
if (list.docIdSet.size === 0) {
|
|
981
1404
|
index.delete(token);
|
|
982
1405
|
untrackToken(token);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (list.deletedDocs.size / list.length > COMPACTION_THRESHOLD) {
|
|
1409
|
+
compactList(list);
|
|
983
1410
|
}
|
|
984
1411
|
},
|
|
985
1412
|
lookup(token) {
|
|
@@ -1001,6 +1428,20 @@ function createInvertedIndex() {
|
|
|
1001
1428
|
}
|
|
1002
1429
|
return results;
|
|
1003
1430
|
},
|
|
1431
|
+
prefixSearch(prefix, limit) {
|
|
1432
|
+
if (prefix.length === 0 || limit <= 0) return [];
|
|
1433
|
+
const candidates = candidatesForPrefix(prefix, prefix.length);
|
|
1434
|
+
const results = [];
|
|
1435
|
+
for (const term of candidates) {
|
|
1436
|
+
if (!term.startsWith(prefix)) continue;
|
|
1437
|
+
const list = index.get(term);
|
|
1438
|
+
if (!list) continue;
|
|
1439
|
+
results.push({ term, documentFrequency: list.docIdSet.size });
|
|
1440
|
+
}
|
|
1441
|
+
results.sort((a, b) => b.documentFrequency - a.documentFrequency);
|
|
1442
|
+
if (results.length > limit) results.length = limit;
|
|
1443
|
+
return results;
|
|
1444
|
+
},
|
|
1004
1445
|
has(token) {
|
|
1005
1446
|
return index.has(token);
|
|
1006
1447
|
},
|
|
@@ -1017,7 +1458,19 @@ function createInvertedIndex() {
|
|
|
1017
1458
|
serialize() {
|
|
1018
1459
|
const result = /* @__PURE__ */ Object.create(null);
|
|
1019
1460
|
for (const [token, list] of index) {
|
|
1020
|
-
|
|
1461
|
+
const postings = [];
|
|
1462
|
+
for (let i = 0; i < list.length; i++) {
|
|
1463
|
+
if (list.deletedDocs.size > 0 && list.deletedDocs.has(list.docIds[i])) continue;
|
|
1464
|
+
postings.push({
|
|
1465
|
+
docId: list.docIds[i],
|
|
1466
|
+
termFrequency: list.termFrequencies[i],
|
|
1467
|
+
fieldName: fieldNameTable.names[list.fieldNameIndices[i]],
|
|
1468
|
+
positions: list.positions ? list.positions[i] : []
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
if (postings.length > 0) {
|
|
1472
|
+
result[token] = { docFrequency: list.docIdSet.size, postings };
|
|
1473
|
+
}
|
|
1021
1474
|
}
|
|
1022
1475
|
return result;
|
|
1023
1476
|
},
|
|
@@ -1025,7 +1478,43 @@ function createInvertedIndex() {
|
|
|
1025
1478
|
index.clear();
|
|
1026
1479
|
charBuckets.clear();
|
|
1027
1480
|
for (const token of Object.keys(data)) {
|
|
1028
|
-
|
|
1481
|
+
const src = data[token];
|
|
1482
|
+
const count = src.postings.length;
|
|
1483
|
+
const docIdSet = /* @__PURE__ */ new Set();
|
|
1484
|
+
const docIds = new Array(count);
|
|
1485
|
+
const termFrequencies = new Uint16Array(count);
|
|
1486
|
+
const fieldNameIndices = new Uint8Array(count);
|
|
1487
|
+
let hasPositions = false;
|
|
1488
|
+
for (let i = 0; i < count; i++) {
|
|
1489
|
+
const p = src.postings[i];
|
|
1490
|
+
docIds[i] = p.docId;
|
|
1491
|
+
termFrequencies[i] = p.termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : p.termFrequency;
|
|
1492
|
+
let fnIndex = fieldNameTable.indexMap.get(p.fieldName);
|
|
1493
|
+
if (fnIndex === void 0) {
|
|
1494
|
+
fnIndex = fieldNameTable.names.length;
|
|
1495
|
+
fieldNameTable.names.push(p.fieldName);
|
|
1496
|
+
fieldNameTable.indexMap.set(p.fieldName, fnIndex);
|
|
1497
|
+
}
|
|
1498
|
+
fieldNameIndices[i] = fnIndex;
|
|
1499
|
+
docIdSet.add(p.docId);
|
|
1500
|
+
if (p.positions.length > 0) hasPositions = true;
|
|
1501
|
+
}
|
|
1502
|
+
let positions = null;
|
|
1503
|
+
if (hasPositions) {
|
|
1504
|
+
positions = new Array(count);
|
|
1505
|
+
for (let i = 0; i < count; i++) {
|
|
1506
|
+
positions[i] = src.postings[i].positions;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
index.set(token, {
|
|
1510
|
+
length: count,
|
|
1511
|
+
docIds,
|
|
1512
|
+
termFrequencies,
|
|
1513
|
+
fieldNameIndices,
|
|
1514
|
+
positions,
|
|
1515
|
+
docIdSet,
|
|
1516
|
+
deletedDocs: /* @__PURE__ */ new Set()
|
|
1517
|
+
});
|
|
1029
1518
|
trackToken(token);
|
|
1030
1519
|
}
|
|
1031
1520
|
}
|
|
@@ -1047,9 +1536,9 @@ function collectUniqueTokens(tokens) {
|
|
|
1047
1536
|
function createPartitionStats() {
|
|
1048
1537
|
const stats = {
|
|
1049
1538
|
totalDocuments: 0,
|
|
1050
|
-
totalFieldLengths:
|
|
1051
|
-
averageFieldLengths:
|
|
1052
|
-
docFrequencies:
|
|
1539
|
+
totalFieldLengths: /* @__PURE__ */ Object.create(null),
|
|
1540
|
+
averageFieldLengths: /* @__PURE__ */ Object.create(null),
|
|
1541
|
+
docFrequencies: /* @__PURE__ */ Object.create(null),
|
|
1053
1542
|
addDocument(fieldLengths, tokens) {
|
|
1054
1543
|
stats.totalDocuments++;
|
|
1055
1544
|
const fields = Object.keys(fieldLengths);
|
|
@@ -1089,7 +1578,7 @@ function createPartitionStats() {
|
|
|
1089
1578
|
},
|
|
1090
1579
|
recalculateAverages() {
|
|
1091
1580
|
const fields = Object.keys(stats.totalFieldLengths);
|
|
1092
|
-
const newAverages =
|
|
1581
|
+
const newAverages = /* @__PURE__ */ Object.create(null);
|
|
1093
1582
|
for (let i = 0; i < fields.length; i++) {
|
|
1094
1583
|
const field = fields[i];
|
|
1095
1584
|
newAverages[field] = stats.totalDocuments > 0 ? stats.totalFieldLengths[field] / stats.totalDocuments : 0;
|
|
@@ -1097,18 +1586,42 @@ function createPartitionStats() {
|
|
|
1097
1586
|
stats.averageFieldLengths = newAverages;
|
|
1098
1587
|
},
|
|
1099
1588
|
serialize() {
|
|
1589
|
+
const tfl = /* @__PURE__ */ Object.create(null);
|
|
1590
|
+
for (const key of Object.keys(stats.totalFieldLengths)) {
|
|
1591
|
+
tfl[key] = stats.totalFieldLengths[key];
|
|
1592
|
+
}
|
|
1593
|
+
const afl = /* @__PURE__ */ Object.create(null);
|
|
1594
|
+
for (const key of Object.keys(stats.averageFieldLengths)) {
|
|
1595
|
+
afl[key] = stats.averageFieldLengths[key];
|
|
1596
|
+
}
|
|
1597
|
+
const df = /* @__PURE__ */ Object.create(null);
|
|
1598
|
+
for (const key of Object.keys(stats.docFrequencies)) {
|
|
1599
|
+
df[key] = stats.docFrequencies[key];
|
|
1600
|
+
}
|
|
1100
1601
|
return {
|
|
1101
1602
|
totalDocuments: stats.totalDocuments,
|
|
1102
|
-
totalFieldLengths:
|
|
1103
|
-
averageFieldLengths:
|
|
1104
|
-
docFrequencies:
|
|
1603
|
+
totalFieldLengths: tfl,
|
|
1604
|
+
averageFieldLengths: afl,
|
|
1605
|
+
docFrequencies: df
|
|
1105
1606
|
};
|
|
1106
1607
|
},
|
|
1107
1608
|
deserialize(data) {
|
|
1108
1609
|
stats.totalDocuments = data.totalDocuments;
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1610
|
+
const tfl = /* @__PURE__ */ Object.create(null);
|
|
1611
|
+
for (const key of Object.keys(data.totalFieldLengths)) {
|
|
1612
|
+
tfl[key] = data.totalFieldLengths[key];
|
|
1613
|
+
}
|
|
1614
|
+
stats.totalFieldLengths = tfl;
|
|
1615
|
+
const afl = /* @__PURE__ */ Object.create(null);
|
|
1616
|
+
for (const key of Object.keys(data.averageFieldLengths)) {
|
|
1617
|
+
afl[key] = data.averageFieldLengths[key];
|
|
1618
|
+
}
|
|
1619
|
+
stats.averageFieldLengths = afl;
|
|
1620
|
+
const df = /* @__PURE__ */ Object.create(null);
|
|
1621
|
+
for (const key of Object.keys(data.docFrequencies)) {
|
|
1622
|
+
df[key] = data.docFrequencies[key];
|
|
1623
|
+
}
|
|
1624
|
+
stats.docFrequencies = df;
|
|
1112
1625
|
}
|
|
1113
1626
|
};
|
|
1114
1627
|
return stats;
|
|
@@ -1259,34 +1772,6 @@ function createGeoIndex() {
|
|
|
1259
1772
|
};
|
|
1260
1773
|
}
|
|
1261
1774
|
|
|
1262
|
-
// src/vector/similarity.ts
|
|
1263
|
-
function magnitude(v) {
|
|
1264
|
-
let sum = 0;
|
|
1265
|
-
for (let i = 0; i < v.length; i++) {
|
|
1266
|
-
sum += v[i] * v[i];
|
|
1267
|
-
}
|
|
1268
|
-
return Math.sqrt(sum);
|
|
1269
|
-
}
|
|
1270
|
-
function cosineSimilarityWithMagnitudes(a, b, magA, magB) {
|
|
1271
|
-
if (magA === 0 || magB === 0) return 0;
|
|
1272
|
-
return dotProduct(a, b) / (magA * magB);
|
|
1273
|
-
}
|
|
1274
|
-
function dotProduct(a, b) {
|
|
1275
|
-
let sum = 0;
|
|
1276
|
-
for (let i = 0; i < a.length; i++) {
|
|
1277
|
-
sum += a[i] * b[i];
|
|
1278
|
-
}
|
|
1279
|
-
return sum;
|
|
1280
|
-
}
|
|
1281
|
-
function euclideanDistance(a, b) {
|
|
1282
|
-
let sum = 0;
|
|
1283
|
-
for (let i = 0; i < a.length; i++) {
|
|
1284
|
-
const diff = a[i] - b[i];
|
|
1285
|
-
sum += diff * diff;
|
|
1286
|
-
}
|
|
1287
|
-
return Math.sqrt(sum);
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
1775
|
// src/vector/brute-force.ts
|
|
1291
1776
|
function createBruteForceVectorStore(dimension) {
|
|
1292
1777
|
const vectors = /* @__PURE__ */ new Map();
|
|
@@ -1355,6 +1840,120 @@ function createBruteForceVectorStore(dimension) {
|
|
|
1355
1840
|
};
|
|
1356
1841
|
}
|
|
1357
1842
|
|
|
1843
|
+
// src/search/vector-search.ts
|
|
1844
|
+
function createVectorSearchEngine(dimension, defaultHNSWConfig) {
|
|
1845
|
+
const bruteForce = createBruteForceVectorStore(dimension);
|
|
1846
|
+
const hnswConfig = defaultHNSWConfig;
|
|
1847
|
+
let hnsw = null;
|
|
1848
|
+
return {
|
|
1849
|
+
get dimension() {
|
|
1850
|
+
return dimension;
|
|
1851
|
+
},
|
|
1852
|
+
get size() {
|
|
1853
|
+
return bruteForce.size;
|
|
1854
|
+
},
|
|
1855
|
+
get isPromoted() {
|
|
1856
|
+
return hnsw !== null;
|
|
1857
|
+
},
|
|
1858
|
+
insert(docId, vector) {
|
|
1859
|
+
bruteForce.insert(docId, vector);
|
|
1860
|
+
if (hnsw) {
|
|
1861
|
+
hnsw.insert(docId, vector);
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
remove(docId) {
|
|
1865
|
+
bruteForce.remove(docId);
|
|
1866
|
+
if (hnsw) {
|
|
1867
|
+
hnsw.remove(docId);
|
|
1868
|
+
}
|
|
1869
|
+
},
|
|
1870
|
+
has(docId) {
|
|
1871
|
+
return bruteForce.has(docId);
|
|
1872
|
+
},
|
|
1873
|
+
search(query, k, metric, minSimilarity, filterDocIds, efSearch) {
|
|
1874
|
+
if (hnsw) {
|
|
1875
|
+
return hnsw.search(query, k, metric, minSimilarity, filterDocIds, efSearch);
|
|
1876
|
+
}
|
|
1877
|
+
return bruteForce.search(query, k, metric, minSimilarity, filterDocIds);
|
|
1878
|
+
},
|
|
1879
|
+
clear() {
|
|
1880
|
+
bruteForce.clear();
|
|
1881
|
+
if (hnsw) {
|
|
1882
|
+
hnsw.clear();
|
|
1883
|
+
hnsw = null;
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
entries() {
|
|
1887
|
+
return bruteForce.entries();
|
|
1888
|
+
},
|
|
1889
|
+
promoteToHNSW(overrideConfig) {
|
|
1890
|
+
const cfg = overrideConfig ?? hnswConfig;
|
|
1891
|
+
const newHnsw = createHNSWIndex(dimension, cfg);
|
|
1892
|
+
for (const [, entry] of bruteForce.entries()) {
|
|
1893
|
+
newHnsw.insert(entry.docId, entry.vector);
|
|
1894
|
+
}
|
|
1895
|
+
hnsw = newHnsw;
|
|
1896
|
+
},
|
|
1897
|
+
demoteToLinear() {
|
|
1898
|
+
if (hnsw) {
|
|
1899
|
+
hnsw.clear();
|
|
1900
|
+
hnsw = null;
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
getHNSWIndex() {
|
|
1904
|
+
return hnsw;
|
|
1905
|
+
},
|
|
1906
|
+
getBruteForceStore() {
|
|
1907
|
+
return bruteForce;
|
|
1908
|
+
},
|
|
1909
|
+
estimateMemoryBytes() {
|
|
1910
|
+
const count = bruteForce.size;
|
|
1911
|
+
if (count === 0) return 0;
|
|
1912
|
+
const MAP_OVERHEAD = 64;
|
|
1913
|
+
const MAP_ENTRY = 72;
|
|
1914
|
+
const VECTOR_ENTRY_OBJ = 32;
|
|
1915
|
+
const AVG_DOCID_BYTES = 56;
|
|
1916
|
+
const TYPED_ARRAY_HEADER = 64;
|
|
1917
|
+
const MAGNITUDE_BYTES = 8;
|
|
1918
|
+
const perBruteForceEntry = MAP_ENTRY + VECTOR_ENTRY_OBJ + AVG_DOCID_BYTES + TYPED_ARRAY_HEADER + MAGNITUDE_BYTES;
|
|
1919
|
+
let bytes = MAP_OVERHEAD + count * (perBruteForceEntry + dimension * 4);
|
|
1920
|
+
if (hnsw) {
|
|
1921
|
+
const HNSW_NODE_OBJ = 64;
|
|
1922
|
+
const SHARED_REFS = 16;
|
|
1923
|
+
const CONN_ARRAY_HEADER = 32;
|
|
1924
|
+
const SET_OVERHEAD = 64;
|
|
1925
|
+
const SET_ENTRY_COST = 72;
|
|
1926
|
+
const m = hnsw.m;
|
|
1927
|
+
const avgLayers = m / (m - 1);
|
|
1928
|
+
const avgConnsLayer0 = m;
|
|
1929
|
+
const avgConnsUpper = Math.ceil(m / 2);
|
|
1930
|
+
const connMemPerNode = CONN_ARRAY_HEADER + (SET_OVERHEAD + avgConnsLayer0 * SET_ENTRY_COST) + Math.max(0, avgLayers - 1) * (SET_OVERHEAD + avgConnsUpper * SET_ENTRY_COST);
|
|
1931
|
+
const perHnswNode = MAP_ENTRY + HNSW_NODE_OBJ + SHARED_REFS + connMemPerNode;
|
|
1932
|
+
bytes += MAP_OVERHEAD + count * perHnswNode;
|
|
1933
|
+
}
|
|
1934
|
+
return Math.round(bytes);
|
|
1935
|
+
},
|
|
1936
|
+
serializeHNSW() {
|
|
1937
|
+
if (!hnsw) return null;
|
|
1938
|
+
return hnsw.serialize();
|
|
1939
|
+
},
|
|
1940
|
+
deserializeHNSW(data) {
|
|
1941
|
+
const cfg = {};
|
|
1942
|
+
const restoredHnsw = createHNSWIndex(dimension, {
|
|
1943
|
+
m: data.m ?? cfg.m,
|
|
1944
|
+
efConstruction: data.efConstruction ?? cfg.efConstruction,
|
|
1945
|
+
metric: data.metric ?? cfg.metric
|
|
1946
|
+
});
|
|
1947
|
+
const vectorMap = /* @__PURE__ */ new Map();
|
|
1948
|
+
for (const [, entry] of bruteForce.entries()) {
|
|
1949
|
+
vectorMap.set(entry.docId, { vector: entry.vector, mag: entry.magnitude });
|
|
1950
|
+
}
|
|
1951
|
+
restoredHnsw.deserialize(data, vectorMap);
|
|
1952
|
+
hnsw = restoredHnsw;
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1358
1957
|
// src/core/field-index.ts
|
|
1359
1958
|
function lowerBound(entries, value) {
|
|
1360
1959
|
let lo = 0;
|
|
@@ -1385,12 +1984,20 @@ function collectDocIds(entries, from, to) {
|
|
|
1385
1984
|
}
|
|
1386
1985
|
function createNumericIndex() {
|
|
1387
1986
|
let entries = [];
|
|
1987
|
+
let sorted = true;
|
|
1988
|
+
function ensureSorted() {
|
|
1989
|
+
if (!sorted) {
|
|
1990
|
+
entries.sort((a, b) => a.value - b.value);
|
|
1991
|
+
sorted = true;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1388
1994
|
return {
|
|
1389
1995
|
insert(docId, value) {
|
|
1390
|
-
|
|
1391
|
-
|
|
1996
|
+
entries.push({ value, docId });
|
|
1997
|
+
sorted = false;
|
|
1392
1998
|
},
|
|
1393
1999
|
remove(docId, value) {
|
|
2000
|
+
ensureSorted();
|
|
1394
2001
|
const start = lowerBound(entries, value);
|
|
1395
2002
|
const end = upperBound(entries, value);
|
|
1396
2003
|
for (let i = start; i < end; i++) {
|
|
@@ -1401,9 +2008,11 @@ function createNumericIndex() {
|
|
|
1401
2008
|
}
|
|
1402
2009
|
},
|
|
1403
2010
|
queryEq(value) {
|
|
2011
|
+
ensureSorted();
|
|
1404
2012
|
return collectDocIds(entries, lowerBound(entries, value), upperBound(entries, value));
|
|
1405
2013
|
},
|
|
1406
2014
|
queryNe(value) {
|
|
2015
|
+
ensureSorted();
|
|
1407
2016
|
const excluded = this.queryEq(value);
|
|
1408
2017
|
const result = /* @__PURE__ */ new Set();
|
|
1409
2018
|
for (const entry of entries) {
|
|
@@ -1412,18 +2021,23 @@ function createNumericIndex() {
|
|
|
1412
2021
|
return result;
|
|
1413
2022
|
},
|
|
1414
2023
|
queryGt(value) {
|
|
2024
|
+
ensureSorted();
|
|
1415
2025
|
return collectDocIds(entries, upperBound(entries, value), entries.length);
|
|
1416
2026
|
},
|
|
1417
2027
|
queryGte(value) {
|
|
2028
|
+
ensureSorted();
|
|
1418
2029
|
return collectDocIds(entries, lowerBound(entries, value), entries.length);
|
|
1419
2030
|
},
|
|
1420
2031
|
queryLt(value) {
|
|
2032
|
+
ensureSorted();
|
|
1421
2033
|
return collectDocIds(entries, 0, lowerBound(entries, value));
|
|
1422
2034
|
},
|
|
1423
2035
|
queryLte(value) {
|
|
2036
|
+
ensureSorted();
|
|
1424
2037
|
return collectDocIds(entries, 0, upperBound(entries, value));
|
|
1425
2038
|
},
|
|
1426
2039
|
queryBetween(min, max) {
|
|
2040
|
+
ensureSorted();
|
|
1427
2041
|
return collectDocIds(entries, lowerBound(entries, min), upperBound(entries, max));
|
|
1428
2042
|
},
|
|
1429
2043
|
getAllDocIds() {
|
|
@@ -1434,12 +2048,15 @@ function createNumericIndex() {
|
|
|
1434
2048
|
},
|
|
1435
2049
|
clear() {
|
|
1436
2050
|
entries = [];
|
|
2051
|
+
sorted = true;
|
|
1437
2052
|
},
|
|
1438
2053
|
serialize() {
|
|
2054
|
+
ensureSorted();
|
|
1439
2055
|
return entries.slice();
|
|
1440
2056
|
},
|
|
1441
2057
|
deserialize(data) {
|
|
1442
2058
|
entries = data.slice();
|
|
2059
|
+
sorted = true;
|
|
1443
2060
|
}
|
|
1444
2061
|
};
|
|
1445
2062
|
}
|
|
@@ -1567,6 +2184,14 @@ function createEnumIndex() {
|
|
|
1567
2184
|
}
|
|
1568
2185
|
|
|
1569
2186
|
// src/core/partition/utils.ts
|
|
2187
|
+
function getOrCreateFieldNameIndex(table, fieldName) {
|
|
2188
|
+
const existing = table.indexMap.get(fieldName);
|
|
2189
|
+
if (existing !== void 0) return existing;
|
|
2190
|
+
const idx = table.names.length;
|
|
2191
|
+
table.names.push(fieldName);
|
|
2192
|
+
table.indexMap.set(fieldName, idx);
|
|
2193
|
+
return idx;
|
|
2194
|
+
}
|
|
1570
2195
|
var VECTOR_PATTERN2 = /^vector\[(\d+)]$/;
|
|
1571
2196
|
function getNestedValue(obj, path) {
|
|
1572
2197
|
const parts = path.split(".");
|
|
@@ -1643,7 +2268,7 @@ function ensureFieldIndex(state, fieldPath, fieldType) {
|
|
|
1643
2268
|
} else {
|
|
1644
2269
|
const dim = parseVectorDimension(fieldType);
|
|
1645
2270
|
if (dim !== null && !state.vectorStores.has(fieldPath)) {
|
|
1646
|
-
state.vectorStores.set(fieldPath,
|
|
2271
|
+
state.vectorStores.set(fieldPath, createVectorSearchEngine(dim));
|
|
1647
2272
|
}
|
|
1648
2273
|
}
|
|
1649
2274
|
}
|
|
@@ -1667,53 +2292,71 @@ function indexStringField(state, docId, fieldPath, text, language, options, fiel
|
|
|
1667
2292
|
const result = tokenize(text, language, tokenizeOptions(options));
|
|
1668
2293
|
fieldLengths[fieldPath] = result.tokens.length;
|
|
1669
2294
|
const fieldTokenList = [];
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
const
|
|
1673
|
-
if (existing) {
|
|
1674
|
-
existing.count++;
|
|
1675
|
-
existing.positions.push(t.position);
|
|
1676
|
-
} else {
|
|
1677
|
-
tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
|
|
1678
|
-
}
|
|
1679
|
-
fieldTokenList.push(t.token);
|
|
1680
|
-
}
|
|
1681
|
-
for (const [token, freq] of tokenFreqs) {
|
|
1682
|
-
state.invertedIdx.insert(token, {
|
|
1683
|
-
docId,
|
|
1684
|
-
termFrequency: freq.count,
|
|
1685
|
-
fieldName: fieldPath,
|
|
1686
|
-
positions: freq.positions
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
tokensByField[fieldPath] = fieldTokenList;
|
|
1690
|
-
}
|
|
1691
|
-
function indexStringArrayField(state, docId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
|
|
1692
|
-
const tokenFreqs = /* @__PURE__ */ new Map();
|
|
1693
|
-
const fieldTokenList = [];
|
|
1694
|
-
let positionOffset = 0;
|
|
1695
|
-
for (const item of arr) {
|
|
1696
|
-
const result = tokenize(item, language, tokenizeOptions(options));
|
|
2295
|
+
const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
|
|
2296
|
+
if (state.trackPositions) {
|
|
2297
|
+
const tokenFreqs = /* @__PURE__ */ new Map();
|
|
1697
2298
|
for (const t of result.tokens) {
|
|
1698
2299
|
const existing = tokenFreqs.get(t.token);
|
|
1699
2300
|
if (existing) {
|
|
1700
2301
|
existing.count++;
|
|
1701
|
-
existing.positions.push(
|
|
2302
|
+
existing.positions.push(t.position);
|
|
1702
2303
|
} else {
|
|
1703
|
-
tokenFreqs.set(t.token, { count: 1, positions: [
|
|
2304
|
+
tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
|
|
1704
2305
|
}
|
|
1705
2306
|
fieldTokenList.push(t.token);
|
|
1706
2307
|
}
|
|
1707
|
-
|
|
2308
|
+
for (const [token, freq] of tokenFreqs) {
|
|
2309
|
+
state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
|
|
2310
|
+
}
|
|
2311
|
+
} else {
|
|
2312
|
+
const tokenCounts = /* @__PURE__ */ new Map();
|
|
2313
|
+
for (const t of result.tokens) {
|
|
2314
|
+
tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
|
|
2315
|
+
fieldTokenList.push(t.token);
|
|
2316
|
+
}
|
|
2317
|
+
for (const [token, count] of tokenCounts) {
|
|
2318
|
+
state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
|
|
2319
|
+
}
|
|
1708
2320
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2321
|
+
tokensByField[fieldPath] = fieldTokenList;
|
|
2322
|
+
}
|
|
2323
|
+
function indexStringArrayField(state, docId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
|
|
2324
|
+
const fieldTokenList = [];
|
|
2325
|
+
const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
|
|
2326
|
+
if (state.trackPositions) {
|
|
2327
|
+
const tokenFreqs = /* @__PURE__ */ new Map();
|
|
2328
|
+
let positionOffset = 0;
|
|
2329
|
+
for (const item of arr) {
|
|
2330
|
+
const result = tokenize(item, language, tokenizeOptions(options));
|
|
2331
|
+
for (const t of result.tokens) {
|
|
2332
|
+
const existing = tokenFreqs.get(t.token);
|
|
2333
|
+
if (existing) {
|
|
2334
|
+
existing.count++;
|
|
2335
|
+
existing.positions.push(positionOffset + t.position);
|
|
2336
|
+
} else {
|
|
2337
|
+
tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
|
|
2338
|
+
}
|
|
2339
|
+
fieldTokenList.push(t.token);
|
|
2340
|
+
}
|
|
2341
|
+
positionOffset += result.tokens.length;
|
|
2342
|
+
}
|
|
2343
|
+
fieldLengths[fieldPath] = fieldTokenList.length;
|
|
2344
|
+
for (const [token, freq] of tokenFreqs) {
|
|
2345
|
+
state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
|
|
2346
|
+
}
|
|
2347
|
+
} else {
|
|
2348
|
+
const tokenCounts = /* @__PURE__ */ new Map();
|
|
2349
|
+
for (const item of arr) {
|
|
2350
|
+
const result = tokenize(item, language, tokenizeOptions(options));
|
|
2351
|
+
for (const t of result.tokens) {
|
|
2352
|
+
tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
|
|
2353
|
+
fieldTokenList.push(t.token);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
fieldLengths[fieldPath] = fieldTokenList.length;
|
|
2357
|
+
for (const [token, count] of tokenCounts) {
|
|
2358
|
+
state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
|
|
2359
|
+
}
|
|
1717
2360
|
}
|
|
1718
2361
|
tokensByField[fieldPath] = fieldTokenList;
|
|
1719
2362
|
}
|
|
@@ -2332,66 +2975,188 @@ function computeBM25WithGlobalStats(termFrequency, globalDocFrequency, globalTot
|
|
|
2332
2975
|
}
|
|
2333
2976
|
|
|
2334
2977
|
// src/core/partition/search.ts
|
|
2978
|
+
var DEFAULT_MAX_RESULTS = 1e3;
|
|
2335
2979
|
function searchFulltext(state, params) {
|
|
2336
|
-
const {
|
|
2980
|
+
const {
|
|
2981
|
+
queryTokens,
|
|
2982
|
+
fields,
|
|
2983
|
+
boost,
|
|
2984
|
+
tolerance = 0,
|
|
2985
|
+
prefixLength = 2,
|
|
2986
|
+
exact = false,
|
|
2987
|
+
bm25Params,
|
|
2988
|
+
globalStats,
|
|
2989
|
+
maxResults,
|
|
2990
|
+
termMatch
|
|
2991
|
+
} = params;
|
|
2337
2992
|
if (queryTokens.length === 0) {
|
|
2338
2993
|
return { scored: [], totalMatched: 0 };
|
|
2339
2994
|
}
|
|
2340
|
-
const docScores = /* @__PURE__ */ new Map();
|
|
2341
2995
|
const totalDocs = globalStats?.totalDocuments ?? state.stats.totalDocuments;
|
|
2342
2996
|
const avgFieldLengths = globalStats?.averageFieldLengths ?? state.stats.averageFieldLengths;
|
|
2343
2997
|
const globalDocFreqs = globalStats?.docFrequencies ?? state.stats.docFrequencies;
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2998
|
+
const scoreFn = globalStats ? computeBM25WithGlobalStats : computeBM25;
|
|
2999
|
+
const docScores = /* @__PURE__ */ new Map();
|
|
3000
|
+
const fieldLengthCache = /* @__PURE__ */ new Map();
|
|
3001
|
+
const useIntersection = termMatch === "all" && queryTokens.length > 1;
|
|
3002
|
+
const fieldNames = state.fieldNameTable.names;
|
|
3003
|
+
if (useIntersection) {
|
|
3004
|
+
const resolved = [];
|
|
3005
|
+
for (const qt of queryTokens) {
|
|
3006
|
+
const rawMatches = exact ? (() => {
|
|
3007
|
+
const postingList = state.invertedIdx.lookup(qt.token);
|
|
3008
|
+
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
3009
|
+
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
3010
|
+
let totalPostings = 0;
|
|
3011
|
+
const matches = [];
|
|
3012
|
+
for (const m of rawMatches) {
|
|
3013
|
+
const docFreq = globalStats ? globalDocFreqs[m.token] ?? m.postingList.docIdSet.size : m.postingList.docIdSet.size;
|
|
3014
|
+
const idf = computeIDF(docFreq, totalDocs);
|
|
3015
|
+
totalPostings += m.postingList.length;
|
|
3016
|
+
matches.push({ token: m.token, docFreq, idf, postingList: m.postingList });
|
|
3017
|
+
}
|
|
3018
|
+
resolved.push({ token: qt.token, matches, totalPostings });
|
|
3019
|
+
}
|
|
3020
|
+
resolved.sort((a, b) => a.totalPostings - b.totalPostings);
|
|
3021
|
+
for (let tokenIndex = 0; tokenIndex < resolved.length; tokenIndex++) {
|
|
3022
|
+
for (const match of resolved[tokenIndex].matches) {
|
|
3023
|
+
const list = match.postingList;
|
|
3024
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3025
|
+
for (let pi = 0; pi < list.length; pi++) {
|
|
3026
|
+
const docId = list.docIds[pi];
|
|
3027
|
+
if (hasDeleted && list.deletedDocs.has(docId)) continue;
|
|
3028
|
+
if (tokenIndex > 0 && !docScores.has(docId)) continue;
|
|
3029
|
+
const fieldName = fieldNames[list.fieldNameIndices[pi]];
|
|
3030
|
+
if (fields && !fields.includes(fieldName)) continue;
|
|
3031
|
+
const termFrequency = list.termFrequencies[pi];
|
|
3032
|
+
const fieldBoost = boost?.[fieldName] ?? 1;
|
|
3033
|
+
const avgLen = avgFieldLengths[fieldName] ?? 1;
|
|
3034
|
+
let cachedLengths = fieldLengthCache.get(docId);
|
|
3035
|
+
if (cachedLengths === void 0) {
|
|
3036
|
+
const storedDoc = state.docStore.get(docId);
|
|
3037
|
+
cachedLengths = storedDoc?.fieldLengths ?? null;
|
|
3038
|
+
fieldLengthCache.set(docId, cachedLengths);
|
|
3039
|
+
}
|
|
3040
|
+
const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
|
|
3041
|
+
let termScore = scoreFn(termFrequency, match.docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
|
|
3042
|
+
termScore *= fieldBoost;
|
|
3043
|
+
const existing = docScores.get(docId);
|
|
3044
|
+
if (existing) {
|
|
3045
|
+
existing.score += termScore;
|
|
3046
|
+
existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
|
|
3047
|
+
existing.fieldLengths[fieldName] = actualFieldLength;
|
|
3048
|
+
existing.idf[match.token] = match.idf;
|
|
3049
|
+
} else {
|
|
3050
|
+
docScores.set(docId, {
|
|
3051
|
+
score: termScore,
|
|
3052
|
+
termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
|
|
3053
|
+
fieldLengths: { [fieldName]: actualFieldLength },
|
|
3054
|
+
idf: { [match.token]: match.idf }
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
} else {
|
|
3061
|
+
for (const qt of queryTokens) {
|
|
3062
|
+
const matchingPostings = exact ? (() => {
|
|
3063
|
+
const postingList = state.invertedIdx.lookup(qt.token);
|
|
3064
|
+
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
3065
|
+
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
3066
|
+
for (const match of matchingPostings) {
|
|
3067
|
+
const docFreq = globalStats ? globalDocFreqs[match.token] ?? match.postingList.docIdSet.size : match.postingList.docIdSet.size;
|
|
3068
|
+
const idf = computeIDF(docFreq, totalDocs);
|
|
3069
|
+
const list = match.postingList;
|
|
3070
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3071
|
+
for (let pi = 0; pi < list.length; pi++) {
|
|
3072
|
+
const docId = list.docIds[pi];
|
|
3073
|
+
if (hasDeleted && list.deletedDocs.has(docId)) continue;
|
|
3074
|
+
const fieldName = fieldNames[list.fieldNameIndices[pi]];
|
|
3075
|
+
if (fields && !fields.includes(fieldName)) continue;
|
|
3076
|
+
const termFrequency = list.termFrequencies[pi];
|
|
3077
|
+
const fieldBoost = boost?.[fieldName] ?? 1;
|
|
3078
|
+
const avgLen = avgFieldLengths[fieldName] ?? 1;
|
|
3079
|
+
let cachedLengths = fieldLengthCache.get(docId);
|
|
3080
|
+
if (cachedLengths === void 0) {
|
|
3081
|
+
const storedDoc = state.docStore.get(docId);
|
|
3082
|
+
cachedLengths = storedDoc?.fieldLengths ?? null;
|
|
3083
|
+
fieldLengthCache.set(docId, cachedLengths);
|
|
3084
|
+
}
|
|
3085
|
+
const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
|
|
3086
|
+
let termScore = scoreFn(termFrequency, docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
|
|
3087
|
+
termScore *= fieldBoost;
|
|
3088
|
+
const existing = docScores.get(docId);
|
|
3089
|
+
if (existing) {
|
|
3090
|
+
existing.score += termScore;
|
|
3091
|
+
existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
|
|
3092
|
+
existing.fieldLengths[fieldName] = actualFieldLength;
|
|
3093
|
+
existing.idf[match.token] = idf;
|
|
3094
|
+
} else {
|
|
3095
|
+
docScores.set(docId, {
|
|
3096
|
+
score: termScore,
|
|
3097
|
+
termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
|
|
3098
|
+
fieldLengths: { [fieldName]: actualFieldLength },
|
|
3099
|
+
idf: { [match.token]: idf }
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
2376
3102
|
}
|
|
2377
3103
|
}
|
|
2378
3104
|
}
|
|
2379
3105
|
}
|
|
2380
|
-
const
|
|
3106
|
+
const totalMatched = docScores.size;
|
|
3107
|
+
const k = maxResults !== void 0 && maxResults > 0 ? maxResults : DEFAULT_MAX_RESULTS;
|
|
3108
|
+
const scored = topKFromMap(docScores, Math.min(k, totalMatched));
|
|
3109
|
+
return { scored, totalMatched };
|
|
3110
|
+
}
|
|
3111
|
+
function topKFromMap(docScores, k) {
|
|
3112
|
+
if (k <= 0) return [];
|
|
3113
|
+
const heap = [];
|
|
2381
3114
|
for (const [docId, data] of docScores) {
|
|
2382
|
-
|
|
2383
|
-
docId,
|
|
3115
|
+
if (heap.length < k) {
|
|
3116
|
+
heap.push({ docId, score: data.score });
|
|
3117
|
+
if (heap.length === k) buildMinHeap(heap);
|
|
3118
|
+
} else if (data.score > heap[0].score) {
|
|
3119
|
+
heap[0] = { docId, score: data.score };
|
|
3120
|
+
siftDown(heap, 0);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
heap.sort((a, b) => b.score - a.score);
|
|
3124
|
+
const result = new Array(heap.length);
|
|
3125
|
+
for (let i = 0; i < heap.length; i++) {
|
|
3126
|
+
const data = docScores.get(heap[i].docId);
|
|
3127
|
+
if (!data) continue;
|
|
3128
|
+
result[i] = {
|
|
3129
|
+
docId: heap[i].docId,
|
|
2384
3130
|
score: data.score,
|
|
2385
3131
|
termFrequencies: data.termFrequencies,
|
|
2386
3132
|
fieldLengths: data.fieldLengths,
|
|
2387
3133
|
idf: data.idf
|
|
2388
|
-
}
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
return result;
|
|
3137
|
+
}
|
|
3138
|
+
function buildMinHeap(heap) {
|
|
3139
|
+
for (let i = (heap.length >> 1) - 1; i >= 0; i--) {
|
|
3140
|
+
siftDown(heap, i);
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
function siftDown(heap, idx) {
|
|
3144
|
+
const len = heap.length;
|
|
3145
|
+
while (true) {
|
|
3146
|
+
let smallest = idx;
|
|
3147
|
+
const left = 2 * idx + 1;
|
|
3148
|
+
const right = 2 * idx + 2;
|
|
3149
|
+
if (left < len && heap[left].score < heap[smallest].score) smallest = left;
|
|
3150
|
+
if (right < len && heap[right].score < heap[smallest].score) smallest = right;
|
|
3151
|
+
if (smallest === idx) break;
|
|
3152
|
+
const tmp = heap[idx];
|
|
3153
|
+
heap[idx] = heap[smallest];
|
|
3154
|
+
heap[smallest] = tmp;
|
|
3155
|
+
idx = smallest;
|
|
2389
3156
|
}
|
|
2390
|
-
scored.sort((a, b) => b.score - a.score);
|
|
2391
|
-
return { scored, totalMatched: scored.length };
|
|
2392
3157
|
}
|
|
2393
3158
|
function searchVector(state, params) {
|
|
2394
|
-
const { field, value, k, similarity = 0, metric = "cosine", filterDocIds } = params;
|
|
3159
|
+
const { field, value, k, similarity = 0, metric = "cosine", filterDocIds, efSearch } = params;
|
|
2395
3160
|
const vecStore = state.vectorStores.get(field);
|
|
2396
3161
|
if (!vecStore) {
|
|
2397
3162
|
return { scored: [], totalMatched: 0 };
|
|
@@ -2404,7 +3169,7 @@ function searchVector(state, params) {
|
|
|
2404
3169
|
);
|
|
2405
3170
|
}
|
|
2406
3171
|
const queryVec = new Float32Array(value);
|
|
2407
|
-
const results = vecStore.search(queryVec, k, metric, similarity, filterDocIds);
|
|
3172
|
+
const results = vecStore.search(queryVec, k, metric, similarity, filterDocIds, efSearch);
|
|
2408
3173
|
return { scored: results, totalMatched: results.length };
|
|
2409
3174
|
}
|
|
2410
3175
|
function buildFilterContext(state, schema) {
|
|
@@ -2587,7 +3352,7 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
|
|
|
2587
3352
|
serializedVectors[path] = {
|
|
2588
3353
|
dimension: store.dimension,
|
|
2589
3354
|
vectors,
|
|
2590
|
-
hnswGraph:
|
|
3355
|
+
hnswGraph: store.serializeHNSW()
|
|
2591
3356
|
};
|
|
2592
3357
|
}
|
|
2593
3358
|
const serializedStats = state.stats.serialize();
|
|
@@ -2611,6 +3376,109 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
|
|
|
2611
3376
|
statistics: serializedStats
|
|
2612
3377
|
};
|
|
2613
3378
|
}
|
|
3379
|
+
function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema) {
|
|
3380
|
+
const flatSchema = getFlatSchema(state, schema);
|
|
3381
|
+
const flatSchemaStrings = {};
|
|
3382
|
+
for (const [k, v] of Object.entries(flatSchema)) {
|
|
3383
|
+
flatSchemaStrings[k] = v;
|
|
3384
|
+
}
|
|
3385
|
+
const wireDocs = /* @__PURE__ */ Object.create(null);
|
|
3386
|
+
for (const [docId, stored] of state.docStore.all()) {
|
|
3387
|
+
wireDocs[docId] = {
|
|
3388
|
+
fields: structuredClone(stored.fields),
|
|
3389
|
+
field_lengths: { ...stored.fieldLengths }
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
const fieldNames = [...state.fieldNameTable.names];
|
|
3393
|
+
const wireEntries = /* @__PURE__ */ Object.create(null);
|
|
3394
|
+
for (const token of state.invertedIdx.tokens()) {
|
|
3395
|
+
const list = state.invertedIdx.lookup(token);
|
|
3396
|
+
if (!list) continue;
|
|
3397
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3398
|
+
const ids = [];
|
|
3399
|
+
const tf = [];
|
|
3400
|
+
const fi = [];
|
|
3401
|
+
const pos = list.positions ? [] : null;
|
|
3402
|
+
for (let i = 0; i < list.length; i++) {
|
|
3403
|
+
if (hasDeleted && list.deletedDocs.has(list.docIds[i])) continue;
|
|
3404
|
+
ids.push(list.docIds[i]);
|
|
3405
|
+
tf.push(list.termFrequencies[i]);
|
|
3406
|
+
fi.push(list.fieldNameIndices[i]);
|
|
3407
|
+
if (pos && list.positions) {
|
|
3408
|
+
pos.push(list.positions[i]);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
if (ids.length > 0) {
|
|
3412
|
+
wireEntries[token] = { df: list.docIdSet.size, ids, tf, fi, pos };
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
const wireNumeric = {};
|
|
3416
|
+
for (const [path, idx] of state.numericIndexes) {
|
|
3417
|
+
wireNumeric[path] = idx.serialize().map((e) => ({ value: e.value, doc_id: e.docId }));
|
|
3418
|
+
}
|
|
3419
|
+
const wireBoolean = {};
|
|
3420
|
+
for (const [path, idx] of state.booleanIndexes) {
|
|
3421
|
+
const s = idx.serialize();
|
|
3422
|
+
wireBoolean[path] = { true_docs: s.trueDocs, false_docs: s.falseDocs };
|
|
3423
|
+
}
|
|
3424
|
+
const wireEnum = {};
|
|
3425
|
+
for (const [path, idx] of state.enumIndexes) {
|
|
3426
|
+
wireEnum[path] = idx.serialize();
|
|
3427
|
+
}
|
|
3428
|
+
const wireGeo = {};
|
|
3429
|
+
for (const [path, idx] of state.geoIndexes) {
|
|
3430
|
+
wireGeo[path] = idx.serialize().map((e) => ({ lat: e.lat, lon: e.lon, doc_id: e.docId }));
|
|
3431
|
+
}
|
|
3432
|
+
const wireVectors = {};
|
|
3433
|
+
for (const [path, store] of state.vectorStores) {
|
|
3434
|
+
const vectors = [];
|
|
3435
|
+
for (const [, entry] of store.entries()) {
|
|
3436
|
+
vectors.push({ doc_id: entry.docId, vector: Array.from(entry.vector) });
|
|
3437
|
+
}
|
|
3438
|
+
const hnswData = store.serializeHNSW();
|
|
3439
|
+
wireVectors[path] = {
|
|
3440
|
+
dimension: store.dimension,
|
|
3441
|
+
vectors,
|
|
3442
|
+
hnsw_graph: hnswData ? {
|
|
3443
|
+
entry_point: hnswData.entryPoint,
|
|
3444
|
+
max_layer: hnswData.maxLayer,
|
|
3445
|
+
m: hnswData.m,
|
|
3446
|
+
ef_construction: hnswData.efConstruction,
|
|
3447
|
+
metric: hnswData.metric,
|
|
3448
|
+
nodes: hnswData.nodes
|
|
3449
|
+
} : null
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
const serializedStats = state.stats.serialize();
|
|
3453
|
+
return {
|
|
3454
|
+
v: 2,
|
|
3455
|
+
index_name: indexName,
|
|
3456
|
+
partition_id: partitionId,
|
|
3457
|
+
total_partitions: totalPartitions,
|
|
3458
|
+
language,
|
|
3459
|
+
schema: flatSchemaStrings,
|
|
3460
|
+
doc_count: state.docStore.count(),
|
|
3461
|
+
avg_doc_length: Object.values(serializedStats.averageFieldLengths).reduce((sum, v) => sum + v, 0),
|
|
3462
|
+
documents: wireDocs,
|
|
3463
|
+
inverted_index: {
|
|
3464
|
+
field_names: fieldNames,
|
|
3465
|
+
entries: wireEntries
|
|
3466
|
+
},
|
|
3467
|
+
field_indexes: {
|
|
3468
|
+
numeric: wireNumeric,
|
|
3469
|
+
boolean: wireBoolean,
|
|
3470
|
+
enum: wireEnum,
|
|
3471
|
+
geopoint: wireGeo
|
|
3472
|
+
},
|
|
3473
|
+
vector_data: wireVectors,
|
|
3474
|
+
statistics: {
|
|
3475
|
+
total_documents: serializedStats.totalDocuments,
|
|
3476
|
+
total_field_lengths: serializedStats.totalFieldLengths,
|
|
3477
|
+
average_field_lengths: serializedStats.averageFieldLengths,
|
|
3478
|
+
doc_frequencies: serializedStats.docFrequencies
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
2614
3482
|
function deserializePartition(state, data, clearFn, schema) {
|
|
2615
3483
|
clearFn();
|
|
2616
3484
|
const invertedData = /* @__PURE__ */ Object.create(null);
|
|
@@ -2621,7 +3489,7 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2621
3489
|
docId: p.docId,
|
|
2622
3490
|
termFrequency: p.termFrequency,
|
|
2623
3491
|
fieldName: p.field,
|
|
2624
|
-
positions: [...p.positions]
|
|
3492
|
+
positions: state.trackPositions ? [...p.positions] : []
|
|
2625
3493
|
}))
|
|
2626
3494
|
};
|
|
2627
3495
|
}
|
|
@@ -2652,10 +3520,13 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2652
3520
|
state.geoIndexes.set(path, geoIdx);
|
|
2653
3521
|
}
|
|
2654
3522
|
for (const [path, vecData] of Object.entries(data.vectorData)) {
|
|
2655
|
-
const store =
|
|
3523
|
+
const store = createVectorSearchEngine(vecData.dimension);
|
|
2656
3524
|
for (const entry of vecData.vectors) {
|
|
2657
3525
|
store.insert(entry.docId, new Float32Array(entry.vector));
|
|
2658
3526
|
}
|
|
3527
|
+
if (vecData.hnswGraph) {
|
|
3528
|
+
store.deserializeHNSW(vecData.hnswGraph);
|
|
3529
|
+
}
|
|
2659
3530
|
state.vectorStores.set(path, store);
|
|
2660
3531
|
}
|
|
2661
3532
|
state.stats.deserialize(data.statistics);
|
|
@@ -2676,9 +3547,15 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2676
3547
|
}
|
|
2677
3548
|
|
|
2678
3549
|
// src/core/partition/index.ts
|
|
2679
|
-
function createPartitionIndex(partitionId) {
|
|
3550
|
+
function createPartitionIndex(partitionId, trackPositions = true, vectorPromotionConfig) {
|
|
3551
|
+
const vectorPromoter = vectorPromotionConfig ? createVectorPromoter({
|
|
3552
|
+
promotionThreshold: vectorPromotionConfig.threshold,
|
|
3553
|
+
hnswConfig: vectorPromotionConfig.hnswConfig,
|
|
3554
|
+
workerStrategy: vectorPromotionConfig.workerStrategy
|
|
3555
|
+
}) : null;
|
|
3556
|
+
const fieldNameTable = { names: [], indexMap: /* @__PURE__ */ new Map() };
|
|
2680
3557
|
const state = {
|
|
2681
|
-
invertedIdx: createInvertedIndex(),
|
|
3558
|
+
invertedIdx: createInvertedIndex(fieldNameTable),
|
|
2682
3559
|
docStore: createDocumentStore(),
|
|
2683
3560
|
stats: createPartitionStats(),
|
|
2684
3561
|
numericIndexes: /* @__PURE__ */ new Map(),
|
|
@@ -2686,10 +3563,13 @@ function createPartitionIndex(partitionId) {
|
|
|
2686
3563
|
enumIndexes: /* @__PURE__ */ new Map(),
|
|
2687
3564
|
geoIndexes: /* @__PURE__ */ new Map(),
|
|
2688
3565
|
vectorStores: /* @__PURE__ */ new Map(),
|
|
3566
|
+
fieldNameTable,
|
|
2689
3567
|
flatSchemaCache: null,
|
|
2690
|
-
lastSchemaRef: null
|
|
3568
|
+
lastSchemaRef: null,
|
|
3569
|
+
trackPositions
|
|
2691
3570
|
};
|
|
2692
3571
|
function clearAll() {
|
|
3572
|
+
vectorPromoter?.shutdown();
|
|
2693
3573
|
state.invertedIdx.clear();
|
|
2694
3574
|
state.docStore.clear();
|
|
2695
3575
|
for (const idx of state.numericIndexes.values()) idx.clear();
|
|
@@ -2723,6 +3603,9 @@ function createPartitionIndex(partitionId) {
|
|
|
2723
3603
|
}
|
|
2724
3604
|
if (options?.validate !== false) {
|
|
2725
3605
|
validateDocument(document, schema);
|
|
3606
|
+
if (options?.strict) {
|
|
3607
|
+
validateDocumentStrict(document, schema);
|
|
3608
|
+
}
|
|
2726
3609
|
}
|
|
2727
3610
|
const flatSchema = getFlatSchema(state, schema);
|
|
2728
3611
|
const { fieldLengths, tokensByField } = indexDocument(
|
|
@@ -2733,8 +3616,13 @@ function createPartitionIndex(partitionId) {
|
|
|
2733
3616
|
language,
|
|
2734
3617
|
options
|
|
2735
3618
|
);
|
|
2736
|
-
|
|
3619
|
+
if (options?.skipClone) {
|
|
3620
|
+
state.docStore.storeRef(docId, document, fieldLengths);
|
|
3621
|
+
} else {
|
|
3622
|
+
state.docStore.store(docId, document, fieldLengths);
|
|
3623
|
+
}
|
|
2737
3624
|
state.stats.addDocument(fieldLengths, tokensByField);
|
|
3625
|
+
vectorPromoter?.check(state.vectorStores);
|
|
2738
3626
|
},
|
|
2739
3627
|
remove(docId, schema, language, options) {
|
|
2740
3628
|
const stored = state.docStore.get(docId);
|
|
@@ -2759,6 +3647,9 @@ function createPartitionIndex(partitionId) {
|
|
|
2759
3647
|
}
|
|
2760
3648
|
if (options?.validate !== false) {
|
|
2761
3649
|
validateDocument(document, schema);
|
|
3650
|
+
if (options?.strict) {
|
|
3651
|
+
validateDocumentStrict(document, schema);
|
|
3652
|
+
}
|
|
2762
3653
|
}
|
|
2763
3654
|
const flatSchema = getFlatSchema(state, schema);
|
|
2764
3655
|
const needsTextReindex = textFieldsChanged(stored.fields, document, flatSchema);
|
|
@@ -2793,6 +3684,11 @@ function createPartitionIndex(partitionId) {
|
|
|
2793
3684
|
if (!stored) return void 0;
|
|
2794
3685
|
return structuredClone(stored.fields);
|
|
2795
3686
|
},
|
|
3687
|
+
getRef(docId) {
|
|
3688
|
+
const stored = state.docStore.get(docId);
|
|
3689
|
+
if (!stored) return void 0;
|
|
3690
|
+
return stored.fields;
|
|
3691
|
+
},
|
|
2796
3692
|
has(docId) {
|
|
2797
3693
|
return state.docStore.has(docId);
|
|
2798
3694
|
},
|
|
@@ -2805,6 +3701,41 @@ function createPartitionIndex(partitionId) {
|
|
|
2805
3701
|
}
|
|
2806
3702
|
},
|
|
2807
3703
|
clear: clearAll,
|
|
3704
|
+
estimateMemoryBytes() {
|
|
3705
|
+
const docCount = state.docStore.count();
|
|
3706
|
+
if (docCount === 0) return 0;
|
|
3707
|
+
const AVG_DOC_OVERHEAD = 350;
|
|
3708
|
+
let bytes = docCount * AVG_DOC_OVERHEAD;
|
|
3709
|
+
const docFreqs = state.stats.docFrequencies;
|
|
3710
|
+
const POSTING_ENTRY_SIZE = 24;
|
|
3711
|
+
const PER_TERM_OVERHEAD = 180;
|
|
3712
|
+
let totalPostings = 0;
|
|
3713
|
+
let termCount = 0;
|
|
3714
|
+
for (const term in docFreqs) {
|
|
3715
|
+
totalPostings += docFreqs[term];
|
|
3716
|
+
termCount++;
|
|
3717
|
+
}
|
|
3718
|
+
bytes += totalPostings * POSTING_ENTRY_SIZE;
|
|
3719
|
+
bytes += termCount * PER_TERM_OVERHEAD;
|
|
3720
|
+
const FIELD_ENTRY_OVERHEAD = 42;
|
|
3721
|
+
bytes += docCount * state.numericIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3722
|
+
bytes += docCount * state.booleanIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3723
|
+
bytes += docCount * state.enumIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3724
|
+
bytes += docCount * state.geoIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3725
|
+
for (const store of state.vectorStores.values()) {
|
|
3726
|
+
bytes += store.estimateMemoryBytes();
|
|
3727
|
+
}
|
|
3728
|
+
return bytes;
|
|
3729
|
+
},
|
|
3730
|
+
vectorFieldCount() {
|
|
3731
|
+
return state.vectorStores.size;
|
|
3732
|
+
},
|
|
3733
|
+
hasPromotedHnsw() {
|
|
3734
|
+
for (const store of state.vectorStores.values()) {
|
|
3735
|
+
if (store.isPromoted) return true;
|
|
3736
|
+
}
|
|
3737
|
+
return false;
|
|
3738
|
+
},
|
|
2808
3739
|
searchFulltext(params) {
|
|
2809
3740
|
return searchFulltext(state, params);
|
|
2810
3741
|
},
|
|
@@ -2817,9 +3748,16 @@ function createPartitionIndex(partitionId) {
|
|
|
2817
3748
|
computeFacets(docIds, config, schema) {
|
|
2818
3749
|
return computeFacets(state, docIds, config, schema);
|
|
2819
3750
|
},
|
|
3751
|
+
suggestTerms(prefix, limit) {
|
|
3752
|
+
return state.invertedIdx.prefixSearch(prefix, limit);
|
|
3753
|
+
},
|
|
2820
3754
|
serialize(indexName, totalPartitions, language, schema) {
|
|
2821
3755
|
return serializePartition(state, partitionId, indexName, totalPartitions, language, schema);
|
|
2822
3756
|
},
|
|
3757
|
+
serializeToBytes(indexName, totalPartitions, language, schema) {
|
|
3758
|
+
const wire = serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema);
|
|
3759
|
+
return encodeRawPayloadV2(wire);
|
|
3760
|
+
},
|
|
2823
3761
|
deserialize(data, schema) {
|
|
2824
3762
|
deserializePartition(state, data, clearAll, schema);
|
|
2825
3763
|
}
|
|
@@ -2830,10 +3768,12 @@ function createPartitionIndex(partitionId) {
|
|
|
2830
3768
|
// src/partitioning/manager.ts
|
|
2831
3769
|
function createPartitionManager(indexName, config, language, router, initialPartitionCount) {
|
|
2832
3770
|
const count = initialPartitionCount ?? 1;
|
|
3771
|
+
const trackPositions = config.trackPositions ?? true;
|
|
3772
|
+
const vectorPromotionConfig = config.vectorPromotion;
|
|
2833
3773
|
let partitions = [];
|
|
2834
3774
|
const docPartitionMap = /* @__PURE__ */ new Map();
|
|
2835
3775
|
for (let i = 0; i < count; i++) {
|
|
2836
|
-
partitions.push(createPartitionIndex(i));
|
|
3776
|
+
partitions.push(createPartitionIndex(i, trackPositions, vectorPromotionConfig));
|
|
2837
3777
|
}
|
|
2838
3778
|
function validatePartitionId(partitionId) {
|
|
2839
3779
|
if (partitionId < 0 || partitionId >= partitions.length) {
|
|
@@ -2882,6 +3822,9 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2882
3822
|
validatePartitionId(partitionId);
|
|
2883
3823
|
return partitions[partitionId];
|
|
2884
3824
|
},
|
|
3825
|
+
partitionAt(index) {
|
|
3826
|
+
return partitions[index];
|
|
3827
|
+
},
|
|
2885
3828
|
getAllPartitions() {
|
|
2886
3829
|
return [...partitions];
|
|
2887
3830
|
},
|
|
@@ -2890,7 +3833,15 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2890
3833
|
rebuildDocPartitionMap();
|
|
2891
3834
|
},
|
|
2892
3835
|
addPartition() {
|
|
2893
|
-
const
|
|
3836
|
+
const currentMaxPartitions = config.partitions?.maxPartitions;
|
|
3837
|
+
if (currentMaxPartitions !== void 0 && partitions.length >= currentMaxPartitions) {
|
|
3838
|
+
throw new NarsilError(
|
|
3839
|
+
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
3840
|
+
`Cannot add partition: maximum of ${currentMaxPartitions} partitions reached`,
|
|
3841
|
+
{ maxPartitions: currentMaxPartitions, currentCount: partitions.length }
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
const partition = createPartitionIndex(partitions.length, trackPositions, vectorPromotionConfig);
|
|
2894
3845
|
partitions.push(partition);
|
|
2895
3846
|
return partition;
|
|
2896
3847
|
},
|
|
@@ -2900,8 +3851,26 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2900
3851
|
rebuildDocPartitionMap();
|
|
2901
3852
|
},
|
|
2902
3853
|
insert(docId, document, options) {
|
|
3854
|
+
const currentMaxDocs = config.partitions?.maxDocsPerPartition;
|
|
3855
|
+
if (currentMaxDocs !== void 0) {
|
|
3856
|
+
const totalCapacity = currentMaxDocs * partitions.length;
|
|
3857
|
+
if (docPartitionMap.size >= totalCapacity) {
|
|
3858
|
+
throw new NarsilError(
|
|
3859
|
+
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
3860
|
+
`Index "${indexName}" has reached its capacity of ${totalCapacity} documents (${currentMaxDocs} per partition \xD7 ${partitions.length} partitions)`,
|
|
3861
|
+
{
|
|
3862
|
+
indexName,
|
|
3863
|
+
currentCount: docPartitionMap.size,
|
|
3864
|
+
totalCapacity,
|
|
3865
|
+
maxDocsPerPartition: currentMaxDocs,
|
|
3866
|
+
partitionCount: partitions.length
|
|
3867
|
+
}
|
|
3868
|
+
);
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
2903
3871
|
const pid = router.route(docId, partitions.length);
|
|
2904
|
-
|
|
3872
|
+
const insertOpts = config.strict ? { ...options, strict: true } : options;
|
|
3873
|
+
partitions[pid].insert(docId, document, config.schema, language, insertOpts);
|
|
2905
3874
|
docPartitionMap.set(docId, pid);
|
|
2906
3875
|
},
|
|
2907
3876
|
remove(docId) {
|
|
@@ -2917,13 +3886,19 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2917
3886
|
if (pid === void 0) {
|
|
2918
3887
|
throw new NarsilError(ErrorCodes.DOC_NOT_FOUND, `Document "${docId}" not found in any partition`, { docId });
|
|
2919
3888
|
}
|
|
2920
|
-
|
|
3889
|
+
const updateOpts = config.strict ? { ...options, strict: true } : options;
|
|
3890
|
+
partitions[pid].update(docId, document, config.schema, language, updateOpts);
|
|
2921
3891
|
},
|
|
2922
3892
|
get(docId) {
|
|
2923
3893
|
const pid = docPartitionMap.get(docId);
|
|
2924
3894
|
if (pid === void 0) return void 0;
|
|
2925
3895
|
return partitions[pid].get(docId);
|
|
2926
3896
|
},
|
|
3897
|
+
getRef(docId) {
|
|
3898
|
+
const pid = docPartitionMap.get(docId);
|
|
3899
|
+
if (pid === void 0) return void 0;
|
|
3900
|
+
return partitions[pid].getRef(docId);
|
|
3901
|
+
},
|
|
2927
3902
|
has(docId) {
|
|
2928
3903
|
return docPartitionMap.has(docId);
|
|
2929
3904
|
},
|
|
@@ -2934,6 +3909,10 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2934
3909
|
validatePartitionId(partitionId);
|
|
2935
3910
|
return partitions[partitionId].serialize(indexName, partitions.length, language.name, config.schema);
|
|
2936
3911
|
},
|
|
3912
|
+
serializePartitionToBytes(partitionId) {
|
|
3913
|
+
validatePartitionId(partitionId);
|
|
3914
|
+
return partitions[partitionId].serializeToBytes(indexName, partitions.length, language.name, config.schema);
|
|
3915
|
+
},
|
|
2937
3916
|
deserializePartition(partitionId, data) {
|
|
2938
3917
|
validatePartitionId(partitionId);
|
|
2939
3918
|
partitions[partitionId].deserialize(data, config.schema);
|
|
@@ -2954,6 +3933,22 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2954
3933
|
}
|
|
2955
3934
|
}
|
|
2956
3935
|
return { totalDocuments, docFrequencies, totalFieldLengths };
|
|
3936
|
+
},
|
|
3937
|
+
estimateMemoryBytes() {
|
|
3938
|
+
let total = 0;
|
|
3939
|
+
for (let i = 0; i < partitions.length; i++) {
|
|
3940
|
+
total += partitions[i].estimateMemoryBytes();
|
|
3941
|
+
}
|
|
3942
|
+
return total;
|
|
3943
|
+
},
|
|
3944
|
+
getPartitionStats() {
|
|
3945
|
+
return partitions.map((p, i) => ({
|
|
3946
|
+
partitionId: i,
|
|
3947
|
+
documentCount: p.count(),
|
|
3948
|
+
estimatedMemoryBytes: p.estimateMemoryBytes(),
|
|
3949
|
+
vectorFieldCount: p.vectorFieldCount(),
|
|
3950
|
+
isHnswPromoted: p.hasPromotedHnsw()
|
|
3951
|
+
}));
|
|
2957
3952
|
}
|
|
2958
3953
|
};
|
|
2959
3954
|
return manager;
|
|
@@ -3050,7 +4045,7 @@ function createDirectExecutor() {
|
|
|
3050
4045
|
}
|
|
3051
4046
|
case "insert": {
|
|
3052
4047
|
const entry = requireIndex(action.indexName);
|
|
3053
|
-
entry.manager.insert(action.docId, action.document);
|
|
4048
|
+
entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
|
|
3054
4049
|
return void 0;
|
|
3055
4050
|
}
|
|
3056
4051
|
case "remove": {
|
|
@@ -3065,14 +4060,15 @@ function createDirectExecutor() {
|
|
|
3065
4060
|
}
|
|
3066
4061
|
case "query": {
|
|
3067
4062
|
const entry = requireIndex(action.indexName);
|
|
3068
|
-
const result = fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
3069
|
-
scoringMode: entry.config.defaultScoring ?? "local"
|
|
4063
|
+
const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
4064
|
+
scoringMode: entry.config.defaultScoring ?? "local",
|
|
4065
|
+
partitionIds: action.partitionIds
|
|
3070
4066
|
});
|
|
3071
4067
|
return result;
|
|
3072
4068
|
}
|
|
3073
4069
|
case "preflight": {
|
|
3074
4070
|
const entry = requireIndex(action.indexName);
|
|
3075
|
-
const result = fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
4071
|
+
const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
3076
4072
|
scoringMode: entry.config.defaultScoring ?? "local"
|
|
3077
4073
|
});
|
|
3078
4074
|
return { count: result.totalMatched };
|
|
@@ -3144,4 +4140,4 @@ function createDirectExecutor() {
|
|
|
3144
4140
|
};
|
|
3145
4141
|
}
|
|
3146
4142
|
|
|
3147
|
-
export { createDirectExecutor, fanOutQuery, getLanguage, registerLanguage, tokenize, validateSchema };
|
|
4143
|
+
export { createDirectExecutor, createPartitionIndex, createPartitionRouter, deserializePayloadV2, detectRuntime, fanOutQuery, fnv1a, getLanguage, kWayMerge, registerLanguage, tokenize, validateSchema };
|