@delali/narsil 0.3.1-next.72 → 0.3.1-next.74

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 (70) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/filesystem.mjs +1 -1
  3. package/dist/adapters/indexeddb.mjs +1 -1
  4. package/dist/{capabilities-D6gjjQyA.d.ts → capabilities-CAMzmkAg.d.ts} +19 -4
  5. package/dist/{chunk-P2ZZN32C.mjs → chunk-2E35UM3G.mjs} +2 -12
  6. package/dist/chunk-3DDUNI6N.mjs +48 -0
  7. package/dist/{chunk-KZTI25D3.mjs → chunk-4MQ5F4IL.mjs} +1 -1
  8. package/dist/{chunk-HVJYTKK7.mjs → chunk-BVBIQQKG.mjs} +947 -1327
  9. package/dist/{chunk-2X2ZQOAB.mjs → chunk-CTOSGRM7.mjs} +101 -21
  10. package/dist/{chunk-7QMTA7AA.mjs → chunk-CUZOX6CN.mjs} +7784 -4276
  11. package/dist/chunk-I23G3IZC.mjs +593 -0
  12. package/dist/{chunk-RV77FR2F.mjs → chunk-I64LXKO3.mjs} +0 -1
  13. package/dist/chunk-KEHK2O6P.mjs +13 -0
  14. package/dist/{chunk-CVPA5Q7R.mjs → chunk-LX4JERXI.mjs} +9 -6
  15. package/dist/{chunk-EDMCZLSJ.mjs → chunk-NOZUQM7J.mjs} +193 -13
  16. package/dist/{chunk-LCCL6EEH.mjs → chunk-PO53FV4Q.mjs} +93 -94
  17. package/dist/chunk-T5ZWEWVD.mjs +658 -0
  18. package/dist/{chunk-Y52WZWAU.mjs → chunk-TVFLHM7M.mjs} +4 -4
  19. package/dist/{chunk-QR4OKRVY.mjs → chunk-VKX2BPXD.mjs} +147 -642
  20. package/dist/{chunk-3HQPUCCT.mjs → chunk-XW25X6AU.mjs} +66 -237
  21. package/dist/{chunk-M6XF5HDE.mjs → chunk-Z3DQPOJQ.mjs} +127 -30
  22. package/dist/{client-C2TiW29y.d.ts → client-C-I70ArH.d.ts} +77 -26
  23. package/dist/client.d.ts +9 -9
  24. package/dist/client.mjs +44 -22
  25. package/dist/{config-BLguB-om.d.ts → config-C6Iw5rCX.d.ts} +11 -4
  26. package/dist/{config-C7Ip8Ife.d.ts → config-DJPH3IQB.d.ts} +11 -2
  27. package/dist/direct-executor-KFAGILJC.mjs +17 -0
  28. package/dist/distribution/coordinator/etcd.d.ts +29 -16
  29. package/dist/distribution/coordinator/etcd.mjs +162 -75
  30. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  31. package/dist/distribution/transport/grpc.d.ts +2 -2
  32. package/dist/distribution/transport/grpc.mjs +22 -2
  33. package/dist/distribution/transport/in-memory.d.ts +2 -2
  34. package/dist/distribution/transport/in-memory.mjs +6 -3
  35. package/dist/distribution/transport/tcp.d.ts +2 -2
  36. package/dist/distribution/transport/tcp.mjs +40 -16
  37. package/dist/distribution.d.ts +9 -9
  38. package/dist/distribution.mjs +647 -366
  39. package/dist/embeddings/openai.mjs +1 -1
  40. package/dist/{engine-Dm4446j1.d.ts → engine-B8eRPO7m.d.ts} +97 -188
  41. package/dist/index.browser.mjs +12132 -8592
  42. package/dist/index.d.ts +27 -8
  43. package/dist/index.mjs +15 -14
  44. package/dist/{memory-CKHTU1CL.d.ts → memory-DJEwWoCi.d.ts} +1204 -968
  45. package/dist/partial-words-DTIHO8G4.d.ts +19 -0
  46. package/dist/partial-words.d.ts +22 -0
  47. package/dist/partial-words.mjs +11 -0
  48. package/dist/pattern.mjs +1 -1
  49. package/dist/persistence/durability/checkpoint-worker.d.ts +57 -29
  50. package/dist/persistence/durability/checkpoint-worker.mjs +26 -16
  51. package/dist/react.d.ts +7 -7
  52. package/dist/react.mjs +0 -1
  53. package/dist/{schema-BBOCdhND.d.ts → schema-DgyDijhf.d.ts} +6 -2
  54. package/dist/server.d.ts +11 -11
  55. package/dist/server.mjs +212 -32
  56. package/dist/{tasks-BECd0b__.d.ts → tasks-BVmAKYBh.d.ts} +3 -3
  57. package/dist/{thread-IBSIZZ6W.mjs → thread-W6DPKEF6.mjs} +82 -21
  58. package/dist/{types-BQDlIvfS.d.ts → types-B3b68nfF.d.ts} +1 -1
  59. package/dist/{types-D5OwhtsQ.d.ts → types-Cb9kiY5P.d.ts} +1 -1
  60. package/dist/{types-BiUmaiWT.d.ts → types-ChrVy4lH.d.ts} +1 -1
  61. package/dist/vector/search-worker.d.ts +1 -1
  62. package/dist/vector/search-worker.mjs +4 -3
  63. package/dist/workers/entry.mjs +4 -4
  64. package/package.json +15 -7
  65. package/dist/chunk-3L4U6EJQ.mjs +0 -49
  66. package/dist/chunk-IFBKLBGB.mjs +0 -7
  67. package/dist/chunk-XZ4PAREY.mjs +0 -25
  68. package/dist/direct-executor-SVCHXR74.mjs +0 -14
  69. package/dist/serialization/crc32-worker.d.ts +0 -20
  70. package/dist/serialization/crc32-worker.mjs +0 -62
@@ -1,16 +1,19 @@
1
- import { cloneProjected, everyValueFacetConfig, mergeFacets, computeFacets, tokenizeOptions, getNestedValue, visitTokens, joinPatternWorkMeter, countsWithoutScores, fanOutMatchCount, fanOutQuery, sanitizeGlobalStats, textFieldsChanged, projectionKeepsField, setNestedValue, fnv1a, kWayMerge, fuzzyPrefixOf, boundedLevenshtein, getFlatSchema, tokenize, getOrCreateFieldNameIndex, tokenizeIterator, getFieldValueByInternalId, undeclaredFilterField, requireValidOperands, polygonContainment, polygonBoxes, radiusBoxes, locationCode, coveringRanges, GEO_UNIT_METRES } from './chunk-QR4OKRVY.mjs';
2
- import { getLanguage, detectRuntime } from './chunk-KZTI25D3.mjs';
3
- import { schemaFieldsOf, isWordIndexedBase, validateDocument, validateDocumentStrict, isQuantizationMode, isStorageMode, requireValidFieldTypes, extractVectorFieldsFromSchema, VECTOR_PATTERN, parsedTypeOf, isListBase, VINCENTY_CONVERGENCE_THRESHOLD, EARTH_RADIUS_METERS, VINCENTY_MAX_ITERATIONS, DEG_TO_RAD } from './chunk-M6XF5HDE.mjs';
4
- import { SCRATCH_SLOTS_PER_THREAD_POOL, osqRecordBytes, openSharedVectorField, osqBitsOf, createVectorStore, DISK_STORAGE_BLOCK_BYTES, createOsqQuantizer, magnitude, createHNSWIndex, nativeStoreScratchBytes, createOrdinalFilter, isTombstoned, addToOrdinalFilter, ordinalFilterValues, nativeScoresOf, toScore, removeFromOrdinalFilter, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, requireWholeNeighbourLists, STORE_CALIBRATION_GENERATION, entryForOrd, toDistance, sharedMemoryAvailable, GRAPH_BUILD_CHUNKS_IN_FLIGHT_PER_WORKER, GRAPH_BUILD_DISPATCH_CHUNK, releaseLocksHeldBy, VECTOR_SEARCH_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, VECTOR_INSERT_TIMEOUT_MS, VECTOR_SCRATCH_SLOTS } from './chunk-LCCL6EEH.mjs';
5
- import { noteFallback } from './chunk-P2ZZN32C.mjs';
6
- import { VALID_HNSW_METRICS } from './chunk-IFBKLBGB.mjs';
7
- import { GrowableUint32, patternSearchModule } from './chunk-3HQPUCCT.mjs';
1
+ import { joinPatternWorkMeter, countsWithoutScores, fanOutMatchCount, fanOutQuery, sanitizeGlobalStats, computeFacets, cloneProjected, projectionKeepsField, setNestedValue, fnv1a, everyValueFacetConfig, mergeFacets, undeclaredFilterField, requireValidOperands, kWayMerge, polygonContainment, polygonBoxes, radiusBoxes, locationCode, coveringRanges, GEO_UNIT_METRES } from './chunk-VKX2BPXD.mjs';
2
+ import { getLanguage, detectRuntime } from './chunk-4MQ5F4IL.mjs';
3
+ import { MAX_DOC_ID_LENGTH } from './chunk-3DDUNI6N.mjs';
4
+ import { SCRATCH_SLOTS_PER_THREAD_POOL, osqRecordBytes, openSharedVectorField, osqBitsOf, createVectorStore, DISK_STORAGE_BLOCK_BYTES, createOsqQuantizer, magnitude, createHNSWIndex, nativeStoreScratchBytes, createOrdinalFilter, isTombstoned, addToOrdinalFilter, ordinalFilterValues, nativeScoresOf, toScore, removeFromOrdinalFilter, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, STORE_CALIBRATION_GENERATION, entryForOrd, toDistance, sharedMemoryAvailable, requireWholeNeighbourLists, topLayerInParts, GRAPH_BUILD_CHUNKS_IN_FLIGHT_PER_WORKER, GRAPH_BUILD_DISPATCH_CHUNK, releaseLocksHeldBy, VECTOR_SEARCH_TIMEOUT_MS, VECTOR_SEARCH_LOAD_TIMEOUT_MS, VECTOR_INSERT_TIMEOUT_MS, VECTOR_SCRATCH_SLOTS } from './chunk-PO53FV4Q.mjs';
5
+ import { noteFallback } from './chunk-2E35UM3G.mjs';
6
+ import { GrowableUint32, patternSearchModule } from './chunk-XW25X6AU.mjs';
8
7
  import { bitsetHas, createBitSet, bitsetSet, bitsetOrInto, bitsetAndNotInto, bitsetAndInto, PATTERN_RUN_CODE_POINTS, bitsetFromSet, bitsetIsEmpty, bitsetAnd, bitsetNot } from './chunk-ZKRV5M2P.mjs';
9
- import { compareCodePoints, appendFoldedCodePoint, toReducedSortValue, readSortField, compareComparableValues, defaultSortMode, truncateSortString, SORT_VALUE_MAX_CODE_POINTS } from './chunk-LJYTW7GT.mjs';
10
- import { NarsilError, ErrorCodes } from './chunk-RV77FR2F.mjs';
8
+ import { encodePartialSpellings, countSpelling, expandTermPrefix, suggestDisplayTerms, spellingCountsOf, codePointOrder, encodeStringBlob, partialWordSearchModule, partitionDocumentFrequency } from './chunk-T5ZWEWVD.mjs';
9
+ import { tokenizeOptions, getNestedValue, visitTokens, textFieldsChanged, fuzzyPrefixOf, boundedLevenshtein, getFlatSchema, tokenize, getOrCreateFieldNameIndex, tokenizeIterator, getFieldValueByInternalId } from './chunk-I23G3IZC.mjs';
10
+ import { schemaFieldsOf, partialFieldsOf, isWordIndexedBase, validateDocument, validateDocumentStrict, encodeDocument, extractVectorFieldsFromSchema, parsedTypeOf, isListBase, VINCENTY_CONVERGENCE_THRESHOLD, EARTH_RADIUS_METERS, VINCENTY_MAX_ITERATIONS, DEG_TO_RAD } from './chunk-Z3DQPOJQ.mjs';
11
+ import { MAX_M, EARLIER_RELEASE_MIN_M, MIN_M } from './chunk-KEHK2O6P.mjs';
12
+ import { compareCodePoints, appendFoldedCodePoint, toReducedSortValue, readSortField, defaultSortMode, compareComparableValues, truncateSortString, SORT_VALUE_MAX_CODE_POINTS } from './chunk-LJYTW7GT.mjs';
13
+ import { NarsilError, ErrorCodes } from './chunk-I64LXKO3.mjs';
11
14
  import { totalmem } from 'os';
12
15
  import { getHeapStatistics } from 'v8';
13
- import { encode, decode } from '@msgpack/msgpack';
16
+ import { decode } from '@msgpack/msgpack';
14
17
 
15
18
  var MEGABYTE_BYTES = 1048576;
16
19
  var IN_MEGABYTES = /^--max-old-space-size=(\d+)$/;
@@ -118,518 +121,6 @@ function resolveIndexAnalysis(config) {
118
121
  customTokenizer: typeof config.tokenizer === "string" ? getTokenizer(config.tokenizer) : config.tokenizer
119
122
  };
120
123
  }
121
- function metadataToWire(meta) {
122
- const wire = {
123
- index_name: meta.indexName,
124
- schema: meta.schema,
125
- language: meta.language,
126
- partition_count: meta.partitionCount,
127
- bm25_params: meta.bm25Params,
128
- created_at: meta.createdAt,
129
- engine_version: meta.engineVersion
130
- };
131
- if (meta.vectorFields) {
132
- wire.vector_fields = meta.vectorFields;
133
- }
134
- if (isNonNegativeInteger(meta.documentCount) && Number.isSafeInteger(meta.documentCount)) {
135
- wire.document_count = meta.documentCount;
136
- }
137
- if (meta.embedding) {
138
- wire.embedding = meta.embedding.adapter !== void 0 ? { adapter: meta.embedding.adapter, fields: meta.embedding.fields } : { fields: meta.embedding.fields };
139
- }
140
- if (meta.surfaceForms !== void 0) {
141
- wire.surface_forms_enabled = meta.surfaceForms;
142
- }
143
- if (meta.analysisRevision !== void 0) {
144
- wire.analysis_revision = meta.analysisRevision;
145
- }
146
- if (meta.tokenizer !== void 0) {
147
- wire.tokenizer = meta.tokenizer;
148
- }
149
- if (meta.stopWords !== void 0) {
150
- wire.stop_words = meta.stopWords;
151
- }
152
- if (meta.stopWordList !== void 0) {
153
- wire.stop_word_list = meta.stopWordList;
154
- }
155
- if (meta.partitionLimits !== void 0) {
156
- wire.partition_limits = {
157
- ...meta.partitionLimits.maxDocsPerPartition !== void 0 ? { max_docs_per_partition: meta.partitionLimits.maxDocsPerPartition } : {},
158
- ...meta.partitionLimits.maxPartitions !== void 0 ? { max_partitions: meta.partitionLimits.maxPartitions } : {},
159
- ...meta.partitionLimits.watermark !== void 0 ? { watermark: meta.partitionLimits.watermark } : {}
160
- };
161
- }
162
- if (meta.defaultScoring !== void 0) {
163
- wire.default_scoring = meta.defaultScoring;
164
- }
165
- if (meta.trackPositions !== void 0) {
166
- wire.track_positions = meta.trackPositions;
167
- }
168
- if (meta.strict !== void 0) {
169
- wire.strict = meta.strict;
170
- }
171
- if (meta.required !== void 0) {
172
- wire.required = meta.required;
173
- }
174
- if (meta.vectorPromotion !== void 0) {
175
- const promotion = meta.vectorPromotion;
176
- wire.vector_promotion = {
177
- ...promotion.threshold !== void 0 ? { threshold: promotion.threshold } : {},
178
- ...promotion.filterThreshold !== void 0 ? { filter_threshold: promotion.filterThreshold } : {},
179
- ...promotion.hnswConfig !== void 0 ? {
180
- hnsw_config: {
181
- ...promotion.hnswConfig.m !== void 0 ? { m: promotion.hnswConfig.m } : {},
182
- ...promotion.hnswConfig.efConstruction !== void 0 ? { ef_construction: promotion.hnswConfig.efConstruction } : {},
183
- ...promotion.hnswConfig.metric !== void 0 ? { metric: promotion.hnswConfig.metric } : {}
184
- }
185
- } : {},
186
- ...promotion.quantization !== void 0 ? { quantization: promotion.quantization } : {},
187
- ...promotion.storage !== void 0 ? { storage: promotion.storage } : {}
188
- };
189
- }
190
- if (meta.patternValueLimit !== void 0) {
191
- wire.pattern_value_limit = meta.patternValueLimit;
192
- }
193
- if (meta.indexUuid !== void 0) {
194
- wire.index_uuid = meta.indexUuid;
195
- }
196
- if (meta.heldPartitions !== void 0) {
197
- wire.held_partitions = meta.heldPartitions;
198
- }
199
- return wire;
200
- }
201
- function wireToMetadata(raw) {
202
- const meta = {
203
- indexName: raw.index_name,
204
- schema: raw.schema ?? {},
205
- language: raw.language ?? "english",
206
- partitionCount: raw.partition_count ?? 1,
207
- bm25Params: raw.bm25_params ?? { k1: 1.2, b: 0.75 },
208
- createdAt: raw.created_at ?? 0,
209
- engineVersion: raw.engine_version ?? "0.0.0"
210
- };
211
- if (raw.vector_fields) {
212
- for (const [fieldPath, field] of Object.entries(raw.vector_fields)) {
213
- if (!isQuantizationMode(field.quantization)) {
214
- throw new NarsilError(
215
- ErrorCodes.CONFIG_INVALID,
216
- `Index metadata names a vector quantization "${String(field.quantization)}" for "${fieldPath}" this engine does not read`,
217
- { fieldPath, quantization: field.quantization }
218
- );
219
- }
220
- }
221
- meta.vectorFields = raw.vector_fields;
222
- }
223
- if (isNonNegativeInteger(raw.document_count) && Number.isSafeInteger(raw.document_count)) {
224
- meta.documentCount = raw.document_count;
225
- }
226
- if (raw.embedding && typeof raw.embedding === "object" && typeof raw.embedding.fields === "object") {
227
- meta.embedding = typeof raw.embedding.adapter === "string" ? { adapter: raw.embedding.adapter, fields: raw.embedding.fields } : { fields: raw.embedding.fields };
228
- }
229
- if (typeof raw.surface_forms_enabled === "boolean") {
230
- meta.surfaceForms = raw.surface_forms_enabled;
231
- }
232
- if (typeof raw.analysis_revision === "string") {
233
- meta.analysisRevision = raw.analysis_revision;
234
- }
235
- if (typeof raw.tokenizer === "string") {
236
- meta.tokenizer = raw.tokenizer;
237
- }
238
- if (typeof raw.stop_words === "string") {
239
- meta.stopWords = raw.stop_words;
240
- }
241
- if (Array.isArray(raw.stop_word_list) && raw.stop_word_list.every((word) => typeof word === "string")) {
242
- meta.stopWordList = raw.stop_word_list;
243
- }
244
- if (raw.partition_limits && typeof raw.partition_limits === "object") {
245
- const limits = {
246
- ...isPositiveInteger(raw.partition_limits.max_docs_per_partition) ? { maxDocsPerPartition: raw.partition_limits.max_docs_per_partition } : {},
247
- ...isPositiveInteger(raw.partition_limits.max_partitions) ? { maxPartitions: raw.partition_limits.max_partitions } : {},
248
- ...isWatermarkFraction(raw.partition_limits.watermark) ? { watermark: raw.partition_limits.watermark } : {}
249
- };
250
- if (Object.keys(limits).length > 0) {
251
- meta.partitionLimits = limits;
252
- }
253
- }
254
- if (raw.default_scoring === "local" || raw.default_scoring === "dfs" || raw.default_scoring === "broadcast") {
255
- meta.defaultScoring = raw.default_scoring;
256
- }
257
- if (typeof raw.track_positions === "boolean") {
258
- meta.trackPositions = raw.track_positions;
259
- }
260
- if (typeof raw.strict === "boolean") {
261
- meta.strict = raw.strict;
262
- }
263
- if (Array.isArray(raw.required) && raw.required.every((field) => typeof field === "string")) {
264
- meta.required = raw.required;
265
- }
266
- if (raw.vector_promotion && typeof raw.vector_promotion === "object") {
267
- const promotion = raw.vector_promotion;
268
- const hnsw = promotion.hnsw_config && typeof promotion.hnsw_config === "object" ? promotion.hnsw_config : void 0;
269
- const metric = hnsw?.metric;
270
- const restored = {};
271
- const hnswConfig = {};
272
- if (isPositiveInteger(hnsw?.m)) {
273
- hnswConfig.m = hnsw?.m;
274
- }
275
- if (isPositiveInteger(hnsw?.ef_construction)) {
276
- hnswConfig.efConstruction = hnsw?.ef_construction;
277
- }
278
- if (metric === "cosine" || metric === "dotProduct" || metric === "euclidean") {
279
- hnswConfig.metric = metric;
280
- }
281
- if (isPositiveInteger(promotion.threshold)) {
282
- restored.threshold = promotion.threshold;
283
- }
284
- if (isSelectivityRatio(promotion.filter_threshold)) {
285
- restored.filterThreshold = promotion.filter_threshold;
286
- }
287
- if (Object.keys(hnswConfig).length > 0) {
288
- restored.hnswConfig = hnswConfig;
289
- }
290
- if (promotion.quantization !== void 0) {
291
- if (!isQuantizationMode(promotion.quantization)) {
292
- throw new NarsilError(
293
- ErrorCodes.CONFIG_INVALID,
294
- `Index metadata names a vector quantization "${String(promotion.quantization)}" this engine does not read`,
295
- { quantization: promotion.quantization }
296
- );
297
- }
298
- restored.quantization = promotion.quantization;
299
- }
300
- if (promotion.storage !== void 0) {
301
- if (!isStorageMode(promotion.storage)) {
302
- throw new NarsilError(
303
- ErrorCodes.CONFIG_INVALID,
304
- `Index metadata names a vector storage "${String(promotion.storage)}" this engine does not read`,
305
- { storage: promotion.storage }
306
- );
307
- }
308
- restored.storage = promotion.storage;
309
- }
310
- if (Object.keys(restored).length > 0) {
311
- meta.vectorPromotion = restored;
312
- }
313
- }
314
- if (raw.pattern_value_limit !== void 0) {
315
- if (!isPositiveInteger(raw.pattern_value_limit)) {
316
- throw new NarsilError(
317
- ErrorCodes.CONFIG_INVALID,
318
- `The pattern value limit in the index metadata is ${String(raw.pattern_value_limit)}, but the limit must be a whole number of code points`,
319
- { patternValueLimit: String(raw.pattern_value_limit) }
320
- );
321
- }
322
- meta.patternValueLimit = raw.pattern_value_limit;
323
- }
324
- if (typeof raw.index_uuid === "string" && raw.index_uuid.length > 0) {
325
- meta.indexUuid = raw.index_uuid;
326
- }
327
- if (Array.isArray(raw.held_partitions) && raw.held_partitions.every(isNonNegativeInteger)) {
328
- meta.heldPartitions = raw.held_partitions;
329
- }
330
- return meta;
331
- }
332
- function isNonNegativeInteger(value) {
333
- return typeof value === "number" && Number.isInteger(value) && value >= 0;
334
- }
335
- function isPositiveInteger(value) {
336
- return typeof value === "number" && Number.isInteger(value) && value >= 1;
337
- }
338
- function isSelectivityRatio(value) {
339
- return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
340
- }
341
- function isWatermarkFraction(value) {
342
- return typeof value === "number" && value > 0 && value <= 1;
343
- }
344
- function serializeMetadata(meta) {
345
- const wire = metadataToWire(meta);
346
- return encode(wire);
347
- }
348
- function deserializeMetadata(data) {
349
- const raw = decode(data);
350
- return wireToMetadata(raw);
351
- }
352
-
353
- // src/serialization/stored-vector-values.ts
354
- function dropStoredVectorValues(documents, schema) {
355
- const vectorFieldPaths = [];
356
- for (const [fieldPath, fieldType] of Object.entries(schema)) {
357
- if (typeof fieldType === "string" && VECTOR_PATTERN.test(fieldType)) {
358
- vectorFieldPaths.push(fieldPath);
359
- }
360
- }
361
- if (vectorFieldPaths.length === 0) {
362
- return;
363
- }
364
- for (const document of Object.values(documents)) {
365
- for (const fieldPath of vectorFieldPaths) {
366
- removeFieldAtPath(document.fields, fieldPath);
367
- }
368
- }
369
- }
370
- function removeFieldAtPath(fields, fieldPath) {
371
- if (!fieldPath.includes(".")) {
372
- delete fields[fieldPath];
373
- return;
374
- }
375
- const segments = fieldPath.split(".");
376
- let current = fields;
377
- for (let i = 0; i < segments.length - 1; i += 1) {
378
- const next = current[segments[i]];
379
- if (next === null || typeof next !== "object") {
380
- return;
381
- }
382
- current = next;
383
- }
384
- delete current[segments[segments.length - 1]];
385
- }
386
-
387
- // src/serialization/payload-v1/partition.ts
388
- function sanitizeSurfaceForms(raw) {
389
- if (raw === void 0 || raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
390
- const result = /* @__PURE__ */ Object.create(null);
391
- let count = 0;
392
- for (const [surface, value] of Object.entries(raw)) {
393
- if (typeof value === "number") {
394
- result[surface] = value;
395
- count++;
396
- } else if (Array.isArray(value) && typeof value[0] === "number" && typeof value[1] === "string") {
397
- result[surface] = [value[0], value[1]];
398
- count++;
399
- }
400
- }
401
- return count > 0 ? result : void 0;
402
- }
403
- function patternIndexesToWire(pattern) {
404
- if (pattern === void 0) return {};
405
- const wire = /* @__PURE__ */ Object.create(null);
406
- for (const [field, index] of Object.entries(pattern)) wire[field] = { doc_ids: index.docIds, runs: index.runs };
407
- return { pattern: wire };
408
- }
409
- function isWireRecord(value) {
410
- return value !== null && typeof value === "object" && !Array.isArray(value);
411
- }
412
- function corruptPatternEntry(field) {
413
- return new NarsilError(
414
- ErrorCodes.PERSISTENCE_LOAD_FAILED,
415
- `The pattern entry of field "${field}" in this partition must hold a list of document IDs and a map of runs`,
416
- { field }
417
- );
418
- }
419
- function patternIndexesFromWire(raw) {
420
- if (raw === void 0 || raw === null) return {};
421
- if (!isWireRecord(raw)) {
422
- throw new NarsilError(
423
- ErrorCodes.PERSISTENCE_LOAD_FAILED,
424
- "The pattern entry of this partition must be a map of fields"
425
- );
426
- }
427
- const pattern = /* @__PURE__ */ Object.create(null);
428
- for (const [field, index] of Object.entries(raw)) {
429
- if (!isWireRecord(index) || !isWireRecord(index.runs) || !Array.isArray(index.doc_ids)) {
430
- throw corruptPatternEntry(field);
431
- }
432
- const docIds = [];
433
- for (const docId of index.doc_ids) {
434
- if (typeof docId !== "string") throw corruptPatternEntry(field);
435
- docIds.push(docId);
436
- }
437
- pattern[field] = { docIds, runs: index.runs };
438
- }
439
- return { pattern };
440
- }
441
- function validateHnswMetric(value) {
442
- if (value === void 0 || value === null) return void 0;
443
- if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
444
- return value;
445
- }
446
- return void 0;
447
- }
448
- function wireToPartition(raw) {
449
- const documents = {};
450
- for (const [docId, doc] of Object.entries(raw.documents ?? {})) {
451
- documents[docId] = {
452
- fields: doc.fields,
453
- fieldLengths: doc.field_lengths ?? {}
454
- };
455
- }
456
- requireValidFieldTypes(raw.schema ?? {});
457
- dropStoredVectorValues(documents, raw.schema ?? {});
458
- const invertedIndex = {};
459
- for (const [token, list] of Object.entries(raw.inverted_index ?? {})) {
460
- invertedIndex[token] = {
461
- docFrequency: list.doc_freq,
462
- postings: (list.postings ?? []).map((p) => ({
463
- docId: p.doc_id,
464
- termFrequency: p.term_freq,
465
- field: p.field,
466
- positions: p.positions ?? []
467
- }))
468
- };
469
- }
470
- const numeric = {};
471
- for (const [field, entries] of Object.entries(raw.field_indexes?.numeric ?? {})) {
472
- numeric[field] = entries.map((e) => ({ value: e.value, docId: e.doc_id }));
473
- }
474
- const boolean = {};
475
- for (const [field, idx] of Object.entries(raw.field_indexes?.boolean ?? {})) {
476
- boolean[field] = { trueDocs: idx.true_docs ?? [], falseDocs: idx.false_docs ?? [] };
477
- }
478
- const geopoint = {};
479
- for (const [field, entries] of Object.entries(raw.field_indexes?.geopoint ?? {})) {
480
- geopoint[field] = entries.map((e) => ({ lat: e.lat, lon: e.lon, docId: e.doc_id }));
481
- }
482
- const vectorData = {};
483
- for (const [field, data] of Object.entries(raw.vector_data ?? {})) {
484
- vectorData[field] = {
485
- dimension: data.dimension,
486
- vectors: (data.vectors ?? []).map((v) => ({ docId: v.doc_id, vector: v.vector })),
487
- hnswGraph: data.hnsw_graph ? {
488
- entryPoint: data.hnsw_graph.entry_point,
489
- maxLayer: data.hnsw_graph.max_layer,
490
- m: data.hnsw_graph.m,
491
- efConstruction: data.hnsw_graph.ef_construction,
492
- metric: validateHnswMetric(data.hnsw_graph.metric),
493
- nodes: data.hnsw_graph.nodes
494
- } : null,
495
- codes: data.codes ? { bits: data.codes.bits, centroid: data.codes.centroid, records: data.codes.records } : null
496
- };
497
- }
498
- return {
499
- indexName: raw.index_name,
500
- partitionId: raw.partition_id,
501
- totalPartitions: raw.total_partitions,
502
- language: raw.language ?? "english",
503
- schema: raw.schema ?? {},
504
- docCount: raw.doc_count ?? 0,
505
- avgDocLength: raw.avg_doc_length ?? 0,
506
- documents,
507
- invertedIndex,
508
- fieldIndexes: {
509
- numeric,
510
- boolean,
511
- enum: raw.field_indexes?.enum ?? {},
512
- geopoint,
513
- ...patternIndexesFromWire(raw.field_indexes?.pattern)
514
- },
515
- surfaceForms: sanitizeSurfaceForms(raw.surface_forms),
516
- vectorData,
517
- statistics: {
518
- totalDocuments: raw.statistics?.total_documents ?? 0,
519
- totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
520
- averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
521
- docFrequencies: raw.statistics?.doc_frequencies ?? {}
522
- }
523
- };
524
- }
525
- function deserializePayloadV1(data) {
526
- const raw = decode(data);
527
- return wireToPartition(raw);
528
- }
529
-
530
- // src/serialization/payload-v2.ts
531
- function encodeRawPayloadV2(wire) {
532
- return encode(wire);
533
- }
534
- function validateHnswMetric2(value) {
535
- if (value === void 0 || value === null) return void 0;
536
- if (typeof value === "string" && VALID_HNSW_METRICS.has(value)) {
537
- return value;
538
- }
539
- return void 0;
540
- }
541
- function storedDocumentsOf(raw) {
542
- requireValidFieldTypes(raw.schema ?? {});
543
- const documents = {};
544
- for (const [docId, doc] of Object.entries(raw.documents ?? {})) {
545
- documents[docId] = {
546
- fields: doc.fields,
547
- fieldLengths: doc.field_lengths ?? {}
548
- };
549
- }
550
- dropStoredVectorValues(documents, raw.schema ?? {});
551
- return documents;
552
- }
553
- function documentsOfPayloadV2(data) {
554
- const raw = decode(data);
555
- return { documents: storedDocumentsOf(raw), schema: raw.schema ?? {} };
556
- }
557
- function deserializePayloadV2(data) {
558
- const raw = decode(data);
559
- const documents = storedDocumentsOf(raw);
560
- const fieldNames = raw.inverted_index?.field_names ?? [];
561
- const invertedIndex = {};
562
- for (const [token, list] of Object.entries(raw.inverted_index?.entries ?? {})) {
563
- const count = list.ids.length;
564
- const postings = new Array(count);
565
- for (let i = 0; i < count; i++) {
566
- postings[i] = {
567
- docId: list.ids[i],
568
- termFrequency: list.tf[i],
569
- field: fieldNames[list.fi[i]] ?? "",
570
- positions: list.pos ? list.pos[i] : []
571
- };
572
- }
573
- invertedIndex[token] = {
574
- docFrequency: list.df,
575
- postings
576
- };
577
- }
578
- const numeric = {};
579
- for (const [field, entries] of Object.entries(raw.field_indexes?.numeric ?? {})) {
580
- numeric[field] = entries.map((e) => ({ value: e.value, docId: e.doc_id }));
581
- }
582
- const boolean = {};
583
- for (const [field, idx] of Object.entries(raw.field_indexes?.boolean ?? {})) {
584
- boolean[field] = { trueDocs: idx.true_docs ?? [], falseDocs: idx.false_docs ?? [] };
585
- }
586
- const geopoint = {};
587
- for (const [field, entries] of Object.entries(raw.field_indexes?.geopoint ?? {})) {
588
- geopoint[field] = entries.map((e) => ({ lat: e.lat, lon: e.lon, docId: e.doc_id }));
589
- }
590
- const vectorData = {};
591
- for (const [field, data2] of Object.entries(raw.vector_data ?? {})) {
592
- vectorData[field] = {
593
- dimension: data2.dimension,
594
- vectors: (data2.vectors ?? []).map((v) => ({ docId: v.doc_id, vector: v.vector })),
595
- hnswGraph: data2.hnsw_graph ? {
596
- entryPoint: data2.hnsw_graph.entry_point,
597
- maxLayer: data2.hnsw_graph.max_layer,
598
- m: data2.hnsw_graph.m,
599
- efConstruction: data2.hnsw_graph.ef_construction,
600
- metric: validateHnswMetric2(data2.hnsw_graph.metric),
601
- nodes: data2.hnsw_graph.nodes
602
- } : null,
603
- codes: data2.codes ? { bits: data2.codes.bits, centroid: data2.codes.centroid, records: data2.codes.records } : null
604
- };
605
- }
606
- return {
607
- indexName: raw.index_name,
608
- partitionId: raw.partition_id,
609
- totalPartitions: raw.total_partitions,
610
- language: raw.language ?? "english",
611
- schema: raw.schema ?? {},
612
- docCount: raw.doc_count ?? 0,
613
- avgDocLength: raw.avg_doc_length ?? 0,
614
- documents,
615
- invertedIndex,
616
- fieldIndexes: {
617
- numeric,
618
- boolean,
619
- enum: raw.field_indexes?.enum ?? {},
620
- geopoint,
621
- ...patternIndexesFromWire(raw.field_indexes?.pattern)
622
- },
623
- surfaceForms: sanitizeSurfaceForms(raw.surface_forms),
624
- vectorData,
625
- statistics: {
626
- totalDocuments: raw.statistics?.total_documents ?? 0,
627
- totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
628
- averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
629
- docFrequencies: raw.statistics?.doc_frequencies ?? {}
630
- }
631
- };
632
- }
633
124
 
634
125
  // src/core/document-store.ts
635
126
  function detachedView(view) {
@@ -889,7 +380,7 @@ function createPostingList() {
889
380
  length: 0,
890
381
  docIds: [],
891
382
  termFrequencies: new Uint16Array(POSTING_LIST_INITIAL_CAPACITY),
892
- fieldNameIndices: new Uint8Array(POSTING_LIST_INITIAL_CAPACITY),
383
+ fieldNameIndices: new Uint16Array(POSTING_LIST_INITIAL_CAPACITY),
893
384
  positions: null,
894
385
  docIdSet: /* @__PURE__ */ new Set(),
895
386
  deletedDocs: /* @__PURE__ */ new Set(),
@@ -903,7 +394,7 @@ function growTypedArrays(list) {
903
394
  const newTF = new Uint16Array(newCap);
904
395
  newTF.set(list.termFrequencies);
905
396
  list.termFrequencies = newTF;
906
- const newFNI = new Uint8Array(newCap);
397
+ const newFNI = new Uint16Array(newCap);
907
398
  newFNI.set(list.fieldNameIndices);
908
399
  list.fieldNameIndices = newFNI;
909
400
  }
@@ -924,8 +415,6 @@ function compactList(list) {
924
415
  }
925
416
  }
926
417
  writeIdx++;
927
- } else {
928
- list.totalTermFrequency -= list.termFrequencies[i];
929
418
  }
930
419
  }
931
420
  list.docIds.length = writeIdx;
@@ -950,8 +439,6 @@ function compactDocEntries(list, internalId) {
950
439
  }
951
440
  }
952
441
  writeIdx++;
953
- } else {
954
- list.totalTermFrequency -= list.termFrequencies[i];
955
442
  }
956
443
  }
957
444
  list.docIds.length = writeIdx;
@@ -1037,12 +524,13 @@ function createInvertedIndex(fieldNameTable) {
1037
524
  }
1038
525
  list.length++;
1039
526
  },
1040
- remove(token, internalId) {
527
+ remove(token, internalId, occurrences) {
1041
528
  const list = index.get(token);
1042
529
  if (!list || !list.docIdSet.has(internalId)) return;
1043
530
  list.structureRevision++;
1044
531
  list.docIdSet.delete(internalId);
1045
532
  list.deletedDocs.add(internalId);
533
+ list.totalTermFrequency = Math.max(0, list.totalTermFrequency - occurrences);
1046
534
  if (list.docIdSet.size === 0) {
1047
535
  index.delete(token);
1048
536
  untrackToken(token);
@@ -1075,6 +563,9 @@ function createInvertedIndex(fieldNameTable) {
1075
563
  lookup(token) {
1076
564
  return index.get(token);
1077
565
  },
566
+ documentFrequency(token) {
567
+ return index.get(token)?.docIdSet.size ?? 0;
568
+ },
1078
569
  fuzzyLookup(token, tolerance, prefixLength) {
1079
570
  if (tolerance === 0) {
1080
571
  const exact = index.get(token);
@@ -1148,7 +639,7 @@ function createInvertedIndex(fieldNameTable) {
1148
639
  const docIdSet = /* @__PURE__ */ new Set();
1149
640
  const docIds = new Array(count);
1150
641
  const termFrequencies = new Uint16Array(count);
1151
- const fieldNameIndices = new Uint8Array(count);
642
+ const fieldNameIndices = new Uint16Array(count);
1152
643
  let hasPositions = false;
1153
644
  let validCount = 0;
1154
645
  let totalTermFrequency = 0;
@@ -2180,6 +1671,13 @@ function resolveInternalId(state, docId) {
2180
1671
  }
2181
1672
  return internalId;
2182
1673
  }
1674
+ function emptyIndexedText(fields) {
1675
+ const holdsPartialField = fields.some((field) => field.partial);
1676
+ return { fieldLengths: {}, tokensByField: {}, partialSpellings: holdsPartialField ? /* @__PURE__ */ new Map() : null };
1677
+ }
1678
+ function partialSpellingsFor(field, indexed) {
1679
+ return field.partial ? indexed.partialSpellings : null;
1680
+ }
2183
1681
  function countSurface(counts, surface, token) {
2184
1682
  if (surface === void 0 || surface.length === 0) return;
2185
1683
  const existing = counts.get(surface);
@@ -2189,6 +1687,18 @@ function countSurface(counts, surface, token) {
2189
1687
  counts.set(surface, { token, count: 1 });
2190
1688
  }
2191
1689
  }
1690
+ function countOccurrence(counts, spellings, surface, token) {
1691
+ countSurface(counts, surface, token);
1692
+ if (spellings !== null) countSpelling(spellings, token, surface ?? token, 1);
1693
+ }
1694
+ function addFieldOccurrences(occurrencesByToken, fieldTokens) {
1695
+ const fieldCounts = /* @__PURE__ */ new Map();
1696
+ for (const token of fieldTokens) fieldCounts.set(token, (fieldCounts.get(token) ?? 0) + 1);
1697
+ for (const [token, count] of fieldCounts) {
1698
+ const stored = count > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : count;
1699
+ occurrencesByToken.set(token, (occurrencesByToken.get(token) ?? 0) + stored);
1700
+ }
1701
+ }
2192
1702
  function applySurfaceCounts(state, counts, direction) {
2193
1703
  for (const [surface, entry] of counts) {
2194
1704
  if (direction === 1) {
@@ -2198,7 +1708,7 @@ function applySurfaceCounts(state, counts, direction) {
2198
1708
  }
2199
1709
  }
2200
1710
  }
2201
- function indexStringField(state, internalId, fieldPath, text, language, options, fieldLengths, tokensByField) {
1711
+ function indexStringField(state, internalId, fieldPath, text, language, options, fieldLengths, tokensByField, spellings = null) {
2202
1712
  const fieldTokenList = [];
2203
1713
  const fieldNameIndex2 = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2204
1714
  const opts = tokenizeOptions(options);
@@ -2214,7 +1724,7 @@ function indexStringField(state, internalId, fieldPath, text, language, options,
2214
1724
  tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
2215
1725
  }
2216
1726
  fieldTokenList.push(t.token);
2217
- countSurface(surfaceCounts, t.surface, t.token);
1727
+ countOccurrence(surfaceCounts, spellings, t.surface, t.token);
2218
1728
  }
2219
1729
  for (const [token, freq] of tokenFreqs) {
2220
1730
  state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex2, freq.positions);
@@ -2224,7 +1734,7 @@ function indexStringField(state, internalId, fieldPath, text, language, options,
2224
1734
  for (const t of tokenizeIterator(text, language, opts)) {
2225
1735
  tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2226
1736
  fieldTokenList.push(t.token);
2227
- countSurface(surfaceCounts, t.surface, t.token);
1737
+ countOccurrence(surfaceCounts, spellings, t.surface, t.token);
2228
1738
  }
2229
1739
  for (const [token, count] of tokenCounts) {
2230
1740
  state.invertedIdx.insert(token, internalId, count, fieldNameIndex2, null);
@@ -2234,7 +1744,7 @@ function indexStringField(state, internalId, fieldPath, text, language, options,
2234
1744
  fieldLengths[fieldPath] = fieldTokenList.length;
2235
1745
  tokensByField[fieldPath] = fieldTokenList;
2236
1746
  }
2237
- function indexStringArrayField(state, internalId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
1747
+ function indexStringArrayField(state, internalId, fieldPath, arr, language, options, fieldLengths, tokensByField, spellings = null) {
2238
1748
  const fieldTokenList = [];
2239
1749
  const fieldNameIndex2 = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2240
1750
  const opts = tokenizeOptions(options);
@@ -2253,7 +1763,7 @@ function indexStringArrayField(state, internalId, fieldPath, arr, language, opti
2253
1763
  tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
2254
1764
  }
2255
1765
  fieldTokenList.push(t.token);
2256
- countSurface(surfaceCounts, t.surface, t.token);
1766
+ countOccurrence(surfaceCounts, spellings, t.surface, t.token);
2257
1767
  itemTokenCount++;
2258
1768
  }
2259
1769
  positionOffset += itemTokenCount;
@@ -2268,7 +1778,7 @@ function indexStringArrayField(state, internalId, fieldPath, arr, language, opti
2268
1778
  for (const t of tokenizeIterator(item, language, opts)) {
2269
1779
  tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2270
1780
  fieldTokenList.push(t.token);
2271
- countSurface(surfaceCounts, t.surface, t.token);
1781
+ countOccurrence(surfaceCounts, spellings, t.surface, t.token);
2272
1782
  }
2273
1783
  }
2274
1784
  fieldLengths[fieldPath] = fieldTokenList.length;
@@ -2280,17 +1790,28 @@ function indexStringArrayField(state, internalId, fieldPath, arr, language, opti
2280
1790
  tokensByField[fieldPath] = fieldTokenList;
2281
1791
  }
2282
1792
  function indexDocument(state, docId, document, fields, language, options) {
2283
- const fieldLengths = {};
2284
- const tokensByField = {};
1793
+ const indexed = emptyIndexedText(fields);
1794
+ const { fieldLengths, tokensByField } = indexed;
2285
1795
  const internalId = resolveInternalId(state, docId);
2286
- for (const { path: fieldPath, base, pattern } of fields) {
1796
+ for (const field of fields) {
1797
+ const { path: fieldPath, base, pattern } = field;
2287
1798
  const value = getNestedValue(document, fieldPath);
2288
1799
  if (value === void 0 || value === null) continue;
2289
1800
  recordValueSize(state.sizeIndexes, internalId, fieldPath, base, value);
2290
1801
  ensureFieldIndex(state, fieldPath, base);
2291
1802
  if (pattern) patternIndexOf(state.patternIndexes, fieldPath).add(internalId, value);
2292
1803
  if (base === "string") {
2293
- indexStringField(state, internalId, fieldPath, value, language, options, fieldLengths, tokensByField);
1804
+ indexStringField(
1805
+ state,
1806
+ internalId,
1807
+ fieldPath,
1808
+ value,
1809
+ language,
1810
+ options,
1811
+ fieldLengths,
1812
+ tokensByField,
1813
+ partialSpellingsFor(field, indexed)
1814
+ );
2294
1815
  } else if (base === "number") {
2295
1816
  getOrCreateNumericIndex(state, fieldPath).insert(internalId, value);
2296
1817
  } else if (base === "boolean") {
@@ -2309,7 +1830,8 @@ function indexDocument(state, docId, document, fields, language, options) {
2309
1830
  language,
2310
1831
  options,
2311
1832
  fieldLengths,
2312
- tokensByField
1833
+ tokensByField,
1834
+ partialSpellingsFor(field, indexed)
2313
1835
  );
2314
1836
  } else if (base === "number[]") {
2315
1837
  const numIdx = getOrCreateNumericIndex(state, fieldPath);
@@ -2324,15 +1846,17 @@ function indexDocument(state, docId, document, fields, language, options) {
2324
1846
  }
2325
1847
  }
2326
1848
  }
2327
- return { fieldLengths, tokensByField };
1849
+ return indexed;
2328
1850
  }
2329
1851
  function removeFromIndexes(state, docId, storedDoc, schemaFields, language, options) {
2330
- const fieldLengths = {};
2331
- const tokensByField = {};
1852
+ const removed = emptyIndexedText(schemaFields);
1853
+ const { fieldLengths, tokensByField } = removed;
2332
1854
  const fields = storedDoc.fields;
2333
1855
  const internalId = resolveInternalId(state, docId);
2334
1856
  const opts = tokenizeOptions(options);
2335
- for (const { path: fieldPath, base, pattern } of schemaFields) {
1857
+ const occurrencesByToken = /* @__PURE__ */ new Map();
1858
+ for (const field of schemaFields) {
1859
+ const { path: fieldPath, base, pattern } = field;
2336
1860
  const value = getNestedValue(fields, fieldPath);
2337
1861
  if (value === void 0 || value === null) continue;
2338
1862
  forgetValueSize(state.sizeIndexes, internalId, fieldPath, base, value);
@@ -2340,16 +1864,15 @@ function removeFromIndexes(state, docId, storedDoc, schemaFields, language, opti
2340
1864
  if (base === "string") {
2341
1865
  const result = tokenize(value, language, opts);
2342
1866
  fieldLengths[fieldPath] = result.tokens.length;
2343
- const uniqueTokens = /* @__PURE__ */ new Set();
2344
1867
  const fieldTokenList = [];
2345
1868
  const surfaceCounts = /* @__PURE__ */ new Map();
1869
+ const spellings = partialSpellingsFor(field, removed);
2346
1870
  for (let i = 0; i < result.tokens.length; i++) {
2347
1871
  const t = result.tokens[i];
2348
- uniqueTokens.add(t.token);
2349
1872
  fieldTokenList.push(t.token);
2350
- countSurface(surfaceCounts, result.surfaces?.[i], t.token);
1873
+ countOccurrence(surfaceCounts, spellings, result.surfaces?.[i], t.token);
2351
1874
  }
2352
- for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
1875
+ addFieldOccurrences(occurrencesByToken, fieldTokenList);
2353
1876
  applySurfaceCounts(state, surfaceCounts, -1);
2354
1877
  tokensByField[fieldPath] = fieldTokenList;
2355
1878
  } else if (base === "number") {
@@ -2362,20 +1885,19 @@ function removeFromIndexes(state, docId, storedDoc, schemaFields, language, opti
2362
1885
  state.geoIndexes.get(fieldPath)?.remove(internalId);
2363
1886
  } else if (base === "string[]") {
2364
1887
  const arr = value;
2365
- const uniqueTokens = /* @__PURE__ */ new Set();
2366
1888
  const fieldTokenList = [];
2367
1889
  const surfaceCounts = /* @__PURE__ */ new Map();
1890
+ const spellings = partialSpellingsFor(field, removed);
2368
1891
  for (const item of arr) {
2369
1892
  const result = tokenize(item, language, opts);
2370
1893
  for (let i = 0; i < result.tokens.length; i++) {
2371
1894
  const t = result.tokens[i];
2372
- uniqueTokens.add(t.token);
2373
1895
  fieldTokenList.push(t.token);
2374
- countSurface(surfaceCounts, result.surfaces?.[i], t.token);
1896
+ countOccurrence(surfaceCounts, spellings, result.surfaces?.[i], t.token);
2375
1897
  }
2376
1898
  }
2377
1899
  fieldLengths[fieldPath] = fieldTokenList.length;
2378
- for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
1900
+ addFieldOccurrences(occurrencesByToken, fieldTokenList);
2379
1901
  applySurfaceCounts(state, surfaceCounts, -1);
2380
1902
  tokensByField[fieldPath] = fieldTokenList;
2381
1903
  } else if (base === "number[]") {
@@ -2395,7 +1917,8 @@ function removeFromIndexes(state, docId, storedDoc, schemaFields, language, opti
2395
1917
  }
2396
1918
  }
2397
1919
  }
2398
- return { fieldLengths, tokensByField };
1920
+ for (const [token, occurrences] of occurrencesByToken) state.invertedIdx.remove(token, internalId, occurrences);
1921
+ return removed;
2399
1922
  }
2400
1923
 
2401
1924
  // src/core/partition/field-updates.ts
@@ -3530,6 +3053,67 @@ function partitionFilterMatches(state, filters, schema, meter) {
3530
3053
  };
3531
3054
  }
3532
3055
 
3056
+ // src/core/scorer.ts
3057
+ var DEFAULT_K1 = 1.2;
3058
+ var DEFAULT_B = 0.75;
3059
+ function computeIDF(docFrequency, totalDocs) {
3060
+ if (totalDocs === 0) return 0;
3061
+ return Math.log((totalDocs - docFrequency + 0.5) / (docFrequency + 0.5) + 1);
3062
+ }
3063
+ function bm25PruningSound(params) {
3064
+ const k1 = params?.k1 ?? DEFAULT_K1;
3065
+ const b = params?.b ?? DEFAULT_B;
3066
+ return Number.isFinite(k1) && k1 >= 0 && b >= 0 && b <= 1;
3067
+ }
3068
+ function resolveBM25Params(params) {
3069
+ return { k1: params?.k1 ?? DEFAULT_K1, b: params?.b ?? DEFAULT_B };
3070
+ }
3071
+ function computeBM25WithIDF(termFrequency, idf, fieldLength, avgFieldLength, k1, b) {
3072
+ if (avgFieldLength === 0) return 0;
3073
+ const numerator = termFrequency * (k1 + 1);
3074
+ const denominator = termFrequency + k1 * (1 - b + b * fieldLength / avgFieldLength);
3075
+ if (denominator === 0) return 0;
3076
+ return idf * (numerator / denominator);
3077
+ }
3078
+ function computeBM25(termFrequency, docFrequency, totalDocs, fieldLength, avgFieldLength, params) {
3079
+ if (totalDocs === 0) return 0;
3080
+ const k1 = params?.k1 ?? DEFAULT_K1;
3081
+ const b = params?.b ?? DEFAULT_B;
3082
+ return computeBM25WithIDF(termFrequency, computeIDF(docFrequency, totalDocs), fieldLength, avgFieldLength, k1, b);
3083
+ }
3084
+
3085
+ // src/core/partition/field-scoring.ts
3086
+ function loadFieldScoring(state, fields, boost, averageFieldLengths) {
3087
+ const fieldNames = state.fieldNameTable.names;
3088
+ const scoring = {
3089
+ searchable: new Uint8Array(fieldNames.length),
3090
+ boosts: new Float64Array(fieldNames.length),
3091
+ averageLengths: new Float64Array(fieldNames.length),
3092
+ lengthColumns: new Array(fieldNames.length).fill(null)
3093
+ };
3094
+ for (let fieldIndex = 0; fieldIndex < fieldNames.length; fieldIndex++) {
3095
+ const fieldName = fieldNames[fieldIndex];
3096
+ scoring.searchable[fieldIndex] = fields === void 0 || fields.includes(fieldName) ? 1 : 0;
3097
+ scoring.boosts[fieldIndex] = boost?.[fieldName] ?? 1;
3098
+ scoring.averageLengths[fieldIndex] = averageFieldLengths[fieldName] ?? 1;
3099
+ scoring.lengthColumns[fieldIndex] = state.docStore.fieldLengthColumn(fieldName);
3100
+ }
3101
+ return scoring;
3102
+ }
3103
+ function fieldLengthOf(columns, fieldIndex, internalId, averageLength) {
3104
+ const column = fieldIndex < columns.length ? columns[fieldIndex] : null;
3105
+ if (column === null || internalId >= column.length) return averageLength;
3106
+ const stored = column[internalId];
3107
+ return stored > 0 ? stored : averageLength;
3108
+ }
3109
+ function bestSearchable(searchable, values) {
3110
+ let best = 0;
3111
+ for (let index = 0; index < searchable.length; index++) {
3112
+ if (searchable[index] === 1 && values[index] > best) best = values[index];
3113
+ }
3114
+ return best;
3115
+ }
3116
+
3533
3117
  // src/core/partition/posting-columns.ts
3534
3118
  function postingColumns(list) {
3535
3119
  const deletedDocs = list.deletedDocs;
@@ -3543,39 +3127,174 @@ function postingColumns(list) {
3543
3127
  };
3544
3128
  }
3545
3129
 
3546
- // src/core/partition/matches.ts
3547
- function markPostingList(matched, list, fieldNames, fields, filterBitset) {
3548
- const { docIds, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(list);
3549
- for (let pi = 0; pi < count; pi++) {
3550
- const internalId = docIds[pi];
3551
- if (hasDeleted && deletedDocs.has(internalId)) continue;
3552
- if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
3553
- if (fields && !fields.includes(fieldNames[fieldNameIndices[pi]])) continue;
3554
- bitsetSet(matched, internalId);
3130
+ // src/core/partition/scoring.ts
3131
+ var EMPTY_COMPONENTS = Object.freeze({});
3132
+ function recordComponents(components, internalId, fieldName, token, termFrequency, fieldLength, idf) {
3133
+ const existing = components.get(internalId);
3134
+ if (existing) {
3135
+ existing.termFrequencies[`${fieldName}:${token}`] = termFrequency;
3136
+ existing.fieldLengths[fieldName] = fieldLength;
3137
+ existing.idf[token] = idf;
3138
+ return;
3555
3139
  }
3140
+ components.set(internalId, {
3141
+ termFrequencies: { [`${fieldName}:${token}`]: termFrequency },
3142
+ fieldLengths: { [fieldName]: fieldLength },
3143
+ idf: { [token]: idf }
3144
+ });
3556
3145
  }
3557
- function searchFulltextMatches(state, params) {
3558
- const { queryTokens, prefixExpansion, fields, tolerance = 0, prefixLength = 2, exact = false, filterBitset } = params;
3559
- const fieldNames = state.fieldNameTable.names;
3560
- const matched = createBitSet(state.docStore.internalIdCapacity());
3561
- for (const qt of queryTokens) {
3562
- if (prefixExpansion && qt.token === prefixExpansion.token) {
3563
- const seen = /* @__PURE__ */ new Set();
3564
- for (const term of [qt.token, ...prefixExpansion.terms]) {
3565
- if (seen.has(term)) continue;
3566
- seen.add(term);
3567
- const postingList = state.invertedIdx.lookup(term);
3568
- if (postingList) markPostingList(matched, postingList, fieldNames, fields, filterBitset);
3569
- }
3570
- continue;
3571
- }
3572
- if (exact || tolerance === 0) {
3573
- const postingList = state.invertedIdx.lookup(qt.token);
3574
- if (postingList) markPostingList(matched, postingList, fieldNames, fields, filterBitset);
3575
- continue;
3576
- }
3577
- for (const match of state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength)) {
3578
- markPostingList(matched, match.postingList, fieldNames, fields, filterBitset);
3146
+ function mergeGroupComponents(components, internalId, contribution) {
3147
+ const existing = components.get(internalId);
3148
+ if (existing) {
3149
+ Object.assign(existing.termFrequencies, contribution.termFrequencies);
3150
+ Object.assign(existing.fieldLengths, contribution.fieldLengths);
3151
+ existing.idf[contribution.token] = contribution.idf;
3152
+ return;
3153
+ }
3154
+ components.set(internalId, {
3155
+ termFrequencies: contribution.termFrequencies,
3156
+ fieldLengths: contribution.fieldLengths,
3157
+ idf: { [contribution.token]: contribution.idf }
3158
+ });
3159
+ }
3160
+
3161
+ // src/core/partition/group-scoring.ts
3162
+ function partialFieldMarks(fieldNames, partialFields) {
3163
+ const marks = new Uint8Array(fieldNames.length);
3164
+ if (partialFields === void 0 || partialFields.length === 0) return marks;
3165
+ for (let fieldIndex = 0; fieldIndex < fieldNames.length; fieldIndex++) {
3166
+ if (partialFields.includes(fieldNames[fieldIndex])) marks[fieldIndex] = 1;
3167
+ }
3168
+ return marks;
3169
+ }
3170
+ function termGroupOf(groups, token) {
3171
+ if (groups === void 0) return void 0;
3172
+ for (const group of groups) {
3173
+ if (group.token === token) return group;
3174
+ }
3175
+ return void 0;
3176
+ }
3177
+ function typoMatchesFor(index, token, group, exact, tolerance, prefixLength) {
3178
+ if (group !== void 0) {
3179
+ if (!group.typoMatches || exact || tolerance === 0) return [];
3180
+ return index.fuzzyLookup(token, tolerance, prefixLength).filter((match) => match.token !== token);
3181
+ }
3182
+ if (!exact) return index.fuzzyLookup(token, tolerance, prefixLength);
3183
+ const postingList = index.lookup(token);
3184
+ return postingList ? [{ token, postingList }] : [];
3185
+ }
3186
+ function resolveGroupMatches(context, group) {
3187
+ let groupDocumentFrequency = 0;
3188
+ const found = [];
3189
+ for (const member of group.members) {
3190
+ const documents = context.globalFrequency(member.token) ?? context.index.documentFrequency(member.token);
3191
+ if (documents > groupDocumentFrequency) groupDocumentFrequency = documents;
3192
+ const postingList = context.index.lookup(member.token);
3193
+ if (postingList === void 0) continue;
3194
+ found.push({ token: member.token, factor: member.factor, partialFactor: member.partialFactor, postingList });
3195
+ }
3196
+ if (found.length === 0) return [];
3197
+ const idf = computeIDF(groupDocumentFrequency, context.totalDocs);
3198
+ return found.map((entry) => ({ ...entry, idf }));
3199
+ }
3200
+ function groupPostingCount(matches) {
3201
+ let total = 0;
3202
+ for (const match of matches) total += match.postingList.length;
3203
+ return total;
3204
+ }
3205
+ function computeGroupContributions(context, matches, collect) {
3206
+ const { fields, fieldNames, filterBitset, partialMarks, k1, b } = context;
3207
+ const scoresAreZero = context.totalDocs === 0;
3208
+ const best = /* @__PURE__ */ new Map();
3209
+ for (const match of matches) {
3210
+ const perTerm = /* @__PURE__ */ new Map();
3211
+ const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
3212
+ match.postingList
3213
+ );
3214
+ for (let pi = 0; pi < count; pi++) {
3215
+ const internalId = docIds[pi];
3216
+ if (hasDeleted && deletedDocs.has(internalId)) continue;
3217
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
3218
+ const fieldIndex = fieldNameIndices[pi];
3219
+ if (fields.searchable[fieldIndex] === 0) continue;
3220
+ const factor = partialMarks[fieldIndex] === 1 ? match.partialFactor : match.factor;
3221
+ if (factor === 0) continue;
3222
+ const fieldName = fieldNames[fieldIndex];
3223
+ const termFrequency = termFrequencies[pi];
3224
+ const fieldBoost = fields.boosts[fieldIndex];
3225
+ const avgLen = fields.averageLengths[fieldIndex];
3226
+ const actualFieldLength = fieldLengthOf(fields.lengthColumns, fieldIndex, internalId, avgLen);
3227
+ const termScore = (scoresAreZero ? 0 : computeBM25WithIDF(termFrequency, match.idf, actualFieldLength, avgLen, k1, b)) * fieldBoost * factor;
3228
+ const existing = perTerm.get(internalId);
3229
+ if (existing) {
3230
+ existing.score += termScore;
3231
+ if (collect) {
3232
+ existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
3233
+ existing.fieldLengths[fieldName] = actualFieldLength;
3234
+ }
3235
+ } else if (collect) {
3236
+ perTerm.set(internalId, {
3237
+ score: termScore,
3238
+ token: match.token,
3239
+ idf: match.idf,
3240
+ termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
3241
+ fieldLengths: { [fieldName]: actualFieldLength }
3242
+ });
3243
+ } else {
3244
+ perTerm.set(internalId, {
3245
+ score: termScore,
3246
+ token: match.token,
3247
+ idf: match.idf,
3248
+ termFrequencies: EMPTY_COMPONENTS,
3249
+ fieldLengths: EMPTY_COMPONENTS
3250
+ });
3251
+ }
3252
+ }
3253
+ for (const [internalId, contribution] of perTerm) {
3254
+ const current = best.get(internalId);
3255
+ if (!current || contribution.score > current.score) {
3256
+ best.set(internalId, contribution);
3257
+ }
3258
+ }
3259
+ }
3260
+ return best;
3261
+ }
3262
+
3263
+ // src/core/partition/matches.ts
3264
+ function markPostingList(matched, list, markedFields, filterBitset) {
3265
+ const { docIds, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(list);
3266
+ for (let pi = 0; pi < count; pi++) {
3267
+ const internalId = docIds[pi];
3268
+ if (hasDeleted && deletedDocs.has(internalId)) continue;
3269
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
3270
+ if (markedFields[fieldNameIndices[pi]] === 0) continue;
3271
+ bitsetSet(matched, internalId);
3272
+ }
3273
+ }
3274
+ function searchedFieldMarks(fieldNames, fields) {
3275
+ const marks = new Uint8Array(fieldNames.length);
3276
+ for (let fieldIndex = 0; fieldIndex < fieldNames.length; fieldIndex++) {
3277
+ if (fields === void 0 || fields.includes(fieldNames[fieldIndex])) marks[fieldIndex] = 1;
3278
+ }
3279
+ return marks;
3280
+ }
3281
+ function searchFulltextMatches(state, params) {
3282
+ const { queryTokens, termGroups, fields, tolerance = 0, prefixLength = 2, exact = false, filterBitset } = params;
3283
+ const fieldNames = state.fieldNameTable.names;
3284
+ const matched = createBitSet(state.docStore.internalIdCapacity());
3285
+ const searched = searchedFieldMarks(fieldNames, fields);
3286
+ const partial = partialFieldMarks(fieldNames, params.partialFields);
3287
+ const searchedPartial = searched.map((mark, fieldIndex) => mark & partial[fieldIndex]);
3288
+ for (const qt of queryTokens) {
3289
+ const group = termGroupOf(termGroups, qt.token);
3290
+ for (const match of typoMatchesFor(state.invertedIdx, qt.token, group, exact, tolerance, prefixLength)) {
3291
+ markPostingList(matched, match.postingList, searched, filterBitset);
3292
+ }
3293
+ if (group === void 0) continue;
3294
+ for (const member of group.members) {
3295
+ const postingList = state.invertedIdx.lookup(member.token);
3296
+ if (postingList === void 0) continue;
3297
+ markPostingList(matched, postingList, member.factor > 0 ? searched : searchedPartial, filterBitset);
3579
3298
  }
3580
3299
  }
3581
3300
  const docStore = state.docStore;
@@ -3663,6 +3382,7 @@ function estimatePartitionBytes(state) {
3663
3382
  bytes += docCount * state.geoIndexes.size * FIELD_ENTRY_OVERHEAD_BYTES;
3664
3383
  bytes += docCount * state.sizeIndexes.size * FIELD_ENTRY_OVERHEAD_BYTES;
3665
3384
  for (const index of state.patternIndexes.values()) bytes += index.bytes();
3385
+ bytes += state.partialWords.bytes();
3666
3386
  return bytes + sortColumnBytes(state);
3667
3387
  }
3668
3388
 
@@ -3794,10 +3514,71 @@ function mergeSegmentState(target, source) {
3794
3514
  mergeFieldIndexes(target, source, idMapping);
3795
3515
  mergeStatistics(target, source);
3796
3516
  mergeSurfaceForms(target, source);
3517
+ target.partialWords.afterMerge(source.partialWords.spellings(), [...idMapping.values()]);
3797
3518
  target.sortColumns = null;
3798
3519
  target.scoreBuffer = null;
3799
3520
  }
3800
3521
 
3522
+ // src/core/partition/partial-words/platform.ts
3523
+ function platformPartialWordSearch() {
3524
+ return partialWordSearchModule;
3525
+ }
3526
+
3527
+ // src/core/partition/partial-words/registry.ts
3528
+ var registered2 = null;
3529
+ function isPartialWordSearchModule(value) {
3530
+ return value !== null && typeof value === "object" && value.name === "partialWords" && "createGrowingSlot" in value && typeof value.createGrowingSlot === "function" && "createFrozenSlot" in value && typeof value.createFrozenSlot === "function" && "buildTable" in value && typeof value.buildTable === "function" && "expand" in value && typeof value.expand === "function";
3531
+ }
3532
+ function registerPartialWordSearch(module) {
3533
+ if (!isPartialWordSearchModule(module)) {
3534
+ throw new NarsilError(
3535
+ ErrorCodes.CONFIG_INVALID,
3536
+ 'Pass registerPartialWordSearch the partialWordSearch object that "@delali/narsil/partial-words" exports'
3537
+ );
3538
+ }
3539
+ registered2 = module;
3540
+ }
3541
+ function availablePartialWordSearch() {
3542
+ return registered2 ?? platformPartialWordSearch();
3543
+ }
3544
+ function requirePartialWordSearchFor(schema) {
3545
+ const fields = partialFieldsOf(schema);
3546
+ if (fields.length === 0 || availablePartialWordSearch() !== null) return;
3547
+ throw new NarsilError(
3548
+ ErrorCodes.CONFIG_INVALID,
3549
+ `The type of field "${fields[0]}" includes "partial", but this bundle contains no partial-word search code. Add import { partialWordSearch } from '@delali/narsil/partial-words', then call registerPartialWordSearch(partialWordSearch) before you create or load the index`,
3550
+ { field: fields[0] }
3551
+ );
3552
+ }
3553
+ var EMPTY_GROWING_SLOT = {
3554
+ readerFor: () => null,
3555
+ spellings: () => null,
3556
+ bytes: () => 0,
3557
+ afterDocument: () => void 0,
3558
+ afterMerge: () => void 0,
3559
+ forgetSpellings: () => void 0,
3560
+ clear: () => void 0
3561
+ };
3562
+ var EMPTY_FROZEN_SLOT = {
3563
+ readerFor: () => null,
3564
+ spellings: () => null,
3565
+ bytes: () => 0,
3566
+ afterRemoval: () => void 0
3567
+ };
3568
+ function createGrowingPartialWords(view) {
3569
+ return availablePartialWordSearch()?.createGrowingSlot(view) ?? EMPTY_GROWING_SLOT;
3570
+ }
3571
+ function createFrozenPartialWords(initial, tokenTable, documents) {
3572
+ return availablePartialWordSearch()?.createFrozenSlot(initial, tokenTable, documents) ?? EMPTY_FROZEN_SLOT;
3573
+ }
3574
+ function buildPartialWordTableFrom(source, tokenTable) {
3575
+ return availablePartialWordSearch()?.buildTable(source, tokenTable) ?? null;
3576
+ }
3577
+ function expandPartialWords(subs, analysis, requests) {
3578
+ const module = availablePartialWordSearch();
3579
+ return module === null ? requests.map(() => []) : module.expand(subs, analysis, requests);
3580
+ }
3581
+
3801
3582
  // src/core/partition/rebuild.ts
3802
3583
  var EMPTY_STATS = {
3803
3584
  totalDocuments: 0,
@@ -3853,70 +3634,10 @@ function rebuildTextIndex(state, schema, language, options) {
3853
3634
  }
3854
3635
  } finally {
3855
3636
  state.invertedIdx.endBatch();
3637
+ state.partialWords.forgetSpellings();
3856
3638
  }
3857
3639
  }
3858
3640
 
3859
- // src/core/scorer.ts
3860
- var DEFAULT_K1 = 1.2;
3861
- var DEFAULT_B = 0.75;
3862
- function computeIDF(docFrequency, totalDocs) {
3863
- if (totalDocs === 0) return 0;
3864
- return Math.log((totalDocs - docFrequency + 0.5) / (docFrequency + 0.5) + 1);
3865
- }
3866
- function bm25PruningSound(params) {
3867
- const k1 = params?.k1 ?? DEFAULT_K1;
3868
- const b = params?.b ?? DEFAULT_B;
3869
- return Number.isFinite(k1) && k1 >= 0 && b >= 0 && b <= 1;
3870
- }
3871
- function resolveBM25Params(params) {
3872
- return { k1: params?.k1 ?? DEFAULT_K1, b: params?.b ?? DEFAULT_B };
3873
- }
3874
- function computeBM25WithIDF(termFrequency, idf, fieldLength, avgFieldLength, k1, b) {
3875
- if (avgFieldLength === 0) return 0;
3876
- const numerator = termFrequency * (k1 + 1);
3877
- const denominator = termFrequency + k1 * (1 - b + b * fieldLength / avgFieldLength);
3878
- if (denominator === 0) return 0;
3879
- return idf * (numerator / denominator);
3880
- }
3881
- function computeBM25(termFrequency, docFrequency, totalDocs, fieldLength, avgFieldLength, params) {
3882
- if (totalDocs === 0) return 0;
3883
- const k1 = params?.k1 ?? DEFAULT_K1;
3884
- const b = params?.b ?? DEFAULT_B;
3885
- return computeBM25WithIDF(termFrequency, computeIDF(docFrequency, totalDocs), fieldLength, avgFieldLength, k1, b);
3886
- }
3887
-
3888
- // src/core/partition/field-scoring.ts
3889
- function loadFieldScoring(state, fields, boost, averageFieldLengths) {
3890
- const fieldNames = state.fieldNameTable.names;
3891
- const scoring = {
3892
- searchable: new Uint8Array(fieldNames.length),
3893
- boosts: new Float64Array(fieldNames.length),
3894
- averageLengths: new Float64Array(fieldNames.length),
3895
- lengthColumns: new Array(fieldNames.length).fill(null)
3896
- };
3897
- for (let fieldIndex = 0; fieldIndex < fieldNames.length; fieldIndex++) {
3898
- const fieldName = fieldNames[fieldIndex];
3899
- scoring.searchable[fieldIndex] = fields === void 0 || fields.includes(fieldName) ? 1 : 0;
3900
- scoring.boosts[fieldIndex] = boost?.[fieldName] ?? 1;
3901
- scoring.averageLengths[fieldIndex] = averageFieldLengths[fieldName] ?? 1;
3902
- scoring.lengthColumns[fieldIndex] = state.docStore.fieldLengthColumn(fieldName);
3903
- }
3904
- return scoring;
3905
- }
3906
- function fieldLengthOf(columns, fieldIndex, internalId, averageLength) {
3907
- const column = fieldIndex < columns.length ? columns[fieldIndex] : null;
3908
- if (column === null || internalId >= column.length) return averageLength;
3909
- const stored = column[internalId];
3910
- return stored > 0 ? stored : averageLength;
3911
- }
3912
- function bestSearchable(searchable, values) {
3913
- let best = 0;
3914
- for (let index = 0; index < searchable.length; index++) {
3915
- if (searchable[index] === 1 && values[index] > best) best = values[index];
3916
- }
3917
- return best;
3918
- }
3919
-
3920
3641
  // src/core/partition/block-bounds.ts
3921
3642
  var ABSENT_FIELD_LENGTH_BOUND = 0;
3922
3643
  var boundsByList = /* @__PURE__ */ new WeakMap();
@@ -4012,37 +3733,6 @@ function blockBoundsFor(list, columns) {
4012
3733
  return built;
4013
3734
  }
4014
3735
 
4015
- // src/core/partition/scoring.ts
4016
- var EMPTY_COMPONENTS = Object.freeze({});
4017
- function recordComponents(components, internalId, fieldName, token, termFrequency, fieldLength, idf) {
4018
- const existing = components.get(internalId);
4019
- if (existing) {
4020
- existing.termFrequencies[`${fieldName}:${token}`] = termFrequency;
4021
- existing.fieldLengths[fieldName] = fieldLength;
4022
- existing.idf[token] = idf;
4023
- return;
4024
- }
4025
- components.set(internalId, {
4026
- termFrequencies: { [`${fieldName}:${token}`]: termFrequency },
4027
- fieldLengths: { [fieldName]: fieldLength },
4028
- idf: { [token]: idf }
4029
- });
4030
- }
4031
- function mergePrefixComponents(components, internalId, contribution) {
4032
- const existing = components.get(internalId);
4033
- if (existing) {
4034
- Object.assign(existing.termFrequencies, contribution.termFrequencies);
4035
- Object.assign(existing.fieldLengths, contribution.fieldLengths);
4036
- existing.idf[contribution.token] = contribution.idf;
4037
- return;
4038
- }
4039
- components.set(internalId, {
4040
- termFrequencies: contribution.termFrequencies,
4041
- fieldLengths: contribution.fieldLengths,
4042
- idf: { [contribution.token]: contribution.idf }
4043
- });
4044
- }
4045
-
4046
3736
  // src/core/partition/top-k-heap.ts
4047
3737
  function candidateWorse(a, b) {
4048
3738
  if (a.score !== b.score) return a.score < b.score;
@@ -4206,7 +3896,7 @@ function topKFromBuffer(buffer, k, resolver, components) {
4206
3896
  // src/core/partition/multi-term-topk.ts
4207
3897
  function prunableMultiTermLists(params, index) {
4208
3898
  if (params.queryTokens.length < 2) return null;
4209
- if (params.prefixExpansion !== void 0) return null;
3899
+ if (params.termGroups !== void 0 && params.termGroups.length > 0) return null;
4210
3900
  if (params.exact !== true && (params.tolerance ?? 0) !== 0) return null;
4211
3901
  if (params.termMatch !== void 0 && params.termMatch !== "any") return null;
4212
3902
  if (params.collectComponents !== false) return null;
@@ -4378,87 +4068,6 @@ function multiTermTopK(request) {
4378
4068
  return { scored: pageFromBuffer(scans, buffer, selection, resolver), totalMatched };
4379
4069
  }
4380
4070
 
4381
- // src/core/partition/prefix-scoring.ts
4382
- function resolvePrefixMatches(context, token, expansionTerms) {
4383
- const found = [];
4384
- const seen = /* @__PURE__ */ new Set();
4385
- for (const term of [token, ...expansionTerms]) {
4386
- if (seen.has(term)) continue;
4387
- seen.add(term);
4388
- const postingList = context.index.lookup(term);
4389
- if (!postingList) continue;
4390
- found.push({ token: term, postingList, docFreq: context.documentFrequency(term, postingList) });
4391
- }
4392
- if (found.length === 0) return [];
4393
- let blendedDf = 0;
4394
- for (const f of found) {
4395
- if (f.docFreq > blendedDf) blendedDf = f.docFreq;
4396
- }
4397
- const blendedIdf = computeIDF(blendedDf, context.totalDocs);
4398
- return found.map((f) => ({
4399
- token: f.token,
4400
- factor: Math.min(1, token.length / f.token.length),
4401
- postingList: f.postingList,
4402
- docFreq: blendedDf,
4403
- idf: blendedIdf
4404
- }));
4405
- }
4406
- function computePrefixContributions(context, matches, collect) {
4407
- const { fields, fieldNames, filterBitset, k1, b } = context;
4408
- const scoresAreZero = context.totalDocs === 0;
4409
- const best = /* @__PURE__ */ new Map();
4410
- for (const match of matches) {
4411
- const perTerm = /* @__PURE__ */ new Map();
4412
- const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
4413
- match.postingList
4414
- );
4415
- for (let pi = 0; pi < count; pi++) {
4416
- const internalId = docIds[pi];
4417
- if (hasDeleted && deletedDocs.has(internalId)) continue;
4418
- if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
4419
- const fieldIndex = fieldNameIndices[pi];
4420
- if (fields.searchable[fieldIndex] === 0) continue;
4421
- const fieldName = fieldNames[fieldIndex];
4422
- const termFrequency = termFrequencies[pi];
4423
- const fieldBoost = fields.boosts[fieldIndex];
4424
- const avgLen = fields.averageLengths[fieldIndex];
4425
- const actualFieldLength = fieldLengthOf(fields.lengthColumns, fieldIndex, internalId, avgLen);
4426
- const termScore = (scoresAreZero ? 0 : computeBM25WithIDF(termFrequency, match.idf, actualFieldLength, avgLen, k1, b)) * fieldBoost * match.factor;
4427
- const existing = perTerm.get(internalId);
4428
- if (existing) {
4429
- existing.score += termScore;
4430
- if (collect) {
4431
- existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
4432
- existing.fieldLengths[fieldName] = actualFieldLength;
4433
- }
4434
- } else if (collect) {
4435
- perTerm.set(internalId, {
4436
- score: termScore,
4437
- token: match.token,
4438
- idf: match.idf,
4439
- termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
4440
- fieldLengths: { [fieldName]: actualFieldLength }
4441
- });
4442
- } else {
4443
- perTerm.set(internalId, {
4444
- score: termScore,
4445
- token: match.token,
4446
- idf: match.idf,
4447
- termFrequencies: EMPTY_COMPONENTS,
4448
- fieldLengths: EMPTY_COMPONENTS
4449
- });
4450
- }
4451
- }
4452
- for (const [internalId, contribution] of perTerm) {
4453
- const current = best.get(internalId);
4454
- if (!current || contribution.score > current.score) {
4455
- best.set(internalId, contribution);
4456
- }
4457
- }
4458
- }
4459
- return best;
4460
- }
4461
-
4462
4071
  // src/core/partition/single-term-topk.ts
4463
4072
  function fieldsCoverEvery(fields, fieldNames) {
4464
4073
  if (fields === void 0) return true;
@@ -4469,7 +4078,7 @@ function fieldsCoverEvery(fields, fieldNames) {
4469
4078
  }
4470
4079
  function prunableSingleTermList(params, index, fieldNames) {
4471
4080
  if (params.queryTokens.length !== 1) return null;
4472
- if (params.prefixExpansion !== void 0) return null;
4081
+ if (params.termGroups !== void 0 && params.termGroups.length > 0) return null;
4473
4082
  if (params.exact !== true && (params.tolerance ?? 0) !== 0) return null;
4474
4083
  if (params.termMatch !== void 0 && params.termMatch !== "any") return null;
4475
4084
  if (params.collectComponents !== false) return null;
@@ -4579,15 +4188,11 @@ function singleTermTopK(request) {
4579
4188
  function globalDocFreqFor(docFreqs, term, fallback) {
4580
4189
  return Object.hasOwn(docFreqs, term) ? docFreqs[term] : fallback;
4581
4190
  }
4582
- function matchesFor(index, token, exact, tolerance, prefixLength) {
4583
- if (!exact) return index.fuzzyLookup(token, tolerance, prefixLength);
4584
- const postingList = index.lookup(token);
4585
- return postingList ? [{ token, postingList }] : [];
4586
- }
4587
4191
  function searchFulltext(state, params) {
4588
4192
  const {
4589
4193
  queryTokens,
4590
- prefixExpansion,
4194
+ termGroups,
4195
+ partialFields,
4591
4196
  fields,
4592
4197
  boost,
4593
4198
  tolerance = 0,
@@ -4619,19 +4224,75 @@ function searchFulltext(state, params) {
4619
4224
  function documentFrequency(token, list) {
4620
4225
  return globalStats ? globalDocFreqFor(globalStats.docFrequencies, token, list.docIdSet.size) : list.docIdSet.size;
4621
4226
  }
4622
- const prefixContext = {
4227
+ const groupContext = {
4623
4228
  index: state.invertedIdx,
4624
4229
  fields: fieldScoring,
4230
+ partialMarks: partialFieldMarks(fieldNames, partialFields),
4625
4231
  fieldNames,
4626
4232
  totalDocs,
4627
4233
  k1,
4628
4234
  b,
4629
4235
  filterBitset,
4630
- documentFrequency
4236
+ globalFrequency(token) {
4237
+ if (globalStats === void 0 || !Object.hasOwn(globalStats.docFrequencies, token)) return void 0;
4238
+ return globalStats.docFrequencies[token];
4239
+ }
4631
4240
  };
4632
- function mergePrefixContribution(internalId, contribution) {
4241
+ function mergeGroupContribution(internalId, contribution) {
4633
4242
  addScore(scoreBuffer, internalId, contribution.score);
4634
- if (components !== null) mergePrefixComponents(components, internalId, contribution);
4243
+ if (components !== null) mergeGroupComponents(components, internalId, contribution);
4244
+ }
4245
+ function resolveMatch(token, postingList) {
4246
+ const docFreq = documentFrequency(token, postingList);
4247
+ return { token, docFreq, idf: computeIDF(docFreq, totalDocs), postingList };
4248
+ }
4249
+ function scorePostings(match, onlyScored) {
4250
+ const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
4251
+ match.postingList
4252
+ );
4253
+ if (!onlyScored && !hasDeleted && filterBitset === void 0 && components === null) {
4254
+ for (let pi = 0; pi < count; pi++) {
4255
+ const internalId = docIds[pi];
4256
+ const fieldIndex = fieldNameIndices[pi];
4257
+ if (searchable[fieldIndex] === 0) continue;
4258
+ const avgLen = averageLengths[fieldIndex];
4259
+ const actualFieldLength = fieldLengthOf(lengthColumns, fieldIndex, internalId, avgLen);
4260
+ const termScore = scoresAreZero ? 0 : computeBM25WithIDF(termFrequencies[pi], match.idf, actualFieldLength, avgLen, k1, b);
4261
+ addScore(scoreBuffer, internalId, termScore * boosts[fieldIndex]);
4262
+ }
4263
+ return;
4264
+ }
4265
+ for (let pi = 0; pi < count; pi++) {
4266
+ const internalId = docIds[pi];
4267
+ if (hasDeleted && deletedDocs.has(internalId)) continue;
4268
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
4269
+ if (onlyScored && !hasScore(scoreBuffer, internalId)) continue;
4270
+ const fieldIndex = fieldNameIndices[pi];
4271
+ if (searchable[fieldIndex] === 0) continue;
4272
+ const termFrequency = termFrequencies[pi];
4273
+ const avgLen = averageLengths[fieldIndex];
4274
+ const actualFieldLength = fieldLengthOf(lengthColumns, fieldIndex, internalId, avgLen);
4275
+ const termScore = scoresAreZero ? 0 : computeBM25WithIDF(termFrequency, match.idf, actualFieldLength, avgLen, k1, b);
4276
+ addScore(scoreBuffer, internalId, termScore * boosts[fieldIndex]);
4277
+ if (components !== null) {
4278
+ recordComponents(
4279
+ components,
4280
+ internalId,
4281
+ fieldNames[fieldIndex],
4282
+ match.token,
4283
+ termFrequency,
4284
+ actualFieldLength,
4285
+ match.idf
4286
+ );
4287
+ }
4288
+ }
4289
+ }
4290
+ function scoreGroup(group, onlyScored) {
4291
+ if (group === null) return;
4292
+ for (const [internalId, contribution] of computeGroupContributions(groupContext, group, collectComponents)) {
4293
+ if (onlyScored && !hasScore(scoreBuffer, internalId)) continue;
4294
+ mergeGroupContribution(internalId, contribution);
4295
+ }
4635
4296
  }
4636
4297
  const prunableList = prunableSingleTermList(params, state.invertedIdx, fieldNames);
4637
4298
  if (prunableList !== null && maxResults !== void 0) {
@@ -4661,127 +4322,30 @@ function searchFulltext(state, params) {
4661
4322
  }
4662
4323
  if (useIntersection) {
4663
4324
  const resolved = [];
4664
- let prefixMatches = [];
4665
4325
  for (const qt of queryTokens) {
4666
- if (prefixExpansion && qt.token === prefixExpansion.token) {
4667
- prefixMatches = resolvePrefixMatches(prefixContext, qt.token, prefixExpansion.terms);
4668
- let totalPostings2 = 0;
4669
- for (const m of prefixMatches) {
4670
- totalPostings2 += m.postingList.length;
4671
- }
4672
- resolved.push({ token: qt.token, matches: [], totalPostings: totalPostings2, isPrefix: true });
4673
- continue;
4674
- }
4675
- const rawMatches = matchesFor(state.invertedIdx, qt.token, exact, tolerance, prefixLength);
4676
- let totalPostings = 0;
4326
+ const group = termGroupOf(termGroups, qt.token);
4327
+ const groupMatches = group === void 0 ? null : resolveGroupMatches(groupContext, group);
4328
+ let totalPostings = groupMatches === null ? 0 : groupPostingCount(groupMatches);
4677
4329
  const matches = [];
4678
- for (const m of rawMatches) {
4679
- const docFreq = documentFrequency(m.token, m.postingList);
4680
- const idf = computeIDF(docFreq, totalDocs);
4330
+ for (const m of typoMatchesFor(state.invertedIdx, qt.token, group, exact, tolerance, prefixLength)) {
4681
4331
  totalPostings += m.postingList.length;
4682
- matches.push({ token: m.token, docFreq, idf, postingList: m.postingList });
4332
+ matches.push(resolveMatch(m.token, m.postingList));
4683
4333
  }
4684
- resolved.push({ token: qt.token, matches, totalPostings });
4334
+ resolved.push({ token: qt.token, matches, totalPostings, group: groupMatches });
4685
4335
  }
4686
4336
  resolved.sort((a, b2) => a.totalPostings - b2.totalPostings);
4687
4337
  for (let tokenIndex = 0; tokenIndex < resolved.length; tokenIndex++) {
4688
- if (resolved[tokenIndex].isPrefix) {
4689
- const contributions = computePrefixContributions(prefixContext, prefixMatches, collectComponents);
4690
- for (const [internalId, contribution] of contributions) {
4691
- if (tokenIndex > 0 && !hasScore(scoreBuffer, internalId)) continue;
4692
- mergePrefixContribution(internalId, contribution);
4693
- }
4694
- continue;
4695
- }
4696
- for (const match of resolved[tokenIndex].matches) {
4697
- const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
4698
- match.postingList
4699
- );
4700
- for (let pi = 0; pi < count; pi++) {
4701
- const internalId = docIds[pi];
4702
- if (hasDeleted && deletedDocs.has(internalId)) continue;
4703
- if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
4704
- if (tokenIndex > 0 && !hasScore(scoreBuffer, internalId)) continue;
4705
- const fieldIndex = fieldNameIndices[pi];
4706
- if (searchable[fieldIndex] === 0) continue;
4707
- const termFrequency = termFrequencies[pi];
4708
- const fieldBoost = boosts[fieldIndex];
4709
- const avgLen = averageLengths[fieldIndex];
4710
- const actualFieldLength = fieldLengthOf(lengthColumns, fieldIndex, internalId, avgLen);
4711
- let termScore = scoresAreZero ? 0 : computeBM25WithIDF(termFrequency, match.idf, actualFieldLength, avgLen, k1, b);
4712
- termScore *= fieldBoost;
4713
- addScore(scoreBuffer, internalId, termScore);
4714
- if (components !== null) {
4715
- recordComponents(
4716
- components,
4717
- internalId,
4718
- fieldNames[fieldIndex],
4719
- match.token,
4720
- termFrequency,
4721
- actualFieldLength,
4722
- match.idf
4723
- );
4724
- }
4725
- }
4726
- }
4338
+ const onlyScored = tokenIndex > 0;
4339
+ for (const match of resolved[tokenIndex].matches) scorePostings(match, onlyScored);
4340
+ scoreGroup(resolved[tokenIndex].group, onlyScored);
4727
4341
  }
4728
4342
  } else {
4729
4343
  for (const qt of queryTokens) {
4730
- if (prefixExpansion && qt.token === prefixExpansion.token) {
4731
- const contributions = computePrefixContributions(
4732
- prefixContext,
4733
- resolvePrefixMatches(prefixContext, qt.token, prefixExpansion.terms),
4734
- collectComponents
4735
- );
4736
- for (const [internalId, contribution] of contributions) {
4737
- mergePrefixContribution(internalId, contribution);
4738
- }
4739
- continue;
4740
- }
4741
- const matchingPostings = matchesFor(state.invertedIdx, qt.token, exact, tolerance, prefixLength);
4742
- for (const match of matchingPostings) {
4743
- const idf = computeIDF(documentFrequency(match.token, match.postingList), totalDocs);
4744
- const { docIds, termFrequencies, fieldNameIndices, deletedDocs, hasDeleted, count } = postingColumns(
4745
- match.postingList
4746
- );
4747
- if (!hasDeleted && filterBitset === void 0 && components === null) {
4748
- for (let pi = 0; pi < count; pi++) {
4749
- const internalId = docIds[pi];
4750
- const fieldIndex = fieldNameIndices[pi];
4751
- if (searchable[fieldIndex] === 0) continue;
4752
- const avgLen = averageLengths[fieldIndex];
4753
- const actualFieldLength = fieldLengthOf(lengthColumns, fieldIndex, internalId, avgLen);
4754
- const termScore = scoresAreZero ? 0 : computeBM25WithIDF(termFrequencies[pi], idf, actualFieldLength, avgLen, k1, b);
4755
- addScore(scoreBuffer, internalId, termScore * boosts[fieldIndex]);
4756
- }
4757
- continue;
4758
- }
4759
- for (let pi = 0; pi < count; pi++) {
4760
- const internalId = docIds[pi];
4761
- if (hasDeleted && deletedDocs.has(internalId)) continue;
4762
- if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
4763
- const fieldIndex = fieldNameIndices[pi];
4764
- if (searchable[fieldIndex] === 0) continue;
4765
- const termFrequency = termFrequencies[pi];
4766
- const fieldBoost = boosts[fieldIndex];
4767
- const avgLen = averageLengths[fieldIndex];
4768
- const actualFieldLength = fieldLengthOf(lengthColumns, fieldIndex, internalId, avgLen);
4769
- let termScore = scoresAreZero ? 0 : computeBM25WithIDF(termFrequency, idf, actualFieldLength, avgLen, k1, b);
4770
- termScore *= fieldBoost;
4771
- addScore(scoreBuffer, internalId, termScore);
4772
- if (components !== null) {
4773
- recordComponents(
4774
- components,
4775
- internalId,
4776
- fieldNames[fieldIndex],
4777
- match.token,
4778
- termFrequency,
4779
- actualFieldLength,
4780
- idf
4781
- );
4782
- }
4783
- }
4344
+ const group = termGroupOf(termGroups, qt.token);
4345
+ for (const m of typoMatchesFor(state.invertedIdx, qt.token, group, exact, tolerance, prefixLength)) {
4346
+ scorePostings(resolveMatch(m.token, m.postingList), false);
4784
4347
  }
4348
+ if (group !== void 0) scoreGroup(resolveGroupMatches(groupContext, group), false);
4785
4349
  }
4786
4350
  }
4787
4351
  const totalMatched = scoreBuffer.touchedCount;
@@ -4931,7 +4495,7 @@ function encodeSegmentState(state) {
4931
4495
  const postingOffsets = new Uint32Array(state.invertedIdx.size() + 1);
4932
4496
  const postingDocIds = new Uint32Array(postings);
4933
4497
  const postingFrequencies = new Uint16Array(postings);
4934
- const postingFieldIndices = new Uint8Array(postings);
4498
+ const postingFieldIndices = new Uint16Array(postings);
4935
4499
  const positionOffsets = hasPositions ? new Uint32Array(postings + 1) : null;
4936
4500
  const positionValues = hasPositions ? new Uint32Array(positions) : null;
4937
4501
  let postingCursor = 0;
@@ -4990,6 +4554,7 @@ function encodeSegmentState(state) {
4990
4554
  totalFieldLengths: fieldLengths.totals,
4991
4555
  docFrequencies,
4992
4556
  surfaceForms: state.surfaceRegistry.size() === 0 ? null : state.surfaceRegistry.serialize(),
4557
+ partialSpellings: state.partialWords.spellings(),
4993
4558
  ...encodeFieldIndexes(state, remap)
4994
4559
  };
4995
4560
  }
@@ -5108,6 +4673,8 @@ function mergeSegmentPayload(target, payload, documents) {
5108
4673
  target.surfaceRegistry.add(surface, value[1], value[0]);
5109
4674
  }
5110
4675
  }
4676
+ const mergedIds = Array.from({ length: payload.docIds.length }, (_, at) => ordinalBase + at);
4677
+ target.partialWords.afterMerge(payload.partialSpellings, mergedIds);
5111
4678
  target.sortColumns = null;
5112
4679
  target.scoreBuffer = null;
5113
4680
  }
@@ -5397,179 +4964,7 @@ function deserializePartition(state, data, clearFn, schema) {
5397
4964
  }
5398
4965
  loadPatternIndexes(state, data.fieldIndexes.pattern, schema);
5399
4966
  rebuildSizeIndexes(state, schema);
5400
- }
5401
-
5402
- // src/core/partition/suggestions.ts
5403
- function verbatimOccurrences(state, token) {
5404
- const postingList = state.invertedIdx.lookup(token);
5405
- if (!postingList) return 0;
5406
- const derived = postingList.totalTermFrequency - state.surfaceRegistry.stemChangedTotalFor(token);
5407
- return derived > 0 ? derived : 0;
5408
- }
5409
- function suggestDisplayTerms(state, surfacePrefix, stemmedPrefix, limit) {
5410
- if (limit <= 0 || surfacePrefix.length === 0) return [];
5411
- const groups = /* @__PURE__ */ new Map();
5412
- for (const candidate of state.surfaceRegistry.candidatesForPrefix(surfacePrefix)) {
5413
- const postingList = state.invertedIdx.lookup(candidate.token);
5414
- if (!postingList || postingList.docIdSet.size === 0) continue;
5415
- let group = groups.get(candidate.token);
5416
- if (!group) {
5417
- group = { documentFrequency: postingList.docIdSet.size, surfaces: [] };
5418
- groups.set(candidate.token, group);
5419
- }
5420
- group.surfaces.push({ surface: candidate.surface, occurrences: candidate.occurrences });
5421
- }
5422
- const termPrefixes = state.surfaceRegistry.size() === 0 && stemmedPrefix !== surfacePrefix && stemmedPrefix.length > 0 ? [surfacePrefix, stemmedPrefix] : [surfacePrefix];
5423
- for (const prefix of termPrefixes) {
5424
- for (const suggestion of state.invertedIdx.prefixSearch(prefix, Number.MAX_SAFE_INTEGER)) {
5425
- const occurrences = verbatimOccurrences(state, suggestion.term);
5426
- if (occurrences === 0) continue;
5427
- let group = groups.get(suggestion.term);
5428
- if (!group) {
5429
- group = { documentFrequency: suggestion.documentFrequency, surfaces: [] };
5430
- groups.set(suggestion.term, group);
5431
- } else if (group.surfaces.some((s) => s.surface === suggestion.term)) {
5432
- continue;
5433
- }
5434
- group.surfaces.push({ surface: suggestion.term, occurrences });
5435
- }
5436
- }
5437
- const results = [];
5438
- for (const [token, group] of groups) {
5439
- results.push({ token, documentFrequency: group.documentFrequency, surfaces: group.surfaces });
5440
- }
5441
- results.sort((a, b) => b.documentFrequency - a.documentFrequency || compareCodePoints(a.token, b.token));
5442
- if (results.length > limit) results.length = limit;
5443
- return results;
5444
- }
5445
- function expandTermPrefix(state, surfacePrefix, stemmedToken, maxExpansions) {
5446
- if (maxExpansions <= 0 || surfacePrefix.length === 0) return [];
5447
- const dfByToken = /* @__PURE__ */ new Map();
5448
- for (const candidate of state.surfaceRegistry.candidatesForPrefix(surfacePrefix)) {
5449
- if (dfByToken.has(candidate.token)) continue;
5450
- const postingList = state.invertedIdx.lookup(candidate.token);
5451
- if (!postingList || postingList.docIdSet.size === 0) continue;
5452
- dfByToken.set(candidate.token, postingList.docIdSet.size);
5453
- }
5454
- const termPrefixes = state.surfaceRegistry.size() === 0 && stemmedToken !== surfacePrefix && stemmedToken.length > 0 ? [surfacePrefix, stemmedToken] : [surfacePrefix];
5455
- for (const prefix of termPrefixes) {
5456
- for (const suggestion of state.invertedIdx.prefixSearch(prefix, Number.MAX_SAFE_INTEGER)) {
5457
- if (dfByToken.has(suggestion.term)) continue;
5458
- if (verbatimOccurrences(state, suggestion.term) === 0) continue;
5459
- dfByToken.set(suggestion.term, suggestion.documentFrequency);
5460
- }
5461
- }
5462
- const entries = Array.from(dfByToken.entries());
5463
- entries.sort((a, b) => b[1] - a[1] || compareCodePoints(a[0], b[0]));
5464
- if (entries.length > maxExpansions) entries.length = maxExpansions;
5465
- return entries.map((e) => e[0]);
5466
- }
5467
-
5468
- // src/core/partition/wire-payload.ts
5469
- function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema) {
5470
- const flatSchema = getFlatSchema(state, schema);
5471
- const flatSchemaStrings = {};
5472
- for (const [k, v] of Object.entries(flatSchema)) {
5473
- flatSchemaStrings[k] = v;
5474
- }
5475
- const wireDocs = /* @__PURE__ */ Object.create(null);
5476
- for (const [docId, stored] of state.docStore.all()) {
5477
- wireDocs[docId] = {
5478
- fields: stored.fields,
5479
- field_lengths: { ...stored.fieldLengths }
5480
- };
5481
- }
5482
- const resolver = state.docStore.resolver();
5483
- const fieldNames = [...state.fieldNameTable.names];
5484
- const wireEntries = /* @__PURE__ */ Object.create(null);
5485
- for (const token of state.invertedIdx.tokens()) {
5486
- const list = state.invertedIdx.lookup(token);
5487
- if (!list) continue;
5488
- const hasDeleted = list.deletedDocs.size > 0;
5489
- const ids = [];
5490
- const tf = [];
5491
- const fi = new Uint8Array(list.length);
5492
- const pos = list.positions ? [] : null;
5493
- let writeIdx = 0;
5494
- for (let i = 0; i < list.length; i++) {
5495
- if (hasDeleted && list.deletedDocs.has(list.docIds[i])) continue;
5496
- const externalId = resolver.toExternal(list.docIds[i]);
5497
- if (externalId === void 0) continue;
5498
- ids.push(externalId);
5499
- tf.push(list.termFrequencies[i]);
5500
- fi[writeIdx] = list.fieldNameIndices[i];
5501
- writeIdx++;
5502
- if (pos && list.positions) {
5503
- pos.push(list.positions[i]);
5504
- }
5505
- }
5506
- if (ids.length > 0) {
5507
- const finalFi = writeIdx < list.length ? fi.subarray(0, writeIdx) : fi;
5508
- wireEntries[token] = { df: list.docIdSet.size, ids, tf, fi: finalFi, pos };
5509
- }
5510
- }
5511
- const wireNumeric = {};
5512
- for (const [path, idx] of state.numericIndexes) {
5513
- wireNumeric[path] = idx.serialize().map((e) => ({
5514
- value: e.value,
5515
- doc_id: resolver.toExternal(e.docId) ?? ""
5516
- }));
5517
- }
5518
- const wireBoolean = {};
5519
- for (const [path, idx] of state.booleanIndexes) {
5520
- const s = idx.serialize();
5521
- wireBoolean[path] = {
5522
- true_docs: s.trueDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== ""),
5523
- false_docs: s.falseDocs.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "")
5524
- };
5525
- }
5526
- const wireEnum = {};
5527
- for (const [path, idx] of state.enumIndexes) {
5528
- const raw = idx.serialize();
5529
- const converted = /* @__PURE__ */ Object.create(null);
5530
- for (const [value, internalIds] of Object.entries(raw)) {
5531
- converted[value] = internalIds.map((id) => resolver.toExternal(id) ?? "").filter((id) => id !== "");
5532
- }
5533
- wireEnum[path] = converted;
5534
- }
5535
- const wireGeo = {};
5536
- for (const [path, idx] of state.geoIndexes) {
5537
- wireGeo[path] = idx.serialize().map((e) => ({
5538
- lat: e.lat,
5539
- lon: e.lon,
5540
- doc_id: resolver.toExternal(e.docId) ?? ""
5541
- }));
5542
- }
5543
- const serializedStats = state.stats.serialize();
5544
- return {
5545
- v: 2,
5546
- index_name: indexName,
5547
- partition_id: partitionId,
5548
- total_partitions: totalPartitions,
5549
- language,
5550
- schema: flatSchemaStrings,
5551
- doc_count: state.docStore.count(),
5552
- avg_doc_length: Object.values(serializedStats.averageFieldLengths).reduce((sum, v) => sum + v, 0),
5553
- documents: wireDocs,
5554
- inverted_index: {
5555
- field_names: fieldNames,
5556
- entries: wireEntries
5557
- },
5558
- field_indexes: {
5559
- numeric: wireNumeric,
5560
- boolean: wireBoolean,
5561
- enum: wireEnum,
5562
- geopoint: wireGeo,
5563
- ...patternIndexesToWire(serializePatternIndexes(state, schema))
5564
- },
5565
- surface_forms: state.surfaceRegistry.serialize(),
5566
- statistics: {
5567
- total_documents: serializedStats.totalDocuments,
5568
- total_field_lengths: serializedStats.totalFieldLengths,
5569
- average_field_lengths: serializedStats.averageFieldLengths,
5570
- doc_frequencies: serializedStats.docFrequencies
5571
- }
5572
- };
4967
+ state.partialWords.forgetSpellings();
5573
4968
  }
5574
4969
 
5575
4970
  // src/core/partition/index.ts
@@ -5586,9 +4981,10 @@ function partitionStateOf(partition) {
5586
4981
  }
5587
4982
  function createPartitionIndex(partitionId, trackPositions = true) {
5588
4983
  const fieldNameTable = { names: [], indexMap: /* @__PURE__ */ new Map() };
4984
+ const docStore = createDocumentStore();
5589
4985
  const state = {
5590
4986
  invertedIdx: createInvertedIndex(fieldNameTable),
5591
- docStore: createDocumentStore(),
4987
+ docStore,
5592
4988
  stats: createPartitionStats(),
5593
4989
  surfaceRegistry: createSurfaceRegistry(),
5594
4990
  numericIndexes: /* @__PURE__ */ new Map(),
@@ -5598,6 +4994,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5598
4994
  patternIndexes: /* @__PURE__ */ new Map(),
5599
4995
  sizeIndexes: /* @__PURE__ */ new Map(),
5600
4996
  fieldNameTable,
4997
+ partialWords: createGrowingPartialWords({ docStore }),
5601
4998
  flatSchemaCache: null,
5602
4999
  lastSchemaRef: null,
5603
5000
  trackPositions,
@@ -5618,6 +5015,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5618
5015
  state.geoIndexes.clear();
5619
5016
  state.patternIndexes.clear();
5620
5017
  state.sizeIndexes.clear();
5018
+ state.partialWords.clear();
5621
5019
  state.stats.deserialize({ totalDocuments: 0, totalFieldLengths: {}, averageFieldLengths: {}, docFrequencies: {} });
5622
5020
  state.flatSchemaCache = null;
5623
5021
  state.lastSchemaRef = null;
@@ -5646,7 +5044,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5646
5044
  }
5647
5045
  }
5648
5046
  const internalId = state.docStore.ensureInternalId(docId);
5649
- const { fieldLengths, tokensByField } = indexDocument(
5047
+ const { fieldLengths, tokensByField, partialSpellings } = indexDocument(
5650
5048
  state,
5651
5049
  docId,
5652
5050
  document,
@@ -5660,6 +5058,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5660
5058
  state.docStore.store(docId, document, fieldLengths);
5661
5059
  }
5662
5060
  state.stats.addDocument(fieldLengths, tokensByField);
5061
+ state.partialWords.afterDocument(partialFieldsOf(schema), internalId, partialSpellings, 1);
5663
5062
  recordSortValues(state, internalId, document);
5664
5063
  },
5665
5064
  remove(docId, schema, language, options) {
@@ -5670,7 +5069,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5670
5069
  partitionId
5671
5070
  });
5672
5071
  }
5673
- const { fieldLengths, tokensByField } = removeFromIndexes(
5072
+ const { fieldLengths, tokensByField, partialSpellings } = removeFromIndexes(
5674
5073
  state,
5675
5074
  docId,
5676
5075
  stored,
@@ -5681,6 +5080,9 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5681
5080
  const internalId = state.docStore.getInternalId(docId);
5682
5081
  state.docStore.remove(docId);
5683
5082
  state.stats.removeDocument(fieldLengths, tokensByField);
5083
+ if (internalId !== void 0) {
5084
+ state.partialWords.afterDocument(partialFieldsOf(schema), internalId, partialSpellings, -1);
5085
+ }
5684
5086
  forgetSortValues(state, internalId);
5685
5087
  },
5686
5088
  beginBatch() {
@@ -5716,29 +5118,19 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5716
5118
  const fields = schemaFieldsOf(schema);
5717
5119
  const needsTextReindex = textFieldsChanged(stored.fields, document, fields);
5718
5120
  if (needsTextReindex) {
5719
- const { fieldLengths: oldFieldLengths, tokensByField: oldTokens } = removeFromIndexes(
5720
- state,
5721
- docId,
5722
- stored,
5723
- fields,
5724
- language,
5725
- options
5726
- );
5727
- state.stats.removeDocument(oldFieldLengths, oldTokens);
5121
+ const previous = removeFromIndexes(state, docId, stored, fields, language, options);
5122
+ state.stats.removeDocument(previous.fieldLengths, previous.tokensByField);
5728
5123
  const previousInternalId = state.docStore.getInternalId(docId);
5124
+ if (previousInternalId !== void 0) {
5125
+ state.partialWords.afterDocument(partialFieldsOf(schema), previousInternalId, previous.partialSpellings, -1);
5126
+ }
5729
5127
  state.docStore.remove(docId);
5730
5128
  forgetSortValues(state, previousInternalId);
5731
5129
  const internalId = state.docStore.ensureInternalId(docId);
5732
- const { fieldLengths: newFieldLengths, tokensByField: newTokens } = indexDocument(
5733
- state,
5734
- docId,
5735
- document,
5736
- fields,
5737
- language,
5738
- options
5739
- );
5740
- state.docStore.store(docId, document, newFieldLengths);
5741
- state.stats.addDocument(newFieldLengths, newTokens);
5130
+ const next = indexDocument(state, docId, document, fields, language, options);
5131
+ state.docStore.store(docId, document, next.fieldLengths);
5132
+ state.stats.addDocument(next.fieldLengths, next.tokensByField);
5133
+ state.partialWords.afterDocument(partialFieldsOf(schema), internalId, next.partialSpellings, 1);
5742
5134
  recordSortValues(state, internalId, document);
5743
5135
  } else {
5744
5136
  updateFieldIndexOnly(state, docId, stored.fields, document, fields);
@@ -5809,15 +5201,17 @@ function createPartitionIndex(partitionId, trackPositions = true) {
5809
5201
  return suggestDisplayTerms(state, surfacePrefix, stemmedPrefix, limit);
5810
5202
  },
5811
5203
  expandTermPrefix(surfacePrefix, stemmedToken, maxExpansions) {
5812
- return expandTermPrefix(state, surfacePrefix, stemmedToken, maxExpansions);
5204
+ return expandTermPrefix([state], surfacePrefix, stemmedToken, maxExpansions);
5205
+ },
5206
+ expandPartialWords(analysis, requests) {
5207
+ return expandPartialWords([state], analysis, requests);
5208
+ },
5209
+ preparePartialWords(analysis) {
5210
+ expandPartialWords([state], analysis, []);
5813
5211
  },
5814
5212
  serialize(indexName, totalPartitions, language, schema) {
5815
5213
  return serializePartition(state, partitionId, indexName, totalPartitions, language, schema);
5816
5214
  },
5817
- serializeToBytes(indexName, totalPartitions, language, schema) {
5818
- const wire = serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema);
5819
- return encodeRawPayloadV2(wire);
5820
- },
5821
5215
  deserialize(data, schema) {
5822
5216
  deserializePartition(state, data, clearAll, schema);
5823
5217
  }
@@ -5847,7 +5241,7 @@ function frozenSegmentBytes(segment) {
5847
5241
  bytes += entry.docIds.byteLength + entry.latitudes.byteLength + entry.longitudes.byteLength;
5848
5242
  }
5849
5243
  for (const reader of segment.patternIndexes.values()) bytes += reader.bytes();
5850
- return bytes;
5244
+ return bytes + segment.partialWords.bytes();
5851
5245
  }
5852
5246
 
5853
5247
  // src/core/partition/composite/compaction.ts
@@ -5982,20 +5376,19 @@ function subMatchesView(matches, layout, subIndex) {
5982
5376
 
5983
5377
  // src/core/partition/composite/stats.ts
5984
5378
  function collectCandidateTerms(subs, params) {
5985
- const { queryTokens, prefixExpansion, tolerance = 0, prefixLength = 2, exact = false } = params;
5379
+ const { queryTokens, termGroups, tolerance = 0, prefixLength = 2, exact = false } = params;
5986
5380
  const terms = /* @__PURE__ */ new Set();
5987
5381
  for (const qt of queryTokens) {
5988
- if (prefixExpansion && qt.token === prefixExpansion.token) {
5989
- terms.add(qt.token);
5990
- for (const term of prefixExpansion.terms) terms.add(term);
5991
- continue;
5382
+ const group = termGroupOf(termGroups, qt.token);
5383
+ if (group !== void 0) {
5384
+ for (const member of group.members) terms.add(member.token);
5992
5385
  }
5993
- if (exact || tolerance === 0) {
5386
+ if (group === void 0 && (exact || tolerance === 0)) {
5994
5387
  terms.add(qt.token);
5995
5388
  continue;
5996
5389
  }
5997
5390
  for (const sub of subs) {
5998
- for (const match of sub.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength)) {
5391
+ for (const match of typoMatchesFor(sub.invertedIdx, qt.token, group, exact, tolerance, prefixLength)) {
5999
5392
  terms.add(match.token);
6000
5393
  }
6001
5394
  }
@@ -6005,11 +5398,7 @@ function collectCandidateTerms(subs, params) {
6005
5398
  function resolveQueryTermFrequencies(subs, params) {
6006
5399
  const frequencies = /* @__PURE__ */ Object.create(null);
6007
5400
  for (const term of collectCandidateTerms(subs, params)) {
6008
- let total = 0;
6009
- for (const sub of subs) {
6010
- const list = sub.invertedIdx.lookup(term);
6011
- if (list !== void 0) total += list.docIdSet.size;
6012
- }
5401
+ const total = partitionDocumentFrequency(subs, term);
6013
5402
  if (total > 0) frequencies[term] = total;
6014
5403
  }
6015
5404
  return frequencies;
@@ -6195,9 +5584,10 @@ function compositeSortValues(subs, docId, fields, fieldTypes, modes) {
6195
5584
 
6196
5585
  // src/core/partition/composite/suggest.ts
6197
5586
  function compositeSuggestTerms(subs, surfacePrefix, stemmedPrefix, limit) {
5587
+ if (limit <= 0) return [];
6198
5588
  const merged = /* @__PURE__ */ new Map();
6199
5589
  for (const sub of subs) {
6200
- for (const suggestion of suggestDisplayTerms(sub, surfacePrefix, stemmedPrefix, limit)) {
5590
+ for (const suggestion of suggestDisplayTerms(sub, surfacePrefix, stemmedPrefix, Number.MAX_SAFE_INTEGER)) {
6201
5591
  let entry = merged.get(suggestion.token);
6202
5592
  if (entry === void 0) {
6203
5593
  entry = { documentFrequency: 0, surfaceOccurrences: /* @__PURE__ */ new Map() };
@@ -6222,19 +5612,6 @@ function compositeSuggestTerms(subs, surfacePrefix, stemmedPrefix, limit) {
6222
5612
  if (suggestions.length > limit) suggestions.length = limit;
6223
5613
  return suggestions;
6224
5614
  }
6225
- function compositeExpandTermPrefix(subs, surfacePrefix, stemmedToken, maxExpansions) {
6226
- const expansions = [];
6227
- const seen = /* @__PURE__ */ new Set();
6228
- for (const sub of subs) {
6229
- for (const term of expandTermPrefix(sub, surfacePrefix, stemmedToken, maxExpansions)) {
6230
- if (seen.has(term)) continue;
6231
- seen.add(term);
6232
- expansions.push(term);
6233
- if (expansions.length === maxExpansions) return expansions;
6234
- }
6235
- }
6236
- return expansions;
6237
- }
6238
5615
 
6239
5616
  // src/core/partition/composite/index.ts
6240
5617
  function isCompositePartition(partition) {
@@ -6340,10 +5717,6 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
6340
5717
  if (frozen.length === 0) return live.serialize(indexName, totalPartitions, language, schema);
6341
5718
  return composedScratch().serialize(indexName, totalPartitions, language, schema);
6342
5719
  },
6343
- serializeToBytes(indexName, totalPartitions, language, schema) {
6344
- if (frozen.length === 0) return live.serializeToBytes(indexName, totalPartitions, language, schema);
6345
- return composedScratch().serializeToBytes(indexName, totalPartitions, language, schema);
6346
- },
6347
5720
  deserialize(data, schema) {
6348
5721
  frozen.length = 0;
6349
5722
  invalidateDocFrequencies();
@@ -6442,7 +5815,13 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
6442
5815
  return compositeSuggestTerms(subs(), surfacePrefix, stemmedPrefix, limit);
6443
5816
  },
6444
5817
  expandTermPrefix(surfacePrefix, stemmedToken, maxExpansions) {
6445
- return compositeExpandTermPrefix(subs(), surfacePrefix, stemmedToken, maxExpansions);
5818
+ return expandTermPrefix(subs(), surfacePrefix, stemmedToken, maxExpansions);
5819
+ },
5820
+ expandPartialWords(analysis, requests) {
5821
+ return expandPartialWords(subs(), analysis, requests);
5822
+ },
5823
+ preparePartialWords(analysis) {
5824
+ expandPartialWords(subs(), analysis, []);
6446
5825
  },
6447
5826
  get(docId, projection) {
6448
5827
  const owner = frozenOwner(docId);
@@ -6503,6 +5882,11 @@ function createCompositePartition(partitionId, trackPositions = true, existingLi
6503
5882
  }
6504
5883
 
6505
5884
  // src/partitioning/insert-options.ts
5885
+ function resolvePartialWordAnalysis(config, language, analysis) {
5886
+ const fields = partialFieldsOf(config.schema);
5887
+ if (fields.length === 0) return null;
5888
+ return { fields, language, options: resolvePartitionInsertOptions(config, analysis) };
5889
+ }
6506
5890
  function resolvePartitionInsertOptions(config, analysis, options) {
6507
5891
  const applyStrict = config.strict === true;
6508
5892
  const applyAnalyzer = analysis.stopWords !== void 0 || analysis.customTokenizer !== void 0;
@@ -6561,8 +5945,10 @@ function createCapacityChecks(source) {
6561
5945
  // src/partitioning/manager/index.ts
6562
5946
  function createPartitionManager(indexName, config, language, router, initialPartitionCount, vectorIndexes) {
6563
5947
  requirePatternSearchFor(config.schema);
5948
+ requirePartialWordSearchFor(config.schema);
6564
5949
  const count = initialPartitionCount ?? 1;
6565
5950
  const analysis = resolveIndexAnalysis(config);
5951
+ const partialWordAnalysis = resolvePartialWordAnalysis(config, language, analysis);
6566
5952
  const trackPositions = config.trackPositions ?? true;
6567
5953
  const vecIndexes = vectorIndexes ?? /* @__PURE__ */ new Map();
6568
5954
  let partitions = [];
@@ -6582,6 +5968,9 @@ function createPartitionManager(indexName, config, language, router, initialPart
6582
5968
  function resolveInsertOptions(options) {
6583
5969
  return resolvePartitionInsertOptions(config, analysis, options);
6584
5970
  }
5971
+ function preparePartialWords(partitionId) {
5972
+ if (partialWordAnalysis !== null) partitions[partitionId].preparePartialWords(partialWordAnalysis);
5973
+ }
6585
5974
  function locateDocument(docId) {
6586
5975
  return docPartitionMap.get(docId);
6587
5976
  }
@@ -6637,6 +6026,9 @@ function createPartitionManager(indexName, config, language, router, initialPart
6637
6026
  get analysis() {
6638
6027
  return analysis;
6639
6028
  },
6029
+ get partialWordAnalysis() {
6030
+ return partialWordAnalysis;
6031
+ },
6640
6032
  getPartition(partitionId) {
6641
6033
  validatePartitionId(partitionId);
6642
6034
  return partitions[partitionId];
@@ -6713,6 +6105,7 @@ function createPartitionManager(indexName, config, language, router, initialPart
6713
6105
  rebuildTextIndex(partitionId) {
6714
6106
  validatePartitionId(partitionId);
6715
6107
  partitions[partitionId].rebuildTextIndex(config.schema, language, resolveInsertOptions());
6108
+ preparePartialWords(partitionId);
6716
6109
  },
6717
6110
  beginBatchRemove() {
6718
6111
  for (let i = 0; i < partitions.length; i++) {
@@ -6774,10 +6167,6 @@ function createPartitionManager(indexName, config, language, router, initialPart
6774
6167
  validatePartitionId(partitionId);
6775
6168
  return partitions[partitionId].serialize(indexName, partitions.length, language.name, config.schema);
6776
6169
  },
6777
- serializePartitionToBytes(partitionId) {
6778
- validatePartitionId(partitionId);
6779
- return partitions[partitionId].serializeToBytes(indexName, partitions.length, language.name, config.schema);
6780
- },
6781
6170
  mergeSegment(partitionId, payload, documents) {
6782
6171
  validatePartitionId(partitionId);
6783
6172
  const partition = partitions[partitionId];
@@ -6818,6 +6207,7 @@ function createPartitionManager(indexName, config, language, router, initialPart
6818
6207
  deserializePartition(partitionId, data) {
6819
6208
  validatePartitionId(partitionId);
6820
6209
  partitions[partitionId].deserialize(data, config.schema);
6210
+ preparePartialWords(partitionId);
6821
6211
  rebuildDocPartitionMapForPartition(partitionId);
6822
6212
  },
6823
6213
  getAggregateStats() {
@@ -6903,6 +6293,9 @@ var WORKER_COPY_MIN_VECTORS = 1024;
6903
6293
  var BUILD_CHUNK_SIZE = 100;
6904
6294
  var REBUILD_REMOVED_RATIO = 0.2;
6905
6295
  var VECTOR_FILE_REPLACED_ABOVE_DEAD_SHARE = 0.2;
6296
+ var VECTOR_FILE_FIXED_OVERHEAD_BYTES = 65536;
6297
+ var VECTOR_FILE_STRING_HEADER_BYTES = 5;
6298
+ var VECTOR_FILE_CENTROID_BYTES_PER_COMPONENT = 9;
6906
6299
  var OSQ4_MIN_DIMENSION = 384;
6907
6300
  var DISK_ADOPTION_YIELD_INTERVAL = 4096;
6908
6301
 
@@ -6962,7 +6355,7 @@ function yieldToEventLoop() {
6962
6355
  return new Promise((resolve) => setTimeout(resolve, 0));
6963
6356
  }
6964
6357
  function* allLiveDocIds(state) {
6965
- for (const [docId] of state.store.entries()) {
6358
+ for (const docId of state.store.docIds()) {
6966
6359
  if (state.tombstones.has(docId)) continue;
6967
6360
  yield docId;
6968
6361
  }
@@ -7083,14 +6476,6 @@ async function nodeThreadId() {
7083
6476
  return 0;
7084
6477
  }
7085
6478
  }
7086
- async function isNodeMainThread() {
7087
- try {
7088
- const workerThreads = await import('worker_threads');
7089
- return workerThreads.isMainThread;
7090
- } catch {
7091
- return true;
7092
- }
7093
- }
7094
6479
 
7095
6480
  // src/workers/entry-point.ts
7096
6481
  var DIST_SEGMENT = "/dist/";
@@ -7855,6 +7240,12 @@ function scheduleBuild(state) {
7855
7240
  }, 0);
7856
7241
  }
7857
7242
 
7243
+ // src/serialization/constants.ts
7244
+ var CHECKSUM_YIELD_CHUNK_BYTES = 4 * 1024 * 1024;
7245
+ var NATIVE_CRC32_MIN_BYTES = 16;
7246
+ var MAX_FILE_BYTES = 2147483647;
7247
+ var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
7248
+
7858
7249
  // src/vector/vector-index/payload.ts
7859
7250
  var VECTOR_INDEX_PAYLOAD_VERSION = 3;
7860
7251
  var VECTOR_INDEX_PART_VECTORS = 65536;
@@ -7881,7 +7272,7 @@ function invalid(message, details) {
7881
7272
  function isRecord(value) {
7882
7273
  return typeof value === "object" && value !== null && !Array.isArray(value);
7883
7274
  }
7884
- function isNonNegativeInteger2(value) {
7275
+ function isNonNegativeInteger(value) {
7885
7276
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
7886
7277
  }
7887
7278
  function isSerializedGraph(value) {
@@ -7911,9 +7302,9 @@ function decodeVectorIndexPart(raw) {
7911
7302
  );
7912
7303
  }
7913
7304
  if (typeof raw.fieldName !== "string") invalid("fieldName must be a string");
7914
- if (!isNonNegativeInteger2(raw.dimension) || raw.dimension === 0) invalid("dimension must be a positive integer");
7915
- if (!isNonNegativeInteger2(raw.part)) invalid("part must be a non-negative integer");
7916
- if (!isNonNegativeInteger2(raw.parts) || raw.parts === 0 || raw.part >= raw.parts) {
7305
+ if (!isNonNegativeInteger(raw.dimension) || raw.dimension === 0) invalid("dimension must be a positive integer");
7306
+ if (!isNonNegativeInteger(raw.part)) invalid("part must be a non-negative integer");
7307
+ if (!isNonNegativeInteger(raw.parts) || raw.parts === 0 || raw.part >= raw.parts) {
7917
7308
  invalid("parts must exceed part", { part: raw.part, parts: raw.parts });
7918
7309
  }
7919
7310
  if (!Array.isArray(raw.docIds) || !raw.docIds.every((docId) => typeof docId === "string")) {
@@ -7952,6 +7343,8 @@ function decodeVectorIndexParts(raw) {
7952
7343
  // src/vector/vector-index/checkpoint-payload.ts
7953
7344
  var VECTOR_FILE_PAYLOAD_VERSION = 1;
7954
7345
  var VECTOR_GRAPH_PAYLOAD_VERSION = 1;
7346
+ var VECTOR_GRAPH_PART_PAYLOAD_VERSION = 2;
7347
+ var VECTOR_GRAPH_PART_VECTORS = 16384;
7955
7348
  var VECTOR_FILE_MAX_VECTORS = VECTOR_INDEX_PART_VECTORS;
7956
7349
  var METRICS = ["cosine", "dotProduct", "euclidean"];
7957
7350
  function invalid2(payload, message, details) {
@@ -8002,14 +7395,16 @@ function decodeVectorFilePayload(raw) {
8002
7395
  function isMetric(value) {
8003
7396
  return METRICS.some((metric) => metric === value);
8004
7397
  }
8005
- function decodeNumberedGraph(raw) {
7398
+ function decodeNumberedGraph(raw, minimumM) {
8006
7399
  const name = "vector graph";
8007
7400
  if (!isRecord2(raw)) invalid2(name, "each graph must be a map");
8008
7401
  const entryPoint = raw.entryPoint ?? null;
8009
7402
  if (entryPoint !== null && !isIntegerUpTo(entryPoint, 4294967295))
8010
7403
  invalid2(name, "entryPoint must be a uint32 or nil");
8011
7404
  if (!isIntegerUpTo(raw.maxLayer, 255)) invalid2(name, "maxLayer must be a uint8");
8012
- if (!isIntegerUpTo(raw.m, 65535) || raw.m === 0) invalid2(name, "m must be a positive uint16");
7405
+ if (!isIntegerUpTo(raw.m, MAX_M) || raw.m < minimumM) {
7406
+ invalid2(name, `m must be a whole number from ${minimumM} to ${MAX_M}`);
7407
+ }
8013
7408
  if (!isIntegerUpTo(raw.efConstruction, 65535)) invalid2(name, "efConstruction must be a uint16");
8014
7409
  if (!isMetric(raw.metric)) invalid2(name, "metric must name a supported metric", { metric: raw.metric });
8015
7410
  if (!(raw.levels instanceof Uint8Array)) invalid2(name, "levels must be bytes");
@@ -8030,7 +7425,56 @@ function decodeVectorGraphPayload(raw) {
8030
7425
  requireVersion(name, raw.v, VECTOR_GRAPH_PAYLOAD_VERSION);
8031
7426
  if (typeof raw.fieldName !== "string") invalid2(name, "fieldName must be a string");
8032
7427
  if (!Array.isArray(raw.graphs)) invalid2(name, "graphs must be a list");
8033
- return { v: VECTOR_GRAPH_PAYLOAD_VERSION, fieldName: raw.fieldName, graphs: raw.graphs.map(decodeNumberedGraph) };
7428
+ return {
7429
+ v: VECTOR_GRAPH_PAYLOAD_VERSION,
7430
+ fieldName: raw.fieldName,
7431
+ graphs: raw.graphs.map((graph) => decodeNumberedGraph(graph, EARLIER_RELEASE_MIN_M))
7432
+ };
7433
+ }
7434
+ function decodeVectorGraphPartPayload(raw) {
7435
+ const name = "vector graph part";
7436
+ if (!isRecord2(raw)) invalid2(name, "payload must be a map");
7437
+ requireVersion(name, raw.v, VECTOR_GRAPH_PART_PAYLOAD_VERSION);
7438
+ if (typeof raw.fieldName !== "string") invalid2(name, "fieldName must be a string");
7439
+ if (!Array.isArray(raw.graphs)) invalid2(name, "graphs must be a list");
7440
+ return {
7441
+ v: VECTOR_GRAPH_PART_PAYLOAD_VERSION,
7442
+ fieldName: raw.fieldName,
7443
+ graphs: raw.graphs.map((graph) => decodeNumberedGraph(graph, MIN_M))
7444
+ };
7445
+ }
7446
+ function sameGraphHeader(a, b) {
7447
+ return a.entryPoint === b.entryPoint && a.maxLayer === b.maxLayer && a.m === b.m && a.efConstruction === b.efConstruction && a.metric === b.metric;
7448
+ }
7449
+ function assembleGraphParts(parts) {
7450
+ const name = "vector graph part";
7451
+ const first = parts[0];
7452
+ if (first === void 0) invalid2(name, "a field that holds a graph must have at least one part");
7453
+ const partLists = first.graphs.map(() => []);
7454
+ for (let p = 0; p < parts.length; p++) {
7455
+ const part = parts[p];
7456
+ const isLast = p === parts.length - 1;
7457
+ if (part.fieldName !== first.fieldName || part.graphs.length !== first.graphs.length) {
7458
+ invalid2(name, `part ${p} lists other graphs than part 0`, { part: p });
7459
+ }
7460
+ for (let g = 0; g < part.graphs.length; g++) {
7461
+ const graph = part.graphs[g];
7462
+ if (!sameGraphHeader(graph, first.graphs[g]))
7463
+ invalid2(name, `part ${p} gives graph ${g} other headers`, { part: p });
7464
+ const levels = graph.levels.length;
7465
+ const fits = isLast ? levels >= 1 && levels <= VECTOR_GRAPH_PART_VECTORS : levels === VECTOR_GRAPH_PART_VECTORS;
7466
+ if (!fits) invalid2(name, `part ${p} gives graph ${g} ${levels} bytes of levels`, { part: p, levels });
7467
+ partLists[g].push(graph);
7468
+ }
7469
+ }
7470
+ const graphs = partLists.map((list) => ({ partSize: VECTOR_GRAPH_PART_VECTORS, parts: list }));
7471
+ return { fieldName: first.fieldName, graphs };
7472
+ }
7473
+ function graphPartsOfEarlierPayload(payload) {
7474
+ return {
7475
+ fieldName: payload.fieldName,
7476
+ graphs: payload.graphs.map((graph) => ({ partSize: Math.max(graph.levels.length, 1), parts: [graph] }))
7477
+ };
8034
7478
  }
8035
7479
 
8036
7480
  // src/vector/vector-index/dead-bits.ts
@@ -8060,36 +7504,6 @@ function deadBitsWhere(count, isDeadAt) {
8060
7504
  }
8061
7505
 
8062
7506
  // src/vector/vector-index/persistence.ts
8063
- function liveDocIds(state) {
8064
- const docIds = [];
8065
- for (let ordinal = 0; ordinal < state.store.slots; ordinal++) {
8066
- const docId = state.store.docIdForOrdinal(ordinal);
8067
- if (docId === void 0 || state.tombstones.has(docId)) continue;
8068
- docIds.push(docId);
8069
- }
8070
- return docIds;
8071
- }
8072
- function partVectors(state, docIds) {
8073
- const dimension = state.dimension;
8074
- const vectors = new Float32Array(docIds.length * dimension);
8075
- for (let i = 0; i < docIds.length; i++) {
8076
- const entry = state.store.get(docIds[i]);
8077
- if (entry !== void 0) vectors.set(entry.vector, i * dimension);
8078
- }
8079
- return vectors;
8080
- }
8081
- function graphsPerPart(state, partOf, parts) {
8082
- const sliced = Array.from({ length: parts }, () => []);
8083
- if (state.hnsw === null || partOf.size === 0) return sliced;
8084
- const graph = state.hnsw.serialize();
8085
- const nodesPerPart = Array.from({ length: parts }, () => []);
8086
- for (const node of graph.nodes) {
8087
- const part = partOf.get(node[0]);
8088
- if (part !== void 0) nodesPerPart[part].push(node);
8089
- }
8090
- for (let part = 0; part < parts; part++) sliced[part].push({ ...graph, nodes: nodesPerPart[part] });
8091
- return sliced;
8092
- }
8093
7507
  function recordFor(state, quantizer, docId) {
8094
7508
  const record = quantizer.recordOf(docId);
8095
7509
  if (record !== void 0) return record;
@@ -8128,36 +7542,6 @@ function partCodes(state, docIds, centroid) {
8128
7542
  }
8129
7543
  return { bits: quantizer.bits, centroid, records };
8130
7544
  }
8131
- function planParts(state) {
8132
- const docIds = liveDocIds(state);
8133
- const parts = Math.max(1, Math.ceil(docIds.length / VECTOR_INDEX_PART_VECTORS));
8134
- const partOf = /* @__PURE__ */ new Map();
8135
- for (let i = 0; i < docIds.length; i++) partOf.set(docIds[i], Math.floor(i / VECTOR_INDEX_PART_VECTORS));
8136
- const graphs = graphsPerPart(state, partOf, parts);
8137
- const centroid = codedCentroid(state);
8138
- return {
8139
- parts,
8140
- readPart(part) {
8141
- const start = part * VECTOR_INDEX_PART_VECTORS;
8142
- const slice = docIds.slice(start, start + VECTOR_INDEX_PART_VECTORS);
8143
- return {
8144
- v: VECTOR_INDEX_PAYLOAD_VERSION,
8145
- fieldName: state.fieldName,
8146
- dimension: state.dimension,
8147
- part,
8148
- parts,
8149
- docIds: slice,
8150
- graphs: graphs[part],
8151
- codes: partCodes(state, slice, centroid),
8152
- vectors: vectorsToBytes(partVectors(state, slice))
8153
- };
8154
- }
8155
- };
8156
- }
8157
- function serialize(state) {
8158
- const plan = planParts(state);
8159
- return Array.from({ length: plan.parts }, (_, part) => plan.readPart(part));
8160
- }
8161
7545
  function splitSequences(parts) {
8162
7546
  const sequences = [];
8163
7547
  let current = null;
@@ -8210,7 +7594,7 @@ function restoreCodes(state, sequences) {
8210
7594
  }
8211
7595
  function restoreGraphs(state, graphs) {
8212
7596
  if (graphs.length === 0) {
8213
- for (const [docId] of state.store.entries()) {
7597
+ for (const docId of state.store.docIds()) {
8214
7598
  if (!state.tombstones.has(docId)) state.buffer.add(docId);
8215
7599
  }
8216
7600
  return;
@@ -8233,14 +7617,14 @@ function restoreGraphs(state, graphs) {
8233
7617
  if (!restoredHnsw.has(nodeDocId) && state.store.has(nodeDocId)) restoredHnsw.insertNode(nodeDocId);
8234
7618
  }
8235
7619
  }
8236
- for (const [docId] of state.store.entries()) {
7620
+ for (const docId of state.store.docIds()) {
8237
7621
  if (state.tombstones.has(docId)) continue;
8238
7622
  if (!restoredHnsw.has(docId)) state.buffer.add(docId);
8239
7623
  }
8240
7624
  if (state.buffer.size > restoredHnsw.size) {
8241
7625
  adoptGraph(state, null);
8242
7626
  state.buffer.clear();
8243
- for (const [docId] of state.store.entries()) {
7627
+ for (const docId of state.store.docIds()) {
8244
7628
  if (!state.tombstones.has(docId)) state.buffer.add(docId);
8245
7629
  }
8246
7630
  }
@@ -8279,6 +7663,17 @@ function deserialize(state, parts) {
8279
7663
 
8280
7664
  // src/vector/vector-index/checkpoint-plan.ts
8281
7665
  var ORDINAL_HOLDS_NO_NUMBER = -1;
7666
+ function vectorsPerFile(dimension, codeBits) {
7667
+ const recordBytes = codeBits === 1 || codeBits === 2 || codeBits === 4 || codeBits === 8 ? osqRecordBytes(dimension, codeBits) : 0;
7668
+ const docIdBytes = VECTOR_FILE_STRING_HEADER_BYTES + MAX_DOC_ID_LENGTH * 3;
7669
+ const perVector = dimension * 4 + recordBytes + docIdBytes;
7670
+ const overhead = VECTOR_FILE_FIXED_OVERHEAD_BYTES + dimension * VECTOR_FILE_CENTROID_BYTES_PER_COMPONENT;
7671
+ const fitting = Math.floor((MAX_FILE_BYTES - overhead) / perVector);
7672
+ return Math.max(1, Math.min(VECTOR_FILE_MAX_VECTORS, fitting));
7673
+ }
7674
+ function fileCapacityOf(state) {
7675
+ return vectorsPerFile(state.dimension, osqBitsOf(state.quantizationMode));
7676
+ }
8282
7677
  function calibrationOf(state) {
8283
7678
  if (codedCentroid(state) === null) return null;
8284
7679
  return Atomics.load(state.store.handles.header, STORE_CALIBRATION_GENERATION);
@@ -8318,10 +7713,10 @@ function filesWorthKeeping(state, listed, calibration, liveCounts) {
8318
7713
  }
8319
7714
  return kept;
8320
7715
  }
8321
- function absorbSmallerPartialFiles(kept, liveCounts, vectorsToWrite) {
7716
+ function absorbSmallerPartialFiles(kept, liveCounts, vectorsToWrite, capacity) {
8322
7717
  let pending = vectorsToWrite;
8323
7718
  for (let index = kept.length - 1; index >= 0 && pending > 0; index--) {
8324
- if (kept[index].ordinals.length >= VECTOR_FILE_MAX_VECTORS) continue;
7719
+ if (kept[index].ordinals.length >= capacity) continue;
8325
7720
  if (liveCounts[index] > pending) return;
8326
7721
  pending += liveCounts[index];
8327
7722
  kept.splice(index, 1);
@@ -8369,13 +7764,14 @@ function numberEveryVector(state, kept, written) {
8369
7764
  }
8370
7765
  return { numberOfOrdinal, ordinalOfNumber };
8371
7766
  }
8372
- function graphOf(state, numbers) {
8373
- if (state.hnsw === null) return null;
8374
- return {
8375
- v: VECTOR_GRAPH_PAYLOAD_VERSION,
8376
- fieldName: state.fieldName,
8377
- graphs: [state.hnsw.serializeNumbered(numbers.numberOfOrdinal, numbers.ordinalOfNumber)]
8378
- };
7767
+ function graphPartsOf(state, numbers) {
7768
+ if (state.hnsw === null || numbers.ordinalOfNumber.length === 0) return null;
7769
+ const parts = state.hnsw.serializeNumbered(
7770
+ numbers.numberOfOrdinal,
7771
+ numbers.ordinalOfNumber,
7772
+ VECTOR_GRAPH_PART_VECTORS
7773
+ );
7774
+ return parts.map((graph) => ({ v: VECTOR_GRAPH_PART_PAYLOAD_VERSION, fieldName: state.fieldName, graphs: [graph] }));
8379
7775
  }
8380
7776
  function readFile(state, planned, docIds, centroid) {
8381
7777
  const dimension = state.dimension;
@@ -8400,18 +7796,19 @@ function readFile(state, planned, docIds, centroid) {
8400
7796
  ordinals
8401
7797
  };
8402
7798
  }
8403
- function planCheckpoint(state, listedKeys) {
7799
+ function planCheckpoint(state, listedKeys, rewritesGraph = false) {
8404
7800
  const signature = signatureOf(state);
8405
7801
  const calibration = signature.calibration;
8406
7802
  const trusted = registryMatches(state, listedKeys);
7803
+ const capacity = fileCapacityOf(state);
8407
7804
  const liveCounts = [];
8408
7805
  const keptFiles = filesWorthKeeping(state, trusted ? state.savedFiles : [], calibration, liveCounts);
8409
- absorbSmallerPartialFiles(keptFiles, liveCounts, countOutside(state, liveCounts));
7806
+ absorbSmallerPartialFiles(keptFiles, liveCounts, countOutside(state, liveCounts), capacity);
8410
7807
  const written = ordinalsOutsideOf(state, keptFiles);
8411
7808
  const writtenDocIds = written.map((ordinal) => state.store.docIdForOrdinal(ordinal) ?? "");
8412
7809
  const centroid = codedCentroid(state);
8413
7810
  const listedBefore = trusted && listedKeys !== null;
8414
- const unchanged = listedBefore && sameSignature(state.savedSignature, signature) && written.length === 0;
7811
+ const unchanged = !rewritesGraph && listedBefore && sameSignature(state.savedSignature, signature) && written.length === 0;
8415
7812
  return {
8416
7813
  unchanged,
8417
7814
  keptFiles,
@@ -8421,12 +7818,12 @@ function planCheckpoint(state, listedKeys) {
8421
7818
  count: file.ordinals.length,
8422
7819
  dead: deadBitsOf(state, file)
8423
7820
  })),
8424
- newFiles: Math.ceil(written.length / VECTOR_FILE_MAX_VECTORS),
8425
- graph: unchanged ? null : graphOf(state, numberEveryVector(state, keptFiles, written)),
7821
+ newFiles: Math.ceil(written.length / capacity),
7822
+ graphParts: unchanged ? null : graphPartsOf(state, numberEveryVector(state, keptFiles, written)),
8426
7823
  signature,
8427
7824
  readNewFile(index) {
8428
- const start = index * VECTOR_FILE_MAX_VECTORS;
8429
- const end = start + VECTOR_FILE_MAX_VECTORS;
7825
+ const start = index * capacity;
7826
+ const end = start + capacity;
8430
7827
  return readFile(state, written.slice(start, end), writtenDocIds.slice(start, end), centroid);
8431
7828
  }
8432
7829
  };
@@ -8446,13 +7843,22 @@ function sameCentroid2(a, b) {
8446
7843
  return true;
8447
7844
  }
8448
7845
  function bufferEveryVectorOutsideTheGraph(state) {
8449
- for (const [docId] of state.store.entries()) {
7846
+ for (const docId of state.store.docIds()) {
8450
7847
  if (state.tombstones.has(docId)) continue;
8451
7848
  if (state.hnsw === null || !state.hnsw.has(docId)) state.buffer.add(docId);
8452
7849
  }
8453
7850
  }
7851
+ function insertNodesOfLaterGraph(state, graph, later, ordinals) {
7852
+ for (const part of later.parts) requireWholeNeighbourLists(part);
7853
+ for (let number = 0; number < ordinals.length; number++) {
7854
+ const ordinal = ordinals[number];
7855
+ if (ordinal === NO_LIVE_VECTOR_AT_POSITION || topLayerInParts(later, number) < 0) continue;
7856
+ const docId = state.store.docIdForOrdinal(ordinal);
7857
+ if (docId !== void 0 && !graph.has(docId)) graph.insertOrdinal(ordinal);
7858
+ }
7859
+ }
8454
7860
  function restoreGraph(state, payload, ordinalOfNumber) {
8455
- const first = payload?.graphs[0];
7861
+ const first = payload?.graphs[0]?.parts[0];
8456
7862
  if (payload === null || first === void 0) {
8457
7863
  bufferEveryVectorOutsideTheGraph(state);
8458
7864
  return;
@@ -8463,17 +7869,9 @@ function restoreGraph(state, payload, ordinalOfNumber) {
8463
7869
  { m: first.m, efConstruction: first.efConstruction, metric: first.metric },
8464
7870
  state.osq ?? void 0
8465
7871
  );
8466
- graph.deserializeNumbered(first, ordinalOfNumber);
7872
+ graph.deserializeNumbered(payload.graphs[0], ordinalOfNumber);
8467
7873
  adoptGraph(state, graph);
8468
- for (const later of payload.graphs.slice(1)) {
8469
- requireWholeNeighbourLists(later);
8470
- for (let number = 0; number < later.levels.length && number < ordinalOfNumber.length; number++) {
8471
- const ordinal = ordinalOfNumber[number];
8472
- if (later.levels[number] === 0 || ordinal === NO_LIVE_VECTOR_AT_POSITION) continue;
8473
- const docId = state.store.docIdForOrdinal(ordinal);
8474
- if (docId !== void 0 && !graph.has(docId)) graph.insertOrdinal(ordinal);
8475
- }
8476
- }
7874
+ for (const later of payload.graphs.slice(1)) insertNodesOfLaterGraph(state, graph, later, ordinalOfNumber);
8477
7875
  bufferEveryVectorOutsideTheGraph(state);
8478
7876
  if (state.buffer.size <= graph.size) return;
8479
7877
  adoptGraph(state, null);
@@ -9154,12 +8552,11 @@ function createVectorIndex(fieldName, dimension, config, workerCopies = VECTOR_W
9154
8552
  },
9155
8553
  maintenanceStatus: () => maintenanceStatus(state),
9156
8554
  estimateMemoryBytes: () => estimateMemoryBytes(state),
9157
- serialize: () => serialize(state),
9158
8555
  deserialize: (parts) => {
9159
8556
  invalidateWorkerCopies(state);
9160
8557
  deserialize(state, parts);
9161
8558
  },
9162
- planCheckpoint: (listedKeys) => planCheckpoint(state, listedKeys),
8559
+ planCheckpoint: (listedKeys, rewritesGraph) => planCheckpoint(state, listedKeys, rewritesGraph),
9163
8560
  recordCheckpoint: (plan, written) => recordCheckpoint(state, plan, written),
9164
8561
  restoreCheckpoint: (shape) => {
9165
8562
  invalidateWorkerCopies(state);
@@ -9277,11 +8674,55 @@ function createFrozenDocTable(idTable, lengthColumns, documents, tombstones) {
9277
8674
  }
9278
8675
  };
9279
8676
  }
8677
+ function removeFieldAtPath(document, fieldPath) {
8678
+ const segments = fieldPath.split(".");
8679
+ let current = document;
8680
+ for (let i = 0; i < segments.length - 1; i += 1) {
8681
+ const next = current[segments[i]];
8682
+ if (next === null || typeof next !== "object" || Array.isArray(next)) return;
8683
+ current = next;
8684
+ }
8685
+ delete current[segments[segments.length - 1]];
8686
+ }
8687
+ function decodeStoredDocument(bytes, discardedFields) {
8688
+ let decoded;
8689
+ try {
8690
+ decoded = decode(bytes);
8691
+ } catch (error) {
8692
+ throw new NarsilError(
8693
+ ErrorCodes.PERSISTENCE_LOAD_FAILED,
8694
+ `A stored document is not MessagePack: ${error instanceof Error ? error.message : String(error)}`
8695
+ );
8696
+ }
8697
+ if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded) || decoded instanceof Uint8Array) {
8698
+ throw new NarsilError(ErrorCodes.PERSISTENCE_LOAD_FAILED, "A stored document decodes to something other than a map");
8699
+ }
8700
+ const document = decoded;
8701
+ if (discardedFields !== void 0) {
8702
+ for (const fieldPath of discardedFields) removeFieldAtPath(document, fieldPath);
8703
+ }
8704
+ return document;
8705
+ }
8706
+ function* storedDocumentsOf(data) {
8707
+ const count = data.offsets.length - 1;
8708
+ for (let ordinal = 0; ordinal < count; ordinal++) {
8709
+ yield decodeStoredDocument(
8710
+ data.blob.subarray(data.offsets[ordinal], data.offsets[ordinal + 1]),
8711
+ data.discardedFields
8712
+ );
8713
+ }
8714
+ }
8715
+ function decodeEveryStoredDocument(data) {
8716
+ const count = data.offsets.length - 1;
8717
+ for (let ordinal = 0; ordinal < count; ordinal++) {
8718
+ decodeStoredDocument(data.blob.subarray(data.offsets[ordinal], data.offsets[ordinal + 1]), data.discardedFields);
8719
+ }
8720
+ }
9280
8721
  function encodeDocumentTableData(documents) {
9281
8722
  const encoded = new Array(documents.length);
9282
8723
  let blobLength = 0;
9283
8724
  for (let ordinal = 0; ordinal < documents.length; ordinal++) {
9284
- const bytes = encode(documents[ordinal]);
8725
+ const bytes = encodeDocument(documents[ordinal]);
9285
8726
  encoded[ordinal] = bytes;
9286
8727
  blobLength += bytes.length;
9287
8728
  }
@@ -9309,11 +8750,14 @@ function wrapEncodedDocumentTable(data) {
9309
8750
  docAt(ordinal) {
9310
8751
  let document = decoded[ordinal];
9311
8752
  if (document === void 0) {
9312
- document = decode(blob.slice(offsets[ordinal], offsets[ordinal + 1]));
8753
+ document = decodeStoredDocument(blob.slice(offsets[ordinal], offsets[ordinal + 1]), data.discardedFields);
9313
8754
  decoded[ordinal] = document;
9314
8755
  decodedBytes += documentValueBytes(document);
9315
8756
  }
9316
8757
  return document;
8758
+ },
8759
+ readAt(ordinal) {
8760
+ return decoded[ordinal] ?? decodeStoredDocument(blob.subarray(offsets[ordinal], offsets[ordinal + 1]), data.discardedFields);
9317
8761
  }
9318
8762
  };
9319
8763
  }
@@ -9327,75 +8771,15 @@ function wrapDocumentArray(documents) {
9327
8771
  byteLength: residentBytes,
9328
8772
  docAt(ordinal) {
9329
8773
  return documents[ordinal];
8774
+ },
8775
+ readAt(ordinal) {
8776
+ return documents[ordinal];
9330
8777
  }
9331
8778
  };
9332
8779
  }
9333
8780
 
9334
- // src/core/partition/frozen/string-blob.ts
9335
- var SURROGATE = /[\ud800-\udfff]/;
9336
- var encoder = new TextEncoder();
9337
- function encodeOneByOne(strings) {
9338
- const encoded = new Array(strings.length);
9339
- let blobLength = 0;
9340
- for (let i = 0; i < strings.length; i++) {
9341
- encoded[i] = encoder.encode(strings[i]);
9342
- blobLength += encoded[i].length;
9343
- }
9344
- const blob = new Uint8Array(blobLength);
9345
- const offsets = new Uint32Array(strings.length + 1);
9346
- let cursor = 0;
9347
- for (let i = 0; i < strings.length; i++) {
9348
- blob.set(encoded[i], cursor);
9349
- cursor += encoded[i].length;
9350
- offsets[i + 1] = cursor;
9351
- }
9352
- return { blob, offsets };
9353
- }
9354
- function basicPlaneUtf8Bytes(text) {
9355
- let bytes = text.length;
9356
- for (let i = 0; i < text.length; i++) {
9357
- const unit = text.charCodeAt(i);
9358
- if (unit >= 128) bytes += unit < 2048 ? 1 : 2;
9359
- }
9360
- return bytes;
9361
- }
9362
- function encodeStringBlob(strings) {
9363
- const joined = strings.join("");
9364
- if (SURROGATE.test(joined)) return encodeOneByOne(strings);
9365
- const blob = encoder.encode(joined);
9366
- const offsets = new Uint32Array(strings.length + 1);
9367
- let cursor = 0;
9368
- for (let i = 0; i < strings.length; i++) {
9369
- cursor += basicPlaneUtf8Bytes(strings[i]);
9370
- offsets[i + 1] = cursor;
9371
- }
9372
- return cursor === blob.length ? { blob, offsets } : encodeOneByOne(strings);
9373
- }
9374
- function orderByComparator(strings) {
9375
- const order = new Array(strings.length);
9376
- for (let i = 0; i < strings.length; i++) order[i] = i;
9377
- return order.sort((a, b) => compareCodePoints(strings[a], strings[b]));
9378
- }
9379
- function alreadyInCodePointOrder(strings) {
9380
- for (let i = 1; i < strings.length; i++) {
9381
- if (compareCodePoints(strings[i - 1], strings[i]) > 0) return false;
9382
- }
9383
- return true;
9384
- }
9385
- function codePointOrder(strings) {
9386
- if (alreadyInCodePointOrder(strings)) return strings.map((_, i) => i);
9387
- if (SURROGATE.test(strings.join(""))) return orderByComparator(strings);
9388
- const positions = /* @__PURE__ */ new Map();
9389
- for (let i = 0; i < strings.length; i++) positions.set(strings[i], i);
9390
- if (positions.size !== strings.length) return orderByComparator(strings);
9391
- const order = new Array(strings.length);
9392
- const sorted = [...strings].sort();
9393
- for (let i = 0; i < sorted.length; i++) order[i] = positions.get(sorted[i]) ?? 0;
9394
- return order;
9395
- }
9396
-
9397
8781
  // src/core/partition/frozen/external-ids.ts
9398
- var encoder2 = new TextEncoder();
8782
+ var encoder = new TextEncoder();
9399
8783
  var decoder = new TextDecoder();
9400
8784
  function encodeExternalIdTableData(docIds) {
9401
8785
  const { blob, offsets } = encodeStringBlob(docIds);
@@ -9427,7 +8811,7 @@ function wrapExternalIdTable(data) {
9427
8811
  bytes: blob.byteLength + offsets.byteLength + sortedOrdinals.byteLength,
9428
8812
  idAt,
9429
8813
  ordinalOf(docId) {
9430
- const query = encoder2.encode(docId);
8814
+ const query = encoder.encode(docId);
9431
8815
  let lo = 0;
9432
8816
  let hi = count;
9433
8817
  while (lo < hi) {
@@ -9713,6 +9097,10 @@ function createFrozenInvertedReader(table, views) {
9713
9097
  if (at < 0) return void 0;
9714
9098
  return views.viewReadOnce(table.payloadSlot(at), table.documentFrequencyAt(at));
9715
9099
  },
9100
+ documentFrequency(token) {
9101
+ const at = table.find(token);
9102
+ return at < 0 ? 0 : table.documentFrequencyAt(at);
9103
+ },
9716
9104
  fuzzyLookup(token, tolerance, prefixLength) {
9717
9105
  if (tolerance === 0) {
9718
9106
  const at = table.find(token);
@@ -9867,7 +9255,7 @@ function createLazySurfaceReader(load) {
9867
9255
  }
9868
9256
 
9869
9257
  // src/core/partition/frozen/token-table.ts
9870
- var encoder3 = new TextEncoder();
9258
+ var encoder2 = new TextEncoder();
9871
9259
  var decoder3 = new TextDecoder();
9872
9260
  function compareBytes2(blob, start, end, query) {
9873
9261
  const length = end - start;
@@ -9931,7 +9319,7 @@ function wrapFrozenTokenTable(data) {
9931
9319
  size,
9932
9320
  bytes: blob.byteLength + offsets.byteLength + payloadSlots.byteLength + documentFrequencies.byteLength,
9933
9321
  find(token) {
9934
- const query = encoder3.encode(token);
9322
+ const query = encoder2.encode(token);
9935
9323
  const at = lowerBound3(query);
9936
9324
  if (at >= size) return -1;
9937
9325
  return compareBytes2(blob, offsets[at], offsets[at + 1], query) === 0 ? at : -1;
@@ -9944,6 +9332,9 @@ function wrapFrozenTokenTable(data) {
9944
9332
  }
9945
9333
  return token;
9946
9334
  },
9335
+ byteLengthAt(sortedIndex) {
9336
+ return offsets[sortedIndex + 1] - offsets[sortedIndex];
9337
+ },
9947
9338
  payloadSlot(sortedIndex) {
9948
9339
  return payloadSlots[sortedIndex];
9949
9340
  },
@@ -9951,7 +9342,7 @@ function wrapFrozenTokenTable(data) {
9951
9342
  return documentFrequencies[sortedIndex];
9952
9343
  },
9953
9344
  prefixRange(prefix) {
9954
- const bytes = encoder3.encode(prefix);
9345
+ const bytes = encoder2.encode(prefix);
9955
9346
  const start = lowerBound3(bytes);
9956
9347
  return { start, end: prefixEnd(start, bytes) };
9957
9348
  },
@@ -9969,8 +9360,8 @@ function wrapFrozenTokenTable(data) {
9969
9360
  fromCodePoint = unit;
9970
9361
  toCodePoint = nextCodePoint(unit);
9971
9362
  }
9972
- const from = encoder3.encode(String.fromCodePoint(fromCodePoint));
9973
- const to = encoder3.encode(String.fromCodePoint(toCodePoint));
9363
+ const from = encoder2.encode(String.fromCodePoint(fromCodePoint));
9364
+ const to = encoder2.encode(String.fromCodePoint(toCodePoint));
9974
9365
  return { start: lowerBound3(from), end: lowerBound3(to) };
9975
9366
  }
9976
9367
  };
@@ -10032,11 +9423,68 @@ function sharedSurfaceTable(payload) {
10032
9423
  if (table.counts.length === 0) return null;
10033
9424
  return { blob: sharedUint8(table.blob), offsets: sharedUint32(table.offsets), counts: sharedUint32(table.counts) };
10034
9425
  }
10035
- function freezeSegmentShared(payload, documents, segmentId) {
9426
+ function sharePartialWordTable(table) {
9427
+ if (typeof SharedArrayBuffer !== "function") return table;
9428
+ return {
9429
+ fields: [...table.fields],
9430
+ spellings: sharedUint8(table.spellings),
9431
+ spellingOffsets: sharedUint32(table.spellingOffsets),
9432
+ spellingTokens: sharedUint32(table.spellingTokens),
9433
+ spellingCounts: sharedUint32(table.spellingCounts),
9434
+ runs: sharedUint32(table.runs),
9435
+ runOffsets: sharedUint32(table.runOffsets),
9436
+ runEntries: sharedUint32(table.runEntries)
9437
+ };
9438
+ }
9439
+ function sharedPartialWordsOf(tokenData, spellings, documentData, analysis) {
9440
+ let source = spellings;
9441
+ if (source === null && analysis !== null && analysis.fields.length > 0) {
9442
+ source = encodePartialSpellings(analysis.fields, spellingCountsOf(storedDocumentsOf(documentData), analysis));
9443
+ }
9444
+ if (source === null || source.fields.length === 0) return null;
9445
+ const table = buildPartialWordTableFrom(source, wrapFrozenTokenTable(tokenData));
9446
+ return table === null ? null : sharePartialWordTable(table);
9447
+ }
9448
+ function shareSegmentArrays(arrays, analysis, segmentId) {
9449
+ return {
9450
+ ...arrays,
9451
+ segmentId: segmentId ?? generateId(),
9452
+ partialWords: sharedPartialWordsOf(arrays.tokenTable, null, arrays.documentTable, analysis)
9453
+ };
9454
+ }
9455
+ function segmentArraysOf(payload, documentData) {
9456
+ const tokenTable = encodeFrozenTokenTableData(payload.tokens, payload.docFrequencies);
9457
+ const idTable = encodeExternalIdTableData(payload.docIds);
9458
+ const surfaceTable = payload.surfaceForms === null ? null : encodeSurfaceTable(payload.surfaceForms);
9459
+ return {
9460
+ documentCount: payload.documentCount,
9461
+ fieldNames: payload.fieldNames,
9462
+ fieldLengthNames: payload.fieldLengthNames,
9463
+ fieldLengthColumns: payload.fieldLengthColumns,
9464
+ totalFieldLengths: payload.totalFieldLengths,
9465
+ tokenTable,
9466
+ idTable,
9467
+ documentTable: documentData,
9468
+ postingOffsets: payload.postingOffsets,
9469
+ postingDocIds: payload.postingDocIds,
9470
+ postingFrequencies: payload.postingFrequencies,
9471
+ postingFieldIndices: payload.postingFieldIndices,
9472
+ positionOffsets: payload.positionOffsets,
9473
+ positionValues: payload.positionValues,
9474
+ numeric: payload.numeric,
9475
+ sizes: payload.sizes,
9476
+ boolean: payload.boolean,
9477
+ enums: payload.enums,
9478
+ geo: payload.geo,
9479
+ pattern: payload.pattern,
9480
+ surfaceTable: surfaceTable === null || surfaceTable.counts.length === 0 ? null : surfaceTable
9481
+ };
9482
+ }
9483
+ function freezeSegmentShared(payload, documents, segmentId, analysis = null) {
10036
9484
  if (typeof SharedArrayBuffer !== "function") return null;
10037
- return freezeEncodedSegmentShared(payload, encodeDocumentTableData(documents), segmentId);
9485
+ return freezeEncodedSegmentShared(payload, encodeDocumentTableData(documents), segmentId, analysis);
10038
9486
  }
10039
- function freezeEncodedSegmentShared(payload, documentData, segmentId) {
9487
+ function freezeEncodedSegmentShared(payload, documentData, segmentId, analysis = null) {
10040
9488
  if (typeof SharedArrayBuffer !== "function") return null;
10041
9489
  const tokenData = encodeFrozenTokenTableData(payload.tokens, payload.docFrequencies);
10042
9490
  const idData = encodeExternalIdTableData(payload.docIds);
@@ -10060,12 +9508,13 @@ function freezeEncodedSegmentShared(payload, documentData, segmentId) {
10060
9508
  },
10061
9509
  documentTable: {
10062
9510
  blob: sharedUint8(documentData.blob),
10063
- offsets: sharedUint32(documentData.offsets)
9511
+ offsets: sharedUint32(documentData.offsets),
9512
+ ...documentData.discardedFields === void 0 ? {} : { discardedFields: documentData.discardedFields }
10064
9513
  },
10065
9514
  postingOffsets: sharedUint32(payload.postingOffsets),
10066
9515
  postingDocIds: sharedUint32(payload.postingDocIds),
10067
9516
  postingFrequencies: sharedUint16(payload.postingFrequencies),
10068
- postingFieldIndices: sharedUint8(payload.postingFieldIndices),
9517
+ postingFieldIndices: sharedUint16(payload.postingFieldIndices),
10069
9518
  positionOffsets: payload.positionOffsets === null ? null : sharedUint32(payload.positionOffsets),
10070
9519
  positionValues: payload.positionValues === null ? null : sharedUint32(payload.positionValues),
10071
9520
  numeric: payload.numeric.map((entry) => ({
@@ -10102,7 +9551,8 @@ function freezeEncodedSegmentShared(payload, documentData, segmentId) {
10102
9551
  offsets: sharedUint32(entry.offsets),
10103
9552
  postings: sharedUint32(entry.postings)
10104
9553
  })),
10105
- surfaceTable: sharedSurfaceTable(payload)
9554
+ surfaceTable: sharedSurfaceTable(payload),
9555
+ partialWords: sharedPartialWordsOf(tokenData, payload.partialSpellings, documentData, analysis)
10106
9556
  };
10107
9557
  }
10108
9558
 
@@ -10138,7 +9588,12 @@ function buildGeoReaders(entries) {
10138
9588
  }
10139
9589
  return readers;
10140
9590
  }
10141
- function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot) {
9591
+ function* survivingDocuments(documentSource, tombstones) {
9592
+ for (let ordinal = 0; ordinal < documentSource.count; ordinal++) {
9593
+ if (!tombstones.has(ordinal)) yield documentSource.readAt(ordinal);
9594
+ }
9595
+ }
9596
+ function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot, partialWordTable) {
10142
9597
  const tombstones = createFrozenTombstones();
10143
9598
  const postingViews = createFrozenPostingViews(source, tombstones);
10144
9599
  const lengthColumns = /* @__PURE__ */ new Map();
@@ -10146,6 +9601,10 @@ function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot
10146
9601
  lengthColumns.set(source.fieldLengthNames[i], source.fieldLengthColumns[i]);
10147
9602
  }
10148
9603
  const docStore = createFrozenDocTable(source.idTable, lengthColumns, documentSource, tombstones);
9604
+ const partialWords = createFrozenPartialWords(partialWordTable, source.tokenTable, {
9605
+ documentAt: (ordinal) => documentSource.readAt(ordinal),
9606
+ survivors: () => survivingDocuments(documentSource, tombstones)
9607
+ });
10149
9608
  const numericIndexes = /* @__PURE__ */ new Map();
10150
9609
  for (const entry of source.numeric) numericIndexes.set(entry.fieldPath, createFrozenNumericReader(entry));
10151
9610
  const sizeIndexes = /* @__PURE__ */ new Map();
@@ -10178,6 +9637,7 @@ function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot
10178
9637
  patternIndexes,
10179
9638
  sizeIndexes,
10180
9639
  fieldNameTable,
9640
+ partialWords,
10181
9641
  trackPositions: source.positionOffsets !== null,
10182
9642
  flatSchemaCache: null,
10183
9643
  lastSchemaRef: null,
@@ -10197,6 +9657,7 @@ function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot
10197
9657
  if (ordinal === void 0) return false;
10198
9658
  const added = tombstones.add(ordinal);
10199
9659
  if (added) {
9660
+ partialWords.afterRemoval(ordinal);
10200
9661
  docStore.releaseSortedDocIds();
10201
9662
  segment.sortColumns = null;
10202
9663
  segment.scoreBuffer = null;
@@ -10212,18 +9673,20 @@ function assembleFrozenSegment(segmentId, source, documentSource, sharedSnapshot
10212
9673
  return segment;
10213
9674
  }
10214
9675
  function createFrozenSegment(payload, documents, segmentId) {
9676
+ const tokenTable = buildFrozenTokenTable(payload.tokens, payload.docFrequencies);
10215
9677
  return assembleFrozenSegment(
10216
9678
  segmentId ?? generateId(),
10217
9679
  {
10218
9680
  ...payload,
10219
- tokenTable: buildFrozenTokenTable(payload.tokens, payload.docFrequencies),
9681
+ tokenTable,
10220
9682
  idTable: buildExternalIdTable(payload.docIds),
10221
9683
  documentTable: null,
10222
9684
  readSurfaceForms: () => payload.surfaceForms,
10223
9685
  docFrequencies: () => payload.docFrequencies
10224
9686
  },
10225
9687
  wrapDocumentArray(documents),
10226
- null
9688
+ null,
9689
+ payload.partialSpellings === null ? null : buildPartialWordTableFrom(payload.partialSpellings, tokenTable)
10227
9690
  );
10228
9691
  }
10229
9692
  function createSharedFrozenSegment(snapshot) {
@@ -10245,9 +9708,137 @@ function createSharedFrozenSegment(snapshot) {
10245
9708
  }
10246
9709
  },
10247
9710
  wrapEncodedDocumentTable(snapshot.documentTable),
10248
- snapshot
9711
+ snapshot,
9712
+ snapshot.partialWords
10249
9713
  );
10250
9714
  }
9715
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
9716
+ var keyDecoder = new TextDecoder("utf-8", { fatal: true });
9717
+ function headerAt(view, offset) {
9718
+ if (offset >= view.byteLength) return null;
9719
+ const tag = view.getUint8(offset);
9720
+ if (tag <= 127 || tag >= 224) return { headerBytes: 1, bodyBytes: 0, children: 0 };
9721
+ if (tag <= 143) return { headerBytes: 1, bodyBytes: 0, children: (tag & 15) * 2 };
9722
+ if (tag <= 159) return { headerBytes: 1, bodyBytes: 0, children: tag & 15 };
9723
+ if (tag <= 191) return { headerBytes: 1, bodyBytes: tag & 31, children: 0 };
9724
+ const lengthBytes = LENGTH_BYTES_BY_TAG.get(tag);
9725
+ if (lengthBytes !== void 0) {
9726
+ if (offset + 1 + lengthBytes > view.byteLength) return null;
9727
+ const length = lengthBytes === 1 ? view.getUint8(offset + 1) : lengthBytes === 2 ? view.getUint16(offset + 1, false) : view.getUint32(offset + 1, false);
9728
+ const headerBytes = 1 + lengthBytes;
9729
+ if (ARRAY_TAGS.has(tag)) return { headerBytes, bodyBytes: 0, children: length };
9730
+ if (MAP_TAGS.has(tag)) return { headerBytes, bodyBytes: 0, children: length * 2 };
9731
+ return { headerBytes, bodyBytes: length + (EXT_TAGS.has(tag) ? 1 : 0), children: 0 };
9732
+ }
9733
+ const fixedBytes = FIXED_BODY_BYTES_BY_TAG.get(tag);
9734
+ return fixedBytes === void 0 ? null : { headerBytes: 1, bodyBytes: fixedBytes, children: 0 };
9735
+ }
9736
+ var LENGTH_BYTES_BY_TAG = /* @__PURE__ */ new Map([
9737
+ [196, 1],
9738
+ [197, 2],
9739
+ [198, 4],
9740
+ [199, 1],
9741
+ [200, 2],
9742
+ [201, 4],
9743
+ [217, 1],
9744
+ [218, 2],
9745
+ [219, 4],
9746
+ [220, 2],
9747
+ [221, 4],
9748
+ [222, 2],
9749
+ [223, 4]
9750
+ ]);
9751
+ var ARRAY_TAGS = /* @__PURE__ */ new Set([220, 221]);
9752
+ var MAP_TAGS = /* @__PURE__ */ new Set([222, 223]);
9753
+ var EXT_TAGS = /* @__PURE__ */ new Set([199, 200, 201]);
9754
+ var FIXED_BODY_BYTES_BY_TAG = /* @__PURE__ */ new Map([
9755
+ [192, 0],
9756
+ [194, 0],
9757
+ [195, 0],
9758
+ [202, 4],
9759
+ [203, 8],
9760
+ [204, 1],
9761
+ [205, 2],
9762
+ [206, 4],
9763
+ [207, 8],
9764
+ [208, 1],
9765
+ [209, 2],
9766
+ [210, 4],
9767
+ [211, 8],
9768
+ [212, 2],
9769
+ [213, 3],
9770
+ [214, 5],
9771
+ [215, 9],
9772
+ [216, 17]
9773
+ ]);
9774
+ function endOfValue(view, start) {
9775
+ let offset = start;
9776
+ let pending = 1;
9777
+ while (pending > 0) {
9778
+ const header = headerAt(view, offset);
9779
+ if (header === null) return null;
9780
+ offset += header.headerBytes + header.bodyBytes;
9781
+ if (offset > view.byteLength) return null;
9782
+ pending += header.children - 1;
9783
+ }
9784
+ return offset;
9785
+ }
9786
+ function stringKeyAt(bytes, view, offset) {
9787
+ const header = headerAt(view, offset);
9788
+ if (header === null || header.children !== 0) return null;
9789
+ const tag = view.getUint8(offset);
9790
+ const isString = tag >= 160 && tag <= 191 || tag === 217 || tag === 218 || tag === 219;
9791
+ const end = offset + header.headerBytes + header.bodyBytes;
9792
+ if (!isString || end > bytes.length) return null;
9793
+ try {
9794
+ return { key: keyDecoder.decode(bytes.subarray(offset + header.headerBytes, end)), end };
9795
+ } catch {
9796
+ return null;
9797
+ }
9798
+ }
9799
+ function topLevelMapKeys(bytes) {
9800
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9801
+ const top = headerAt(view, 0);
9802
+ if (top === null || top.bodyBytes !== 0) return null;
9803
+ const tag = view.getUint8(0);
9804
+ if (!(tag >= 128 && tag <= 143 || MAP_TAGS.has(tag))) return null;
9805
+ const keys = /* @__PURE__ */ new Set();
9806
+ let offset = top.headerBytes;
9807
+ for (let pair = 0; pair < top.children / 2; pair += 1) {
9808
+ const key = stringKeyAt(bytes, view, offset);
9809
+ if (key === null) return null;
9810
+ const valueEnd = endOfValue(view, key.end);
9811
+ if (valueEnd === null) return null;
9812
+ keys.add(key.key);
9813
+ offset = valueEnd;
9814
+ }
9815
+ return keys;
9816
+ }
9817
+ function decodeMapWithoutFields(bytes, skippedFields) {
9818
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9819
+ const top = headerAt(view, 0);
9820
+ if (top === null || top.children === 0 || top.bodyBytes !== 0) return null;
9821
+ const tag = view.getUint8(0);
9822
+ const isMap = tag >= 128 && tag <= 143 || MAP_TAGS.has(tag);
9823
+ if (!isMap) return null;
9824
+ const document = {};
9825
+ let offset = top.headerBytes;
9826
+ for (let pair = 0; pair < top.children / 2; pair += 1) {
9827
+ const key = stringKeyAt(bytes, view, offset);
9828
+ if (key === null || FORBIDDEN_KEYS.has(key.key)) return null;
9829
+ const valueEnd = endOfValue(view, key.end);
9830
+ if (valueEnd === null) return null;
9831
+ if (!skippedFields.has(key.key)) {
9832
+ try {
9833
+ document[key.key] = decode(bytes.subarray(key.end, valueEnd));
9834
+ } catch {
9835
+ return null;
9836
+ }
9837
+ }
9838
+ offset = valueEnd;
9839
+ }
9840
+ return offset === bytes.length ? document : null;
9841
+ }
10251
9842
 
10252
9843
  // src/core/partition/frozen/merge-fields.ts
10253
9844
  function indexByFieldPath(entries) {
@@ -10510,7 +10101,7 @@ function mergePostings2(inputs) {
10510
10101
  const postingOffsets = new Uint32Array(tokenCount + 1);
10511
10102
  const postingDocIds = new Uint32Array(totalPostings);
10512
10103
  const postingFrequencies = new Uint16Array(totalPostings);
10513
- const postingFieldIndices = new Uint8Array(totalPostings);
10104
+ const postingFieldIndices = new Uint16Array(totalPostings);
10514
10105
  const positionOffsets = hasPositions ? new Uint32Array(totalPostings + 1) : null;
10515
10106
  const positionValues = hasPositions ? new Uint32Array(totalPositions) : null;
10516
10107
  let postingCursor = 0;
@@ -10584,11 +10175,15 @@ function mergePostings2(inputs) {
10584
10175
  }
10585
10176
 
10586
10177
  // src/core/partition/frozen/merge.ts
10587
- function remapOf(segment, docIds) {
10178
+ function selectionKeeps(selection, ordinal) {
10179
+ return selection.keep === void 0 ? !selection.segment.isTombstoned(ordinal) : selection.keep(ordinal);
10180
+ }
10181
+ function remapOf(selection, docIds) {
10182
+ const { segment } = selection;
10588
10183
  const count = segment.arrays.documentCount;
10589
10184
  const remap = new Int32Array(count).fill(-1);
10590
10185
  for (let ordinal = 0; ordinal < count; ordinal++) {
10591
- if (segment.isTombstoned(ordinal)) continue;
10186
+ if (!selectionKeeps(selection, ordinal)) continue;
10592
10187
  remap[ordinal] = docIds.length;
10593
10188
  docIds.push(segment.arrays.idTable.idAt(ordinal));
10594
10189
  }
@@ -10601,7 +10196,7 @@ function fieldNameIndex(fieldNames, name) {
10601
10196
  return fieldNames.length - 1;
10602
10197
  }
10603
10198
  function fieldIndexRemapOf(segment, fieldNames) {
10604
- const remap = new Uint8Array(segment.arrays.fieldNames.length);
10199
+ const remap = new Uint16Array(segment.arrays.fieldNames.length);
10605
10200
  for (let i = 0; i < segment.arrays.fieldNames.length; i++) {
10606
10201
  remap[i] = fieldNameIndex(fieldNames, segment.arrays.fieldNames[i]);
10607
10202
  }
@@ -10647,20 +10242,34 @@ function mergeSurfaceForms2(inputs) {
10647
10242
  }
10648
10243
  return registry.size() === 0 ? null : registry.serialize();
10649
10244
  }
10245
+ function holdsADiscardedField(bytes, discardedFields) {
10246
+ const keys = topLevelMapKeys(bytes);
10247
+ if (keys === null) return true;
10248
+ for (const fieldPath of discardedFields) {
10249
+ const dot = fieldPath.indexOf(".");
10250
+ if (keys.has(dot < 0 ? fieldPath : fieldPath.slice(0, dot))) return true;
10251
+ }
10252
+ return false;
10253
+ }
10650
10254
  function encodedDocumentsOf(input) {
10651
10255
  const table = input.segment.arrays.documentTable;
10652
- if (table !== null) {
10653
- return (ordinal) => table.blob.subarray(table.offsets[ordinal], table.offsets[ordinal + 1]);
10654
- }
10655
10256
  const encoded = new Array(input.remap.length);
10656
- return (ordinal) => {
10257
+ const encodeOrdinal = (ordinal) => {
10657
10258
  let bytes = encoded[ordinal];
10658
10259
  if (bytes === void 0) {
10659
- bytes = encode(input.segment.documentSource.docAt(ordinal));
10260
+ bytes = encodeDocument(input.segment.documentSource.docAt(ordinal));
10660
10261
  encoded[ordinal] = bytes;
10661
10262
  }
10662
10263
  return bytes;
10663
10264
  };
10265
+ if (table === null) return encodeOrdinal;
10266
+ const discardedFields = table.discardedFields;
10267
+ return (ordinal) => {
10268
+ if (encoded[ordinal] !== void 0) return encodeOrdinal(ordinal);
10269
+ const stored = table.blob.subarray(table.offsets[ordinal], table.offsets[ordinal + 1]);
10270
+ if (discardedFields === void 0 || !holdsADiscardedField(stored, discardedFields)) return stored;
10271
+ return encodeOrdinal(ordinal);
10272
+ };
10664
10273
  }
10665
10274
  function mergeDocuments2(inputs, documentCount) {
10666
10275
  const readers = inputs.map(encodedDocumentsOf);
@@ -10682,14 +10291,18 @@ function mergeDocuments2(inputs, documentCount) {
10682
10291
  }
10683
10292
  return { blob, offsets };
10684
10293
  }
10685
- function mergeFrozenSegments(segments) {
10294
+ function mergeFrozenSegments(segments, analysis) {
10686
10295
  if (typeof SharedArrayBuffer !== "function") return null;
10296
+ const merged = mergeSegmentContents(segments.map((segment) => ({ segment })));
10297
+ return freezeEncodedSegmentShared(merged.payload, merged.documents, void 0, analysis);
10298
+ }
10299
+ function mergeSegmentContents(selections) {
10687
10300
  const docIds = [];
10688
10301
  const fieldNames = [];
10689
- const inputs = segments.map((segment) => ({
10690
- segment,
10691
- remap: remapOf(segment, docIds),
10692
- fieldIndexRemap: fieldIndexRemapOf(segment, fieldNames)
10302
+ const inputs = selections.map((selection) => ({
10303
+ segment: selection.segment,
10304
+ remap: remapOf(selection, docIds),
10305
+ fieldIndexRemap: fieldIndexRemapOf(selection.segment, fieldNames)
10693
10306
  }));
10694
10307
  const documentCount = docIds.length;
10695
10308
  const postings = mergePostings2(inputs);
@@ -10710,9 +10323,10 @@ function mergeFrozenSegments(segments) {
10710
10323
  totalFieldLengths: lengths.totalFieldLengths,
10711
10324
  docFrequencies: postings.docFrequencies,
10712
10325
  surfaceForms: mergeSurfaceForms2(inputs),
10326
+ partialSpellings: null,
10713
10327
  ...mergeFieldIndexes2(inputs)
10714
10328
  };
10715
- return freezeEncodedSegmentShared(payload, mergeDocuments2(inputs, documentCount));
10329
+ return { payload, documents: mergeDocuments2(inputs, documentCount) };
10716
10330
  }
10717
10331
 
10718
10332
  // src/core/partition/segment-postings.ts
@@ -10826,7 +10440,7 @@ var TextPostings = class {
10826
10440
  const writeCursor = postingOffsets.slice(0, tokenCount);
10827
10441
  const postingDocIds = new Uint32Array(postings);
10828
10442
  const postingFrequencies = new Uint16Array(postings);
10829
- const postingFieldIndices = new Uint8Array(postings);
10443
+ const postingFieldIndices = new Uint16Array(postings);
10830
10444
  const logRowOfPosting = new Uint32Array(postings);
10831
10445
  for (let row = 0; row < postings; row++) {
10832
10446
  const target = writeCursor[this.logToken.values[row]]++;
@@ -10980,14 +10594,18 @@ function buildSegmentPayload(documents, schema, language, options, trackPosition
10980
10594
  patternIndexes: /* @__PURE__ */ new Map(),
10981
10595
  sizeIndexes: /* @__PURE__ */ new Map()
10982
10596
  };
10597
+ const partialFields = partialFieldsOf(schema);
10598
+ const spellings = /* @__PURE__ */ new Map();
10599
+ let fieldSpellings = null;
10983
10600
  let positionOffset = 0;
10984
10601
  const addToken = (token, position, surface) => {
10985
10602
  postings.addToken(token, positionOffset + position);
10986
10603
  surfaces.count(surface, token);
10604
+ if (fieldSpellings !== null) countSpelling(fieldSpellings, token, surface ?? token, 1);
10987
10605
  };
10988
10606
  for (let ordinal = 0; ordinal < documents.length; ordinal++) {
10989
10607
  const document = documents[ordinal].document;
10990
- for (const { path: fieldPath, base: fieldType, pattern } of fields) {
10608
+ for (const { path: fieldPath, base: fieldType, pattern, partial } of fields) {
10991
10609
  const value = getNestedValue(document, fieldPath);
10992
10610
  if (value === void 0 || value === null) continue;
10993
10611
  recordValueSize(filters.sizeIndexes, ordinal, fieldPath, fieldType, value);
@@ -10996,6 +10614,7 @@ function buildSegmentPayload(documents, schema, language, options, trackPosition
10996
10614
  indexFilterValue(filters, ordinal, fieldPath, fieldType, value);
10997
10615
  continue;
10998
10616
  }
10617
+ fieldSpellings = partial ? spellings : null;
10999
10618
  postings.beginField();
11000
10619
  positionOffset = 0;
11001
10620
  for (const text of fieldType === "string" ? [value] : value) {
@@ -11016,6 +10635,7 @@ function buildSegmentPayload(documents, schema, language, options, trackPosition
11016
10635
  fieldLengthColumns: lengths.columns,
11017
10636
  totalFieldLengths: lengths.totalsByName(),
11018
10637
  surfaceForms: surfaces.encode(),
10638
+ partialSpellings: partialFields.length === 0 ? null : encodePartialSpellings(partialFields, spellings),
11019
10639
  ...encodeFieldIndexes(filters, identity)
11020
10640
  };
11021
10641
  }
@@ -11082,7 +10702,7 @@ function runSegmentAction(entry, action) {
11082
10702
  }
11083
10703
  case "compactSegments": {
11084
10704
  const partition = requireComposite(entry, action.indexName, action.partitionId, "compact");
11085
- return mergeFrozenSegments(partition.frozenSegmentsById(action.segmentIds));
10705
+ return mergeFrozenSegments(partition.frozenSegmentsById(action.segmentIds), entry.manager.partialWordAnalysis);
11086
10706
  }
11087
10707
  case "swapSegments": {
11088
10708
  requireComposite(entry, action.indexName, action.partitionId, "swap");
@@ -11559,4 +11179,4 @@ function createDirectExecutor(options) {
11559
11179
  };
11560
11180
  }
11561
11181
 
11562
- export { CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, FIELD_FILTER_OPERATORS, FILTER_EXPRESSION_KEYS, NO_LIVE_VECTOR_AT_POSITION, SHUTDOWN_TIMEOUT_MS, VECTOR_FILE_MAX_VECTORS, buildSegmentPayload, countDead, createCompositePartition, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, deadBitsWhere, deadBytesFor, decodeVectorFilePayload, decodeVectorGraphPayload, decodeVectorIndexParts, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, documentsOfPayloadV2, encodeSegmentState, freezeSegmentShared, generateId, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isCompositePartition, isNodeMainThread, mergeFrozenSegments, missingWorkerEntry, nodeThreadId, readHeapStatistics, registerPatternSearch, registerStopWords, registerTokenizer, requirePatternSearchFor, resolveIndexAnalysis, resolvePartitionInsertOptions, resolveRequestThreadCount, resolveWorkerCount, resolveWorkerEntry, serializeMetadata, spawnNodeWorker, splitWorkerBudget, workerResourceLimits };
11182
+ export { CHECKSUM_YIELD_CHUNK_BYTES, CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, FIELD_FILTER_OPERATORS, FILTER_EXPRESSION_KEYS, MAX_FILE_BYTES, NATIVE_CRC32_MIN_BYTES, NO_LIVE_VECTOR_AT_POSITION, SHUTDOWN_TIMEOUT_MS, VALID_HNSW_METRICS, VECTOR_FILE_MAX_VECTORS, VECTOR_FILE_PAYLOAD_VERSION, assembleGraphParts, buildSegmentPayload, bytesToVectors, countDead, createDirectExecutor, createFrozenSegment, createPartitionIndex, createPartitionManager, createPartitionRouter, createSharedFrozenSegment, deadBitsWhere, deadBytesFor, decodeEveryStoredDocument, decodeMapWithoutFields, decodeStoredDocument, decodeVectorFilePayload, decodeVectorGraphPartPayload, decodeVectorGraphPayload, decodeVectorIndexParts, encodeSegmentState, freezeSegmentShared, generateId, getStopWords, getTokenizer, graphPartsOfEarlierPayload, hasStopWords, hasTokenizer, isCompositePartition, isDead, mergeFrozenSegments, mergeSegmentContents, missingWorkerEntry, nodeThreadId, partitionStateOf, readHeapStatistics, registerPartialWordSearch, registerPatternSearch, registerStopWords, registerTokenizer, requirePartialWordSearchFor, requirePatternSearchFor, resolveIndexAnalysis, resolvePartitionInsertOptions, resolveRequestThreadCount, resolveWorkerCount, resolveWorkerEntry, segmentArraysOf, selectionKeeps, shareSegmentArrays, spawnNodeWorker, splitWorkerBudget, survivorDocumentsOf, vectorsPerFile, vectorsToBytes, workerResourceLimits };