@delali/narsil 0.2.4-next.48 → 0.2.4-next.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/adapters/indexeddb.mjs +15 -10
- package/dist/{options-BC68VPoE.d.ts → capabilities-JrVQOwi4.d.ts} +55 -47
- package/dist/{chunk-BFIH7OV5.mjs → chunk-2GWVQWGA.mjs} +3 -1
- package/dist/chunk-AV4IJKD2.mjs +35 -0
- package/dist/{chunk-EV45L35L.mjs → chunk-BEIK3EOZ.mjs} +845 -2407
- package/dist/chunk-CBLS46IE.mjs +10 -0
- package/dist/chunk-CMVG2UEF.mjs +2038 -0
- package/dist/chunk-CV5H7XL7.mjs +876 -0
- package/dist/{chunk-JQUL2T6I.mjs → chunk-GCM3JFVA.mjs} +1 -2
- package/dist/{chunk-XWICBGYT.mjs → chunk-MHE7YWAU.mjs} +3 -5
- package/dist/{chunk-UZTT2BKQ.mjs → chunk-ND34I3SV.mjs} +1202 -1881
- package/dist/{chunk-PM52SMBT.mjs → chunk-OOSIDHA3.mjs} +2 -1
- package/dist/chunk-ORBLD3EL.mjs +6 -0
- package/dist/chunk-PIWKZPP7.mjs +64 -0
- package/dist/{chunk-4COAUBLJ.mjs → chunk-PMBHH44R.mjs} +124 -8
- package/dist/chunk-RP23PWMV.mjs +11 -0
- package/dist/chunk-UNEEFTEU.mjs +7 -0
- package/dist/{chunk-WTONLDU3.mjs → chunk-WYJXSLER.mjs} +9 -19
- package/dist/{chunk-DFZDV5FV.mjs → chunk-X6YZO4MQ.mjs} +19 -15
- package/dist/chunk-XE6XR2OE.mjs +23 -0
- package/dist/chunk-YEO6G2NG.mjs +6 -0
- package/dist/chunk-ZHUQKZBE.mjs +1292 -0
- package/dist/{client-C5BkUqB8.d.ts → client-CBOVHdjp.d.ts} +31 -7
- package/dist/client.d.ts +7 -7
- package/dist/client.mjs +250 -232
- package/dist/{config-B-ZI-ft5.d.ts → config-Czh2YNDi.d.ts} +2 -2
- package/dist/{config-BZRExglL.d.ts → config-OON7IJ_B.d.ts} +50 -13
- package/dist/direct-executor-OREXU72C.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +2 -3
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.mjs +2 -2
- package/dist/distribution/transport/grpc.mjs +7 -9
- package/dist/distribution/transport/in-memory.mjs +2 -1
- package/dist/distribution/transport/tcp.mjs +3 -2
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +142 -101
- package/dist/embeddings/openai.mjs +5 -3
- package/dist/{engine-Cjr1uBLa.d.ts → engine-9PtFY50q.d.ts} +125 -69
- package/dist/index.browser.mjs +4689 -3330
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +14 -8
- package/dist/invalidation/broadcast-channel.mjs +2 -1
- package/dist/invalidation/filesystem.mjs +3 -4
- package/dist/{search-DDHjfXhJ.d.ts → memory-Bzd5xcEW.d.ts} +89 -63
- package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +14 -8
- package/dist/react.d.ts +4 -4
- package/dist/react.mjs +7 -5
- package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
- package/dist/server.d.ts +47 -42
- package/dist/server.mjs +338 -821
- package/dist/thread-PZLEO6JL.mjs +310 -0
- package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
- package/dist/vector/hnsw-build-worker.mjs +3 -4
- package/dist/vector/search-worker.mjs +4 -123
- package/dist/workers/entry.mjs +47 -3
- package/package.json +5 -1
- package/dist/direct-executor-EOTFGXTO.mjs +0 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { P as
|
|
1
|
+
import { P as PartitionConfig, A as AnyDocument, b as InsertOptions, W as WriteOptions } from './schema-BqRx9t4E.js';
|
|
2
|
+
import { V as VectorMaintenanceResult, M as MemoryStats, B as BatchResult, L as ListParams, g as ListResult, I as IndexInfo, f as IndexStats, P as PartitionStatsResult, Q as QueryParams, e as QueryResult, h as PreflightResult, i as SuggestParams, j as SuggestResult } from './memory-Bzd5xcEW.js';
|
|
3
3
|
import { e as TaskRecord, b as ImportResult, H as HttpIndexConfig, c as TaskListQuery, T as TaskListPage } from './tasks-Bn4R1NPN.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -248,6 +248,8 @@ interface BulkOperations {
|
|
|
248
248
|
* @param indexName - This names the index holding the documents.
|
|
249
249
|
* @param updates - Each entry names the document to replace and holds its
|
|
250
250
|
* replacement.
|
|
251
|
+
* @param writeOptions - These per-write settings reach the server, and apply
|
|
252
|
+
* to the whole batch.
|
|
251
253
|
* @param options - This sets the signal, the deadline, and the headers for
|
|
252
254
|
* this request.
|
|
253
255
|
* @returns The result lists the ids replaced, and each failure with its
|
|
@@ -256,17 +258,19 @@ interface BulkOperations {
|
|
|
256
258
|
updateBatch(indexName: string, updates: Array<{
|
|
257
259
|
docId: string;
|
|
258
260
|
document: AnyDocument;
|
|
259
|
-
}>, options?: RequestOptions): Promise<BatchResult>;
|
|
261
|
+
}>, writeOptions?: WriteOptions, options?: RequestOptions): Promise<BatchResult>;
|
|
260
262
|
/**
|
|
261
263
|
* Removes many documents in one request and reports each one's outcome.
|
|
262
264
|
*
|
|
263
265
|
* @param indexName - This names the index holding the documents.
|
|
264
266
|
* @param docIds - These name the documents to remove.
|
|
267
|
+
* @param writeOptions - These per-write settings reach the server, and apply
|
|
268
|
+
* to the whole batch.
|
|
265
269
|
* @param options - This sets the signal, the deadline, and the headers for
|
|
266
270
|
* this request.
|
|
267
271
|
* @returns The result lists the ids removed, and each failure with its error.
|
|
268
272
|
*/
|
|
269
|
-
removeBatch(indexName: string, docIds: string[], options?: RequestOptions): Promise<BatchResult>;
|
|
273
|
+
removeBatch(indexName: string, docIds: string[], writeOptions?: WriteOptions, options?: RequestOptions): Promise<BatchResult>;
|
|
270
274
|
/**
|
|
271
275
|
* Reads many documents by id in one request.
|
|
272
276
|
*
|
|
@@ -409,12 +413,14 @@ interface DocumentOperations {
|
|
|
409
413
|
* @param indexName - This names the index that receives the document.
|
|
410
414
|
* @param docId - This is the id to write at.
|
|
411
415
|
* @param document - Its fields must match the types the schema declares.
|
|
416
|
+
* @param writeOptions - These per-write settings reach the server, such as
|
|
417
|
+
* waiting for every worker copy to apply the write.
|
|
412
418
|
* @param options - This sets the signal, the deadline, and the headers for
|
|
413
419
|
* this request.
|
|
414
420
|
* @returns The result names the id, and says whether the write created the
|
|
415
421
|
* document or replaced one.
|
|
416
422
|
*/
|
|
417
|
-
put(indexName: string, docId: string, document: AnyDocument, options?: RequestOptions): Promise<PutResult>;
|
|
423
|
+
put(indexName: string, docId: string, document: AnyDocument, writeOptions?: WriteOptions, options?: RequestOptions): Promise<PutResult>;
|
|
418
424
|
/**
|
|
419
425
|
* Replaces the stored document at an id.
|
|
420
426
|
*
|
|
@@ -422,23 +428,41 @@ interface DocumentOperations {
|
|
|
422
428
|
* @param docId - This names the document to replace.
|
|
423
429
|
* @param document - This replacement goes through the same schema validation
|
|
424
430
|
* an insert would.
|
|
431
|
+
* @param writeOptions - These per-write settings reach the server, such as
|
|
432
|
+
* waiting for every worker copy to apply the write.
|
|
425
433
|
* @param options - This sets the signal, the deadline, and the headers for
|
|
426
434
|
* this request.
|
|
427
435
|
* @throws A `NarsilError` with `DOC_NOT_FOUND` when the index holds no such
|
|
428
436
|
* document.
|
|
429
437
|
*/
|
|
430
|
-
update(indexName: string, docId: string, document: AnyDocument, options?: RequestOptions): Promise<void>;
|
|
438
|
+
update(indexName: string, docId: string, document: AnyDocument, writeOptions?: WriteOptions, options?: RequestOptions): Promise<void>;
|
|
431
439
|
/**
|
|
432
440
|
* Removes one document.
|
|
433
441
|
*
|
|
434
442
|
* @param indexName - This names the index holding the document.
|
|
435
443
|
* @param docId - This names the document to remove.
|
|
444
|
+
* @param writeOptions - These per-write settings reach the server, such as
|
|
445
|
+
* waiting for every worker copy to apply the removal.
|
|
436
446
|
* @param options - This sets the signal, the deadline, and the headers for
|
|
437
447
|
* this request.
|
|
438
448
|
* @throws A `NarsilError` with `DOC_NOT_FOUND` when the index holds no such
|
|
439
449
|
* document.
|
|
440
450
|
*/
|
|
441
|
-
remove(indexName: string, docId: string, options?: RequestOptions): Promise<void>;
|
|
451
|
+
remove(indexName: string, docId: string, writeOptions?: WriteOptions, options?: RequestOptions): Promise<void>;
|
|
452
|
+
/**
|
|
453
|
+
* Resolves once every worker copy of an index on the server has applied
|
|
454
|
+
* every write that returned before the call.
|
|
455
|
+
*
|
|
456
|
+
* A write returns once the server's main copy holds it, and the copies
|
|
457
|
+
* apply it afterwards, so a query can come back without a write that
|
|
458
|
+
* returned before it. Call this after a run of writes, or pass `wait: true`
|
|
459
|
+
* on one write, before a query that has to see them.
|
|
460
|
+
*
|
|
461
|
+
* @param indexName - This names the index whose copies have to catch up.
|
|
462
|
+
* @param options - This sets the signal, the deadline, and the headers for
|
|
463
|
+
* this request.
|
|
464
|
+
*/
|
|
465
|
+
waitForWrites(indexName: string, options?: RequestOptions): Promise<void>;
|
|
442
466
|
/**
|
|
443
467
|
* Counts the documents in an index.
|
|
444
468
|
*
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 } from './
|
|
2
|
-
export { A as ASYNC_IMPORT_CAPABILITY, E as ErrorEnvelope, I as INDEX_LIFECYCLE_CAPABILITY, R as REBUILD_ANALYSIS_CAPABILITY, S as SERVER_CAPABILITIES, T as TASK_CANCEL_CAPABILITY, a as TASK_FILTER_CAPABILITY } from './
|
|
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 } from './memory-Bzd5xcEW.js';
|
|
2
|
+
export { A as ASYNC_IMPORT_CAPABILITY, E as ErrorEnvelope, I as INDEX_LIFECYCLE_CAPABILITY, R as REBUILD_ANALYSIS_CAPABILITY, S as SERVER_CAPABILITIES, T as TASK_CANCEL_CAPABILITY, a as TASK_FILTER_CAPABILITY } from './capabilities-JrVQOwi4.js';
|
|
3
3
|
export { C as CapabilitiesResponse, a as CreateIndexEmbedding, H as HttpIndexConfig, I as ImportError, b as ImportResult, T as TaskListPage, c as TaskListQuery, d as TaskProgress, e as TaskRecord, f as TaskStatus, g as TaskType } from './tasks-Bn4R1NPN.js';
|
|
4
|
-
export { A as AdminOperations, B as BulkOperations, D as DocumentOperations, F as FetchFunction, I as ImportSource, a as IndexOperations, N as NarsilClient, b as NarsilClientOptions, P as PutResult, R as RequestOptions, S as SearchOperations, c as ServerOperations, d as ServerVersion, T as TaskOperations, W as WaitForTaskOptions, e as createNarsilClient } from './client-
|
|
5
|
-
import './schema-
|
|
4
|
+
export { A as AdminOperations, B as BulkOperations, D as DocumentOperations, F as FetchFunction, I as ImportSource, a as IndexOperations, N as NarsilClient, b as NarsilClientOptions, P as PutResult, R as RequestOptions, S as SearchOperations, c as ServerOperations, d as ServerVersion, T as TaskOperations, W as WaitForTaskOptions, e as createNarsilClient } from './client-CBOVHdjp.js';
|
|
5
|
+
import './schema-BqRx9t4E.js';
|
|
6
6
|
import './adapters-79OIa4xm.js';
|
|
7
|
-
import './config-
|
|
8
|
-
import './config-
|
|
9
|
-
import './types-
|
|
7
|
+
import './config-Czh2YNDi.js';
|
|
8
|
+
import './config-OON7IJ_B.js';
|
|
9
|
+
import './types-DXPxaXPO.js';
|
|
10
10
|
import './types-D5OwhtsQ.js';
|
package/dist/client.mjs
CHANGED
|
@@ -166,220 +166,10 @@ function encodeJson(value) {
|
|
|
166
166
|
return JSON.stringify(value, toPlainNumbers);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
// src/client/
|
|
170
|
-
var
|
|
169
|
+
// src/client/constants.ts
|
|
170
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
171
171
|
var NO_TIMEOUT = 0;
|
|
172
|
-
|
|
173
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
174
|
-
}
|
|
175
|
-
function unrefTimer(timer) {
|
|
176
|
-
const unrefable = timer;
|
|
177
|
-
unrefable.unref?.();
|
|
178
|
-
}
|
|
179
|
-
function startDeadline(timeoutMs, caller) {
|
|
180
|
-
const controller = new AbortController();
|
|
181
|
-
let expired = false;
|
|
182
|
-
const abortWithCaller = () => controller.abort();
|
|
183
|
-
if (caller?.aborted) controller.abort();
|
|
184
|
-
else caller?.addEventListener("abort", abortWithCaller);
|
|
185
|
-
let timer;
|
|
186
|
-
if (timeoutMs > 0 && !controller.signal.aborted) {
|
|
187
|
-
timer = setTimeout(() => {
|
|
188
|
-
expired = true;
|
|
189
|
-
controller.abort();
|
|
190
|
-
}, timeoutMs);
|
|
191
|
-
unrefTimer(timer);
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
signal: controller.signal,
|
|
195
|
-
expired: () => expired,
|
|
196
|
-
release: () => {
|
|
197
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
198
|
-
caller?.removeEventListener("abort", abortWithCaller);
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
var ResponseTooLargeError = class extends Error {
|
|
203
|
-
constructor(limitBytes) {
|
|
204
|
-
super(`The answer passed the ${limitBytes} byte ceiling this client reads`);
|
|
205
|
-
this.limitBytes = limitBytes;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
async function readStreamCapped(stream, limitBytes) {
|
|
209
|
-
const reader = stream.getReader();
|
|
210
|
-
const chunks = [];
|
|
211
|
-
let total = 0;
|
|
212
|
-
try {
|
|
213
|
-
for (; ; ) {
|
|
214
|
-
const { done, value } = await reader.read();
|
|
215
|
-
if (done) break;
|
|
216
|
-
total += value.byteLength;
|
|
217
|
-
if (total > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
218
|
-
chunks.push(value);
|
|
219
|
-
}
|
|
220
|
-
} finally {
|
|
221
|
-
reader.releaseLock();
|
|
222
|
-
if (total > limitBytes) await stream.cancel().catch(() => void 0);
|
|
223
|
-
}
|
|
224
|
-
const body = new Uint8Array(total);
|
|
225
|
-
let offset = 0;
|
|
226
|
-
for (const chunk of chunks) {
|
|
227
|
-
body.set(chunk, offset);
|
|
228
|
-
offset += chunk.byteLength;
|
|
229
|
-
}
|
|
230
|
-
return body;
|
|
231
|
-
}
|
|
232
|
-
async function readAnswer(response, limitBytes, wantsBytes) {
|
|
233
|
-
const stream = response.body;
|
|
234
|
-
if (limitBytes > 0 && stream !== null && stream !== void 0) {
|
|
235
|
-
const body = await readStreamCapped(stream, limitBytes);
|
|
236
|
-
return wantsBytes ? { text: "", bytes: body } : { text: new TextDecoder().decode(body), bytes: null };
|
|
237
|
-
}
|
|
238
|
-
if (wantsBytes) {
|
|
239
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
240
|
-
if (limitBytes > 0 && bytes.byteLength > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
241
|
-
return { text: "", bytes };
|
|
242
|
-
}
|
|
243
|
-
const text = await response.text();
|
|
244
|
-
if (limitBytes > 0 && text.length > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
245
|
-
return { text, bytes: null };
|
|
246
|
-
}
|
|
247
|
-
function invalidResponse(message, details) {
|
|
248
|
-
return new NarsilError(ClientErrorCodes.CLIENT_INVALID_RESPONSE, message, details);
|
|
249
|
-
}
|
|
250
|
-
function errorFromBody(status, payload, url) {
|
|
251
|
-
const envelope = isRecord(payload) && isRecord(payload.error) ? payload.error : void 0;
|
|
252
|
-
if (envelope === void 0 || typeof envelope.code !== "string" || typeof envelope.message !== "string") {
|
|
253
|
-
return invalidResponse(`The server answered HTTP ${status} without a Narsil error envelope`, { url, status });
|
|
254
|
-
}
|
|
255
|
-
const details = isRecord(envelope.details) ? envelope.details : {};
|
|
256
|
-
return new NarsilError(envelope.code, envelope.message, { ...details, status });
|
|
257
|
-
}
|
|
258
|
-
function isAbsoluteUrl(value) {
|
|
259
|
-
try {
|
|
260
|
-
new URL(value);
|
|
261
|
-
return true;
|
|
262
|
-
} catch {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function normaliseBase(url) {
|
|
267
|
-
const trimmed = url.trim();
|
|
268
|
-
if (trimmed.length === 0) {
|
|
269
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, 'The client needs a server address, and "url" is empty');
|
|
270
|
-
}
|
|
271
|
-
if (!trimmed.startsWith("/") && !isAbsoluteUrl(trimmed)) {
|
|
272
|
-
throw new NarsilError(
|
|
273
|
-
ErrorCodes.CONFIG_INVALID,
|
|
274
|
-
`The client cannot read "${trimmed}" as a server address; pass an absolute URL or a path starting with "/"`,
|
|
275
|
-
{ url: trimmed }
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
279
|
-
}
|
|
280
|
-
function withQuery(path, query) {
|
|
281
|
-
if (query === void 0) return path;
|
|
282
|
-
const params = new URLSearchParams();
|
|
283
|
-
for (const [key, value] of Object.entries(query)) {
|
|
284
|
-
if (value !== void 0) params.set(key, value);
|
|
285
|
-
}
|
|
286
|
-
const encoded = params.toString();
|
|
287
|
-
return encoded.length === 0 ? path : `${path}?${encoded}`;
|
|
288
|
-
}
|
|
289
|
-
function createTransport(options) {
|
|
290
|
-
const base = normaliseBase(options.url);
|
|
291
|
-
const send = options.fetch ?? ((input, init) => fetch(input, init));
|
|
292
|
-
const baseHeaders = { ...options.headers };
|
|
293
|
-
if (options.apiKey !== void 0 && options.apiKey.length > 0) {
|
|
294
|
-
baseHeaders.authorization = `Bearer ${options.apiKey}`;
|
|
295
|
-
}
|
|
296
|
-
function resolveTimeout(spec) {
|
|
297
|
-
const perCall = spec.options?.timeoutMs;
|
|
298
|
-
if (perCall !== void 0) return perCall;
|
|
299
|
-
if (options.timeoutMs !== void 0) return options.timeoutMs;
|
|
300
|
-
return spec.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
301
|
-
}
|
|
302
|
-
function resolveResponseCeiling(spec) {
|
|
303
|
-
const perCall = spec.options?.maxResponseBytes;
|
|
304
|
-
if (perCall !== void 0) return perCall;
|
|
305
|
-
return options.maxResponseBytes ?? 0;
|
|
306
|
-
}
|
|
307
|
-
async function exchange(spec) {
|
|
308
|
-
const url = `${base}${withQuery(spec.path, spec.query)}`;
|
|
309
|
-
const headers = { ...baseHeaders, ...spec.options?.headers };
|
|
310
|
-
if (spec.contentType !== void 0) headers["content-type"] = spec.contentType;
|
|
311
|
-
const timeoutMs = resolveTimeout(spec);
|
|
312
|
-
const deadline = startDeadline(timeoutMs, spec.options?.signal);
|
|
313
|
-
try {
|
|
314
|
-
const init = { method: spec.method, headers, signal: deadline.signal };
|
|
315
|
-
if (spec.body !== void 0) init.body = spec.body;
|
|
316
|
-
const response = await send(url, init);
|
|
317
|
-
const wantsBytes = spec.binaryAnswer === true && response.ok;
|
|
318
|
-
const answer = await readAnswer(response, resolveResponseCeiling(spec), wantsBytes);
|
|
319
|
-
return { status: response.status, ...answer };
|
|
320
|
-
} catch (err) {
|
|
321
|
-
if (err instanceof ResponseTooLargeError) {
|
|
322
|
-
throw invalidResponse(`The server at ${url} answered more than this client reads: ${err.message}`, {
|
|
323
|
-
url,
|
|
324
|
-
maxResponseBytes: err.limitBytes
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (deadline.expired()) {
|
|
328
|
-
throw new NarsilError(
|
|
329
|
-
ClientErrorCodes.CLIENT_REQUEST_TIMEOUT,
|
|
330
|
-
`The server at ${url} did not answer within ${timeoutMs} ms`,
|
|
331
|
-
{ url, timeoutMs }
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
if (spec.options?.signal?.aborted === true) {
|
|
335
|
-
throw new NarsilError(ClientErrorCodes.CLIENT_REQUEST_ABORTED, `The caller cancelled the request to ${url}`, {
|
|
336
|
-
url
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
throw new NarsilError(
|
|
340
|
-
ClientErrorCodes.CLIENT_CONNECTION_FAILED,
|
|
341
|
-
`The client could not reach ${url}: ${describeError(err)}`,
|
|
342
|
-
{ url }
|
|
343
|
-
);
|
|
344
|
-
} finally {
|
|
345
|
-
deadline.release();
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
function parse(status, text, path) {
|
|
349
|
-
if (text.length === 0) return void 0;
|
|
350
|
-
try {
|
|
351
|
-
return JSON.parse(text);
|
|
352
|
-
} catch {
|
|
353
|
-
throw invalidResponse(`The server answered HTTP ${status} with a body that is not JSON`, { url: path, status });
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
async function json(spec) {
|
|
357
|
-
const { status, text } = await exchange(spec);
|
|
358
|
-
const payload = parse(status, text, spec.path);
|
|
359
|
-
if (status >= 200 && status < 300) return payload;
|
|
360
|
-
throw errorFromBody(status, payload, spec.path);
|
|
361
|
-
}
|
|
362
|
-
return {
|
|
363
|
-
json,
|
|
364
|
-
async jsonOrNull(spec, absentCode) {
|
|
365
|
-
try {
|
|
366
|
-
return await json(spec);
|
|
367
|
-
} catch (err) {
|
|
368
|
-
if (err instanceof NarsilError && err.code === absentCode) return null;
|
|
369
|
-
throw err;
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
async binary(spec) {
|
|
373
|
-
const { status, text, bytes } = await exchange(spec);
|
|
374
|
-
if (bytes !== null) return bytes;
|
|
375
|
-
throw errorFromBody(status, parse(status, text, spec.path), spec.path);
|
|
376
|
-
},
|
|
377
|
-
async probe(spec) {
|
|
378
|
-
const { status } = await exchange(spec);
|
|
379
|
-
return status;
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
172
|
+
var DEFAULT_TASK_POLL_INTERVAL_MS = 250;
|
|
383
173
|
|
|
384
174
|
// src/client/paths.ts
|
|
385
175
|
function indexPath(indexName) {
|
|
@@ -393,7 +183,7 @@ function taskPath(taskId) {
|
|
|
393
183
|
}
|
|
394
184
|
|
|
395
185
|
// src/client/response-shape.ts
|
|
396
|
-
function
|
|
186
|
+
function isRecord(value) {
|
|
397
187
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
398
188
|
}
|
|
399
189
|
function malformed(path, expected) {
|
|
@@ -402,27 +192,27 @@ function malformed(path, expected) {
|
|
|
402
192
|
});
|
|
403
193
|
}
|
|
404
194
|
function readArray(payload, key, path) {
|
|
405
|
-
if (!
|
|
195
|
+
if (!isRecord(payload) || !Array.isArray(payload[key])) throw malformed(path, `"${key}" array`);
|
|
406
196
|
return payload[key];
|
|
407
197
|
}
|
|
408
198
|
function readNumber(payload, key, path) {
|
|
409
|
-
if (!
|
|
199
|
+
if (!isRecord(payload) || typeof payload[key] !== "number") throw malformed(path, `"${key}" number`);
|
|
410
200
|
return payload[key];
|
|
411
201
|
}
|
|
412
202
|
function readBoolean(payload, key, path) {
|
|
413
|
-
if (!
|
|
203
|
+
if (!isRecord(payload) || typeof payload[key] !== "boolean") throw malformed(path, `"${key}" flag`);
|
|
414
204
|
return payload[key];
|
|
415
205
|
}
|
|
416
206
|
function readString(payload, key, path) {
|
|
417
|
-
if (!
|
|
207
|
+
if (!isRecord(payload) || typeof payload[key] !== "string") throw malformed(path, `"${key}" string`);
|
|
418
208
|
return payload[key];
|
|
419
209
|
}
|
|
420
210
|
function readObject(payload, key, path) {
|
|
421
|
-
if (!
|
|
211
|
+
if (!isRecord(payload) || !isRecord(payload[key])) throw malformed(path, `"${key}" object`);
|
|
422
212
|
return payload[key];
|
|
423
213
|
}
|
|
424
214
|
function readBody(payload, path) {
|
|
425
|
-
if (!
|
|
215
|
+
if (!isRecord(payload)) throw malformed(path, "JSON object");
|
|
426
216
|
return payload;
|
|
427
217
|
}
|
|
428
218
|
|
|
@@ -544,11 +334,19 @@ function createBulkOperations(transport) {
|
|
|
544
334
|
options
|
|
545
335
|
);
|
|
546
336
|
},
|
|
547
|
-
updateBatch(indexName, updates, options) {
|
|
548
|
-
return batch(
|
|
337
|
+
updateBatch(indexName, updates, writeOptions, options) {
|
|
338
|
+
return batch(
|
|
339
|
+
indexName,
|
|
340
|
+
{ action: "update", updates, ...writeOptions === void 0 ? {} : { options: writeOptions } },
|
|
341
|
+
options
|
|
342
|
+
);
|
|
549
343
|
},
|
|
550
|
-
removeBatch(indexName, docIds, options) {
|
|
551
|
-
return batch(
|
|
344
|
+
removeBatch(indexName, docIds, writeOptions, options) {
|
|
345
|
+
return batch(
|
|
346
|
+
indexName,
|
|
347
|
+
{ action: "delete", docIds, ...writeOptions === void 0 ? {} : { options: writeOptions } },
|
|
348
|
+
options
|
|
349
|
+
);
|
|
552
350
|
},
|
|
553
351
|
async getMultiple(indexName, docIds, options) {
|
|
554
352
|
const path = `${indexPath(indexName)}/documents/_multi-get`;
|
|
@@ -612,28 +410,36 @@ function createDocumentOperations(transport) {
|
|
|
612
410
|
const path = `${documentPath(indexName, docId)}/_exists`;
|
|
613
411
|
return readBoolean(await transport.json({ method: "GET", path, options }), "exists", path);
|
|
614
412
|
},
|
|
615
|
-
async put(indexName, docId, document, options) {
|
|
413
|
+
async put(indexName, docId, document, writeOptions, options) {
|
|
616
414
|
const path = documentPath(indexName, docId);
|
|
617
415
|
const payload = await transport.json({
|
|
618
416
|
method: "PUT",
|
|
619
417
|
path,
|
|
620
|
-
body: encodeJson({ document }),
|
|
418
|
+
body: encodeJson({ document, ...writeOptions === void 0 ? {} : { options: writeOptions } }),
|
|
621
419
|
contentType: "application/json",
|
|
622
420
|
options
|
|
623
421
|
});
|
|
624
422
|
return { id: readString(payload, "id", path), created: readBoolean(payload, "created", path) };
|
|
625
423
|
},
|
|
626
|
-
async update(indexName, docId, document, options) {
|
|
424
|
+
async update(indexName, docId, document, writeOptions, options) {
|
|
627
425
|
await transport.json({
|
|
628
426
|
method: "PATCH",
|
|
629
427
|
path: documentPath(indexName, docId),
|
|
630
|
-
body: encodeJson({ document }),
|
|
428
|
+
body: encodeJson({ document, ...writeOptions === void 0 ? {} : { options: writeOptions } }),
|
|
631
429
|
contentType: "application/json",
|
|
632
430
|
options
|
|
633
431
|
});
|
|
634
432
|
},
|
|
635
|
-
async remove(indexName, docId, options) {
|
|
636
|
-
await transport.json({
|
|
433
|
+
async remove(indexName, docId, writeOptions, options) {
|
|
434
|
+
await transport.json({
|
|
435
|
+
method: "DELETE",
|
|
436
|
+
path: documentPath(indexName, docId),
|
|
437
|
+
...writeOptions?.wait === true ? { query: { wait: "true" } } : {},
|
|
438
|
+
options
|
|
439
|
+
});
|
|
440
|
+
},
|
|
441
|
+
async waitForWrites(indexName, options) {
|
|
442
|
+
await transport.json({ method: "POST", path: `${indexPath(indexName)}/_wait-for-writes`, options });
|
|
637
443
|
},
|
|
638
444
|
async countDocuments(indexName, options) {
|
|
639
445
|
const path = `${indexPath(indexName)}/count`;
|
|
@@ -642,6 +448,219 @@ function createDocumentOperations(transport) {
|
|
|
642
448
|
};
|
|
643
449
|
}
|
|
644
450
|
|
|
451
|
+
// src/client/http.ts
|
|
452
|
+
function isRecord2(value) {
|
|
453
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
454
|
+
}
|
|
455
|
+
function unrefTimer(timer) {
|
|
456
|
+
const unrefable = timer;
|
|
457
|
+
unrefable.unref?.();
|
|
458
|
+
}
|
|
459
|
+
function startDeadline(timeoutMs, caller) {
|
|
460
|
+
const controller = new AbortController();
|
|
461
|
+
let expired = false;
|
|
462
|
+
const abortWithCaller = () => controller.abort();
|
|
463
|
+
if (caller?.aborted) controller.abort();
|
|
464
|
+
else caller?.addEventListener("abort", abortWithCaller);
|
|
465
|
+
let timer;
|
|
466
|
+
if (timeoutMs > 0 && !controller.signal.aborted) {
|
|
467
|
+
timer = setTimeout(() => {
|
|
468
|
+
expired = true;
|
|
469
|
+
controller.abort();
|
|
470
|
+
}, timeoutMs);
|
|
471
|
+
unrefTimer(timer);
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
signal: controller.signal,
|
|
475
|
+
expired: () => expired,
|
|
476
|
+
release: () => {
|
|
477
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
478
|
+
caller?.removeEventListener("abort", abortWithCaller);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
var ResponseTooLargeError = class extends Error {
|
|
483
|
+
constructor(limitBytes) {
|
|
484
|
+
super(`The answer passed the ${limitBytes} byte ceiling this client reads`);
|
|
485
|
+
this.limitBytes = limitBytes;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
async function readStreamCapped(stream, limitBytes) {
|
|
489
|
+
const reader = stream.getReader();
|
|
490
|
+
const chunks = [];
|
|
491
|
+
let total = 0;
|
|
492
|
+
try {
|
|
493
|
+
for (; ; ) {
|
|
494
|
+
const { done, value } = await reader.read();
|
|
495
|
+
if (done) break;
|
|
496
|
+
total += value.byteLength;
|
|
497
|
+
if (total > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
498
|
+
chunks.push(value);
|
|
499
|
+
}
|
|
500
|
+
} finally {
|
|
501
|
+
reader.releaseLock();
|
|
502
|
+
if (total > limitBytes) await stream.cancel().catch(() => void 0);
|
|
503
|
+
}
|
|
504
|
+
const body = new Uint8Array(total);
|
|
505
|
+
let offset = 0;
|
|
506
|
+
for (const chunk of chunks) {
|
|
507
|
+
body.set(chunk, offset);
|
|
508
|
+
offset += chunk.byteLength;
|
|
509
|
+
}
|
|
510
|
+
return body;
|
|
511
|
+
}
|
|
512
|
+
async function readAnswer(response, limitBytes, wantsBytes) {
|
|
513
|
+
const stream = response.body;
|
|
514
|
+
if (limitBytes > 0 && stream !== null && stream !== void 0) {
|
|
515
|
+
const body = await readStreamCapped(stream, limitBytes);
|
|
516
|
+
return wantsBytes ? { text: "", bytes: body } : { text: new TextDecoder().decode(body), bytes: null };
|
|
517
|
+
}
|
|
518
|
+
if (wantsBytes) {
|
|
519
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
520
|
+
if (limitBytes > 0 && bytes.byteLength > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
521
|
+
return { text: "", bytes };
|
|
522
|
+
}
|
|
523
|
+
const text = await response.text();
|
|
524
|
+
if (limitBytes > 0 && text.length > limitBytes) throw new ResponseTooLargeError(limitBytes);
|
|
525
|
+
return { text, bytes: null };
|
|
526
|
+
}
|
|
527
|
+
function invalidResponse(message, details) {
|
|
528
|
+
return new NarsilError(ClientErrorCodes.CLIENT_INVALID_RESPONSE, message, details);
|
|
529
|
+
}
|
|
530
|
+
function errorFromBody(status, payload, url) {
|
|
531
|
+
const envelope = isRecord2(payload) && isRecord2(payload.error) ? payload.error : void 0;
|
|
532
|
+
if (envelope === void 0 || typeof envelope.code !== "string" || typeof envelope.message !== "string") {
|
|
533
|
+
return invalidResponse(`The server answered HTTP ${status} without a Narsil error envelope`, { url, status });
|
|
534
|
+
}
|
|
535
|
+
const details = isRecord2(envelope.details) ? envelope.details : {};
|
|
536
|
+
return new NarsilError(envelope.code, envelope.message, { ...details, status });
|
|
537
|
+
}
|
|
538
|
+
function isAbsoluteUrl(value) {
|
|
539
|
+
try {
|
|
540
|
+
new URL(value);
|
|
541
|
+
return true;
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function normaliseBase(url) {
|
|
547
|
+
const trimmed = url.trim();
|
|
548
|
+
if (trimmed.length === 0) {
|
|
549
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, 'The client needs a server address, and "url" is empty');
|
|
550
|
+
}
|
|
551
|
+
if (!trimmed.startsWith("/") && !isAbsoluteUrl(trimmed)) {
|
|
552
|
+
throw new NarsilError(
|
|
553
|
+
ErrorCodes.CONFIG_INVALID,
|
|
554
|
+
`The client cannot read "${trimmed}" as a server address; pass an absolute URL or a path starting with "/"`,
|
|
555
|
+
{ url: trimmed }
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
559
|
+
}
|
|
560
|
+
function withQuery(path, query) {
|
|
561
|
+
if (query === void 0) return path;
|
|
562
|
+
const params = new URLSearchParams();
|
|
563
|
+
for (const [key, value] of Object.entries(query)) {
|
|
564
|
+
if (value !== void 0) params.set(key, value);
|
|
565
|
+
}
|
|
566
|
+
const encoded = params.toString();
|
|
567
|
+
return encoded.length === 0 ? path : `${path}?${encoded}`;
|
|
568
|
+
}
|
|
569
|
+
function createTransport(options) {
|
|
570
|
+
const base = normaliseBase(options.url);
|
|
571
|
+
const send = options.fetch ?? ((input, init) => fetch(input, init));
|
|
572
|
+
const baseHeaders = { ...options.headers };
|
|
573
|
+
if (options.apiKey !== void 0 && options.apiKey.length > 0) {
|
|
574
|
+
baseHeaders.authorization = `Bearer ${options.apiKey}`;
|
|
575
|
+
}
|
|
576
|
+
function resolveTimeout(spec) {
|
|
577
|
+
const perCall = spec.options?.timeoutMs;
|
|
578
|
+
if (perCall !== void 0) return perCall;
|
|
579
|
+
if (options.timeoutMs !== void 0) return options.timeoutMs;
|
|
580
|
+
return spec.defaultTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
581
|
+
}
|
|
582
|
+
function resolveResponseCeiling(spec) {
|
|
583
|
+
const perCall = spec.options?.maxResponseBytes;
|
|
584
|
+
if (perCall !== void 0) return perCall;
|
|
585
|
+
return options.maxResponseBytes ?? 0;
|
|
586
|
+
}
|
|
587
|
+
async function exchange(spec) {
|
|
588
|
+
const url = `${base}${withQuery(spec.path, spec.query)}`;
|
|
589
|
+
const headers = { ...baseHeaders, ...spec.options?.headers };
|
|
590
|
+
if (spec.contentType !== void 0) headers["content-type"] = spec.contentType;
|
|
591
|
+
const timeoutMs = resolveTimeout(spec);
|
|
592
|
+
const deadline = startDeadline(timeoutMs, spec.options?.signal);
|
|
593
|
+
try {
|
|
594
|
+
const init = { method: spec.method, headers, signal: deadline.signal };
|
|
595
|
+
if (spec.body !== void 0) init.body = spec.body;
|
|
596
|
+
const response = await send(url, init);
|
|
597
|
+
const wantsBytes = spec.binaryAnswer === true && response.ok;
|
|
598
|
+
const answer = await readAnswer(response, resolveResponseCeiling(spec), wantsBytes);
|
|
599
|
+
return { status: response.status, ...answer };
|
|
600
|
+
} catch (err) {
|
|
601
|
+
if (err instanceof ResponseTooLargeError) {
|
|
602
|
+
throw invalidResponse(`The server at ${url} answered more than this client reads: ${err.message}`, {
|
|
603
|
+
url,
|
|
604
|
+
maxResponseBytes: err.limitBytes
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (deadline.expired()) {
|
|
608
|
+
throw new NarsilError(
|
|
609
|
+
ClientErrorCodes.CLIENT_REQUEST_TIMEOUT,
|
|
610
|
+
`The server at ${url} did not answer within ${timeoutMs} ms`,
|
|
611
|
+
{ url, timeoutMs }
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (spec.options?.signal?.aborted === true) {
|
|
615
|
+
throw new NarsilError(ClientErrorCodes.CLIENT_REQUEST_ABORTED, `The caller cancelled the request to ${url}`, {
|
|
616
|
+
url
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
throw new NarsilError(
|
|
620
|
+
ClientErrorCodes.CLIENT_CONNECTION_FAILED,
|
|
621
|
+
`The client could not reach ${url}: ${describeError(err)}`,
|
|
622
|
+
{ url }
|
|
623
|
+
);
|
|
624
|
+
} finally {
|
|
625
|
+
deadline.release();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function parse(status, text, path) {
|
|
629
|
+
if (text.length === 0) return void 0;
|
|
630
|
+
try {
|
|
631
|
+
return JSON.parse(text);
|
|
632
|
+
} catch {
|
|
633
|
+
throw invalidResponse(`The server answered HTTP ${status} with a body that is not JSON`, { url: path, status });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function json(spec) {
|
|
637
|
+
const { status, text } = await exchange(spec);
|
|
638
|
+
const payload = parse(status, text, spec.path);
|
|
639
|
+
if (status >= 200 && status < 300) return payload;
|
|
640
|
+
throw errorFromBody(status, payload, spec.path);
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
json,
|
|
644
|
+
async jsonOrNull(spec, absentCode) {
|
|
645
|
+
try {
|
|
646
|
+
return await json(spec);
|
|
647
|
+
} catch (err) {
|
|
648
|
+
if (err instanceof NarsilError && err.code === absentCode) return null;
|
|
649
|
+
throw err;
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
async binary(spec) {
|
|
653
|
+
const { status, text, bytes } = await exchange(spec);
|
|
654
|
+
if (bytes !== null) return bytes;
|
|
655
|
+
throw errorFromBody(status, parse(status, text, spec.path), spec.path);
|
|
656
|
+
},
|
|
657
|
+
async probe(spec) {
|
|
658
|
+
const { status } = await exchange(spec);
|
|
659
|
+
return status;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
645
664
|
// src/client/indexes.ts
|
|
646
665
|
function createIndexOperations(transport) {
|
|
647
666
|
return {
|
|
@@ -746,7 +765,6 @@ function createServerOperations(transport) {
|
|
|
746
765
|
}
|
|
747
766
|
|
|
748
767
|
// src/client/tasks.ts
|
|
749
|
-
var DEFAULT_POLL_INTERVAL_MS = 250;
|
|
750
768
|
function isTerminal(record) {
|
|
751
769
|
return record.status === "succeeded" || record.status === "failed" || record.status === "cancelled";
|
|
752
770
|
}
|
|
@@ -794,7 +812,7 @@ function createTaskOperations(transport) {
|
|
|
794
812
|
return readBody(await transport.json({ method: "POST", path, options }), path);
|
|
795
813
|
},
|
|
796
814
|
async waitForTask(taskId, options) {
|
|
797
|
-
const interval = options?.pollIntervalMs ??
|
|
815
|
+
const interval = options?.pollIntervalMs ?? DEFAULT_TASK_POLL_INTERVAL_MS;
|
|
798
816
|
const waitTimeoutMs = options?.waitTimeoutMs ?? 0;
|
|
799
817
|
const startedAt = Date.now();
|
|
800
818
|
let reported = null;
|