@delali/sirannon-db 0.3.4-next.62 → 0.3.4

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 (108) hide show
  1. package/README.md +79 -56
  2. package/dist/backup/index.d.ts +44 -45
  3. package/dist/backup/index.mjs +7 -7
  4. package/dist/backup-scheduler/index.d.ts +42 -41
  5. package/dist/backup-scheduler/index.mjs +4 -4
  6. package/dist/baseline-Co3iIsN_.d.ts +19 -0
  7. package/dist/change-tracker-DqCdeEyx.d.ts +128 -0
  8. package/dist/{chunk-VOSJBZ6Q.mjs → chunk-2ES3DT4A.mjs} +71 -33
  9. package/dist/{chunk-6CMCO7S4.mjs → chunk-2O3PP4CF.mjs} +1 -1
  10. package/dist/{chunk-E7Q7HE46.mjs → chunk-3BZLZ47R.mjs} +26 -26
  11. package/dist/chunk-4KASJKKK.mjs +2501 -0
  12. package/dist/{chunk-MYNI2WFS.mjs → chunk-52XW3Z76.mjs} +40 -8
  13. package/dist/chunk-55YRLSM2.mjs +57 -0
  14. package/dist/{chunk-KDAHYFW7.mjs → chunk-6AOJCNRU.mjs} +5 -5
  15. package/dist/{chunk-KXTYR2OQ.mjs → chunk-A3PHCO6W.mjs} +39 -111
  16. package/dist/chunk-ADNWEHGZ.mjs +299 -0
  17. package/dist/{chunk-2QLXDHAP.mjs → chunk-BLMRNSUG.mjs} +11 -2
  18. package/dist/chunk-BVMZJOVM.mjs +5 -0
  19. package/dist/{chunk-IWGIYDMZ.mjs → chunk-BZWDPF4G.mjs} +1 -1
  20. package/dist/{chunk-YVF2ZJTE.mjs → chunk-CZL4D3BM.mjs} +1 -1
  21. package/dist/{chunk-7SC2TZAS.mjs → chunk-DX4WGJL7.mjs} +2 -2
  22. package/dist/{chunk-45MKELYA.mjs → chunk-ESTZZPT5.mjs} +132 -13
  23. package/dist/{chunk-QP5L5DFJ.mjs → chunk-FJPR2CJX.mjs} +3 -3
  24. package/dist/chunk-HR7K3GZW.mjs +36 -0
  25. package/dist/{chunk-LXCGPZPA.mjs → chunk-IEKIYO4V.mjs} +1 -1
  26. package/dist/{chunk-TUD5CJ76.mjs → chunk-KB4OLJAD.mjs} +26 -19
  27. package/dist/{chunk-PBRXXISQ.mjs → chunk-KINDFBQP.mjs} +3 -2
  28. package/dist/{chunk-E6IMX7RS.mjs → chunk-KJLNYOOL.mjs} +2 -2
  29. package/dist/{chunk-BTTFW4Z4.mjs → chunk-KU32OT4H.mjs} +2 -10
  30. package/dist/{chunk-JQWGHSEH.mjs → chunk-NVSQ2T5Q.mjs} +71 -70
  31. package/dist/{chunk-W4EUTJNT.mjs → chunk-NZGSDALX.mjs} +94 -96
  32. package/dist/{chunk-U445A3C4.mjs → chunk-QGVRRKB2.mjs} +1 -1
  33. package/dist/{chunk-GYCEJNU4.mjs → chunk-R7GBXNTO.mjs} +1 -1
  34. package/dist/{chunk-7C36BCSN.mjs → chunk-VDN6FHU4.mjs} +1 -1
  35. package/dist/{chunk-6IYS4WLH.mjs → chunk-VO3ZWA2G.mjs} +142 -115
  36. package/dist/chunk-WOMADPM4.mjs +12 -0
  37. package/dist/{chunk-OUSWVNWT.mjs → chunk-YCMB6GR4.mjs} +1 -1
  38. package/dist/{chunk-YNTDOFTN.mjs → chunk-YW7IKQ7T.mjs} +1 -1
  39. package/dist/{chunk-M2BEAH5I.mjs → chunk-ZN4HI5FK.mjs} +126 -72
  40. package/dist/{chunk-SBL6GN43.mjs → chunk-ZYWI45EN.mjs} +1 -1
  41. package/dist/client/index.d.ts +135 -137
  42. package/dist/client/index.mjs +177 -82
  43. package/dist/client/topology.d.ts +31 -31
  44. package/dist/client/topology.mjs +42 -23
  45. package/dist/client-base-BxTocU1x.d.ts +398 -0
  46. package/dist/client-streams-YKANXMWO.mjs +112 -0
  47. package/dist/codegen/cli.mjs +4 -4
  48. package/dist/codegen/index.d.ts +36 -29
  49. package/dist/codegen/index.mjs +4 -4
  50. package/dist/core/index.d.ts +130 -115
  51. package/dist/core/index.mjs +387 -297
  52. package/dist/core/writer-worker.mjs +3 -3
  53. package/dist/database-CGDsXW1K.d.ts +959 -0
  54. package/dist/database-backup-DcLhxoa1.d.ts +143 -0
  55. package/dist/driver/better-sqlite3.d.ts +11 -10
  56. package/dist/driver/better-sqlite3.mjs +11 -11
  57. package/dist/driver/bun.d.ts +9 -8
  58. package/dist/driver/expo.d.ts +6 -7
  59. package/dist/driver/node.d.ts +11 -10
  60. package/dist/driver/node.mjs +13 -11
  61. package/dist/driver/wa-sqlite.d.ts +6 -6
  62. package/dist/errors-CJUkQ-KL.d.ts +199 -0
  63. package/dist/file-migrations/index.d.ts +20 -18
  64. package/dist/file-migrations/index.mjs +3 -3
  65. package/dist/forward-rpc-VRWFZXHN.mjs +44 -0
  66. package/dist/operation-registry-CGhNT-Sn.d.ts +96 -0
  67. package/dist/primary-wins-CHNOYuN7.d.ts +81 -0
  68. package/dist/protocol-DFpNNsIT.d.ts +153 -0
  69. package/dist/query-types-5B02ixH0.d.ts +129 -0
  70. package/dist/react/index.d.ts +22 -22
  71. package/dist/replication/coordinator/etcd.d.ts +37 -31
  72. package/dist/replication/coordinator/etcd.mjs +296 -131
  73. package/dist/replication/index.d.ts +201 -161
  74. package/dist/replication/index.mjs +630 -529
  75. package/dist/restore-options-CH8mE-Tb.d.ts +84 -0
  76. package/dist/server/index.d.ts +201 -191
  77. package/dist/server/index.mjs +736 -619
  78. package/dist/server-options-aYtCkefX.d.ts +317 -0
  79. package/dist/server-streams-24XL4T43.mjs +254 -0
  80. package/dist/sirannon-J3EgPVD7.d.ts +179 -0
  81. package/dist/transport/grpc.d.ts +37 -24
  82. package/dist/transport/grpc.mjs +83 -3199
  83. package/dist/transport/memory.d.ts +25 -26
  84. package/dist/transport/memory.mjs +18 -9
  85. package/dist/types-BrAiDGOb.d.ts +505 -0
  86. package/dist/types-CEJG0eFS.d.ts +88 -0
  87. package/dist/types-Dc8oWlxj.d.ts +1580 -0
  88. package/dist/types-DqODKeOq.d.ts +123 -0
  89. package/dist/types-guEKSzMr.d.ts +367 -0
  90. package/package.json +7 -7
  91. package/dist/baseline-D93hcIEE.d.ts +0 -17
  92. package/dist/change-tracker-CjRFuZdB.d.ts +0 -144
  93. package/dist/client-base-Cmd9EVme.d.ts +0 -397
  94. package/dist/database-BLQvqjsw.d.ts +0 -917
  95. package/dist/database-backup-B7oszQ2n.d.ts +0 -141
  96. package/dist/errors-Dei4GdBb.d.ts +0 -196
  97. package/dist/operation-registry-oiPuSYI6.d.ts +0 -94
  98. package/dist/primary-wins-B0np8JS3.d.ts +0 -85
  99. package/dist/protocol-HI-nY8DD.d.ts +0 -152
  100. package/dist/query-types-p1liOP-b.d.ts +0 -127
  101. package/dist/restore-options-CksYsH1l.d.ts +0 -83
  102. package/dist/server-options-Bj5iJnjx.d.ts +0 -292
  103. package/dist/sirannon-BPxC5tC2.d.ts +0 -178
  104. package/dist/types-B0EhgDbr.d.ts +0 -340
  105. package/dist/types-CbjfRwrK.d.ts +0 -499
  106. package/dist/types-CjhxcjhA.d.ts +0 -123
  107. package/dist/types-DPSmjU54.d.ts +0 -1518
  108. package/dist/types-DTO4CYhW.d.ts +0 -88
@@ -1,8 +1,8 @@
1
1
  import { compatibilityAllowsPromotion } from '../../chunk-ER7ODTDA.mjs';
2
- import { CoordinatorError, NoSafePrimaryError } from '../../chunk-BTTFW4Z4.mjs';
3
- import '../../chunk-2QLXDHAP.mjs';
4
- import '../../chunk-PBRXXISQ.mjs';
5
- import { Etcd3 } from 'etcd3';
2
+ import { CoordinatorError, NoSafePrimaryError } from '../../chunk-KU32OT4H.mjs';
3
+ import { optionalDependencyError } from '../../chunk-WOMADPM4.mjs';
4
+ import '../../chunk-BLMRNSUG.mjs';
5
+ import '../../chunk-KINDFBQP.mjs';
6
6
 
7
7
  // src/replication/coordinator/group-rules.ts
8
8
  var MIN_AUTOMATIC_FAILOVER_VOTERS = 3;
@@ -287,7 +287,10 @@ function controllerLeaseKey(clusterId) {
287
287
  return `clusters/${encodeKey(clusterId)}/controller`;
288
288
  }
289
289
  function nodeSessionKey(clusterId, nodeId) {
290
- return `clusters/${encodeKey(clusterId)}/nodes/${encodeKey(nodeId)}`;
290
+ return `${nodeSessionPrefix(clusterId)}${encodeKey(nodeId)}`;
291
+ }
292
+ function nodeSessionPrefix(clusterId) {
293
+ return `clusters/${encodeKey(clusterId)}/nodes/`;
291
294
  }
292
295
  function replicationGroupKey(clusterId, groupId) {
293
296
  return `clusters/${encodeKey(clusterId)}/groups/${encodeKey(groupId)}`;
@@ -299,6 +302,145 @@ function encodeKey(value) {
299
302
  return encodeURIComponent(value);
300
303
  }
301
304
 
305
+ // src/replication/coordinator/etcd-lease-registry.ts
306
+ async function readLeaseAt(namespace, key) {
307
+ const value = await namespace.get(key).string();
308
+ return value ? parseLease(value) : null;
309
+ }
310
+ async function revokeLeaseQuietly(lease) {
311
+ try {
312
+ await lease.revoke();
313
+ } catch {
314
+ lease.release();
315
+ }
316
+ }
317
+ var EtcdLeaseRegistry = class {
318
+ constructor(onLeaseLost) {
319
+ this.onLeaseLost = onLeaseLost;
320
+ }
321
+ entries = /* @__PURE__ */ new Map();
322
+ get(leaseId) {
323
+ return this.entries.get(leaseId);
324
+ }
325
+ forget(leaseId) {
326
+ this.entries.delete(leaseId);
327
+ }
328
+ leaseIdsForKey(key) {
329
+ const matches = [];
330
+ for (const [leaseId, entry] of this.entries) {
331
+ if (entry.key === key) matches.push(leaseId);
332
+ }
333
+ return matches;
334
+ }
335
+ track(lease, entry) {
336
+ this.entries.set(entry.leaseId, { ...entry, lease });
337
+ lease.on("lost", (err) => {
338
+ this.entries.delete(entry.leaseId);
339
+ this.onLeaseLost?.(err instanceof Error ? err : new CoordinatorError(String(err)));
340
+ });
341
+ }
342
+ async discardSuperseded(key, keepLeaseId) {
343
+ const superseded = [];
344
+ for (const [leaseId, entry] of this.entries) {
345
+ if (entry.key === key && leaseId !== keepLeaseId) {
346
+ this.entries.delete(leaseId);
347
+ superseded.push(entry);
348
+ }
349
+ }
350
+ await Promise.allSettled(superseded.map((entry) => revokeLeaseQuietly(entry.lease)));
351
+ }
352
+ async revokeAll() {
353
+ const revokes = [];
354
+ for (const entry of this.entries.values()) {
355
+ revokes.push(revokeLeaseQuietly(entry.lease));
356
+ }
357
+ this.entries.clear();
358
+ await Promise.allSettled(revokes);
359
+ }
360
+ };
361
+
362
+ // src/replication/coordinator/etcd-controller-lease.ts
363
+ async function acquireEtcdControllerLease(namespace, leases, input) {
364
+ const key = controllerLeaseKey(input.clusterId);
365
+ const lease = namespace.lease(ttlMsToSeconds(input.ttlMs));
366
+ const leaseId = await lease.grant();
367
+ const grantedAtMs = Date.now();
368
+ const value = serializeLease({
369
+ id: leaseId,
370
+ kind: "controller",
371
+ clusterId: input.clusterId,
372
+ holderId: input.holderId,
373
+ ttlMs: input.ttlMs,
374
+ grantedAtMs,
375
+ expiresAtMs: grantedAtMs + input.ttlMs,
376
+ metadata: cloneMetadata(input.metadata)
377
+ });
378
+ const result = await namespace.if(key, "Create", "==", 0).then(namespace.put(key).value(value).lease(leaseId)).commit();
379
+ if (!result.succeeded) {
380
+ await revokeLeaseQuietly(lease);
381
+ return { acquired: false, lease: await readLeaseAt(namespace, key) };
382
+ }
383
+ leases.track(lease, {
384
+ leaseId,
385
+ key,
386
+ ttlMs: input.ttlMs,
387
+ ttlSeconds: ttlMsToSeconds(input.ttlMs),
388
+ kind: "controller",
389
+ clusterId: input.clusterId,
390
+ holderId: input.holderId,
391
+ metadata: cloneMetadata(input.metadata)
392
+ });
393
+ return { acquired: true, lease: parseLease(value) };
394
+ }
395
+ async function watchEtcdControllerLease(namespace, clusterId, watcher, watchers, onWatcherError) {
396
+ const key = controllerLeaseKey(clusterId);
397
+ let holder = null;
398
+ let reading = true;
399
+ let changedWhileReading = false;
400
+ const report = () => {
401
+ try {
402
+ watcher(holder);
403
+ } catch (err) {
404
+ onWatcherError?.(err instanceof Error ? err : new Error(String(err)));
405
+ }
406
+ };
407
+ const record = (next) => {
408
+ holder = next;
409
+ if (reading) {
410
+ changedWhileReading = true;
411
+ return;
412
+ }
413
+ report();
414
+ };
415
+ const etcdWatcher = await namespace.watch().key(key).create();
416
+ watchers.add(etcdWatcher);
417
+ etcdWatcher.on("put", (kv) => {
418
+ record(parseLease(kv.value.toString("utf8")));
419
+ });
420
+ etcdWatcher.on("delete", () => {
421
+ record(null);
422
+ });
423
+ etcdWatcher.on("error", (err) => {
424
+ onWatcherError?.(err instanceof Error ? err : new Error(String(err)));
425
+ });
426
+ try {
427
+ const value = await namespace.get(key).string();
428
+ if (!changedWhileReading) {
429
+ holder = value ? parseLease(value) : null;
430
+ }
431
+ } catch (err) {
432
+ watchers.delete(etcdWatcher);
433
+ await etcdWatcher.cancel();
434
+ throw err;
435
+ }
436
+ reading = false;
437
+ report();
438
+ return async () => {
439
+ watchers.delete(etcdWatcher);
440
+ await etcdWatcher.cancel();
441
+ };
442
+ }
443
+
302
444
  // src/replication/coordinator/etcd-group-store.ts
303
445
  var MAX_CAS_ATTEMPTS = 5;
304
446
  var MAX_PROMOTION_ATTEMPTS = 3;
@@ -473,115 +615,104 @@ var EtcdGroupStore = class {
473
615
  }
474
616
  };
475
617
 
476
- // src/replication/coordinator/etcd-lease-registry.ts
477
- async function revokeLeaseQuietly(lease) {
618
+ // src/replication/coordinator/etcd-loader.ts
619
+ var ETCD3_DEPENDENCY = {
620
+ packageName: "etcd3",
621
+ neededBy: "The etcd coordinator",
622
+ installCommand: "pnpm add -E etcd3",
623
+ code: "COORDINATOR_DEPENDENCY_MISSING"
624
+ };
625
+ async function loadEtcd3Module() {
478
626
  try {
479
- await lease.revoke();
480
- } catch {
481
- lease.release();
627
+ return await import('etcd3');
628
+ } catch (err) {
629
+ throw optionalDependencyError(ETCD3_DEPENDENCY, err);
482
630
  }
483
631
  }
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);
632
+
633
+ // src/replication/coordinator/etcd-session-watch.ts
634
+ function nodeIdOf(key, prefix) {
635
+ return decodeURIComponent(key.slice(prefix.length));
636
+ }
637
+ async function watchEtcdNodeSessions(namespace, clusterId, watcher, watchers, onWatcherError) {
638
+ const prefix = nodeSessionPrefix(clusterId);
639
+ const live = /* @__PURE__ */ new Set();
640
+ const goneWhileReading = /* @__PURE__ */ new Set();
641
+ let reading = true;
642
+ const report = () => {
643
+ try {
644
+ watcher([...live]);
645
+ } catch (err) {
646
+ onWatcherError?.(err instanceof Error ? err : new Error(String(err)));
499
647
  }
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
- }
648
+ };
649
+ const etcdWatcher = await namespace.watch().prefix(prefix).create();
650
+ watchers.add(etcdWatcher);
651
+ etcdWatcher.on("put", (kv) => {
652
+ live.add(nodeIdOf(kv.key.toString("utf8"), prefix));
653
+ if (!reading) report();
654
+ });
655
+ etcdWatcher.on("delete", (kv) => {
656
+ const nodeId = nodeIdOf(kv.key.toString("utf8"), prefix);
657
+ live.delete(nodeId);
658
+ if (reading) {
659
+ goneWhileReading.add(nodeId);
660
+ return;
516
661
  }
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));
662
+ report();
663
+ });
664
+ etcdWatcher.on("error", (err) => {
665
+ onWatcherError?.(err instanceof Error ? err : new Error(String(err)));
666
+ });
667
+ try {
668
+ for (const key of await namespace.getAll().prefix(prefix).keys()) {
669
+ const nodeId = nodeIdOf(key, prefix);
670
+ if (!goneWhileReading.has(nodeId)) {
671
+ live.add(nodeId);
672
+ }
523
673
  }
524
- this.entries.clear();
525
- await Promise.allSettled(revokes);
526
- }
527
- };
674
+ } catch (err) {
675
+ watchers.delete(etcdWatcher);
676
+ await etcdWatcher.cancel();
677
+ throw err;
678
+ }
679
+ reading = false;
680
+ report();
681
+ return async () => {
682
+ watchers.delete(etcdWatcher);
683
+ await etcdWatcher.cancel();
684
+ };
685
+ }
528
686
 
529
687
  // src/replication/coordinator/etcd.ts
530
688
  var EtcdClusterCoordinator = class {
531
- client;
532
- namespace;
689
+ options;
690
+ connection = null;
533
691
  onWatcherError;
534
692
  leases;
535
693
  grantedNodeSessionLeaseIds = /* @__PURE__ */ new Map();
536
694
  watchers = /* @__PURE__ */ new Set();
537
- groups;
538
695
  constructor(options) {
539
696
  assertEtcdOptions(options);
540
- this.client = new Etcd3(toEtcdOptions(options));
541
- this.namespace = this.client.namespace(normaliseKeyPrefix(options.keyPrefix));
697
+ this.options = options;
542
698
  this.onWatcherError = options.onWatcherError;
543
699
  this.leases = new EtcdLeaseRegistry(this.onWatcherError);
544
- this.groups = new EtcdGroupStore(this.namespace, this.watchers, this.onWatcherError);
545
700
  }
546
- /** Bids for the controller lease, and reports who holds it. */
701
+ /** Tries to acquire the controller lease, and returns whether it succeeded along with the current lease. */
547
702
  async tryAcquireControllerLease(input) {
548
703
  assertNonEmpty(input.clusterId, "clusterId");
549
704
  assertNonEmpty(input.holderId, "holderId");
550
705
  assertPositiveTtl(input.ttlMs);
551
- const key = controllerLeaseKey(input.clusterId);
552
- const lease = this.namespace.lease(ttlMsToSeconds(input.ttlMs));
553
- const leaseId = await lease.grant();
554
- const grantedAtMs = Date.now();
555
- const value = serializeLease({
556
- id: leaseId,
557
- kind: "controller",
558
- clusterId: input.clusterId,
559
- holderId: input.holderId,
560
- ttlMs: input.ttlMs,
561
- grantedAtMs,
562
- expiresAtMs: grantedAtMs + input.ttlMs,
563
- metadata: cloneMetadata(input.metadata)
564
- });
565
- const result = await this.namespace.if(key, "Create", "==", 0).then(this.namespace.put(key).value(value).lease(leaseId)).commit();
566
- if (!result.succeeded) {
567
- await revokeLeaseQuietly(lease);
568
- const current = await this.getLeaseFromKey(key);
569
- return { acquired: false, lease: current };
570
- }
571
- this.leases.track(lease, {
572
- leaseId,
573
- key,
574
- ttlMs: input.ttlMs,
575
- ttlSeconds: ttlMsToSeconds(input.ttlMs),
576
- kind: "controller",
577
- clusterId: input.clusterId,
578
- holderId: input.holderId,
579
- metadata: cloneMetadata(input.metadata)
580
- });
581
- const parsed = parseLease(value);
582
- return { acquired: true, lease: parsed };
706
+ const { namespace } = await this.connect();
707
+ return acquireEtcdControllerLease(namespace, this.leases, input);
708
+ }
709
+ /** Calls `watcher` with the current controller lease when the watch starts and after each change, and returns a function that stops the watch. */
710
+ async watchControllerLease(clusterId, watcher) {
711
+ assertNonEmpty(clusterId, "clusterId");
712
+ const { namespace } = await this.connect();
713
+ return watchEtcdControllerLease(namespace, clusterId, watcher, this.watchers, this.onWatcherError);
583
714
  }
584
- /** Extends a lease, and reports false once it has already lapsed. */
715
+ /** Extends a lease, and returns false for a lease that is expired or unknown to this coordinator. */
585
716
  async renewLease(leaseId, ttlMs) {
586
717
  assertNonEmpty(leaseId, "leaseId");
587
718
  assertPositiveTtl(ttlMs);
@@ -607,9 +738,10 @@ var EtcdClusterCoordinator = class {
607
738
  metadata: cloneMetadata(entry.metadata)
608
739
  };
609
740
  const value = entry.kind === "node-session" && entry.nodeSession ? JSON.stringify({ ...entry.nodeSession, lease: leaseValue }) : serializeLease(leaseValue);
741
+ const { namespace } = await this.connect();
610
742
  let refreshed;
611
743
  try {
612
- const result = await this.namespace.if(entry.key, "Lease", "==", leaseId).then(this.namespace.put(entry.key).value(value).ignoreLease()).commit();
744
+ const result = await namespace.if(entry.key, "Lease", "==", leaseId).then(namespace.put(entry.key).value(value).ignoreLease()).commit();
613
745
  refreshed = result.succeeded === true;
614
746
  } catch {
615
747
  return true;
@@ -621,7 +753,7 @@ var EtcdClusterCoordinator = class {
621
753
  entry.ttlMs = ttlMs;
622
754
  return true;
623
755
  }
624
- /** Gives up a lease at once instead of waiting for it to lapse. */
756
+ /** Deletes a lease's key if the key still refers to that lease, then revokes the lease, and returns whether the delete succeeded. */
625
757
  async releaseLease(leaseId) {
626
758
  assertNonEmpty(leaseId, "leaseId");
627
759
  const entry = this.leases.get(leaseId);
@@ -629,11 +761,12 @@ var EtcdClusterCoordinator = class {
629
761
  return false;
630
762
  }
631
763
  this.leases.forget(leaseId);
632
- const currentValue = await this.namespace.get(entry.key).string();
764
+ const { namespace } = await this.connect();
765
+ const currentValue = await namespace.get(entry.key).string();
633
766
  const currentLeaseId = currentValue ? parseLeaseIdForEntry(entry.kind, currentValue) : null;
634
767
  let released = false;
635
768
  if (currentLeaseId === leaseId && currentValue) {
636
- const result = await this.namespace.if(entry.key, "Value", "==", currentValue).then(this.namespace.delete().key(entry.key)).commit();
769
+ const result = await namespace.if(entry.key, "Value", "==", currentValue).then(namespace.delete().key(entry.key)).commit();
637
770
  released = result.succeeded === true;
638
771
  }
639
772
  await revokeLeaseQuietly(entry.lease);
@@ -644,8 +777,9 @@ var EtcdClusterCoordinator = class {
644
777
  assertNonEmpty(input.clusterId, "clusterId");
645
778
  assertNonEmpty(input.nodeId, "nodeId");
646
779
  assertPositiveTtl(input.ttlMs);
780
+ const { namespace } = await this.connect();
647
781
  const key = nodeSessionKey(input.clusterId, input.nodeId);
648
- const existingRawSession = await this.namespace.get(key).string();
782
+ const existingRawSession = await namespace.get(key).string();
649
783
  if (existingRawSession) {
650
784
  const existingSession = parseNodeSession(existingRawSession);
651
785
  const supersedesOwnSession = this.grantedNodeSessionLeaseIds.get(key) === existingSession.lease.id;
@@ -653,7 +787,7 @@ var EtcdClusterCoordinator = class {
653
787
  throw new CoordinatorError(`Node session '${input.nodeId}' is already registered`);
654
788
  }
655
789
  }
656
- const lease = this.namespace.lease(ttlMsToSeconds(input.ttlMs));
790
+ const lease = namespace.lease(ttlMsToSeconds(input.ttlMs));
657
791
  const leaseId = await lease.grant();
658
792
  const grantedAtMs = Date.now();
659
793
  const session = {
@@ -677,8 +811,8 @@ var EtcdClusterCoordinator = class {
677
811
  metadata: cloneMetadata(input.metadata)
678
812
  };
679
813
  const rawSession = JSON.stringify(session);
680
- const transaction = existingRawSession ? this.namespace.if(key, "Value", "==", existingRawSession) : this.namespace.if(key, "Create", "==", 0);
681
- const result = await transaction.then(this.namespace.put(key).value(rawSession).lease(leaseId)).commit();
814
+ const transaction = existingRawSession ? namespace.if(key, "Value", "==", existingRawSession) : namespace.if(key, "Create", "==", 0);
815
+ const result = await transaction.then(namespace.put(key).value(rawSession).lease(leaseId)).commit();
682
816
  if (!result.succeeded) {
683
817
  await revokeLeaseQuietly(lease);
684
818
  throw new CoordinatorError(`Node session '${input.nodeId}' registration conflicted with a concurrent write`);
@@ -707,14 +841,21 @@ var EtcdClusterCoordinator = class {
707
841
  });
708
842
  return parseNodeSession(rawSession);
709
843
  }
710
- /** Reads one node's session, and returns null once its lease has lapsed. */
844
+ /** Returns one node's session, or null once its lease expires. */
711
845
  async getLiveNodeSession(clusterId, nodeId) {
712
846
  assertNonEmpty(clusterId, "clusterId");
713
847
  assertNonEmpty(nodeId, "nodeId");
714
- const value = await this.namespace.get(nodeSessionKey(clusterId, nodeId)).string();
848
+ const { namespace } = await this.connect();
849
+ const value = await namespace.get(nodeSessionKey(clusterId, nodeId)).string();
715
850
  return value ? parseNodeSession(value) : null;
716
851
  }
717
- /** Ends one node's membership at once. */
852
+ /** Calls `watcher` with the IDs of every node that holds a live session, and returns a function that stops the watch. */
853
+ async watchNodeSessions(clusterId, watcher) {
854
+ assertNonEmpty(clusterId, "clusterId");
855
+ const { namespace } = await this.connect();
856
+ return watchEtcdNodeSessions(namespace, clusterId, watcher, this.watchers, this.onWatcherError);
857
+ }
858
+ /** Ends one node's session at once by releasing its leases. */
718
859
  async deregisterNodeSession(clusterId, nodeId) {
719
860
  assertNonEmpty(clusterId, "clusterId");
720
861
  assertNonEmpty(nodeId, "nodeId");
@@ -722,39 +863,47 @@ var EtcdClusterCoordinator = class {
722
863
  await this.releaseLease(leaseId);
723
864
  }
724
865
  }
725
- /** Writes the group's state, which seeds a new group or replaces an existing one. */
726
- setReplicationGroupState(input) {
727
- return this.groups.setReplicationGroupState(input);
866
+ /** Writes the group's state, which creates a new group or replaces an existing one. */
867
+ async setReplicationGroupState(input) {
868
+ const { groups } = await this.connect();
869
+ return groups.setReplicationGroupState(input);
728
870
  }
729
- /** Reads the group's state, and returns null when the group is absent. */
730
- getReplicationGroupState(clusterId, groupId) {
731
- return this.groups.getReplicationGroupState(clusterId, groupId);
871
+ /** Returns the group's state, or null when etcd has no state for the group. */
872
+ async getReplicationGroupState(clusterId, groupId) {
873
+ const { groups } = await this.connect();
874
+ return groups.getReplicationGroupState(clusterId, groupId);
732
875
  }
733
- /** Calls back on each change to the group's state, and returns a function that stops the watch. */
734
- watchReplicationGroup(clusterId, groupId, watcher) {
735
- return this.groups.watchReplicationGroup(clusterId, groupId, watcher);
876
+ /** Calls `watcher` with the new state after each change to the group, and returns a function that stops the watch. */
877
+ async watchReplicationGroup(clusterId, groupId, watcher) {
878
+ const { groups } = await this.connect();
879
+ return groups.watchReplicationGroup(clusterId, groupId, watcher);
736
880
  }
737
- /** Promotes a node only while the group is still at the term the caller read. */
738
- compareAndAdvancePrimaryTerm(input) {
739
- return this.groups.compareAndAdvancePrimaryTerm(input);
881
+ /** Makes a node primary and advances the term, but only while the group is still at the term that the caller expects. */
882
+ async compareAndAdvancePrimaryTerm(input) {
883
+ const { groups } = await this.connect();
884
+ return groups.compareAndAdvancePrimaryTerm(input);
740
885
  }
741
- /** Replaces the group's in-sync set, and optionally moves its durability point. */
742
- updateInSyncSet(input) {
743
- return this.groups.updateInSyncSet(input);
886
+ /** Replaces the group's in-sync set with a subset of it, and can advance the durability point. */
887
+ async updateInSyncSet(input) {
888
+ const { groups } = await this.connect();
889
+ return groups.updateInSyncSet(input);
744
890
  }
745
891
  /** Adds one caught-up node to the in-sync set. */
746
- admitNodeToInSyncSet(input) {
747
- return this.groups.admitNodeToInSyncSet(input);
892
+ async admitNodeToInSyncSet(input) {
893
+ const { groups } = await this.connect();
894
+ return groups.admitNodeToInSyncSet(input);
748
895
  }
749
- /** Marks one node as being taken out of service, rebuilt, or quarantined. */
750
- updateNodeMaintenance(input) {
751
- return this.groups.updateNodeMaintenance(input);
896
+ /** Sets or clears one node's draining, repairing, and faulted flags. */
897
+ async updateNodeMaintenance(input) {
898
+ const { groups } = await this.connect();
899
+ return groups.updateNodeMaintenance(input);
752
900
  }
753
- /** Promotes whichever in-sync replica is safe to write. */
754
- promoteEligibleReplica(input) {
755
- return this.groups.promoteEligibleReplica(input);
901
+ /** Promotes an eligible in-sync replica, and throws a `NoSafePrimaryError` when no replica qualifies. */
902
+ async promoteEligibleReplica(input) {
903
+ const { groups } = await this.connect();
904
+ return groups.promoteEligibleReplica(input);
756
905
  }
757
- /** Closes the etcd client and stops every watch. */
906
+ /** Stops every watch, revokes every lease that this coordinator granted, and closes the etcd client. */
758
907
  async close() {
759
908
  const watcherCancels = [];
760
909
  for (const watcher of this.watchers) {
@@ -764,11 +913,27 @@ var EtcdClusterCoordinator = class {
764
913
  await Promise.allSettled(watcherCancels);
765
914
  this.grantedNodeSessionLeaseIds.clear();
766
915
  await this.leases.revokeAll();
767
- this.client.close();
768
- }
769
- async getLeaseFromKey(key) {
770
- const value = await this.namespace.get(key).string();
771
- return value ? parseLease(value) : null;
916
+ const connection = this.connection;
917
+ if (!connection) return;
918
+ const opened = await connection.catch(() => null);
919
+ opened?.client.close();
920
+ }
921
+ connect() {
922
+ if (!this.connection) {
923
+ this.connection = loadEtcd3Module().then(
924
+ ({ Etcd3 }) => {
925
+ const client = new Etcd3(toEtcdOptions(this.options));
926
+ const namespace = client.namespace(normaliseKeyPrefix(this.options.keyPrefix));
927
+ const groups = new EtcdGroupStore(namespace, this.watchers, this.onWatcherError);
928
+ return { client, namespace, groups };
929
+ },
930
+ (err) => {
931
+ this.connection = null;
932
+ throw err;
933
+ }
934
+ );
935
+ }
936
+ return this.connection;
772
937
  }
773
938
  };
774
939
  function createEtcdCoordinator(options) {