@delali/narsil 0.2.2 → 0.2.3-next.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +12 -9
  2. package/dist/adapters/filesystem.d.ts +1 -1
  3. package/dist/adapters/filesystem.mjs +1 -1
  4. package/dist/adapters/indexeddb.d.ts +1 -1
  5. package/dist/adapters/indexeddb.mjs +1 -1
  6. package/dist/adapters/memory.d.ts +1 -1
  7. package/dist/{adapters-CVqI5NBt.d.ts → adapters-79OIa4xm.d.ts} +1 -1
  8. package/dist/brute-force-DbHYCTc3.d.ts +8 -0
  9. package/dist/chunk-BFIH7OV5.mjs +185 -0
  10. package/dist/chunk-C6VVLMRX.mjs +63 -0
  11. package/dist/{chunk-UNJZ7ZDB.mjs → chunk-EMI63EXP.mjs} +33 -2
  12. package/dist/chunk-MFFAJ2NR.mjs +135 -0
  13. package/dist/chunk-OBXS7OZM.mjs +376 -0
  14. package/dist/{chunk-ASI6AOW2.mjs → chunk-RO4EFWE6.mjs} +7833 -3524
  15. package/dist/{chunk-WLR5BFL3.mjs → chunk-TEAJO7CU.mjs} +3067 -2087
  16. package/dist/{chunk-S7RJJMHI.mjs → chunk-WDL632CN.mjs} +699 -222
  17. package/dist/{chunk-BKAI2WXI.mjs → chunk-WTONLDU3.mjs} +5 -0
  18. package/dist/client-DQHB_swQ.d.ts +793 -0
  19. package/dist/client.d.ts +6 -0
  20. package/dist/client.mjs +843 -0
  21. package/dist/{config-DXypXAfN.d.ts → config-ChZ7lfqc.d.ts} +3 -3
  22. package/dist/direct-executor-YARCDMJF.mjs +8 -0
  23. package/dist/distribution/coordinator/etcd.d.ts +3 -3
  24. package/dist/distribution/coordinator/etcd.mjs +471 -2
  25. package/dist/distribution/coordinator/in-memory.d.ts +3 -3
  26. package/dist/distribution/coordinator/in-memory.mjs +286 -1
  27. package/dist/distribution/transport/grpc.d.ts +45 -0
  28. package/dist/distribution/transport/grpc.mjs +586 -0
  29. package/dist/distribution/transport/in-memory.d.ts +118 -0
  30. package/dist/distribution/transport/in-memory.mjs +214 -0
  31. package/dist/distribution/transport/tcp.d.ts +27 -0
  32. package/dist/distribution/transport/tcp.mjs +595 -0
  33. package/dist/distribution.d.ts +241 -192
  34. package/dist/distribution.mjs +6620 -4795
  35. package/dist/embeddings/openai.d.ts +1 -1
  36. package/dist/embeddings/openai.mjs +1 -1
  37. package/dist/{engine-CO9Wnfie.d.ts → engine-DScGlT2Z.d.ts} +30 -3
  38. package/dist/index.browser.mjs +10035 -3914
  39. package/dist/index.d.ts +8 -8
  40. package/dist/index.mjs +8 -5
  41. package/dist/invalidation/broadcast-channel.d.ts +1 -1
  42. package/dist/invalidation/filesystem.d.ts +1 -1
  43. package/dist/invalidation/filesystem.mjs +3 -1
  44. package/dist/invalidation/noop.d.ts +1 -1
  45. package/dist/options-BORJg5XB.d.ts +234 -0
  46. package/dist/persistence/durability/checkpoint-worker.d.ts +4 -2
  47. package/dist/persistence/durability/checkpoint-worker.mjs +8 -5
  48. package/dist/react.d.ts +340 -0
  49. package/dist/react.mjs +687 -0
  50. package/dist/{schema-CE6S_aAr.d.ts → schema-emTQfEiX.d.ts} +21 -7
  51. package/dist/{search-DLBtiPdQ.d.ts → search-CQ-9bKxo.d.ts} +226 -14
  52. package/dist/server.d.ts +42 -356
  53. package/dist/server.mjs +493 -106
  54. package/dist/tasks-CnMLpfk-.d.ts +274 -0
  55. package/dist/types-BQDlIvfS.d.ts +34 -0
  56. package/dist/{types-BV3nAKTL.d.ts → types-Bfhhnh3G.d.ts} +23 -1
  57. package/dist/types-D5OwhtsQ.d.ts +97 -0
  58. package/dist/vector/hnsw-build-worker.d.ts +2 -7
  59. package/dist/vector/hnsw-build-worker.mjs +2 -1
  60. package/dist/vector/search-worker.d.ts +373 -0
  61. package/dist/vector/search-worker.mjs +279 -0
  62. package/dist/workers/entry.mjs +43 -25
  63. package/package.json +42 -2
  64. package/dist/chunk-FKK76OR7.mjs +0 -456
  65. package/dist/chunk-RQLXC2YS.mjs +0 -269
  66. package/dist/direct-executor-3W4QRA4E.mjs +0 -5
@@ -0,0 +1,843 @@
1
+ // src/errors.ts
2
+ var ErrorCodes = {
3
+ SCHEMA_INVALID_TYPE: "SCHEMA_INVALID_TYPE",
4
+ SCHEMA_MISSING_FIELD: "SCHEMA_MISSING_FIELD",
5
+ SCHEMA_DEPTH_EXCEEDED: "SCHEMA_DEPTH_EXCEEDED",
6
+ SCHEMA_INVALID_VECTOR_DIMENSION: "SCHEMA_INVALID_VECTOR_DIMENSION",
7
+ SCHEMA_INVALID_GEOPOINT: "SCHEMA_INVALID_GEOPOINT",
8
+ DOC_NOT_FOUND: "DOC_NOT_FOUND",
9
+ DOC_ALREADY_EXISTS: "DOC_ALREADY_EXISTS",
10
+ DOC_VALIDATION_FAILED: "DOC_VALIDATION_FAILED",
11
+ INDEX_NOT_FOUND: "INDEX_NOT_FOUND",
12
+ INDEX_ALREADY_EXISTS: "INDEX_ALREADY_EXISTS",
13
+ INDEX_ORPHANED: "INDEX_ORPHANED",
14
+ PARTITION_CORRUPTED: "PARTITION_CORRUPTED",
15
+ PARTITION_REBALANCING_BACKPRESSURE: "PARTITION_REBALANCING_BACKPRESSURE",
16
+ WORKER_CRASHED: "WORKER_CRASHED",
17
+ WORKER_BUSY: "WORKER_BUSY",
18
+ WORKER_TIMEOUT: "WORKER_TIMEOUT",
19
+ PERSISTENCE_SAVE_FAILED: "PERSISTENCE_SAVE_FAILED",
20
+ PERSISTENCE_LOAD_FAILED: "PERSISTENCE_LOAD_FAILED",
21
+ PERSISTENCE_DELETE_FAILED: "PERSISTENCE_DELETE_FAILED",
22
+ PERSISTENCE_CRC_MISMATCH: "PERSISTENCE_CRC_MISMATCH",
23
+ PERSISTENCE_WAL_CORRUPT: "PERSISTENCE_WAL_CORRUPT",
24
+ PERSISTENCE_FSYNC_FAILED: "PERSISTENCE_FSYNC_FAILED",
25
+ SEARCH_INVALID_FIELD: "SEARCH_INVALID_FIELD",
26
+ SEARCH_INVALID_VECTOR_SIZE: "SEARCH_INVALID_VECTOR_SIZE",
27
+ VECTOR_DIMENSION_MISMATCH: "VECTOR_DIMENSION_MISMATCH",
28
+ SEARCH_INVALID_FILTER: "SEARCH_INVALID_FILTER",
29
+ SEARCH_INVALID_MODE: "SEARCH_INVALID_MODE",
30
+ SEARCH_INVALID_CURSOR: "SEARCH_INVALID_CURSOR",
31
+ SEARCH_RESULT_WINDOW_EXCEEDED: "SEARCH_RESULT_WINDOW_EXCEEDED",
32
+ LANGUAGE_NOT_SUPPORTED: "LANGUAGE_NOT_SUPPORTED",
33
+ ENVELOPE_VERSION_MISMATCH: "ENVELOPE_VERSION_MISMATCH",
34
+ ENVELOPE_INVALID_MAGIC: "ENVELOPE_INVALID_MAGIC",
35
+ PARTITION_CAPACITY_EXCEEDED: "PARTITION_CAPACITY_EXCEEDED",
36
+ EMBEDDING_FAILED: "EMBEDDING_FAILED",
37
+ EMBEDDING_DIMENSION_MISMATCH: "EMBEDDING_DIMENSION_MISMATCH",
38
+ EMBEDDING_NO_SOURCE: "EMBEDDING_NO_SOURCE",
39
+ EMBEDDING_CONFIG_INVALID: "EMBEDDING_CONFIG_INVALID",
40
+ DOC_MISSING_REQUIRED_FIELD: "DOC_MISSING_REQUIRED_FIELD",
41
+ CONFIG_INVALID: "CONFIG_INVALID",
42
+ QUERY_ROUTING_FAILED: "QUERY_ROUTING_FAILED",
43
+ QUERY_PARTIAL_FAILURE: "QUERY_PARTIAL_FAILURE",
44
+ QUERY_NODE_TIMEOUT: "QUERY_NODE_TIMEOUT",
45
+ QUERY_NO_ACTIVE_REPLICA: "QUERY_NO_ACTIVE_REPLICA",
46
+ CLUSTER_OPERATION_UNSUPPORTED: "CLUSTER_OPERATION_UNSUPPORTED",
47
+ ALLOCATION_NO_DATA_NODES: "ALLOCATION_NO_DATA_NODES",
48
+ ALLOCATION_INVALID_CONFIG: "ALLOCATION_INVALID_CONFIG",
49
+ ALLOCATION_FAILED: "ALLOCATION_FAILED",
50
+ CONTROLLER_LEASE_LOST: "CONTROLLER_LEASE_LOST",
51
+ CONTROLLER_NOT_ACTIVE: "CONTROLLER_NOT_ACTIVE",
52
+ CONTROLLER_METADATA_INVALID: "CONTROLLER_METADATA_INVALID",
53
+ NODE_BOOTSTRAP_FAILED: "NODE_BOOTSTRAP_FAILED",
54
+ NODE_ALREADY_JOINED: "NODE_ALREADY_JOINED",
55
+ NODE_NOT_JOINED: "NODE_NOT_JOINED",
56
+ COORDINATOR_DEPENDENCY_MISSING: "COORDINATOR_DEPENDENCY_MISSING",
57
+ TRANSPORT_DEPENDENCY_MISSING: "TRANSPORT_DEPENDENCY_MISSING",
58
+ REPLICATION_ENTRY_INVALID: "REPLICATION_ENTRY_INVALID",
59
+ REPLICATION_INSYNC_REMOVAL_FAILED: "REPLICATION_INSYNC_REMOVAL_FAILED",
60
+ REPLICATION_ROLLBACK_FAILED: "REPLICATION_ROLLBACK_FAILED",
61
+ REPLICATION_LOG_FULL: "REPLICATION_LOG_FULL",
62
+ REPLICATION_ENTRY_CORRUPT: "REPLICATION_ENTRY_CORRUPT",
63
+ REPLICATION_SNAPSHOT_CORRUPT: "REPLICATION_SNAPSHOT_CORRUPT",
64
+ REPLICATION_TERM_MISMATCH: "REPLICATION_TERM_MISMATCH",
65
+ REPLICATION_SYNC_FAILED: "REPLICATION_SYNC_FAILED",
66
+ PARTITION_NOT_PRIMARY: "PARTITION_NOT_PRIMARY",
67
+ PARTITION_UNASSIGNED: "PARTITION_UNASSIGNED",
68
+ INSUFFICIENT_REPLICAS: "INSUFFICIENT_REPLICAS",
69
+ SNAPSHOT_SYNC_UNAUTHORIZED: "SNAPSHOT_SYNC_UNAUTHORIZED",
70
+ SNAPSHOT_SYNC_REQUEST_INVALID: "SNAPSHOT_SYNC_REQUEST_INVALID",
71
+ SNAPSHOT_SYNC_INDEX_NOT_FOUND: "SNAPSHOT_SYNC_INDEX_NOT_FOUND",
72
+ SNAPSHOT_SYNC_TOO_LARGE: "SNAPSHOT_SYNC_TOO_LARGE",
73
+ SNAPSHOT_SYNC_CAPACITY_EXHAUSTED: "SNAPSHOT_SYNC_CAPACITY_EXHAUSTED",
74
+ SNAPSHOT_SYNC_SNAPSHOT_FAILED: "SNAPSHOT_SYNC_SNAPSHOT_FAILED",
75
+ SNAPSHOT_SYNC_DECODE_FAILED: "SNAPSHOT_SYNC_DECODE_FAILED",
76
+ SNAPSHOT_SYNC_FRAME_INVALID: "SNAPSHOT_SYNC_FRAME_INVALID",
77
+ SNAPSHOT_SYNC_HEADER_INVALID: "SNAPSHOT_SYNC_HEADER_INVALID",
78
+ SNAPSHOT_SYNC_HEADER_MISMATCH: "SNAPSHOT_SYNC_HEADER_MISMATCH",
79
+ SNAPSHOT_SYNC_CHUNK_OUT_OF_ORDER: "SNAPSHOT_SYNC_CHUNK_OUT_OF_ORDER",
80
+ SNAPSHOT_SYNC_CHUNK_OVERFLOW: "SNAPSHOT_SYNC_CHUNK_OVERFLOW",
81
+ SNAPSHOT_SYNC_CHUNK_SIZE_EXCEEDED: "SNAPSHOT_SYNC_CHUNK_SIZE_EXCEEDED",
82
+ SNAPSHOT_SYNC_CHUNK_MISSING: "SNAPSHOT_SYNC_CHUNK_MISSING",
83
+ SNAPSHOT_SYNC_END_MISSING: "SNAPSHOT_SYNC_END_MISSING",
84
+ SNAPSHOT_SYNC_CHECKSUM_MISMATCH: "SNAPSHOT_SYNC_CHECKSUM_MISMATCH",
85
+ SNAPSHOT_SYNC_PRIMARY_ERROR: "SNAPSHOT_SYNC_PRIMARY_ERROR",
86
+ SNAPSHOT_SYNC_NO_TARGETS: "SNAPSHOT_SYNC_NO_TARGETS",
87
+ SNAPSHOT_SYNC_TRANSPORT_FAILED: "SNAPSHOT_SYNC_TRANSPORT_FAILED",
88
+ SNAPSHOT_SYNC_SCHEMA_UNAVAILABLE: "SNAPSHOT_SYNC_SCHEMA_UNAVAILABLE",
89
+ SNAPSHOT_SYNC_RESTORE_FAILED: "SNAPSHOT_SYNC_RESTORE_FAILED",
90
+ SNAPSHOT_SYNC_RESTORE_CLEANUP_FAILED: "SNAPSHOT_SYNC_RESTORE_CLEANUP_FAILED",
91
+ SNAPSHOT_SYNC_TIMEOUT: "SNAPSHOT_SYNC_TIMEOUT",
92
+ SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE: "SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE",
93
+ SNAPSHOT_SYNC_NOT_ASSIGNED: "SNAPSHOT_SYNC_NOT_ASSIGNED",
94
+ SNAPSHOT_SYNC_ABORTED: "SNAPSHOT_SYNC_ABORTED"
95
+ };
96
+ var ServerErrorCodes = {
97
+ INVALID_REQUEST: "INVALID_REQUEST",
98
+ INVALID_JSON: "INVALID_JSON",
99
+ EMPTY_BODY: "EMPTY_BODY",
100
+ PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
101
+ NOT_FOUND: "NOT_FOUND",
102
+ TASK_NOT_FOUND: "TASK_NOT_FOUND",
103
+ TASK_NOT_CANCELLABLE: "TASK_NOT_CANCELLABLE",
104
+ TASK_OWNED_BY_ANOTHER_INSTANCE: "TASK_OWNED_BY_ANOTHER_INSTANCE",
105
+ TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
106
+ HOOK_ERROR: "HOOK_ERROR",
107
+ INTERNAL_ERROR: "INTERNAL_ERROR"
108
+ };
109
+ var ClientErrorCodes = {
110
+ CLIENT_CONNECTION_FAILED: "CLIENT_CONNECTION_FAILED",
111
+ CLIENT_REQUEST_TIMEOUT: "CLIENT_REQUEST_TIMEOUT",
112
+ CLIENT_REQUEST_ABORTED: "CLIENT_REQUEST_ABORTED",
113
+ CLIENT_INVALID_RESPONSE: "CLIENT_INVALID_RESPONSE",
114
+ CLIENT_TASK_TIMEOUT: "CLIENT_TASK_TIMEOUT",
115
+ CLIENT_UNEXPECTED_ERROR: "CLIENT_UNEXPECTED_ERROR"
116
+ };
117
+ var NarsilError = class extends Error {
118
+ /** This says which failure it is. */
119
+ code;
120
+ /** This carries the values behind the failure, such as the field, index, or limit involved. It is empty when the code says everything. */
121
+ details;
122
+ /**
123
+ * Builds an error carrying a code the caller can branch on.
124
+ *
125
+ * @param code - The failure this error reports, from {@link ErrorCodes} for
126
+ * an engine failure, {@link ServerErrorCodes} for one the HTTP layer raised,
127
+ * or {@link ClientErrorCodes} for one that stopped a request from reaching a
128
+ * server.
129
+ * @param message - Plain description, for a log or a person.
130
+ * @param details - Values behind the failure, which reach
131
+ * {@link NarsilError.details}.
132
+ */
133
+ constructor(code, message, details) {
134
+ super(message);
135
+ this.name = "NarsilError";
136
+ this.code = code;
137
+ this.details = details ?? {};
138
+ }
139
+ };
140
+ function describeError(error) {
141
+ return error instanceof Error ? error.message : String(error);
142
+ }
143
+
144
+ // src/server/capabilities.ts
145
+ var ASYNC_IMPORT_CAPABILITY = "documents.import.async";
146
+ var TASK_CANCEL_CAPABILITY = "tasks.cancel";
147
+ var TASK_FILTER_CAPABILITY = "tasks.filter";
148
+ var REBUILD_ANALYSIS_CAPABILITY = "indexes.rebuildAnalysis";
149
+ var SERVER_CAPABILITIES = [
150
+ ASYNC_IMPORT_CAPABILITY,
151
+ TASK_CANCEL_CAPABILITY,
152
+ TASK_FILTER_CAPABILITY,
153
+ REBUILD_ANALYSIS_CAPABILITY
154
+ ];
155
+
156
+ // src/json-encoding.ts
157
+ function toPlainNumbers(key, value) {
158
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
159
+ return Array.from(value);
160
+ }
161
+ return value;
162
+ }
163
+ function encodeJson(value) {
164
+ return JSON.stringify(value, toPlainNumbers);
165
+ }
166
+
167
+ // src/client/http.ts
168
+ var DEFAULT_TIMEOUT_MS = 3e4;
169
+ var NO_TIMEOUT = 0;
170
+ function isRecord(value) {
171
+ return typeof value === "object" && value !== null && !Array.isArray(value);
172
+ }
173
+ function unrefTimer(timer) {
174
+ const unrefable = timer;
175
+ unrefable.unref?.();
176
+ }
177
+ function startDeadline(timeoutMs, caller) {
178
+ const controller = new AbortController();
179
+ let expired = false;
180
+ const abortWithCaller = () => controller.abort();
181
+ if (caller?.aborted) controller.abort();
182
+ else caller?.addEventListener("abort", abortWithCaller);
183
+ let timer;
184
+ if (timeoutMs > 0 && !controller.signal.aborted) {
185
+ timer = setTimeout(() => {
186
+ expired = true;
187
+ controller.abort();
188
+ }, timeoutMs);
189
+ unrefTimer(timer);
190
+ }
191
+ return {
192
+ signal: controller.signal,
193
+ expired: () => expired,
194
+ release: () => {
195
+ if (timer !== void 0) clearTimeout(timer);
196
+ caller?.removeEventListener("abort", abortWithCaller);
197
+ }
198
+ };
199
+ }
200
+ var ResponseTooLargeError = class extends Error {
201
+ constructor(limitBytes) {
202
+ super(`The answer passed the ${limitBytes} byte ceiling this client reads`);
203
+ this.limitBytes = limitBytes;
204
+ }
205
+ };
206
+ async function readStreamCapped(stream, limitBytes) {
207
+ const reader = stream.getReader();
208
+ const chunks = [];
209
+ let total = 0;
210
+ try {
211
+ for (; ; ) {
212
+ const { done, value } = await reader.read();
213
+ if (done) break;
214
+ total += value.byteLength;
215
+ if (total > limitBytes) throw new ResponseTooLargeError(limitBytes);
216
+ chunks.push(value);
217
+ }
218
+ } finally {
219
+ reader.releaseLock();
220
+ if (total > limitBytes) await stream.cancel().catch(() => void 0);
221
+ }
222
+ const body = new Uint8Array(total);
223
+ let offset = 0;
224
+ for (const chunk of chunks) {
225
+ body.set(chunk, offset);
226
+ offset += chunk.byteLength;
227
+ }
228
+ return body;
229
+ }
230
+ async function readAnswer(response, limitBytes, wantsBytes) {
231
+ const stream = response.body;
232
+ if (limitBytes > 0 && stream !== null && stream !== void 0) {
233
+ const body = await readStreamCapped(stream, limitBytes);
234
+ return wantsBytes ? { text: "", bytes: body } : { text: new TextDecoder().decode(body), bytes: null };
235
+ }
236
+ if (wantsBytes) {
237
+ const bytes = new Uint8Array(await response.arrayBuffer());
238
+ if (limitBytes > 0 && bytes.byteLength > limitBytes) throw new ResponseTooLargeError(limitBytes);
239
+ return { text: "", bytes };
240
+ }
241
+ const text = await response.text();
242
+ if (limitBytes > 0 && text.length > limitBytes) throw new ResponseTooLargeError(limitBytes);
243
+ return { text, bytes: null };
244
+ }
245
+ function invalidResponse(message, details) {
246
+ return new NarsilError(ClientErrorCodes.CLIENT_INVALID_RESPONSE, message, details);
247
+ }
248
+ function errorFromBody(status, payload, url) {
249
+ const envelope = isRecord(payload) && isRecord(payload.error) ? payload.error : void 0;
250
+ if (envelope === void 0 || typeof envelope.code !== "string" || typeof envelope.message !== "string") {
251
+ return invalidResponse(`The server answered HTTP ${status} without a Narsil error envelope`, { url, status });
252
+ }
253
+ const details = isRecord(envelope.details) ? envelope.details : {};
254
+ return new NarsilError(envelope.code, envelope.message, { ...details, status });
255
+ }
256
+ function isAbsoluteUrl(value) {
257
+ try {
258
+ new URL(value);
259
+ return true;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ function normaliseBase(url) {
265
+ const trimmed = url.trim();
266
+ if (trimmed.length === 0) {
267
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, 'The client needs a server address, and "url" is empty');
268
+ }
269
+ if (!trimmed.startsWith("/") && !isAbsoluteUrl(trimmed)) {
270
+ throw new NarsilError(
271
+ ErrorCodes.CONFIG_INVALID,
272
+ `The client cannot read "${trimmed}" as a server address; pass an absolute URL or a path starting with "/"`,
273
+ { url: trimmed }
274
+ );
275
+ }
276
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
277
+ }
278
+ function withQuery(path, query) {
279
+ if (query === void 0) return path;
280
+ const params = new URLSearchParams();
281
+ for (const [key, value] of Object.entries(query)) {
282
+ if (value !== void 0) params.set(key, value);
283
+ }
284
+ const encoded = params.toString();
285
+ return encoded.length === 0 ? path : `${path}?${encoded}`;
286
+ }
287
+ function createTransport(options) {
288
+ const base = normaliseBase(options.url);
289
+ const send = options.fetch ?? ((input, init) => fetch(input, init));
290
+ const baseHeaders = { ...options.headers };
291
+ if (options.apiKey !== void 0 && options.apiKey.length > 0) {
292
+ baseHeaders.authorization = `Bearer ${options.apiKey}`;
293
+ }
294
+ function resolveTimeout(spec) {
295
+ const perCall = spec.options?.timeoutMs;
296
+ if (perCall !== void 0) return perCall;
297
+ if (options.timeoutMs !== void 0) return options.timeoutMs;
298
+ return spec.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
299
+ }
300
+ function resolveResponseCeiling(spec) {
301
+ const perCall = spec.options?.maxResponseBytes;
302
+ if (perCall !== void 0) return perCall;
303
+ return options.maxResponseBytes ?? 0;
304
+ }
305
+ async function exchange(spec) {
306
+ const url = `${base}${withQuery(spec.path, spec.query)}`;
307
+ const headers = { ...baseHeaders, ...spec.options?.headers };
308
+ if (spec.contentType !== void 0) headers["content-type"] = spec.contentType;
309
+ const timeoutMs = resolveTimeout(spec);
310
+ const deadline = startDeadline(timeoutMs, spec.options?.signal);
311
+ try {
312
+ const init = { method: spec.method, headers, signal: deadline.signal };
313
+ if (spec.body !== void 0) init.body = spec.body;
314
+ const response = await send(url, init);
315
+ const wantsBytes = spec.binaryAnswer === true && response.ok;
316
+ const answer = await readAnswer(response, resolveResponseCeiling(spec), wantsBytes);
317
+ return { status: response.status, ...answer };
318
+ } catch (err) {
319
+ if (err instanceof ResponseTooLargeError) {
320
+ throw invalidResponse(`The server at ${url} answered more than this client reads: ${err.message}`, {
321
+ url,
322
+ maxResponseBytes: err.limitBytes
323
+ });
324
+ }
325
+ if (deadline.expired()) {
326
+ throw new NarsilError(
327
+ ClientErrorCodes.CLIENT_REQUEST_TIMEOUT,
328
+ `The server at ${url} did not answer within ${timeoutMs} ms`,
329
+ { url, timeoutMs }
330
+ );
331
+ }
332
+ if (spec.options?.signal?.aborted === true) {
333
+ throw new NarsilError(ClientErrorCodes.CLIENT_REQUEST_ABORTED, `The caller cancelled the request to ${url}`, {
334
+ url
335
+ });
336
+ }
337
+ throw new NarsilError(
338
+ ClientErrorCodes.CLIENT_CONNECTION_FAILED,
339
+ `The client could not reach ${url}: ${describeError(err)}`,
340
+ { url }
341
+ );
342
+ } finally {
343
+ deadline.release();
344
+ }
345
+ }
346
+ function parse(status, text, path) {
347
+ if (text.length === 0) return void 0;
348
+ try {
349
+ return JSON.parse(text);
350
+ } catch {
351
+ throw invalidResponse(`The server answered HTTP ${status} with a body that is not JSON`, { url: path, status });
352
+ }
353
+ }
354
+ async function json(spec) {
355
+ const { status, text } = await exchange(spec);
356
+ const payload = parse(status, text, spec.path);
357
+ if (status >= 200 && status < 300) return payload;
358
+ throw errorFromBody(status, payload, spec.path);
359
+ }
360
+ return {
361
+ json,
362
+ async jsonOrNull(spec, absentCode) {
363
+ try {
364
+ return await json(spec);
365
+ } catch (err) {
366
+ if (err instanceof NarsilError && err.code === absentCode) return null;
367
+ throw err;
368
+ }
369
+ },
370
+ async binary(spec) {
371
+ const { status, text, bytes } = await exchange(spec);
372
+ if (bytes !== null) return bytes;
373
+ throw errorFromBody(status, parse(status, text, spec.path), spec.path);
374
+ },
375
+ async probe(spec) {
376
+ const { status } = await exchange(spec);
377
+ return status;
378
+ }
379
+ };
380
+ }
381
+
382
+ // src/client/paths.ts
383
+ function indexPath(indexName) {
384
+ return `/indexes/${encodeURIComponent(indexName)}`;
385
+ }
386
+ function documentPath(indexName, docId) {
387
+ return `${indexPath(indexName)}/documents/${encodeURIComponent(docId)}`;
388
+ }
389
+ function taskPath(taskId) {
390
+ return `/tasks/${encodeURIComponent(taskId)}`;
391
+ }
392
+
393
+ // src/client/response-shape.ts
394
+ function isRecord2(value) {
395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
396
+ }
397
+ function malformed(path, expected) {
398
+ return new NarsilError(ClientErrorCodes.CLIENT_INVALID_RESPONSE, `The answer from ${path} holds no ${expected}`, {
399
+ url: path
400
+ });
401
+ }
402
+ function readArray(payload, key, path) {
403
+ if (!isRecord2(payload) || !Array.isArray(payload[key])) throw malformed(path, `"${key}" array`);
404
+ return payload[key];
405
+ }
406
+ function readNumber(payload, key, path) {
407
+ if (!isRecord2(payload) || typeof payload[key] !== "number") throw malformed(path, `"${key}" number`);
408
+ return payload[key];
409
+ }
410
+ function readBoolean(payload, key, path) {
411
+ if (!isRecord2(payload) || typeof payload[key] !== "boolean") throw malformed(path, `"${key}" flag`);
412
+ return payload[key];
413
+ }
414
+ function readString(payload, key, path) {
415
+ if (!isRecord2(payload) || typeof payload[key] !== "string") throw malformed(path, `"${key}" string`);
416
+ return payload[key];
417
+ }
418
+ function readObject(payload, key, path) {
419
+ if (!isRecord2(payload) || !isRecord2(payload[key])) throw malformed(path, `"${key}" object`);
420
+ return payload[key];
421
+ }
422
+ function readBody(payload, path) {
423
+ if (!isRecord2(payload)) throw malformed(path, "JSON object");
424
+ return payload;
425
+ }
426
+
427
+ // src/client/admin.ts
428
+ function createAdminOperations(transport) {
429
+ async function postJson(path, body, options) {
430
+ const payload = await transport.json({
431
+ method: "POST",
432
+ path,
433
+ body: encodeJson(body),
434
+ contentType: "application/json",
435
+ options
436
+ });
437
+ return readBody(payload, path);
438
+ }
439
+ return {
440
+ async checkpoint(indexName, options) {
441
+ await transport.json({ method: "POST", path: `${indexPath(indexName)}/_checkpoint`, options });
442
+ },
443
+ snapshot(indexName, options) {
444
+ return transport.binary({
445
+ method: "GET",
446
+ path: `${indexPath(indexName)}/snapshot`,
447
+ binaryAnswer: true,
448
+ defaultTimeoutMs: NO_TIMEOUT,
449
+ options
450
+ });
451
+ },
452
+ async restore(indexName, data, options) {
453
+ const path = `${indexPath(indexName)}/restore`;
454
+ const payload = await transport.json({
455
+ method: "POST",
456
+ path,
457
+ body: data,
458
+ contentType: "application/octet-stream",
459
+ defaultTimeoutMs: NO_TIMEOUT,
460
+ options
461
+ });
462
+ return readBody(payload, path);
463
+ },
464
+ async vectorMaintenanceStatus(indexName, options) {
465
+ const path = `${indexPath(indexName)}/vector-maintenance`;
466
+ const payload = await transport.json({ method: "GET", path, options });
467
+ return readArray(payload, "fields", path);
468
+ },
469
+ async compactVectors(indexName, fieldName, options) {
470
+ await postJson(
471
+ `${indexPath(indexName)}/vectors/_compact`,
472
+ fieldName === void 0 ? {} : { field: fieldName },
473
+ options
474
+ );
475
+ },
476
+ optimizeVectors(indexName, fieldName, options) {
477
+ return postJson(
478
+ `${indexPath(indexName)}/vectors/_optimize`,
479
+ fieldName === void 0 ? {} : { field: fieldName },
480
+ options
481
+ );
482
+ },
483
+ rebalance(indexName, targetPartitionCount, options) {
484
+ return postJson(`${indexPath(indexName)}/_rebalance`, { targetPartitionCount }, options);
485
+ },
486
+ async updatePartitionConfig(indexName, partitionConfig, options) {
487
+ await postJson(`${indexPath(indexName)}/partition-config`, partitionConfig, options);
488
+ },
489
+ async rebuildAnalysis(indexName, options) {
490
+ const path = `${indexPath(indexName)}/_rebuild-analysis`;
491
+ return readBody(await transport.json({ method: "POST", path, options }), path);
492
+ },
493
+ async getMemoryStats(options) {
494
+ const path = "/stats/memory";
495
+ return readBody(await transport.json({ method: "GET", path, options }), path);
496
+ }
497
+ };
498
+ }
499
+
500
+ // src/client/bulk.ts
501
+ function toNdjson(source) {
502
+ if (typeof source === "string") return source;
503
+ if (Array.isArray(source)) return source.map((document) => encodeJson(document)).join("\n");
504
+ return source;
505
+ }
506
+ function toBatchResult(payload, path) {
507
+ const failed = readArray(payload, "failed", path);
508
+ return {
509
+ succeeded: readArray(payload, "succeeded", path),
510
+ failed: failed.map((entry) => {
511
+ const record = readBody(entry, path);
512
+ const docId = typeof record.docId === "string" ? record.docId : "";
513
+ const error = readBody(record.error, path);
514
+ const code = typeof error.code === "string" ? error.code : ServerErrorCodes.INTERNAL_ERROR;
515
+ const message = typeof error.message === "string" ? error.message : "The server refused the document";
516
+ const details = typeof error.details === "object" && error.details !== null ? error.details : void 0;
517
+ return { docId, error: new NarsilError(code, message, details) };
518
+ })
519
+ };
520
+ }
521
+ function createBulkOperations(transport) {
522
+ function batch(indexName, body, options) {
523
+ const path = `${indexPath(indexName)}/documents/_batch`;
524
+ return transport.json({ method: "POST", path, body: encodeJson(body), contentType: "application/json", options }).then((payload) => toBatchResult(payload, path));
525
+ }
526
+ function importBody(indexName, source, asynchronous, options) {
527
+ return {
528
+ method: "POST",
529
+ path: `${indexPath(indexName)}/documents/_import`,
530
+ ...asynchronous ? { query: { async: "true" } } : {},
531
+ body: toNdjson(source),
532
+ contentType: "application/x-ndjson",
533
+ defaultTimeoutMs: NO_TIMEOUT,
534
+ options
535
+ };
536
+ }
537
+ return {
538
+ insertBatch(indexName, documents, insertOptions, options) {
539
+ return batch(
540
+ indexName,
541
+ { action: "insert", documents, ...insertOptions === void 0 ? {} : { options: insertOptions } },
542
+ options
543
+ );
544
+ },
545
+ updateBatch(indexName, updates, options) {
546
+ return batch(indexName, { action: "update", updates }, options);
547
+ },
548
+ removeBatch(indexName, docIds, options) {
549
+ return batch(indexName, { action: "delete", docIds }, options);
550
+ },
551
+ async getMultiple(indexName, docIds, options) {
552
+ const path = `${indexPath(indexName)}/documents/_multi-get`;
553
+ const payload = await transport.json({
554
+ method: "POST",
555
+ path,
556
+ body: encodeJson({ docIds }),
557
+ contentType: "application/json",
558
+ options
559
+ });
560
+ const documents = readObject(payload, "documents", path);
561
+ return new Map(Object.entries(documents));
562
+ },
563
+ async listDocuments(indexName, params, options) {
564
+ const path = `${indexPath(indexName)}/documents/_list`;
565
+ const payload = await transport.json({
566
+ method: "POST",
567
+ path,
568
+ body: encodeJson(params ?? {}),
569
+ contentType: "application/json",
570
+ options
571
+ });
572
+ return readBody(payload, path);
573
+ },
574
+ async importDocuments(indexName, source, options) {
575
+ const spec = importBody(indexName, source, false, options);
576
+ return readBody(await transport.json(spec), spec.path);
577
+ },
578
+ async startImport(indexName, source, options) {
579
+ const spec = importBody(indexName, source, true, options);
580
+ return readBody(await transport.json(spec), spec.path);
581
+ }
582
+ };
583
+ }
584
+
585
+ // src/client/documents.ts
586
+ function createDocumentOperations(transport) {
587
+ return {
588
+ async insert(indexName, document, docId, insertOptions, options) {
589
+ const path = `${indexPath(indexName)}/documents`;
590
+ const payload = await transport.json({
591
+ method: "POST",
592
+ path,
593
+ body: encodeJson({
594
+ document,
595
+ ...docId === void 0 ? {} : { id: docId },
596
+ ...insertOptions === void 0 ? {} : { options: insertOptions }
597
+ }),
598
+ contentType: "application/json",
599
+ options
600
+ });
601
+ return readString(payload, "id", path);
602
+ },
603
+ async get(indexName, docId, options) {
604
+ const path = documentPath(indexName, docId);
605
+ const payload = await transport.jsonOrNull({ method: "GET", path, options }, ErrorCodes.DOC_NOT_FOUND);
606
+ if (payload === null) return void 0;
607
+ return readObject(payload, "document", path);
608
+ },
609
+ async has(indexName, docId, options) {
610
+ const path = `${documentPath(indexName, docId)}/_exists`;
611
+ return readBoolean(await transport.json({ method: "GET", path, options }), "exists", path);
612
+ },
613
+ async put(indexName, docId, document, options) {
614
+ const path = documentPath(indexName, docId);
615
+ const payload = await transport.json({
616
+ method: "PUT",
617
+ path,
618
+ body: encodeJson({ document }),
619
+ contentType: "application/json",
620
+ options
621
+ });
622
+ return { id: readString(payload, "id", path), created: readBoolean(payload, "created", path) };
623
+ },
624
+ async update(indexName, docId, document, options) {
625
+ await transport.json({
626
+ method: "PATCH",
627
+ path: documentPath(indexName, docId),
628
+ body: encodeJson({ document }),
629
+ contentType: "application/json",
630
+ options
631
+ });
632
+ },
633
+ async remove(indexName, docId, options) {
634
+ await transport.json({ method: "DELETE", path: documentPath(indexName, docId), options });
635
+ },
636
+ async countDocuments(indexName, options) {
637
+ const path = `${indexPath(indexName)}/count`;
638
+ return readNumber(await transport.json({ method: "GET", path, options }), "count", path);
639
+ }
640
+ };
641
+ }
642
+
643
+ // src/client/indexes.ts
644
+ function createIndexOperations(transport) {
645
+ return {
646
+ async createIndex(name, config, options) {
647
+ await transport.json({
648
+ method: "POST",
649
+ path: "/indexes",
650
+ body: encodeJson({ name, config }),
651
+ contentType: "application/json",
652
+ options
653
+ });
654
+ },
655
+ async listIndexes(options) {
656
+ const path = "/indexes";
657
+ return readArray(await transport.json({ method: "GET", path, options }), "indexes", path);
658
+ },
659
+ async dropIndex(name, options) {
660
+ await transport.json({ method: "DELETE", path: indexPath(name), options });
661
+ },
662
+ async getStats(indexName, options) {
663
+ const path = `${indexPath(indexName)}/stats`;
664
+ return readBody(await transport.json({ method: "GET", path, options }), path);
665
+ },
666
+ async getPartitionStats(indexName, options) {
667
+ const path = `${indexPath(indexName)}/partitions`;
668
+ const payload = await transport.json({ method: "GET", path, options });
669
+ return readArray(payload, "partitions", path);
670
+ },
671
+ async clear(indexName, options) {
672
+ await transport.json({ method: "POST", path: `${indexPath(indexName)}/_clear`, options });
673
+ }
674
+ };
675
+ }
676
+
677
+ // src/client/search.ts
678
+ function createSearchOperations(transport) {
679
+ async function post(path, params, options) {
680
+ const payload = await transport.json({
681
+ method: "POST",
682
+ path,
683
+ body: encodeJson(params),
684
+ contentType: "application/json",
685
+ options
686
+ });
687
+ return readBody(payload, path);
688
+ }
689
+ return {
690
+ query(indexName, params, options) {
691
+ return post(`${indexPath(indexName)}/search`, params, options);
692
+ },
693
+ preflight(indexName, params, options) {
694
+ return post(`${indexPath(indexName)}/search/preflight`, params, options);
695
+ },
696
+ suggest(indexName, params, options) {
697
+ return post(`${indexPath(indexName)}/suggest`, params, options);
698
+ }
699
+ };
700
+ }
701
+
702
+ // src/client/server-info.ts
703
+ function createServerOperations(transport) {
704
+ let announced = null;
705
+ async function readCapabilities(options) {
706
+ const path = "/capabilities";
707
+ const payload = await transport.jsonOrNull({ method: "GET", path, options }, ServerErrorCodes.NOT_FOUND);
708
+ if (payload === null) return /* @__PURE__ */ new Set();
709
+ return new Set(readArray(payload, "capabilities", path));
710
+ }
711
+ function load(options) {
712
+ if (announced === null) {
713
+ announced = readCapabilities(options).catch((err) => {
714
+ announced = null;
715
+ throw err;
716
+ });
717
+ }
718
+ return announced;
719
+ }
720
+ return {
721
+ async version(options) {
722
+ const path = "/version";
723
+ return readBody(await transport.json({ method: "GET", path, options }), path);
724
+ },
725
+ async capabilities(options) {
726
+ return [...await load(options)];
727
+ },
728
+ async supports(capability, options) {
729
+ return (await load(options)).has(capability);
730
+ },
731
+ async isAlive(options) {
732
+ return await transport.probe({ method: "GET", path: "/livez", options }) === 200;
733
+ },
734
+ async isReady(options) {
735
+ return await transport.probe({ method: "GET", path: "/readyz", options }) === 200;
736
+ }
737
+ };
738
+ }
739
+
740
+ // src/client/tasks.ts
741
+ var DEFAULT_POLL_INTERVAL_MS = 250;
742
+ function isTerminal(record) {
743
+ return record.status === "succeeded" || record.status === "failed" || record.status === "cancelled";
744
+ }
745
+ function progressKey(record) {
746
+ const progress = record.progress;
747
+ if (progress === void 0) return record.status;
748
+ return `${record.status}:${progress.bytesProcessed}:${progress.indexed}:${progress.failed}`;
749
+ }
750
+ function sleep(milliseconds, signal) {
751
+ return new Promise((resolve) => {
752
+ const timer = setTimeout(() => {
753
+ signal?.removeEventListener("abort", stop);
754
+ resolve();
755
+ }, milliseconds);
756
+ function stop() {
757
+ clearTimeout(timer);
758
+ resolve();
759
+ }
760
+ signal?.addEventListener("abort", stop, { once: true });
761
+ });
762
+ }
763
+ function listQuery(query) {
764
+ return {
765
+ indexName: query?.indexName,
766
+ type: query?.type === void 0 || query.type.length === 0 ? void 0 : query.type.join(","),
767
+ status: query?.status === void 0 || query.status.length === 0 ? void 0 : query.status.join(","),
768
+ from: query?.from === void 0 ? void 0 : String(query.from),
769
+ limit: query?.limit === void 0 ? void 0 : String(query.limit)
770
+ };
771
+ }
772
+ function createTaskOperations(transport) {
773
+ const operations = {
774
+ async getTask(taskId, options) {
775
+ const path = taskPath(taskId);
776
+ const payload = await transport.jsonOrNull({ method: "GET", path, options }, ServerErrorCodes.TASK_NOT_FOUND);
777
+ return payload === null ? null : readBody(payload, path);
778
+ },
779
+ async listTasks(query, options) {
780
+ const path = "/tasks";
781
+ const payload = await transport.json({ method: "GET", path, query: listQuery(query), options });
782
+ return readBody(payload, path);
783
+ },
784
+ async cancelTask(taskId, options) {
785
+ const path = `${taskPath(taskId)}/_cancel`;
786
+ return readBody(await transport.json({ method: "POST", path, options }), path);
787
+ },
788
+ async waitForTask(taskId, options) {
789
+ const interval = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
790
+ const waitTimeoutMs = options?.waitTimeoutMs ?? 0;
791
+ const startedAt = Date.now();
792
+ let reported = null;
793
+ for (; ; ) {
794
+ if (options?.signal?.aborted === true) {
795
+ throw new NarsilError(
796
+ ClientErrorCodes.CLIENT_REQUEST_ABORTED,
797
+ `The caller stopped waiting for task "${taskId}"`,
798
+ { taskId }
799
+ );
800
+ }
801
+ const record = await operations.getTask(taskId, options);
802
+ if (record === null) {
803
+ throw new NarsilError(
804
+ ServerErrorCodes.TASK_NOT_FOUND,
805
+ `The server no longer holds task "${taskId}", so its outcome is unknown`,
806
+ { taskId }
807
+ );
808
+ }
809
+ const key = progressKey(record);
810
+ if (key !== reported) {
811
+ reported = key;
812
+ options?.onProgress?.(record);
813
+ }
814
+ if (isTerminal(record)) return record;
815
+ if (waitTimeoutMs > 0 && Date.now() - startedAt >= waitTimeoutMs) {
816
+ throw new NarsilError(
817
+ ClientErrorCodes.CLIENT_TASK_TIMEOUT,
818
+ `Task "${taskId}" was still ${record.status} after ${waitTimeoutMs} ms, and it keeps running`,
819
+ { taskId, waitTimeoutMs, status: record.status }
820
+ );
821
+ }
822
+ await sleep(interval, options?.signal);
823
+ }
824
+ }
825
+ };
826
+ return operations;
827
+ }
828
+
829
+ // src/client/client.ts
830
+ function createNarsilClient(options) {
831
+ const transport = createTransport(options);
832
+ return {
833
+ ...createIndexOperations(transport),
834
+ ...createDocumentOperations(transport),
835
+ ...createBulkOperations(transport),
836
+ ...createSearchOperations(transport),
837
+ ...createTaskOperations(transport),
838
+ ...createAdminOperations(transport),
839
+ ...createServerOperations(transport)
840
+ };
841
+ }
842
+
843
+ export { ASYNC_IMPORT_CAPABILITY, ClientErrorCodes, ErrorCodes, NarsilError, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, ServerErrorCodes, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, createNarsilClient };