@delali/narsil 0.3.1-next.72 → 0.3.1-next.74

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 (70) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/filesystem.mjs +1 -1
  3. package/dist/adapters/indexeddb.mjs +1 -1
  4. package/dist/{capabilities-D6gjjQyA.d.ts → capabilities-CAMzmkAg.d.ts} +19 -4
  5. package/dist/{chunk-P2ZZN32C.mjs → chunk-2E35UM3G.mjs} +2 -12
  6. package/dist/chunk-3DDUNI6N.mjs +48 -0
  7. package/dist/{chunk-KZTI25D3.mjs → chunk-4MQ5F4IL.mjs} +1 -1
  8. package/dist/{chunk-HVJYTKK7.mjs → chunk-BVBIQQKG.mjs} +947 -1327
  9. package/dist/{chunk-2X2ZQOAB.mjs → chunk-CTOSGRM7.mjs} +101 -21
  10. package/dist/{chunk-7QMTA7AA.mjs → chunk-CUZOX6CN.mjs} +7784 -4276
  11. package/dist/chunk-I23G3IZC.mjs +593 -0
  12. package/dist/{chunk-RV77FR2F.mjs → chunk-I64LXKO3.mjs} +0 -1
  13. package/dist/chunk-KEHK2O6P.mjs +13 -0
  14. package/dist/{chunk-CVPA5Q7R.mjs → chunk-LX4JERXI.mjs} +9 -6
  15. package/dist/{chunk-EDMCZLSJ.mjs → chunk-NOZUQM7J.mjs} +193 -13
  16. package/dist/{chunk-LCCL6EEH.mjs → chunk-PO53FV4Q.mjs} +93 -94
  17. package/dist/chunk-T5ZWEWVD.mjs +658 -0
  18. package/dist/{chunk-Y52WZWAU.mjs → chunk-TVFLHM7M.mjs} +4 -4
  19. package/dist/{chunk-QR4OKRVY.mjs → chunk-VKX2BPXD.mjs} +147 -642
  20. package/dist/{chunk-3HQPUCCT.mjs → chunk-XW25X6AU.mjs} +66 -237
  21. package/dist/{chunk-M6XF5HDE.mjs → chunk-Z3DQPOJQ.mjs} +127 -30
  22. package/dist/{client-C2TiW29y.d.ts → client-C-I70ArH.d.ts} +77 -26
  23. package/dist/client.d.ts +9 -9
  24. package/dist/client.mjs +44 -22
  25. package/dist/{config-BLguB-om.d.ts → config-C6Iw5rCX.d.ts} +11 -4
  26. package/dist/{config-C7Ip8Ife.d.ts → config-DJPH3IQB.d.ts} +11 -2
  27. package/dist/direct-executor-KFAGILJC.mjs +17 -0
  28. package/dist/distribution/coordinator/etcd.d.ts +29 -16
  29. package/dist/distribution/coordinator/etcd.mjs +162 -75
  30. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  31. package/dist/distribution/transport/grpc.d.ts +2 -2
  32. package/dist/distribution/transport/grpc.mjs +22 -2
  33. package/dist/distribution/transport/in-memory.d.ts +2 -2
  34. package/dist/distribution/transport/in-memory.mjs +6 -3
  35. package/dist/distribution/transport/tcp.d.ts +2 -2
  36. package/dist/distribution/transport/tcp.mjs +40 -16
  37. package/dist/distribution.d.ts +9 -9
  38. package/dist/distribution.mjs +647 -366
  39. package/dist/embeddings/openai.mjs +1 -1
  40. package/dist/{engine-Dm4446j1.d.ts → engine-B8eRPO7m.d.ts} +97 -188
  41. package/dist/index.browser.mjs +12132 -8592
  42. package/dist/index.d.ts +27 -8
  43. package/dist/index.mjs +15 -14
  44. package/dist/{memory-CKHTU1CL.d.ts → memory-DJEwWoCi.d.ts} +1204 -968
  45. package/dist/partial-words-DTIHO8G4.d.ts +19 -0
  46. package/dist/partial-words.d.ts +22 -0
  47. package/dist/partial-words.mjs +11 -0
  48. package/dist/pattern.mjs +1 -1
  49. package/dist/persistence/durability/checkpoint-worker.d.ts +57 -29
  50. package/dist/persistence/durability/checkpoint-worker.mjs +26 -16
  51. package/dist/react.d.ts +7 -7
  52. package/dist/react.mjs +0 -1
  53. package/dist/{schema-BBOCdhND.d.ts → schema-DgyDijhf.d.ts} +6 -2
  54. package/dist/server.d.ts +11 -11
  55. package/dist/server.mjs +212 -32
  56. package/dist/{tasks-BECd0b__.d.ts → tasks-BVmAKYBh.d.ts} +3 -3
  57. package/dist/{thread-IBSIZZ6W.mjs → thread-W6DPKEF6.mjs} +82 -21
  58. package/dist/{types-BQDlIvfS.d.ts → types-B3b68nfF.d.ts} +1 -1
  59. package/dist/{types-D5OwhtsQ.d.ts → types-Cb9kiY5P.d.ts} +1 -1
  60. package/dist/{types-BiUmaiWT.d.ts → types-ChrVy4lH.d.ts} +1 -1
  61. package/dist/vector/search-worker.d.ts +1 -1
  62. package/dist/vector/search-worker.mjs +4 -3
  63. package/dist/workers/entry.mjs +4 -4
  64. package/package.json +15 -7
  65. package/dist/chunk-3L4U6EJQ.mjs +0 -49
  66. package/dist/chunk-IFBKLBGB.mjs +0 -7
  67. package/dist/chunk-XZ4PAREY.mjs +0 -25
  68. package/dist/direct-executor-SVCHXR74.mjs +0 -14
  69. package/dist/serialization/crc32-worker.d.ts +0 -20
  70. package/dist/serialization/crc32-worker.mjs +0 -62
package/dist/server.mjs CHANGED
@@ -1,21 +1,24 @@
1
- import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsHeaderReader, 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-EDMCZLSJ.mjs';
2
- export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-EDMCZLSJ.mjs';
3
- import { validateIndexName } from './chunk-Y52WZWAU.mjs';
4
- import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-CVPA5Q7R.mjs';
5
- import './chunk-XZ4PAREY.mjs';
6
- import { detectRuntime } from './chunk-KZTI25D3.mjs';
7
- import './chunk-M6XF5HDE.mjs';
1
+ import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsHeaderReader, 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_RESTORE_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, sendStream, SNAPSHOT_ARCHIVE_CONTENT_TYPE, DOCUMENT_EXPORT_CONTENT_TYPE, IMPORT_FLUSHES_IN_FLIGHT, badRequest, SERVER_CAPABILITIES, toHttpError, RELAY_STREAM_CHUNKS_IN_FLIGHT, MIN_UNBOUNDED_SERVER_LIMIT, MIN_COUNTED_SERVER_LIMIT } from './chunk-NOZUQM7J.mjs';
2
+ export { ASYNC_IMPORT_CAPABILITY, DOCUMENT_EXPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-NOZUQM7J.mjs';
3
+ import { validateIndexName } from './chunk-TVFLHM7M.mjs';
4
+ import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-LX4JERXI.mjs';
5
+ import { detectRuntime } from './chunk-4MQ5F4IL.mjs';
6
+ import './chunk-3DDUNI6N.mjs';
8
7
  import './chunk-TGP5M5R7.mjs';
9
- import './chunk-P2ZZN32C.mjs';
8
+ import './chunk-2E35UM3G.mjs';
9
+ import './chunk-Z3DQPOJQ.mjs';
10
+ import './chunk-KEHK2O6P.mjs';
10
11
  import './chunk-U2BKYEC7.mjs';
11
12
  import './chunk-LJYTW7GT.mjs';
12
13
  import { startIdleHeapCleanup } from './chunk-Y3L5MDRV.mjs';
13
- import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-RV77FR2F.mjs';
14
- export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-RV77FR2F.mjs';
14
+ import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-I64LXKO3.mjs';
15
+ export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-I64LXKO3.mjs';
15
16
  import { randomUUID } from 'crypto';
17
+ import { createReadStream } from 'fs';
18
+ import { rm } from 'fs/promises';
19
+ import path from 'path';
16
20
  import { MessageChannel } from 'worker_threads';
17
21
 
18
- // src/server/handlers/admin.ts
19
22
  function createAdminHandlers(deps) {
20
23
  const { engine, tasks, limits } = deps;
21
24
  async function checkpoint(ctx) {
@@ -28,9 +31,12 @@ function createAdminHandlers(deps) {
28
31
  }
29
32
  async function snapshot(ctx) {
30
33
  try {
31
- const bytes = await engine.snapshot(ctx.params[0]);
32
- if (ctx.abort.aborted) return;
33
- sendBinary(ctx.res, bytes, ctx.abort);
34
+ const archive = await engine.snapshot(ctx.params[0]);
35
+ if (ctx.abort.aborted) {
36
+ await archive.cancel();
37
+ return;
38
+ }
39
+ await sendStream(ctx.res, ctx.abort, archive, [["Content-Type", SNAPSHOT_ARCHIVE_CONTENT_TYPE]]);
34
40
  } catch (err) {
35
41
  respondError(ctx, err);
36
42
  }
@@ -118,15 +124,26 @@ function createAdminHandlers(deps) {
118
124
  respondJson(ctx, record, 202);
119
125
  }
120
126
  async function restore(ctx) {
121
- const raw = ctx.rawBody;
122
- if (!raw || raw.length === 0) {
127
+ const spooled = ctx.spooledBody;
128
+ if (spooled === null || spooled.bytes === 0) {
129
+ await spooled?.remove();
123
130
  sendError(ctx.res, 400, ServerErrorCodes.EMPTY_BODY, "Request body is empty");
124
131
  return;
125
132
  }
126
133
  const name = ctx.params[0];
127
- const bytes = new Uint8Array(raw);
128
- const record = await tasks.start("restore", name, () => engine.restore(name, bytes));
129
- respondJson(ctx, record, 202);
134
+ try {
135
+ const record = await tasks.start("restore", name, async () => {
136
+ try {
137
+ await engine.restore(name, createReadStream(spooled.path));
138
+ } finally {
139
+ await spooled.remove();
140
+ }
141
+ });
142
+ respondJson(ctx, record, 202);
143
+ } catch (err) {
144
+ await spooled.remove();
145
+ respondError(ctx, err);
146
+ }
130
147
  }
131
148
  async function getTask(ctx) {
132
149
  const record = await tasks.get(ctx.params[0]);
@@ -270,6 +287,54 @@ function createClusterHandlers(deps) {
270
287
  }
271
288
  return { topology, allocation };
272
289
  }
290
+ function importResultOf(result, maxErrors) {
291
+ const errors = result.refused.slice(0, maxErrors);
292
+ return {
293
+ indexed: result.imported,
294
+ failed: result.refusedCount,
295
+ errors,
296
+ errorsTruncated: result.refusedCount > errors.length
297
+ };
298
+ }
299
+ function createDocumentExportHandlers(deps) {
300
+ const { engine, tasks, limits } = deps;
301
+ async function exportDocuments(ctx) {
302
+ try {
303
+ const lines = await engine.exportDocuments(ctx.params[0]);
304
+ if (ctx.abort.aborted) {
305
+ await lines.cancel();
306
+ return;
307
+ }
308
+ await sendStream(ctx.res, ctx.abort, lines, [["Content-Type", DOCUMENT_EXPORT_CONTENT_TYPE]]);
309
+ } catch (err) {
310
+ respondError(ctx, err);
311
+ }
312
+ }
313
+ async function importExport(ctx) {
314
+ const spooled = ctx.spooledBody;
315
+ if (spooled === null || spooled.bytes === 0) {
316
+ await spooled?.remove();
317
+ sendError(ctx.res, 400, ServerErrorCodes.EMPTY_BODY, "Request body is empty");
318
+ return;
319
+ }
320
+ const name = ctx.params[0];
321
+ try {
322
+ const record = await tasks.start("importExport", name, async (context) => {
323
+ try {
324
+ const result = await engine.importDocumentExport(name, createReadStream(spooled.path));
325
+ context.reportResult(importResultOf(result, limits.maxImportErrors));
326
+ } finally {
327
+ await spooled.remove();
328
+ }
329
+ });
330
+ respondJson(ctx, record, 202);
331
+ } catch (err) {
332
+ await spooled.remove();
333
+ respondError(ctx, err);
334
+ }
335
+ }
336
+ return { exportDocuments, importExport };
337
+ }
273
338
 
274
339
  // src/server/handlers/documents.ts
275
340
  function createDocumentHandlers(deps) {
@@ -757,6 +822,7 @@ var ENGINE_METHOD_SAMPLE = ["createIndex", "listIndexes", "insert", "query", "ge
757
822
  var LIMITS_UNBOUNDED_AT_ZERO = [
758
823
  "maxBodyBytes",
759
824
  "maxImportBytes",
825
+ "maxRestoreBytes",
760
826
  "maxLineBytes",
761
827
  "maxImportErrors",
762
828
  "maxConcurrentRequests",
@@ -808,10 +874,32 @@ function bodyBytes(body) {
808
874
  if (typeof body === "string") return Buffer.from(body, "utf8");
809
875
  return body;
810
876
  }
811
- function createCapturingResponse(remoteAddress) {
877
+ function createRelayAbort() {
878
+ const listeners = [];
879
+ let aborted = false;
880
+ return {
881
+ get aborted() {
882
+ return aborted;
883
+ },
884
+ onAbort(fn) {
885
+ if (aborted) fn();
886
+ else listeners.push(fn);
887
+ },
888
+ trigger() {
889
+ if (aborted) return;
890
+ aborted = true;
891
+ for (const fn of listeners) fn();
892
+ }
893
+ };
894
+ }
895
+ function createCapturingResponse(remoteAddress, requestId, sendPart) {
812
896
  let status = DEFAULT_STATUS;
813
897
  const headers = [];
814
898
  let finished = false;
899
+ let streaming = false;
900
+ let inFlight = 0;
901
+ let writable = null;
902
+ const abort = createRelayAbort();
815
903
  let resolveDone = () => void 0;
816
904
  const done = new Promise((resolve) => {
817
905
  resolveDone = resolve;
@@ -819,25 +907,57 @@ function createCapturingResponse(remoteAddress) {
819
907
  function finish(body) {
820
908
  if (finished) return;
821
909
  finished = true;
910
+ if (streaming) {
911
+ sendPart({ type: "stream-end", requestId, failed: false }, []);
912
+ resolveDone(null);
913
+ return;
914
+ }
822
915
  resolveDone({ status, headers, body });
823
916
  }
917
+ function startStreaming() {
918
+ if (streaming) return;
919
+ streaming = true;
920
+ sendPart({ type: "stream-start", requestId, status, headers }, []);
921
+ }
824
922
  const sink = {
825
923
  cork(callback) {
826
924
  callback();
827
925
  return sink;
828
926
  },
829
927
  writeStatus(line) {
830
- if (!finished) status = statusOf(line);
928
+ if (!finished && !streaming) status = statusOf(line);
831
929
  return sink;
832
930
  },
833
931
  writeHeader(key, value) {
834
- if (!finished) headers.push([key, value]);
932
+ if (!finished && !streaming) headers.push([key, value]);
835
933
  return sink;
836
934
  },
837
935
  end(body) {
838
936
  finish(bodyBytes(body));
839
937
  return sink;
840
938
  },
939
+ write(chunk) {
940
+ if (finished || abort.aborted) return true;
941
+ startStreaming();
942
+ const copy = chunk.slice();
943
+ inFlight += 1;
944
+ sendPart({ type: "stream-chunk", requestId, chunk: copy }, [copy.buffer]);
945
+ return inFlight < RELAY_STREAM_CHUNKS_IN_FLIGHT;
946
+ },
947
+ pause: () => void 0,
948
+ resume: () => void 0,
949
+ close() {
950
+ if (finished) return sink;
951
+ finished = true;
952
+ if (streaming) {
953
+ sendPart({ type: "stream-end", requestId, failed: true }, []);
954
+ resolveDone(null);
955
+ } else {
956
+ status = NO_RESPONSE_STATUS;
957
+ resolveDone({ status, headers, body: new Uint8Array(0) });
958
+ }
959
+ return sink;
960
+ },
841
961
  endWithoutBody() {
842
962
  finish(new Uint8Array(0));
843
963
  return sink;
@@ -847,16 +967,45 @@ function createCapturingResponse(remoteAddress) {
847
967
  return [true, true];
848
968
  },
849
969
  getWriteOffset: () => 0,
850
- onWritable: () => sink,
970
+ onWritable(handler) {
971
+ writable = handler;
972
+ return sink;
973
+ },
851
974
  onAborted: () => sink,
852
975
  onData: () => sink,
853
976
  getRemoteAddressAsText: () => new TextEncoder().encode(remoteAddress).buffer
854
977
  };
855
978
  return {
856
979
  sink,
980
+ abort,
857
981
  done,
982
+ credit() {
983
+ inFlight = Math.max(0, inFlight - 1);
984
+ if (inFlight >= RELAY_STREAM_CHUNKS_IN_FLIGHT || writable === null) return;
985
+ const handler = writable;
986
+ writable = null;
987
+ handler(0);
988
+ },
989
+ cancel() {
990
+ abort.trigger();
991
+ if (writable !== null) {
992
+ const handler = writable;
993
+ writable = null;
994
+ handler(0);
995
+ }
996
+ if (!finished) {
997
+ finished = true;
998
+ resolveDone(null);
999
+ }
1000
+ },
858
1001
  finishUnanswered() {
859
1002
  if (finished) return;
1003
+ if (streaming) {
1004
+ finished = true;
1005
+ sendPart({ type: "stream-end", requestId, failed: true }, []);
1006
+ resolveDone(null);
1007
+ return;
1008
+ }
860
1009
  status = NO_RESPONSE_STATUS;
861
1010
  finish(new Uint8Array(0));
862
1011
  }
@@ -867,22 +1016,36 @@ function toRelayedResponse(requestId, captured) {
867
1016
  }
868
1017
 
869
1018
  // src/server/request-threads/host.ts
870
- var NO_ABORT = { aborted: false, onAbort: () => void 0 };
1019
+ var RELAYED_TYPES = /* @__PURE__ */ new Set(["request", "authorize", "embed", "stream-credit", "stream-cancel"]);
871
1020
  function isRelayRequest(message) {
872
1021
  if (typeof message !== "object" || message === null) return false;
873
1022
  const { type, requestId } = message;
874
1023
  if (type === "touch") return true;
875
- return typeof requestId === "number" && (type === "request" || type === "authorize" || type === "embed");
1024
+ return typeof requestId === "number" && typeof type === "string" && RELAYED_TYPES.has(type);
876
1025
  }
877
- async function answerRequest(handlers, request) {
878
- const captured = createCapturingResponse(request.hookContext?.remoteAddress ?? "");
1026
+ function spooledBodyOf(relayed) {
1027
+ if (relayed === null) return null;
1028
+ return {
1029
+ path: relayed.path,
1030
+ bytes: relayed.bytes,
1031
+ remove: () => rm(path.dirname(relayed.path), { recursive: true, force: true })
1032
+ };
1033
+ }
1034
+ async function answerRequest(handlers, request, port, streams) {
1035
+ const captured = createCapturingResponse(
1036
+ request.hookContext?.remoteAddress ?? "",
1037
+ request.requestId,
1038
+ (part, moved) => port.postMessage(part, moved)
1039
+ );
1040
+ streams.set(request.requestId, captured);
879
1041
  const ctx = {
880
1042
  res: captured.sink,
881
1043
  params: request.params,
882
1044
  query: new URLSearchParams(request.query),
883
1045
  contentType: request.contentType,
884
1046
  rawBody: request.rawBody === null ? null : Buffer.from(request.rawBody.buffer, request.rawBody.byteOffset, request.rawBody.byteLength),
885
- abort: NO_ABORT,
1047
+ spooledBody: spooledBodyOf(request.spooledBody),
1048
+ abort: captured.abort,
886
1049
  hookContext: request.hookContext
887
1050
  };
888
1051
  try {
@@ -891,7 +1054,9 @@ async function answerRequest(handlers, request) {
891
1054
  sendError(ctx.res, 500, ServerErrorCodes.INTERNAL_ERROR, "An unexpected error occurred");
892
1055
  }
893
1056
  captured.finishUnanswered();
894
- return toRelayedResponse(request.requestId, await captured.done);
1057
+ const answered = await captured.done;
1058
+ streams.delete(request.requestId);
1059
+ return answered === null ? null : toRelayedResponse(request.requestId, answered);
895
1060
  }
896
1061
  async function answerAuthorize(authorize, request) {
897
1062
  if (authorize === void 0) {
@@ -942,14 +1107,25 @@ async function startRequestThreads(options) {
942
1107
  const threads = /* @__PURE__ */ new Map();
943
1108
  let stopped = false;
944
1109
  function relayFrom(port) {
1110
+ const streams = /* @__PURE__ */ new Map();
945
1111
  port.on("message", (message) => {
946
1112
  if (!isRelayRequest(message)) return;
947
1113
  if (message.type === "touch") {
948
1114
  core.orchestrator.noteAccess(message.indexName);
949
1115
  return;
950
1116
  }
951
- const reply = message.type === "request" ? answerRequest(handlers, message) : message.type === "authorize" ? answerAuthorize(authorize, message) : answerEmbed(core, message);
952
- void reply.then((answer) => port.postMessage(answer));
1117
+ if (message.type === "stream-credit") {
1118
+ streams.get(message.requestId)?.credit();
1119
+ return;
1120
+ }
1121
+ if (message.type === "stream-cancel") {
1122
+ streams.get(message.requestId)?.cancel();
1123
+ return;
1124
+ }
1125
+ const reply = message.type === "request" ? answerRequest(handlers, message, port, streams) : message.type === "authorize" ? answerAuthorize(authorize, message) : answerEmbed(core, message);
1126
+ void reply.then((answer) => {
1127
+ if (answer !== null) port.postMessage(answer);
1128
+ });
953
1129
  });
954
1130
  }
955
1131
  async function stopThread(workerId, thread) {
@@ -1254,6 +1430,7 @@ function resolveLimits(limits) {
1254
1430
  return {
1255
1431
  maxBodyBytes: limits?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
1256
1432
  maxImportBytes: limits?.maxImportBytes ?? DEFAULT_MAX_IMPORT_BYTES,
1433
+ maxRestoreBytes: limits?.maxRestoreBytes ?? DEFAULT_MAX_RESTORE_BYTES,
1257
1434
  maxLineBytes: limits?.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES,
1258
1435
  importBatchSize: limits?.importBatchSize ?? DEFAULT_IMPORT_BATCH_SIZE,
1259
1436
  maxConcurrentRequests: limits?.maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS,
@@ -1287,6 +1464,7 @@ function mainThreadHandlers(deps) {
1287
1464
  const doc = createDocumentHandlers(deps);
1288
1465
  const search = createSearchHandlers(deps);
1289
1466
  const admin = createAdminHandlers(deps);
1467
+ const documentExport = createDocumentExportHandlers(deps);
1290
1468
  const health = createHealthHandlers(deps);
1291
1469
  const cluster = createClusterHandlers(deps);
1292
1470
  const version = createVersionHandler(deps);
@@ -1333,7 +1511,9 @@ function mainThreadHandlers(deps) {
1333
1511
  optimize: admin.optimize,
1334
1512
  rebalance: admin.rebalance,
1335
1513
  partitionConfig: admin.partitionConfig,
1336
- restore: admin.restore
1514
+ restore: admin.restore,
1515
+ exportDocuments: documentExport.exportDocuments,
1516
+ importExport: documentExport.importExport
1337
1517
  };
1338
1518
  }
1339
1519
  var NarsilHttpServer = class {
@@ -1,4 +1,4 @@
1
- import { M as MemoryStats } from './memory-CKHTU1CL.js';
1
+ import { M as MemoryStats } from './memory-DJEwWoCi.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
@@ -142,7 +142,7 @@ interface MemoryStatsResponse extends MemoryStats {
142
142
  *
143
143
  * @public
144
144
  */
145
- type TaskType = 'optimizeVectors' | 'rebalance' | 'restore' | 'import' | 'rebuildAnalysis';
145
+ type TaskType = 'optimizeVectors' | 'rebalance' | 'restore' | 'import' | 'importExport' | 'rebuildAnalysis';
146
146
  /**
147
147
  * Where a long-running operation stands.
148
148
  *
@@ -207,7 +207,7 @@ interface TaskRecord {
207
207
  cancelRequestedAt?: number;
208
208
  /** How far the work has gone, which an import reports and other task types omit. */
209
209
  progress?: TaskProgress;
210
- /** What the work produced, which an import reports once it finishes. */
210
+ /** What the work produced, which an `import` or `importExport` task reports once it finishes. */
211
211
  result?: ImportResult;
212
212
  /** This says why the task failed, and a failed task alone carries it. */
213
213
  error?: {
@@ -1,13 +1,15 @@
1
- import { loadUWebSockets, childAppOf, createRouteRunner, corsHeaderReader, createSharedRequestGate, registerServerRoutes, sendRelayed, createDocumentReadHandlers, respondJson, SERVER_CAPABILITIES, versionReport, sendError, serverRoutes, parseJson, validateSuggest, rejectInvalid, respondError, validateQuery } from './chunk-EDMCZLSJ.mjs';
2
- import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-2X2ZQOAB.mjs';
3
- import './chunk-XZ4PAREY.mjs';
4
- import { resolveProjection, projectionKeepsField, setNestedValue } from './chunk-QR4OKRVY.mjs';
5
- import { extractVectorFieldsFromSchema } from './chunk-M6XF5HDE.mjs';
1
+ import { loadUWebSockets, childAppOf, createRouteRunner, corsHeaderReader, createSharedRequestGate, registerServerRoutes, sendRelayed, createDocumentReadHandlers, respondJson, SERVER_CAPABILITIES, versionReport, statusLine, sendError, serverRoutes, parseJson, validateSuggest, rejectInvalid, respondError, validateQuery } from './chunk-NOZUQM7J.mjs';
2
+ import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-CTOSGRM7.mjs';
3
+ import { resolveProjection, projectionKeepsField, setNestedValue } from './chunk-VKX2BPXD.mjs';
4
+ import './chunk-3DDUNI6N.mjs';
6
5
  import './chunk-ZJ4CSAB2.mjs';
7
- import './chunk-P2ZZN32C.mjs';
6
+ import './chunk-2E35UM3G.mjs';
8
7
  import './chunk-ZKRV5M2P.mjs';
8
+ import './chunk-I23G3IZC.mjs';
9
+ import { extractVectorFieldsFromSchema } from './chunk-Z3DQPOJQ.mjs';
10
+ import './chunk-KEHK2O6P.mjs';
9
11
  import './chunk-LJYTW7GT.mjs';
10
- import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-RV77FR2F.mjs';
12
+ import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-I64LXKO3.mjs';
11
13
 
12
14
  // src/server/request-threads/held-vectors.ts
13
15
  function managerWithHeldVectors(manager, fieldPaths, vectorOf) {
@@ -173,6 +175,46 @@ function isRelayReply(message) {
173
175
  const { type, requestId } = message;
174
176
  return typeof requestId === "number" && (type === "response" || type === "authorized" || type === "embedded");
175
177
  }
178
+ function isStreamPart(message) {
179
+ if (typeof message !== "object" || message === null) return false;
180
+ const { type, requestId } = message;
181
+ return typeof requestId === "number" && (type === "stream-start" || type === "stream-chunk" || type === "stream-end");
182
+ }
183
+ function writeStreamPart(port, writer, part) {
184
+ const { ctx } = writer;
185
+ if (part.type === "stream-end") {
186
+ if (!ctx.abort.aborted) {
187
+ ctx.res.cork(() => {
188
+ if (part.failed) ctx.res.close();
189
+ else ctx.res.end();
190
+ });
191
+ }
192
+ writer.finish();
193
+ return;
194
+ }
195
+ if (ctx.abort.aborted) return;
196
+ if (part.type === "stream-start") {
197
+ writer.started = true;
198
+ ctx.res.cork(() => {
199
+ ctx.res.writeStatus(statusLine(part.status));
200
+ for (const [key, value] of part.headers) ctx.res.writeHeader(key, value);
201
+ });
202
+ return;
203
+ }
204
+ let writable = true;
205
+ ctx.res.cork(() => {
206
+ writable = ctx.res.write(part.chunk);
207
+ });
208
+ const credit = () => port.postMessage({ type: "stream-credit", requestId: part.requestId });
209
+ if (writable) {
210
+ credit();
211
+ return;
212
+ }
213
+ ctx.res.onWritable(() => {
214
+ credit();
215
+ return true;
216
+ });
217
+ }
176
218
  function errorOf(reply) {
177
219
  const error = reply.error ?? { code: ErrorCodes.EMBEDDING_FAILED, message: "The main thread returned no vector" };
178
220
  return new NarsilError(error.code, error.message, error.details);
@@ -205,11 +247,18 @@ function createRelayClient(port, touchIntervalMs) {
205
247
  pending.clear();
206
248
  for (const entry of waiting) entry.reject(portClosedError());
207
249
  }
250
+ const writers = /* @__PURE__ */ new Map();
208
251
  port.on("message", (message) => {
252
+ if (isStreamPart(message)) {
253
+ const writer = writers.get(message.requestId);
254
+ if (writer !== void 0) writeStreamPart(port, writer, message);
255
+ return;
256
+ }
209
257
  if (!isRelayReply(message)) return;
210
258
  const waiting = pending.get(message.requestId);
211
259
  if (waiting === void 0) return;
212
260
  pending.delete(message.requestId);
261
+ writers.delete(message.requestId);
213
262
  waiting.resolve(message);
214
263
  });
215
264
  port.on("close", failAll);
@@ -244,24 +293,36 @@ function createRelayClient(port, touchIntervalMs) {
244
293
  let reply;
245
294
  const body = relayedBody(ctx.rawBody);
246
295
  try {
247
- reply = await send(
248
- (requestId) => ({
249
- type: "request",
250
- requestId,
251
- route,
252
- params: ctx.params,
253
- query: ctx.query.toString(),
254
- contentType: ctx.contentType,
255
- rawBody: body.bytes,
256
- hookContext: ctx.hookContext
257
- }),
258
- body.moved
259
- );
296
+ reply = await new Promise((resolve, reject) => {
297
+ void send((requestId) => {
298
+ writers.set(requestId, {
299
+ ctx,
300
+ started: false,
301
+ finish: () => {
302
+ writers.delete(requestId);
303
+ pending.delete(requestId);
304
+ resolve(null);
305
+ }
306
+ });
307
+ ctx.abort.onAbort(() => port.postMessage({ type: "stream-cancel", requestId }));
308
+ return {
309
+ type: "request",
310
+ requestId,
311
+ route,
312
+ params: ctx.params,
313
+ query: ctx.query.toString(),
314
+ contentType: ctx.contentType,
315
+ rawBody: body.bytes,
316
+ spooledBody: ctx.spooledBody === null ? null : { path: ctx.spooledBody.path, bytes: ctx.spooledBody.bytes },
317
+ hookContext: ctx.hookContext
318
+ };
319
+ }, body.moved).then(resolve, reject);
320
+ });
260
321
  } catch {
261
322
  if (!ctx.abort.aborted) sendUnavailable(ctx.res);
262
323
  return;
263
324
  }
264
- if (reply.type !== "response" || ctx.abort.aborted) return;
325
+ if (reply === null || reply.type !== "response" || ctx.abort.aborted) return;
265
326
  sendRelayed(ctx.res, ctx.abort, reply.status, reply.headers, reply.body);
266
327
  },
267
328
  async embed(indexName, vector) {
@@ -1,4 +1,4 @@
1
- import { T as TransportConfig } from './types-D5OwhtsQ.js';
1
+ import { T as TransportConfig } from './types-Cb9kiY5P.js';
2
2
 
3
3
  /**
4
4
  * The certificate material one node presents to its peers.
@@ -31,7 +31,7 @@ interface TransportConfig {
31
31
  requestTimeout: number;
32
32
  /** A replication entry may take this long to reach a replica and come back acknowledged. */
33
33
  replicationTimeout: number;
34
- /** A whole snapshot transfer, which moves a partition between nodes, may take this long. */
34
+ /** A node waits this long for each next chunk of a snapshot transfer, which moves a partition or an index between nodes, so that the node can receive a transfer of any size while the sender keeps sending. */
35
35
  snapshotTimeout: number;
36
36
  }
37
37
  /**
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './schema-BBOCdhND.js';
1
+ import { S as SchemaDefinition } from './schema-DgyDijhf.js';
2
2
 
3
3
  /**
4
4
  * What a node does in a cluster. A node may hold several roles at once.
@@ -1,4 +1,4 @@
1
- import { e as VectorQuantizationMode } from '../schema-BBOCdhND.js';
1
+ import { e as VectorQuantizationMode } from '../schema-DgyDijhf.js';
2
2
  import { A as ArenaSimd } from '../simd-DW5AtJ6B.js';
3
3
  import '../adapters-79OIa4xm.js';
4
4
 
@@ -1,8 +1,9 @@
1
- import { openSharedVectorField, MAX_WORKER_COPIES, createVectorStore, osqBitsOf, createHNSWIndex, createOsqQuantizer, osqRecordBytes } from '../chunk-LCCL6EEH.mjs';
1
+ import { openSharedVectorField, MAX_WORKER_COPIES, createVectorStore, osqBitsOf, createHNSWIndex, createOsqQuantizer, osqRecordBytes } from '../chunk-PO53FV4Q.mjs';
2
2
  import '../chunk-ZJ4CSAB2.mjs';
3
- import '../chunk-P2ZZN32C.mjs';
3
+ import '../chunk-2E35UM3G.mjs';
4
+ import '../chunk-KEHK2O6P.mjs';
4
5
  import '../chunk-LJYTW7GT.mjs';
5
- import '../chunk-RV77FR2F.mjs';
6
+ import '../chunk-I64LXKO3.mjs';
6
7
 
7
8
  // src/vector/worker-copy.ts
8
9
  function restoreQuantizer(snapshot, store) {
@@ -1,6 +1,6 @@
1
1
  import { startIdleHeapCleanup } from '../chunk-Y3L5MDRV.mjs';
2
2
  import { threadSlotOfWorker, isValidWorkerAction } from '../chunk-POWOTUKJ.mjs';
3
- import { ErrorCodes, NarsilError, isNarsilError } from '../chunk-RV77FR2F.mjs';
3
+ import { ErrorCodes, NarsilError, isNarsilError } from '../chunk-I64LXKO3.mjs';
4
4
 
5
5
  // src/workers/action-handler.ts
6
6
  function buildErrorResponse(requestId, code, message, details) {
@@ -18,7 +18,7 @@ async function importBootstrapModule(moduleUrl) {
18
18
  await import(moduleUrl);
19
19
  }
20
20
  async function stopServingRequests() {
21
- const { closeRequestThread } = await import('../thread-IBSIZZ6W.mjs');
21
+ const { closeRequestThread } = await import('../thread-W6DPKEF6.mjs');
22
22
  closeRequestThread();
23
23
  }
24
24
  function holdsCopies(executor) {
@@ -49,7 +49,7 @@ function createActionHandler(executor) {
49
49
  if (!holdsCopies(executor)) {
50
50
  throw new NarsilError(ErrorCodes.CONFIG_INVALID, "This executor holds no copies to answer requests from");
51
51
  }
52
- const starting = import('../thread-IBSIZZ6W.mjs').then(
52
+ const starting = import('../thread-W6DPKEF6.mjs').then(
53
53
  ({ serveRequests }) => serveRequests(executor, action.settings)
54
54
  );
55
55
  serving.start = starting.then(
@@ -102,7 +102,7 @@ async function setup() {
102
102
  } catch {
103
103
  parentPort = null;
104
104
  }
105
- const { createDirectExecutor } = await import('../direct-executor-SVCHXR74.mjs');
105
+ const { createDirectExecutor } = await import('../direct-executor-KFAGILJC.mjs');
106
106
  const handleAction = createActionHandler(createDirectExecutor({ threadSlot }));
107
107
  if (parentPort) {
108
108
  const port = parentPort;