@delali/narsil 0.2.3 → 0.2.4-next.43

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 (36) hide show
  1. package/dist/adapters/filesystem.mjs +1 -1
  2. package/dist/adapters/indexeddb.mjs +1 -1
  3. package/dist/{chunk-MMSWEEZT.mjs → chunk-KEDL6FFZ.mjs} +315 -51
  4. package/dist/{chunk-WU2UZEVD.mjs → chunk-O4Q5JYNK.mjs} +1 -1
  5. package/dist/{chunk-GB2IIJJG.mjs → chunk-TLRUUMVE.mjs} +1 -4
  6. package/dist/{chunk-GRWFY2HI.mjs → chunk-YGQMKTKG.mjs} +120 -36
  7. package/dist/{client-BobVnoIK.d.ts → client-B5uO5jL1.d.ts} +3 -3
  8. package/dist/client.d.ts +8 -8
  9. package/dist/client.mjs +1 -4
  10. package/dist/{config-Byyl1QqH.d.ts → config-B09-cud9.d.ts} +2 -2
  11. package/dist/{config-fLC4lbmg.d.ts → config-NgUCkc6a.d.ts} +2 -2
  12. package/dist/{direct-executor-MSJEVPZU.mjs → direct-executor-7O23GEHE.mjs} +2 -2
  13. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  14. package/dist/distribution/coordinator/etcd.mjs +1 -1
  15. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  16. package/dist/distribution/transport/grpc.mjs +1 -1
  17. package/dist/distribution.d.ts +8 -8
  18. package/dist/distribution.mjs +705 -272
  19. package/dist/embeddings/openai.mjs +1 -1
  20. package/dist/{engine-BE45D5ZG.d.ts → engine-CjLhdDIm.d.ts} +21 -20
  21. package/dist/index.browser.mjs +436 -91
  22. package/dist/index.d.ts +7 -7
  23. package/dist/index.mjs +4 -4
  24. package/dist/{options-EXYFcem-.d.ts → options-BakECW-r.d.ts} +2 -2
  25. package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
  26. package/dist/persistence/durability/checkpoint-worker.mjs +4 -4
  27. package/dist/react.d.ts +6 -6
  28. package/dist/react.mjs +1 -4
  29. package/dist/{schema-ZRbk8BgH.d.ts → schema-DXOd96kx.d.ts} +16 -5
  30. package/dist/{search-DHuXwJYk.d.ts → search-BGouJxH4.d.ts} +22 -9
  31. package/dist/server.d.ts +10 -10
  32. package/dist/server.mjs +6 -9
  33. package/dist/{tasks-CnMLpfk-.d.ts → tasks-Bn4R1NPN.d.ts} +1 -1
  34. package/dist/{types-B5Tsaalz.d.ts → types-BTE33VZS.d.ts} +1 -1
  35. package/dist/workers/entry.mjs +2 -2
  36. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from '../chunk-GB2IIJJG.mjs';
1
+ import { NarsilError, ErrorCodes } from '../chunk-TLRUUMVE.mjs';
2
2
 
3
3
  // src/persistence/filesystem.ts
4
4
  var cachedFs = null;
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from '../chunk-GB2IIJJG.mjs';
1
+ import { NarsilError, ErrorCodes } from '../chunk-TLRUUMVE.mjs';
2
2
 
3
3
  // src/persistence/indexeddb.ts
4
4
  var DEFAULT_DB_NAME = "narsil";
@@ -1,10 +1,10 @@
1
1
  import { ReplicationMessageTypes } from './chunk-JQUL2T6I.mjs';
2
- import { MAX_INDEX_NAME_LENGTH } from './chunk-WU2UZEVD.mjs';
3
- import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, createDirectExecutor, createPartitionRouter, clampRowCount, DEFAULT_PAGE_SIZE, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, flattenSchema, isTextFieldType, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, tokenize, decodePageCursor, requireMatchingCursor, encodePageCursor, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1, validateRequiredFields, mergePartitionStats, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, createPartitionIndex, fnv1a, createWorkerPool, validateDocument, validateDocumentStrict, createFrozenSegment, detectRuntime, isNodeMainThread, spawnNodeWorker, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, deserializeMetadata, isCompositePartition, serializeMetadata, kWayMerge } from './chunk-GRWFY2HI.mjs';
2
+ import { MAX_INDEX_NAME_LENGTH } from './chunk-O4Q5JYNK.mjs';
3
+ import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, createDirectExecutor, createPartitionRouter, clampRowCount, DEFAULT_PAGE_SIZE, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, flattenSchema, isTextFieldType, fnv1aBytes, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, countsWithoutScores, fanOutMatchCount, tokenize, decodePageCursor, requireMatchingCursor, encodePageCursor, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1, validateRequiredFields, mergePartitionStats, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, createPartitionIndex, fnv1a, createWorkerPool, partitionsIn, validateDocument, validateDocumentStrict, createFrozenSegment, detectRuntime, isNodeMainThread, spawnNodeWorker, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, deserializeMetadata, isCompositePartition, serializeMetadata, kWayMerge } from './chunk-YGQMKTKG.mjs';
4
4
  import { crc32, crc32Init, crc32Update, crc32Final } from './chunk-KMMJMDUA.mjs';
5
5
  import { compareCodePoints, compareComparableKeys } from './chunk-BFIH7OV5.mjs';
6
6
  import { generateId, createRequestId, isValidWorkerResponse } from './chunk-WTONLDU3.mjs';
7
- import { ErrorCodes, NarsilError } from './chunk-GB2IIJJG.mjs';
7
+ import { ErrorCodes, NarsilError } from './chunk-TLRUUMVE.mjs';
8
8
  import { encode, decode } from '@msgpack/msgpack';
9
9
 
10
10
  // src/partitioning/rebalancer.ts
@@ -57,7 +57,7 @@ function createRebalancer() {
57
57
  skipClone: true,
58
58
  stopWordOverride: manager.analysis.stopWords,
59
59
  customTokenizer: manager.analysis.customTokenizer,
60
- collectSurfaces: manager.config.surfaceForms === true
60
+ collectSurfaces: manager.config.surfaceForms !== false
61
61
  };
62
62
  for (const partition of currentPartitions) {
63
63
  const docIds = [...partition.docIds()];
@@ -952,6 +952,9 @@ function writeHeader(header) {
952
952
  }
953
953
  return buffer;
954
954
  }
955
+ function hasNrslMagic(data) {
956
+ return data.length >= MAGIC_BYTES.length && data[0] === MAGIC_BYTES[0] && data[1] === MAGIC_BYTES[1] && data[2] === MAGIC_BYTES[2] && data[3] === MAGIC_BYTES[3];
957
+ }
955
958
  function readHeader(data) {
956
959
  if (data.length < HEADER_SIZE) {
957
960
  throw new NarsilError(
@@ -987,6 +990,7 @@ function readHeader(data) {
987
990
  // src/serialization/envelope.ts
988
991
  var CURRENT_ENVELOPE_VERSION = 1;
989
992
  var SNAPSHOT_ENVELOPE_VERSION = 2;
993
+ var INDEX_SNAPSHOT_ENVELOPE_VERSION = 3;
990
994
  function parseEngineVersion(version) {
991
995
  const parts = version.split(".");
992
996
  return [parseInt(parts[0] ?? "0", 10), parseInt(parts[1] ?? "0", 10), parseInt(parts[2] ?? "0", 10)];
@@ -1131,6 +1135,19 @@ function concatEnvelopeParts(parts) {
1131
1135
  async function unpackEnvelopeBytes(data) {
1132
1136
  return unpackEnvelope(data, SNAPSHOT_ENVELOPE_VERSION);
1133
1137
  }
1138
+ async function packIndexSnapshotEnvelope(payloadBytes) {
1139
+ return packEnvelope(payloadBytes, { checksum: true, envelopeFormatVersion: INDEX_SNAPSHOT_ENVELOPE_VERSION });
1140
+ }
1141
+ async function unpackIndexSnapshotEnvelope(data) {
1142
+ const { header, payloadBytes } = await unpackEnvelope(data, INDEX_SNAPSHOT_ENVELOPE_VERSION);
1143
+ if (header.envelopeFormatVersion !== INDEX_SNAPSHOT_ENVELOPE_VERSION) {
1144
+ throw new NarsilError(
1145
+ ErrorCodes.ENVELOPE_VERSION_MISMATCH,
1146
+ `Expected an index snapshot envelope of format v${INDEX_SNAPSHOT_ENVELOPE_VERSION}, received format v${header.envelopeFormatVersion}`
1147
+ );
1148
+ }
1149
+ return payloadBytes;
1150
+ }
1134
1151
  async function writeMetadataEnvelope(meta, options = {}) {
1135
1152
  const payloadBytes = serializeMetadata(meta);
1136
1153
  return packEnvelope(payloadBytes, options);
@@ -4946,14 +4963,34 @@ async function awaitCompactions(state) {
4946
4963
  var DEFAULT_BACKPRESSURE_LIMIT = 100;
4947
4964
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
4948
4965
  var SHUTDOWN_TIMEOUT = 5e3;
4966
+ var CONSECUTIVE_TIMEOUTS_BEFORE_DEATH = 3;
4967
+ function errorFromEventLike(event) {
4968
+ if (event instanceof Error) {
4969
+ return event;
4970
+ }
4971
+ if (typeof event === "object" && event !== null) {
4972
+ const detail = event;
4973
+ if (detail.error instanceof Error) {
4974
+ return detail.error;
4975
+ }
4976
+ if (typeof detail.message === "string" && detail.message.length > 0) {
4977
+ return new Error(detail.message);
4978
+ }
4979
+ }
4980
+ return new Error("The worker reported an error event carrying no detail");
4981
+ }
4949
4982
  function createWorkerExecutor(worker, config) {
4950
- const backpressureLimit = DEFAULT_BACKPRESSURE_LIMIT;
4951
- const requestTimeout = DEFAULT_REQUEST_TIMEOUT;
4983
+ const backpressureLimit = config?.backpressureLimit ?? DEFAULT_BACKPRESSURE_LIMIT;
4984
+ const requestTimeout = config?.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
4952
4985
  const pending = /* @__PURE__ */ new Map();
4986
+ let deathError = null;
4987
+ let shutdownRequested = false;
4988
+ let consecutiveTimeouts = 0;
4953
4989
  function processResponse(msg) {
4954
4990
  if (!isValidWorkerResponse(msg)) {
4955
4991
  return;
4956
4992
  }
4993
+ consecutiveTimeouts = 0;
4957
4994
  const response = msg;
4958
4995
  const entry = pending.get(response.requestId);
4959
4996
  if (!entry) {
@@ -4967,15 +5004,36 @@ function createWorkerExecutor(worker, config) {
4967
5004
  entry.resolve(response.data);
4968
5005
  }
4969
5006
  }
5007
+ function handleDeath(cause) {
5008
+ if (deathError !== null) {
5009
+ return;
5010
+ }
5011
+ const message = cause instanceof Error ? cause.message : String(cause);
5012
+ deathError = new NarsilError(ErrorCodes.WORKER_CRASHED, `Worker died: ${message}`);
5013
+ for (const [id, entry] of pending) {
5014
+ clearTimeout(entry.timeoutId);
5015
+ entry.reject(deathError);
5016
+ pending.delete(id);
5017
+ }
5018
+ if (!shutdownRequested) {
5019
+ config?.onDeath?.(cause instanceof Error ? cause : new Error(message));
5020
+ }
5021
+ }
4970
5022
  if (typeof worker.on === "function") {
4971
5023
  worker.on("message", (msg) => processResponse(msg));
5024
+ worker.on("error", (cause) => handleDeath(cause));
5025
+ worker.on("exit", (code) => handleDeath(new Error(`Worker exited with code ${String(code)}`)));
4972
5026
  } else if (typeof worker.addEventListener === "function") {
4973
5027
  worker.addEventListener("message", (event) => {
4974
5028
  const msg = event.data;
4975
5029
  processResponse(msg);
4976
5030
  });
5031
+ worker.addEventListener("error", (event) => handleDeath(errorFromEventLike(event)));
4977
5032
  }
4978
5033
  function execute(action) {
5034
+ if (deathError !== null) {
5035
+ return Promise.reject(deathError);
5036
+ }
4979
5037
  if (pending.size >= backpressureLimit) {
4980
5038
  return Promise.reject(
4981
5039
  new NarsilError(ErrorCodes.WORKER_BUSY, `Backpressure limit of ${backpressureLimit} pending requests reached`)
@@ -4987,6 +5045,10 @@ function createWorkerExecutor(worker, config) {
4987
5045
  const timeoutId = setTimeout(() => {
4988
5046
  pending.delete(requestId);
4989
5047
  reject(new NarsilError(ErrorCodes.WORKER_TIMEOUT, `Request ${requestId} timed out after ${requestTimeout}ms`));
5048
+ consecutiveTimeouts += 1;
5049
+ if (consecutiveTimeouts >= CONSECUTIVE_TIMEOUTS_BEFORE_DEATH) {
5050
+ handleDeath(new Error(`no answer to ${CONSECUTIVE_TIMEOUTS_BEFORE_DEATH} consecutive requests`));
5051
+ }
4990
5052
  }, requestTimeout);
4991
5053
  pending.set(requestId, {
4992
5054
  resolve,
@@ -4997,6 +5059,10 @@ function createWorkerExecutor(worker, config) {
4997
5059
  });
4998
5060
  }
4999
5061
  async function shutdown() {
5062
+ shutdownRequested = true;
5063
+ if (deathError !== null) {
5064
+ return;
5065
+ }
5000
5066
  const requestId = createRequestId();
5001
5067
  const shutdownAction = { type: "shutdown", requestId };
5002
5068
  const shutdownPromise = new Promise((resolve, reject) => {
@@ -5038,15 +5104,15 @@ async function createWorkerFactory(entryPoint) {
5038
5104
  const resolvedEntry = resolveEntryPoint();
5039
5105
  if (runtime.supportsWorkerThreads) {
5040
5106
  const workerThreadsModule = await import('worker_threads');
5041
- return function nodeFactory(_workerId) {
5107
+ return function nodeFactory(_workerId, onDeath) {
5042
5108
  const instance = new workerThreadsModule.Worker(new URL(resolvedEntry));
5043
- return createWorkerExecutor(instance);
5109
+ return createWorkerExecutor(instance, { onDeath });
5044
5110
  };
5045
5111
  }
5046
5112
  if (runtime.supportsWebWorkers) {
5047
- return function webFactory(_workerId) {
5113
+ return function webFactory(_workerId, onDeath) {
5048
5114
  const instance = new Worker(resolvedEntry, { type: "module" });
5049
- return createWorkerExecutor(instance);
5115
+ return createWorkerExecutor(instance, { onDeath });
5050
5116
  };
5051
5117
  }
5052
5118
  throw new NarsilError(ErrorCodes.WORKER_CRASHED, `No worker support available on runtime "${runtime.runtime}"`);
@@ -5102,7 +5168,13 @@ async function runPromotion(state, reason) {
5102
5168
  return;
5103
5169
  }
5104
5170
  const factory = await createWorkerFactory();
5105
- const pool = createWorkerPool({ count: state.config?.workers?.count, workerFactory: factory });
5171
+ const pool = createWorkerPool({
5172
+ count: state.config?.workers?.count,
5173
+ workerFactory: factory,
5174
+ onWorkerCrash(workerId, indexNames, error) {
5175
+ state.callbacks?.onWorkerCrash?.(workerId, indexNames, error);
5176
+ }
5177
+ });
5106
5178
  for (const name of promotable) {
5107
5179
  pool.addIndexToAll(name);
5108
5180
  }
@@ -5423,6 +5495,9 @@ function createEngineCore(config) {
5423
5495
  if (handlers) {
5424
5496
  for (const handler of handlers) handler({ workerCount, reason });
5425
5497
  }
5498
+ void Promise.resolve(pluginRegistry.runHook("onWorkerPromote", { workerCount, reason })).catch((err) => {
5499
+ console.warn("onWorkerPromote plugin hook failed:", err instanceof Error ? err.message : String(err));
5500
+ });
5426
5501
  },
5427
5502
  onPromotionFailure(reason, error, retryable) {
5428
5503
  const handlers = eventHandlers.get("workerPromoteFailure");
@@ -5431,6 +5506,14 @@ function createEngineCore(config) {
5431
5506
  return;
5432
5507
  }
5433
5508
  for (const handler of handlers) handler({ reason, error, retryable });
5509
+ },
5510
+ onWorkerCrash(workerId, indexNames, error) {
5511
+ const handlers = eventHandlers.get("workerCrash");
5512
+ if (!handlers || handlers.size === 0) {
5513
+ console.warn(`Worker ${workerId} crashed:`, error);
5514
+ return;
5515
+ }
5516
+ for (const handler of handlers) handler({ workerId, indexNames, error });
5434
5517
  }
5435
5518
  });
5436
5519
  const rebalancer = createRebalancer();
@@ -7389,6 +7472,15 @@ async function executeRebalance(manager, indexName, targetPartitionCount, ctx) {
7389
7472
  }
7390
7473
  }
7391
7474
  });
7475
+ try {
7476
+ await ctx.pluginRegistry.runHook("onPartitionSplit", {
7477
+ indexName,
7478
+ oldPartitionCount: oldCount,
7479
+ newPartitionCount: targetPartitionCount
7480
+ });
7481
+ } catch (hookErr) {
7482
+ console.warn("onPartitionSplit plugin hook failed:", hookErr instanceof Error ? hookErr.message : String(hookErr));
7483
+ }
7392
7484
  await replayQueued(manager, indexName, waq, ctx);
7393
7485
  if (ctx.durabilityManager) {
7394
7486
  await ctx.durabilityManager.persistMetadata(indexName);
@@ -7603,7 +7695,7 @@ async function createSnapshot(manager, entry) {
7603
7695
  const config = entry.config;
7604
7696
  const bm25 = config.bm25;
7605
7697
  const { encode: encode9 } = await import('@msgpack/msgpack');
7606
- return encode9({
7698
+ const payload = encode9({
7607
7699
  version: 2,
7608
7700
  schema: config.schema,
7609
7701
  language: entry.language.name,
@@ -7633,15 +7725,17 @@ async function createSnapshot(manager, entry) {
7633
7725
  partitions: partitionBuffers,
7634
7726
  vectorIndexes: vectorPayloads
7635
7727
  });
7728
+ return packIndexSnapshotEnvelope(payload);
7636
7729
  }
7637
7730
  async function restoreFromSnapshot(indexName, data, deps) {
7638
7731
  if (!(data instanceof Uint8Array)) {
7639
7732
  throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Snapshot data must be a Uint8Array");
7640
7733
  }
7734
+ const payloadBytes = hasNrslMagic(data) ? await unpackIndexSnapshotEnvelope(data) : data;
7641
7735
  const { decode: decode9 } = await import('@msgpack/msgpack');
7642
7736
  let decoded;
7643
7737
  try {
7644
- decoded = decode9(data);
7738
+ decoded = decode9(payloadBytes);
7645
7739
  } catch (err) {
7646
7740
  throw new NarsilError(
7647
7741
  ErrorCodes.DOC_VALIDATION_FAILED,
@@ -7836,6 +7930,137 @@ function readProcessMemory() {
7836
7930
  }
7837
7931
  }
7838
7932
 
7933
+ // src/search/cursor-binding.ts
7934
+ var ABSENT_TAG = 0;
7935
+ var NULL_TAG = 1;
7936
+ var FALSE_TAG = 2;
7937
+ var TRUE_TAG = 3;
7938
+ var NUMBER_TAG = 4;
7939
+ var STRING_TAG = 5;
7940
+ var LIST_TAG = 6;
7941
+ var MAP_TAG = 7;
7942
+ var encoder = new TextEncoder();
7943
+ function ensureCapacity(stream, extra) {
7944
+ if (stream.length + extra <= stream.bytes.length) return;
7945
+ const grown = new Uint8Array(Math.max(stream.bytes.length * 2, stream.length + extra));
7946
+ grown.set(stream.bytes.subarray(0, stream.length));
7947
+ stream.bytes = grown;
7948
+ }
7949
+ function writeByte(stream, value) {
7950
+ ensureCapacity(stream, 1);
7951
+ stream.bytes[stream.length] = value;
7952
+ stream.length += 1;
7953
+ }
7954
+ function writeUint32(stream, value) {
7955
+ ensureCapacity(stream, 4);
7956
+ stream.bytes[stream.length] = value >>> 24 & 255;
7957
+ stream.bytes[stream.length + 1] = value >>> 16 & 255;
7958
+ stream.bytes[stream.length + 2] = value >>> 8 & 255;
7959
+ stream.bytes[stream.length + 3] = value & 255;
7960
+ stream.length += 4;
7961
+ }
7962
+ var numberBytes = new DataView(new ArrayBuffer(8));
7963
+ function writeNumber(stream, value) {
7964
+ numberBytes.setFloat64(0, value, false);
7965
+ ensureCapacity(stream, 8);
7966
+ for (let i = 0; i < 8; i++) {
7967
+ stream.bytes[stream.length + i] = numberBytes.getUint8(i);
7968
+ }
7969
+ stream.length += 8;
7970
+ }
7971
+ function writeString(stream, value) {
7972
+ const encoded = encoder.encode(value);
7973
+ writeUint32(stream, encoded.length);
7974
+ ensureCapacity(stream, encoded.length);
7975
+ stream.bytes.set(encoded, stream.length);
7976
+ stream.length += encoded.length;
7977
+ }
7978
+ function writeValue(stream, value) {
7979
+ if (value === void 0) {
7980
+ writeByte(stream, ABSENT_TAG);
7981
+ return;
7982
+ }
7983
+ if (value === null) {
7984
+ writeByte(stream, NULL_TAG);
7985
+ return;
7986
+ }
7987
+ if (typeof value === "boolean") {
7988
+ writeByte(stream, value ? TRUE_TAG : FALSE_TAG);
7989
+ return;
7990
+ }
7991
+ if (typeof value === "number") {
7992
+ writeByte(stream, NUMBER_TAG);
7993
+ writeNumber(stream, value);
7994
+ return;
7995
+ }
7996
+ if (typeof value === "string") {
7997
+ writeByte(stream, STRING_TAG);
7998
+ writeString(stream, value);
7999
+ return;
8000
+ }
8001
+ if (Array.isArray(value)) {
8002
+ writeByte(stream, LIST_TAG);
8003
+ writeUint32(stream, value.length);
8004
+ for (const member of value) {
8005
+ writeValue(stream, member ?? null);
8006
+ }
8007
+ return;
8008
+ }
8009
+ const record = value;
8010
+ const keys = Object.keys(record).sort(compareCodePoints);
8011
+ writeByte(stream, MAP_TAG);
8012
+ writeUint32(stream, keys.length);
8013
+ for (const key of keys) {
8014
+ writeByte(stream, STRING_TAG);
8015
+ writeString(stream, key);
8016
+ writeValue(stream, record[key] ?? null);
8017
+ }
8018
+ }
8019
+ function boundVectorOf(vector) {
8020
+ if (vector === void 0) return void 0;
8021
+ const bound = { ...vector };
8022
+ if (vector.value !== void 0) {
8023
+ bound.value = Array.from(vector.value, Math.fround);
8024
+ }
8025
+ if (vector.text !== void 0) {
8026
+ delete bound.value;
8027
+ }
8028
+ return bound;
8029
+ }
8030
+ var FILTERS_SLOT = 2;
8031
+ var BOUND_SLOT_COUNT = 15;
8032
+ function bindingOf(values) {
8033
+ const stream = { bytes: new Uint8Array(256), length: 0 };
8034
+ for (const value of values) {
8035
+ writeValue(stream, value);
8036
+ }
8037
+ return fnv1aBytes(stream.bytes.subarray(0, stream.length)).toString(16).padStart(8, "0");
8038
+ }
8039
+ function queryBindingOf(params) {
8040
+ return bindingOf([
8041
+ params.term,
8042
+ params.fields,
8043
+ params.filters,
8044
+ params.boost,
8045
+ params.minScore,
8046
+ params.termMatch,
8047
+ params.tolerance,
8048
+ params.prefixLength,
8049
+ params.prefix,
8050
+ params.exact,
8051
+ params.pinned,
8052
+ params.mode,
8053
+ params.hybrid,
8054
+ boundVectorOf(params.vector),
8055
+ params.scoring ?? "local"
8056
+ ]);
8057
+ }
8058
+ function listBindingOf(filters) {
8059
+ const values = new Array(BOUND_SLOT_COUNT).fill(void 0);
8060
+ values[FILTERS_SLOT] = filters;
8061
+ return bindingOf(values);
8062
+ }
8063
+
7839
8064
  // src/engine/list-documents/index.ts
7840
8065
  function clampListLimit(limit) {
7841
8066
  const clamped = Math.max(1, clampLimit(limit));
@@ -7870,7 +8095,7 @@ function collectCandidates(partition, anchor, matches, wanted, out) {
7870
8095
  taken++;
7871
8096
  }
7872
8097
  }
7873
- function pageInIdOrder(partitions, cursor, filtered, limit) {
8098
+ function pageInIdOrder(partitions, cursor, filtered, limit, binding) {
7874
8099
  const anchor = cursor === null ? null : cursor.anchor;
7875
8100
  const candidates = [];
7876
8101
  for (let index = 0; index < partitions.length; index++) {
@@ -7885,10 +8110,10 @@ function pageInIdOrder(partitions, cursor, filtered, limit) {
7885
8110
  candidates.sort(compareCodePoints);
7886
8111
  const hasMore = candidates.length > limit;
7887
8112
  const ids = hasMore ? candidates.slice(0, limit) : candidates;
7888
- const nextCursor = hasMore && ids.length > 0 ? { anchor: ids[ids.length - 1], score: null, sortKey: null, sortSignature: null } : null;
8113
+ const nextCursor = hasMore && ids.length > 0 ? { anchor: ids[ids.length - 1], score: null, sortKey: null, sortSignature: null, binding } : null;
7889
8114
  return { ids, nextCursor };
7890
8115
  }
7891
- function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, schema) {
8116
+ function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, schema, binding) {
7892
8117
  const normalized = normalizeSort(sort);
7893
8118
  const fields = normalized.map((entry) => entry.field);
7894
8119
  const directions = normalized.map((entry) => entry.direction);
@@ -7911,7 +8136,7 @@ function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, s
7911
8136
  const hasMore = merged.length > limit;
7912
8137
  const page = hasMore ? merged.slice(0, limit) : merged;
7913
8138
  const last = page[page.length - 1];
7914
- const nextCursor = hasMore && last !== void 0 ? { anchor: last.id, score: null, sortKey: last.key, sortSignature: signature } : null;
8139
+ const nextCursor = hasMore && last !== void 0 ? { anchor: last.id, score: null, sortKey: last.key, sortSignature: signature, binding } : null;
7915
8140
  return { ids: page.map((entry) => entry.id), nextCursor };
7916
8141
  }
7917
8142
  function countAcrossPartitions(manager, partitionIds, partitions) {
@@ -7930,15 +8155,16 @@ function executeListDocuments(params, context) {
7930
8155
  const limit = clampListLimit(params.limit);
7931
8156
  const signature = sortSignatureOf(params.sort);
7932
8157
  requireSortableFields(params.sort, schema);
8158
+ const binding = listBindingOf(params.filters);
7933
8159
  let cursor = null;
7934
8160
  if (params.cursor !== void 0) {
7935
8161
  cursor = decodePageCursor(params.cursor);
7936
- requireMatchingCursor(cursor, params.cursor, signature, false);
8162
+ requireMatchingCursor(cursor, params.cursor, signature, false, binding);
7937
8163
  }
7938
8164
  const partitions = context.partitionIds === void 0 ? manager.getAllPartitions() : context.partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
7939
8165
  const filtered = params.filters === void 0 ? null : collectFilterMatches(partitions, params.filters, schema);
7940
8166
  const total = filtered === null ? countAcrossPartitions(manager, context.partitionIds, partitions) : filtered.total;
7941
- const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema);
8167
+ const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit, binding) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema, binding);
7942
8168
  const projection = resolveProjection(params.document);
7943
8169
  const documents = [];
7944
8170
  for (const docId of page.ids) {
@@ -8006,18 +8232,14 @@ function applyGrouping(hits, group, getDocument) {
8006
8232
  }
8007
8233
  const result = { values: entry.values, hits: groupHits };
8008
8234
  if (group.reduce) {
8009
- try {
8010
- let accumulator = group.reduce.initialValue();
8011
- for (const hit of groupHits) {
8012
- const doc = getDocument(hit.id);
8013
- if (doc) {
8014
- accumulator = group.reduce.reducer(accumulator, doc, hit.score ?? 0);
8015
- }
8016
- }
8017
- ;
8018
- result.reduced = accumulator;
8019
- } catch (err) {
8020
- result.reducerError = err instanceof Error ? err.message : String(err);
8235
+ const folded = foldGroupReducer(
8236
+ group.reduce,
8237
+ groupHits.map((hit) => ({ document: getDocument(hit.id), score: hit.score ?? 0 }))
8238
+ );
8239
+ if (folded.reducerError !== void 0) {
8240
+ result.reducerError = folded.reducerError;
8241
+ } else {
8242
+ result.reduced = folded.reduced;
8021
8243
  }
8022
8244
  }
8023
8245
  groups.push(result);
@@ -8030,19 +8252,49 @@ function applyGrouping(hits, group, getDocument) {
8030
8252
  const bId = b.hits.length > 0 ? b.hits[0].id : "";
8031
8253
  return compareCodePoints(aId, bId);
8032
8254
  });
8255
+ if (group.limit !== void 0 && Number.isFinite(group.limit) && group.limit >= 0) {
8256
+ const cap = Math.floor(group.limit);
8257
+ if (groups.length > cap) {
8258
+ groups.length = cap;
8259
+ }
8260
+ }
8033
8261
  return groups;
8034
8262
  }
8263
+ function foldGroupReducer(reduce, rows) {
8264
+ try {
8265
+ let accumulator = reduce.initialValue();
8266
+ for (const row of rows) {
8267
+ if (row.document !== void 0) {
8268
+ accumulator = reduce.reducer(accumulator, row.document, row.score);
8269
+ }
8270
+ }
8271
+ return { reduced: accumulator };
8272
+ } catch (err) {
8273
+ return { reducerError: err instanceof Error ? err.message : String(err) };
8274
+ }
8275
+ }
8035
8276
 
8036
8277
  // src/search/pinning.ts
8278
+ function dedupePinnedEntries(pinned) {
8279
+ const deduped = [];
8280
+ const seen = /* @__PURE__ */ new Set();
8281
+ for (const entry of pinned) {
8282
+ if (seen.has(entry.docId)) continue;
8283
+ seen.add(entry.docId);
8284
+ deduped.push(entry);
8285
+ }
8286
+ return deduped;
8287
+ }
8037
8288
  function applyPinning(hits, pinned, resolveDoc) {
8038
8289
  const result = hits.slice();
8039
- const pinnedDocIds = new Set(pinned.map((p) => p.docId));
8290
+ const deduped = dedupePinnedEntries(pinned);
8291
+ const pinnedDocIds = new Set(deduped.map((entry) => entry.docId));
8040
8292
  for (let i = result.length - 1; i >= 0; i--) {
8041
8293
  if (pinnedDocIds.has(result[i].id)) {
8042
8294
  result.splice(i, 1);
8043
8295
  }
8044
8296
  }
8045
- const sorted = pinned.slice().sort((a, b) => a.position - b.position);
8297
+ const sorted = deduped.sort((a, b) => a.position - b.position);
8046
8298
  for (const entry of sorted) {
8047
8299
  const doc = resolveDoc(entry.docId);
8048
8300
  if (!doc) continue;
@@ -8236,17 +8488,7 @@ function applyHighlights(hits, params, language, analysis) {
8236
8488
 
8237
8489
  // src/engine/query/shared.ts
8238
8490
  function partitionsFor(manager, partitionIds) {
8239
- if (partitionIds === void 0) {
8240
- return manager.getAllPartitions();
8241
- }
8242
- const partitions = [];
8243
- for (const partitionId of partitionIds) {
8244
- const partition = manager.partitionAt(partitionId);
8245
- if (partition !== void 0) {
8246
- partitions.push(partition);
8247
- }
8248
- }
8249
- return partitions;
8491
+ return partitionsIn(manager, partitionIds);
8250
8492
  }
8251
8493
  function coverageFor(manager, partitionIds) {
8252
8494
  if (partitionIds === void 0) {
@@ -8340,7 +8582,7 @@ function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
8340
8582
  let anchorId = null;
8341
8583
  if (params.searchAfter !== void 0) {
8342
8584
  const decoded = decodePageCursor(params.searchAfter);
8343
- requireMatchingCursor(decoded, params.searchAfter, sortSignature, true);
8585
+ requireMatchingCursor(decoded, params.searchAfter, sortSignature, true, context.cursorBinding);
8344
8586
  anchorKey = decoded.sortKey;
8345
8587
  anchorId = decoded.anchor;
8346
8588
  }
@@ -8385,7 +8627,13 @@ function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
8385
8627
  const last = page[page.length - 1];
8386
8628
  let cursor;
8387
8629
  if (hasMore && last !== void 0) {
8388
- cursor = encodePageCursor({ anchor: last.id, score: null, sortKey: last.key, sortSignature });
8630
+ cursor = encodePageCursor({
8631
+ anchor: last.id,
8632
+ score: null,
8633
+ sortKey: last.key,
8634
+ sortSignature,
8635
+ binding: context.cursorBinding
8636
+ });
8389
8637
  }
8390
8638
  return {
8391
8639
  hits: page.map((entry) => ({ id: entry.id, document: void 0 })),
@@ -8671,7 +8919,7 @@ async function executeQuery(params, context) {
8671
8919
  if (params.group) {
8672
8920
  groups = applyGrouping(hits, params.group, (docId) => manager.getRef(docId));
8673
8921
  }
8674
- if (params.pinned) {
8922
+ if (params.pinned && params.searchAfter === void 0 && context.partitionIds === void 0) {
8675
8923
  hits = applyPinning(hits, params.pinned, (docId) => {
8676
8924
  const doc = manager.getRef(docId);
8677
8925
  if (!doc) return void 0;
@@ -8686,7 +8934,16 @@ async function executeQuery(params, context) {
8686
8934
  sortKeyOf
8687
8935
  };
8688
8936
  }
8689
- const paged = applyPagination(hits, limit, offset, params.searchAfter, sortContext);
8937
+ const pinnedIds = params.pinned !== void 0 && params.searchAfter === void 0 ? new Set(params.pinned.map((entry) => entry.docId)) : void 0;
8938
+ const paged = applyPagination(
8939
+ hits,
8940
+ limit,
8941
+ offset,
8942
+ context.cursorBinding,
8943
+ params.searchAfter,
8944
+ sortContext,
8945
+ pinnedIds
8946
+ );
8690
8947
  paginated = paged.paginated;
8691
8948
  nextCursor = paged.nextCursor;
8692
8949
  count = fanOutResult.totalMatched;
@@ -8755,6 +9012,11 @@ async function executePreflight(params, context) {
8755
9012
  } else if (isHybridMode && hasGlobalVectorIndex) {
8756
9013
  const result = await executeHybridSearch(params, context, preflightLimit, preflightOffset);
8757
9014
  totalMatched = result.totalMatched;
9015
+ } else if (countsWithoutScores(params)) {
9016
+ totalMatched = fanOutMatchCount(manager, params, language, config.schema, {
9017
+ searchOptions: searchOptionsFor(manager),
9018
+ partitionIds: context.partitionIds
9019
+ });
8758
9020
  } else {
8759
9021
  const scoring = scoringConfigFor(params, context);
8760
9022
  const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
@@ -8908,7 +9170,8 @@ async function runEngineQuery(core, indexName, params, options) {
8908
9170
  workerSearch,
8909
9171
  indexName,
8910
9172
  broadcastStats: broadcastStatsFor(core, options),
8911
- partitionIds: options?.partitionIds
9173
+ partitionIds: options?.partitionIds,
9174
+ cursorBinding: queryBindingOf(params)
8912
9175
  });
8913
9176
  try {
8914
9177
  await core.pluginRegistry.runHook("afterSearch", {
@@ -8942,7 +9205,8 @@ async function runEnginePreflight(core, indexName, params, options) {
8942
9205
  workerSearch,
8943
9206
  indexName,
8944
9207
  broadcastStats: broadcastStatsFor(core, options),
8945
- partitionIds: options?.partitionIds
9208
+ partitionIds: options?.partitionIds,
9209
+ cursorBinding: queryBindingOf(params)
8946
9210
  });
8947
9211
  if (core.analysisRebuild.isStale(indexName)) {
8948
9212
  result.analysisStale = true;
@@ -9253,4 +9517,4 @@ async function createNarsil(config) {
9253
9517
  // src/index.ts
9254
9518
  var VERSION = "0.1.0";
9255
9519
 
9256
- export { DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, VERSION, WIRE_BATCH_BUDGET, applyDeleteEntry, applyIndexEntry, chunkByBudget, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, now, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
9520
+ export { DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, VERSION, WIRE_BATCH_BUDGET, applyDeleteEntry, applyIndexEntry, chunkByBudget, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, dedupePinnedEntries, foldGroupReducer, listBindingOf, now, queryBindingOf, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from './chunk-GB2IIJJG.mjs';
1
+ import { NarsilError, ErrorCodes } from './chunk-TLRUUMVE.mjs';
2
2
  import { encode, decode } from '@msgpack/msgpack';
3
3
 
4
4
  var INDEX_CONFIG_PREFIX = "_narsil/index/";
@@ -1,7 +1,6 @@
1
1
  // src/errors.ts
2
2
  var ErrorCodes = {
3
3
  SCHEMA_INVALID_TYPE: "SCHEMA_INVALID_TYPE",
4
- SCHEMA_MISSING_FIELD: "SCHEMA_MISSING_FIELD",
5
4
  SCHEMA_DEPTH_EXCEEDED: "SCHEMA_DEPTH_EXCEEDED",
6
5
  SCHEMA_INVALID_VECTOR_DIMENSION: "SCHEMA_INVALID_VECTOR_DIMENSION",
7
6
  SCHEMA_INVALID_GEOPOINT: "SCHEMA_INVALID_GEOPOINT",
@@ -23,7 +22,6 @@ var ErrorCodes = {
23
22
  PERSISTENCE_WAL_CORRUPT: "PERSISTENCE_WAL_CORRUPT",
24
23
  PERSISTENCE_FSYNC_FAILED: "PERSISTENCE_FSYNC_FAILED",
25
24
  SEARCH_INVALID_FIELD: "SEARCH_INVALID_FIELD",
26
- SEARCH_INVALID_VECTOR_SIZE: "SEARCH_INVALID_VECTOR_SIZE",
27
25
  VECTOR_DIMENSION_MISMATCH: "VECTOR_DIMENSION_MISMATCH",
28
26
  SEARCH_INVALID_FILTER: "SEARCH_INVALID_FILTER",
29
27
  SEARCH_INVALID_MODE: "SEARCH_INVALID_MODE",
@@ -47,8 +45,6 @@ var ErrorCodes = {
47
45
  ALLOCATION_NO_DATA_NODES: "ALLOCATION_NO_DATA_NODES",
48
46
  ALLOCATION_INVALID_CONFIG: "ALLOCATION_INVALID_CONFIG",
49
47
  ALLOCATION_FAILED: "ALLOCATION_FAILED",
50
- CONTROLLER_LEASE_LOST: "CONTROLLER_LEASE_LOST",
51
- CONTROLLER_NOT_ACTIVE: "CONTROLLER_NOT_ACTIVE",
52
48
  CONTROLLER_METADATA_INVALID: "CONTROLLER_METADATA_INVALID",
53
49
  NODE_BOOTSTRAP_FAILED: "NODE_BOOTSTRAP_FAILED",
54
50
  NODE_ALREADY_JOINED: "NODE_ALREADY_JOINED",
@@ -104,6 +100,7 @@ var ServerErrorCodes = {
104
100
  TASK_NOT_FOUND: "TASK_NOT_FOUND",
105
101
  TASK_NOT_CANCELLABLE: "TASK_NOT_CANCELLABLE",
106
102
  TASK_OWNED_BY_ANOTHER_INSTANCE: "TASK_OWNED_BY_ANOTHER_INSTANCE",
103
+ TASK_INTERRUPTED: "TASK_INTERRUPTED",
107
104
  TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
108
105
  HOOK_ERROR: "HOOK_ERROR",
109
106
  INTERNAL_ERROR: "INTERNAL_ERROR"