@delali/narsil 0.1.0 → 0.1.2
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 +257 -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-GVN2W4VA.mjs +570 -0
- package/dist/chunk-HRABJLPB.mjs +64 -0
- package/dist/{chunk-5HXMVMSM.mjs → chunk-LIRVYUQ5.mjs} +1174 -181
- package/dist/direct-executor-DFRFDLFP.mjs +4 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.mjs +1367 -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 +17 -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);
|
|
@@ -871,6 +936,280 @@ function heapPop(heap) {
|
|
|
871
936
|
}
|
|
872
937
|
return top;
|
|
873
938
|
}
|
|
939
|
+
function encodeRawPayloadV2(wire) {
|
|
940
|
+
return encode(wire);
|
|
941
|
+
}
|
|
942
|
+
var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
|
|
943
|
+
function validateHnswMetric(value) {
|
|
944
|
+
if (value === void 0 || value === null) return void 0;
|
|
945
|
+
if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
|
|
946
|
+
return value;
|
|
947
|
+
}
|
|
948
|
+
return void 0;
|
|
949
|
+
}
|
|
950
|
+
function deserializePayloadV2(data) {
|
|
951
|
+
const raw = decode(data);
|
|
952
|
+
const documents = {};
|
|
953
|
+
for (const [docId, doc] of Object.entries(raw.documents ?? {})) {
|
|
954
|
+
documents[docId] = {
|
|
955
|
+
fields: doc.fields,
|
|
956
|
+
fieldLengths: doc.field_lengths ?? {}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const fieldNames = raw.inverted_index?.field_names ?? [];
|
|
960
|
+
const invertedIndex = {};
|
|
961
|
+
for (const [token, list] of Object.entries(raw.inverted_index?.entries ?? {})) {
|
|
962
|
+
const count = list.ids.length;
|
|
963
|
+
const postings = new Array(count);
|
|
964
|
+
for (let i = 0; i < count; i++) {
|
|
965
|
+
postings[i] = {
|
|
966
|
+
docId: list.ids[i],
|
|
967
|
+
termFrequency: list.tf[i],
|
|
968
|
+
field: fieldNames[list.fi[i]] ?? "",
|
|
969
|
+
positions: list.pos ? list.pos[i] : []
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
invertedIndex[token] = {
|
|
973
|
+
docFrequency: list.df,
|
|
974
|
+
postings
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
const numeric = {};
|
|
978
|
+
for (const [field, entries] of Object.entries(raw.field_indexes?.numeric ?? {})) {
|
|
979
|
+
numeric[field] = entries.map((e) => ({ value: e.value, docId: e.doc_id }));
|
|
980
|
+
}
|
|
981
|
+
const boolean = {};
|
|
982
|
+
for (const [field, idx] of Object.entries(raw.field_indexes?.boolean ?? {})) {
|
|
983
|
+
boolean[field] = { trueDocs: idx.true_docs ?? [], falseDocs: idx.false_docs ?? [] };
|
|
984
|
+
}
|
|
985
|
+
const geopoint = {};
|
|
986
|
+
for (const [field, entries] of Object.entries(raw.field_indexes?.geopoint ?? {})) {
|
|
987
|
+
geopoint[field] = entries.map((e) => ({ lat: e.lat, lon: e.lon, docId: e.doc_id }));
|
|
988
|
+
}
|
|
989
|
+
const vectorData = {};
|
|
990
|
+
for (const [field, data2] of Object.entries(raw.vector_data ?? {})) {
|
|
991
|
+
vectorData[field] = {
|
|
992
|
+
dimension: data2.dimension,
|
|
993
|
+
vectors: (data2.vectors ?? []).map((v) => ({ docId: v.doc_id, vector: v.vector })),
|
|
994
|
+
hnswGraph: data2.hnsw_graph ? {
|
|
995
|
+
entryPoint: data2.hnsw_graph.entry_point,
|
|
996
|
+
maxLayer: data2.hnsw_graph.max_layer,
|
|
997
|
+
m: data2.hnsw_graph.m,
|
|
998
|
+
efConstruction: data2.hnsw_graph.ef_construction,
|
|
999
|
+
metric: validateHnswMetric(data2.hnsw_graph.metric),
|
|
1000
|
+
nodes: data2.hnsw_graph.nodes
|
|
1001
|
+
} : null
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
indexName: raw.index_name,
|
|
1006
|
+
partitionId: raw.partition_id,
|
|
1007
|
+
totalPartitions: raw.total_partitions,
|
|
1008
|
+
language: raw.language ?? "english",
|
|
1009
|
+
schema: raw.schema ?? {},
|
|
1010
|
+
docCount: raw.doc_count ?? 0,
|
|
1011
|
+
avgDocLength: raw.avg_doc_length ?? 0,
|
|
1012
|
+
documents,
|
|
1013
|
+
invertedIndex,
|
|
1014
|
+
fieldIndexes: {
|
|
1015
|
+
numeric,
|
|
1016
|
+
boolean,
|
|
1017
|
+
enum: raw.field_indexes?.enum ?? {},
|
|
1018
|
+
geopoint
|
|
1019
|
+
},
|
|
1020
|
+
vectorData,
|
|
1021
|
+
statistics: {
|
|
1022
|
+
totalDocuments: raw.statistics?.total_documents ?? 0,
|
|
1023
|
+
totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
|
|
1024
|
+
averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
|
|
1025
|
+
docFrequencies: raw.statistics?.doc_frequencies ?? {}
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/runtime/detect.ts
|
|
1031
|
+
function detectRuntimeName() {
|
|
1032
|
+
if ("Bun" in globalThis) {
|
|
1033
|
+
return "bun";
|
|
1034
|
+
}
|
|
1035
|
+
if ("Deno" in globalThis) {
|
|
1036
|
+
return "deno";
|
|
1037
|
+
}
|
|
1038
|
+
const proc = globalThis.process;
|
|
1039
|
+
if (typeof proc === "object" && proc !== null && typeof proc.versions === "object" && typeof proc.versions?.node === "string") {
|
|
1040
|
+
return "node";
|
|
1041
|
+
}
|
|
1042
|
+
return "browser";
|
|
1043
|
+
}
|
|
1044
|
+
function getCpuCount() {
|
|
1045
|
+
const nav = globalThis.navigator;
|
|
1046
|
+
if (nav && typeof nav.hardwareConcurrency === "number" && nav.hardwareConcurrency >= 1) {
|
|
1047
|
+
return nav.hardwareConcurrency;
|
|
1048
|
+
}
|
|
1049
|
+
return 1;
|
|
1050
|
+
}
|
|
1051
|
+
function detectRuntime() {
|
|
1052
|
+
const runtime = detectRuntimeName();
|
|
1053
|
+
return {
|
|
1054
|
+
runtime,
|
|
1055
|
+
cpuCount: getCpuCount(),
|
|
1056
|
+
supportsWorkerThreads: runtime === "node" || runtime === "bun",
|
|
1057
|
+
supportsWebWorkers: runtime === "browser" || runtime === "deno" || runtime === "bun",
|
|
1058
|
+
supportsFileSystem: runtime === "node" || runtime === "bun" || runtime === "deno",
|
|
1059
|
+
supportsIndexedDB: runtime === "browser" || "indexedDB" in globalThis,
|
|
1060
|
+
supportsBroadcastChannel: "BroadcastChannel" in globalThis
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/vector/promoter.ts
|
|
1065
|
+
function detectWorkerStrategy() {
|
|
1066
|
+
const runtime = detectRuntime();
|
|
1067
|
+
const url = import.meta.url;
|
|
1068
|
+
const hasBundledWorker = url.endsWith(".mjs") || url.endsWith(".js");
|
|
1069
|
+
if (runtime.supportsWorkerThreads && hasBundledWorker) return "worker-threads";
|
|
1070
|
+
if (runtime.supportsWebWorkers && hasBundledWorker) return "web-worker";
|
|
1071
|
+
return "synchronous";
|
|
1072
|
+
}
|
|
1073
|
+
function collectVectorsForWorker(engine) {
|
|
1074
|
+
const vectors = [];
|
|
1075
|
+
for (const [, entry] of engine.entries()) {
|
|
1076
|
+
vectors.push({ docId: entry.docId, values: Array.from(entry.vector) });
|
|
1077
|
+
}
|
|
1078
|
+
return vectors;
|
|
1079
|
+
}
|
|
1080
|
+
function buildWorkerRequest(engine, hnswConfig) {
|
|
1081
|
+
return {
|
|
1082
|
+
type: "build",
|
|
1083
|
+
vectors: collectVectorsForWorker(engine),
|
|
1084
|
+
dimension: engine.dimension,
|
|
1085
|
+
config: hnswConfig ?? {}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function resolveWorkerUrl() {
|
|
1089
|
+
const base = import.meta.url;
|
|
1090
|
+
if (base.endsWith(".mjs") || base.endsWith(".js")) {
|
|
1091
|
+
return new URL("./hnsw-build-worker.mjs", base).href;
|
|
1092
|
+
}
|
|
1093
|
+
const replaced = base.replace(/\/src\/vector\/[^/]+$/, "/dist/vector/hnsw-build-worker.mjs");
|
|
1094
|
+
return replaced;
|
|
1095
|
+
}
|
|
1096
|
+
function spawnNodeWorker(request, onComplete, onError) {
|
|
1097
|
+
const entryUrl = resolveWorkerUrl();
|
|
1098
|
+
let worker = null;
|
|
1099
|
+
import('worker_threads').then((wt) => {
|
|
1100
|
+
worker = new wt.Worker(new URL(entryUrl));
|
|
1101
|
+
worker.on("message", (msg) => {
|
|
1102
|
+
const response = msg;
|
|
1103
|
+
worker?.terminate();
|
|
1104
|
+
if (response.type === "success") {
|
|
1105
|
+
onComplete(response.graph);
|
|
1106
|
+
} else {
|
|
1107
|
+
onError(new Error(response.message));
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
worker.on("error", (err) => {
|
|
1111
|
+
worker?.terminate();
|
|
1112
|
+
onError(err instanceof Error ? err : new Error(String(err)));
|
|
1113
|
+
});
|
|
1114
|
+
worker.postMessage(request);
|
|
1115
|
+
}).catch(onError);
|
|
1116
|
+
return {
|
|
1117
|
+
terminate() {
|
|
1118
|
+
worker?.terminate();
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function spawnWebWorker(request, onComplete, onError) {
|
|
1123
|
+
const entryUrl = resolveWorkerUrl();
|
|
1124
|
+
const WorkerCtor = globalThis.Worker;
|
|
1125
|
+
const worker = new WorkerCtor(entryUrl, { type: "module" });
|
|
1126
|
+
worker.onmessage = (event) => {
|
|
1127
|
+
const response = event.data;
|
|
1128
|
+
worker.terminate();
|
|
1129
|
+
if (response.type === "success") {
|
|
1130
|
+
onComplete(response.graph);
|
|
1131
|
+
} else {
|
|
1132
|
+
onError(new Error(response.message));
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
worker.onerror = (event) => {
|
|
1136
|
+
worker.terminate();
|
|
1137
|
+
onError(event.error ?? new Error(event.message ?? "Web Worker error"));
|
|
1138
|
+
};
|
|
1139
|
+
worker.postMessage(request);
|
|
1140
|
+
return {
|
|
1141
|
+
terminate() {
|
|
1142
|
+
worker.terminate();
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function createVectorPromoter(config) {
|
|
1147
|
+
const threshold = config?.promotionThreshold ?? 1e4;
|
|
1148
|
+
const hnswConfig = config?.hnswConfig;
|
|
1149
|
+
const promoting = /* @__PURE__ */ new Set();
|
|
1150
|
+
const strategy = config?.workerStrategy ?? detectWorkerStrategy();
|
|
1151
|
+
const pendingWorkers = /* @__PURE__ */ new Set();
|
|
1152
|
+
return {
|
|
1153
|
+
get strategy() {
|
|
1154
|
+
return strategy;
|
|
1155
|
+
},
|
|
1156
|
+
check(engines) {
|
|
1157
|
+
for (const [field, engine] of engines) {
|
|
1158
|
+
if (engine.isPromoted || promoting.has(field)) continue;
|
|
1159
|
+
if (engine.size < threshold) continue;
|
|
1160
|
+
promoting.add(field);
|
|
1161
|
+
if (strategy === "worker-threads") {
|
|
1162
|
+
const request = buildWorkerRequest(engine, hnswConfig);
|
|
1163
|
+
const handle = spawnNodeWorker(
|
|
1164
|
+
request,
|
|
1165
|
+
(graph) => {
|
|
1166
|
+
pendingWorkers.delete(handle);
|
|
1167
|
+
if (!engine.isPromoted) {
|
|
1168
|
+
engine.deserializeHNSW(graph);
|
|
1169
|
+
}
|
|
1170
|
+
promoting.delete(field);
|
|
1171
|
+
},
|
|
1172
|
+
() => {
|
|
1173
|
+
pendingWorkers.delete(handle);
|
|
1174
|
+
promoting.delete(field);
|
|
1175
|
+
}
|
|
1176
|
+
);
|
|
1177
|
+
pendingWorkers.add(handle);
|
|
1178
|
+
} else if (strategy === "web-worker") {
|
|
1179
|
+
const request = buildWorkerRequest(engine, hnswConfig);
|
|
1180
|
+
const handle = spawnWebWorker(
|
|
1181
|
+
request,
|
|
1182
|
+
(graph) => {
|
|
1183
|
+
pendingWorkers.delete(handle);
|
|
1184
|
+
if (!engine.isPromoted) {
|
|
1185
|
+
engine.deserializeHNSW(graph);
|
|
1186
|
+
}
|
|
1187
|
+
promoting.delete(field);
|
|
1188
|
+
},
|
|
1189
|
+
() => {
|
|
1190
|
+
pendingWorkers.delete(handle);
|
|
1191
|
+
promoting.delete(field);
|
|
1192
|
+
}
|
|
1193
|
+
);
|
|
1194
|
+
pendingWorkers.add(handle);
|
|
1195
|
+
} else {
|
|
1196
|
+
try {
|
|
1197
|
+
engine.promoteToHNSW(hnswConfig);
|
|
1198
|
+
} finally {
|
|
1199
|
+
promoting.delete(field);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
shutdown() {
|
|
1205
|
+
for (const handle of pendingWorkers) {
|
|
1206
|
+
handle.terminate();
|
|
1207
|
+
}
|
|
1208
|
+
pendingWorkers.clear();
|
|
1209
|
+
promoting.clear();
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
874
1213
|
|
|
875
1214
|
// src/core/document-store.ts
|
|
876
1215
|
function createDocumentStore() {
|
|
@@ -879,6 +1218,9 @@ function createDocumentStore() {
|
|
|
879
1218
|
store(docId, document, fieldLengths) {
|
|
880
1219
|
docs.set(docId, { fields: structuredClone(document), fieldLengths });
|
|
881
1220
|
},
|
|
1221
|
+
storeRef(docId, document, fieldLengths) {
|
|
1222
|
+
docs.set(docId, { fields: document, fieldLengths });
|
|
1223
|
+
},
|
|
882
1224
|
get(docId) {
|
|
883
1225
|
return docs.get(docId);
|
|
884
1226
|
},
|
|
@@ -914,7 +1256,10 @@ function createDocumentStore() {
|
|
|
914
1256
|
}
|
|
915
1257
|
|
|
916
1258
|
// src/core/inverted-index.ts
|
|
917
|
-
|
|
1259
|
+
var INITIAL_CAPACITY = 8;
|
|
1260
|
+
var MAX_TERM_FREQUENCY = 65535;
|
|
1261
|
+
var COMPACTION_THRESHOLD = 0.3;
|
|
1262
|
+
function createInvertedIndex(fieldNameTable) {
|
|
918
1263
|
const index = /* @__PURE__ */ new Map();
|
|
919
1264
|
const charBuckets = /* @__PURE__ */ new Map();
|
|
920
1265
|
function trackToken(token) {
|
|
@@ -953,33 +1298,112 @@ function createInvertedIndex() {
|
|
|
953
1298
|
}
|
|
954
1299
|
return filtered;
|
|
955
1300
|
}
|
|
1301
|
+
function getOrCreateList(token) {
|
|
1302
|
+
let list = index.get(token);
|
|
1303
|
+
if (!list) {
|
|
1304
|
+
list = {
|
|
1305
|
+
length: 0,
|
|
1306
|
+
docIds: [],
|
|
1307
|
+
termFrequencies: new Uint16Array(INITIAL_CAPACITY),
|
|
1308
|
+
fieldNameIndices: new Uint8Array(INITIAL_CAPACITY),
|
|
1309
|
+
positions: null,
|
|
1310
|
+
docIdSet: /* @__PURE__ */ new Set(),
|
|
1311
|
+
deletedDocs: /* @__PURE__ */ new Set()
|
|
1312
|
+
};
|
|
1313
|
+
index.set(token, list);
|
|
1314
|
+
trackToken(token);
|
|
1315
|
+
}
|
|
1316
|
+
return list;
|
|
1317
|
+
}
|
|
1318
|
+
function growTypedArrays(list) {
|
|
1319
|
+
const newCap = list.termFrequencies.length * 2;
|
|
1320
|
+
const newTF = new Uint16Array(newCap);
|
|
1321
|
+
newTF.set(list.termFrequencies);
|
|
1322
|
+
list.termFrequencies = newTF;
|
|
1323
|
+
const newFNI = new Uint8Array(newCap);
|
|
1324
|
+
newFNI.set(list.fieldNameIndices);
|
|
1325
|
+
list.fieldNameIndices = newFNI;
|
|
1326
|
+
}
|
|
1327
|
+
function compactList(list) {
|
|
1328
|
+
if (list.deletedDocs.size === 0) return;
|
|
1329
|
+
let writeIdx = 0;
|
|
1330
|
+
for (let i = 0; i < list.length; i++) {
|
|
1331
|
+
if (!list.deletedDocs.has(list.docIds[i])) {
|
|
1332
|
+
if (writeIdx !== i) {
|
|
1333
|
+
list.docIds[writeIdx] = list.docIds[i];
|
|
1334
|
+
list.termFrequencies[writeIdx] = list.termFrequencies[i];
|
|
1335
|
+
list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
|
|
1336
|
+
if (list.positions) {
|
|
1337
|
+
list.positions[writeIdx] = list.positions[i];
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
writeIdx++;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
list.docIds.length = writeIdx;
|
|
1344
|
+
if (list.positions) list.positions.length = writeIdx;
|
|
1345
|
+
list.length = writeIdx;
|
|
1346
|
+
list.deletedDocs.clear();
|
|
1347
|
+
}
|
|
1348
|
+
function compactDocEntries(list, docId) {
|
|
1349
|
+
let writeIdx = 0;
|
|
1350
|
+
for (let i = 0; i < list.length; i++) {
|
|
1351
|
+
if (list.docIds[i] !== docId) {
|
|
1352
|
+
if (writeIdx !== i) {
|
|
1353
|
+
list.docIds[writeIdx] = list.docIds[i];
|
|
1354
|
+
list.termFrequencies[writeIdx] = list.termFrequencies[i];
|
|
1355
|
+
list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
|
|
1356
|
+
if (list.positions) {
|
|
1357
|
+
list.positions[writeIdx] = list.positions[i];
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
writeIdx++;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
list.docIds.length = writeIdx;
|
|
1364
|
+
if (list.positions) list.positions.length = writeIdx;
|
|
1365
|
+
list.length = writeIdx;
|
|
1366
|
+
}
|
|
956
1367
|
return {
|
|
957
|
-
insert(token,
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
list
|
|
961
|
-
|
|
962
|
-
trackToken(token);
|
|
1368
|
+
insert(token, docId, termFrequency, fieldNameIndex, positions) {
|
|
1369
|
+
const list = getOrCreateList(token);
|
|
1370
|
+
if (list.deletedDocs.size > 0 && list.deletedDocs.has(docId)) {
|
|
1371
|
+
compactDocEntries(list, docId);
|
|
1372
|
+
list.deletedDocs.delete(docId);
|
|
963
1373
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1374
|
+
if (list.length >= list.termFrequencies.length) {
|
|
1375
|
+
growTypedArrays(list);
|
|
1376
|
+
}
|
|
1377
|
+
const idx = list.length;
|
|
1378
|
+
list.docIds.push(docId);
|
|
1379
|
+
list.termFrequencies[idx] = termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : termFrequency;
|
|
1380
|
+
list.fieldNameIndices[idx] = fieldNameIndex;
|
|
1381
|
+
if (positions !== null) {
|
|
1382
|
+
if (!list.positions) {
|
|
1383
|
+
list.positions = [];
|
|
1384
|
+
for (let j = 0; j < idx; j++) list.positions.push([]);
|
|
1385
|
+
}
|
|
1386
|
+
list.positions.push(positions);
|
|
1387
|
+
} else if (list.positions !== null) {
|
|
1388
|
+
list.positions.push([]);
|
|
1389
|
+
}
|
|
1390
|
+
if (!list.docIdSet.has(docId)) {
|
|
1391
|
+
list.docIdSet.add(docId);
|
|
968
1392
|
}
|
|
969
|
-
|
|
970
|
-
list.postings.push(entry);
|
|
971
|
-
if (isNewDoc) list.docFrequency++;
|
|
1393
|
+
list.length++;
|
|
972
1394
|
},
|
|
973
1395
|
remove(token, docId) {
|
|
974
1396
|
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) {
|
|
1397
|
+
if (!list || !list.docIdSet.has(docId)) return;
|
|
1398
|
+
list.docIdSet.delete(docId);
|
|
1399
|
+
list.deletedDocs.add(docId);
|
|
1400
|
+
if (list.docIdSet.size === 0) {
|
|
981
1401
|
index.delete(token);
|
|
982
1402
|
untrackToken(token);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (list.deletedDocs.size / list.length > COMPACTION_THRESHOLD) {
|
|
1406
|
+
compactList(list);
|
|
983
1407
|
}
|
|
984
1408
|
},
|
|
985
1409
|
lookup(token) {
|
|
@@ -1001,6 +1425,20 @@ function createInvertedIndex() {
|
|
|
1001
1425
|
}
|
|
1002
1426
|
return results;
|
|
1003
1427
|
},
|
|
1428
|
+
prefixSearch(prefix, limit) {
|
|
1429
|
+
if (prefix.length === 0 || limit <= 0) return [];
|
|
1430
|
+
const candidates = candidatesForPrefix(prefix, prefix.length);
|
|
1431
|
+
const results = [];
|
|
1432
|
+
for (const term of candidates) {
|
|
1433
|
+
if (!term.startsWith(prefix)) continue;
|
|
1434
|
+
const list = index.get(term);
|
|
1435
|
+
if (!list) continue;
|
|
1436
|
+
results.push({ term, documentFrequency: list.docIdSet.size });
|
|
1437
|
+
}
|
|
1438
|
+
results.sort((a, b) => b.documentFrequency - a.documentFrequency);
|
|
1439
|
+
if (results.length > limit) results.length = limit;
|
|
1440
|
+
return results;
|
|
1441
|
+
},
|
|
1004
1442
|
has(token) {
|
|
1005
1443
|
return index.has(token);
|
|
1006
1444
|
},
|
|
@@ -1017,7 +1455,19 @@ function createInvertedIndex() {
|
|
|
1017
1455
|
serialize() {
|
|
1018
1456
|
const result = /* @__PURE__ */ Object.create(null);
|
|
1019
1457
|
for (const [token, list] of index) {
|
|
1020
|
-
|
|
1458
|
+
const postings = [];
|
|
1459
|
+
for (let i = 0; i < list.length; i++) {
|
|
1460
|
+
if (list.deletedDocs.size > 0 && list.deletedDocs.has(list.docIds[i])) continue;
|
|
1461
|
+
postings.push({
|
|
1462
|
+
docId: list.docIds[i],
|
|
1463
|
+
termFrequency: list.termFrequencies[i],
|
|
1464
|
+
fieldName: fieldNameTable.names[list.fieldNameIndices[i]],
|
|
1465
|
+
positions: list.positions ? list.positions[i] : []
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
if (postings.length > 0) {
|
|
1469
|
+
result[token] = { docFrequency: list.docIdSet.size, postings };
|
|
1470
|
+
}
|
|
1021
1471
|
}
|
|
1022
1472
|
return result;
|
|
1023
1473
|
},
|
|
@@ -1025,7 +1475,43 @@ function createInvertedIndex() {
|
|
|
1025
1475
|
index.clear();
|
|
1026
1476
|
charBuckets.clear();
|
|
1027
1477
|
for (const token of Object.keys(data)) {
|
|
1028
|
-
|
|
1478
|
+
const src = data[token];
|
|
1479
|
+
const count = src.postings.length;
|
|
1480
|
+
const docIdSet = /* @__PURE__ */ new Set();
|
|
1481
|
+
const docIds = new Array(count);
|
|
1482
|
+
const termFrequencies = new Uint16Array(count);
|
|
1483
|
+
const fieldNameIndices = new Uint8Array(count);
|
|
1484
|
+
let hasPositions = false;
|
|
1485
|
+
for (let i = 0; i < count; i++) {
|
|
1486
|
+
const p = src.postings[i];
|
|
1487
|
+
docIds[i] = p.docId;
|
|
1488
|
+
termFrequencies[i] = p.termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : p.termFrequency;
|
|
1489
|
+
let fnIndex = fieldNameTable.indexMap.get(p.fieldName);
|
|
1490
|
+
if (fnIndex === void 0) {
|
|
1491
|
+
fnIndex = fieldNameTable.names.length;
|
|
1492
|
+
fieldNameTable.names.push(p.fieldName);
|
|
1493
|
+
fieldNameTable.indexMap.set(p.fieldName, fnIndex);
|
|
1494
|
+
}
|
|
1495
|
+
fieldNameIndices[i] = fnIndex;
|
|
1496
|
+
docIdSet.add(p.docId);
|
|
1497
|
+
if (p.positions.length > 0) hasPositions = true;
|
|
1498
|
+
}
|
|
1499
|
+
let positions = null;
|
|
1500
|
+
if (hasPositions) {
|
|
1501
|
+
positions = new Array(count);
|
|
1502
|
+
for (let i = 0; i < count; i++) {
|
|
1503
|
+
positions[i] = src.postings[i].positions;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
index.set(token, {
|
|
1507
|
+
length: count,
|
|
1508
|
+
docIds,
|
|
1509
|
+
termFrequencies,
|
|
1510
|
+
fieldNameIndices,
|
|
1511
|
+
positions,
|
|
1512
|
+
docIdSet,
|
|
1513
|
+
deletedDocs: /* @__PURE__ */ new Set()
|
|
1514
|
+
});
|
|
1029
1515
|
trackToken(token);
|
|
1030
1516
|
}
|
|
1031
1517
|
}
|
|
@@ -1047,9 +1533,9 @@ function collectUniqueTokens(tokens) {
|
|
|
1047
1533
|
function createPartitionStats() {
|
|
1048
1534
|
const stats = {
|
|
1049
1535
|
totalDocuments: 0,
|
|
1050
|
-
totalFieldLengths:
|
|
1051
|
-
averageFieldLengths:
|
|
1052
|
-
docFrequencies:
|
|
1536
|
+
totalFieldLengths: /* @__PURE__ */ Object.create(null),
|
|
1537
|
+
averageFieldLengths: /* @__PURE__ */ Object.create(null),
|
|
1538
|
+
docFrequencies: /* @__PURE__ */ Object.create(null),
|
|
1053
1539
|
addDocument(fieldLengths, tokens) {
|
|
1054
1540
|
stats.totalDocuments++;
|
|
1055
1541
|
const fields = Object.keys(fieldLengths);
|
|
@@ -1089,7 +1575,7 @@ function createPartitionStats() {
|
|
|
1089
1575
|
},
|
|
1090
1576
|
recalculateAverages() {
|
|
1091
1577
|
const fields = Object.keys(stats.totalFieldLengths);
|
|
1092
|
-
const newAverages =
|
|
1578
|
+
const newAverages = /* @__PURE__ */ Object.create(null);
|
|
1093
1579
|
for (let i = 0; i < fields.length; i++) {
|
|
1094
1580
|
const field = fields[i];
|
|
1095
1581
|
newAverages[field] = stats.totalDocuments > 0 ? stats.totalFieldLengths[field] / stats.totalDocuments : 0;
|
|
@@ -1097,18 +1583,42 @@ function createPartitionStats() {
|
|
|
1097
1583
|
stats.averageFieldLengths = newAverages;
|
|
1098
1584
|
},
|
|
1099
1585
|
serialize() {
|
|
1586
|
+
const tfl = /* @__PURE__ */ Object.create(null);
|
|
1587
|
+
for (const key of Object.keys(stats.totalFieldLengths)) {
|
|
1588
|
+
tfl[key] = stats.totalFieldLengths[key];
|
|
1589
|
+
}
|
|
1590
|
+
const afl = /* @__PURE__ */ Object.create(null);
|
|
1591
|
+
for (const key of Object.keys(stats.averageFieldLengths)) {
|
|
1592
|
+
afl[key] = stats.averageFieldLengths[key];
|
|
1593
|
+
}
|
|
1594
|
+
const df = /* @__PURE__ */ Object.create(null);
|
|
1595
|
+
for (const key of Object.keys(stats.docFrequencies)) {
|
|
1596
|
+
df[key] = stats.docFrequencies[key];
|
|
1597
|
+
}
|
|
1100
1598
|
return {
|
|
1101
1599
|
totalDocuments: stats.totalDocuments,
|
|
1102
|
-
totalFieldLengths:
|
|
1103
|
-
averageFieldLengths:
|
|
1104
|
-
docFrequencies:
|
|
1600
|
+
totalFieldLengths: tfl,
|
|
1601
|
+
averageFieldLengths: afl,
|
|
1602
|
+
docFrequencies: df
|
|
1105
1603
|
};
|
|
1106
1604
|
},
|
|
1107
1605
|
deserialize(data) {
|
|
1108
1606
|
stats.totalDocuments = data.totalDocuments;
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1607
|
+
const tfl = /* @__PURE__ */ Object.create(null);
|
|
1608
|
+
for (const key of Object.keys(data.totalFieldLengths)) {
|
|
1609
|
+
tfl[key] = data.totalFieldLengths[key];
|
|
1610
|
+
}
|
|
1611
|
+
stats.totalFieldLengths = tfl;
|
|
1612
|
+
const afl = /* @__PURE__ */ Object.create(null);
|
|
1613
|
+
for (const key of Object.keys(data.averageFieldLengths)) {
|
|
1614
|
+
afl[key] = data.averageFieldLengths[key];
|
|
1615
|
+
}
|
|
1616
|
+
stats.averageFieldLengths = afl;
|
|
1617
|
+
const df = /* @__PURE__ */ Object.create(null);
|
|
1618
|
+
for (const key of Object.keys(data.docFrequencies)) {
|
|
1619
|
+
df[key] = data.docFrequencies[key];
|
|
1620
|
+
}
|
|
1621
|
+
stats.docFrequencies = df;
|
|
1112
1622
|
}
|
|
1113
1623
|
};
|
|
1114
1624
|
return stats;
|
|
@@ -1259,34 +1769,6 @@ function createGeoIndex() {
|
|
|
1259
1769
|
};
|
|
1260
1770
|
}
|
|
1261
1771
|
|
|
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
1772
|
// src/vector/brute-force.ts
|
|
1291
1773
|
function createBruteForceVectorStore(dimension) {
|
|
1292
1774
|
const vectors = /* @__PURE__ */ new Map();
|
|
@@ -1355,6 +1837,120 @@ function createBruteForceVectorStore(dimension) {
|
|
|
1355
1837
|
};
|
|
1356
1838
|
}
|
|
1357
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
|
+
|
|
1358
1954
|
// src/core/field-index.ts
|
|
1359
1955
|
function lowerBound(entries, value) {
|
|
1360
1956
|
let lo = 0;
|
|
@@ -1385,12 +1981,20 @@ function collectDocIds(entries, from, to) {
|
|
|
1385
1981
|
}
|
|
1386
1982
|
function createNumericIndex() {
|
|
1387
1983
|
let entries = [];
|
|
1984
|
+
let sorted = true;
|
|
1985
|
+
function ensureSorted() {
|
|
1986
|
+
if (!sorted) {
|
|
1987
|
+
entries.sort((a, b) => a.value - b.value);
|
|
1988
|
+
sorted = true;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1388
1991
|
return {
|
|
1389
1992
|
insert(docId, value) {
|
|
1390
|
-
|
|
1391
|
-
|
|
1993
|
+
entries.push({ value, docId });
|
|
1994
|
+
sorted = false;
|
|
1392
1995
|
},
|
|
1393
1996
|
remove(docId, value) {
|
|
1997
|
+
ensureSorted();
|
|
1394
1998
|
const start = lowerBound(entries, value);
|
|
1395
1999
|
const end = upperBound(entries, value);
|
|
1396
2000
|
for (let i = start; i < end; i++) {
|
|
@@ -1401,9 +2005,11 @@ function createNumericIndex() {
|
|
|
1401
2005
|
}
|
|
1402
2006
|
},
|
|
1403
2007
|
queryEq(value) {
|
|
2008
|
+
ensureSorted();
|
|
1404
2009
|
return collectDocIds(entries, lowerBound(entries, value), upperBound(entries, value));
|
|
1405
2010
|
},
|
|
1406
2011
|
queryNe(value) {
|
|
2012
|
+
ensureSorted();
|
|
1407
2013
|
const excluded = this.queryEq(value);
|
|
1408
2014
|
const result = /* @__PURE__ */ new Set();
|
|
1409
2015
|
for (const entry of entries) {
|
|
@@ -1412,18 +2018,23 @@ function createNumericIndex() {
|
|
|
1412
2018
|
return result;
|
|
1413
2019
|
},
|
|
1414
2020
|
queryGt(value) {
|
|
2021
|
+
ensureSorted();
|
|
1415
2022
|
return collectDocIds(entries, upperBound(entries, value), entries.length);
|
|
1416
2023
|
},
|
|
1417
2024
|
queryGte(value) {
|
|
2025
|
+
ensureSorted();
|
|
1418
2026
|
return collectDocIds(entries, lowerBound(entries, value), entries.length);
|
|
1419
2027
|
},
|
|
1420
2028
|
queryLt(value) {
|
|
2029
|
+
ensureSorted();
|
|
1421
2030
|
return collectDocIds(entries, 0, lowerBound(entries, value));
|
|
1422
2031
|
},
|
|
1423
2032
|
queryLte(value) {
|
|
2033
|
+
ensureSorted();
|
|
1424
2034
|
return collectDocIds(entries, 0, upperBound(entries, value));
|
|
1425
2035
|
},
|
|
1426
2036
|
queryBetween(min, max) {
|
|
2037
|
+
ensureSorted();
|
|
1427
2038
|
return collectDocIds(entries, lowerBound(entries, min), upperBound(entries, max));
|
|
1428
2039
|
},
|
|
1429
2040
|
getAllDocIds() {
|
|
@@ -1434,12 +2045,15 @@ function createNumericIndex() {
|
|
|
1434
2045
|
},
|
|
1435
2046
|
clear() {
|
|
1436
2047
|
entries = [];
|
|
2048
|
+
sorted = true;
|
|
1437
2049
|
},
|
|
1438
2050
|
serialize() {
|
|
2051
|
+
ensureSorted();
|
|
1439
2052
|
return entries.slice();
|
|
1440
2053
|
},
|
|
1441
2054
|
deserialize(data) {
|
|
1442
2055
|
entries = data.slice();
|
|
2056
|
+
sorted = true;
|
|
1443
2057
|
}
|
|
1444
2058
|
};
|
|
1445
2059
|
}
|
|
@@ -1567,6 +2181,14 @@ function createEnumIndex() {
|
|
|
1567
2181
|
}
|
|
1568
2182
|
|
|
1569
2183
|
// src/core/partition/utils.ts
|
|
2184
|
+
function getOrCreateFieldNameIndex(table, fieldName) {
|
|
2185
|
+
const existing = table.indexMap.get(fieldName);
|
|
2186
|
+
if (existing !== void 0) return existing;
|
|
2187
|
+
const idx = table.names.length;
|
|
2188
|
+
table.names.push(fieldName);
|
|
2189
|
+
table.indexMap.set(fieldName, idx);
|
|
2190
|
+
return idx;
|
|
2191
|
+
}
|
|
1570
2192
|
var VECTOR_PATTERN2 = /^vector\[(\d+)]$/;
|
|
1571
2193
|
function getNestedValue(obj, path) {
|
|
1572
2194
|
const parts = path.split(".");
|
|
@@ -1643,7 +2265,7 @@ function ensureFieldIndex(state, fieldPath, fieldType) {
|
|
|
1643
2265
|
} else {
|
|
1644
2266
|
const dim = parseVectorDimension(fieldType);
|
|
1645
2267
|
if (dim !== null && !state.vectorStores.has(fieldPath)) {
|
|
1646
|
-
state.vectorStores.set(fieldPath,
|
|
2268
|
+
state.vectorStores.set(fieldPath, createVectorSearchEngine(dim));
|
|
1647
2269
|
}
|
|
1648
2270
|
}
|
|
1649
2271
|
}
|
|
@@ -1667,53 +2289,71 @@ function indexStringField(state, docId, fieldPath, text, language, options, fiel
|
|
|
1667
2289
|
const result = tokenize(text, language, tokenizeOptions(options));
|
|
1668
2290
|
fieldLengths[fieldPath] = result.tokens.length;
|
|
1669
2291
|
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));
|
|
2292
|
+
const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
|
|
2293
|
+
if (state.trackPositions) {
|
|
2294
|
+
const tokenFreqs = /* @__PURE__ */ new Map();
|
|
1697
2295
|
for (const t of result.tokens) {
|
|
1698
2296
|
const existing = tokenFreqs.get(t.token);
|
|
1699
2297
|
if (existing) {
|
|
1700
2298
|
existing.count++;
|
|
1701
|
-
existing.positions.push(
|
|
2299
|
+
existing.positions.push(t.position);
|
|
1702
2300
|
} else {
|
|
1703
|
-
tokenFreqs.set(t.token, { count: 1, positions: [
|
|
2301
|
+
tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
|
|
1704
2302
|
}
|
|
1705
2303
|
fieldTokenList.push(t.token);
|
|
1706
2304
|
}
|
|
1707
|
-
|
|
2305
|
+
for (const [token, freq] of tokenFreqs) {
|
|
2306
|
+
state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
|
|
2307
|
+
}
|
|
2308
|
+
} else {
|
|
2309
|
+
const tokenCounts = /* @__PURE__ */ new Map();
|
|
2310
|
+
for (const t of result.tokens) {
|
|
2311
|
+
tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
|
|
2312
|
+
fieldTokenList.push(t.token);
|
|
2313
|
+
}
|
|
2314
|
+
for (const [token, count] of tokenCounts) {
|
|
2315
|
+
state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
|
|
2316
|
+
}
|
|
1708
2317
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2318
|
+
tokensByField[fieldPath] = fieldTokenList;
|
|
2319
|
+
}
|
|
2320
|
+
function indexStringArrayField(state, docId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
|
|
2321
|
+
const fieldTokenList = [];
|
|
2322
|
+
const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
|
|
2323
|
+
if (state.trackPositions) {
|
|
2324
|
+
const tokenFreqs = /* @__PURE__ */ new Map();
|
|
2325
|
+
let positionOffset = 0;
|
|
2326
|
+
for (const item of arr) {
|
|
2327
|
+
const result = tokenize(item, language, tokenizeOptions(options));
|
|
2328
|
+
for (const t of result.tokens) {
|
|
2329
|
+
const existing = tokenFreqs.get(t.token);
|
|
2330
|
+
if (existing) {
|
|
2331
|
+
existing.count++;
|
|
2332
|
+
existing.positions.push(positionOffset + t.position);
|
|
2333
|
+
} else {
|
|
2334
|
+
tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
|
|
2335
|
+
}
|
|
2336
|
+
fieldTokenList.push(t.token);
|
|
2337
|
+
}
|
|
2338
|
+
positionOffset += result.tokens.length;
|
|
2339
|
+
}
|
|
2340
|
+
fieldLengths[fieldPath] = fieldTokenList.length;
|
|
2341
|
+
for (const [token, freq] of tokenFreqs) {
|
|
2342
|
+
state.invertedIdx.insert(token, docId, freq.count, fieldNameIndex, freq.positions);
|
|
2343
|
+
}
|
|
2344
|
+
} else {
|
|
2345
|
+
const tokenCounts = /* @__PURE__ */ new Map();
|
|
2346
|
+
for (const item of arr) {
|
|
2347
|
+
const result = tokenize(item, language, tokenizeOptions(options));
|
|
2348
|
+
for (const t of result.tokens) {
|
|
2349
|
+
tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
|
|
2350
|
+
fieldTokenList.push(t.token);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
fieldLengths[fieldPath] = fieldTokenList.length;
|
|
2354
|
+
for (const [token, count] of tokenCounts) {
|
|
2355
|
+
state.invertedIdx.insert(token, docId, count, fieldNameIndex, null);
|
|
2356
|
+
}
|
|
1717
2357
|
}
|
|
1718
2358
|
tokensByField[fieldPath] = fieldTokenList;
|
|
1719
2359
|
}
|
|
@@ -2332,66 +2972,188 @@ function computeBM25WithGlobalStats(termFrequency, globalDocFrequency, globalTot
|
|
|
2332
2972
|
}
|
|
2333
2973
|
|
|
2334
2974
|
// src/core/partition/search.ts
|
|
2975
|
+
var DEFAULT_MAX_RESULTS = 1e3;
|
|
2335
2976
|
function searchFulltext(state, params) {
|
|
2336
|
-
const {
|
|
2977
|
+
const {
|
|
2978
|
+
queryTokens,
|
|
2979
|
+
fields,
|
|
2980
|
+
boost,
|
|
2981
|
+
tolerance = 0,
|
|
2982
|
+
prefixLength = 2,
|
|
2983
|
+
exact = false,
|
|
2984
|
+
bm25Params,
|
|
2985
|
+
globalStats,
|
|
2986
|
+
maxResults,
|
|
2987
|
+
termMatch
|
|
2988
|
+
} = params;
|
|
2337
2989
|
if (queryTokens.length === 0) {
|
|
2338
2990
|
return { scored: [], totalMatched: 0 };
|
|
2339
2991
|
}
|
|
2340
|
-
const docScores = /* @__PURE__ */ new Map();
|
|
2341
2992
|
const totalDocs = globalStats?.totalDocuments ?? state.stats.totalDocuments;
|
|
2342
2993
|
const avgFieldLengths = globalStats?.averageFieldLengths ?? state.stats.averageFieldLengths;
|
|
2343
2994
|
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
|
-
|
|
2995
|
+
const scoreFn = globalStats ? computeBM25WithGlobalStats : computeBM25;
|
|
2996
|
+
const docScores = /* @__PURE__ */ new Map();
|
|
2997
|
+
const fieldLengthCache = /* @__PURE__ */ new Map();
|
|
2998
|
+
const useIntersection = termMatch === "all" && queryTokens.length > 1;
|
|
2999
|
+
const fieldNames = state.fieldNameTable.names;
|
|
3000
|
+
if (useIntersection) {
|
|
3001
|
+
const resolved = [];
|
|
3002
|
+
for (const qt of queryTokens) {
|
|
3003
|
+
const rawMatches = exact ? (() => {
|
|
3004
|
+
const postingList = state.invertedIdx.lookup(qt.token);
|
|
3005
|
+
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
3006
|
+
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
3007
|
+
let totalPostings = 0;
|
|
3008
|
+
const matches = [];
|
|
3009
|
+
for (const m of rawMatches) {
|
|
3010
|
+
const docFreq = globalStats ? globalDocFreqs[m.token] ?? m.postingList.docIdSet.size : m.postingList.docIdSet.size;
|
|
3011
|
+
const idf = computeIDF(docFreq, totalDocs);
|
|
3012
|
+
totalPostings += m.postingList.length;
|
|
3013
|
+
matches.push({ token: m.token, docFreq, idf, postingList: m.postingList });
|
|
3014
|
+
}
|
|
3015
|
+
resolved.push({ token: qt.token, matches, totalPostings });
|
|
3016
|
+
}
|
|
3017
|
+
resolved.sort((a, b) => a.totalPostings - b.totalPostings);
|
|
3018
|
+
for (let tokenIndex = 0; tokenIndex < resolved.length; tokenIndex++) {
|
|
3019
|
+
for (const match of resolved[tokenIndex].matches) {
|
|
3020
|
+
const list = match.postingList;
|
|
3021
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3022
|
+
for (let pi = 0; pi < list.length; pi++) {
|
|
3023
|
+
const docId = list.docIds[pi];
|
|
3024
|
+
if (hasDeleted && list.deletedDocs.has(docId)) continue;
|
|
3025
|
+
if (tokenIndex > 0 && !docScores.has(docId)) continue;
|
|
3026
|
+
const fieldName = fieldNames[list.fieldNameIndices[pi]];
|
|
3027
|
+
if (fields && !fields.includes(fieldName)) continue;
|
|
3028
|
+
const termFrequency = list.termFrequencies[pi];
|
|
3029
|
+
const fieldBoost = boost?.[fieldName] ?? 1;
|
|
3030
|
+
const avgLen = avgFieldLengths[fieldName] ?? 1;
|
|
3031
|
+
let cachedLengths = fieldLengthCache.get(docId);
|
|
3032
|
+
if (cachedLengths === void 0) {
|
|
3033
|
+
const storedDoc = state.docStore.get(docId);
|
|
3034
|
+
cachedLengths = storedDoc?.fieldLengths ?? null;
|
|
3035
|
+
fieldLengthCache.set(docId, cachedLengths);
|
|
3036
|
+
}
|
|
3037
|
+
const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
|
|
3038
|
+
let termScore = scoreFn(termFrequency, match.docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
|
|
3039
|
+
termScore *= fieldBoost;
|
|
3040
|
+
const existing = docScores.get(docId);
|
|
3041
|
+
if (existing) {
|
|
3042
|
+
existing.score += termScore;
|
|
3043
|
+
existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
|
|
3044
|
+
existing.fieldLengths[fieldName] = actualFieldLength;
|
|
3045
|
+
existing.idf[match.token] = match.idf;
|
|
3046
|
+
} else {
|
|
3047
|
+
docScores.set(docId, {
|
|
3048
|
+
score: termScore,
|
|
3049
|
+
termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
|
|
3050
|
+
fieldLengths: { [fieldName]: actualFieldLength },
|
|
3051
|
+
idf: { [match.token]: match.idf }
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
} else {
|
|
3058
|
+
for (const qt of queryTokens) {
|
|
3059
|
+
const matchingPostings = exact ? (() => {
|
|
3060
|
+
const postingList = state.invertedIdx.lookup(qt.token);
|
|
3061
|
+
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
3062
|
+
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
3063
|
+
for (const match of matchingPostings) {
|
|
3064
|
+
const docFreq = globalStats ? globalDocFreqs[match.token] ?? match.postingList.docIdSet.size : match.postingList.docIdSet.size;
|
|
3065
|
+
const idf = computeIDF(docFreq, totalDocs);
|
|
3066
|
+
const list = match.postingList;
|
|
3067
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3068
|
+
for (let pi = 0; pi < list.length; pi++) {
|
|
3069
|
+
const docId = list.docIds[pi];
|
|
3070
|
+
if (hasDeleted && list.deletedDocs.has(docId)) continue;
|
|
3071
|
+
const fieldName = fieldNames[list.fieldNameIndices[pi]];
|
|
3072
|
+
if (fields && !fields.includes(fieldName)) continue;
|
|
3073
|
+
const termFrequency = list.termFrequencies[pi];
|
|
3074
|
+
const fieldBoost = boost?.[fieldName] ?? 1;
|
|
3075
|
+
const avgLen = avgFieldLengths[fieldName] ?? 1;
|
|
3076
|
+
let cachedLengths = fieldLengthCache.get(docId);
|
|
3077
|
+
if (cachedLengths === void 0) {
|
|
3078
|
+
const storedDoc = state.docStore.get(docId);
|
|
3079
|
+
cachedLengths = storedDoc?.fieldLengths ?? null;
|
|
3080
|
+
fieldLengthCache.set(docId, cachedLengths);
|
|
3081
|
+
}
|
|
3082
|
+
const actualFieldLength = cachedLengths?.[fieldName] ?? avgLen;
|
|
3083
|
+
let termScore = scoreFn(termFrequency, docFreq, totalDocs, actualFieldLength, avgLen, bm25Params);
|
|
3084
|
+
termScore *= fieldBoost;
|
|
3085
|
+
const existing = docScores.get(docId);
|
|
3086
|
+
if (existing) {
|
|
3087
|
+
existing.score += termScore;
|
|
3088
|
+
existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
|
|
3089
|
+
existing.fieldLengths[fieldName] = actualFieldLength;
|
|
3090
|
+
existing.idf[match.token] = idf;
|
|
3091
|
+
} else {
|
|
3092
|
+
docScores.set(docId, {
|
|
3093
|
+
score: termScore,
|
|
3094
|
+
termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
|
|
3095
|
+
fieldLengths: { [fieldName]: actualFieldLength },
|
|
3096
|
+
idf: { [match.token]: idf }
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
2376
3099
|
}
|
|
2377
3100
|
}
|
|
2378
3101
|
}
|
|
2379
3102
|
}
|
|
2380
|
-
const
|
|
3103
|
+
const totalMatched = docScores.size;
|
|
3104
|
+
const k = maxResults !== void 0 && maxResults > 0 ? maxResults : DEFAULT_MAX_RESULTS;
|
|
3105
|
+
const scored = topKFromMap(docScores, Math.min(k, totalMatched));
|
|
3106
|
+
return { scored, totalMatched };
|
|
3107
|
+
}
|
|
3108
|
+
function topKFromMap(docScores, k) {
|
|
3109
|
+
if (k <= 0) return [];
|
|
3110
|
+
const heap = [];
|
|
2381
3111
|
for (const [docId, data] of docScores) {
|
|
2382
|
-
|
|
2383
|
-
docId,
|
|
3112
|
+
if (heap.length < k) {
|
|
3113
|
+
heap.push({ docId, score: data.score });
|
|
3114
|
+
if (heap.length === k) buildMinHeap(heap);
|
|
3115
|
+
} else if (data.score > heap[0].score) {
|
|
3116
|
+
heap[0] = { docId, score: data.score };
|
|
3117
|
+
siftDown(heap, 0);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
heap.sort((a, b) => b.score - a.score);
|
|
3121
|
+
const result = new Array(heap.length);
|
|
3122
|
+
for (let i = 0; i < heap.length; i++) {
|
|
3123
|
+
const data = docScores.get(heap[i].docId);
|
|
3124
|
+
if (!data) continue;
|
|
3125
|
+
result[i] = {
|
|
3126
|
+
docId: heap[i].docId,
|
|
2384
3127
|
score: data.score,
|
|
2385
3128
|
termFrequencies: data.termFrequencies,
|
|
2386
3129
|
fieldLengths: data.fieldLengths,
|
|
2387
3130
|
idf: data.idf
|
|
2388
|
-
}
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
return result;
|
|
3134
|
+
}
|
|
3135
|
+
function buildMinHeap(heap) {
|
|
3136
|
+
for (let i = (heap.length >> 1) - 1; i >= 0; i--) {
|
|
3137
|
+
siftDown(heap, i);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
function siftDown(heap, idx) {
|
|
3141
|
+
const len = heap.length;
|
|
3142
|
+
while (true) {
|
|
3143
|
+
let smallest = idx;
|
|
3144
|
+
const left = 2 * idx + 1;
|
|
3145
|
+
const right = 2 * idx + 2;
|
|
3146
|
+
if (left < len && heap[left].score < heap[smallest].score) smallest = left;
|
|
3147
|
+
if (right < len && heap[right].score < heap[smallest].score) smallest = right;
|
|
3148
|
+
if (smallest === idx) break;
|
|
3149
|
+
const tmp = heap[idx];
|
|
3150
|
+
heap[idx] = heap[smallest];
|
|
3151
|
+
heap[smallest] = tmp;
|
|
3152
|
+
idx = smallest;
|
|
2389
3153
|
}
|
|
2390
|
-
scored.sort((a, b) => b.score - a.score);
|
|
2391
|
-
return { scored, totalMatched: scored.length };
|
|
2392
3154
|
}
|
|
2393
3155
|
function searchVector(state, params) {
|
|
2394
|
-
const { field, value, k, similarity = 0, metric = "cosine", filterDocIds } = params;
|
|
3156
|
+
const { field, value, k, similarity = 0, metric = "cosine", filterDocIds, efSearch } = params;
|
|
2395
3157
|
const vecStore = state.vectorStores.get(field);
|
|
2396
3158
|
if (!vecStore) {
|
|
2397
3159
|
return { scored: [], totalMatched: 0 };
|
|
@@ -2404,7 +3166,7 @@ function searchVector(state, params) {
|
|
|
2404
3166
|
);
|
|
2405
3167
|
}
|
|
2406
3168
|
const queryVec = new Float32Array(value);
|
|
2407
|
-
const results = vecStore.search(queryVec, k, metric, similarity, filterDocIds);
|
|
3169
|
+
const results = vecStore.search(queryVec, k, metric, similarity, filterDocIds, efSearch);
|
|
2408
3170
|
return { scored: results, totalMatched: results.length };
|
|
2409
3171
|
}
|
|
2410
3172
|
function buildFilterContext(state, schema) {
|
|
@@ -2587,7 +3349,7 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
|
|
|
2587
3349
|
serializedVectors[path] = {
|
|
2588
3350
|
dimension: store.dimension,
|
|
2589
3351
|
vectors,
|
|
2590
|
-
hnswGraph:
|
|
3352
|
+
hnswGraph: store.serializeHNSW()
|
|
2591
3353
|
};
|
|
2592
3354
|
}
|
|
2593
3355
|
const serializedStats = state.stats.serialize();
|
|
@@ -2611,6 +3373,109 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
|
|
|
2611
3373
|
statistics: serializedStats
|
|
2612
3374
|
};
|
|
2613
3375
|
}
|
|
3376
|
+
function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema) {
|
|
3377
|
+
const flatSchema = getFlatSchema(state, schema);
|
|
3378
|
+
const flatSchemaStrings = {};
|
|
3379
|
+
for (const [k, v] of Object.entries(flatSchema)) {
|
|
3380
|
+
flatSchemaStrings[k] = v;
|
|
3381
|
+
}
|
|
3382
|
+
const wireDocs = /* @__PURE__ */ Object.create(null);
|
|
3383
|
+
for (const [docId, stored] of state.docStore.all()) {
|
|
3384
|
+
wireDocs[docId] = {
|
|
3385
|
+
fields: structuredClone(stored.fields),
|
|
3386
|
+
field_lengths: { ...stored.fieldLengths }
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
const fieldNames = [...state.fieldNameTable.names];
|
|
3390
|
+
const wireEntries = /* @__PURE__ */ Object.create(null);
|
|
3391
|
+
for (const token of state.invertedIdx.tokens()) {
|
|
3392
|
+
const list = state.invertedIdx.lookup(token);
|
|
3393
|
+
if (!list) continue;
|
|
3394
|
+
const hasDeleted = list.deletedDocs.size > 0;
|
|
3395
|
+
const ids = [];
|
|
3396
|
+
const tf = [];
|
|
3397
|
+
const fi = [];
|
|
3398
|
+
const pos = list.positions ? [] : null;
|
|
3399
|
+
for (let i = 0; i < list.length; i++) {
|
|
3400
|
+
if (hasDeleted && list.deletedDocs.has(list.docIds[i])) continue;
|
|
3401
|
+
ids.push(list.docIds[i]);
|
|
3402
|
+
tf.push(list.termFrequencies[i]);
|
|
3403
|
+
fi.push(list.fieldNameIndices[i]);
|
|
3404
|
+
if (pos && list.positions) {
|
|
3405
|
+
pos.push(list.positions[i]);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
if (ids.length > 0) {
|
|
3409
|
+
wireEntries[token] = { df: list.docIdSet.size, ids, tf, fi, pos };
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
const wireNumeric = {};
|
|
3413
|
+
for (const [path, idx] of state.numericIndexes) {
|
|
3414
|
+
wireNumeric[path] = idx.serialize().map((e) => ({ value: e.value, doc_id: e.docId }));
|
|
3415
|
+
}
|
|
3416
|
+
const wireBoolean = {};
|
|
3417
|
+
for (const [path, idx] of state.booleanIndexes) {
|
|
3418
|
+
const s = idx.serialize();
|
|
3419
|
+
wireBoolean[path] = { true_docs: s.trueDocs, false_docs: s.falseDocs };
|
|
3420
|
+
}
|
|
3421
|
+
const wireEnum = {};
|
|
3422
|
+
for (const [path, idx] of state.enumIndexes) {
|
|
3423
|
+
wireEnum[path] = idx.serialize();
|
|
3424
|
+
}
|
|
3425
|
+
const wireGeo = {};
|
|
3426
|
+
for (const [path, idx] of state.geoIndexes) {
|
|
3427
|
+
wireGeo[path] = idx.serialize().map((e) => ({ lat: e.lat, lon: e.lon, doc_id: e.docId }));
|
|
3428
|
+
}
|
|
3429
|
+
const wireVectors = {};
|
|
3430
|
+
for (const [path, store] of state.vectorStores) {
|
|
3431
|
+
const vectors = [];
|
|
3432
|
+
for (const [, entry] of store.entries()) {
|
|
3433
|
+
vectors.push({ doc_id: entry.docId, vector: Array.from(entry.vector) });
|
|
3434
|
+
}
|
|
3435
|
+
const hnswData = store.serializeHNSW();
|
|
3436
|
+
wireVectors[path] = {
|
|
3437
|
+
dimension: store.dimension,
|
|
3438
|
+
vectors,
|
|
3439
|
+
hnsw_graph: hnswData ? {
|
|
3440
|
+
entry_point: hnswData.entryPoint,
|
|
3441
|
+
max_layer: hnswData.maxLayer,
|
|
3442
|
+
m: hnswData.m,
|
|
3443
|
+
ef_construction: hnswData.efConstruction,
|
|
3444
|
+
metric: hnswData.metric,
|
|
3445
|
+
nodes: hnswData.nodes
|
|
3446
|
+
} : null
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
const serializedStats = state.stats.serialize();
|
|
3450
|
+
return {
|
|
3451
|
+
v: 2,
|
|
3452
|
+
index_name: indexName,
|
|
3453
|
+
partition_id: partitionId,
|
|
3454
|
+
total_partitions: totalPartitions,
|
|
3455
|
+
language,
|
|
3456
|
+
schema: flatSchemaStrings,
|
|
3457
|
+
doc_count: state.docStore.count(),
|
|
3458
|
+
avg_doc_length: Object.values(serializedStats.averageFieldLengths).reduce((sum, v) => sum + v, 0),
|
|
3459
|
+
documents: wireDocs,
|
|
3460
|
+
inverted_index: {
|
|
3461
|
+
field_names: fieldNames,
|
|
3462
|
+
entries: wireEntries
|
|
3463
|
+
},
|
|
3464
|
+
field_indexes: {
|
|
3465
|
+
numeric: wireNumeric,
|
|
3466
|
+
boolean: wireBoolean,
|
|
3467
|
+
enum: wireEnum,
|
|
3468
|
+
geopoint: wireGeo
|
|
3469
|
+
},
|
|
3470
|
+
vector_data: wireVectors,
|
|
3471
|
+
statistics: {
|
|
3472
|
+
total_documents: serializedStats.totalDocuments,
|
|
3473
|
+
total_field_lengths: serializedStats.totalFieldLengths,
|
|
3474
|
+
average_field_lengths: serializedStats.averageFieldLengths,
|
|
3475
|
+
doc_frequencies: serializedStats.docFrequencies
|
|
3476
|
+
}
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
2614
3479
|
function deserializePartition(state, data, clearFn, schema) {
|
|
2615
3480
|
clearFn();
|
|
2616
3481
|
const invertedData = /* @__PURE__ */ Object.create(null);
|
|
@@ -2621,7 +3486,7 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2621
3486
|
docId: p.docId,
|
|
2622
3487
|
termFrequency: p.termFrequency,
|
|
2623
3488
|
fieldName: p.field,
|
|
2624
|
-
positions: [...p.positions]
|
|
3489
|
+
positions: state.trackPositions ? [...p.positions] : []
|
|
2625
3490
|
}))
|
|
2626
3491
|
};
|
|
2627
3492
|
}
|
|
@@ -2652,10 +3517,13 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2652
3517
|
state.geoIndexes.set(path, geoIdx);
|
|
2653
3518
|
}
|
|
2654
3519
|
for (const [path, vecData] of Object.entries(data.vectorData)) {
|
|
2655
|
-
const store =
|
|
3520
|
+
const store = createVectorSearchEngine(vecData.dimension);
|
|
2656
3521
|
for (const entry of vecData.vectors) {
|
|
2657
3522
|
store.insert(entry.docId, new Float32Array(entry.vector));
|
|
2658
3523
|
}
|
|
3524
|
+
if (vecData.hnswGraph) {
|
|
3525
|
+
store.deserializeHNSW(vecData.hnswGraph);
|
|
3526
|
+
}
|
|
2659
3527
|
state.vectorStores.set(path, store);
|
|
2660
3528
|
}
|
|
2661
3529
|
state.stats.deserialize(data.statistics);
|
|
@@ -2676,9 +3544,15 @@ function deserializePartition(state, data, clearFn, schema) {
|
|
|
2676
3544
|
}
|
|
2677
3545
|
|
|
2678
3546
|
// src/core/partition/index.ts
|
|
2679
|
-
function createPartitionIndex(partitionId) {
|
|
3547
|
+
function createPartitionIndex(partitionId, trackPositions = true, vectorPromotionConfig) {
|
|
3548
|
+
const vectorPromoter = vectorPromotionConfig ? createVectorPromoter({
|
|
3549
|
+
promotionThreshold: vectorPromotionConfig.threshold,
|
|
3550
|
+
hnswConfig: vectorPromotionConfig.hnswConfig,
|
|
3551
|
+
workerStrategy: vectorPromotionConfig.workerStrategy
|
|
3552
|
+
}) : null;
|
|
3553
|
+
const fieldNameTable = { names: [], indexMap: /* @__PURE__ */ new Map() };
|
|
2680
3554
|
const state = {
|
|
2681
|
-
invertedIdx: createInvertedIndex(),
|
|
3555
|
+
invertedIdx: createInvertedIndex(fieldNameTable),
|
|
2682
3556
|
docStore: createDocumentStore(),
|
|
2683
3557
|
stats: createPartitionStats(),
|
|
2684
3558
|
numericIndexes: /* @__PURE__ */ new Map(),
|
|
@@ -2686,10 +3560,13 @@ function createPartitionIndex(partitionId) {
|
|
|
2686
3560
|
enumIndexes: /* @__PURE__ */ new Map(),
|
|
2687
3561
|
geoIndexes: /* @__PURE__ */ new Map(),
|
|
2688
3562
|
vectorStores: /* @__PURE__ */ new Map(),
|
|
3563
|
+
fieldNameTable,
|
|
2689
3564
|
flatSchemaCache: null,
|
|
2690
|
-
lastSchemaRef: null
|
|
3565
|
+
lastSchemaRef: null,
|
|
3566
|
+
trackPositions
|
|
2691
3567
|
};
|
|
2692
3568
|
function clearAll() {
|
|
3569
|
+
vectorPromoter?.shutdown();
|
|
2693
3570
|
state.invertedIdx.clear();
|
|
2694
3571
|
state.docStore.clear();
|
|
2695
3572
|
for (const idx of state.numericIndexes.values()) idx.clear();
|
|
@@ -2723,6 +3600,9 @@ function createPartitionIndex(partitionId) {
|
|
|
2723
3600
|
}
|
|
2724
3601
|
if (options?.validate !== false) {
|
|
2725
3602
|
validateDocument(document, schema);
|
|
3603
|
+
if (options?.strict) {
|
|
3604
|
+
validateDocumentStrict(document, schema);
|
|
3605
|
+
}
|
|
2726
3606
|
}
|
|
2727
3607
|
const flatSchema = getFlatSchema(state, schema);
|
|
2728
3608
|
const { fieldLengths, tokensByField } = indexDocument(
|
|
@@ -2733,8 +3613,13 @@ function createPartitionIndex(partitionId) {
|
|
|
2733
3613
|
language,
|
|
2734
3614
|
options
|
|
2735
3615
|
);
|
|
2736
|
-
|
|
3616
|
+
if (options?.skipClone) {
|
|
3617
|
+
state.docStore.storeRef(docId, document, fieldLengths);
|
|
3618
|
+
} else {
|
|
3619
|
+
state.docStore.store(docId, document, fieldLengths);
|
|
3620
|
+
}
|
|
2737
3621
|
state.stats.addDocument(fieldLengths, tokensByField);
|
|
3622
|
+
vectorPromoter?.check(state.vectorStores);
|
|
2738
3623
|
},
|
|
2739
3624
|
remove(docId, schema, language, options) {
|
|
2740
3625
|
const stored = state.docStore.get(docId);
|
|
@@ -2759,6 +3644,9 @@ function createPartitionIndex(partitionId) {
|
|
|
2759
3644
|
}
|
|
2760
3645
|
if (options?.validate !== false) {
|
|
2761
3646
|
validateDocument(document, schema);
|
|
3647
|
+
if (options?.strict) {
|
|
3648
|
+
validateDocumentStrict(document, schema);
|
|
3649
|
+
}
|
|
2762
3650
|
}
|
|
2763
3651
|
const flatSchema = getFlatSchema(state, schema);
|
|
2764
3652
|
const needsTextReindex = textFieldsChanged(stored.fields, document, flatSchema);
|
|
@@ -2793,6 +3681,11 @@ function createPartitionIndex(partitionId) {
|
|
|
2793
3681
|
if (!stored) return void 0;
|
|
2794
3682
|
return structuredClone(stored.fields);
|
|
2795
3683
|
},
|
|
3684
|
+
getRef(docId) {
|
|
3685
|
+
const stored = state.docStore.get(docId);
|
|
3686
|
+
if (!stored) return void 0;
|
|
3687
|
+
return stored.fields;
|
|
3688
|
+
},
|
|
2796
3689
|
has(docId) {
|
|
2797
3690
|
return state.docStore.has(docId);
|
|
2798
3691
|
},
|
|
@@ -2805,6 +3698,41 @@ function createPartitionIndex(partitionId) {
|
|
|
2805
3698
|
}
|
|
2806
3699
|
},
|
|
2807
3700
|
clear: clearAll,
|
|
3701
|
+
estimateMemoryBytes() {
|
|
3702
|
+
const docCount = state.docStore.count();
|
|
3703
|
+
if (docCount === 0) return 0;
|
|
3704
|
+
const AVG_DOC_OVERHEAD = 350;
|
|
3705
|
+
let bytes = docCount * AVG_DOC_OVERHEAD;
|
|
3706
|
+
const docFreqs = state.stats.docFrequencies;
|
|
3707
|
+
const POSTING_ENTRY_SIZE = 24;
|
|
3708
|
+
const PER_TERM_OVERHEAD = 180;
|
|
3709
|
+
let totalPostings = 0;
|
|
3710
|
+
let termCount = 0;
|
|
3711
|
+
for (const term in docFreqs) {
|
|
3712
|
+
totalPostings += docFreqs[term];
|
|
3713
|
+
termCount++;
|
|
3714
|
+
}
|
|
3715
|
+
bytes += totalPostings * POSTING_ENTRY_SIZE;
|
|
3716
|
+
bytes += termCount * PER_TERM_OVERHEAD;
|
|
3717
|
+
const FIELD_ENTRY_OVERHEAD = 42;
|
|
3718
|
+
bytes += docCount * state.numericIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3719
|
+
bytes += docCount * state.booleanIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3720
|
+
bytes += docCount * state.enumIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3721
|
+
bytes += docCount * state.geoIndexes.size * FIELD_ENTRY_OVERHEAD;
|
|
3722
|
+
for (const store of state.vectorStores.values()) {
|
|
3723
|
+
bytes += store.estimateMemoryBytes();
|
|
3724
|
+
}
|
|
3725
|
+
return bytes;
|
|
3726
|
+
},
|
|
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
|
+
},
|
|
2808
3736
|
searchFulltext(params) {
|
|
2809
3737
|
return searchFulltext(state, params);
|
|
2810
3738
|
},
|
|
@@ -2817,9 +3745,16 @@ function createPartitionIndex(partitionId) {
|
|
|
2817
3745
|
computeFacets(docIds, config, schema) {
|
|
2818
3746
|
return computeFacets(state, docIds, config, schema);
|
|
2819
3747
|
},
|
|
3748
|
+
suggestTerms(prefix, limit) {
|
|
3749
|
+
return state.invertedIdx.prefixSearch(prefix, limit);
|
|
3750
|
+
},
|
|
2820
3751
|
serialize(indexName, totalPartitions, language, schema) {
|
|
2821
3752
|
return serializePartition(state, partitionId, indexName, totalPartitions, language, schema);
|
|
2822
3753
|
},
|
|
3754
|
+
serializeToBytes(indexName, totalPartitions, language, schema) {
|
|
3755
|
+
const wire = serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema);
|
|
3756
|
+
return encodeRawPayloadV2(wire);
|
|
3757
|
+
},
|
|
2823
3758
|
deserialize(data, schema) {
|
|
2824
3759
|
deserializePartition(state, data, clearAll, schema);
|
|
2825
3760
|
}
|
|
@@ -2830,10 +3765,12 @@ function createPartitionIndex(partitionId) {
|
|
|
2830
3765
|
// src/partitioning/manager.ts
|
|
2831
3766
|
function createPartitionManager(indexName, config, language, router, initialPartitionCount) {
|
|
2832
3767
|
const count = initialPartitionCount ?? 1;
|
|
3768
|
+
const trackPositions = config.trackPositions ?? true;
|
|
3769
|
+
const vectorPromotionConfig = config.vectorPromotion;
|
|
2833
3770
|
let partitions = [];
|
|
2834
3771
|
const docPartitionMap = /* @__PURE__ */ new Map();
|
|
2835
3772
|
for (let i = 0; i < count; i++) {
|
|
2836
|
-
partitions.push(createPartitionIndex(i));
|
|
3773
|
+
partitions.push(createPartitionIndex(i, trackPositions, vectorPromotionConfig));
|
|
2837
3774
|
}
|
|
2838
3775
|
function validatePartitionId(partitionId) {
|
|
2839
3776
|
if (partitionId < 0 || partitionId >= partitions.length) {
|
|
@@ -2882,6 +3819,9 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2882
3819
|
validatePartitionId(partitionId);
|
|
2883
3820
|
return partitions[partitionId];
|
|
2884
3821
|
},
|
|
3822
|
+
partitionAt(index) {
|
|
3823
|
+
return partitions[index];
|
|
3824
|
+
},
|
|
2885
3825
|
getAllPartitions() {
|
|
2886
3826
|
return [...partitions];
|
|
2887
3827
|
},
|
|
@@ -2890,7 +3830,15 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2890
3830
|
rebuildDocPartitionMap();
|
|
2891
3831
|
},
|
|
2892
3832
|
addPartition() {
|
|
2893
|
-
const
|
|
3833
|
+
const currentMaxPartitions = config.partitions?.maxPartitions;
|
|
3834
|
+
if (currentMaxPartitions !== void 0 && partitions.length >= currentMaxPartitions) {
|
|
3835
|
+
throw new NarsilError(
|
|
3836
|
+
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
3837
|
+
`Cannot add partition: maximum of ${currentMaxPartitions} partitions reached`,
|
|
3838
|
+
{ maxPartitions: currentMaxPartitions, currentCount: partitions.length }
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
const partition = createPartitionIndex(partitions.length, trackPositions, vectorPromotionConfig);
|
|
2894
3842
|
partitions.push(partition);
|
|
2895
3843
|
return partition;
|
|
2896
3844
|
},
|
|
@@ -2900,8 +3848,26 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2900
3848
|
rebuildDocPartitionMap();
|
|
2901
3849
|
},
|
|
2902
3850
|
insert(docId, document, options) {
|
|
3851
|
+
const currentMaxDocs = config.partitions?.maxDocsPerPartition;
|
|
3852
|
+
if (currentMaxDocs !== void 0) {
|
|
3853
|
+
const totalCapacity = currentMaxDocs * partitions.length;
|
|
3854
|
+
if (docPartitionMap.size >= totalCapacity) {
|
|
3855
|
+
throw new NarsilError(
|
|
3856
|
+
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
3857
|
+
`Index "${indexName}" has reached its capacity of ${totalCapacity} documents (${currentMaxDocs} per partition \xD7 ${partitions.length} partitions)`,
|
|
3858
|
+
{
|
|
3859
|
+
indexName,
|
|
3860
|
+
currentCount: docPartitionMap.size,
|
|
3861
|
+
totalCapacity,
|
|
3862
|
+
maxDocsPerPartition: currentMaxDocs,
|
|
3863
|
+
partitionCount: partitions.length
|
|
3864
|
+
}
|
|
3865
|
+
);
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
2903
3868
|
const pid = router.route(docId, partitions.length);
|
|
2904
|
-
|
|
3869
|
+
const insertOpts = config.strict ? { ...options, strict: true } : options;
|
|
3870
|
+
partitions[pid].insert(docId, document, config.schema, language, insertOpts);
|
|
2905
3871
|
docPartitionMap.set(docId, pid);
|
|
2906
3872
|
},
|
|
2907
3873
|
remove(docId) {
|
|
@@ -2917,13 +3883,19 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2917
3883
|
if (pid === void 0) {
|
|
2918
3884
|
throw new NarsilError(ErrorCodes.DOC_NOT_FOUND, `Document "${docId}" not found in any partition`, { docId });
|
|
2919
3885
|
}
|
|
2920
|
-
|
|
3886
|
+
const updateOpts = config.strict ? { ...options, strict: true } : options;
|
|
3887
|
+
partitions[pid].update(docId, document, config.schema, language, updateOpts);
|
|
2921
3888
|
},
|
|
2922
3889
|
get(docId) {
|
|
2923
3890
|
const pid = docPartitionMap.get(docId);
|
|
2924
3891
|
if (pid === void 0) return void 0;
|
|
2925
3892
|
return partitions[pid].get(docId);
|
|
2926
3893
|
},
|
|
3894
|
+
getRef(docId) {
|
|
3895
|
+
const pid = docPartitionMap.get(docId);
|
|
3896
|
+
if (pid === void 0) return void 0;
|
|
3897
|
+
return partitions[pid].getRef(docId);
|
|
3898
|
+
},
|
|
2927
3899
|
has(docId) {
|
|
2928
3900
|
return docPartitionMap.has(docId);
|
|
2929
3901
|
},
|
|
@@ -2934,6 +3906,10 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2934
3906
|
validatePartitionId(partitionId);
|
|
2935
3907
|
return partitions[partitionId].serialize(indexName, partitions.length, language.name, config.schema);
|
|
2936
3908
|
},
|
|
3909
|
+
serializePartitionToBytes(partitionId) {
|
|
3910
|
+
validatePartitionId(partitionId);
|
|
3911
|
+
return partitions[partitionId].serializeToBytes(indexName, partitions.length, language.name, config.schema);
|
|
3912
|
+
},
|
|
2937
3913
|
deserializePartition(partitionId, data) {
|
|
2938
3914
|
validatePartitionId(partitionId);
|
|
2939
3915
|
partitions[partitionId].deserialize(data, config.schema);
|
|
@@ -2954,6 +3930,22 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
2954
3930
|
}
|
|
2955
3931
|
}
|
|
2956
3932
|
return { totalDocuments, docFrequencies, totalFieldLengths };
|
|
3933
|
+
},
|
|
3934
|
+
estimateMemoryBytes() {
|
|
3935
|
+
let total = 0;
|
|
3936
|
+
for (let i = 0; i < partitions.length; i++) {
|
|
3937
|
+
total += partitions[i].estimateMemoryBytes();
|
|
3938
|
+
}
|
|
3939
|
+
return total;
|
|
3940
|
+
},
|
|
3941
|
+
getPartitionStats() {
|
|
3942
|
+
return partitions.map((p, i) => ({
|
|
3943
|
+
partitionId: i,
|
|
3944
|
+
documentCount: p.count(),
|
|
3945
|
+
estimatedMemoryBytes: p.estimateMemoryBytes(),
|
|
3946
|
+
vectorFieldCount: p.vectorFieldCount(),
|
|
3947
|
+
isHnswPromoted: p.hasPromotedHnsw()
|
|
3948
|
+
}));
|
|
2957
3949
|
}
|
|
2958
3950
|
};
|
|
2959
3951
|
return manager;
|
|
@@ -3050,7 +4042,7 @@ function createDirectExecutor() {
|
|
|
3050
4042
|
}
|
|
3051
4043
|
case "insert": {
|
|
3052
4044
|
const entry = requireIndex(action.indexName);
|
|
3053
|
-
entry.manager.insert(action.docId, action.document);
|
|
4045
|
+
entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
|
|
3054
4046
|
return void 0;
|
|
3055
4047
|
}
|
|
3056
4048
|
case "remove": {
|
|
@@ -3065,14 +4057,15 @@ function createDirectExecutor() {
|
|
|
3065
4057
|
}
|
|
3066
4058
|
case "query": {
|
|
3067
4059
|
const entry = requireIndex(action.indexName);
|
|
3068
|
-
const result = fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
3069
|
-
scoringMode: entry.config.defaultScoring ?? "local"
|
|
4060
|
+
const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
4061
|
+
scoringMode: entry.config.defaultScoring ?? "local",
|
|
4062
|
+
partitionIds: action.partitionIds
|
|
3070
4063
|
});
|
|
3071
4064
|
return result;
|
|
3072
4065
|
}
|
|
3073
4066
|
case "preflight": {
|
|
3074
4067
|
const entry = requireIndex(action.indexName);
|
|
3075
|
-
const result = fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
4068
|
+
const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
|
|
3076
4069
|
scoringMode: entry.config.defaultScoring ?? "local"
|
|
3077
4070
|
});
|
|
3078
4071
|
return { count: result.totalMatched };
|
|
@@ -3144,4 +4137,4 @@ function createDirectExecutor() {
|
|
|
3144
4137
|
};
|
|
3145
4138
|
}
|
|
3146
4139
|
|
|
3147
|
-
export { createDirectExecutor, fanOutQuery, getLanguage, registerLanguage, tokenize, validateSchema };
|
|
4140
|
+
export { createDirectExecutor, createPartitionIndex, createPartitionRouter, deserializePayloadV2, detectRuntime, fanOutQuery, fnv1a, getLanguage, kWayMerge, registerLanguage, tokenize, validateSchema };
|