@serfab/cadre-core 0.9.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.
Files changed (193) hide show
  1. package/README.md +255 -253
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
@@ -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 { IRawStorage } from '@optimystic/db-p2p';
8
- import type { SAppConfig, StrandMode } from './types.js';
9
- import { bootstrapFounderMembership } from './strand-membership-writer.js';
10
- import { strandMemberKeyPair } from './strand-member-key.js';
11
-
12
- const log = debug('sereus:cadre:strand-db');
13
- const timing = debug('sereus:cadre:timing');
14
-
15
- export interface StrandDatabaseConfig {
16
- /** The strand ID */
17
- strandId: string;
18
- /** sApp configuration containing the schema */
19
- sAppConfig: SAppConfig;
20
- /** Libp2p node for the strand network */
21
- libp2pNode: Libp2p;
22
- /** Coordinated repo from the libp2p node */
23
- coordinatedRepo: IRepo;
24
- /**
25
- * Lifecycle mode. `'bootstrap'` selects a purely local transactor so the strand
26
- * can initialize (e.g. apply schema DDL) without network round trips on a solo
27
- * node. `'networked'` (the default) uses the network transactor.
28
- */
29
- mode?: StrandMode;
30
- /**
31
- * Raw storage backing the strand. When mode is `'bootstrap'` this is also used
32
- * by the optimystic plugin's local transactor so DML lands on the host's
33
- * persistent storage instead of in-memory. Must be the same instance the
34
- * libp2p node was created with — sharing the instance avoids cache divergence
35
- * across the two consumers.
36
- */
37
- rawStorage?: IRawStorage;
38
- /**
39
- * Strand type (`'o'` open / `'c'` closed) from the control-network strand row.
40
- * Drives the founder bootstrap: open strands get a `Header` only; closed strands
41
- * also get the founding `Member`+`Manager`.
42
- */
43
- strandType: 'o' | 'c';
44
- /**
45
- * The closed-strand `MemberPrivateKey` (base64 protobuf) from the strand row.
46
- * Required when `founder` is true and `strandType` is `'c'` — it derives the
47
- * founding `Member.Key`/`Manager.MemberKey`. Absent for open strands.
48
- */
49
- memberPrivateKey?: string;
50
- /**
51
- * Whether this node founds the strand. When true, {@link initialize} runs the
52
- * one-time founder membership bootstrap after the schema is applied. Joiners
53
- * leave this false and write nothing (rows arrive via sync). Defaults to false.
54
- */
55
- founder?: boolean;
56
- }
57
-
58
- /**
59
- * StrandDatabase manages the sApp schema for a strand using Quereus with the
60
- * Optimystic backend. Each strand instance has its own isolated database with
61
- * the sApp's schema applied.
62
- *
63
- * This class owns the `Database` lifecycle (creation, `getDatabase()`, `close()`)
64
- * but delegates the actual SQL-surface composition — plugin registration, node
65
- * wiring, catalog hydration, schema apply — to `connectToStrand` from
66
- * `@serfab/quereus-plugin-sereus`, the single shared composition. The libp2p
67
- * node is injected here, so `connectToStrand` never *creates* the node;
68
- * `StrandInstanceManager` owns the node lifecycle. (The strand connection's
69
- * `shutdown` still stops the injected node via the collection factory, so the
70
- * manager's own `node.stop()` is an idempotent second stop — see `close()`.)
71
- */
72
- export class StrandDatabase {
73
- private db: Database | null = null;
74
- private shutdownStrand: SereusPluginResult['shutdown'] | null = null;
75
- private readonly config: StrandDatabaseConfig;
76
- private initialized = false;
77
-
78
- constructor(config: StrandDatabaseConfig) {
79
- this.config = config;
80
- }
81
-
82
- /**
83
- * Initialize the database — create the `Database` and delegate the strand
84
- * SQL-surface composition (plugins, node wiring, hydrate, schema apply) to
85
- * `connectToStrand` with the injected libp2p node.
86
- */
87
- async initialize(): Promise<void> {
88
- if (this.initialized) {
89
- log('StrandDatabase for strand %s already initialized', this.config.strandId);
90
- return;
91
- }
92
-
93
- const sid = this.config.strandId;
94
- const mode: StrandMode = this.config.mode ?? 'networked';
95
- log('Initializing StrandDatabase for strand: %s (sApp: %s v%s, mode=%s)',
96
- sid, this.config.sAppConfig.id, this.config.sAppConfig.version, mode);
97
-
98
- this.db = new Database();
99
-
100
- // Delegate to the shared composition. The node is injected, so:
101
- // - `connectToStrand` never creates a node (its `createdNode` stays null);
102
- // `StrandInstanceManager` owns the node lifecycle. Its `shutdown` does
103
- // still stop the injected node via the collection factory, so the
104
- // manager's later `node.stop()` is an idempotent second stop;
105
- // - `storage` (the strand's raw storage, same instance the node uses) is
106
- // handed to the plugin's local transactor in bootstrap mode so DML lands
107
- // on the host backend rather than in-memory.
108
- const t0 = performance.now();
109
- const result = await connectToStrand(this.db, {
110
- strandId: sid,
111
- schema: this.config.sAppConfig.schema,
112
- mode,
113
- storage: this.config.rawStorage,
114
- libp2pNode: this.config.libp2pNode,
115
- coordinatedRepo: this.config.coordinatedRepo,
116
- enableCache: true,
117
- });
118
- this.shutdownStrand = result.shutdown;
119
- timing('[strandDb:%s] connectToStrand: %dms (hydrated tables=%d, indexes=%d)',
120
- sid, Math.round(performance.now() - t0),
121
- result.hydrated?.tables ?? 0, result.hydrated?.indexes ?? 0);
122
-
123
- // Founder-only: write the one-time membership bootstrap now that the schema is
124
- // applied (connectToStrand has returned). A throw here propagates out of
125
- // initialize() so buildStrandRuntime's rollback tears the half-built strand
126
- // down rather than leaking a node. Joiners skip this — their rows arrive via sync.
127
- if (this.config.founder === true) {
128
- await this.bootstrapFounder();
129
- }
130
-
131
- this.initialized = true;
132
- log('StrandDatabase for strand %s initialized successfully', sid);
133
- }
134
-
135
- /**
136
- * Run the founder membership bootstrap against the freshly-composed strand DB.
137
- *
138
- * Derives the founding keypair from the closed-strand `memberPrivateKey` (the
139
- * `Member.Key`/`Manager.MemberKey` are its public key); a closed strand with no
140
- * `memberPrivateKey` throws, because it could never seat a founding manager.
141
- * Open strands derive no keypair (Header only). Idempotent — see
142
- * {@link bootstrapFounderMembership}.
143
- */
144
- private async bootstrapFounder(): Promise<void> {
145
- const { strandId, strandType, memberPrivateKey, sAppConfig } = this.config;
146
- const founderKeyPair = strandType === 'c'
147
- ? this.deriveFounderKeyPair(strandId, memberPrivateKey)
148
- : undefined;
149
- await bootstrapFounderMembership(this.db!, {
150
- strandId,
151
- type: strandType,
152
- sApp: sAppConfig,
153
- founderKeyPair,
154
- });
155
- }
156
-
157
- /**
158
- * Derive the founding keypair for a closed strand from its `memberPrivateKey`,
159
- * failing loudly when the key is absent (a closed strand with no founding
160
- * Manager can never admit anyone).
161
- */
162
- private deriveFounderKeyPair(strandId: string, memberPrivateKey: string | undefined) {
163
- if (!memberPrivateKey) {
164
- throw new Error(
165
- `Cannot found closed strand ${strandId}: the strand row has no MemberPrivateKey. ` +
166
- 'A closed strand needs a founding Member/Manager derived from that key.',
167
- );
168
- }
169
- return strandMemberKeyPair(memberPrivateKey);
170
- }
171
-
172
- /**
173
- * Get the underlying database for queries
174
- */
175
- getDatabase(): Database {
176
- this.ensureInitialized();
177
- return this.db!;
178
- }
179
-
180
- /**
181
- * Close the database and cleanup resources. Runs the strand-connection
182
- * shutdown (collection-factory teardown, which also stops the injected node),
183
- * then closes the `Database`. `StrandInstanceManager.releaseRuntime` issues a
184
- * further idempotent `node.stop()` after this returns.
185
- */
186
- async close(): Promise<void> {
187
- if (this.shutdownStrand) {
188
- await this.shutdownStrand();
189
- this.shutdownStrand = null;
190
- }
191
- if (this.db) {
192
- void this.db.close();
193
- this.db = null;
194
- }
195
- this.initialized = false;
196
- log('StrandDatabase for strand %s closed', this.config.strandId);
197
- }
198
-
199
- private ensureInitialized(): void {
200
- if (!this.initialized || !this.db) {
201
- throw new Error(`StrandDatabase for strand ${this.config.strandId} not initialized. Call initialize() first.`);
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
+ }