@serfab/quereus-plugin-sereus 0.10.0 → 0.12.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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Wrap an embedder-supplied `IRawStorage` in `@optimystic/db-p2p`'s write-through
3
+ * raw-storage cache (`CachedRawStorage`) before handing it to a node or transactor.
4
+ *
5
+ * Why: bringing a control database or strand up is raw-storage-operation bound —
6
+ * uncached, a cold start re-reads the same handful of blocks ~2000 times (see the
7
+ * budget history in cadre-core's `test/control-start-storage-op-budget.spec.ts` and
8
+ * the sereus tickets `complete/optimystic-block-read-amplification-on-control-start`
9
+ * + `complete/optimystic-schema-catalog-reread-per-write-blows-storage-budgets`).
10
+ * Upstream ships the remedy as an opt-in wrapper — adoption is deliberately the
11
+ * consumer's choice — and this module is where Sereus opts in. It lives in this
12
+ * package (not cadre-core) for the same reason `cluster-size.ts` does: both the SQL
13
+ * plugin's own connectors (`composeStrand`, including the browser/IndexedDB path,
14
+ * where per-operation latency is highest) and cadre-core's control/strand seams
15
+ * need the identical wrap, and two copies could disagree.
16
+ *
17
+ * Soundness rests on the cache's single-process-owner invariant (upstream
18
+ * docs/storage.md §6): every write to the backend must funnel through the wrapped
19
+ * instance. Two rules uphold that:
20
+ *
21
+ * - **One wrap per inner instance** (WeakMap memo): the same `IRawStorage` reaches
22
+ * this function from several seams at once — cadre-core's node wiring and the
23
+ * strand backfill share the instance the node was built with, and the SQL
24
+ * plugin's `composeStrand` re-wraps whatever it is handed. Two live caches over
25
+ * one backend would each miss the other's writes; the memo makes the wrap
26
+ * idempotent instead — a caller wrapping an already-wrapped or already-seen
27
+ * instance gets the same object back.
28
+ * - **A Sereus process owns its data stores** — no second process writes the same
29
+ * backend, which is the invariant's other half.
30
+ *
31
+ * `MemoryRawStorage` is returned unwrapped on upstream's own guidance: the backend
32
+ * is already in-memory, so the cache adds bookkeeping with nothing to save.
33
+ *
34
+ * **{@link wrapStorageWithCache} and {@link disposeStorageCache} are a matched pair
35
+ * with a holder count between them.** Every wrap that hands back a cache this module
36
+ * created takes one claim on it; every dispose of that cache releases one. The cache
37
+ * is emptied, unregistered from the shared pool, and forgotten only when the LAST
38
+ * claim goes. That is what makes overlapping scopes safe: `CadreNodeConfig.storage.provider`
39
+ * may be a single `IRawStorage` shared by the control database and every workspace,
40
+ * and without a count the first scope to stop would retire the cache the others are
41
+ * still reading through — then the next scope to start, finding the memo empty, would
42
+ * build a SECOND cache over the same backend, and each would serve its own stale view
43
+ * (including remembered "this block does not exist" answers). It is also what lets
44
+ * `composeStrand` release its own claim unconditionally on `shutdown()` without having
45
+ * to guess whether it or cadre-core created the wrapper.
46
+ *
47
+ * The memo dedupes *concurrent* seams; it is not what keeps a cache alive across a
48
+ * runtime rebuild. That is cadre-core's job, and it does it by OWNERSHIP: one
49
+ * resolved store per scope per runtime lifetime — a strand's store for the life of
50
+ * its instance (a hibernation quiesce → resume reuses the store already resolved,
51
+ * so the wake lands on a warm cache), the control store for the life of one
52
+ * `CadreNode.start()`. Because the store outlives the rebuild, so does its wrap,
53
+ * whether or not the embedder's provider returns a stable instance.
54
+ *
55
+ * When a scope's runtime stops for good, it calls {@link disposeStorageCache} — the
56
+ * release half of that ownership. Without it the retired wrapper's `CacheStoreHandle`
57
+ * would stay in the shared pool's registry for the process lifetime (`unregisterStore`
58
+ * is the only removal), one orphan per stop.
59
+ *
60
+ * NOTE: upstream ships an equivalent counted wrap of its own — `withReadCache`, which
61
+ * hands back a `ReadCacheLease` per caller and additionally dedupes by
62
+ * `getStoreIdentity()`, so two DISTINCT instances over one directory share one cache.
63
+ * This module keeps its own count instead, because its public surface releases by
64
+ * WRAPPER (`disposeStorageCache(storage)`) rather than by lease, and `withReadCache`
65
+ * returns no lease on its already-cached pass-through — which would leave
66
+ * `composeStrand`'s unconditional release unable to tell its own claim from cadre-core's.
67
+ * Adopting the lease API would mean changing that surface at all three call sites; worth
68
+ * doing if Sereus ever needs the identity-keyed dedupe (two `FileRawStorage` over one
69
+ * path), and not before.
70
+ */
71
+ import { type IRawStorage } from '@optimystic/db-p2p/rn';
72
+ /**
73
+ * The cached view of `storage`, memoized per instance, with one holder claim taken for
74
+ * this caller — release it with {@link disposeStorageCache} when this caller's scope
75
+ * ends. `label` names the store in the shared pool's `stats()` (e.g. `control`, or the
76
+ * strand id); on a memo hit the first caller's label sticks, since a second cache over
77
+ * one backend is the failure that matters and a stale pool label is not.
78
+ */
79
+ export declare function wrapStorageWithCache(storage: IRawStorage, label: string): IRawStorage;
80
+ /**
81
+ * Release this caller's claim on the cache wrapper {@link wrapStorageWithCache} returned.
82
+ * The wrapper is emptied, unregistered from the shared pool, and dropped from the memo
83
+ * only when the LAST holder releases — so a scope stopping never retires a cache another
84
+ * live scope is still reading through, and a still-held wrapper is never replaced by a
85
+ * competing second cache over the same backend.
86
+ *
87
+ * A wrapper with no live claim is left alone: that covers both a cache the embedder built
88
+ * itself (theirs to dispose) and a late second dispose of an already-retired wrapper,
89
+ * whose successor in the memo must not be dropped.
90
+ *
91
+ * No-op for anything this module returned unwrapped (e.g. `MemoryRawStorage`), so callers
92
+ * need no `instanceof` test. Only the wrapper is released: the inner store the embedder
93
+ * returned is never closed — that handle stays the embedder's to manage.
94
+ */
95
+ export declare function disposeStorageCache(storage: IRawStorage): Promise<void>;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Wrap an embedder-supplied `IRawStorage` in `@optimystic/db-p2p`'s write-through
3
+ * raw-storage cache (`CachedRawStorage`) before handing it to a node or transactor.
4
+ *
5
+ * Why: bringing a control database or strand up is raw-storage-operation bound —
6
+ * uncached, a cold start re-reads the same handful of blocks ~2000 times (see the
7
+ * budget history in cadre-core's `test/control-start-storage-op-budget.spec.ts` and
8
+ * the sereus tickets `complete/optimystic-block-read-amplification-on-control-start`
9
+ * + `complete/optimystic-schema-catalog-reread-per-write-blows-storage-budgets`).
10
+ * Upstream ships the remedy as an opt-in wrapper — adoption is deliberately the
11
+ * consumer's choice — and this module is where Sereus opts in. It lives in this
12
+ * package (not cadre-core) for the same reason `cluster-size.ts` does: both the SQL
13
+ * plugin's own connectors (`composeStrand`, including the browser/IndexedDB path,
14
+ * where per-operation latency is highest) and cadre-core's control/strand seams
15
+ * need the identical wrap, and two copies could disagree.
16
+ *
17
+ * Soundness rests on the cache's single-process-owner invariant (upstream
18
+ * docs/storage.md §6): every write to the backend must funnel through the wrapped
19
+ * instance. Two rules uphold that:
20
+ *
21
+ * - **One wrap per inner instance** (WeakMap memo): the same `IRawStorage` reaches
22
+ * this function from several seams at once — cadre-core's node wiring and the
23
+ * strand backfill share the instance the node was built with, and the SQL
24
+ * plugin's `composeStrand` re-wraps whatever it is handed. Two live caches over
25
+ * one backend would each miss the other's writes; the memo makes the wrap
26
+ * idempotent instead — a caller wrapping an already-wrapped or already-seen
27
+ * instance gets the same object back.
28
+ * - **A Sereus process owns its data stores** — no second process writes the same
29
+ * backend, which is the invariant's other half.
30
+ *
31
+ * `MemoryRawStorage` is returned unwrapped on upstream's own guidance: the backend
32
+ * is already in-memory, so the cache adds bookkeeping with nothing to save.
33
+ *
34
+ * **{@link wrapStorageWithCache} and {@link disposeStorageCache} are a matched pair
35
+ * with a holder count between them.** Every wrap that hands back a cache this module
36
+ * created takes one claim on it; every dispose of that cache releases one. The cache
37
+ * is emptied, unregistered from the shared pool, and forgotten only when the LAST
38
+ * claim goes. That is what makes overlapping scopes safe: `CadreNodeConfig.storage.provider`
39
+ * may be a single `IRawStorage` shared by the control database and every workspace,
40
+ * and without a count the first scope to stop would retire the cache the others are
41
+ * still reading through — then the next scope to start, finding the memo empty, would
42
+ * build a SECOND cache over the same backend, and each would serve its own stale view
43
+ * (including remembered "this block does not exist" answers). It is also what lets
44
+ * `composeStrand` release its own claim unconditionally on `shutdown()` without having
45
+ * to guess whether it or cadre-core created the wrapper.
46
+ *
47
+ * The memo dedupes *concurrent* seams; it is not what keeps a cache alive across a
48
+ * runtime rebuild. That is cadre-core's job, and it does it by OWNERSHIP: one
49
+ * resolved store per scope per runtime lifetime — a strand's store for the life of
50
+ * its instance (a hibernation quiesce → resume reuses the store already resolved,
51
+ * so the wake lands on a warm cache), the control store for the life of one
52
+ * `CadreNode.start()`. Because the store outlives the rebuild, so does its wrap,
53
+ * whether or not the embedder's provider returns a stable instance.
54
+ *
55
+ * When a scope's runtime stops for good, it calls {@link disposeStorageCache} — the
56
+ * release half of that ownership. Without it the retired wrapper's `CacheStoreHandle`
57
+ * would stay in the shared pool's registry for the process lifetime (`unregisterStore`
58
+ * is the only removal), one orphan per stop.
59
+ *
60
+ * NOTE: upstream ships an equivalent counted wrap of its own — `withReadCache`, which
61
+ * hands back a `ReadCacheLease` per caller and additionally dedupes by
62
+ * `getStoreIdentity()`, so two DISTINCT instances over one directory share one cache.
63
+ * This module keeps its own count instead, because its public surface releases by
64
+ * WRAPPER (`disposeStorageCache(storage)`) rather than by lease, and `withReadCache`
65
+ * returns no lease on its already-cached pass-through — which would leave
66
+ * `composeStrand`'s unconditional release unable to tell its own claim from cadre-core's.
67
+ * Adopting the lease API would mean changing that surface at all three call sites; worth
68
+ * doing if Sereus ever needs the identity-keyed dedupe (two `FileRawStorage` over one
69
+ * path), and not before.
70
+ */
71
+ // Imported from the `/rn` entry — the platform-neutral surface (no Node-only
72
+ // transports) — because `composeStrand` reaches this module on the browser path
73
+ // too. Both db-p2p entrypoints re-export the same storage modules, so class
74
+ // identity (the `instanceof` checks below) is unaffected on Node.
75
+ import { CachedRawStorage, MemoryRawStorage } from '@optimystic/db-p2p/rn';
76
+ const wraps = new WeakMap();
77
+ const claims = new WeakMap();
78
+ /**
79
+ * Count one more holder of `wrapped`, if this module created it. A cache built outside
80
+ * this module stays its creator's to release, so it is left uncounted and
81
+ * {@link disposeStorageCache} no-ops on it.
82
+ */
83
+ function addHolder(wrapped) {
84
+ const claim = claims.get(wrapped);
85
+ if (claim) {
86
+ claim.holders += 1;
87
+ }
88
+ }
89
+ /**
90
+ * The cached view of `storage`, memoized per instance, with one holder claim taken for
91
+ * this caller — release it with {@link disposeStorageCache} when this caller's scope
92
+ * ends. `label` names the store in the shared pool's `stats()` (e.g. `control`, or the
93
+ * strand id); on a memo hit the first caller's label sticks, since a second cache over
94
+ * one backend is the failure that matters and a stale pool label is not.
95
+ */
96
+ export function wrapStorageWithCache(storage, label) {
97
+ if (storage instanceof MemoryRawStorage) {
98
+ return storage;
99
+ }
100
+ if (storage instanceof CachedRawStorage) {
101
+ // Already the wrapper this module handed out: cadre-core wraps at its own seams
102
+ // and passes the wrapper down to `composeStrand`, which wraps again. Count the
103
+ // new holder rather than stacking a second cache over one backend.
104
+ // NOTE: a RETIRED wrapper (last holder already released) is also returned as-is,
105
+ // uncounted — the caller gets a dead cache with no error. No call site can reach
106
+ // that today: both retire sites drop their reference in the same block that
107
+ // releases. If a caller ever holds a wrapper across its own release, make this
108
+ // branch throw on an absent claim rather than hand back the corpse.
109
+ addHolder(storage);
110
+ return storage;
111
+ }
112
+ const memoized = wraps.get(storage);
113
+ if (memoized) {
114
+ addHolder(memoized);
115
+ return memoized;
116
+ }
117
+ const wrapped = new CachedRawStorage(storage, undefined, label);
118
+ wraps.set(storage, wrapped);
119
+ claims.set(wrapped, { inner: storage, holders: 1 });
120
+ return wrapped;
121
+ }
122
+ /**
123
+ * Release this caller's claim on the cache wrapper {@link wrapStorageWithCache} returned.
124
+ * The wrapper is emptied, unregistered from the shared pool, and dropped from the memo
125
+ * only when the LAST holder releases — so a scope stopping never retires a cache another
126
+ * live scope is still reading through, and a still-held wrapper is never replaced by a
127
+ * competing second cache over the same backend.
128
+ *
129
+ * A wrapper with no live claim is left alone: that covers both a cache the embedder built
130
+ * itself (theirs to dispose) and a late second dispose of an already-retired wrapper,
131
+ * whose successor in the memo must not be dropped.
132
+ *
133
+ * No-op for anything this module returned unwrapped (e.g. `MemoryRawStorage`), so callers
134
+ * need no `instanceof` test. Only the wrapper is released: the inner store the embedder
135
+ * returned is never closed — that handle stays the embedder's to manage.
136
+ */
137
+ export async function disposeStorageCache(storage) {
138
+ if (!(storage instanceof CachedRawStorage)) {
139
+ return;
140
+ }
141
+ const claim = claims.get(storage);
142
+ if (!claim) {
143
+ return;
144
+ }
145
+ // Decrement, and on zero retire the bookkeeping, all in this SYNCHRONOUS prefix —
146
+ // before the await below. A wrap of the same inner instance interleaved between the
147
+ // two would otherwise find a memo entry pointing at a dying cache.
148
+ claim.holders -= 1;
149
+ if (claim.holders > 0) {
150
+ return;
151
+ }
152
+ claims.delete(storage);
153
+ wraps.delete(claim.inner);
154
+ await storage.dispose();
155
+ }
156
+ //# sourceMappingURL=cached-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-storage.js","sourceRoot":"","sources":["../src/cached-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH,6EAA6E;AAC7E,gFAAgF;AAChF,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAoB,MAAM,uBAAuB,CAAC;AAE7F,MAAM,KAAK,GAAG,IAAI,OAAO,EAAiC,CAAC;AAmB3D,MAAM,MAAM,GAAG,IAAI,OAAO,EAAgC,CAAC;AAE3D;;;;GAIG;AACH,SAAS,SAAS,CAAC,OAAyB;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACpB,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAoB,EAAE,KAAa;IACvE,IAAI,OAAO,YAAY,gBAAgB,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,IAAI,OAAO,YAAY,gBAAgB,EAAE,CAAC;QACzC,gFAAgF;QAChF,+EAA+E;QAC/E,mEAAmE;QACnE,iFAAiF;QACjF,iFAAiF;QACjF,4EAA4E;QAC5E,+EAA+E;QAC/E,oEAAoE;QACpE,SAAS,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,QAAQ,EAAE,CAAC;QACd,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpB,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAChE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAoB;IAC7D,IAAI,CAAC,CAAC,OAAO,YAAY,gBAAgB,CAAC,EAAE,CAAC;QAC5C,OAAO;IACR,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO;IACR,CAAC;IACD,kFAAkF;IAClF,oFAAoF;IACpF,mEAAmE;IACnE,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACnB,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;IACR,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvB,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;AACzB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
2
2
  import type { Libp2p } from '@libp2p/interface';
3
- import type { IRawStorage } from '@optimystic/db-p2p';
4
- import type { StrandConnectionOptions, SereusPluginResult } from './types.js';
3
+ import type { IRawStorage, NodeOptions } from '@optimystic/db-p2p';
4
+ import type { StrandConnectionOptions, SereusPluginResult, StrandTransactor } from './types.js';
5
5
  /** Registration shape shared by the crypto and optimystic plugin results. */
6
6
  interface PluginRegistrations {
7
7
  vtables?: VTablePluginInfo[];
@@ -29,13 +29,26 @@ export interface CreateNodeContext {
29
29
  * default of 10, which gates writes on any smaller party.
30
30
  */
31
31
  clusterSize: number;
32
+ /**
33
+ * The strand cluster policy, resolved here so no platform has to remember it.
34
+ * Pass it to `createLibp2pNode` verbatim.
35
+ *
36
+ * Omitting it does NOT fall back to something equivalent: Optimystic then computes
37
+ * its read-repair corroboration floor against `clusterSize` instead of against
38
+ * {@link STRAND_CLUSTER_POLICY}'s `assumedClusterSize`, which is a stricter demand
39
+ * than a two-machine strand can meet — so repair of a damaged block gives up where
40
+ * it would otherwise succeed. That is the same defect class that took the *control*
41
+ * network's e2e suite down before `42cd12c`; see `cluster-size.ts` for the reasoning
42
+ * written out in full.
43
+ */
44
+ clusterPolicy: NonNullable<NodeOptions['clusterPolicy']>;
32
45
  /** Resolved persistent storage to back the node, if any. */
33
46
  storage?: IRawStorage;
34
47
  }
35
48
  /** Context passed to the platform's storage-resolution strategy. */
36
49
  export interface ResolveStorageContext {
37
50
  strandId: string;
38
- resolvedTransactor: string;
51
+ resolvedTransactor: StrandTransactor;
39
52
  /** The storage the caller passed in `options.storage`, if any. */
40
53
  requestedStorage?: IRawStorage;
41
54
  }
@@ -1,7 +1,8 @@
1
1
  import debug from 'debug';
2
2
  import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
3
3
  import { STRAND_SCHEMA } from './strand-schema.js';
4
- import { resolveStrandClusterSize } from './cluster-size.js';
4
+ import { resolveStrandClusterSize, STRAND_CLUSTER_POLICY } from './cluster-size.js';
5
+ import { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
5
6
  const log = debug('sereus:plugin:strand');
6
7
  const timing = debug('sereus:plugin:strand:timing');
7
8
  /**
@@ -33,53 +34,67 @@ export function applyRegistrations(db, result) {
33
34
  * vtab, hydrate the catalog, then apply the sApp schema.
34
35
  */
35
36
  export async function composeStrand(db, options, platform) {
36
- const { strandId, bootstrapNodes = [], schema, port = 0, enableCache = true, fretProfile = 'edge', mode, } = options;
37
+ const { strandId, bootstrapNodes = [], schema, port = 0, enableCache = true, fretProfile = 'edge', transactor = 'network', } = options;
37
38
  // Resolve (and validate) up front so a nonsense value fails before any plugin
38
39
  // registration or node creation has happened.
39
40
  const clusterSize = resolveStrandClusterSize(options.clusterSize);
40
- // Resolve the transactor. `mode` is the public knob: bootstrap -> local,
41
- // networked -> network. The legacy `transactor` override (used by unit
42
- // tests with `'test'`) only applies when `mode` is unspecified.
43
- let resolvedTransactor;
44
- if (mode !== undefined) {
45
- resolvedTransactor = mode === 'bootstrap' ? 'local' : 'network';
46
- }
47
- else if (options.transactor !== undefined) {
48
- resolvedTransactor = options.transactor;
49
- }
50
- else {
51
- resolvedTransactor = 'network';
52
- }
41
+ // The storage engine every write and read below goes through — see
42
+ // `StrandConnectionOptions.transactor`. Named separately from the option so
43
+ // the default is applied exactly once and reported on the result.
44
+ const resolvedTransactor = transactor;
53
45
  const networkName = `strand-${strandId}`;
54
- log('Connecting to strand %s (network: %s, mode=%s, transactor=%s)', strandId, networkName, mode ?? '(default)', resolvedTransactor);
46
+ log('Connecting to strand %s (network: %s, transactor=%s)', strandId, networkName, resolvedTransactor);
55
47
  // Resolve storage. Node passes `options.storage` through; the browser
56
- // defaults to IndexedDB. The resolved instance feeds BOTH the plugin's
57
- // bootstrap `rawStorageFactory` and node creation, so it must be decided up
58
- // front (before pluginConfig is built).
59
- const storage = platform.resolveStorage
48
+ // defaults to IndexedDB. The resolved instance feeds BOTH the local
49
+ // transactor's `rawStorageFactory` and node creation, so it must be decided
50
+ // up front (before pluginConfig is built). Wrapped in the write-through
51
+ // raw-storage cache (cached-storage.ts) — idempotent, so a caller that
52
+ // already wrapped (cadre-core's seams) passes through unchanged, with this
53
+ // composition counted as one more holder of that same cache.
54
+ const resolvedStorage = platform.resolveStorage
60
55
  ? await platform.resolveStorage({ strandId, resolvedTransactor, requestedStorage: options.storage })
61
56
  : options.storage;
62
- // 1. Register crypto plugin (digest, sign, verify, etc.)
63
- await platform.registerCrypto(db);
64
- log('Registered crypto plugin');
65
- // 2. Register optimystic plugin with transactor defaults. In local mode with
66
- // persistent storage, hand the same instance to the plugin so the local
67
- // transactor persists DML on the host backend (not in-memory).
68
- const pluginConfig = {
69
- default_transactor: resolvedTransactor,
70
- default_key_network: 'libp2p',
71
- default_network_name: networkName,
72
- enable_cache: enableCache,
57
+ const storage = resolvedStorage ? wrapStorageWithCache(resolvedStorage, strandId) : resolvedStorage;
58
+ /**
59
+ * Release THIS composition's claim on the storage cache — the matching half of the
60
+ * wrap above, which took one. `wrapStorageWithCache` counts holders, so this is
61
+ * unconditionally correct whoever created the wrapper: the cache stays live while
62
+ * cadre-core (or any other seam over the same store) still holds a claim, and is
63
+ * emptied and unregistered from the shared cache pool when the last one goes. Holding
64
+ * a claim past the strand's life is not free — the registration claims the backing
65
+ * store in the pool, and a claim outliving its strand makes the next connect over that
66
+ * same directory throw.
67
+ *
68
+ * Latched, so a caller that calls `shutdown()` twice releases one claim, not two — a
69
+ * second release would consume another scope's claim.
70
+ */
71
+ let storageCacheReleased = false;
72
+ const releaseStorageCache = async () => {
73
+ if (storageCacheReleased || !storage) {
74
+ return;
75
+ }
76
+ storageCacheReleased = true;
77
+ await disposeStorageCache(storage);
73
78
  };
74
- if (resolvedTransactor === 'local' && storage) {
75
- pluginConfig.rawStorageFactory = () => storage;
79
+ // 1-2. Register the crypto and optimystic plugins. Guarded on its own, because the
80
+ // wrap above has ALREADY taken a holder claim: a registration that throws must release
81
+ // it, or the claim — and the backing store's registration in the shared cache pool —
82
+ // outlives the failed connect for the process lifetime, and no other scope can retire
83
+ // the cache. Separate from the larger try below, which has a collection factory and
84
+ // possibly a node to tear down as well; neither exists yet here.
85
+ let pluginResult;
86
+ try {
87
+ pluginResult = await registerStrandPlugins(db, platform, {
88
+ resolvedTransactor,
89
+ networkName,
90
+ enableCache,
91
+ storage,
92
+ });
93
+ }
94
+ catch (err) {
95
+ await releaseStorageCache();
96
+ throw err;
76
97
  }
77
- // The plugin's published signature is `Record<string, SqlValue>` but it
78
- // also reads `rawStorageFactory` (a function reference) from the same map.
79
- // Cast through unknown rather than widen the public type.
80
- const pluginResult = optimysticPlugin(db, pluginConfig);
81
- applyRegistrations(db, pluginResult);
82
- log('Registered optimystic vtables and functions');
83
98
  const { collectionFactory } = pluginResult;
84
99
  let createdNode = null;
85
100
  let hydrated;
@@ -98,7 +113,7 @@ export async function composeStrand(db, options, platform) {
98
113
  log('Using injected libp2p node');
99
114
  }
100
115
  else {
101
- const created = await platform.createNode({ networkName, bootstrapNodes, fretProfile, port, clusterSize, storage });
116
+ const created = await platform.createNode({ networkName, bootstrapNodes, fretProfile, port, clusterSize, clusterPolicy: STRAND_CLUSTER_POLICY, storage });
102
117
  createdNode = created;
103
118
  node = created;
104
119
  const repo = created.coordinatedRepo;
@@ -141,11 +156,11 @@ export async function composeStrand(db, options, platform) {
141
156
  // Manager/Member, invite/peer flows) is owned by the lifecycle ticket
142
157
  // `strand-membership-lifecycle-population`.
143
158
  log('Applying Strand membership schema for strand %s', strandId);
144
- await db.exec(`
145
- declare schema Strand {
146
- ${STRAND_SCHEMA}
147
- }
148
- apply schema Strand;
159
+ await db.exec(`
160
+ declare schema Strand {
161
+ ${STRAND_SCHEMA}
162
+ }
163
+ apply schema Strand;
149
164
  `);
150
165
  log('Strand membership schema applied');
151
166
  // 7. Apply the sApp schema, if provided.
@@ -155,11 +170,11 @@ export async function composeStrand(db, options, platform) {
155
170
  // here so it stays a one-location edit across Node, browser, and cadre-core.
156
171
  if (schema) {
157
172
  log('Applying sApp schema for strand %s', strandId);
158
- await db.exec(`
159
- declare schema App {
160
- ${schema}
161
- }
162
- apply schema App;
173
+ await db.exec(`
174
+ declare schema App {
175
+ ${schema}
176
+ }
177
+ apply schema App;
163
178
  `);
164
179
  log('sApp schema applied');
165
180
  }
@@ -170,6 +185,7 @@ export async function composeStrand(db, options, platform) {
170
185
  if (createdNode) {
171
186
  await createdNode.stop();
172
187
  }
188
+ await releaseStorageCache();
173
189
  throw err;
174
190
  }
175
191
  // 8. Return result with hydrate counts + shutdown handler. `shutdown` tears
@@ -185,14 +201,44 @@ export async function composeStrand(db, options, platform) {
185
201
  functions: [],
186
202
  collations: [],
187
203
  hydrated,
204
+ transactor: resolvedTransactor,
188
205
  async shutdown() {
189
206
  log('Shutting down strand connection %s', strandId);
190
207
  await collectionFactory.shutdown();
191
208
  if (createdNode) {
192
209
  await createdNode.stop();
193
210
  }
211
+ await releaseStorageCache();
194
212
  log('Strand connection %s shut down', strandId);
195
213
  },
196
214
  };
197
215
  }
216
+ /**
217
+ * Steps 1-2 of {@link composeStrand}: register the crypto plugin, then the optimystic
218
+ * plugin carrying this strand's transactor defaults. Split out so the composition can
219
+ * guard the pair against the storage-cache claim it has already taken.
220
+ */
221
+ async function registerStrandPlugins(db, platform, strand) {
222
+ await platform.registerCrypto(db);
223
+ log('Registered crypto plugin');
224
+ // On the local transactor with persistent storage, hand the same instance to the
225
+ // plugin so DML persists on the host backend (not in-memory).
226
+ const pluginConfig = {
227
+ default_transactor: strand.resolvedTransactor,
228
+ default_key_network: 'libp2p',
229
+ default_network_name: strand.networkName,
230
+ enable_cache: strand.enableCache,
231
+ };
232
+ if (strand.resolvedTransactor === 'local' && strand.storage) {
233
+ const storage = strand.storage;
234
+ pluginConfig.rawStorageFactory = () => storage;
235
+ }
236
+ // The plugin's published signature is `Record<string, SqlValue>` but it also reads
237
+ // `rawStorageFactory` (a function reference) from the same map. Cast through unknown
238
+ // rather than widen the public type.
239
+ const pluginResult = optimysticPlugin(db, pluginConfig);
240
+ applyRegistrations(db, pluginResult);
241
+ log('Registered optimystic vtables and functions');
242
+ return pluginResult;
243
+ }
198
244
  //# sourceMappingURL=compose-strand.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose-strand.js","sourceRoot":"","sources":["../src/compose-strand.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compose-strand.js","sourceRoot":"","sources":["../src/compose-strand.ts"],"names":[],"mappings":"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"}
@@ -30,7 +30,7 @@ export async function connectToStrandBrowser(db, options) {
30
30
  const dbHandle = await openOptimysticWebDb(`sereus-strand-${strandId}`);
31
31
  return new IndexedDBRawStorage(dbHandle);
32
32
  },
33
- async createNode({ networkName, bootstrapNodes, fretProfile, clusterSize, storage }) {
33
+ async createNode({ networkName, bootstrapNodes, fretProfile, clusterSize, clusterPolicy, storage }) {
34
34
  return createLibp2pNode({
35
35
  transports: [webSockets(), circuitRelayTransport()],
36
36
  listenAddrs: [],
@@ -38,6 +38,7 @@ export async function connectToStrandBrowser(db, options) {
38
38
  networkName,
39
39
  fretProfile,
40
40
  clusterSize,
41
+ clusterPolicy,
41
42
  ...(storage && { storage }),
42
43
  });
43
44
  },
@@ -1 +1 @@
1
- {"version":3,"file":"connect-browser.js","sourceRoot":"","sources":["../src/connect-browser.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,0CAA0C,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAG1F,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASxE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAAY,EACZ,OAAgC;IAEhC,OAAO,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE;QACjC,cAAc,CAAC,QAAQ;YACtB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAkC,CAAC,CAAC;QAC3F,CAAC;QACD,KAAK,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAE;YACtE,IAAI,gBAAgB;gBAAE,OAAO,gBAAgB,CAAC;YAC9C,iEAAiE;YACjE,8CAA8C;YAC9C,IAAI,kBAAkB,KAAK,MAAM;gBAAE,OAAO,SAAS,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;YACxE,OAAO,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE;YAClF,OAAO,gBAAgB,CAAC;gBACvB,UAAU,EAAE,CAAC,UAAU,EAAE,EAAE,qBAAqB,EAAE,CAAC;gBACnD,WAAW,EAAE,EAAE;gBACf,cAAc;gBACd,WAAW;gBACX,WAAW;gBACX,WAAW;gBACX,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;aAC3B,CAAoB,CAAC;QACvB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"connect-browser.js","sourceRoot":"","sources":["../src/connect-browser.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,0CAA0C,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAG1F,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASxE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAAY,EACZ,OAAgC;IAEhC,OAAO,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE;QACjC,cAAc,CAAC,QAAQ;YACtB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAkC,CAAC,CAAC;QAC3F,CAAC;QACD,KAAK,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAE;YACtE,IAAI,gBAAgB;gBAAE,OAAO,gBAAgB,CAAC;YAC9C,iEAAiE;YACjE,8CAA8C;YAC9C,IAAI,kBAAkB,KAAK,MAAM;gBAAE,OAAO,SAAS,CAAC;YACpD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;YACxE,OAAO,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE;YACjG,OAAO,gBAAgB,CAAC;gBACvB,UAAU,EAAE,CAAC,UAAU,EAAE,EAAE,qBAAqB,EAAE,CAAC;gBACnD,WAAW,EAAE,EAAE;gBACf,cAAc;gBACd,WAAW;gBACX,WAAW;gBACX,WAAW;gBACX,aAAa;gBACb,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;aAC3B,CAAoB,CAAC;QACvB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
package/dist/connect.js CHANGED
@@ -13,7 +13,7 @@ export async function connectToStrand(db, options) {
13
13
  async registerCrypto(database) {
14
14
  await registerPlugin(database, cryptoPlugin);
15
15
  },
16
- async createNode({ networkName, bootstrapNodes, fretProfile, port, clusterSize, storage }) {
16
+ async createNode({ networkName, bootstrapNodes, fretProfile, port, clusterSize, clusterPolicy, storage }) {
17
17
  // Dynamically import to keep the module cross-platform friendly: this
18
18
  // pulls the Node-only `@optimystic/db-p2p` (TCP) entry, and is only
19
19
  // reached when actually creating a node (never for injected nodes).
@@ -24,6 +24,7 @@ export async function connectToStrand(db, options) {
24
24
  networkName,
25
25
  fretProfile,
26
26
  clusterSize,
27
+ clusterPolicy,
27
28
  ...(storage && { storage }),
28
29
  });
29
30
  },
@@ -1 +1 @@
1
- {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,YAAY,MAAM,0CAA0C,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,EAAY,EACZ,OAAgC;IAEhC,OAAO,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE;QACjC,KAAK,CAAC,cAAc,CAAC,QAAQ;YAC5B,MAAM,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE;YACxF,sEAAsE;YACtE,oEAAoE;YACpE,oEAAoE;YACpE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAChE,OAAO,gBAAgB,CAAC;gBACvB,IAAI;gBACJ,cAAc;gBACd,WAAW;gBACX,WAAW;gBACX,WAAW;gBACX,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;aAC3B,CAAoB,CAAC;QACvB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,YAAY,MAAM,0CAA0C,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,EAAY,EACZ,OAAgC;IAEhC,OAAO,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE;QACjC,KAAK,CAAC,cAAc,CAAC,QAAQ;YAC5B,MAAM,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE;YACvG,sEAAsE;YACtE,oEAAoE;YACpE,oEAAoE;YACpE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAChE,OAAO,gBAAgB,CAAC;gBACvB,IAAI;gBACJ,cAAc;gBACd,WAAW;gBACX,WAAW;gBACX,WAAW;gBACX,aAAa;gBACb,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;aAC3B,CAAoB,CAAC;QACvB,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { connectToStrand } from './connect.js';
2
2
  export { MIN_CLUSTER_SIZE, CONTROL_REPLICATION_BREADTH, CONTROL_CLUSTER_POLICY, DEFAULT_STRAND_CLUSTER_SIZE, STRAND_CLUSTER_POLICY, resolveStrandClusterSize } from './cluster-size.js';
3
- export type { StrandConnectionOptions, SereusPluginResult } from './types.js';
3
+ export { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
4
+ export type { StrandConnectionOptions, SereusPluginResult, StrandTransactor } from './types.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { connectToStrand } from './connect.js';
2
2
  export { MIN_CLUSTER_SIZE, CONTROL_REPLICATION_BREADTH, CONTROL_CLUSTER_POLICY, DEFAULT_STRAND_CLUSTER_SIZE, STRAND_CLUSTER_POLICY, resolveStrandClusterSize } from './cluster-size.js';
3
+ export { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACN,gBAAgB,EAChB,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACxB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACN,gBAAgB,EAChB,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}