@serfab/cadre-core 1.18.0 → 1.19.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.
- package/dist/cadre-node.d.ts +65 -2
- package/dist/cadre-node.js +120 -4
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-read-retry.d.ts +19 -16
- package/dist/control-read-retry.js +22 -54
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +50 -6
- package/dist/control-retry.js +53 -6
- package/dist/control-retry.js.map +1 -1
- package/dist/control-write-retry.d.ts +24 -16
- package/dist/control-write-retry.js +58 -30
- package/dist/control-write-retry.js.map +1 -1
- package/dist/edge-node/config.d.ts +8 -2
- package/dist/edge-node/config.js +5 -1
- package/dist/edge-node/config.js.map +1 -1
- package/dist/edge-node/edge-join.js +2 -0
- package/dist/edge-node/edge-join.js.map +1 -1
- package/dist/edge-node/edge-node.d.ts +10 -0
- package/dist/edge-node/edge-node.js +52 -8
- package/dist/edge-node/edge-node.js.map +1 -1
- package/dist/edge-node/options.d.ts +11 -2
- package/dist/edge-node/options.js +11 -4
- package/dist/edge-node/options.js.map +1 -1
- package/dist/edge-node/platform.d.ts +15 -3
- package/dist/edge-node/strands.js +3 -2
- package/dist/edge-node/strands.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +8 -11
- package/dist/peer-join-backfill.js +22 -35
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/self-address-update.d.ts +13 -0
- package/dist/self-address-update.js +19 -0
- package/dist/self-address-update.js.map +1 -0
- package/dist/strand-database.d.ts +56 -0
- package/dist/strand-database.js +87 -0
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +67 -2
- package/dist/strand-instance-manager.js +105 -4
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +13 -1
- package/dist/strand-membership-reconciler.js +23 -14
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +16 -2
- package/dist/strand-membership-writer.js +54 -8
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-offline.d.ts +131 -0
- package/dist/strand-offline.js +142 -0
- package/dist/strand-offline.js.map +1 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/cadre-node.ts +11456 -11326
- package/src/control-read-retry.ts +22 -58
- package/src/control-retry.ts +54 -6
- package/src/control-write-retry.ts +58 -30
- package/src/edge-node/config.ts +11 -2
- package/src/edge-node/edge-join.ts +1 -0
- package/src/edge-node/edge-node.ts +55 -6
- package/src/edge-node/options.ts +20 -4
- package/src/edge-node/platform.ts +15 -3
- package/src/edge-node/strands.ts +3 -2
- package/src/index.ts +917 -899
- package/src/peer-join-backfill.ts +33 -43
- package/src/self-address-update.ts +19 -0
- package/src/strand-database.ts +466 -352
- package/src/strand-instance-manager.ts +2210 -2092
- package/src/strand-membership-reconciler.ts +29 -14
- package/src/strand-membership-writer.ts +61 -14
- package/src/strand-offline.ts +247 -0
- package/src/types.ts +2960 -2926
package/dist/strand-database.js
CHANGED
|
@@ -5,6 +5,7 @@ import { bootstrapFounderMembership } from './strand-membership-writer.js';
|
|
|
5
5
|
import { strandMemberKeyPair } from './strand-member-key.js';
|
|
6
6
|
import { StrandCommitRecord } from './strand-commit-durability.js';
|
|
7
7
|
import { TableWatchRegistry } from './table-change-watch.js';
|
|
8
|
+
import { discardQueued, StrandOfflineReport } from './strand-offline.js';
|
|
8
9
|
const log = debug('sereus:cadre:strand-db');
|
|
9
10
|
const timing = debug('sereus:cadre:timing');
|
|
10
11
|
/**
|
|
@@ -31,6 +32,8 @@ export class StrandDatabase {
|
|
|
31
32
|
this.createEmptyCollections = null;
|
|
32
33
|
this.initialized = false;
|
|
33
34
|
this.unsubscribeCommits = null;
|
|
35
|
+
this.offlineQueue = null;
|
|
36
|
+
this.offlineReport = null;
|
|
34
37
|
/** Subscriptions handed out by {@link watchTables}. */
|
|
35
38
|
this.tableWatches = new TableWatchRegistry();
|
|
36
39
|
this.config = config;
|
|
@@ -54,6 +57,7 @@ export class StrandDatabase {
|
|
|
54
57
|
this.founderSApp();
|
|
55
58
|
}
|
|
56
59
|
log('Initializing StrandDatabase for strand: %s (%s)', sid, sAppConfig ? `sApp: ${sAppConfig.id} v${sAppConfig.version}` : 'storage replica');
|
|
60
|
+
const offline = this.config.offline && { journal: await this.config.offline.journal() };
|
|
57
61
|
this.db = new Database();
|
|
58
62
|
// Delegate to the shared composition. The node is injected, so:
|
|
59
63
|
// - `connectToStrand` never creates or stops a node; `StrandInstanceManager`
|
|
@@ -74,20 +78,32 @@ export class StrandDatabase {
|
|
|
74
78
|
libp2pNode: this.config.libp2pNode,
|
|
75
79
|
coordinatedRepo: this.config.coordinatedRepo,
|
|
76
80
|
enableCache: true,
|
|
81
|
+
offline,
|
|
77
82
|
});
|
|
78
83
|
this.shutdownStrand = result.shutdown;
|
|
79
84
|
this.resolvedTransactor = result.transactor;
|
|
80
85
|
this.createEmptyCollections = result.createEmptyCollections;
|
|
81
86
|
// Before the founder bootstrap, so its commits are reported like any other.
|
|
82
87
|
this.subscribeToCommits(result);
|
|
88
|
+
this.followOffline(result);
|
|
83
89
|
timing('[strandDb:%s] connectToStrand: %dms (hydrated tables=%d, indexes=%d)', sid, Math.round(performance.now() - t0), result.hydrated?.tables ?? 0, result.hydrated?.indexes ?? 0);
|
|
84
90
|
// Founder-only: write the one-time membership bootstrap now that the schema is
|
|
85
91
|
// applied (connectToStrand has returned). A throw here propagates out of
|
|
86
92
|
// initialize(), and the caller closes this half-built database (close() runs the
|
|
87
93
|
// composition's shutdown). Joiners skip this — their rows arrive via sync.
|
|
94
|
+
// NOTE: a bootstrap with rows to write while `hydrate` restored queued transactions is refused
|
|
95
|
+
// (`StrandOfflineQueueBusyError`), failing every build, so that queue could never drain. Not
|
|
96
|
+
// reachable today: rows are written only while the founding is incomplete (Member empty, or no
|
|
97
|
+
// Manager and none retired), and an incomplete founding fails the build before the database is
|
|
98
|
+
// ever published for the app to queue in. If a founder is ever published with its founding
|
|
99
|
+
// incomplete, skip the bootstrap here while the queue is non-empty and run it once the queue drains.
|
|
88
100
|
if (this.config.founder === true) {
|
|
89
101
|
await this.bootstrapFounder();
|
|
90
102
|
}
|
|
103
|
+
// After `hydrate` restored the queue and after the founder bootstrap (its `Strand` writes are
|
|
104
|
+
// network-acknowledged in either mode); before the first-sync probe, so in offline mode a read
|
|
105
|
+
// the cohort cannot answer is served from this machine's copy there as it is to the app.
|
|
106
|
+
this.applyOfflineMode();
|
|
91
107
|
this.initialized = true;
|
|
92
108
|
log('StrandDatabase for strand %s initialized successfully', sid);
|
|
93
109
|
}
|
|
@@ -99,6 +115,39 @@ export class StrandDatabase {
|
|
|
99
115
|
unsubscribeHeld();
|
|
100
116
|
};
|
|
101
117
|
}
|
|
118
|
+
followOffline(result) {
|
|
119
|
+
const { offline } = this.config;
|
|
120
|
+
if (!offline) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.offlineQueue = result.offline ?? null;
|
|
124
|
+
this.offlineReport = new StrandOfflineReport(this.config.strandId, offline, result);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Put the queue in the strand's current mode ({@link StrandDatabaseOffline.mode}). It sets the
|
|
128
|
+
* acknowledgement of transactions begun from now on; an open one keeps the one it began with.
|
|
129
|
+
* No-op without a queue, and before {@link initialize} has composed one (it applies the mode itself).
|
|
130
|
+
*/
|
|
131
|
+
applyOfflineMode() {
|
|
132
|
+
const queue = this.offlineQueue;
|
|
133
|
+
if (!queue || !this.config.offline) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (this.config.offline.mode() === 'offline') {
|
|
137
|
+
queue.enter();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
queue.exit();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The database was handed to the app (`StrandInstanceManager.publishDatabase`): report the
|
|
145
|
+
* queue's state, since restored transactions' `queued` statuses were delivered before anything
|
|
146
|
+
* subscribed, and an `App` schema apply still waiting for the queue.
|
|
147
|
+
*/
|
|
148
|
+
reportPublished() {
|
|
149
|
+
this.offlineReport?.published();
|
|
150
|
+
}
|
|
102
151
|
/**
|
|
103
152
|
* Run the founder membership bootstrap against the freshly-composed strand DB.
|
|
104
153
|
*
|
|
@@ -188,6 +237,10 @@ export class StrandDatabase {
|
|
|
188
237
|
*/
|
|
189
238
|
async attachAppSchema(sAppConfig) {
|
|
190
239
|
this.ensureInitialized();
|
|
240
|
+
// NOTE: unlike the connect-time apply (`applyAppSchemaAfterQueue`), this is not deferred behind
|
|
241
|
+
// the offline queue: a replica writes no app transactions, so no queued transaction wrote an
|
|
242
|
+
// `App` table and `QueuedEntriesBlockSchemaChangeError` cannot refuse it. If a replica ever
|
|
243
|
+
// writes app rows offline, route this through the deferral instead of throwing.
|
|
191
244
|
await applyAppSchema(this.db, sAppConfig.schema);
|
|
192
245
|
this.config.sAppConfig = sAppConfig;
|
|
193
246
|
log('StrandDatabase for strand %s attached sApp %s v%s', this.config.strandId, sAppConfig.id, sAppConfig.version);
|
|
@@ -222,6 +275,37 @@ export class StrandDatabase {
|
|
|
222
275
|
this.ensureInitialized();
|
|
223
276
|
return this.commits.get(transactionId);
|
|
224
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* The strand connection's offline queue (docs/strands.md → "Offline mode"), or `undefined` on a
|
|
280
|
+
* node without `strandOffline`. The mode is set through `CadreNode.setStrandOfflineMode`, which
|
|
281
|
+
* outlives this database; listeners taken here die with it, as {@link watchTables} subscriptions do.
|
|
282
|
+
*/
|
|
283
|
+
get offline() {
|
|
284
|
+
this.ensureInitialized();
|
|
285
|
+
return this.offlineQueue ?? undefined;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Whether this database runs the `App` schema its launch was given. `'waiting-for-queue'` while
|
|
289
|
+
* queued transactions hold the apply back, so `StrandInstance.sAppInfo` names a version whose new
|
|
290
|
+
* tables or columns are not here yet; check this before relying on them.
|
|
291
|
+
*/
|
|
292
|
+
appSchemaState() {
|
|
293
|
+
this.ensureInitialized();
|
|
294
|
+
return this.offlineReport?.appSchemaState() ?? 'applied';
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The transaction ids the offline queue lists (`entries()`, the ones a restore has not put back
|
|
298
|
+
* yet included), or `undefined` with no queue: none configured, not composed yet, or closed.
|
|
299
|
+
* Unlike {@link offline} it does not require {@link initialize} to have finished, so the manager
|
|
300
|
+
* can read a database still being built or about to close.
|
|
301
|
+
*/
|
|
302
|
+
queuedTransactionIds() {
|
|
303
|
+
return this.offlineQueue?.entries().map((entry) => entry.transactionId);
|
|
304
|
+
}
|
|
305
|
+
/** {@link discardQueued} on this database's queue; `undefined` with no queue, as {@link queuedTransactionIds}. */
|
|
306
|
+
discardQueuedTransactions() {
|
|
307
|
+
return this.offlineQueue ? discardQueued(this.offlineQueue) : undefined;
|
|
308
|
+
}
|
|
225
309
|
/**
|
|
226
310
|
* The Optimystic transactor this strand's connection resolved to — always
|
|
227
311
|
* `'network'` here, since cadre-core passes no `transactor` option and takes
|
|
@@ -243,6 +327,9 @@ export class StrandDatabase {
|
|
|
243
327
|
// First, so an event from the closing connection reaches no app handler or table watch.
|
|
244
328
|
this.unsubscribeCommits?.();
|
|
245
329
|
this.unsubscribeCommits = null;
|
|
330
|
+
this.offlineReport?.release();
|
|
331
|
+
this.offlineReport = null;
|
|
332
|
+
this.offlineQueue = null;
|
|
246
333
|
this.tableWatches.disposeAll();
|
|
247
334
|
if (this.shutdownStrand) {
|
|
248
335
|
await this.shutdownStrand();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PrivateKey } from '@libp2p/interface';
|
|
2
2
|
import type { StrandCommitDurability, StrandCommitHeld } from './strand-commit-durability.js';
|
|
3
|
+
import type { StrandOfflineConfig, StrandOfflineListeners, StrandOfflineMode } from './strand-offline.js';
|
|
3
4
|
import { type PeerJoinBackfillConfig } from './peer-join-backfill.js';
|
|
4
5
|
import { type StrandNetworkStateStore } from './strand-network-state.js';
|
|
5
6
|
import { type StrandRevocationEnforcementConfig } from './strand-revocation-enforcer.js';
|
|
@@ -228,6 +229,13 @@ export interface StartStrandConfig {
|
|
|
228
229
|
* `strand:commit-held` event. Retained and isolated like {@link onCommitDurability}.
|
|
229
230
|
*/
|
|
230
231
|
onCommitHeld?: (event: StrandCommitHeld) => void;
|
|
232
|
+
/**
|
|
233
|
+
* Offline mode for this strand (docs/strands.md → "Offline mode"), forwarded from
|
|
234
|
+
* `CadreNodeConfig.strandOffline`, with where each database built for the launch reports its
|
|
235
|
+
* queue and its app-schema apply (`CadreNode` emits them as events). Absent: no database gets an
|
|
236
|
+
* offline queue. The mode itself is the manager's, per strand ({@link StrandInstanceManager.setOfflineMode}).
|
|
237
|
+
*/
|
|
238
|
+
offline?: StrandOfflineConfig & StrandOfflineListeners;
|
|
231
239
|
/**
|
|
232
240
|
* Re-announce this strand's delegate peer id (`delegatePeerId` — the strand
|
|
233
241
|
* node's own transport peer id) to ONE relay (`relayAddr`, a direct dial addr
|
|
@@ -409,6 +417,20 @@ export declare class StrandInstanceManager {
|
|
|
409
417
|
* {@link releaseRuntime}, so a stop or a quiesce cancels it.
|
|
410
418
|
*/
|
|
411
419
|
private unreachableJoinRetries;
|
|
420
|
+
/**
|
|
421
|
+
* The offline mode set per strand id ({@link setOfflineMode}); a strand absent here runs in its
|
|
422
|
+
* launch's `initialMode`. Never cleared: the mode belongs to the strand, not to one database, so
|
|
423
|
+
* it outlives hibernation, every rebuild and a stop, for as long as the process runs.
|
|
424
|
+
*/
|
|
425
|
+
private offlineModes;
|
|
426
|
+
/**
|
|
427
|
+
* The transaction ids each strand's offline queue listed when its database last closed
|
|
428
|
+
* ({@link releaseRuntime}), for a leave from a hibernating strand, whose queue is then only in
|
|
429
|
+
* its journal ({@link queuedTransactionIds}). Exact while the strand hibernates: nothing drains
|
|
430
|
+
* a journal without a database. Deleted with the instance: on {@link stopStrand}, and when a
|
|
431
|
+
* {@link startStrand} fails.
|
|
432
|
+
*/
|
|
433
|
+
private closedQueues;
|
|
412
434
|
private stopping;
|
|
413
435
|
constructor();
|
|
414
436
|
/**
|
|
@@ -479,6 +501,12 @@ export declare class StrandInstanceManager {
|
|
|
479
501
|
* {@link releaseRuntime} to close.
|
|
480
502
|
*/
|
|
481
503
|
private buildStrandDatabase;
|
|
504
|
+
/**
|
|
505
|
+
* One build's {@link StrandDatabaseOffline}: the strand's journal, which the database resolves as
|
|
506
|
+
* it initializes (a throw or rejection fails the build), and the strand's mode, which it reads each
|
|
507
|
+
* time it applies it.
|
|
508
|
+
*/
|
|
509
|
+
private offlineDatabaseConfig;
|
|
482
510
|
/**
|
|
483
511
|
* Whether this strand's raw store holds no block. False for a store that cannot list its
|
|
484
512
|
* blocks, or whose listing fails, so such a launch probes at once.
|
|
@@ -601,8 +629,9 @@ export declare class StrandInstanceManager {
|
|
|
601
629
|
* this removal again — the in-memory retained key is the last chance.
|
|
602
630
|
*
|
|
603
631
|
* Never throws. Quiet no-op for a strand that is untracked, open, or launched
|
|
604
|
-
* without a party key; a quiesced (hibernating) strand,
|
|
605
|
-
*
|
|
632
|
+
* without a party key; a quiesced (hibernating) strand, a strand whose offline queue
|
|
633
|
+
* still holds transactions (the writer refuses with `StrandOfflineQueueBusyError`), or a
|
|
634
|
+
* strand whose write quorum is already unreachable, leaves the stale binding behind with a log —
|
|
606
635
|
* it grants nothing today (admission is not allowlist-gated) and only
|
|
607
636
|
* mis-credits diversity. The strand's own membership reconciler is stopped —
|
|
608
637
|
* and its in-flight pass awaited — FIRST, so a pass that was already past its
|
|
@@ -786,6 +815,42 @@ export declare class StrandInstanceManager {
|
|
|
786
815
|
* test of its own.
|
|
787
816
|
*/
|
|
788
817
|
private publishDatabase;
|
|
818
|
+
/**
|
|
819
|
+
* A tracked strand's offline mode: the one last set ({@link setOfflineMode}), else its launch's
|
|
820
|
+
* `initialMode`, else `'online'`.
|
|
821
|
+
*
|
|
822
|
+
* @throws when the strand is not tracked.
|
|
823
|
+
*/
|
|
824
|
+
offlineMode(strandId: string): StrandOfflineMode;
|
|
825
|
+
private currentOfflineMode;
|
|
826
|
+
/**
|
|
827
|
+
* Set a tracked strand's offline mode, for the rest of the process (see {@link offlineModes}).
|
|
828
|
+
* A live database takes it now, published or still behind the first-sync gate, for transactions
|
|
829
|
+
* begun from then on; a build in progress reads it when it applies the mode, and a hibernating
|
|
830
|
+
* strand gets it at its next build.
|
|
831
|
+
*
|
|
832
|
+
* @throws when the strand is not tracked.
|
|
833
|
+
*/
|
|
834
|
+
setOfflineMode(strandId: string, mode: StrandOfflineMode): void;
|
|
835
|
+
/**
|
|
836
|
+
* The transaction ids a strand's offline queue holds: its live database's, else what its
|
|
837
|
+
* database listed when it last closed (a hibernating strand, see {@link closedQueues}), else none.
|
|
838
|
+
*
|
|
839
|
+
* NOTE: a strand this process has built no database for reads as empty, though its journal may
|
|
840
|
+
* hold transactions from an earlier run, which come back if the strand is ever attached again.
|
|
841
|
+
* If apps are found leaving strands with transactions journaled that way, read the journal here.
|
|
842
|
+
*/
|
|
843
|
+
queuedTransactionIds(strandId: string): readonly string[];
|
|
844
|
+
/**
|
|
845
|
+
* Withdraw what the live database's offline queue never sent (`StrandDatabase.discardQueuedTransactions`),
|
|
846
|
+
* returning the ids it kept; `undefined` when no live database has a queue (a hibernating
|
|
847
|
+
* strand, whose queue is back once a build restores it from the journal).
|
|
848
|
+
*/
|
|
849
|
+
discardQueuedTransactions(strandId: string): readonly string[] | undefined;
|
|
850
|
+
/** A strand's database: the published one, else the one still behind the first-sync gate. */
|
|
851
|
+
private liveDatabase;
|
|
852
|
+
/** Keep what a closing database's queue lists, for {@link queuedTransactionIds}; one with no queue changes nothing. */
|
|
853
|
+
private rememberQueue;
|
|
789
854
|
/** Reject every {@link whenWritable} waiter for a strand that is going away. */
|
|
790
855
|
private rejectWritableWaiters;
|
|
791
856
|
/**
|
|
@@ -254,6 +254,20 @@ export class StrandInstanceManager {
|
|
|
254
254
|
* {@link releaseRuntime}, so a stop or a quiesce cancels it.
|
|
255
255
|
*/
|
|
256
256
|
this.unreachableJoinRetries = new Map();
|
|
257
|
+
/**
|
|
258
|
+
* The offline mode set per strand id ({@link setOfflineMode}); a strand absent here runs in its
|
|
259
|
+
* launch's `initialMode`. Never cleared: the mode belongs to the strand, not to one database, so
|
|
260
|
+
* it outlives hibernation, every rebuild and a stop, for as long as the process runs.
|
|
261
|
+
*/
|
|
262
|
+
this.offlineModes = new Map();
|
|
263
|
+
/**
|
|
264
|
+
* The transaction ids each strand's offline queue listed when its database last closed
|
|
265
|
+
* ({@link releaseRuntime}), for a leave from a hibernating strand, whose queue is then only in
|
|
266
|
+
* its journal ({@link queuedTransactionIds}). Exact while the strand hibernates: nothing drains
|
|
267
|
+
* a journal without a database. Deleted with the instance: on {@link stopStrand}, and when a
|
|
268
|
+
* {@link startStrand} fails.
|
|
269
|
+
*/
|
|
270
|
+
this.closedQueues = new Map();
|
|
257
271
|
this.stopping = false;
|
|
258
272
|
log('StrandInstanceManager created');
|
|
259
273
|
}
|
|
@@ -360,6 +374,8 @@ export class StrandInstanceManager {
|
|
|
360
374
|
// reads both, and a config without an instance would strand the config.
|
|
361
375
|
this.instances.delete(strandId);
|
|
362
376
|
this.launchConfigs.delete(strandId);
|
|
377
|
+
// The cleanup above remembered the half-built database's queue; an untracked strand keeps none.
|
|
378
|
+
this.closedQueues.delete(strandId);
|
|
363
379
|
// The store this launch resolved goes with it: nothing owns it any more, and a
|
|
364
380
|
// retained cache wrapper would be handed back (already retired) on a retry.
|
|
365
381
|
await this.disposeStrandStorage(strandId);
|
|
@@ -783,7 +799,8 @@ export class StrandInstanceManager {
|
|
|
783
799
|
// reconciler that would otherwise write it is disarmed (see buildStrandRuntime).
|
|
784
800
|
bindFounderPeer: config.membershipReconciliation?.enabled !== false,
|
|
785
801
|
onCommitDurability: config.onCommitDurability,
|
|
786
|
-
onCommitHeld: config.onCommitHeld
|
|
802
|
+
onCommitHeld: config.onCommitHeld,
|
|
803
|
+
offline: this.offlineDatabaseConfig(strandId, config.offline)
|
|
787
804
|
});
|
|
788
805
|
const storageRepo = node.storageRepo;
|
|
789
806
|
const gate = new StrandFirstSyncGate({
|
|
@@ -856,6 +873,22 @@ export class StrandInstanceManager {
|
|
|
856
873
|
}
|
|
857
874
|
return 'built';
|
|
858
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* One build's {@link StrandDatabaseOffline}: the strand's journal, which the database resolves as
|
|
878
|
+
* it initializes (a throw or rejection fails the build), and the strand's mode, which it reads each
|
|
879
|
+
* time it applies it.
|
|
880
|
+
*/
|
|
881
|
+
offlineDatabaseConfig(strandId, offline) {
|
|
882
|
+
if (!offline) {
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
885
|
+
const { journal, initialMode: _initialMode, ...listeners } = offline;
|
|
886
|
+
return {
|
|
887
|
+
...listeners,
|
|
888
|
+
journal: () => journal(strandId),
|
|
889
|
+
mode: () => this.currentOfflineMode(strandId, offline)
|
|
890
|
+
};
|
|
891
|
+
}
|
|
859
892
|
/**
|
|
860
893
|
* Whether this strand's raw store holds no block. False for a store that cannot list its
|
|
861
894
|
* blocks, or whose listing fails, so such a launch probes at once.
|
|
@@ -1014,9 +1047,11 @@ export class StrandInstanceManager {
|
|
|
1014
1047
|
if (gate) {
|
|
1015
1048
|
gate.stop();
|
|
1016
1049
|
this.firstSyncGates.delete(instance.strandId);
|
|
1050
|
+
this.rememberQueue(instance.strandId, gate.database);
|
|
1017
1051
|
await gate.database.close();
|
|
1018
1052
|
}
|
|
1019
1053
|
if (instance.database) {
|
|
1054
|
+
this.rememberQueue(instance.strandId, instance.database);
|
|
1020
1055
|
await instance.database.close();
|
|
1021
1056
|
instance.database = undefined;
|
|
1022
1057
|
}
|
|
@@ -1146,8 +1181,9 @@ export class StrandInstanceManager {
|
|
|
1146
1181
|
* this removal again — the in-memory retained key is the last chance.
|
|
1147
1182
|
*
|
|
1148
1183
|
* Never throws. Quiet no-op for a strand that is untracked, open, or launched
|
|
1149
|
-
* without a party key; a quiesced (hibernating) strand,
|
|
1150
|
-
*
|
|
1184
|
+
* without a party key; a quiesced (hibernating) strand, a strand whose offline queue
|
|
1185
|
+
* still holds transactions (the writer refuses with `StrandOfflineQueueBusyError`), or a
|
|
1186
|
+
* strand whose write quorum is already unreachable, leaves the stale binding behind with a log —
|
|
1151
1187
|
* it grants nothing today (admission is not allowlist-gated) and only
|
|
1152
1188
|
* mis-credits diversity. The strand's own membership reconciler is stopped —
|
|
1153
1189
|
* and its in-flight pass awaited — FIRST, so a pass that was already past its
|
|
@@ -1179,7 +1215,8 @@ export class StrandInstanceManager {
|
|
|
1179
1215
|
return;
|
|
1180
1216
|
}
|
|
1181
1217
|
try {
|
|
1182
|
-
// Background write on the app's database: never join a transaction the app has open
|
|
1218
|
+
// Background write on the app's database: never join a transaction the app has open, and
|
|
1219
|
+
// never wait behind its offline queue (the writer refuses both; the catch below logs it).
|
|
1183
1220
|
await removeMemberPeer(db, {
|
|
1184
1221
|
memberKeyPair: strandMemberKeyPair(config.partyMemberPrivateKey),
|
|
1185
1222
|
peerId
|
|
@@ -1581,12 +1618,75 @@ export class StrandInstanceManager {
|
|
|
1581
1618
|
if (wasGated) {
|
|
1582
1619
|
this.launchConfigs.get(strandId)?.onWritable?.(strandId);
|
|
1583
1620
|
}
|
|
1621
|
+
database.reportPublished();
|
|
1584
1622
|
// Neither throws nor awaits: publishing must stay synchronous. `start()` is a no-op on an
|
|
1585
1623
|
// enforcer already started or stopped, and the reconciler pass chains on the loop's own
|
|
1586
1624
|
// tail, so it cannot overlap a pass already in flight.
|
|
1587
1625
|
this.revocationEnforcers.get(strandId)?.start();
|
|
1588
1626
|
void this.membershipReconcilers.get(strandId)?.reconcile();
|
|
1589
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* A tracked strand's offline mode: the one last set ({@link setOfflineMode}), else its launch's
|
|
1630
|
+
* `initialMode`, else `'online'`.
|
|
1631
|
+
*
|
|
1632
|
+
* @throws when the strand is not tracked.
|
|
1633
|
+
*/
|
|
1634
|
+
offlineMode(strandId) {
|
|
1635
|
+
const config = this.launchConfigs.get(strandId);
|
|
1636
|
+
if (!config) {
|
|
1637
|
+
throw new Error(`Cannot read the offline mode of strand ${strandId}: not tracked`);
|
|
1638
|
+
}
|
|
1639
|
+
return this.currentOfflineMode(strandId, config.offline);
|
|
1640
|
+
}
|
|
1641
|
+
currentOfflineMode(strandId, offline) {
|
|
1642
|
+
return this.offlineModes.get(strandId) ?? offline?.initialMode ?? 'online';
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Set a tracked strand's offline mode, for the rest of the process (see {@link offlineModes}).
|
|
1646
|
+
* A live database takes it now, published or still behind the first-sync gate, for transactions
|
|
1647
|
+
* begun from then on; a build in progress reads it when it applies the mode, and a hibernating
|
|
1648
|
+
* strand gets it at its next build.
|
|
1649
|
+
*
|
|
1650
|
+
* @throws when the strand is not tracked.
|
|
1651
|
+
*/
|
|
1652
|
+
setOfflineMode(strandId, mode) {
|
|
1653
|
+
const instance = this.instances.get(strandId);
|
|
1654
|
+
if (!instance) {
|
|
1655
|
+
throw new Error(`Cannot set the offline mode of strand ${strandId}: not tracked`);
|
|
1656
|
+
}
|
|
1657
|
+
this.offlineModes.set(strandId, mode);
|
|
1658
|
+
this.liveDatabase(strandId)?.applyOfflineMode();
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* The transaction ids a strand's offline queue holds: its live database's, else what its
|
|
1662
|
+
* database listed when it last closed (a hibernating strand, see {@link closedQueues}), else none.
|
|
1663
|
+
*
|
|
1664
|
+
* NOTE: a strand this process has built no database for reads as empty, though its journal may
|
|
1665
|
+
* hold transactions from an earlier run, which come back if the strand is ever attached again.
|
|
1666
|
+
* If apps are found leaving strands with transactions journaled that way, read the journal here.
|
|
1667
|
+
*/
|
|
1668
|
+
queuedTransactionIds(strandId) {
|
|
1669
|
+
return this.liveDatabase(strandId)?.queuedTransactionIds() ?? this.closedQueues.get(strandId) ?? [];
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Withdraw what the live database's offline queue never sent (`StrandDatabase.discardQueuedTransactions`),
|
|
1673
|
+
* returning the ids it kept; `undefined` when no live database has a queue (a hibernating
|
|
1674
|
+
* strand, whose queue is back once a build restores it from the journal).
|
|
1675
|
+
*/
|
|
1676
|
+
discardQueuedTransactions(strandId) {
|
|
1677
|
+
return this.liveDatabase(strandId)?.discardQueuedTransactions();
|
|
1678
|
+
}
|
|
1679
|
+
/** A strand's database: the published one, else the one still behind the first-sync gate. */
|
|
1680
|
+
liveDatabase(strandId) {
|
|
1681
|
+
return this.instances.get(strandId)?.database ?? this.firstSyncGates.get(strandId)?.database;
|
|
1682
|
+
}
|
|
1683
|
+
/** Keep what a closing database's queue lists, for {@link queuedTransactionIds}; one with no queue changes nothing. */
|
|
1684
|
+
rememberQueue(strandId, database) {
|
|
1685
|
+
const queued = database.queuedTransactionIds();
|
|
1686
|
+
if (queued !== undefined) {
|
|
1687
|
+
this.closedQueues.set(strandId, queued);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1590
1690
|
/** Reject every {@link whenWritable} waiter for a strand that is going away. */
|
|
1591
1691
|
rejectWritableWaiters(strandId, reason) {
|
|
1592
1692
|
const waiters = this.writableWaiters.get(strandId);
|
|
@@ -1667,6 +1767,7 @@ export class StrandInstanceManager {
|
|
|
1667
1767
|
instance.status = 'stopped';
|
|
1668
1768
|
this.instances.delete(strandId);
|
|
1669
1769
|
this.launchConfigs.delete(strandId);
|
|
1770
|
+
this.closedQueues.delete(strandId);
|
|
1670
1771
|
// Storage is released only here — NOT in releaseRuntime, which a quiesce shares.
|
|
1671
1772
|
await this.disposeStrandStorage(strandId);
|
|
1672
1773
|
log('Strand %s stopped successfully', strandId);
|