@serfab/cadre-core 1.16.0 → 1.18.0

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 (268) hide show
  1. package/README.md +4 -3
  2. package/dist/bootstrap-peer-store-file.js +1 -1
  3. package/dist/bootstrap-peer-store.d.ts +1 -1
  4. package/dist/bootstrap-peer-store.js +4 -4
  5. package/dist/cadre-invite-protocol.d.ts +81 -142
  6. package/dist/cadre-invite-protocol.js +168 -458
  7. package/dist/cadre-invite-protocol.js.map +1 -1
  8. package/dist/cadre-join-request.d.ts +15 -2
  9. package/dist/cadre-join-request.js +22 -3
  10. package/dist/cadre-join-request.js.map +1 -1
  11. package/dist/cadre-node.d.ts +1089 -475
  12. package/dist/cadre-node.js +2170 -1037
  13. package/dist/cadre-node.js.map +1 -1
  14. package/dist/control-authorization.d.ts +36 -14
  15. package/dist/control-authorization.js +20 -3
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.d.ts +88 -32
  18. package/dist/control-cohort.js +91 -33
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +856 -318
  21. package/dist/control-database.js +1422 -701
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-formation-recorder.d.ts +46 -43
  24. package/dist/control-formation-recorder.js +77 -78
  25. package/dist/control-formation-recorder.js.map +1 -1
  26. package/dist/control-protocol-guard.d.ts +11 -4
  27. package/dist/control-protocol-guard.js +20 -5
  28. package/dist/control-protocol-guard.js.map +1 -1
  29. package/dist/control-schema-version.d.ts +97 -0
  30. package/dist/control-schema-version.js +225 -0
  31. package/dist/control-schema-version.js.map +1 -0
  32. package/dist/control-schema.d.ts +45 -5
  33. package/dist/control-schema.js +262 -78
  34. package/dist/control-schema.js.map +1 -1
  35. package/dist/control-write-retry.d.ts +45 -10
  36. package/dist/control-write-retry.js +92 -22
  37. package/dist/control-write-retry.js.map +1 -1
  38. package/dist/delegate-admission.d.ts +13 -0
  39. package/dist/delegate-admission.js +27 -0
  40. package/dist/delegate-admission.js.map +1 -1
  41. package/dist/ed25519-key.d.ts +10 -7
  42. package/dist/ed25519-key.js +23 -20
  43. package/dist/ed25519-key.js.map +1 -1
  44. package/dist/edge-node/config.d.ts +122 -0
  45. package/dist/edge-node/config.js +199 -0
  46. package/dist/edge-node/config.js.map +1 -0
  47. package/dist/edge-node/dissolve.d.ts +95 -0
  48. package/dist/edge-node/dissolve.js +184 -0
  49. package/dist/edge-node/dissolve.js.map +1 -0
  50. package/dist/edge-node/edge-join.d.ts +44 -0
  51. package/dist/edge-node/edge-join.js +227 -0
  52. package/dist/edge-node/edge-join.js.map +1 -0
  53. package/dist/edge-node/edge-node.d.ts +295 -0
  54. package/dist/edge-node/edge-node.js +704 -0
  55. package/dist/edge-node/edge-node.js.map +1 -0
  56. package/dist/edge-node/index.d.ts +18 -0
  57. package/dist/edge-node/index.js +18 -0
  58. package/dist/edge-node/index.js.map +1 -0
  59. package/dist/edge-node/join-credential.d.ts +11 -0
  60. package/dist/edge-node/join-credential.js +11 -0
  61. package/dist/edge-node/join-credential.js.map +1 -0
  62. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  63. package/dist/edge-node/lazy-raw-storage.js +134 -0
  64. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  65. package/dist/edge-node/node-local.d.ts +60 -0
  66. package/dist/edge-node/node-local.js +58 -0
  67. package/dist/edge-node/node-local.js.map +1 -0
  68. package/dist/edge-node/options.d.ts +83 -0
  69. package/dist/edge-node/options.js +110 -0
  70. package/dist/edge-node/options.js.map +1 -0
  71. package/dist/edge-node/owner-grant.d.ts +41 -0
  72. package/dist/edge-node/owner-grant.js +38 -0
  73. package/dist/edge-node/owner-grant.js.map +1 -0
  74. package/dist/edge-node/platform.d.ts +140 -0
  75. package/dist/edge-node/platform.js +12 -0
  76. package/dist/edge-node/platform.js.map +1 -0
  77. package/dist/edge-node/strand-move.d.ts +115 -0
  78. package/dist/edge-node/strand-move.js +223 -0
  79. package/dist/edge-node/strand-move.js.map +1 -0
  80. package/dist/edge-node/strands.d.ts +28 -0
  81. package/dist/edge-node/strands.js +60 -0
  82. package/dist/edge-node/strands.js.map +1 -0
  83. package/dist/enrolled-machine-store.d.ts +3 -3
  84. package/dist/enrolled-machine-store.js +3 -3
  85. package/dist/formation-token.d.ts +48 -0
  86. package/dist/formation-token.js +26 -0
  87. package/dist/formation-token.js.map +1 -0
  88. package/dist/index.d.ts +27 -17
  89. package/dist/index.js +39 -17
  90. package/dist/index.js.map +1 -1
  91. package/dist/joined-strand-store.d.ts +29 -12
  92. package/dist/joined-strand-store.js +66 -27
  93. package/dist/joined-strand-store.js.map +1 -1
  94. package/dist/key-store.d.ts +9 -4
  95. package/dist/key-store.js +9 -4
  96. package/dist/key-store.js.map +1 -1
  97. package/dist/link-budget.d.ts +9 -7
  98. package/dist/link-budget.js +9 -7
  99. package/dist/link-budget.js.map +1 -1
  100. package/dist/machine-removal.d.ts +19 -39
  101. package/dist/machine-removal.js +24 -98
  102. package/dist/machine-removal.js.map +1 -1
  103. package/dist/member-redemption.d.ts +256 -0
  104. package/dist/member-redemption.js +490 -0
  105. package/dist/member-redemption.js.map +1 -0
  106. package/dist/membership-view.d.ts +91 -0
  107. package/dist/membership-view.js +110 -0
  108. package/dist/membership-view.js.map +1 -0
  109. package/dist/owner-anchor-sync.d.ts +4 -4
  110. package/dist/owner-anchor-sync.js +16 -15
  111. package/dist/owner-anchor-sync.js.map +1 -1
  112. package/dist/owner-grant-protocol.d.ts +251 -0
  113. package/dist/owner-grant-protocol.js +344 -0
  114. package/dist/owner-grant-protocol.js.map +1 -0
  115. package/dist/owner-key-export.d.ts +70 -0
  116. package/dist/owner-key-export.js +279 -0
  117. package/dist/owner-key-export.js.map +1 -0
  118. package/dist/owner-key-flows.d.ts +55 -0
  119. package/dist/owner-key-flows.js +60 -0
  120. package/dist/owner-key-flows.js.map +1 -0
  121. package/dist/owner-key-store.d.ts +31 -0
  122. package/dist/owner-key-store.js +81 -0
  123. package/dist/owner-key-store.js.map +1 -0
  124. package/dist/owner-keys.d.ts +78 -0
  125. package/dist/owner-keys.js +133 -0
  126. package/dist/owner-keys.js.map +1 -0
  127. package/dist/owner-signer.d.ts +54 -0
  128. package/dist/owner-signer.js +51 -0
  129. package/dist/owner-signer.js.map +1 -0
  130. package/dist/peer-addr-book.d.ts +10 -1
  131. package/dist/peer-addr-book.js +4 -3
  132. package/dist/peer-addr-book.js.map +1 -1
  133. package/dist/peer-authorization.d.ts +83 -18
  134. package/dist/peer-authorization.js +123 -53
  135. package/dist/peer-authorization.js.map +1 -1
  136. package/dist/peer-join-backfill.d.ts +89 -20
  137. package/dist/peer-join-backfill.js +265 -108
  138. package/dist/peer-join-backfill.js.map +1 -1
  139. package/dist/pending-join-runner.d.ts +48 -18
  140. package/dist/pending-join-runner.js +74 -56
  141. package/dist/pending-join-runner.js.map +1 -1
  142. package/dist/provider/rent-node.js +1 -1
  143. package/dist/provider/rent-node.js.map +1 -1
  144. package/dist/seed-bootstrap.d.ts +162 -133
  145. package/dist/seed-bootstrap.js +300 -283
  146. package/dist/seed-bootstrap.js.map +1 -1
  147. package/dist/seed-trust-policy.js +4 -4
  148. package/dist/strand-addr-protocol.d.ts +4 -1
  149. package/dist/strand-addr-protocol.js +8 -2
  150. package/dist/strand-addr-protocol.js.map +1 -1
  151. package/dist/strand-cohort.d.ts +3 -0
  152. package/dist/strand-cohort.js.map +1 -1
  153. package/dist/strand-database.d.ts +24 -2
  154. package/dist/strand-database.js +29 -6
  155. package/dist/strand-database.js.map +1 -1
  156. package/dist/strand-first-sync-gate.d.ts +176 -88
  157. package/dist/strand-first-sync-gate.js +288 -121
  158. package/dist/strand-first-sync-gate.js.map +1 -1
  159. package/dist/strand-formation-deadlines.d.ts +9 -9
  160. package/dist/strand-formation-deadlines.js +9 -9
  161. package/dist/strand-formation-manager.d.ts +23 -17
  162. package/dist/strand-formation-manager.js +46 -44
  163. package/dist/strand-formation-manager.js.map +1 -1
  164. package/dist/strand-formation-protocol.d.ts +5 -8
  165. package/dist/strand-formation-protocol.js +101 -16
  166. package/dist/strand-formation-protocol.js.map +1 -1
  167. package/dist/strand-instance-manager.d.ts +27 -18
  168. package/dist/strand-instance-manager.js +119 -50
  169. package/dist/strand-instance-manager.js.map +1 -1
  170. package/dist/strand-membership-reconciler.d.ts +16 -13
  171. package/dist/strand-membership-reconciler.js +30 -22
  172. package/dist/strand-membership-reconciler.js.map +1 -1
  173. package/dist/strand-membership-writer.d.ts +45 -22
  174. package/dist/strand-membership-writer.js +226 -131
  175. package/dist/strand-membership-writer.js.map +1 -1
  176. package/dist/strand-move.d.ts +6 -5
  177. package/dist/strand-move.js +36 -32
  178. package/dist/strand-move.js.map +1 -1
  179. package/dist/strand-peer-redial.js +2 -3
  180. package/dist/strand-peer-redial.js.map +1 -1
  181. package/dist/strand-revocation-enforcer.d.ts +134 -60
  182. package/dist/strand-revocation-enforcer.js +144 -71
  183. package/dist/strand-revocation-enforcer.js.map +1 -1
  184. package/dist/strand-solicitation.d.ts +50 -39
  185. package/dist/strand-solicitation.js +10 -3
  186. package/dist/strand-solicitation.js.map +1 -1
  187. package/dist/strand-watcher.d.ts +80 -13
  188. package/dist/strand-watcher.js +152 -48
  189. package/dist/strand-watcher.js.map +1 -1
  190. package/dist/table-change-watch.d.ts +63 -0
  191. package/dist/table-change-watch.js +209 -0
  192. package/dist/table-change-watch.js.map +1 -0
  193. package/dist/timeout-scheduler.d.ts +4 -3
  194. package/dist/timeout-scheduler.js +4 -3
  195. package/dist/timeout-scheduler.js.map +1 -1
  196. package/dist/types.d.ts +374 -150
  197. package/dist/types.js +4 -4
  198. package/dist/types.js.map +1 -1
  199. package/package.json +17 -8
  200. package/src/bootstrap-peer-store-file.ts +44 -44
  201. package/src/bootstrap-peer-store.ts +214 -214
  202. package/src/cadre-invite-protocol.ts +740 -1052
  203. package/src/cadre-join-request.ts +24 -3
  204. package/src/cadre-node.ts +2402 -1079
  205. package/src/control-authorization.ts +47 -14
  206. package/src/control-cohort.ts +204 -128
  207. package/src/control-database.ts +1749 -820
  208. package/src/control-formation-recorder.ts +359 -353
  209. package/src/control-protocol-guard.ts +23 -6
  210. package/src/control-schema-version.ts +301 -0
  211. package/src/control-schema.ts +273 -78
  212. package/src/control-write-retry.ts +641 -567
  213. package/src/delegate-admission.ts +351 -314
  214. package/src/ed25519-key.ts +174 -171
  215. package/src/edge-node/config.ts +277 -0
  216. package/src/edge-node/dissolve.ts +251 -0
  217. package/src/edge-node/edge-join.ts +319 -0
  218. package/src/edge-node/edge-node.ts +1045 -0
  219. package/src/edge-node/index.ts +59 -0
  220. package/src/edge-node/join-credential.ts +16 -0
  221. package/src/edge-node/lazy-raw-storage.ts +178 -0
  222. package/src/edge-node/node-local.ts +84 -0
  223. package/src/edge-node/options.ts +179 -0
  224. package/src/edge-node/owner-grant.ts +63 -0
  225. package/src/edge-node/platform.ts +151 -0
  226. package/src/edge-node/strand-move.ts +270 -0
  227. package/src/edge-node/strands.ts +72 -0
  228. package/src/enrolled-machine-store.ts +3 -3
  229. package/src/formation-token.ts +63 -0
  230. package/src/index.ts +899 -789
  231. package/src/joined-strand-store.ts +73 -28
  232. package/src/key-store.ts +10 -4
  233. package/src/link-budget.ts +9 -7
  234. package/src/machine-removal.ts +26 -111
  235. package/src/member-redemption.ts +649 -0
  236. package/src/membership-view.ts +144 -0
  237. package/src/owner-anchor-sync.ts +19 -18
  238. package/src/owner-grant-protocol.ts +547 -0
  239. package/src/owner-key-export.ts +348 -0
  240. package/src/owner-key-flows.ts +109 -0
  241. package/src/owner-key-store.ts +85 -0
  242. package/src/owner-keys.ts +178 -0
  243. package/src/owner-signer.ts +94 -0
  244. package/src/peer-addr-book.ts +4 -3
  245. package/src/peer-authorization.ts +558 -488
  246. package/src/peer-join-backfill.ts +971 -761
  247. package/src/pending-join-runner.ts +76 -53
  248. package/src/provider/rent-node.ts +1 -1
  249. package/src/seed-bootstrap.ts +343 -349
  250. package/src/seed-trust-policy.ts +4 -4
  251. package/src/strand-addr-protocol.ts +547 -538
  252. package/src/strand-cohort.ts +4 -0
  253. package/src/strand-database.ts +352 -319
  254. package/src/strand-first-sync-gate.ts +334 -133
  255. package/src/strand-formation-deadlines.ts +227 -227
  256. package/src/strand-formation-manager.ts +731 -725
  257. package/src/strand-formation-protocol.ts +122 -24
  258. package/src/strand-instance-manager.ts +2092 -2026
  259. package/src/strand-membership-reconciler.ts +33 -25
  260. package/src/strand-membership-writer.ts +2093 -1974
  261. package/src/strand-move.ts +50 -38
  262. package/src/strand-peer-redial.ts +2 -3
  263. package/src/strand-revocation-enforcer.ts +740 -654
  264. package/src/strand-solicitation.ts +61 -34
  265. package/src/strand-watcher.ts +175 -56
  266. package/src/table-change-watch.ts +285 -0
  267. package/src/timeout-scheduler.ts +29 -28
  268. package/src/types.ts +391 -152
@@ -0,0 +1,704 @@
1
+ /**
2
+ * `createEdgeNode` — build, start, stop and rebuild an edge device's `CadreNode` the prescribed
3
+ * way: identity and owner key in the app's key store, the four node-local stores durable and
4
+ * party-scoped, raw storage per scope from the app's storage, owner genesis, the last start
5
+ * remembered for unattended starts, and the switch to another party's cadre by machine
6
+ * invitation or owner grant.
7
+ *
8
+ * Nothing platform-specific is imported here. The app passes in its key store, its storage and
9
+ * its transports (`EdgeNodePlatform`), so a Node test runs this code over fakes, and the React
10
+ * Native kit's `createPhoneNode` is this over the secure store and rn-leveldb.
11
+ */
12
+ import { CadreNode } from '../cadre-node.js';
13
+ import { DEFAULT_IDENTITY_KEY_ID } from '../key-store.js';
14
+ import { loadOrCreateIdentityKey } from '../identity-key.js';
15
+ import { exportOwnerKey, importOwnerKeyInto } from '../owner-key-flows.js';
16
+ import { createOwnerKey, loadOwnerSigner } from '../owner-key-store.js';
17
+ import { PersistentBootstrapPeerStore } from '../bootstrap-peer-store.js';
18
+ import { PersistentEnrolledMachineStore } from '../enrolled-machine-store.js';
19
+ import { PersistentStrandNetworkStateStore } from '../strand-network-state.js';
20
+ import { PersistentTrustedOwnerStore } from '../trusted-owner-store.js';
21
+ import { readStrandHoldings } from '../strand-move.js';
22
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
23
+ import { webSockets } from '@libp2p/websockets';
24
+ import { buildEdgeNodeConfig, requireOwnerKeyLabel, runOwnerGenesis, wireJoinedOwnerKey } from './config.js';
25
+ import { finishMarkedRemoval, finishRemovalAtStart, formerPartyRead, formerPartyRefusal, readFormerParty, removeParty, StrandMovePendingError, withoutFormerParty, } from './dissolve.js';
26
+ import { joinCadre } from './edge-join.js';
27
+ import { bootstrapPeersKvKey, enrolledMachinesKvKey, kvStoreSlot, strandNetworkKvKey, trustedOwnersKvKey, } from './node-local.js';
28
+ import { parseSavedStart, serializeSavedStart, startAfterRemoval } from './options.js';
29
+ import { redeemOwnerGrantText } from './owner-grant.js';
30
+ import { DEFAULT_EDGE_NODE_NAMES } from './platform.js';
31
+ import { adoptMoveRecord, adoptMovesInto, pendingMove, StrandMoveRecords, toError } from './strand-move.js';
32
+ /** Edge nodes by storage id: two over the same storage would share databases and an identity. */
33
+ const edgeNodes = new Map();
34
+ /**
35
+ * Create the app's edge node. Call once per app. Throws when an edge node over the same storage
36
+ * (`platform.storage.id`) is starting or running in this process; a stopped or failed one is
37
+ * replaced (a development reload re-runs the module that calls this).
38
+ */
39
+ export function createEdgeNode(platform) {
40
+ const id = platform.storage.id;
41
+ const existing = edgeNodes.get(id);
42
+ if (existing && ['starting', 'joining', 'running'].includes(existing.status.state)) {
43
+ throw new Error(`an edge node over storage '${id}' is already ${existing.status.state}; create one per app and share it`);
44
+ }
45
+ const created = new EdgeNodeRuntime(platform, { ...DEFAULT_EDGE_NODE_NAMES, ...platform.names });
46
+ edgeNodes.set(id, created);
47
+ return created;
48
+ }
49
+ class EdgeNodeRuntime {
50
+ constructor(platform, names) {
51
+ this.platform = platform;
52
+ this.names = names;
53
+ this.current = null;
54
+ this.runningOptions = null;
55
+ this.starting = null;
56
+ this.stopping = null;
57
+ /** One per join called and not yet settled, so a stop can cancel them. */
58
+ this.joins = new Set();
59
+ /**
60
+ * Start, stop, restart, join and dissolve run one at a time, in call order: each opens or closes
61
+ * the stores the others use, so a start overlapping a stop would build on handles the stop then closes.
62
+ */
63
+ this.transitions = Promise.resolve();
64
+ this.currentStatus = { state: 'stopped' };
65
+ this.lastUnfinishedRemoval = null;
66
+ this.subscriptions = new Set();
67
+ this.statusListeners = new Set();
68
+ this.keyStore = platform.keyStore;
69
+ this.moves = new StrandMoveRecords(this.keyStore);
70
+ }
71
+ get status() {
72
+ return this.currentStatus;
73
+ }
74
+ get node() {
75
+ return this.currentStatus.state === 'running' ? this.current : null;
76
+ }
77
+ get options() {
78
+ return this.currentStatus.state === 'running' ? this.runningOptions : null;
79
+ }
80
+ get unfinishedRemoval() {
81
+ return this.lastUnfinishedRemoval;
82
+ }
83
+ identityPeerId() {
84
+ const status = this.currentStatus;
85
+ // A join may wait minutes for a member; the screen showing the join request must not wait with it.
86
+ const peerId = status.state === 'running' || status.state === 'joining' ? status.node.peerId : undefined;
87
+ if (peerId)
88
+ return Promise.resolve(peerId.toString());
89
+ return this.inTurn(async () => {
90
+ const key = await loadOrCreateIdentityKey(this.keyStore, DEFAULT_IDENTITY_KEY_ID);
91
+ // A public key's string form is its peer id's.
92
+ return key.publicKey.toString();
93
+ });
94
+ }
95
+ start(options) {
96
+ if (this.starting)
97
+ return this.starting;
98
+ return this.trackStart(() => (this.current?.isRunning ? Promise.resolve(this.current) : this.buildAndStart(options)));
99
+ }
100
+ stop() {
101
+ // A join can wait for a member until its invitation expires; the user's disconnect ends it.
102
+ // NOTE: the cancelled join starts the party it left again, and only then does this stop
103
+ // stop it. If that restart makes Disconnect noticeably slow, let a join cancelled by a stop
104
+ // save the left start with `autoStart: false` and stay stopped instead.
105
+ for (const join of this.joins)
106
+ join.abort(new Error('the edge node was stopped during a join'));
107
+ return this.trackStop(() => this.stopAndClearAutoStart());
108
+ }
109
+ restart(options) {
110
+ this.starting = null;
111
+ return this.trackStart(async () => {
112
+ await this.shutDown();
113
+ return this.buildAndStart(options);
114
+ });
115
+ }
116
+ joinCadre(credential, options = {}) {
117
+ // A start called from here on runs after this join rather than joining a start it replaces.
118
+ this.starting = null;
119
+ const cancel = new AbortController();
120
+ const release = relayAbort(options.signal, cancel);
121
+ this.joins.add(cancel);
122
+ const joining = this.inTurn(() => joinCadre(this, credential, options, cancel.signal));
123
+ const settled = () => {
124
+ this.joins.delete(cancel);
125
+ release();
126
+ };
127
+ joining.then(settled, settled);
128
+ return joining;
129
+ }
130
+ async loadSavedStart() {
131
+ // A stop in progress decides `autoStart`: answer after it, so an unattended start does
132
+ // not act on the value the stop is replacing. Its failure is its caller's.
133
+ await this.stopping?.catch(() => undefined);
134
+ return this.readSavedStart();
135
+ }
136
+ linkRequestSlot() {
137
+ return kvStoreSlot(this.nodeLocalRecords(), this.names.linkRequestKey);
138
+ }
139
+ recommendationSlot() {
140
+ return kvStoreSlot(this.nodeLocalRecords(), this.names.recommendationsKey);
141
+ }
142
+ /** The node-local store for a record the app keeps across stops and starts. */
143
+ nodeLocalRecords() {
144
+ // Each get and set asks the platform for the store again: a stop closes it, and the record outlives the stop.
145
+ const kv = async () => {
146
+ // Opening it while a stop is closing it would take a second lock on the same name.
147
+ // NOTE: a restart, join or dissolve closes it too, with no `stopping` to wait on, so a
148
+ // read in that short window can fail on the lock. If that is ever seen, track each shutdown as `stopping` is tracked.
149
+ await this.stopping?.catch(() => undefined);
150
+ return this.nodeLocalKv();
151
+ };
152
+ return {
153
+ get: async (key) => (await kv()).get(key),
154
+ set: async (key, value) => (await kv()).set(key, value),
155
+ flush: async () => (await kv()).flush(),
156
+ };
157
+ }
158
+ async pendingStrandMove() {
159
+ const formerPartyId = (this.runningOptions ?? (await this.readSavedStart())?.options)?.formerPartyId;
160
+ if (formerPartyId === undefined)
161
+ return undefined;
162
+ const record = await this.moves.load(formerPartyId);
163
+ return record && pendingMove(record);
164
+ }
165
+ finishStrandMove() {
166
+ return this.inTurn(async () => {
167
+ const node = this.node;
168
+ const running = this.runningOptions;
169
+ const formerPartyId = running?.formerPartyId;
170
+ if (!node?.isRunning || !running || formerPartyId === undefined)
171
+ return undefined;
172
+ const record = await this.moves.load(formerPartyId);
173
+ if (record?.targetPartyId !== node.partyId)
174
+ return undefined;
175
+ // A joined start that settled as a member stopped asking for the owner row after its
176
+ // bound; one that arrived since makes this adoption an owner's rather than a refusal.
177
+ // A founded party never re-asks here: genesis is its owner step, and a signer already
178
+ // wired is never replaced.
179
+ if (running.origin === 'joined' && node.getOwnerSigner() === null && (await this.wireJoinedOwner(node, false)) === 'enrolled') {
180
+ this.reportEnrolled(node);
181
+ }
182
+ const report = await adoptMoveRecord(node, this.moves, record);
183
+ // The adoption wires the key or throws, so a start that settled as a member is an owner now.
184
+ this.reportEnrolled(node);
185
+ return report;
186
+ });
187
+ }
188
+ dissolveFormerParty(options = {}) {
189
+ // A start called from here on runs after this dissolve rather than joining a start whose node it stops.
190
+ this.starting = null;
191
+ return this.inTurn(() => this.dissolve(options.discard ?? []));
192
+ }
193
+ on(event, handler) {
194
+ const subscription = {
195
+ attach: (node) => node.on(event, handler),
196
+ detach: (node) => node.off(event, handler),
197
+ };
198
+ this.subscriptions.add(subscription);
199
+ if (this.current)
200
+ subscription.attach(this.current);
201
+ return () => {
202
+ this.subscriptions.delete(subscription);
203
+ if (this.current)
204
+ subscription.detach(this.current);
205
+ };
206
+ }
207
+ onStatus(listener) {
208
+ this.statusListeners.add(listener);
209
+ return () => {
210
+ this.statusListeners.delete(listener);
211
+ };
212
+ }
213
+ ownerPublicKey() {
214
+ return this.node?.getOwnerSigner()?.publicKey ?? null;
215
+ }
216
+ // ── owner keys ───────────────────────────────────────────────────────────
217
+ // In turn with the transitions: genesis creates the owner slot's key inside a start, and the
218
+ // slot's check-then-set must not race an import.
219
+ listOwnerKeys() {
220
+ return this.onRunningNode((node) => node.listOwnerKeys());
221
+ }
222
+ exportOwnerKey(options = {}) {
223
+ return this.inTurn(() => exportOwnerKey(this.keyStore, options));
224
+ }
225
+ importOwnerKey(text, passphrase) {
226
+ return this.inTurn(async () => {
227
+ const node = this.node?.isRunning ? this.node : null;
228
+ const imported = await importOwnerKeyInto(this.keyStore, node, text, passphrase);
229
+ if (imported.wired && node)
230
+ this.reportEnrolled(node);
231
+ return imported;
232
+ });
233
+ }
234
+ planOwnerKeyRemoval(key) {
235
+ return this.onRunningNode((node) => node.planOwnerKeyRemoval(key));
236
+ }
237
+ removeOwner(key, options = {}) {
238
+ return this.onRunningNode((node) => node.removeOwner(key, options));
239
+ }
240
+ createOwnerGrant(options = {}) {
241
+ return this.onRunningNode((node) => node.createOwnerGrant(options));
242
+ }
243
+ listOwnerGrants() {
244
+ return this.onRunningNode((node) => node.listOwnerGrants());
245
+ }
246
+ withdrawOwnerGrant(key) {
247
+ return this.onRunningNode((node) => node.withdrawOwnerGrant(key));
248
+ }
249
+ redeemOwnerGrant(text) {
250
+ return this.onRunningNode(async (node) => {
251
+ const redeemed = await redeemOwnerGrantText(this, node, text);
252
+ if (redeemed.wired)
253
+ this.reportEnrolled(node);
254
+ return redeemed;
255
+ });
256
+ }
257
+ /** Run `op` on the running node, in turn with start, stop and join; throws when none is running. */
258
+ onRunningNode(op) {
259
+ return this.inTurn(async () => {
260
+ const node = this.node;
261
+ if (!node?.isRunning)
262
+ throw new Error('The edge node is not running; start it first');
263
+ return await op(node);
264
+ });
265
+ }
266
+ /** A key was wired after the start settled: a start reported as a member is an owner now. */
267
+ reportEnrolled(node) {
268
+ if (this.currentStatus.state === 'running' && this.currentStatus.owner !== 'enrolled') {
269
+ this.setStatus({ state: 'running', node, owner: 'enrolled' });
270
+ }
271
+ }
272
+ // ── transitions ──────────────────────────────────────────────────────────
273
+ /** Run `op` after every transition called before it, whether those succeeded or failed. */
274
+ inTurn(op) {
275
+ const result = this.transitions.then(op);
276
+ this.transitions = result.catch(() => undefined);
277
+ return result;
278
+ }
279
+ /**
280
+ * Queue a transition that rewrites the saved start as it takes the node down (a stop, a
281
+ * removal), and let `loadSavedStart` and the app-link slots wait for it until it settles.
282
+ */
283
+ trackStop(op) {
284
+ // A start called from here on runs after this transition rather than joining the one it ends.
285
+ this.starting = null;
286
+ const stopping = this.inTurn(op);
287
+ this.stopping = stopping;
288
+ const settled = () => {
289
+ if (this.stopping === stopping)
290
+ this.stopping = null;
291
+ };
292
+ stopping.then(settled, settled);
293
+ return stopping;
294
+ }
295
+ /** Queue a start, and let later `start` calls join it until it settles. */
296
+ trackStart(op) {
297
+ const starting = this.inTurn(op);
298
+ this.starting = starting;
299
+ const settled = () => {
300
+ if (this.starting === starting)
301
+ this.starting = null;
302
+ };
303
+ starting.then(settled, settled);
304
+ return starting;
305
+ }
306
+ // ── start ────────────────────────────────────────────────────────────────
307
+ // `bringUp`, `readAlone`, `buildAndStart`, `ownerSlotSigner`, `wireJoinedOwner`,
308
+ // `saveRunningStart` and `reportRunning` are public as the join's and the owner-grant
309
+ // redemption's view of this runtime (`JoinRuntime`, `OwnerGrantRuntime`); the class itself
310
+ // never leaves this module.
311
+ async buildAndStart(options) {
312
+ this.setStatus({ state: 'starting' });
313
+ try {
314
+ const { node, resolved } = await this.bringUp(options);
315
+ const owner = await this.settleOwnerKey(node, resolved);
316
+ // Finishes a move a join left: the app was killed before adopting, or its key was wired
317
+ // only now. A move record the key store refuses to read fails the start.
318
+ // NOTE: a pending move delays `running` by its control writes, a few per strand. If a
319
+ // resumed move makes a start noticeably slow, adopt after `markRunning` instead, as a
320
+ // transition of its own.
321
+ if (owner === 'enrolled')
322
+ await adoptMovesInto(node, this.moves);
323
+ await this.markRunning(node, resolved, owner);
324
+ return node;
325
+ }
326
+ catch (err) {
327
+ // The start's own error is the one to report; a failure tearing down after it is only logged.
328
+ await this.shutDown().catch((cleanupErr) => {
329
+ console.warn('[cadre-core/edge-node] cleaning up after a failed start failed:', cleanupErr);
330
+ });
331
+ const error = toError(err);
332
+ this.setStatus({ state: 'failed', error });
333
+ throw error;
334
+ }
335
+ }
336
+ /** Build the node for `options`, every subscription attached, and start it, after finishing a begun dissolve. */
337
+ async bringUp(options) {
338
+ const removal = await finishRemovalAtStart(this.partyStorage());
339
+ this.noteRemovalAtStart(removal);
340
+ const resolved = withoutFormerParty(await this.resolveOptions(options), removal.partyId);
341
+ const node = new CadreNode(await this.buildConfig(resolved));
342
+ this.current = node;
343
+ for (const subscription of this.subscriptions)
344
+ subscription.attach(node);
345
+ this.watchRemoval(node);
346
+ await node.start();
347
+ return { node, resolved };
348
+ }
349
+ /** What `unfinishedRemoval` reports after a start: a marker that could not be read leaves the last report standing. */
350
+ noteRemovalAtStart(removal) {
351
+ if (removal.partyId !== undefined) {
352
+ this.lastUnfinishedRemoval = removal.error ? { partyId: removal.partyId, error: removal.error } : null;
353
+ }
354
+ else if (!removal.error) {
355
+ this.lastUnfinishedRemoval = null;
356
+ }
357
+ }
358
+ /**
359
+ * Start a node for `options`' party only to run `read` on it, then stop it. Nothing subscribes,
360
+ * nothing is saved and no strand launches: the node never reports running. A join exports the
361
+ * strands of a stopped party this way, and a dissolve reads the party it removes.
362
+ */
363
+ async readAlone(options, read, purpose) {
364
+ this.setStatus({ state: 'starting' });
365
+ try {
366
+ const config = await this.buildConfig(await this.resolveOptions(options));
367
+ const node = new CadreNode({ ...config, strandFilter: { mode: 'none' } });
368
+ this.current = node;
369
+ await node.start();
370
+ return await read(node);
371
+ }
372
+ finally {
373
+ // The read's own error, if any, is the one to report.
374
+ await this.shutDown().catch((err) => {
375
+ console.warn(`[cadre-core/edge-node] stopping the node started to ${purpose} failed:`, err);
376
+ });
377
+ }
378
+ }
379
+ /** A founded party runs owner genesis; a joined one only wires a key the party already lists. */
380
+ settleOwnerKey(node, options) {
381
+ if (options.origin === 'joined')
382
+ return this.wireJoinedOwner(node, false);
383
+ return runOwnerGenesis(node, { keyStore: this.keyStore, label: options.ownerKeyLabel, timeoutMs: this.platform.ownerGenesisTimeoutMs });
384
+ }
385
+ /** The owner slot's key, created when the slot is empty: what an owner grant seats. Inside a transition, as the slot's check-then-set must be. */
386
+ async ownerSlotSigner() {
387
+ return (await loadOwnerSigner(this.keyStore)) ?? (await createOwnerKey(this.keyStore));
388
+ }
389
+ /** `granted`: an owner grant has just seated the slot's key, so wait for the row. */
390
+ wireJoinedOwner(node, granted) {
391
+ return wireJoinedOwnerKey(node, { keyStore: this.keyStore, timeoutMs: this.platform.ownerGenesisTimeoutMs, granted });
392
+ }
393
+ /** Report a node that came up, saving its options only now, so a failed start never replaces the last one that came up. */
394
+ async markRunning(node, resolved, owner) {
395
+ await this.saveRunningStart(resolved);
396
+ this.reportRunning(node, resolved, owner);
397
+ }
398
+ saveRunningStart(resolved) {
399
+ return this.saveStart({ options: resolved, autoStart: true, writtenBy: this.platform.dataVersion });
400
+ }
401
+ reportRunning(node, resolved, owner) {
402
+ this.runningOptions = resolved;
403
+ this.setStatus({ state: 'running', node, owner });
404
+ }
405
+ /**
406
+ * The options as this start runs and saves them. The Noise mode is resolved, so a later
407
+ * build's default does not change a device that already ran. Options naming no `origin`
408
+ * take the saved start's `origin` and `formerPartyId` when it is for the same party, so a
409
+ * party this app joined, started again from a hand-typed party id, does not run owner genesis.
410
+ */
411
+ async resolveOptions(options) {
412
+ requireOwnerKeyLabel(options.ownerKeyLabel);
413
+ const resolved = { ...options };
414
+ const defaultMode = this.platform.noiseCrypto?.defaultMode;
415
+ if (resolved.noiseCryptoMode === undefined && defaultMode !== undefined)
416
+ resolved.noiseCryptoMode = defaultMode;
417
+ if (resolved.origin === undefined) {
418
+ const saved = (await this.readSavedStart())?.options;
419
+ if (saved?.partyId === resolved.partyId) {
420
+ if (saved.origin !== undefined)
421
+ resolved.origin = saved.origin;
422
+ if (saved.formerPartyId !== undefined)
423
+ resolved.formerPartyId = saved.formerPartyId;
424
+ }
425
+ }
426
+ return resolved;
427
+ }
428
+ /**
429
+ * Open the node-local stores and assemble the config. A store read failure propagates and fails
430
+ * the start: an unreadable trusted-owner anchor is a refusal to start, not a silent downgrade to
431
+ * trusting nobody, and starting empty would let the next write destroy an intact anchor. (The
432
+ * enrolled-machine store cold-starts instead; a lost repair hint must not stop a node.)
433
+ */
434
+ async buildConfig(options) {
435
+ const { partyId, relayAddrs } = options;
436
+ const { platform } = this;
437
+ const config = buildEdgeNodeConfig({
438
+ ...options,
439
+ noiseCrypto: this.buildNoiseCrypto(options.noiseCryptoMode),
440
+ keyStore: this.keyStore,
441
+ storageProvider: (scope) => platform.storage.rawStorage(scope),
442
+ transports: (platform.transports ?? defaultTransports)(relayAddrs),
443
+ listenAddrs: platform.listenAddrs?.(relayAddrs) ?? [],
444
+ allowPrivateDial: platform.allowPrivateDial ?? true,
445
+ trustedOwnerStore: await PersistentTrustedOwnerStore.open(this.trustedOwnerSlot(partyId), partyId),
446
+ bootstrapPeerStore: await PersistentBootstrapPeerStore.open(this.nodeLocalSlot(bootstrapPeersKvKey(partyId)), partyId),
447
+ enrolledMachineStore: await PersistentEnrolledMachineStore.open(this.nodeLocalSlot(enrolledMachinesKvKey(partyId)), partyId),
448
+ strandNetworkStateStore: await PersistentStrandNetworkStateStore.open(this.nodeLocalSlot(strandNetworkKvKey(partyId)), partyId),
449
+ });
450
+ return platform.configure ? platform.configure(config) : config;
451
+ }
452
+ buildNoiseCrypto(mode) {
453
+ const noiseCrypto = this.platform.noiseCrypto;
454
+ return noiseCrypto && mode ? noiseCrypto.build(mode) : undefined;
455
+ }
456
+ // ── dissolve ─────────────────────────────────────────────────────────────
457
+ // NOTE: this file is about 1050 lines (`wc -l`), the dissolve's and the removal's transitions
458
+ // the largest part (the storage they delete is in `dissolve.ts`; the join is `edge-join.ts`,
459
+ // the owner-grant redemption `owner-grant.ts`), then the owner-key and owner-grant
460
+ // pass-throughs. If any of them grows further, move it to a module of its own over a narrow
461
+ // view of this runtime, as the join is.
462
+ /** docs/architecture.md → "Dissolving the party it left". */
463
+ async dissolve(discard) {
464
+ await this.finishMarkedRemoval();
465
+ const { node, running, formerPartyId } = this.runningWithFormerParty();
466
+ const record = await this.moves.load(formerPartyId);
467
+ if (record)
468
+ throw new StrandMovePendingError(pendingMove(record));
469
+ const holdings = await readStrandHoldings(node);
470
+ try {
471
+ await this.shutDown();
472
+ const content = await this.readAlone(formerPartyRead(formerPartyId, running), readFormerParty, 'read the party it left');
473
+ const refusal = formerPartyRefusal(content, holdings, discard, Date.now());
474
+ if (refusal)
475
+ throw refusal;
476
+ await removeParty(this.partyStorage(), formerPartyId);
477
+ }
478
+ catch (err) {
479
+ await this.startAgainAfterDissolve(running);
480
+ throw err;
481
+ }
482
+ await this.buildAndStart(withoutFormerParty(running, formerPartyId));
483
+ }
484
+ runningWithFormerParty() {
485
+ const node = this.node;
486
+ const running = this.runningOptions;
487
+ const formerPartyId = running?.formerPartyId;
488
+ if (!node?.isRunning || !running || formerPartyId === undefined) {
489
+ throw new Error('Nothing to dissolve: the edge node is not running with a former party, one this app founded and left');
490
+ }
491
+ // Only a hand-built start names its own party; dissolving it would delete the running party's control database.
492
+ if (formerPartyId === running.partyId) {
493
+ throw new Error(`Cannot dissolve party ${formerPartyId}: it is the running party, not one this app left`);
494
+ }
495
+ return { node, running, formerPartyId };
496
+ }
497
+ /** After a refused or failed dissolve. The dissolve's own error is the one its caller gets; this one is only logged. */
498
+ async startAgainAfterDissolve(running) {
499
+ try {
500
+ await this.buildAndStart(running);
501
+ }
502
+ catch (err) {
503
+ console.warn('[cadre-core/edge-node] starting the running party again after a dissolve failed:', err);
504
+ }
505
+ }
506
+ /** Finish the removal a marker names, if any; finished, it is no longer an unfinished one to report. */
507
+ async finishMarkedRemoval() {
508
+ await finishMarkedRemoval(this.partyStorage());
509
+ this.lastUnfinishedRemoval = null;
510
+ }
511
+ // ── removal ──────────────────────────────────────────────────────────────
512
+ /**
513
+ * Answer `node` reporting that an owner removed this app from its party
514
+ * ({@link forgetRemovedParty}). Attached before the node starts, so no report comes before it
515
+ * and `removedFromCadre()` has nothing to add; never detached, so a report raised while the
516
+ * node stops, which it made while running, is still answered.
517
+ *
518
+ * NOTE: a device removed while its app was not running never sees its own row live after the
519
+ * next start, so it never reports (docs/architecture.md → "The removed machine learns it"), and
520
+ * keeps the party's data while every other machine refuses it. If users meet this, persist
521
+ * "seen live" per party so the rule survives a restart.
522
+ */
523
+ watchRemoval(node) {
524
+ node.on('cadre:removed', (removal) => {
525
+ void this.trackStop(() => this.forgetRemovedParty(removal));
526
+ });
527
+ }
528
+ /**
529
+ * In turn with start, stop and join: stop the removed party's node, point the saved start away
530
+ * from that party, delete its local storage and report `removed`. A node of another party, started
531
+ * by a join that ran before this turn, keeps running; only the removed party's storage goes.
532
+ * Nothing starts unattended afterwards: the user sees why the app left its cadre first. A
533
+ * failure is logged and leaves `status` `failed`; never rejects.
534
+ *
535
+ * NOTE: `start` does not refuse the removed party's options, so an app that cached them builds
536
+ * that party again on empty storage. If one is seen doing so, refuse a start of `partyId` while
537
+ * `status` is `removed`.
538
+ */
539
+ async forgetRemovedParty(removal) {
540
+ const { partyId } = removal;
541
+ try {
542
+ if (this.current !== null && this.current.partyId !== partyId) {
543
+ await this.removeLocalParty(partyId);
544
+ if (this.runningOptions)
545
+ this.runningOptions = withoutFormerParty(this.runningOptions, partyId);
546
+ return;
547
+ }
548
+ await this.shutDown().catch((err) => {
549
+ // `shutDown` has closed every store and dropped the node whether or not its stop threw.
550
+ console.warn(`[cadre-core/edge-node] stopping the node of party ${partyId}, which removed this app, failed:`, err);
551
+ });
552
+ const formerPartyId = await this.leaveRemovedStart(partyId);
553
+ await this.removeLocalParty(partyId);
554
+ this.setStatus({ state: 'removed', partyId, removal, ...(formerPartyId !== undefined && { formerPartyId }) });
555
+ }
556
+ catch (err) {
557
+ console.warn(`[cadre-core/edge-node] acting on this app's removal from party ${partyId} failed:`, err);
558
+ this.setStatus({ state: 'failed', error: toError(err) });
559
+ }
560
+ }
561
+ /**
562
+ * Replace a saved start of `partyId` with {@link startAfterRemoval}'s, or delete it, before any
563
+ * of the party's storage goes, so no start builds that party again on deleted storage; returns
564
+ * the former party the new start names. A saved start of another party is left alone. Throws a
565
+ * failed read or write, and the storage then stays, matching the saved start that still names it.
566
+ */
567
+ async leaveRemovedStart(partyId) {
568
+ const saved = await this.readSavedStart();
569
+ if (saved?.options.partyId !== partyId)
570
+ return undefined;
571
+ const next = startAfterRemoval(saved);
572
+ if (next)
573
+ await this.nodeLocalSlot(this.names.savedStartKey).save(serializeSavedStart(next));
574
+ else
575
+ await this.deleteSavedStart();
576
+ return next?.options.partyId;
577
+ }
578
+ /**
579
+ * Delete `partyId`'s local storage with the dissolve's marked removal, after finishing one a
580
+ * failure left, so this marker does not replace it. A failure is logged: once the marker is
581
+ * written, the next start finishes the removal.
582
+ */
583
+ async removeLocalParty(partyId) {
584
+ try {
585
+ await this.finishMarkedRemoval();
586
+ await removeParty(this.partyStorage(), partyId);
587
+ }
588
+ catch (err) {
589
+ console.warn(`[cadre-core/edge-node] deleting the storage of party ${partyId}, which removed this app, failed:`, err);
590
+ }
591
+ }
592
+ // ── stop ─────────────────────────────────────────────────────────────────
593
+ async stopAndClearAutoStart() {
594
+ // NOTE: with no node running (the last start failed) the saved record is left alone, so an
595
+ // earlier `autoStart: true` survives this stop. The reference app offers Disconnect only
596
+ // while connected; if an app lets a user stop a node whose start failed, rewrite the
597
+ // saved record with `autoStart: false` here too.
598
+ if (this.runningOptions) {
599
+ await this.saveStart({ options: this.runningOptions, autoStart: false, writtenBy: this.platform.dataVersion });
600
+ }
601
+ await this.shutDown();
602
+ }
603
+ /**
604
+ * Stop the node and close every store, the node's references cleared first: a throwing
605
+ * `stop()` must not leave a node whose stores the `finally` has just closed, which the next
606
+ * `start` would hand back as running.
607
+ */
608
+ async shutDown() {
609
+ const stopping = this.current;
610
+ this.current = null;
611
+ this.runningOptions = null;
612
+ try {
613
+ if (stopping) {
614
+ for (const subscription of this.subscriptions)
615
+ subscription.detach(stopping);
616
+ await stopping.stop();
617
+ }
618
+ }
619
+ finally {
620
+ // The node-local store first, flushed, then every raw store: the platform's contract.
621
+ await this.platform.storage.close();
622
+ this.setStatus({ state: 'stopped' });
623
+ }
624
+ }
625
+ // ── storage ──────────────────────────────────────────────────────────────
626
+ nodeLocalKv() {
627
+ return this.platform.storage.nodeLocal();
628
+ }
629
+ nodeLocalSlot(key) {
630
+ return kvStoreSlot(this.nodeLocalKv(), key);
631
+ }
632
+ /** The party's trusted-owner anchor: the platform's slot, else the node-local key. */
633
+ trustedOwnerSlot(partyId) {
634
+ return this.platform.storage.trustedOwnerSlot?.(partyId) ?? nodeLocalRemovableSlot(this.nodeLocalKv(), trustedOwnersKvKey(partyId));
635
+ }
636
+ partyStorage() {
637
+ return {
638
+ nodeLocal: this.nodeLocalKv(),
639
+ markerKey: this.names.dissolveMarkerKey,
640
+ savedStartKey: this.names.savedStartKey,
641
+ destroyScope: (scope) => this.platform.storage.destroyScope(scope),
642
+ trustedOwnerSlot: (partyId) => this.trustedOwnerSlot(partyId),
643
+ keyStore: this.keyStore,
644
+ moves: this.moves,
645
+ };
646
+ }
647
+ /** The saved start, read directly: inside a transition, `loadSavedStart` would wait on a stop queued behind it. */
648
+ async readSavedStart() {
649
+ return parseSavedStart(await this.nodeLocalSlot(this.names.savedStartKey).load());
650
+ }
651
+ /** Delete the saved start, durably; a failed write throws. */
652
+ async deleteSavedStart() {
653
+ const kv = this.nodeLocalKv();
654
+ await kv.delete(this.names.savedStartKey);
655
+ await kv.flush();
656
+ }
657
+ /** Best-effort: a failed write is logged and changes nothing about the node. */
658
+ async saveStart(saved) {
659
+ try {
660
+ await this.nodeLocalSlot(this.names.savedStartKey).save(serializeSavedStart(saved));
661
+ }
662
+ catch (err) {
663
+ console.warn('[cadre-core/edge-node] could not save the start options:', err);
664
+ }
665
+ }
666
+ setStatus(status) {
667
+ this.currentStatus = status;
668
+ for (const listener of this.statusListeners) {
669
+ try {
670
+ listener(status);
671
+ }
672
+ catch (err) {
673
+ console.warn('[cadre-core/edge-node] status listener threw:', err);
674
+ }
675
+ }
676
+ }
677
+ }
678
+ /** The default anchor slot: one node-local key, whose removal is durable when it resolves, as `kvStoreSlot`'s save is. */
679
+ function nodeLocalRemovableSlot(kv, key) {
680
+ return {
681
+ ...kvStoreSlot(kv, key),
682
+ remove: async () => {
683
+ await kv.delete(key);
684
+ await kv.flush();
685
+ },
686
+ };
687
+ }
688
+ /** Abort `target` with `source`'s reason when `source` aborts; returns the unsubscribe. */
689
+ function relayAbort(source, target) {
690
+ if (!source)
691
+ return () => { };
692
+ const abort = () => target.abort(source.reason);
693
+ if (source.aborted) {
694
+ abort();
695
+ return () => { };
696
+ }
697
+ source.addEventListener('abort', abort, { once: true });
698
+ return () => source.removeEventListener('abort', abort);
699
+ }
700
+ /** WebSockets to dial the party's machines and relays, and circuit relay to be reachable through one. */
701
+ function defaultTransports() {
702
+ return [webSockets(), circuitRelayTransport()];
703
+ }
704
+ //# sourceMappingURL=edge-node.js.map