@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.
- package/README.md +1 -1
- package/dist/{options-Dtk99qUs.d.ts → capabilities-JrVQOwi4.d.ts} +55 -47
- package/dist/chunk-AV4IJKD2.mjs +35 -0
- package/dist/{chunk-M6JGMKA5.mjs → chunk-BEIK3EOZ.mjs} +503 -2273
- package/dist/chunk-CMVG2UEF.mjs +2038 -0
- package/dist/chunk-CV5H7XL7.mjs +876 -0
- package/dist/{chunk-KGRYQVZ5.mjs → chunk-MHE7YWAU.mjs} +2 -1
- package/dist/{chunk-56PJBIJ6.mjs → chunk-ND34I3SV.mjs} +427 -1455
- package/dist/chunk-PIWKZPP7.mjs +64 -0
- package/dist/{chunk-HGJC5L65.mjs → chunk-PMBHH44R.mjs} +121 -2
- package/dist/chunk-RP23PWMV.mjs +11 -0
- package/dist/{chunk-RDGXYRF4.mjs → chunk-WYJXSLER.mjs} +8 -19
- package/dist/{chunk-JXHUHFJ5.mjs → chunk-X6YZO4MQ.mjs} +2 -11
- package/dist/{chunk-OWCFK42Q.mjs → chunk-XE6XR2OE.mjs} +3 -10
- package/dist/chunk-ZHUQKZBE.mjs +1292 -0
- package/dist/{client-DX7xPcNA.d.ts → client-CBOVHdjp.d.ts} +31 -7
- package/dist/client.d.ts +7 -7
- package/dist/client.mjs +26 -10
- package/dist/{config-B41W23Us.d.ts → config-Czh2YNDi.d.ts} +2 -2
- package/dist/{config-Co-zAXqO.d.ts → config-OON7IJ_B.d.ts} +26 -5
- package/dist/direct-executor-OREXU72C.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +28 -11
- package/dist/{engine-CABZg25p.d.ts → engine-9PtFY50q.d.ts} +114 -62
- package/dist/index.browser.mjs +3255 -2516
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +11 -6
- package/dist/{search-FjWhZN3f.d.ts → memory-Bzd5xcEW.d.ts} +88 -79
- package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +11 -6
- package/dist/react.d.ts +4 -4
- package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
- package/dist/server.d.ts +47 -42
- package/dist/server.mjs +321 -817
- package/dist/thread-PZLEO6JL.mjs +310 -0
- package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
- package/dist/vector/hnsw-build-worker.mjs +2 -1
- package/dist/vector/search-worker.mjs +3 -121
- package/dist/workers/entry.mjs +47 -3
- package/package.json +5 -1
- 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,
|
|
3
|
-
import {
|
|
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
|
|
6
|
-
import {
|
|
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
|
-
|
|
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
|
|
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 (
|
|
840
|
-
|
|
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
|
|
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 (
|
|
1480
|
+
if (settled2) {
|
|
1471
1481
|
return;
|
|
1472
1482
|
}
|
|
1473
|
-
|
|
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(
|
|
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
|
|
5634
|
+
if (queue !== void 0) await settled(queue);
|
|
5526
5635
|
return;
|
|
5527
5636
|
}
|
|
5528
5637
|
while (state.replicationQueues.size > 0) {
|
|
5529
|
-
|
|
5530
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
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(
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
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
|
-
|
|
6647
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
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
|
-
|
|
10470
|
-
return withOpenIndex(indexName, () =>
|
|
9436
|
+
update(indexName, docId, document, options) {
|
|
9437
|
+
return withOpenIndex(indexName, () => updateDocument(mutationCtx, indexName, docId, document, options));
|
|
10471
9438
|
},
|
|
10472
|
-
|
|
10473
|
-
return withOpenIndex(indexName, () =>
|
|
9439
|
+
updateBatch(indexName, updates, options) {
|
|
9440
|
+
return withOpenIndex(indexName, () => updateDocumentBatch(mutationCtx, indexName, updates, options));
|
|
10474
9441
|
},
|
|
10475
|
-
|
|
10476
|
-
|
|
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,
|
|
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 };
|