@delali/narsil 0.2.4-next.48 → 0.2.4-next.50
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 +2 -2
- package/dist/adapters/indexeddb.mjs +15 -10
- package/dist/{options-BC68VPoE.d.ts → capabilities-JrVQOwi4.d.ts} +55 -47
- package/dist/{chunk-BFIH7OV5.mjs → chunk-2GWVQWGA.mjs} +3 -1
- package/dist/chunk-AV4IJKD2.mjs +35 -0
- package/dist/{chunk-EV45L35L.mjs → chunk-BEIK3EOZ.mjs} +845 -2407
- package/dist/chunk-CBLS46IE.mjs +10 -0
- package/dist/chunk-CMVG2UEF.mjs +2038 -0
- package/dist/chunk-CV5H7XL7.mjs +876 -0
- package/dist/{chunk-JQUL2T6I.mjs → chunk-GCM3JFVA.mjs} +1 -2
- package/dist/{chunk-XWICBGYT.mjs → chunk-MHE7YWAU.mjs} +3 -5
- package/dist/{chunk-UZTT2BKQ.mjs → chunk-ND34I3SV.mjs} +1202 -1881
- package/dist/{chunk-PM52SMBT.mjs → chunk-OOSIDHA3.mjs} +2 -1
- package/dist/chunk-ORBLD3EL.mjs +6 -0
- package/dist/chunk-PIWKZPP7.mjs +64 -0
- package/dist/{chunk-4COAUBLJ.mjs → chunk-PMBHH44R.mjs} +124 -8
- package/dist/chunk-RP23PWMV.mjs +11 -0
- package/dist/chunk-UNEEFTEU.mjs +7 -0
- package/dist/{chunk-WTONLDU3.mjs → chunk-WYJXSLER.mjs} +9 -19
- package/dist/{chunk-DFZDV5FV.mjs → chunk-X6YZO4MQ.mjs} +19 -15
- package/dist/chunk-XE6XR2OE.mjs +23 -0
- package/dist/chunk-YEO6G2NG.mjs +6 -0
- package/dist/chunk-ZHUQKZBE.mjs +1292 -0
- package/dist/{client-C5BkUqB8.d.ts → client-CBOVHdjp.d.ts} +31 -7
- package/dist/client.d.ts +7 -7
- package/dist/client.mjs +250 -232
- package/dist/{config-B-ZI-ft5.d.ts → config-Czh2YNDi.d.ts} +2 -2
- package/dist/{config-BZRExglL.d.ts → config-OON7IJ_B.d.ts} +50 -13
- package/dist/direct-executor-OREXU72C.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +2 -3
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.mjs +2 -2
- package/dist/distribution/transport/grpc.mjs +7 -9
- package/dist/distribution/transport/in-memory.mjs +2 -1
- package/dist/distribution/transport/tcp.mjs +3 -2
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +142 -101
- package/dist/embeddings/openai.mjs +5 -3
- package/dist/{engine-Cjr1uBLa.d.ts → engine-9PtFY50q.d.ts} +125 -69
- package/dist/index.browser.mjs +4689 -3330
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +14 -8
- package/dist/invalidation/broadcast-channel.mjs +2 -1
- package/dist/invalidation/filesystem.mjs +3 -4
- package/dist/{search-DDHjfXhJ.d.ts → memory-Bzd5xcEW.d.ts} +89 -63
- package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +14 -8
- package/dist/react.d.ts +4 -4
- package/dist/react.mjs +7 -5
- package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
- package/dist/server.d.ts +47 -42
- package/dist/server.mjs +338 -821
- package/dist/thread-PZLEO6JL.mjs +310 -0
- package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
- package/dist/vector/hnsw-build-worker.mjs +3 -4
- package/dist/vector/search-worker.mjs +4 -123
- package/dist/workers/entry.mjs +47 -3
- package/package.json +5 -1
- package/dist/direct-executor-EOTFGXTO.mjs +0 -8
|
@@ -1,489 +1,103 @@
|
|
|
1
|
+
import { countsWithoutScores, fanOutMatchCount, fanOutQuery, sanitizeGlobalStats, cloneProjected, validateDocument, validateDocumentStrict, projectionKeepsField, fnv1a, VECTOR_PATTERN, extractVectorFieldsFromSchema, boundedLevenshtein, bitsetHas, createBitSet, bitsetSet, isTextFieldType, flattenSchema, tokenize, bitsetFromSet, tokenizeIterator, mergeFacets, bitsetAnd, bitsetOr, bitsetNot, kWayMerge } from './chunk-CMVG2UEF.mjs';
|
|
2
|
+
import { detectRuntime } from './chunk-AV4IJKD2.mjs';
|
|
1
3
|
import { english } from './chunk-TGP5M5R7.mjs';
|
|
2
|
-
import { createScalarQuantizer } from './chunk-
|
|
3
|
-
import {
|
|
4
|
-
import { compareCodePoints,
|
|
5
|
-
import {
|
|
6
|
-
import { NarsilError, ErrorCodes
|
|
4
|
+
import { createScalarQuantizer, openSharedWorkerCopy } from './chunk-PMBHH44R.mjs';
|
|
5
|
+
import { createVectorStore, createHNSWIndex, createOrdinalFilter, addToOrdinalFilter, removeFromOrdinalFilter, ordinalFilterValues, magnitude, toScore, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, searchOrdinals, WASM_PAGE_BYTES, entryForOrd, toDistance, MAX_WASM_PAGES, ABSENT_DOCUMENT_RANK, HNSW_BUILD_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, VECTOR_SEARCH_TIMEOUT_MS } from './chunk-X6YZO4MQ.mjs';
|
|
6
|
+
import { compareCodePoints, toComparableSortValue, readSortField, compareComparableValues, SORT_VALUE_MAX_CODE_POINTS } from './chunk-2GWVQWGA.mjs';
|
|
7
|
+
import { createRequestId } from './chunk-WYJXSLER.mjs';
|
|
8
|
+
import { NarsilError, ErrorCodes } from './chunk-P4ELXVJQ.mjs';
|
|
9
|
+
import { getHeapStatistics } from 'v8';
|
|
7
10
|
import { encode, decode } from '@msgpack/msgpack';
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"string[]",
|
|
19
|
-
"number[]",
|
|
20
|
-
"boolean[]",
|
|
21
|
-
"enum[]"
|
|
22
|
-
]);
|
|
23
|
-
function isTextFieldType(fieldType) {
|
|
24
|
-
return fieldType === "string" || fieldType === SORTABLE_TEXT_FIELD_TYPE;
|
|
25
|
-
}
|
|
26
|
-
var VECTOR_PATTERN = /^vector\[(\d+)]$/;
|
|
27
|
-
var FIELD_NAME_PATTERN = /^[A-Za-z0-9_]+$/;
|
|
28
|
-
var RESERVED_ROOT_FIELDS = /* @__PURE__ */ new Set(["id"]);
|
|
29
|
-
var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
30
|
-
var MAX_NESTING_DEPTH = 4;
|
|
31
|
-
function isPlainObject(value) {
|
|
32
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/schema/validator/field-values.ts
|
|
36
|
-
function validateGeopoint(path, value) {
|
|
37
|
-
if (!isPlainObject(value)) {
|
|
38
|
-
throw new NarsilError(
|
|
39
|
-
ErrorCodes.SCHEMA_INVALID_GEOPOINT,
|
|
40
|
-
`Field "${path}" expected a geopoint object with lat and lon`,
|
|
41
|
-
{
|
|
42
|
-
field: path,
|
|
43
|
-
received: Array.isArray(value) ? "array" : typeof value
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
if (typeof value.lat !== "number" || typeof value.lon !== "number" || Number.isNaN(value.lat) || Number.isNaN(value.lon)) {
|
|
48
|
-
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_GEOPOINT, `Field "${path}" geopoint requires numeric lat and lon`, {
|
|
49
|
-
field: path,
|
|
50
|
-
lat: Number.isNaN(value.lat) ? "NaN" : typeof value.lat,
|
|
51
|
-
lon: Number.isNaN(value.lon) ? "NaN" : typeof value.lon
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (value.lat < -90 || value.lat > 90) {
|
|
55
|
-
throw new NarsilError(
|
|
56
|
-
ErrorCodes.SCHEMA_INVALID_GEOPOINT,
|
|
57
|
-
`Field "${path}" geopoint lat must be between -90 and 90`,
|
|
58
|
-
{
|
|
59
|
-
field: path,
|
|
60
|
-
lat: value.lat
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
if (value.lon < -180 || value.lon > 180) {
|
|
65
|
-
throw new NarsilError(
|
|
66
|
-
ErrorCodes.SCHEMA_INVALID_GEOPOINT,
|
|
67
|
-
`Field "${path}" geopoint lon must be between -180 and 180`,
|
|
68
|
-
{
|
|
69
|
-
field: path,
|
|
70
|
-
lon: value.lon
|
|
71
|
-
}
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function validateVector(path, value, expectedDimension) {
|
|
76
|
-
const isArray = Array.isArray(value);
|
|
77
|
-
const isFloat32 = value instanceof Float32Array;
|
|
78
|
-
if (!isArray && !isFloat32) {
|
|
79
|
-
throw new NarsilError(
|
|
80
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
81
|
-
`Field "${path}" expected a vector (Array or Float32Array)`,
|
|
82
|
-
{ field: path, received: typeof value }
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
const arr = value;
|
|
86
|
-
if (arr.length !== expectedDimension) {
|
|
87
|
-
throw new NarsilError(
|
|
88
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
89
|
-
`Field "${path}" expected vector of dimension ${expectedDimension}, got ${arr.length}`,
|
|
90
|
-
{ field: path, expected: expectedDimension, received: arr.length }
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
for (let i = 0; i < arr.length; i++) {
|
|
94
|
-
if (typeof arr[i] !== "number" || Number.isNaN(arr[i])) {
|
|
95
|
-
throw new NarsilError(
|
|
96
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
97
|
-
`Field "${path}" vector element at index ${i} is not a valid number`,
|
|
98
|
-
{ field: path, index: i, value: arr[i] }
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function validateTypedArray(path, value, elementType) {
|
|
104
|
-
if (!Array.isArray(value)) {
|
|
105
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" expected an array`, {
|
|
106
|
-
field: path,
|
|
107
|
-
received: typeof value
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
for (let i = 0; i < value.length; i++) {
|
|
111
|
-
const el = value[i];
|
|
112
|
-
if (typeof el !== elementType) {
|
|
113
|
-
throw new NarsilError(
|
|
114
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
115
|
-
`Field "${path}" array element at index ${i} expected ${elementType}, got ${typeof el}`,
|
|
116
|
-
{ field: path, index: i, expected: elementType, received: typeof el }
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
if (elementType === "number" && Number.isNaN(el)) {
|
|
120
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" array element at index ${i} is NaN`, {
|
|
121
|
-
field: path,
|
|
122
|
-
index: i
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function validateFieldValue(path, value, type) {
|
|
128
|
-
switch (type) {
|
|
129
|
-
case "string":
|
|
130
|
-
case SORTABLE_TEXT_FIELD_TYPE:
|
|
131
|
-
if (typeof value !== "string") {
|
|
132
|
-
throw new NarsilError(
|
|
133
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
134
|
-
`Field "${path}" expected string, got ${typeof value}`,
|
|
135
|
-
{
|
|
136
|
-
field: path,
|
|
137
|
-
expected: "string",
|
|
138
|
-
received: typeof value
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return;
|
|
143
|
-
case "number":
|
|
144
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
145
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" expected a valid number`, {
|
|
146
|
-
field: path,
|
|
147
|
-
expected: "number",
|
|
148
|
-
received: Number.isNaN(value) ? "NaN" : typeof value
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
case "boolean":
|
|
153
|
-
if (typeof value !== "boolean") {
|
|
154
|
-
throw new NarsilError(
|
|
155
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
156
|
-
`Field "${path}" expected boolean, got ${typeof value}`,
|
|
157
|
-
{
|
|
158
|
-
field: path,
|
|
159
|
-
expected: "boolean",
|
|
160
|
-
received: typeof value
|
|
161
|
-
}
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
case "enum":
|
|
166
|
-
if (typeof value !== "string") {
|
|
167
|
-
throw new NarsilError(
|
|
168
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
169
|
-
`Field "${path}" expected enum (string), got ${typeof value}`,
|
|
170
|
-
{
|
|
171
|
-
field: path,
|
|
172
|
-
expected: "enum",
|
|
173
|
-
received: typeof value
|
|
174
|
-
}
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
case "geopoint":
|
|
179
|
-
validateGeopoint(path, value);
|
|
180
|
-
return;
|
|
181
|
-
case "string[]":
|
|
182
|
-
validateTypedArray(path, value, "string");
|
|
183
|
-
return;
|
|
184
|
-
case "number[]":
|
|
185
|
-
validateTypedArray(path, value, "number");
|
|
186
|
-
return;
|
|
187
|
-
case "boolean[]":
|
|
188
|
-
validateTypedArray(path, value, "boolean");
|
|
189
|
-
return;
|
|
190
|
-
case "enum[]":
|
|
191
|
-
validateTypedArray(path, value, "string");
|
|
192
|
-
return;
|
|
193
|
-
default: {
|
|
194
|
-
const vectorMatch = VECTOR_PATTERN.exec(type);
|
|
195
|
-
if (vectorMatch) {
|
|
196
|
-
validateVector(path, value, Number.parseInt(vectorMatch[1], 10));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
12
|
+
function readHeapStatistics() {
|
|
13
|
+
try {
|
|
14
|
+
const stats = getHeapStatistics();
|
|
15
|
+
const usedBytes = stats.used_heap_size;
|
|
16
|
+
const limitBytes = stats.heap_size_limit;
|
|
17
|
+
if (!Number.isFinite(usedBytes) || !Number.isFinite(limitBytes) || limitBytes <= 0) return null;
|
|
18
|
+
return { usedBytes, limitBytes };
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
199
21
|
}
|
|
200
22
|
}
|
|
201
23
|
|
|
202
|
-
// src/
|
|
203
|
-
var
|
|
204
|
-
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
field: path
|
|
208
|
-
});
|
|
24
|
+
// src/languages/registry.ts
|
|
25
|
+
var languages = /* @__PURE__ */ new Map();
|
|
26
|
+
languages.set("english", english);
|
|
27
|
+
function registerLanguage(module) {
|
|
28
|
+
languages.set(module.name, module);
|
|
209
29
|
}
|
|
210
|
-
function
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
function walkStorable(value, path, depth, ancestors, skip) {
|
|
215
|
-
if (value === null || value === void 0) return;
|
|
216
|
-
const kind = typeof value;
|
|
217
|
-
if (kind === "string" || kind === "boolean") return;
|
|
218
|
-
if (kind === "number") return;
|
|
219
|
-
if (kind === "bigint" || kind === "symbol" || kind === "function") {
|
|
220
|
-
reject(path, `holds a ${kind}, which cannot be stored or recovered`);
|
|
221
|
-
}
|
|
222
|
-
const container = value;
|
|
223
|
-
if (container instanceof Date || container instanceof Uint8Array) return;
|
|
224
|
-
const isArray = Array.isArray(container);
|
|
225
|
-
if (!isArray && !isPlainObject2(container)) {
|
|
226
|
-
reject(
|
|
227
|
-
path,
|
|
228
|
-
"holds a value that does not survive storage; use plain objects, arrays, strings, numbers, booleans, dates, or Uint8Array bytes"
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
if (depth >= MAX_DOCUMENT_NESTING_DEPTH) {
|
|
232
|
-
reject(path, `nests deeper than ${MAX_DOCUMENT_NESTING_DEPTH} levels`);
|
|
233
|
-
}
|
|
234
|
-
if (ancestors.has(container)) {
|
|
235
|
-
reject(path, "refers back to one of its own ancestors");
|
|
236
|
-
}
|
|
237
|
-
ancestors.add(container);
|
|
238
|
-
if (isArray) {
|
|
239
|
-
const items = container;
|
|
240
|
-
for (let i = 0; i < items.length; i++) {
|
|
241
|
-
walkStorable(items[i], `${path}[${i}]`, depth + 1, ancestors, skip);
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
const record = container;
|
|
245
|
-
for (const key of Object.keys(record)) {
|
|
246
|
-
const keyPath = path === "" ? key : `${path}.${key}`;
|
|
247
|
-
if (RESERVED_OBJECT_KEYS.has(key)) {
|
|
248
|
-
reject(keyPath, "uses a reserved object key");
|
|
249
|
-
}
|
|
250
|
-
if (skip.has(keyPath)) continue;
|
|
251
|
-
walkStorable(record[key], keyPath, depth + 1, ancestors, skip);
|
|
252
|
-
}
|
|
30
|
+
function getLanguage(name) {
|
|
31
|
+
const lang = languages.get(name);
|
|
32
|
+
if (!lang) {
|
|
33
|
+
throw new NarsilError(ErrorCodes.LANGUAGE_NOT_SUPPORTED, `Language "${name}" is not registered`, { language: name });
|
|
253
34
|
}
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
function assertStorableDocument(document, skipFieldPaths) {
|
|
257
|
-
walkStorable(document, "", 0, /* @__PURE__ */ new Set(), skipFieldPaths ?? /* @__PURE__ */ new Set());
|
|
35
|
+
return lang;
|
|
258
36
|
}
|
|
259
37
|
|
|
260
|
-
// src/
|
|
261
|
-
var
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
const collect = (level, prefix) => {
|
|
267
|
-
for (const [field, type] of Object.entries(level)) {
|
|
268
|
-
const path = prefix === "" ? field : `${prefix}.${field}`;
|
|
269
|
-
if (typeof type === "string" && VECTOR_PATTERN.test(type)) {
|
|
270
|
-
paths?.add(path);
|
|
271
|
-
} else if (isPlainObject(type)) {
|
|
272
|
-
collect(type, path);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
collect(schema, "");
|
|
277
|
-
vectorPathCache.set(schema, paths);
|
|
278
|
-
return paths;
|
|
279
|
-
}
|
|
280
|
-
function validateDocumentFields(doc, schema, prefix) {
|
|
281
|
-
for (const [field, type] of Object.entries(schema)) {
|
|
282
|
-
const path = prefix ? `${prefix}.${field}` : field;
|
|
283
|
-
const value = doc[field];
|
|
284
|
-
if (value === void 0 || value === null) continue;
|
|
285
|
-
if (isPlainObject(type)) {
|
|
286
|
-
if (!isPlainObject(value)) {
|
|
287
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" expected an object`, {
|
|
288
|
-
field: path,
|
|
289
|
-
received: Array.isArray(value) ? "array" : typeof value
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
validateDocumentFields(value, type, path);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
validateFieldValue(path, value, type);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function validateDocument(document, schema) {
|
|
299
|
-
if (!isPlainObject(document)) {
|
|
300
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document must be a plain object", {
|
|
301
|
-
received: typeof document
|
|
302
|
-
});
|
|
38
|
+
// src/analysis/registry.ts
|
|
39
|
+
var tokenizers = /* @__PURE__ */ new Map();
|
|
40
|
+
var stopWordSets = /* @__PURE__ */ new Map();
|
|
41
|
+
function requireName(name, kind) {
|
|
42
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
43
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `A registered ${kind} needs a non-empty name`, { name });
|
|
303
44
|
}
|
|
304
|
-
assertStorableDocument(document, vectorFieldPaths(schema));
|
|
305
|
-
validateDocumentFields(document, schema, "");
|
|
306
45
|
}
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (!(key in schema)) {
|
|
312
|
-
extras.push(path);
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
const schemaType = schema[key];
|
|
316
|
-
if (isPlainObject(schemaType) && isPlainObject(doc[key])) {
|
|
317
|
-
collectExtraFields(doc[key], schemaType, path, extras);
|
|
318
|
-
}
|
|
46
|
+
function registerTokenizer(name, tokenizer) {
|
|
47
|
+
requireName(name, "tokenizer");
|
|
48
|
+
if (typeof tokenizer?.tokenize !== "function") {
|
|
49
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" needs a tokenize method`, { tokenizer: name });
|
|
319
50
|
}
|
|
51
|
+
tokenizers.set(name, tokenizer);
|
|
320
52
|
}
|
|
321
|
-
function
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (extras.length > 0) {
|
|
53
|
+
function registerStopWords(name, stopWords) {
|
|
54
|
+
requireName(name, "stop word set");
|
|
55
|
+
if (!(stopWords instanceof Set) && typeof stopWords !== "function") {
|
|
325
56
|
throw new NarsilError(
|
|
326
|
-
ErrorCodes.
|
|
327
|
-
`
|
|
328
|
-
{
|
|
57
|
+
ErrorCodes.CONFIG_INVALID,
|
|
58
|
+
`Stop word set "${name}" needs a Set of words or a function returning one`,
|
|
59
|
+
{ stopWords: name }
|
|
329
60
|
);
|
|
330
61
|
}
|
|
62
|
+
stopWordSets.set(name, stopWords);
|
|
331
63
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
const segments = path.split(".");
|
|
335
|
-
let current = document;
|
|
336
|
-
for (const segment of segments) {
|
|
337
|
-
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
338
|
-
current = current[segment];
|
|
339
|
-
}
|
|
340
|
-
return current;
|
|
64
|
+
function hasTokenizer(name) {
|
|
65
|
+
return tokenizers.has(name);
|
|
341
66
|
}
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
for (const field of required) {
|
|
345
|
-
const value = resolveNestedValue(document, field);
|
|
346
|
-
if (value === void 0 || value === null) {
|
|
347
|
-
throw new NarsilError(ErrorCodes.DOC_MISSING_REQUIRED_FIELD, `Document is missing required field "${field}"`, {
|
|
348
|
-
field
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
67
|
+
function hasStopWords(name) {
|
|
68
|
+
return stopWordSets.has(name);
|
|
352
69
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
`Schema nesting exceeds the maximum depth of ${MAX_NESTING_DEPTH} at "${prefix}"`,
|
|
360
|
-
{ path: prefix, maxDepth: MAX_NESTING_DEPTH }
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
for (const [field, type] of Object.entries(schema)) {
|
|
364
|
-
const path = prefix ? `${prefix}.${field}` : field;
|
|
365
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(field)) {
|
|
366
|
-
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field name "${field}" is not allowed in a schema`, {
|
|
367
|
-
field,
|
|
368
|
-
path
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
if (!FIELD_NAME_PATTERN.test(field)) {
|
|
372
|
-
throw new NarsilError(
|
|
373
|
-
ErrorCodes.SCHEMA_INVALID_TYPE,
|
|
374
|
-
`Field name "${field}" contains characters that are not allowed; use letters, digits, and underscores only`,
|
|
375
|
-
{ field, path }
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
if (depth === 1 && RESERVED_ROOT_FIELDS.has(field)) {
|
|
379
|
-
throw new NarsilError(
|
|
380
|
-
ErrorCodes.SCHEMA_INVALID_TYPE,
|
|
381
|
-
`Field "${field}" is reserved and cannot be defined in a schema`,
|
|
382
|
-
{ field, path }
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
if (isPlainObject(type)) {
|
|
386
|
-
validateSchemaFields(type, depth + 1, path);
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (typeof type !== "string") {
|
|
390
|
-
throw new NarsilError(
|
|
391
|
-
ErrorCodes.SCHEMA_INVALID_TYPE,
|
|
392
|
-
`Field "${path}" has an invalid type definition: ${String(type)}`,
|
|
393
|
-
{
|
|
394
|
-
field: path,
|
|
395
|
-
type: String(type)
|
|
396
|
-
}
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
if (SCALAR_FIELD_TYPES.has(type)) continue;
|
|
400
|
-
const vectorMatch = VECTOR_PATTERN.exec(type);
|
|
401
|
-
if (vectorMatch) {
|
|
402
|
-
const dimension = Number.parseInt(vectorMatch[1], 10);
|
|
403
|
-
if (dimension <= 0) {
|
|
404
|
-
throw new NarsilError(
|
|
405
|
-
ErrorCodes.SCHEMA_INVALID_VECTOR_DIMENSION,
|
|
406
|
-
`Vector field "${path}" must have a positive dimension, got ${dimension}`,
|
|
407
|
-
{ field: path, dimension }
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field "${path}" has an unsupported type: "${type}"`, {
|
|
413
|
-
field: path,
|
|
414
|
-
type
|
|
70
|
+
function getTokenizer(name) {
|
|
71
|
+
const tokenizer = tokenizers.get(name);
|
|
72
|
+
if (!tokenizer) {
|
|
73
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" is not registered on this engine`, {
|
|
74
|
+
tokenizer: name,
|
|
75
|
+
available: [...tokenizers.keys()]
|
|
415
76
|
});
|
|
416
77
|
}
|
|
78
|
+
return tokenizer;
|
|
417
79
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
80
|
+
function getStopWords(name) {
|
|
81
|
+
const stopWords = stopWordSets.get(name);
|
|
82
|
+
if (!stopWords) {
|
|
83
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Stop word set "${name}" is not registered on this engine`, {
|
|
84
|
+
stopWords: name,
|
|
85
|
+
available: [...stopWordSets.keys()]
|
|
422
86
|
});
|
|
423
87
|
}
|
|
424
|
-
|
|
425
|
-
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, "Schema must define at least one field");
|
|
426
|
-
}
|
|
427
|
-
validateSchemaFields(schema, 1, "");
|
|
428
|
-
}
|
|
429
|
-
function flattenRecursive(schema, prefix, result) {
|
|
430
|
-
for (const [field, type] of Object.entries(schema)) {
|
|
431
|
-
const path = prefix ? `${prefix}.${field}` : field;
|
|
432
|
-
if (isPlainObject(type)) {
|
|
433
|
-
flattenRecursive(type, path, result);
|
|
434
|
-
} else {
|
|
435
|
-
result[path] = type;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
function flattenSchema(schema) {
|
|
440
|
-
const result = {};
|
|
441
|
-
flattenRecursive(schema, "", result);
|
|
442
|
-
return result;
|
|
88
|
+
return stopWords;
|
|
443
89
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (match) {
|
|
450
|
-
result.set(fieldPath, Number.parseInt(match[1], 10));
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return result;
|
|
90
|
+
function resolveIndexAnalysis(config) {
|
|
91
|
+
return {
|
|
92
|
+
stopWords: typeof config.stopWords === "string" ? getStopWords(config.stopWords) : config.stopWords,
|
|
93
|
+
customTokenizer: typeof config.tokenizer === "string" ? getTokenizer(config.tokenizer) : config.tokenizer
|
|
94
|
+
};
|
|
454
95
|
}
|
|
455
96
|
|
|
456
|
-
// src/
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
function isPositiveInteger(value) {
|
|
461
|
-
return Number.isInteger(value) && value >= 1;
|
|
462
|
-
}
|
|
463
|
-
function validateVectorPromotion(config) {
|
|
464
|
-
if (config === void 0) return;
|
|
465
|
-
if (config.threshold !== void 0 && !isPositiveInteger(config.threshold)) {
|
|
466
|
-
fail("vectorPromotion.threshold must be a positive integer", { threshold: config.threshold });
|
|
467
|
-
}
|
|
468
|
-
const quantization = config.quantization;
|
|
469
|
-
if (quantization !== void 0 && quantization !== "sq8" && quantization !== "none") {
|
|
470
|
-
fail("vectorPromotion.quantization must be 'sq8' or 'none'", { quantization });
|
|
471
|
-
}
|
|
472
|
-
const hnsw = config.hnswConfig;
|
|
473
|
-
if (hnsw === void 0) return;
|
|
474
|
-
if (hnsw.m !== void 0 && (!isPositiveInteger(hnsw.m) || hnsw.m > MAX_M)) {
|
|
475
|
-
fail(`vectorPromotion.hnswConfig.m must be a positive integer no greater than ${MAX_M}`, { m: hnsw.m });
|
|
476
|
-
}
|
|
477
|
-
if (hnsw.efConstruction !== void 0 && !isPositiveInteger(hnsw.efConstruction)) {
|
|
478
|
-
fail("vectorPromotion.hnswConfig.efConstruction must be a positive integer", {
|
|
479
|
-
efConstruction: hnsw.efConstruction
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
const metric = hnsw.metric;
|
|
483
|
-
if (metric !== void 0 && metric !== "cosine" && metric !== "dotProduct" && metric !== "euclidean") {
|
|
484
|
-
fail("vectorPromotion.hnswConfig.metric must be 'cosine', 'dotProduct', or 'euclidean'", { metric });
|
|
485
|
-
}
|
|
486
|
-
}
|
|
97
|
+
// src/serialization/constants.ts
|
|
98
|
+
var CHECKSUM_TIMEOUT_MS = 12e4;
|
|
99
|
+
var CHECKSUM_YIELD_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
100
|
+
var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
|
|
487
101
|
function metadataToWire(meta) {
|
|
488
102
|
const wire = {
|
|
489
103
|
index_name: meta.indexName,
|
|
@@ -596,8 +210,8 @@ function wireToMetadata(raw) {
|
|
|
596
210
|
}
|
|
597
211
|
if (raw.partition_limits && typeof raw.partition_limits === "object") {
|
|
598
212
|
const limits = {
|
|
599
|
-
...
|
|
600
|
-
...
|
|
213
|
+
...isPositiveInteger(raw.partition_limits.max_docs_per_partition) ? { maxDocsPerPartition: raw.partition_limits.max_docs_per_partition } : {},
|
|
214
|
+
...isPositiveInteger(raw.partition_limits.max_partitions) ? { maxPartitions: raw.partition_limits.max_partitions } : {},
|
|
601
215
|
...isWatermarkFraction(raw.partition_limits.watermark) ? { watermark: raw.partition_limits.watermark } : {}
|
|
602
216
|
};
|
|
603
217
|
if (Object.keys(limits).length > 0) {
|
|
@@ -622,16 +236,16 @@ function wireToMetadata(raw) {
|
|
|
622
236
|
const metric = hnsw?.metric;
|
|
623
237
|
const restored = {};
|
|
624
238
|
const hnswConfig = {};
|
|
625
|
-
if (
|
|
239
|
+
if (isPositiveInteger(hnsw?.m)) {
|
|
626
240
|
hnswConfig.m = hnsw?.m;
|
|
627
241
|
}
|
|
628
|
-
if (
|
|
242
|
+
if (isPositiveInteger(hnsw?.ef_construction)) {
|
|
629
243
|
hnswConfig.efConstruction = hnsw?.ef_construction;
|
|
630
244
|
}
|
|
631
245
|
if (metric === "cosine" || metric === "dotProduct" || metric === "euclidean") {
|
|
632
246
|
hnswConfig.metric = metric;
|
|
633
247
|
}
|
|
634
|
-
if (
|
|
248
|
+
if (isPositiveInteger(promotion.threshold)) {
|
|
635
249
|
restored.threshold = promotion.threshold;
|
|
636
250
|
}
|
|
637
251
|
if (isSelectivityRatio(promotion.filter_threshold)) {
|
|
@@ -658,7 +272,7 @@ function wireToMetadata(raw) {
|
|
|
658
272
|
function isNonNegativeInteger(value) {
|
|
659
273
|
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
660
274
|
}
|
|
661
|
-
function
|
|
275
|
+
function isPositiveInteger(value) {
|
|
662
276
|
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
663
277
|
}
|
|
664
278
|
function isSelectivityRatio(value) {
|
|
@@ -678,17 +292,17 @@ function deserializeMetadata(data) {
|
|
|
678
292
|
|
|
679
293
|
// src/serialization/stored-vector-values.ts
|
|
680
294
|
function dropStoredVectorValues(documents, schema) {
|
|
681
|
-
const
|
|
295
|
+
const vectorFieldPaths = [];
|
|
682
296
|
for (const [fieldPath, fieldType] of Object.entries(schema)) {
|
|
683
297
|
if (typeof fieldType === "string" && VECTOR_PATTERN.test(fieldType)) {
|
|
684
|
-
|
|
298
|
+
vectorFieldPaths.push(fieldPath);
|
|
685
299
|
}
|
|
686
300
|
}
|
|
687
|
-
if (
|
|
301
|
+
if (vectorFieldPaths.length === 0) {
|
|
688
302
|
return;
|
|
689
303
|
}
|
|
690
304
|
for (const document of Object.values(documents)) {
|
|
691
|
-
for (const fieldPath of
|
|
305
|
+
for (const fieldPath of vectorFieldPaths) {
|
|
692
306
|
removeFieldAtPath(document.fields, fieldPath);
|
|
693
307
|
}
|
|
694
308
|
}
|
|
@@ -726,7 +340,6 @@ function sanitizeSurfaceForms(raw) {
|
|
|
726
340
|
}
|
|
727
341
|
return count > 0 ? result : void 0;
|
|
728
342
|
}
|
|
729
|
-
var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
|
|
730
343
|
function validateHnswMetric(value) {
|
|
731
344
|
if (value === void 0 || value === null) return void 0;
|
|
732
345
|
if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
|
|
@@ -824,10 +437,9 @@ function deserializePayloadV1(data) {
|
|
|
824
437
|
function encodeRawPayloadV2(wire) {
|
|
825
438
|
return encode(wire);
|
|
826
439
|
}
|
|
827
|
-
var VALID_HNSW_METRICS2 = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
|
|
828
440
|
function validateHnswMetric2(value) {
|
|
829
441
|
if (value === void 0 || value === null) return void 0;
|
|
830
|
-
if (typeof value === "string" &&
|
|
442
|
+
if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
|
|
831
443
|
return value;
|
|
832
444
|
}
|
|
833
445
|
return void 0;
|
|
@@ -1168,65 +780,18 @@ function createDocumentStore() {
|
|
|
1168
780
|
};
|
|
1169
781
|
}
|
|
1170
782
|
|
|
1171
|
-
// src/core/
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const m = a.length;
|
|
1176
|
-
const n = b.length;
|
|
1177
|
-
if (m === 0) {
|
|
1178
|
-
return n <= tolerance ? { distance: n, withinTolerance: true } : { distance: n, withinTolerance: false };
|
|
1179
|
-
}
|
|
1180
|
-
if (n === 0) {
|
|
1181
|
-
return m <= tolerance ? { distance: m, withinTolerance: true } : { distance: m, withinTolerance: false };
|
|
1182
|
-
}
|
|
1183
|
-
if (a.startsWith(b)) {
|
|
1184
|
-
const diff2 = m - n;
|
|
1185
|
-
return diff2 <= tolerance ? { distance: diff2, withinTolerance: true } : { distance: diff2, withinTolerance: false };
|
|
1186
|
-
}
|
|
1187
|
-
if (b.startsWith(a)) {
|
|
1188
|
-
return { distance: 0, withinTolerance: true };
|
|
1189
|
-
}
|
|
1190
|
-
const diff = Math.abs(m - n);
|
|
1191
|
-
if (diff > tolerance) return { distance: -1, withinTolerance: false };
|
|
1192
|
-
const matrix = [];
|
|
1193
|
-
for (let i = 0; i <= m; i++) {
|
|
1194
|
-
matrix[i] = [i];
|
|
1195
|
-
for (let j = 1; j <= n; j++) {
|
|
1196
|
-
matrix[i][j] = i === 0 ? j : 0;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
for (let i = 1; i <= m; i++) {
|
|
1200
|
-
let rowMin = Infinity;
|
|
1201
|
-
for (let j = 1; j <= n; j++) {
|
|
1202
|
-
if (a[i - 1] === b[j - 1]) {
|
|
1203
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1204
|
-
} else {
|
|
1205
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + 1);
|
|
1206
|
-
}
|
|
1207
|
-
rowMin = Math.min(rowMin, matrix[i][j]);
|
|
1208
|
-
}
|
|
1209
|
-
if (rowMin > tolerance) {
|
|
1210
|
-
return { distance: -1, withinTolerance: false };
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
const finalDistance = matrix[m][n];
|
|
1214
|
-
return {
|
|
1215
|
-
distance: finalDistance,
|
|
1216
|
-
withinTolerance: finalDistance <= tolerance
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
783
|
+
// src/core/constants.ts
|
|
784
|
+
var POSTING_LIST_INITIAL_CAPACITY = 8;
|
|
785
|
+
var POSTING_LIST_COMPACTION_THRESHOLD = 0.3;
|
|
786
|
+
var MAX_TERM_FREQUENCY = 65535;
|
|
1219
787
|
|
|
1220
788
|
// src/core/posting-list.ts
|
|
1221
|
-
var INITIAL_CAPACITY = 8;
|
|
1222
|
-
var MAX_TERM_FREQUENCY = 65535;
|
|
1223
|
-
var COMPACTION_THRESHOLD = 0.3;
|
|
1224
789
|
function createPostingList() {
|
|
1225
790
|
return {
|
|
1226
791
|
length: 0,
|
|
1227
792
|
docIds: [],
|
|
1228
|
-
termFrequencies: new Uint16Array(
|
|
1229
|
-
fieldNameIndices: new Uint8Array(
|
|
793
|
+
termFrequencies: new Uint16Array(POSTING_LIST_INITIAL_CAPACITY),
|
|
794
|
+
fieldNameIndices: new Uint8Array(POSTING_LIST_INITIAL_CAPACITY),
|
|
1230
795
|
positions: null,
|
|
1231
796
|
docIdSet: /* @__PURE__ */ new Set(),
|
|
1232
797
|
deletedDocs: /* @__PURE__ */ new Set(),
|
|
@@ -1394,7 +959,7 @@ function createInvertedIndex(fieldNameTable) {
|
|
|
1394
959
|
batchDirtyTokens.add(token);
|
|
1395
960
|
return;
|
|
1396
961
|
}
|
|
1397
|
-
if (list.deletedDocs.size / list.length >
|
|
962
|
+
if (list.deletedDocs.size / list.length > POSTING_LIST_COMPACTION_THRESHOLD) {
|
|
1398
963
|
compactList(list);
|
|
1399
964
|
}
|
|
1400
965
|
},
|
|
@@ -1407,7 +972,7 @@ function createInvertedIndex(fieldNameTable) {
|
|
|
1407
972
|
for (const token of batchDirtyTokens) {
|
|
1408
973
|
const list = index.get(token);
|
|
1409
974
|
if (!list) continue;
|
|
1410
|
-
if (list.deletedDocs.size / list.length >
|
|
975
|
+
if (list.deletedDocs.size / list.length > POSTING_LIST_COMPACTION_THRESHOLD) {
|
|
1411
976
|
compactList(list);
|
|
1412
977
|
}
|
|
1413
978
|
}
|
|
@@ -1551,137 +1116,6 @@ function createInvertedIndex(fieldNameTable) {
|
|
|
1551
1116
|
};
|
|
1552
1117
|
}
|
|
1553
1118
|
|
|
1554
|
-
// src/core/projection.ts
|
|
1555
|
-
function splitPaths(paths) {
|
|
1556
|
-
if (!paths) return [];
|
|
1557
|
-
const split = [];
|
|
1558
|
-
for (const path of paths) {
|
|
1559
|
-
const segments = path.split(".").filter((segment) => segment.length > 0);
|
|
1560
|
-
if (segments.length === 0) continue;
|
|
1561
|
-
if (segments.some((segment) => PROTOTYPE_POLLUTION_KEYS.has(segment))) continue;
|
|
1562
|
-
split.push(segments);
|
|
1563
|
-
}
|
|
1564
|
-
return split;
|
|
1565
|
-
}
|
|
1566
|
-
function overlaps(a, b) {
|
|
1567
|
-
const shared = Math.min(a.length, b.length);
|
|
1568
|
-
for (let i = 0; i < shared; i++) {
|
|
1569
|
-
if (a[i] !== b[i]) return false;
|
|
1570
|
-
}
|
|
1571
|
-
return true;
|
|
1572
|
-
}
|
|
1573
|
-
function covers(outer, inner) {
|
|
1574
|
-
if (outer.length > inner.length) return false;
|
|
1575
|
-
for (let i = 0; i < outer.length; i++) {
|
|
1576
|
-
if (outer[i] !== inner[i]) return false;
|
|
1577
|
-
}
|
|
1578
|
-
return true;
|
|
1579
|
-
}
|
|
1580
|
-
function readPath(source, path) {
|
|
1581
|
-
let current = source;
|
|
1582
|
-
for (const segment of path) {
|
|
1583
|
-
if (typeof current !== "object" || current === null || Array.isArray(current))
|
|
1584
|
-
return { found: false, value: void 0 };
|
|
1585
|
-
const record = current;
|
|
1586
|
-
if (!Object.hasOwn(record, segment)) return { found: false, value: void 0 };
|
|
1587
|
-
current = record[segment];
|
|
1588
|
-
}
|
|
1589
|
-
return { found: true, value: current };
|
|
1590
|
-
}
|
|
1591
|
-
function writePath(target, path, value) {
|
|
1592
|
-
let current = target;
|
|
1593
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
1594
|
-
const segment = path[i];
|
|
1595
|
-
const existing = current[segment];
|
|
1596
|
-
if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
|
|
1597
|
-
current = existing;
|
|
1598
|
-
continue;
|
|
1599
|
-
}
|
|
1600
|
-
const created = {};
|
|
1601
|
-
current[segment] = created;
|
|
1602
|
-
current = created;
|
|
1603
|
-
}
|
|
1604
|
-
current[path[path.length - 1]] = value;
|
|
1605
|
-
}
|
|
1606
|
-
function omitPath(source, path) {
|
|
1607
|
-
const segment = path[0];
|
|
1608
|
-
if (!Object.hasOwn(source, segment)) return source;
|
|
1609
|
-
const copy = { ...source };
|
|
1610
|
-
if (path.length === 1) {
|
|
1611
|
-
delete copy[segment];
|
|
1612
|
-
return copy;
|
|
1613
|
-
}
|
|
1614
|
-
const child = copy[segment];
|
|
1615
|
-
if (typeof child !== "object" || child === null || Array.isArray(child)) return copy;
|
|
1616
|
-
copy[segment] = omitPath(child, path.slice(1));
|
|
1617
|
-
return copy;
|
|
1618
|
-
}
|
|
1619
|
-
function cloneExcept(value, path, exclude) {
|
|
1620
|
-
const reachesDeeper = exclude.some((excluded) => excluded.length > path.length && covers(path, excluded));
|
|
1621
|
-
if (!reachesDeeper) return structuredClone(value);
|
|
1622
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return structuredClone(value);
|
|
1623
|
-
const source = value;
|
|
1624
|
-
const kept = {};
|
|
1625
|
-
for (const key of Object.keys(source)) {
|
|
1626
|
-
const childPath = [...path, key];
|
|
1627
|
-
if (exclude.some((excluded) => covers(excluded, childPath))) continue;
|
|
1628
|
-
kept[key] = cloneExcept(source[key], childPath, exclude);
|
|
1629
|
-
}
|
|
1630
|
-
return kept;
|
|
1631
|
-
}
|
|
1632
|
-
function resolveProjection(projection) {
|
|
1633
|
-
if (projection === void 0 || projection === true) return { kind: "full" };
|
|
1634
|
-
if (projection === false) return { kind: "none" };
|
|
1635
|
-
if (typeof projection !== "object" || projection === null) return { kind: "full" };
|
|
1636
|
-
const askedInclude = Array.isArray(projection.include) && projection.include.length > 0;
|
|
1637
|
-
const askedExclude = Array.isArray(projection.exclude) && projection.exclude.length > 0;
|
|
1638
|
-
if (!askedInclude && !askedExclude) return { kind: "full" };
|
|
1639
|
-
return {
|
|
1640
|
-
kind: "fields",
|
|
1641
|
-
include: askedInclude ? splitPaths(projection.include) : null,
|
|
1642
|
-
exclude: splitPaths(projection.exclude)
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
function projectionKeepsField(projection, fieldPath) {
|
|
1646
|
-
if (projection.kind === "full") return true;
|
|
1647
|
-
if (projection.kind === "none") return false;
|
|
1648
|
-
const path = fieldPath.split(".").filter((segment) => segment.length > 0);
|
|
1649
|
-
if (path.length === 0) return false;
|
|
1650
|
-
if (projection.include && !projection.include.some((included) => overlaps(included, path))) return false;
|
|
1651
|
-
return !projection.exclude.some((excluded) => covers(excluded, path));
|
|
1652
|
-
}
|
|
1653
|
-
function cloneProjected(document, projection) {
|
|
1654
|
-
if (projection === void 0 || projection.kind === "full") return structuredClone(document);
|
|
1655
|
-
if (projection.kind === "none") return {};
|
|
1656
|
-
if (projection.include === null) return cloneExcept(document, [], projection.exclude);
|
|
1657
|
-
const projected = {};
|
|
1658
|
-
for (const path of projection.include) {
|
|
1659
|
-
if (projection.exclude.some((excluded) => covers(excluded, path))) continue;
|
|
1660
|
-
const { found, value } = readPath(document, path);
|
|
1661
|
-
if (!found) continue;
|
|
1662
|
-
writePath(projected, path, cloneExcept(value, path, projection.exclude));
|
|
1663
|
-
}
|
|
1664
|
-
return projected;
|
|
1665
|
-
}
|
|
1666
|
-
function applyProjection(document, projection) {
|
|
1667
|
-
if (projection.kind === "full") return document;
|
|
1668
|
-
if (projection.kind === "none") return {};
|
|
1669
|
-
let projected;
|
|
1670
|
-
if (projection.include) {
|
|
1671
|
-
projected = {};
|
|
1672
|
-
for (const path of projection.include) {
|
|
1673
|
-
const { found, value } = readPath(document, path);
|
|
1674
|
-
if (found) writePath(projected, path, value);
|
|
1675
|
-
}
|
|
1676
|
-
} else {
|
|
1677
|
-
projected = document;
|
|
1678
|
-
}
|
|
1679
|
-
for (const path of projection.exclude) {
|
|
1680
|
-
projected = omitPath(projected, path);
|
|
1681
|
-
}
|
|
1682
|
-
return projected;
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
1119
|
// src/core/statistics.ts
|
|
1686
1120
|
function applyUniqueTokens(tokens, docFrequencies, increment) {
|
|
1687
1121
|
const fields = Object.keys(tokens);
|
|
@@ -1925,84 +1359,23 @@ function createSurfaceRegistry() {
|
|
|
1925
1359
|
};
|
|
1926
1360
|
}
|
|
1927
1361
|
|
|
1928
|
-
// src/core/
|
|
1929
|
-
function
|
|
1930
|
-
|
|
1362
|
+
// src/core/partition/utils.ts
|
|
1363
|
+
function getOrCreateFieldNameIndex(table, fieldName) {
|
|
1364
|
+
const existing = table.indexMap.get(fieldName);
|
|
1365
|
+
if (existing !== void 0) return existing;
|
|
1366
|
+
const idx = table.names.length;
|
|
1367
|
+
table.names.push(fieldName);
|
|
1368
|
+
table.indexMap.set(fieldName, idx);
|
|
1369
|
+
return idx;
|
|
1931
1370
|
}
|
|
1932
|
-
function
|
|
1933
|
-
const
|
|
1934
|
-
|
|
1935
|
-
|
|
1371
|
+
function getNestedValue(obj, path) {
|
|
1372
|
+
const parts = path.split(".");
|
|
1373
|
+
let current = obj;
|
|
1374
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1375
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
1376
|
+
current = current[parts[i]];
|
|
1936
1377
|
}
|
|
1937
|
-
|
|
1938
|
-
function bitsetHas(bs, index) {
|
|
1939
|
-
const word = index >>> 5;
|
|
1940
|
-
if (word >= bs.length) return false;
|
|
1941
|
-
return (bs[word] & 1 << (index & 31)) !== 0;
|
|
1942
|
-
}
|
|
1943
|
-
function bitsetAnd(a, b) {
|
|
1944
|
-
const len = Math.min(a.length, b.length);
|
|
1945
|
-
const result = new Uint32Array(len);
|
|
1946
|
-
for (let i = 0; i < len; i++) {
|
|
1947
|
-
result[i] = a[i] & b[i];
|
|
1948
|
-
}
|
|
1949
|
-
return result;
|
|
1950
|
-
}
|
|
1951
|
-
function bitsetOr(a, b) {
|
|
1952
|
-
const longer = a.length >= b.length ? a : b;
|
|
1953
|
-
const shorter = a.length >= b.length ? b : a;
|
|
1954
|
-
const result = new Uint32Array(longer.length);
|
|
1955
|
-
for (let i = 0; i < shorter.length; i++) {
|
|
1956
|
-
result[i] = longer[i] | shorter[i];
|
|
1957
|
-
}
|
|
1958
|
-
for (let i = shorter.length; i < longer.length; i++) {
|
|
1959
|
-
result[i] = longer[i];
|
|
1960
|
-
}
|
|
1961
|
-
return result;
|
|
1962
|
-
}
|
|
1963
|
-
function bitsetNot(bs, capacity) {
|
|
1964
|
-
const result = new Uint32Array(bs.length);
|
|
1965
|
-
const fullWords = capacity >>> 5;
|
|
1966
|
-
for (let i = 0; i < fullWords; i++) {
|
|
1967
|
-
result[i] = ~bs[i];
|
|
1968
|
-
}
|
|
1969
|
-
const remainder = capacity & 31;
|
|
1970
|
-
if (remainder > 0 && fullWords < bs.length) {
|
|
1971
|
-
result[fullWords] = ~bs[fullWords] & (1 << remainder) - 1;
|
|
1972
|
-
}
|
|
1973
|
-
return result;
|
|
1974
|
-
}
|
|
1975
|
-
function bitsetIsEmpty(bs) {
|
|
1976
|
-
for (let i = 0; i < bs.length; i++) {
|
|
1977
|
-
if (bs[i] !== 0) return false;
|
|
1978
|
-
}
|
|
1979
|
-
return true;
|
|
1980
|
-
}
|
|
1981
|
-
function bitsetFromSet(set, capacity) {
|
|
1982
|
-
const bs = createBitSet(capacity);
|
|
1983
|
-
for (const id of set) {
|
|
1984
|
-
bs[id >>> 5] |= 1 << (id & 31);
|
|
1985
|
-
}
|
|
1986
|
-
return bs;
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
// src/core/partition/utils.ts
|
|
1990
|
-
function getOrCreateFieldNameIndex(table, fieldName) {
|
|
1991
|
-
const existing = table.indexMap.get(fieldName);
|
|
1992
|
-
if (existing !== void 0) return existing;
|
|
1993
|
-
const idx = table.names.length;
|
|
1994
|
-
table.names.push(fieldName);
|
|
1995
|
-
table.indexMap.set(fieldName, idx);
|
|
1996
|
-
return idx;
|
|
1997
|
-
}
|
|
1998
|
-
function getNestedValue(obj, path) {
|
|
1999
|
-
const parts = path.split(".");
|
|
2000
|
-
let current = obj;
|
|
2001
|
-
for (let i = 0; i < parts.length; i++) {
|
|
2002
|
-
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
2003
|
-
current = current[parts[i]];
|
|
2004
|
-
}
|
|
2005
|
-
return current;
|
|
1378
|
+
return current;
|
|
2006
1379
|
}
|
|
2007
1380
|
function stringArraysEqual(a, b) {
|
|
2008
1381
|
if (a === b) return true;
|
|
@@ -2190,9 +1563,13 @@ function computeFacets(state, matched, config, schema) {
|
|
|
2190
1563
|
return result;
|
|
2191
1564
|
}
|
|
2192
1565
|
|
|
1566
|
+
// src/geo/constants.ts
|
|
1567
|
+
var DEG_TO_RAD = Math.PI / 180;
|
|
1568
|
+
var VINCENTY_MAX_ITERATIONS = 200;
|
|
1569
|
+
var VINCENTY_CONVERGENCE_THRESHOLD = 1e-12;
|
|
1570
|
+
|
|
2193
1571
|
// src/geo/haversine.ts
|
|
2194
1572
|
var EARTH_RADIUS_METERS = 63710088e-1;
|
|
2195
|
-
var DEG_TO_RAD = Math.PI / 180;
|
|
2196
1573
|
function haversineDistance(lat1, lon1, lat2, lon2) {
|
|
2197
1574
|
const phi1 = lat1 * DEG_TO_RAD;
|
|
2198
1575
|
const phi2 = lat2 * DEG_TO_RAD;
|
|
@@ -2230,13 +1607,10 @@ function isPointInPolygon(lat, lon, polygon) {
|
|
|
2230
1607
|
var WGS84_A = 6378137;
|
|
2231
1608
|
var WGS84_F = 1 / 298.257223563;
|
|
2232
1609
|
var WGS84_B = WGS84_A * (1 - WGS84_F);
|
|
2233
|
-
var DEG_TO_RAD2 = Math.PI / 180;
|
|
2234
|
-
var MAX_ITERATIONS = 200;
|
|
2235
|
-
var CONVERGENCE_THRESHOLD = 1e-12;
|
|
2236
1610
|
function vincentyDistance(lat1, lon1, lat2, lon2) {
|
|
2237
|
-
const phi1 = lat1 *
|
|
2238
|
-
const phi2 = lat2 *
|
|
2239
|
-
const L = (lon2 - lon1) *
|
|
1611
|
+
const phi1 = lat1 * DEG_TO_RAD;
|
|
1612
|
+
const phi2 = lat2 * DEG_TO_RAD;
|
|
1613
|
+
const L = (lon2 - lon1) * DEG_TO_RAD;
|
|
2240
1614
|
const U1 = Math.atan((1 - WGS84_F) * Math.tan(phi1));
|
|
2241
1615
|
const U2 = Math.atan((1 - WGS84_F) * Math.tan(phi2));
|
|
2242
1616
|
const sinU1 = Math.sin(U1);
|
|
@@ -2251,7 +1625,7 @@ function vincentyDistance(lat1, lon1, lat2, lon2) {
|
|
|
2251
1625
|
let sinAlpha = 0;
|
|
2252
1626
|
let cos2Alpha = 0;
|
|
2253
1627
|
let cos2SigmaM = 0;
|
|
2254
|
-
let iterations =
|
|
1628
|
+
let iterations = VINCENTY_MAX_ITERATIONS;
|
|
2255
1629
|
do {
|
|
2256
1630
|
const sinLambda = Math.sin(lambda);
|
|
2257
1631
|
const cosLambda = Math.cos(lambda);
|
|
@@ -2267,7 +1641,7 @@ function vincentyDistance(lat1, lon1, lat2, lon2) {
|
|
|
2267
1641
|
const C = WGS84_F / 16 * cos2Alpha * (4 + WGS84_F * (4 - 3 * cos2Alpha));
|
|
2268
1642
|
prevLambda = lambda;
|
|
2269
1643
|
lambda = L + (1 - C) * WGS84_F * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
|
|
2270
|
-
} while (Math.abs(lambda - prevLambda) >
|
|
1644
|
+
} while (Math.abs(lambda - prevLambda) > VINCENTY_CONVERGENCE_THRESHOLD && --iterations > 0);
|
|
2271
1645
|
if (iterations === 0) {
|
|
2272
1646
|
return haversineDistance(lat1, lon1, lat2, lon2);
|
|
2273
1647
|
}
|
|
@@ -2696,452 +2070,6 @@ function createNumericIndex() {
|
|
|
2696
2070
|
};
|
|
2697
2071
|
}
|
|
2698
2072
|
|
|
2699
|
-
// src/core/tokenizer/normalize.ts
|
|
2700
|
-
var DOTTED_CAPITAL_I = "\u0130";
|
|
2701
|
-
var COMBINING_DOT_ABOVE = "\u0307";
|
|
2702
|
-
var ARMENIAN_MARKS = /[\u055B-\u055F]/;
|
|
2703
|
-
var FOLDED_FORMS = /\u0307|[\u0130\u055B-\u055F\u2018\u2019\u02BC\u02BB]/g;
|
|
2704
|
-
var WIDTH_FORMS = /[\uFF01-\uFF5E\uFF61-\uFF9F]/;
|
|
2705
|
-
var WIDTH_FORMS_GLOBAL = /[\uFF01-\uFF5E\uFF61-\uFF9F]/g;
|
|
2706
|
-
var FULLWIDTH_ASCII_END = 65374;
|
|
2707
|
-
var FULLWIDTH_ASCII_OFFSET = 65248;
|
|
2708
|
-
var HALFWIDTH_KANA_START = 65377;
|
|
2709
|
-
var HALFWIDTH_VOICED_MARK = 65438;
|
|
2710
|
-
var HALFWIDTH_SEMI_VOICED_MARK = 65439;
|
|
2711
|
-
var COMBINING_VOICED_MARK = "\u3099";
|
|
2712
|
-
var COMBINING_SEMI_VOICED_MARK = "\u309A";
|
|
2713
|
-
var HALFWIDTH_KANA_TARGETS = "\u3002\u300C\u300D\u3001\u30FB\u30F2\u30A1\u30A3\u30A5\u30A7\u30A9\u30E3\u30E5\u30E7\u30C3\u30FC\u30A2\u30A4\u30A6\u30A8\u30AA\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD\u30BF\u30C1\u30C4\u30C6\u30C8\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D2\u30D5\u30D8\u30DB\u30DE\u30DF\u30E0\u30E1\u30E2\u30E4\u30E6\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EF\u30F3";
|
|
2714
|
-
function foldForm(match) {
|
|
2715
|
-
if (match === DOTTED_CAPITAL_I) return "i";
|
|
2716
|
-
if (match === COMBINING_DOT_ABOVE || ARMENIAN_MARKS.test(match)) return "";
|
|
2717
|
-
return "'";
|
|
2718
|
-
}
|
|
2719
|
-
function foldWidth(match) {
|
|
2720
|
-
const codePoint = match.charCodeAt(0);
|
|
2721
|
-
if (codePoint <= FULLWIDTH_ASCII_END) return String.fromCharCode(codePoint - FULLWIDTH_ASCII_OFFSET);
|
|
2722
|
-
if (codePoint === HALFWIDTH_VOICED_MARK) return COMBINING_VOICED_MARK;
|
|
2723
|
-
if (codePoint === HALFWIDTH_SEMI_VOICED_MARK) return COMBINING_SEMI_VOICED_MARK;
|
|
2724
|
-
return HALFWIDTH_KANA_TARGETS[codePoint - HALFWIDTH_KANA_START];
|
|
2725
|
-
}
|
|
2726
|
-
function isAscii(text) {
|
|
2727
|
-
for (let i = 0; i < text.length; i++) {
|
|
2728
|
-
if (text.charCodeAt(i) > 127) return false;
|
|
2729
|
-
}
|
|
2730
|
-
return true;
|
|
2731
|
-
}
|
|
2732
|
-
function foldWidthForms(text) {
|
|
2733
|
-
if (!WIDTH_FORMS.test(text)) return text;
|
|
2734
|
-
return text.replace(WIDTH_FORMS_GLOBAL, foldWidth);
|
|
2735
|
-
}
|
|
2736
|
-
function normalizeForSplitting(text) {
|
|
2737
|
-
if (isAscii(text)) return text.toLowerCase();
|
|
2738
|
-
return foldWidthForms(text).normalize("NFC").replace(FOLDED_FORMS, foldForm).toLowerCase();
|
|
2739
|
-
}
|
|
2740
|
-
function stripDiacritics(text) {
|
|
2741
|
-
return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
// src/core/tokenizer/cache.ts
|
|
2745
|
-
var CACHE_SIZE_FLOOR = 5e4;
|
|
2746
|
-
var CACHE_SIZE_CEILING = 2e6;
|
|
2747
|
-
var BYTES_PER_ENTRY = 200;
|
|
2748
|
-
var NODE_CACHE_ENTRIES = 1e6;
|
|
2749
|
-
var UNKNOWN_MEMORY_CACHE_ENTRIES = 2e5;
|
|
2750
|
-
function clampCacheSize(entries) {
|
|
2751
|
-
return Math.max(CACHE_SIZE_FLOOR, Math.min(entries, CACHE_SIZE_CEILING));
|
|
2752
|
-
}
|
|
2753
|
-
function computeDefaultCacheSize() {
|
|
2754
|
-
try {
|
|
2755
|
-
if (typeof process !== "undefined" && typeof process.versions?.node === "string" && typeof window === "undefined") {
|
|
2756
|
-
const constrainedMemory = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
|
|
2757
|
-
if (constrainedMemory > 0) {
|
|
2758
|
-
return clampCacheSize(Math.floor(constrainedMemory * 0.05 / BYTES_PER_ENTRY));
|
|
2759
|
-
}
|
|
2760
|
-
return clampCacheSize(NODE_CACHE_ENTRIES);
|
|
2761
|
-
}
|
|
2762
|
-
if (typeof navigator !== "undefined") {
|
|
2763
|
-
const mem = navigator.deviceMemory;
|
|
2764
|
-
if (typeof mem === "number" && mem > 0) {
|
|
2765
|
-
let entries;
|
|
2766
|
-
if (mem <= 1) entries = 1e5;
|
|
2767
|
-
else if (mem <= 4) entries = 25e4;
|
|
2768
|
-
else entries = 5e5;
|
|
2769
|
-
return clampCacheSize(entries);
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
} catch {
|
|
2773
|
-
return clampCacheSize(UNKNOWN_MEMORY_CACHE_ENTRIES);
|
|
2774
|
-
}
|
|
2775
|
-
return clampCacheSize(UNKNOWN_MEMORY_CACHE_ENTRIES);
|
|
2776
|
-
}
|
|
2777
|
-
var normalizationCache = /* @__PURE__ */ new Map();
|
|
2778
|
-
var normalizationCacheSize = 0;
|
|
2779
|
-
var maxCacheSize = computeDefaultCacheSize();
|
|
2780
|
-
var cachedLangName = "";
|
|
2781
|
-
var cachedFlags = "";
|
|
2782
|
-
var cachedBucket = null;
|
|
2783
|
-
function configureNormalizationCache(maxSize) {
|
|
2784
|
-
if (!Number.isFinite(maxSize)) {
|
|
2785
|
-
throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must be a finite number", {
|
|
2786
|
-
received: maxSize
|
|
2787
|
-
});
|
|
2788
|
-
}
|
|
2789
|
-
if (maxSize < 0) {
|
|
2790
|
-
throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must not be negative", {
|
|
2791
|
-
received: maxSize
|
|
2792
|
-
});
|
|
2793
|
-
}
|
|
2794
|
-
if (maxSize === 0) {
|
|
2795
|
-
throw createNarsilError(
|
|
2796
|
-
ErrorCodes.CONFIG_INVALID,
|
|
2797
|
-
"tokenizerCacheSize must be greater than zero; the normalization cache cannot be disabled",
|
|
2798
|
-
{
|
|
2799
|
-
received: maxSize
|
|
2800
|
-
}
|
|
2801
|
-
);
|
|
2802
|
-
}
|
|
2803
|
-
maxCacheSize = Math.max(CACHE_SIZE_FLOOR, Math.min(Math.floor(maxSize), CACHE_SIZE_CEILING));
|
|
2804
|
-
if (normalizationCacheSize > maxCacheSize) {
|
|
2805
|
-
evictOldestEntries(normalizationCacheSize - maxCacheSize);
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
function getCacheBucket(language, flags) {
|
|
2809
|
-
const langKey = `${language.name}@${language.revision}`;
|
|
2810
|
-
if (cachedBucket && langKey === cachedLangName && flags === cachedFlags) return cachedBucket;
|
|
2811
|
-
const key = `${langKey}:${flags}`;
|
|
2812
|
-
let bucket = normalizationCache.get(key);
|
|
2813
|
-
if (!bucket) {
|
|
2814
|
-
bucket = /* @__PURE__ */ new Map();
|
|
2815
|
-
normalizationCache.set(key, bucket);
|
|
2816
|
-
}
|
|
2817
|
-
cachedLangName = langKey;
|
|
2818
|
-
cachedFlags = flags;
|
|
2819
|
-
cachedBucket = bucket;
|
|
2820
|
-
return bucket;
|
|
2821
|
-
}
|
|
2822
|
-
function evictOldestEntries(count) {
|
|
2823
|
-
let remaining = count;
|
|
2824
|
-
for (const [key, bucket] of normalizationCache) {
|
|
2825
|
-
if (remaining <= 0) break;
|
|
2826
|
-
if (bucket.size <= remaining) {
|
|
2827
|
-
remaining -= bucket.size;
|
|
2828
|
-
normalizationCacheSize -= bucket.size;
|
|
2829
|
-
normalizationCache.delete(key);
|
|
2830
|
-
if (bucket === cachedBucket) cachedBucket = null;
|
|
2831
|
-
} else {
|
|
2832
|
-
let deleted = 0;
|
|
2833
|
-
for (const raw of bucket.keys()) {
|
|
2834
|
-
if (deleted >= remaining) break;
|
|
2835
|
-
bucket.delete(raw);
|
|
2836
|
-
deleted++;
|
|
2837
|
-
}
|
|
2838
|
-
normalizationCacheSize -= deleted;
|
|
2839
|
-
remaining = 0;
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
function transformToken(raw, language, stem, removeDiacritics) {
|
|
2844
|
-
const flags = (stem ? "s" : "") + (removeDiacritics ? "d" : "");
|
|
2845
|
-
let bucket = getCacheBucket(language, flags);
|
|
2846
|
-
const cached = bucket.get(raw);
|
|
2847
|
-
if (cached !== void 0) return cached;
|
|
2848
|
-
let normalized = raw;
|
|
2849
|
-
if (language.normalizer) {
|
|
2850
|
-
normalized = language.normalizer(normalized);
|
|
2851
|
-
}
|
|
2852
|
-
if (removeDiacritics) {
|
|
2853
|
-
normalized = stripDiacritics(normalized);
|
|
2854
|
-
}
|
|
2855
|
-
if (stem && language.stemmer) {
|
|
2856
|
-
normalized = language.stemmer(normalized);
|
|
2857
|
-
}
|
|
2858
|
-
if (normalizationCacheSize >= maxCacheSize) {
|
|
2859
|
-
evictOldestEntries(Math.max(1, maxCacheSize >>> 2));
|
|
2860
|
-
bucket = getCacheBucket(language, flags);
|
|
2861
|
-
}
|
|
2862
|
-
bucket.set(raw, normalized);
|
|
2863
|
-
normalizationCacheSize++;
|
|
2864
|
-
return normalized;
|
|
2865
|
-
}
|
|
2866
|
-
function clearNormalizationCache() {
|
|
2867
|
-
normalizationCache.clear();
|
|
2868
|
-
normalizationCacheSize = 0;
|
|
2869
|
-
cachedBucket = null;
|
|
2870
|
-
}
|
|
2871
|
-
function resetNormalizationCache() {
|
|
2872
|
-
clearNormalizationCache();
|
|
2873
|
-
maxCacheSize = computeDefaultCacheSize();
|
|
2874
|
-
}
|
|
2875
|
-
function getNormalizationCacheSize() {
|
|
2876
|
-
return normalizationCacheSize;
|
|
2877
|
-
}
|
|
2878
|
-
|
|
2879
|
-
// src/core/tokenizer/ngrams.ts
|
|
2880
|
-
var OTHER_SCRIPT = 0;
|
|
2881
|
-
var HAN_SCRIPT = 1;
|
|
2882
|
-
var HIRAGANA_SCRIPT = 2;
|
|
2883
|
-
var KATAKANA_SCRIPT = 3;
|
|
2884
|
-
var HANGUL_SCRIPT = 4;
|
|
2885
|
-
var THAI_SCRIPT = 5;
|
|
2886
|
-
var LAO_SCRIPT = 6;
|
|
2887
|
-
var KHMER_SCRIPT = 7;
|
|
2888
|
-
var MYANMAR_SCRIPT = 8;
|
|
2889
|
-
var PROLONGED_SOUND_MARK = 12540;
|
|
2890
|
-
var IDEOGRAPHIC_ITERATION_MARK = 12293;
|
|
2891
|
-
var LOWEST_NGRAM_CODE_POINT = 3584;
|
|
2892
|
-
var LOWEST_MARK_CODE_POINT = 768;
|
|
2893
|
-
var THAI_BLOCK_END = 3711;
|
|
2894
|
-
var LAO_BLOCK_END = 3839;
|
|
2895
|
-
var MYANMAR_BLOCK_START = 4096;
|
|
2896
|
-
var MYANMAR_BLOCK_END = 4255;
|
|
2897
|
-
var KHMER_BLOCK_START = 6016;
|
|
2898
|
-
var KHMER_BLOCK_END = 6655;
|
|
2899
|
-
var SOUTHEAST_ASIAN_BLOCKS_END = 6656;
|
|
2900
|
-
var HIRAGANA_BLOCK_START = 12352;
|
|
2901
|
-
var HIRAGANA_BLOCK_END = 12447;
|
|
2902
|
-
var COMBINING_VOICED_MARK2 = 12441;
|
|
2903
|
-
var COMBINING_SEMI_VOICED_MARK2 = 12442;
|
|
2904
|
-
var KATAKANA_BLOCK_START = 12448;
|
|
2905
|
-
var KATAKANA_BLOCK_END = 12543;
|
|
2906
|
-
var HAN_EXTENSION_A_START = 13312;
|
|
2907
|
-
var HAN_EXTENSION_A_END = 19903;
|
|
2908
|
-
var HAN_UNIFIED_START = 19968;
|
|
2909
|
-
var HAN_UNIFIED_END = 40959;
|
|
2910
|
-
var HANGUL_SYLLABLE_START = 44032;
|
|
2911
|
-
var HANGUL_SYLLABLE_END = 55203;
|
|
2912
|
-
var HAN_COMPATIBILITY_START = 63744;
|
|
2913
|
-
var HAN_COMPATIBILITY_END = 64255;
|
|
2914
|
-
var SUPPLEMENTARY_PLANE_START = 65536;
|
|
2915
|
-
var MARK = /\p{M}/uy;
|
|
2916
|
-
var HAN = /\p{Script=Han}/uy;
|
|
2917
|
-
var HIRAGANA = /\p{Script=Hiragana}/uy;
|
|
2918
|
-
var KATAKANA = /\p{Script=Katakana}/uy;
|
|
2919
|
-
var HANGUL = /\p{Script=Hangul}/uy;
|
|
2920
|
-
var THAI = /\p{Script=Thai}/uy;
|
|
2921
|
-
var LAO = /\p{Script=Lao}/uy;
|
|
2922
|
-
var KHMER = /\p{Script=Khmer}/uy;
|
|
2923
|
-
var MYANMAR = /\p{Script=Myanmar}/uy;
|
|
2924
|
-
function matchesAt(pattern, part, index) {
|
|
2925
|
-
pattern.lastIndex = index;
|
|
2926
|
-
return pattern.test(part);
|
|
2927
|
-
}
|
|
2928
|
-
function isMark(part, index, codePoint) {
|
|
2929
|
-
if (codePoint < LOWEST_MARK_CODE_POINT) return false;
|
|
2930
|
-
if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return false;
|
|
2931
|
-
if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) {
|
|
2932
|
-
return codePoint === COMBINING_VOICED_MARK2 || codePoint === COMBINING_SEMI_VOICED_MARK2;
|
|
2933
|
-
}
|
|
2934
|
-
if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return false;
|
|
2935
|
-
if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return false;
|
|
2936
|
-
return matchesAt(MARK, part, index);
|
|
2937
|
-
}
|
|
2938
|
-
function classifyOutsideCommonBlocks(part, index) {
|
|
2939
|
-
if (matchesAt(HAN, part, index)) return HAN_SCRIPT;
|
|
2940
|
-
if (matchesAt(HIRAGANA, part, index)) return HIRAGANA_SCRIPT;
|
|
2941
|
-
if (matchesAt(KATAKANA, part, index)) return KATAKANA_SCRIPT;
|
|
2942
|
-
if (matchesAt(HANGUL, part, index)) return HANGUL_SCRIPT;
|
|
2943
|
-
if (matchesAt(THAI, part, index)) return THAI_SCRIPT;
|
|
2944
|
-
if (matchesAt(LAO, part, index)) return LAO_SCRIPT;
|
|
2945
|
-
if (matchesAt(KHMER, part, index)) return KHMER_SCRIPT;
|
|
2946
|
-
if (matchesAt(MYANMAR, part, index)) return MYANMAR_SCRIPT;
|
|
2947
|
-
return OTHER_SCRIPT;
|
|
2948
|
-
}
|
|
2949
|
-
function classifySoutheastAsian(part, index, codePoint) {
|
|
2950
|
-
if (codePoint <= THAI_BLOCK_END) return matchesAt(THAI, part, index) ? THAI_SCRIPT : OTHER_SCRIPT;
|
|
2951
|
-
if (codePoint <= LAO_BLOCK_END) return matchesAt(LAO, part, index) ? LAO_SCRIPT : OTHER_SCRIPT;
|
|
2952
|
-
if (codePoint >= MYANMAR_BLOCK_START && codePoint <= MYANMAR_BLOCK_END) {
|
|
2953
|
-
return matchesAt(MYANMAR, part, index) ? MYANMAR_SCRIPT : OTHER_SCRIPT;
|
|
2954
|
-
}
|
|
2955
|
-
if (codePoint >= KHMER_BLOCK_START && codePoint <= KHMER_BLOCK_END) {
|
|
2956
|
-
return matchesAt(KHMER, part, index) ? KHMER_SCRIPT : OTHER_SCRIPT;
|
|
2957
|
-
}
|
|
2958
|
-
return classifyOutsideCommonBlocks(part, index);
|
|
2959
|
-
}
|
|
2960
|
-
function classify(part, index, codePoint, current) {
|
|
2961
|
-
if (codePoint < LOWEST_NGRAM_CODE_POINT) return OTHER_SCRIPT;
|
|
2962
|
-
if (codePoint < SOUTHEAST_ASIAN_BLOCKS_END) return classifySoutheastAsian(part, index, codePoint);
|
|
2963
|
-
if (codePoint === PROLONGED_SOUND_MARK) {
|
|
2964
|
-
return current === HIRAGANA_SCRIPT ? HIRAGANA_SCRIPT : KATAKANA_SCRIPT;
|
|
2965
|
-
}
|
|
2966
|
-
if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= HIRAGANA_BLOCK_END) return HIRAGANA_SCRIPT;
|
|
2967
|
-
if (codePoint >= KATAKANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) return KATAKANA_SCRIPT;
|
|
2968
|
-
if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return HAN_SCRIPT;
|
|
2969
|
-
if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return HAN_SCRIPT;
|
|
2970
|
-
if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return HANGUL_SCRIPT;
|
|
2971
|
-
if (codePoint >= HAN_COMPATIBILITY_START && codePoint <= HAN_COMPATIBILITY_END) return HAN_SCRIPT;
|
|
2972
|
-
if (codePoint === IDEOGRAPHIC_ITERATION_MARK) return HAN_SCRIPT;
|
|
2973
|
-
return classifyOutsideCommonBlocks(part, index);
|
|
2974
|
-
}
|
|
2975
|
-
function emitRun(part, offsets, kind, size, out) {
|
|
2976
|
-
const count = offsets.length - 1;
|
|
2977
|
-
if (count < 1) return;
|
|
2978
|
-
if (kind === OTHER_SCRIPT || count <= size) {
|
|
2979
|
-
out.push(part.slice(offsets[0], offsets[count]));
|
|
2980
|
-
return;
|
|
2981
|
-
}
|
|
2982
|
-
for (let start = 0; start + size <= count; start++) {
|
|
2983
|
-
out.push(part.slice(offsets[start], offsets[start + size]));
|
|
2984
|
-
}
|
|
2985
|
-
}
|
|
2986
|
-
function expandNgrams(part, size, out) {
|
|
2987
|
-
const length = part.length;
|
|
2988
|
-
if (length === 0) return;
|
|
2989
|
-
const offsets = [];
|
|
2990
|
-
let kind = OTHER_SCRIPT;
|
|
2991
|
-
let index = 0;
|
|
2992
|
-
while (index < length) {
|
|
2993
|
-
const codePoint = part.codePointAt(index);
|
|
2994
|
-
if (codePoint === void 0) break;
|
|
2995
|
-
const width = codePoint >= SUPPLEMENTARY_PLANE_START ? 2 : 1;
|
|
2996
|
-
if (offsets.length > 0 && isMark(part, index, codePoint)) {
|
|
2997
|
-
index += width;
|
|
2998
|
-
continue;
|
|
2999
|
-
}
|
|
3000
|
-
const next = classify(part, index, codePoint, kind);
|
|
3001
|
-
if (offsets.length > 0 && next !== kind) {
|
|
3002
|
-
offsets.push(index);
|
|
3003
|
-
emitRun(part, offsets, kind, size, out);
|
|
3004
|
-
offsets.length = 0;
|
|
3005
|
-
}
|
|
3006
|
-
kind = next;
|
|
3007
|
-
offsets.push(index);
|
|
3008
|
-
index += width;
|
|
3009
|
-
}
|
|
3010
|
-
offsets.push(length);
|
|
3011
|
-
emitRun(part, offsets, kind, size, out);
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
// src/core/tokenizer/tokenize.ts
|
|
3015
|
-
var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{M}\p{N}_'-]+/u;
|
|
3016
|
-
var DEFAULT_MIN_TOKEN_LENGTH = 1;
|
|
3017
|
-
function resolveStopWords(language, override) {
|
|
3018
|
-
if (!override) return language.stopWords;
|
|
3019
|
-
if (override instanceof Set) return override;
|
|
3020
|
-
return override(language.stopWords);
|
|
3021
|
-
}
|
|
3022
|
-
function splitText(text, language) {
|
|
3023
|
-
const pattern = language.tokenizer?.splitPattern ?? DEFAULT_SPLIT_PATTERN;
|
|
3024
|
-
const parts = text.split(pattern);
|
|
3025
|
-
const ngramSize = language.tokenizer?.ngramSize;
|
|
3026
|
-
if (ngramSize === void 0 || ngramSize < 1) return parts;
|
|
3027
|
-
const size = Math.floor(ngramSize);
|
|
3028
|
-
const expanded = [];
|
|
3029
|
-
for (const part of parts) {
|
|
3030
|
-
expandNgrams(part, size, expanded);
|
|
3031
|
-
}
|
|
3032
|
-
return expanded;
|
|
3033
|
-
}
|
|
3034
|
-
var CHAR_APOSTROPHE = 39;
|
|
3035
|
-
var CHAR_S = 115;
|
|
3036
|
-
function stripPossessive(token) {
|
|
3037
|
-
const len = token.length;
|
|
3038
|
-
if (len >= 2 && token.charCodeAt(len - 1) === CHAR_S && token.charCodeAt(len - 2) === CHAR_APOSTROPHE) {
|
|
3039
|
-
return token.slice(0, -2);
|
|
3040
|
-
}
|
|
3041
|
-
if (len >= 1 && token.charCodeAt(len - 1) === CHAR_APOSTROPHE) {
|
|
3042
|
-
return token.slice(0, -1);
|
|
3043
|
-
}
|
|
3044
|
-
return token;
|
|
3045
|
-
}
|
|
3046
|
-
function tokenize(text, language, options) {
|
|
3047
|
-
const {
|
|
3048
|
-
stem = true,
|
|
3049
|
-
removeStopWords = true,
|
|
3050
|
-
removeDiacritics = false,
|
|
3051
|
-
collectSurfaces = false,
|
|
3052
|
-
customTokenizer,
|
|
3053
|
-
stopWordOverride
|
|
3054
|
-
} = options ?? {};
|
|
3055
|
-
if (customTokenizer) {
|
|
3056
|
-
const customResult = customTokenizer.tokenize(text);
|
|
3057
|
-
const originals = customResult.map((t) => t.token);
|
|
3058
|
-
return {
|
|
3059
|
-
tokens: customResult,
|
|
3060
|
-
originalTokens: originals
|
|
3061
|
-
};
|
|
3062
|
-
}
|
|
3063
|
-
const normalized = normalizeForSplitting(text);
|
|
3064
|
-
const rawParts = splitText(normalized, language);
|
|
3065
|
-
const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
|
|
3066
|
-
const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
|
|
3067
|
-
const stemsTokens = stem && language.stemmer !== null;
|
|
3068
|
-
const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
|
|
3069
|
-
const stripPossessives = language.tokenizer?.stripPossessive ?? false;
|
|
3070
|
-
const tokens = [];
|
|
3071
|
-
const originalTokens = [];
|
|
3072
|
-
const surfaces = collectSurfaces && stemsTokens ? [] : void 0;
|
|
3073
|
-
let position = 0;
|
|
3074
|
-
for (const part of rawParts) {
|
|
3075
|
-
const candidate = stripPossessives ? stripPossessive(part) : part;
|
|
3076
|
-
if (candidate.length < minLength) {
|
|
3077
|
-
position++;
|
|
3078
|
-
continue;
|
|
3079
|
-
}
|
|
3080
|
-
if (stopWords.has(candidate)) {
|
|
3081
|
-
position++;
|
|
3082
|
-
continue;
|
|
3083
|
-
}
|
|
3084
|
-
const processed = transformToken(candidate, language, stem, effectiveDiacritics);
|
|
3085
|
-
if (processed.length > 0) {
|
|
3086
|
-
tokens.push({ token: processed, position });
|
|
3087
|
-
originalTokens.push(part);
|
|
3088
|
-
if (surfaces) {
|
|
3089
|
-
const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
|
|
3090
|
-
surfaces.push(surface === processed ? void 0 : surface);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
position++;
|
|
3094
|
-
}
|
|
3095
|
-
return { tokens, originalTokens, surfaces };
|
|
3096
|
-
}
|
|
3097
|
-
function* tokenizeIterator(text, language, options) {
|
|
3098
|
-
const {
|
|
3099
|
-
stem = true,
|
|
3100
|
-
removeStopWords = true,
|
|
3101
|
-
removeDiacritics = false,
|
|
3102
|
-
collectSurfaces = false,
|
|
3103
|
-
customTokenizer,
|
|
3104
|
-
stopWordOverride
|
|
3105
|
-
} = options ?? {};
|
|
3106
|
-
if (customTokenizer) {
|
|
3107
|
-
const customResult = customTokenizer.tokenize(text);
|
|
3108
|
-
for (const entry of customResult) {
|
|
3109
|
-
yield entry;
|
|
3110
|
-
}
|
|
3111
|
-
return;
|
|
3112
|
-
}
|
|
3113
|
-
const normalized = normalizeForSplitting(text);
|
|
3114
|
-
const rawParts = splitText(normalized, language);
|
|
3115
|
-
const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
|
|
3116
|
-
const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
|
|
3117
|
-
const stemsTokens = stem && language.stemmer !== null;
|
|
3118
|
-
const wantSurfaces = collectSurfaces && stemsTokens;
|
|
3119
|
-
const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
|
|
3120
|
-
const stripPossessives = language.tokenizer?.stripPossessive ?? false;
|
|
3121
|
-
let position = 0;
|
|
3122
|
-
for (const part of rawParts) {
|
|
3123
|
-
const candidate = stripPossessives ? stripPossessive(part) : part;
|
|
3124
|
-
if (candidate.length < minLength) {
|
|
3125
|
-
position++;
|
|
3126
|
-
continue;
|
|
3127
|
-
}
|
|
3128
|
-
if (stopWords.has(candidate)) {
|
|
3129
|
-
position++;
|
|
3130
|
-
continue;
|
|
3131
|
-
}
|
|
3132
|
-
const processed = transformToken(candidate, language, stem, effectiveDiacritics);
|
|
3133
|
-
if (processed.length > 0) {
|
|
3134
|
-
if (wantSurfaces) {
|
|
3135
|
-
const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
|
|
3136
|
-
yield { token: processed, position, surface: surface === processed ? void 0 : surface };
|
|
3137
|
-
} else {
|
|
3138
|
-
yield { token: processed, position };
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
position++;
|
|
3142
|
-
}
|
|
3143
|
-
}
|
|
3144
|
-
|
|
3145
2073
|
// src/core/partition/indexing.ts
|
|
3146
2074
|
function ensureFieldIndex(state, fieldPath, fieldType) {
|
|
3147
2075
|
if ((fieldType === "number" || fieldType === "number[]") && !state.numericIndexes.has(fieldPath)) {
|
|
@@ -3998,14 +2926,27 @@ function partitionFilterMatches(state, filters, schema) {
|
|
|
3998
2926
|
};
|
|
3999
2927
|
}
|
|
4000
2928
|
|
|
2929
|
+
// src/core/partition/posting-columns.ts
|
|
2930
|
+
function postingColumns(list) {
|
|
2931
|
+
const deletedDocs = list.deletedDocs;
|
|
2932
|
+
return {
|
|
2933
|
+
docIds: list.docIds,
|
|
2934
|
+
termFrequencies: list.termFrequencies,
|
|
2935
|
+
fieldNameIndices: list.fieldNameIndices,
|
|
2936
|
+
deletedDocs,
|
|
2937
|
+
hasDeleted: deletedDocs.size > 0,
|
|
2938
|
+
count: list.length
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
|
|
4001
2942
|
// src/core/partition/matches.ts
|
|
4002
2943
|
function markPostingList(matched, list, fieldNames, fields, filterBitset) {
|
|
4003
|
-
const hasDeleted = list
|
|
4004
|
-
for (let pi = 0; pi <
|
|
4005
|
-
const internalId =
|
|
4006
|
-
if (hasDeleted &&
|
|
2944
|
+
const { docIds, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(list);
|
|
2945
|
+
for (let pi = 0; pi < count; pi++) {
|
|
2946
|
+
const internalId = docIds[pi];
|
|
2947
|
+
if (hasDeleted && deletedDocs.has(internalId)) continue;
|
|
4007
2948
|
if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
|
|
4008
|
-
if (fields && !fields.includes(fieldNames[
|
|
2949
|
+
if (fields && !fields.includes(fieldNames[fieldNameIndices[pi]])) continue;
|
|
4009
2950
|
bitsetSet(matched, internalId);
|
|
4010
2951
|
}
|
|
4011
2952
|
}
|
|
@@ -4076,6 +3017,12 @@ function searchFulltextMatches(state, params) {
|
|
|
4076
3017
|
};
|
|
4077
3018
|
}
|
|
4078
3019
|
|
|
3020
|
+
// src/core/partition/constants.ts
|
|
3021
|
+
var TARGET_BLOCK_ENTRIES = 128;
|
|
3022
|
+
var SCORE_BUFFER_INITIAL_TOUCHED_CAPACITY = 1024;
|
|
3023
|
+
var SORT_COLUMN_MINIMUM_REBUILD_THRESHOLD = 1024;
|
|
3024
|
+
var SORT_COLUMN_REBUILD_FRACTION_SHIFT = 2;
|
|
3025
|
+
|
|
4079
3026
|
// src/core/partition/sort-columns/order.ts
|
|
4080
3027
|
var MISSING_RANK = -1;
|
|
4081
3028
|
var BYTES_PER_RANK = 4;
|
|
@@ -4301,8 +3248,6 @@ function createValueStore(initialKind) {
|
|
|
4301
3248
|
}
|
|
4302
3249
|
|
|
4303
3250
|
// src/core/partition/sort-columns/index.ts
|
|
4304
|
-
var MINIMUM_REBUILD_THRESHOLD = 1024;
|
|
4305
|
-
var REBUILD_FRACTION_SHIFT = 2;
|
|
4306
3251
|
function liveInternalIds(docStore) {
|
|
4307
3252
|
const ids = [];
|
|
4308
3253
|
for (const internalId of docStore.allInternalIds()) ids.push(internalId);
|
|
@@ -4312,8 +3257,8 @@ function createSortColumnSet(docStore) {
|
|
|
4312
3257
|
const columns = /* @__PURE__ */ new Map();
|
|
4313
3258
|
function rebuildThreshold() {
|
|
4314
3259
|
const count = docStore.count();
|
|
4315
|
-
const fraction = count >>
|
|
4316
|
-
return fraction >
|
|
3260
|
+
const fraction = count >> SORT_COLUMN_REBUILD_FRACTION_SHIFT;
|
|
3261
|
+
return fraction > SORT_COLUMN_MINIMUM_REBUILD_THRESHOLD ? fraction : SORT_COLUMN_MINIMUM_REBUILD_THRESHOLD;
|
|
4317
3262
|
}
|
|
4318
3263
|
function rebuild(entry) {
|
|
4319
3264
|
entry.order = buildOrder(entry.store, docStore.allInternalIds(), docStore.internalIdCapacity());
|
|
@@ -4942,13 +3887,12 @@ function sortSelection(heap) {
|
|
|
4942
3887
|
}
|
|
4943
3888
|
|
|
4944
3889
|
// src/core/partition/score-buffer.ts
|
|
4945
|
-
var INITIAL_TOUCHED_CAPACITY = 1024;
|
|
4946
3890
|
var MAX_GENERATION = 2147483647;
|
|
4947
3891
|
function createScoreBuffer(capacity) {
|
|
4948
3892
|
return {
|
|
4949
3893
|
scores: new Float64Array(capacity),
|
|
4950
3894
|
stamps: new Int32Array(capacity),
|
|
4951
|
-
touched: new Int32Array(Math.max(1, Math.min(capacity,
|
|
3895
|
+
touched: new Int32Array(Math.max(1, Math.min(capacity, SCORE_BUFFER_INITIAL_TOUCHED_CAPACITY))),
|
|
4952
3896
|
touchedCount: 0,
|
|
4953
3897
|
generation: 0
|
|
4954
3898
|
};
|
|
@@ -5033,7 +3977,6 @@ function topKFromBuffer(buffer, k, resolver, components) {
|
|
|
5033
3977
|
}
|
|
5034
3978
|
|
|
5035
3979
|
// src/core/partition/block-bounds.ts
|
|
5036
|
-
var TARGET_BLOCK_ENTRIES = 128;
|
|
5037
3980
|
var ABSENT_FIELD_LENGTH_BOUND = 0;
|
|
5038
3981
|
var boundsByList = /* @__PURE__ */ new WeakMap();
|
|
5039
3982
|
function fieldLengthBound(columns, fieldIndex, internalId) {
|
|
@@ -5142,12 +4085,27 @@ function bestSearchable(searchable, values) {
|
|
|
5142
4085
|
}
|
|
5143
4086
|
return best;
|
|
5144
4087
|
}
|
|
4088
|
+
function prunableSingleTermList(params, index) {
|
|
4089
|
+
if (params.queryTokens.length !== 1) return null;
|
|
4090
|
+
if (params.prefixExpansion !== void 0) return null;
|
|
4091
|
+
if (params.exact !== true && (params.tolerance ?? 0) !== 0) return null;
|
|
4092
|
+
if (params.termMatch !== void 0 && params.termMatch !== "any") return null;
|
|
4093
|
+
if (params.collectComponents !== false) return null;
|
|
4094
|
+
if (params.collectMatchedSet !== void 0) return null;
|
|
4095
|
+
if (params.maxResults === void 0) return null;
|
|
4096
|
+
if (params.fields !== void 0) return null;
|
|
4097
|
+
if (params.filterBitset !== void 0) return null;
|
|
4098
|
+
if (!bm25PruningSound(params.bm25Params)) return null;
|
|
4099
|
+
const list = index.lookup(params.queryTokens[0].token);
|
|
4100
|
+
if (list === void 0) return null;
|
|
4101
|
+
if (!list.ordered) return null;
|
|
4102
|
+
return list;
|
|
4103
|
+
}
|
|
5145
4104
|
function singleTermTopK(request) {
|
|
5146
4105
|
const { list, docFrequency, totalDocs, bm25Params, limit, resolver } = request;
|
|
5147
4106
|
const { fieldSearchable, fieldBoosts, fieldAvgLengths, fieldLengthColumns } = request;
|
|
5148
4107
|
const wanted = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
|
|
5149
|
-
const deleted = list
|
|
5150
|
-
const hasDeleted = deleted.size > 0;
|
|
4108
|
+
const { docIds, termFrequencies, fieldNameIndices, deletedDocs: deleted, hasDeleted } = postingColumns(list);
|
|
5151
4109
|
const bounds = blockBoundsFor(list, fieldLengthColumns);
|
|
5152
4110
|
const maxBoost = bestSearchable(fieldSearchable, fieldBoosts);
|
|
5153
4111
|
const maxAverageLength = bestSearchable(fieldSearchable, fieldAvgLengths);
|
|
@@ -5174,19 +4132,19 @@ function singleTermTopK(request) {
|
|
|
5174
4132
|
}
|
|
5175
4133
|
let entry = start;
|
|
5176
4134
|
while (entry < end) {
|
|
5177
|
-
const internalId =
|
|
4135
|
+
const internalId = docIds[entry];
|
|
5178
4136
|
if (hasDeleted && deleted.has(internalId)) {
|
|
5179
|
-
while (entry < end &&
|
|
4137
|
+
while (entry < end && docIds[entry] === internalId) entry++;
|
|
5180
4138
|
continue;
|
|
5181
4139
|
}
|
|
5182
4140
|
let score = 0;
|
|
5183
4141
|
let scored2 = false;
|
|
5184
|
-
while (entry < end &&
|
|
5185
|
-
const fieldIndex =
|
|
4142
|
+
while (entry < end && docIds[entry] === internalId) {
|
|
4143
|
+
const fieldIndex = fieldNameIndices[entry];
|
|
5186
4144
|
if (fieldSearchable[fieldIndex] === 1) {
|
|
5187
4145
|
const fieldLength = fieldLengthOf(fieldLengthColumns, fieldIndex, internalId, fieldAvgLengths[fieldIndex]);
|
|
5188
4146
|
score += computeBM25(
|
|
5189
|
-
|
|
4147
|
+
termFrequencies[entry],
|
|
5190
4148
|
docFrequency,
|
|
5191
4149
|
totalDocs,
|
|
5192
4150
|
fieldLength,
|
|
@@ -5238,21 +4196,10 @@ function singleTermTopK(request) {
|
|
|
5238
4196
|
function globalDocFreqFor(docFreqs, term, fallback) {
|
|
5239
4197
|
return Object.hasOwn(docFreqs, term) ? docFreqs[term] : fallback;
|
|
5240
4198
|
}
|
|
5241
|
-
function
|
|
5242
|
-
if (
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
if (params.termMatch !== void 0 && params.termMatch !== "any") return null;
|
|
5246
|
-
if (params.collectComponents !== false) return null;
|
|
5247
|
-
if (params.collectMatchedSet !== void 0) return null;
|
|
5248
|
-
if (params.maxResults === void 0) return null;
|
|
5249
|
-
if (params.fields !== void 0) return null;
|
|
5250
|
-
if (params.filterBitset !== void 0) return null;
|
|
5251
|
-
if (!bm25PruningSound(params.bm25Params)) return null;
|
|
5252
|
-
const list = index.lookup(params.queryTokens[0].token);
|
|
5253
|
-
if (list === void 0) return null;
|
|
5254
|
-
if (!list.ordered) return null;
|
|
5255
|
-
return list;
|
|
4199
|
+
function matchesFor(index, token, exact, tolerance, prefixLength) {
|
|
4200
|
+
if (!exact) return index.fuzzyLookup(token, tolerance, prefixLength);
|
|
4201
|
+
const postingList = index.lookup(token);
|
|
4202
|
+
return postingList ? [{ token, postingList }] : [];
|
|
5256
4203
|
}
|
|
5257
4204
|
function searchFulltext(state, params) {
|
|
5258
4205
|
const {
|
|
@@ -5333,17 +4280,18 @@ function searchFulltext(state, params) {
|
|
|
5333
4280
|
const best = /* @__PURE__ */ new Map();
|
|
5334
4281
|
for (const match of matches) {
|
|
5335
4282
|
const perTerm = /* @__PURE__ */ new Map();
|
|
5336
|
-
const
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
4283
|
+
const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
|
|
4284
|
+
match.postingList
|
|
4285
|
+
);
|
|
4286
|
+
for (let pi = 0; pi < count; pi++) {
|
|
4287
|
+
const internalId = docIds[pi];
|
|
4288
|
+
if (hasDeleted && deletedDocs.has(internalId)) continue;
|
|
5341
4289
|
if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
|
|
5342
|
-
const fieldIndex =
|
|
4290
|
+
const fieldIndex = fieldNameIndices[pi];
|
|
5343
4291
|
loadFieldMeta(fieldIndex);
|
|
5344
4292
|
if (fieldSearchable[fieldIndex] === 0) continue;
|
|
5345
4293
|
const fieldName = fieldNames[fieldIndex];
|
|
5346
|
-
const termFrequency =
|
|
4294
|
+
const termFrequency = termFrequencies[pi];
|
|
5347
4295
|
const fieldBoost = fieldBoosts[fieldIndex];
|
|
5348
4296
|
const avgLen = fieldAvgLengths[fieldIndex];
|
|
5349
4297
|
const actualFieldLength = resolveFieldLength(internalId, fieldIndex, avgLen);
|
|
@@ -5415,10 +4363,7 @@ function searchFulltext(state, params) {
|
|
|
5415
4363
|
resolved.push({ token: qt.token, matches: [], totalPostings: totalPostings2, isPrefix: true });
|
|
5416
4364
|
continue;
|
|
5417
4365
|
}
|
|
5418
|
-
const rawMatches = exact
|
|
5419
|
-
const postingList = state.invertedIdx.lookup(qt.token);
|
|
5420
|
-
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
5421
|
-
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
4366
|
+
const rawMatches = matchesFor(state.invertedIdx, qt.token, exact, tolerance, prefixLength);
|
|
5422
4367
|
let totalPostings = 0;
|
|
5423
4368
|
const matches = [];
|
|
5424
4369
|
for (const m of rawMatches) {
|
|
@@ -5440,17 +4385,18 @@ function searchFulltext(state, params) {
|
|
|
5440
4385
|
continue;
|
|
5441
4386
|
}
|
|
5442
4387
|
for (const match of resolved[tokenIndex].matches) {
|
|
5443
|
-
const
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
4388
|
+
const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
|
|
4389
|
+
match.postingList
|
|
4390
|
+
);
|
|
4391
|
+
for (let pi = 0; pi < count; pi++) {
|
|
4392
|
+
const internalId = docIds[pi];
|
|
4393
|
+
if (hasDeleted && deletedDocs.has(internalId)) continue;
|
|
5448
4394
|
if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
|
|
5449
4395
|
if (tokenIndex > 0 && !hasScore(scoreBuffer, internalId)) continue;
|
|
5450
|
-
const fieldIndex =
|
|
4396
|
+
const fieldIndex = fieldNameIndices[pi];
|
|
5451
4397
|
loadFieldMeta(fieldIndex);
|
|
5452
4398
|
if (fieldSearchable[fieldIndex] === 0) continue;
|
|
5453
|
-
const termFrequency =
|
|
4399
|
+
const termFrequency = termFrequencies[pi];
|
|
5454
4400
|
const fieldBoost = fieldBoosts[fieldIndex];
|
|
5455
4401
|
const avgLen = fieldAvgLengths[fieldIndex];
|
|
5456
4402
|
const actualFieldLength = resolveFieldLength(internalId, fieldIndex, avgLen);
|
|
@@ -5483,23 +4429,21 @@ function searchFulltext(state, params) {
|
|
|
5483
4429
|
}
|
|
5484
4430
|
continue;
|
|
5485
4431
|
}
|
|
5486
|
-
const matchingPostings = exact
|
|
5487
|
-
const postingList = state.invertedIdx.lookup(qt.token);
|
|
5488
|
-
return postingList ? [{ token: qt.token, postingList }] : [];
|
|
5489
|
-
})() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
|
|
4432
|
+
const matchingPostings = matchesFor(state.invertedIdx, qt.token, exact, tolerance, prefixLength);
|
|
5490
4433
|
for (const match of matchingPostings) {
|
|
5491
4434
|
const docFreq = globalStats ? globalDocFreqFor(globalDocFreqs, match.token, match.postingList.docIdSet.size) : match.postingList.docIdSet.size;
|
|
5492
4435
|
const idf = computeIDF(docFreq, totalDocs);
|
|
5493
|
-
const
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
4436
|
+
const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
|
|
4437
|
+
match.postingList
|
|
4438
|
+
);
|
|
4439
|
+
for (let pi = 0; pi < count; pi++) {
|
|
4440
|
+
const internalId = docIds[pi];
|
|
4441
|
+
if (hasDeleted && deletedDocs.has(internalId)) continue;
|
|
5498
4442
|
if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
|
|
5499
|
-
const fieldIndex =
|
|
4443
|
+
const fieldIndex = fieldNameIndices[pi];
|
|
5500
4444
|
loadFieldMeta(fieldIndex);
|
|
5501
4445
|
if (fieldSearchable[fieldIndex] === 0) continue;
|
|
5502
|
-
const termFrequency =
|
|
4446
|
+
const termFrequency = termFrequencies[pi];
|
|
5503
4447
|
const fieldBoost = fieldBoosts[fieldIndex];
|
|
5504
4448
|
const avgLen = fieldAvgLengths[fieldIndex];
|
|
5505
4449
|
const actualFieldLength = resolveFieldLength(internalId, fieldIndex, avgLen);
|
|
@@ -6412,32 +5356,21 @@ function createPartitionIndex(partitionId, trackPositions = true) {
|
|
|
6412
5356
|
return partition;
|
|
6413
5357
|
}
|
|
6414
5358
|
|
|
6415
|
-
// src/
|
|
6416
|
-
function
|
|
6417
|
-
const
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
}
|
|
6431
|
-
}
|
|
6432
|
-
const result = {};
|
|
6433
|
-
for (const [field, valueMap] of merged) {
|
|
6434
|
-
const values = {};
|
|
6435
|
-
for (const [value, count] of valueMap) {
|
|
6436
|
-
values[value] = count;
|
|
6437
|
-
}
|
|
6438
|
-
result[field] = { values, count: valueMap.size, errorBound: bounds.get(field) ?? 0 };
|
|
6439
|
-
}
|
|
6440
|
-
return result;
|
|
5359
|
+
// src/core/id-generator.ts
|
|
5360
|
+
function generateId() {
|
|
5361
|
+
const bytes = new Uint8Array(16);
|
|
5362
|
+
crypto.getRandomValues(bytes);
|
|
5363
|
+
const now = Date.now();
|
|
5364
|
+
bytes[0] = Math.floor(now / 2 ** 40);
|
|
5365
|
+
bytes[1] = Math.floor(now / 2 ** 32) % 256;
|
|
5366
|
+
bytes[2] = Math.floor(now / 2 ** 24) % 256;
|
|
5367
|
+
bytes[3] = Math.floor(now / 2 ** 16) % 256;
|
|
5368
|
+
bytes[4] = Math.floor(now / 2 ** 8) % 256;
|
|
5369
|
+
bytes[5] = now % 256;
|
|
5370
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
5371
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
5372
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
5373
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
6441
5374
|
}
|
|
6442
5375
|
|
|
6443
5376
|
// src/core/partition/frozen/doc-table.ts
|
|
@@ -7147,6 +6080,9 @@ function createFrozenTombstones() {
|
|
|
7147
6080
|
removed.add(internalId);
|
|
7148
6081
|
revision++;
|
|
7149
6082
|
return true;
|
|
6083
|
+
},
|
|
6084
|
+
values() {
|
|
6085
|
+
return removed.values();
|
|
7150
6086
|
}
|
|
7151
6087
|
};
|
|
7152
6088
|
}
|
|
@@ -7270,7 +6206,7 @@ function buildGeoReaders(entries) {
|
|
|
7270
6206
|
}
|
|
7271
6207
|
return readers;
|
|
7272
6208
|
}
|
|
7273
|
-
function assembleFrozenSegment(segmentId, source, documentSource) {
|
|
6209
|
+
function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot) {
|
|
7274
6210
|
const tombstones = createFrozenTombstones();
|
|
7275
6211
|
const postingViews = createFrozenPostingViews(source, tombstones);
|
|
7276
6212
|
const lengthColumns = /* @__PURE__ */ new Map();
|
|
@@ -7291,6 +6227,7 @@ function assembleFrozenSegment(segmentId, source, documentSource) {
|
|
|
7291
6227
|
const segment = {
|
|
7292
6228
|
segmentId,
|
|
7293
6229
|
documentSource,
|
|
6230
|
+
sharedSnapshot,
|
|
7294
6231
|
invertedIdx: createFrozenInvertedReader(source.tokenTable, postingViews),
|
|
7295
6232
|
docStore,
|
|
7296
6233
|
stats: buildStatsView(source),
|
|
@@ -7321,6 +6258,11 @@ function assembleFrozenSegment(segmentId, source, documentSource) {
|
|
|
7321
6258
|
segment.scoreBuffer = null;
|
|
7322
6259
|
}
|
|
7323
6260
|
return added;
|
|
6261
|
+
},
|
|
6262
|
+
tombstonedDocIds() {
|
|
6263
|
+
const docIds = [];
|
|
6264
|
+
for (const ordinal of tombstones.values()) docIds.push(source.idTable.idAt(ordinal));
|
|
6265
|
+
return docIds;
|
|
7324
6266
|
}
|
|
7325
6267
|
};
|
|
7326
6268
|
return segment;
|
|
@@ -7334,7 +6276,8 @@ function createFrozenSegment(payload, documents, segmentId) {
|
|
|
7334
6276
|
idTable: buildExternalIdTable(payload.docIds),
|
|
7335
6277
|
docFrequencies: () => payload.docFrequencies
|
|
7336
6278
|
},
|
|
7337
|
-
wrapDocumentArray(documents)
|
|
6279
|
+
wrapDocumentArray(documents),
|
|
6280
|
+
null
|
|
7338
6281
|
);
|
|
7339
6282
|
}
|
|
7340
6283
|
function createSharedFrozenSegment(snapshot) {
|
|
@@ -7353,7 +6296,8 @@ function createSharedFrozenSegment(snapshot) {
|
|
|
7353
6296
|
return frequencies;
|
|
7354
6297
|
}
|
|
7355
6298
|
},
|
|
7356
|
-
wrapEncodedDocumentTable(snapshot.documentTable)
|
|
6299
|
+
wrapEncodedDocumentTable(snapshot.documentTable),
|
|
6300
|
+
snapshot
|
|
7357
6301
|
);
|
|
7358
6302
|
}
|
|
7359
6303
|
|
|
@@ -7395,6 +6339,15 @@ function buildCompactedSegmentPayload(segments) {
|
|
|
7395
6339
|
}
|
|
7396
6340
|
return { payload: scratch.encodeSegment(), documents };
|
|
7397
6341
|
}
|
|
6342
|
+
function freezeLiveTailInto(live, liveState, frozen, freeze) {
|
|
6343
|
+
if (live.count() === 0) return null;
|
|
6344
|
+
const payload = live.encodeSegment();
|
|
6345
|
+
const segment = freeze(payload, survivorDocumentsOf(liveState, payload));
|
|
6346
|
+
if (segment === null) return null;
|
|
6347
|
+
live.clear();
|
|
6348
|
+
frozen.push(segment);
|
|
6349
|
+
return segment;
|
|
6350
|
+
}
|
|
7398
6351
|
function swapFrozenSegmentList(frozen, dropSegmentIds, replacement, partitionId) {
|
|
7399
6352
|
const dropped = resolveFrozenSegments(frozen, dropSegmentIds, partitionId);
|
|
7400
6353
|
const dropSet = new Set(dropSegmentIds);
|
|
@@ -7434,11 +6387,15 @@ function subBitsetView(composite, layout, subIndex) {
|
|
|
7434
6387
|
const endWord = subIndex + 1 < layout.bases.length ? layout.bases[subIndex + 1] >> 5 : Math.ceil(layout.totalCapacity / WORD_BITS);
|
|
7435
6388
|
return composite.subarray(startWord, endWord);
|
|
7436
6389
|
}
|
|
6390
|
+
function placeSubBitset(composite, layout, subIndex, subBits) {
|
|
6391
|
+
const words = subBitsetView(composite, layout, subIndex).length;
|
|
6392
|
+
if (words === 0) return;
|
|
6393
|
+
composite.set(subBits.length > words ? subBits.subarray(0, words) : subBits, layout.bases[subIndex] >> 5);
|
|
6394
|
+
}
|
|
7437
6395
|
function compositeFiltersBitset(subs, layout, filters, schema) {
|
|
7438
6396
|
const composite = new Uint32Array(Math.ceil(layout.totalCapacity / WORD_BITS));
|
|
7439
6397
|
for (let i = 0; i < subs.length; i++) {
|
|
7440
|
-
|
|
7441
|
-
composite.set(subBits, layout.bases[i] >> 5);
|
|
6398
|
+
placeSubBitset(composite, layout, i, applyPartitionFiltersBitset(subs[i], filters, schema));
|
|
7442
6399
|
}
|
|
7443
6400
|
return composite;
|
|
7444
6401
|
}
|
|
@@ -7491,123 +6448,6 @@ function subMatchesView(matches, layout, subIndex) {
|
|
|
7491
6448
|
};
|
|
7492
6449
|
}
|
|
7493
6450
|
|
|
7494
|
-
// src/core/partition/scored-merge.ts
|
|
7495
|
-
function kWayMerge(arrays) {
|
|
7496
|
-
const nonEmpty = arrays.filter((a) => a.length > 0);
|
|
7497
|
-
if (nonEmpty.length === 0) return [];
|
|
7498
|
-
if (nonEmpty.length === 1) return nonEmpty[0];
|
|
7499
|
-
if (nonEmpty.length <= 4) {
|
|
7500
|
-
return sequentialMerge(nonEmpty);
|
|
7501
|
-
}
|
|
7502
|
-
return heapMerge(nonEmpty);
|
|
7503
|
-
}
|
|
7504
|
-
function sequentialMerge(arrays) {
|
|
7505
|
-
let merged = arrays[0];
|
|
7506
|
-
for (let i = 1; i < arrays.length; i++) {
|
|
7507
|
-
merged = mergeTwoSorted(merged, arrays[i]);
|
|
7508
|
-
}
|
|
7509
|
-
return merged;
|
|
7510
|
-
}
|
|
7511
|
-
function mergeTwoSorted(a, b) {
|
|
7512
|
-
const result = new Array(a.length + b.length);
|
|
7513
|
-
let ai = 0;
|
|
7514
|
-
let bi = 0;
|
|
7515
|
-
let ri = 0;
|
|
7516
|
-
while (ai < a.length && bi < b.length) {
|
|
7517
|
-
if (a[ai].score > b[bi].score || a[ai].score === b[bi].score && compareCodePoints(a[ai].docId, b[bi].docId) <= 0) {
|
|
7518
|
-
result[ri++] = a[ai++];
|
|
7519
|
-
} else {
|
|
7520
|
-
result[ri++] = b[bi++];
|
|
7521
|
-
}
|
|
7522
|
-
}
|
|
7523
|
-
while (ai < a.length) {
|
|
7524
|
-
result[ri++] = a[ai++];
|
|
7525
|
-
}
|
|
7526
|
-
while (bi < b.length) {
|
|
7527
|
-
result[ri++] = b[bi++];
|
|
7528
|
-
}
|
|
7529
|
-
return result;
|
|
7530
|
-
}
|
|
7531
|
-
function heapMerge(arrays) {
|
|
7532
|
-
const heap = [];
|
|
7533
|
-
let totalSize = 0;
|
|
7534
|
-
for (let i = 0; i < arrays.length; i++) {
|
|
7535
|
-
if (arrays[i].length > 0) {
|
|
7536
|
-
totalSize += arrays[i].length;
|
|
7537
|
-
heapPush(heap, {
|
|
7538
|
-
score: arrays[i][0].score,
|
|
7539
|
-
docId: arrays[i][0].docId,
|
|
7540
|
-
partitionIdx: i,
|
|
7541
|
-
resultIdx: 0
|
|
7542
|
-
});
|
|
7543
|
-
}
|
|
7544
|
-
}
|
|
7545
|
-
const result = new Array(totalSize);
|
|
7546
|
-
let writeIdx = 0;
|
|
7547
|
-
while (heap.length > 0) {
|
|
7548
|
-
const top = heapPop(heap);
|
|
7549
|
-
result[writeIdx++] = arrays[top.partitionIdx][top.resultIdx];
|
|
7550
|
-
const nextIdx = top.resultIdx + 1;
|
|
7551
|
-
if (nextIdx < arrays[top.partitionIdx].length) {
|
|
7552
|
-
const nextDoc = arrays[top.partitionIdx][nextIdx];
|
|
7553
|
-
heapPush(heap, {
|
|
7554
|
-
score: nextDoc.score,
|
|
7555
|
-
docId: nextDoc.docId,
|
|
7556
|
-
partitionIdx: top.partitionIdx,
|
|
7557
|
-
resultIdx: nextIdx
|
|
7558
|
-
});
|
|
7559
|
-
}
|
|
7560
|
-
}
|
|
7561
|
-
return result;
|
|
7562
|
-
}
|
|
7563
|
-
function heapNodeGreater(a, b) {
|
|
7564
|
-
if (a.score !== b.score) return a.score > b.score;
|
|
7565
|
-
return compareCodePoints(a.docId, b.docId) < 0;
|
|
7566
|
-
}
|
|
7567
|
-
function heapPush(heap, node) {
|
|
7568
|
-
heap.push(node);
|
|
7569
|
-
let idx = heap.length - 1;
|
|
7570
|
-
while (idx > 0) {
|
|
7571
|
-
const parentIdx = idx - 1 >> 1;
|
|
7572
|
-
if (heapNodeGreater(heap[idx], heap[parentIdx])) {
|
|
7573
|
-
const tmp = heap[idx];
|
|
7574
|
-
heap[idx] = heap[parentIdx];
|
|
7575
|
-
heap[parentIdx] = tmp;
|
|
7576
|
-
idx = parentIdx;
|
|
7577
|
-
} else {
|
|
7578
|
-
break;
|
|
7579
|
-
}
|
|
7580
|
-
}
|
|
7581
|
-
}
|
|
7582
|
-
function heapPop(heap) {
|
|
7583
|
-
const top = heap[0];
|
|
7584
|
-
const last = heap.pop();
|
|
7585
|
-
if (heap.length > 0) {
|
|
7586
|
-
heap[0] = last;
|
|
7587
|
-
let idx = 0;
|
|
7588
|
-
while (true) {
|
|
7589
|
-
const left = 2 * idx + 1;
|
|
7590
|
-
const right = 2 * idx + 2;
|
|
7591
|
-
let largest = idx;
|
|
7592
|
-
if (left < heap.length && heapNodeGreater(heap[left], heap[largest])) {
|
|
7593
|
-
largest = left;
|
|
7594
|
-
}
|
|
7595
|
-
if (right < heap.length && heapNodeGreater(heap[right], heap[largest])) {
|
|
7596
|
-
largest = right;
|
|
7597
|
-
}
|
|
7598
|
-
if (largest !== idx) {
|
|
7599
|
-
const tmp = heap[idx];
|
|
7600
|
-
heap[idx] = heap[largest];
|
|
7601
|
-
heap[largest] = tmp;
|
|
7602
|
-
idx = largest;
|
|
7603
|
-
} else {
|
|
7604
|
-
break;
|
|
7605
|
-
}
|
|
7606
|
-
}
|
|
7607
|
-
}
|
|
7608
|
-
return top;
|
|
7609
|
-
}
|
|
7610
|
-
|
|
7611
6451
|
// src/core/partition/composite/stats.ts
|
|
7612
6452
|
function collectCandidateTerms(subs, params) {
|
|
7613
6453
|
const { queryTokens, prefixExpansion, tolerance = 0, prefixLength = 2, exact = false } = params;
|
|
@@ -7700,10 +6540,30 @@ function subParams(params, layout, subIndex, globalStats) {
|
|
|
7700
6540
|
filterBitset: params.filterBitset === void 0 ? void 0 : subBitsetView(params.filterBitset, layout, subIndex)
|
|
7701
6541
|
};
|
|
7702
6542
|
}
|
|
6543
|
+
function onlyPopulatedSub(subs) {
|
|
6544
|
+
let found = -1;
|
|
6545
|
+
for (let i = 0; i < subs.length; i++) {
|
|
6546
|
+
if (subs[i].stats.totalDocuments === 0) continue;
|
|
6547
|
+
if (found !== -1) return -1;
|
|
6548
|
+
found = i;
|
|
6549
|
+
}
|
|
6550
|
+
return found;
|
|
6551
|
+
}
|
|
6552
|
+
function searchSingleSub(subs, layout, params, index) {
|
|
6553
|
+
const result = searchFulltext(subs[index], subParams(params, layout, index, params.globalStats));
|
|
6554
|
+
if (result.matchedOrdinalBitset === void 0) return result;
|
|
6555
|
+
const matchedOrdinalBitset = createBitSet(layout.totalCapacity);
|
|
6556
|
+
placeSubBitset(matchedOrdinalBitset, layout, index, result.matchedOrdinalBitset);
|
|
6557
|
+
return { ...result, matchedOrdinalBitset };
|
|
6558
|
+
}
|
|
7703
6559
|
function compositeSearchFulltext(subs, layout, params) {
|
|
7704
6560
|
if (params.queryTokens.length === 0) {
|
|
7705
6561
|
return { scored: [], totalMatched: 0 };
|
|
7706
6562
|
}
|
|
6563
|
+
const single = onlyPopulatedSub(subs);
|
|
6564
|
+
if (single !== -1) {
|
|
6565
|
+
return searchSingleSub(subs, layout, params, single);
|
|
6566
|
+
}
|
|
7707
6567
|
const globalStats = compositeQueryStats(subs, params);
|
|
7708
6568
|
const results = subs.map((sub, index) => searchFulltext(sub, subParams(params, layout, index, globalStats)));
|
|
7709
6569
|
const scored = kWayMerge(results.map((result) => result.scored));
|
|
@@ -7721,7 +6581,7 @@ function compositeSearchFulltext(subs, layout, params) {
|
|
|
7721
6581
|
const matchedOrdinalBitset = createBitSet(layout.totalCapacity);
|
|
7722
6582
|
for (let i = 0; i < results.length; i++) {
|
|
7723
6583
|
const subBitset = results[i].matchedOrdinalBitset;
|
|
7724
|
-
if (subBitset !== void 0) matchedOrdinalBitset
|
|
6584
|
+
if (subBitset !== void 0) placeSubBitset(matchedOrdinalBitset, layout, i, subBitset);
|
|
7725
6585
|
}
|
|
7726
6586
|
return { scored, totalMatched, matchedOrdinalBitset };
|
|
7727
6587
|
}
|
|
@@ -7765,7 +6625,7 @@ function compositeSearchMatches(subs, layout, params) {
|
|
|
7765
6625
|
ordinalBitset() {
|
|
7766
6626
|
const combined = createBitSet(layout.totalCapacity);
|
|
7767
6627
|
for (let i = 0; i < subMatches.length; i++) {
|
|
7768
|
-
combined
|
|
6628
|
+
placeSubBitset(combined, layout, i, subMatches[i].ordinalBitset());
|
|
7769
6629
|
}
|
|
7770
6630
|
return combined;
|
|
7771
6631
|
}
|
|
@@ -7848,17 +6708,6 @@ function compositeExpandTermPrefix(subs, surfacePrefix, stemmedToken, maxExpansi
|
|
|
7848
6708
|
function isCompositePartition(partition) {
|
|
7849
6709
|
return "attachFrozenSegment" in partition;
|
|
7850
6710
|
}
|
|
7851
|
-
function survivorDocuments(sub, payload) {
|
|
7852
|
-
return payload.docIds.map((docId) => {
|
|
7853
|
-
const stored = sub.docStore.get(docId);
|
|
7854
|
-
if (stored === void 0) {
|
|
7855
|
-
throw new NarsilError(ErrorCodes.PARTITION_CORRUPTED, `Document "${docId}" vanished while composing a segment`, {
|
|
7856
|
-
docId
|
|
7857
|
-
});
|
|
7858
|
-
}
|
|
7859
|
-
return stored.fields;
|
|
7860
|
-
});
|
|
7861
|
-
}
|
|
7862
6711
|
function createCompositePartition(partitionId, trackPositions = true, existingLive) {
|
|
7863
6712
|
const live = existingLive ?? createPartitionIndex(partitionId, trackPositions);
|
|
7864
6713
|
const liveState = partitionStateOf(live);
|
|
@@ -7881,7 +6730,7 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
|
|
|
7881
6730
|
for (const sub of subs()) {
|
|
7882
6731
|
const payload = encodeSegmentState(sub);
|
|
7883
6732
|
if (payload.documentCount === 0) continue;
|
|
7884
|
-
scratch.mergeSegmentPayload(payload,
|
|
6733
|
+
scratch.mergeSegmentPayload(payload, survivorDocumentsOf(sub, payload));
|
|
7885
6734
|
}
|
|
7886
6735
|
return scratch;
|
|
7887
6736
|
}
|
|
@@ -7917,6 +6766,16 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
|
|
|
7917
6766
|
swapFrozenSegmentList(frozen, dropSegmentIds, replacement, partitionId);
|
|
7918
6767
|
invalidateDocFrequencies();
|
|
7919
6768
|
},
|
|
6769
|
+
freezeLiveTail(freeze) {
|
|
6770
|
+
const segment = freezeLiveTailInto(live, liveState, frozen, freeze);
|
|
6771
|
+
if (segment !== null) invalidateDocFrequencies();
|
|
6772
|
+
return segment;
|
|
6773
|
+
},
|
|
6774
|
+
replaceLiveTail(segment) {
|
|
6775
|
+
live.clear();
|
|
6776
|
+
frozen.push(segment);
|
|
6777
|
+
invalidateDocFrequencies();
|
|
6778
|
+
},
|
|
7920
6779
|
appendFrozenSegment(payload, documents) {
|
|
7921
6780
|
for (const docId of payload.docIds) {
|
|
7922
6781
|
if (live.has(docId) || frozenOwner(docId) !== void 0) {
|
|
@@ -7961,7 +6820,7 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
|
|
|
7961
6820
|
for (const segment of frozen) {
|
|
7962
6821
|
const payload = encodeSegmentState(segment);
|
|
7963
6822
|
if (payload.documentCount === 0) continue;
|
|
7964
|
-
live.mergeSegmentPayload(payload,
|
|
6823
|
+
live.mergeSegmentPayload(payload, survivorDocumentsOf(segment, payload));
|
|
7965
6824
|
}
|
|
7966
6825
|
frozen.length = 0;
|
|
7967
6826
|
}
|
|
@@ -8120,792 +6979,30 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
|
|
|
8120
6979
|
},
|
|
8121
6980
|
estimateMemoryBytes() {
|
|
8122
6981
|
let total = live.estimateMemoryBytes();
|
|
8123
|
-
for (const segment of frozen) {
|
|
8124
|
-
total += estimatePartitionBytes(segment);
|
|
8125
|
-
}
|
|
8126
|
-
return total;
|
|
8127
|
-
}
|
|
8128
|
-
};
|
|
8129
|
-
}
|
|
8130
|
-
|
|
8131
|
-
// src/languages/registry.ts
|
|
8132
|
-
var languages = /* @__PURE__ */ new Map();
|
|
8133
|
-
languages.set("english", english);
|
|
8134
|
-
function registerLanguage(module) {
|
|
8135
|
-
languages.set(module.name, module);
|
|
8136
|
-
}
|
|
8137
|
-
function getLanguage(name) {
|
|
8138
|
-
const lang = languages.get(name);
|
|
8139
|
-
if (!lang) {
|
|
8140
|
-
throw new NarsilError(ErrorCodes.LANGUAGE_NOT_SUPPORTED, `Language "${name}" is not registered`, { language: name });
|
|
8141
|
-
}
|
|
8142
|
-
return lang;
|
|
8143
|
-
}
|
|
8144
|
-
|
|
8145
|
-
// src/partitioning/distributed-scoring.ts
|
|
8146
|
-
function analysedQueryTokens(term, language, analysis) {
|
|
8147
|
-
const { tokens } = tokenize(term, language, {
|
|
8148
|
-
stem: true,
|
|
8149
|
-
removeStopWords: true,
|
|
8150
|
-
customTokenizer: analysis.customTokenizer,
|
|
8151
|
-
stopWordOverride: analysis.stopWords
|
|
8152
|
-
});
|
|
8153
|
-
const unique = /* @__PURE__ */ new Set();
|
|
8154
|
-
for (const { token } of tokens) {
|
|
8155
|
-
unique.add(token);
|
|
8156
|
-
}
|
|
8157
|
-
return unique;
|
|
8158
|
-
}
|
|
8159
|
-
function copyNumericRecord(source) {
|
|
8160
|
-
const copy = /* @__PURE__ */ Object.create(null);
|
|
8161
|
-
for (const [key, value] of Object.entries(source)) {
|
|
8162
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
8163
|
-
copy[key] = value;
|
|
8164
|
-
}
|
|
8165
|
-
}
|
|
8166
|
-
return copy;
|
|
8167
|
-
}
|
|
8168
|
-
function sanitizeGlobalStats(stats) {
|
|
8169
|
-
return {
|
|
8170
|
-
totalDocuments: typeof stats.totalDocuments === "number" && Number.isFinite(stats.totalDocuments) ? stats.totalDocuments : 0,
|
|
8171
|
-
docFrequencies: copyNumericRecord(stats.docFrequencies),
|
|
8172
|
-
totalFieldLengths: copyNumericRecord(stats.totalFieldLengths),
|
|
8173
|
-
averageFieldLengths: copyNumericRecord(stats.averageFieldLengths)
|
|
8174
|
-
};
|
|
8175
|
-
}
|
|
8176
|
-
function pruneStatsToQueryTerms(stats, term, language, analysis) {
|
|
8177
|
-
const docFrequencies = /* @__PURE__ */ Object.create(null);
|
|
8178
|
-
for (const token of analysedQueryTokens(term, language, analysis)) {
|
|
8179
|
-
if (Object.hasOwn(stats.docFrequencies, token)) {
|
|
8180
|
-
docFrequencies[token] = stats.docFrequencies[token];
|
|
8181
|
-
}
|
|
8182
|
-
}
|
|
8183
|
-
return {
|
|
8184
|
-
totalDocuments: stats.totalDocuments,
|
|
8185
|
-
docFrequencies,
|
|
8186
|
-
totalFieldLengths: stats.totalFieldLengths,
|
|
8187
|
-
averageFieldLengths: stats.averageFieldLengths
|
|
8188
|
-
};
|
|
8189
|
-
}
|
|
8190
|
-
function collectQueryTermStats(manager, term, language, analysis, partitionIds) {
|
|
8191
|
-
const queryTokens = analysedQueryTokens(term, language, analysis);
|
|
8192
|
-
let totalDocuments = 0;
|
|
8193
|
-
const docFrequencies = /* @__PURE__ */ Object.create(null);
|
|
8194
|
-
const totalFieldLengths = /* @__PURE__ */ Object.create(null);
|
|
8195
|
-
const statsPartitions = partitionIds === void 0 ? manager.getAllPartitions() : partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
|
|
8196
|
-
for (const partition of statsPartitions) {
|
|
8197
|
-
const stats = partition.stats;
|
|
8198
|
-
totalDocuments += stats.totalDocuments;
|
|
8199
|
-
for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
|
|
8200
|
-
totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
|
|
8201
|
-
}
|
|
8202
|
-
for (const token of queryTokens) {
|
|
8203
|
-
if (Object.hasOwn(stats.docFrequencies, token)) {
|
|
8204
|
-
docFrequencies[token] = (docFrequencies[token] ?? 0) + stats.docFrequencies[token];
|
|
8205
|
-
}
|
|
8206
|
-
}
|
|
8207
|
-
}
|
|
8208
|
-
const averageFieldLengths = /* @__PURE__ */ Object.create(null);
|
|
8209
|
-
if (totalDocuments > 0) {
|
|
8210
|
-
for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
|
|
8211
|
-
averageFieldLengths[field] = totalLength / totalDocuments;
|
|
8212
|
-
}
|
|
8213
|
-
}
|
|
8214
|
-
return { totalDocuments, docFrequencies, totalFieldLengths, averageFieldLengths };
|
|
8215
|
-
}
|
|
8216
|
-
function mergePartitionStats(statsArray) {
|
|
8217
|
-
let totalDocuments = 0;
|
|
8218
|
-
const docFrequencies = /* @__PURE__ */ Object.create(null);
|
|
8219
|
-
const totalFieldLengths = /* @__PURE__ */ Object.create(null);
|
|
8220
|
-
for (const stats of statsArray) {
|
|
8221
|
-
totalDocuments += stats.totalDocuments;
|
|
8222
|
-
for (const [term, freq] of Object.entries(stats.docFrequencies)) {
|
|
8223
|
-
docFrequencies[term] = (docFrequencies[term] ?? 0) + freq;
|
|
8224
|
-
}
|
|
8225
|
-
for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
|
|
8226
|
-
totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
|
|
8227
|
-
}
|
|
8228
|
-
}
|
|
8229
|
-
const averageFieldLengths = /* @__PURE__ */ Object.create(null);
|
|
8230
|
-
if (totalDocuments > 0) {
|
|
8231
|
-
for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
|
|
8232
|
-
averageFieldLengths[field] = totalLength / totalDocuments;
|
|
8233
|
-
}
|
|
8234
|
-
}
|
|
8235
|
-
return {
|
|
8236
|
-
totalDocuments,
|
|
8237
|
-
docFrequencies,
|
|
8238
|
-
totalFieldLengths,
|
|
8239
|
-
averageFieldLengths
|
|
8240
|
-
};
|
|
8241
|
-
}
|
|
8242
|
-
|
|
8243
|
-
// src/search/cursor-codec.ts
|
|
8244
|
-
function encodeCursorText(text) {
|
|
8245
|
-
if (typeof Buffer !== "undefined") return Buffer.from(text, "utf-8").toString("base64");
|
|
8246
|
-
const bytes = new TextEncoder().encode(text);
|
|
8247
|
-
let binary = "";
|
|
8248
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
8249
|
-
return btoa(binary);
|
|
8250
|
-
}
|
|
8251
|
-
function decodeCursorText(encoded) {
|
|
8252
|
-
if (typeof Buffer !== "undefined") return Buffer.from(encoded, "base64").toString("utf-8");
|
|
8253
|
-
const binary = atob(encoded);
|
|
8254
|
-
const bytes = new Uint8Array(binary.length);
|
|
8255
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
8256
|
-
return new TextDecoder().decode(bytes);
|
|
8257
|
-
}
|
|
8258
|
-
|
|
8259
|
-
// src/search/sorting.ts
|
|
8260
|
-
function isFieldList(sort) {
|
|
8261
|
-
return Array.isArray(sort);
|
|
8262
|
-
}
|
|
8263
|
-
function normalizeSort(sort) {
|
|
8264
|
-
if (sort === void 0) return [];
|
|
8265
|
-
if (isFieldList(sort)) {
|
|
8266
|
-
return sort.map((entry) => ({ field: entry.field, direction: entry.direction }));
|
|
8267
|
-
}
|
|
8268
|
-
return Object.entries(sort).map(([field, direction]) => ({ field, direction }));
|
|
8269
|
-
}
|
|
8270
|
-
function requireSortableFields(sort, schema) {
|
|
8271
|
-
const fields = normalizeSort(sort);
|
|
8272
|
-
if (fields.length === 0) return;
|
|
8273
|
-
const flatSchema = flattenSchema(schema);
|
|
8274
|
-
for (const entry of fields) {
|
|
8275
|
-
if (flatSchema[entry.field] !== "string") continue;
|
|
8276
|
-
throw new NarsilError(
|
|
8277
|
-
ErrorCodes.SEARCH_INVALID_FIELD,
|
|
8278
|
-
`A sort names text field "${entry.field}" only where the schema declares it "${SORTABLE_TEXT_FIELD_TYPE}", because ordering text costs far more memory per document than ordering a number`,
|
|
8279
|
-
{ field: entry.field, fieldType: "string" }
|
|
8280
|
-
);
|
|
8281
|
-
}
|
|
8282
|
-
}
|
|
8283
|
-
function readFieldValue(obj, path) {
|
|
8284
|
-
return readSortField(obj, path);
|
|
8285
|
-
}
|
|
8286
|
-
function readSortValues(document, fields) {
|
|
8287
|
-
if (!document) return fields.map(() => void 0);
|
|
8288
|
-
return fields.map((field) => readFieldValue(document, field));
|
|
8289
|
-
}
|
|
8290
|
-
function applySorting(hits, sort, sortKeyOf) {
|
|
8291
|
-
const normalized = normalizeSort(sort);
|
|
8292
|
-
if (normalized.length === 0) return hits;
|
|
8293
|
-
const directions = normalized.map((entry) => entry.direction);
|
|
8294
|
-
const keyCache = /* @__PURE__ */ new Map();
|
|
8295
|
-
for (const hit of hits) {
|
|
8296
|
-
keyCache.set(hit.id, sortKeyOf(hit.id));
|
|
8297
|
-
}
|
|
8298
|
-
const sorted = hits.slice();
|
|
8299
|
-
sorted.sort(
|
|
8300
|
-
(a, b) => compareComparableKeys(keyCache.get(a.id) ?? [], keyCache.get(b.id) ?? [], directions) || compareCodePoints(a.id, b.id)
|
|
8301
|
-
);
|
|
8302
|
-
return sorted;
|
|
8303
|
-
}
|
|
8304
|
-
|
|
8305
|
-
// src/search/cursor.ts
|
|
8306
|
-
var CURSOR_VERSION = 3;
|
|
8307
|
-
var MAX_CURSOR_LENGTH = 40960;
|
|
8308
|
-
var MAX_SORT_FIELDS = 8;
|
|
8309
|
-
var MAX_SORT_FIELD_NAME_LENGTH = 255;
|
|
8310
|
-
var MAX_ANCHOR_CODE_POINTS = 512;
|
|
8311
|
-
var MAX_SORT_VALUE_CODE_POINTS = 512;
|
|
8312
|
-
function invalidCursor(cursor, reason) {
|
|
8313
|
-
const shown = cursor.length > 100 ? `${cursor.slice(0, 100)}...` : cursor;
|
|
8314
|
-
return new NarsilError(ErrorCodes.SEARCH_INVALID_CURSOR, `Failed to decode cursor: ${reason}`, { cursor: shown });
|
|
8315
|
-
}
|
|
8316
|
-
function exceedsCodePoints(value, maximum) {
|
|
8317
|
-
if (value.length <= maximum) return false;
|
|
8318
|
-
let counted = 0;
|
|
8319
|
-
let i = 0;
|
|
8320
|
-
while (i < value.length) {
|
|
8321
|
-
const unit = value.charCodeAt(i);
|
|
8322
|
-
if (unit >= 55296 && unit < 56320 && i + 1 < value.length) {
|
|
8323
|
-
const low = value.charCodeAt(i + 1);
|
|
8324
|
-
if (low >= 56320 && low < 57344) i++;
|
|
8325
|
-
}
|
|
8326
|
-
i++;
|
|
8327
|
-
counted++;
|
|
8328
|
-
if (counted > maximum) return true;
|
|
8329
|
-
}
|
|
8330
|
-
return false;
|
|
8331
|
-
}
|
|
8332
|
-
function sortSignatureOf(sort) {
|
|
8333
|
-
const fields = normalizeSort(sort);
|
|
8334
|
-
if (fields.length === 0) return null;
|
|
8335
|
-
if (fields.length > MAX_SORT_FIELDS) {
|
|
8336
|
-
throw new NarsilError(
|
|
8337
|
-
ErrorCodes.SEARCH_INVALID_FIELD,
|
|
8338
|
-
`A sort names at most ${MAX_SORT_FIELDS} fields, because the paging cursor carries one value per field`,
|
|
8339
|
-
{ count: fields.length, limit: MAX_SORT_FIELDS }
|
|
8340
|
-
);
|
|
8341
|
-
}
|
|
8342
|
-
for (const entry of fields) {
|
|
8343
|
-
if (entry.field.length > MAX_SORT_FIELD_NAME_LENGTH) {
|
|
8344
|
-
throw new NarsilError(
|
|
8345
|
-
ErrorCodes.SEARCH_INVALID_FIELD,
|
|
8346
|
-
`A sort field name holds at most ${MAX_SORT_FIELD_NAME_LENGTH} characters, because the paging cursor carries the sort with every page`,
|
|
8347
|
-
{ field: entry.field.slice(0, 64), length: entry.field.length, limit: MAX_SORT_FIELD_NAME_LENGTH }
|
|
8348
|
-
);
|
|
8349
|
-
}
|
|
8350
|
-
}
|
|
8351
|
-
return JSON.stringify(fields.map((entry) => [entry.field, entry.direction]));
|
|
8352
|
-
}
|
|
8353
|
-
function encodePageCursor(cursor) {
|
|
8354
|
-
const payload = { v: CURSOR_VERSION, a: cursor.anchor };
|
|
8355
|
-
if (cursor.sortKey !== null && cursor.sortSignature !== null) {
|
|
8356
|
-
payload.k = cursor.sortKey.map(toComparableSortValue);
|
|
8357
|
-
payload.o = cursor.sortSignature;
|
|
8358
|
-
} else if (cursor.score !== null) {
|
|
8359
|
-
payload.s = cursor.score;
|
|
8360
|
-
}
|
|
8361
|
-
payload.q = cursor.binding;
|
|
8362
|
-
return encodeCursorText(JSON.stringify(payload));
|
|
8363
|
-
}
|
|
8364
|
-
function decodeSortKey(value, cursor) {
|
|
8365
|
-
if (!Array.isArray(value)) throw invalidCursor(cursor, '"k" must be an array');
|
|
8366
|
-
if (value.length > MAX_SORT_FIELDS) throw invalidCursor(cursor, '"k" carries too many sort values');
|
|
8367
|
-
const key = [];
|
|
8368
|
-
for (const entry of value) {
|
|
8369
|
-
if (entry === null || typeof entry === "boolean") {
|
|
8370
|
-
key.push(entry);
|
|
8371
|
-
continue;
|
|
8372
|
-
}
|
|
8373
|
-
if (typeof entry === "number") {
|
|
8374
|
-
if (!Number.isFinite(entry)) throw invalidCursor(cursor, '"k" holds a number that is not finite');
|
|
8375
|
-
key.push(entry);
|
|
8376
|
-
continue;
|
|
8377
|
-
}
|
|
8378
|
-
if (typeof entry === "string") {
|
|
8379
|
-
if (exceedsCodePoints(entry, MAX_SORT_VALUE_CODE_POINTS)) {
|
|
8380
|
-
throw invalidCursor(cursor, '"k" holds an oversized sort value');
|
|
8381
|
-
}
|
|
8382
|
-
key.push(entry);
|
|
8383
|
-
continue;
|
|
8384
|
-
}
|
|
8385
|
-
throw invalidCursor(cursor, '"k" accepts a string, a finite number, a boolean, or null');
|
|
8386
|
-
}
|
|
8387
|
-
return key;
|
|
8388
|
-
}
|
|
8389
|
-
function decodePageCursor(cursor) {
|
|
8390
|
-
if (cursor.length > MAX_CURSOR_LENGTH) throw invalidCursor(cursor, "the cursor is too long");
|
|
8391
|
-
let json;
|
|
8392
|
-
try {
|
|
8393
|
-
json = decodeCursorText(cursor);
|
|
8394
|
-
} catch {
|
|
8395
|
-
throw invalidCursor(cursor, "invalid base64 encoding");
|
|
8396
|
-
}
|
|
8397
|
-
let parsed;
|
|
8398
|
-
try {
|
|
8399
|
-
parsed = JSON.parse(json);
|
|
8400
|
-
} catch {
|
|
8401
|
-
throw invalidCursor(cursor, "invalid JSON");
|
|
8402
|
-
}
|
|
8403
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8404
|
-
throw invalidCursor(cursor, "expected an object");
|
|
8405
|
-
}
|
|
8406
|
-
const { v, a, s, k, o, q } = parsed;
|
|
8407
|
-
if (v !== CURSOR_VERSION) throw invalidCursor(cursor, `unsupported cursor version ${String(v)}`);
|
|
8408
|
-
if (typeof q !== "string" || !/^[0-9a-f]{8}$/.test(q)) {
|
|
8409
|
-
throw invalidCursor(cursor, '"q" must be 8 lowercase hex digits');
|
|
8410
|
-
}
|
|
8411
|
-
if (typeof a !== "string" || a.length === 0) throw invalidCursor(cursor, '"a" must be a non-empty string');
|
|
8412
|
-
if (exceedsCodePoints(a, MAX_ANCHOR_CODE_POINTS)) {
|
|
8413
|
-
throw invalidCursor(cursor, '"a" is too long to be a document id');
|
|
8414
|
-
}
|
|
8415
|
-
if (s !== void 0 && (typeof s !== "number" || !Number.isFinite(s))) {
|
|
8416
|
-
throw invalidCursor(cursor, '"s" must be a finite number');
|
|
8417
|
-
}
|
|
8418
|
-
if (s !== void 0 && k !== void 0) throw invalidCursor(cursor, 'a cursor carries "s" or "k", never both');
|
|
8419
|
-
const sortKey = k === void 0 ? null : decodeSortKey(k, cursor);
|
|
8420
|
-
if (o !== void 0 && typeof o !== "string") throw invalidCursor(cursor, '"o" must be a string');
|
|
8421
|
-
if (sortKey === null !== (o === void 0)) {
|
|
8422
|
-
throw invalidCursor(cursor, '"k" and "o" have to arrive together');
|
|
8423
|
-
}
|
|
8424
|
-
return {
|
|
8425
|
-
anchor: a,
|
|
8426
|
-
score: typeof s === "number" ? s : null,
|
|
8427
|
-
sortKey,
|
|
8428
|
-
sortSignature: typeof o === "string" ? o : null,
|
|
8429
|
-
binding: q
|
|
8430
|
-
};
|
|
8431
|
-
}
|
|
8432
|
-
function requireMatchingCursor(cursor, encoded, signature, scoreAnchored, binding) {
|
|
8433
|
-
if (cursor.binding !== binding) {
|
|
8434
|
-
throw invalidCursor(encoded, "the cursor belongs to a different query");
|
|
8435
|
-
}
|
|
8436
|
-
if (signature !== null) {
|
|
8437
|
-
if (cursor.sortSignature !== signature) {
|
|
8438
|
-
throw invalidCursor(encoded, "the cursor belongs to a different sort order");
|
|
8439
|
-
}
|
|
8440
|
-
return;
|
|
8441
|
-
}
|
|
8442
|
-
if (cursor.sortSignature !== null) {
|
|
8443
|
-
throw invalidCursor(encoded, "the cursor belongs to a sorted request");
|
|
8444
|
-
}
|
|
8445
|
-
if (scoreAnchored && cursor.score === null) {
|
|
8446
|
-
throw invalidCursor(encoded, "the cursor carries no score to seek from");
|
|
8447
|
-
}
|
|
8448
|
-
if (!scoreAnchored && cursor.score !== null) {
|
|
8449
|
-
throw invalidCursor(encoded, "the cursor belongs to a search rather than a listing");
|
|
8450
|
-
}
|
|
8451
|
-
}
|
|
8452
|
-
|
|
8453
|
-
// src/search/pagination.ts
|
|
8454
|
-
var RESULT_WINDOW = 1e4;
|
|
8455
|
-
var DEFAULT_PAGE_SIZE = 10;
|
|
8456
|
-
function clampRowCount(value, fallback) {
|
|
8457
|
-
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
8458
|
-
return Math.max(0, Math.floor(value));
|
|
8459
|
-
}
|
|
8460
|
-
function requireWithinResultWindow(limit, offset) {
|
|
8461
|
-
const depth = offset + limit;
|
|
8462
|
-
if (depth <= RESULT_WINDOW) return;
|
|
8463
|
-
throw new NarsilError(
|
|
8464
|
-
ErrorCodes.SEARCH_RESULT_WINDOW_EXCEEDED,
|
|
8465
|
-
`A request reaches the first ${RESULT_WINDOW} results, and offset + limit is ${depth}. Page past that with the cursor each result carries`,
|
|
8466
|
-
{ limit, offset, window: RESULT_WINDOW }
|
|
8467
|
-
);
|
|
8468
|
-
}
|
|
8469
|
-
function lastOrganicResult(sliced, pinnedIds) {
|
|
8470
|
-
for (let i = sliced.length - 1; i >= 0; i--) {
|
|
8471
|
-
if (pinnedIds === void 0 || !pinnedIds.has(sliced[i].id)) return sliced[i];
|
|
8472
|
-
}
|
|
8473
|
-
return void 0;
|
|
8474
|
-
}
|
|
8475
|
-
function ordersAfterAnchor(result, anchor, sort) {
|
|
8476
|
-
if (sort !== void 0 && anchor.sortKey !== null) {
|
|
8477
|
-
const comparison = compareSortValues(sort.sortKeyOf(result.id), anchor.sortKey, sort.directions) || compareCodePoints(result.id, anchor.anchor);
|
|
8478
|
-
return comparison > 0;
|
|
8479
|
-
}
|
|
8480
|
-
if (anchor.score === null) return true;
|
|
8481
|
-
if (result.score === void 0) return true;
|
|
8482
|
-
if (result.score < anchor.score) return true;
|
|
8483
|
-
return result.score === anchor.score && compareCodePoints(result.id, anchor.anchor) > 0;
|
|
8484
|
-
}
|
|
8485
|
-
function applyPagination(results, limit, offset, binding, cursor, sort, pinnedIds) {
|
|
8486
|
-
const decoded = cursor ? decodePageCursor(cursor) : null;
|
|
8487
|
-
if (decoded !== null && cursor !== void 0) {
|
|
8488
|
-
requireMatchingCursor(decoded, cursor, sort?.signature ?? null, true, binding);
|
|
8489
|
-
}
|
|
8490
|
-
if (limit <= 0) {
|
|
8491
|
-
return { paginated: [] };
|
|
8492
|
-
}
|
|
8493
|
-
let startIndex = 0;
|
|
8494
|
-
if (decoded !== null) {
|
|
8495
|
-
startIndex = results.length;
|
|
8496
|
-
for (let i = 0; i < results.length; i++) {
|
|
8497
|
-
if (ordersAfterAnchor(results[i], decoded, sort)) {
|
|
8498
|
-
startIndex = i;
|
|
8499
|
-
break;
|
|
8500
|
-
}
|
|
8501
|
-
}
|
|
8502
|
-
}
|
|
8503
|
-
const afterOffset = startIndex + offset;
|
|
8504
|
-
const sliced = results.slice(afterOffset, afterOffset + limit);
|
|
8505
|
-
let nextCursor;
|
|
8506
|
-
const hasMore = afterOffset + limit < results.length;
|
|
8507
|
-
const lastResult = lastOrganicResult(sliced, pinnedIds);
|
|
8508
|
-
if (hasMore && lastResult !== void 0) {
|
|
8509
|
-
nextCursor = sort !== void 0 ? encodePageCursor({
|
|
8510
|
-
anchor: lastResult.id,
|
|
8511
|
-
score: null,
|
|
8512
|
-
sortKey: sort.sortKeyOf(lastResult.id).map(toComparableSortValue),
|
|
8513
|
-
sortSignature: sort.signature,
|
|
8514
|
-
binding
|
|
8515
|
-
}) : encodePageCursor({
|
|
8516
|
-
anchor: lastResult.id,
|
|
8517
|
-
score: lastResult.score ?? null,
|
|
8518
|
-
sortKey: null,
|
|
8519
|
-
sortSignature: null,
|
|
8520
|
-
binding
|
|
8521
|
-
});
|
|
8522
|
-
}
|
|
8523
|
-
return { paginated: sliced, nextCursor };
|
|
8524
|
-
}
|
|
8525
|
-
|
|
8526
|
-
// src/search/fulltext.ts
|
|
8527
|
-
var PREFIX_MAX_EXPANSIONS = 50;
|
|
8528
|
-
function resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken) {
|
|
8529
|
-
const term = params.term;
|
|
8530
|
-
if (term === void 0) return void 0;
|
|
8531
|
-
const unstemmed = tokenize(term, language, {
|
|
8532
|
-
stem: false,
|
|
8533
|
-
removeStopWords: true,
|
|
8534
|
-
customTokenizer: options?.customTokenizer,
|
|
8535
|
-
stopWordOverride: options?.stopWords
|
|
8536
|
-
});
|
|
8537
|
-
let surfacePrefix;
|
|
8538
|
-
for (const t of unstemmed.tokens) {
|
|
8539
|
-
if (t.position === lastToken.position) {
|
|
8540
|
-
surfacePrefix = t.token;
|
|
8541
|
-
break;
|
|
8542
|
-
}
|
|
8543
|
-
}
|
|
8544
|
-
if (surfacePrefix === void 0 || surfacePrefix.length === 0) return void 0;
|
|
8545
|
-
const expansions = partition.expandTermPrefix(surfacePrefix, lastToken.token, PREFIX_MAX_EXPANSIONS);
|
|
8546
|
-
const terms = [];
|
|
8547
|
-
for (const expanded of expansions) {
|
|
8548
|
-
if (expanded !== lastToken.token) terms.push(expanded);
|
|
8549
|
-
}
|
|
8550
|
-
for (const qt of queryTokens) {
|
|
8551
|
-
if (qt.token === lastToken.token) {
|
|
8552
|
-
return { token: qt.token, terms };
|
|
8553
|
-
}
|
|
8554
|
-
}
|
|
8555
|
-
return void 0;
|
|
8556
|
-
}
|
|
8557
|
-
function prepareFulltextQuery(partition, params, language, schema, options) {
|
|
8558
|
-
if (!params.term || params.term.trim().length === 0) return null;
|
|
8559
|
-
if (params.fields && params.fields.length === 0) return null;
|
|
8560
|
-
const flatSchema = flattenSchema(schema);
|
|
8561
|
-
if (params.fields) {
|
|
8562
|
-
validateSearchFields(params.fields, flatSchema);
|
|
8563
|
-
}
|
|
8564
|
-
const queryTokenResult = tokenize(params.term, language, {
|
|
8565
|
-
stem: true,
|
|
8566
|
-
removeStopWords: true,
|
|
8567
|
-
customTokenizer: options?.customTokenizer,
|
|
8568
|
-
stopWordOverride: options?.stopWords
|
|
8569
|
-
});
|
|
8570
|
-
if (queryTokenResult.tokens.length === 0) return null;
|
|
8571
|
-
const queryTokens = deduplicateTokens(queryTokenResult.tokens);
|
|
8572
|
-
let prefixExpansion;
|
|
8573
|
-
if (params.prefix === true && params.exact !== true) {
|
|
8574
|
-
const lastToken = queryTokenResult.tokens[queryTokenResult.tokens.length - 1];
|
|
8575
|
-
prefixExpansion = resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken);
|
|
8576
|
-
}
|
|
8577
|
-
let filterBitset;
|
|
8578
|
-
if (params.filters) {
|
|
8579
|
-
filterBitset = partition.applyFiltersBitset(params.filters, schema);
|
|
8580
|
-
if (bitsetIsEmpty(filterBitset)) return null;
|
|
8581
|
-
}
|
|
8582
|
-
return { queryTokens, prefixExpansion, filterBitset };
|
|
8583
|
-
}
|
|
8584
|
-
function fulltextMatches(partition, params, language, schema, options) {
|
|
8585
|
-
const prepared = prepareFulltextQuery(partition, params, language, schema, options);
|
|
8586
|
-
if (prepared === null) return null;
|
|
8587
|
-
return partition.searchFulltextMatches({
|
|
8588
|
-
queryTokens: prepared.queryTokens,
|
|
8589
|
-
prefixExpansion: prepared.prefixExpansion,
|
|
8590
|
-
fields: params.fields,
|
|
8591
|
-
tolerance: params.tolerance ?? 0,
|
|
8592
|
-
prefixLength: params.prefixLength ?? 2,
|
|
8593
|
-
exact: params.exact ?? false,
|
|
8594
|
-
filterBitset: prepared.filterBitset
|
|
8595
|
-
});
|
|
8596
|
-
}
|
|
8597
|
-
function fulltextSearch(partition, params, language, schema, options) {
|
|
8598
|
-
const prepared = prepareFulltextQuery(partition, params, language, schema, options);
|
|
8599
|
-
if (prepared === null) {
|
|
8600
|
-
return { scored: [], totalMatched: 0 };
|
|
8601
|
-
}
|
|
8602
|
-
const { queryTokens, prefixExpansion, filterBitset } = prepared;
|
|
8603
|
-
const needsAllResults = params.minScore !== void 0 || params.termMatch !== void 0 && params.termMatch !== "any" || params.sort !== void 0 || params.group !== void 0 || params.pinned !== void 0 || params.searchAfter !== void 0;
|
|
8604
|
-
const maxResults = needsAllResults ? void 0 : clampRowCount(params.limit, DEFAULT_PAGE_SIZE) + clampRowCount(params.offset, 0) + 1;
|
|
8605
|
-
const collectMatchedSet = params.facets !== void 0 && !needsAllResults ? "ordinals" : void 0;
|
|
8606
|
-
const collectComponents = params.includeScoreComponents === true || params.termMatch !== void 0 && params.termMatch !== "any";
|
|
8607
|
-
const rawResult = partition.searchFulltext({
|
|
8608
|
-
queryTokens,
|
|
8609
|
-
prefixExpansion,
|
|
8610
|
-
fields: params.fields,
|
|
8611
|
-
boost: params.boost,
|
|
8612
|
-
tolerance: params.tolerance ?? 0,
|
|
8613
|
-
prefixLength: params.prefixLength ?? 2,
|
|
8614
|
-
exact: params.exact ?? false,
|
|
8615
|
-
bm25Params: options?.bm25Params,
|
|
8616
|
-
globalStats: options?.globalStats,
|
|
8617
|
-
maxResults,
|
|
8618
|
-
termMatch: params.termMatch,
|
|
8619
|
-
filterBitset,
|
|
8620
|
-
collectComponents,
|
|
8621
|
-
collectMatchedSet
|
|
8622
|
-
});
|
|
8623
|
-
let scored = rawResult.scored;
|
|
8624
|
-
const termMatch = params.termMatch ?? "any";
|
|
8625
|
-
if (termMatch !== "any") {
|
|
8626
|
-
scored = filterByTermCoverage(
|
|
8627
|
-
scored,
|
|
8628
|
-
queryTokens,
|
|
8629
|
-
termMatch,
|
|
8630
|
-
params.tolerance ?? 0,
|
|
8631
|
-
params.exact ?? false,
|
|
8632
|
-
prefixExpansion
|
|
8633
|
-
);
|
|
8634
|
-
}
|
|
8635
|
-
if (params.minScore !== void 0 && params.minScore > 0) {
|
|
8636
|
-
const threshold = params.minScore;
|
|
8637
|
-
scored = scored.filter((doc) => doc.score >= threshold);
|
|
8638
|
-
}
|
|
8639
|
-
const totalMatched = needsAllResults ? scored.length : rawResult.totalMatched;
|
|
8640
|
-
if (params.facets === void 0) {
|
|
8641
|
-
return { scored, totalMatched };
|
|
8642
|
-
}
|
|
8643
|
-
if (needsAllResults) {
|
|
8644
|
-
return { scored, totalMatched, matchedIds: scored.map((doc) => doc.docId) };
|
|
8645
|
-
}
|
|
8646
|
-
return { scored, totalMatched, matchedOrdinalBitset: rawResult.matchedOrdinalBitset };
|
|
8647
|
-
}
|
|
8648
|
-
function validateSearchFields(fields, flatSchema) {
|
|
8649
|
-
for (const field of fields) {
|
|
8650
|
-
const fieldType = flatSchema[field];
|
|
8651
|
-
if (!fieldType) {
|
|
8652
|
-
throw new NarsilError(ErrorCodes.SEARCH_INVALID_FIELD, `Field "${field}" does not exist in the schema`, { field });
|
|
8653
|
-
}
|
|
8654
|
-
if (!isTextFieldType(fieldType) && fieldType !== "string[]") {
|
|
8655
|
-
throw new NarsilError(
|
|
8656
|
-
ErrorCodes.SEARCH_INVALID_FIELD,
|
|
8657
|
-
`Field "${field}" has type "${fieldType}" which cannot be used for full-text search`,
|
|
8658
|
-
{ field, fieldType }
|
|
8659
|
-
);
|
|
8660
|
-
}
|
|
8661
|
-
}
|
|
8662
|
-
}
|
|
8663
|
-
function deduplicateTokens(tokens) {
|
|
8664
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8665
|
-
const result = [];
|
|
8666
|
-
for (const t of tokens) {
|
|
8667
|
-
if (!seen.has(t.token)) {
|
|
8668
|
-
seen.add(t.token);
|
|
8669
|
-
result.push(t);
|
|
8670
|
-
}
|
|
8671
|
-
}
|
|
8672
|
-
return result;
|
|
8673
|
-
}
|
|
8674
|
-
function filterByTermCoverage(scored, queryTokens, policy, tolerance, exact, prefixExpansion) {
|
|
8675
|
-
const requiredCount = policy === "all" ? queryTokens.length : policy;
|
|
8676
|
-
if (requiredCount <= 0) return scored;
|
|
8677
|
-
if (requiredCount > queryTokens.length) return [];
|
|
8678
|
-
const expansionTerms = prefixExpansion ? new Set(prefixExpansion.terms) : void 0;
|
|
8679
|
-
return scored.filter((doc) => {
|
|
8680
|
-
const matched = countDocTermMatches(doc, queryTokens, tolerance, exact, prefixExpansion?.token, expansionTerms);
|
|
8681
|
-
return matched >= requiredCount;
|
|
8682
|
-
});
|
|
8683
|
-
}
|
|
8684
|
-
function countDocTermMatches(doc, queryTokens, tolerance, exact, prefixToken, expansionTerms) {
|
|
8685
|
-
const indexTokens = Object.keys(doc.idf);
|
|
8686
|
-
if (indexTokens.length === 0) return 0;
|
|
8687
|
-
let count = 0;
|
|
8688
|
-
for (const qt of queryTokens) {
|
|
8689
|
-
if (prefixToken !== void 0 && qt.token === prefixToken) {
|
|
8690
|
-
if (prefixTokenSatisfied(qt.token, indexTokens, expansionTerms)) {
|
|
8691
|
-
count++;
|
|
8692
|
-
}
|
|
8693
|
-
continue;
|
|
8694
|
-
}
|
|
8695
|
-
if (queryTermSatisfied(qt.token, indexTokens, tolerance, exact)) {
|
|
8696
|
-
count++;
|
|
8697
|
-
}
|
|
8698
|
-
}
|
|
8699
|
-
return count;
|
|
8700
|
-
}
|
|
8701
|
-
function prefixTokenSatisfied(prefixToken, indexTokens, expansionTerms) {
|
|
8702
|
-
for (const indexToken of indexTokens) {
|
|
8703
|
-
if (indexToken === prefixToken) return true;
|
|
8704
|
-
if (expansionTerms?.has(indexToken)) return true;
|
|
8705
|
-
}
|
|
8706
|
-
return false;
|
|
8707
|
-
}
|
|
8708
|
-
function queryTermSatisfied(queryToken, indexTokens, tolerance, exact) {
|
|
8709
|
-
if (exact || tolerance === 0) {
|
|
8710
|
-
return indexTokens.includes(queryToken);
|
|
8711
|
-
}
|
|
8712
|
-
for (const indexToken of indexTokens) {
|
|
8713
|
-
if (boundedLevenshtein(queryToken, indexToken, tolerance).withinTolerance) {
|
|
8714
|
-
return true;
|
|
8715
|
-
}
|
|
8716
|
-
}
|
|
8717
|
-
return false;
|
|
8718
|
-
}
|
|
8719
|
-
|
|
8720
|
-
// src/partitioning/partition-selection.ts
|
|
8721
|
-
function partitionsIn(manager, partitionIds) {
|
|
8722
|
-
if (partitionIds === void 0) {
|
|
8723
|
-
return manager.getAllPartitions();
|
|
8724
|
-
}
|
|
8725
|
-
const partitions = [];
|
|
8726
|
-
for (const partitionId of partitionIds) {
|
|
8727
|
-
const partition = manager.partitionAt(partitionId);
|
|
8728
|
-
if (partition !== void 0) {
|
|
8729
|
-
partitions.push(partition);
|
|
8730
|
-
}
|
|
8731
|
-
}
|
|
8732
|
-
return partitions;
|
|
8733
|
-
}
|
|
8734
|
-
|
|
8735
|
-
// src/partitioning/fan-out.ts
|
|
8736
|
-
async function fanOutQuery(manager, params, language, schema, config, searchOptions) {
|
|
8737
|
-
const partitions = partitionsIn(manager, config.partitionIds);
|
|
8738
|
-
if (partitions.length === 0) {
|
|
8739
|
-
return { scored: [], totalMatched: 0 };
|
|
8740
|
-
}
|
|
8741
|
-
let globalStats;
|
|
8742
|
-
const effectiveMode = resolveEffectiveMode(config);
|
|
8743
|
-
if (effectiveMode === "dfs") {
|
|
8744
|
-
if (params.term !== void 0 && params.term.trim().length > 0) {
|
|
8745
|
-
globalStats = collectQueryTermStats(
|
|
8746
|
-
manager,
|
|
8747
|
-
params.term,
|
|
8748
|
-
language,
|
|
8749
|
-
{
|
|
8750
|
-
stopWords: searchOptions?.stopWords,
|
|
8751
|
-
customTokenizer: searchOptions?.customTokenizer
|
|
8752
|
-
},
|
|
8753
|
-
config.partitionIds
|
|
8754
|
-
);
|
|
8755
|
-
}
|
|
8756
|
-
} else if (effectiveMode === "broadcast") {
|
|
8757
|
-
globalStats = config.globalStats;
|
|
8758
|
-
}
|
|
8759
|
-
const options = buildSearchOptions(searchOptions, globalStats);
|
|
8760
|
-
if (partitions.length === 1 && !config.dispatcher) {
|
|
8761
|
-
const result = dispatchSinglePartition(partitions[0], params, language, schema, options);
|
|
8762
|
-
let facets2;
|
|
8763
|
-
if (params.facets) {
|
|
8764
|
-
facets2 = partitions[0].computeFacets(facetMatchSetOf(result), params.facets, schema);
|
|
8765
|
-
}
|
|
8766
|
-
return { scored: result.scored, totalMatched: result.totalMatched, facets: facets2 };
|
|
8767
|
-
}
|
|
8768
|
-
let outcomes;
|
|
8769
|
-
let facets;
|
|
8770
|
-
if (config.dispatcher) {
|
|
8771
|
-
outcomes = await dispatchWithDispatcher(partitions, params, language, schema, options, config.dispatcher);
|
|
8772
|
-
if (params.facets) {
|
|
8773
|
-
facets = collectAndMergeFacets(outcomes, params, schema);
|
|
8774
|
-
}
|
|
8775
|
-
} else {
|
|
8776
|
-
outcomes = [];
|
|
8777
|
-
const partitionFacets = [];
|
|
8778
|
-
for (const partition of partitions) {
|
|
8779
|
-
const result = dispatchSinglePartition(partition, params, language, schema, options);
|
|
8780
|
-
if (params.facets) {
|
|
8781
|
-
partitionFacets.push(partition.computeFacets(facetMatchSetOf(result), params.facets, schema));
|
|
8782
|
-
}
|
|
8783
|
-
outcomes.push({ result, partition });
|
|
8784
|
-
}
|
|
8785
|
-
if (params.facets) {
|
|
8786
|
-
facets = mergeFacets(partitionFacets);
|
|
8787
|
-
}
|
|
8788
|
-
}
|
|
8789
|
-
const allScoredArrays = outcomes.map((o) => o.result.scored);
|
|
8790
|
-
const merged = kWayMerge(allScoredArrays);
|
|
8791
|
-
let totalMatched = 0;
|
|
8792
|
-
for (const outcome of outcomes) {
|
|
8793
|
-
totalMatched += outcome.result.totalMatched;
|
|
8794
|
-
}
|
|
8795
|
-
return { scored: merged, totalMatched, facets };
|
|
8796
|
-
}
|
|
8797
|
-
function facetMatchSetOf(result) {
|
|
8798
|
-
if (result.matchedOrdinalBitset !== void 0) {
|
|
8799
|
-
return { ordinalBitset: result.matchedOrdinalBitset };
|
|
8800
|
-
}
|
|
8801
|
-
return new Set(result.matchedIds ?? []);
|
|
8802
|
-
}
|
|
8803
|
-
function resolveEffectiveMode(config) {
|
|
8804
|
-
if (config.scoringMode === "broadcast" && !config.globalStats) {
|
|
8805
|
-
return "dfs";
|
|
8806
|
-
}
|
|
8807
|
-
return config.scoringMode;
|
|
8808
|
-
}
|
|
8809
|
-
function buildSearchOptions(base, globalStats) {
|
|
8810
|
-
return {
|
|
8811
|
-
...base,
|
|
8812
|
-
globalStats: globalStats ?? base?.globalStats
|
|
8813
|
-
};
|
|
8814
|
-
}
|
|
8815
|
-
function dispatchWithDispatcher(partitions, params, language, schema, options, dispatcher) {
|
|
8816
|
-
const promises = partitions.map(
|
|
8817
|
-
(partition) => dispatcher.dispatch(partition, params, language, schema, options).then((result) => ({ result, partition }))
|
|
8818
|
-
);
|
|
8819
|
-
return Promise.all(promises);
|
|
8820
|
-
}
|
|
8821
|
-
function dispatchSinglePartition(partition, params, language, schema, options) {
|
|
8822
|
-
return fulltextSearch(partition, params, language, schema, options);
|
|
8823
|
-
}
|
|
8824
|
-
function collectAndMergeFacets(outcomes, params, schema) {
|
|
8825
|
-
const partitionFacets = [];
|
|
8826
|
-
for (const outcome of outcomes) {
|
|
8827
|
-
if (!params.facets) continue;
|
|
8828
|
-
const facetResult = outcome.partition.computeFacets(facetMatchSetOf(outcome.result), params.facets, schema);
|
|
8829
|
-
partitionFacets.push(facetResult);
|
|
8830
|
-
}
|
|
8831
|
-
return mergeFacets(partitionFacets);
|
|
8832
|
-
}
|
|
8833
|
-
|
|
8834
|
-
// src/partitioning/insert-options.ts
|
|
8835
|
-
function resolvePartitionInsertOptions(config, analysis, options) {
|
|
8836
|
-
const applyStrict = config.strict === true;
|
|
8837
|
-
const applyAnalyzer = analysis.stopWords !== void 0 || analysis.customTokenizer !== void 0;
|
|
8838
|
-
const applySurfaces = config.surfaceForms !== false;
|
|
8839
|
-
if (!applyStrict && !applyAnalyzer && !applySurfaces) return options;
|
|
8840
|
-
const resolved = { ...options };
|
|
8841
|
-
if (applyStrict) resolved.strict = true;
|
|
8842
|
-
if (applyAnalyzer) {
|
|
8843
|
-
resolved.stopWordOverride = options?.stopWordOverride ?? analysis.stopWords;
|
|
8844
|
-
resolved.customTokenizer = options?.customTokenizer ?? analysis.customTokenizer;
|
|
8845
|
-
}
|
|
8846
|
-
if (applySurfaces) resolved.collectSurfaces = true;
|
|
8847
|
-
return resolved;
|
|
8848
|
-
}
|
|
8849
|
-
|
|
8850
|
-
// src/analysis/registry.ts
|
|
8851
|
-
var tokenizers = /* @__PURE__ */ new Map();
|
|
8852
|
-
var stopWordSets = /* @__PURE__ */ new Map();
|
|
8853
|
-
function requireName(name, kind) {
|
|
8854
|
-
if (typeof name !== "string" || name.trim().length === 0) {
|
|
8855
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `A registered ${kind} needs a non-empty name`, { name });
|
|
8856
|
-
}
|
|
8857
|
-
}
|
|
8858
|
-
function registerTokenizer(name, tokenizer) {
|
|
8859
|
-
requireName(name, "tokenizer");
|
|
8860
|
-
if (typeof tokenizer?.tokenize !== "function") {
|
|
8861
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" needs a tokenize method`, { tokenizer: name });
|
|
8862
|
-
}
|
|
8863
|
-
tokenizers.set(name, tokenizer);
|
|
8864
|
-
}
|
|
8865
|
-
function registerStopWords(name, stopWords) {
|
|
8866
|
-
requireName(name, "stop word set");
|
|
8867
|
-
if (!(stopWords instanceof Set) && typeof stopWords !== "function") {
|
|
8868
|
-
throw new NarsilError(
|
|
8869
|
-
ErrorCodes.CONFIG_INVALID,
|
|
8870
|
-
`Stop word set "${name}" needs a Set of words or a function returning one`,
|
|
8871
|
-
{ stopWords: name }
|
|
8872
|
-
);
|
|
8873
|
-
}
|
|
8874
|
-
stopWordSets.set(name, stopWords);
|
|
8875
|
-
}
|
|
8876
|
-
function hasTokenizer(name) {
|
|
8877
|
-
return tokenizers.has(name);
|
|
8878
|
-
}
|
|
8879
|
-
function hasStopWords(name) {
|
|
8880
|
-
return stopWordSets.has(name);
|
|
8881
|
-
}
|
|
8882
|
-
function getTokenizer(name) {
|
|
8883
|
-
const tokenizer = tokenizers.get(name);
|
|
8884
|
-
if (!tokenizer) {
|
|
8885
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" is not registered on this engine`, {
|
|
8886
|
-
tokenizer: name,
|
|
8887
|
-
available: [...tokenizers.keys()]
|
|
8888
|
-
});
|
|
8889
|
-
}
|
|
8890
|
-
return tokenizer;
|
|
8891
|
-
}
|
|
8892
|
-
function getStopWords(name) {
|
|
8893
|
-
const stopWords = stopWordSets.get(name);
|
|
8894
|
-
if (!stopWords) {
|
|
8895
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Stop word set "${name}" is not registered on this engine`, {
|
|
8896
|
-
stopWords: name,
|
|
8897
|
-
available: [...stopWordSets.keys()]
|
|
8898
|
-
});
|
|
8899
|
-
}
|
|
8900
|
-
return stopWords;
|
|
8901
|
-
}
|
|
8902
|
-
function resolveIndexAnalysis(config) {
|
|
8903
|
-
return {
|
|
8904
|
-
stopWords: typeof config.stopWords === "string" ? getStopWords(config.stopWords) : config.stopWords,
|
|
8905
|
-
customTokenizer: typeof config.tokenizer === "string" ? getTokenizer(config.tokenizer) : config.tokenizer
|
|
6982
|
+
for (const segment of frozen) {
|
|
6983
|
+
total += estimatePartitionBytes(segment);
|
|
6984
|
+
}
|
|
6985
|
+
return total;
|
|
6986
|
+
}
|
|
8906
6987
|
};
|
|
8907
6988
|
}
|
|
8908
6989
|
|
|
6990
|
+
// src/partitioning/insert-options.ts
|
|
6991
|
+
function resolvePartitionInsertOptions(config, analysis, options) {
|
|
6992
|
+
const applyStrict = config.strict === true;
|
|
6993
|
+
const applyAnalyzer = analysis.stopWords !== void 0 || analysis.customTokenizer !== void 0;
|
|
6994
|
+
const applySurfaces = config.surfaceForms !== false;
|
|
6995
|
+
if (!applyStrict && !applyAnalyzer && !applySurfaces) return options;
|
|
6996
|
+
const resolved = { ...options };
|
|
6997
|
+
if (applyStrict) resolved.strict = true;
|
|
6998
|
+
if (applyAnalyzer) {
|
|
6999
|
+
resolved.stopWordOverride = options?.stopWordOverride ?? analysis.stopWords;
|
|
7000
|
+
resolved.customTokenizer = options?.customTokenizer ?? analysis.customTokenizer;
|
|
7001
|
+
}
|
|
7002
|
+
if (applySurfaces) resolved.collectSurfaces = true;
|
|
7003
|
+
return resolved;
|
|
7004
|
+
}
|
|
7005
|
+
|
|
8909
7006
|
// src/partitioning/manager/nested-values.ts
|
|
8910
7007
|
function setNestedValue(obj, path, value) {
|
|
8911
7008
|
if (!path.includes(".")) {
|
|
@@ -9171,6 +7268,14 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
9171
7268
|
if (docId !== void 0) docPartitionMap.set(docId, partitionId);
|
|
9172
7269
|
}
|
|
9173
7270
|
},
|
|
7271
|
+
freezeLiveTail(partitionId, freeze) {
|
|
7272
|
+
validatePartitionId(partitionId);
|
|
7273
|
+
return asCompositePartition(partitionId).freezeLiveTail(freeze);
|
|
7274
|
+
},
|
|
7275
|
+
replaceLiveTail(partitionId, segment) {
|
|
7276
|
+
validatePartitionId(partitionId);
|
|
7277
|
+
asCompositePartition(partitionId).replaceLiveTail(segment);
|
|
7278
|
+
},
|
|
9174
7279
|
deserializePartition(partitionId, data) {
|
|
9175
7280
|
validatePartitionId(partitionId);
|
|
9176
7281
|
partitions[partitionId].deserialize(data, config.schema);
|
|
@@ -9222,35 +7327,6 @@ function createPartitionManager(indexName, config, language, router, initialPart
|
|
|
9222
7327
|
return manager;
|
|
9223
7328
|
}
|
|
9224
7329
|
|
|
9225
|
-
// src/partitioning/match-count.ts
|
|
9226
|
-
function countsWithoutScores(params) {
|
|
9227
|
-
return params.minScore === void 0 && (params.termMatch === void 0 || params.termMatch === "any");
|
|
9228
|
-
}
|
|
9229
|
-
function fanOutMatchCount(manager, params, language, schema, options) {
|
|
9230
|
-
let count = 0;
|
|
9231
|
-
for (const partition of partitionsIn(manager, options?.partitionIds)) {
|
|
9232
|
-
const matches = fulltextMatches(partition, params, language, schema, options?.searchOptions);
|
|
9233
|
-
if (matches !== null) count += matches.count;
|
|
9234
|
-
}
|
|
9235
|
-
return count;
|
|
9236
|
-
}
|
|
9237
|
-
|
|
9238
|
-
// src/core/hash.ts
|
|
9239
|
-
var FNV_OFFSET_BASIS = 2166136261;
|
|
9240
|
-
var FNV_PRIME = 16777619;
|
|
9241
|
-
var encoder3 = new TextEncoder();
|
|
9242
|
-
function fnv1aBytes(bytes) {
|
|
9243
|
-
let hash = FNV_OFFSET_BASIS;
|
|
9244
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
9245
|
-
hash ^= bytes[i];
|
|
9246
|
-
hash = Math.imul(hash, FNV_PRIME);
|
|
9247
|
-
}
|
|
9248
|
-
return hash >>> 0;
|
|
9249
|
-
}
|
|
9250
|
-
function fnv1a(input) {
|
|
9251
|
-
return fnv1aBytes(encoder3.encode(input));
|
|
9252
|
-
}
|
|
9253
|
-
|
|
9254
7330
|
// src/partitioning/router.ts
|
|
9255
7331
|
function createPartitionRouter() {
|
|
9256
7332
|
function route(docId, partitionCount) {
|
|
@@ -9280,10 +7356,10 @@ function createPartitionRouter() {
|
|
|
9280
7356
|
}
|
|
9281
7357
|
|
|
9282
7358
|
// src/workers/node-worker.ts
|
|
9283
|
-
async function spawnNodeWorker(entryPoint) {
|
|
7359
|
+
async function spawnNodeWorker(entryPoint, resourceLimits) {
|
|
9284
7360
|
try {
|
|
9285
7361
|
const workerThreads = await import('worker_threads');
|
|
9286
|
-
return new workerThreads.Worker(entryPoint);
|
|
7362
|
+
return new workerThreads.Worker(entryPoint, { resourceLimits });
|
|
9287
7363
|
} catch {
|
|
9288
7364
|
return null;
|
|
9289
7365
|
}
|
|
@@ -9297,42 +7373,7 @@ async function isNodeMainThread() {
|
|
|
9297
7373
|
}
|
|
9298
7374
|
}
|
|
9299
7375
|
|
|
9300
|
-
// src/runtime/detect.ts
|
|
9301
|
-
function detectRuntimeName() {
|
|
9302
|
-
if ("Bun" in globalThis) {
|
|
9303
|
-
return "bun";
|
|
9304
|
-
}
|
|
9305
|
-
if ("Deno" in globalThis) {
|
|
9306
|
-
return "deno";
|
|
9307
|
-
}
|
|
9308
|
-
const proc = globalThis.process;
|
|
9309
|
-
if (typeof proc === "object" && proc !== null && typeof proc.versions === "object" && typeof proc.versions?.node === "string") {
|
|
9310
|
-
return "node";
|
|
9311
|
-
}
|
|
9312
|
-
return "browser";
|
|
9313
|
-
}
|
|
9314
|
-
function getCpuCount() {
|
|
9315
|
-
const nav = globalThis.navigator;
|
|
9316
|
-
if (nav && typeof nav.hardwareConcurrency === "number" && nav.hardwareConcurrency >= 1) {
|
|
9317
|
-
return nav.hardwareConcurrency;
|
|
9318
|
-
}
|
|
9319
|
-
return 1;
|
|
9320
|
-
}
|
|
9321
|
-
function detectRuntime() {
|
|
9322
|
-
const runtime = detectRuntimeName();
|
|
9323
|
-
return {
|
|
9324
|
-
runtime,
|
|
9325
|
-
cpuCount: getCpuCount(),
|
|
9326
|
-
supportsWorkerThreads: runtime === "node" || runtime === "bun",
|
|
9327
|
-
supportsWebWorkers: runtime === "browser" || runtime === "deno" || runtime === "bun",
|
|
9328
|
-
supportsFileSystem: runtime === "node" || runtime === "bun" || runtime === "deno",
|
|
9329
|
-
supportsIndexedDB: runtime === "browser" || "indexedDB" in globalThis,
|
|
9330
|
-
supportsBroadcastChannel: "BroadcastChannel" in globalThis
|
|
9331
|
-
};
|
|
9332
|
-
}
|
|
9333
|
-
|
|
9334
7376
|
// src/vector/hnsw-worker-dispatch.ts
|
|
9335
|
-
var BUILD_TIMEOUT_MS = 12e4;
|
|
9336
7377
|
function resolveWorkerEntryPoint() {
|
|
9337
7378
|
const base = import.meta.url;
|
|
9338
7379
|
const distIndex = base.lastIndexOf("/dist/");
|
|
@@ -9398,7 +7439,7 @@ async function dispatchWorkerBuild(docIds, vectorData, dimension, config, timeou
|
|
|
9398
7439
|
message: "No worker thread support available in the current runtime"
|
|
9399
7440
|
};
|
|
9400
7441
|
}
|
|
9401
|
-
const effectiveTimeout = timeoutMs ??
|
|
7442
|
+
const effectiveTimeout = timeoutMs ?? HNSW_BUILD_TIMEOUT_MS;
|
|
9402
7443
|
let transferBuffer;
|
|
9403
7444
|
if (skipCopy) {
|
|
9404
7445
|
transferBuffer = vectorData;
|
|
@@ -9468,7 +7509,7 @@ async function dispatchWorkerBuild(docIds, vectorData, dimension, config, timeou
|
|
|
9468
7509
|
});
|
|
9469
7510
|
}
|
|
9470
7511
|
|
|
9471
|
-
// src/vector/vector-index/
|
|
7512
|
+
// src/vector/vector-index/constants.ts
|
|
9472
7513
|
var DEFAULT_PROMOTION_THRESHOLD = 1024;
|
|
9473
7514
|
var DEFAULT_FILTER_THRESHOLD = 0.03;
|
|
9474
7515
|
var ESTIMATED_MS_PER_TOMBSTONE = 0.05;
|
|
@@ -9477,9 +7518,25 @@ var WORKER_BUILD_SIZE_THRESHOLD = 5e3;
|
|
|
9477
7518
|
var WORKER_COPY_MIN_VECTORS = 1024;
|
|
9478
7519
|
var BUILD_CHUNK_SIZE = 100;
|
|
9479
7520
|
var REBUILD_REMOVED_RATIO = 0.2;
|
|
7521
|
+
|
|
7522
|
+
// src/vector/vector-index/shared.ts
|
|
7523
|
+
var VECTOR_WORKER_COPIES_ALLOWED = { enabled: true };
|
|
9480
7524
|
function liveSize(state) {
|
|
9481
7525
|
return state.store.size - state.tombstones.size;
|
|
9482
7526
|
}
|
|
7527
|
+
function assignStorePartitions(state, resolve) {
|
|
7528
|
+
for (let ordinal = 0; ordinal < state.store.slots; ordinal += 1) {
|
|
7529
|
+
if (state.store.partitionOfOrdinal(ordinal) !== void 0) continue;
|
|
7530
|
+
const docId = state.store.docIdForOrdinal(ordinal);
|
|
7531
|
+
if (docId === void 0) continue;
|
|
7532
|
+
const partitionId = resolve(docId);
|
|
7533
|
+
if (partitionId === void 0) {
|
|
7534
|
+
state.store.forgetPartition(docId);
|
|
7535
|
+
continue;
|
|
7536
|
+
}
|
|
7537
|
+
state.store.setPartition(docId, partitionId);
|
|
7538
|
+
}
|
|
7539
|
+
}
|
|
9483
7540
|
function adoptGraph(state, graph) {
|
|
9484
7541
|
if (graph === null && state.hnsw !== null) {
|
|
9485
7542
|
state.hnsw.clear();
|
|
@@ -9588,28 +7645,53 @@ function recalibrateFromStore(state) {
|
|
|
9588
7645
|
sq8.recalibrateAll(storeVectors());
|
|
9589
7646
|
}
|
|
9590
7647
|
|
|
7648
|
+
// src/workers/constants.ts
|
|
7649
|
+
var WORKER_YOUNG_GENERATION_MB = 24;
|
|
7650
|
+
var MIN_WORKER_COUNT = 2;
|
|
7651
|
+
var MAX_WORKER_COUNT = 8;
|
|
7652
|
+
var FALLBACK_CPU_COUNT = 4;
|
|
7653
|
+
var MIN_CORES_FOR_SEVERAL_REQUEST_THREADS = 3;
|
|
7654
|
+
var DEFAULT_BACKPRESSURE_LIMIT = 100;
|
|
7655
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
7656
|
+
var SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
7657
|
+
var CONSECUTIVE_TIMEOUTS_BEFORE_DEATH = 3;
|
|
7658
|
+
|
|
9591
7659
|
// src/workers/pool.ts
|
|
9592
7660
|
function toFinite(value) {
|
|
9593
7661
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
9594
7662
|
}
|
|
9595
|
-
function
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
let cpuCount = 4;
|
|
7663
|
+
function toHeapLimit(value) {
|
|
7664
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
7665
|
+
}
|
|
7666
|
+
function detectCpuCount() {
|
|
9600
7667
|
try {
|
|
9601
7668
|
if (navigator?.hardwareConcurrency) {
|
|
9602
|
-
|
|
9603
|
-
}
|
|
7669
|
+
return navigator.hardwareConcurrency;
|
|
7670
|
+
}
|
|
7671
|
+
if (typeof process !== "undefined") {
|
|
9604
7672
|
const ap = process.availableParallelism;
|
|
9605
7673
|
if (typeof ap === "function") {
|
|
9606
|
-
|
|
7674
|
+
return ap();
|
|
9607
7675
|
}
|
|
9608
7676
|
}
|
|
9609
7677
|
} catch {
|
|
9610
|
-
|
|
7678
|
+
return FALLBACK_CPU_COUNT;
|
|
7679
|
+
}
|
|
7680
|
+
return FALLBACK_CPU_COUNT;
|
|
7681
|
+
}
|
|
7682
|
+
function resolveWorkerCount(requested) {
|
|
7683
|
+
if (requested !== void 0 && requested > 0) {
|
|
7684
|
+
return requested;
|
|
9611
7685
|
}
|
|
9612
|
-
return Math.max(
|
|
7686
|
+
return Math.max(MIN_WORKER_COUNT, Math.min(MAX_WORKER_COUNT, detectCpuCount() - 1));
|
|
7687
|
+
}
|
|
7688
|
+
function resolveRequestThreadCount(requested) {
|
|
7689
|
+
if (detectCpuCount() < MIN_CORES_FOR_SEVERAL_REQUEST_THREADS) return 1;
|
|
7690
|
+
return resolveWorkerCount(requested);
|
|
7691
|
+
}
|
|
7692
|
+
function splitWorkerBudget(total) {
|
|
7693
|
+
const keyword = Math.ceil(total / 2);
|
|
7694
|
+
return { keyword, vector: total - keyword };
|
|
9613
7695
|
}
|
|
9614
7696
|
function createWorkerPool(config) {
|
|
9615
7697
|
const workerCount = resolveWorkerCount(config.count);
|
|
@@ -9618,29 +7700,61 @@ function createWorkerPool(config) {
|
|
|
9618
7700
|
const indexAssignment = /* @__PURE__ */ new Map();
|
|
9619
7701
|
let isShutdown = false;
|
|
9620
7702
|
function handleWorkerDeath(slotIndex, error) {
|
|
9621
|
-
if (isShutdown
|
|
7703
|
+
if (isShutdown) {
|
|
9622
7704
|
return;
|
|
9623
7705
|
}
|
|
9624
|
-
deadSlots.add(slotIndex);
|
|
9625
7706
|
const slot = workers.get(slotIndex);
|
|
7707
|
+
if (slot !== void 0 && !slot.serving) {
|
|
7708
|
+
workers.delete(slotIndex);
|
|
7709
|
+
return;
|
|
7710
|
+
}
|
|
7711
|
+
if (deadSlots.has(slotIndex)) {
|
|
7712
|
+
return;
|
|
7713
|
+
}
|
|
7714
|
+
deadSlots.add(slotIndex);
|
|
9626
7715
|
const indexNames = slot ? [...slot.indexes].sort() : [];
|
|
9627
7716
|
workers.delete(slotIndex);
|
|
9628
7717
|
config.onWorkerCrash?.(slotIndex, indexNames, error);
|
|
9629
7718
|
}
|
|
9630
|
-
function
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
indexes: /* @__PURE__ */ new Set()
|
|
9639
|
-
};
|
|
9640
|
-
workers.set(slotIndex, slot);
|
|
9641
|
-
}
|
|
7719
|
+
function spawnSlot(slotIndex, serving) {
|
|
7720
|
+
const slot = {
|
|
7721
|
+
executor: config.workerFactory(slotIndex, (error) => handleWorkerDeath(slotIndex, error)),
|
|
7722
|
+
indexes: /* @__PURE__ */ new Set(),
|
|
7723
|
+
inFlight: 0,
|
|
7724
|
+
serving
|
|
7725
|
+
};
|
|
7726
|
+
workers.set(slotIndex, slot);
|
|
9642
7727
|
return slot;
|
|
9643
7728
|
}
|
|
7729
|
+
function ensureWorker(slotIndex) {
|
|
7730
|
+
const slot = workers.get(slotIndex);
|
|
7731
|
+
if (slot !== void 0) return slot;
|
|
7732
|
+
if (deadSlots.has(slotIndex)) return void 0;
|
|
7733
|
+
return spawnSlot(slotIndex, true);
|
|
7734
|
+
}
|
|
7735
|
+
function deadWorkerIds() {
|
|
7736
|
+
return [...deadSlots].filter((slotIndex) => !workers.has(slotIndex)).sort((a, b) => a - b);
|
|
7737
|
+
}
|
|
7738
|
+
function spawnReplacement(slotIndex) {
|
|
7739
|
+
if (isShutdown || !deadSlots.has(slotIndex) || workers.has(slotIndex)) return null;
|
|
7740
|
+
const slot = spawnSlot(slotIndex, false);
|
|
7741
|
+
return {
|
|
7742
|
+
workerId: slotIndex,
|
|
7743
|
+
executor: slot.executor,
|
|
7744
|
+
hold(indexName) {
|
|
7745
|
+
slot.indexes.add(indexName);
|
|
7746
|
+
},
|
|
7747
|
+
admit() {
|
|
7748
|
+
if (workers.get(slotIndex) !== slot) return;
|
|
7749
|
+
deadSlots.delete(slotIndex);
|
|
7750
|
+
slot.serving = true;
|
|
7751
|
+
},
|
|
7752
|
+
abandon() {
|
|
7753
|
+
if (workers.get(slotIndex) === slot) workers.delete(slotIndex);
|
|
7754
|
+
void slot.executor.shutdown().catch(() => void 0);
|
|
7755
|
+
}
|
|
7756
|
+
};
|
|
7757
|
+
}
|
|
9644
7758
|
function assignSlot(indexName) {
|
|
9645
7759
|
const preferred = fnv1a(indexName) % workerCount;
|
|
9646
7760
|
for (let probe = 0; probe < workerCount; probe++) {
|
|
@@ -9679,11 +7793,11 @@ function createWorkerPool(config) {
|
|
|
9679
7793
|
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index "${indexName}" is not registered in the worker pool`);
|
|
9680
7794
|
}
|
|
9681
7795
|
const slot = workers.get(slotIndex);
|
|
9682
|
-
if (slot) {
|
|
7796
|
+
if (slot?.serving) {
|
|
9683
7797
|
return slot.executor;
|
|
9684
7798
|
}
|
|
9685
7799
|
for (const survivor of workers.values()) {
|
|
9686
|
-
if (survivor.indexes.has(indexName)) {
|
|
7800
|
+
if (survivor.serving && survivor.indexes.has(indexName)) {
|
|
9687
7801
|
return survivor.executor;
|
|
9688
7802
|
}
|
|
9689
7803
|
}
|
|
@@ -9706,6 +7820,7 @@ function createWorkerPool(config) {
|
|
|
9706
7820
|
workerId: indices[n],
|
|
9707
7821
|
heapUsed: toFinite(report?.heapUsed),
|
|
9708
7822
|
heapTotal: toFinite(report?.heapTotal),
|
|
7823
|
+
heapLimit: toHeapLimit(report?.heapLimit),
|
|
9709
7824
|
external: toFinite(report?.external)
|
|
9710
7825
|
});
|
|
9711
7826
|
}
|
|
@@ -9728,6 +7843,57 @@ function createWorkerPool(config) {
|
|
|
9728
7843
|
function getAllExecutors() {
|
|
9729
7844
|
return [...workers.values()].map((slot) => slot.executor);
|
|
9730
7845
|
}
|
|
7846
|
+
function executorEntries() {
|
|
7847
|
+
return [...workers].map(([workerId, slot]) => ({ workerId, executor: slot.executor }));
|
|
7848
|
+
}
|
|
7849
|
+
function executorsHolding(indexName) {
|
|
7850
|
+
const holders = [];
|
|
7851
|
+
for (const slot of workers.values()) {
|
|
7852
|
+
if (slot.indexes.has(indexName)) holders.push(slot.executor);
|
|
7853
|
+
}
|
|
7854
|
+
return holders;
|
|
7855
|
+
}
|
|
7856
|
+
function lease(workerId, slot) {
|
|
7857
|
+
slot.inFlight += 1;
|
|
7858
|
+
let released = false;
|
|
7859
|
+
return {
|
|
7860
|
+
workerId,
|
|
7861
|
+
executor: slot.executor,
|
|
7862
|
+
release() {
|
|
7863
|
+
if (released) return;
|
|
7864
|
+
released = true;
|
|
7865
|
+
slot.inFlight -= 1;
|
|
7866
|
+
}
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
7869
|
+
function leaseLeastBusy() {
|
|
7870
|
+
let chosenId = -1;
|
|
7871
|
+
let chosen = null;
|
|
7872
|
+
for (const [workerId, slot] of workers) {
|
|
7873
|
+
if (!slot.serving) continue;
|
|
7874
|
+
if (chosen === null || slot.inFlight < chosen.inFlight) {
|
|
7875
|
+
chosenId = workerId;
|
|
7876
|
+
chosen = slot;
|
|
7877
|
+
}
|
|
7878
|
+
if (chosen.inFlight === 0) break;
|
|
7879
|
+
}
|
|
7880
|
+
return chosen === null ? null : lease(chosenId, chosen);
|
|
7881
|
+
}
|
|
7882
|
+
function queriesInFlight() {
|
|
7883
|
+
let total = 0;
|
|
7884
|
+
for (const slot of workers.values()) {
|
|
7885
|
+
if (slot.serving) total += slot.inFlight;
|
|
7886
|
+
}
|
|
7887
|
+
return total;
|
|
7888
|
+
}
|
|
7889
|
+
function leaseIdle(limit) {
|
|
7890
|
+
const leases = [];
|
|
7891
|
+
for (const [workerId, slot] of workers) {
|
|
7892
|
+
if (leases.length >= limit) break;
|
|
7893
|
+
if (slot.serving && slot.inFlight === 0) leases.push(lease(workerId, slot));
|
|
7894
|
+
}
|
|
7895
|
+
return leases;
|
|
7896
|
+
}
|
|
9731
7897
|
function addIndexToAll(indexName) {
|
|
9732
7898
|
if (isShutdown) {
|
|
9733
7899
|
throw new NarsilError(ErrorCodes.WORKER_CRASHED, "Worker pool has been shut down");
|
|
@@ -9737,9 +7903,23 @@ function createWorkerPool(config) {
|
|
|
9737
7903
|
}
|
|
9738
7904
|
indexAssignment.set(indexName, assignSlot(indexName));
|
|
9739
7905
|
}
|
|
7906
|
+
function spawnAll() {
|
|
7907
|
+
if (isShutdown) {
|
|
7908
|
+
throw new NarsilError(ErrorCodes.WORKER_CRASHED, "Worker pool has been shut down");
|
|
7909
|
+
}
|
|
7910
|
+
for (let i = 0; i < workerCount; i++) ensureWorker(i);
|
|
7911
|
+
}
|
|
9740
7912
|
return {
|
|
9741
7913
|
getExecutor,
|
|
9742
7914
|
getAllExecutors,
|
|
7915
|
+
executorEntries,
|
|
7916
|
+
executorsHolding,
|
|
7917
|
+
leaseLeastBusy,
|
|
7918
|
+
leaseIdle,
|
|
7919
|
+
queriesInFlight,
|
|
7920
|
+
spawnAll,
|
|
7921
|
+
deadWorkerIds,
|
|
7922
|
+
spawnReplacement,
|
|
9743
7923
|
get workerCount() {
|
|
9744
7924
|
return workerCount;
|
|
9745
7925
|
},
|
|
@@ -9751,6 +7931,9 @@ function createWorkerPool(config) {
|
|
|
9751
7931
|
};
|
|
9752
7932
|
}
|
|
9753
7933
|
|
|
7934
|
+
// src/workers/resource-limits.ts
|
|
7935
|
+
var WORKER_RESOURCE_LIMITS = { maxYoungGenerationSizeMb: WORKER_YOUNG_GENERATION_MB };
|
|
7936
|
+
|
|
9754
7937
|
// src/vector/search-pool/spawn.ts
|
|
9755
7938
|
function resolveWorkerEntryPoint2() {
|
|
9756
7939
|
const base = import.meta.url;
|
|
@@ -9764,7 +7947,7 @@ async function spawnWorker2(entryPoint) {
|
|
|
9764
7947
|
const runtime = detectRuntime();
|
|
9765
7948
|
if (runtime.supportsWorkerThreads) {
|
|
9766
7949
|
try {
|
|
9767
|
-
return await spawnNodeWorker(new URL(entryPoint));
|
|
7950
|
+
return await spawnNodeWorker(new URL(entryPoint), WORKER_RESOURCE_LIMITS);
|
|
9768
7951
|
} catch {
|
|
9769
7952
|
return null;
|
|
9770
7953
|
}
|
|
@@ -9799,8 +7982,6 @@ function listenForFailure(worker, handler) {
|
|
|
9799
7982
|
}
|
|
9800
7983
|
|
|
9801
7984
|
// src/vector/search-pool/index.ts
|
|
9802
|
-
var SEARCH_TIMEOUT_MS = 3e4;
|
|
9803
|
-
var LOAD_TIMEOUT_MS = 3e5;
|
|
9804
7985
|
async function createVectorSearchPool(requestedCount) {
|
|
9805
7986
|
const entryPoint = resolveWorkerEntryPoint2();
|
|
9806
7987
|
const count = resolveWorkerCount(requestedCount);
|
|
@@ -9832,22 +8013,22 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9832
8013
|
if (slots.length === 0) return null;
|
|
9833
8014
|
let requestCounter = 0;
|
|
9834
8015
|
function send(slot, key, message, timeoutMs) {
|
|
9835
|
-
return new Promise((resolve,
|
|
8016
|
+
return new Promise((resolve, reject) => {
|
|
9836
8017
|
if (!slot.alive) {
|
|
9837
|
-
|
|
8018
|
+
reject(new Error("Vector search worker is not running"));
|
|
9838
8019
|
return;
|
|
9839
8020
|
}
|
|
9840
8021
|
const timer = setTimeout(() => {
|
|
9841
8022
|
slot.pending.delete(key);
|
|
9842
|
-
|
|
8023
|
+
reject(new Error(`Vector search worker did not answer within ${timeoutMs}ms`));
|
|
9843
8024
|
}, timeoutMs);
|
|
9844
|
-
slot.pending.set(key, { resolve, reject
|
|
8025
|
+
slot.pending.set(key, { resolve, reject, timer });
|
|
9845
8026
|
try {
|
|
9846
8027
|
slot.worker.postMessage(message);
|
|
9847
8028
|
} catch (err) {
|
|
9848
8029
|
slot.pending.delete(key);
|
|
9849
8030
|
clearTimeout(timer);
|
|
9850
|
-
|
|
8031
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
9851
8032
|
}
|
|
9852
8033
|
});
|
|
9853
8034
|
}
|
|
@@ -9863,7 +8044,7 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9863
8044
|
async function sendSearch(slot, request) {
|
|
9864
8045
|
slot.outstanding += 1;
|
|
9865
8046
|
try {
|
|
9866
|
-
return await send(slot, request.requestId, request,
|
|
8047
|
+
return await send(slot, request.requestId, request, VECTOR_SEARCH_TIMEOUT_MS);
|
|
9867
8048
|
} finally {
|
|
9868
8049
|
slot.outstanding -= 1;
|
|
9869
8050
|
}
|
|
@@ -9880,7 +8061,7 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9880
8061
|
slots.map((slot) => {
|
|
9881
8062
|
requestCounter += 1;
|
|
9882
8063
|
const requestId = `${requestCounter}`;
|
|
9883
|
-
return send(slot, requestId, { type: "load", requestId, handle, snapshot },
|
|
8064
|
+
return send(slot, requestId, { type: "load", requestId, handle, snapshot }, VECTOR_SEARCH_LOAD_TIMEOUT_MS);
|
|
9884
8065
|
})
|
|
9885
8066
|
);
|
|
9886
8067
|
return outcomes.every(
|
|
@@ -9896,7 +8077,7 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9896
8077
|
slot,
|
|
9897
8078
|
requestId,
|
|
9898
8079
|
{ type: "loadShared", requestId, handle, scratchSlot, snapshot },
|
|
9899
|
-
|
|
8080
|
+
VECTOR_SEARCH_LOAD_TIMEOUT_MS
|
|
9900
8081
|
);
|
|
9901
8082
|
})
|
|
9902
8083
|
);
|
|
@@ -9909,7 +8090,7 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9909
8090
|
slots.map((slot) => {
|
|
9910
8091
|
requestCounter += 1;
|
|
9911
8092
|
const requestId = `${requestCounter}`;
|
|
9912
|
-
return send(slot, requestId, { type: "drop", requestId, handle },
|
|
8093
|
+
return send(slot, requestId, { type: "drop", requestId, handle }, VECTOR_SEARCH_TIMEOUT_MS);
|
|
9913
8094
|
})
|
|
9914
8095
|
);
|
|
9915
8096
|
},
|
|
@@ -9977,11 +8158,11 @@ async function createVectorSearchPool(requestedCount) {
|
|
|
9977
8158
|
var sharedPool = null;
|
|
9978
8159
|
var poolHolders = 0;
|
|
9979
8160
|
var workersUnavailable = false;
|
|
9980
|
-
function acquireVectorSearchPool() {
|
|
8161
|
+
function acquireVectorSearchPool(requestedCount) {
|
|
9981
8162
|
poolHolders += 1;
|
|
9982
8163
|
if (workersUnavailable) return Promise.resolve(null);
|
|
9983
8164
|
if (sharedPool === null) {
|
|
9984
|
-
sharedPool = createVectorSearchPool().then(
|
|
8165
|
+
sharedPool = createVectorSearchPool(requestedCount).then(
|
|
9985
8166
|
(pool) => {
|
|
9986
8167
|
if (pool === null) workersUnavailable = true;
|
|
9987
8168
|
return pool;
|
|
@@ -10004,9 +8185,52 @@ async function releaseVectorSearchPool() {
|
|
|
10004
8185
|
await pool?.shutdown();
|
|
10005
8186
|
}
|
|
10006
8187
|
|
|
8188
|
+
// src/vector/shared-generation/doc-ids.ts
|
|
8189
|
+
var ABSENT_PARTITION = -1;
|
|
8190
|
+
function buildSharedDocIdTable(store, slots) {
|
|
8191
|
+
const encoder3 = new TextEncoder();
|
|
8192
|
+
const encoded = new Array(slots);
|
|
8193
|
+
let totalBytes = 0;
|
|
8194
|
+
for (let ordinal = 0; ordinal < slots; ordinal++) {
|
|
8195
|
+
const docId = store.docIdForOrdinal(ordinal);
|
|
8196
|
+
if (docId === void 0) {
|
|
8197
|
+
encoded[ordinal] = null;
|
|
8198
|
+
continue;
|
|
8199
|
+
}
|
|
8200
|
+
const bytes = encoder3.encode(docId);
|
|
8201
|
+
encoded[ordinal] = bytes;
|
|
8202
|
+
totalBytes += bytes.length;
|
|
8203
|
+
}
|
|
8204
|
+
const table = {
|
|
8205
|
+
bytes: new Uint8Array(new SharedArrayBuffer(totalBytes)),
|
|
8206
|
+
offsets: new Uint32Array(new SharedArrayBuffer((slots + 1) * 4)),
|
|
8207
|
+
partitions: new Int32Array(new SharedArrayBuffer(slots * 4))
|
|
8208
|
+
};
|
|
8209
|
+
let cursor = 0;
|
|
8210
|
+
for (let ordinal = 0; ordinal < slots; ordinal++) {
|
|
8211
|
+
table.offsets[ordinal] = cursor;
|
|
8212
|
+
const bytes = encoded[ordinal];
|
|
8213
|
+
if (bytes === null) {
|
|
8214
|
+
table.partitions[ordinal] = ABSENT_PARTITION;
|
|
8215
|
+
continue;
|
|
8216
|
+
}
|
|
8217
|
+
table.bytes.set(bytes, cursor);
|
|
8218
|
+
cursor += bytes.length;
|
|
8219
|
+
table.partitions[ordinal] = store.partitionOfOrdinal(ordinal) ?? ABSENT_PARTITION;
|
|
8220
|
+
}
|
|
8221
|
+
table.offsets[slots] = cursor;
|
|
8222
|
+
return table;
|
|
8223
|
+
}
|
|
8224
|
+
var decoder3 = new TextDecoder();
|
|
8225
|
+
function docIdAt(table, ordinal) {
|
|
8226
|
+
if (ordinal < 0 || ordinal >= table.partitions.length) return void 0;
|
|
8227
|
+
const start = table.offsets[ordinal];
|
|
8228
|
+
const end = table.offsets[ordinal + 1];
|
|
8229
|
+
if (start === end) return void 0;
|
|
8230
|
+
return decoder3.decode(table.bytes.subarray(start, end));
|
|
8231
|
+
}
|
|
8232
|
+
|
|
10007
8233
|
// src/vector/shared-generation/layout.ts
|
|
10008
|
-
var WASM_PAGE_BYTES = 65536;
|
|
10009
|
-
var MAX_WASM_PAGES = 65536;
|
|
10010
8234
|
var SCRATCH_ALIGNMENT = 16;
|
|
10011
8235
|
function alignUp(bytes) {
|
|
10012
8236
|
return Math.ceil(bytes / SCRATCH_ALIGNMENT) * SCRATCH_ALIGNMENT;
|
|
@@ -10129,13 +8353,62 @@ function invalidateWorkerCopies(state) {
|
|
|
10129
8353
|
if (state.workerCopyHandle === null) return;
|
|
10130
8354
|
const handle = state.workerCopyHandle;
|
|
10131
8355
|
const pool = state.workerCopyPool;
|
|
8356
|
+
const hosted = state.workerCopyMode === "hosted";
|
|
10132
8357
|
state.workerCopyHandle = null;
|
|
10133
8358
|
state.workerCopyPool = null;
|
|
10134
8359
|
state.workerCopyRevision = -1;
|
|
10135
8360
|
state.workerCopyMode = null;
|
|
8361
|
+
if (hosted) {
|
|
8362
|
+
void state.workerCopies.host?.drop(state.indexName, state.fieldName, handle).catch(() => void 0);
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
10136
8365
|
void pool?.drop(handle).catch(() => void 0);
|
|
10137
8366
|
void releaseVectorSearchPool().catch(() => void 0);
|
|
10138
8367
|
}
|
|
8368
|
+
function refreshWorkerCopies(state) {
|
|
8369
|
+
invalidateWorkerCopies(state);
|
|
8370
|
+
scheduleWorkerCopyLoad(state);
|
|
8371
|
+
}
|
|
8372
|
+
async function loadHostedCopy(state, host) {
|
|
8373
|
+
const revision = state.revision;
|
|
8374
|
+
if (!host.holdsIndex(state.indexName)) return false;
|
|
8375
|
+
if (!state.store.partitionsKnown) {
|
|
8376
|
+
assignStorePartitions(state, (docId) => host.resolvePartition(state.indexName, docId));
|
|
8377
|
+
}
|
|
8378
|
+
const shared = freezeSharedGeneration(
|
|
8379
|
+
{
|
|
8380
|
+
dimension: state.dimension,
|
|
8381
|
+
store: state.store,
|
|
8382
|
+
hnsw: state.hnsw,
|
|
8383
|
+
quantizer: state.sq8,
|
|
8384
|
+
quantization: state.quantizationMode
|
|
8385
|
+
},
|
|
8386
|
+
host.scratchSlotCount
|
|
8387
|
+
);
|
|
8388
|
+
if (shared === null) return false;
|
|
8389
|
+
const docIds = buildSharedDocIdTable(state.store, state.store.slots);
|
|
8390
|
+
handleCounter += 1;
|
|
8391
|
+
const handle = `${state.indexName}/${state.fieldName}#${handleCounter}`;
|
|
8392
|
+
state.workerCopyHandle = handle;
|
|
8393
|
+
state.workerCopyRevision = revision;
|
|
8394
|
+
state.workerCopyMode = "hosted";
|
|
8395
|
+
let loaded = false;
|
|
8396
|
+
try {
|
|
8397
|
+
loaded = await host.loadShared(state.indexName, state.fieldName, handle, {
|
|
8398
|
+
snapshot: shared,
|
|
8399
|
+
docIds,
|
|
8400
|
+
filterThreshold: state.filterThreshold
|
|
8401
|
+
});
|
|
8402
|
+
} catch {
|
|
8403
|
+
loaded = false;
|
|
8404
|
+
}
|
|
8405
|
+
if (!loaded && state.workerCopyHandle === handle) {
|
|
8406
|
+
state.workerCopyHandle = null;
|
|
8407
|
+
state.workerCopyRevision = -1;
|
|
8408
|
+
state.workerCopyMode = null;
|
|
8409
|
+
}
|
|
8410
|
+
return !state.disposed && state.revision !== revision;
|
|
8411
|
+
}
|
|
10139
8412
|
function captureCloneSnapshot(state) {
|
|
10140
8413
|
if (!state.hnsw) return null;
|
|
10141
8414
|
return {
|
|
@@ -10148,18 +8421,27 @@ function captureCloneSnapshot(state) {
|
|
|
10148
8421
|
};
|
|
10149
8422
|
}
|
|
10150
8423
|
function scheduleWorkerCopyLoad(state) {
|
|
8424
|
+
if (!state.workerCopies.enabled) return;
|
|
10151
8425
|
if (state.disposed || state.workerCopyLoading || state.building) return;
|
|
10152
8426
|
if (state.workerCopyHandle !== null) return;
|
|
10153
8427
|
if (!state.hnsw || state.buffer.size > 0) return;
|
|
10154
|
-
|
|
8428
|
+
const host = state.workerCopies.host;
|
|
8429
|
+
if (host === void 0 && liveSize(state) < WORKER_COPY_MIN_VECTORS) return;
|
|
10155
8430
|
state.workerCopyLoading = true;
|
|
10156
|
-
void loadWorkerCopies(state).
|
|
10157
|
-
|
|
10158
|
-
|
|
8431
|
+
const loading = host === void 0 ? loadWorkerCopies(state).then(() => false) : loadHostedCopy(state, host);
|
|
8432
|
+
void loading.then(
|
|
8433
|
+
(superseded) => {
|
|
8434
|
+
state.workerCopyLoading = false;
|
|
8435
|
+
if (superseded) scheduleWorkerCopyLoad(state);
|
|
8436
|
+
},
|
|
8437
|
+
() => {
|
|
8438
|
+
state.workerCopyLoading = false;
|
|
8439
|
+
}
|
|
8440
|
+
);
|
|
10159
8441
|
}
|
|
10160
8442
|
async function loadWorkerCopies(state) {
|
|
10161
8443
|
const revision = state.revision;
|
|
10162
|
-
const pool = await acquireVectorSearchPool();
|
|
8444
|
+
const pool = await acquireVectorSearchPool(state.workerCopies.count);
|
|
10163
8445
|
if (pool === null) {
|
|
10164
8446
|
await releaseVectorSearchPool();
|
|
10165
8447
|
return;
|
|
@@ -10209,7 +8491,7 @@ async function loadWorkerCopies(state) {
|
|
|
10209
8491
|
async function searchViaWorkerCopies(state, query, k, metric, minSimilarity, efSearch, filter) {
|
|
10210
8492
|
const pool = state.workerCopyPool;
|
|
10211
8493
|
const handle = state.workerCopyHandle;
|
|
10212
|
-
if (pool === null || handle === null) return null;
|
|
8494
|
+
if (pool === null || handle === null || state.workerCopyMode === "hosted") return null;
|
|
10213
8495
|
if (state.workerCopyRevision !== state.revision) return null;
|
|
10214
8496
|
try {
|
|
10215
8497
|
if (state.workerCopyMode === "shared") {
|
|
@@ -10320,6 +8602,8 @@ function triggerBuild(state) {
|
|
|
10320
8602
|
state.pendingBuild = null;
|
|
10321
8603
|
if (state.buffer.size > 0) {
|
|
10322
8604
|
scheduleBuild(state);
|
|
8605
|
+
} else if (state.workerCopies.host !== void 0) {
|
|
8606
|
+
scheduleWorkerCopyLoad(state);
|
|
10323
8607
|
}
|
|
10324
8608
|
}
|
|
10325
8609
|
})();
|
|
@@ -10760,7 +9044,7 @@ function searchWithFilter(state, query, k, options, filter) {
|
|
|
10760
9044
|
}
|
|
10761
9045
|
|
|
10762
9046
|
// src/vector/vector-index/index.ts
|
|
10763
|
-
function createVectorIndex(fieldName, dimension, config) {
|
|
9047
|
+
function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_WORKER_COPIES_ALLOWED, indexName = "") {
|
|
10764
9048
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
10765
9049
|
throw new NarsilError(
|
|
10766
9050
|
ErrorCodes.VECTOR_DIMENSION_MISMATCH,
|
|
@@ -10776,6 +9060,7 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
10776
9060
|
const dimensionScale = dimension / 256;
|
|
10777
9061
|
const store = createVectorStore();
|
|
10778
9062
|
const state = {
|
|
9063
|
+
indexName,
|
|
10779
9064
|
fieldName,
|
|
10780
9065
|
dimension,
|
|
10781
9066
|
dimensionScale,
|
|
@@ -10783,6 +9068,7 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
10783
9068
|
filterThreshold,
|
|
10784
9069
|
quantizationMode,
|
|
10785
9070
|
hnswConfig,
|
|
9071
|
+
workerCopies,
|
|
10786
9072
|
store,
|
|
10787
9073
|
tombstones: /* @__PURE__ */ new Set(),
|
|
10788
9074
|
buffer: /* @__PURE__ */ new Set(),
|
|
@@ -10816,19 +9102,6 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
10816
9102
|
state.store.insert(docId, vector, partitionId);
|
|
10817
9103
|
state.buffer.add(docId);
|
|
10818
9104
|
}
|
|
10819
|
-
function assignPartitions(resolve) {
|
|
10820
|
-
for (let ordinal = 0; ordinal < state.store.slots; ordinal += 1) {
|
|
10821
|
-
if (state.store.partitionOfOrdinal(ordinal) !== void 0) continue;
|
|
10822
|
-
const docId = state.store.docIdForOrdinal(ordinal);
|
|
10823
|
-
if (docId === void 0) continue;
|
|
10824
|
-
const partitionId = resolve(docId);
|
|
10825
|
-
if (partitionId === void 0) {
|
|
10826
|
-
state.store.forgetPartition(docId);
|
|
10827
|
-
continue;
|
|
10828
|
-
}
|
|
10829
|
-
state.store.setPartition(docId, partitionId);
|
|
10830
|
-
}
|
|
10831
|
-
}
|
|
10832
9105
|
function remove(docId) {
|
|
10833
9106
|
if (!state.store.has(docId)) return;
|
|
10834
9107
|
invalidateWorkerCopies(state);
|
|
@@ -10898,12 +9171,13 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
10898
9171
|
insert,
|
|
10899
9172
|
remove,
|
|
10900
9173
|
partitionsKnown: () => state.store.partitionsKnown,
|
|
10901
|
-
assignPartitions,
|
|
9174
|
+
assignPartitions: (resolve) => assignStorePartitions(state, resolve),
|
|
10902
9175
|
scheduleBuild: () => scheduleBuild(state),
|
|
10903
9176
|
awaitPendingBuild,
|
|
10904
9177
|
dispose,
|
|
10905
9178
|
search: (query, k, options) => search(state, query, k, options),
|
|
10906
9179
|
searchParallel,
|
|
9180
|
+
refreshWorkerCopies: () => refreshWorkerCopies(state),
|
|
10907
9181
|
getVector,
|
|
10908
9182
|
has,
|
|
10909
9183
|
compact: () => {
|
|
@@ -10924,9 +9198,200 @@ function createVectorIndex(fieldName, dimension, config) {
|
|
|
10924
9198
|
};
|
|
10925
9199
|
}
|
|
10926
9200
|
|
|
9201
|
+
// src/workers/segment-actions.ts
|
|
9202
|
+
function isSegmentAction(action) {
|
|
9203
|
+
return action.type === "buildSegment" || action.type === "mergeSegments" || action.type === "attachSegments" || action.type === "compactSegments" || action.type === "swapSegments" || action.type === "freezeLiveTail";
|
|
9204
|
+
}
|
|
9205
|
+
function growPartitionsTo(manager, partitionId) {
|
|
9206
|
+
while (manager.partitionCount <= partitionId) {
|
|
9207
|
+
manager.addPartition();
|
|
9208
|
+
}
|
|
9209
|
+
}
|
|
9210
|
+
function requireComposite(entry, indexName, partitionId, verb) {
|
|
9211
|
+
const partition = entry.manager.getPartition(partitionId);
|
|
9212
|
+
if (!isCompositePartition(partition)) {
|
|
9213
|
+
throw new NarsilError(
|
|
9214
|
+
ErrorCodes.PARTITION_CORRUPTED,
|
|
9215
|
+
`Partition ${partitionId} of "${indexName}" holds no frozen segments to ${verb}`,
|
|
9216
|
+
{ indexName, partitionId }
|
|
9217
|
+
);
|
|
9218
|
+
}
|
|
9219
|
+
return partition;
|
|
9220
|
+
}
|
|
9221
|
+
function runSegmentAction(entry, action) {
|
|
9222
|
+
switch (action.type) {
|
|
9223
|
+
case "buildSegment": {
|
|
9224
|
+
const segment = createPartitionIndex(0, entry.config.trackPositions ?? true);
|
|
9225
|
+
const options = resolvePartitionInsertOptions(entry.config, entry.manager.analysis, action.options);
|
|
9226
|
+
segment.beginBatch();
|
|
9227
|
+
for (const doc of action.documents) {
|
|
9228
|
+
segment.insert(doc.docId, doc.document, entry.config.schema, entry.language, options);
|
|
9229
|
+
}
|
|
9230
|
+
segment.endBatch();
|
|
9231
|
+
return segment.encodeSegment();
|
|
9232
|
+
}
|
|
9233
|
+
case "mergeSegments": {
|
|
9234
|
+
for (const segment of action.segments) {
|
|
9235
|
+
if (segment.payload.docIds.some((docId) => entry.manager.has(docId))) {
|
|
9236
|
+
console.warn(
|
|
9237
|
+
`Skipping replicated segment for index "${action.indexName}": its documents already exist on this copy`
|
|
9238
|
+
);
|
|
9239
|
+
continue;
|
|
9240
|
+
}
|
|
9241
|
+
entry.manager.mergeSegment(segment.partitionId, segment.payload, segment.documents);
|
|
9242
|
+
}
|
|
9243
|
+
return void 0;
|
|
9244
|
+
}
|
|
9245
|
+
case "attachSegments": {
|
|
9246
|
+
for (const segment of action.segments) {
|
|
9247
|
+
growPartitionsTo(entry.manager, segment.partitionId);
|
|
9248
|
+
const frozen = createSharedFrozenSegment(segment.snapshot);
|
|
9249
|
+
for (const docId of segment.tombstonedDocIds ?? []) frozen.tombstoneDocument(docId);
|
|
9250
|
+
entry.manager.attachFrozenSegment(segment.partitionId, frozen);
|
|
9251
|
+
}
|
|
9252
|
+
return void 0;
|
|
9253
|
+
}
|
|
9254
|
+
case "compactSegments": {
|
|
9255
|
+
const partition = requireComposite(entry, action.indexName, action.partitionId, "compact");
|
|
9256
|
+
const segments = partition.frozenSegmentsById(action.segmentIds);
|
|
9257
|
+
const { payload, documents } = buildCompactedSegmentPayload(segments);
|
|
9258
|
+
return freezeSegmentShared(payload, documents);
|
|
9259
|
+
}
|
|
9260
|
+
case "swapSegments": {
|
|
9261
|
+
const partition = requireComposite(entry, action.indexName, action.partitionId, "swap");
|
|
9262
|
+
partition.swapFrozenSegments(action.dropSegmentIds, createSharedFrozenSegment(action.snapshot));
|
|
9263
|
+
return void 0;
|
|
9264
|
+
}
|
|
9265
|
+
case "freezeLiveTail": {
|
|
9266
|
+
growPartitionsTo(entry.manager, action.partitionId);
|
|
9267
|
+
const tail = entry.manager.getPartition(action.partitionId);
|
|
9268
|
+
const held = isCompositePartition(tail) ? tail.live.count() : tail.count();
|
|
9269
|
+
if (held !== action.snapshot.documentCount) {
|
|
9270
|
+
console.warn(
|
|
9271
|
+
`Partition ${action.partitionId} of "${action.indexName}" held ${held} live documents where the frozen tail holds ${action.snapshot.documentCount}`
|
|
9272
|
+
);
|
|
9273
|
+
}
|
|
9274
|
+
entry.manager.replaceLiveTail(action.partitionId, createSharedFrozenSegment(action.snapshot));
|
|
9275
|
+
return void 0;
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
|
|
9280
|
+
// src/vector/shared-generation/searcher.ts
|
|
9281
|
+
function bruteForceOrdinals(state, rankByOrdinal, query, k, metric, minSimilarity, filter) {
|
|
9282
|
+
const arenaQuery = state.store.prepareQueryArena(query);
|
|
9283
|
+
const queryMagnitude = arenaQuery ? arenaQuery.magnitude : magnitude(query);
|
|
9284
|
+
const bestFirst = (a, b) => b.score - a.score || rankByOrdinal[a.ord] - rankByOrdinal[b.ord];
|
|
9285
|
+
const heap = createBoundedMaxHeap(bestFirst, k);
|
|
9286
|
+
for (const ord of ordinalFilterValues(filter)) {
|
|
9287
|
+
if (ord >= rankByOrdinal.length || state.tombstones[ord] === 1) continue;
|
|
9288
|
+
let distance;
|
|
9289
|
+
if (arenaQuery) {
|
|
9290
|
+
distance = state.store.distanceFromArena(arenaQuery, ord, metric);
|
|
9291
|
+
if (distance === Number.POSITIVE_INFINITY) continue;
|
|
9292
|
+
} else {
|
|
9293
|
+
const entry = entryForOrd(state, ord);
|
|
9294
|
+
if (!entry) continue;
|
|
9295
|
+
distance = toDistance(query, entry.vector, queryMagnitude, entry.magnitude, metric);
|
|
9296
|
+
}
|
|
9297
|
+
const score = toScore(distance, metric);
|
|
9298
|
+
if (score >= minSimilarity) heap.push({ ord, score });
|
|
9299
|
+
}
|
|
9300
|
+
return heap.toSortedArray().reverse();
|
|
9301
|
+
}
|
|
9302
|
+
function ordinalsByDocId(docIds) {
|
|
9303
|
+
const ordinals = /* @__PURE__ */ new Map();
|
|
9304
|
+
for (let ordinal = 0; ordinal < docIds.partitions.length; ordinal++) {
|
|
9305
|
+
const docId = docIdAt(docIds, ordinal);
|
|
9306
|
+
if (docId !== void 0) ordinals.set(docId, ordinal);
|
|
9307
|
+
}
|
|
9308
|
+
return ordinals;
|
|
9309
|
+
}
|
|
9310
|
+
function createSharedVectorSearcher(options) {
|
|
9311
|
+
const { fieldName, snapshot, scratchSlot, docIds, holdsDocument } = options;
|
|
9312
|
+
const filterThreshold = options.filterThreshold ?? DEFAULT_FILTER_THRESHOLD;
|
|
9313
|
+
const copy = openSharedWorkerCopy(snapshot, scratchSlot);
|
|
9314
|
+
const slots = docIds.partitions.length;
|
|
9315
|
+
let ordinalIndex = null;
|
|
9316
|
+
function hitsFor(query, k, searchOptions, filter) {
|
|
9317
|
+
const { metric, minSimilarity, efSearch } = searchOptions;
|
|
9318
|
+
const liveSize2 = copy.searchState.nodeCount - copy.searchState.tombstoneCount;
|
|
9319
|
+
if (filter !== void 0 && liveSize2 > 0 && filter.count / liveSize2 < filterThreshold) {
|
|
9320
|
+
return bruteForceOrdinals(copy.searchState, copy.rankByOrdinal, query, k, metric, minSimilarity, filter);
|
|
9321
|
+
}
|
|
9322
|
+
return searchOrdinals(copy.searchState, query, k, metric, minSimilarity, copy.rankByOrdinal, filter, efSearch);
|
|
9323
|
+
}
|
|
9324
|
+
function filterForDocIds(allowed) {
|
|
9325
|
+
if (ordinalIndex === null) ordinalIndex = ordinalsByDocId(docIds);
|
|
9326
|
+
const filter = createOrdinalFilter(slots);
|
|
9327
|
+
for (const docId of allowed) {
|
|
9328
|
+
const ordinal = ordinalIndex.get(docId);
|
|
9329
|
+
if (ordinal !== void 0) addToOrdinalFilter(filter, ordinal);
|
|
9330
|
+
}
|
|
9331
|
+
return filter;
|
|
9332
|
+
}
|
|
9333
|
+
function filterForPartitions(allowed) {
|
|
9334
|
+
const filter = createOrdinalFilter(slots);
|
|
9335
|
+
for (let ordinal = 0; ordinal < slots; ordinal++) {
|
|
9336
|
+
if (allowed.has(docIds.partitions[ordinal])) addToOrdinalFilter(filter, ordinal);
|
|
9337
|
+
}
|
|
9338
|
+
return filter;
|
|
9339
|
+
}
|
|
9340
|
+
function filterFor(searchOptions) {
|
|
9341
|
+
if (searchOptions.filterDocIds !== void 0) return filterForDocIds(searchOptions.filterDocIds);
|
|
9342
|
+
if (searchOptions.filterPartitions !== void 0) return filterForPartitions(searchOptions.filterPartitions);
|
|
9343
|
+
return void 0;
|
|
9344
|
+
}
|
|
9345
|
+
return {
|
|
9346
|
+
fieldName,
|
|
9347
|
+
dimension: snapshot.dimension,
|
|
9348
|
+
partitionsKnown: () => true,
|
|
9349
|
+
assignPartitions: () => void 0,
|
|
9350
|
+
searchParallel(query, k, searchOptions) {
|
|
9351
|
+
const filter = filterFor(searchOptions);
|
|
9352
|
+
if (filter !== void 0 && filter.count === 0) return Promise.resolve([]);
|
|
9353
|
+
const hits = hitsFor(query, k, searchOptions, filter);
|
|
9354
|
+
const results = [];
|
|
9355
|
+
for (const hit of hits) {
|
|
9356
|
+
const docId = docIdAt(docIds, hit.ord);
|
|
9357
|
+
if (docId === void 0 || !holdsDocument(docId)) continue;
|
|
9358
|
+
results.push({ docId, score: hit.score });
|
|
9359
|
+
}
|
|
9360
|
+
return Promise.resolve(results);
|
|
9361
|
+
}
|
|
9362
|
+
};
|
|
9363
|
+
}
|
|
9364
|
+
|
|
9365
|
+
// src/workers/vector-copies.ts
|
|
9366
|
+
function createHeldVectorCopies() {
|
|
9367
|
+
return { searchers: /* @__PURE__ */ new Map(), handles: /* @__PURE__ */ new Map() };
|
|
9368
|
+
}
|
|
9369
|
+
function loadHeldVectorCopy(held, manager, fieldName, handle, copy, scratchSlot) {
|
|
9370
|
+
held.searchers.set(
|
|
9371
|
+
fieldName,
|
|
9372
|
+
createSharedVectorSearcher({
|
|
9373
|
+
fieldName,
|
|
9374
|
+
snapshot: copy.snapshot,
|
|
9375
|
+
scratchSlot,
|
|
9376
|
+
docIds: copy.docIds,
|
|
9377
|
+
filterThreshold: copy.filterThreshold,
|
|
9378
|
+
holdsDocument: (docId) => manager.has(docId)
|
|
9379
|
+
})
|
|
9380
|
+
);
|
|
9381
|
+
held.handles.set(fieldName, handle);
|
|
9382
|
+
}
|
|
9383
|
+
function dropHeldVectorCopy(held, fieldName, handle) {
|
|
9384
|
+
if (held.handles.get(fieldName) !== handle) return;
|
|
9385
|
+
held.searchers.delete(fieldName);
|
|
9386
|
+
held.handles.delete(fieldName);
|
|
9387
|
+
}
|
|
9388
|
+
|
|
10927
9389
|
// src/workers/direct-executor.ts
|
|
10928
|
-
|
|
9390
|
+
var NO_VECTOR_WORKER_COPIES = { enabled: false };
|
|
9391
|
+
function createDirectExecutor(options) {
|
|
10929
9392
|
const indexes = /* @__PURE__ */ new Map();
|
|
9393
|
+
const vectorWorkerCopies = options?.vectorWorkerCopies ?? NO_VECTOR_WORKER_COPIES;
|
|
9394
|
+
const scratchSlot = options?.scratchSlot ?? 0;
|
|
10930
9395
|
function requireIndex(indexName) {
|
|
10931
9396
|
const entry = indexes.get(indexName);
|
|
10932
9397
|
if (!entry) {
|
|
@@ -10936,7 +9401,17 @@ function createDirectExecutor() {
|
|
|
10936
9401
|
}
|
|
10937
9402
|
return entry;
|
|
10938
9403
|
}
|
|
10939
|
-
function
|
|
9404
|
+
function vectorIndexesFor(indexName, config) {
|
|
9405
|
+
const vectorIndexes = /* @__PURE__ */ new Map();
|
|
9406
|
+
for (const [fieldPath, dim] of extractVectorFieldsFromSchema(config.schema)) {
|
|
9407
|
+
vectorIndexes.set(
|
|
9408
|
+
fieldPath,
|
|
9409
|
+
createVectorIndex(fieldPath, dim, config.vectorPromotion, vectorWorkerCopies, indexName)
|
|
9410
|
+
);
|
|
9411
|
+
}
|
|
9412
|
+
return vectorIndexes;
|
|
9413
|
+
}
|
|
9414
|
+
function createIndex(indexName, config, language, analysisStale = false) {
|
|
10940
9415
|
if (indexes.has(indexName)) {
|
|
10941
9416
|
throw new NarsilError(ErrorCodes.INDEX_ALREADY_EXISTS, `Index "${indexName}" already exists`, {
|
|
10942
9417
|
indexName
|
|
@@ -10944,11 +9419,7 @@ function createDirectExecutor() {
|
|
|
10944
9419
|
}
|
|
10945
9420
|
const router = createPartitionRouter();
|
|
10946
9421
|
const partitionCount = config.partitions?.maxPartitions ?? 1;
|
|
10947
|
-
const
|
|
10948
|
-
const vectorIndexes = /* @__PURE__ */ new Map();
|
|
10949
|
-
for (const [fieldPath, dim] of vectorFields) {
|
|
10950
|
-
vectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, config.vectorPromotion));
|
|
10951
|
-
}
|
|
9422
|
+
const vectorIndexes = vectorIndexesFor(indexName, config);
|
|
10952
9423
|
const manager = createPartitionManager(indexName, config, language, router, partitionCount, vectorIndexes);
|
|
10953
9424
|
indexes.set(indexName, {
|
|
10954
9425
|
manager,
|
|
@@ -10959,7 +9430,9 @@ function createDirectExecutor() {
|
|
|
10959
9430
|
stopWords: manager.analysis.stopWords,
|
|
10960
9431
|
customTokenizer: manager.analysis.customTokenizer
|
|
10961
9432
|
},
|
|
10962
|
-
vectorIndexes
|
|
9433
|
+
vectorIndexes,
|
|
9434
|
+
vectorCopies: createHeldVectorCopies(),
|
|
9435
|
+
analysisStale
|
|
10963
9436
|
});
|
|
10964
9437
|
}
|
|
10965
9438
|
function dropIndex(indexName) {
|
|
@@ -10978,7 +9451,21 @@ function createDirectExecutor() {
|
|
|
10978
9451
|
function getManager(indexName) {
|
|
10979
9452
|
return indexes.get(indexName)?.manager;
|
|
10980
9453
|
}
|
|
9454
|
+
function queryContextOf(indexName) {
|
|
9455
|
+
const entry = indexes.get(indexName);
|
|
9456
|
+
if (entry === void 0) return void 0;
|
|
9457
|
+
return {
|
|
9458
|
+
manager: entry.manager,
|
|
9459
|
+
config: entry.config,
|
|
9460
|
+
language: entry.language,
|
|
9461
|
+
vectorSearchers: entry.vectorCopies.searchers,
|
|
9462
|
+
analysisStale: entry.analysisStale
|
|
9463
|
+
};
|
|
9464
|
+
}
|
|
10981
9465
|
async function execute(action) {
|
|
9466
|
+
if (isSegmentAction(action)) {
|
|
9467
|
+
return runSegmentAction(requireIndex(action.indexName), action);
|
|
9468
|
+
}
|
|
10982
9469
|
switch (action.type) {
|
|
10983
9470
|
case "bootstrap": {
|
|
10984
9471
|
throw new NarsilError(
|
|
@@ -10987,79 +9474,35 @@ function createDirectExecutor() {
|
|
|
10987
9474
|
{ moduleUrl: action.moduleUrl }
|
|
10988
9475
|
);
|
|
10989
9476
|
}
|
|
9477
|
+
case "serveRequests":
|
|
9478
|
+
case "stopServing": {
|
|
9479
|
+
throw new NarsilError(
|
|
9480
|
+
ErrorCodes.CONFIG_INVALID,
|
|
9481
|
+
"Only a worker thread serves requests, and the thread that owns this executor receives them itself"
|
|
9482
|
+
);
|
|
9483
|
+
}
|
|
10990
9484
|
case "createIndex": {
|
|
10991
9485
|
const language = getLanguage(action.config.language ?? "english");
|
|
10992
|
-
createIndex(action.indexName, action.config, language);
|
|
9486
|
+
createIndex(action.indexName, action.config, language, action.analysisStale ?? false);
|
|
10993
9487
|
return void 0;
|
|
10994
9488
|
}
|
|
10995
9489
|
case "dropIndex": {
|
|
10996
9490
|
dropIndex(action.indexName);
|
|
10997
9491
|
return void 0;
|
|
10998
9492
|
}
|
|
10999
|
-
case "
|
|
11000
|
-
const entry = requireIndex(action.indexName);
|
|
11001
|
-
entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
|
|
11002
|
-
return void 0;
|
|
11003
|
-
}
|
|
11004
|
-
case "buildSegment": {
|
|
11005
|
-
const entry = requireIndex(action.indexName);
|
|
11006
|
-
const segment = createPartitionIndex(0, entry.config.trackPositions ?? true);
|
|
11007
|
-
const options = resolvePartitionInsertOptions(entry.config, entry.manager.analysis, action.options);
|
|
11008
|
-
segment.beginBatch();
|
|
11009
|
-
for (const doc of action.documents) {
|
|
11010
|
-
segment.insert(doc.docId, doc.document, entry.config.schema, entry.language, options);
|
|
11011
|
-
}
|
|
11012
|
-
segment.endBatch();
|
|
11013
|
-
return segment.encodeSegment();
|
|
11014
|
-
}
|
|
11015
|
-
case "mergeSegments": {
|
|
9493
|
+
case "loadVectorCopy": {
|
|
11016
9494
|
const entry = requireIndex(action.indexName);
|
|
11017
|
-
|
|
11018
|
-
if (segment.payload.docIds.some((docId) => entry.manager.has(docId))) {
|
|
11019
|
-
console.warn(
|
|
11020
|
-
`Skipping replicated segment for index "${action.indexName}": its documents already exist on this copy`
|
|
11021
|
-
);
|
|
11022
|
-
continue;
|
|
11023
|
-
}
|
|
11024
|
-
entry.manager.mergeSegment(segment.partitionId, segment.payload, segment.documents);
|
|
11025
|
-
}
|
|
9495
|
+
loadHeldVectorCopy(entry.vectorCopies, entry.manager, action.fieldName, action.handle, action.copy, scratchSlot);
|
|
11026
9496
|
return void 0;
|
|
11027
9497
|
}
|
|
11028
|
-
case "
|
|
11029
|
-
const entry =
|
|
11030
|
-
|
|
11031
|
-
while (entry.manager.partitionCount <= segment.partitionId) {
|
|
11032
|
-
entry.manager.addPartition();
|
|
11033
|
-
}
|
|
11034
|
-
entry.manager.attachFrozenSegment(segment.partitionId, createSharedFrozenSegment(segment.snapshot));
|
|
11035
|
-
}
|
|
9498
|
+
case "dropVectorCopy": {
|
|
9499
|
+
const entry = indexes.get(action.indexName);
|
|
9500
|
+
if (entry !== void 0) dropHeldVectorCopy(entry.vectorCopies, action.fieldName, action.handle);
|
|
11036
9501
|
return void 0;
|
|
11037
9502
|
}
|
|
11038
|
-
case "
|
|
11039
|
-
const entry = requireIndex(action.indexName);
|
|
11040
|
-
const partition = entry.manager.getPartition(action.partitionId);
|
|
11041
|
-
if (!isCompositePartition(partition)) {
|
|
11042
|
-
throw new NarsilError(
|
|
11043
|
-
ErrorCodes.PARTITION_CORRUPTED,
|
|
11044
|
-
`Partition ${action.partitionId} of "${action.indexName}" holds no frozen segments to compact`,
|
|
11045
|
-
{ indexName: action.indexName, partitionId: action.partitionId }
|
|
11046
|
-
);
|
|
11047
|
-
}
|
|
11048
|
-
const segments = partition.frozenSegmentsById(action.segmentIds);
|
|
11049
|
-
const { payload, documents } = buildCompactedSegmentPayload(segments);
|
|
11050
|
-
return freezeSegmentShared(payload, documents);
|
|
11051
|
-
}
|
|
11052
|
-
case "swapSegments": {
|
|
9503
|
+
case "insert": {
|
|
11053
9504
|
const entry = requireIndex(action.indexName);
|
|
11054
|
-
|
|
11055
|
-
if (!isCompositePartition(partition)) {
|
|
11056
|
-
throw new NarsilError(
|
|
11057
|
-
ErrorCodes.PARTITION_CORRUPTED,
|
|
11058
|
-
`Partition ${action.partitionId} of "${action.indexName}" holds no frozen segments to swap`,
|
|
11059
|
-
{ indexName: action.indexName, partitionId: action.partitionId }
|
|
11060
|
-
);
|
|
11061
|
-
}
|
|
11062
|
-
partition.swapFrozenSegments(action.dropSegmentIds, createSharedFrozenSegment(action.snapshot));
|
|
9505
|
+
entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
|
|
11063
9506
|
return void 0;
|
|
11064
9507
|
}
|
|
11065
9508
|
case "remove": {
|
|
@@ -11134,13 +9577,9 @@ function createDirectExecutor() {
|
|
|
11134
9577
|
partition.clear();
|
|
11135
9578
|
}
|
|
11136
9579
|
entry.manager.setPartitions(partitions);
|
|
11137
|
-
|
|
11138
|
-
const newVectorIndexes = /* @__PURE__ */ new Map();
|
|
11139
|
-
for (const [fieldPath, dim] of vectorFields) {
|
|
11140
|
-
newVectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, entry.config.vectorPromotion));
|
|
11141
|
-
}
|
|
11142
|
-
entry.manager.resetVectorIndexes(newVectorIndexes);
|
|
9580
|
+
entry.manager.resetVectorIndexes(vectorIndexesFor(action.indexName, entry.config));
|
|
11143
9581
|
entry.vectorIndexes = entry.manager.getVectorIndexes();
|
|
9582
|
+
entry.vectorCopies = createHeldVectorCopies();
|
|
11144
9583
|
return void 0;
|
|
11145
9584
|
}
|
|
11146
9585
|
case "serialize": {
|
|
@@ -11149,15 +9588,13 @@ function createDirectExecutor() {
|
|
|
11149
9588
|
}
|
|
11150
9589
|
case "deserialize": {
|
|
11151
9590
|
const entry = requireIndex(action.indexName);
|
|
11152
|
-
|
|
11153
|
-
entry.manager.addPartition();
|
|
11154
|
-
}
|
|
9591
|
+
growPartitionsTo(entry.manager, action.partitionId);
|
|
11155
9592
|
entry.manager.deserializePartition(action.partitionId, action.data);
|
|
11156
9593
|
return void 0;
|
|
11157
9594
|
}
|
|
11158
9595
|
case "memoryReport": {
|
|
11159
|
-
const
|
|
11160
|
-
return
|
|
9596
|
+
const usage = typeof process !== "undefined" && process.memoryUsage ? process.memoryUsage() : {};
|
|
9597
|
+
return { ...usage, heapLimit: readHeapStatistics()?.limitBytes ?? null };
|
|
11161
9598
|
}
|
|
11162
9599
|
case "shutdown": {
|
|
11163
9600
|
for (const [, entry] of indexes) {
|
|
@@ -11177,10 +9614,11 @@ function createDirectExecutor() {
|
|
|
11177
9614
|
execute,
|
|
11178
9615
|
shutdown,
|
|
11179
9616
|
getManager,
|
|
9617
|
+
queryContextOf,
|
|
11180
9618
|
createIndex,
|
|
11181
9619
|
dropIndex,
|
|
11182
9620
|
listIndexes
|
|
11183
9621
|
};
|
|
11184
9622
|
}
|
|
11185
9623
|
|
|
11186
|
-
export {
|
|
9624
|
+
export { CHECKSUM_TIMEOUT_MS, CHECKSUM_YIELD_CHUNK_BYTES, CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, FIELD_FILTER_OPERATORS, FILTER_EXPRESSION_KEYS, SHUTDOWN_TIMEOUT_MS, WORKER_RESOURCE_LIMITS, buildCompactedSegmentPayload, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, createVectorIndex, createWorkerPool, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, encodeSegmentState, freezeSegmentShared, generateId, getLanguage, getNestedValue, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isCompositePartition, isNodeMainThread, readHeapStatistics, registerLanguage, registerStopWords, registerTokenizer, resolveIndexAnalysis, resolveRequestThreadCount, resolveWorkerCount, serializeMetadata, spawnNodeWorker, splitWorkerBudget };
|