@delali/narsil 0.2.4-next.49 → 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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/{options-Dtk99qUs.d.ts → capabilities-JrVQOwi4.d.ts} +55 -47
  3. package/dist/chunk-AV4IJKD2.mjs +35 -0
  4. package/dist/{chunk-M6JGMKA5.mjs → chunk-BEIK3EOZ.mjs} +503 -2273
  5. package/dist/chunk-CMVG2UEF.mjs +2038 -0
  6. package/dist/chunk-CV5H7XL7.mjs +876 -0
  7. package/dist/{chunk-KGRYQVZ5.mjs → chunk-MHE7YWAU.mjs} +2 -1
  8. package/dist/{chunk-56PJBIJ6.mjs → chunk-ND34I3SV.mjs} +427 -1455
  9. package/dist/chunk-PIWKZPP7.mjs +64 -0
  10. package/dist/{chunk-HGJC5L65.mjs → chunk-PMBHH44R.mjs} +121 -2
  11. package/dist/chunk-RP23PWMV.mjs +11 -0
  12. package/dist/{chunk-RDGXYRF4.mjs → chunk-WYJXSLER.mjs} +8 -19
  13. package/dist/{chunk-JXHUHFJ5.mjs → chunk-X6YZO4MQ.mjs} +2 -11
  14. package/dist/{chunk-OWCFK42Q.mjs → chunk-XE6XR2OE.mjs} +3 -10
  15. package/dist/chunk-ZHUQKZBE.mjs +1292 -0
  16. package/dist/{client-DX7xPcNA.d.ts → client-CBOVHdjp.d.ts} +31 -7
  17. package/dist/client.d.ts +7 -7
  18. package/dist/client.mjs +26 -10
  19. package/dist/{config-B41W23Us.d.ts → config-Czh2YNDi.d.ts} +2 -2
  20. package/dist/{config-Co-zAXqO.d.ts → config-OON7IJ_B.d.ts} +26 -5
  21. package/dist/direct-executor-OREXU72C.mjs +12 -0
  22. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  23. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  24. package/dist/distribution.d.ts +8 -8
  25. package/dist/distribution.mjs +28 -11
  26. package/dist/{engine-CABZg25p.d.ts → engine-9PtFY50q.d.ts} +114 -62
  27. package/dist/index.browser.mjs +3255 -2516
  28. package/dist/index.d.ts +7 -7
  29. package/dist/index.mjs +11 -6
  30. package/dist/{search-FjWhZN3f.d.ts → memory-Bzd5xcEW.d.ts} +88 -79
  31. package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
  32. package/dist/persistence/durability/checkpoint-worker.mjs +11 -6
  33. package/dist/react.d.ts +4 -4
  34. package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
  35. package/dist/server.d.ts +47 -42
  36. package/dist/server.mjs +321 -817
  37. package/dist/thread-PZLEO6JL.mjs +310 -0
  38. package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
  39. package/dist/vector/hnsw-build-worker.mjs +2 -1
  40. package/dist/vector/search-worker.mjs +3 -121
  41. package/dist/workers/entry.mjs +47 -3
  42. package/package.json +5 -1
  43. package/dist/direct-executor-HRTDWMVB.mjs +0 -9
@@ -1,491 +1,97 @@
1
- import { MAX_SORT_FIELDS, MAX_SORT_FIELD_NAME_LENGTH, MAX_CURSOR_LENGTH, RESULT_WINDOW, DEFAULT_PAGE_SIZE, PREFIX_MAX_EXPANSIONS, MAX_CURSOR_ANCHOR_CODE_POINTS } from './chunk-CBLS46IE.mjs';
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';
2
3
  import { english } from './chunk-TGP5M5R7.mjs';
3
- import { createScalarQuantizer } from './chunk-HGJC5L65.mjs';
4
- import { MAX_M, createVectorStore, createHNSWIndex, createOrdinalFilter, addToOrdinalFilter, removeFromOrdinalFilter, ordinalFilterValues, magnitude, toScore, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, WASM_PAGE_BYTES, ABSENT_DOCUMENT_RANK, HNSW_BUILD_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, MAX_WASM_PAGES, VECTOR_SEARCH_TIMEOUT_MS } from './chunk-JXHUHFJ5.mjs';
5
- import { compareCodePoints, compareComparableKeys, toComparableSortValue, readSortField, SORT_VALUE_MAX_CODE_POINTS, compareSortValues, compareComparableValues } from './chunk-2GWVQWGA.mjs';
6
- import { generateId, createRequestId } from './chunk-RDGXYRF4.mjs';
7
- import { NarsilError, ErrorCodes, createNarsilError } from './chunk-P4ELXVJQ.mjs';
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';
8
10
  import { encode, decode } from '@msgpack/msgpack';
9
11
 
10
- // src/schema/validator/shared.ts
11
- var SORTABLE_TEXT_FIELD_TYPE = "string:sortable";
12
- var SCALAR_FIELD_TYPES = /* @__PURE__ */ new Set([
13
- "string",
14
- SORTABLE_TEXT_FIELD_TYPE,
15
- "number",
16
- "boolean",
17
- "enum",
18
- "geopoint",
19
- "string[]",
20
- "number[]",
21
- "boolean[]",
22
- "enum[]"
23
- ]);
24
- function isTextFieldType(fieldType) {
25
- return fieldType === "string" || fieldType === SORTABLE_TEXT_FIELD_TYPE;
26
- }
27
- var VECTOR_PATTERN = /^vector\[(\d+)]$/;
28
- var FIELD_NAME_PATTERN = /^[A-Za-z0-9_]+$/;
29
- var RESERVED_ROOT_FIELDS = /* @__PURE__ */ new Set(["id"]);
30
- var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
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/schema/constants.ts
203
- var MAX_NESTING_DEPTH = 4;
204
- var MAX_DOCUMENT_NESTING_DEPTH = 32;
205
-
206
- // src/schema/validator/storable.ts
207
- var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
208
- function reject(path, reason) {
209
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path === "" ? "(document)" : path}" ${reason}`, {
210
- field: path
211
- });
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);
212
29
  }
213
- function isPlainObject2(value) {
214
- const proto = Object.getPrototypeOf(value);
215
- return proto === Object.prototype || proto === null;
216
- }
217
- function walkStorable(value, path, depth, ancestors, skip) {
218
- if (value === null || value === void 0) return;
219
- const kind = typeof value;
220
- if (kind === "string" || kind === "boolean") return;
221
- if (kind === "number") return;
222
- if (kind === "bigint" || kind === "symbol" || kind === "function") {
223
- reject(path, `holds a ${kind}, which cannot be stored or recovered`);
224
- }
225
- const container = value;
226
- if (container instanceof Date || container instanceof Uint8Array) return;
227
- const isArray = Array.isArray(container);
228
- if (!isArray && !isPlainObject2(container)) {
229
- reject(
230
- path,
231
- "holds a value that does not survive storage; use plain objects, arrays, strings, numbers, booleans, dates, or Uint8Array bytes"
232
- );
233
- }
234
- if (depth >= MAX_DOCUMENT_NESTING_DEPTH) {
235
- reject(path, `nests deeper than ${MAX_DOCUMENT_NESTING_DEPTH} levels`);
236
- }
237
- if (ancestors.has(container)) {
238
- reject(path, "refers back to one of its own ancestors");
239
- }
240
- ancestors.add(container);
241
- if (isArray) {
242
- const items = container;
243
- for (let i = 0; i < items.length; i++) {
244
- walkStorable(items[i], `${path}[${i}]`, depth + 1, ancestors, skip);
245
- }
246
- } else {
247
- const record = container;
248
- for (const key of Object.keys(record)) {
249
- const keyPath = path === "" ? key : `${path}.${key}`;
250
- if (RESERVED_OBJECT_KEYS.has(key)) {
251
- reject(keyPath, "uses a reserved object key");
252
- }
253
- if (skip.has(keyPath)) continue;
254
- walkStorable(record[key], keyPath, depth + 1, ancestors, skip);
255
- }
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 });
256
34
  }
257
- ancestors.delete(container);
258
- }
259
- function assertStorableDocument(document, skipFieldPaths) {
260
- walkStorable(document, "", 0, /* @__PURE__ */ new Set(), skipFieldPaths ?? /* @__PURE__ */ new Set());
35
+ return lang;
261
36
  }
262
37
 
263
- // src/schema/validator/document.ts
264
- var vectorPathCache = /* @__PURE__ */ new WeakMap();
265
- function vectorFieldPaths(schema) {
266
- let paths = vectorPathCache.get(schema);
267
- if (paths !== void 0) return paths;
268
- paths = /* @__PURE__ */ new Set();
269
- const collect = (level, prefix) => {
270
- for (const [field, type] of Object.entries(level)) {
271
- const path = prefix === "" ? field : `${prefix}.${field}`;
272
- if (typeof type === "string" && VECTOR_PATTERN.test(type)) {
273
- paths?.add(path);
274
- } else if (isPlainObject(type)) {
275
- collect(type, path);
276
- }
277
- }
278
- };
279
- collect(schema, "");
280
- vectorPathCache.set(schema, paths);
281
- return paths;
282
- }
283
- function validateDocumentFields(doc, schema, prefix) {
284
- for (const [field, type] of Object.entries(schema)) {
285
- const path = prefix ? `${prefix}.${field}` : field;
286
- const value = doc[field];
287
- if (value === void 0 || value === null) continue;
288
- if (isPlainObject(type)) {
289
- if (!isPlainObject(value)) {
290
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" expected an object`, {
291
- field: path,
292
- received: Array.isArray(value) ? "array" : typeof value
293
- });
294
- }
295
- validateDocumentFields(value, type, path);
296
- continue;
297
- }
298
- validateFieldValue(path, value, type);
299
- }
300
- }
301
- function validateDocument(document, schema) {
302
- if (!isPlainObject(document)) {
303
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document must be a plain object", {
304
- received: typeof document
305
- });
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 });
306
44
  }
307
- assertStorableDocument(document, vectorFieldPaths(schema));
308
- validateDocumentFields(document, schema, "");
309
45
  }
310
- function collectExtraFields(doc, schema, prefix, extras) {
311
- for (const key of Object.keys(doc)) {
312
- if (key === "id") continue;
313
- const path = prefix ? `${prefix}.${key}` : key;
314
- if (!(key in schema)) {
315
- extras.push(path);
316
- continue;
317
- }
318
- const schemaType = schema[key];
319
- if (isPlainObject(schemaType) && isPlainObject(doc[key])) {
320
- collectExtraFields(doc[key], schemaType, path, extras);
321
- }
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 });
322
50
  }
51
+ tokenizers.set(name, tokenizer);
323
52
  }
324
- function validateDocumentStrict(document, schema) {
325
- const extras = [];
326
- collectExtraFields(document, schema, "", extras);
327
- if (extras.length > 0) {
53
+ function registerStopWords(name, stopWords) {
54
+ requireName(name, "stop word set");
55
+ if (!(stopWords instanceof Set) && typeof stopWords !== "function") {
328
56
  throw new NarsilError(
329
- ErrorCodes.DOC_VALIDATION_FAILED,
330
- `Document contains fields not defined in schema: ${extras.join(", ")}`,
331
- { extraFields: extras }
57
+ ErrorCodes.CONFIG_INVALID,
58
+ `Stop word set "${name}" needs a Set of words or a function returning one`,
59
+ { stopWords: name }
332
60
  );
333
61
  }
62
+ stopWordSets.set(name, stopWords);
334
63
  }
335
- function resolveNestedValue(document, path) {
336
- if (!path.includes(".")) return document[path];
337
- const segments = path.split(".");
338
- let current = document;
339
- for (const segment of segments) {
340
- if (current === null || current === void 0 || typeof current !== "object") return void 0;
341
- current = current[segment];
342
- }
343
- return current;
64
+ function hasTokenizer(name) {
65
+ return tokenizers.has(name);
344
66
  }
345
- function validateRequiredFields(document, required) {
346
- if (required.length === 0) return;
347
- for (const field of required) {
348
- const value = resolveNestedValue(document, field);
349
- if (value === void 0 || value === null) {
350
- throw new NarsilError(ErrorCodes.DOC_MISSING_REQUIRED_FIELD, `Document is missing required field "${field}"`, {
351
- field
352
- });
353
- }
354
- }
67
+ function hasStopWords(name) {
68
+ return stopWordSets.has(name);
355
69
  }
356
-
357
- // src/schema/validator/schema.ts
358
- function validateSchemaFields(schema, depth, prefix) {
359
- if (depth > MAX_NESTING_DEPTH) {
360
- throw new NarsilError(
361
- ErrorCodes.SCHEMA_DEPTH_EXCEEDED,
362
- `Schema nesting exceeds the maximum depth of ${MAX_NESTING_DEPTH} at "${prefix}"`,
363
- { path: prefix, maxDepth: MAX_NESTING_DEPTH }
364
- );
365
- }
366
- for (const [field, type] of Object.entries(schema)) {
367
- const path = prefix ? `${prefix}.${field}` : field;
368
- if (PROTOTYPE_POLLUTION_KEYS.has(field)) {
369
- throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field name "${field}" is not allowed in a schema`, {
370
- field,
371
- path
372
- });
373
- }
374
- if (!FIELD_NAME_PATTERN.test(field)) {
375
- throw new NarsilError(
376
- ErrorCodes.SCHEMA_INVALID_TYPE,
377
- `Field name "${field}" contains characters that are not allowed; use letters, digits, and underscores only`,
378
- { field, path }
379
- );
380
- }
381
- if (depth === 1 && RESERVED_ROOT_FIELDS.has(field)) {
382
- throw new NarsilError(
383
- ErrorCodes.SCHEMA_INVALID_TYPE,
384
- `Field "${field}" is reserved and cannot be defined in a schema`,
385
- { field, path }
386
- );
387
- }
388
- if (isPlainObject(type)) {
389
- validateSchemaFields(type, depth + 1, path);
390
- continue;
391
- }
392
- if (typeof type !== "string") {
393
- throw new NarsilError(
394
- ErrorCodes.SCHEMA_INVALID_TYPE,
395
- `Field "${path}" has an invalid type definition: ${String(type)}`,
396
- {
397
- field: path,
398
- type: String(type)
399
- }
400
- );
401
- }
402
- if (SCALAR_FIELD_TYPES.has(type)) continue;
403
- const vectorMatch = VECTOR_PATTERN.exec(type);
404
- if (vectorMatch) {
405
- const dimension = Number.parseInt(vectorMatch[1], 10);
406
- if (dimension <= 0) {
407
- throw new NarsilError(
408
- ErrorCodes.SCHEMA_INVALID_VECTOR_DIMENSION,
409
- `Vector field "${path}" must have a positive dimension, got ${dimension}`,
410
- { field: path, dimension }
411
- );
412
- }
413
- continue;
414
- }
415
- throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field "${path}" has an unsupported type: "${type}"`, {
416
- field: path,
417
- 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()]
418
76
  });
419
77
  }
78
+ return tokenizer;
420
79
  }
421
- function validateSchema(schema) {
422
- if (!isPlainObject(schema)) {
423
- throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, "Schema must be a plain object", {
424
- received: typeof schema
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()]
425
86
  });
426
87
  }
427
- if (Object.keys(schema).length === 0) {
428
- throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, "Schema must define at least one field");
429
- }
430
- validateSchemaFields(schema, 1, "");
431
- }
432
- function flattenRecursive(schema, prefix, result) {
433
- for (const [field, type] of Object.entries(schema)) {
434
- const path = prefix ? `${prefix}.${field}` : field;
435
- if (isPlainObject(type)) {
436
- flattenRecursive(type, path, result);
437
- } else {
438
- result[path] = type;
439
- }
440
- }
441
- }
442
- function flattenSchema(schema) {
443
- const result = {};
444
- flattenRecursive(schema, "", result);
445
- return result;
446
- }
447
- function extractVectorFieldsFromSchema(schema) {
448
- const flat = flattenSchema(schema);
449
- const result = /* @__PURE__ */ new Map();
450
- for (const [fieldPath, fieldType] of Object.entries(flat)) {
451
- const match = VECTOR_PATTERN.exec(fieldType);
452
- if (match) {
453
- result.set(fieldPath, Number.parseInt(match[1], 10));
454
- }
455
- }
456
- return result;
457
- }
458
-
459
- // src/schema/validator/vector-promotion.ts
460
- function fail(message, details) {
461
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, message, details);
462
- }
463
- function isPositiveInteger(value) {
464
- return Number.isInteger(value) && value >= 1;
88
+ return stopWords;
465
89
  }
466
- function validateVectorPromotion(config) {
467
- if (config === void 0) return;
468
- if (config.threshold !== void 0 && !isPositiveInteger(config.threshold)) {
469
- fail("vectorPromotion.threshold must be a positive integer", { threshold: config.threshold });
470
- }
471
- const quantization = config.quantization;
472
- if (quantization !== void 0 && quantization !== "sq8" && quantization !== "none") {
473
- fail("vectorPromotion.quantization must be 'sq8' or 'none'", { quantization });
474
- }
475
- const hnsw = config.hnswConfig;
476
- if (hnsw === void 0) return;
477
- if (hnsw.m !== void 0 && (!isPositiveInteger(hnsw.m) || hnsw.m > MAX_M)) {
478
- fail(`vectorPromotion.hnswConfig.m must be a positive integer no greater than ${MAX_M}`, { m: hnsw.m });
479
- }
480
- if (hnsw.efConstruction !== void 0 && !isPositiveInteger(hnsw.efConstruction)) {
481
- fail("vectorPromotion.hnswConfig.efConstruction must be a positive integer", {
482
- efConstruction: hnsw.efConstruction
483
- });
484
- }
485
- const metric = hnsw.metric;
486
- if (metric !== void 0 && metric !== "cosine" && metric !== "dotProduct" && metric !== "euclidean") {
487
- fail("vectorPromotion.hnswConfig.metric must be 'cosine', 'dotProduct', or 'euclidean'", { metric });
488
- }
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
+ };
489
95
  }
490
96
 
491
97
  // src/serialization/constants.ts
@@ -604,8 +210,8 @@ function wireToMetadata(raw) {
604
210
  }
605
211
  if (raw.partition_limits && typeof raw.partition_limits === "object") {
606
212
  const limits = {
607
- ...isPositiveInteger2(raw.partition_limits.max_docs_per_partition) ? { maxDocsPerPartition: raw.partition_limits.max_docs_per_partition } : {},
608
- ...isPositiveInteger2(raw.partition_limits.max_partitions) ? { maxPartitions: raw.partition_limits.max_partitions } : {},
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 } : {},
609
215
  ...isWatermarkFraction(raw.partition_limits.watermark) ? { watermark: raw.partition_limits.watermark } : {}
610
216
  };
611
217
  if (Object.keys(limits).length > 0) {
@@ -630,16 +236,16 @@ function wireToMetadata(raw) {
630
236
  const metric = hnsw?.metric;
631
237
  const restored = {};
632
238
  const hnswConfig = {};
633
- if (isPositiveInteger2(hnsw?.m)) {
239
+ if (isPositiveInteger(hnsw?.m)) {
634
240
  hnswConfig.m = hnsw?.m;
635
241
  }
636
- if (isPositiveInteger2(hnsw?.ef_construction)) {
242
+ if (isPositiveInteger(hnsw?.ef_construction)) {
637
243
  hnswConfig.efConstruction = hnsw?.ef_construction;
638
244
  }
639
245
  if (metric === "cosine" || metric === "dotProduct" || metric === "euclidean") {
640
246
  hnswConfig.metric = metric;
641
247
  }
642
- if (isPositiveInteger2(promotion.threshold)) {
248
+ if (isPositiveInteger(promotion.threshold)) {
643
249
  restored.threshold = promotion.threshold;
644
250
  }
645
251
  if (isSelectivityRatio(promotion.filter_threshold)) {
@@ -666,7 +272,7 @@ function wireToMetadata(raw) {
666
272
  function isNonNegativeInteger(value) {
667
273
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
668
274
  }
669
- function isPositiveInteger2(value) {
275
+ function isPositiveInteger(value) {
670
276
  return typeof value === "number" && Number.isInteger(value) && value >= 1;
671
277
  }
672
278
  function isSelectivityRatio(value) {
@@ -686,17 +292,17 @@ function deserializeMetadata(data) {
686
292
 
687
293
  // src/serialization/stored-vector-values.ts
688
294
  function dropStoredVectorValues(documents, schema) {
689
- const vectorFieldPaths2 = [];
295
+ const vectorFieldPaths = [];
690
296
  for (const [fieldPath, fieldType] of Object.entries(schema)) {
691
297
  if (typeof fieldType === "string" && VECTOR_PATTERN.test(fieldType)) {
692
- vectorFieldPaths2.push(fieldPath);
298
+ vectorFieldPaths.push(fieldPath);
693
299
  }
694
300
  }
695
- if (vectorFieldPaths2.length === 0) {
301
+ if (vectorFieldPaths.length === 0) {
696
302
  return;
697
303
  }
698
304
  for (const document of Object.values(documents)) {
699
- for (const fieldPath of vectorFieldPaths2) {
305
+ for (const fieldPath of vectorFieldPaths) {
700
306
  removeFieldAtPath(document.fields, fieldPath);
701
307
  }
702
308
  }
@@ -1179,55 +785,6 @@ var POSTING_LIST_INITIAL_CAPACITY = 8;
1179
785
  var POSTING_LIST_COMPACTION_THRESHOLD = 0.3;
1180
786
  var MAX_TERM_FREQUENCY = 65535;
1181
787
 
1182
- // src/core/fuzzy.ts
1183
- function boundedLevenshtein(a, b, tolerance) {
1184
- if (tolerance < 0) return { distance: -1, withinTolerance: false };
1185
- if (a === b) return { distance: 0, withinTolerance: true };
1186
- const m = a.length;
1187
- const n = b.length;
1188
- if (m === 0) {
1189
- return n <= tolerance ? { distance: n, withinTolerance: true } : { distance: n, withinTolerance: false };
1190
- }
1191
- if (n === 0) {
1192
- return m <= tolerance ? { distance: m, withinTolerance: true } : { distance: m, withinTolerance: false };
1193
- }
1194
- if (a.startsWith(b)) {
1195
- const diff2 = m - n;
1196
- return diff2 <= tolerance ? { distance: diff2, withinTolerance: true } : { distance: diff2, withinTolerance: false };
1197
- }
1198
- if (b.startsWith(a)) {
1199
- return { distance: 0, withinTolerance: true };
1200
- }
1201
- const diff = Math.abs(m - n);
1202
- if (diff > tolerance) return { distance: -1, withinTolerance: false };
1203
- const matrix = [];
1204
- for (let i = 0; i <= m; i++) {
1205
- matrix[i] = [i];
1206
- for (let j = 1; j <= n; j++) {
1207
- matrix[i][j] = i === 0 ? j : 0;
1208
- }
1209
- }
1210
- for (let i = 1; i <= m; i++) {
1211
- let rowMin = Infinity;
1212
- for (let j = 1; j <= n; j++) {
1213
- if (a[i - 1] === b[j - 1]) {
1214
- matrix[i][j] = matrix[i - 1][j - 1];
1215
- } else {
1216
- matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + 1);
1217
- }
1218
- rowMin = Math.min(rowMin, matrix[i][j]);
1219
- }
1220
- if (rowMin > tolerance) {
1221
- return { distance: -1, withinTolerance: false };
1222
- }
1223
- }
1224
- const finalDistance = matrix[m][n];
1225
- return {
1226
- distance: finalDistance,
1227
- withinTolerance: finalDistance <= tolerance
1228
- };
1229
- }
1230
-
1231
788
  // src/core/posting-list.ts
1232
789
  function createPostingList() {
1233
790
  return {
@@ -1559,137 +1116,6 @@ function createInvertedIndex(fieldNameTable) {
1559
1116
  };
1560
1117
  }
1561
1118
 
1562
- // src/core/projection.ts
1563
- function splitPaths(paths) {
1564
- if (!paths) return [];
1565
- const split = [];
1566
- for (const path of paths) {
1567
- const segments = path.split(".").filter((segment) => segment.length > 0);
1568
- if (segments.length === 0) continue;
1569
- if (segments.some((segment) => PROTOTYPE_POLLUTION_KEYS.has(segment))) continue;
1570
- split.push(segments);
1571
- }
1572
- return split;
1573
- }
1574
- function overlaps(a, b) {
1575
- const shared = Math.min(a.length, b.length);
1576
- for (let i = 0; i < shared; i++) {
1577
- if (a[i] !== b[i]) return false;
1578
- }
1579
- return true;
1580
- }
1581
- function covers(outer, inner) {
1582
- if (outer.length > inner.length) return false;
1583
- for (let i = 0; i < outer.length; i++) {
1584
- if (outer[i] !== inner[i]) return false;
1585
- }
1586
- return true;
1587
- }
1588
- function readPath(source, path) {
1589
- let current = source;
1590
- for (const segment of path) {
1591
- if (typeof current !== "object" || current === null || Array.isArray(current))
1592
- return { found: false, value: void 0 };
1593
- const record = current;
1594
- if (!Object.hasOwn(record, segment)) return { found: false, value: void 0 };
1595
- current = record[segment];
1596
- }
1597
- return { found: true, value: current };
1598
- }
1599
- function writePath(target, path, value) {
1600
- let current = target;
1601
- for (let i = 0; i < path.length - 1; i++) {
1602
- const segment = path[i];
1603
- const existing = current[segment];
1604
- if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
1605
- current = existing;
1606
- continue;
1607
- }
1608
- const created = {};
1609
- current[segment] = created;
1610
- current = created;
1611
- }
1612
- current[path[path.length - 1]] = value;
1613
- }
1614
- function omitPath(source, path) {
1615
- const segment = path[0];
1616
- if (!Object.hasOwn(source, segment)) return source;
1617
- const copy = { ...source };
1618
- if (path.length === 1) {
1619
- delete copy[segment];
1620
- return copy;
1621
- }
1622
- const child = copy[segment];
1623
- if (typeof child !== "object" || child === null || Array.isArray(child)) return copy;
1624
- copy[segment] = omitPath(child, path.slice(1));
1625
- return copy;
1626
- }
1627
- function cloneExcept(value, path, exclude) {
1628
- const reachesDeeper = exclude.some((excluded) => excluded.length > path.length && covers(path, excluded));
1629
- if (!reachesDeeper) return structuredClone(value);
1630
- if (typeof value !== "object" || value === null || Array.isArray(value)) return structuredClone(value);
1631
- const source = value;
1632
- const kept = {};
1633
- for (const key of Object.keys(source)) {
1634
- const childPath = [...path, key];
1635
- if (exclude.some((excluded) => covers(excluded, childPath))) continue;
1636
- kept[key] = cloneExcept(source[key], childPath, exclude);
1637
- }
1638
- return kept;
1639
- }
1640
- function resolveProjection(projection) {
1641
- if (projection === void 0 || projection === true) return { kind: "full" };
1642
- if (projection === false) return { kind: "none" };
1643
- if (typeof projection !== "object" || projection === null) return { kind: "full" };
1644
- const askedInclude = Array.isArray(projection.include) && projection.include.length > 0;
1645
- const askedExclude = Array.isArray(projection.exclude) && projection.exclude.length > 0;
1646
- if (!askedInclude && !askedExclude) return { kind: "full" };
1647
- return {
1648
- kind: "fields",
1649
- include: askedInclude ? splitPaths(projection.include) : null,
1650
- exclude: splitPaths(projection.exclude)
1651
- };
1652
- }
1653
- function projectionKeepsField(projection, fieldPath) {
1654
- if (projection.kind === "full") return true;
1655
- if (projection.kind === "none") return false;
1656
- const path = fieldPath.split(".").filter((segment) => segment.length > 0);
1657
- if (path.length === 0) return false;
1658
- if (projection.include && !projection.include.some((included) => overlaps(included, path))) return false;
1659
- return !projection.exclude.some((excluded) => covers(excluded, path));
1660
- }
1661
- function cloneProjected(document, projection) {
1662
- if (projection === void 0 || projection.kind === "full") return structuredClone(document);
1663
- if (projection.kind === "none") return {};
1664
- if (projection.include === null) return cloneExcept(document, [], projection.exclude);
1665
- const projected = {};
1666
- for (const path of projection.include) {
1667
- if (projection.exclude.some((excluded) => covers(excluded, path))) continue;
1668
- const { found, value } = readPath(document, path);
1669
- if (!found) continue;
1670
- writePath(projected, path, cloneExcept(value, path, projection.exclude));
1671
- }
1672
- return projected;
1673
- }
1674
- function applyProjection(document, projection) {
1675
- if (projection.kind === "full") return document;
1676
- if (projection.kind === "none") return {};
1677
- let projected;
1678
- if (projection.include) {
1679
- projected = {};
1680
- for (const path of projection.include) {
1681
- const { found, value } = readPath(document, path);
1682
- if (found) writePath(projected, path, value);
1683
- }
1684
- } else {
1685
- projected = document;
1686
- }
1687
- for (const path of projection.exclude) {
1688
- projected = omitPath(projected, path);
1689
- }
1690
- return projected;
1691
- }
1692
-
1693
1119
  // src/core/statistics.ts
1694
1120
  function applyUniqueTokens(tokens, docFrequencies, increment) {
1695
1121
  const fields = Object.keys(tokens);
@@ -1933,84 +1359,23 @@ function createSurfaceRegistry() {
1933
1359
  };
1934
1360
  }
1935
1361
 
1936
- // src/core/bitset.ts
1937
- function createBitSet(capacity) {
1938
- return new Uint32Array(capacity + 31 >>> 5 || 1);
1939
- }
1940
- function bitsetSet(bs, index) {
1941
- const word = index >>> 5;
1942
- if (word < bs.length) {
1943
- bs[word] |= 1 << (index & 31);
1944
- }
1945
- }
1946
- function bitsetHas(bs, index) {
1947
- const word = index >>> 5;
1948
- if (word >= bs.length) return false;
1949
- return (bs[word] & 1 << (index & 31)) !== 0;
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;
1950
1370
  }
1951
- function bitsetAnd(a, b) {
1952
- const len = Math.min(a.length, b.length);
1953
- const result = new Uint32Array(len);
1954
- for (let i = 0; i < len; i++) {
1955
- result[i] = a[i] & b[i];
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]];
1956
1377
  }
1957
- return result;
1958
- }
1959
- function bitsetOr(a, b) {
1960
- const longer = a.length >= b.length ? a : b;
1961
- const shorter = a.length >= b.length ? b : a;
1962
- const result = new Uint32Array(longer.length);
1963
- for (let i = 0; i < shorter.length; i++) {
1964
- result[i] = longer[i] | shorter[i];
1965
- }
1966
- for (let i = shorter.length; i < longer.length; i++) {
1967
- result[i] = longer[i];
1968
- }
1969
- return result;
1970
- }
1971
- function bitsetNot(bs, capacity) {
1972
- const result = new Uint32Array(bs.length);
1973
- const fullWords = capacity >>> 5;
1974
- for (let i = 0; i < fullWords; i++) {
1975
- result[i] = ~bs[i];
1976
- }
1977
- const remainder = capacity & 31;
1978
- if (remainder > 0 && fullWords < bs.length) {
1979
- result[fullWords] = ~bs[fullWords] & (1 << remainder) - 1;
1980
- }
1981
- return result;
1982
- }
1983
- function bitsetIsEmpty(bs) {
1984
- for (let i = 0; i < bs.length; i++) {
1985
- if (bs[i] !== 0) return false;
1986
- }
1987
- return true;
1988
- }
1989
- function bitsetFromSet(set, capacity) {
1990
- const bs = createBitSet(capacity);
1991
- for (const id of set) {
1992
- bs[id >>> 5] |= 1 << (id & 31);
1993
- }
1994
- return bs;
1995
- }
1996
-
1997
- // src/core/partition/utils.ts
1998
- function getOrCreateFieldNameIndex(table, fieldName) {
1999
- const existing = table.indexMap.get(fieldName);
2000
- if (existing !== void 0) return existing;
2001
- const idx = table.names.length;
2002
- table.names.push(fieldName);
2003
- table.indexMap.set(fieldName, idx);
2004
- return idx;
2005
- }
2006
- function getNestedValue(obj, path) {
2007
- const parts = path.split(".");
2008
- let current = obj;
2009
- for (let i = 0; i < parts.length; i++) {
2010
- if (current === null || current === void 0 || typeof current !== "object") return void 0;
2011
- current = current[parts[i]];
2012
- }
2013
- return current;
1378
+ return current;
2014
1379
  }
2015
1380
  function stringArraysEqual(a, b) {
2016
1381
  if (a === b) return true;
@@ -2705,461 +2070,6 @@ function createNumericIndex() {
2705
2070
  };
2706
2071
  }
2707
2072
 
2708
- // src/core/tokenizer/constants.ts
2709
- var TOKEN_CACHE_SIZE_FLOOR = 5e4;
2710
- var TOKEN_CACHE_SIZE_CEILING = 2e6;
2711
- var TOKEN_CACHE_BYTES_PER_ENTRY = 200;
2712
- var NODE_TOKEN_CACHE_ENTRIES = 1e6;
2713
- var UNKNOWN_MEMORY_TOKEN_CACHE_ENTRIES = 2e5;
2714
- var CONSTRAINED_MEMORY_TOKEN_CACHE_FRACTION = 0.05;
2715
- var SMALL_DEVICE_MEMORY_GB = 1;
2716
- var MEDIUM_DEVICE_MEMORY_GB = 4;
2717
- var SMALL_DEVICE_TOKEN_CACHE_ENTRIES = 1e5;
2718
- var MEDIUM_DEVICE_TOKEN_CACHE_ENTRIES = 25e4;
2719
- var LARGE_DEVICE_TOKEN_CACHE_ENTRIES = 5e5;
2720
- var DEFAULT_MIN_TOKEN_LENGTH = 1;
2721
-
2722
- // src/core/tokenizer/normalize.ts
2723
- var DOTTED_CAPITAL_I = "\u0130";
2724
- var COMBINING_DOT_ABOVE = "\u0307";
2725
- var ARMENIAN_MARKS = /[\u055B-\u055F]/;
2726
- var FOLDED_FORMS = /\u0307|[\u0130\u055B-\u055F\u2018\u2019\u02BC\u02BB]/g;
2727
- var WIDTH_FORMS = /[\uFF01-\uFF5E\uFF61-\uFF9F]/;
2728
- var WIDTH_FORMS_GLOBAL = /[\uFF01-\uFF5E\uFF61-\uFF9F]/g;
2729
- var FULLWIDTH_ASCII_END = 65374;
2730
- var FULLWIDTH_ASCII_OFFSET = 65248;
2731
- var HALFWIDTH_KANA_START = 65377;
2732
- var HALFWIDTH_VOICED_MARK = 65438;
2733
- var HALFWIDTH_SEMI_VOICED_MARK = 65439;
2734
- var COMBINING_VOICED_MARK = "\u3099";
2735
- var COMBINING_SEMI_VOICED_MARK = "\u309A";
2736
- 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";
2737
- function foldForm(match) {
2738
- if (match === DOTTED_CAPITAL_I) return "i";
2739
- if (match === COMBINING_DOT_ABOVE || ARMENIAN_MARKS.test(match)) return "";
2740
- return "'";
2741
- }
2742
- function foldWidth(match) {
2743
- const codePoint = match.charCodeAt(0);
2744
- if (codePoint <= FULLWIDTH_ASCII_END) return String.fromCharCode(codePoint - FULLWIDTH_ASCII_OFFSET);
2745
- if (codePoint === HALFWIDTH_VOICED_MARK) return COMBINING_VOICED_MARK;
2746
- if (codePoint === HALFWIDTH_SEMI_VOICED_MARK) return COMBINING_SEMI_VOICED_MARK;
2747
- return HALFWIDTH_KANA_TARGETS[codePoint - HALFWIDTH_KANA_START];
2748
- }
2749
- function isAscii(text) {
2750
- for (let i = 0; i < text.length; i++) {
2751
- if (text.charCodeAt(i) > 127) return false;
2752
- }
2753
- return true;
2754
- }
2755
- function foldWidthForms(text) {
2756
- if (!WIDTH_FORMS.test(text)) return text;
2757
- return text.replace(WIDTH_FORMS_GLOBAL, foldWidth);
2758
- }
2759
- function normalizeForSplitting(text) {
2760
- if (isAscii(text)) return text.toLowerCase();
2761
- return foldWidthForms(text).normalize("NFC").replace(FOLDED_FORMS, foldForm).toLowerCase();
2762
- }
2763
- function stripDiacritics(text) {
2764
- return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2765
- }
2766
-
2767
- // src/core/tokenizer/cache.ts
2768
- function clampCacheSize(entries) {
2769
- return Math.max(TOKEN_CACHE_SIZE_FLOOR, Math.min(entries, TOKEN_CACHE_SIZE_CEILING));
2770
- }
2771
- function computeDefaultCacheSize() {
2772
- try {
2773
- if (typeof process !== "undefined" && typeof process.versions?.node === "string" && typeof window === "undefined") {
2774
- const constrainedMemory = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
2775
- if (constrainedMemory > 0) {
2776
- const budget = constrainedMemory * CONSTRAINED_MEMORY_TOKEN_CACHE_FRACTION;
2777
- return clampCacheSize(Math.floor(budget / TOKEN_CACHE_BYTES_PER_ENTRY));
2778
- }
2779
- return clampCacheSize(NODE_TOKEN_CACHE_ENTRIES);
2780
- }
2781
- if (typeof navigator !== "undefined") {
2782
- const mem = navigator.deviceMemory;
2783
- if (typeof mem === "number" && mem > 0) {
2784
- let entries;
2785
- if (mem <= SMALL_DEVICE_MEMORY_GB) entries = SMALL_DEVICE_TOKEN_CACHE_ENTRIES;
2786
- else if (mem <= MEDIUM_DEVICE_MEMORY_GB) entries = MEDIUM_DEVICE_TOKEN_CACHE_ENTRIES;
2787
- else entries = LARGE_DEVICE_TOKEN_CACHE_ENTRIES;
2788
- return clampCacheSize(entries);
2789
- }
2790
- }
2791
- } catch {
2792
- return clampCacheSize(UNKNOWN_MEMORY_TOKEN_CACHE_ENTRIES);
2793
- }
2794
- return clampCacheSize(UNKNOWN_MEMORY_TOKEN_CACHE_ENTRIES);
2795
- }
2796
- var normalizationCache = /* @__PURE__ */ new Map();
2797
- var normalizationCacheSize = 0;
2798
- var maxCacheSize = computeDefaultCacheSize();
2799
- var cachedLangName = "";
2800
- var cachedFlags = "";
2801
- var cachedBucket = null;
2802
- function configureNormalizationCache(maxSize) {
2803
- if (!Number.isFinite(maxSize)) {
2804
- throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must be a finite number", {
2805
- received: maxSize
2806
- });
2807
- }
2808
- if (maxSize < 0) {
2809
- throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must not be negative", {
2810
- received: maxSize
2811
- });
2812
- }
2813
- if (maxSize === 0) {
2814
- throw createNarsilError(
2815
- ErrorCodes.CONFIG_INVALID,
2816
- "tokenizerCacheSize must be greater than zero; the normalization cache cannot be disabled",
2817
- {
2818
- received: maxSize
2819
- }
2820
- );
2821
- }
2822
- maxCacheSize = Math.max(TOKEN_CACHE_SIZE_FLOOR, Math.min(Math.floor(maxSize), TOKEN_CACHE_SIZE_CEILING));
2823
- if (normalizationCacheSize > maxCacheSize) {
2824
- evictOldestEntries(normalizationCacheSize - maxCacheSize);
2825
- }
2826
- }
2827
- function getCacheBucket(language, flags) {
2828
- const langKey = `${language.name}@${language.revision}`;
2829
- if (cachedBucket && langKey === cachedLangName && flags === cachedFlags) return cachedBucket;
2830
- const key = `${langKey}:${flags}`;
2831
- let bucket = normalizationCache.get(key);
2832
- if (!bucket) {
2833
- bucket = /* @__PURE__ */ new Map();
2834
- normalizationCache.set(key, bucket);
2835
- }
2836
- cachedLangName = langKey;
2837
- cachedFlags = flags;
2838
- cachedBucket = bucket;
2839
- return bucket;
2840
- }
2841
- function evictOldestEntries(count) {
2842
- let remaining = count;
2843
- for (const [key, bucket] of normalizationCache) {
2844
- if (remaining <= 0) break;
2845
- if (bucket.size <= remaining) {
2846
- remaining -= bucket.size;
2847
- normalizationCacheSize -= bucket.size;
2848
- normalizationCache.delete(key);
2849
- if (bucket === cachedBucket) cachedBucket = null;
2850
- } else {
2851
- let deleted = 0;
2852
- for (const raw of bucket.keys()) {
2853
- if (deleted >= remaining) break;
2854
- bucket.delete(raw);
2855
- deleted++;
2856
- }
2857
- normalizationCacheSize -= deleted;
2858
- remaining = 0;
2859
- }
2860
- }
2861
- }
2862
- function transformToken(raw, language, stem, removeDiacritics) {
2863
- const flags = (stem ? "s" : "") + (removeDiacritics ? "d" : "");
2864
- let bucket = getCacheBucket(language, flags);
2865
- const cached = bucket.get(raw);
2866
- if (cached !== void 0) return cached;
2867
- let normalized = raw;
2868
- if (language.normalizer) {
2869
- normalized = language.normalizer(normalized);
2870
- }
2871
- if (removeDiacritics) {
2872
- normalized = stripDiacritics(normalized);
2873
- }
2874
- if (stem && language.stemmer) {
2875
- normalized = language.stemmer(normalized);
2876
- }
2877
- if (normalizationCacheSize >= maxCacheSize) {
2878
- evictOldestEntries(Math.max(1, maxCacheSize >>> 2));
2879
- bucket = getCacheBucket(language, flags);
2880
- }
2881
- bucket.set(raw, normalized);
2882
- normalizationCacheSize++;
2883
- return normalized;
2884
- }
2885
- function clearNormalizationCache() {
2886
- normalizationCache.clear();
2887
- normalizationCacheSize = 0;
2888
- cachedBucket = null;
2889
- }
2890
- function resetNormalizationCache() {
2891
- clearNormalizationCache();
2892
- maxCacheSize = computeDefaultCacheSize();
2893
- }
2894
- function getNormalizationCacheSize() {
2895
- return normalizationCacheSize;
2896
- }
2897
-
2898
- // src/core/tokenizer/ngrams.ts
2899
- var OTHER_SCRIPT = 0;
2900
- var HAN_SCRIPT = 1;
2901
- var HIRAGANA_SCRIPT = 2;
2902
- var KATAKANA_SCRIPT = 3;
2903
- var HANGUL_SCRIPT = 4;
2904
- var THAI_SCRIPT = 5;
2905
- var LAO_SCRIPT = 6;
2906
- var KHMER_SCRIPT = 7;
2907
- var MYANMAR_SCRIPT = 8;
2908
- var PROLONGED_SOUND_MARK = 12540;
2909
- var IDEOGRAPHIC_ITERATION_MARK = 12293;
2910
- var LOWEST_NGRAM_CODE_POINT = 3584;
2911
- var LOWEST_MARK_CODE_POINT = 768;
2912
- var THAI_BLOCK_END = 3711;
2913
- var LAO_BLOCK_END = 3839;
2914
- var MYANMAR_BLOCK_START = 4096;
2915
- var MYANMAR_BLOCK_END = 4255;
2916
- var KHMER_BLOCK_START = 6016;
2917
- var KHMER_BLOCK_END = 6655;
2918
- var SOUTHEAST_ASIAN_BLOCKS_END = 6656;
2919
- var HIRAGANA_BLOCK_START = 12352;
2920
- var HIRAGANA_BLOCK_END = 12447;
2921
- var COMBINING_VOICED_MARK2 = 12441;
2922
- var COMBINING_SEMI_VOICED_MARK2 = 12442;
2923
- var KATAKANA_BLOCK_START = 12448;
2924
- var KATAKANA_BLOCK_END = 12543;
2925
- var HAN_EXTENSION_A_START = 13312;
2926
- var HAN_EXTENSION_A_END = 19903;
2927
- var HAN_UNIFIED_START = 19968;
2928
- var HAN_UNIFIED_END = 40959;
2929
- var HANGUL_SYLLABLE_START = 44032;
2930
- var HANGUL_SYLLABLE_END = 55203;
2931
- var HAN_COMPATIBILITY_START = 63744;
2932
- var HAN_COMPATIBILITY_END = 64255;
2933
- var SUPPLEMENTARY_PLANE_START = 65536;
2934
- var MARK = /\p{M}/uy;
2935
- var HAN = /\p{Script=Han}/uy;
2936
- var HIRAGANA = /\p{Script=Hiragana}/uy;
2937
- var KATAKANA = /\p{Script=Katakana}/uy;
2938
- var HANGUL = /\p{Script=Hangul}/uy;
2939
- var THAI = /\p{Script=Thai}/uy;
2940
- var LAO = /\p{Script=Lao}/uy;
2941
- var KHMER = /\p{Script=Khmer}/uy;
2942
- var MYANMAR = /\p{Script=Myanmar}/uy;
2943
- function matchesAt(pattern, part, index) {
2944
- pattern.lastIndex = index;
2945
- return pattern.test(part);
2946
- }
2947
- function isMark(part, index, codePoint) {
2948
- if (codePoint < LOWEST_MARK_CODE_POINT) return false;
2949
- if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return false;
2950
- if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) {
2951
- return codePoint === COMBINING_VOICED_MARK2 || codePoint === COMBINING_SEMI_VOICED_MARK2;
2952
- }
2953
- if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return false;
2954
- if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return false;
2955
- return matchesAt(MARK, part, index);
2956
- }
2957
- function classifyOutsideCommonBlocks(part, index) {
2958
- if (matchesAt(HAN, part, index)) return HAN_SCRIPT;
2959
- if (matchesAt(HIRAGANA, part, index)) return HIRAGANA_SCRIPT;
2960
- if (matchesAt(KATAKANA, part, index)) return KATAKANA_SCRIPT;
2961
- if (matchesAt(HANGUL, part, index)) return HANGUL_SCRIPT;
2962
- if (matchesAt(THAI, part, index)) return THAI_SCRIPT;
2963
- if (matchesAt(LAO, part, index)) return LAO_SCRIPT;
2964
- if (matchesAt(KHMER, part, index)) return KHMER_SCRIPT;
2965
- if (matchesAt(MYANMAR, part, index)) return MYANMAR_SCRIPT;
2966
- return OTHER_SCRIPT;
2967
- }
2968
- function classifySoutheastAsian(part, index, codePoint) {
2969
- if (codePoint <= THAI_BLOCK_END) return matchesAt(THAI, part, index) ? THAI_SCRIPT : OTHER_SCRIPT;
2970
- if (codePoint <= LAO_BLOCK_END) return matchesAt(LAO, part, index) ? LAO_SCRIPT : OTHER_SCRIPT;
2971
- if (codePoint >= MYANMAR_BLOCK_START && codePoint <= MYANMAR_BLOCK_END) {
2972
- return matchesAt(MYANMAR, part, index) ? MYANMAR_SCRIPT : OTHER_SCRIPT;
2973
- }
2974
- if (codePoint >= KHMER_BLOCK_START && codePoint <= KHMER_BLOCK_END) {
2975
- return matchesAt(KHMER, part, index) ? KHMER_SCRIPT : OTHER_SCRIPT;
2976
- }
2977
- return classifyOutsideCommonBlocks(part, index);
2978
- }
2979
- function classify(part, index, codePoint, current) {
2980
- if (codePoint < LOWEST_NGRAM_CODE_POINT) return OTHER_SCRIPT;
2981
- if (codePoint < SOUTHEAST_ASIAN_BLOCKS_END) return classifySoutheastAsian(part, index, codePoint);
2982
- if (codePoint === PROLONGED_SOUND_MARK) {
2983
- return current === HIRAGANA_SCRIPT ? HIRAGANA_SCRIPT : KATAKANA_SCRIPT;
2984
- }
2985
- if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= HIRAGANA_BLOCK_END) return HIRAGANA_SCRIPT;
2986
- if (codePoint >= KATAKANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) return KATAKANA_SCRIPT;
2987
- if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return HAN_SCRIPT;
2988
- if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return HAN_SCRIPT;
2989
- if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return HANGUL_SCRIPT;
2990
- if (codePoint >= HAN_COMPATIBILITY_START && codePoint <= HAN_COMPATIBILITY_END) return HAN_SCRIPT;
2991
- if (codePoint === IDEOGRAPHIC_ITERATION_MARK) return HAN_SCRIPT;
2992
- return classifyOutsideCommonBlocks(part, index);
2993
- }
2994
- function emitRun(part, offsets, kind, size, out) {
2995
- const count = offsets.length - 1;
2996
- if (count < 1) return;
2997
- if (kind === OTHER_SCRIPT || count <= size) {
2998
- out.push(part.slice(offsets[0], offsets[count]));
2999
- return;
3000
- }
3001
- for (let start = 0; start + size <= count; start++) {
3002
- out.push(part.slice(offsets[start], offsets[start + size]));
3003
- }
3004
- }
3005
- function expandNgrams(part, size, out) {
3006
- const length = part.length;
3007
- if (length === 0) return;
3008
- const offsets = [];
3009
- let kind = OTHER_SCRIPT;
3010
- let index = 0;
3011
- while (index < length) {
3012
- const codePoint = part.codePointAt(index);
3013
- if (codePoint === void 0) break;
3014
- const width = codePoint >= SUPPLEMENTARY_PLANE_START ? 2 : 1;
3015
- if (offsets.length > 0 && isMark(part, index, codePoint)) {
3016
- index += width;
3017
- continue;
3018
- }
3019
- const next = classify(part, index, codePoint, kind);
3020
- if (offsets.length > 0 && next !== kind) {
3021
- offsets.push(index);
3022
- emitRun(part, offsets, kind, size, out);
3023
- offsets.length = 0;
3024
- }
3025
- kind = next;
3026
- offsets.push(index);
3027
- index += width;
3028
- }
3029
- offsets.push(length);
3030
- emitRun(part, offsets, kind, size, out);
3031
- }
3032
-
3033
- // src/core/tokenizer/tokenize.ts
3034
- var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{M}\p{N}_'-]+/u;
3035
- function resolveStopWords(language, override) {
3036
- if (!override) return language.stopWords;
3037
- if (override instanceof Set) return override;
3038
- return override(language.stopWords);
3039
- }
3040
- function splitText(text, language) {
3041
- const pattern = language.tokenizer?.splitPattern ?? DEFAULT_SPLIT_PATTERN;
3042
- const parts = text.split(pattern);
3043
- const ngramSize = language.tokenizer?.ngramSize;
3044
- if (ngramSize === void 0 || ngramSize < 1) return parts;
3045
- const size = Math.floor(ngramSize);
3046
- const expanded = [];
3047
- for (const part of parts) {
3048
- expandNgrams(part, size, expanded);
3049
- }
3050
- return expanded;
3051
- }
3052
- var CHAR_APOSTROPHE = 39;
3053
- var CHAR_S = 115;
3054
- function stripPossessive(token) {
3055
- const len = token.length;
3056
- if (len >= 2 && token.charCodeAt(len - 1) === CHAR_S && token.charCodeAt(len - 2) === CHAR_APOSTROPHE) {
3057
- return token.slice(0, -2);
3058
- }
3059
- if (len >= 1 && token.charCodeAt(len - 1) === CHAR_APOSTROPHE) {
3060
- return token.slice(0, -1);
3061
- }
3062
- return token;
3063
- }
3064
- function tokenize(text, language, options) {
3065
- const {
3066
- stem = true,
3067
- removeStopWords = true,
3068
- removeDiacritics = false,
3069
- collectSurfaces = false,
3070
- customTokenizer,
3071
- stopWordOverride
3072
- } = options ?? {};
3073
- if (customTokenizer) {
3074
- const customResult = customTokenizer.tokenize(text);
3075
- const originals = customResult.map((t) => t.token);
3076
- return {
3077
- tokens: customResult,
3078
- originalTokens: originals
3079
- };
3080
- }
3081
- const normalized = normalizeForSplitting(text);
3082
- const rawParts = splitText(normalized, language);
3083
- const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
3084
- const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
3085
- const stemsTokens = stem && language.stemmer !== null;
3086
- const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
3087
- const stripPossessives = language.tokenizer?.stripPossessive ?? false;
3088
- const tokens = [];
3089
- const originalTokens = [];
3090
- const surfaces = collectSurfaces && stemsTokens ? [] : void 0;
3091
- let position = 0;
3092
- for (const part of rawParts) {
3093
- const candidate = stripPossessives ? stripPossessive(part) : part;
3094
- if (candidate.length < minLength) {
3095
- position++;
3096
- continue;
3097
- }
3098
- if (stopWords.has(candidate)) {
3099
- position++;
3100
- continue;
3101
- }
3102
- const processed = transformToken(candidate, language, stem, effectiveDiacritics);
3103
- if (processed.length > 0) {
3104
- tokens.push({ token: processed, position });
3105
- originalTokens.push(part);
3106
- if (surfaces) {
3107
- const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
3108
- surfaces.push(surface === processed ? void 0 : surface);
3109
- }
3110
- }
3111
- position++;
3112
- }
3113
- return { tokens, originalTokens, surfaces };
3114
- }
3115
- function* tokenizeIterator(text, language, options) {
3116
- const {
3117
- stem = true,
3118
- removeStopWords = true,
3119
- removeDiacritics = false,
3120
- collectSurfaces = false,
3121
- customTokenizer,
3122
- stopWordOverride
3123
- } = options ?? {};
3124
- if (customTokenizer) {
3125
- const customResult = customTokenizer.tokenize(text);
3126
- for (const entry of customResult) {
3127
- yield entry;
3128
- }
3129
- return;
3130
- }
3131
- const normalized = normalizeForSplitting(text);
3132
- const rawParts = splitText(normalized, language);
3133
- const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
3134
- const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
3135
- const stemsTokens = stem && language.stemmer !== null;
3136
- const wantSurfaces = collectSurfaces && stemsTokens;
3137
- const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
3138
- const stripPossessives = language.tokenizer?.stripPossessive ?? false;
3139
- let position = 0;
3140
- for (const part of rawParts) {
3141
- const candidate = stripPossessives ? stripPossessive(part) : part;
3142
- if (candidate.length < minLength) {
3143
- position++;
3144
- continue;
3145
- }
3146
- if (stopWords.has(candidate)) {
3147
- position++;
3148
- continue;
3149
- }
3150
- const processed = transformToken(candidate, language, stem, effectiveDiacritics);
3151
- if (processed.length > 0) {
3152
- if (wantSurfaces) {
3153
- const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
3154
- yield { token: processed, position, surface: surface === processed ? void 0 : surface };
3155
- } else {
3156
- yield { token: processed, position };
3157
- }
3158
- }
3159
- position++;
3160
- }
3161
- }
3162
-
3163
2073
  // src/core/partition/indexing.ts
3164
2074
  function ensureFieldIndex(state, fieldPath, fieldType) {
3165
2075
  if ((fieldType === "number" || fieldType === "number[]") && !state.numericIndexes.has(fieldPath)) {
@@ -6446,32 +5356,21 @@ function createPartitionIndex(partitionId, trackPositions = true) {
6446
5356
  return partition;
6447
5357
  }
6448
5358
 
6449
- // src/search/facets.ts
6450
- function mergeFacets(partitionFacets) {
6451
- const merged = /* @__PURE__ */ new Map();
6452
- const bounds = /* @__PURE__ */ new Map();
6453
- for (const partition of partitionFacets) {
6454
- for (const [field, facetResult] of Object.entries(partition)) {
6455
- let fieldMap = merged.get(field);
6456
- if (!fieldMap) {
6457
- fieldMap = /* @__PURE__ */ new Map();
6458
- merged.set(field, fieldMap);
6459
- }
6460
- bounds.set(field, (bounds.get(field) ?? 0) + facetResult.errorBound);
6461
- for (const [value, count] of Object.entries(facetResult.values)) {
6462
- fieldMap.set(value, (fieldMap.get(value) ?? 0) + count);
6463
- }
6464
- }
6465
- }
6466
- const result = {};
6467
- for (const [field, valueMap] of merged) {
6468
- const values = {};
6469
- for (const [value, count] of valueMap) {
6470
- values[value] = count;
6471
- }
6472
- result[field] = { values, count: valueMap.size, errorBound: bounds.get(field) ?? 0 };
6473
- }
6474
- 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)}`;
6475
5374
  }
6476
5375
 
6477
5376
  // src/core/partition/frozen/doc-table.ts
@@ -7549,123 +6448,6 @@ function subMatchesView(matches, layout, subIndex) {
7549
6448
  };
7550
6449
  }
7551
6450
 
7552
- // src/core/partition/scored-merge.ts
7553
- function kWayMerge(arrays) {
7554
- const nonEmpty = arrays.filter((a) => a.length > 0);
7555
- if (nonEmpty.length === 0) return [];
7556
- if (nonEmpty.length === 1) return nonEmpty[0];
7557
- if (nonEmpty.length <= 4) {
7558
- return sequentialMerge(nonEmpty);
7559
- }
7560
- return heapMerge(nonEmpty);
7561
- }
7562
- function sequentialMerge(arrays) {
7563
- let merged = arrays[0];
7564
- for (let i = 1; i < arrays.length; i++) {
7565
- merged = mergeTwoSorted(merged, arrays[i]);
7566
- }
7567
- return merged;
7568
- }
7569
- function mergeTwoSorted(a, b) {
7570
- const result = new Array(a.length + b.length);
7571
- let ai = 0;
7572
- let bi = 0;
7573
- let ri = 0;
7574
- while (ai < a.length && bi < b.length) {
7575
- if (a[ai].score > b[bi].score || a[ai].score === b[bi].score && compareCodePoints(a[ai].docId, b[bi].docId) <= 0) {
7576
- result[ri++] = a[ai++];
7577
- } else {
7578
- result[ri++] = b[bi++];
7579
- }
7580
- }
7581
- while (ai < a.length) {
7582
- result[ri++] = a[ai++];
7583
- }
7584
- while (bi < b.length) {
7585
- result[ri++] = b[bi++];
7586
- }
7587
- return result;
7588
- }
7589
- function heapMerge(arrays) {
7590
- const heap = [];
7591
- let totalSize = 0;
7592
- for (let i = 0; i < arrays.length; i++) {
7593
- if (arrays[i].length > 0) {
7594
- totalSize += arrays[i].length;
7595
- heapPush(heap, {
7596
- score: arrays[i][0].score,
7597
- docId: arrays[i][0].docId,
7598
- partitionIdx: i,
7599
- resultIdx: 0
7600
- });
7601
- }
7602
- }
7603
- const result = new Array(totalSize);
7604
- let writeIdx = 0;
7605
- while (heap.length > 0) {
7606
- const top = heapPop(heap);
7607
- result[writeIdx++] = arrays[top.partitionIdx][top.resultIdx];
7608
- const nextIdx = top.resultIdx + 1;
7609
- if (nextIdx < arrays[top.partitionIdx].length) {
7610
- const nextDoc = arrays[top.partitionIdx][nextIdx];
7611
- heapPush(heap, {
7612
- score: nextDoc.score,
7613
- docId: nextDoc.docId,
7614
- partitionIdx: top.partitionIdx,
7615
- resultIdx: nextIdx
7616
- });
7617
- }
7618
- }
7619
- return result;
7620
- }
7621
- function heapNodeGreater(a, b) {
7622
- if (a.score !== b.score) return a.score > b.score;
7623
- return compareCodePoints(a.docId, b.docId) < 0;
7624
- }
7625
- function heapPush(heap, node) {
7626
- heap.push(node);
7627
- let idx = heap.length - 1;
7628
- while (idx > 0) {
7629
- const parentIdx = idx - 1 >> 1;
7630
- if (heapNodeGreater(heap[idx], heap[parentIdx])) {
7631
- const tmp = heap[idx];
7632
- heap[idx] = heap[parentIdx];
7633
- heap[parentIdx] = tmp;
7634
- idx = parentIdx;
7635
- } else {
7636
- break;
7637
- }
7638
- }
7639
- }
7640
- function heapPop(heap) {
7641
- const top = heap[0];
7642
- const last = heap.pop();
7643
- if (heap.length > 0) {
7644
- heap[0] = last;
7645
- let idx = 0;
7646
- while (true) {
7647
- const left = 2 * idx + 1;
7648
- const right = 2 * idx + 2;
7649
- let largest = idx;
7650
- if (left < heap.length && heapNodeGreater(heap[left], heap[largest])) {
7651
- largest = left;
7652
- }
7653
- if (right < heap.length && heapNodeGreater(heap[right], heap[largest])) {
7654
- largest = right;
7655
- }
7656
- if (largest !== idx) {
7657
- const tmp = heap[idx];
7658
- heap[idx] = heap[largest];
7659
- heap[largest] = tmp;
7660
- idx = largest;
7661
- } else {
7662
- break;
7663
- }
7664
- }
7665
- }
7666
- return top;
7667
- }
7668
-
7669
6451
  // src/core/partition/composite/stats.ts
7670
6452
  function collectCandidateTerms(subs, params) {
7671
6453
  const { queryTokens, prefixExpansion, tolerance = 0, prefixLength = 2, exact = false } = params;
@@ -8198,707 +6980,12 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
8198
6980
  estimateMemoryBytes() {
8199
6981
  let total = live.estimateMemoryBytes();
8200
6982
  for (const segment of frozen) {
8201
- total += estimatePartitionBytes(segment);
8202
- }
8203
- return total;
8204
- }
8205
- };
8206
- }
8207
-
8208
- // src/languages/registry.ts
8209
- var languages = /* @__PURE__ */ new Map();
8210
- languages.set("english", english);
8211
- function registerLanguage(module) {
8212
- languages.set(module.name, module);
8213
- }
8214
- function getLanguage(name) {
8215
- const lang = languages.get(name);
8216
- if (!lang) {
8217
- throw new NarsilError(ErrorCodes.LANGUAGE_NOT_SUPPORTED, `Language "${name}" is not registered`, { language: name });
8218
- }
8219
- return lang;
8220
- }
8221
-
8222
- // src/partitioning/distributed-scoring.ts
8223
- function analysedQueryTokens(term, language, analysis) {
8224
- const { tokens } = tokenize(term, language, {
8225
- stem: true,
8226
- removeStopWords: true,
8227
- customTokenizer: analysis.customTokenizer,
8228
- stopWordOverride: analysis.stopWords
8229
- });
8230
- const unique = /* @__PURE__ */ new Set();
8231
- for (const { token } of tokens) {
8232
- unique.add(token);
8233
- }
8234
- return unique;
8235
- }
8236
- function copyNumericRecord(source) {
8237
- const copy = /* @__PURE__ */ Object.create(null);
8238
- for (const [key, value] of Object.entries(source)) {
8239
- if (typeof value === "number" && Number.isFinite(value)) {
8240
- copy[key] = value;
8241
- }
8242
- }
8243
- return copy;
8244
- }
8245
- function sanitizeGlobalStats(stats) {
8246
- return {
8247
- totalDocuments: typeof stats.totalDocuments === "number" && Number.isFinite(stats.totalDocuments) ? stats.totalDocuments : 0,
8248
- docFrequencies: copyNumericRecord(stats.docFrequencies),
8249
- totalFieldLengths: copyNumericRecord(stats.totalFieldLengths),
8250
- averageFieldLengths: copyNumericRecord(stats.averageFieldLengths)
8251
- };
8252
- }
8253
- function pruneStatsToQueryTerms(stats, term, language, analysis) {
8254
- const docFrequencies = /* @__PURE__ */ Object.create(null);
8255
- for (const token of analysedQueryTokens(term, language, analysis)) {
8256
- if (Object.hasOwn(stats.docFrequencies, token)) {
8257
- docFrequencies[token] = stats.docFrequencies[token];
8258
- }
8259
- }
8260
- return {
8261
- totalDocuments: stats.totalDocuments,
8262
- docFrequencies,
8263
- totalFieldLengths: stats.totalFieldLengths,
8264
- averageFieldLengths: stats.averageFieldLengths
8265
- };
8266
- }
8267
- function collectQueryTermStats(manager, term, language, analysis, partitionIds) {
8268
- const queryTokens = analysedQueryTokens(term, language, analysis);
8269
- let totalDocuments = 0;
8270
- const docFrequencies = /* @__PURE__ */ Object.create(null);
8271
- const totalFieldLengths = /* @__PURE__ */ Object.create(null);
8272
- const statsPartitions = partitionIds === void 0 ? manager.getAllPartitions() : partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
8273
- for (const partition of statsPartitions) {
8274
- const stats = partition.stats;
8275
- totalDocuments += stats.totalDocuments;
8276
- for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
8277
- totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
8278
- }
8279
- for (const token of queryTokens) {
8280
- if (Object.hasOwn(stats.docFrequencies, token)) {
8281
- docFrequencies[token] = (docFrequencies[token] ?? 0) + stats.docFrequencies[token];
8282
- }
8283
- }
8284
- }
8285
- const averageFieldLengths = /* @__PURE__ */ Object.create(null);
8286
- if (totalDocuments > 0) {
8287
- for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
8288
- averageFieldLengths[field] = totalLength / totalDocuments;
8289
- }
8290
- }
8291
- return { totalDocuments, docFrequencies, totalFieldLengths, averageFieldLengths };
8292
- }
8293
- function mergePartitionStats(statsArray) {
8294
- let totalDocuments = 0;
8295
- const docFrequencies = /* @__PURE__ */ Object.create(null);
8296
- const totalFieldLengths = /* @__PURE__ */ Object.create(null);
8297
- for (const stats of statsArray) {
8298
- totalDocuments += stats.totalDocuments;
8299
- for (const [term, freq] of Object.entries(stats.docFrequencies)) {
8300
- docFrequencies[term] = (docFrequencies[term] ?? 0) + freq;
8301
- }
8302
- for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
8303
- totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
8304
- }
8305
- }
8306
- const averageFieldLengths = /* @__PURE__ */ Object.create(null);
8307
- if (totalDocuments > 0) {
8308
- for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
8309
- averageFieldLengths[field] = totalLength / totalDocuments;
8310
- }
8311
- }
8312
- return {
8313
- totalDocuments,
8314
- docFrequencies,
8315
- totalFieldLengths,
8316
- averageFieldLengths
8317
- };
8318
- }
8319
-
8320
- // src/search/cursor-codec.ts
8321
- function encodeCursorText(text) {
8322
- if (typeof Buffer !== "undefined") return Buffer.from(text, "utf-8").toString("base64");
8323
- const bytes = new TextEncoder().encode(text);
8324
- let binary = "";
8325
- for (const byte of bytes) binary += String.fromCharCode(byte);
8326
- return btoa(binary);
8327
- }
8328
- function decodeCursorText(encoded) {
8329
- if (typeof Buffer !== "undefined") return Buffer.from(encoded, "base64").toString("utf-8");
8330
- const binary = atob(encoded);
8331
- const bytes = new Uint8Array(binary.length);
8332
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
8333
- return new TextDecoder().decode(bytes);
8334
- }
8335
-
8336
- // src/search/sorting.ts
8337
- function isFieldList(sort) {
8338
- return Array.isArray(sort);
8339
- }
8340
- function normalizeSort(sort) {
8341
- if (sort === void 0) return [];
8342
- if (isFieldList(sort)) {
8343
- return sort.map((entry) => ({ field: entry.field, direction: entry.direction }));
8344
- }
8345
- return Object.entries(sort).map(([field, direction]) => ({ field, direction }));
8346
- }
8347
- function requireSortableFields(sort, schema) {
8348
- const fields = normalizeSort(sort);
8349
- if (fields.length === 0) return;
8350
- const flatSchema = flattenSchema(schema);
8351
- for (const entry of fields) {
8352
- if (flatSchema[entry.field] !== "string") continue;
8353
- throw new NarsilError(
8354
- ErrorCodes.SEARCH_INVALID_FIELD,
8355
- `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`,
8356
- { field: entry.field, fieldType: "string" }
8357
- );
8358
- }
8359
- }
8360
- function readFieldValue(obj, path) {
8361
- return readSortField(obj, path);
8362
- }
8363
- function readSortValues(document, fields) {
8364
- if (!document) return fields.map(() => void 0);
8365
- return fields.map((field) => readFieldValue(document, field));
8366
- }
8367
- function applySorting(hits, sort, sortKeyOf) {
8368
- const normalized = normalizeSort(sort);
8369
- if (normalized.length === 0) return hits;
8370
- const directions = normalized.map((entry) => entry.direction);
8371
- const keyCache = /* @__PURE__ */ new Map();
8372
- for (const hit of hits) {
8373
- keyCache.set(hit.id, sortKeyOf(hit.id));
8374
- }
8375
- const sorted = hits.slice();
8376
- sorted.sort(
8377
- (a, b) => compareComparableKeys(keyCache.get(a.id) ?? [], keyCache.get(b.id) ?? [], directions) || compareCodePoints(a.id, b.id)
8378
- );
8379
- return sorted;
8380
- }
8381
-
8382
- // src/search/cursor.ts
8383
- var CURSOR_VERSION = 3;
8384
- function invalidCursor(cursor, reason) {
8385
- const shown = cursor.length > 100 ? `${cursor.slice(0, 100)}...` : cursor;
8386
- return new NarsilError(ErrorCodes.SEARCH_INVALID_CURSOR, `Failed to decode cursor: ${reason}`, { cursor: shown });
8387
- }
8388
- function exceedsCodePoints(value, maximum) {
8389
- if (value.length <= maximum) return false;
8390
- let counted = 0;
8391
- let i = 0;
8392
- while (i < value.length) {
8393
- const unit = value.charCodeAt(i);
8394
- if (unit >= 55296 && unit < 56320 && i + 1 < value.length) {
8395
- const low = value.charCodeAt(i + 1);
8396
- if (low >= 56320 && low < 57344) i++;
8397
- }
8398
- i++;
8399
- counted++;
8400
- if (counted > maximum) return true;
8401
- }
8402
- return false;
8403
- }
8404
- function sortSignatureOf(sort) {
8405
- const fields = normalizeSort(sort);
8406
- if (fields.length === 0) return null;
8407
- if (fields.length > MAX_SORT_FIELDS) {
8408
- throw new NarsilError(
8409
- ErrorCodes.SEARCH_INVALID_FIELD,
8410
- `A sort names at most ${MAX_SORT_FIELDS} fields, because the paging cursor carries one value per field`,
8411
- { count: fields.length, limit: MAX_SORT_FIELDS }
8412
- );
8413
- }
8414
- for (const entry of fields) {
8415
- if (entry.field.length > MAX_SORT_FIELD_NAME_LENGTH) {
8416
- throw new NarsilError(
8417
- ErrorCodes.SEARCH_INVALID_FIELD,
8418
- `A sort field name holds at most ${MAX_SORT_FIELD_NAME_LENGTH} characters, because the paging cursor carries the sort with every page`,
8419
- { field: entry.field.slice(0, 64), length: entry.field.length, limit: MAX_SORT_FIELD_NAME_LENGTH }
8420
- );
8421
- }
8422
- }
8423
- return JSON.stringify(fields.map((entry) => [entry.field, entry.direction]));
8424
- }
8425
- function encodePageCursor(cursor) {
8426
- const payload = { v: CURSOR_VERSION, a: cursor.anchor };
8427
- if (cursor.sortKey !== null && cursor.sortSignature !== null) {
8428
- payload.k = cursor.sortKey.map(toComparableSortValue);
8429
- payload.o = cursor.sortSignature;
8430
- } else if (cursor.score !== null) {
8431
- payload.s = cursor.score;
8432
- }
8433
- payload.q = cursor.binding;
8434
- return encodeCursorText(JSON.stringify(payload));
8435
- }
8436
- function decodeSortKey(value, cursor) {
8437
- if (!Array.isArray(value)) throw invalidCursor(cursor, '"k" must be an array');
8438
- if (value.length > MAX_SORT_FIELDS) throw invalidCursor(cursor, '"k" carries too many sort values');
8439
- const key = [];
8440
- for (const entry of value) {
8441
- if (entry === null || typeof entry === "boolean") {
8442
- key.push(entry);
8443
- continue;
8444
- }
8445
- if (typeof entry === "number") {
8446
- if (!Number.isFinite(entry)) throw invalidCursor(cursor, '"k" holds a number that is not finite');
8447
- key.push(entry);
8448
- continue;
8449
- }
8450
- if (typeof entry === "string") {
8451
- if (exceedsCodePoints(entry, SORT_VALUE_MAX_CODE_POINTS)) {
8452
- throw invalidCursor(cursor, '"k" holds an oversized sort value');
8453
- }
8454
- key.push(entry);
8455
- continue;
8456
- }
8457
- throw invalidCursor(cursor, '"k" accepts a string, a finite number, a boolean, or null');
8458
- }
8459
- return key;
8460
- }
8461
- function decodePageCursor(cursor) {
8462
- if (cursor.length > MAX_CURSOR_LENGTH) throw invalidCursor(cursor, "the cursor is too long");
8463
- let json;
8464
- try {
8465
- json = decodeCursorText(cursor);
8466
- } catch {
8467
- throw invalidCursor(cursor, "invalid base64 encoding");
8468
- }
8469
- let parsed;
8470
- try {
8471
- parsed = JSON.parse(json);
8472
- } catch {
8473
- throw invalidCursor(cursor, "invalid JSON");
8474
- }
8475
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8476
- throw invalidCursor(cursor, "expected an object");
8477
- }
8478
- const { v, a, s, k, o, q } = parsed;
8479
- if (v !== CURSOR_VERSION) throw invalidCursor(cursor, `unsupported cursor version ${String(v)}`);
8480
- if (typeof q !== "string" || !/^[0-9a-f]{8}$/.test(q)) {
8481
- throw invalidCursor(cursor, '"q" must be 8 lowercase hex digits');
8482
- }
8483
- if (typeof a !== "string" || a.length === 0) throw invalidCursor(cursor, '"a" must be a non-empty string');
8484
- if (exceedsCodePoints(a, MAX_CURSOR_ANCHOR_CODE_POINTS)) {
8485
- throw invalidCursor(cursor, '"a" is too long to be a document id');
8486
- }
8487
- if (s !== void 0 && (typeof s !== "number" || !Number.isFinite(s))) {
8488
- throw invalidCursor(cursor, '"s" must be a finite number');
8489
- }
8490
- if (s !== void 0 && k !== void 0) throw invalidCursor(cursor, 'a cursor carries "s" or "k", never both');
8491
- const sortKey = k === void 0 ? null : decodeSortKey(k, cursor);
8492
- if (o !== void 0 && typeof o !== "string") throw invalidCursor(cursor, '"o" must be a string');
8493
- if (sortKey === null !== (o === void 0)) {
8494
- throw invalidCursor(cursor, '"k" and "o" have to arrive together');
8495
- }
8496
- return {
8497
- anchor: a,
8498
- score: typeof s === "number" ? s : null,
8499
- sortKey,
8500
- sortSignature: typeof o === "string" ? o : null,
8501
- binding: q
8502
- };
8503
- }
8504
- function requireMatchingCursor(cursor, encoded, signature, scoreAnchored, binding) {
8505
- if (cursor.binding !== binding) {
8506
- throw invalidCursor(encoded, "the cursor belongs to a different query");
8507
- }
8508
- if (signature !== null) {
8509
- if (cursor.sortSignature !== signature) {
8510
- throw invalidCursor(encoded, "the cursor belongs to a different sort order");
8511
- }
8512
- return;
8513
- }
8514
- if (cursor.sortSignature !== null) {
8515
- throw invalidCursor(encoded, "the cursor belongs to a sorted request");
8516
- }
8517
- if (scoreAnchored && cursor.score === null) {
8518
- throw invalidCursor(encoded, "the cursor carries no score to seek from");
8519
- }
8520
- if (!scoreAnchored && cursor.score !== null) {
8521
- throw invalidCursor(encoded, "the cursor belongs to a search rather than a listing");
8522
- }
8523
- }
8524
-
8525
- // src/search/pagination.ts
8526
- function clampRowCount(value, fallback) {
8527
- if (value === void 0 || !Number.isFinite(value)) return fallback;
8528
- return Math.max(0, Math.floor(value));
8529
- }
8530
- function requireWithinResultWindow(limit, offset) {
8531
- const depth = offset + limit;
8532
- if (depth <= RESULT_WINDOW) return;
8533
- throw new NarsilError(
8534
- ErrorCodes.SEARCH_RESULT_WINDOW_EXCEEDED,
8535
- `A request reaches the first ${RESULT_WINDOW} results, and offset + limit is ${depth}. Page past that with the cursor each result carries`,
8536
- { limit, offset, window: RESULT_WINDOW }
8537
- );
8538
- }
8539
- function lastOrganicResult(sliced, pinnedIds) {
8540
- for (let i = sliced.length - 1; i >= 0; i--) {
8541
- if (pinnedIds === void 0 || !pinnedIds.has(sliced[i].id)) return sliced[i];
8542
- }
8543
- return void 0;
8544
- }
8545
- function ordersAfterAnchor(result, anchor, sort) {
8546
- if (sort !== void 0 && anchor.sortKey !== null) {
8547
- const comparison = compareSortValues(sort.sortKeyOf(result.id), anchor.sortKey, sort.directions) || compareCodePoints(result.id, anchor.anchor);
8548
- return comparison > 0;
8549
- }
8550
- if (anchor.score === null) return true;
8551
- if (result.score === void 0) return true;
8552
- if (result.score < anchor.score) return true;
8553
- return result.score === anchor.score && compareCodePoints(result.id, anchor.anchor) > 0;
8554
- }
8555
- function applyPagination(results, limit, offset, binding, cursor, sort, pinnedIds) {
8556
- const decoded = cursor ? decodePageCursor(cursor) : null;
8557
- if (decoded !== null && cursor !== void 0) {
8558
- requireMatchingCursor(decoded, cursor, sort?.signature ?? null, true, binding);
8559
- }
8560
- if (limit <= 0) {
8561
- return { paginated: [] };
8562
- }
8563
- let startIndex = 0;
8564
- if (decoded !== null) {
8565
- startIndex = results.length;
8566
- for (let i = 0; i < results.length; i++) {
8567
- if (ordersAfterAnchor(results[i], decoded, sort)) {
8568
- startIndex = i;
8569
- break;
8570
- }
8571
- }
8572
- }
8573
- const afterOffset = startIndex + offset;
8574
- const sliced = results.slice(afterOffset, afterOffset + limit);
8575
- let nextCursor;
8576
- const hasMore = afterOffset + limit < results.length;
8577
- const lastResult = lastOrganicResult(sliced, pinnedIds);
8578
- if (hasMore && lastResult !== void 0) {
8579
- nextCursor = sort !== void 0 ? encodePageCursor({
8580
- anchor: lastResult.id,
8581
- score: null,
8582
- sortKey: sort.sortKeyOf(lastResult.id).map(toComparableSortValue),
8583
- sortSignature: sort.signature,
8584
- binding
8585
- }) : encodePageCursor({
8586
- anchor: lastResult.id,
8587
- score: lastResult.score ?? null,
8588
- sortKey: null,
8589
- sortSignature: null,
8590
- binding
8591
- });
8592
- }
8593
- return { paginated: sliced, nextCursor };
8594
- }
8595
-
8596
- // src/search/fulltext.ts
8597
- function resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken) {
8598
- const term = params.term;
8599
- if (term === void 0) return void 0;
8600
- const unstemmed = tokenize(term, language, {
8601
- stem: false,
8602
- removeStopWords: true,
8603
- customTokenizer: options?.customTokenizer,
8604
- stopWordOverride: options?.stopWords
8605
- });
8606
- let surfacePrefix;
8607
- for (const t of unstemmed.tokens) {
8608
- if (t.position === lastToken.position) {
8609
- surfacePrefix = t.token;
8610
- break;
8611
- }
8612
- }
8613
- if (surfacePrefix === void 0 || surfacePrefix.length === 0) return void 0;
8614
- const expansions = partition.expandTermPrefix(surfacePrefix, lastToken.token, PREFIX_MAX_EXPANSIONS);
8615
- const terms = [];
8616
- for (const expanded of expansions) {
8617
- if (expanded !== lastToken.token) terms.push(expanded);
8618
- }
8619
- for (const qt of queryTokens) {
8620
- if (qt.token === lastToken.token) {
8621
- return { token: qt.token, terms };
8622
- }
8623
- }
8624
- return void 0;
8625
- }
8626
- function prepareFulltextQuery(partition, params, language, schema, options) {
8627
- if (!params.term || params.term.trim().length === 0) return null;
8628
- if (params.fields && params.fields.length === 0) return null;
8629
- const flatSchema = flattenSchema(schema);
8630
- if (params.fields) {
8631
- validateSearchFields(params.fields, flatSchema);
8632
- }
8633
- const queryTokenResult = tokenize(params.term, language, {
8634
- stem: true,
8635
- removeStopWords: true,
8636
- customTokenizer: options?.customTokenizer,
8637
- stopWordOverride: options?.stopWords
8638
- });
8639
- if (queryTokenResult.tokens.length === 0) return null;
8640
- const queryTokens = deduplicateTokens(queryTokenResult.tokens);
8641
- let prefixExpansion;
8642
- if (params.prefix === true && params.exact !== true) {
8643
- const lastToken = queryTokenResult.tokens[queryTokenResult.tokens.length - 1];
8644
- prefixExpansion = resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken);
8645
- }
8646
- let filterBitset;
8647
- if (params.filters) {
8648
- filterBitset = partition.applyFiltersBitset(params.filters, schema);
8649
- if (bitsetIsEmpty(filterBitset)) return null;
8650
- }
8651
- return { queryTokens, prefixExpansion, filterBitset };
8652
- }
8653
- function fulltextMatches(partition, params, language, schema, options) {
8654
- const prepared = prepareFulltextQuery(partition, params, language, schema, options);
8655
- if (prepared === null) return null;
8656
- return partition.searchFulltextMatches({
8657
- queryTokens: prepared.queryTokens,
8658
- prefixExpansion: prepared.prefixExpansion,
8659
- fields: params.fields,
8660
- tolerance: params.tolerance ?? 0,
8661
- prefixLength: params.prefixLength ?? 2,
8662
- exact: params.exact ?? false,
8663
- filterBitset: prepared.filterBitset
8664
- });
8665
- }
8666
- function fulltextSearch(partition, params, language, schema, options) {
8667
- const prepared = prepareFulltextQuery(partition, params, language, schema, options);
8668
- if (prepared === null) {
8669
- return { scored: [], totalMatched: 0 };
8670
- }
8671
- const { queryTokens, prefixExpansion, filterBitset } = prepared;
8672
- 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;
8673
- const maxResults = needsAllResults ? void 0 : clampRowCount(params.limit, DEFAULT_PAGE_SIZE) + clampRowCount(params.offset, 0) + 1;
8674
- const collectMatchedSet = params.facets !== void 0 && !needsAllResults ? "ordinals" : void 0;
8675
- const collectComponents = params.includeScoreComponents === true || params.termMatch !== void 0 && params.termMatch !== "any";
8676
- const rawResult = partition.searchFulltext({
8677
- queryTokens,
8678
- prefixExpansion,
8679
- fields: params.fields,
8680
- boost: params.boost,
8681
- tolerance: params.tolerance ?? 0,
8682
- prefixLength: params.prefixLength ?? 2,
8683
- exact: params.exact ?? false,
8684
- bm25Params: options?.bm25Params,
8685
- globalStats: options?.globalStats,
8686
- maxResults,
8687
- termMatch: params.termMatch,
8688
- filterBitset,
8689
- collectComponents,
8690
- collectMatchedSet
8691
- });
8692
- let scored = rawResult.scored;
8693
- const termMatch = params.termMatch ?? "any";
8694
- if (termMatch !== "any") {
8695
- scored = filterByTermCoverage(
8696
- scored,
8697
- queryTokens,
8698
- termMatch,
8699
- params.tolerance ?? 0,
8700
- params.exact ?? false,
8701
- prefixExpansion
8702
- );
8703
- }
8704
- if (params.minScore !== void 0 && params.minScore > 0) {
8705
- const threshold = params.minScore;
8706
- scored = scored.filter((doc) => doc.score >= threshold);
8707
- }
8708
- const totalMatched = needsAllResults ? scored.length : rawResult.totalMatched;
8709
- if (params.facets === void 0) {
8710
- return { scored, totalMatched };
8711
- }
8712
- if (needsAllResults) {
8713
- return { scored, totalMatched, matchedIds: scored.map((doc) => doc.docId) };
8714
- }
8715
- return { scored, totalMatched, matchedOrdinalBitset: rawResult.matchedOrdinalBitset };
8716
- }
8717
- function validateSearchFields(fields, flatSchema) {
8718
- for (const field of fields) {
8719
- const fieldType = flatSchema[field];
8720
- if (!fieldType) {
8721
- throw new NarsilError(ErrorCodes.SEARCH_INVALID_FIELD, `Field "${field}" does not exist in the schema`, { field });
8722
- }
8723
- if (!isTextFieldType(fieldType) && fieldType !== "string[]") {
8724
- throw new NarsilError(
8725
- ErrorCodes.SEARCH_INVALID_FIELD,
8726
- `Field "${field}" has type "${fieldType}" which cannot be used for full-text search`,
8727
- { field, fieldType }
8728
- );
8729
- }
8730
- }
8731
- }
8732
- function deduplicateTokens(tokens) {
8733
- const seen = /* @__PURE__ */ new Set();
8734
- const result = [];
8735
- for (const t of tokens) {
8736
- if (!seen.has(t.token)) {
8737
- seen.add(t.token);
8738
- result.push(t);
8739
- }
8740
- }
8741
- return result;
8742
- }
8743
- function filterByTermCoverage(scored, queryTokens, policy, tolerance, exact, prefixExpansion) {
8744
- const requiredCount = policy === "all" ? queryTokens.length : policy;
8745
- if (requiredCount <= 0) return scored;
8746
- if (requiredCount > queryTokens.length) return [];
8747
- const expansionTerms = prefixExpansion ? new Set(prefixExpansion.terms) : void 0;
8748
- return scored.filter((doc) => {
8749
- const matched = countDocTermMatches(doc, queryTokens, tolerance, exact, prefixExpansion?.token, expansionTerms);
8750
- return matched >= requiredCount;
8751
- });
8752
- }
8753
- function countDocTermMatches(doc, queryTokens, tolerance, exact, prefixToken, expansionTerms) {
8754
- const indexTokens = Object.keys(doc.idf);
8755
- if (indexTokens.length === 0) return 0;
8756
- let count = 0;
8757
- for (const qt of queryTokens) {
8758
- if (prefixToken !== void 0 && qt.token === prefixToken) {
8759
- if (prefixTokenSatisfied(qt.token, indexTokens, expansionTerms)) {
8760
- count++;
8761
- }
8762
- continue;
8763
- }
8764
- if (queryTermSatisfied(qt.token, indexTokens, tolerance, exact)) {
8765
- count++;
8766
- }
8767
- }
8768
- return count;
8769
- }
8770
- function prefixTokenSatisfied(prefixToken, indexTokens, expansionTerms) {
8771
- for (const indexToken of indexTokens) {
8772
- if (indexToken === prefixToken) return true;
8773
- if (expansionTerms?.has(indexToken)) return true;
8774
- }
8775
- return false;
8776
- }
8777
- function queryTermSatisfied(queryToken, indexTokens, tolerance, exact) {
8778
- if (exact || tolerance === 0) {
8779
- return indexTokens.includes(queryToken);
8780
- }
8781
- for (const indexToken of indexTokens) {
8782
- if (boundedLevenshtein(queryToken, indexToken, tolerance).withinTolerance) {
8783
- return true;
8784
- }
8785
- }
8786
- return false;
8787
- }
8788
-
8789
- // src/partitioning/partition-selection.ts
8790
- function partitionsIn(manager, partitionIds) {
8791
- if (partitionIds === void 0) {
8792
- return manager.getAllPartitions();
8793
- }
8794
- const partitions = [];
8795
- for (const partitionId of partitionIds) {
8796
- const partition = manager.partitionAt(partitionId);
8797
- if (partition !== void 0) {
8798
- partitions.push(partition);
8799
- }
8800
- }
8801
- return partitions;
8802
- }
8803
-
8804
- // src/partitioning/fan-out.ts
8805
- async function fanOutQuery(manager, params, language, schema, config, searchOptions) {
8806
- const partitions = partitionsIn(manager, config.partitionIds);
8807
- if (partitions.length === 0) {
8808
- return { scored: [], totalMatched: 0 };
8809
- }
8810
- let globalStats;
8811
- const effectiveMode = resolveEffectiveMode(config);
8812
- if (effectiveMode === "dfs") {
8813
- if (params.term !== void 0 && params.term.trim().length > 0) {
8814
- globalStats = collectQueryTermStats(
8815
- manager,
8816
- params.term,
8817
- language,
8818
- {
8819
- stopWords: searchOptions?.stopWords,
8820
- customTokenizer: searchOptions?.customTokenizer
8821
- },
8822
- config.partitionIds
8823
- );
8824
- }
8825
- } else if (effectiveMode === "broadcast") {
8826
- globalStats = config.globalStats;
8827
- }
8828
- const options = buildSearchOptions(searchOptions, globalStats);
8829
- if (partitions.length === 1 && !config.dispatcher) {
8830
- const result = dispatchSinglePartition(partitions[0], params, language, schema, options);
8831
- let facets2;
8832
- if (params.facets) {
8833
- facets2 = partitions[0].computeFacets(facetMatchSetOf(result), params.facets, schema);
8834
- }
8835
- return { scored: result.scored, totalMatched: result.totalMatched, facets: facets2 };
8836
- }
8837
- let outcomes;
8838
- let facets;
8839
- if (config.dispatcher) {
8840
- outcomes = await dispatchWithDispatcher(partitions, params, language, schema, options, config.dispatcher);
8841
- if (params.facets) {
8842
- facets = collectAndMergeFacets(outcomes, params, schema);
8843
- }
8844
- } else {
8845
- outcomes = [];
8846
- const partitionFacets = [];
8847
- for (const partition of partitions) {
8848
- const result = dispatchSinglePartition(partition, params, language, schema, options);
8849
- if (params.facets) {
8850
- partitionFacets.push(partition.computeFacets(facetMatchSetOf(result), params.facets, schema));
6983
+ total += estimatePartitionBytes(segment);
8851
6984
  }
8852
- outcomes.push({ result, partition });
8853
- }
8854
- if (params.facets) {
8855
- facets = mergeFacets(partitionFacets);
6985
+ return total;
8856
6986
  }
8857
- }
8858
- const allScoredArrays = outcomes.map((o) => o.result.scored);
8859
- const merged = kWayMerge(allScoredArrays);
8860
- let totalMatched = 0;
8861
- for (const outcome of outcomes) {
8862
- totalMatched += outcome.result.totalMatched;
8863
- }
8864
- return { scored: merged, totalMatched, facets };
8865
- }
8866
- function facetMatchSetOf(result) {
8867
- if (result.matchedOrdinalBitset !== void 0) {
8868
- return { ordinalBitset: result.matchedOrdinalBitset };
8869
- }
8870
- return new Set(result.matchedIds ?? []);
8871
- }
8872
- function resolveEffectiveMode(config) {
8873
- if (config.scoringMode === "broadcast" && !config.globalStats) {
8874
- return "dfs";
8875
- }
8876
- return config.scoringMode;
8877
- }
8878
- function buildSearchOptions(base, globalStats) {
8879
- return {
8880
- ...base,
8881
- globalStats: globalStats ?? base?.globalStats
8882
6987
  };
8883
6988
  }
8884
- function dispatchWithDispatcher(partitions, params, language, schema, options, dispatcher) {
8885
- const promises = partitions.map(
8886
- (partition) => dispatcher.dispatch(partition, params, language, schema, options).then((result) => ({ result, partition }))
8887
- );
8888
- return Promise.all(promises);
8889
- }
8890
- function dispatchSinglePartition(partition, params, language, schema, options) {
8891
- return fulltextSearch(partition, params, language, schema, options);
8892
- }
8893
- function collectAndMergeFacets(outcomes, params, schema) {
8894
- const partitionFacets = [];
8895
- for (const outcome of outcomes) {
8896
- if (!params.facets) continue;
8897
- const facetResult = outcome.partition.computeFacets(facetMatchSetOf(outcome.result), params.facets, schema);
8898
- partitionFacets.push(facetResult);
8899
- }
8900
- return mergeFacets(partitionFacets);
8901
- }
8902
6989
 
8903
6990
  // src/partitioning/insert-options.ts
8904
6991
  function resolvePartitionInsertOptions(config, analysis, options) {
@@ -8916,65 +7003,6 @@ function resolvePartitionInsertOptions(config, analysis, options) {
8916
7003
  return resolved;
8917
7004
  }
8918
7005
 
8919
- // src/analysis/registry.ts
8920
- var tokenizers = /* @__PURE__ */ new Map();
8921
- var stopWordSets = /* @__PURE__ */ new Map();
8922
- function requireName(name, kind) {
8923
- if (typeof name !== "string" || name.trim().length === 0) {
8924
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, `A registered ${kind} needs a non-empty name`, { name });
8925
- }
8926
- }
8927
- function registerTokenizer(name, tokenizer) {
8928
- requireName(name, "tokenizer");
8929
- if (typeof tokenizer?.tokenize !== "function") {
8930
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" needs a tokenize method`, { tokenizer: name });
8931
- }
8932
- tokenizers.set(name, tokenizer);
8933
- }
8934
- function registerStopWords(name, stopWords) {
8935
- requireName(name, "stop word set");
8936
- if (!(stopWords instanceof Set) && typeof stopWords !== "function") {
8937
- throw new NarsilError(
8938
- ErrorCodes.CONFIG_INVALID,
8939
- `Stop word set "${name}" needs a Set of words or a function returning one`,
8940
- { stopWords: name }
8941
- );
8942
- }
8943
- stopWordSets.set(name, stopWords);
8944
- }
8945
- function hasTokenizer(name) {
8946
- return tokenizers.has(name);
8947
- }
8948
- function hasStopWords(name) {
8949
- return stopWordSets.has(name);
8950
- }
8951
- function getTokenizer(name) {
8952
- const tokenizer = tokenizers.get(name);
8953
- if (!tokenizer) {
8954
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" is not registered on this engine`, {
8955
- tokenizer: name,
8956
- available: [...tokenizers.keys()]
8957
- });
8958
- }
8959
- return tokenizer;
8960
- }
8961
- function getStopWords(name) {
8962
- const stopWords = stopWordSets.get(name);
8963
- if (!stopWords) {
8964
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Stop word set "${name}" is not registered on this engine`, {
8965
- stopWords: name,
8966
- available: [...stopWordSets.keys()]
8967
- });
8968
- }
8969
- return stopWords;
8970
- }
8971
- function resolveIndexAnalysis(config) {
8972
- return {
8973
- stopWords: typeof config.stopWords === "string" ? getStopWords(config.stopWords) : config.stopWords,
8974
- customTokenizer: typeof config.tokenizer === "string" ? getTokenizer(config.tokenizer) : config.tokenizer
8975
- };
8976
- }
8977
-
8978
7006
  // src/partitioning/manager/nested-values.ts
8979
7007
  function setNestedValue(obj, path, value) {
8980
7008
  if (!path.includes(".")) {
@@ -9299,35 +7327,6 @@ function createPartitionManager(indexName, config, language, router, initialPart
9299
7327
  return manager;
9300
7328
  }
9301
7329
 
9302
- // src/partitioning/match-count.ts
9303
- function countsWithoutScores(params) {
9304
- return params.minScore === void 0 && (params.termMatch === void 0 || params.termMatch === "any");
9305
- }
9306
- function fanOutMatchCount(manager, params, language, schema, options) {
9307
- let count = 0;
9308
- for (const partition of partitionsIn(manager, options?.partitionIds)) {
9309
- const matches = fulltextMatches(partition, params, language, schema, options?.searchOptions);
9310
- if (matches !== null) count += matches.count;
9311
- }
9312
- return count;
9313
- }
9314
-
9315
- // src/core/hash.ts
9316
- var FNV_OFFSET_BASIS = 2166136261;
9317
- var FNV_PRIME = 16777619;
9318
- var encoder3 = new TextEncoder();
9319
- function fnv1aBytes(bytes) {
9320
- let hash = FNV_OFFSET_BASIS;
9321
- for (let i = 0; i < bytes.length; i++) {
9322
- hash ^= bytes[i];
9323
- hash = Math.imul(hash, FNV_PRIME);
9324
- }
9325
- return hash >>> 0;
9326
- }
9327
- function fnv1a(input) {
9328
- return fnv1aBytes(encoder3.encode(input));
9329
- }
9330
-
9331
7330
  // src/partitioning/router.ts
9332
7331
  function createPartitionRouter() {
9333
7332
  function route(docId, partitionCount) {
@@ -9374,40 +7373,6 @@ async function isNodeMainThread() {
9374
7373
  }
9375
7374
  }
9376
7375
 
9377
- // src/runtime/detect.ts
9378
- function detectRuntimeName() {
9379
- if ("Bun" in globalThis) {
9380
- return "bun";
9381
- }
9382
- if ("Deno" in globalThis) {
9383
- return "deno";
9384
- }
9385
- const proc = globalThis.process;
9386
- if (typeof proc === "object" && proc !== null && typeof proc.versions === "object" && typeof proc.versions?.node === "string") {
9387
- return "node";
9388
- }
9389
- return "browser";
9390
- }
9391
- function getCpuCount() {
9392
- const nav = globalThis.navigator;
9393
- if (nav && typeof nav.hardwareConcurrency === "number" && nav.hardwareConcurrency >= 1) {
9394
- return nav.hardwareConcurrency;
9395
- }
9396
- return 1;
9397
- }
9398
- function detectRuntime() {
9399
- const runtime = detectRuntimeName();
9400
- return {
9401
- runtime,
9402
- cpuCount: getCpuCount(),
9403
- supportsWorkerThreads: runtime === "node" || runtime === "bun",
9404
- supportsWebWorkers: runtime === "browser" || runtime === "deno" || runtime === "bun",
9405
- supportsFileSystem: runtime === "node" || runtime === "bun" || runtime === "deno",
9406
- supportsIndexedDB: runtime === "browser" || "indexedDB" in globalThis,
9407
- supportsBroadcastChannel: "BroadcastChannel" in globalThis
9408
- };
9409
- }
9410
-
9411
7376
  // src/vector/hnsw-worker-dispatch.ts
9412
7377
  function resolveWorkerEntryPoint() {
9413
7378
  const base = import.meta.url;
@@ -9559,6 +7524,19 @@ var VECTOR_WORKER_COPIES_ALLOWED = { enabled: true };
9559
7524
  function liveSize(state) {
9560
7525
  return state.store.size - state.tombstones.size;
9561
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
+ }
9562
7540
  function adoptGraph(state, graph) {
9563
7541
  if (graph === null && state.hnsw !== null) {
9564
7542
  state.hnsw.clear();
@@ -9672,6 +7650,7 @@ var WORKER_YOUNG_GENERATION_MB = 24;
9672
7650
  var MIN_WORKER_COUNT = 2;
9673
7651
  var MAX_WORKER_COUNT = 8;
9674
7652
  var FALLBACK_CPU_COUNT = 4;
7653
+ var MIN_CORES_FOR_SEVERAL_REQUEST_THREADS = 3;
9675
7654
  var DEFAULT_BACKPRESSURE_LIMIT = 100;
9676
7655
  var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
9677
7656
  var SHUTDOWN_TIMEOUT_MS = 5e3;
@@ -9681,24 +7660,34 @@ var CONSECUTIVE_TIMEOUTS_BEFORE_DEATH = 3;
9681
7660
  function toFinite(value) {
9682
7661
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
9683
7662
  }
9684
- function resolveWorkerCount(requested) {
9685
- if (requested !== void 0 && requested > 0) {
9686
- return requested;
9687
- }
9688
- let cpuCount = FALLBACK_CPU_COUNT;
7663
+ function toHeapLimit(value) {
7664
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
7665
+ }
7666
+ function detectCpuCount() {
9689
7667
  try {
9690
7668
  if (navigator?.hardwareConcurrency) {
9691
- cpuCount = navigator.hardwareConcurrency;
9692
- } else if (typeof process !== "undefined") {
7669
+ return navigator.hardwareConcurrency;
7670
+ }
7671
+ if (typeof process !== "undefined") {
9693
7672
  const ap = process.availableParallelism;
9694
7673
  if (typeof ap === "function") {
9695
- cpuCount = ap();
7674
+ return ap();
9696
7675
  }
9697
7676
  }
9698
7677
  } catch {
9699
- cpuCount = FALLBACK_CPU_COUNT;
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;
9700
7685
  }
9701
- return Math.max(MIN_WORKER_COUNT, Math.min(MAX_WORKER_COUNT, cpuCount - 1));
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);
9702
7691
  }
9703
7692
  function splitWorkerBudget(total) {
9704
7693
  const keyword = Math.ceil(total / 2);
@@ -9831,6 +7820,7 @@ function createWorkerPool(config) {
9831
7820
  workerId: indices[n],
9832
7821
  heapUsed: toFinite(report?.heapUsed),
9833
7822
  heapTotal: toFinite(report?.heapTotal),
7823
+ heapLimit: toHeapLimit(report?.heapLimit),
9834
7824
  external: toFinite(report?.external)
9835
7825
  });
9836
7826
  }
@@ -9853,6 +7843,9 @@ function createWorkerPool(config) {
9853
7843
  function getAllExecutors() {
9854
7844
  return [...workers.values()].map((slot) => slot.executor);
9855
7845
  }
7846
+ function executorEntries() {
7847
+ return [...workers].map(([workerId, slot]) => ({ workerId, executor: slot.executor }));
7848
+ }
9856
7849
  function executorsHolding(indexName) {
9857
7850
  const holders = [];
9858
7851
  for (const slot of workers.values()) {
@@ -9919,6 +7912,7 @@ function createWorkerPool(config) {
9919
7912
  return {
9920
7913
  getExecutor,
9921
7914
  getAllExecutors,
7915
+ executorEntries,
9922
7916
  executorsHolding,
9923
7917
  leaseLeastBusy,
9924
7918
  leaseIdle,
@@ -10019,22 +8013,22 @@ async function createVectorSearchPool(requestedCount) {
10019
8013
  if (slots.length === 0) return null;
10020
8014
  let requestCounter = 0;
10021
8015
  function send(slot, key, message, timeoutMs) {
10022
- return new Promise((resolve, reject2) => {
8016
+ return new Promise((resolve, reject) => {
10023
8017
  if (!slot.alive) {
10024
- reject2(new Error("Vector search worker is not running"));
8018
+ reject(new Error("Vector search worker is not running"));
10025
8019
  return;
10026
8020
  }
10027
8021
  const timer = setTimeout(() => {
10028
8022
  slot.pending.delete(key);
10029
- reject2(new Error(`Vector search worker did not answer within ${timeoutMs}ms`));
8023
+ reject(new Error(`Vector search worker did not answer within ${timeoutMs}ms`));
10030
8024
  }, timeoutMs);
10031
- slot.pending.set(key, { resolve, reject: reject2, timer });
8025
+ slot.pending.set(key, { resolve, reject, timer });
10032
8026
  try {
10033
8027
  slot.worker.postMessage(message);
10034
8028
  } catch (err) {
10035
8029
  slot.pending.delete(key);
10036
8030
  clearTimeout(timer);
10037
- reject2(err instanceof Error ? err : new Error(String(err)));
8031
+ reject(err instanceof Error ? err : new Error(String(err)));
10038
8032
  }
10039
8033
  });
10040
8034
  }
@@ -10191,6 +8185,51 @@ async function releaseVectorSearchPool() {
10191
8185
  await pool?.shutdown();
10192
8186
  }
10193
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
+
10194
8233
  // src/vector/shared-generation/layout.ts
10195
8234
  var SCRATCH_ALIGNMENT = 16;
10196
8235
  function alignUp(bytes) {
@@ -10314,13 +8353,62 @@ function invalidateWorkerCopies(state) {
10314
8353
  if (state.workerCopyHandle === null) return;
10315
8354
  const handle = state.workerCopyHandle;
10316
8355
  const pool = state.workerCopyPool;
8356
+ const hosted = state.workerCopyMode === "hosted";
10317
8357
  state.workerCopyHandle = null;
10318
8358
  state.workerCopyPool = null;
10319
8359
  state.workerCopyRevision = -1;
10320
8360
  state.workerCopyMode = null;
8361
+ if (hosted) {
8362
+ void state.workerCopies.host?.drop(state.indexName, state.fieldName, handle).catch(() => void 0);
8363
+ return;
8364
+ }
10321
8365
  void pool?.drop(handle).catch(() => void 0);
10322
8366
  void releaseVectorSearchPool().catch(() => void 0);
10323
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
+ }
10324
8412
  function captureCloneSnapshot(state) {
10325
8413
  if (!state.hnsw) return null;
10326
8414
  return {
@@ -10337,11 +8425,19 @@ function scheduleWorkerCopyLoad(state) {
10337
8425
  if (state.disposed || state.workerCopyLoading || state.building) return;
10338
8426
  if (state.workerCopyHandle !== null) return;
10339
8427
  if (!state.hnsw || state.buffer.size > 0) return;
10340
- if (liveSize(state) < WORKER_COPY_MIN_VECTORS) return;
8428
+ const host = state.workerCopies.host;
8429
+ if (host === void 0 && liveSize(state) < WORKER_COPY_MIN_VECTORS) return;
10341
8430
  state.workerCopyLoading = true;
10342
- void loadWorkerCopies(state).finally(() => {
10343
- state.workerCopyLoading = false;
10344
- });
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
+ );
10345
8441
  }
10346
8442
  async function loadWorkerCopies(state) {
10347
8443
  const revision = state.revision;
@@ -10395,7 +8491,7 @@ async function loadWorkerCopies(state) {
10395
8491
  async function searchViaWorkerCopies(state, query, k, metric, minSimilarity, efSearch, filter) {
10396
8492
  const pool = state.workerCopyPool;
10397
8493
  const handle = state.workerCopyHandle;
10398
- if (pool === null || handle === null) return null;
8494
+ if (pool === null || handle === null || state.workerCopyMode === "hosted") return null;
10399
8495
  if (state.workerCopyRevision !== state.revision) return null;
10400
8496
  try {
10401
8497
  if (state.workerCopyMode === "shared") {
@@ -10506,6 +8602,8 @@ function triggerBuild(state) {
10506
8602
  state.pendingBuild = null;
10507
8603
  if (state.buffer.size > 0) {
10508
8604
  scheduleBuild(state);
8605
+ } else if (state.workerCopies.host !== void 0) {
8606
+ scheduleWorkerCopyLoad(state);
10509
8607
  }
10510
8608
  }
10511
8609
  })();
@@ -10946,7 +9044,7 @@ function searchWithFilter(state, query, k, options, filter) {
10946
9044
  }
10947
9045
 
10948
9046
  // src/vector/vector-index/index.ts
10949
- function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_WORKER_COPIES_ALLOWED) {
9047
+ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_WORKER_COPIES_ALLOWED, indexName = "") {
10950
9048
  if (!Number.isInteger(dimension) || dimension <= 0) {
10951
9049
  throw new NarsilError(
10952
9050
  ErrorCodes.VECTOR_DIMENSION_MISMATCH,
@@ -10962,6 +9060,7 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
10962
9060
  const dimensionScale = dimension / 256;
10963
9061
  const store = createVectorStore();
10964
9062
  const state = {
9063
+ indexName,
10965
9064
  fieldName,
10966
9065
  dimension,
10967
9066
  dimensionScale,
@@ -11003,19 +9102,6 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
11003
9102
  state.store.insert(docId, vector, partitionId);
11004
9103
  state.buffer.add(docId);
11005
9104
  }
11006
- function assignPartitions(resolve) {
11007
- for (let ordinal = 0; ordinal < state.store.slots; ordinal += 1) {
11008
- if (state.store.partitionOfOrdinal(ordinal) !== void 0) continue;
11009
- const docId = state.store.docIdForOrdinal(ordinal);
11010
- if (docId === void 0) continue;
11011
- const partitionId = resolve(docId);
11012
- if (partitionId === void 0) {
11013
- state.store.forgetPartition(docId);
11014
- continue;
11015
- }
11016
- state.store.setPartition(docId, partitionId);
11017
- }
11018
- }
11019
9105
  function remove(docId) {
11020
9106
  if (!state.store.has(docId)) return;
11021
9107
  invalidateWorkerCopies(state);
@@ -11085,12 +9171,13 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
11085
9171
  insert,
11086
9172
  remove,
11087
9173
  partitionsKnown: () => state.store.partitionsKnown,
11088
- assignPartitions,
9174
+ assignPartitions: (resolve) => assignStorePartitions(state, resolve),
11089
9175
  scheduleBuild: () => scheduleBuild(state),
11090
9176
  awaitPendingBuild,
11091
9177
  dispose,
11092
9178
  search: (query, k, options) => search(state, query, k, options),
11093
9179
  searchParallel,
9180
+ refreshWorkerCopies: () => refreshWorkerCopies(state),
11094
9181
  getVector,
11095
9182
  has,
11096
9183
  compact: () => {
@@ -11111,16 +9198,200 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
11111
9198
  };
11112
9199
  }
11113
9200
 
11114
- // src/workers/direct-executor.ts
11115
- var NO_VECTOR_WORKER_COPIES = { enabled: false };
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
+ }
11116
9205
  function growPartitionsTo(manager, partitionId) {
11117
9206
  while (manager.partitionCount <= partitionId) {
11118
9207
  manager.addPartition();
11119
9208
  }
11120
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
+
9389
+ // src/workers/direct-executor.ts
9390
+ var NO_VECTOR_WORKER_COPIES = { enabled: false };
11121
9391
  function createDirectExecutor(options) {
11122
9392
  const indexes = /* @__PURE__ */ new Map();
11123
9393
  const vectorWorkerCopies = options?.vectorWorkerCopies ?? NO_VECTOR_WORKER_COPIES;
9394
+ const scratchSlot = options?.scratchSlot ?? 0;
11124
9395
  function requireIndex(indexName) {
11125
9396
  const entry = indexes.get(indexName);
11126
9397
  if (!entry) {
@@ -11130,7 +9401,17 @@ function createDirectExecutor(options) {
11130
9401
  }
11131
9402
  return entry;
11132
9403
  }
11133
- function createIndex(indexName, config, language) {
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) {
11134
9415
  if (indexes.has(indexName)) {
11135
9416
  throw new NarsilError(ErrorCodes.INDEX_ALREADY_EXISTS, `Index "${indexName}" already exists`, {
11136
9417
  indexName
@@ -11138,11 +9419,7 @@ function createDirectExecutor(options) {
11138
9419
  }
11139
9420
  const router = createPartitionRouter();
11140
9421
  const partitionCount = config.partitions?.maxPartitions ?? 1;
11141
- const vectorFields = extractVectorFieldsFromSchema(config.schema);
11142
- const vectorIndexes = /* @__PURE__ */ new Map();
11143
- for (const [fieldPath, dim] of vectorFields) {
11144
- vectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, config.vectorPromotion, vectorWorkerCopies));
11145
- }
9422
+ const vectorIndexes = vectorIndexesFor(indexName, config);
11146
9423
  const manager = createPartitionManager(indexName, config, language, router, partitionCount, vectorIndexes);
11147
9424
  indexes.set(indexName, {
11148
9425
  manager,
@@ -11153,7 +9430,9 @@ function createDirectExecutor(options) {
11153
9430
  stopWords: manager.analysis.stopWords,
11154
9431
  customTokenizer: manager.analysis.customTokenizer
11155
9432
  },
11156
- vectorIndexes
9433
+ vectorIndexes,
9434
+ vectorCopies: createHeldVectorCopies(),
9435
+ analysisStale
11157
9436
  });
11158
9437
  }
11159
9438
  function dropIndex(indexName) {
@@ -11172,7 +9451,21 @@ function createDirectExecutor(options) {
11172
9451
  function getManager(indexName) {
11173
9452
  return indexes.get(indexName)?.manager;
11174
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
+ }
11175
9465
  async function execute(action) {
9466
+ if (isSegmentAction(action)) {
9467
+ return runSegmentAction(requireIndex(action.indexName), action);
9468
+ }
11176
9469
  switch (action.type) {
11177
9470
  case "bootstrap": {
11178
9471
  throw new NarsilError(
@@ -11181,92 +9474,35 @@ function createDirectExecutor(options) {
11181
9474
  { moduleUrl: action.moduleUrl }
11182
9475
  );
11183
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
+ }
11184
9484
  case "createIndex": {
11185
9485
  const language = getLanguage(action.config.language ?? "english");
11186
- createIndex(action.indexName, action.config, language);
9486
+ createIndex(action.indexName, action.config, language, action.analysisStale ?? false);
11187
9487
  return void 0;
11188
9488
  }
11189
9489
  case "dropIndex": {
11190
9490
  dropIndex(action.indexName);
11191
9491
  return void 0;
11192
9492
  }
11193
- case "insert": {
11194
- const entry = requireIndex(action.indexName);
11195
- entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
11196
- return void 0;
11197
- }
11198
- case "buildSegment": {
11199
- const entry = requireIndex(action.indexName);
11200
- const segment = createPartitionIndex(0, entry.config.trackPositions ?? true);
11201
- const options2 = resolvePartitionInsertOptions(entry.config, entry.manager.analysis, action.options);
11202
- segment.beginBatch();
11203
- for (const doc of action.documents) {
11204
- segment.insert(doc.docId, doc.document, entry.config.schema, entry.language, options2);
11205
- }
11206
- segment.endBatch();
11207
- return segment.encodeSegment();
11208
- }
11209
- case "mergeSegments": {
11210
- const entry = requireIndex(action.indexName);
11211
- for (const segment of action.segments) {
11212
- if (segment.payload.docIds.some((docId) => entry.manager.has(docId))) {
11213
- console.warn(
11214
- `Skipping replicated segment for index "${action.indexName}": its documents already exist on this copy`
11215
- );
11216
- continue;
11217
- }
11218
- entry.manager.mergeSegment(segment.partitionId, segment.payload, segment.documents);
11219
- }
11220
- return void 0;
11221
- }
11222
- case "attachSegments": {
9493
+ case "loadVectorCopy": {
11223
9494
  const entry = requireIndex(action.indexName);
11224
- for (const segment of action.segments) {
11225
- growPartitionsTo(entry.manager, segment.partitionId);
11226
- const frozen = createSharedFrozenSegment(segment.snapshot);
11227
- for (const docId of segment.tombstonedDocIds ?? []) frozen.tombstoneDocument(docId);
11228
- entry.manager.attachFrozenSegment(segment.partitionId, frozen);
11229
- }
9495
+ loadHeldVectorCopy(entry.vectorCopies, entry.manager, action.fieldName, action.handle, action.copy, scratchSlot);
11230
9496
  return void 0;
11231
9497
  }
11232
- case "compactSegments": {
11233
- const entry = requireIndex(action.indexName);
11234
- const partition = entry.manager.getPartition(action.partitionId);
11235
- if (!isCompositePartition(partition)) {
11236
- throw new NarsilError(
11237
- ErrorCodes.PARTITION_CORRUPTED,
11238
- `Partition ${action.partitionId} of "${action.indexName}" holds no frozen segments to compact`,
11239
- { indexName: action.indexName, partitionId: action.partitionId }
11240
- );
11241
- }
11242
- const segments = partition.frozenSegmentsById(action.segmentIds);
11243
- const { payload, documents } = buildCompactedSegmentPayload(segments);
11244
- return freezeSegmentShared(payload, documents);
11245
- }
11246
- case "swapSegments": {
11247
- const entry = requireIndex(action.indexName);
11248
- const partition = entry.manager.getPartition(action.partitionId);
11249
- if (!isCompositePartition(partition)) {
11250
- throw new NarsilError(
11251
- ErrorCodes.PARTITION_CORRUPTED,
11252
- `Partition ${action.partitionId} of "${action.indexName}" holds no frozen segments to swap`,
11253
- { indexName: action.indexName, partitionId: action.partitionId }
11254
- );
11255
- }
11256
- partition.swapFrozenSegments(action.dropSegmentIds, createSharedFrozenSegment(action.snapshot));
9498
+ case "dropVectorCopy": {
9499
+ const entry = indexes.get(action.indexName);
9500
+ if (entry !== void 0) dropHeldVectorCopy(entry.vectorCopies, action.fieldName, action.handle);
11257
9501
  return void 0;
11258
9502
  }
11259
- case "freezeLiveTail": {
9503
+ case "insert": {
11260
9504
  const entry = requireIndex(action.indexName);
11261
- growPartitionsTo(entry.manager, action.partitionId);
11262
- const tail = entry.manager.getPartition(action.partitionId);
11263
- const held = isCompositePartition(tail) ? tail.live.count() : tail.count();
11264
- if (held !== action.snapshot.documentCount) {
11265
- console.warn(
11266
- `Partition ${action.partitionId} of "${action.indexName}" held ${held} live documents where the frozen tail holds ${action.snapshot.documentCount}`
11267
- );
11268
- }
11269
- entry.manager.replaceLiveTail(action.partitionId, createSharedFrozenSegment(action.snapshot));
9505
+ entry.manager.insert(action.docId, action.document, action.skipClone ? { skipClone: true } : void 0);
11270
9506
  return void 0;
11271
9507
  }
11272
9508
  case "remove": {
@@ -11341,16 +9577,9 @@ function createDirectExecutor(options) {
11341
9577
  partition.clear();
11342
9578
  }
11343
9579
  entry.manager.setPartitions(partitions);
11344
- const vectorFields = extractVectorFieldsFromSchema(entry.config.schema);
11345
- const newVectorIndexes = /* @__PURE__ */ new Map();
11346
- for (const [fieldPath, dim] of vectorFields) {
11347
- newVectorIndexes.set(
11348
- fieldPath,
11349
- createVectorIndex(fieldPath, dim, entry.config.vectorPromotion, vectorWorkerCopies)
11350
- );
11351
- }
11352
- entry.manager.resetVectorIndexes(newVectorIndexes);
9580
+ entry.manager.resetVectorIndexes(vectorIndexesFor(action.indexName, entry.config));
11353
9581
  entry.vectorIndexes = entry.manager.getVectorIndexes();
9582
+ entry.vectorCopies = createHeldVectorCopies();
11354
9583
  return void 0;
11355
9584
  }
11356
9585
  case "serialize": {
@@ -11364,8 +9593,8 @@ function createDirectExecutor(options) {
11364
9593
  return void 0;
11365
9594
  }
11366
9595
  case "memoryReport": {
11367
- const report = typeof process !== "undefined" && process.memoryUsage ? process.memoryUsage() : {};
11368
- return report;
9596
+ const usage = typeof process !== "undefined" && process.memoryUsage ? process.memoryUsage() : {};
9597
+ return { ...usage, heapLimit: readHeapStatistics()?.limitBytes ?? null };
11369
9598
  }
11370
9599
  case "shutdown": {
11371
9600
  for (const [, entry] of indexes) {
@@ -11385,10 +9614,11 @@ function createDirectExecutor(options) {
11385
9614
  execute,
11386
9615
  shutdown,
11387
9616
  getManager,
9617
+ queryContextOf,
11388
9618
  createIndex,
11389
9619
  dropIndex,
11390
9620
  listIndexes
11391
9621
  };
11392
9622
  }
11393
9623
 
11394
- 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, MAX_NESTING_DEPTH, SHUTDOWN_TIMEOUT_MS, WORKER_RESOURCE_LIMITS, applyPagination, applyProjection, applySorting, buildCompactedSegmentPayload, clampRowCount, clearNormalizationCache, collectQueryTermStats, configureNormalizationCache, countsWithoutScores, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, createVectorIndex, createWorkerPool, decodePageCursor, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, detectRuntime, encodePageCursor, encodeSegmentState, extractVectorFieldsFromSchema, fanOutMatchCount, fanOutQuery, flattenSchema, fnv1a, fnv1aBytes, freezeSegmentShared, fulltextMatches, getLanguage, getNestedValue, getNormalizationCacheSize, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isCompositePartition, isNodeMainThread, isTextFieldType, kWayMerge, mergeFacets, mergePartitionStats, normalizeSort, partitionsIn, pruneStatsToQueryTerms, readSortValues, registerLanguage, registerStopWords, registerTokenizer, requireMatchingCursor, requireSortableFields, requireWithinResultWindow, resetNormalizationCache, resolveIndexAnalysis, resolveProjection, resolveWorkerCount, serializeMetadata, sortSignatureOf, spawnNodeWorker, splitWorkerBudget, tokenize, validateDocument, validateDocumentStrict, validateRequiredFields, validateSchema, validateVectorPromotion };
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 };