@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/dist/server.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-EMI63EXP.mjs';
2
- export { ServerErrorCodes } from './chunk-EMI63EXP.mjs';
1
+ import { validateIndexName } from './chunk-WU2UZEVD.mjs';
2
+ import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-GB2IIJJG.mjs';
3
+ export { ServerErrorCodes } from './chunk-GB2IIJJG.mjs';
3
4
  import { randomUUID } from 'crypto';
4
5
 
5
6
  // src/server/capabilities.ts
@@ -117,6 +118,8 @@ var STATUS_BY_CODE = {
117
118
  CONTROLLER_NOT_ACTIVE: 503,
118
119
  NODE_ALREADY_JOINED: 409,
119
120
  NODE_NOT_JOINED: 503,
121
+ NODE_NOT_READY: 503,
122
+ NODE_NOT_CONTROLLER: 503,
120
123
  NODE_BOOTSTRAP_FAILED: 503,
121
124
  COORDINATOR_DEPENDENCY_MISSING: 500,
122
125
  SNAPSHOT_SYNC_UNAUTHORIZED: 403,
@@ -649,6 +652,79 @@ function createCapabilitiesHandler() {
649
652
  return { report };
650
653
  }
651
654
 
655
+ // src/server/handlers/cluster.ts
656
+ function requireCluster(deps) {
657
+ if (deps.cluster === void 0) {
658
+ throw new NarsilError(
659
+ ErrorCodes.CLUSTER_OPERATION_UNSUPPORTED,
660
+ "This server fronts a single engine, so it reports no cluster",
661
+ {}
662
+ );
663
+ }
664
+ return deps.cluster;
665
+ }
666
+ function describeNode(cluster) {
667
+ return { ...cluster.getNodeInfo(), readiness: cluster.getReadiness(), isController: cluster.isControllerActive() };
668
+ }
669
+ function malformedIndexName(indexName) {
670
+ try {
671
+ validateIndexName(indexName);
672
+ return null;
673
+ } catch (error) {
674
+ return error instanceof Error ? error.message : String(error);
675
+ }
676
+ }
677
+ function createClusterHandlers(deps) {
678
+ async function topology(ctx) {
679
+ try {
680
+ const cluster = requireCluster(deps);
681
+ const [nodes, controllerNodeId] = await Promise.all([cluster.listNodes(), cluster.getControllerNodeId()]);
682
+ respondJson(ctx, {
683
+ node: describeNode(cluster),
684
+ controllerNodeId,
685
+ nodes: nodes.map((node) => ({ nodeId: node.nodeId, address: node.address, roles: [...node.roles] }))
686
+ });
687
+ } catch (err) {
688
+ respondError(ctx, err);
689
+ }
690
+ }
691
+ async function allocation(ctx) {
692
+ try {
693
+ const cluster = requireCluster(deps);
694
+ const indexName = ctx.params[0] ?? "";
695
+ const malformed = malformedIndexName(indexName);
696
+ if (malformed !== null) {
697
+ badRequest(ctx.res, malformed, { indexName });
698
+ return;
699
+ }
700
+ const table = await cluster.getAllocation(indexName);
701
+ if (table === null) {
702
+ respondJson(ctx, { indexName, allocated: false, version: null, partitions: [] });
703
+ return;
704
+ }
705
+ const partitions = [];
706
+ for (const [partitionId, assignment] of table.assignments) {
707
+ partitions.push({
708
+ partitionId,
709
+ state: assignment.state,
710
+ primary: assignment.primary,
711
+ primaryTerm: assignment.primaryTerm,
712
+ commitPoint: assignment.commitPoint,
713
+ replicas: [...assignment.replicas],
714
+ inSyncSet: [...assignment.inSyncSet],
715
+ lastHolders: [...assignment.lastHolders ?? []],
716
+ unassignedReason: assignment.unassignedReason ?? null
717
+ });
718
+ }
719
+ partitions.sort((left, right) => left.partitionId - right.partitionId);
720
+ respondJson(ctx, { indexName, allocated: partitions.length > 0, version: table.version, partitions });
721
+ } catch (err) {
722
+ respondError(ctx, err);
723
+ }
724
+ }
725
+ return { topology, allocation };
726
+ }
727
+
652
728
  // src/server/handlers/documents.ts
653
729
  function createDocumentHandlers(deps) {
654
730
  const { engine, limits } = deps;
@@ -804,11 +880,25 @@ function createHealthHandlers(deps) {
804
880
  respondJson(ctx, { status: "ok" });
805
881
  }
806
882
  function readyz(ctx) {
807
- if (deps.isReady()) {
808
- respondJson(ctx, { status: "ready" });
809
- } else {
810
- respondJson(ctx, { status: "unavailable" }, 503);
883
+ const cluster = deps.cluster;
884
+ if (cluster === void 0) {
885
+ if (deps.isReady()) {
886
+ respondJson(ctx, { status: "ready" });
887
+ } else {
888
+ respondJson(ctx, { status: "unavailable" }, 503);
889
+ }
890
+ return;
811
891
  }
892
+ const readiness = cluster.getReadiness();
893
+ const ready = deps.isReady() && readiness === "SERVING";
894
+ respondJson(
895
+ ctx,
896
+ {
897
+ status: ready ? "ready" : "unavailable",
898
+ cluster: describeNode(cluster)
899
+ },
900
+ ready ? 200 : 503
901
+ );
812
902
  }
813
903
  return { livez, readyz };
814
904
  }
@@ -1576,7 +1666,8 @@ var NarsilHttpServer = class {
1576
1666
  tasks: new TaskRegistry(taskStore, instanceId, limits.maxConcurrentTasks),
1577
1667
  limits,
1578
1668
  isReady: () => this.ready,
1579
- build: resolveBuild(options.build)
1669
+ build: resolveBuild(options.build),
1670
+ cluster: options.cluster
1580
1671
  };
1581
1672
  }
1582
1673
  async listen() {
@@ -1622,6 +1713,7 @@ var NarsilHttpServer = class {
1622
1713
  const search = createSearchHandlers(this.deps);
1623
1714
  const admin = createAdminHandlers(this.deps);
1624
1715
  const health = createHealthHandlers(this.deps);
1716
+ const cluster = createClusterHandlers(this.deps);
1625
1717
  const version = createVersionHandler(this.deps);
1626
1718
  const capabilities = createCapabilitiesHandler();
1627
1719
  const importNdjson = createImportHandler(this.deps);
@@ -1641,6 +1733,7 @@ var NarsilHttpServer = class {
1641
1733
  app.get("/health", json(health.livez, { maxBytes: 0, skipHooks: true }));
1642
1734
  app.get("/version", json(version.report, { maxBytes: 0, skipHooks: true }));
1643
1735
  app.get("/capabilities", json(capabilities.report, { maxBytes: 0, skipHooks: true }));
1736
+ app.get("/cluster", json(cluster.topology, { maxBytes: 0 }));
1644
1737
  app.get("/stats/memory", json(admin.memory, { maxBytes: 0 }));
1645
1738
  app.get("/tasks", json(admin.listTasks, { maxBytes: 0 }));
1646
1739
  app.get("/tasks/:id", json(admin.getTask, { paramCount: 1, maxBytes: 0 }));
@@ -1650,6 +1743,7 @@ var NarsilHttpServer = class {
1650
1743
  app.del("/indexes/:name", json(idx.drop, { paramCount: 1, maxBytes: 0 }));
1651
1744
  app.get("/indexes/:name/stats", json(idx.stats, { paramCount: 1, maxBytes: 0 }));
1652
1745
  app.get("/indexes/:name/partitions", json(idx.partitions, { paramCount: 1, maxBytes: 0 }));
1746
+ app.get("/indexes/:name/cluster", json(cluster.allocation, { paramCount: 1, maxBytes: 0 }));
1653
1747
  app.post("/indexes/:name/_clear", json(idx.clear, { paramCount: 1, maxBytes: 0 }));
1654
1748
  app.get("/indexes/:name/count", json(doc.count, { paramCount: 1, maxBytes: 0 }));
1655
1749
  app.post("/indexes/:name/documents", json(doc.insert, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes }));
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './schema-emTQfEiX.js';
1
+ import { S as SchemaDefinition } from './schema-ZRbk8BgH.js';
2
2
 
3
3
  /**
4
4
  * What a node does in a cluster. A node may hold several roles at once.
@@ -20,6 +20,19 @@ type NodeRole = 'data' | 'coordinator' | 'controller';
20
20
  * @public
21
21
  */
22
22
  type PartitionState = 'UNASSIGNED' | 'INITIALISING' | 'ACTIVE' | 'MIGRATING' | 'DECOMMISSIONING';
23
+ /**
24
+ * Why a partition stays `UNASSIGNED` after the controller has asked its last holders for a copy.
25
+ *
26
+ * `HOLDER_OFFLINE` means a last holder has yet to register again, so the controller is still waiting for it.
27
+ * `HOLDER_UNREACHABLE` means a registered holder left the request unanswered or answered with a payload the
28
+ * controller could not read. `HOLDER_IDENTITY_MISMATCH` means a holder answered under a different index identity,
29
+ * so its copy holds the documents of an earlier index of the same name, or it keeps no copy of that name at all.
30
+ * `HOLDER_WITHOUT_DATA` means every holder answered under the coordinator's identity with a list that left the
31
+ * partition out.
32
+ *
33
+ * @public
34
+ */
35
+ type UnassignedReason = 'HOLDER_OFFLINE' | 'HOLDER_UNREACHABLE' | 'HOLDER_IDENTITY_MISMATCH' | 'HOLDER_WITHOUT_DATA';
23
36
  /**
24
37
  * What one node has to offer, which the allocator weighs when it places
25
38
  * partitions.
@@ -67,12 +80,16 @@ interface PartitionAssignment {
67
80
  replicas: string[];
68
81
  /** These replicas are current enough to be promoted, which is the set a failover picks from. */
69
82
  inSyncSet: string[];
83
+ /** These nodes still hold a copy of a partition no node serves, and the field stays until the partition is `ACTIVE`. */
84
+ lastHolders?: string[];
70
85
  /** This says where the partition stands in its move between nodes. */
71
86
  state: PartitionState;
72
87
  /** This rises each time the primary changes, which is what makes a stale primary's writes detectable. */
73
88
  primaryTerm: number;
74
89
  /** The primary has acknowledged every sequence number up to here, so every in-sync replica holds them. */
75
90
  commitPoint: number;
91
+ /** Why the partition stays `UNASSIGNED`, which the controller records each time it asks the last holders. */
92
+ unassignedReason?: UnassignedReason;
76
93
  }
77
94
  /**
78
95
  * How one index's partitions are spread across the cluster.
@@ -323,4 +340,4 @@ interface ClusterCoordinator {
323
340
  shutdown(): Promise<void>;
324
341
  }
325
342
 
326
- export type { AllocationTable as A, ClusterCoordinator as C, NodeRole as N, PartitionAssignment as P, SchemaEvent as S, NodeCapacity as a, AllocationConstraints as b, AllocationEvent as c, NodeEvent as d, NodeRegistration as e, PartitionState as f };
343
+ export type { AllocationTable as A, ClusterCoordinator as C, NodeRole as N, PartitionAssignment as P, SchemaEvent as S, UnassignedReason as U, NodeRegistration as a, NodeCapacity as b, AllocationConstraints as c, AllocationEvent as d, NodeEvent as e, PartitionState as f };
@@ -1,5 +1,5 @@
1
1
  import { isValidWorkerAction } from '../chunk-WTONLDU3.mjs';
2
- import { NarsilError, ErrorCodes } from '../chunk-EMI63EXP.mjs';
2
+ import { NarsilError, ErrorCodes } from '../chunk-GB2IIJJG.mjs';
3
3
 
4
4
  // src/workers/action-handler.ts
5
5
  function buildErrorResponse(requestId, code, message) {
@@ -55,7 +55,7 @@ async function setup() {
55
55
  } catch {
56
56
  parentPort = null;
57
57
  }
58
- const { createDirectExecutor } = await import('../direct-executor-YARCDMJF.mjs');
58
+ const { createDirectExecutor } = await import('../direct-executor-MSJEVPZU.mjs');
59
59
  const handleAction = createActionHandler(createDirectExecutor());
60
60
  if (parentPort) {
61
61
  const port = parentPort;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delali/narsil",
3
- "version": "0.2.3-next.34",
3
+ "version": "0.2.3-next.36",
4
4
  "type": "module",
5
5
  "description": "A distributed full-text search engine with cross-language portable binary format.",
6
6
  "author": "Delali (https://sondelali.com)",