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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/{options-Dtk99qUs.d.ts → capabilities-JrVQOwi4.d.ts} +55 -47
  3. package/dist/chunk-AV4IJKD2.mjs +35 -0
  4. package/dist/{chunk-M6JGMKA5.mjs → chunk-BEIK3EOZ.mjs} +503 -2273
  5. package/dist/chunk-CMVG2UEF.mjs +2038 -0
  6. package/dist/chunk-CV5H7XL7.mjs +876 -0
  7. package/dist/{chunk-KGRYQVZ5.mjs → chunk-MHE7YWAU.mjs} +2 -1
  8. package/dist/{chunk-56PJBIJ6.mjs → chunk-ND34I3SV.mjs} +427 -1455
  9. package/dist/chunk-PIWKZPP7.mjs +64 -0
  10. package/dist/{chunk-HGJC5L65.mjs → chunk-PMBHH44R.mjs} +121 -2
  11. package/dist/chunk-RP23PWMV.mjs +11 -0
  12. package/dist/{chunk-RDGXYRF4.mjs → chunk-WYJXSLER.mjs} +8 -19
  13. package/dist/{chunk-JXHUHFJ5.mjs → chunk-X6YZO4MQ.mjs} +2 -11
  14. package/dist/{chunk-OWCFK42Q.mjs → chunk-XE6XR2OE.mjs} +3 -10
  15. package/dist/chunk-ZHUQKZBE.mjs +1292 -0
  16. package/dist/{client-DX7xPcNA.d.ts → client-CBOVHdjp.d.ts} +31 -7
  17. package/dist/client.d.ts +7 -7
  18. package/dist/client.mjs +26 -10
  19. package/dist/{config-B41W23Us.d.ts → config-Czh2YNDi.d.ts} +2 -2
  20. package/dist/{config-Co-zAXqO.d.ts → config-OON7IJ_B.d.ts} +26 -5
  21. package/dist/direct-executor-OREXU72C.mjs +12 -0
  22. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  23. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  24. package/dist/distribution.d.ts +8 -8
  25. package/dist/distribution.mjs +28 -11
  26. package/dist/{engine-CABZg25p.d.ts → engine-9PtFY50q.d.ts} +114 -62
  27. package/dist/index.browser.mjs +3255 -2516
  28. package/dist/index.d.ts +7 -7
  29. package/dist/index.mjs +11 -6
  30. package/dist/{search-FjWhZN3f.d.ts → memory-Bzd5xcEW.d.ts} +88 -79
  31. package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
  32. package/dist/persistence/durability/checkpoint-worker.mjs +11 -6
  33. package/dist/react.d.ts +4 -4
  34. package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
  35. package/dist/server.d.ts +47 -42
  36. package/dist/server.mjs +321 -817
  37. package/dist/thread-PZLEO6JL.mjs +310 -0
  38. package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
  39. package/dist/vector/hnsw-build-worker.mjs +2 -1
  40. package/dist/vector/search-worker.mjs +3 -121
  41. package/dist/workers/entry.mjs +47 -3
  42. package/package.json +5 -1
  43. package/dist/direct-executor-HRTDWMVB.mjs +0 -9
@@ -1,9 +1,13 @@
1
+ import { validateWorkerConfig, validateIndexName, validateBM25Params, validatePartitionConfig, executeQuery, queryBindingOf, executePreflight, executeSuggest, executeListDocuments, validateDocId } from './chunk-ZHUQKZBE.mjs';
1
2
  import { ReplicationMessageTypes } from './chunk-GCM3JFVA.mjs';
2
- import { WIRE_BATCH_BUDGET, DEFAULT_LIMIT, MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, STATISTICS_BROADCAST_INTERVAL_MS, MAX_SWEEP_INTERVAL_MS, DEFAULT_OFFSET, MIN_SWEEP_INTERVAL_MS, FOREIGN_STATS_TTL_MS, MIN_DOCUMENTS_FOR_SEGMENTS, BATCH_CHUNK_SIZE, MAX_DOC_ID_LENGTH } from './chunk-OWCFK42Q.mjs';
3
- import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, clampRowCount, splitWorkerBudget, resolveWorkerCount, createDirectExecutor, createPartitionRouter, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, detectRuntime, flattenSchema, isTextFieldType, fnv1aBytes, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, countsWithoutScores, fanOutMatchCount, tokenize, decodePageCursor, requireMatchingCursor, encodePageCursor, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1, mergePartitionStats, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, createPartitionIndex, fnv1a, partitionsIn, isNodeMainThread, spawnNodeWorker, CHECKSUM_TIMEOUT_MS, CHECKSUM_YIELD_CHUNK_BYTES, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, createFrozenSegment, isCompositePartition, deserializeMetadata, serializeMetadata, createWorkerPool, WORKER_RESOURCE_LIMITS, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, SHUTDOWN_TIMEOUT_MS, CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, encodeSegmentState, validateRequiredFields, kWayMerge, validateDocument, validateDocumentStrict } from './chunk-M6JGMKA5.mjs';
3
+ import { WIRE_BATCH_BUDGET, resolveVectorText, bindEngineCore } from './chunk-PIWKZPP7.mjs';
4
+ import { STATISTICS_BROADCAST_INTERVAL_MS, MAX_SWEEP_INTERVAL_MS, MIN_SWEEP_INTERVAL_MS, HEAP_PRESSURE_WARNING_FRACTION, FOREIGN_STATS_TTL_MS, HEAP_PRESSURE_REARM_FRACTION, MIN_DOCUMENTS_FOR_SEGMENTS, BATCH_CHUNK_SIZE } from './chunk-XE6XR2OE.mjs';
5
+ import { getNestedValue, generateId, getLanguage, splitWorkerBudget, resolveWorkerCount, createDirectExecutor, createPartitionRouter, createPartitionManager, createVectorIndex, readHeapStatistics, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1, resolveRequestThreadCount, createPartitionIndex, createWorkerPool, isNodeMainThread, spawnNodeWorker, CHECKSUM_TIMEOUT_MS, CHECKSUM_YIELD_CHUNK_BYTES, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, createFrozenSegment, isCompositePartition, WORKER_RESOURCE_LIMITS, deserializeMetadata, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, serializeMetadata, SHUTDOWN_TIMEOUT_MS, CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, encodeSegmentState } from './chunk-BEIK3EOZ.mjs';
6
+ import { extractVectorFieldsFromSchema, validateSchema, validateVectorPromotion, collectQueryTermStats, flattenSchema, isTextFieldType, mergePartitionStats, fnv1a, kWayMerge, mergeFacets, validateRequiredFields, validateDocument, validateDocumentStrict } from './chunk-CMVG2UEF.mjs';
7
+ import { detectRuntime } from './chunk-AV4IJKD2.mjs';
4
8
  import { crc32, crc32Init, crc32Update, crc32Final } from './chunk-KMMJMDUA.mjs';
5
- import { compareCodePoints, compareComparableKeys } from './chunk-2GWVQWGA.mjs';
6
- import { generateId, createRequestId, isValidWorkerResponse } from './chunk-RDGXYRF4.mjs';
9
+ import { compareCodePoints } from './chunk-2GWVQWGA.mjs';
10
+ import { createRequestId, isValidWorkerResponse } from './chunk-WYJXSLER.mjs';
7
11
  import { ErrorCodes, NarsilError } from './chunk-P4ELXVJQ.mjs';
8
12
  import { encode, decode } from '@msgpack/msgpack';
9
13
 
@@ -272,6 +276,20 @@ function validateRequiredFieldsInSchema(required, schema) {
272
276
  }
273
277
  }
274
278
 
279
+ // src/engine/events.ts
280
+ function emitEngineEvent(eventHandlers, event, payload) {
281
+ const handlers = eventHandlers.get(event);
282
+ if (!handlers || handlers.size === 0) return 0;
283
+ for (const handler of handlers) {
284
+ try {
285
+ handler(payload);
286
+ } catch (err) {
287
+ console.warn(`${event} handler error:`, err instanceof Error ? err.message : String(err));
288
+ }
289
+ }
290
+ return handlers.size;
291
+ }
292
+
275
293
  // src/engine/analysis-rebuild.ts
276
294
  function yieldToEventLoop() {
277
295
  return new Promise((resolve) => {
@@ -293,15 +311,7 @@ function wireAnalysisRebuild(wiring) {
293
311
  await (durability.checkpointFromMemory?.(indexName) ?? durability.checkpoint(indexName));
294
312
  },
295
313
  emit(payload) {
296
- const handlers = wiring.eventHandlers.get("analysisRebuild");
297
- if (!handlers) return;
298
- for (const handler of handlers) {
299
- try {
300
- handler(payload);
301
- } catch (err) {
302
- console.warn("analysisRebuild handler error:", err instanceof Error ? err.message : String(err));
303
- }
304
- }
314
+ emitEngineEvent(wiring.eventHandlers, "analysisRebuild", payload);
305
315
  }
306
316
  });
307
317
  }
@@ -827,7 +837,7 @@ async function chunkedChecksum(payload) {
827
837
  }
828
838
  function runWorkerChecksum(worker, payload) {
829
839
  return new Promise((resolve, reject) => {
830
- let settled = false;
840
+ let settled2 = false;
831
841
  const timeoutId = setTimeout(
832
842
  () => settle(() => reject(new Error(`Checksum worker timed out after ${CHECKSUM_TIMEOUT_MS}ms`))),
833
843
  CHECKSUM_TIMEOUT_MS
@@ -836,8 +846,8 @@ function runWorkerChecksum(worker, payload) {
836
846
  timeoutId.unref();
837
847
  }
838
848
  function settle(action) {
839
- if (settled) return;
840
- settled = true;
849
+ if (settled2) return;
850
+ settled2 = true;
841
851
  clearTimeout(timeoutId);
842
852
  try {
843
853
  void worker.terminate();
@@ -1447,7 +1457,7 @@ function discardWorker(worker) {
1447
1457
  }
1448
1458
  function runWorker(worker, request) {
1449
1459
  return new Promise((resolve) => {
1450
- let settled = false;
1460
+ let settled2 = false;
1451
1461
  const onMessage = (msg) => {
1452
1462
  const response = msg;
1453
1463
  if (response.type === "success") {
@@ -1467,10 +1477,10 @@ function runWorker(worker, request) {
1467
1477
  timeoutId.unref();
1468
1478
  }
1469
1479
  function settle(outcome, discard) {
1470
- if (settled) {
1480
+ if (settled2) {
1471
1481
  return;
1472
1482
  }
1473
- settled = true;
1483
+ settled2 = true;
1474
1484
  clearTimeout(timeoutId);
1475
1485
  worker.off("message", onMessage);
1476
1486
  worker.off("error", onError);
@@ -5165,6 +5175,94 @@ function createInvalidationIntegration(deps) {
5165
5175
  };
5166
5176
  }
5167
5177
 
5178
+ // src/engine/heap-pressure.ts
5179
+ function createHeapPressureNotifier(deps) {
5180
+ let warned = false;
5181
+ function check(indexName) {
5182
+ const heap = deps.readHeap();
5183
+ if (heap === null) return;
5184
+ const fraction = heap.usedBytes / heap.limitBytes;
5185
+ if (warned) {
5186
+ if (fraction < HEAP_PRESSURE_REARM_FRACTION) warned = false;
5187
+ return;
5188
+ }
5189
+ if (fraction < HEAP_PRESSURE_WARNING_FRACTION) return;
5190
+ warned = true;
5191
+ deps.emit({
5192
+ indexName,
5193
+ heapUsed: heap.usedBytes,
5194
+ heapLimit: heap.limitBytes,
5195
+ estimatedMemoryBytes: deps.estimateIndexBytes(indexName)
5196
+ });
5197
+ }
5198
+ return { check };
5199
+ }
5200
+
5201
+ // src/engine/watermark.ts
5202
+ function createWatermarkNotifier(deps) {
5203
+ const latchedCapacity = /* @__PURE__ */ new Map();
5204
+ function check(indexName) {
5205
+ const partitionConfig = deps.getPartitionConfig(indexName);
5206
+ const maxDocs = partitionConfig?.maxDocsPerPartition;
5207
+ const watermark = partitionConfig?.watermark;
5208
+ if (maxDocs === void 0 || watermark === void 0) {
5209
+ latchedCapacity.delete(indexName);
5210
+ return;
5211
+ }
5212
+ const manager = deps.getManager(indexName);
5213
+ if (!manager) {
5214
+ latchedCapacity.delete(indexName);
5215
+ return;
5216
+ }
5217
+ const partitionCount = manager.partitionCount;
5218
+ const capacity = maxDocs * partitionCount;
5219
+ const documentCount = manager.countDocuments();
5220
+ const threshold = watermark * capacity;
5221
+ if (documentCount < threshold) {
5222
+ latchedCapacity.delete(indexName);
5223
+ return;
5224
+ }
5225
+ if (latchedCapacity.get(indexName) === capacity) return;
5226
+ latchedCapacity.set(indexName, capacity);
5227
+ deps.emit({ indexName, documentCount, capacity, partitionCount });
5228
+ }
5229
+ function forget(indexName) {
5230
+ latchedCapacity.delete(indexName);
5231
+ }
5232
+ return { check, forget };
5233
+ }
5234
+
5235
+ // src/engine/notifiers.ts
5236
+ function wireWatermarkNotifier(wiring) {
5237
+ return createWatermarkNotifier({
5238
+ getManager: wiring.getManager,
5239
+ getPartitionConfig: (indexName) => wiring.indexRegistry.get(indexName)?.config.partitions,
5240
+ emit: (payload) => emitEngineEvent(wiring.eventHandlers, "partitionWatermark", payload)
5241
+ });
5242
+ }
5243
+ function warnUnheardHeapPressure(payload) {
5244
+ console.warn(
5245
+ `Heap at ${payload.heapUsed} of ${payload.heapLimit} bytes after index "${payload.indexName}"; raise --max-old-space-size-percentage or close an idle index`
5246
+ );
5247
+ }
5248
+ function wireHeapPressureNotifier(wiring) {
5249
+ return createHeapPressureNotifier({
5250
+ readHeap: readHeapStatistics,
5251
+ estimateIndexBytes: (indexName) => wiring.getManager(indexName)?.estimateMemoryBytes() ?? 0,
5252
+ emit: (payload) => {
5253
+ if (emitEngineEvent(wiring.eventHandlers, "heapPressure", payload) === 0) warnUnheardHeapPressure(payload);
5254
+ }
5255
+ });
5256
+ }
5257
+ function checkHeapAfterRecovery(core) {
5258
+ const sized = [...core.indexRegistry.keys()].map((indexName) => ({
5259
+ indexName,
5260
+ bytes: core.executor.getManager(indexName)?.estimateMemoryBytes() ?? 0
5261
+ }));
5262
+ sized.sort((a, b) => b.bytes - a.bytes);
5263
+ for (const { indexName } of sized) core.heapPressureNotifier.check(indexName);
5264
+ }
5265
+
5168
5266
  // src/core/partition/frozen/share.ts
5169
5267
  function shareFrozenSegment(segment) {
5170
5268
  if (segment.sharedSnapshot !== null) {
@@ -5245,10 +5343,17 @@ async function sendPartition(indexName, executors, captured) {
5245
5343
  )
5246
5344
  );
5247
5345
  }
5248
- async function transferIndexToPool(indexName, pool, config, manager) {
5249
- await loadIndexOntoWorkers(indexName, pool.getAllExecutors(), config, manager, () => pool.addIndexToAll(indexName));
5346
+ async function transferIndexToPool(indexName, pool, config, manager, analysisStale = false) {
5347
+ await loadIndexOntoWorkers(
5348
+ indexName,
5349
+ pool.getAllExecutors(),
5350
+ config,
5351
+ manager,
5352
+ () => pool.addIndexToAll(indexName),
5353
+ analysisStale
5354
+ );
5250
5355
  }
5251
- async function loadIndexOntoWorkers(indexName, executors, config, manager, onDropped) {
5356
+ async function loadIndexOntoWorkers(indexName, executors, config, manager, onDropped, analysisStale = false) {
5252
5357
  const captured = capturePartitions(manager);
5253
5358
  await Promise.allSettled(
5254
5359
  executors.map(
@@ -5262,7 +5367,8 @@ async function loadIndexOntoWorkers(indexName, executors, config, manager, onDro
5262
5367
  type: "createIndex",
5263
5368
  indexName,
5264
5369
  config,
5265
- requestId: `resync-create-${indexName}`
5370
+ requestId: `resync-create-${indexName}`,
5371
+ analysisStale
5266
5372
  })
5267
5373
  )
5268
5374
  );
@@ -5519,15 +5625,37 @@ function queueForCopies(state, action) {
5519
5625
  if (state.workerPool === null || !state.scaledOutIndexes.has(action.indexName)) return;
5520
5626
  enqueueReplication(state, action.indexName, action);
5521
5627
  }
5628
+ function settled(queue) {
5629
+ return Promise.all([...queue.inFlight, queue.tail]).then(() => void 0);
5630
+ }
5522
5631
  async function awaitReplicationIdle(state, indexName) {
5523
5632
  if (indexName !== void 0) {
5524
5633
  const queue = state.replicationQueues.get(indexName);
5525
- if (queue !== void 0) await queue.tail;
5634
+ if (queue !== void 0) await settled(queue);
5526
5635
  return;
5527
5636
  }
5528
5637
  while (state.replicationQueues.size > 0) {
5529
- const tails = Array.from(state.replicationQueues.values(), (queue) => queue.tail);
5530
- await Promise.all(tails);
5638
+ await Promise.all(Array.from(state.replicationQueues.values(), settled));
5639
+ }
5640
+ }
5641
+ async function awaitWritesApplied(state, indexName) {
5642
+ let awaitedTransition;
5643
+ let awaitedRepair = null;
5644
+ for (; ; ) {
5645
+ const transition = state.copyTransitions.get(indexName);
5646
+ if (transition !== void 0 && transition !== awaitedTransition) {
5647
+ awaitedTransition = transition;
5648
+ await transition.done;
5649
+ continue;
5650
+ }
5651
+ const repair = state.poolRepair;
5652
+ if (repair !== null && repair !== awaitedRepair) {
5653
+ awaitedRepair = repair;
5654
+ await repair;
5655
+ continue;
5656
+ }
5657
+ await awaitReplicationIdle(state, indexName);
5658
+ return;
5531
5659
  }
5532
5660
  }
5533
5661
 
@@ -5770,7 +5898,7 @@ function createWorkerExecutor(worker, config) {
5770
5898
  });
5771
5899
  worker.addEventListener("error", (event) => handleDeath(errorFromEventLike(event)));
5772
5900
  }
5773
- function execute(action) {
5901
+ function execute(action, transfer) {
5774
5902
  if (deathError !== null) {
5775
5903
  return Promise.reject(deathError);
5776
5904
  }
@@ -5795,7 +5923,8 @@ function createWorkerExecutor(worker, config) {
5795
5923
  reject,
5796
5924
  timeoutId
5797
5925
  });
5798
- worker.postMessage(taggedAction);
5926
+ if (transfer === void 0) worker.postMessage(taggedAction);
5927
+ else worker.postMessage(taggedAction, transfer);
5799
5928
  });
5800
5929
  }
5801
5930
  async function shutdown() {
@@ -5844,9 +5973,10 @@ async function createWorkerFactory(entryPoint) {
5844
5973
  const resolvedEntry = resolveEntryPoint();
5845
5974
  if (runtime.supportsWorkerThreads) {
5846
5975
  const workerThreadsModule = await import('worker_threads');
5847
- return function nodeFactory(_workerId, onDeath) {
5976
+ return function nodeFactory(workerId, onDeath) {
5848
5977
  const instance = new workerThreadsModule.Worker(new URL(resolvedEntry), {
5849
- resourceLimits: WORKER_RESOURCE_LIMITS
5978
+ resourceLimits: WORKER_RESOURCE_LIMITS,
5979
+ workerData: { workerId }
5850
5980
  });
5851
5981
  return createWorkerExecutor(instance, { onDeath });
5852
5982
  };
@@ -5860,6 +5990,105 @@ async function createWorkerFactory(entryPoint) {
5860
5990
  throw new NarsilError(ErrorCodes.WORKER_CRASHED, `No worker support available on runtime "${runtime.runtime}"`);
5861
5991
  }
5862
5992
 
5993
+ // src/engine/orchestration/vector-copies.ts
5994
+ function holdsIndex(state, indexName) {
5995
+ return state.scaledOutIndexes.has(indexName) || state.copyLoadBuffers.has(indexName);
5996
+ }
5997
+ function sharedCopyHostOf(state) {
5998
+ return {
5999
+ get scratchSlotCount() {
6000
+ return state.workerPool?.workerCount ?? state.keywordWorkerCount;
6001
+ },
6002
+ holdsIndex: (indexName) => holdsIndex(state, indexName),
6003
+ resolvePartition: (indexName, docId) => state.executor.getManager(indexName)?.partitionIdOf(docId),
6004
+ async loadShared(indexName, fieldName, handle, copy) {
6005
+ if (!holdsIndex(state, indexName)) return false;
6006
+ await replicateToWorkers(state, {
6007
+ type: "loadVectorCopy",
6008
+ indexName,
6009
+ fieldName,
6010
+ handle,
6011
+ copy,
6012
+ requestId: createRequestId()
6013
+ });
6014
+ await awaitWritesApplied(state, indexName);
6015
+ return state.scaledOutIndexes.has(indexName);
6016
+ },
6017
+ async drop(indexName, fieldName, handle) {
6018
+ if (!holdsIndex(state, indexName)) return;
6019
+ await replicateToWorkers(state, {
6020
+ type: "dropVectorCopy",
6021
+ indexName,
6022
+ fieldName,
6023
+ handle,
6024
+ requestId: createRequestId()
6025
+ });
6026
+ }
6027
+ };
6028
+ }
6029
+ function refreshVectorCopies(state, indexName) {
6030
+ if (state.vectorCopyPolicy?.host === void 0) return;
6031
+ const manager = state.executor.getManager(indexName);
6032
+ if (manager === void 0) return;
6033
+ for (const vectorIndex of manager.getVectorIndexes().values()) vectorIndex.refreshWorkerCopies();
6034
+ }
6035
+
6036
+ // src/engine/orchestration/request-threads.ts
6037
+ async function announceRequestThreads(state, pool) {
6038
+ const listener = state.requestThreads;
6039
+ if (listener === null) return;
6040
+ for (const { workerId, executor } of pool.executorEntries()) {
6041
+ await listener.onWorkerReady(workerId, executor);
6042
+ }
6043
+ }
6044
+ function scheduleRequestThreadPoolRestart(state) {
6045
+ if (state.requestThreads === null) return;
6046
+ const timer = setTimeout(
6047
+ () => {
6048
+ if (state.requestThreads === null || state.workerPool !== null) return;
6049
+ void ensurePool(state).then(() => scaleOutReadyIndexes(state)).catch((err) => {
6050
+ console.warn("Restarting the request threads failed:", err);
6051
+ scheduleRequestThreadPoolRestart(state);
6052
+ });
6053
+ },
6054
+ Math.max(0, state.poolRetryAt - Date.now())
6055
+ );
6056
+ if (typeof timer.unref === "function") timer.unref();
6057
+ }
6058
+ async function serveRequestsOnWorkers(state, listener) {
6059
+ state.requestThreads = listener;
6060
+ state.keywordWorkerCount = resolveRequestThreadCount(state.config?.workers?.count);
6061
+ if (state.vectorCopyPolicy !== void 0) {
6062
+ state.vectorCopyPolicy.host = sharedCopyHostOf(state);
6063
+ state.vectorCopyPolicy.enabled = state.workersEnabled;
6064
+ }
6065
+ if (state.poolStart !== null) await state.poolStart.catch(() => void 0);
6066
+ const running = state.workerPool;
6067
+ if (running !== null && running.workerCount !== state.keywordWorkerCount) {
6068
+ retirePool(state, running);
6069
+ state.poolRetryAt = 0;
6070
+ }
6071
+ const pool = await ensurePool(state);
6072
+ for (const indexName of state.indexRegistry.keys()) {
6073
+ refreshVectorCopies(state, indexName);
6074
+ if (indexReadyForCopies(state, indexName)) {
6075
+ await scaleOutIndex(state, indexName, copyThresholdReason(state, indexName));
6076
+ }
6077
+ }
6078
+ return pool.workerCount;
6079
+ }
6080
+ function requestThreadCountOf(state) {
6081
+ return resolveRequestThreadCount(state.config?.workers?.count);
6082
+ }
6083
+ function stopRequestThreads(state) {
6084
+ const listener = state.requestThreads;
6085
+ if (listener === null) return;
6086
+ state.requestThreads = null;
6087
+ const pool = state.workerPool;
6088
+ if (pool === null) return;
6089
+ for (const { workerId } of pool.executorEntries()) listener.onWorkerGone(workerId);
6090
+ }
6091
+
5863
6092
  // src/engine/orchestration/repair.ts
5864
6093
  var COPY_RESTART_REASON = "A request arrived after every worker crashed and the restart delay passed";
5865
6094
  function nextRestartDelay(state) {
@@ -5878,10 +6107,13 @@ function retirePool(state, pool) {
5878
6107
  for (const indexName of state.scaledOutIndexes) state.droppedCopies.set(indexName, COPY_RESTART_REASON);
5879
6108
  state.scaledOutIndexes.clear();
5880
6109
  state.segmentLedger.clear();
6110
+ for (const { workerId } of pool.executorEntries()) state.requestThreads?.onWorkerGone(workerId);
5881
6111
  void pool.shutdown().catch(() => void 0);
6112
+ scheduleRequestThreadPoolRestart(state);
5882
6113
  }
5883
6114
  function handleWorkerCrash(state, pool, workerId, indexNames, error) {
5884
6115
  state.callbacks?.onWorkerCrash?.(workerId, indexNames, error);
6116
+ state.requestThreads?.onWorkerGone(workerId);
5885
6117
  if (pool.getAllExecutors().length === 0) {
5886
6118
  retirePool(state, pool);
5887
6119
  return;
@@ -5909,12 +6141,20 @@ async function loadOntoReplacement(state, replacement, indexName) {
5909
6141
  const buffered = [];
5910
6142
  state.copyLoadBuffers.set(indexName, buffered);
5911
6143
  try {
5912
- await loadIndexOntoWorkers(indexName, [replacement.executor], entry.config, manager);
6144
+ await loadIndexOntoWorkers(
6145
+ indexName,
6146
+ [replacement.executor],
6147
+ entry.config,
6148
+ manager,
6149
+ void 0,
6150
+ state.callbacks?.isAnalysisStale?.(indexName)
6151
+ );
5913
6152
  replacement.hold(indexName);
5914
6153
  } finally {
5915
6154
  state.copyLoadBuffers.delete(indexName);
5916
6155
  for (const action of buffered) enqueueReplication(state, indexName, action);
5917
6156
  }
6157
+ refreshVectorCopies(state, indexName);
5918
6158
  }
5919
6159
  async function replaceWorker(state, pool, workerId) {
5920
6160
  const replacement = pool.spawnReplacement(workerId);
@@ -5931,6 +6171,7 @@ async function replaceWorker(state, pool, workerId) {
5931
6171
  await loadOntoReplacement(state, replacement, indexName);
5932
6172
  }
5933
6173
  replacement.admit();
6174
+ await state.requestThreads?.onWorkerReady(workerId, replacement.executor);
5934
6175
  } catch (err) {
5935
6176
  replacement.abandon();
5936
6177
  throw err;
@@ -5960,7 +6201,7 @@ function repairPool(state, pool) {
5960
6201
  // src/engine/orchestration/scale-out.ts
5961
6202
  var COPY_RELOAD_REASON = "A request arrived after an idle spell dropped the worker copies";
5962
6203
  function copiesAllowed(state) {
5963
- if (!state.workersEnabled || state.scaleOutBlocked) return false;
6204
+ if (!state.workersEnabled || state.scaleOutBlocked || state.shuttingDown) return false;
5964
6205
  return state.workerPool !== null || Date.now() >= state.poolRetryAt;
5965
6206
  }
5966
6207
  async function startPool(state) {
@@ -5995,9 +6236,15 @@ async function ensurePool(state) {
5995
6236
  if (state.workerPool !== null) return state.workerPool;
5996
6237
  if (state.poolStart === null) {
5997
6238
  state.poolStart = startPool(state).then(
5998
- (pool) => {
6239
+ async (pool) => {
5999
6240
  state.workerPool = pool;
6000
6241
  state.poolStart = null;
6242
+ try {
6243
+ await announceRequestThreads(state, pool);
6244
+ } catch (err) {
6245
+ retirePool(state, pool);
6246
+ throw err;
6247
+ }
6001
6248
  return pool;
6002
6249
  },
6003
6250
  (err) => {
@@ -6046,7 +6293,7 @@ async function loadCopies(state, indexName, reason) {
6046
6293
  const buffered = [];
6047
6294
  state.copyLoadBuffers.set(indexName, buffered);
6048
6295
  try {
6049
- await transferIndexToPool(indexName, pool, entry.config, manager);
6296
+ await transferIndexToPool(indexName, pool, entry.config, manager, state.callbacks?.isAnalysisStale?.(indexName));
6050
6297
  state.scaledOutIndexes.add(indexName);
6051
6298
  state.lastAccessAt.set(indexName, Date.now());
6052
6299
  state.poolRetryDelayMs = POOL_RESTART_DELAY_MS;
@@ -6058,6 +6305,7 @@ async function loadCopies(state, indexName, reason) {
6058
6305
  state.copyLoadBuffers.delete(indexName);
6059
6306
  }
6060
6307
  if (reload) state.copyReloadCounts.set(indexName, (state.copyReloadCounts.get(indexName) ?? 0) + 1);
6308
+ refreshVectorCopies(state, indexName);
6061
6309
  scheduleIdleMerge(state, indexName);
6062
6310
  state.callbacks?.onCopiesLoaded?.(pool.workerCount, reason);
6063
6311
  }
@@ -6219,7 +6467,7 @@ async function runSplit(leases, scope, indexName, params, globalStats) {
6219
6467
  return mergeWorkerResults(results);
6220
6468
  }
6221
6469
  function takeMainCopyTurn(state) {
6222
- if (state.mainCopyTurnTaken) return false;
6470
+ if (state.mainCopyQueries === "none" || state.mainCopyTurnTaken) return false;
6223
6471
  state.mainCopyTurnTaken = true;
6224
6472
  afterCurrentTurn(() => {
6225
6473
  state.mainCopyTurnTaken = false;
@@ -6235,8 +6483,6 @@ async function searchViaWorker(state, indexName, params, globalStats, partitionI
6235
6483
  const pool = state.workerPool;
6236
6484
  if (!pool) return null;
6237
6485
  if (!state.scaledOutIndexes.has(indexName) || state.copyLoadBuffers.has(indexName)) return null;
6238
- const pendingReplication = state.replicationQueues.get(indexName);
6239
- if (pendingReplication !== void 0 && pendingReplication.pendingActions > 0) return null;
6240
6486
  const manager = state.executor.getManager(indexName);
6241
6487
  if (!manager) return null;
6242
6488
  if (answersFasterOnMainCopy(state, pool, manager)) return null;
@@ -6286,14 +6532,16 @@ function workersEnabledByDefault() {
6286
6532
  function copyIdleTimeoutBeforeClose(closeAfterMs) {
6287
6533
  return closeAfterMs === void 0 ? DEFAULT_COPY_IDLE_TIMEOUT_MS : Math.min(DEFAULT_COPY_IDLE_TIMEOUT_MS, closeAfterMs);
6288
6534
  }
6289
- function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6535
+ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks, vectorCopyPolicy) {
6290
6536
  const state = {
6291
6537
  config,
6292
6538
  executor,
6293
6539
  indexRegistry,
6294
6540
  callbacks,
6541
+ vectorCopyPolicy,
6295
6542
  workersEnabled: config?.workers?.enabled ?? workersEnabledByDefault(),
6296
6543
  keywordWorkerCount: splitWorkerBudget(resolveWorkerCount(config?.workers?.count)).keyword,
6544
+ requestThreads: null,
6297
6545
  copyThreshold: config?.workers?.promotionThreshold ?? DEFAULT_COPY_THRESHOLD,
6298
6546
  copyIdleTimeoutMs: config?.workers?.idleTimeoutMs ?? copyIdleTimeoutBeforeClose(config?.lifecycle?.idleTimeoutMs),
6299
6547
  bootstrapModule: config?.workers?.bootstrapModule,
@@ -6315,8 +6563,10 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6315
6563
  poolRetryDelayMs: POOL_RESTART_DELAY_MS,
6316
6564
  poolRepair: null,
6317
6565
  mainCopyTurnTaken: false,
6566
+ mainCopyQueries: config?.workers?.mainCopyQueries,
6318
6567
  repairTimer: null,
6319
6568
  scaleOutBlocked: false,
6569
+ shuttingDown: false,
6320
6570
  idleSweep: null
6321
6571
  };
6322
6572
  startIdleSweep(state);
@@ -6337,6 +6587,7 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6337
6587
  workerId: report.workerId,
6338
6588
  heapUsed: report.heapUsed,
6339
6589
  heapTotal: report.heapTotal,
6590
+ heapLimit: report.heapLimit,
6340
6591
  external: report.external
6341
6592
  }));
6342
6593
  }
@@ -6352,8 +6603,10 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6352
6603
  return reports;
6353
6604
  }
6354
6605
  async function shutdown() {
6606
+ state.shuttingDown = true;
6355
6607
  stopIdleSweep(state);
6356
6608
  cancelRepair(state);
6609
+ stopRequestThreads(state);
6357
6610
  for (const indexName of [...state.idleMergeTimers.keys()]) cancelIdleMerge(state, indexName);
6358
6611
  await Promise.allSettled([...state.copyTransitions.values()].map((transition) => transition.done));
6359
6612
  if (state.poolRepair !== null) await state.poolRepair;
@@ -6374,8 +6627,9 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6374
6627
  const manager = executor.getManager(indexName);
6375
6628
  if (pool !== null && entry !== void 0 && manager !== void 0) {
6376
6629
  try {
6377
- await transferIndexToPool(indexName, pool, entry.config, manager);
6630
+ await transferIndexToPool(indexName, pool, entry.config, manager, callbacks?.isAnalysisStale?.(indexName));
6378
6631
  state.scaledOutIndexes.add(indexName);
6632
+ refreshVectorCopies(state, indexName);
6379
6633
  } catch (repairError) {
6380
6634
  throw new NarsilError(
6381
6635
  ErrorCodes.WORKER_CRASHED,
@@ -6394,6 +6648,16 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6394
6648
  cause: failure.reason instanceof Error ? failure.reason.message : String(failure.reason)
6395
6649
  });
6396
6650
  }
6651
+ function withdrawCopies(indexName) {
6652
+ const pool = state.workerPool;
6653
+ if (pool === null) return;
6654
+ const holders = pool.executorsHolding(indexName);
6655
+ pool.removeIndex(indexName);
6656
+ state.segmentLedger.delete(indexName);
6657
+ void Promise.allSettled(
6658
+ holders.map((worker) => worker.execute({ type: "dropIndex", indexName, requestId: `desync-drop-${indexName}` }))
6659
+ );
6660
+ }
6397
6661
  async function closeIndex(indexName) {
6398
6662
  if (state.poolRepair !== null) await state.poolRepair;
6399
6663
  const transition = state.copyTransitions.get(indexName);
@@ -6431,6 +6695,7 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6431
6695
  scaleOutBeforeBatch: (indexName, incomingCount) => scaleOutBeforeBatch(state, indexName, incomingCount),
6432
6696
  replicateToWorkers: replicate,
6433
6697
  awaitReplication: (indexName) => awaitReplicationIdle(state, indexName),
6698
+ awaitWrites: (indexName) => awaitWritesApplied(state, indexName),
6434
6699
  awaitCompactions: () => awaitCompactions(state),
6435
6700
  openIndex,
6436
6701
  closeIndex,
@@ -6439,9 +6704,19 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6439
6704
  segmentBuildConcurrency: (indexName) => segmentBuildConcurrency(state, indexName),
6440
6705
  searchViaWorker: (indexName, params, globalStats, partitionIds) => searchViaWorker(state, indexName, params, globalStats, partitionIds),
6441
6706
  hasWorkerPool: () => state.workerPool !== null,
6707
+ mainCopyQueries: () => state.mainCopyQueries ?? "lone",
6708
+ shareMainThread: () => {
6709
+ if (state.mainCopyQueries === void 0) state.mainCopyQueries = "none";
6710
+ },
6711
+ serveRequestsOnWorkers: (listener) => serveRequestsOnWorkers(state, listener),
6712
+ requestThreadCount: () => requestThreadCountOf(state),
6713
+ copyIdleTimeoutMs: () => state.copyIdleTimeoutMs,
6714
+ stopRequestThreads: () => stopRequestThreads(state),
6442
6715
  desyncIndex: (indexName) => {
6443
6716
  state.desyncedIndexes.add(indexName);
6444
- return state.scaledOutIndexes.delete(indexName);
6717
+ const wasScaledOut = state.scaledOutIndexes.delete(indexName);
6718
+ if (wasScaledOut) withdrawCopies(indexName);
6719
+ return wasScaledOut;
6445
6720
  },
6446
6721
  resyncIndex,
6447
6722
  noteAccess: (indexName) => noteAccess(state, indexName),
@@ -6451,160 +6726,20 @@ function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
6451
6726
  };
6452
6727
  }
6453
6728
 
6454
- // src/engine/validation.ts
6455
- function now() {
6456
- if (typeof performance !== "undefined" && typeof performance.now === "function") {
6457
- return performance.now();
6458
- }
6459
- return Date.now();
6460
- }
6461
- function validateIndexName(name) {
6462
- if (!name || name.length === 0) {
6463
- throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
6464
- }
6465
- if (name.length > MAX_INDEX_NAME_LENGTH) {
6466
- throw new NarsilError(
6467
- ErrorCodes.INDEX_NOT_FOUND,
6468
- `Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`,
6469
- {
6470
- indexName: name,
6471
- length: name.length
6472
- }
6473
- );
6474
- }
6475
- if (!INDEX_NAME_PATTERN.test(name)) {
6476
- throw new NarsilError(
6477
- ErrorCodes.INDEX_NOT_FOUND,
6478
- `Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
6479
- { indexName: name }
6480
- );
6481
- }
6482
- if (name.includes("..")) {
6483
- throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index name "${name}" must not contain ".."`, { indexName: name });
6484
- }
6485
- }
6486
- function validateDocId(docId) {
6487
- if (!docId || docId.length === 0) {
6488
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
6489
- }
6490
- if (docId.length > MAX_DOC_ID_LENGTH) {
6491
- throw new NarsilError(
6492
- ErrorCodes.DOC_VALIDATION_FAILED,
6493
- `Document ID must not exceed ${MAX_DOC_ID_LENGTH} characters`,
6494
- {
6495
- docId,
6496
- length: docId.length
6497
- }
6498
- );
6499
- }
6500
- if (docId.includes("\0")) {
6501
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
6502
- }
6503
- }
6504
- function validatePartitionConfig(partitions) {
6505
- const { maxDocsPerPartition, maxPartitions, watermark } = partitions;
6506
- if (maxDocsPerPartition !== void 0 && (!Number.isInteger(maxDocsPerPartition) || maxDocsPerPartition < 1)) {
6507
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxDocsPerPartition must be a positive integer", {
6508
- maxDocsPerPartition
6509
- });
6510
- }
6511
- if (maxPartitions !== void 0 && (!Number.isInteger(maxPartitions) || maxPartitions < 1)) {
6512
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxPartitions must be a positive integer", {
6513
- maxPartitions
6514
- });
6515
- }
6516
- if (watermark !== void 0 && (typeof watermark !== "number" || !(watermark > 0) || watermark > 1)) {
6517
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.watermark must be above 0 and at most 1", {
6518
- watermark
6519
- });
6520
- }
6521
- }
6522
- function requirePositiveSafeInteger(field, value) {
6523
- if (value === void 0) return;
6524
- if (!Number.isSafeInteger(value) || value < 1) {
6525
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, `${field} must be a positive safe integer`, { field, value });
6526
- }
6527
- }
6528
- function validateWorkerConfig(workers, lifecycle) {
6529
- if (workers === void 0) return;
6530
- requirePositiveSafeInteger("workers.count", workers.count);
6531
- requirePositiveSafeInteger("workers.promotionThreshold", workers.promotionThreshold);
6532
- requirePositiveSafeInteger("workers.idleTimeoutMs", workers.idleTimeoutMs);
6533
- const closeAfter = lifecycle?.idleTimeoutMs;
6534
- if (workers.idleTimeoutMs !== void 0 && closeAfter !== void 0 && workers.idleTimeoutMs > closeAfter) {
6535
- throw new NarsilError(
6536
- ErrorCodes.CONFIG_INVALID,
6537
- "workers.idleTimeoutMs must be at most lifecycle.idleTimeoutMs, so that an index drops its copies before it closes",
6538
- { workersIdleTimeoutMs: workers.idleTimeoutMs, lifecycleIdleTimeoutMs: closeAfter }
6539
- );
6540
- }
6541
- }
6542
- function validateBM25Params(bm25) {
6543
- if (bm25 === void 0) return;
6544
- const { k1, b } = bm25;
6545
- if (k1 !== void 0 && (!Number.isFinite(k1) || k1 < 0)) {
6546
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.k1 must be a finite number at or above 0", { k1 });
6547
- }
6548
- if (b !== void 0 && (!Number.isFinite(b) || b < 0 || b > 1)) {
6549
- throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.b must be between 0 and 1", { b });
6550
- }
6551
- }
6552
- function clampLimit(limit) {
6553
- return clampRowCount(limit, DEFAULT_LIMIT);
6554
- }
6555
- function clampOffset(offset) {
6556
- return clampRowCount(offset, DEFAULT_OFFSET);
6557
- }
6558
-
6559
6729
  // src/engine/vector-fields.ts
6560
6730
  function getVectorFieldPaths(schema) {
6561
6731
  return new Set(extractVectorFieldsFromSchema(schema).keys());
6562
6732
  }
6563
6733
 
6564
- // src/engine/watermark.ts
6565
- function createWatermarkNotifier(deps) {
6566
- const latchedCapacity = /* @__PURE__ */ new Map();
6567
- function check(indexName) {
6568
- const partitionConfig = deps.getPartitionConfig(indexName);
6569
- const maxDocs = partitionConfig?.maxDocsPerPartition;
6570
- const watermark = partitionConfig?.watermark;
6571
- if (maxDocs === void 0 || watermark === void 0) {
6572
- latchedCapacity.delete(indexName);
6573
- return;
6574
- }
6575
- const manager = deps.getManager(indexName);
6576
- if (!manager) {
6577
- latchedCapacity.delete(indexName);
6578
- return;
6579
- }
6580
- const partitionCount = manager.partitionCount;
6581
- const capacity = maxDocs * partitionCount;
6582
- const documentCount = manager.countDocuments();
6583
- const threshold = watermark * capacity;
6584
- if (documentCount < threshold) {
6585
- latchedCapacity.delete(indexName);
6586
- return;
6587
- }
6588
- if (latchedCapacity.get(indexName) === capacity) return;
6589
- latchedCapacity.set(indexName, capacity);
6590
- deps.emit({ indexName, documentCount, capacity, partitionCount });
6591
- }
6592
- function forget(indexName) {
6593
- latchedCapacity.delete(indexName);
6594
- }
6595
- return { check, forget };
6596
- }
6597
-
6598
6734
  // src/engine/core.ts
6599
6735
  function createEngineCore(config, hooks) {
6600
6736
  validateWorkerConfig(config?.workers, config?.lifecycle);
6601
6737
  const vectorWorkerCount = splitWorkerBudget(resolveWorkerCount(config?.workers?.count)).vector;
6602
- const executor = createDirectExecutor({
6603
- vectorWorkerCopies: {
6604
- enabled: (config?.workers?.enabled ?? workersEnabledByDefault()) && vectorWorkerCount > 0,
6605
- count: vectorWorkerCount
6606
- }
6607
- });
6738
+ const vectorCopyPolicy = {
6739
+ enabled: (config?.workers?.enabled ?? workersEnabledByDefault()) && vectorWorkerCount > 0,
6740
+ count: vectorWorkerCount
6741
+ };
6742
+ const executor = createDirectExecutor({ vectorWorkerCopies: vectorCopyPolicy });
6608
6743
  const pluginRegistry = createPluginRegistry();
6609
6744
  if (config?.plugins) {
6610
6745
  for (const plugin of config.plugins) pluginRegistry.register(plugin);
@@ -6622,36 +6757,38 @@ function createEngineCore(config, hooks) {
6622
6757
  const shutdownState = { isShutdown: false };
6623
6758
  const abortController = new AbortController();
6624
6759
  const rebalancingIndexes = /* @__PURE__ */ new Set();
6625
- const orchestrator = createWorkerOrchestrator(config, executor, indexRegistry, {
6626
- shouldDeferCopies() {
6627
- return rebalancingIndexes.size > 0;
6628
- },
6629
- onCopiesLoaded(workerCount, reason) {
6630
- const handlers = eventHandlers.get("workerPromote");
6631
- if (handlers) {
6632
- for (const handler of handlers) handler({ workerCount, reason });
6633
- }
6634
- void Promise.resolve(pluginRegistry.runHook("onWorkerPromote", { workerCount, reason })).catch((err) => {
6635
- console.warn("onWorkerPromote plugin hook failed:", err instanceof Error ? err.message : String(err));
6636
- });
6637
- },
6638
- onCopyLoadFailure(reason, error, retryable) {
6639
- const handlers = eventHandlers.get("workerPromoteFailure");
6640
- if (!handlers || handlers.size === 0) {
6641
- console.warn(`Loading worker copies failed (${reason}):`, error);
6642
- return;
6760
+ const orchestrator = createWorkerOrchestrator(
6761
+ config,
6762
+ executor,
6763
+ indexRegistry,
6764
+ {
6765
+ shouldDeferCopies() {
6766
+ return rebalancingIndexes.size > 0;
6767
+ },
6768
+ isAnalysisStale(indexName) {
6769
+ return analysisRebuild.isStale(indexName);
6770
+ },
6771
+ onCopiesLoaded(workerCount, reason) {
6772
+ emitEngineEvent(eventHandlers, "workerPromote", { workerCount, reason });
6773
+ void Promise.resolve(pluginRegistry.runHook("onWorkerPromote", { workerCount, reason })).catch(
6774
+ (err) => {
6775
+ console.warn("onWorkerPromote plugin hook failed:", err instanceof Error ? err.message : String(err));
6776
+ }
6777
+ );
6778
+ },
6779
+ onCopyLoadFailure(reason, error, retryable) {
6780
+ if (emitEngineEvent(eventHandlers, "workerPromoteFailure", { reason, error, retryable }) === 0) {
6781
+ console.warn(`Loading worker copies failed (${reason}):`, error);
6782
+ }
6783
+ },
6784
+ onWorkerCrash(workerId, indexNames, error) {
6785
+ if (emitEngineEvent(eventHandlers, "workerCrash", { workerId, indexNames, error }) === 0) {
6786
+ console.warn(`Worker ${workerId} crashed:`, error);
6787
+ }
6643
6788
  }
6644
- for (const handler of handlers) handler({ reason, error, retryable });
6645
6789
  },
6646
- onWorkerCrash(workerId, indexNames, error) {
6647
- const handlers = eventHandlers.get("workerCrash");
6648
- if (!handlers || handlers.size === 0) {
6649
- console.warn(`Worker ${workerId} crashed:`, error);
6650
- return;
6651
- }
6652
- for (const handler of handlers) handler({ workerId, indexNames, error });
6653
- }
6654
- });
6790
+ vectorCopyPolicy
6791
+ );
6655
6792
  const rebalancer = createRebalancer();
6656
6793
  const rebalanceRouter = createPartitionRouter();
6657
6794
  const waqMap = /* @__PURE__ */ new Map();
@@ -6760,10 +6897,7 @@ function createEngineCore(config, hooks) {
6760
6897
  indexRegistry,
6761
6898
  createIndexFromMetadata,
6762
6899
  emitFatalError(error) {
6763
- const handlers = eventHandlers.get("durabilityError");
6764
- if (handlers) {
6765
- for (const handler of handlers) handler({ error });
6766
- }
6900
+ emitEngineEvent(eventHandlers, "durabilityError", { error });
6767
6901
  },
6768
6902
  publishCheckpointedPartitions: (indexName, partitions) => invalidation?.publishPartitions(indexName, partitions) ?? Promise.resolve(),
6769
6903
  recordCheckpoint(indexName, documentCount, partitionCount) {
@@ -6786,29 +6920,18 @@ function createEngineCore(config, hooks) {
6786
6920
  },
6787
6921
  reloadIndex: (indexName) => durability?.manager.reloadIndex?.(indexName) ?? Promise.resolve(),
6788
6922
  onError(error) {
6789
- const handlers = eventHandlers.get("invalidationError");
6790
- if (!handlers || handlers.size === 0) {
6923
+ if (emitEngineEvent(eventHandlers, "invalidationError", { error }) === 0) {
6791
6924
  console.warn("Invalidation error:", error);
6792
- return;
6793
- }
6794
- for (const handler of handlers) handler({ error });
6795
- }
6796
- });
6797
- const watermarkNotifier = createWatermarkNotifier({
6798
- getManager: (indexName) => executor.getManager(indexName),
6799
- getPartitionConfig: (indexName) => indexRegistry.get(indexName)?.config.partitions,
6800
- emit(payload) {
6801
- const handlers = eventHandlers.get("partitionWatermark");
6802
- if (!handlers) return;
6803
- for (const handler of handlers) {
6804
- try {
6805
- handler(payload);
6806
- } catch (err) {
6807
- console.warn("partitionWatermark handler error:", err instanceof Error ? err.message : String(err));
6808
- }
6809
6925
  }
6810
6926
  }
6811
6927
  });
6928
+ const notifierWiring = {
6929
+ eventHandlers,
6930
+ indexRegistry,
6931
+ getManager: (indexName) => executor.getManager(indexName)
6932
+ };
6933
+ const watermarkNotifier = wireWatermarkNotifier(notifierWiring);
6934
+ const heapPressureNotifier = wireHeapPressureNotifier(notifierWiring);
6812
6935
  const analysisRebuild = wireAnalysisRebuild({
6813
6936
  config: config?.analysis,
6814
6937
  eventHandlers,
@@ -6827,7 +6950,10 @@ function createEngineCore(config, hooks) {
6827
6950
  rebalancingIndexes,
6828
6951
  requireManager,
6829
6952
  onClose: hooks?.onIndexClose,
6830
- onOpen: hooks?.onIndexOpen,
6953
+ onOpen: (indexName) => {
6954
+ heapPressureNotifier.check(indexName);
6955
+ return hooks?.onIndexOpen?.(indexName);
6956
+ },
6831
6957
  onAccess: (indexName) => orchestrator.noteAccess(indexName)
6832
6958
  });
6833
6959
  const mutationCtx = {
@@ -6841,11 +6967,13 @@ function createEngineCore(config, hooks) {
6841
6967
  requireIndex,
6842
6968
  requireManager,
6843
6969
  bufferIfRebalancing,
6970
+ awaitRebalanceReplay: (indexName) => waqMap.get(indexName)?.whenReplayed() ?? Promise.resolve(),
6844
6971
  isRebalancing: (indexName) => rebalancingIndexes.has(indexName),
6845
6972
  pendingRebalanceWrites: (indexName) => waqMap.get(indexName)?.size ?? 0,
6846
6973
  rebalanceTargetPartitionCount: (indexName) => rebalanceTargets.get(indexName),
6847
6974
  bufferedDocState: (indexName, docId) => waqMap.get(indexName)?.bufferedDocState(docId),
6848
- checkWatermark: watermarkNotifier.check
6975
+ checkWatermark: watermarkNotifier.check,
6976
+ checkHeapPressure: heapPressureNotifier.check
6849
6977
  };
6850
6978
  const rebalanceCtx = {
6851
6979
  rebalancer,
@@ -6881,6 +7009,7 @@ function createEngineCore(config, hooks) {
6881
7009
  requireManager,
6882
7010
  bufferIfRebalancing,
6883
7011
  watermarkNotifier,
7012
+ heapPressureNotifier,
6884
7013
  analysisRebuild,
6885
7014
  indexState,
6886
7015
  mutationCtx,
@@ -7391,6 +7520,12 @@ async function embedChunkDocuments(entry, documents, chunkStart, chunkEnd, signa
7391
7520
  }
7392
7521
  }
7393
7522
 
7523
+ // src/engine/mutations/write-visibility.ts
7524
+ async function awaitWriteVisibility(ctx, indexName) {
7525
+ await ctx.awaitRebalanceReplay(indexName);
7526
+ await ctx.orchestrator.awaitWrites(indexName);
7527
+ }
7528
+
7394
7529
  // src/engine/mutations/insert.ts
7395
7530
  async function insertDocument(ctx, indexName, document, docId, options) {
7396
7531
  ctx.guardShutdown();
@@ -7476,6 +7611,8 @@ async function insertDocument(ctx, indexName, document, docId, options) {
7476
7611
  }
7477
7612
  if (buffered) {
7478
7613
  ctx.checkWatermark(indexName);
7614
+ ctx.checkHeapPressure(indexName);
7615
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
7479
7616
  return resolvedDocId;
7480
7617
  }
7481
7618
  try {
@@ -7498,7 +7635,9 @@ async function insertDocument(ctx, indexName, document, docId, options) {
7498
7635
  }
7499
7636
  }
7500
7637
  ctx.checkWatermark(indexName);
7638
+ ctx.checkHeapPressure(indexName);
7501
7639
  await ctx.orchestrator.scaleOutReadyIndexes();
7640
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
7502
7641
  return resolvedDocId;
7503
7642
  }
7504
7643
 
@@ -7911,7 +8050,9 @@ async function insertBatchViaSegments(ctx, indexName, documents, options) {
7911
8050
  vecIndexes.get(fieldPath)?.scheduleBuild();
7912
8051
  }
7913
8052
  ctx.checkWatermark(indexName);
8053
+ ctx.checkHeapPressure(indexName);
7914
8054
  await ctx.orchestrator.scaleOutReadyIndexes();
8055
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
7915
8056
  return { succeeded: outcome.succeeded, failed };
7916
8057
  }
7917
8058
 
@@ -8073,12 +8214,14 @@ async function insertDocumentBatch(ctx, indexName, documents, options) {
8073
8214
  }
8074
8215
  }
8075
8216
  ctx.checkWatermark(indexName);
8217
+ ctx.checkHeapPressure(indexName);
8076
8218
  await ctx.orchestrator.scaleOutReadyIndexes();
8219
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8077
8220
  return { succeeded, failed };
8078
8221
  }
8079
8222
 
8080
8223
  // src/engine/mutations/remove.ts
8081
- async function removeDocument(ctx, indexName, docId) {
8224
+ async function removeDocument(ctx, indexName, docId, options) {
8082
8225
  ctx.guardShutdown();
8083
8226
  ctx.requireIndex(indexName);
8084
8227
  validateDocId(docId);
@@ -8117,6 +8260,7 @@ async function removeDocument(ctx, indexName, docId) {
8117
8260
  await applyRemove();
8118
8261
  }
8119
8262
  if (buffered) {
8263
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8120
8264
  return;
8121
8265
  }
8122
8266
  try {
@@ -8130,8 +8274,9 @@ async function removeDocument(ctx, indexName, docId) {
8130
8274
  docId,
8131
8275
  requestId: `replicate-remove-${docId}`
8132
8276
  });
8277
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8133
8278
  }
8134
- async function removeDocumentBatch(ctx, indexName, docIds) {
8279
+ async function removeDocumentBatch(ctx, indexName, docIds, options) {
8135
8280
  ctx.guardShutdown();
8136
8281
  ctx.requireIndex(indexName);
8137
8282
  const succeeded = [];
@@ -8159,6 +8304,7 @@ async function removeDocumentBatch(ctx, indexName, docIds) {
8159
8304
  } finally {
8160
8305
  manager.endBatchRemove();
8161
8306
  }
8307
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8162
8308
  return { succeeded, failed };
8163
8309
  }
8164
8310
 
@@ -8176,7 +8322,7 @@ function prepareUpdatePartitionDoc(document, extractedVectors) {
8176
8322
  }
8177
8323
  return { partitionDoc };
8178
8324
  }
8179
- async function updateDocument(ctx, indexName, docId, document) {
8325
+ async function updateDocument(ctx, indexName, docId, document, options) {
8180
8326
  ctx.guardShutdown();
8181
8327
  const entry = ctx.requireIndex(indexName);
8182
8328
  validateDocId(docId);
@@ -8278,6 +8424,8 @@ async function updateDocument(ctx, indexName, docId, document) {
8278
8424
  await applyUpdate();
8279
8425
  }
8280
8426
  if (buffered) {
8427
+ ctx.checkHeapPressure(indexName);
8428
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8281
8429
  return;
8282
8430
  }
8283
8431
  try {
@@ -8304,8 +8452,10 @@ async function updateDocument(ctx, indexName, docId, document) {
8304
8452
  vecIndex.scheduleBuild();
8305
8453
  }
8306
8454
  }
8455
+ ctx.checkHeapPressure(indexName);
8456
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8307
8457
  }
8308
- async function updateDocumentBatch(ctx, indexName, updates) {
8458
+ async function updateDocumentBatch(ctx, indexName, updates, options) {
8309
8459
  ctx.guardShutdown();
8310
8460
  const entry = ctx.requireIndex(indexName);
8311
8461
  const succeeded = [];
@@ -8344,6 +8494,7 @@ async function updateDocumentBatch(ctx, indexName, updates) {
8344
8494
  vecIndex.scheduleBuild();
8345
8495
  }
8346
8496
  }
8497
+ if (options?.wait === true) await awaitWriteVisibility(ctx, indexName);
8347
8498
  return { succeeded, failed };
8348
8499
  }
8349
8500
 
@@ -8352,6 +8503,10 @@ function createWriteAheadQueue(maxSize = DEFAULT_WRITE_AHEAD_QUEUE_MAX_SIZE) {
8352
8503
  const entries = [];
8353
8504
  const docStates = /* @__PURE__ */ new Map();
8354
8505
  let nextSequence = 1;
8506
+ let resolveReplayed = () => void 0;
8507
+ const replayed = new Promise((resolve) => {
8508
+ resolveReplayed = resolve;
8509
+ });
8355
8510
  return {
8356
8511
  push(entry) {
8357
8512
  if (entries.length >= maxSize) {
@@ -8378,6 +8533,12 @@ function createWriteAheadQueue(maxSize = DEFAULT_WRITE_AHEAD_QUEUE_MAX_SIZE) {
8378
8533
  bufferedDocState(docId) {
8379
8534
  return docStates.get(docId);
8380
8535
  },
8536
+ whenReplayed() {
8537
+ return replayed;
8538
+ },
8539
+ markReplayed() {
8540
+ resolveReplayed();
8541
+ },
8381
8542
  get size() {
8382
8543
  return entries.length;
8383
8544
  },
@@ -8551,12 +8712,11 @@ async function executeRebalance(manager, indexName, targetPartitionCount, ctx) {
8551
8712
  const oldCount = manager.partitionCount;
8552
8713
  await ctx.rebalancer.rebalance(manager, targetPartitionCount, ctx.router, (progress) => {
8553
8714
  if (progress.phase === "complete") {
8554
- const handlers = ctx.eventHandlers.get("partitionRebalance");
8555
- if (handlers) {
8556
- for (const handler of handlers) {
8557
- handler({ indexName, oldCount, newCount: targetPartitionCount });
8558
- }
8559
- }
8715
+ emitEngineEvent(ctx.eventHandlers, "partitionRebalance", {
8716
+ indexName,
8717
+ oldCount,
8718
+ newCount: targetPartitionCount
8719
+ });
8560
8720
  }
8561
8721
  });
8562
8722
  try {
@@ -8596,6 +8756,7 @@ async function executeRebalance(manager, indexName, targetPartitionCount, ctx) {
8596
8756
  ctx.rebalancingIndexes.delete(indexName);
8597
8757
  ctx.rebalanceTargets.delete(indexName);
8598
8758
  ctx.waqMap.delete(indexName);
8759
+ waq.markReplayed();
8599
8760
  }
8600
8761
  ctx.checkWatermark(indexName);
8601
8762
  }
@@ -9023,6 +9184,7 @@ function readProcessMemory() {
9023
9184
  return {
9024
9185
  heapUsed: usage.heapUsed,
9025
9186
  heapTotal: usage.heapTotal,
9187
+ heapLimit: readHeapStatistics()?.limitBytes ?? null,
9026
9188
  external: usage.external,
9027
9189
  rss: usage.rss
9028
9190
  };
@@ -9031,1204 +9193,6 @@ function readProcessMemory() {
9031
9193
  }
9032
9194
  }
9033
9195
 
9034
- // src/search/cursor-binding.ts
9035
- var ABSENT_TAG = 0;
9036
- var NULL_TAG = 1;
9037
- var FALSE_TAG = 2;
9038
- var TRUE_TAG = 3;
9039
- var NUMBER_TAG = 4;
9040
- var STRING_TAG = 5;
9041
- var LIST_TAG = 6;
9042
- var MAP_TAG = 7;
9043
- var encoder = new TextEncoder();
9044
- function ensureCapacity(stream, extra) {
9045
- if (stream.length + extra <= stream.bytes.length) return;
9046
- const grown = new Uint8Array(Math.max(stream.bytes.length * 2, stream.length + extra));
9047
- grown.set(stream.bytes.subarray(0, stream.length));
9048
- stream.bytes = grown;
9049
- }
9050
- function writeByte(stream, value) {
9051
- ensureCapacity(stream, 1);
9052
- stream.bytes[stream.length] = value;
9053
- stream.length += 1;
9054
- }
9055
- function writeUint32(stream, value) {
9056
- ensureCapacity(stream, 4);
9057
- stream.bytes[stream.length] = value >>> 24 & 255;
9058
- stream.bytes[stream.length + 1] = value >>> 16 & 255;
9059
- stream.bytes[stream.length + 2] = value >>> 8 & 255;
9060
- stream.bytes[stream.length + 3] = value & 255;
9061
- stream.length += 4;
9062
- }
9063
- var numberBytes = new DataView(new ArrayBuffer(8));
9064
- function writeNumber(stream, value) {
9065
- numberBytes.setFloat64(0, value, false);
9066
- ensureCapacity(stream, 8);
9067
- for (let i = 0; i < 8; i++) {
9068
- stream.bytes[stream.length + i] = numberBytes.getUint8(i);
9069
- }
9070
- stream.length += 8;
9071
- }
9072
- function writeString(stream, value) {
9073
- const encoded = encoder.encode(value);
9074
- writeUint32(stream, encoded.length);
9075
- ensureCapacity(stream, encoded.length);
9076
- stream.bytes.set(encoded, stream.length);
9077
- stream.length += encoded.length;
9078
- }
9079
- function writeValue(stream, value) {
9080
- if (value === void 0) {
9081
- writeByte(stream, ABSENT_TAG);
9082
- return;
9083
- }
9084
- if (value === null) {
9085
- writeByte(stream, NULL_TAG);
9086
- return;
9087
- }
9088
- if (typeof value === "boolean") {
9089
- writeByte(stream, value ? TRUE_TAG : FALSE_TAG);
9090
- return;
9091
- }
9092
- if (typeof value === "number") {
9093
- writeByte(stream, NUMBER_TAG);
9094
- writeNumber(stream, value);
9095
- return;
9096
- }
9097
- if (typeof value === "string") {
9098
- writeByte(stream, STRING_TAG);
9099
- writeString(stream, value);
9100
- return;
9101
- }
9102
- if (Array.isArray(value)) {
9103
- writeByte(stream, LIST_TAG);
9104
- writeUint32(stream, value.length);
9105
- for (const member of value) {
9106
- writeValue(stream, member ?? null);
9107
- }
9108
- return;
9109
- }
9110
- const record = value;
9111
- const keys = Object.keys(record).sort(compareCodePoints);
9112
- writeByte(stream, MAP_TAG);
9113
- writeUint32(stream, keys.length);
9114
- for (const key of keys) {
9115
- writeByte(stream, STRING_TAG);
9116
- writeString(stream, key);
9117
- writeValue(stream, record[key] ?? null);
9118
- }
9119
- }
9120
- function boundVectorOf(vector) {
9121
- if (vector === void 0) return void 0;
9122
- const bound = { ...vector };
9123
- if (vector.value !== void 0) {
9124
- bound.value = Array.from(vector.value, Math.fround);
9125
- }
9126
- if (vector.text !== void 0) {
9127
- delete bound.value;
9128
- }
9129
- return bound;
9130
- }
9131
- var FILTERS_SLOT = 2;
9132
- var BOUND_SLOT_COUNT = 15;
9133
- function bindingOf(values) {
9134
- const stream = { bytes: new Uint8Array(256), length: 0 };
9135
- for (const value of values) {
9136
- writeValue(stream, value);
9137
- }
9138
- return fnv1aBytes(stream.bytes.subarray(0, stream.length)).toString(16).padStart(8, "0");
9139
- }
9140
- function queryBindingOf(params) {
9141
- return bindingOf([
9142
- params.term,
9143
- params.fields,
9144
- params.filters,
9145
- params.boost,
9146
- params.minScore,
9147
- params.termMatch,
9148
- params.tolerance,
9149
- params.prefixLength,
9150
- params.prefix,
9151
- params.exact,
9152
- params.pinned,
9153
- params.mode,
9154
- params.hybrid,
9155
- boundVectorOf(params.vector),
9156
- params.scoring ?? "local"
9157
- ]);
9158
- }
9159
- function listBindingOf(filters) {
9160
- const values = new Array(BOUND_SLOT_COUNT).fill(void 0);
9161
- values[FILTERS_SLOT] = filters;
9162
- return bindingOf(values);
9163
- }
9164
-
9165
- // src/engine/list-documents/index.ts
9166
- function clampListLimit(limit) {
9167
- const clamped = Math.max(1, clampLimit(limit));
9168
- requireWithinResultWindow(clamped, 0);
9169
- return clamped;
9170
- }
9171
- function firstIdAfter(sorted, anchor) {
9172
- let low = 0;
9173
- let high = sorted.length;
9174
- while (low < high) {
9175
- const mid = low + high >>> 1;
9176
- if (compareCodePoints(sorted[mid], anchor) <= 0) low = mid + 1;
9177
- else high = mid;
9178
- }
9179
- return low;
9180
- }
9181
- function collectFilterMatches(partitions, filters, schema) {
9182
- const perPartition = partitions.map((partition) => partition.filterMatches(filters, schema));
9183
- let total = 0;
9184
- for (const matches of perPartition) total += matches.count;
9185
- return { matchesFor: (partitionIndex) => perPartition[partitionIndex] ?? null, total };
9186
- }
9187
- function collectCandidates(partition, anchor, matches, wanted, out) {
9188
- const sorted = partition.sortedDocIds();
9189
- let index = anchor === null ? 0 : firstIdAfter(sorted, anchor);
9190
- let taken = 0;
9191
- while (index < sorted.length && taken < wanted) {
9192
- const docId = sorted[index];
9193
- index++;
9194
- if (matches !== null && !matches.hasExternal(docId)) continue;
9195
- out.push(docId);
9196
- taken++;
9197
- }
9198
- }
9199
- function pageInIdOrder(partitions, cursor, filtered, limit, binding) {
9200
- const anchor = cursor === null ? null : cursor.anchor;
9201
- const candidates = [];
9202
- for (let index = 0; index < partitions.length; index++) {
9203
- collectCandidates(
9204
- partitions[index],
9205
- anchor,
9206
- filtered === null ? null : filtered.matchesFor(index),
9207
- limit + 1,
9208
- candidates
9209
- );
9210
- }
9211
- candidates.sort(compareCodePoints);
9212
- const hasMore = candidates.length > limit;
9213
- const ids = hasMore ? candidates.slice(0, limit) : candidates;
9214
- const nextCursor = hasMore && ids.length > 0 ? { anchor: ids[ids.length - 1], score: null, sortKey: null, sortSignature: null, binding } : null;
9215
- return { ids, nextCursor };
9216
- }
9217
- function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, schema, binding) {
9218
- const normalized = normalizeSort(sort);
9219
- const fields = normalized.map((entry) => entry.field);
9220
- const directions = normalized.map((entry) => entry.direction);
9221
- const flatSchema = flattenSchema(schema);
9222
- const fieldTypes = fields.map((field) => flatSchema[field]);
9223
- const request = {
9224
- fields,
9225
- directions,
9226
- fieldTypes,
9227
- limit: limit + 1,
9228
- anchorKey: cursor === null ? null : cursor.sortKey,
9229
- anchorId: cursor === null ? null : cursor.anchor
9230
- };
9231
- const merged = [];
9232
- for (let index = 0; index < partitions.length; index++) {
9233
- const matches = filtered === null ? null : filtered.matchesFor(index);
9234
- for (const entry of partitions[index].sortedPage({ ...request, matches })) merged.push(entry);
9235
- }
9236
- merged.sort((a, b) => compareComparableKeys(a.key, b.key, directions) || compareCodePoints(a.id, b.id));
9237
- const hasMore = merged.length > limit;
9238
- const page = hasMore ? merged.slice(0, limit) : merged;
9239
- const last = page[page.length - 1];
9240
- const nextCursor = hasMore && last !== void 0 ? { anchor: last.id, score: null, sortKey: last.key, sortSignature: signature, binding } : null;
9241
- return { ids: page.map((entry) => entry.id), nextCursor };
9242
- }
9243
- function countAcrossPartitions(manager, partitionIds, partitions) {
9244
- if (partitionIds === void 0) {
9245
- return manager.countDocuments();
9246
- }
9247
- let total = 0;
9248
- for (const partition of partitions) {
9249
- total += partition.count();
9250
- }
9251
- return total;
9252
- }
9253
- function executeListDocuments(params, context) {
9254
- const { manager, schema } = context;
9255
- const startTime = now();
9256
- const limit = clampListLimit(params.limit);
9257
- const signature = sortSignatureOf(params.sort);
9258
- requireSortableFields(params.sort, schema);
9259
- const binding = listBindingOf(params.filters);
9260
- let cursor = null;
9261
- if (params.cursor !== void 0) {
9262
- cursor = decodePageCursor(params.cursor);
9263
- requireMatchingCursor(cursor, params.cursor, signature, false, binding);
9264
- }
9265
- const partitions = context.partitionIds === void 0 ? manager.getAllPartitions() : context.partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
9266
- const filtered = params.filters === void 0 ? null : collectFilterMatches(partitions, params.filters, schema);
9267
- const total = filtered === null ? countAcrossPartitions(manager, context.partitionIds, partitions) : filtered.total;
9268
- const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit, binding) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema, binding);
9269
- const projection = resolveProjection(params.document);
9270
- const documents = [];
9271
- for (const docId of page.ids) {
9272
- if (projection.kind === "none") {
9273
- documents.push({ id: docId, document: {} });
9274
- continue;
9275
- }
9276
- const stored = manager.get(docId, projection);
9277
- if (stored === void 0) continue;
9278
- documents.push({ id: docId, document: stored });
9279
- }
9280
- if (page.nextCursor === null) {
9281
- for (const partition of partitions) partition.releaseSortedDocIds();
9282
- }
9283
- return {
9284
- documents,
9285
- cursor: page.nextCursor === null ? null : encodePageCursor(page.nextCursor),
9286
- total,
9287
- elapsed: now() - startTime
9288
- };
9289
- }
9290
-
9291
- // src/search/grouping.ts
9292
- function getNestedValue2(obj, path) {
9293
- const segments = path.split(".");
9294
- let current = obj;
9295
- for (const segment of segments) {
9296
- if (current === null || current === void 0 || typeof current !== "object") {
9297
- return void 0;
9298
- }
9299
- if (!Object.hasOwn(current, segment)) {
9300
- return void 0;
9301
- }
9302
- current = current[segment];
9303
- }
9304
- return current;
9305
- }
9306
- function applyGrouping(hits, group, getDocument) {
9307
- if (!group.fields || group.fields.length === 0) {
9308
- return [{ values: {}, hits }];
9309
- }
9310
- const groupMap = /* @__PURE__ */ new Map();
9311
- for (const hit of hits) {
9312
- const doc = getDocument(hit.id);
9313
- const fieldValues = [];
9314
- const valuesRecord = {};
9315
- for (const field of group.fields) {
9316
- const value = doc ? getNestedValue2(doc, field) : void 0;
9317
- fieldValues.push(value);
9318
- valuesRecord[field] = value;
9319
- }
9320
- const compositeKey = JSON.stringify(fieldValues);
9321
- const existing = groupMap.get(compositeKey);
9322
- if (existing) {
9323
- existing.hits.push(hit);
9324
- } else {
9325
- groupMap.set(compositeKey, { values: valuesRecord, hits: [hit] });
9326
- }
9327
- }
9328
- const groups = [];
9329
- for (const entry of groupMap.values()) {
9330
- let groupHits = entry.hits;
9331
- if (group.maxPerGroup !== void 0 && group.maxPerGroup > 0) {
9332
- groupHits = groupHits.slice(0, group.maxPerGroup);
9333
- }
9334
- const result = { values: entry.values, hits: groupHits };
9335
- if (group.reduce) {
9336
- const folded = foldGroupReducer(
9337
- group.reduce,
9338
- groupHits.map((hit) => ({ document: getDocument(hit.id), score: hit.score ?? 0 }))
9339
- );
9340
- if (folded.reducerError !== void 0) {
9341
- result.reducerError = folded.reducerError;
9342
- } else {
9343
- result.reduced = folded.reduced;
9344
- }
9345
- }
9346
- groups.push(result);
9347
- }
9348
- groups.sort((a, b) => {
9349
- const aScore = a.hits.length > 0 ? a.hits[0].score ?? 0 : 0;
9350
- const bScore = b.hits.length > 0 ? b.hits[0].score ?? 0 : 0;
9351
- if (aScore !== bScore) return bScore - aScore;
9352
- const aId = a.hits.length > 0 ? a.hits[0].id : "";
9353
- const bId = b.hits.length > 0 ? b.hits[0].id : "";
9354
- return compareCodePoints(aId, bId);
9355
- });
9356
- if (group.limit !== void 0 && Number.isFinite(group.limit) && group.limit >= 0) {
9357
- const cap = Math.floor(group.limit);
9358
- if (groups.length > cap) {
9359
- groups.length = cap;
9360
- }
9361
- }
9362
- return groups;
9363
- }
9364
- function foldGroupReducer(reduce, rows) {
9365
- try {
9366
- let accumulator = reduce.initialValue();
9367
- for (const row of rows) {
9368
- if (row.document !== void 0) {
9369
- accumulator = reduce.reducer(accumulator, row.document, row.score);
9370
- }
9371
- }
9372
- return { reduced: accumulator };
9373
- } catch (err) {
9374
- return { reducerError: err instanceof Error ? err.message : String(err) };
9375
- }
9376
- }
9377
-
9378
- // src/search/pinning.ts
9379
- function dedupePinnedEntries(pinned) {
9380
- const deduped = [];
9381
- const seen = /* @__PURE__ */ new Set();
9382
- for (const entry of pinned) {
9383
- if (seen.has(entry.docId)) continue;
9384
- seen.add(entry.docId);
9385
- deduped.push(entry);
9386
- }
9387
- return deduped;
9388
- }
9389
- function applyPinning(hits, pinned, resolveDoc) {
9390
- const result = hits.slice();
9391
- const deduped = dedupePinnedEntries(pinned);
9392
- const pinnedDocIds = new Set(deduped.map((entry) => entry.docId));
9393
- for (let i = result.length - 1; i >= 0; i--) {
9394
- if (pinnedDocIds.has(result[i].id)) {
9395
- result.splice(i, 1);
9396
- }
9397
- }
9398
- const sorted = deduped.sort((a, b) => a.position - b.position);
9399
- for (const entry of sorted) {
9400
- const doc = resolveDoc(entry.docId);
9401
- if (!doc) continue;
9402
- const pos = Math.max(0, Math.min(entry.position, result.length));
9403
- result.splice(pos, 0, doc);
9404
- }
9405
- return result;
9406
- }
9407
-
9408
- // src/highlighting/highlighter.ts
9409
- function buildTokenCharOffsets(text, originalTokens) {
9410
- const offsets = [];
9411
- let searchFrom = 0;
9412
- for (const token of originalTokens) {
9413
- const idx = text.indexOf(token, searchFrom);
9414
- if (idx === -1) continue;
9415
- offsets.push({ start: idx, end: idx + token.length });
9416
- searchFrom = idx + token.length;
9417
- }
9418
- return offsets;
9419
- }
9420
- function mergeOverlapping(ranges) {
9421
- if (ranges.length === 0) return [];
9422
- const sorted = [...ranges].sort((a, b) => a.start - b.start);
9423
- const merged = [sorted[0]];
9424
- for (let i = 1; i < sorted.length; i++) {
9425
- const prev = merged[merged.length - 1];
9426
- const curr = sorted[i];
9427
- if (curr.start <= prev.end) {
9428
- prev.end = Math.max(prev.end, curr.end);
9429
- } else {
9430
- merged.push(curr);
9431
- }
9432
- }
9433
- return merged;
9434
- }
9435
- function findDensestWindow(positions, text, windowSize) {
9436
- if (positions.length === 0) {
9437
- return { start: 0, end: Math.min(windowSize, text.length) };
9438
- }
9439
- let bestStart = 0;
9440
- let bestCount = 0;
9441
- for (const pos of positions) {
9442
- const windowStart = Math.max(0, pos.start - Math.floor(windowSize / 2));
9443
- const windowEnd = Math.min(text.length, windowStart + windowSize);
9444
- const adjustedStart = Math.max(0, windowEnd - windowSize);
9445
- let count = 0;
9446
- for (const p of positions) {
9447
- if (p.start >= adjustedStart && p.end <= windowEnd) {
9448
- count++;
9449
- }
9450
- }
9451
- if (count > bestCount) {
9452
- bestCount = count;
9453
- bestStart = adjustedStart;
9454
- }
9455
- }
9456
- return { start: bestStart, end: Math.min(text.length, bestStart + windowSize) };
9457
- }
9458
- function applySnippetTruncation(taggedText, originalText, positions, maxLen, preTag, postTag) {
9459
- if (maxLen <= 0 || originalText.length <= maxLen) {
9460
- return { snippet: taggedText, adjustedPositions: positions };
9461
- }
9462
- const window = findDensestWindow(positions, originalText, maxLen);
9463
- const snippetRaw = originalText.substring(window.start, window.end);
9464
- const adjustedPositions = [];
9465
- for (const pos of positions) {
9466
- if (pos.start >= window.start && pos.end <= window.end) {
9467
- adjustedPositions.push({
9468
- start: pos.start - window.start,
9469
- end: pos.end - window.start
9470
- });
9471
- }
9472
- }
9473
- let snippet = snippetRaw;
9474
- const sortedDesc = [...adjustedPositions].sort((a, b) => b.start - a.start);
9475
- for (const pos of sortedDesc) {
9476
- snippet = snippet.substring(0, pos.start) + preTag + snippet.substring(pos.start, pos.end) + postTag + snippet.substring(pos.end);
9477
- }
9478
- const needsLeadingEllipsis = window.start > 0;
9479
- const needsTrailingEllipsis = window.end < originalText.length;
9480
- if (needsLeadingEllipsis) snippet = `...${snippet}`;
9481
- if (needsTrailingEllipsis) snippet = `${snippet}...`;
9482
- return { snippet, adjustedPositions };
9483
- }
9484
- function highlightField(text, queryTokens, language, options) {
9485
- const preTag = options?.preTag ?? "<mark>";
9486
- const postTag = options?.postTag ?? "</mark>";
9487
- const maxSnippetLength = options?.maxSnippetLength ?? 200;
9488
- if (text.length === 0) {
9489
- return { snippet: text, positions: [] };
9490
- }
9491
- const fieldResult = tokenize(text, language, { stem: false, removeStopWords: false });
9492
- if (fieldResult.tokens.length === 0) {
9493
- return { snippet: text, positions: [] };
9494
- }
9495
- const lowercaseText = text.normalize("NFC").toLowerCase();
9496
- const charOffsets = buildTokenCharOffsets(lowercaseText, fieldResult.originalTokens);
9497
- const stemmedQueryTokens = queryTokens.map((qt) => {
9498
- if (language.stemmer) {
9499
- return language.stemmer(qt.token.toLowerCase());
9500
- }
9501
- return qt.token.toLowerCase();
9502
- });
9503
- const matchedRanges = [];
9504
- const prefixToken = options?.prefixToken;
9505
- for (let i = 0; i < fieldResult.tokens.length; i++) {
9506
- if (i >= charOffsets.length) break;
9507
- const fieldToken = fieldResult.tokens[i].token;
9508
- const stemmedField = language.stemmer ? language.stemmer(fieldToken) : fieldToken;
9509
- if (prefixToken !== void 0 && fieldToken.startsWith(prefixToken)) {
9510
- matchedRanges.push({ start: charOffsets[i].start, end: charOffsets[i].end });
9511
- continue;
9512
- }
9513
- for (const stemmedQuery of stemmedQueryTokens) {
9514
- if (stemmedField === stemmedQuery) {
9515
- matchedRanges.push({ start: charOffsets[i].start, end: charOffsets[i].end });
9516
- break;
9517
- }
9518
- }
9519
- }
9520
- const mergedPositions = mergeOverlapping(matchedRanges);
9521
- if (mergedPositions.length === 0) {
9522
- if (maxSnippetLength > 0 && text.length > maxSnippetLength) {
9523
- const truncated = text.substring(0, maxSnippetLength);
9524
- return { snippet: `${truncated}...`, positions: [] };
9525
- }
9526
- return { snippet: text, positions: [] };
9527
- }
9528
- if (maxSnippetLength > 0 && text.length > maxSnippetLength) {
9529
- const { snippet, adjustedPositions } = applySnippetTruncation(
9530
- text,
9531
- text,
9532
- mergedPositions,
9533
- maxSnippetLength,
9534
- preTag,
9535
- postTag
9536
- );
9537
- return { snippet, positions: adjustedPositions };
9538
- }
9539
- let result = text;
9540
- const sortedDesc = [...mergedPositions].sort((a, b) => b.start - a.start);
9541
- for (const pos of sortedDesc) {
9542
- result = result.substring(0, pos.start) + preTag + result.substring(pos.start, pos.end) + postTag + result.substring(pos.end);
9543
- }
9544
- return { snippet: result, positions: mergedPositions };
9545
- }
9546
-
9547
- // src/engine/query/highlight.ts
9548
- function applyHighlights(hits, params, language, analysis) {
9549
- if (!params.highlight) return;
9550
- const queryTokenResult = tokenize(params.term ?? "", language, {
9551
- stem: true,
9552
- removeStopWords: true,
9553
- customTokenizer: analysis.customTokenizer,
9554
- stopWordOverride: analysis.stopWords
9555
- });
9556
- let prefixToken;
9557
- if (params.prefix === true && params.exact !== true && queryTokenResult.tokens.length > 0) {
9558
- const lastPosition = queryTokenResult.tokens[queryTokenResult.tokens.length - 1].position;
9559
- const unstemmed = tokenize(params.term ?? "", language, {
9560
- stem: false,
9561
- removeStopWords: true,
9562
- customTokenizer: analysis.customTokenizer,
9563
- stopWordOverride: analysis.stopWords
9564
- });
9565
- for (const t of unstemmed.tokens) {
9566
- if (t.position === lastPosition) {
9567
- prefixToken = t.token;
9568
- break;
9569
- }
9570
- }
9571
- }
9572
- for (const hit of hits) {
9573
- const highlights = {};
9574
- for (const field of params.highlight.fields) {
9575
- const doc = hit.document;
9576
- const fieldValue = doc[field];
9577
- if (typeof fieldValue === "string") {
9578
- highlights[field] = highlightField(fieldValue, queryTokenResult.tokens, language, {
9579
- preTag: params.highlight.preTag,
9580
- postTag: params.highlight.postTag,
9581
- maxSnippetLength: params.highlight.maxSnippetLength,
9582
- prefixToken
9583
- });
9584
- }
9585
- }
9586
- hit.highlights = highlights;
9587
- }
9588
- }
9589
-
9590
- // src/engine/query/shared.ts
9591
- function partitionsFor(manager, partitionIds) {
9592
- return partitionsIn(manager, partitionIds);
9593
- }
9594
- function coverageFor(manager, partitionIds) {
9595
- if (partitionIds === void 0) {
9596
- return {
9597
- totalPartitions: manager.partitionCount,
9598
- queriedPartitions: manager.partitionCount,
9599
- timedOutPartitions: 0,
9600
- failedPartitions: 0
9601
- };
9602
- }
9603
- const queriedPartitions = partitionsFor(manager, partitionIds).length;
9604
- return {
9605
- totalPartitions: partitionIds.length,
9606
- queriedPartitions,
9607
- timedOutPartitions: 0,
9608
- failedPartitions: partitionIds.length - queriedPartitions
9609
- };
9610
- }
9611
- function partitionsForVectorSearch(manager, vecIndex, partitionIds) {
9612
- if (partitionIds === void 0) {
9613
- return void 0;
9614
- }
9615
- if (!vecIndex.partitionsKnown()) {
9616
- vecIndex.assignPartitions((docId) => manager.partitionIdOf(docId));
9617
- }
9618
- return new Set(partitionIds);
9619
- }
9620
- function scoringConfigFor(params, context) {
9621
- const scoringMode = params.scoring ?? context.config.defaultScoring ?? "local";
9622
- if (scoringMode !== "broadcast") {
9623
- return { scoringMode, partitionIds: context.partitionIds };
9624
- }
9625
- return { scoringMode, globalStats: context.broadcastStats?.(context.indexName), partitionIds: context.partitionIds };
9626
- }
9627
- function broadcastStatsForWorker(params, context, scoring) {
9628
- if (scoring.scoringMode !== "broadcast" || scoring.globalStats === void 0) {
9629
- return void 0;
9630
- }
9631
- const term = params.term;
9632
- if (term === void 0 || term.trim().length === 0) {
9633
- return void 0;
9634
- }
9635
- return pruneStatsToQueryTerms(scoring.globalStats, term, context.language, context.manager.analysis);
9636
- }
9637
- function searchOptionsFor(manager) {
9638
- return {
9639
- bm25Params: manager.config.bm25,
9640
- stopWords: manager.analysis.stopWords,
9641
- customTokenizer: manager.analysis.customTokenizer
9642
- };
9643
- }
9644
- function collectFilterDocIds(manager, params, schema, partitionIds) {
9645
- const filterDocIds = /* @__PURE__ */ new Set();
9646
- if (!params.filters) return filterDocIds;
9647
- for (const partition of partitionsFor(manager, partitionIds)) {
9648
- const partitionFiltered = partition.applyFilters(params.filters, schema);
9649
- for (const docId of partitionFiltered) {
9650
- filterDocIds.add(docId);
9651
- }
9652
- }
9653
- return filterDocIds;
9654
- }
9655
- function vectorResultsToScored(results) {
9656
- return results.map((r) => ({
9657
- docId: r.docId,
9658
- score: r.score,
9659
- termFrequencies: {},
9660
- fieldLengths: {},
9661
- idf: {}
9662
- }));
9663
- }
9664
- function resolveVectorIndex(manager, fieldName) {
9665
- return manager.getVectorIndexes().get(fieldName);
9666
- }
9667
- function clampAlpha(alpha) {
9668
- if (alpha === void 0) return 0.5;
9669
- if (!Number.isFinite(alpha)) return 0.5;
9670
- if (alpha < 0) return 0;
9671
- if (alpha > 1) return 1;
9672
- return alpha;
9673
- }
9674
-
9675
- // src/engine/query/sorted.ts
9676
- function sortsWithoutScores(params) {
9677
- return params.includeScores !== true && params.includeScoreComponents !== true && params.minScore === void 0 && params.group === void 0 && params.pinned === void 0 && (params.termMatch === void 0 || params.termMatch === "any");
9678
- }
9679
- function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
9680
- const { manager, language, config } = context;
9681
- const schema = config.schema;
9682
- let anchorKey = null;
9683
- let anchorId = null;
9684
- if (params.searchAfter !== void 0) {
9685
- const decoded = decodePageCursor(params.searchAfter);
9686
- requireMatchingCursor(decoded, params.searchAfter, sortSignature, true, context.cursorBinding);
9687
- anchorKey = decoded.sortKey;
9688
- anchorId = decoded.anchor;
9689
- }
9690
- const normalized = normalizeSort(params.sort);
9691
- const fields = normalized.map((entry) => entry.field);
9692
- const directions = normalized.map((entry) => entry.direction);
9693
- const flatSchema = flattenSchema(schema);
9694
- const fieldTypes = fields.map((field) => flatSchema[field]);
9695
- const options = searchOptionsFor(manager);
9696
- const partitions = partitionsFor(manager, context.partitionIds);
9697
- const partitionLimit = offset + limit + 1;
9698
- const merged = [];
9699
- const partitionFacets = [];
9700
- let count = 0;
9701
- for (const partition of partitions) {
9702
- const matches = fulltextMatches(partition, params, language, schema, options);
9703
- if (params.facets !== void 0) {
9704
- partitionFacets.push(
9705
- partition.computeFacets(
9706
- matches === null ? /* @__PURE__ */ new Set() : { ordinalBitset: matches.ordinalBitset() },
9707
- params.facets,
9708
- schema
9709
- )
9710
- );
9711
- }
9712
- if (matches === null || matches.count === 0) continue;
9713
- count += matches.count;
9714
- const page2 = partition.sortedPage({
9715
- fields,
9716
- directions,
9717
- fieldTypes,
9718
- limit: partitionLimit,
9719
- anchorKey,
9720
- anchorId,
9721
- matches
9722
- });
9723
- for (const entry of page2) merged.push(entry);
9724
- }
9725
- merged.sort((a, b) => compareComparableKeys(a.key, b.key, directions) || compareCodePoints(a.id, b.id));
9726
- const hasMore = merged.length > offset + limit;
9727
- const page = merged.slice(offset, offset + limit);
9728
- const last = page[page.length - 1];
9729
- let cursor;
9730
- if (hasMore && last !== void 0) {
9731
- cursor = encodePageCursor({
9732
- anchor: last.id,
9733
- score: null,
9734
- sortKey: last.key,
9735
- sortSignature,
9736
- binding: context.cursorBinding
9737
- });
9738
- }
9739
- return {
9740
- hits: page.map((entry) => ({ id: entry.id, document: void 0 })),
9741
- count,
9742
- cursor,
9743
- facets: partitionFacets.length > 0 ? mergeFacets(partitionFacets) : void 0
9744
- };
9745
- }
9746
-
9747
- // src/search/fusion.ts
9748
- function reciprocalRankFusion(lists, options) {
9749
- const k = options.k > 0 ? options.k : 60;
9750
- const scores = /* @__PURE__ */ new Map();
9751
- const docData = /* @__PURE__ */ new Map();
9752
- for (const list of lists) {
9753
- for (let rank = 0; rank < list.length; rank++) {
9754
- const doc = list[rank];
9755
- const rrfContribution = 1 / (k + rank + 1);
9756
- scores.set(doc.docId, (scores.get(doc.docId) ?? 0) + rrfContribution);
9757
- if (!docData.has(doc.docId)) {
9758
- docData.set(doc.docId, doc);
9759
- }
9760
- }
9761
- }
9762
- const result = [];
9763
- for (const [docId, score] of scores) {
9764
- const original = docData.get(docId);
9765
- if (!original) continue;
9766
- result.push({
9767
- docId,
9768
- score,
9769
- termFrequencies: original.termFrequencies,
9770
- fieldLengths: original.fieldLengths,
9771
- idf: original.idf
9772
- });
9773
- }
9774
- result.sort((a, b) => b.score - a.score || compareCodePoints(a.docId, b.docId));
9775
- return result;
9776
- }
9777
- function linearCombination(textResults, vectorResults, options) {
9778
- const { alpha } = options;
9779
- const normalizedText = minMaxNormalize(textResults);
9780
- const normalizedVector = minMaxNormalize(vectorResults);
9781
- const textScoreMap = /* @__PURE__ */ new Map();
9782
- for (const doc of normalizedText) {
9783
- textScoreMap.set(doc.docId, doc.score);
9784
- }
9785
- const textDocMap = /* @__PURE__ */ new Map();
9786
- for (const doc of textResults) {
9787
- textDocMap.set(doc.docId, doc);
9788
- }
9789
- const vectorScoreMap = /* @__PURE__ */ new Map();
9790
- for (const doc of normalizedVector) {
9791
- vectorScoreMap.set(doc.docId, doc.score);
9792
- }
9793
- const allDocIds = /* @__PURE__ */ new Set();
9794
- for (const doc of textResults) allDocIds.add(doc.docId);
9795
- for (const doc of vectorResults) allDocIds.add(doc.docId);
9796
- const result = [];
9797
- for (const docId of allDocIds) {
9798
- const tScore = textScoreMap.get(docId) ?? 0;
9799
- const vScore = vectorScoreMap.get(docId) ?? 0;
9800
- const combined = alpha * vScore + (1 - alpha) * tScore;
9801
- const original = textDocMap.get(docId);
9802
- result.push({
9803
- docId,
9804
- score: combined,
9805
- termFrequencies: original?.termFrequencies ?? {},
9806
- fieldLengths: original?.fieldLengths ?? {},
9807
- idf: original?.idf ?? {}
9808
- });
9809
- }
9810
- result.sort((a, b) => b.score - a.score || compareCodePoints(a.docId, b.docId));
9811
- return result;
9812
- }
9813
- function minMaxNormalize(docs) {
9814
- if (docs.length === 0) return [];
9815
- let min = docs[0].score;
9816
- let max = docs[0].score;
9817
- for (let i = 1; i < docs.length; i++) {
9818
- if (docs[i].score < min) min = docs[i].score;
9819
- if (docs[i].score > max) max = docs[i].score;
9820
- }
9821
- const range = max - min;
9822
- if (range === 0) {
9823
- return docs.map((d) => ({ docId: d.docId, score: 1 }));
9824
- }
9825
- return docs.map((d) => ({ docId: d.docId, score: (d.score - min) / range }));
9826
- }
9827
-
9828
- // src/engine/query/vector.ts
9829
- async function executeVectorSearch(params, manager, config, limit, offset, partitionIds) {
9830
- const vectorConfig = params.vector;
9831
- if (!vectorConfig || !vectorConfig.value) {
9832
- return { scored: [], totalMatched: 0 };
9833
- }
9834
- const vecIndex = resolveVectorIndex(manager, vectorConfig.field);
9835
- if (!vecIndex) {
9836
- return { scored: [], totalMatched: 0 };
9837
- }
9838
- let filterDocIds;
9839
- let filterPartitions;
9840
- if (params.filters) {
9841
- filterDocIds = collectFilterDocIds(manager, params, config.schema, partitionIds);
9842
- if (filterDocIds.size === 0) {
9843
- return { scored: [], totalMatched: 0 };
9844
- }
9845
- } else {
9846
- filterPartitions = partitionsForVectorSearch(manager, vecIndex, partitionIds);
9847
- }
9848
- const queryVec = new Float32Array(vectorConfig.value);
9849
- const k = limit + offset + 1;
9850
- const results = await vecIndex.searchParallel(queryVec, k, {
9851
- metric: vectorConfig.metric ?? "cosine",
9852
- minSimilarity: vectorConfig.similarity ?? -Infinity,
9853
- ...filterDocIds !== void 0 ? { filterDocIds } : {},
9854
- ...filterPartitions !== void 0 ? { filterPartitions } : {},
9855
- efSearch: vectorConfig.efSearch
9856
- });
9857
- const scored = vectorResultsToScored(results);
9858
- return { scored, totalMatched: scored.length };
9859
- }
9860
- async function executeHybridSearch(params, context, limit, offset) {
9861
- const { manager, language, config, workerSearch, indexName } = context;
9862
- const { vector: _vector, mode: _mode, hybrid: _hybrid, ...textOnlyParams } = params;
9863
- let filterDocIds;
9864
- if (params.filters) {
9865
- filterDocIds = collectFilterDocIds(manager, params, config.schema, context.partitionIds);
9866
- if (filterDocIds.size === 0) {
9867
- return { scored: [], totalMatched: 0 };
9868
- }
9869
- }
9870
- let textFanOutResult;
9871
- const scoring = scoringConfigFor(params, context);
9872
- const textWorkerResult = workerSearch ? await workerSearch(
9873
- indexName,
9874
- textOnlyParams,
9875
- broadcastStatsForWorker(textOnlyParams, context, scoring),
9876
- context.partitionIds
9877
- ) : null;
9878
- if (textWorkerResult) {
9879
- textFanOutResult = textWorkerResult;
9880
- } else {
9881
- textFanOutResult = await fanOutQuery(
9882
- manager,
9883
- textOnlyParams,
9884
- language,
9885
- config.schema,
9886
- scoring,
9887
- searchOptionsFor(manager)
9888
- );
9889
- }
9890
- const vectorConfig = params.vector;
9891
- if (!vectorConfig || !vectorConfig.value) {
9892
- return {
9893
- scored: textFanOutResult.scored,
9894
- totalMatched: textFanOutResult.totalMatched,
9895
- facets: textFanOutResult.facets
9896
- };
9897
- }
9898
- let vectorScored = [];
9899
- const vecIndex = resolveVectorIndex(manager, vectorConfig.field);
9900
- if (vecIndex) {
9901
- const queryVec = new Float32Array(vectorConfig.value);
9902
- const vectorK = limit + offset + 1;
9903
- const filterPartitions = filterDocIds === void 0 ? partitionsForVectorSearch(manager, vecIndex, context.partitionIds) : void 0;
9904
- const vectorResults = await vecIndex.searchParallel(queryVec, vectorK, {
9905
- metric: vectorConfig.metric ?? "cosine",
9906
- minSimilarity: vectorConfig.similarity ?? -Infinity,
9907
- ...filterDocIds !== void 0 ? { filterDocIds } : {},
9908
- ...filterPartitions !== void 0 ? { filterPartitions } : {},
9909
- efSearch: vectorConfig.efSearch
9910
- });
9911
- vectorScored = vectorResultsToScored(vectorResults);
9912
- }
9913
- const hybridConfig = params.hybrid ?? {};
9914
- const strategy = hybridConfig.strategy ?? "rrf";
9915
- let fusedScored;
9916
- if (strategy === "rrf") {
9917
- const rrfK = hybridConfig.k !== void 0 && hybridConfig.k > 0 ? hybridConfig.k : 60;
9918
- fusedScored = reciprocalRankFusion([textFanOutResult.scored, vectorScored], { k: rrfK });
9919
- } else {
9920
- const alpha = clampAlpha(hybridConfig.alpha);
9921
- fusedScored = linearCombination(textFanOutResult.scored, vectorScored, { alpha });
9922
- }
9923
- if (params.minScore !== void 0 && params.minScore > 0) {
9924
- const threshold = params.minScore;
9925
- fusedScored = fusedScored.filter((doc) => doc.score >= threshold);
9926
- }
9927
- return {
9928
- scored: fusedScored,
9929
- totalMatched: fusedScored.length,
9930
- facets: textFanOutResult.facets
9931
- };
9932
- }
9933
-
9934
- // src/engine/query/index.ts
9935
- async function executeQuery(params, context) {
9936
- const { manager, language, config, workerSearch, indexName } = context;
9937
- const startTime = now();
9938
- const limit = clampLimit(params.limit);
9939
- const offset = clampOffset(params.offset);
9940
- requireWithinResultWindow(limit, offset);
9941
- const sortFields = normalizeSort(params.sort);
9942
- const sortSignature = sortSignatureOf(params.sort);
9943
- const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
9944
- const hasVector = params.vector !== void 0 && params.vector.value !== void 0;
9945
- const isHybridMode = params.mode === "hybrid" || hasTerm && hasVector;
9946
- const isVectorOnly = (params.mode === "vector" || hasVector && !hasTerm) && !isHybridMode;
9947
- if (sortSignature !== null && (isHybridMode || params.hybrid !== void 0)) {
9948
- throw new NarsilError(
9949
- ErrorCodes.SEARCH_INVALID_MODE,
9950
- "A hybrid query cannot carry a sort, because fusion defines the order of hybrid results",
9951
- { sort: sortFields.map((entry) => entry.field) }
9952
- );
9953
- }
9954
- requireSortableFields(params.sort, config.schema);
9955
- let paginated;
9956
- let nextCursor;
9957
- let count;
9958
- let facets;
9959
- let groups;
9960
- if (sortSignature !== null && hasTerm && !isVectorOnly && !isHybridMode && sortsWithoutScores(params)) {
9961
- const page = executeSortedQueryPage(params, context, limit, offset, sortSignature);
9962
- paginated = page.hits;
9963
- nextCursor = page.cursor;
9964
- count = page.count;
9965
- facets = page.facets;
9966
- } else {
9967
- const requestedVectorField = params.vector?.field;
9968
- const hasGlobalVectorIndex = requestedVectorField !== void 0 && manager.getVectorIndexes().has(requestedVectorField);
9969
- let fanOutResult;
9970
- if (isVectorOnly && hasGlobalVectorIndex) {
9971
- fanOutResult = await executeVectorSearch(params, manager, config, limit, offset, context.partitionIds);
9972
- } else if (isHybridMode && hasGlobalVectorIndex) {
9973
- fanOutResult = await executeHybridSearch(params, context, limit, offset);
9974
- } else {
9975
- const scoring = scoringConfigFor(params, context);
9976
- const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
9977
- if (workerResult) {
9978
- fanOutResult = workerResult;
9979
- } else {
9980
- fanOutResult = await fanOutQuery(manager, params, language, config.schema, scoring, searchOptionsFor(manager));
9981
- }
9982
- }
9983
- const needsFullHits = params.sort !== void 0 || params.group !== void 0 || params.pinned !== void 0 || params.searchAfter !== void 0;
9984
- let hits;
9985
- if (needsFullHits) {
9986
- hits = fanOutResult.scored.map((scored) => ({
9987
- id: scored.docId,
9988
- score: scored.score,
9989
- document: void 0,
9990
- scoreComponents: params.includeScoreComponents ? { termFrequencies: scored.termFrequencies, fieldLengths: scored.fieldLengths, idf: scored.idf } : void 0
9991
- }));
9992
- } else {
9993
- const end = Math.min(offset + limit + 1, fanOutResult.scored.length);
9994
- hits = new Array(end);
9995
- for (let i = 0; i < end; i++) {
9996
- const scored = fanOutResult.scored[i];
9997
- hits[i] = {
9998
- id: scored.docId,
9999
- score: scored.score,
10000
- document: void 0,
10001
- scoreComponents: params.includeScoreComponents ? { termFrequencies: scored.termFrequencies, fieldLengths: scored.fieldLengths, idf: scored.idf } : void 0
10002
- };
10003
- }
10004
- }
10005
- const sortFieldNames = sortFields.map((entry) => entry.field);
10006
- const sortFlatSchema = sortFieldNames.length === 0 ? {} : flattenSchema(config.schema);
10007
- const sortFieldTypes = sortFieldNames.map((field) => sortFlatSchema[field]);
10008
- const sortKeyCache = /* @__PURE__ */ new Map();
10009
- const sortKeyOf = (docId) => {
10010
- let key = sortKeyCache.get(docId);
10011
- if (key === void 0) {
10012
- key = manager.sortValues(docId, sortFieldNames, sortFieldTypes);
10013
- sortKeyCache.set(docId, key);
10014
- }
10015
- return key;
10016
- };
10017
- if (params.sort) {
10018
- hits = applySorting(hits, params.sort, sortKeyOf);
10019
- }
10020
- if (params.group) {
10021
- groups = applyGrouping(hits, params.group, (docId) => manager.getRef(docId));
10022
- }
10023
- if (params.pinned && params.searchAfter === void 0 && context.partitionIds === void 0) {
10024
- hits = applyPinning(hits, params.pinned, (docId) => {
10025
- const doc = manager.getRef(docId);
10026
- if (!doc) return void 0;
10027
- return { id: docId, score: 0, document: doc };
10028
- });
10029
- }
10030
- let sortContext;
10031
- if (sortSignature !== null) {
10032
- sortContext = {
10033
- signature: sortSignature,
10034
- directions: sortFields.map((entry) => entry.direction),
10035
- sortKeyOf
10036
- };
10037
- }
10038
- const pinnedIds = params.pinned !== void 0 && params.searchAfter === void 0 ? new Set(params.pinned.map((entry) => entry.docId)) : void 0;
10039
- const paged = applyPagination(
10040
- hits,
10041
- limit,
10042
- offset,
10043
- context.cursorBinding,
10044
- params.searchAfter,
10045
- sortContext,
10046
- pinnedIds
10047
- );
10048
- paginated = paged.paginated;
10049
- nextCursor = paged.nextCursor;
10050
- count = fanOutResult.totalMatched;
10051
- facets = fanOutResult.facets;
10052
- if (sortSignature !== null && params.includeScores !== true) {
10053
- for (const hit of hits) hit.score = void 0;
10054
- }
10055
- }
10056
- const projection = resolveProjection(params.document);
10057
- if (projection.kind === "none") {
10058
- for (const hit of paginated) {
10059
- hit.document = {};
10060
- }
10061
- } else {
10062
- for (const hit of paginated) {
10063
- hit.document = manager.get(hit.id, projection) ?? {};
10064
- }
10065
- }
10066
- if (groups) {
10067
- for (const group of groups) {
10068
- for (const hit of group.hits) {
10069
- if (projection.kind === "none") {
10070
- hit.document = {};
10071
- continue;
10072
- }
10073
- hit.document = manager.get(hit.id, projection) ?? {};
10074
- }
10075
- }
10076
- }
10077
- if (params.highlight) {
10078
- applyHighlights(paginated, params, language, manager.analysis);
10079
- }
10080
- const elapsed = now() - startTime;
10081
- return {
10082
- hits: paginated,
10083
- count,
10084
- elapsed,
10085
- cursor: nextCursor,
10086
- facets,
10087
- groups,
10088
- coverage: coverageFor(manager, context.partitionIds)
10089
- };
10090
- }
10091
- async function executePreflight(params, context) {
10092
- const { manager, language, config, workerSearch, indexName } = context;
10093
- const startTime = now();
10094
- const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
10095
- const hasVector = params.vector !== void 0 && params.vector.value !== void 0;
10096
- const isHybridMode = params.mode === "hybrid" || hasTerm && hasVector;
10097
- const isVectorOnly = (params.mode === "vector" || hasVector && !hasTerm) && !isHybridMode;
10098
- const requestedVectorField = params.vector?.field;
10099
- const hasGlobalVectorIndex = requestedVectorField !== void 0 && manager.getVectorIndexes().has(requestedVectorField);
10100
- let totalMatched;
10101
- const preflightLimit = 1e3;
10102
- const preflightOffset = 0;
10103
- if (isVectorOnly && hasGlobalVectorIndex) {
10104
- const result = await executeVectorSearch(
10105
- params,
10106
- manager,
10107
- config,
10108
- preflightLimit,
10109
- preflightOffset,
10110
- context.partitionIds
10111
- );
10112
- totalMatched = result.totalMatched;
10113
- } else if (isHybridMode && hasGlobalVectorIndex) {
10114
- const result = await executeHybridSearch(params, context, preflightLimit, preflightOffset);
10115
- totalMatched = result.totalMatched;
10116
- } else if (countsWithoutScores(params)) {
10117
- totalMatched = fanOutMatchCount(manager, params, language, config.schema, {
10118
- searchOptions: searchOptionsFor(manager),
10119
- partitionIds: context.partitionIds
10120
- });
10121
- } else {
10122
- const scoring = scoringConfigFor(params, context);
10123
- const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
10124
- if (workerResult) {
10125
- totalMatched = workerResult.totalMatched;
10126
- } else {
10127
- const fanOutResult = await fanOutQuery(
10128
- manager,
10129
- params,
10130
- language,
10131
- config.schema,
10132
- scoring,
10133
- searchOptionsFor(manager)
10134
- );
10135
- totalMatched = fanOutResult.totalMatched;
10136
- }
10137
- }
10138
- const elapsed = now() - startTime;
10139
- return { count: totalMatched, elapsed };
10140
- }
10141
-
10142
- // src/engine/resolve-vector-text.ts
10143
- async function resolveVectorText(params, embeddingAdapter, signal, embeddingAdapterName) {
10144
- if (!params.vector) return params;
10145
- if (params.vector.text === void 0 && params.vector.value === void 0) return params;
10146
- if (params.vector.value !== void 0 && params.vector.text === void 0) return params;
10147
- if (params.vector.text !== void 0 && params.vector.value !== void 0) {
10148
- throw new NarsilError(ErrorCodes.EMBEDDING_CONFIG_INVALID, "Vector query cannot have both 'text' and 'value'");
10149
- }
10150
- if (!embeddingAdapter) {
10151
- throw new NarsilError(
10152
- ErrorCodes.EMBEDDING_CONFIG_INVALID,
10153
- embeddingAdapterName ? `Vector query with 'text' needs embedding adapter "${embeddingAdapterName}", which is not registered on this engine. Register it with registerEmbeddingAdapter or supply a raw vector.` : "Vector query with 'text' requires an embedding adapter on the index or instance",
10154
- embeddingAdapterName ? { adapter: embeddingAdapterName } : void 0
10155
- );
10156
- }
10157
- if (typeof params.vector.text !== "string") {
10158
- throw new NarsilError(
10159
- ErrorCodes.DOC_VALIDATION_FAILED,
10160
- `Vector query 'text' must be a string, got ${typeof params.vector.text}`
10161
- );
10162
- }
10163
- if (params.vector.text.length === 0) {
10164
- throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Vector query 'text' must not be empty");
10165
- }
10166
- const raw = await embeddingAdapter.embed(params.vector.text, "query", signal);
10167
- if (!(raw instanceof Float32Array)) {
10168
- throw new NarsilError(
10169
- ErrorCodes.EMBEDDING_FAILED,
10170
- `Adapter returned ${typeof raw} for query embedding, expected Float32Array`
10171
- );
10172
- }
10173
- if (raw.length !== embeddingAdapter.dimensions) {
10174
- throw new NarsilError(
10175
- ErrorCodes.EMBEDDING_DIMENSION_MISMATCH,
10176
- `Adapter returned ${raw.length}-dimensional vector for query, expected ${embeddingAdapter.dimensions}`,
10177
- { expected: embeddingAdapter.dimensions, actual: raw.length }
10178
- );
10179
- }
10180
- const resolved = { ...params.vector, value: Array.from(raw) };
10181
- delete resolved.text;
10182
- return { ...params, vector: resolved };
10183
- }
10184
-
10185
- // src/engine/suggest.ts
10186
- function executeSuggest(manager, language, params, partitionIds) {
10187
- const t0 = performance.now();
10188
- const ceiling = partitionIds === void 0 ? MAX_SUGGEST_LIMIT : MAX_SUGGEST_SCATTER_LIMIT;
10189
- const limit = Math.max(1, Math.min(clampRowCount(params.limit, DEFAULT_SUGGEST_LIMIT), ceiling));
10190
- const rawPrefix = params.prefix.trim();
10191
- if (rawPrefix.length === 0) {
10192
- return { terms: [], elapsed: performance.now() - t0 };
10193
- }
10194
- const unstemmed = tokenize(rawPrefix, language, { stem: false, removeStopWords: false });
10195
- const lastToken = unstemmed.tokens.length > 0 ? unstemmed.tokens[unstemmed.tokens.length - 1].token : rawPrefix.toLowerCase();
10196
- if (lastToken.length === 0) {
10197
- return { terms: [], elapsed: performance.now() - t0 };
10198
- }
10199
- const stemmed = language.stemmer ? language.stemmer(lastToken) : lastToken;
10200
- const merged = /* @__PURE__ */ new Map();
10201
- const perPartitionLimit = limit * 2;
10202
- const partitions = partitionIds === void 0 ? manager.getAllPartitions() : partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
10203
- for (const partition of partitions) {
10204
- for (const suggestion of partition.suggestTerms(lastToken, stemmed, perPartitionLimit)) {
10205
- let entry = merged.get(suggestion.token);
10206
- if (!entry) {
10207
- entry = { documentFrequency: 0, surfaceOccurrences: /* @__PURE__ */ new Map() };
10208
- merged.set(suggestion.token, entry);
10209
- }
10210
- entry.documentFrequency += suggestion.documentFrequency;
10211
- for (const s of suggestion.surfaces) {
10212
- entry.surfaceOccurrences.set(s.surface, (entry.surfaceOccurrences.get(s.surface) ?? 0) + s.occurrences);
10213
- }
10214
- }
10215
- }
10216
- const terms = Array.from(merged.values()).map((entry) => ({ term: pickDisplaySurface(entry.surfaceOccurrences), documentFrequency: entry.documentFrequency })).sort((a, b) => b.documentFrequency - a.documentFrequency || compareCodePoints(a.term, b.term));
10217
- if (terms.length > limit) terms.length = limit;
10218
- return { terms, elapsed: performance.now() - t0 };
10219
- }
10220
- function pickDisplaySurface(surfaceOccurrences) {
10221
- let best = "";
10222
- let bestCount = -1;
10223
- for (const [surface, occurrences] of surfaceOccurrences) {
10224
- if (occurrences > bestCount || occurrences === bestCount && compareCodePoints(surface, best) < 0) {
10225
- best = surface;
10226
- bestCount = occurrences;
10227
- }
10228
- }
10229
- return best;
10230
- }
10231
-
10232
9196
  // src/narsil/reads.ts
10233
9197
  function scopedParams(params, options) {
10234
9198
  if (options?.globalStats === void 0) {
@@ -10463,17 +9427,22 @@ function createNarsilFromCore(core, config) {
10463
9427
  insertBatch(indexName, documents, options) {
10464
9428
  return withOpenIndex(indexName, () => insertDocumentBatch(mutationCtx, indexName, documents, options));
10465
9429
  },
10466
- remove(indexName, docId) {
10467
- return withOpenIndex(indexName, () => removeDocument(mutationCtx, indexName, docId));
9430
+ remove(indexName, docId, options) {
9431
+ return withOpenIndex(indexName, () => removeDocument(mutationCtx, indexName, docId, options));
9432
+ },
9433
+ removeBatch(indexName, docIds, options) {
9434
+ return withOpenIndex(indexName, () => removeDocumentBatch(mutationCtx, indexName, docIds, options));
10468
9435
  },
10469
- removeBatch(indexName, docIds) {
10470
- return withOpenIndex(indexName, () => removeDocumentBatch(mutationCtx, indexName, docIds));
9436
+ update(indexName, docId, document, options) {
9437
+ return withOpenIndex(indexName, () => updateDocument(mutationCtx, indexName, docId, document, options));
10471
9438
  },
10472
- update(indexName, docId, document) {
10473
- return withOpenIndex(indexName, () => updateDocument(mutationCtx, indexName, docId, document));
9439
+ updateBatch(indexName, updates, options) {
9440
+ return withOpenIndex(indexName, () => updateDocumentBatch(mutationCtx, indexName, updates, options));
10474
9441
  },
10475
- updateBatch(indexName, updates) {
10476
- return withOpenIndex(indexName, () => updateDocumentBatch(mutationCtx, indexName, updates));
9442
+ waitForWrites(indexName) {
9443
+ guardShutdown();
9444
+ requireIndex(indexName);
9445
+ return orchestrator.awaitWrites(indexName);
10477
9446
  },
10478
9447
  async get(indexName, docId) {
10479
9448
  return withOpenIndex(indexName, () => executor.execute({ type: "get", indexName, docId, requestId: docId }));
@@ -10537,6 +9506,7 @@ function createNarsilFromCore(core, config) {
10537
9506
  clearAnalysisStale: core.analysisRebuild.clearStale,
10538
9507
  indexState: core.indexState
10539
9508
  });
9509
+ core.heapPressureNotifier.check(indexName);
10540
9510
  },
10541
9511
  async checkpoint(indexName) {
10542
9512
  guardShutdown();
@@ -10662,6 +9632,7 @@ function createNarsilFromCore(core, config) {
10662
9632
  await shutdownEngine(core);
10663
9633
  }
10664
9634
  };
9635
+ bindEngineCore(narsil, core);
10665
9636
  return narsil;
10666
9637
  }
10667
9638
 
@@ -10670,6 +9641,7 @@ async function createNarsil(config) {
10670
9641
  const core = createEngineCore(config);
10671
9642
  if (core.durability) {
10672
9643
  await core.durability.manager.recover(config?.lifecycle !== void 0);
9644
+ checkHeapAfterRecovery(core);
10673
9645
  }
10674
9646
  if (core.invalidation) {
10675
9647
  await core.invalidation.start();
@@ -10681,4 +9653,4 @@ async function createNarsil(config) {
10681
9653
  // src/index.ts
10682
9654
  var VERSION = "0.1.0";
10683
9655
 
10684
- export { VERSION, applyDeleteEntry, applyIndexEntry, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, dedupePinnedEntries, foldGroupReducer, listBindingOf, now, queryBindingOf, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
9656
+ export { VERSION, applyDeleteEntry, applyIndexEntry, checkHeapAfterRecovery, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };