@serfab/cadre-core 0.10.0 → 0.11.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.
- package/README.md +255 -255
- package/dist/cadre-node.d.ts +203 -28
- package/dist/cadre-node.js +445 -84
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +82 -117
- package/dist/control-database.js +217 -295
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +8 -7
- package/dist/control-formation-recorder.js +8 -7
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +789 -774
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +15 -5
- package/dist/control-write-retry.js +19 -18
- package/dist/control-write-retry.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/peer-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +7 -0
- package/dist/seed-bootstrap.js +15 -13
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +8 -8
- package/dist/strand-formation-manager.js +15 -18
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +52 -21
- package/dist/strand-instance-manager.js +100 -34
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +6 -4
- package/dist/strand-membership-writer.js +38 -36
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/types.d.ts +39 -31
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +172 -172
- package/src/cadre-node.ts +5158 -4771
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -114
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +2406 -2506
- package/src/control-formation-recorder.ts +305 -304
- package/src/control-schema.ts +801 -786
- package/src/control-stream.ts +205 -205
- package/src/control-write-retry.ts +483 -473
- package/src/delegate-admission.ts +302 -302
- package/src/device-token.ts +94 -94
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +134 -134
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -61
- package/src/formation-approval.ts +604 -604
- package/src/fs-atomic.ts +107 -107
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -109
- package/src/index.ts +1 -0
- package/src/key-store-file.ts +132 -132
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +204 -204
- package/src/node-local-snapshot.ts +260 -260
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -259
- package/src/peer-record.ts +164 -164
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +111 -111
- package/src/relay-reservation.ts +823 -823
- package/src/seed-bootstrap.ts +15 -15
- package/src/seed-trust-policy.ts +140 -140
- package/src/strand-addr-protocol.ts +441 -441
- package/src/strand-backfill.ts +366 -366
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +491 -494
- package/src/strand-formation-protocol.ts +692 -692
- package/src/strand-instance-manager.ts +108 -50
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -182
- package/src/strand-membership-writer.ts +1432 -1430
- package/src/strand-solicitation.ts +472 -472
- package/src/strand-transport-key.ts +58 -58
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +314 -314
- package/src/strand-watcher.ts +314 -314
- package/src/trusted-owner-store-file.ts +41 -41
- package/src/trusted-owner-store.ts +171 -171
- package/src/types.ts +1309 -1303
package/src/strand-cohort.ts
CHANGED
|
@@ -1,77 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* Derive the membership view of a strand's cohort from the control network's
|
|
40
|
-
* CadrePeer rows: the peerIds of members other than `selfPeerId` (the strand-addr
|
|
41
|
-
* RPC fan-out targets) and whether any other member exists at all.
|
|
42
|
-
*
|
|
43
|
-
* Deliberately does NOT read `CadrePeer.Multiaddr` — that field carries each
|
|
44
|
-
* node's *control*-network address, which must not seed the *strand* mesh
|
|
45
|
-
* (dialing it reaches the remote's control instance, not its strand instance).
|
|
46
|
-
* The strand-network bootstrap addresses are resolved on demand over the control
|
|
47
|
-
* mesh from these peerIds. `hasOtherPeers` reflects membership presence, NOT
|
|
48
|
-
* dialability, so a cohort whose strand addrs are not yet resolvable still
|
|
49
|
-
* selects `networked` mode.
|
|
50
|
-
*/
|
|
51
|
-
export function deriveCohortMembers(peers: CohortPeerRow[], selfPeerId?: string): CohortMembers {
|
|
52
|
-
const otherPeerIds: string[] = [];
|
|
53
|
-
const seen = new Set<string>();
|
|
54
|
-
|
|
55
|
-
for (const peer of peers) {
|
|
56
|
-
if (selfPeerId !== undefined && peer.peerId === selfPeerId) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (seen.has(peer.peerId)) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
seen.add(peer.peerId);
|
|
63
|
-
otherPeerIds.push(peer.peerId);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { otherPeerIds, hasOtherPeers: otherPeerIds.length > 0 };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Choose the strand mode. An explicit mode (from `addStrand`) always wins. When
|
|
71
|
-
* omitted (the `handleStrandAdded` discovery path, or an `addStrand` caller that
|
|
72
|
-
* leaves it unset), infer: `bootstrap` for a solo/first node (no other peers),
|
|
73
|
-
* `networked` once the cohort has other members.
|
|
74
|
-
*/
|
|
75
|
-
export function selectStrandMode(explicitMode: StrandMode | undefined, hasOtherPeers: boolean): StrandMode {
|
|
76
|
-
return explicitMode ?? (hasOtherPeers ? 'networked' : 'bootstrap');
|
|
77
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* A single CadrePeer membership row, as returned by
|
|
3
|
+
* `ControlDatabase.queryCadrePeers()`.
|
|
4
|
+
*/
|
|
5
|
+
export interface CohortPeerRow {
|
|
6
|
+
peerId: string;
|
|
7
|
+
/** Comma-joined list of multiaddr strings; `''` when no addrs were recorded. */
|
|
8
|
+
multiaddr: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Derive the membership view of a strand's cohort from the control network's
|
|
13
|
+
* CadrePeer rows: the peerIds of members other than `selfPeerId` — the
|
|
14
|
+
* strand-addr RPC fan-out targets, deduplicated, in row order.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately does NOT read `CadrePeer.Multiaddr` — that field carries each
|
|
17
|
+
* node's *control*-network address, which must not seed the *strand* mesh
|
|
18
|
+
* (dialing it reaches the remote's control instance, not its strand instance).
|
|
19
|
+
* The strand-network bootstrap addresses are resolved on demand over the control
|
|
20
|
+
* mesh from these peerIds.
|
|
21
|
+
*/
|
|
22
|
+
export function deriveCohortMembers(peers: CohortPeerRow[], selfPeerId?: string): string[] {
|
|
23
|
+
const otherPeerIds: string[] = [];
|
|
24
|
+
const seen = new Set<string>();
|
|
25
|
+
|
|
26
|
+
for (const peer of peers) {
|
|
27
|
+
if (selfPeerId !== undefined && peer.peerId === selfPeerId) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (seen.has(peer.peerId)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
seen.add(peer.peerId);
|
|
34
|
+
otherPeerIds.push(peer.peerId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return otherPeerIds;
|
|
38
|
+
}
|
package/src/strand-database.ts
CHANGED
|
@@ -1,204 +1,202 @@
|
|
|
1
|
-
import debug from 'debug';
|
|
2
|
-
import { Database } from '@quereus/quereus';
|
|
3
|
-
import { connectToStrand } from '@serfab/quereus-plugin-sereus';
|
|
4
|
-
import type { SereusPluginResult } from '@serfab/quereus-plugin-sereus';
|
|
5
|
-
import type { Libp2p } from '@libp2p/interface';
|
|
6
|
-
import type { IRepo } from '@optimystic/db-core';
|
|
7
|
-
import type {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
sid,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
*
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
}
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { Database } from '@quereus/quereus';
|
|
3
|
+
import { connectToStrand } from '@serfab/quereus-plugin-sereus';
|
|
4
|
+
import type { SereusPluginResult } from '@serfab/quereus-plugin-sereus';
|
|
5
|
+
import type { Libp2p } from '@libp2p/interface';
|
|
6
|
+
import type { IRepo } from '@optimystic/db-core';
|
|
7
|
+
import type { SAppConfig } from './types.js';
|
|
8
|
+
import { bootstrapFounderMembership } from './strand-membership-writer.js';
|
|
9
|
+
import { strandMemberKeyPair } from './strand-member-key.js';
|
|
10
|
+
|
|
11
|
+
const log = debug('sereus:cadre:strand-db');
|
|
12
|
+
const timing = debug('sereus:cadre:timing');
|
|
13
|
+
|
|
14
|
+
export interface StrandDatabaseConfig {
|
|
15
|
+
/** The strand ID */
|
|
16
|
+
strandId: string;
|
|
17
|
+
/** sApp configuration containing the schema */
|
|
18
|
+
sAppConfig: SAppConfig;
|
|
19
|
+
/** Libp2p node for the strand network */
|
|
20
|
+
libp2pNode: Libp2p;
|
|
21
|
+
/** Coordinated repo from the libp2p node */
|
|
22
|
+
coordinatedRepo: IRepo;
|
|
23
|
+
/**
|
|
24
|
+
* Strand type (`'o'` open / `'c'` closed) from the control-network strand row.
|
|
25
|
+
* Drives the founder bootstrap: open strands get a `Header` only; closed strands
|
|
26
|
+
* also get the founding `Member`+`Manager`.
|
|
27
|
+
*/
|
|
28
|
+
strandType: 'o' | 'c';
|
|
29
|
+
/**
|
|
30
|
+
* The closed-strand `MemberPrivateKey` (base64 protobuf) from the strand row.
|
|
31
|
+
* Required when `founder` is true and `strandType` is `'c'` — it derives the
|
|
32
|
+
* founding `Member.Key`/`Manager.MemberKey`. Absent for open strands.
|
|
33
|
+
*/
|
|
34
|
+
memberPrivateKey?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether this node founds the strand. When true, {@link initialize} runs the
|
|
37
|
+
* one-time founder membership bootstrap after the schema is applied. Joiners
|
|
38
|
+
* leave this false and write nothing (rows arrive via sync). Defaults to false.
|
|
39
|
+
*/
|
|
40
|
+
founder?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* StrandDatabase manages the sApp schema for a strand using Quereus with the
|
|
45
|
+
* Optimystic backend. Each strand instance has its own isolated database with
|
|
46
|
+
* the sApp's schema applied.
|
|
47
|
+
*
|
|
48
|
+
* This class owns the `Database` lifecycle (creation, `getDatabase()`, `close()`)
|
|
49
|
+
* but delegates the actual SQL-surface composition — plugin registration, node
|
|
50
|
+
* wiring, catalog hydration, schema apply — to `connectToStrand` from
|
|
51
|
+
* `@serfab/quereus-plugin-sereus`, the single shared composition. The libp2p
|
|
52
|
+
* node is injected here, so `connectToStrand` never *creates* the node;
|
|
53
|
+
* `StrandInstanceManager` owns the node lifecycle. (The strand connection's
|
|
54
|
+
* `shutdown` still stops the injected node via the collection factory, so the
|
|
55
|
+
* manager's own `node.stop()` is an idempotent second stop — see `close()`.)
|
|
56
|
+
*/
|
|
57
|
+
export class StrandDatabase {
|
|
58
|
+
private db: Database | null = null;
|
|
59
|
+
private shutdownStrand: SereusPluginResult['shutdown'] | null = null;
|
|
60
|
+
private resolvedTransactor: SereusPluginResult['transactor'] | null = null;
|
|
61
|
+
private readonly config: StrandDatabaseConfig;
|
|
62
|
+
private initialized = false;
|
|
63
|
+
|
|
64
|
+
constructor(config: StrandDatabaseConfig) {
|
|
65
|
+
this.config = config;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initialize the database — create the `Database` and delegate the strand
|
|
70
|
+
* SQL-surface composition (plugins, node wiring, hydrate, schema apply) to
|
|
71
|
+
* `connectToStrand` with the injected libp2p node.
|
|
72
|
+
*/
|
|
73
|
+
async initialize(): Promise<void> {
|
|
74
|
+
if (this.initialized) {
|
|
75
|
+
log('StrandDatabase for strand %s already initialized', this.config.strandId);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const sid = this.config.strandId;
|
|
80
|
+
log('Initializing StrandDatabase for strand: %s (sApp: %s v%s)',
|
|
81
|
+
sid, this.config.sAppConfig.id, this.config.sAppConfig.version);
|
|
82
|
+
|
|
83
|
+
this.db = new Database();
|
|
84
|
+
|
|
85
|
+
// Delegate to the shared composition. The node is injected, so:
|
|
86
|
+
// - `connectToStrand` never creates a node (its `createdNode` stays null);
|
|
87
|
+
// `StrandInstanceManager` owns the node lifecycle. Its `shutdown` does
|
|
88
|
+
// still stop the injected node via the collection factory, so the
|
|
89
|
+
// manager's later `node.stop()` is an idempotent second stop;
|
|
90
|
+
// - no `storage` is passed: the plugin consumes it only to build a local
|
|
91
|
+
// transactor's raw-storage factory or to create a node it was not given,
|
|
92
|
+
// and cadre-core always injects the node and runs the network transactor.
|
|
93
|
+
// (The plugin keeps its `storage` option for the browser entry point.)
|
|
94
|
+
const t0 = performance.now();
|
|
95
|
+
const result = await connectToStrand(this.db, {
|
|
96
|
+
strandId: sid,
|
|
97
|
+
schema: this.config.sAppConfig.schema,
|
|
98
|
+
libp2pNode: this.config.libp2pNode,
|
|
99
|
+
coordinatedRepo: this.config.coordinatedRepo,
|
|
100
|
+
enableCache: true,
|
|
101
|
+
});
|
|
102
|
+
this.shutdownStrand = result.shutdown;
|
|
103
|
+
this.resolvedTransactor = result.transactor;
|
|
104
|
+
timing('[strandDb:%s] connectToStrand: %dms (hydrated tables=%d, indexes=%d)',
|
|
105
|
+
sid, Math.round(performance.now() - t0),
|
|
106
|
+
result.hydrated?.tables ?? 0, result.hydrated?.indexes ?? 0);
|
|
107
|
+
|
|
108
|
+
// Founder-only: write the one-time membership bootstrap now that the schema is
|
|
109
|
+
// applied (connectToStrand has returned). A throw here propagates out of
|
|
110
|
+
// initialize() so buildStrandRuntime's rollback tears the half-built strand
|
|
111
|
+
// down rather than leaking a node. Joiners skip this — their rows arrive via sync.
|
|
112
|
+
if (this.config.founder === true) {
|
|
113
|
+
await this.bootstrapFounder();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.initialized = true;
|
|
117
|
+
log('StrandDatabase for strand %s initialized successfully', sid);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run the founder membership bootstrap against the freshly-composed strand DB.
|
|
122
|
+
*
|
|
123
|
+
* Derives the founding keypair from the closed-strand `memberPrivateKey` (the
|
|
124
|
+
* `Member.Key`/`Manager.MemberKey` are its public key); a closed strand with no
|
|
125
|
+
* `memberPrivateKey` throws, because it could never seat a founding manager.
|
|
126
|
+
* Open strands derive no keypair (Header only). Idempotent — see
|
|
127
|
+
* {@link bootstrapFounderMembership}.
|
|
128
|
+
*/
|
|
129
|
+
private async bootstrapFounder(): Promise<void> {
|
|
130
|
+
const { strandId, strandType, memberPrivateKey, sAppConfig } = this.config;
|
|
131
|
+
const founderKeyPair = strandType === 'c'
|
|
132
|
+
? this.deriveFounderKeyPair(strandId, memberPrivateKey)
|
|
133
|
+
: undefined;
|
|
134
|
+
await bootstrapFounderMembership(this.db!, {
|
|
135
|
+
strandId,
|
|
136
|
+
type: strandType,
|
|
137
|
+
sApp: sAppConfig,
|
|
138
|
+
founderKeyPair,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Derive the founding keypair for a closed strand from its `memberPrivateKey`,
|
|
144
|
+
* failing loudly when the key is absent (a closed strand with no founding
|
|
145
|
+
* Manager can never admit anyone).
|
|
146
|
+
*/
|
|
147
|
+
private deriveFounderKeyPair(strandId: string, memberPrivateKey: string | undefined) {
|
|
148
|
+
if (!memberPrivateKey) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Cannot found closed strand ${strandId}: the strand row has no MemberPrivateKey. ` +
|
|
151
|
+
'A closed strand needs a founding Member/Manager derived from that key.',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return strandMemberKeyPair(memberPrivateKey);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the underlying database for queries
|
|
159
|
+
*/
|
|
160
|
+
getDatabase(): Database {
|
|
161
|
+
this.ensureInitialized();
|
|
162
|
+
return this.db!;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The Optimystic transactor this strand's connection resolved to — always
|
|
167
|
+
* `'network'` here, since cadre-core passes no `transactor` option and takes
|
|
168
|
+
* the plugin's default. Exposed so a spec measuring or asserting the network
|
|
169
|
+
* path (`strand-solo-write-budget.spec.ts`) can pin the engine it ran on
|
|
170
|
+
* rather than assume it.
|
|
171
|
+
*/
|
|
172
|
+
getTransactor(): SereusPluginResult['transactor'] {
|
|
173
|
+
this.ensureInitialized();
|
|
174
|
+
return this.resolvedTransactor!;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Close the database and cleanup resources. Runs the strand-connection
|
|
179
|
+
* shutdown (collection-factory teardown, which also stops the injected node),
|
|
180
|
+
* then closes the `Database`. `StrandInstanceManager.releaseRuntime` issues a
|
|
181
|
+
* further idempotent `node.stop()` after this returns.
|
|
182
|
+
*/
|
|
183
|
+
async close(): Promise<void> {
|
|
184
|
+
if (this.shutdownStrand) {
|
|
185
|
+
await this.shutdownStrand();
|
|
186
|
+
this.shutdownStrand = null;
|
|
187
|
+
}
|
|
188
|
+
if (this.db) {
|
|
189
|
+
void this.db.close();
|
|
190
|
+
this.db = null;
|
|
191
|
+
}
|
|
192
|
+
this.resolvedTransactor = null;
|
|
193
|
+
this.initialized = false;
|
|
194
|
+
log('StrandDatabase for strand %s closed', this.config.strandId);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private ensureInitialized(): void {
|
|
198
|
+
if (!this.initialized || !this.db) {
|
|
199
|
+
throw new Error(`StrandDatabase for strand ${this.config.strandId} not initialized. Call initialize() first.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|