@delali/narsil 0.2.4-next.51 → 0.2.4-next.54
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 +33 -27
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/{capabilities-BWm4IITU.d.ts → capabilities-DlYw0Ov0.d.ts} +13 -2
- package/dist/{chunk-KMMJMDUA.mjs → chunk-3L4U6EJQ.mjs} +15 -10
- package/dist/chunk-4NVPQ6S5.mjs +366 -0
- package/dist/{chunk-CMVG2UEF.mjs → chunk-5T6AOR37.mjs} +56 -518
- package/dist/{chunk-ND34I3SV.mjs → chunk-BH5SYB5W.mjs} +3295 -2149
- package/dist/{chunk-XE6XR2OE.mjs → chunk-D2S642IV.mjs} +4 -2
- package/dist/{chunk-P4ELXVJQ.mjs → chunk-FRQVMFY7.mjs} +7 -1
- package/dist/chunk-IFBKLBGB.mjs +7 -0
- package/dist/chunk-L225SHNB.mjs +513 -0
- package/dist/{chunk-MHE7YWAU.mjs → chunk-NTZYA3SJ.mjs} +3 -3
- package/dist/{chunk-WYJXSLER.mjs → chunk-POWOTUKJ.mjs} +7 -1
- package/dist/{chunk-CV5H7XL7.mjs → chunk-RGS7FMCB.mjs} +53 -10
- package/dist/{chunk-ZHUQKZBE.mjs → chunk-TVT5V4E7.mjs} +101 -34
- package/dist/chunk-U2AL3SON.mjs +4198 -0
- package/dist/chunk-UTOFOJOR.mjs +135 -0
- package/dist/{chunk-BEIK3EOZ.mjs → chunk-XNQAIO74.mjs} +5523 -3847
- package/dist/chunk-Y3L5MDRV.mjs +96 -0
- package/dist/chunk-ZJ4CSAB2.mjs +13 -0
- package/dist/{client-B_X6_AbT.d.ts → client-CQUqt9Ir.d.ts} +13 -4
- package/dist/client.d.ts +8 -8
- package/dist/client.mjs +20 -14
- package/dist/{config-Czh2YNDi.d.ts → config-CNas6wJH.d.ts} +2 -2
- package/dist/{config-OON7IJ_B.d.ts → config-bXxnJrQc.d.ts} +80 -35
- package/dist/direct-executor-RBA5O7EN.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +1 -1
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/transport/grpc.mjs +1 -1
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +32 -17
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-9PtFY50q.d.ts → engine-Dj4I91Jv.d.ts} +21 -8
- package/dist/index.browser.mjs +12962 -7786
- package/dist/index.d.ts +9 -21
- package/dist/index.mjs +14 -13
- package/dist/{memory-Bzd5xcEW.d.ts → memory-xrXOZMbw.d.ts} +68 -13
- package/dist/persistence/durability/checkpoint-worker.d.ts +138 -3
- package/dist/persistence/durability/checkpoint-worker.mjs +32 -18
- package/dist/react.d.ts +11 -11
- package/dist/react.mjs +16 -4
- package/dist/{schema-BqRx9t4E.d.ts → schema-mLpXZBEh.d.ts} +43 -12
- package/dist/serialization/crc32-worker.mjs +2 -1
- package/dist/server.d.ts +13 -10
- package/dist/server.mjs +136 -47
- package/dist/simd-DW5AtJ6B.d.ts +41 -0
- package/dist/{tasks-DT7L1p6w.d.ts → tasks-Df73Tjgq.d.ts} +9 -5
- package/dist/{thread-PZLEO6JL.mjs → thread-5UP6MGED.mjs} +131 -43
- package/dist/{types-DXPxaXPO.d.ts → types-BTIVO8DI.d.ts} +1 -1
- package/dist/vector/search-worker.d.ts +292 -128
- package/dist/vector/search-worker.mjs +70 -54
- package/dist/workers/entry.mjs +30 -14
- package/package.json +52 -31
- package/dist/brute-force-DbHYCTc3.d.ts +0 -8
- package/dist/chunk-CBLS46IE.mjs +0 -10
- package/dist/chunk-PIWKZPP7.mjs +0 -64
- package/dist/chunk-PMBHH44R.mjs +0 -492
- package/dist/chunk-RP23PWMV.mjs +0 -11
- package/dist/chunk-X6YZO4MQ.mjs +0 -1572
- package/dist/direct-executor-OREXU72C.mjs +0 -12
- package/dist/vector/hnsw-build-worker.d.ts +0 -105
- package/dist/vector/hnsw-build-worker.mjs +0 -123
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { MAX_SORT_FIELDS } from './chunk-
|
|
2
|
-
import {
|
|
1
|
+
import { MAX_SORT_FIELDS } from './chunk-L225SHNB.mjs';
|
|
2
|
+
import { nativeSearchCoreIsEnabled, loadNativeCore } from './chunk-UTOFOJOR.mjs';
|
|
3
|
+
import { NarsilError, ServerErrorCodes, ErrorCodes } from './chunk-FRQVMFY7.mjs';
|
|
3
4
|
|
|
4
5
|
// src/server/capabilities.ts
|
|
5
6
|
var ASYNC_IMPORT_CAPABILITY = "documents.import.async";
|
|
@@ -122,6 +123,7 @@ var STATUS_BY_CODE = {
|
|
|
122
123
|
PARTITION_CORRUPTED: 503,
|
|
123
124
|
WORKER_CRASHED: 503,
|
|
124
125
|
WORKER_TIMEOUT: 503,
|
|
126
|
+
WORKER_ACTION_FAILED: 500,
|
|
125
127
|
PERSISTENCE_SAVE_FAILED: 503,
|
|
126
128
|
PERSISTENCE_LOAD_FAILED: 503,
|
|
127
129
|
PERSISTENCE_DELETE_FAILED: 503,
|
|
@@ -217,12 +219,15 @@ var DEFAULT_MAX_BODY_BYTES = 16 * BYTES_PER_MB;
|
|
|
217
219
|
var DEFAULT_MAX_IMPORT_BYTES = 100 * BYTES_PER_MB;
|
|
218
220
|
var DEFAULT_MAX_LINE_BYTES = 4 * BYTES_PER_MB;
|
|
219
221
|
var DEFAULT_IMPORT_BATCH_SIZE = 1e3;
|
|
222
|
+
var IMPORT_FLUSHES_IN_FLIGHT = 4;
|
|
220
223
|
var DEFAULT_MAX_CONCURRENT_REQUESTS = 0;
|
|
221
224
|
var DEFAULT_MAX_RESULT_WINDOW = 1e4;
|
|
222
225
|
var DEFAULT_MAX_FETCH_DOCUMENTS = 1e4;
|
|
223
226
|
var DEFAULT_MAX_IMPORT_ERRORS = 100;
|
|
224
227
|
var DEFAULT_MAX_TASK_PAGE_SIZE = 1e3;
|
|
225
228
|
var DEFAULT_MAX_CONCURRENT_TASKS = 4;
|
|
229
|
+
var MIN_COUNTED_SERVER_LIMIT = 1;
|
|
230
|
+
var MIN_UNBOUNDED_SERVER_LIMIT = 0;
|
|
226
231
|
var RESPONSE_STREAM_THRESHOLD_BYTES = 64 * 1024;
|
|
227
232
|
var MAX_INDEX_TOUCH_INTERVAL_MS = 1e3;
|
|
228
233
|
var INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT = 4;
|
|
@@ -483,7 +488,7 @@ function validateRebalance(body) {
|
|
|
483
488
|
return null;
|
|
484
489
|
}
|
|
485
490
|
var TASK_TYPES = ["optimizeVectors", "rebalance", "restore", "import", "rebuildAnalysis"];
|
|
486
|
-
var TASK_STATUSES = ["
|
|
491
|
+
var TASK_STATUSES = ["running", "succeeded", "failed", "cancelled"];
|
|
487
492
|
function parseCountParam(raw, field, minimum) {
|
|
488
493
|
if (raw === null) return minimum;
|
|
489
494
|
const value = Number(raw);
|
|
@@ -602,6 +607,22 @@ function indexDocNotFound(docId) {
|
|
|
602
607
|
return new NarsilError(ErrorCodes.DOC_NOT_FOUND, `Document "${docId}" not found`, { docId });
|
|
603
608
|
}
|
|
604
609
|
|
|
610
|
+
// src/vector/native/search-path.ts
|
|
611
|
+
function vectorSearchPath() {
|
|
612
|
+
return nativeSearchCoreIsEnabled() && loadNativeCore() !== null ? "native" : "wasm";
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/server/handlers/version.ts
|
|
616
|
+
function versionReport(build) {
|
|
617
|
+
return { name: "narsil", ...build, vectorSearch: vectorSearchPath() };
|
|
618
|
+
}
|
|
619
|
+
function createVersionHandler(deps) {
|
|
620
|
+
function report(ctx) {
|
|
621
|
+
respondJson(ctx, versionReport(deps.build));
|
|
622
|
+
}
|
|
623
|
+
return { report };
|
|
624
|
+
}
|
|
625
|
+
|
|
605
626
|
// src/server/request.ts
|
|
606
627
|
function initAbortHandler(res) {
|
|
607
628
|
const listeners = [];
|
|
@@ -620,7 +641,22 @@ function initAbortHandler(res) {
|
|
|
620
641
|
}
|
|
621
642
|
};
|
|
622
643
|
}
|
|
623
|
-
function
|
|
644
|
+
function declaredBodyBytes(header) {
|
|
645
|
+
return /^\d{1,15}$/.test(header) ? Number(header) : null;
|
|
646
|
+
}
|
|
647
|
+
function bodyWithRoomFor(body, filled, needed, maxBytes) {
|
|
648
|
+
if (needed <= body.length) return body;
|
|
649
|
+
const grown = Buffer.allocUnsafeSlow(Math.min(maxBytes, Math.max(needed, body.length * 2)));
|
|
650
|
+
grown.set(body.subarray(0, filled));
|
|
651
|
+
return grown;
|
|
652
|
+
}
|
|
653
|
+
function bodyOfExactly(body, filled) {
|
|
654
|
+
if (filled === body.length) return body;
|
|
655
|
+
const exact = Buffer.allocUnsafeSlow(filled);
|
|
656
|
+
exact.set(body.subarray(0, filled));
|
|
657
|
+
return exact;
|
|
658
|
+
}
|
|
659
|
+
function readBody(res, maxBytes, abort, declaredBytes = null) {
|
|
624
660
|
return new Promise((resolve, reject) => {
|
|
625
661
|
if (abort.aborted) {
|
|
626
662
|
reject(new Error("aborted"));
|
|
@@ -628,7 +664,9 @@ function readBody(res, maxBytes, abort) {
|
|
|
628
664
|
}
|
|
629
665
|
let done = false;
|
|
630
666
|
let total = 0;
|
|
631
|
-
|
|
667
|
+
let filled = 0;
|
|
668
|
+
let body = null;
|
|
669
|
+
const reserved = declaredBytes !== null && declaredBytes <= maxBytes ? declaredBytes : 0;
|
|
632
670
|
abort.onAbort(() => {
|
|
633
671
|
if (done) return;
|
|
634
672
|
done = true;
|
|
@@ -645,10 +683,14 @@ function readBody(res, maxBytes, abort) {
|
|
|
645
683
|
reject(new Error("payload too large"));
|
|
646
684
|
return;
|
|
647
685
|
}
|
|
648
|
-
|
|
686
|
+
const arrived = new Uint8Array(chunk);
|
|
687
|
+
const firstSize = isLast ? arrived.length : Math.max(reserved, arrived.length);
|
|
688
|
+
body = bodyWithRoomFor(body ?? Buffer.allocUnsafeSlow(firstSize), filled, total, maxBytes);
|
|
689
|
+
body.set(arrived, filled);
|
|
690
|
+
filled = total;
|
|
649
691
|
if (isLast) {
|
|
650
692
|
done = true;
|
|
651
|
-
resolve(
|
|
693
|
+
resolve(bodyOfExactly(body, filled));
|
|
652
694
|
}
|
|
653
695
|
});
|
|
654
696
|
});
|
|
@@ -703,6 +745,7 @@ function createRouteRunner(deps) {
|
|
|
703
745
|
}
|
|
704
746
|
const query = new URLSearchParams(req.getQuery() ?? "");
|
|
705
747
|
const contentType = req.getHeader("content-type");
|
|
748
|
+
const declaredBytes = needsBody ? declaredBodyBytes(req.getHeader("content-length")) : null;
|
|
706
749
|
let hookContext = null;
|
|
707
750
|
if (useHook) {
|
|
708
751
|
const headers = {};
|
|
@@ -728,7 +771,7 @@ function createRouteRunner(deps) {
|
|
|
728
771
|
);
|
|
729
772
|
return;
|
|
730
773
|
}
|
|
731
|
-
const bodyPromise = needsBody ? readBody(res, opts.maxBytes, abort) : Promise.resolve(null);
|
|
774
|
+
const bodyPromise = needsBody ? readBody(res, opts.maxBytes, abort, declaredBytes) : Promise.resolve(null);
|
|
732
775
|
const hookPromise = hookContext !== null && authorize !== void 0 ? authorize(hookContext) : Promise.resolve({ allowed: true });
|
|
733
776
|
Promise.all([bodyPromise, hookPromise]).then(async ([rawBody, authorization]) => {
|
|
734
777
|
if (abort.aborted) return;
|
|
@@ -767,7 +810,7 @@ function serverRoutes(limits) {
|
|
|
767
810
|
return [
|
|
768
811
|
{ method: "get", path: "/livez", handler: "livez", options: probe },
|
|
769
812
|
{ method: "get", path: "/readyz", handler: "readyz", options: probe },
|
|
770
|
-
{ method: "get", path: "/health", handler: "
|
|
813
|
+
{ method: "get", path: "/health", handler: "readyz", options: probe },
|
|
771
814
|
{ method: "get", path: "/version", handler: "version", options: probe },
|
|
772
815
|
{ method: "get", path: "/capabilities", handler: "capabilities", options: probe },
|
|
773
816
|
{ method: "get", path: "/cluster", handler: "clusterTopology", options: bare },
|
|
@@ -873,4 +916,4 @@ function childAppOf(app) {
|
|
|
873
916
|
return candidate;
|
|
874
917
|
}
|
|
875
918
|
|
|
876
|
-
export { ASYNC_IMPORT_CAPABILITY, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_RETAINED_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_TASK_PAGE_SIZE, INDEX_LIFECYCLE_CAPABILITY, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, MAIN_THREAD_GATE_SLOT, MAX_INDEX_TOUCH_INTERVAL_MS, REBUILD_ANALYSIS_CAPABILITY, RUNNING_TASK_TTL_MS, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, TASK_PROGRESS_WRITE_INTERVAL_MS, TERMINAL_TASK_TTL_MS, acceptorOf, authorizerFor, badRequest, childAppOf, clearGateSlot, corsWriter, createDocumentReadHandlers, createRouteRunner, createSharedGateBuffer, createSharedRequestGate, gateSlotOfWorker, httpStatusForNarsilError, loadUWebSockets, parseJson, parseJsonOptional, parseTaskListQuery, registerServerRoutes, rejectInvalid, resolveCors, respondError, respondJson, sendBinary, sendError, sendRelayed, serializeBatchResult, serializeNarsilError, serverRoutes, validateBatch, validateCreateIndex, validateDocumentBody, validateQuery, validateRebalance, validateSuggest };
|
|
919
|
+
export { ASYNC_IMPORT_CAPABILITY, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_RETAINED_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_TASK_PAGE_SIZE, IMPORT_FLUSHES_IN_FLIGHT, INDEX_LIFECYCLE_CAPABILITY, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, MAIN_THREAD_GATE_SLOT, MAX_INDEX_TOUCH_INTERVAL_MS, MIN_COUNTED_SERVER_LIMIT, MIN_UNBOUNDED_SERVER_LIMIT, REBUILD_ANALYSIS_CAPABILITY, RUNNING_TASK_TTL_MS, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, TASK_PROGRESS_WRITE_INTERVAL_MS, TERMINAL_TASK_TTL_MS, acceptorOf, authorizerFor, badRequest, childAppOf, clearGateSlot, corsWriter, createDocumentReadHandlers, createRouteRunner, createSharedGateBuffer, createSharedRequestGate, createVersionHandler, gateSlotOfWorker, httpStatusForNarsilError, loadUWebSockets, parseJson, parseJsonOptional, parseTaskListQuery, registerServerRoutes, rejectInvalid, resolveCors, respondError, respondJson, sendBinary, sendError, sendRelayed, serializeBatchResult, serializeNarsilError, serverRoutes, toHttpError, validateBatch, validateCreateIndex, validateDocumentBody, validateQuery, validateRebalance, validateSuggest, versionReport };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, MAX_DOC_ID_LENGTH, DEFAULT_LIMIT, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, DEFAULT_OFFSET } from './chunk-
|
|
2
|
-
import { clampRowCount, sortSignatureOf, requireSortableFields, decodePageCursor, requireMatchingCursor, resolveProjection, encodePageCursor, requireWithinResultWindow, normalizeSort, fanOutQuery,
|
|
1
|
+
import { MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, MAX_DOC_ID_LENGTH, DEFAULT_LIMIT, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, ABSOLUTE_WINDOWS_PATH_PATTERN, MODULE_URL_SCHEME_PATTERN, DEFAULT_OFFSET } from './chunk-D2S642IV.mjs';
|
|
2
|
+
import { clampRowCount, sortSignatureOf, requireSortableFields, decodePageCursor, requireMatchingCursor, resolveProjection, encodePageCursor, requireWithinResultWindow, normalizeSort, fanOutQuery, applySorting, applyPagination, projectionKeepsField, countsWithoutScores, fanOutMatchCount, tokenize, fnv1aBytes, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, partitionsIn } from './chunk-5T6AOR37.mjs';
|
|
3
|
+
import { flattenSchema } from './chunk-L225SHNB.mjs';
|
|
4
|
+
import { MIN_OVERSAMPLE } from './chunk-ZJ4CSAB2.mjs';
|
|
3
5
|
import { compareCodePoints, compareComparableKeys } from './chunk-2GWVQWGA.mjs';
|
|
4
|
-
import { NarsilError, ErrorCodes } from './chunk-
|
|
6
|
+
import { NarsilError, ErrorCodes } from './chunk-FRQVMFY7.mjs';
|
|
5
7
|
|
|
6
8
|
// src/search/cursor-binding.ts
|
|
7
9
|
var ABSENT_TAG = 0;
|
|
@@ -169,32 +171,37 @@ function now() {
|
|
|
169
171
|
return Date.now();
|
|
170
172
|
}
|
|
171
173
|
function validateIndexName(name) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
const given = name;
|
|
175
|
+
if (typeof given !== "string" || name.length === 0) {
|
|
176
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "Index name must be a string of at least one character", {
|
|
177
|
+
received: given === null ? "null" : typeof given
|
|
178
|
+
});
|
|
174
179
|
}
|
|
175
180
|
if (name.length > MAX_INDEX_NAME_LENGTH) {
|
|
176
|
-
throw new NarsilError(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
indexName: name,
|
|
181
|
-
length: name.length
|
|
182
|
-
}
|
|
183
|
-
);
|
|
181
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`, {
|
|
182
|
+
indexName: name,
|
|
183
|
+
length: name.length
|
|
184
|
+
});
|
|
184
185
|
}
|
|
185
186
|
if (!INDEX_NAME_PATTERN.test(name)) {
|
|
186
187
|
throw new NarsilError(
|
|
187
|
-
ErrorCodes.
|
|
188
|
+
ErrorCodes.CONFIG_INVALID,
|
|
188
189
|
`Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
|
|
189
190
|
{ indexName: name }
|
|
190
191
|
);
|
|
191
192
|
}
|
|
192
193
|
if (name.includes("..")) {
|
|
193
|
-
throw new NarsilError(ErrorCodes.
|
|
194
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Index name "${name}" must not contain ".."`, { indexName: name });
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
function validateDocId(docId) {
|
|
197
|
-
|
|
198
|
+
const given = docId;
|
|
199
|
+
if (typeof given !== "string") {
|
|
200
|
+
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must be a string", {
|
|
201
|
+
received: given === null ? "null" : Array.isArray(given) ? "array" : typeof given
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (docId.length === 0) {
|
|
198
205
|
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
|
|
199
206
|
}
|
|
200
207
|
if (docId.length > MAX_DOC_ID_LENGTH) {
|
|
@@ -235,6 +242,16 @@ function requirePositiveSafeInteger(field, value) {
|
|
|
235
242
|
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `${field} must be a positive safe integer`, { field, value });
|
|
236
243
|
}
|
|
237
244
|
}
|
|
245
|
+
function validateBootstrapModule(bootstrapModule) {
|
|
246
|
+
if (bootstrapModule === void 0) return;
|
|
247
|
+
const absolute = bootstrapModule.startsWith("/") || ABSOLUTE_WINDOWS_PATH_PATTERN.test(bootstrapModule) || MODULE_URL_SCHEME_PATTERN.test(bootstrapModule);
|
|
248
|
+
if (absolute) return;
|
|
249
|
+
throw new NarsilError(
|
|
250
|
+
ErrorCodes.CONFIG_INVALID,
|
|
251
|
+
`workers.bootstrapModule is "${bootstrapModule}", which a worker would resolve against Narsil's own directory, so pass an absolute path or a URL such as new URL('./bootstrap.js', import.meta.url).href`,
|
|
252
|
+
{ bootstrapModule }
|
|
253
|
+
);
|
|
254
|
+
}
|
|
238
255
|
function validateWorkerConfig(workers, lifecycle) {
|
|
239
256
|
if (workers === void 0) return;
|
|
240
257
|
requirePositiveSafeInteger("workers.count", workers.count);
|
|
@@ -245,6 +262,7 @@ function validateWorkerConfig(workers, lifecycle) {
|
|
|
245
262
|
mainCopyQueries: workers.mainCopyQueries
|
|
246
263
|
});
|
|
247
264
|
}
|
|
265
|
+
validateBootstrapModule(workers.bootstrapModule);
|
|
248
266
|
const closeAfter = lifecycle?.idleTimeoutMs;
|
|
249
267
|
if (workers.idleTimeoutMs !== void 0 && closeAfter !== void 0 && workers.idleTimeoutMs > closeAfter) {
|
|
250
268
|
throw new NarsilError(
|
|
@@ -566,21 +584,19 @@ function findDensestWindow(positions, text, windowSize) {
|
|
|
566
584
|
}
|
|
567
585
|
function applySnippetTruncation(taggedText, originalText, positions, maxLen, preTag, postTag) {
|
|
568
586
|
if (maxLen <= 0 || originalText.length <= maxLen) {
|
|
569
|
-
return { snippet: taggedText,
|
|
587
|
+
return { snippet: taggedText, visiblePositions: positions };
|
|
570
588
|
}
|
|
571
589
|
const window = findDensestWindow(positions, originalText, maxLen);
|
|
572
590
|
const snippetRaw = originalText.substring(window.start, window.end);
|
|
573
|
-
const
|
|
591
|
+
const visiblePositions = [];
|
|
592
|
+
const withinSnippet = [];
|
|
574
593
|
for (const pos of positions) {
|
|
575
|
-
if (pos.start
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
end: pos.end - window.start
|
|
579
|
-
});
|
|
580
|
-
}
|
|
594
|
+
if (pos.start < window.start || pos.end > window.end) continue;
|
|
595
|
+
visiblePositions.push(pos);
|
|
596
|
+
withinSnippet.push({ start: pos.start - window.start, end: pos.end - window.start });
|
|
581
597
|
}
|
|
582
598
|
let snippet = snippetRaw;
|
|
583
|
-
const sortedDesc = [...
|
|
599
|
+
const sortedDesc = [...withinSnippet].sort((a, b) => b.start - a.start);
|
|
584
600
|
for (const pos of sortedDesc) {
|
|
585
601
|
snippet = snippet.substring(0, pos.start) + preTag + snippet.substring(pos.start, pos.end) + postTag + snippet.substring(pos.end);
|
|
586
602
|
}
|
|
@@ -588,7 +604,7 @@ function applySnippetTruncation(taggedText, originalText, positions, maxLen, pre
|
|
|
588
604
|
const needsTrailingEllipsis = window.end < originalText.length;
|
|
589
605
|
if (needsLeadingEllipsis) snippet = `...${snippet}`;
|
|
590
606
|
if (needsTrailingEllipsis) snippet = `${snippet}...`;
|
|
591
|
-
return { snippet,
|
|
607
|
+
return { snippet, visiblePositions };
|
|
592
608
|
}
|
|
593
609
|
function highlightField(text, queryTokens, language, options) {
|
|
594
610
|
const preTag = options?.preTag ?? "<mark>";
|
|
@@ -635,7 +651,7 @@ function highlightField(text, queryTokens, language, options) {
|
|
|
635
651
|
return { snippet: text, positions: [] };
|
|
636
652
|
}
|
|
637
653
|
if (maxSnippetLength > 0 && text.length > maxSnippetLength) {
|
|
638
|
-
const { snippet,
|
|
654
|
+
const { snippet, visiblePositions } = applySnippetTruncation(
|
|
639
655
|
text,
|
|
640
656
|
text,
|
|
641
657
|
mergedPositions,
|
|
@@ -643,7 +659,7 @@ function highlightField(text, queryTokens, language, options) {
|
|
|
643
659
|
preTag,
|
|
644
660
|
postTag
|
|
645
661
|
);
|
|
646
|
-
return { snippet, positions:
|
|
662
|
+
return { snippet, positions: visiblePositions };
|
|
647
663
|
}
|
|
648
664
|
let result = text;
|
|
649
665
|
const sortedDesc = [...mergedPositions].sort((a, b) => b.start - a.start);
|
|
@@ -654,7 +670,7 @@ function highlightField(text, queryTokens, language, options) {
|
|
|
654
670
|
}
|
|
655
671
|
|
|
656
672
|
// src/engine/query/highlight.ts
|
|
657
|
-
function applyHighlights(hits, params, language, analysis) {
|
|
673
|
+
function applyHighlights(hits, params, language, analysis, readStoredDocument) {
|
|
658
674
|
if (!params.highlight) return;
|
|
659
675
|
const queryTokenResult = tokenize(params.term ?? "", language, {
|
|
660
676
|
stem: true,
|
|
@@ -680,9 +696,14 @@ function applyHighlights(hits, params, language, analysis) {
|
|
|
680
696
|
}
|
|
681
697
|
for (const hit of hits) {
|
|
682
698
|
const highlights = {};
|
|
699
|
+
const projected = hit.document;
|
|
700
|
+
let stored;
|
|
683
701
|
for (const field of params.highlight.fields) {
|
|
684
|
-
|
|
685
|
-
|
|
702
|
+
let fieldValue = projected[field];
|
|
703
|
+
if (typeof fieldValue !== "string" && readStoredDocument !== void 0) {
|
|
704
|
+
stored ??= readStoredDocument(hit.id);
|
|
705
|
+
fieldValue = stored?.[field];
|
|
706
|
+
}
|
|
686
707
|
if (typeof fieldValue === "string") {
|
|
687
708
|
highlights[field] = highlightField(fieldValue, queryTokenResult.tokens, language, {
|
|
688
709
|
preTag: params.highlight.preTag,
|
|
@@ -776,6 +797,32 @@ function vectorSearchersOf(context) {
|
|
|
776
797
|
function resolveVectorIndex(context, fieldName) {
|
|
777
798
|
return vectorSearchersOf(context).get(fieldName);
|
|
778
799
|
}
|
|
800
|
+
var SEARCH_MODES = /* @__PURE__ */ new Set(["fulltext", "vector", "hybrid"]);
|
|
801
|
+
function requireKnownMode(params) {
|
|
802
|
+
if (params.mode === void 0 || SEARCH_MODES.has(params.mode)) return;
|
|
803
|
+
throw new NarsilError(
|
|
804
|
+
ErrorCodes.SEARCH_INVALID_MODE,
|
|
805
|
+
`A search takes the mode "fulltext", "vector", or "hybrid", while this query names "${String(params.mode)}"`,
|
|
806
|
+
{ mode: String(params.mode), modes: [...SEARCH_MODES] }
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
function requireVectorSearchable(params, context, needsVector) {
|
|
810
|
+
if (!needsVector) return;
|
|
811
|
+
const field = params.vector?.field;
|
|
812
|
+
if (field === void 0) {
|
|
813
|
+
throw new NarsilError(
|
|
814
|
+
ErrorCodes.SEARCH_INVALID_MODE,
|
|
815
|
+
`A "${params.mode}" query needs a vector clause naming the field to search`,
|
|
816
|
+
{ mode: params.mode }
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
if (vectorSearchersOf(context).has(field)) return;
|
|
820
|
+
throw new NarsilError(
|
|
821
|
+
ErrorCodes.SEARCH_INVALID_FIELD,
|
|
822
|
+
`Field "${field}" has no vector index, so a vector search cannot use it`,
|
|
823
|
+
{ field, vectorFields: [...vectorSearchersOf(context).keys()] }
|
|
824
|
+
);
|
|
825
|
+
}
|
|
779
826
|
function clampAlpha(alpha) {
|
|
780
827
|
if (alpha === void 0) return 0.5;
|
|
781
828
|
if (!Number.isFinite(alpha)) return 0.5;
|
|
@@ -938,6 +985,18 @@ function minMaxNormalize(docs) {
|
|
|
938
985
|
}
|
|
939
986
|
|
|
940
987
|
// src/engine/query/vector.ts
|
|
988
|
+
function oversampleOf(vectorConfig) {
|
|
989
|
+
const oversample = vectorConfig.oversample;
|
|
990
|
+
if (oversample === void 0) return void 0;
|
|
991
|
+
if (typeof oversample !== "number" || !Number.isFinite(oversample) || oversample < MIN_OVERSAMPLE) {
|
|
992
|
+
throw new NarsilError(
|
|
993
|
+
ErrorCodes.CONFIG_INVALID,
|
|
994
|
+
`vector.oversample must be a finite number of at least ${MIN_OVERSAMPLE}`,
|
|
995
|
+
{ oversample }
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return oversample;
|
|
999
|
+
}
|
|
941
1000
|
async function executeVectorSearch(params, context, limit, offset) {
|
|
942
1001
|
const { manager, config, partitionIds } = context;
|
|
943
1002
|
const vectorConfig = params.vector;
|
|
@@ -965,7 +1024,8 @@ async function executeVectorSearch(params, context, limit, offset) {
|
|
|
965
1024
|
minSimilarity: vectorConfig.similarity ?? -Infinity,
|
|
966
1025
|
...filterDocIds !== void 0 ? { filterDocIds } : {},
|
|
967
1026
|
...filterPartitions !== void 0 ? { filterPartitions } : {},
|
|
968
|
-
efSearch: vectorConfig.efSearch
|
|
1027
|
+
efSearch: vectorConfig.efSearch,
|
|
1028
|
+
oversample: oversampleOf(vectorConfig)
|
|
969
1029
|
});
|
|
970
1030
|
const scored = vectorResultsToScored(results);
|
|
971
1031
|
return { scored, totalMatched: scored.length };
|
|
@@ -994,7 +1054,8 @@ async function vectorLeg(vectorConfig, queryVector, context, filterDocIds, k) {
|
|
|
994
1054
|
minSimilarity: vectorConfig.similarity ?? -Infinity,
|
|
995
1055
|
...filterDocIds !== void 0 ? { filterDocIds } : {},
|
|
996
1056
|
...filterPartitions !== void 0 ? { filterPartitions } : {},
|
|
997
|
-
efSearch: vectorConfig.efSearch
|
|
1057
|
+
efSearch: vectorConfig.efSearch,
|
|
1058
|
+
oversample: oversampleOf(vectorConfig)
|
|
998
1059
|
});
|
|
999
1060
|
return vectorResultsToScored(results);
|
|
1000
1061
|
}
|
|
@@ -1049,6 +1110,7 @@ async function executeQuery(params, context) {
|
|
|
1049
1110
|
const offset = clampOffset(params.offset);
|
|
1050
1111
|
requireWithinResultWindow(limit, offset);
|
|
1051
1112
|
const sortFields = normalizeSort(params.sort);
|
|
1113
|
+
requireKnownMode(params);
|
|
1052
1114
|
const sortSignature = sortSignatureOf(params.sort);
|
|
1053
1115
|
const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
|
|
1054
1116
|
const hasVector = params.vector !== void 0 && params.vector.value !== void 0;
|
|
@@ -1062,6 +1124,7 @@ async function executeQuery(params, context) {
|
|
|
1062
1124
|
);
|
|
1063
1125
|
}
|
|
1064
1126
|
requireSortableFields(params.sort, config.schema);
|
|
1127
|
+
requireVectorSearchable(params, context, isVectorOnly || isHybridMode);
|
|
1065
1128
|
let paginated;
|
|
1066
1129
|
let nextCursor;
|
|
1067
1130
|
let count;
|
|
@@ -1185,7 +1248,9 @@ async function executeQuery(params, context) {
|
|
|
1185
1248
|
}
|
|
1186
1249
|
}
|
|
1187
1250
|
if (params.highlight) {
|
|
1188
|
-
|
|
1251
|
+
const dropped = params.highlight.fields.some((field) => !projectionKeepsField(projection, field));
|
|
1252
|
+
const readStoredDocument = dropped ? (docId) => manager.get(docId, { kind: "full" }) : void 0;
|
|
1253
|
+
applyHighlights(paginated, params, language, manager.analysis, readStoredDocument);
|
|
1189
1254
|
}
|
|
1190
1255
|
const elapsed = now() - startTime;
|
|
1191
1256
|
return {
|
|
@@ -1201,10 +1266,12 @@ async function executeQuery(params, context) {
|
|
|
1201
1266
|
async function executePreflight(params, context) {
|
|
1202
1267
|
const { manager, language, config, workerSearch, indexName } = context;
|
|
1203
1268
|
const startTime = now();
|
|
1269
|
+
requireKnownMode(params);
|
|
1204
1270
|
const hasTerm = params.term !== void 0 && params.term.trim().length > 0;
|
|
1205
1271
|
const hasVector = params.vector !== void 0 && params.vector.value !== void 0;
|
|
1206
1272
|
const isHybridMode = params.mode === "hybrid" || hasTerm && hasVector;
|
|
1207
1273
|
const isVectorOnly = (params.mode === "vector" || hasVector && !hasTerm) && !isHybridMode;
|
|
1274
|
+
requireVectorSearchable(params, context, isVectorOnly || isHybridMode);
|
|
1208
1275
|
const requestedVectorField = params.vector?.field;
|
|
1209
1276
|
const hasGlobalVectorIndex = requestedVectorField !== void 0 && vectorSearchersOf(context).has(requestedVectorField);
|
|
1210
1277
|
let totalMatched;
|