@delali/sirannon-db 0.2.0 → 0.2.1

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 (75) hide show
  1. package/dist/backup-scheduler/index.d.ts +15 -1
  2. package/dist/backup-scheduler/index.mjs +2 -2
  3. package/dist/baseline-D93hcIEE.d.ts +17 -0
  4. package/dist/{change-tracker-DKRVUC3l.d.ts → change-tracker-DDmXB754.d.ts} +56 -8
  5. package/dist/{chunk-5NOIGN5Y.mjs → chunk-2QLXDHAP.mjs} +1 -1
  6. package/dist/{chunk-LNY2VVHE.mjs → chunk-7C36BCSN.mjs} +1 -1
  7. package/dist/{chunk-NVQS53NT.mjs → chunk-7FQRQH5Z.mjs} +53 -64
  8. package/dist/{chunk-D7LAYTKN.mjs → chunk-7R4ER4FB.mjs} +1 -1
  9. package/dist/{chunk-FHWTZFI4.mjs → chunk-BQFQ65OL.mjs} +1 -1
  10. package/dist/{chunk-O7SLN3GI.mjs → chunk-BTTFW4Z4.mjs} +1 -1
  11. package/dist/{chunk-H6PIVVDN.mjs → chunk-CCZK6LCB.mjs} +38 -25
  12. package/dist/{chunk-JZGINXTN.mjs → chunk-HCCGEIZ2.mjs} +2 -2
  13. package/dist/{chunk-67M7KAH6.mjs → chunk-IWGIYDMZ.mjs} +1 -1
  14. package/dist/{chunk-LFZ37BSX.mjs → chunk-OUSWVNWT.mjs} +1 -1
  15. package/dist/{chunk-HR5CWTLC.mjs → chunk-P2VJYRVY.mjs} +60 -7
  16. package/dist/{chunk-UC3SCMIN.mjs → chunk-PBRXXISQ.mjs} +3 -0
  17. package/dist/{chunk-JU64Y7HM.mjs → chunk-SBL6GN43.mjs} +1 -1
  18. package/dist/{chunk-EBJXPQQO.mjs → chunk-UPKKSUPA.mjs} +2 -2
  19. package/dist/{chunk-TJF5GZSV.mjs → chunk-VOSJBZ6Q.mjs} +1 -1
  20. package/dist/{chunk-PIKHN33N.mjs → chunk-VOYGMAU7.mjs} +9 -1
  21. package/dist/{chunk-H237TXZW.mjs → chunk-WJ67DTD6.mjs} +48 -6
  22. package/dist/{chunk-OQVZBEBY.mjs → chunk-XF2HH5E6.mjs} +4 -61
  23. package/dist/client/index.d.ts +211 -12
  24. package/dist/client/index.mjs +155 -67
  25. package/dist/client/topology.d.ts +55 -7
  26. package/dist/client/topology.mjs +20 -1
  27. package/dist/{client-base-CLWmH5Ln.d.ts → client-base-CmZO0v3m.d.ts} +133 -24
  28. package/dist/codegen/cli.mjs +3 -3
  29. package/dist/codegen/index.d.ts +92 -2
  30. package/dist/codegen/index.mjs +3 -3
  31. package/dist/core/index.d.ts +177 -15
  32. package/dist/core/index.mjs +2481 -2226
  33. package/dist/core/writer-worker.mjs +3 -3
  34. package/dist/database-B5Qv1-cU.d.ts +380 -0
  35. package/dist/driver/better-sqlite3.d.ts +18 -1
  36. package/dist/driver/better-sqlite3.mjs +5 -5
  37. package/dist/driver/bun.d.ts +28 -0
  38. package/dist/driver/expo.d.ts +17 -0
  39. package/dist/driver/node.d.ts +18 -1
  40. package/dist/driver/node.mjs +5 -5
  41. package/dist/driver/wa-sqlite.d.ts +18 -1
  42. package/dist/{errors-Bw5MdNCu.d.ts → errors-Dei4GdBb.d.ts} +80 -7
  43. package/dist/file-migrations/index.d.ts +54 -2
  44. package/dist/file-migrations/index.mjs +3 -3
  45. package/dist/{operation-registry-9DcvxcE5.d.ts → operation-registry-hlbhqu7q.d.ts} +50 -1
  46. package/dist/{primary-wins-DPAm2AKG.d.ts → primary-wins-B0np8JS3.d.ts} +25 -1
  47. package/dist/protocol-rqANt-9Q.d.ts +152 -0
  48. package/dist/query-types-DL3LtPvY.d.ts +95 -0
  49. package/dist/react/index.d.ts +58 -3
  50. package/dist/replication/coordinator/etcd.d.ts +63 -3
  51. package/dist/replication/coordinator/etcd.mjs +82 -46
  52. package/dist/replication/index.d.ts +329 -95
  53. package/dist/replication/index.mjs +256 -141
  54. package/dist/server/index.d.ts +230 -12
  55. package/dist/server/index.mjs +63 -50
  56. package/dist/{server-options-1JHu8pid.d.ts → server-options-Dab_Jvd_.d.ts} +96 -12
  57. package/dist/sirannon-CMhiJa5Y.d.ts +111 -0
  58. package/dist/transport/grpc.d.ts +93 -9
  59. package/dist/transport/grpc.mjs +63 -20
  60. package/dist/transport/memory.d.ts +50 -20
  61. package/dist/transport/memory.mjs +25 -0
  62. package/dist/{types-CL6piSnD.d.ts → types-BCejqzNA.d.ts} +20 -0
  63. package/dist/types-CMBcFPhb.d.ts +336 -0
  64. package/dist/types-CjhxcjhA.d.ts +123 -0
  65. package/dist/types-DyrCiWuc.d.ts +499 -0
  66. package/dist/types-rVZKnKN-.d.ts +591 -0
  67. package/package.json +7 -1
  68. package/dist/baseline-Br77Fnhb.d.ts +0 -6
  69. package/dist/database-BY0L5Q2n.d.ts +0 -172
  70. package/dist/protocol-6KrSq2Hy.d.ts +0 -66
  71. package/dist/sirannon-DaQSyhbJ.d.ts +0 -36
  72. package/dist/types-B7gmEsZW.d.ts +0 -221
  73. package/dist/types-BsVabqSI.d.ts +0 -139
  74. package/dist/types-C_D8IhpO.d.ts +0 -60
  75. package/dist/types-zhnRXrsb.d.ts +0 -384
@@ -1,18 +1,73 @@
1
- import { L as LiveQueryOptions, a as LiveQuery, b as LiveQueryState } from '../types-CL6piSnD.js';
2
- import { O as OperationRef } from '../operation-registry-9DcvxcE5.js';
3
- import { P as Params } from '../types-zhnRXrsb.js';
1
+ import { L as LiveQueryOptions, a as LiveQuery, b as LiveQueryState } from '../types-BCejqzNA.js';
2
+ import { O as OperationRef } from '../operation-registry-hlbhqu7q.js';
3
+ import { P as Params } from '../query-types-DL3LtPvY.js';
4
4
 
5
+ /**
6
+ * The one method these hooks need from a database, so both a local
7
+ * `Database` and a `RemoteDatabase` satisfy it.
8
+ *
9
+ * @public
10
+ */
5
11
  interface LiveDatabase {
12
+ /** Opens a live query for a registered read. */
6
13
  live(operation: string | OperationRef<never, never>, args?: never, options?: LiveQueryOptions): Promise<LiveQuery<unknown>>;
7
14
  }
15
+ /**
16
+ * Settings for one call to `useLiveQuery`.
17
+ *
18
+ * @public
19
+ */
8
20
  interface UseLiveQueryOptions extends LiveQueryOptions {
21
+ /** Set false to hold the query closed, which suits a query that depends on data you do not have yet. */
9
22
  enabled?: boolean;
10
23
  }
24
+ /**
25
+ * Subscribes a component to a registered read and re-renders it as the rows change.
26
+ *
27
+ * The query closes when the component unmounts.
28
+ *
29
+ * @param database - Database the read runs against.
30
+ * @param operation - Name of the registered read.
31
+ * @param args - Arguments the read takes.
32
+ * @param options - Whether the query runs, plus the live-query settings.
33
+ * @returns Whether the query is pending, ready with rows, or failed.
34
+ *
35
+ * @public
36
+ */
11
37
  declare function useLiveQuery<Row = Record<string, unknown>>(database: LiveDatabase, operation: string, args?: Params, options?: UseLiveQueryOptions): LiveQueryState<Row>;
38
+ /**
39
+ * Subscribes a component to a registered read and re-renders it as the rows change.
40
+ *
41
+ * @param database - Database the read runs against.
42
+ * @param operation - Reference to the registered read, which carries its argument and row types.
43
+ * @param args - Arguments the read takes.
44
+ * @param options - Whether the query runs, plus the live-query settings.
45
+ * @returns Whether the query is pending, ready with rows, or failed.
46
+ *
47
+ * @public
48
+ */
12
49
  declare function useLiveQuery<Args, Row>(database: LiveDatabase, operation: OperationRef<Args, Row>, args: Args, options?: UseLiveQueryOptions): LiveQueryState<Row>;
50
+ /**
51
+ * Returns a stable callback that runs a registered write.
52
+ *
53
+ * @param database - Database the write runs against.
54
+ * @param command - Reference to the registered write, which carries its argument type.
55
+ * @returns A callback that takes the write's arguments and resolves with its result.
56
+ *
57
+ * @public
58
+ */
13
59
  declare function useCommand<Args, Result>(database: {
14
60
  execute(operation: OperationRef<Args, unknown>, args: Args): Promise<Result>;
15
61
  }, command: OperationRef<Args, unknown>): (args: Args) => Promise<Result>;
62
+ /**
63
+ * Returns a stable callback that runs a statement or a registered write by name.
64
+ *
65
+ * @param database - Database the write runs against.
66
+ * @param command - The statement to run, or the name of a registered write.
67
+ * @returns A callback that takes the parameters and resolves with the result.
68
+ *
69
+ * @public
70
+ */
16
71
  declare function useCommand<Result>(database: {
17
72
  execute(sql: string, params?: Params): Promise<Result>;
18
73
  }, command: string): (params?: Params) => Promise<Result>;
@@ -1,18 +1,57 @@
1
1
  import { IOptions } from 'etcd3';
2
- import { C as ClusterCoordinator, A as AcquireControllerLeaseInput, b as AcquireControllerLeaseResult, c as RegisterNodeSessionInput, d as CoordinatorNodeSession, S as SetReplicationGroupStateInput, R as ReplicationGroupState, e as ReplicationGroupWatcher, f as CoordinatorWatchDisposer, g as CompareAndAdvancePrimaryTermInput, h as CompareAndAdvancePrimaryTermResult, U as UpdateInSyncSetInput, i as AdmitNodeToInSyncSetInput, j as UpdateNodeMaintenanceInput, P as PromoteEligibleReplicaInput } from '../../types-BsVabqSI.js';
2
+ import { C as ClusterCoordinator, A as AcquireControllerLeaseInput, a as AcquireControllerLeaseResult, R as RegisterNodeSessionInput, b as CoordinatorNodeSession, S as SetReplicationGroupStateInput, c as ReplicationGroupState, d as ReplicationGroupWatcher, e as CoordinatorWatchDisposer, f as CompareAndAdvancePrimaryTermInput, g as CompareAndAdvancePrimaryTermResult, U as UpdateInSyncSetInput, h as AdmitNodeToInSyncSetInput, i as UpdateNodeMaintenanceInput, P as PromoteEligibleReplicaInput } from '../../types-CMBcFPhb.js';
3
3
 
4
+ /**
5
+ * @public
6
+ *
7
+ * Where the etcd coordinator connects, under which key prefix it stores group state, and how it authenticates.
8
+ */
4
9
  interface EtcdClusterCoordinatorOptions {
10
+ /**
11
+ * etcd endpoints to connect to. Production access requires https addresses.
12
+ */
5
13
  hosts: string | string[];
14
+ /**
15
+ * Prefix applied to every key this coordinator writes, which keeps clusters apart in one etcd.
16
+ */
6
17
  keyPrefix: string;
18
+ /**
19
+ * TLS material, which production access requires.
20
+ */
7
21
  credentials?: IOptions['credentials'];
22
+ /**
23
+ * Username and password, as an alternative to a client certificate.
24
+ */
8
25
  auth?: IOptions['auth'];
26
+ /**
27
+ * Options passed straight through to the underlying gRPC channel.
28
+ */
9
29
  grpcOptions?: IOptions['grpcOptions'];
30
+ /**
31
+ * Milliseconds to wait for a connection to etcd.
32
+ */
10
33
  dialTimeoutMs?: number;
34
+ /**
35
+ * Milliseconds any single etcd call may take.
36
+ */
11
37
  defaultCallTimeoutMs?: number;
38
+ /**
39
+ * Accepts plain http endpoints without credentials, which suits tests only.
40
+ */
12
41
  allowInsecure?: boolean;
42
+ /**
43
+ * Called when a group watch fails so that a caller can log it or raise an alert.
44
+ */
13
45
  onWatcherError?: (error: Error) => void;
14
46
  }
15
47
 
48
+ /**
49
+ * @public
50
+ *
51
+ * Stores primary authority, node sessions, group state, and the in-sync set in etcd.
52
+ *
53
+ * Build one with {@link createEtcdCoordinator}.
54
+ */
16
55
  declare class EtcdClusterCoordinator implements ClusterCoordinator {
17
56
  private readonly client;
18
57
  private readonly namespace;
@@ -22,25 +61,46 @@ declare class EtcdClusterCoordinator implements ClusterCoordinator {
22
61
  private readonly watchers;
23
62
  private readonly groups;
24
63
  constructor(options: EtcdClusterCoordinatorOptions);
64
+ /** Bids for the controller lease, and reports who holds it. */
25
65
  tryAcquireControllerLease(input: AcquireControllerLeaseInput): Promise<AcquireControllerLeaseResult>;
66
+ /** Extends a lease, and reports false once it has already lapsed. */
26
67
  renewLease(leaseId: string, ttlMs: number): Promise<boolean>;
68
+ /** Gives up a lease at once instead of waiting for it to lapse. */
27
69
  releaseLease(leaseId: string): Promise<boolean>;
70
+ /** Records one node as a live member of the cluster. */
28
71
  registerNodeSession(input: RegisterNodeSessionInput): Promise<CoordinatorNodeSession>;
72
+ /** Reads one node's session, and returns null once its lease has lapsed. */
29
73
  getLiveNodeSession(clusterId: string, nodeId: string): Promise<CoordinatorNodeSession | null>;
74
+ /** Ends one node's membership at once. */
30
75
  deregisterNodeSession(clusterId: string, nodeId: string): Promise<void>;
76
+ /** Writes the group's state, which seeds a new group or replaces an existing one. */
31
77
  setReplicationGroupState(input: SetReplicationGroupStateInput): Promise<ReplicationGroupState>;
78
+ /** Reads the group's state, and returns null when the group is absent. */
32
79
  getReplicationGroupState(clusterId: string, groupId: string): Promise<ReplicationGroupState | null>;
80
+ /** Calls back on each change to the group's state, and returns a function that stops the watch. */
33
81
  watchReplicationGroup(clusterId: string, groupId: string, watcher: ReplicationGroupWatcher): Promise<CoordinatorWatchDisposer>;
82
+ /** Promotes a node only while the group is still at the term the caller read. */
34
83
  compareAndAdvancePrimaryTerm(input: CompareAndAdvancePrimaryTermInput): Promise<CompareAndAdvancePrimaryTermResult>;
84
+ /** Replaces the group's in-sync set, and optionally moves its durability point. */
35
85
  updateInSyncSet(input: UpdateInSyncSetInput): Promise<ReplicationGroupState | null>;
86
+ /** Adds one caught-up node to the in-sync set. */
36
87
  admitNodeToInSyncSet(input: AdmitNodeToInSyncSetInput): Promise<ReplicationGroupState | null>;
88
+ /** Marks one node as being taken out of service, rebuilt, or quarantined. */
37
89
  updateNodeMaintenance(input: UpdateNodeMaintenanceInput): Promise<ReplicationGroupState | null>;
90
+ /** Promotes whichever in-sync replica is safe to write. */
38
91
  promoteEligibleReplica(input: PromoteEligibleReplicaInput): Promise<ReplicationGroupState>;
92
+ /** Closes the etcd client and stops every watch. */
39
93
  close(): Promise<void>;
40
- private discardSupersededLeases;
41
94
  private getLeaseFromKey;
42
- private trackLease;
43
95
  }
96
+ /**
97
+ * @public
98
+ *
99
+ * Builds a coordinator backed by etcd.
100
+ *
101
+ * @param options - etcd endpoints, key prefix, credentials, and timeouts.
102
+ * @returns The coordinator, ready to pass to a replication engine.
103
+ */
44
104
  declare function createEtcdCoordinator(options: EtcdClusterCoordinatorOptions): EtcdClusterCoordinator;
45
105
 
46
106
  export { EtcdClusterCoordinator, type EtcdClusterCoordinatorOptions, createEtcdCoordinator };
@@ -1,7 +1,7 @@
1
1
  import { compatibilityAllowsPromotion } from '../../chunk-ER7ODTDA.mjs';
2
- import { CoordinatorError, NoSafePrimaryError } from '../../chunk-O7SLN3GI.mjs';
3
- import '../../chunk-5NOIGN5Y.mjs';
4
- import '../../chunk-UC3SCMIN.mjs';
2
+ import { CoordinatorError, NoSafePrimaryError } from '../../chunk-BTTFW4Z4.mjs';
3
+ import '../../chunk-2QLXDHAP.mjs';
4
+ import '../../chunk-PBRXXISQ.mjs';
5
5
  import { Etcd3 } from 'etcd3';
6
6
 
7
7
  // src/replication/coordinator/group-rules.ts
@@ -473,12 +473,65 @@ var EtcdGroupStore = class {
473
473
  }
474
474
  };
475
475
 
476
+ // src/replication/coordinator/etcd-lease-registry.ts
477
+ async function revokeLeaseQuietly(lease) {
478
+ try {
479
+ await lease.revoke();
480
+ } catch {
481
+ lease.release();
482
+ }
483
+ }
484
+ var EtcdLeaseRegistry = class {
485
+ constructor(onLeaseLost) {
486
+ this.onLeaseLost = onLeaseLost;
487
+ }
488
+ entries = /* @__PURE__ */ new Map();
489
+ get(leaseId) {
490
+ return this.entries.get(leaseId);
491
+ }
492
+ forget(leaseId) {
493
+ this.entries.delete(leaseId);
494
+ }
495
+ leaseIdsForKey(key) {
496
+ const matches = [];
497
+ for (const [leaseId, entry] of this.entries) {
498
+ if (entry.key === key) matches.push(leaseId);
499
+ }
500
+ return matches;
501
+ }
502
+ track(lease, entry) {
503
+ this.entries.set(entry.leaseId, { ...entry, lease });
504
+ lease.on("lost", (err) => {
505
+ this.entries.delete(entry.leaseId);
506
+ this.onLeaseLost?.(err instanceof Error ? err : new CoordinatorError(String(err)));
507
+ });
508
+ }
509
+ async discardSuperseded(key, keepLeaseId) {
510
+ const superseded = [];
511
+ for (const [leaseId, entry] of this.entries) {
512
+ if (entry.key === key && leaseId !== keepLeaseId) {
513
+ this.entries.delete(leaseId);
514
+ superseded.push(entry);
515
+ }
516
+ }
517
+ await Promise.allSettled(superseded.map((entry) => revokeLeaseQuietly(entry.lease)));
518
+ }
519
+ async revokeAll() {
520
+ const revokes = [];
521
+ for (const entry of this.entries.values()) {
522
+ revokes.push(revokeLeaseQuietly(entry.lease));
523
+ }
524
+ this.entries.clear();
525
+ await Promise.allSettled(revokes);
526
+ }
527
+ };
528
+
476
529
  // src/replication/coordinator/etcd.ts
477
530
  var EtcdClusterCoordinator = class {
478
531
  client;
479
532
  namespace;
480
533
  onWatcherError;
481
- leases = /* @__PURE__ */ new Map();
534
+ leases;
482
535
  grantedNodeSessionLeaseIds = /* @__PURE__ */ new Map();
483
536
  watchers = /* @__PURE__ */ new Set();
484
537
  groups;
@@ -487,8 +540,10 @@ var EtcdClusterCoordinator = class {
487
540
  this.client = new Etcd3(toEtcdOptions(options));
488
541
  this.namespace = this.client.namespace(normaliseKeyPrefix(options.keyPrefix));
489
542
  this.onWatcherError = options.onWatcherError;
543
+ this.leases = new EtcdLeaseRegistry(this.onWatcherError);
490
544
  this.groups = new EtcdGroupStore(this.namespace, this.watchers, this.onWatcherError);
491
545
  }
546
+ /** Bids for the controller lease, and reports who holds it. */
492
547
  async tryAcquireControllerLease(input) {
493
548
  assertNonEmpty(input.clusterId, "clusterId");
494
549
  assertNonEmpty(input.holderId, "holderId");
@@ -513,7 +568,7 @@ var EtcdClusterCoordinator = class {
513
568
  const current = await this.getLeaseFromKey(key);
514
569
  return { acquired: false, lease: current };
515
570
  }
516
- this.trackLease(lease, {
571
+ this.leases.track(lease, {
517
572
  leaseId,
518
573
  key,
519
574
  ttlMs: input.ttlMs,
@@ -526,6 +581,7 @@ var EtcdClusterCoordinator = class {
526
581
  const parsed = parseLease(value);
527
582
  return { acquired: true, lease: parsed };
528
583
  }
584
+ /** Extends a lease, and reports false once it has already lapsed. */
529
585
  async renewLease(leaseId, ttlMs) {
530
586
  assertNonEmpty(leaseId, "leaseId");
531
587
  assertPositiveTtl(ttlMs);
@@ -536,7 +592,7 @@ var EtcdClusterCoordinator = class {
536
592
  try {
537
593
  await entry.lease.keepaliveOnce();
538
594
  } catch {
539
- this.leases.delete(leaseId);
595
+ this.leases.forget(leaseId);
540
596
  return false;
541
597
  }
542
598
  const renewedAtMs = Date.now();
@@ -559,19 +615,20 @@ var EtcdClusterCoordinator = class {
559
615
  return true;
560
616
  }
561
617
  if (!refreshed) {
562
- this.leases.delete(leaseId);
618
+ this.leases.forget(leaseId);
563
619
  return false;
564
620
  }
565
621
  entry.ttlMs = ttlMs;
566
622
  return true;
567
623
  }
624
+ /** Gives up a lease at once instead of waiting for it to lapse. */
568
625
  async releaseLease(leaseId) {
569
626
  assertNonEmpty(leaseId, "leaseId");
570
627
  const entry = this.leases.get(leaseId);
571
628
  if (!entry) {
572
629
  return false;
573
630
  }
574
- this.leases.delete(leaseId);
631
+ this.leases.forget(leaseId);
575
632
  const currentValue = await this.namespace.get(entry.key).string();
576
633
  const currentLeaseId = currentValue ? parseLeaseIdForEntry(entry.kind, currentValue) : null;
577
634
  let released = false;
@@ -582,6 +639,7 @@ var EtcdClusterCoordinator = class {
582
639
  await revokeLeaseQuietly(entry.lease);
583
640
  return released;
584
641
  }
642
+ /** Records one node as a live member of the cluster. */
585
643
  async registerNodeSession(input) {
586
644
  assertNonEmpty(input.clusterId, "clusterId");
587
645
  assertNonEmpty(input.nodeId, "nodeId");
@@ -625,9 +683,9 @@ var EtcdClusterCoordinator = class {
625
683
  await revokeLeaseQuietly(lease);
626
684
  throw new CoordinatorError(`Node session '${input.nodeId}' registration conflicted with a concurrent write`);
627
685
  }
628
- await this.discardSupersededLeases(key, leaseId);
686
+ await this.leases.discardSuperseded(key, leaseId);
629
687
  this.grantedNodeSessionLeaseIds.set(key, leaseId);
630
- this.trackLease(lease, {
688
+ this.leases.track(lease, {
631
689
  leaseId,
632
690
  key,
633
691
  ttlMs: input.ttlMs,
@@ -649,46 +707,54 @@ var EtcdClusterCoordinator = class {
649
707
  });
650
708
  return parseNodeSession(rawSession);
651
709
  }
710
+ /** Reads one node's session, and returns null once its lease has lapsed. */
652
711
  async getLiveNodeSession(clusterId, nodeId) {
653
712
  assertNonEmpty(clusterId, "clusterId");
654
713
  assertNonEmpty(nodeId, "nodeId");
655
714
  const value = await this.namespace.get(nodeSessionKey(clusterId, nodeId)).string();
656
715
  return value ? parseNodeSession(value) : null;
657
716
  }
717
+ /** Ends one node's membership at once. */
658
718
  async deregisterNodeSession(clusterId, nodeId) {
659
719
  assertNonEmpty(clusterId, "clusterId");
660
720
  assertNonEmpty(nodeId, "nodeId");
661
- const key = nodeSessionKey(clusterId, nodeId);
662
- for (const [leaseId, entry] of this.leases) {
663
- if (entry.key === key) {
664
- await this.releaseLease(leaseId);
665
- }
721
+ for (const leaseId of this.leases.leaseIdsForKey(nodeSessionKey(clusterId, nodeId))) {
722
+ await this.releaseLease(leaseId);
666
723
  }
667
724
  }
725
+ /** Writes the group's state, which seeds a new group or replaces an existing one. */
668
726
  setReplicationGroupState(input) {
669
727
  return this.groups.setReplicationGroupState(input);
670
728
  }
729
+ /** Reads the group's state, and returns null when the group is absent. */
671
730
  getReplicationGroupState(clusterId, groupId) {
672
731
  return this.groups.getReplicationGroupState(clusterId, groupId);
673
732
  }
733
+ /** Calls back on each change to the group's state, and returns a function that stops the watch. */
674
734
  watchReplicationGroup(clusterId, groupId, watcher) {
675
735
  return this.groups.watchReplicationGroup(clusterId, groupId, watcher);
676
736
  }
737
+ /** Promotes a node only while the group is still at the term the caller read. */
677
738
  compareAndAdvancePrimaryTerm(input) {
678
739
  return this.groups.compareAndAdvancePrimaryTerm(input);
679
740
  }
741
+ /** Replaces the group's in-sync set, and optionally moves its durability point. */
680
742
  updateInSyncSet(input) {
681
743
  return this.groups.updateInSyncSet(input);
682
744
  }
745
+ /** Adds one caught-up node to the in-sync set. */
683
746
  admitNodeToInSyncSet(input) {
684
747
  return this.groups.admitNodeToInSyncSet(input);
685
748
  }
749
+ /** Marks one node as being taken out of service, rebuilt, or quarantined. */
686
750
  updateNodeMaintenance(input) {
687
751
  return this.groups.updateNodeMaintenance(input);
688
752
  }
753
+ /** Promotes whichever in-sync replica is safe to write. */
689
754
  promoteEligibleReplica(input) {
690
755
  return this.groups.promoteEligibleReplica(input);
691
756
  }
757
+ /** Closes the etcd client and stops every watch. */
692
758
  async close() {
693
759
  const watcherCancels = [];
694
760
  for (const watcher of this.watchers) {
@@ -696,47 +762,17 @@ var EtcdClusterCoordinator = class {
696
762
  }
697
763
  this.watchers.clear();
698
764
  await Promise.allSettled(watcherCancels);
699
- const leaseRevokes = [];
700
- for (const entry of this.leases.values()) {
701
- leaseRevokes.push(revokeLeaseQuietly(entry.lease));
702
- }
703
- this.leases.clear();
704
765
  this.grantedNodeSessionLeaseIds.clear();
705
- await Promise.allSettled(leaseRevokes);
766
+ await this.leases.revokeAll();
706
767
  this.client.close();
707
768
  }
708
- async discardSupersededLeases(key, keepLeaseId) {
709
- const superseded = [];
710
- for (const [leaseId, entry] of this.leases) {
711
- if (entry.key === key && leaseId !== keepLeaseId) {
712
- this.leases.delete(leaseId);
713
- superseded.push(entry);
714
- }
715
- }
716
- await Promise.allSettled(superseded.map((entry) => revokeLeaseQuietly(entry.lease)));
717
- }
718
769
  async getLeaseFromKey(key) {
719
770
  const value = await this.namespace.get(key).string();
720
771
  return value ? parseLease(value) : null;
721
772
  }
722
- trackLease(lease, entry) {
723
- const fullEntry = { ...entry, lease };
724
- this.leases.set(entry.leaseId, fullEntry);
725
- lease.on("lost", (err) => {
726
- this.leases.delete(entry.leaseId);
727
- this.onWatcherError?.(err instanceof Error ? err : new CoordinatorError(String(err)));
728
- });
729
- }
730
773
  };
731
774
  function createEtcdCoordinator(options) {
732
775
  return new EtcdClusterCoordinator(options);
733
776
  }
734
- async function revokeLeaseQuietly(lease) {
735
- try {
736
- await lease.revoke();
737
- } catch {
738
- lease.release();
739
- }
740
- }
741
777
 
742
778
  export { EtcdClusterCoordinator, createEtcdCoordinator };