@delali/narsil 0.2.3-next.34 → 0.2.3-next.36

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 (40) 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/{chunk-EMI63EXP.mjs → chunk-GB2IIJJG.mjs} +2 -0
  5. package/dist/{chunk-RO4EFWE6.mjs → chunk-GRWFY2HI.mjs} +10 -1
  6. package/dist/{chunk-36ER4WCG.mjs → chunk-JQUL2T6I.mjs} +2 -1
  7. package/dist/{chunk-GN7KF675.mjs → chunk-MMSWEEZT.mjs} +88 -164
  8. package/dist/{chunk-GODECMDT.mjs → chunk-PM52SMBT.mjs} +1 -1
  9. package/dist/chunk-WU2UZEVD.mjs +137 -0
  10. package/dist/{client-DQHB_swQ.d.ts → client-BobVnoIK.d.ts} +2 -2
  11. package/dist/client.d.ts +8 -4
  12. package/dist/client.mjs +2 -0
  13. package/dist/{config-ChZ7lfqc.d.ts → config-Byyl1QqH.d.ts} +2 -2
  14. package/dist/config-fLC4lbmg.d.ts +194 -0
  15. package/dist/{direct-executor-YARCDMJF.mjs → direct-executor-MSJEVPZU.mjs} +2 -2
  16. package/dist/distribution/coordinator/etcd.d.ts +5 -7
  17. package/dist/distribution/coordinator/etcd.mjs +27 -8
  18. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  19. package/dist/distribution/transport/grpc.mjs +3 -3
  20. package/dist/distribution/transport/in-memory.mjs +1 -1
  21. package/dist/distribution/transport/tcp.mjs +2 -2
  22. package/dist/distribution.d.ts +10 -108
  23. package/dist/distribution.mjs +1328 -370
  24. package/dist/embeddings/openai.mjs +1 -1
  25. package/dist/{engine-DScGlT2Z.d.ts → engine-BE45D5ZG.d.ts} +3 -3
  26. package/dist/index.browser.mjs +90 -24
  27. package/dist/index.d.ts +7 -7
  28. package/dist/index.mjs +5 -4
  29. package/dist/{options-BORJg5XB.d.ts → options-EXYFcem-.d.ts} +9 -0
  30. package/dist/persistence/durability/checkpoint-worker.d.ts +3 -1
  31. package/dist/persistence/durability/checkpoint-worker.mjs +5 -4
  32. package/dist/react.d.ts +4 -4
  33. package/dist/react.mjs +2 -0
  34. package/dist/{schema-emTQfEiX.d.ts → schema-ZRbk8BgH.d.ts} +1 -0
  35. package/dist/{search-CQ-9bKxo.d.ts → search-DHuXwJYk.d.ts} +29 -2
  36. package/dist/server.d.ts +9 -5
  37. package/dist/server.mjs +101 -7
  38. package/dist/{types-BhdINMtE.d.ts → types-B5Tsaalz.d.ts} +19 -2
  39. package/dist/workers/entry.mjs +2 -2
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -79,7 +79,7 @@ const results = await narsil.query('products', {
79
79
  })
80
80
  ```
81
81
 
82
- Every hit holds the document, its id, and its BM25 score. `results.count` reports how many documents matched in total, and `results.elapsed` reports the query time in milliseconds.
82
+ Every hit holds the document, its id, and its BM25 score. `results.count` reports how many documents matched in total, `results.elapsed` reports the query time in milliseconds, and `results.coverage` reports how many partitions the search read, which is how you spot a cluster that answered from part of its data.
83
83
 
84
84
  ## Features
85
85
 
@@ -129,7 +129,7 @@ The [specification](../spec/) defines the `.nrsl` format, the analysis pipeline,
129
129
 
130
130
  ## Distribution
131
131
 
132
- `@delali/narsil/distribution` holds Narsil's multi-node cluster mode: nodes and roles, replication, coordinator adapters for etcd and in-process testing, TCP and gRPC transports with mutual TLS, and distributed query routing. A cluster node creates, drops, clears, writes, updates, searches, lists, counts, and suggests across every partition, and the [cluster example](examples/cluster) runs three node processes against etcd and survives a kill. The layer is experimental and its APIs change without notice, so pin an exact version before you depend on it. The [cluster guide](../../docs/cluster.md) covers the surface, and [`packages/spec/distribution`](../spec/distribution) specifies the contract every implementation follows.
132
+ `@delali/narsil/distribution` holds Narsil's multi-node cluster mode: nodes and roles, replication, coordinator adapters for etcd and in-process testing, TCP and gRPC transports with mutual TLS, and distributed query routing. A cluster node creates, drops, clears, writes, updates, searches, lists, counts, and suggests across every partition, and the [cluster dashboard example](examples/cluster-dashboard) runs three nodes against etcd and shows what each partition does while you cut the links between them. The layer is experimental and its APIs change without notice, so pin an exact version before you depend on it. The [cluster guide](../../docs/cluster.md) covers the surface, and [`packages/spec/distribution`](../spec/distribution) specifies the contract every implementation follows.
133
133
 
134
134
  ## Search quality
135
135
 
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from '../chunk-EMI63EXP.mjs';
1
+ import { NarsilError, ErrorCodes } from '../chunk-GB2IIJJG.mjs';
2
2
 
3
3
  // src/persistence/filesystem.ts
4
4
  var cachedFs = null;
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from '../chunk-EMI63EXP.mjs';
1
+ import { NarsilError, ErrorCodes } from '../chunk-GB2IIJJG.mjs';
2
2
 
3
3
  // src/persistence/indexeddb.ts
4
4
  var DEFAULT_DB_NAME = "narsil";
@@ -53,6 +53,8 @@ var ErrorCodes = {
53
53
  NODE_BOOTSTRAP_FAILED: "NODE_BOOTSTRAP_FAILED",
54
54
  NODE_ALREADY_JOINED: "NODE_ALREADY_JOINED",
55
55
  NODE_NOT_JOINED: "NODE_NOT_JOINED",
56
+ NODE_NOT_READY: "NODE_NOT_READY",
57
+ NODE_NOT_CONTROLLER: "NODE_NOT_CONTROLLER",
56
58
  COORDINATOR_DEPENDENCY_MISSING: "COORDINATOR_DEPENDENCY_MISSING",
57
59
  TRANSPORT_DEPENDENCY_MISSING: "TRANSPORT_DEPENDENCY_MISSING",
58
60
  REPLICATION_ENTRY_INVALID: "REPLICATION_ENTRY_INVALID",
@@ -3,7 +3,7 @@ import { createScalarQuantizer } from './chunk-OBXS7OZM.mjs';
3
3
  import { MAX_M, createVectorStore, createHNSWIndex, createOrdinalFilter, addToOrdinalFilter, removeFromOrdinalFilter, ordinalFilterValues, magnitude, createBoundedMaxHeap, toScore, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes, ordinalFilterHas, ABSENT_DOCUMENT_RANK } from './chunk-WDL632CN.mjs';
4
4
  import { compareCodePoints, compareComparableKeys, toComparableSortValue, readSortField, compareSortValues, compareComparableValues, SORT_VALUE_MAX_CODE_POINTS } from './chunk-BFIH7OV5.mjs';
5
5
  import { generateId, createRequestId } from './chunk-WTONLDU3.mjs';
6
- import { NarsilError, ErrorCodes, createNarsilError } from './chunk-EMI63EXP.mjs';
6
+ import { NarsilError, ErrorCodes, createNarsilError } from './chunk-GB2IIJJG.mjs';
7
7
  import { encode, decode } from '@msgpack/msgpack';
8
8
 
9
9
  // src/schema/validator/shared.ts
@@ -552,6 +552,9 @@ function metadataToWire(meta) {
552
552
  if (meta.indexUuid !== void 0) {
553
553
  wire.index_uuid = meta.indexUuid;
554
554
  }
555
+ if (meta.heldPartitions !== void 0) {
556
+ wire.held_partitions = meta.heldPartitions;
557
+ }
555
558
  return wire;
556
559
  }
557
560
  function wireToMetadata(raw) {
@@ -641,8 +644,14 @@ function wireToMetadata(raw) {
641
644
  if (typeof raw.index_uuid === "string" && raw.index_uuid.length > 0) {
642
645
  meta.indexUuid = raw.index_uuid;
643
646
  }
647
+ if (Array.isArray(raw.held_partitions) && raw.held_partitions.every(isNonNegativeInteger)) {
648
+ meta.heldPartitions = raw.held_partitions;
649
+ }
644
650
  return meta;
645
651
  }
652
+ function isNonNegativeInteger(value) {
653
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
654
+ }
646
655
  function isPositiveInteger2(value) {
647
656
  return typeof value === "number" && Number.isInteger(value) && value >= 1;
648
657
  }
@@ -58,7 +58,8 @@ var QueryMessageTypes = {
58
58
  var ClusterMessageTypes = {
59
59
  PING: "cluster.ping",
60
60
  PONG: "cluster.pong",
61
- BOOTSTRAP_COMPLETE: "cluster.bootstrap_complete"
61
+ BOOTSTRAP_COMPLETE: "cluster.bootstrap_complete",
62
+ PARTITION_STORES: "cluster.partition_stores"
62
63
  };
63
64
 
64
65
  export { ClusterMessageTypes, DEFAULT_TRANSPORT_CONFIG, MAX_MESSAGE_SIZE_BYTES, QueryMessageTypes, ReplicationMessageTypes, TransportError, TransportErrorCodes };
@@ -1,9 +1,10 @@
1
- import { ReplicationMessageTypes } from './chunk-36ER4WCG.mjs';
2
- 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, serializeMetadata, isNodeMainThread, spawnNodeWorker, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, deserializeMetadata, isCompositePartition, kWayMerge } from './chunk-RO4EFWE6.mjs';
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';
3
4
  import { crc32, crc32Init, crc32Update, crc32Final } from './chunk-KMMJMDUA.mjs';
4
5
  import { compareCodePoints, compareComparableKeys } from './chunk-BFIH7OV5.mjs';
5
6
  import { generateId, createRequestId, isValidWorkerResponse } from './chunk-WTONLDU3.mjs';
6
- import { ErrorCodes, NarsilError } from './chunk-EMI63EXP.mjs';
7
+ import { ErrorCodes, NarsilError } from './chunk-GB2IIJJG.mjs';
7
8
  import { encode, decode } from '@msgpack/msgpack';
8
9
 
9
10
  // src/partitioning/rebalancer.ts
@@ -3751,6 +3752,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
3751
3752
  const mode = config.mode ?? "sync";
3752
3753
  const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_ASYNC_FLUSH_INTERVAL_MS;
3753
3754
  const indexes = /* @__PURE__ */ new Map();
3755
+ const metadataWrites = /* @__PURE__ */ new Map();
3754
3756
  let checkpointTimer = null;
3755
3757
  let asyncFlushTimer = null;
3756
3758
  let shuttingDown = false;
@@ -3803,6 +3805,15 @@ function createDurabilityManager(config, hooks, directoryOverride) {
3803
3805
  }
3804
3806
  return partition;
3805
3807
  }
3808
+ function queueMetadataWrite(indexName, write) {
3809
+ const previous = metadataWrites.get(indexName) ?? Promise.resolve();
3810
+ const queued = previous.then(write);
3811
+ metadataWrites.set(
3812
+ indexName,
3813
+ queued.catch(() => void 0)
3814
+ );
3815
+ return queued;
3816
+ }
3806
3817
  function startCheckpointTimer() {
3807
3818
  if (checkpointTimer !== null || shuttingDown || fatalError !== null || checkpointIntervalMs <= 0) {
3808
3819
  return;
@@ -3960,6 +3971,9 @@ function createDurabilityManager(config, hooks, directoryOverride) {
3960
3971
  }
3961
3972
  startCheckpointTimer();
3962
3973
  },
3974
+ highestPersistedSeqNo(indexName, partitionId) {
3975
+ return indexes.get(indexName)?.partitions.get(partitionId)?.appliedSeqNo ?? 0;
3976
+ },
3963
3977
  async recordMutation(record) {
3964
3978
  if (fatalError !== null) {
3965
3979
  throw fatalError;
@@ -4007,14 +4021,16 @@ function createDurabilityManager(config, hooks, directoryOverride) {
4007
4021
  }
4008
4022
  return allocatedSeqNo;
4009
4023
  },
4010
- async persistMetadata(indexName) {
4011
- const metadata = hooks.buildMetadata(indexName);
4012
- if (metadata === void 0) {
4013
- return;
4014
- }
4015
- const bytes = await writeMetadataEnvelope(metadata, { checksum: true });
4016
- await directory.atomicWrite(`${indexName}/meta`, bytes);
4017
- getOrCreateIndexState(indexName);
4024
+ persistMetadata(indexName) {
4025
+ return queueMetadataWrite(indexName, async () => {
4026
+ const metadata = hooks.buildMetadata(indexName);
4027
+ if (metadata === void 0) {
4028
+ return;
4029
+ }
4030
+ const bytes = await writeMetadataEnvelope(metadata, { checksum: true });
4031
+ await directory.atomicWrite(`${indexName}/meta`, bytes);
4032
+ getOrCreateIndexState(indexName);
4033
+ });
4018
4034
  },
4019
4035
  checkpoint(indexName) {
4020
4036
  return checkpointIndex(indexName);
@@ -4071,6 +4087,7 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
4071
4087
  const checkpointIntervalMs = config.checkpointIntervalMs ?? DEFAULT_CHECKPOINT_INTERVAL_MS;
4072
4088
  const checkpointMutationThreshold = config.checkpointMutationThreshold ?? DEFAULT_CHECKPOINT_MUTATION_THRESHOLD;
4073
4089
  const indexes = /* @__PURE__ */ new Map();
4090
+ const metadataWrites = /* @__PURE__ */ new Map();
4074
4091
  let checkpointTimer = null;
4075
4092
  let shuttingDown = false;
4076
4093
  function getOrCreateIndexState(indexName) {
@@ -4149,6 +4166,15 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
4149
4166
  indexState.checkpointInFlight = run;
4150
4167
  return run;
4151
4168
  }
4169
+ function queueMetadataWrite(indexName, write) {
4170
+ const previous = metadataWrites.get(indexName) ?? Promise.resolve();
4171
+ const queued = previous.then(write);
4172
+ metadataWrites.set(
4173
+ indexName,
4174
+ queued.catch(() => void 0)
4175
+ );
4176
+ return queued;
4177
+ }
4152
4178
  async function recoverIndex(indexName) {
4153
4179
  const metaBytes = await adapter.load(metadataKey(indexName));
4154
4180
  if (metaBytes === null) {
@@ -4183,6 +4209,9 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
4183
4209
  }
4184
4210
  startCheckpointTimer();
4185
4211
  },
4212
+ highestPersistedSeqNo() {
4213
+ return 0;
4214
+ },
4186
4215
  async recordMutation(record) {
4187
4216
  const indexState = getOrCreateIndexState(record.indexName);
4188
4217
  const buffered = indexState.applyChain.then(() => record.apply());
@@ -4197,14 +4226,16 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
4197
4226
  }
4198
4227
  return 0;
4199
4228
  },
4200
- async persistMetadata(indexName) {
4201
- const metadata = hooks.buildMetadata(indexName);
4202
- if (metadata === void 0) {
4203
- return;
4204
- }
4205
- const bytes = await writeMetadataEnvelope(metadata, { checksum: true });
4206
- await adapter.save(metadataKey(indexName), bytes);
4207
- getOrCreateIndexState(indexName);
4229
+ persistMetadata(indexName) {
4230
+ return queueMetadataWrite(indexName, async () => {
4231
+ const metadata = hooks.buildMetadata(indexName);
4232
+ if (metadata === void 0) {
4233
+ return;
4234
+ }
4235
+ const bytes = await writeMetadataEnvelope(metadata, { checksum: true });
4236
+ await adapter.save(metadataKey(indexName), bytes);
4237
+ getOrCreateIndexState(indexName);
4238
+ });
4208
4239
  },
4209
4240
  checkpoint(indexName) {
4210
4241
  return checkpointIndex(indexName);
@@ -4297,6 +4328,9 @@ function buildMetadata(indexName, hooks) {
4297
4328
  if (config.indexUuid !== void 0) {
4298
4329
  metadata.indexUuid = config.indexUuid;
4299
4330
  }
4331
+ if (config.heldPartitions !== void 0) {
4332
+ metadata.heldPartitions = config.heldPartitions;
4333
+ }
4300
4334
  return metadata;
4301
4335
  }
4302
4336
  function createDurabilityIntegration(tier, hooks) {
@@ -4358,6 +4392,7 @@ function createDurabilityFromTier(tier, wiring) {
4358
4392
  } : void 0;
4359
4393
  return {
4360
4394
  ...entry.indexUuid !== null ? { indexUuid: entry.indexUuid } : {},
4395
+ ...entry.heldPartitions !== null ? { heldPartitions: entry.heldPartitions } : {},
4361
4396
  schema: flattenSchema(entry.config.schema),
4362
4397
  language: entry.language.name,
4363
4398
  k1: entry.config.bm25?.k1 ?? 1.2,
@@ -5476,7 +5511,8 @@ function createEngineCore(config) {
5476
5511
  embeddingAdapter,
5477
5512
  embeddingAdapterName: adapterName,
5478
5513
  vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
5479
- indexUuid: metadata.indexUuid ?? null
5514
+ indexUuid: metadata.indexUuid ?? null,
5515
+ heldPartitions: metadata.heldPartitions ?? null
5480
5516
  });
5481
5517
  if (metadata.analysisRevision !== language.revision) {
5482
5518
  analysisRebuild.markStale({
@@ -5602,141 +5638,9 @@ function createEngineCore(config) {
5602
5638
  rebalanceCtx
5603
5639
  };
5604
5640
  }
5605
- var INDEX_CONFIG_PREFIX = "_narsil/index/";
5606
- var INDEX_CONFIG_SUFFIX = "/config";
5607
- var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
5608
- var MAX_INDEX_NAME_LENGTH = 255;
5609
- var MAX_PARTITION_COUNT = 65536;
5610
- var MAX_REPLICATION_FACTOR = 255;
5611
- function truncateForDisplay(value) {
5612
- const str = String(value);
5613
- if (str.length > 100) {
5614
- return `${str.slice(0, 100)}...`;
5615
- }
5616
- return str;
5617
- }
5618
- function validateIndexName(indexName) {
5619
- if (indexName.length === 0 || indexName.length > MAX_INDEX_NAME_LENGTH) {
5620
- throw new NarsilError(
5621
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5622
- `Index name must be between 1 and ${MAX_INDEX_NAME_LENGTH} characters`,
5623
- { indexName: truncateForDisplay(indexName) }
5624
- );
5625
- }
5626
- if (indexName.includes("\0") || indexName.includes("/") || indexName.includes("\\") || indexName.includes("..")) {
5627
- throw new NarsilError(
5628
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5629
- "Index name contains forbidden characters (/, \\, .., or null bytes)",
5630
- { indexName: truncateForDisplay(indexName) }
5631
- );
5632
- }
5633
- if (!INDEX_NAME_PATTERN.test(indexName)) {
5634
- throw new NarsilError(
5635
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5636
- "Index name must start with an alphanumeric character and contain only alphanumeric characters, hyphens, underscores, or dots",
5637
- { indexName: truncateForDisplay(indexName) }
5638
- );
5639
- }
5640
- }
5641
- function isValidInteger(value) {
5642
- return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
5643
- }
5644
- function indexConfigKey(indexName) {
5645
- return `${INDEX_CONFIG_PREFIX}${indexName}${INDEX_CONFIG_SUFFIX}`;
5646
- }
5647
- function isRecord2(value) {
5648
- return value !== null && typeof value === "object" && !Array.isArray(value);
5649
- }
5650
- function validateDecodedMetadata(decoded, indexName) {
5651
- if (!isRecord2(decoded)) {
5652
- throw new NarsilError(
5653
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5654
- `Index metadata for '${indexName}' is not an object`,
5655
- { indexName }
5656
- );
5657
- }
5658
- if (typeof decoded.indexUuid !== "string" || decoded.indexUuid.length === 0 || decoded.indexUuid.length > 128) {
5659
- throw new NarsilError(
5660
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5661
- `Index metadata for '${indexName}' has invalid indexUuid`,
5662
- { indexName, received: truncateForDisplay(decoded.indexUuid) }
5663
- );
5664
- }
5665
- if (typeof decoded.indexName !== "string") {
5666
- throw new NarsilError(
5667
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5668
- `Index metadata for '${indexName}' has invalid indexName`,
5669
- { indexName, received: truncateForDisplay(decoded.indexName) }
5670
- );
5671
- }
5672
- if (!isValidInteger(decoded.partitionCount) || decoded.partitionCount <= 0 || decoded.partitionCount > MAX_PARTITION_COUNT) {
5673
- throw new NarsilError(
5674
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5675
- `Index metadata for '${indexName}' has invalid partitionCount (must be an integer between 1 and ${MAX_PARTITION_COUNT})`,
5676
- { indexName, partitionCount: truncateForDisplay(decoded.partitionCount) }
5677
- );
5678
- }
5679
- if (!isValidInteger(decoded.replicationFactor) || decoded.replicationFactor < 0 || decoded.replicationFactor > MAX_REPLICATION_FACTOR) {
5680
- throw new NarsilError(
5681
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5682
- `Index metadata for '${indexName}' has invalid replicationFactor (must be an integer between 0 and ${MAX_REPLICATION_FACTOR})`,
5683
- { indexName, replicationFactor: truncateForDisplay(decoded.replicationFactor) }
5684
- );
5685
- }
5686
- if (!isRecord2(decoded.constraints)) {
5687
- throw new NarsilError(
5688
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5689
- `Index metadata for '${indexName}' has invalid constraints`,
5690
- { indexName }
5691
- );
5692
- }
5693
- const constraints = decoded.constraints;
5694
- if (constraints.maxShardsPerNode !== void 0 && constraints.maxShardsPerNode !== null && typeof constraints.maxShardsPerNode !== "number") {
5695
- throw new NarsilError(
5696
- ErrorCodes.CONTROLLER_METADATA_INVALID,
5697
- `Index metadata for '${indexName}' has invalid maxShardsPerNode (must be a number or null)`,
5698
- { indexName, maxShardsPerNode: truncateForDisplay(constraints.maxShardsPerNode) }
5699
- );
5700
- }
5701
- return {
5702
- indexUuid: decoded.indexUuid,
5703
- indexName: decoded.indexName,
5704
- partitionCount: decoded.partitionCount,
5705
- replicationFactor: decoded.replicationFactor,
5706
- constraints: {
5707
- zoneAwareness: constraints.zoneAwareness === true,
5708
- zoneAttribute: typeof constraints.zoneAttribute === "string" ? constraints.zoneAttribute : "zone",
5709
- maxShardsPerNode: typeof constraints.maxShardsPerNode === "number" ? constraints.maxShardsPerNode : null
5710
- }
5711
- };
5712
- }
5713
- async function putIndexMetadata(coordinator, metadata) {
5714
- validateIndexName(metadata.indexName);
5715
- const key = indexConfigKey(metadata.indexName);
5716
- const encoded = encode(metadata);
5717
- const bytes = new Uint8Array(encoded);
5718
- if (await coordinator.compareAndSet(key, null, bytes)) {
5719
- return true;
5720
- }
5721
- const current = await coordinator.get(key);
5722
- if (current === null || current.byteLength > 0) {
5723
- return false;
5724
- }
5725
- return coordinator.compareAndSet(key, current, bytes);
5726
- }
5727
- async function getIndexMetadata(coordinator, indexName) {
5728
- validateIndexName(indexName);
5729
- const key = indexConfigKey(indexName);
5730
- const raw = await coordinator.get(key);
5731
- if (raw === null || raw.byteLength === 0) {
5732
- return null;
5733
- }
5734
- const decoded = decode(raw);
5735
- return validateDecodedMetadata(decoded, indexName);
5736
- }
5737
5641
 
5738
5642
  // src/engine/validation.ts
5739
- var INDEX_NAME_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
5643
+ var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
5740
5644
  var MAX_DOC_ID_LENGTH = 512;
5741
5645
  var BATCH_CHUNK_SIZE = 1e3;
5742
5646
  var DEFAULT_LIMIT = DEFAULT_PAGE_SIZE;
@@ -5747,7 +5651,7 @@ function now() {
5747
5651
  }
5748
5652
  return Date.now();
5749
5653
  }
5750
- function validateIndexName2(name) {
5654
+ function validateIndexName(name) {
5751
5655
  if (!name || name.length === 0) {
5752
5656
  throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
5753
5657
  }
@@ -5761,7 +5665,7 @@ function validateIndexName2(name) {
5761
5665
  }
5762
5666
  );
5763
5667
  }
5764
- if (!INDEX_NAME_PATTERN2.test(name)) {
5668
+ if (!INDEX_NAME_PATTERN.test(name)) {
5765
5669
  throw new NarsilError(
5766
5670
  ErrorCodes.INDEX_NOT_FOUND,
5767
5671
  `Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
@@ -5828,7 +5732,7 @@ function clampOffset(offset) {
5828
5732
  // src/engine/index-lifecycle.ts
5829
5733
  async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
5830
5734
  core.guardShutdown();
5831
- validateIndexName2(name);
5735
+ validateIndexName(name);
5832
5736
  if (core.indexRegistry.has(name)) {
5833
5737
  throw new NarsilError(ErrorCodes.INDEX_ALREADY_EXISTS, `Index "${name}" already exists`, { indexName: name });
5834
5738
  }
@@ -5887,7 +5791,8 @@ async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
5887
5791
  embeddingAdapter: resolvedEmbeddingAdapter,
5888
5792
  embeddingAdapterName,
5889
5793
  vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
5890
- indexUuid: indexUuid ?? null
5794
+ indexUuid: indexUuid ?? null,
5795
+ heldPartitions: null
5891
5796
  });
5892
5797
  if (core.durability) {
5893
5798
  await core.durability.manager.persistMetadata(name);
@@ -7697,8 +7602,8 @@ async function createSnapshot(manager, entry) {
7697
7602
  }
7698
7603
  const config = entry.config;
7699
7604
  const bm25 = config.bm25;
7700
- const { encode: encode10 } = await import('@msgpack/msgpack');
7701
- return encode10({
7605
+ const { encode: encode9 } = await import('@msgpack/msgpack');
7606
+ return encode9({
7702
7607
  version: 2,
7703
7608
  schema: config.schema,
7704
7609
  language: entry.language.name,
@@ -7733,10 +7638,10 @@ async function restoreFromSnapshot(indexName, data, deps) {
7733
7638
  if (!(data instanceof Uint8Array)) {
7734
7639
  throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Snapshot data must be a Uint8Array");
7735
7640
  }
7736
- const { decode: decode10 } = await import('@msgpack/msgpack');
7641
+ const { decode: decode9 } = await import('@msgpack/msgpack');
7737
7642
  let decoded;
7738
7643
  try {
7739
- decoded = decode10(data);
7644
+ decoded = decode9(data);
7740
7645
  } catch (err) {
7741
7646
  throw new NarsilError(
7742
7647
  ErrorCodes.DOC_VALIDATION_FAILED,
@@ -7817,7 +7722,8 @@ async function restoreFromSnapshot(indexName, data, deps) {
7817
7722
  embeddingAdapter,
7818
7723
  embeddingAdapterName: adapterName,
7819
7724
  vectorFieldPaths,
7820
- indexUuid: null
7725
+ indexUuid: null,
7726
+ heldPartitions: null
7821
7727
  });
7822
7728
  try {
7823
7729
  const manager = deps.requireManager(indexName);
@@ -8342,6 +8248,23 @@ function partitionsFor(manager, partitionIds) {
8342
8248
  }
8343
8249
  return partitions;
8344
8250
  }
8251
+ function coverageFor(manager, partitionIds) {
8252
+ if (partitionIds === void 0) {
8253
+ return {
8254
+ totalPartitions: manager.partitionCount,
8255
+ queriedPartitions: manager.partitionCount,
8256
+ timedOutPartitions: 0,
8257
+ failedPartitions: 0
8258
+ };
8259
+ }
8260
+ const queriedPartitions = partitionsFor(manager, partitionIds).length;
8261
+ return {
8262
+ totalPartitions: partitionIds.length,
8263
+ queriedPartitions,
8264
+ timedOutPartitions: 0,
8265
+ failedPartitions: partitionIds.length - queriedPartitions
8266
+ };
8267
+ }
8345
8268
  function partitionsForVectorSearch(manager, vecIndex, partitionIds) {
8346
8269
  if (partitionIds === void 0) {
8347
8270
  return void 0;
@@ -8803,7 +8726,8 @@ async function executeQuery(params, context) {
8803
8726
  elapsed,
8804
8727
  cursor: nextCursor,
8805
8728
  facets,
8806
- groups
8729
+ groups,
8730
+ coverage: coverageFor(manager, context.partitionIds)
8807
8731
  };
8808
8732
  }
8809
8733
  async function executePreflight(params, context) {
@@ -9329,4 +9253,4 @@ async function createNarsil(config) {
9329
9253
  // src/index.ts
9330
9254
  var VERSION = "0.1.0";
9331
9255
 
9332
- export { DEFAULT_SUGGEST_LIMIT, MAX_INDEX_NAME_LENGTH, MAX_PARTITION_COUNT, 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, getIndexMetadata, indexConfigKey, now, putIndexMetadata, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateIndexName, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
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 };
@@ -1,4 +1,4 @@
1
- import { MAX_MESSAGE_SIZE_BYTES, TransportError, TransportErrorCodes } from './chunk-36ER4WCG.mjs';
1
+ import { MAX_MESSAGE_SIZE_BYTES, TransportError, TransportErrorCodes } from './chunk-JQUL2T6I.mjs';
2
2
  import { encode, decode } from '@msgpack/msgpack';
3
3
 
4
4
  // src/distribution/transport/tcp/types.ts
@@ -0,0 +1,137 @@
1
+ import { NarsilError, ErrorCodes } from './chunk-GB2IIJJG.mjs';
2
+ import { encode, decode } from '@msgpack/msgpack';
3
+
4
+ var INDEX_CONFIG_PREFIX = "_narsil/index/";
5
+ var INDEX_CONFIG_SUFFIX = "/config";
6
+ var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
7
+ var MAX_INDEX_NAME_LENGTH = 255;
8
+ var MAX_PARTITION_COUNT = 65536;
9
+ var MAX_REPLICATION_FACTOR = 255;
10
+ function truncateForDisplay(value) {
11
+ const str = String(value);
12
+ if (str.length > 100) {
13
+ return `${str.slice(0, 100)}...`;
14
+ }
15
+ return str;
16
+ }
17
+ function validateIndexName(indexName) {
18
+ if (indexName.length === 0 || indexName.length > MAX_INDEX_NAME_LENGTH) {
19
+ throw new NarsilError(
20
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
21
+ `Index name must be between 1 and ${MAX_INDEX_NAME_LENGTH} characters`,
22
+ { indexName: truncateForDisplay(indexName) }
23
+ );
24
+ }
25
+ if (indexName.includes("\0") || indexName.includes("/") || indexName.includes("\\") || indexName.includes("..")) {
26
+ throw new NarsilError(
27
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
28
+ "Index name contains forbidden characters (/, \\, .., or null bytes)",
29
+ { indexName: truncateForDisplay(indexName) }
30
+ );
31
+ }
32
+ if (!INDEX_NAME_PATTERN.test(indexName)) {
33
+ throw new NarsilError(
34
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
35
+ "Index name must start with an alphanumeric character and contain only alphanumeric characters, hyphens, underscores, or dots",
36
+ { indexName: truncateForDisplay(indexName) }
37
+ );
38
+ }
39
+ }
40
+ function isValidInteger(value) {
41
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
42
+ }
43
+ function indexConfigKey(indexName) {
44
+ return `${INDEX_CONFIG_PREFIX}${indexName}${INDEX_CONFIG_SUFFIX}`;
45
+ }
46
+ function isRecord(value) {
47
+ return value !== null && typeof value === "object" && !Array.isArray(value);
48
+ }
49
+ function validateDecodedMetadata(decoded, indexName) {
50
+ if (!isRecord(decoded)) {
51
+ throw new NarsilError(
52
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
53
+ `Index metadata for '${indexName}' is not an object`,
54
+ { indexName }
55
+ );
56
+ }
57
+ if (typeof decoded.indexUuid !== "string" || decoded.indexUuid.length === 0 || decoded.indexUuid.length > 128) {
58
+ throw new NarsilError(
59
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
60
+ `Index metadata for '${indexName}' has invalid indexUuid`,
61
+ { indexName, received: truncateForDisplay(decoded.indexUuid) }
62
+ );
63
+ }
64
+ if (typeof decoded.indexName !== "string") {
65
+ throw new NarsilError(
66
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
67
+ `Index metadata for '${indexName}' has invalid indexName`,
68
+ { indexName, received: truncateForDisplay(decoded.indexName) }
69
+ );
70
+ }
71
+ if (!isValidInteger(decoded.partitionCount) || decoded.partitionCount <= 0 || decoded.partitionCount > MAX_PARTITION_COUNT) {
72
+ throw new NarsilError(
73
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
74
+ `Index metadata for '${indexName}' has invalid partitionCount (must be an integer between 1 and ${MAX_PARTITION_COUNT})`,
75
+ { indexName, partitionCount: truncateForDisplay(decoded.partitionCount) }
76
+ );
77
+ }
78
+ if (!isValidInteger(decoded.replicationFactor) || decoded.replicationFactor < 0 || decoded.replicationFactor > MAX_REPLICATION_FACTOR) {
79
+ throw new NarsilError(
80
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
81
+ `Index metadata for '${indexName}' has invalid replicationFactor (must be an integer between 0 and ${MAX_REPLICATION_FACTOR})`,
82
+ { indexName, replicationFactor: truncateForDisplay(decoded.replicationFactor) }
83
+ );
84
+ }
85
+ if (!isRecord(decoded.constraints)) {
86
+ throw new NarsilError(
87
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
88
+ `Index metadata for '${indexName}' has invalid constraints`,
89
+ { indexName }
90
+ );
91
+ }
92
+ const constraints = decoded.constraints;
93
+ if (constraints.maxShardsPerNode !== void 0 && constraints.maxShardsPerNode !== null && typeof constraints.maxShardsPerNode !== "number") {
94
+ throw new NarsilError(
95
+ ErrorCodes.CONTROLLER_METADATA_INVALID,
96
+ `Index metadata for '${indexName}' has invalid maxShardsPerNode (must be a number or null)`,
97
+ { indexName, maxShardsPerNode: truncateForDisplay(constraints.maxShardsPerNode) }
98
+ );
99
+ }
100
+ return {
101
+ indexUuid: decoded.indexUuid,
102
+ indexName: decoded.indexName,
103
+ partitionCount: decoded.partitionCount,
104
+ replicationFactor: decoded.replicationFactor,
105
+ constraints: {
106
+ zoneAwareness: constraints.zoneAwareness === true,
107
+ zoneAttribute: typeof constraints.zoneAttribute === "string" ? constraints.zoneAttribute : "zone",
108
+ maxShardsPerNode: typeof constraints.maxShardsPerNode === "number" ? constraints.maxShardsPerNode : null
109
+ }
110
+ };
111
+ }
112
+ async function putIndexMetadata(coordinator, metadata) {
113
+ validateIndexName(metadata.indexName);
114
+ const key = indexConfigKey(metadata.indexName);
115
+ const encoded = encode(metadata);
116
+ const bytes = new Uint8Array(encoded);
117
+ if (await coordinator.compareAndSet(key, null, bytes)) {
118
+ return true;
119
+ }
120
+ const current = await coordinator.get(key);
121
+ if (current === null || current.byteLength > 0) {
122
+ return false;
123
+ }
124
+ return coordinator.compareAndSet(key, current, bytes);
125
+ }
126
+ async function getIndexMetadata(coordinator, indexName) {
127
+ validateIndexName(indexName);
128
+ const key = indexConfigKey(indexName);
129
+ const raw = await coordinator.get(key);
130
+ if (raw === null || raw.byteLength === 0) {
131
+ return null;
132
+ }
133
+ const decoded = decode(raw);
134
+ return validateDecodedMetadata(decoded, indexName);
135
+ }
136
+
137
+ export { MAX_INDEX_NAME_LENGTH, MAX_PARTITION_COUNT, getIndexMetadata, indexConfigKey, putIndexMetadata, validateIndexName };
@@ -1,6 +1,6 @@
1
+ import { V as VectorMaintenanceResult, M as MemoryStats, B as BatchResult, L as ListParams, g as ListResult, I as IndexInfo, f as IndexStats, P as PartitionStatsResult, Q as QueryParams, e as QueryResult, h as PreflightResult, i as SuggestParams, j as SuggestResult } from './search-DHuXwJYk.js';
2
+ import { P as PartitionConfig, A as AnyDocument, a as InsertOptions } from './schema-ZRbk8BgH.js';
1
3
  import { e as TaskRecord, b as ImportResult, H as HttpIndexConfig, c as TaskListQuery, T as TaskListPage } from './tasks-CnMLpfk-.js';
2
- import { P as PartitionConfig, A as AnyDocument, a as InsertOptions } from './schema-emTQfEiX.js';
3
- import { V as VectorMaintenanceResult, M as MemoryStats, B as BatchResult, L as ListParams, g as ListResult, I as IndexInfo, f as IndexStats, P as PartitionStatsResult, Q as QueryParams, e as QueryResult, h as PreflightResult, i as SuggestParams, j as SuggestResult } from './search-CQ-9bKxo.js';
4
4
 
5
5
  /**
6
6
  * This is the `fetch` implementation the client sends every request through.