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

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 (59) 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-CYeAWYxQ.d.ts} +19 -4
  5. package/dist/{chunk-Y52WZWAU.mjs → chunk-2WLD2NFW.mjs} +4 -4
  6. package/dist/{chunk-QR4OKRVY.mjs → chunk-3YKWSO54.mjs} +8 -3
  7. package/dist/{chunk-KZTI25D3.mjs → chunk-4MQ5F4IL.mjs} +1 -1
  8. package/dist/{chunk-CVPA5Q7R.mjs → chunk-AFKH4ZK3.mjs} +9 -6
  9. package/dist/{chunk-M6XF5HDE.mjs → chunk-AK3BLGCI.mjs} +141 -11
  10. package/dist/{chunk-7QMTA7AA.mjs → chunk-CVFAFZSN.mjs} +7771 -4271
  11. package/dist/{chunk-RV77FR2F.mjs → chunk-I64LXKO3.mjs} +0 -1
  12. package/dist/{chunk-EDMCZLSJ.mjs → chunk-LPBWCHBE.mjs} +193 -13
  13. package/dist/{chunk-P2ZZN32C.mjs → chunk-PU43WDY4.mjs} +4 -2
  14. package/dist/{chunk-HVJYTKK7.mjs → chunk-THMSFOJO.mjs} +365 -774
  15. package/dist/{chunk-LCCL6EEH.mjs → chunk-WVDFG45M.mjs} +92 -94
  16. package/dist/{chunk-3HQPUCCT.mjs → chunk-XW25X6AU.mjs} +66 -237
  17. package/dist/{chunk-2X2ZQOAB.mjs → chunk-ZULSEZCE.mjs} +3 -4
  18. package/dist/{client-C2TiW29y.d.ts → client-D698E5ge.d.ts} +76 -25
  19. package/dist/client.d.ts +7 -7
  20. package/dist/client.mjs +44 -22
  21. package/dist/{config-BLguB-om.d.ts → config-Bv8etwUZ.d.ts} +10 -3
  22. package/dist/{config-C7Ip8Ife.d.ts → config-QXU3HMpq.d.ts} +10 -1
  23. package/dist/direct-executor-5SRDFZAS.mjs +13 -0
  24. package/dist/distribution/coordinator/etcd.d.ts +27 -14
  25. package/dist/distribution/coordinator/etcd.mjs +162 -75
  26. package/dist/distribution/transport/grpc.d.ts +2 -2
  27. package/dist/distribution/transport/grpc.mjs +22 -2
  28. package/dist/distribution/transport/in-memory.d.ts +2 -2
  29. package/dist/distribution/transport/in-memory.mjs +6 -3
  30. package/dist/distribution/transport/tcp.d.ts +2 -2
  31. package/dist/distribution/transport/tcp.mjs +40 -16
  32. package/dist/distribution.d.ts +6 -6
  33. package/dist/distribution.mjs +637 -366
  34. package/dist/embeddings/openai.mjs +1 -1
  35. package/dist/{engine-Dm4446j1.d.ts → engine-D_OzKZ-6.d.ts} +96 -187
  36. package/dist/index.browser.mjs +7179 -4031
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.mjs +11 -14
  39. package/dist/{memory-CKHTU1CL.d.ts → memory-B4jMmygw.d.ts} +213 -2
  40. package/dist/pattern.mjs +1 -1
  41. package/dist/persistence/durability/checkpoint-worker.d.ts +24 -16
  42. package/dist/persistence/durability/checkpoint-worker.mjs +22 -16
  43. package/dist/react.d.ts +6 -6
  44. package/dist/react.mjs +0 -1
  45. package/dist/server.d.ts +9 -9
  46. package/dist/server.mjs +210 -32
  47. package/dist/{tasks-BECd0b__.d.ts → tasks-DpgCpW1x.d.ts} +3 -3
  48. package/dist/{thread-IBSIZZ6W.mjs → thread-3LNVA43V.mjs} +79 -21
  49. package/dist/{types-BQDlIvfS.d.ts → types-B3b68nfF.d.ts} +1 -1
  50. package/dist/{types-D5OwhtsQ.d.ts → types-Cb9kiY5P.d.ts} +1 -1
  51. package/dist/vector/search-worker.mjs +3 -3
  52. package/dist/workers/entry.mjs +4 -4
  53. package/package.json +7 -7
  54. package/dist/chunk-3L4U6EJQ.mjs +0 -49
  55. package/dist/chunk-IFBKLBGB.mjs +0 -7
  56. package/dist/chunk-XZ4PAREY.mjs +0 -25
  57. package/dist/direct-executor-SVCHXR74.mjs +0 -14
  58. package/dist/serialization/crc32-worker.d.ts +0 -20
  59. package/dist/serialization/crc32-worker.mjs +0 -62
package/dist/server.mjs CHANGED
@@ -1,21 +1,22 @@
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-LPBWCHBE.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-LPBWCHBE.mjs';
3
+ import { validateIndexName } from './chunk-2WLD2NFW.mjs';
4
+ import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-AFKH4ZK3.mjs';
5
+ import { detectRuntime } from './chunk-4MQ5F4IL.mjs';
6
+ import './chunk-AK3BLGCI.mjs';
8
7
  import './chunk-TGP5M5R7.mjs';
9
- import './chunk-P2ZZN32C.mjs';
8
+ import './chunk-PU43WDY4.mjs';
10
9
  import './chunk-U2BKYEC7.mjs';
11
10
  import './chunk-LJYTW7GT.mjs';
12
11
  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';
12
+ import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-I64LXKO3.mjs';
13
+ export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-I64LXKO3.mjs';
15
14
  import { randomUUID } from 'crypto';
15
+ import { createReadStream } from 'fs';
16
+ import { rm } from 'fs/promises';
17
+ import path from 'path';
16
18
  import { MessageChannel } from 'worker_threads';
17
19
 
18
- // src/server/handlers/admin.ts
19
20
  function createAdminHandlers(deps) {
20
21
  const { engine, tasks, limits } = deps;
21
22
  async function checkpoint(ctx) {
@@ -28,9 +29,12 @@ function createAdminHandlers(deps) {
28
29
  }
29
30
  async function snapshot(ctx) {
30
31
  try {
31
- const bytes = await engine.snapshot(ctx.params[0]);
32
- if (ctx.abort.aborted) return;
33
- sendBinary(ctx.res, bytes, ctx.abort);
32
+ const archive = await engine.snapshot(ctx.params[0]);
33
+ if (ctx.abort.aborted) {
34
+ await archive.cancel();
35
+ return;
36
+ }
37
+ await sendStream(ctx.res, ctx.abort, archive, [["Content-Type", SNAPSHOT_ARCHIVE_CONTENT_TYPE]]);
34
38
  } catch (err) {
35
39
  respondError(ctx, err);
36
40
  }
@@ -118,15 +122,26 @@ function createAdminHandlers(deps) {
118
122
  respondJson(ctx, record, 202);
119
123
  }
120
124
  async function restore(ctx) {
121
- const raw = ctx.rawBody;
122
- if (!raw || raw.length === 0) {
125
+ const spooled = ctx.spooledBody;
126
+ if (spooled === null || spooled.bytes === 0) {
127
+ await spooled?.remove();
123
128
  sendError(ctx.res, 400, ServerErrorCodes.EMPTY_BODY, "Request body is empty");
124
129
  return;
125
130
  }
126
131
  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);
132
+ try {
133
+ const record = await tasks.start("restore", name, async () => {
134
+ try {
135
+ await engine.restore(name, createReadStream(spooled.path));
136
+ } finally {
137
+ await spooled.remove();
138
+ }
139
+ });
140
+ respondJson(ctx, record, 202);
141
+ } catch (err) {
142
+ await spooled.remove();
143
+ respondError(ctx, err);
144
+ }
130
145
  }
131
146
  async function getTask(ctx) {
132
147
  const record = await tasks.get(ctx.params[0]);
@@ -270,6 +285,54 @@ function createClusterHandlers(deps) {
270
285
  }
271
286
  return { topology, allocation };
272
287
  }
288
+ function importResultOf(result, maxErrors) {
289
+ const errors = result.refused.slice(0, maxErrors);
290
+ return {
291
+ indexed: result.imported,
292
+ failed: result.refusedCount,
293
+ errors,
294
+ errorsTruncated: result.refusedCount > errors.length
295
+ };
296
+ }
297
+ function createDocumentExportHandlers(deps) {
298
+ const { engine, tasks, limits } = deps;
299
+ async function exportDocuments(ctx) {
300
+ try {
301
+ const lines = await engine.exportDocuments(ctx.params[0]);
302
+ if (ctx.abort.aborted) {
303
+ await lines.cancel();
304
+ return;
305
+ }
306
+ await sendStream(ctx.res, ctx.abort, lines, [["Content-Type", DOCUMENT_EXPORT_CONTENT_TYPE]]);
307
+ } catch (err) {
308
+ respondError(ctx, err);
309
+ }
310
+ }
311
+ async function importExport(ctx) {
312
+ const spooled = ctx.spooledBody;
313
+ if (spooled === null || spooled.bytes === 0) {
314
+ await spooled?.remove();
315
+ sendError(ctx.res, 400, ServerErrorCodes.EMPTY_BODY, "Request body is empty");
316
+ return;
317
+ }
318
+ const name = ctx.params[0];
319
+ try {
320
+ const record = await tasks.start("importExport", name, async (context) => {
321
+ try {
322
+ const result = await engine.importDocumentExport(name, createReadStream(spooled.path));
323
+ context.reportResult(importResultOf(result, limits.maxImportErrors));
324
+ } finally {
325
+ await spooled.remove();
326
+ }
327
+ });
328
+ respondJson(ctx, record, 202);
329
+ } catch (err) {
330
+ await spooled.remove();
331
+ respondError(ctx, err);
332
+ }
333
+ }
334
+ return { exportDocuments, importExport };
335
+ }
273
336
 
274
337
  // src/server/handlers/documents.ts
275
338
  function createDocumentHandlers(deps) {
@@ -757,6 +820,7 @@ var ENGINE_METHOD_SAMPLE = ["createIndex", "listIndexes", "insert", "query", "ge
757
820
  var LIMITS_UNBOUNDED_AT_ZERO = [
758
821
  "maxBodyBytes",
759
822
  "maxImportBytes",
823
+ "maxRestoreBytes",
760
824
  "maxLineBytes",
761
825
  "maxImportErrors",
762
826
  "maxConcurrentRequests",
@@ -808,10 +872,32 @@ function bodyBytes(body) {
808
872
  if (typeof body === "string") return Buffer.from(body, "utf8");
809
873
  return body;
810
874
  }
811
- function createCapturingResponse(remoteAddress) {
875
+ function createRelayAbort() {
876
+ const listeners = [];
877
+ let aborted = false;
878
+ return {
879
+ get aborted() {
880
+ return aborted;
881
+ },
882
+ onAbort(fn) {
883
+ if (aborted) fn();
884
+ else listeners.push(fn);
885
+ },
886
+ trigger() {
887
+ if (aborted) return;
888
+ aborted = true;
889
+ for (const fn of listeners) fn();
890
+ }
891
+ };
892
+ }
893
+ function createCapturingResponse(remoteAddress, requestId, sendPart) {
812
894
  let status = DEFAULT_STATUS;
813
895
  const headers = [];
814
896
  let finished = false;
897
+ let streaming = false;
898
+ let inFlight = 0;
899
+ let writable = null;
900
+ const abort = createRelayAbort();
815
901
  let resolveDone = () => void 0;
816
902
  const done = new Promise((resolve) => {
817
903
  resolveDone = resolve;
@@ -819,25 +905,57 @@ function createCapturingResponse(remoteAddress) {
819
905
  function finish(body) {
820
906
  if (finished) return;
821
907
  finished = true;
908
+ if (streaming) {
909
+ sendPart({ type: "stream-end", requestId, failed: false }, []);
910
+ resolveDone(null);
911
+ return;
912
+ }
822
913
  resolveDone({ status, headers, body });
823
914
  }
915
+ function startStreaming() {
916
+ if (streaming) return;
917
+ streaming = true;
918
+ sendPart({ type: "stream-start", requestId, status, headers }, []);
919
+ }
824
920
  const sink = {
825
921
  cork(callback) {
826
922
  callback();
827
923
  return sink;
828
924
  },
829
925
  writeStatus(line) {
830
- if (!finished) status = statusOf(line);
926
+ if (!finished && !streaming) status = statusOf(line);
831
927
  return sink;
832
928
  },
833
929
  writeHeader(key, value) {
834
- if (!finished) headers.push([key, value]);
930
+ if (!finished && !streaming) headers.push([key, value]);
835
931
  return sink;
836
932
  },
837
933
  end(body) {
838
934
  finish(bodyBytes(body));
839
935
  return sink;
840
936
  },
937
+ write(chunk) {
938
+ if (finished || abort.aborted) return true;
939
+ startStreaming();
940
+ const copy = chunk.slice();
941
+ inFlight += 1;
942
+ sendPart({ type: "stream-chunk", requestId, chunk: copy }, [copy.buffer]);
943
+ return inFlight < RELAY_STREAM_CHUNKS_IN_FLIGHT;
944
+ },
945
+ pause: () => void 0,
946
+ resume: () => void 0,
947
+ close() {
948
+ if (finished) return sink;
949
+ finished = true;
950
+ if (streaming) {
951
+ sendPart({ type: "stream-end", requestId, failed: true }, []);
952
+ resolveDone(null);
953
+ } else {
954
+ status = NO_RESPONSE_STATUS;
955
+ resolveDone({ status, headers, body: new Uint8Array(0) });
956
+ }
957
+ return sink;
958
+ },
841
959
  endWithoutBody() {
842
960
  finish(new Uint8Array(0));
843
961
  return sink;
@@ -847,16 +965,45 @@ function createCapturingResponse(remoteAddress) {
847
965
  return [true, true];
848
966
  },
849
967
  getWriteOffset: () => 0,
850
- onWritable: () => sink,
968
+ onWritable(handler) {
969
+ writable = handler;
970
+ return sink;
971
+ },
851
972
  onAborted: () => sink,
852
973
  onData: () => sink,
853
974
  getRemoteAddressAsText: () => new TextEncoder().encode(remoteAddress).buffer
854
975
  };
855
976
  return {
856
977
  sink,
978
+ abort,
857
979
  done,
980
+ credit() {
981
+ inFlight = Math.max(0, inFlight - 1);
982
+ if (inFlight >= RELAY_STREAM_CHUNKS_IN_FLIGHT || writable === null) return;
983
+ const handler = writable;
984
+ writable = null;
985
+ handler(0);
986
+ },
987
+ cancel() {
988
+ abort.trigger();
989
+ if (writable !== null) {
990
+ const handler = writable;
991
+ writable = null;
992
+ handler(0);
993
+ }
994
+ if (!finished) {
995
+ finished = true;
996
+ resolveDone(null);
997
+ }
998
+ },
858
999
  finishUnanswered() {
859
1000
  if (finished) return;
1001
+ if (streaming) {
1002
+ finished = true;
1003
+ sendPart({ type: "stream-end", requestId, failed: true }, []);
1004
+ resolveDone(null);
1005
+ return;
1006
+ }
860
1007
  status = NO_RESPONSE_STATUS;
861
1008
  finish(new Uint8Array(0));
862
1009
  }
@@ -867,22 +1014,36 @@ function toRelayedResponse(requestId, captured) {
867
1014
  }
868
1015
 
869
1016
  // src/server/request-threads/host.ts
870
- var NO_ABORT = { aborted: false, onAbort: () => void 0 };
1017
+ var RELAYED_TYPES = /* @__PURE__ */ new Set(["request", "authorize", "embed", "stream-credit", "stream-cancel"]);
871
1018
  function isRelayRequest(message) {
872
1019
  if (typeof message !== "object" || message === null) return false;
873
1020
  const { type, requestId } = message;
874
1021
  if (type === "touch") return true;
875
- return typeof requestId === "number" && (type === "request" || type === "authorize" || type === "embed");
1022
+ return typeof requestId === "number" && typeof type === "string" && RELAYED_TYPES.has(type);
876
1023
  }
877
- async function answerRequest(handlers, request) {
878
- const captured = createCapturingResponse(request.hookContext?.remoteAddress ?? "");
1024
+ function spooledBodyOf(relayed) {
1025
+ if (relayed === null) return null;
1026
+ return {
1027
+ path: relayed.path,
1028
+ bytes: relayed.bytes,
1029
+ remove: () => rm(path.dirname(relayed.path), { recursive: true, force: true })
1030
+ };
1031
+ }
1032
+ async function answerRequest(handlers, request, port, streams) {
1033
+ const captured = createCapturingResponse(
1034
+ request.hookContext?.remoteAddress ?? "",
1035
+ request.requestId,
1036
+ (part, moved) => port.postMessage(part, moved)
1037
+ );
1038
+ streams.set(request.requestId, captured);
879
1039
  const ctx = {
880
1040
  res: captured.sink,
881
1041
  params: request.params,
882
1042
  query: new URLSearchParams(request.query),
883
1043
  contentType: request.contentType,
884
1044
  rawBody: request.rawBody === null ? null : Buffer.from(request.rawBody.buffer, request.rawBody.byteOffset, request.rawBody.byteLength),
885
- abort: NO_ABORT,
1045
+ spooledBody: spooledBodyOf(request.spooledBody),
1046
+ abort: captured.abort,
886
1047
  hookContext: request.hookContext
887
1048
  };
888
1049
  try {
@@ -891,7 +1052,9 @@ async function answerRequest(handlers, request) {
891
1052
  sendError(ctx.res, 500, ServerErrorCodes.INTERNAL_ERROR, "An unexpected error occurred");
892
1053
  }
893
1054
  captured.finishUnanswered();
894
- return toRelayedResponse(request.requestId, await captured.done);
1055
+ const answered = await captured.done;
1056
+ streams.delete(request.requestId);
1057
+ return answered === null ? null : toRelayedResponse(request.requestId, answered);
895
1058
  }
896
1059
  async function answerAuthorize(authorize, request) {
897
1060
  if (authorize === void 0) {
@@ -942,14 +1105,25 @@ async function startRequestThreads(options) {
942
1105
  const threads = /* @__PURE__ */ new Map();
943
1106
  let stopped = false;
944
1107
  function relayFrom(port) {
1108
+ const streams = /* @__PURE__ */ new Map();
945
1109
  port.on("message", (message) => {
946
1110
  if (!isRelayRequest(message)) return;
947
1111
  if (message.type === "touch") {
948
1112
  core.orchestrator.noteAccess(message.indexName);
949
1113
  return;
950
1114
  }
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));
1115
+ if (message.type === "stream-credit") {
1116
+ streams.get(message.requestId)?.credit();
1117
+ return;
1118
+ }
1119
+ if (message.type === "stream-cancel") {
1120
+ streams.get(message.requestId)?.cancel();
1121
+ return;
1122
+ }
1123
+ const reply = message.type === "request" ? answerRequest(handlers, message, port, streams) : message.type === "authorize" ? answerAuthorize(authorize, message) : answerEmbed(core, message);
1124
+ void reply.then((answer) => {
1125
+ if (answer !== null) port.postMessage(answer);
1126
+ });
953
1127
  });
954
1128
  }
955
1129
  async function stopThread(workerId, thread) {
@@ -1254,6 +1428,7 @@ function resolveLimits(limits) {
1254
1428
  return {
1255
1429
  maxBodyBytes: limits?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
1256
1430
  maxImportBytes: limits?.maxImportBytes ?? DEFAULT_MAX_IMPORT_BYTES,
1431
+ maxRestoreBytes: limits?.maxRestoreBytes ?? DEFAULT_MAX_RESTORE_BYTES,
1257
1432
  maxLineBytes: limits?.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES,
1258
1433
  importBatchSize: limits?.importBatchSize ?? DEFAULT_IMPORT_BATCH_SIZE,
1259
1434
  maxConcurrentRequests: limits?.maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS,
@@ -1287,6 +1462,7 @@ function mainThreadHandlers(deps) {
1287
1462
  const doc = createDocumentHandlers(deps);
1288
1463
  const search = createSearchHandlers(deps);
1289
1464
  const admin = createAdminHandlers(deps);
1465
+ const documentExport = createDocumentExportHandlers(deps);
1290
1466
  const health = createHealthHandlers(deps);
1291
1467
  const cluster = createClusterHandlers(deps);
1292
1468
  const version = createVersionHandler(deps);
@@ -1333,7 +1509,9 @@ function mainThreadHandlers(deps) {
1333
1509
  optimize: admin.optimize,
1334
1510
  rebalance: admin.rebalance,
1335
1511
  partitionConfig: admin.partitionConfig,
1336
- restore: admin.restore
1512
+ restore: admin.restore,
1513
+ exportDocuments: documentExport.exportDocuments,
1514
+ importExport: documentExport.importExport
1337
1515
  };
1338
1516
  }
1339
1517
  var NarsilHttpServer = class {
@@ -1,4 +1,4 @@
1
- import { M as MemoryStats } from './memory-CKHTU1CL.js';
1
+ import { M as MemoryStats } from './memory-B4jMmygw.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,12 @@
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-LPBWCHBE.mjs';
2
+ import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-ZULSEZCE.mjs';
3
+ import { resolveProjection, projectionKeepsField, setNestedValue } from './chunk-3YKWSO54.mjs';
4
+ import { extractVectorFieldsFromSchema } from './chunk-AK3BLGCI.mjs';
6
5
  import './chunk-ZJ4CSAB2.mjs';
7
- import './chunk-P2ZZN32C.mjs';
6
+ import './chunk-PU43WDY4.mjs';
8
7
  import './chunk-ZKRV5M2P.mjs';
9
8
  import './chunk-LJYTW7GT.mjs';
10
- import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-RV77FR2F.mjs';
9
+ import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-I64LXKO3.mjs';
11
10
 
12
11
  // src/server/request-threads/held-vectors.ts
13
12
  function managerWithHeldVectors(manager, fieldPaths, vectorOf) {
@@ -173,6 +172,46 @@ function isRelayReply(message) {
173
172
  const { type, requestId } = message;
174
173
  return typeof requestId === "number" && (type === "response" || type === "authorized" || type === "embedded");
175
174
  }
175
+ function isStreamPart(message) {
176
+ if (typeof message !== "object" || message === null) return false;
177
+ const { type, requestId } = message;
178
+ return typeof requestId === "number" && (type === "stream-start" || type === "stream-chunk" || type === "stream-end");
179
+ }
180
+ function writeStreamPart(port, writer, part) {
181
+ const { ctx } = writer;
182
+ if (part.type === "stream-end") {
183
+ if (!ctx.abort.aborted) {
184
+ ctx.res.cork(() => {
185
+ if (part.failed) ctx.res.close();
186
+ else ctx.res.end();
187
+ });
188
+ }
189
+ writer.finish();
190
+ return;
191
+ }
192
+ if (ctx.abort.aborted) return;
193
+ if (part.type === "stream-start") {
194
+ writer.started = true;
195
+ ctx.res.cork(() => {
196
+ ctx.res.writeStatus(statusLine(part.status));
197
+ for (const [key, value] of part.headers) ctx.res.writeHeader(key, value);
198
+ });
199
+ return;
200
+ }
201
+ let writable = true;
202
+ ctx.res.cork(() => {
203
+ writable = ctx.res.write(part.chunk);
204
+ });
205
+ const credit = () => port.postMessage({ type: "stream-credit", requestId: part.requestId });
206
+ if (writable) {
207
+ credit();
208
+ return;
209
+ }
210
+ ctx.res.onWritable(() => {
211
+ credit();
212
+ return true;
213
+ });
214
+ }
176
215
  function errorOf(reply) {
177
216
  const error = reply.error ?? { code: ErrorCodes.EMBEDDING_FAILED, message: "The main thread returned no vector" };
178
217
  return new NarsilError(error.code, error.message, error.details);
@@ -205,11 +244,18 @@ function createRelayClient(port, touchIntervalMs) {
205
244
  pending.clear();
206
245
  for (const entry of waiting) entry.reject(portClosedError());
207
246
  }
247
+ const writers = /* @__PURE__ */ new Map();
208
248
  port.on("message", (message) => {
249
+ if (isStreamPart(message)) {
250
+ const writer = writers.get(message.requestId);
251
+ if (writer !== void 0) writeStreamPart(port, writer, message);
252
+ return;
253
+ }
209
254
  if (!isRelayReply(message)) return;
210
255
  const waiting = pending.get(message.requestId);
211
256
  if (waiting === void 0) return;
212
257
  pending.delete(message.requestId);
258
+ writers.delete(message.requestId);
213
259
  waiting.resolve(message);
214
260
  });
215
261
  port.on("close", failAll);
@@ -244,24 +290,36 @@ function createRelayClient(port, touchIntervalMs) {
244
290
  let reply;
245
291
  const body = relayedBody(ctx.rawBody);
246
292
  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
- );
293
+ reply = await new Promise((resolve, reject) => {
294
+ void send((requestId) => {
295
+ writers.set(requestId, {
296
+ ctx,
297
+ started: false,
298
+ finish: () => {
299
+ writers.delete(requestId);
300
+ pending.delete(requestId);
301
+ resolve(null);
302
+ }
303
+ });
304
+ ctx.abort.onAbort(() => port.postMessage({ type: "stream-cancel", requestId }));
305
+ return {
306
+ type: "request",
307
+ requestId,
308
+ route,
309
+ params: ctx.params,
310
+ query: ctx.query.toString(),
311
+ contentType: ctx.contentType,
312
+ rawBody: body.bytes,
313
+ spooledBody: ctx.spooledBody === null ? null : { path: ctx.spooledBody.path, bytes: ctx.spooledBody.bytes },
314
+ hookContext: ctx.hookContext
315
+ };
316
+ }, body.moved).then(resolve, reject);
317
+ });
260
318
  } catch {
261
319
  if (!ctx.abort.aborted) sendUnavailable(ctx.res);
262
320
  return;
263
321
  }
264
- if (reply.type !== "response" || ctx.abort.aborted) return;
322
+ if (reply === null || reply.type !== "response" || ctx.abort.aborted) return;
265
323
  sendRelayed(ctx.res, ctx.abort, reply.status, reply.headers, reply.body);
266
324
  },
267
325
  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,8 +1,8 @@
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-WVDFG45M.mjs';
2
2
  import '../chunk-ZJ4CSAB2.mjs';
3
- import '../chunk-P2ZZN32C.mjs';
3
+ import '../chunk-PU43WDY4.mjs';
4
4
  import '../chunk-LJYTW7GT.mjs';
5
- import '../chunk-RV77FR2F.mjs';
5
+ import '../chunk-I64LXKO3.mjs';
6
6
 
7
7
  // src/vector/worker-copy.ts
8
8
  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-3LNVA43V.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-3LNVA43V.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-5SRDFZAS.mjs');
106
106
  const handleAction = createActionHandler(createDirectExecutor({ threadSlot }));
107
107
  if (parentPort) {
108
108
  const port = parentPort;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delali/narsil",
3
- "version": "0.3.1-next.72",
3
+ "version": "0.3.1-next.73",
4
4
  "type": "module",
5
5
  "description": "Distributed search engine with full-text, vector, hybrid, and geosearch. One engine, embedded or on a server, with a cross-language .nrsl format.",
6
6
  "author": "Delali (https://sondelali.com)",
@@ -231,11 +231,11 @@
231
231
  }
232
232
  },
233
233
  "optionalDependencies": {
234
- "@delali/narsil-native-darwin-arm64": "0.3.1-next.72",
235
- "@delali/narsil-native-darwin-x64": "0.3.1-next.72",
236
- "@delali/narsil-native-linux-arm64": "0.3.1-next.72",
237
- "@delali/narsil-native-linux-x64": "0.3.1-next.72",
238
- "@delali/narsil-native-win32-arm64": "0.3.1-next.72",
239
- "@delali/narsil-native-win32-x64": "0.3.1-next.72"
234
+ "@delali/narsil-native-darwin-arm64": "0.3.1-next.73",
235
+ "@delali/narsil-native-darwin-x64": "0.3.1-next.73",
236
+ "@delali/narsil-native-linux-arm64": "0.3.1-next.73",
237
+ "@delali/narsil-native-linux-x64": "0.3.1-next.73",
238
+ "@delali/narsil-native-win32-arm64": "0.3.1-next.73",
239
+ "@delali/narsil-native-win32-x64": "0.3.1-next.73"
240
240
  }
241
241
  }