@delali/narsil 0.3.1-next.71 → 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 (69) 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-Dt-7B4Wz.d.ts → capabilities-CYeAWYxQ.d.ts} +19 -4
  5. package/dist/{chunk-3JGWULDF.mjs → chunk-2WLD2NFW.mjs} +5 -5
  6. package/dist/{chunk-LMO5TJUS.mjs → chunk-3YKWSO54.mjs} +642 -196
  7. package/dist/{chunk-HWQLTLNG.mjs → chunk-4MQ5F4IL.mjs} +1 -1
  8. package/dist/{chunk-FVLQ63B7.mjs → chunk-AFKH4ZK3.mjs} +30 -6
  9. package/dist/{chunk-653BNMAF.mjs → chunk-AK3BLGCI.mjs} +195 -25
  10. package/dist/{chunk-IOR2K26O.mjs → chunk-CVFAFZSN.mjs} +8000 -4391
  11. package/dist/{chunk-USKFU7J5.mjs → chunk-I64LXKO3.mjs} +1 -1
  12. package/dist/{chunk-OI42GWLR.mjs → chunk-LJYTW7GT.mjs} +57 -11
  13. package/dist/{chunk-2STVUTNA.mjs → chunk-LPBWCHBE.mjs} +194 -13
  14. package/dist/{chunk-YNVIUGF5.mjs → chunk-PU43WDY4.mjs} +4 -2
  15. package/dist/{chunk-M76V3OGJ.mjs → chunk-THMSFOJO.mjs} +1289 -1405
  16. package/dist/{chunk-YJDQ7NLA.mjs → chunk-WVDFG45M.mjs} +93 -95
  17. package/dist/chunk-XW25X6AU.mjs +614 -0
  18. package/dist/chunk-ZKRV5M2P.mjs +75 -0
  19. package/dist/{chunk-P4DSQ6M4.mjs → chunk-ZULSEZCE.mjs} +68 -28
  20. package/dist/{client-COek5GXK.d.ts → client-D698E5ge.d.ts} +77 -26
  21. package/dist/client.d.ts +9 -9
  22. package/dist/client.mjs +45 -22
  23. package/dist/{config-rBpbgTw8.d.ts → config-Bv8etwUZ.d.ts} +11 -4
  24. package/dist/{config-Dfanyqqt.d.ts → config-QXU3HMpq.d.ts} +26 -2
  25. package/dist/direct-executor-5SRDFZAS.mjs +13 -0
  26. package/dist/distribution/coordinator/etcd.d.ts +29 -16
  27. package/dist/distribution/coordinator/etcd.mjs +163 -76
  28. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  29. package/dist/distribution/coordinator/in-memory.mjs +1 -1
  30. package/dist/distribution/transport/grpc.d.ts +2 -2
  31. package/dist/distribution/transport/grpc.mjs +22 -2
  32. package/dist/distribution/transport/in-memory.d.ts +2 -2
  33. package/dist/distribution/transport/in-memory.mjs +6 -3
  34. package/dist/distribution/transport/tcp.d.ts +2 -2
  35. package/dist/distribution/transport/tcp.mjs +40 -16
  36. package/dist/distribution.d.ts +9 -9
  37. package/dist/distribution.mjs +682 -388
  38. package/dist/embeddings/openai.mjs +1 -1
  39. package/dist/{engine-igk0aMpQ.d.ts → engine-D_OzKZ-6.d.ts} +97 -188
  40. package/dist/index.browser.mjs +11671 -7427
  41. package/dist/index.d.ts +27 -8
  42. package/dist/index.mjs +13 -14
  43. package/dist/invalidation/filesystem.mjs +1 -1
  44. package/dist/{memory-C7UMCMH3.d.ts → memory-B4jMmygw.d.ts} +279 -27
  45. package/dist/pattern-Cledccbl.d.ts +18 -0
  46. package/dist/pattern.d.ts +22 -0
  47. package/dist/pattern.mjs +7 -0
  48. package/dist/persistence/durability/checkpoint-worker.d.ts +41 -17
  49. package/dist/persistence/durability/checkpoint-worker.mjs +24 -16
  50. package/dist/react.d.ts +7 -7
  51. package/dist/react.mjs +1 -1
  52. package/dist/{schema-Bgzr4g7b.d.ts → schema-BBOCdhND.d.ts} +9 -7
  53. package/dist/server.d.ts +11 -11
  54. package/dist/server.mjs +213 -34
  55. package/dist/{tasks-BZt_zes6.d.ts → tasks-DpgCpW1x.d.ts} +3 -3
  56. package/dist/{thread-JEVAYETR.mjs → thread-3LNVA43V.mjs} +94 -27
  57. package/dist/{types-BQDlIvfS.d.ts → types-B3b68nfF.d.ts} +1 -1
  58. package/dist/{types-gOUqhs2F.d.ts → types-BiUmaiWT.d.ts} +1 -1
  59. package/dist/{types-D5OwhtsQ.d.ts → types-Cb9kiY5P.d.ts} +1 -1
  60. package/dist/vector/search-worker.d.ts +1 -1
  61. package/dist/vector/search-worker.mjs +4 -4
  62. package/dist/workers/entry.mjs +4 -4
  63. package/package.json +15 -7
  64. package/dist/chunk-3L4U6EJQ.mjs +0 -49
  65. package/dist/chunk-GIVAGD3Q.mjs +0 -25
  66. package/dist/chunk-IFBKLBGB.mjs +0 -7
  67. package/dist/direct-executor-GYJEKNV5.mjs +0 -12
  68. package/dist/serialization/crc32-worker.d.ts +0 -20
  69. package/dist/serialization/crc32-worker.mjs +0 -62
@@ -1,9 +1,8 @@
1
- import { MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, MAX_DOC_ID_LENGTH, DEFAULT_LIMIT, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, ABSOLUTE_WINDOWS_PATH_PATTERN, MODULE_URL_SCHEME_PATTERN, DEFAULT_OFFSET } from './chunk-GIVAGD3Q.mjs';
2
- import { clampRowCount, sortSignatureOf, requireSortableFields, decodePageCursor, requireMatchingCursor, resolveProjection, encodePageCursor, requireWithinResultWindow, normalizeSort, fanOutQuery, sortModesOf, applySorting, applyPagination, projectionKeepsField, countsWithoutScores, fanOutMatchCount, tokenize, fnv1aBytes, requireValidFilter, everyValueFacetConfig, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, partitionsIn, undeclaredFieldOnStrictIndex, fuzzyTermMatches } from './chunk-LMO5TJUS.mjs';
3
- import { MIN_PATTERN_VALUE_LIMIT, MAX_PATTERN_VALUE_LIMIT, DEFAULT_MAX_PER_GROUP, flattenSchema, DEFAULT_LINEAR_ALPHA, MIN_LINEAR_ALPHA, MAX_LINEAR_ALPHA, DEFAULT_RANK_CONSTANT, MIN_RANK_CONSTANT, DEFAULT_FUZZY_PREFIX_LENGTH, RESULT_WINDOW, isWordIndexedFieldType, isGeopointOrVectorType, MAX_TOLERANCE, MAX_PREFIX_LENGTH } from './chunk-653BNMAF.mjs';
1
+ import { clampRowCount, sortSignatureOf, requireSortableFields, decodePageCursor, requireMatchingCursor, resolveProjection, encodePageCursor, requireWithinResultWindow, normalizeSort, fanOutQuery, sortModesOf, applySorting, applyPagination, projectionKeepsField, countsWithoutScores, fanOutMatchCount, tokenize, fnv1aBytes, requireValidFilter, createPatternWorkMeter, everyValueFacetConfig, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, partitionsIn, undeclaredFieldOnStrictIndex, fuzzyTermMatches } from './chunk-3YKWSO54.mjs';
2
+ import { MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, MAX_DOC_ID_LENGTH, MIN_PATTERN_VALUE_LIMIT, MAX_PATTERN_VALUE_LIMIT, DEFAULT_LIMIT, DEFAULT_MAX_PER_GROUP, flattenSchema, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, ABSOLUTE_WINDOWS_PATH_PATTERN, MODULE_URL_SCHEME_PATTERN, DEFAULT_LINEAR_ALPHA, MIN_LINEAR_ALPHA, MAX_LINEAR_ALPHA, DEFAULT_RANK_CONSTANT, MIN_RANK_CONSTANT, DEFAULT_OFFSET, DEFAULT_FUZZY_PREFIX_LENGTH, RESULT_WINDOW, isWordIndexedFieldType, isGeopointOrVectorType, MAX_TOLERANCE, MAX_PREFIX_LENGTH } from './chunk-AK3BLGCI.mjs';
4
3
  import { MIN_OVERSAMPLE } from './chunk-ZJ4CSAB2.mjs';
5
- import { compareCodePoints, compareComparableKeys } from './chunk-OI42GWLR.mjs';
6
- import { NarsilError, ErrorCodes } from './chunk-USKFU7J5.mjs';
4
+ import { holdsUnpairedSurrogate, compareCodePoints, compareComparableKeys } from './chunk-LJYTW7GT.mjs';
5
+ import { NarsilError, ErrorCodes } from './chunk-I64LXKO3.mjs';
7
6
 
8
7
  // src/search/cursor-binding.ts
9
8
  var ABSENT_TAG = 0;
@@ -225,6 +224,9 @@ function requireValidTypoTolerance(params) {
225
224
  }
226
225
 
227
226
  // src/engine/query/shared.ts
227
+ function withPatternWork(context) {
228
+ return { ...context, patternWork: createPatternWorkMeter(context.patternWorkCap) };
229
+ }
228
230
  function partitionsFor(manager, partitionIds) {
229
231
  return partitionsIn(manager, partitionIds);
230
232
  }
@@ -271,18 +273,19 @@ function broadcastStatsForWorker(params, context, scoring) {
271
273
  }
272
274
  return pruneStatsToQueryTerms(scoring.globalStats, term, context.language, context.manager.analysis);
273
275
  }
274
- function searchOptionsFor(manager) {
276
+ function searchOptionsFor(manager, patternWork) {
275
277
  return {
276
278
  bm25Params: manager.config.bm25,
277
279
  stopWords: manager.analysis.stopWords,
278
- customTokenizer: manager.analysis.customTokenizer
280
+ customTokenizer: manager.analysis.customTokenizer,
281
+ patternWork
279
282
  };
280
283
  }
281
- function collectFilterDocIds(manager, params, schema, partitionIds) {
284
+ function collectFilterDocIds(manager, params, schema, partitionIds, patternWork) {
282
285
  const filterDocIds = /* @__PURE__ */ new Set();
283
286
  if (!params.filters) return filterDocIds;
284
287
  for (const partition of partitionsFor(manager, partitionIds)) {
285
- const partitionFiltered = partition.applyFilters(params.filters, schema);
288
+ const partitionFiltered = partition.applyFilters(params.filters, schema, patternWork);
286
289
  for (const docId of partitionFiltered) {
287
290
  filterDocIds.add(docId);
288
291
  }
@@ -317,11 +320,11 @@ function requireValidQueryOptions(params, config) {
317
320
  const strict = config.strict === true;
318
321
  requireValidTypoTolerance(params);
319
322
  requireSortableFields(params.sort, config.schema, strict);
320
- requireValidQueryFilter(params.filters, config.schema, strict);
323
+ requireValidQueryFilter(params.filters, config.schema);
321
324
  requireUsableQueryFields(params, config.schema, strict);
322
325
  }
323
- function requireValidQueryFilter(filters, schema, strict = false) {
324
- if (filters !== void 0) requireValidFilter(filters, flattenSchema(schema), strict);
326
+ function requireValidQueryFilter(filters, schema) {
327
+ if (filters !== void 0) requireValidFilter(filters, flattenSchema(schema));
325
328
  }
326
329
  function requireVectorSearchable(params, context, needsVector) {
327
330
  if (!needsVector) return;
@@ -395,6 +398,13 @@ function validateDocId(docId) {
395
398
  if (docId.includes("\0")) {
396
399
  throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
397
400
  }
401
+ if (holdsUnpairedSurrogate(docId)) {
402
+ throw new NarsilError(
403
+ ErrorCodes.DOC_VALIDATION_FAILED,
404
+ "Document ID holds an unpaired surrogate, the half of a character such as an emoji whose other half is missing, so the engine cannot store it as UTF-8",
405
+ { docId }
406
+ );
407
+ }
398
408
  }
399
409
  function validatePartitionConfig(partitions) {
400
410
  const { maxDocsPerPartition, maxPartitions, watermark } = partitions;
@@ -493,8 +503,9 @@ function firstIdAfter(sorted, anchor) {
493
503
  }
494
504
  return low;
495
505
  }
496
- function collectFilterMatches(partitions, filters, schema) {
497
- const perPartition = partitions.map((partition) => partition.filterMatches(filters, schema));
506
+ function collectFilterMatches(partitions, filters, schema, patternWorkCap) {
507
+ const meter = createPatternWorkMeter(patternWorkCap);
508
+ const perPartition = partitions.map((partition) => partition.filterMatches(filters, schema, meter));
498
509
  let total = 0;
499
510
  for (const matches of perPartition) total += matches.count;
500
511
  return { matchesFor: (partitionIndex) => perPartition[partitionIndex] ?? null, total };
@@ -572,7 +583,7 @@ function executeListDocuments(params, context) {
572
583
  const limit = clampListLimit(params.limit);
573
584
  const signature = sortSignatureOf(params.sort);
574
585
  requireSortableFields(params.sort, schema, context.strict === true);
575
- requireValidQueryFilter(params.filters, schema, context.strict === true);
586
+ requireValidQueryFilter(params.filters, schema);
576
587
  const binding = listBindingOf(params.filters);
577
588
  let cursor = null;
578
589
  if (params.cursor !== void 0) {
@@ -580,7 +591,7 @@ function executeListDocuments(params, context) {
580
591
  requireMatchingCursor(cursor, params.cursor, signature, false, binding);
581
592
  }
582
593
  const partitions = context.partitionIds === void 0 ? manager.getAllPartitions() : context.partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
583
- const filtered = params.filters === void 0 ? null : collectFilterMatches(partitions, params.filters, schema);
594
+ const filtered = params.filters === void 0 ? null : collectFilterMatches(partitions, params.filters, schema, context.patternWorkCap);
584
595
  const total = filtered === null ? countAcrossPartitions(manager, context.partitionIds, partitions) : filtered.total;
585
596
  const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit, binding) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema, binding);
586
597
  const projection = resolveProjection(params.document);
@@ -947,7 +958,7 @@ function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
947
958
  const flatSchema = flattenSchema(schema);
948
959
  const fieldTypes = fields.map((field) => flatSchema[field]);
949
960
  const modes = sortModesOf(normalized);
950
- const options = searchOptionsFor(manager);
961
+ const options = searchOptionsFor(manager, context.patternWork);
951
962
  const partitions = partitionsFor(manager, context.partitionIds);
952
963
  const partitionLimit = offset + limit + 1;
953
964
  const merged = [];
@@ -1148,7 +1159,7 @@ async function executeVectorSearch(params, context, limit, offset, cursorDepth =
1148
1159
  let filterDocIds;
1149
1160
  let filterPartitions;
1150
1161
  if (params.filters) {
1151
- filterDocIds = collectFilterDocIds(manager, params, config.schema, partitionIds);
1162
+ filterDocIds = collectFilterDocIds(manager, params, config.schema, partitionIds, context.patternWork);
1152
1163
  if (filterDocIds.size === 0) {
1153
1164
  return { scored: [], totalMatched: 0 };
1154
1165
  }
@@ -1176,11 +1187,19 @@ async function textLeg(textOnlyParams, context) {
1176
1187
  indexName,
1177
1188
  textOnlyParams,
1178
1189
  broadcastStatsForWorker(textOnlyParams, context, scoring),
1179
- context.partitionIds
1190
+ context.partitionIds,
1191
+ context.patternWork
1180
1192
  );
1181
1193
  if (viaWorker) return viaWorker;
1182
1194
  }
1183
- return fanOutQuery(manager, textOnlyParams, language, config.schema, scoring, searchOptionsFor(manager));
1195
+ return fanOutQuery(
1196
+ manager,
1197
+ textOnlyParams,
1198
+ language,
1199
+ config.schema,
1200
+ scoring,
1201
+ searchOptionsFor(manager, context.patternWork)
1202
+ );
1184
1203
  }
1185
1204
  async function vectorLeg(vectorConfig, queryVector, context, filterDocIds, k) {
1186
1205
  const { manager } = context;
@@ -1206,7 +1225,7 @@ async function executeHybridSearch(params, context, limit, offset, cursorDepth =
1206
1225
  const fusion = resolveHybridFusion(params.hybrid);
1207
1226
  let filterDocIds;
1208
1227
  if (params.filters) {
1209
- filterDocIds = collectFilterDocIds(manager, params, config.schema, context.partitionIds);
1228
+ filterDocIds = collectFilterDocIds(manager, params, config.schema, context.partitionIds, context.patternWork);
1210
1229
  if (filterDocIds.size === 0) {
1211
1230
  return { scored: [], totalMatched: 0 };
1212
1231
  }
@@ -1250,7 +1269,8 @@ function requireCursorDepth(cursor, signature, binding) {
1250
1269
  requireMatchingCursor(decoded, cursor, signature, true, binding);
1251
1270
  return decoded.depth ?? 0;
1252
1271
  }
1253
- async function executeQuery(params, context) {
1272
+ async function executeQuery(params, callerContext) {
1273
+ const context = withPatternWork(callerContext);
1254
1274
  const { manager, language, config, workerSearch, indexName } = context;
1255
1275
  const startTime = now();
1256
1276
  const limit = clampLimit(params.limit);
@@ -1296,11 +1316,24 @@ async function executeQuery(params, context) {
1296
1316
  fanOutResult = await executeHybridSearch(params, context, limit, offset, depth);
1297
1317
  } else {
1298
1318
  const scoring = scoringConfigFor(params, context);
1299
- const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
1319
+ const workerResult = workerSearch ? await workerSearch(
1320
+ indexName,
1321
+ params,
1322
+ broadcastStatsForWorker(params, context, scoring),
1323
+ context.partitionIds,
1324
+ context.patternWork
1325
+ ) : null;
1300
1326
  if (workerResult) {
1301
1327
  fanOutResult = workerResult;
1302
1328
  } else {
1303
- fanOutResult = await fanOutQuery(manager, params, language, config.schema, scoring, searchOptionsFor(manager));
1329
+ fanOutResult = await fanOutQuery(
1330
+ manager,
1331
+ params,
1332
+ language,
1333
+ config.schema,
1334
+ scoring,
1335
+ searchOptionsFor(manager, context.patternWork)
1336
+ );
1304
1337
  }
1305
1338
  }
1306
1339
  const needsFullHits = params.sort !== void 0 || params.group !== void 0 || params.pinned !== void 0 || params.searchAfter !== void 0;
@@ -1420,7 +1453,8 @@ async function executeQuery(params, context) {
1420
1453
  coverage: coverageFor(manager, context.partitionIds)
1421
1454
  };
1422
1455
  }
1423
- async function executePreflight(params, context) {
1456
+ async function executePreflight(params, callerContext) {
1457
+ const context = withPatternWork(callerContext);
1424
1458
  const { manager, language, config, workerSearch, indexName } = context;
1425
1459
  const startTime = now();
1426
1460
  requireKnownMode(params);
@@ -1446,12 +1480,18 @@ async function executePreflight(params, context) {
1446
1480
  countExact = result.matchedExact !== false;
1447
1481
  } else if (countsWithoutScores(params)) {
1448
1482
  totalMatched = fanOutMatchCount(manager, params, language, config.schema, {
1449
- searchOptions: searchOptionsFor(manager),
1483
+ searchOptions: searchOptionsFor(manager, context.patternWork),
1450
1484
  partitionIds: context.partitionIds
1451
1485
  });
1452
1486
  } else {
1453
1487
  const scoring = scoringConfigFor(params, context);
1454
- const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
1488
+ const workerResult = workerSearch ? await workerSearch(
1489
+ indexName,
1490
+ params,
1491
+ broadcastStatsForWorker(params, context, scoring),
1492
+ context.partitionIds,
1493
+ context.patternWork
1494
+ ) : null;
1455
1495
  if (workerResult) {
1456
1496
  totalMatched = workerResult.totalMatched;
1457
1497
  } else {
@@ -1461,7 +1501,7 @@ async function executePreflight(params, context) {
1461
1501
  language,
1462
1502
  config.schema,
1463
1503
  scoring,
1464
- searchOptionsFor(manager)
1504
+ searchOptionsFor(manager, context.patternWork)
1465
1505
  );
1466
1506
  totalMatched = fanOutResult.totalMatched;
1467
1507
  }
@@ -1,6 +1,6 @@
1
- import { P as PartitionConfig, A as AnyDocument, a as InsertOptions, W as WriteOptions } from './schema-Bgzr4g7b.js';
2
- import { V as VectorMaintenanceResult, 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, j as SuggestParams, k as SuggestResult } from './memory-C7UMCMH3.js';
3
- import { e as TaskRecord, M as MemoryStatsResponse, b as ImportResult, H as HttpIndexConfig, c as TaskListQuery, T as TaskListPage } from './tasks-BZt_zes6.js';
1
+ import { P as PartitionConfig, A as AnyDocument, a as InsertOptions, W as WriteOptions } from './schema-BBOCdhND.js';
2
+ import { V as VectorMaintenanceResult, B as BatchResult, L as ListParams, h as ListResult, I as IndexInfo, g as IndexStats, P as PartitionStatsResult, Q as QueryParams, e as QueryResult, j as PreflightResult, k as SuggestParams, l as SuggestResult } from './memory-B4jMmygw.js';
3
+ import { e as TaskRecord, M as MemoryStatsResponse, b as ImportResult, H as HttpIndexConfig, c as TaskListQuery, T as TaskListPage } from './tasks-DpgCpW1x.js';
4
4
 
5
5
  /**
6
6
  * This is the `fetch` implementation the client sends every request through.
@@ -74,14 +74,15 @@ interface RequestOptions {
74
74
  }
75
75
 
76
76
  /**
77
- * These methods run the operations that maintain an index: checkpoints,
78
- * snapshots, vector maintenance, partitioning, and analysis rebuilds.
77
+ * These methods carry out the operations that maintain an index: checkpoints,
78
+ * snapshots, document exports, vector maintenance, partitioning, and analysis
79
+ * rebuilds.
79
80
  *
80
- * Four of them can run for minutes, so each answers with a {@link TaskRecord}
81
- * while the work carries on, and you follow it with
82
- * {@link TaskOperations.waitForTask}. The {@link Narsil} methods they mirror
83
- * return nothing and finish before they resolve, which is the one place this
84
- * client parts from the engine.
81
+ * Five of them can take minutes, so each answers with a {@link TaskRecord}
82
+ * while the work continues. Follow that task with
83
+ * {@link TaskOperations.waitForTask}. The {@link Narsil} methods that they
84
+ * mirror finish their work before they resolve, which is the one place where
85
+ * this client differs from the engine.
85
86
  *
86
87
  * @public
87
88
  */
@@ -96,35 +97,85 @@ interface AdminOperations {
96
97
  */
97
98
  checkpoint(indexName: string, options?: RequestOptions): Promise<void>;
98
99
  /**
99
- * Downloads one index as a portable `.nrsl` file, which any Narsil
100
- * implementation can read back.
100
+ * Returns a stream of an index's snapshot archive. Any implementation of
101
+ * the Narsil format can restore the archive.
101
102
  *
102
- * The whole index crosses the network, so this call sets no deadline of its
103
- * own and waits for as long as the download takes until
104
- * {@link NarsilClientOptions.timeoutMs} or a per-call `timeoutMs` sets one.
103
+ * The download takes longer as the index grows, so this method sets no
104
+ * deadline of its own. It waits until the download ends, unless
105
+ * {@link NarsilClientOptions.timeoutMs} or a per-call `timeoutMs` sets a
106
+ * deadline.
105
107
  *
106
- * @param indexName - This names the index to serialise.
108
+ * @param indexName - This names the index to stream.
107
109
  * @param options - This sets the signal, the deadline, and the headers for
108
110
  * this request.
109
- * @returns The bytes make up the file.
111
+ * @returns The stream holds the archive's bytes in the order that the server
112
+ * sends them.
110
113
  */
111
- snapshot(indexName: string, options?: RequestOptions): Promise<Uint8Array>;
114
+ snapshot(indexName: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
112
115
  /**
113
- * Replaces an index with the contents of a `.nrsl` file, dropping whatever
114
- * the index held.
116
+ * Uploads a snapshot archive and replaces the named index with the
117
+ * archive's contents. The method also accepts the single `.nrsl` file that
118
+ * `snapshot` returned in release 0.3.
115
119
  *
116
- * The load runs as a task, so this resolves once the server has read the
117
- * bytes, and the index comes back afterwards.
120
+ * The server writes the upload to a temporary file and restores the index
121
+ * in a task. This method therefore resolves once the server holds the whole
122
+ * upload, and the task's status changes to `succeeded` once the restored
123
+ * index is ready.
118
124
  *
119
125
  * @param indexName - This names the index to replace.
120
- * @param data - These are the file's bytes.
126
+ * @param data - This is the archive, as one byte array or a stream of bytes.
121
127
  * @param options - This sets the signal, the deadline, and the headers for
122
128
  * this request.
123
129
  * @returns The record is the task to follow.
124
- * @throws A `NarsilError` with `DOC_VALIDATION_FAILED` or an `ENVELOPE_`
125
- * code, reported on the task, when the bytes hold no readable snapshot.
130
+ * @throws A `NarsilError` with `PAYLOAD_TOO_LARGE` when the upload passes
131
+ * the server's `maxRestoreBytes` limit, and with `EMPTY_BODY` for an empty
132
+ * upload. The task reports `PERSISTENCE_LOAD_FAILED`,
133
+ * `PERSISTENCE_CRC_MISMATCH`, `ENVELOPE_VERSION_MISMATCH`, or
134
+ * `CONFIG_INVALID` in its `error` when a check of the archive fails.
126
135
  */
127
- restore(indexName: string, data: Uint8Array, options?: RequestOptions): Promise<TaskRecord>;
136
+ restore(indexName: string, data: Uint8Array | ReadableStream<Uint8Array>, options?: RequestOptions): Promise<TaskRecord>;
137
+ /**
138
+ * Returns a stream of an index's document export. The export is JSON Lines
139
+ * text, with the index configuration on the first line and one document on
140
+ * each later line. Pass the export to
141
+ * {@link AdminOperations.importDocumentExport} to load it into a server of
142
+ * this release or a later one.
143
+ *
144
+ * The download takes longer as the index grows, so this method sets no
145
+ * deadline of its own. It waits until the download ends, unless
146
+ * {@link NarsilClientOptions.timeoutMs} or a per-call `timeoutMs` sets a
147
+ * deadline.
148
+ *
149
+ * @param indexName - This names the index to export.
150
+ * @param options - This sets the signal, the deadline, and the headers for
151
+ * this request.
152
+ * @returns The stream holds the export's UTF-8 bytes in the order that the
153
+ * server sends them.
154
+ */
155
+ exportDocuments(indexName: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
156
+ /**
157
+ * Uploads a document export and imports it into an index in a task. Where
158
+ * the name is free, the engine creates the index from the export's
159
+ * configuration. The engine replaces each document whose ID the index
160
+ * already contains.
161
+ *
162
+ * The server writes the upload to a temporary file before the task starts,
163
+ * so this method resolves once the server holds the whole upload. When the
164
+ * task finishes, its result holds the number of imported documents and the
165
+ * refused lines.
166
+ *
167
+ * @param indexName - This names the index that receives the documents.
168
+ * @param data - This is the export, as one byte array or a stream of bytes.
169
+ * @param options - This sets the signal, the deadline, and the headers for
170
+ * this request.
171
+ * @returns The record is the task to follow.
172
+ * @throws A `NarsilError` with `PAYLOAD_TOO_LARGE` when the upload passes
173
+ * the server's `maxRestoreBytes` limit, and with `EMPTY_BODY` for an empty
174
+ * upload. The task reports `CONFIG_INVALID` in its `error` when the first
175
+ * line is anything other than an export header, or when the named index
176
+ * exists with a different schema.
177
+ */
178
+ importDocumentExport(indexName: string, data: Uint8Array | ReadableStream<Uint8Array>, options?: RequestOptions): Promise<TaskRecord>;
128
179
  /**
129
180
  * Reports the state of each vector field, and what maintenance would cost.
130
181
  *
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, i as isNarsilError } from './memory-C7UMCMH3.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-Dt-7B4Wz.js';
3
- export { C as CapabilitiesResponse, a as CreateIndexEmbedding, H as HttpIndexConfig, I as ImportError, b as ImportResult, M as MemoryStatsResponse, T as TaskListPage, c as TaskListQuery, d as TaskProgress, e as TaskRecord, f as TaskStatus, g as TaskType } from './tasks-BZt_zes6.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-COek5GXK.js';
5
- import './schema-Bgzr4g7b.js';
1
+ export { C as ClientErrorCode, a as ClientErrorCodes, E as ErrorCode, b as ErrorCodes, N as NarsilError, c as NarsilErrorCode, S as ServerErrorCode, d as ServerErrorCodes, i as isNarsilError } from './memory-B4jMmygw.js';
2
+ export { A as ASYNC_IMPORT_CAPABILITY, D as DOCUMENT_EXPORT_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-CYeAWYxQ.js';
3
+ export { C as CapabilitiesResponse, a as CreateIndexEmbedding, H as HttpIndexConfig, I as ImportError, b as ImportResult, M as MemoryStatsResponse, T as TaskListPage, c as TaskListQuery, d as TaskProgress, e as TaskRecord, f as TaskStatus, g as TaskType } from './tasks-DpgCpW1x.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-D698E5ge.js';
5
+ import './schema-BBOCdhND.js';
6
6
  import './adapters-79OIa4xm.js';
7
- import './config-rBpbgTw8.js';
8
- import './config-Dfanyqqt.js';
9
- import './types-gOUqhs2F.js';
10
- import './types-D5OwhtsQ.js';
7
+ import './config-Bv8etwUZ.js';
8
+ import './config-QXU3HMpq.js';
9
+ import './types-BiUmaiWT.js';
10
+ import './types-Cb9kiY5P.js';
package/dist/client.mjs CHANGED
@@ -31,6 +31,7 @@ var ErrorCodes = {
31
31
  SEARCH_INVALID_MODE: "SEARCH_INVALID_MODE",
32
32
  SEARCH_INVALID_CURSOR: "SEARCH_INVALID_CURSOR",
33
33
  SEARCH_RESULT_WINDOW_EXCEEDED: "SEARCH_RESULT_WINDOW_EXCEEDED",
34
+ SEARCH_WORK_CAP_EXCEEDED: "SEARCH_WORK_CAP_EXCEEDED",
34
35
  LANGUAGE_NOT_SUPPORTED: "LANGUAGE_NOT_SUPPORTED",
35
36
  ENVELOPE_VERSION_MISMATCH: "ENVELOPE_VERSION_MISMATCH",
36
37
  ENVELOPE_INVALID_MAGIC: "ENVELOPE_INVALID_MAGIC",
@@ -71,7 +72,6 @@ var ErrorCodes = {
71
72
  SNAPSHOT_SYNC_UNAUTHORIZED: "SNAPSHOT_SYNC_UNAUTHORIZED",
72
73
  SNAPSHOT_SYNC_REQUEST_INVALID: "SNAPSHOT_SYNC_REQUEST_INVALID",
73
74
  SNAPSHOT_SYNC_INDEX_NOT_FOUND: "SNAPSHOT_SYNC_INDEX_NOT_FOUND",
74
- SNAPSHOT_SYNC_TOO_LARGE: "SNAPSHOT_SYNC_TOO_LARGE",
75
75
  SNAPSHOT_SYNC_CAPACITY_EXHAUSTED: "SNAPSHOT_SYNC_CAPACITY_EXHAUSTED",
76
76
  SNAPSHOT_SYNC_SNAPSHOT_FAILED: "SNAPSHOT_SYNC_SNAPSHOT_FAILED",
77
77
  SNAPSHOT_SYNC_DECODE_FAILED: "SNAPSHOT_SYNC_DECODE_FAILED",
@@ -154,12 +154,14 @@ var TASK_CANCEL_CAPABILITY = "tasks.cancel";
154
154
  var TASK_FILTER_CAPABILITY = "tasks.filter";
155
155
  var REBUILD_ANALYSIS_CAPABILITY = "indexes.rebuildAnalysis";
156
156
  var INDEX_LIFECYCLE_CAPABILITY = "indexes.lifecycle";
157
+ var DOCUMENT_EXPORT_CAPABILITY = "indexes.documentExport";
157
158
  var SERVER_CAPABILITIES = [
158
159
  ASYNC_IMPORT_CAPABILITY,
159
160
  TASK_CANCEL_CAPABILITY,
160
161
  TASK_FILTER_CAPABILITY,
161
162
  REBUILD_ANALYSIS_CAPABILITY,
162
- INDEX_LIFECYCLE_CAPABILITY
163
+ INDEX_LIFECYCLE_CAPABILITY,
164
+ DOCUMENT_EXPORT_CAPABILITY
163
165
  ];
164
166
 
165
167
  // src/json-encoding.ts
@@ -240,10 +242,9 @@ function createAdminOperations(transport) {
240
242
  await transport.json({ method: "POST", path: `${indexPath(indexName)}/_checkpoint`, options });
241
243
  },
242
244
  snapshot(indexName, options) {
243
- return transport.binary({
245
+ return transport.stream({
244
246
  method: "GET",
245
247
  path: `${indexPath(indexName)}/snapshot`,
246
- binaryAnswer: true,
247
248
  defaultTimeoutMs: NO_TIMEOUT,
248
249
  options
249
250
  });
@@ -260,6 +261,26 @@ function createAdminOperations(transport) {
260
261
  });
261
262
  return readBody(payload, path);
262
263
  },
264
+ exportDocuments(indexName, options) {
265
+ return transport.stream({
266
+ method: "GET",
267
+ path: `${indexPath(indexName)}/export`,
268
+ defaultTimeoutMs: NO_TIMEOUT,
269
+ options
270
+ });
271
+ },
272
+ async importDocumentExport(indexName, data, options) {
273
+ const path = `${indexPath(indexName)}/import`;
274
+ const payload = await transport.json({
275
+ method: "POST",
276
+ path,
277
+ body: data,
278
+ contentType: "application/x-ndjson",
279
+ defaultTimeoutMs: NO_TIMEOUT,
280
+ options
281
+ });
282
+ return readBody(payload, path);
283
+ },
263
284
  async vectorMaintenanceStatus(indexName, options) {
264
285
  const path = `${indexPath(indexName)}/vector-maintenance`;
265
286
  const payload = await transport.json({ method: "GET", path, options });
@@ -516,20 +537,14 @@ async function readStreamCapped(stream, limitBytes) {
516
537
  }
517
538
  return body;
518
539
  }
519
- async function readAnswer(response, limitBytes, wantsBytes) {
540
+ async function readAnswer(response, limitBytes) {
520
541
  const stream = response.body;
521
542
  if (limitBytes > 0 && stream !== null && stream !== void 0) {
522
- const body = await readStreamCapped(stream, limitBytes);
523
- return wantsBytes ? { text: "", bytes: body } : { text: new TextDecoder().decode(body), bytes: null };
524
- }
525
- if (wantsBytes) {
526
- const bytes = new Uint8Array(await response.arrayBuffer());
527
- if (limitBytes > 0 && bytes.byteLength > limitBytes) throw new ResponseTooLargeError(limitBytes);
528
- return { text: "", bytes };
543
+ return new TextDecoder().decode(await readStreamCapped(stream, limitBytes));
529
544
  }
530
545
  const text = await response.text();
531
546
  if (limitBytes > 0 && text.length > limitBytes) throw new ResponseTooLargeError(limitBytes);
532
- return { text, bytes: null };
547
+ return text;
533
548
  }
534
549
  function invalidResponse(message, details) {
535
550
  return new NarsilError(ClientErrorCodes.CLIENT_INVALID_RESPONSE, message, details);
@@ -591,19 +606,27 @@ function createTransport(options) {
591
606
  if (perCall !== void 0) return perCall;
592
607
  return options.maxResponseBytes ?? 0;
593
608
  }
594
- async function exchange(spec) {
609
+ async function exchange(spec, keepsStream = false) {
595
610
  const url = `${base}${withQuery(spec.path, spec.query)}`;
596
611
  const headers = { ...baseHeaders, ...spec.options?.headers };
597
612
  if (spec.contentType !== void 0) headers["content-type"] = spec.contentType;
598
613
  const timeoutMs = resolveTimeout(spec);
599
614
  const deadline = startDeadline(timeoutMs, spec.options?.signal);
615
+ let handedOver = false;
600
616
  try {
601
617
  const init = { method: spec.method, headers, signal: deadline.signal };
602
618
  if (spec.body !== void 0) init.body = spec.body;
619
+ if (spec.body instanceof ReadableStream) init.duplex = "half";
603
620
  const response = await send(url, init);
604
- const wantsBytes = spec.binaryAnswer === true && response.ok;
605
- const answer = await readAnswer(response, resolveResponseCeiling(spec), wantsBytes);
606
- return { status: response.status, url, ...answer };
621
+ if (keepsStream && response.ok && response.body !== null) {
622
+ handedOver = true;
623
+ const stream = response.body.pipeThrough(
624
+ new TransformStream({ flush: () => deadline.release() })
625
+ );
626
+ return { status: response.status, url, text: "", stream };
627
+ }
628
+ const text = await readAnswer(response, resolveResponseCeiling(spec));
629
+ return { status: response.status, url, stream: null, text };
607
630
  } catch (err) {
608
631
  if (err instanceof ResponseTooLargeError) {
609
632
  throw invalidResponse(`The server at ${url} answered more than this client reads: ${err.message}`, {
@@ -629,7 +652,7 @@ function createTransport(options) {
629
652
  { url }
630
653
  );
631
654
  } finally {
632
- deadline.release();
655
+ if (!handedOver) deadline.release();
633
656
  }
634
657
  }
635
658
  function parse(status, text, path) {
@@ -656,9 +679,9 @@ function createTransport(options) {
656
679
  throw err;
657
680
  }
658
681
  },
659
- async binary(spec) {
660
- const { status, text, bytes, url } = await exchange(spec);
661
- if (bytes !== null) return bytes;
682
+ async stream(spec) {
683
+ const { status, text, stream, url } = await exchange(spec, true);
684
+ if (stream !== null) return stream;
662
685
  throw errorFromBody(status, parse(status, text, url), url);
663
686
  },
664
687
  async probe(spec) {
@@ -873,4 +896,4 @@ function createNarsilClient(options) {
873
896
  };
874
897
  }
875
898
 
876
- export { ASYNC_IMPORT_CAPABILITY, ClientErrorCodes, ErrorCodes, INDEX_LIFECYCLE_CAPABILITY, NarsilError, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, ServerErrorCodes, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, createNarsilClient, isNarsilError };
899
+ export { ASYNC_IMPORT_CAPABILITY, ClientErrorCodes, DOCUMENT_EXPORT_CAPABILITY, ErrorCodes, INDEX_LIFECYCLE_CAPABILITY, NarsilError, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, ServerErrorCodes, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, createNarsilClient, isNarsilError };
@@ -1,6 +1,6 @@
1
- import { N as NarsilConfig } from './config-Dfanyqqt.js';
2
- import { A as AllocationTable, N as NodeRole, a as NodeRegistration, C as ClusterCoordinator, b as NodeCapacity } from './types-gOUqhs2F.js';
3
- import { N as NodeTransport } from './types-D5OwhtsQ.js';
1
+ import { N as NarsilConfig } from './config-QXU3HMpq.js';
2
+ import { A as AllocationTable, N as NodeRole, a as NodeRegistration, C as ClusterCoordinator, b as NodeCapacity } from './types-BiUmaiWT.js';
3
+ import { N as NodeTransport } from './types-Cb9kiY5P.js';
4
4
 
5
5
  /**
6
6
  * How much replication history a primary keeps, and how many replicas have to
@@ -41,7 +41,14 @@ interface ClusterNodeConfig {
41
41
  nodeId?: string;
42
42
  /** This node offers this much capacity, which the allocator weighs. It offers {@link DEFAULT_CAPACITY} by default. */
43
43
  capacity?: NodeCapacity;
44
- /** These settings configure the engine this node runs locally, such as its persistence and durability. */
44
+ /**
45
+ * These settings configure the engine this node runs locally, such as its persistence and durability.
46
+ *
47
+ * When the node sends or receives a copy of an index, it writes that copy to disk so that the copy never has to fit
48
+ * in memory. The copy goes into a `.snapshot-transfer` directory inside the durability directory, or into the
49
+ * system's temporary directory when the engine has no durability directory, and the node deletes it once the
50
+ * transfer ends.
51
+ */
45
52
  engine?: NarsilConfig;
46
53
  /** These replication settings override the engine's own, and anything you leave out keeps its value. */
47
54
  replication?: Partial<ReplicationConfig>;
@@ -1,6 +1,6 @@
1
1
  import { P as PersistenceAdapter, I as InvalidationAdapter, E as EmbeddingAdapter } from './adapters-79OIa4xm.js';
2
- import { Q as QueryParams, e as QueryResult } from './memory-C7UMCMH3.js';
3
- import { I as IndexConfig, A as AnyDocument } from './schema-Bgzr4g7b.js';
2
+ import { Q as QueryParams, e as QueryResult, f as NarsilEventHandlers } from './memory-B4jMmygw.js';
3
+ import { I as IndexConfig, A as AnyDocument } from './schema-BBOCdhND.js';
4
4
 
5
5
  /**
6
6
  * Hooks that the engine calls alongside its own work, so that an application
@@ -197,6 +197,30 @@ interface NarsilConfig {
197
197
  analysis?: AnalysisConfig;
198
198
  /** These limits close indexes while keeping their durable files ready to reopen. */
199
199
  lifecycle?: IndexLifecycleConfig;
200
+ /**
201
+ * These listeners receive events from the moment that the engine starts.
202
+ * The engine registers each one before it recovers any index, so a listener
203
+ * here receives the `layoutUpgrade` events of that recovery, which come
204
+ * before {@link createNarsil} returns the engine. For a name outside
205
+ * {@link NarsilEventMap}, or an entry that is not a function,
206
+ * {@link createNarsil} throws `CONFIG_INVALID`.
207
+ */
208
+ events?: NarsilEventHandlers;
209
+ /**
210
+ * This is the most work that the engine may do on the pattern tests of one
211
+ * search, counted over every partition that it searches. The engine adds one
212
+ * unit for each run of three code points that it looks up in a pattern
213
+ * index, one for each entry that it reads from a pattern index, and one for
214
+ * each step of its matcher. For a text whose case fold is shorter than three
215
+ * code points, it counts each document that it checks as an entry that it
216
+ * reads. It counts an `in` or `nin` test as one `eq` test for each distinct
217
+ * value in its list. Once the count passes the cap, the engine throws
218
+ * `SEARCH_WORK_CAP_EXCEEDED`. The cap
219
+ * defaults to 25,000,000 units. For any value other than a whole number from
220
+ * 1 to `Number.MAX_SAFE_INTEGER`, {@link createNarsil} throws
221
+ * `CONFIG_INVALID`.
222
+ */
223
+ patternWorkCap?: number;
200
224
  }
201
225
  /**
202
226
  * Limits on the indexes one engine keeps open in memory.
@@ -0,0 +1,13 @@
1
+ export { createDirectExecutor } from './chunk-THMSFOJO.mjs';
2
+ import './chunk-4MQ5F4IL.mjs';
3
+ import './chunk-3YKWSO54.mjs';
4
+ import './chunk-AK3BLGCI.mjs';
5
+ import './chunk-TGP5M5R7.mjs';
6
+ import './chunk-WVDFG45M.mjs';
7
+ import './chunk-ZJ4CSAB2.mjs';
8
+ import './chunk-PU43WDY4.mjs';
9
+ import './chunk-XW25X6AU.mjs';
10
+ import './chunk-ZKRV5M2P.mjs';
11
+ import './chunk-U2BKYEC7.mjs';
12
+ import './chunk-LJYTW7GT.mjs';
13
+ import './chunk-I64LXKO3.mjs';