@delali/narsil 0.2.4-next.48 → 0.2.4-next.50

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