@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
|
@@ -40,14 +40,36 @@ type SchemaDefinition = {
|
|
|
40
40
|
[field: string]: FieldType | SchemaDefinition;
|
|
41
41
|
};
|
|
42
42
|
/**
|
|
43
|
-
* How the engine
|
|
43
|
+
* How the engine codes vectors once a field is promoted to an HNSW graph.
|
|
44
44
|
*
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
45
|
+
* Every `osq` mode applies optimised scalar quantization, which centres each
|
|
46
|
+
* vector on the field's centroid and then fits an interval to that vector
|
|
47
|
+
* alone. The digit in the mode name gives the bits that each dimension's code
|
|
48
|
+
* holds, so `osq8` stores a byte per dimension, while `osq4`, `osq2`, and
|
|
49
|
+
* `osq1` store four, two, and one bit. A search ranks its candidates by their
|
|
50
|
+
* codes before it re-scores the nearest of them against the full-precision
|
|
51
|
+
* vectors. `none` stores no code, so the engine ranks by the vectors alone.
|
|
52
|
+
*
|
|
53
|
+
* When the configuration leaves this unset, the engine takes `osq4` at 384
|
|
54
|
+
* dimensions and above and `osq8` below 384.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
type VectorQuantizationMode = 'osq8' | 'osq4' | 'osq2' | 'osq1' | 'none';
|
|
59
|
+
/**
|
|
60
|
+
* Where a vector field keeps its full-precision vectors.
|
|
61
|
+
*
|
|
62
|
+
* A `memory` field holds them in memory. A `disk` field keeps its codes and
|
|
63
|
+
* its graph in memory, while it reads a full-precision vector from the
|
|
64
|
+
* field's checkpoint file whenever a search re-scores that candidate or a
|
|
65
|
+
* document read returns it. A `disk` field therefore needs filesystem
|
|
66
|
+
* durability. Asking for one on an engine without it fails with
|
|
67
|
+
* `CONFIG_INVALID`. When the configuration leaves this unset, an engine with
|
|
68
|
+
* filesystem durability takes `disk` and every other engine takes `memory`.
|
|
47
69
|
*
|
|
48
70
|
* @public
|
|
49
71
|
*/
|
|
50
|
-
type
|
|
72
|
+
type VectorStorageMode = 'memory' | 'disk';
|
|
51
73
|
/**
|
|
52
74
|
* Controls when a vector field moves from a brute-force scan to an HNSW graph,
|
|
53
75
|
* and how that graph is built.
|
|
@@ -74,8 +96,10 @@ interface VectorIndexConfig {
|
|
|
74
96
|
efConstruction?: number;
|
|
75
97
|
metric?: 'cosine' | 'dotProduct' | 'euclidean';
|
|
76
98
|
};
|
|
77
|
-
/** The engine
|
|
99
|
+
/** The engine codes the promoted vectors in this mode. When this is unset, it picks the mode by the field's dimension. Set `none` to rank by the vectors alone. */
|
|
78
100
|
quantization?: VectorQuantizationMode;
|
|
101
|
+
/** The engine keeps the field's full-precision vectors here. When this is unset, it picks `disk` under filesystem durability and `memory` on every other engine. */
|
|
102
|
+
storage?: VectorStorageMode;
|
|
79
103
|
}
|
|
80
104
|
/**
|
|
81
105
|
* Connects an index to an embedding adapter and says which fields feed it.
|
|
@@ -190,19 +214,26 @@ interface CustomTokenizer {
|
|
|
190
214
|
}>;
|
|
191
215
|
}
|
|
192
216
|
/**
|
|
193
|
-
* How an index
|
|
217
|
+
* How many partitions an index opens with, and how many documents it accepts.
|
|
194
218
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
219
|
+
* An index opens with `maxPartitions` partitions and keeps that count until
|
|
220
|
+
* {@link Narsil.rebalance} changes it, because the engine adds no partition on
|
|
221
|
+
* its own. Set `watermark` to learn through the `partitionWatermark` event
|
|
222
|
+
* when an index fills its capacity.
|
|
197
223
|
*
|
|
198
224
|
* @public
|
|
199
225
|
*/
|
|
200
226
|
interface PartitionConfig {
|
|
201
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* The index accepts this many documents for each of its partitions, so its
|
|
229
|
+
* capacity is this number multiplied by its partition count. A write beyond
|
|
230
|
+
* that capacity raises `PARTITION_CAPACITY_EXCEEDED`. Leave the field unset
|
|
231
|
+
* to accept any number.
|
|
232
|
+
*/
|
|
202
233
|
maxDocsPerPartition?: number;
|
|
203
|
-
/** The index
|
|
234
|
+
/** The index opens with this many partitions, 1 by default, and no rebalance may take it higher. */
|
|
204
235
|
maxPartitions?: number;
|
|
205
|
-
/** The engine
|
|
236
|
+
/** The engine emits `partitionWatermark` once the index fills this share of its capacity, above 0 and at most 1. */
|
|
206
237
|
watermark?: number;
|
|
207
238
|
}
|
|
208
239
|
/**
|
|
@@ -250,4 +281,4 @@ interface InsertOptions extends WriteOptions {
|
|
|
250
281
|
skipClone?: boolean;
|
|
251
282
|
}
|
|
252
283
|
|
|
253
|
-
export type { AnyDocument as A, BM25Params as B, CustomTokenizer as C, EmbeddingFieldConfig as E, FieldType as F, IndexConfig as I, PartitionConfig as P, SchemaDefinition as S, VectorIndexConfig as V, WriteOptions as W,
|
|
284
|
+
export type { AnyDocument as A, BM25Params as B, CustomTokenizer as C, EmbeddingFieldConfig as E, FieldType as F, IndexConfig as I, PartitionConfig as P, SchemaDefinition as S, VectorIndexConfig as V, WriteOptions as W, InsertOptions as a, StopWordOverride as b, ScoringMode as c, VectorQuantizationMode as d, VectorStorageMode as e };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export {
|
|
1
|
+
export { C as ClientErrorCode, a as ClientErrorCodes, E as ErrorCode, b as ErrorCodes, N as NarsilError, c as NarsilErrorCode, S as ServerErrorCode, d as ServerErrorCodes, i as isNarsilError } from './memory-xrXOZMbw.js';
|
|
2
|
+
import { b as ServerOptions, N as NarsilServer } from './capabilities-DlYw0Ov0.js';
|
|
3
|
+
export { A as ASYNC_IMPORT_CAPABILITY, C as CorsOptions, E as ErrorEnvelope, I as INDEX_LIFECYCLE_CAPABILITY, O as OnRequestHook, R as REBUILD_ANALYSIS_CAPABILITY, c as RequestContext, d as RequestDenial, S as SERVER_CAPABILITIES, e as ServerLimits, T as TASK_CANCEL_CAPABILITY, a as TASK_FILTER_CAPABILITY } from './capabilities-DlYw0Ov0.js';
|
|
4
|
+
import { a as Narsil } from './engine-Dj4I91Jv.js';
|
|
5
|
+
import { d as TaskProgress, b as ImportResult, h as TaskStore, e as TaskRecord } from './tasks-Df73Tjgq.js';
|
|
6
|
+
export { C as CapabilitiesResponse, a as CreateIndexEmbedding, i as CreateIndexRequest, H as HttpIndexConfig, I as ImportError, M as MemoryStatsResponse, T as TaskListPage, c as TaskListQuery, f as TaskStatus, g as TaskType } from './tasks-Df73Tjgq.js';
|
|
7
|
+
import './schema-mLpXZBEh.js';
|
|
7
8
|
import './adapters-79OIa4xm.js';
|
|
8
|
-
import './config-
|
|
9
|
-
import './config-
|
|
10
|
-
import './
|
|
11
|
-
import './types-DXPxaXPO.js';
|
|
9
|
+
import './config-CNas6wJH.js';
|
|
10
|
+
import './config-bXxnJrQc.js';
|
|
11
|
+
import './types-BTIVO8DI.js';
|
|
12
12
|
import './types-D5OwhtsQ.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -59,6 +59,9 @@ type TaskOperation = (ctx: TaskContext) => Promise<void>;
|
|
|
59
59
|
* @param options - Address, CORS, request limits, the authentication hook, and
|
|
60
60
|
* the task store. Omit it to bind to loopback with the default limits.
|
|
61
61
|
* @returns The server, ready for {@link NarsilServer.listen}.
|
|
62
|
+
* @throws A `NarsilError` with `CONFIG_INVALID` where the first argument is
|
|
63
|
+
* some value other than an engine, and where a field of `options.limits` holds
|
|
64
|
+
* a value outside its range.
|
|
62
65
|
*
|
|
63
66
|
* @public
|
|
64
67
|
*/
|
package/dist/server.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsWriter, createSharedRequestGate, MAIN_THREAD_GATE_SLOT, registerServerRoutes, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_BODY_BYTES, RUNNING_TASK_TTL_MS, DEFAULT_TASK_PAGE_SIZE, TERMINAL_TASK_TTL_MS, TASK_PROGRESS_WRITE_INTERVAL_MS, serializeNarsilError, acceptorOf, createDocumentReadHandlers, sendError, respondError, respondJson, MAX_INDEX_TOUCH_INTERVAL_MS, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, clearGateSlot, parseJson, validateCreateIndex, rejectInvalid, validateBatch, serializeBatchResult, validateDocumentBody, validateSuggest, validateQuery, parseTaskListQuery, parseJsonOptional, validateRebalance, sendBinary, badRequest, SERVER_CAPABILITIES } from './chunk-
|
|
2
|
-
export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-
|
|
3
|
-
import { validateIndexName } from './chunk-
|
|
4
|
-
import { engineCoreOf, resolveVectorText } from './chunk-
|
|
5
|
-
import './chunk-XE6XR2OE.mjs';
|
|
1
|
+
import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsWriter, createSharedRequestGate, MAIN_THREAD_GATE_SLOT, registerServerRoutes, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_BODY_BYTES, RUNNING_TASK_TTL_MS, DEFAULT_TASK_PAGE_SIZE, TERMINAL_TASK_TTL_MS, TASK_PROGRESS_WRITE_INTERVAL_MS, serializeNarsilError, createVersionHandler, acceptorOf, createDocumentReadHandlers, sendError, respondError, respondJson, MAX_INDEX_TOUCH_INTERVAL_MS, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, clearGateSlot, parseJson, validateCreateIndex, rejectInvalid, validateBatch, serializeBatchResult, validateDocumentBody, validateSuggest, validateQuery, parseTaskListQuery, parseJsonOptional, validateRebalance, sendBinary, IMPORT_FLUSHES_IN_FLIGHT, badRequest, SERVER_CAPABILITIES, toHttpError, MIN_UNBOUNDED_SERVER_LIMIT, MIN_COUNTED_SERVER_LIMIT } from './chunk-RGS7FMCB.mjs';
|
|
2
|
+
export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-RGS7FMCB.mjs';
|
|
3
|
+
import { validateIndexName } from './chunk-NTZYA3SJ.mjs';
|
|
4
|
+
import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-4NVPQ6S5.mjs';
|
|
6
5
|
import { detectRuntime } from './chunk-AV4IJKD2.mjs';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import
|
|
9
|
-
|
|
6
|
+
import './chunk-D2S642IV.mjs';
|
|
7
|
+
import './chunk-L225SHNB.mjs';
|
|
8
|
+
import './chunk-UTOFOJOR.mjs';
|
|
9
|
+
import { startIdleHeapCleanup } from './chunk-Y3L5MDRV.mjs';
|
|
10
|
+
import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-FRQVMFY7.mjs';
|
|
11
|
+
export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-FRQVMFY7.mjs';
|
|
10
12
|
import { randomUUID } from 'crypto';
|
|
11
13
|
import { MessageChannel } from 'worker_threads';
|
|
12
14
|
|
|
@@ -444,10 +446,7 @@ async function runImport(engine, options) {
|
|
|
444
446
|
failed += 1;
|
|
445
447
|
if (errors.length < maxErrors) errors.push(error);
|
|
446
448
|
};
|
|
447
|
-
const
|
|
448
|
-
if (pending.length === 0) return;
|
|
449
|
-
const documents = pending.map((entry) => entry.document);
|
|
450
|
-
pending = [];
|
|
449
|
+
const flushDocuments = async (documents) => {
|
|
451
450
|
const result = await engine.insertBatch(indexName, documents, { skipClone: true });
|
|
452
451
|
indexed += result.succeeded.length;
|
|
453
452
|
for (const failure of result.failed) {
|
|
@@ -455,36 +454,74 @@ async function runImport(engine, options) {
|
|
|
455
454
|
recordFailure({ docId: failure.docId, code: serialized.code, message: serialized.message });
|
|
456
455
|
}
|
|
457
456
|
};
|
|
457
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
458
|
+
let flushError;
|
|
459
|
+
const startFlush = () => {
|
|
460
|
+
if (pending.length === 0) return;
|
|
461
|
+
const documents = pending.map((entry) => entry.document);
|
|
462
|
+
pending = [];
|
|
463
|
+
const run = flushDocuments(documents).catch((err) => {
|
|
464
|
+
flushError ??= err;
|
|
465
|
+
}).finally(() => inFlight.delete(run));
|
|
466
|
+
inFlight.add(run);
|
|
467
|
+
};
|
|
468
|
+
const settleFlushes = async () => {
|
|
469
|
+
await Promise.all(inFlight);
|
|
470
|
+
if (flushError !== void 0) throw flushError;
|
|
471
|
+
};
|
|
458
472
|
const report = () => {
|
|
459
473
|
onProgress?.({ indexed, failed, bytesProcessed, bytesTotal });
|
|
460
474
|
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
476
|
+
const repeated = [];
|
|
477
|
+
try {
|
|
478
|
+
for (const line of iterateNdjson(body, maxLineBytes)) {
|
|
479
|
+
bytesProcessed = line.bytesConsumed;
|
|
480
|
+
let parsed;
|
|
481
|
+
try {
|
|
482
|
+
parsed = JSON.parse(line.text);
|
|
483
|
+
} catch {
|
|
484
|
+
recordFailure({ line: line.lineNumber, code: ServerErrorCodes.INVALID_JSON, message: "Line is not valid JSON" });
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (!isPlainObject(parsed)) {
|
|
488
|
+
recordFailure({
|
|
489
|
+
line: line.lineNumber,
|
|
490
|
+
code: ServerErrorCodes.INVALID_REQUEST,
|
|
491
|
+
message: "Line is not a JSON object"
|
|
492
|
+
});
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const docId = providedDocId(parsed);
|
|
496
|
+
if (docId !== void 0) {
|
|
497
|
+
if (seenIds.has(docId)) {
|
|
498
|
+
repeated.push({ document: parsed, line: line.lineNumber });
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
seenIds.add(docId);
|
|
502
|
+
}
|
|
503
|
+
pending.push({ document: parsed, line: line.lineNumber });
|
|
504
|
+
if (pending.length >= batchSize) {
|
|
505
|
+
signal?.throwIfAborted();
|
|
506
|
+
startFlush();
|
|
507
|
+
if (inFlight.size >= IMPORT_FLUSHES_IN_FLIGHT) await Promise.race(inFlight);
|
|
508
|
+
if (flushError !== void 0) await settleFlushes();
|
|
509
|
+
report();
|
|
510
|
+
await yieldToEventLoop();
|
|
511
|
+
signal?.throwIfAborted();
|
|
512
|
+
}
|
|
477
513
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
await flush();
|
|
482
|
-
report();
|
|
483
|
-
await yieldToEventLoop();
|
|
514
|
+
startFlush();
|
|
515
|
+
await settleFlushes();
|
|
516
|
+
for (let start = 0; start < repeated.length; start += batchSize) {
|
|
484
517
|
signal?.throwIfAborted();
|
|
518
|
+
await flushDocuments(repeated.slice(start, start + batchSize).map((entry) => entry.document));
|
|
485
519
|
}
|
|
520
|
+
} catch (err) {
|
|
521
|
+
await Promise.allSettled(inFlight);
|
|
522
|
+
report();
|
|
523
|
+
throw err;
|
|
486
524
|
}
|
|
487
|
-
await flush();
|
|
488
525
|
bytesProcessed = bytesTotal;
|
|
489
526
|
report();
|
|
490
527
|
return { indexed, failed, errors, errorsTruncated: failed > errors.length };
|
|
@@ -711,12 +748,48 @@ function createSearchHandlers(deps) {
|
|
|
711
748
|
return { search, preflight, suggest };
|
|
712
749
|
}
|
|
713
750
|
|
|
714
|
-
// src/server/
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
751
|
+
// src/server/options-validation.ts
|
|
752
|
+
var ENGINE_METHOD_SAMPLE = ["createIndex", "listIndexes", "insert", "query", "get", "shutdown"];
|
|
753
|
+
var LIMITS_UNBOUNDED_AT_ZERO = [
|
|
754
|
+
"maxBodyBytes",
|
|
755
|
+
"maxImportBytes",
|
|
756
|
+
"maxLineBytes",
|
|
757
|
+
"maxImportErrors",
|
|
758
|
+
"maxConcurrentRequests",
|
|
759
|
+
"maxConcurrentTasks"
|
|
760
|
+
];
|
|
761
|
+
var COUNTED_LIMITS = ["importBatchSize", "maxResultWindow", "maxFetchDocuments", "maxTaskPageSize"];
|
|
762
|
+
function assertServerEngine(engine) {
|
|
763
|
+
const candidate = engine;
|
|
764
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
765
|
+
throw new NarsilError(
|
|
766
|
+
ErrorCodes.CONFIG_INVALID,
|
|
767
|
+
"The first argument to createServer must be the engine that createNarsil returns, with the options second",
|
|
768
|
+
{ received: candidate === null ? "null" : typeof candidate }
|
|
769
|
+
);
|
|
718
770
|
}
|
|
719
|
-
|
|
771
|
+
const members = candidate;
|
|
772
|
+
const missing = ENGINE_METHOD_SAMPLE.filter((method) => typeof members[method] !== "function");
|
|
773
|
+
if (missing.length === 0) return;
|
|
774
|
+
throw new NarsilError(
|
|
775
|
+
ErrorCodes.CONFIG_INVALID,
|
|
776
|
+
`The first argument to createServer offers no ${missing.join(", ")}, so it is some value other than the engine that createNarsil returns`,
|
|
777
|
+
{ missing }
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
function assertLimit(field, value, minimum) {
|
|
781
|
+
if (value === void 0) return;
|
|
782
|
+
if (Number.isInteger(value) && value >= minimum) return;
|
|
783
|
+
throw new NarsilError(
|
|
784
|
+
ErrorCodes.CONFIG_INVALID,
|
|
785
|
+
`limits.${field} must be a whole number of at least ${minimum}; however, this configuration sets it to ${String(value)}`,
|
|
786
|
+
{ field, value, minimum }
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
function assertServerLimits(limits) {
|
|
790
|
+
if (limits === void 0) return;
|
|
791
|
+
for (const field of LIMITS_UNBOUNDED_AT_ZERO) assertLimit(field, limits[field], MIN_UNBOUNDED_SERVER_LIMIT);
|
|
792
|
+
for (const field of COUNTED_LIMITS) assertLimit(field, limits[field], MIN_COUNTED_SERVER_LIMIT);
|
|
720
793
|
}
|
|
721
794
|
|
|
722
795
|
// src/server/request-threads/captured-response.ts
|
|
@@ -804,7 +877,7 @@ async function answerRequest(handlers, request) {
|
|
|
804
877
|
params: request.params,
|
|
805
878
|
query: new URLSearchParams(request.query),
|
|
806
879
|
contentType: request.contentType,
|
|
807
|
-
rawBody: request.rawBody === null ? null : Buffer.from(request.rawBody),
|
|
880
|
+
rawBody: request.rawBody === null ? null : Buffer.from(request.rawBody.buffer, request.rawBody.byteOffset, request.rawBody.byteLength),
|
|
808
881
|
abort: NO_ABORT,
|
|
809
882
|
hookContext: request.hookContext
|
|
810
883
|
};
|
|
@@ -848,8 +921,8 @@ async function answerEmbed(core, request) {
|
|
|
848
921
|
error: null
|
|
849
922
|
};
|
|
850
923
|
} catch (err) {
|
|
851
|
-
|
|
852
|
-
return { type: "embedded", requestId: request.requestId, value: null, error };
|
|
924
|
+
if (!isNarsilError(err)) console.warn("Embedding a query for a request thread failed:", err);
|
|
925
|
+
return { type: "embedded", requestId: request.requestId, value: null, error: toHttpError(err).body };
|
|
853
926
|
}
|
|
854
927
|
}
|
|
855
928
|
function touchIntervalFor(copyIdleTimeoutMs) {
|
|
@@ -952,6 +1025,12 @@ async function startRequestThreads(options) {
|
|
|
952
1025
|
};
|
|
953
1026
|
}
|
|
954
1027
|
|
|
1028
|
+
// src/server/task-status.ts
|
|
1029
|
+
var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
|
|
1030
|
+
function isTerminalTaskStatus(status) {
|
|
1031
|
+
return TERMINAL_TASK_STATUSES.has(status);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
955
1034
|
// src/server/task-store.ts
|
|
956
1035
|
var InMemoryTaskStore = class {
|
|
957
1036
|
/**
|
|
@@ -1004,7 +1083,7 @@ var InMemoryTaskStore = class {
|
|
|
1004
1083
|
if (this.records.size <= this.maxRetained) return;
|
|
1005
1084
|
for (const [id, record] of this.records) {
|
|
1006
1085
|
if (this.records.size <= this.maxRetained) break;
|
|
1007
|
-
if (record.status
|
|
1086
|
+
if (isTerminalTaskStatus(record.status)) this.records.delete(id);
|
|
1008
1087
|
}
|
|
1009
1088
|
}
|
|
1010
1089
|
};
|
|
@@ -1081,7 +1160,7 @@ var TaskRegistry = class {
|
|
|
1081
1160
|
async cancel(id) {
|
|
1082
1161
|
const stored = await this.store.get(id);
|
|
1083
1162
|
if (!stored) return { outcome: "not-found", record: null };
|
|
1084
|
-
if (stored.status
|
|
1163
|
+
if (isTerminalTaskStatus(stored.status)) {
|
|
1085
1164
|
return { outcome: "already-finished", record: stored };
|
|
1086
1165
|
}
|
|
1087
1166
|
const task = this.live.get(id);
|
|
@@ -1110,7 +1189,7 @@ var TaskRegistry = class {
|
|
|
1110
1189
|
}
|
|
1111
1190
|
for (const record of records) {
|
|
1112
1191
|
if (record.owner !== this.instanceId) continue;
|
|
1113
|
-
if (record.status
|
|
1192
|
+
if (isTerminalTaskStatus(record.status)) continue;
|
|
1114
1193
|
const failed = {
|
|
1115
1194
|
...record,
|
|
1116
1195
|
status: "failed",
|
|
@@ -1262,7 +1341,10 @@ var NarsilHttpServer = class {
|
|
|
1262
1341
|
listenSocket = null;
|
|
1263
1342
|
ready = false;
|
|
1264
1343
|
threads = null;
|
|
1344
|
+
stopIdleHeapCleanup = () => void 0;
|
|
1265
1345
|
constructor(engine, options = {}) {
|
|
1346
|
+
assertServerEngine(engine);
|
|
1347
|
+
assertServerLimits(options.limits);
|
|
1266
1348
|
this.host = options.host ?? "127.0.0.1";
|
|
1267
1349
|
this.port = options.port ?? 9876;
|
|
1268
1350
|
this.options = options;
|
|
@@ -1301,10 +1383,13 @@ var NarsilHttpServer = class {
|
|
|
1301
1383
|
});
|
|
1302
1384
|
});
|
|
1303
1385
|
await this.deps.tasks.reconcile();
|
|
1386
|
+
this.stopIdleHeapCleanup = startIdleHeapCleanup();
|
|
1304
1387
|
this.ready = true;
|
|
1305
1388
|
}
|
|
1306
1389
|
async close() {
|
|
1307
1390
|
this.ready = false;
|
|
1391
|
+
this.stopIdleHeapCleanup();
|
|
1392
|
+
this.stopIdleHeapCleanup = () => void 0;
|
|
1308
1393
|
if (this.listenSocket && this.uws) {
|
|
1309
1394
|
this.uws.us_listen_socket_close(this.listenSocket);
|
|
1310
1395
|
this.listenSocket = null;
|
|
@@ -1315,6 +1400,10 @@ var NarsilHttpServer = class {
|
|
|
1315
1400
|
await threads.stop();
|
|
1316
1401
|
}
|
|
1317
1402
|
}
|
|
1403
|
+
async shutdown() {
|
|
1404
|
+
await this.close();
|
|
1405
|
+
await this.engine.shutdown();
|
|
1406
|
+
}
|
|
1318
1407
|
get listeningPort() {
|
|
1319
1408
|
if (!this.listenSocket || !this.uws) return -1;
|
|
1320
1409
|
return this.uws.us_socket_local_port(this.listenSocket);
|
|
@@ -1336,7 +1425,7 @@ var NarsilHttpServer = class {
|
|
|
1336
1425
|
writeCors: cors ? corsWriter(cors) : void 0
|
|
1337
1426
|
});
|
|
1338
1427
|
registerServerRoutes(app, run, handlers, limits, cors);
|
|
1339
|
-
if (core === void 0 || !detectRuntime().supportsWorkerThreads) return;
|
|
1428
|
+
if (core === void 0 || threadCount === 0 || !detectRuntime().supportsWorkerThreads) return;
|
|
1340
1429
|
try {
|
|
1341
1430
|
this.threads = await startRequestThreads({ core, app, handlers, authorize, gate, limits, build, cors });
|
|
1342
1431
|
} catch (err) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports whether the runtime loaded the SIMD module that speeds up vector
|
|
3
|
+
* distance work.
|
|
4
|
+
*
|
|
5
|
+
* Vector search works either way, so read this to explain a difference in
|
|
6
|
+
* search latency between two hosts rather than to decide whether to search.
|
|
7
|
+
*
|
|
8
|
+
* @returns True once the SIMD module has loaded.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
declare function isSimdAvailable(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The distance kernels bound to one memory, which read their operands at byte
|
|
15
|
+
* offsets inside that memory so that a caller measures against a vector where
|
|
16
|
+
* it already lies.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
interface ArenaSimd {
|
|
21
|
+
/** The kernels read every operand out of this memory. */
|
|
22
|
+
memory: WebAssembly.Memory;
|
|
23
|
+
/** Returns the dot product of two float32 vectors at the given byte offsets. */
|
|
24
|
+
dot_product: (ptrA: number, ptrB: number, len: number) => number;
|
|
25
|
+
/** Returns the length of the float32 vector at the given byte offset. */
|
|
26
|
+
magnitude: (ptr: number, len: number) => number;
|
|
27
|
+
/** Returns the squared euclidean distance between two float32 vectors at the given byte offsets. */
|
|
28
|
+
squared_euclidean_distance: (ptrA: number, ptrB: number, len: number) => number;
|
|
29
|
+
/** Returns the integer dot product of two byte code vectors at the given byte offsets. */
|
|
30
|
+
dot_u8: (ptrA: number, ptrB: number, len: number) => number;
|
|
31
|
+
/** Returns the integer squared distance between two byte code vectors at the given byte offsets. */
|
|
32
|
+
sqdist_u8: (ptrA: number, ptrB: number, len: number) => number;
|
|
33
|
+
/** Returns the sum of level products of two 4-bit codes at the given byte offsets, each holding two levels per byte. */
|
|
34
|
+
osq_dot_nibbles_4x4: (ptrA: number, ptrB: number, bytes: number) => number;
|
|
35
|
+
/** Returns the sum of level products of a 1-bit or 2-bit code and a query staged as four bit planes. Each plane spans `planeBytes`, with one bit for each of the code's lowest level bits. */
|
|
36
|
+
osq_dot_bits: (ptrDoc: number, ptrPlanes: number, planeBytes: number, docBits: number) => number;
|
|
37
|
+
/** Returns the sum of level products of two 1-bit codes or two 2-bit codes at the given byte offsets. */
|
|
38
|
+
osq_dot_bits_pair: (ptrA: number, ptrB: number, bytes: number, bits: number) => number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { type ArenaSimd as A, isSimdAvailable as i };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MemoryStats } from './memory-
|
|
1
|
+
import { M as MemoryStats } from './memory-xrXOZMbw.js';
|
|
2
2
|
|
|
3
3
|
/** Declarative index configuration accepted over HTTP. Function-valued engine
|
|
4
4
|
* options (custom tokenizer, stopWords-as-function, group reducer, embedding
|
|
@@ -144,12 +144,16 @@ type TaskType = 'optimizeVectors' | 'rebalance' | 'restore' | 'import' | 'rebuil
|
|
|
144
144
|
/**
|
|
145
145
|
* Where a long-running operation stands.
|
|
146
146
|
*
|
|
147
|
-
* A task
|
|
148
|
-
*
|
|
147
|
+
* A task starts `running`, because the server begins the work as it accepts
|
|
148
|
+
* the request. Where the server already drives
|
|
149
|
+
* {@link ServerLimits.maxConcurrentTasks} tasks, it answers 429
|
|
150
|
+
* `TOO_MANY_REQUESTS` and starts none. A task reaches `cancelled` only where
|
|
151
|
+
* the work itself stops early, so a cancel that arrives too late leaves the
|
|
152
|
+
* task `succeeded`.
|
|
149
153
|
*
|
|
150
154
|
* @public
|
|
151
155
|
*/
|
|
152
|
-
type TaskStatus = '
|
|
156
|
+
type TaskStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
153
157
|
/**
|
|
154
158
|
* How far a running import has gone, which is what a progress bar reads.
|
|
155
159
|
*
|
|
@@ -192,7 +196,7 @@ interface TaskRecord {
|
|
|
192
196
|
owner: string;
|
|
193
197
|
/** The server accepted the task at this many milliseconds since the epoch. */
|
|
194
198
|
createdAt: number;
|
|
195
|
-
/** Work started at this many milliseconds since the epoch
|
|
199
|
+
/** Work started at this many milliseconds since the epoch. */
|
|
196
200
|
startedAt?: number;
|
|
197
201
|
/** Work ended at this many milliseconds since the epoch, and a task omits it until it reaches a final status. */
|
|
198
202
|
completedAt?: number;
|