@serfab/cadre-core 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +9 -8
  2. package/dist/authority-key.d.ts +41 -0
  3. package/dist/authority-key.js +52 -0
  4. package/dist/authority-key.js.map +1 -0
  5. package/dist/cadre-node.d.ts +770 -11
  6. package/dist/cadre-node.js +1900 -88
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/canonical-datetime.d.ts +33 -0
  9. package/dist/canonical-datetime.js +38 -0
  10. package/dist/canonical-datetime.js.map +1 -0
  11. package/dist/canonical-json.d.ts +12 -0
  12. package/dist/canonical-json.js +33 -0
  13. package/dist/canonical-json.js.map +1 -0
  14. package/dist/control-cohort.d.ts +63 -0
  15. package/dist/control-cohort.js +80 -0
  16. package/dist/control-cohort.js.map +1 -0
  17. package/dist/control-database.d.ts +259 -5
  18. package/dist/control-database.js +541 -128
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +89 -0
  21. package/dist/control-formation-recorder.js +139 -0
  22. package/dist/control-formation-recorder.js.map +1 -0
  23. package/dist/control-schema.d.ts +12 -0
  24. package/dist/control-schema.js +199 -0
  25. package/dist/control-schema.js.map +1 -0
  26. package/dist/control-stream.d.ts +54 -0
  27. package/dist/control-stream.js +104 -0
  28. package/dist/control-stream.js.map +1 -0
  29. package/dist/device-token.d.ts +50 -0
  30. package/dist/device-token.js +66 -0
  31. package/dist/device-token.js.map +1 -0
  32. package/dist/diagnostics/connection-path.d.ts +118 -0
  33. package/dist/diagnostics/connection-path.js +204 -0
  34. package/dist/diagnostics/connection-path.js.map +1 -0
  35. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  36. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  37. package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
  38. package/dist/hibernation-manager.d.ts +79 -0
  39. package/dist/hibernation-manager.js +173 -23
  40. package/dist/hibernation-manager.js.map +1 -1
  41. package/dist/index.d.ts +25 -4
  42. package/dist/index.js +59 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/key-store-file.d.ts +52 -0
  45. package/dist/key-store-file.js +194 -0
  46. package/dist/key-store-file.js.map +1 -0
  47. package/dist/key-store.d.ts +75 -0
  48. package/dist/key-store.js +66 -0
  49. package/dist/key-store.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +26 -0
  51. package/dist/peer-authorization.js +40 -0
  52. package/dist/peer-authorization.js.map +1 -0
  53. package/dist/peer-record.d.ts +89 -0
  54. package/dist/peer-record.js +117 -0
  55. package/dist/peer-record.js.map +1 -0
  56. package/dist/push-credentials.d.ts +34 -0
  57. package/dist/push-credentials.js +81 -0
  58. package/dist/push-credentials.js.map +1 -0
  59. package/dist/push-fanout.d.ts +137 -0
  60. package/dist/push-fanout.js +197 -0
  61. package/dist/push-fanout.js.map +1 -0
  62. package/dist/push-notifier-apns.d.ts +53 -0
  63. package/dist/push-notifier-apns.js +195 -0
  64. package/dist/push-notifier-apns.js.map +1 -0
  65. package/dist/push-notifier-fcm.d.ts +38 -0
  66. package/dist/push-notifier-fcm.js +183 -0
  67. package/dist/push-notifier-fcm.js.map +1 -0
  68. package/dist/push-notifier-shared.d.ts +18 -0
  69. package/dist/push-notifier-shared.js +25 -0
  70. package/dist/push-notifier-shared.js.map +1 -0
  71. package/dist/push-notifier.d.ts +71 -0
  72. package/dist/push-notifier.js +50 -0
  73. package/dist/push-notifier.js.map +1 -0
  74. package/dist/schema-verification.d.ts +11 -2
  75. package/dist/schema-verification.js +15 -4
  76. package/dist/schema-verification.js.map +1 -1
  77. package/dist/seed-bootstrap.d.ts +197 -9
  78. package/dist/seed-bootstrap.js +396 -137
  79. package/dist/seed-bootstrap.js.map +1 -1
  80. package/dist/seed-trust-policy.d.ts +63 -0
  81. package/dist/seed-trust-policy.js +56 -0
  82. package/dist/seed-trust-policy.js.map +1 -0
  83. package/dist/strand-addr-protocol.d.ts +132 -0
  84. package/dist/strand-addr-protocol.js +313 -0
  85. package/dist/strand-addr-protocol.js.map +1 -0
  86. package/dist/strand-cohort.d.ts +54 -0
  87. package/dist/strand-cohort.js +38 -0
  88. package/dist/strand-cohort.js.map +1 -0
  89. package/dist/strand-database.d.ts +54 -6
  90. package/dist/strand-database.js +88 -100
  91. package/dist/strand-database.js.map +1 -1
  92. package/dist/strand-formation-manager.d.ts +79 -19
  93. package/dist/strand-formation-manager.js +196 -104
  94. package/dist/strand-formation-manager.js.map +1 -1
  95. package/dist/strand-formation-protocol.d.ts +162 -0
  96. package/dist/strand-formation-protocol.js +279 -0
  97. package/dist/strand-formation-protocol.js.map +1 -0
  98. package/dist/strand-instance-manager.d.ts +73 -4
  99. package/dist/strand-instance-manager.js +167 -41
  100. package/dist/strand-instance-manager.js.map +1 -1
  101. package/dist/strand-member-key.d.ts +29 -0
  102. package/dist/strand-member-key.js +38 -0
  103. package/dist/strand-member-key.js.map +1 -0
  104. package/dist/strand-member-registry.d.ts +98 -0
  105. package/dist/strand-member-registry.js +129 -0
  106. package/dist/strand-member-registry.js.map +1 -0
  107. package/dist/strand-membership-writer.d.ts +361 -0
  108. package/dist/strand-membership-writer.js +449 -0
  109. package/dist/strand-membership-writer.js.map +1 -0
  110. package/dist/strand-solicitation.d.ts +77 -2
  111. package/dist/strand-solicitation.js +25 -4
  112. package/dist/strand-solicitation.js.map +1 -1
  113. package/dist/strand-wake-payload.d.ts +30 -0
  114. package/dist/strand-wake-payload.js +20 -0
  115. package/dist/strand-wake-payload.js.map +1 -0
  116. package/dist/strand-wake-protocol.d.ts +114 -0
  117. package/dist/strand-wake-protocol.js +257 -0
  118. package/dist/strand-wake-protocol.js.map +1 -0
  119. package/dist/strand-watcher.d.ts +6 -2
  120. package/dist/strand-watcher.js +57 -17
  121. package/dist/strand-watcher.js.map +1 -1
  122. package/dist/types.d.ts +406 -23
  123. package/dist/types.js +22 -5
  124. package/dist/types.js.map +1 -1
  125. package/package.json +14 -8
  126. package/src/authority-key.ts +68 -0
  127. package/src/cadre-node.ts +2937 -948
  128. package/src/canonical-datetime.ts +39 -0
  129. package/src/canonical-json.ts +30 -0
  130. package/src/control-cohort.ts +103 -0
  131. package/src/control-database.ts +931 -383
  132. package/src/control-formation-recorder.ts +151 -0
  133. package/src/control-schema.ts +198 -0
  134. package/src/control-stream.ts +128 -0
  135. package/src/device-token.ts +89 -0
  136. package/src/diagnostics/connection-path.ts +277 -0
  137. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  138. package/src/hibernation-manager.ts +197 -28
  139. package/src/index.ts +273 -79
  140. package/src/key-store-file.ts +203 -0
  141. package/src/key-store.ts +99 -0
  142. package/src/peer-authorization.ts +53 -0
  143. package/src/peer-record.ts +146 -0
  144. package/src/push-credentials.ts +84 -0
  145. package/src/push-fanout.ts +272 -0
  146. package/src/push-notifier-apns.ts +243 -0
  147. package/src/push-notifier-fcm.ts +221 -0
  148. package/src/push-notifier-shared.ts +28 -0
  149. package/src/push-notifier.ts +93 -0
  150. package/src/schema-verification.ts +18 -4
  151. package/src/seed-bootstrap.ts +464 -188
  152. package/src/seed-trust-policy.ts +107 -0
  153. package/src/strand-addr-protocol.ts +410 -0
  154. package/src/strand-cohort.ts +77 -0
  155. package/src/strand-database.ts +113 -141
  156. package/src/strand-formation-manager.ts +233 -142
  157. package/src/strand-formation-protocol.ts +391 -0
  158. package/src/strand-instance-manager.ts +207 -56
  159. package/src/strand-member-key.ts +39 -0
  160. package/src/strand-member-registry.ts +177 -0
  161. package/src/strand-membership-writer.ts +640 -0
  162. package/src/strand-solicitation.ts +93 -4
  163. package/src/strand-wake-payload.ts +32 -0
  164. package/src/strand-wake-protocol.ts +331 -0
  165. package/src/strand-watcher.ts +61 -17
  166. package/src/types.ts +1012 -582
@@ -1,33 +1,17 @@
1
1
  import debug from 'debug';
2
- import { Database, registerPlugin } from '@quereus/quereus';
3
- import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
4
- import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
2
+ import { Database } from '@quereus/quereus';
3
+ import { connectToStrand } from '@serfab/quereus-plugin-sereus';
4
+ import type { SereusPluginResult } from '@serfab/quereus-plugin-sereus';
5
5
  import type { Libp2p } from '@libp2p/interface';
6
6
  import type { IRepo } from '@optimystic/db-core';
7
7
  import type { IRawStorage } from '@optimystic/db-p2p';
8
8
  import type { SAppConfig, StrandMode } from './types.js';
9
+ import { bootstrapFounderMembership } from './strand-membership-writer.js';
10
+ import { strandMemberKeyPair } from './strand-member-key.js';
9
11
 
10
12
  const log = debug('sereus:cadre:strand-db');
11
13
  const timing = debug('sereus:cadre:timing');
12
14
 
13
- /**
14
- * Minimal interface for the CollectionFactory returned by the optimystic plugin.
15
- * We only need the methods we actually use.
16
- */
17
- interface CollectionFactory {
18
- registerLibp2pNode(networkName: string, node: Libp2p, coordinatedRepo: IRepo): void;
19
- shutdown(): Promise<void>;
20
- }
21
-
22
- /** Result of registering the optimystic plugin */
23
- interface OptimysticPluginResult {
24
- collectionFactory: CollectionFactory;
25
- vtables: Array<{ name: string; module: unknown; auxData: unknown }>;
26
- functions: Array<{ schema: unknown }>;
27
- hydrate: (db: Database) => Promise<{ tables: number; indexes: number }>;
28
- [key: string]: unknown;
29
- }
30
-
31
15
  export interface StrandDatabaseConfig {
32
16
  /** The strand ID */
33
17
  strandId: string;
@@ -51,15 +35,43 @@ export interface StrandDatabaseConfig {
51
35
  * across the two consumers.
52
36
  */
53
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`+`Authority`.
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`/`Authority.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;
54
56
  }
55
57
 
56
58
  /**
57
- * StrandDatabase manages the sApp schema for a strand using Quereus with Optimystic backend.
58
- * Each strand instance has its own isolated database with the sApp's schema applied.
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()`.)
59
71
  */
60
72
  export class StrandDatabase {
61
73
  private db: Database | null = null;
62
- private collectionFactory: CollectionFactory | null = null;
74
+ private shutdownStrand: SereusPluginResult['shutdown'] | null = null;
63
75
  private readonly config: StrandDatabaseConfig;
64
76
  private initialized = false;
65
77
 
@@ -68,7 +80,9 @@ export class StrandDatabase {
68
80
  }
69
81
 
70
82
  /**
71
- * Initialize the database - register plugins and execute sApp schema
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.
72
86
  */
73
87
  async initialize(): Promise<void> {
74
88
  if (this.initialized) {
@@ -76,127 +90,83 @@ export class StrandDatabase {
76
90
  return;
77
91
  }
78
92
 
79
- log('Initializing StrandDatabase for strand: %s (sApp: %s v%s)',
80
- this.config.strandId,
81
- this.config.sAppConfig.id,
82
- this.config.sAppConfig.version
83
- );
84
-
85
- // Create database instance
86
- this.db = new Database();
87
93
  const sid = this.config.strandId;
88
-
89
- // Register crypto plugin (provides digest, sign, verify functions)
90
- let t0 = performance.now();
91
- await registerPlugin(this.db, cryptoPlugin);
92
- timing('[strandDb:%s] cryptoPlugin: %dms', sid, Math.round(performance.now() - t0));
93
- log('Registered crypto plugin');
94
-
95
- // Register optimystic plugin. In `bootstrap` mode we route through the
96
- // local transactor so a solo node can initialize (schema apply etc.)
97
- // without network round trips. In `networked` mode the network transactor
98
- // is used. Mode is fixed for the lifetime of this Database instance; the
99
- // caller must restart the strand to transition modes.
100
- t0 = performance.now();
101
- const networkName = `strand-${sid}`;
102
94
  const mode: StrandMode = this.config.mode ?? 'networked';
103
- const defaultTransactor = mode === 'bootstrap' ? 'local' : 'network';
104
- // In bootstrap mode the local transactor IS the data path. Hand the plugin
105
- // the strand's raw storage (same instance the libp2p node uses) so writes
106
- // persist to the host backend instead of in-memory storage.
107
- const rawStorage = this.config.rawStorage;
108
- const pluginConfig: Record<string, unknown> = {
109
- default_transactor: defaultTransactor,
110
- default_key_network: 'libp2p',
111
- default_network_name: networkName,
112
- enable_cache: true,
113
- };
114
- if (mode === 'bootstrap' && rawStorage) {
115
- pluginConfig.rawStorageFactory = () => rawStorage;
116
- }
117
- // The plugin's published signature is `Record<string, SqlValue>` but it
118
- // also reads `rawStorageFactory` (a function reference) from the same map.
119
- // Cast through unknown rather than widen the public type.
120
- const pluginResult = optimysticPlugin(
121
- this.db,
122
- pluginConfig as unknown as Parameters<typeof optimysticPlugin>[1]
123
- ) as OptimysticPluginResult;
124
- log('Optimystic plugin registered for strand %s (mode=%s, transactor=%s, persistentStorage=%s)',
125
- sid, mode, defaultTransactor, mode === 'bootstrap' && !!rawStorage);
95
+ log('Initializing StrandDatabase for strand: %s (sApp: %s v%s, mode=%s)',
96
+ sid, this.config.sAppConfig.id, this.config.sAppConfig.version, mode);
126
97
 
127
- // Register vtables and functions manually since we need access to collectionFactory
128
- for (const vtable of pluginResult.vtables as Array<{ name: string; module: unknown; auxData: unknown }>) {
129
- this.db.registerModule(vtable.name, vtable.module as any, vtable.auxData);
130
- }
131
- for (const func of pluginResult.functions as Array<{ schema: unknown }>) {
132
- this.db.registerFunction(func.schema as any);
133
- }
134
- timing('[strandDb:%s] optimysticPlugin: %dms', sid, Math.round(performance.now() - t0));
135
-
136
- this.collectionFactory = pluginResult.collectionFactory;
137
-
138
- // Inject the libp2p node into the collection factory
139
- t0 = performance.now();
140
- this.collectionFactory.registerLibp2pNode(
141
- networkName,
142
- this.config.libp2pNode,
143
- this.config.coordinatedRepo
144
- );
145
- timing('[strandDb:%s] registerLibp2pNode: %dms', sid, Math.round(performance.now() - t0));
146
- log('Registered libp2p node with collection factory');
98
+ this.db = new Database();
147
99
 
148
- // Set optimystic as the default virtual table module so that
149
- // `declare schema` tables (which omit USING) are backed by optimystic
150
- // instead of the built-in memory module.
151
- t0 = performance.now();
152
- this.db.setDefaultVtabName('optimystic');
153
- this.db.setDefaultVtabArgs({
154
- networkName,
155
- transactor: defaultTransactor,
156
- keyNetwork: 'libp2p',
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,
157
117
  });
158
- timing('[strandDb:%s] setDefaultVtab: %dms', sid, Math.round(performance.now() - t0));
159
- log('Set default vtab to optimystic (networkName=%s, transactor=%s)', networkName, defaultTransactor);
160
-
161
- // Hydrate Quereus's catalog from any persisted optimystic vtab schemas
162
- // before running `apply schema App;`. Without this, a warm restart diffs
163
- // the wrapped DDL against an empty catalog and re-emits CREATE TABLE /
164
- // CREATE INDEX for every persisted object — which round-trips through
165
- // optimystic storage and is the cause of the ~160s warm-start regression
166
- // tracked in strand-database-must-hydrate-catalog-before-apply-schema.
167
- // No-op on first launch (empty storage).
168
- t0 = performance.now();
169
- const hydrated = await pluginResult.hydrate(this.db);
170
- timing('[strandDb:%s] hydrate: %dms (tables=%d, indexes=%d)',
171
- sid, Math.round(performance.now() - t0), hydrated.tables, hydrated.indexes);
172
- log('Hydrated catalog for strand %s (tables=%d, indexes=%d)',
173
- sid, hydrated.tables, hydrated.indexes);
174
-
175
- // Execute the sApp schema DDL
176
- t0 = performance.now();
177
- await this.executeSchema();
178
- timing('[strandDb:%s] executeSchema: %dms', sid, Math.round(performance.now() - t0));
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
+ }
179
130
 
180
131
  this.initialized = true;
181
132
  log('StrandDatabase for strand %s initialized successfully', sid);
182
133
  }
183
134
 
184
- private async executeSchema(): Promise<void> {
185
- const rawSchema = this.config.sAppConfig.schema;
186
- log('Applying sApp schema for strand %s', this.config.strandId);
187
-
188
- // Wrap the raw schema DDL in a declarative schema block and apply it.
189
- // This ensures proper schema management with diff/apply semantics.
190
- // The schema is applied to a named schema 'App' to keep it isolated.
191
- const wrappedSchema = `
192
- declare schema App {
193
- ${rawSchema}
194
- }
195
- apply schema App;
196
- `;
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`/`Authority.MemberKey` are its public key); a closed strand with no
140
+ * `memberPrivateKey` throws, because it could never seat a founding authority.
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
+ }
197
156
 
198
- await this.db!.exec(wrappedSchema);
199
- log('sApp schema applied to strand %s', this.config.strandId);
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
+ * Authority 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/Authority derived from that key.',
167
+ );
168
+ }
169
+ return strandMemberKeyPair(memberPrivateKey);
200
170
  }
201
171
 
202
172
  /**
@@ -208,15 +178,18 @@ export class StrandDatabase {
208
178
  }
209
179
 
210
180
  /**
211
- * Close the database and cleanup resources
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.
212
185
  */
213
186
  async close(): Promise<void> {
214
- if (this.collectionFactory) {
215
- await this.collectionFactory.shutdown();
216
- this.collectionFactory = null;
187
+ if (this.shutdownStrand) {
188
+ await this.shutdownStrand();
189
+ this.shutdownStrand = null;
217
190
  }
218
191
  if (this.db) {
219
- this.db.close();
192
+ void this.db.close();
220
193
  this.db = null;
221
194
  }
222
195
  this.initialized = false;
@@ -229,4 +202,3 @@ export class StrandDatabase {
229
202
  }
230
203
  }
231
204
  }
232
-