@serfab/quereus-plugin-sereus 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/README.md CHANGED
@@ -274,6 +274,10 @@ on shutdown.
274
274
 
275
275
  The result's `onCommitDurability(listener)` is called after every commit that wrote something, before the commit returns, with who holds what it wrote; `onCommitFullyHeld(listener)` is called once when a commit reported `followed` below full replication becomes held by its whole cohort. Both pass through from `@optimystic/quereus-plugin-optimystic`, whose README explains the report ([A commit's durability](https://github.com/gotchoices/optimystic/blob/main/packages/quereus-plugin-optimystic/README.md#a-commits-durability--pluginoncommitdurabilitylistener)); the `SqlCommitDurability` and `CommitFullyHeld` types are re-exported here. A cadre app reads the same reports through `@serfab/cadre-core` ([strands.md → A commit's durability](../../docs/strands.md#a-commits-durability)).
276
276
 
277
+ ### Offline mode
278
+
279
+ With `offline` given, the result's `offline` is the queue's controller (`enter()`, `exit()`, `whenDrained()`, `onStatus(listener)`, `entries()`, …), passed through from `@optimystic/quereus-plugin-optimystic` ([Offline mode](https://github.com/gotchoices/optimystic/blob/main/packages/quereus-plugin-optimystic/README.md#offline-mode--pluginoffline)); `offlineControllerOf(db)` finds it from the `Database`, and the status, state and error types are re-exported here. The `Strand` tables are always acknowledged by the network, and an `App` schema apply that queued transactions refuse is retried once they drain rather than failing the connect: the result's `appSchema` settles with how it ended, and `appSchemaDeferredBy` names the transactions that refused it. Both, and where the queue is saved, are in [strands.md → Offline mode](../../docs/strands.md#offline-mode).
280
+
277
281
  ### Founding a strand: `createEmptyCollections()`
278
282
 
279
283
  A table nobody has written has no collection, so no single machine can show it empty: every read of it asks the other members, and fails while they are offline. The result's `createEmptyCollections(except?)` commits an empty collection for every table the `Strand` and `App` schemas declare that has none yet, and resolves to how many it committed. `except` lists qualified tables (`'Strand.Header'`) the caller is about to write: their inserts create their collections, so every tree of those tables is left out, and a name matching no declared table throws. The founder calls it once, before its founding write, while no other machine can have written to the strand; cadre-core's founder bootstrap does this, leaving out the tables its founding transaction writes ([strands.md → Joining](../../docs/strands.md#joining-no-writes-before-the-first-sync)). Call it only while nothing else writes through the `Database`: it commits whatever is staged in those collections outside any SQL transaction.
@@ -301,6 +305,7 @@ The same step is exported on its own as `createEmptyCollections(db, bridge, sche
301
305
  | `coordinatedRepo` | IRepo | — | Required when `libp2pNode` is provided |
302
306
  | `transactor` | `'local' \| 'network' \| 'test'` | `'network'` | Storage engine transactions go through. `'network'`: the strand's libp2p cohort, which a lone node coordinates for itself — the only value an application should use. `'local'`: this process's raw storage directly, no peers consulted (in-process tests and tooling). `'test'`: Optimystic's in-memory fake; no libp2p node is created unless one is injected (unit tests) |
303
307
  | `storage` | IRawStorage | — | Persistent raw storage. Borrowed — not closed on `shutdown()` |
308
+ | `offline` | `StrandOfflineOptions` | — | Turn on Optimystic's offline queue: the plugin's `offline` block less `journalLabel`, which is derived from the strand id. Programmatic only; no plugin setting carries it. See [Offline mode](#offline-mode) |
304
309
 
305
310
  ### Plugin settings (plugin-loader / Quoomb)
306
311
 
@@ -0,0 +1,45 @@
1
+ import { type Database } from '@quereus/quereus';
2
+ import type { StrandOfflineController, AppSchemaOutcome } from './types.js';
3
+ /**
4
+ * Step 7 of `composeStrand`: declare the sApp's schema as `App` and apply it — a
5
+ * declarative diff against the catalog, so re-applying a schema that is already in place
6
+ * emits nothing. The one site that applies an `App` schema: `composeStrand` calls it at
7
+ * bring-up (through {@link applyAppSchemaAfterQueue}, whose deferred retry shares its
8
+ * declaration), and cadre-core's `StrandDatabase.attachAppSchema` calls it on a live storage
9
+ * replica when an app claims the strand. The database must already be composed (optimystic
10
+ * set as the default vtab), or the tables land in memory instead of the strand.
11
+ *
12
+ * Refuses a schema holding an item the Quereus parser skipped: it keeps any item whose leading
13
+ * keyword it does not model (`create unique index …`, a misspelled `tabel`, `domain`) as an
14
+ * opaque placeholder that apply ignores, so the app would otherwise run without that item.
15
+ *
16
+ * Refuses a schema holding a `seed` item: the apply runs on every node of the strand at every
17
+ * connect, so seed inserts would put a network write on each bring-up and two nodes inserting
18
+ * the same key at once would collide. Rows an app needs at birth are the founding app's to write.
19
+ */
20
+ export declare function applyAppSchema(db: Database, schema: string): Promise<void>;
21
+ /** What {@link applyAppSchemaAfterQueue} reports, in the shape `SereusPluginResult` carries it. */
22
+ export interface AppSchemaApply {
23
+ appSchema: Promise<AppSchemaOutcome>;
24
+ appSchemaDeferredBy?: readonly string[];
25
+ }
26
+ /**
27
+ * Step 7 of `composeStrand`: {@link applyAppSchema}, except that an apply refused by queued
28
+ * offline transactions (`QueuedEntriesBlockSchemaChangeError` on the error's `cause` chain) does not
29
+ * throw. Quereus unwinds the refused apply, so `db` keeps the `App` schema it had; the apply is
30
+ * retried in the background each time `queue` drains, for as long as the queue is what refuses it,
31
+ * and `appSchema` settles with how that ended. Any other error from the first apply is thrown.
32
+ *
33
+ * Waiting for the queue before returning was rejected: offline, that withholds the strand from the
34
+ * app right after an app update, which is when offline mode matters. So was refusing the connect:
35
+ * the queue can drain only while the composition is alive.
36
+ *
37
+ * `released` is aborted when the composition shuts down; a retry not yet landed then settles
38
+ * `cancelled`.
39
+ *
40
+ * A retry runs while the app is using `db`, so it is one atomic batch that refuses to run inside a
41
+ * transaction another caller holds open, and waits for that transaction to end: run inside it, the
42
+ * apply would be committed or rolled back with the app's rows, and a rollback leaves the index it
43
+ * built holding entries for rows that are gone.
44
+ */
45
+ export declare function applyAppSchemaAfterQueue(db: Database, schema: string, queue: Pick<StrandOfflineController, 'whenDrained'>, released: AbortSignal): Promise<AppSchemaApply>;
@@ -0,0 +1,147 @@
1
+ import debug from 'debug';
2
+ import { TransactionActiveError } from '@quereus/quereus';
3
+ import { QueuedEntriesBlockSchemaChangeError, QueueDisposedError } from '@optimystic/quereus-plugin-optimystic/plugin';
4
+ const log = debug('sereus:plugin:strand');
5
+ /**
6
+ * Step 7 of `composeStrand`: declare the sApp's schema as `App` and apply it — a
7
+ * declarative diff against the catalog, so re-applying a schema that is already in place
8
+ * emits nothing. The one site that applies an `App` schema: `composeStrand` calls it at
9
+ * bring-up (through {@link applyAppSchemaAfterQueue}, whose deferred retry shares its
10
+ * declaration), and cadre-core's `StrandDatabase.attachAppSchema` calls it on a live storage
11
+ * replica when an app claims the strand. The database must already be composed (optimystic
12
+ * set as the default vtab), or the tables land in memory instead of the strand.
13
+ *
14
+ * Refuses a schema holding an item the Quereus parser skipped: it keeps any item whose leading
15
+ * keyword it does not model (`create unique index …`, a misspelled `tabel`, `domain`) as an
16
+ * opaque placeholder that apply ignores, so the app would otherwise run without that item.
17
+ *
18
+ * Refuses a schema holding a `seed` item: the apply runs on every node of the strand at every
19
+ * connect, so seed inserts would put a network write on each bring-up and two nodes inserting
20
+ * the same key at once would collide. Rows an app needs at birth are the founding app's to write.
21
+ */
22
+ export async function applyAppSchema(db, schema) {
23
+ await declareAppSchema(db, schema);
24
+ await db.exec('apply schema App;');
25
+ }
26
+ /** Declare `schema` as `App`, refusing the items {@link applyAppSchema} refuses. Writes nothing to the strand. */
27
+ async function declareAppSchema(db, schema) {
28
+ await db.exec(`
29
+ declare schema App {
30
+ ${schema}
31
+ }
32
+ `);
33
+ assertNoIgnoredItems(db);
34
+ assertNoSeedItems(db);
35
+ }
36
+ /**
37
+ * Step 7 of `composeStrand`: {@link applyAppSchema}, except that an apply refused by queued
38
+ * offline transactions (`QueuedEntriesBlockSchemaChangeError` on the error's `cause` chain) does not
39
+ * throw. Quereus unwinds the refused apply, so `db` keeps the `App` schema it had; the apply is
40
+ * retried in the background each time `queue` drains, for as long as the queue is what refuses it,
41
+ * and `appSchema` settles with how that ended. Any other error from the first apply is thrown.
42
+ *
43
+ * Waiting for the queue before returning was rejected: offline, that withholds the strand from the
44
+ * app right after an app update, which is when offline mode matters. So was refusing the connect:
45
+ * the queue can drain only while the composition is alive.
46
+ *
47
+ * `released` is aborted when the composition shuts down; a retry not yet landed then settles
48
+ * `cancelled`.
49
+ *
50
+ * A retry runs while the app is using `db`, so it is one atomic batch that refuses to run inside a
51
+ * transaction another caller holds open, and waits for that transaction to end: run inside it, the
52
+ * apply would be committed or rolled back with the app's rows, and a rollback leaves the index it
53
+ * built holding entries for rows that are gone.
54
+ */
55
+ export async function applyAppSchemaAfterQueue(db, schema, queue, released) {
56
+ try {
57
+ await applyAppSchema(db, schema);
58
+ log('sApp schema applied');
59
+ return { appSchema: Promise.resolve({ state: 'applied' }) };
60
+ }
61
+ catch (err) {
62
+ const refusal = queueRefusalIn(err);
63
+ if (!refusal) {
64
+ throw err;
65
+ }
66
+ log('sApp schema apply refused by queued transactions %o; retrying once they settle', refusal.transactionIds);
67
+ return {
68
+ appSchema: retryAppSchemaAfterDrain(db, schema, queue, released),
69
+ appSchemaDeferredBy: refusal.transactionIds,
70
+ };
71
+ }
72
+ }
73
+ async function retryAppSchemaAfterDrain(db, schema, queue, released) {
74
+ // NOTE: this loops without a pause when `whenDrained` resolves at once while the refusal stands
75
+ // (a segment left sealed with nothing queued, say). Upstream counts a removal still in progress
76
+ // as undrained, so no such state is known; if one shows up as a busy loop, back off between tries.
77
+ for (;;) {
78
+ try {
79
+ await queue.whenDrained();
80
+ if (released.aborted) {
81
+ return { state: 'cancelled' };
82
+ }
83
+ await declareAppSchema(db, schema);
84
+ await db.exec('apply schema App;', undefined, { transaction: true });
85
+ log('Deferred sApp schema applied');
86
+ return { state: 'applied', deferred: true };
87
+ }
88
+ catch (err) {
89
+ if (released.aborted || err instanceof QueueDisposedError) {
90
+ log('Deferred sApp schema apply cancelled by shutdown');
91
+ return { state: 'cancelled' };
92
+ }
93
+ if (err instanceof TransactionActiveError) {
94
+ log('Deferred sApp schema apply waiting for an open transaction to end');
95
+ await abortableDelay(OPEN_TRANSACTION_RETRY_MS, released);
96
+ continue;
97
+ }
98
+ const refusal = queueRefusalIn(err);
99
+ if (!refusal) {
100
+ log('Deferred sApp schema apply failed: %O', err);
101
+ return { state: 'failed', error: err };
102
+ }
103
+ log('sApp schema apply refused again by queued transactions %o', refusal.transactionIds);
104
+ }
105
+ }
106
+ }
107
+ /** The offline queue's refusal `err` is or wraps: Quereus rewraps it out of `apply schema`, keeping it as `cause`. */
108
+ function queueRefusalIn(err) {
109
+ for (let at = err; at instanceof Error; at = at.cause) {
110
+ if (at instanceof QueuedEntriesBlockSchemaChangeError) {
111
+ return at;
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+ /** How long a deferred sApp schema apply waits before trying again while another caller's transaction is open. */
117
+ const OPEN_TRANSACTION_RETRY_MS = 250;
118
+ /** Resolves after `ms`, or as soon as `signal` aborts. */
119
+ function abortableDelay(ms, signal) {
120
+ return new Promise(resolve => {
121
+ const done = () => {
122
+ clearTimeout(timer);
123
+ signal.removeEventListener('abort', done);
124
+ resolve();
125
+ };
126
+ const timer = setTimeout(done, ms);
127
+ signal.addEventListener('abort', done, { once: true });
128
+ });
129
+ }
130
+ function declaredAppItems(db) {
131
+ return db.declaredSchemaManager.getDeclaredSchema('App')?.items ?? [];
132
+ }
133
+ function assertNoIgnoredItems(db) {
134
+ const ignored = declaredAppItems(db).filter(item => item.type === 'declareIgnored').length;
135
+ if (ignored > 0) {
136
+ // Only a count: Quereus leaves an ignored item's source text empty.
137
+ throw new Error(`sApp schema has ${ignored} item(s) the parser does not recognize (a \`create …\` prefix or a misspelled item keyword such as \`tabel\`); items are \`table\`, \`index\`, \`unique index\`, \`view\`, \`materialized view\` and \`assertion\``);
138
+ }
139
+ }
140
+ function assertNoSeedItems(db) {
141
+ // Quereus refuses a second seed for one table at declare, so these names are distinct.
142
+ const tables = declaredAppItems(db).flatMap(item => item.type === 'declaredSeed' ? [item.tableName] : []);
143
+ if (tables.length > 0) {
144
+ throw new Error(`sApp schema has seed item(s) for table(s) ${tables.join(', ')}; Sereus does not apply seed rows to a strand — insert the rows an app needs at birth from the app when it founds the strand`);
145
+ }
146
+ }
147
+ //# sourceMappingURL=app-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-schema.js","sourceRoot":"","sources":["../src/app-schema.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,13 @@
1
1
  import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
2
2
  import type { Libp2p } from '@libp2p/interface';
3
- import type { IRawStorage, NodeOptions } from '@optimystic/db-p2p';
4
- import type { StrandConnectionOptions, SereusPluginResult, StrandTransactor } from './types.js';
3
+ import type { IKVStore, IRawStorage, NodeOptions } from '@optimystic/db-p2p';
4
+ import type { StrandConnectionOptions, SereusPluginResult, StrandTransactor, StrandOfflineController } from './types.js';
5
+ /**
6
+ * The offline controller of the live strand composition over `db`, or `undefined` when it was
7
+ * composed without `offline`, has shut down, or `db` is no strand composition. For Sereus's own
8
+ * writers, which are handed a bare `Database` and must see whether its queue holds transactions.
9
+ */
10
+ export declare function offlineControllerOf(db: Database): StrandOfflineController | undefined;
5
11
  /** Registration shape shared by the crypto and optimystic plugin results. */
6
12
  interface PluginRegistrations {
7
13
  vtables?: VTablePluginInfo[];
@@ -71,6 +77,13 @@ export interface StrandPlatform {
71
77
  * default to IndexedDB so a reload survives.
72
78
  */
73
79
  resolveStorage?(ctx: ResolveStorageContext): Promise<IRawStorage | undefined> | IRawStorage | undefined;
80
+ /**
81
+ * The store to journal offline transactions in when `options.offline` names no `journal`.
82
+ * Called after {@link resolveStorage}, only when `options.offline` is given. Optional — when
83
+ * omitted, or when it answers `undefined`, the queue lives in memory (Node). The browser answers
84
+ * a store over the IndexedDB database it defaulted the strand's storage to.
85
+ */
86
+ defaultOfflineJournal?(): IKVStore | undefined;
74
87
  /**
75
88
  * Create a libp2p node. Called only when no node is injected and the
76
89
  * transactor needs one. Node creates a TCP node via `@optimystic/db-p2p`;
@@ -90,21 +103,4 @@ export interface StrandPlatform {
90
103
  * vtab, hydrate the catalog, then apply the sApp schema.
91
104
  */
92
105
  export declare function composeStrand(db: Database, options: StrandConnectionOptions, platform: StrandPlatform): Promise<SereusPluginResult>;
93
- /**
94
- * Step 7 of {@link composeStrand}: declare the sApp's schema as `App` and apply it — a
95
- * declarative diff against the catalog, so re-applying a schema that is already in place
96
- * emits nothing. The one site that applies an `App` schema: `composeStrand` calls it at
97
- * bring-up, and cadre-core's `StrandDatabase.attachAppSchema` calls it on a live storage
98
- * replica when an app claims the strand. The database must already be composed (optimystic
99
- * set as the default vtab), or the tables land in memory instead of the strand.
100
- *
101
- * Refuses a schema holding an item the Quereus parser skipped: it keeps any item whose leading
102
- * keyword it does not model (`create unique index …`, a misspelled `tabel`, `domain`) as an
103
- * opaque placeholder that apply ignores, so the app would otherwise run without that item.
104
- *
105
- * Refuses a schema holding a `seed` item: the apply runs on every node of the strand at every
106
- * connect, so seed inserts would put a network write on each bring-up and two nodes inserting
107
- * the same key at once would collide. Rows an app needs at birth are the founding app's to write.
108
- */
109
- export declare function applyAppSchema(db: Database, schema: string): Promise<void>;
110
106
  export {};
@@ -1,5 +1,6 @@
1
1
  import debug from 'debug';
2
2
  import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
3
+ import { applyAppSchemaAfterQueue } from './app-schema.js';
3
4
  import { STRAND_SCHEMA } from './strand-schema.js';
4
5
  import { resolveStrandClusterSize, STRAND_CLUSTER_POLICY } from './cluster-size.js';
5
6
  import { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
@@ -8,18 +9,33 @@ const log = debug('sereus:plugin:strand');
8
9
  const timing = debug('sereus:plugin:strand:timing');
9
10
  /** The schemas whose tables a strand founder gives a collection: strand membership, then the sApp's. */
10
11
  const FOUNDED_SCHEMAS = ['Strand', 'App'];
12
+ /** The offline controller of each live strand composition composed with `offline`, by its `Database`. */
13
+ const offlineControllers = new WeakMap();
11
14
  /**
12
- * Release a composition's hold on the optimystic plugin. The transaction bridge goes first: its
13
- * durability follower subscribes to the node's `StorageRepo`, and cadre-core keeps a strand's node
14
- * across a database rebuild, so a bridge never disposed stays subscribed once per rebuild. Then the
15
- * collection factory: a node the composition created is stopped with it; an injected node is left
16
- * running, because it belongs to the caller — cadre-core keeps a strand's node up across a failed
17
- * database build and retries the build on it.
15
+ * The offline controller of the live strand composition over `db`, or `undefined` when it was
16
+ * composed without `offline`, has shut down, or `db` is no strand composition. For Sereus's own
17
+ * writers, which are handed a bare `Database` and must see whether its queue holds transactions.
18
+ */
19
+ export function offlineControllerOf(db) {
20
+ return offlineControllers.get(db);
21
+ }
22
+ /**
23
+ * Release a composition's hold on the optimystic plugin. The offline queue goes first: its drain
24
+ * keeps timers and may have a submission in flight, which reports its commit through the bridge's
25
+ * follower, so the bridge must outlive it (the plugin's own `dispose()` orders them the same way).
26
+ * A queue `hydrate` restored before a failed composition is stopped here too. Then the transaction
27
+ * bridge: its durability follower subscribes to the node's `StorageRepo`, and cadre-core keeps a
28
+ * strand's node across a database rebuild, so a bridge never disposed stays subscribed once per
29
+ * rebuild. Then the collection factory: a node the composition created is stopped with it; an
30
+ * injected node is left running, because it belongs to the caller — cadre-core keeps a strand's
31
+ * node up across a failed database build and retries the build on it.
18
32
  *
19
- * Disposes the bridge itself rather than calling the plugin's `dispose()`, which would release the
20
- * factory a second time on the created-node path (`shutdown()` ends with the same `dispose()`).
33
+ * Disposes the queue and bridge itself rather than calling the plugin's `dispose()`, which would
34
+ * release the factory a second time on the created-node path (`shutdown()` ends with the same
35
+ * `dispose()`).
21
36
  */
22
37
  async function releaseOptimysticPlugin(plugin, createdNode) {
38
+ await plugin.offline.dispose();
23
39
  // NOTE: the bridge's follower re-subscribes at the start of any transaction, so a write begun on
24
40
  // this `Database` after release (before its owner closes it) subscribes again and nothing
25
41
  // releases it. cadre-core stops its own writers before releasing; if an app write in flight at
@@ -119,6 +135,7 @@ export async function composeStrand(db, options, platform) {
119
135
  networkName,
120
136
  enableCache,
121
137
  storage,
138
+ offline: options.offline && strandOfflineConfig(strandId, options.offline, platform),
122
139
  });
123
140
  }
124
141
  catch (err) {
@@ -126,6 +143,13 @@ export async function composeStrand(db, options, platform) {
126
143
  throw err;
127
144
  }
128
145
  const { collectionFactory } = pluginResult;
146
+ const offline = options.offline && pluginResult.offline;
147
+ if (offline) {
148
+ offlineControllers.set(db, offline);
149
+ }
150
+ // Aborted by `shutdown`, so a deferred sApp schema apply still waiting settles `cancelled`.
151
+ const released = new AbortController();
152
+ let appSchemaApply;
129
153
  let createdNode = null;
130
154
  let hydrated;
131
155
  try {
@@ -194,6 +218,13 @@ export async function composeStrand(db, options, platform) {
194
218
  // nothing here writes membership rows. Founder bootstrap (Header, founding
195
219
  // Manager/Member, invite/peer flows) is owned by the lifecycle ticket
196
220
  // `strand-membership-lifecycle-population`.
221
+ //
222
+ // NOTE: unlike step 7, this apply is not deferred behind the offline queue. Every `Strand`
223
+ // table is tagged `"optimystic.offline" = 'network'`, so the only queued transactions that
224
+ // write one are network-acknowledged commits waiting behind app transactions, and the only
225
+ // change this schema has made since is a tag, which the queue does not refuse. If a
226
+ // later strand schema changes how a table's rows are stored (an index, a column), this apply
227
+ // can be refused by `QueuedEntriesBlockSchemaChangeError` the same way and needs step 7's path.
197
228
  log('Applying Strand membership schema for strand %s', strandId);
198
229
  await db.exec(`
199
230
  declare schema Strand {
@@ -218,14 +249,18 @@ export async function composeStrand(db, options, platform) {
218
249
  // differ: once one has run, any later step that fails leaves the schema partially
219
250
  // migrated instead of restored. No schema in this repo evolves that way, so this
220
251
  // is a note for whoever first ships a migration that does.
252
+ //
253
+ // The one exception: an apply refused by queued offline transactions does not fail the
254
+ // connect. The strand comes up on the `App` schema it had, and the apply is retried as the
255
+ // queue drains (see `applyAppSchemaAfterQueue`).
221
256
  if (schema) {
222
257
  log('Applying sApp schema for strand %s', strandId);
223
- await applyAppSchema(db, schema);
224
- log('sApp schema applied');
258
+ appSchemaApply = await applyAppSchemaAfterQueue(db, schema, pluginResult.offline, released.signal);
225
259
  }
226
260
  }
227
261
  catch (err) {
228
262
  // Clean up resources if setup fails after partial initialization.
263
+ offlineControllers.delete(db);
229
264
  await releaseOptimysticPlugin(pluginResult, createdNode);
230
265
  await releaseStorageCache();
231
266
  throw err;
@@ -242,8 +277,15 @@ export async function composeStrand(db, options, platform) {
242
277
  onCommitDurability: listener => pluginResult.onCommitDurability(listener),
243
278
  onCommitFullyHeld: listener => pluginResult.onCommitFullyHeld(listener),
244
279
  createEmptyCollections: except => createEmptyCollections(db, pluginResult.txnBridge, FOUNDED_SCHEMAS, except),
280
+ ...(offline && { offline }),
281
+ ...appSchemaApply,
245
282
  async shutdown() {
246
283
  log('Shutting down strand connection %s', strandId);
284
+ // NOTE: a deferred sApp schema apply already running is not awaited: it runs on against the
285
+ // released plugin, Quereus unwinds it when it fails, and `appSchema` settles `cancelled`. If
286
+ // that ever leaves a catalog write landing after close, await `appSchema` here first.
287
+ released.abort();
288
+ offlineControllers.delete(db);
247
289
  await releaseOptimysticPlugin(pluginResult, createdNode);
248
290
  await releaseStorageCache();
249
291
  log('Strand connection %s shut down', strandId);
@@ -251,47 +293,22 @@ export async function composeStrand(db, options, platform) {
251
293
  };
252
294
  }
253
295
  /**
254
- * Step 7 of {@link composeStrand}: declare the sApp's schema as `App` and apply it — a
255
- * declarative diff against the catalog, so re-applying a schema that is already in place
256
- * emits nothing. The one site that applies an `App` schema: `composeStrand` calls it at
257
- * bring-up, and cadre-core's `StrandDatabase.attachAppSchema` calls it on a live storage
258
- * replica when an app claims the strand. The database must already be composed (optimystic
259
- * set as the default vtab), or the tables land in memory instead of the strand.
260
- *
261
- * Refuses a schema holding an item the Quereus parser skipped: it keeps any item whose leading
262
- * keyword it does not model (`create unique index …`, a misspelled `tabel`, `domain`) as an
263
- * opaque placeholder that apply ignores, so the app would otherwise run without that item.
264
- *
265
- * Refuses a schema holding a `seed` item: the apply runs on every node of the strand at every
266
- * connect, so seed inserts would put a network write on each bring-up and two nodes inserting
267
- * the same key at once would collide. Rows an app needs at birth are the founding app's to write.
296
+ * The Optimystic plugin's `offline` block for this strand: the caller's options, the platform's
297
+ * journal when the caller named none, and the journal label, which the plugin refuses without one.
268
298
  */
269
- export async function applyAppSchema(db, schema) {
270
- await db.exec(`
271
- declare schema App {
272
- ${schema}
273
- }
274
- `);
275
- assertNoIgnoredItems(db);
276
- assertNoSeedItems(db);
277
- await db.exec('apply schema App;');
299
+ function strandOfflineConfig(strandId, offline, platform) {
300
+ const journal = offline.journal ?? platform.defaultOfflineJournal?.();
301
+ return journal === undefined
302
+ ? { ...offline }
303
+ : { ...offline, journal, journalLabel: strandJournalLabel(strandId) };
278
304
  }
279
- function declaredAppItems(db) {
280
- return db.declaredSchemaManager.getDeclaredSchema('App')?.items ?? [];
281
- }
282
- function assertNoIgnoredItems(db) {
283
- const ignored = declaredAppItems(db).filter(item => item.type === 'declareIgnored').length;
284
- if (ignored > 0) {
285
- // Only a count: Quereus leaves an ignored item's source text empty.
286
- throw new Error(`sApp schema has ${ignored} item(s) the parser does not recognize (a \`create …\` prefix or a misspelled item keyword such as \`tabel\`); items are \`table\`, \`index\`, \`unique index\`, \`view\`, \`materialized view\` and \`assertion\``);
287
- }
288
- }
289
- function assertNoSeedItems(db) {
290
- // Quereus refuses a second seed for one table at declare, so these names are distinct.
291
- const tables = declaredAppItems(db).flatMap(item => item.type === 'declaredSeed' ? [item.tableName] : []);
292
- if (tables.length > 0) {
293
- throw new Error(`sApp schema has seed item(s) for table(s) ${tables.join(', ')}; Sereus does not apply seed rows to a strand — insert the rows an app needs at birth from the app when it founds the strand`);
294
- }
305
+ /**
306
+ * The offline journal label for a strand. A label is `[a-z0-9_-]` and a strand id `[a-z0-9._-]`, so
307
+ * `_` becomes `__` and `.` becomes `_d`: no two strand ids share a label, and two strands journaling
308
+ * into one store keep their records apart.
309
+ */
310
+ function strandJournalLabel(strandId) {
311
+ return `strand-${strandId.replaceAll('_', '__').replaceAll('.', '_d')}`;
295
312
  }
296
313
  /**
297
314
  * Steps 1-2 of {@link composeStrand}: register the crypto plugin, then the optimystic
@@ -313,9 +330,16 @@ async function registerStrandPlugins(db, platform, strand) {
313
330
  const storage = strand.storage;
314
331
  pluginConfig.rawStorageFactory = () => storage;
315
332
  }
333
+ // NOTE: `networkCommitTimeoutMs` is left at upstream's default (five minutes); nothing was
334
+ // measured to choose another. A network-acknowledged commit made while transactions are queued
335
+ // (any `Strand` write, all of them tagged 'network') waits that long behind them holding
336
+ // Quereus's execution lock. If that ever shows up as a frozen app, lower it for strands here.
337
+ if (strand.offline) {
338
+ pluginConfig.offline = strand.offline;
339
+ }
316
340
  // The plugin's published signature is `Record<string, SqlValue>` but it also reads
317
- // `rawStorageFactory` (a function reference) from the same map. Cast through unknown
318
- // rather than widen the public type.
341
+ // `rawStorageFactory` (a function reference) and `offline` (an object) from the same
342
+ // map. Cast through unknown rather than widen the public type.
319
343
  const pluginResult = optimysticPlugin(db, pluginConfig);
320
344
  applyRegistrations(db, pluginResult);
321
345
  log('Registered optimystic vtables and functions');
@@ -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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"}
@@ -8,6 +8,8 @@ import type { StrandConnectionOptions, SereusPluginResult } from './types.js';
8
8
  * - crypto registered inline (no runtime `@quereus/quereus` import, so the
9
9
  * bundle does not duplicate the host's instance),
10
10
  * - storage defaulting to `IndexedDBRawStorage` keyed by `sereus-strand-<id>`,
11
+ * - with `offline` given and no `journal`, the offline queue journaled into that same
12
+ * IndexedDB database (its `kv` object store) when the storage was defaulted,
11
13
  * - a node created via the TCP-free `@optimystic/db-p2p/rn` entry with explicit
12
14
  * `webSockets()` + `circuitRelayTransport()` transports.
13
15
  */
@@ -2,7 +2,7 @@ import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
2
2
  import { createLibp2pNode } from '@optimystic/db-p2p/rn';
3
3
  import { webSockets } from '@libp2p/websockets';
4
4
  import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
5
- import { IndexedDBRawStorage, openOptimysticWebDb } from '@optimystic/db-p2p-storage-web';
5
+ import { IndexedDBKVStore, IndexedDBRawStorage, openOptimysticWebDb } from '@optimystic/db-p2p-storage-web';
6
6
  import { composeStrand, applyRegistrations } from './compose-strand.js';
7
7
  /**
8
8
  * Connect a Quereus Database to a Sereus strand from a browser/worker.
@@ -12,10 +12,14 @@ import { composeStrand, applyRegistrations } from './compose-strand.js';
12
12
  * - crypto registered inline (no runtime `@quereus/quereus` import, so the
13
13
  * bundle does not duplicate the host's instance),
14
14
  * - storage defaulting to `IndexedDBRawStorage` keyed by `sereus-strand-<id>`,
15
+ * - with `offline` given and no `journal`, the offline queue journaled into that same
16
+ * IndexedDB database (its `kv` object store) when the storage was defaulted,
15
17
  * - a node created via the TCP-free `@optimystic/db-p2p/rn` entry with explicit
16
18
  * `webSockets()` + `circuitRelayTransport()` transports.
17
19
  */
18
20
  export async function connectToStrandBrowser(db, options) {
21
+ // The IndexedDB database the storage was defaulted to, if it was; the offline journal shares it.
22
+ let defaultedDb;
19
23
  return composeStrand(db, options, {
20
24
  registerCrypto(database) {
21
25
  applyRegistrations(database, cryptoPlugin(database, {}));
@@ -27,8 +31,11 @@ export async function connectToStrandBrowser(db, options) {
27
31
  // unit-test fake transactor needs no storage.
28
32
  if (resolvedTransactor === 'test')
29
33
  return undefined;
30
- const dbHandle = await openOptimysticWebDb(`sereus-strand-${strandId}`);
31
- return new IndexedDBRawStorage(dbHandle);
34
+ defaultedDb = await openOptimysticWebDb(`sereus-strand-${strandId}`);
35
+ return new IndexedDBRawStorage(defaultedDb);
36
+ },
37
+ defaultOfflineJournal() {
38
+ return defaultedDb && new IndexedDBKVStore(defaultedDb);
32
39
  },
33
40
  async createNode({ networkName, bootstrapNodes, fretProfile, clusterSize, clusterPolicy, storage }) {
34
41
  return createLibp2pNode({
@@ -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,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"}
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,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAA8B,MAAM,gCAAgC,CAAC;AAGxI,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASxE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAAY,EACZ,OAAgC;IAEhC,iGAAiG;IACjG,IAAI,WAA8C,CAAC;IACnD,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,WAAW,GAAG,MAAM,mBAAmB,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;YACrE,OAAO,IAAI,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;QACD,qBAAqB;YACpB,OAAO,WAAW,IAAI,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACzD,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/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export { connectToStrand } from './connect.js';
2
- export { applyAppSchema } from './compose-strand.js';
2
+ export { offlineControllerOf } from './compose-strand.js';
3
+ export { applyAppSchema } from './app-schema.js';
3
4
  export { createEmptyCollections, type CollectionRegistrySource } from './empty-collections.js';
4
5
  export { MIN_CLUSTER_SIZE, COHORT_READ_DEADLINE_MS, CONTROL_REPLICATION_BREADTH, CONTROL_CLUSTER_POLICY, DEFAULT_STRAND_CLUSTER_SIZE, STRAND_CLUSTER_POLICY, resolveStrandClusterSize, resolveRepairYardstick, controlClusterPolicy, strandClusterPolicy } from './cluster-size.js';
5
6
  export { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
6
- export type { StrandConnectionOptions, SereusPluginResult, StrandTransactor } from './types.js';
7
+ export type { StrandConnectionOptions, SereusPluginResult, StrandTransactor, StrandOfflineOptions, StrandOfflineController, AppSchemaOutcome, } from './types.js';
7
8
  export type { SqlCommitDurability, CommitFullyHeld } from '@optimystic/quereus-plugin-optimystic/plugin';
9
+ export { OfflineCommitTimeoutError, QueuedEntriesBlockSchemaChangeError, EntryNotDiscardableError, UnknownEntryError, QueueDisposedError, OfflineJournalError, } from '@optimystic/quereus-plugin-optimystic/plugin';
10
+ export type { QueuedTransactionStatus, OfflineModeState, CommitOutlook } from '@optimystic/quereus-plugin-optimystic/plugin';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { connectToStrand } from './connect.js';
2
- export { applyAppSchema } from './compose-strand.js';
2
+ export { offlineControllerOf } from './compose-strand.js';
3
+ export { applyAppSchema } from './app-schema.js';
3
4
  export { createEmptyCollections } from './empty-collections.js';
4
5
  export { MIN_CLUSTER_SIZE, COHORT_READ_DEADLINE_MS, CONTROL_REPLICATION_BREADTH, CONTROL_CLUSTER_POLICY, DEFAULT_STRAND_CLUSTER_SIZE, STRAND_CLUSTER_POLICY, resolveStrandClusterSize, resolveRepairYardstick, controlClusterPolicy, strandClusterPolicy } from './cluster-size.js';
5
6
  export { wrapStorageWithCache, disposeStorageCache } from './cached-storage.js';
7
+ export { OfflineCommitTimeoutError, QueuedEntriesBlockSchemaChangeError, EntryNotDiscardableError, UnknownEntryError, QueueDisposedError, OfflineJournalError, } from '@optimystic/quereus-plugin-optimystic/plugin';
6
8
  //# 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,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAiC,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EACN,gBAAgB,EAChB,uBAAuB,EACvB,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,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,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAiC,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EACN,gBAAgB,EAChB,uBAAuB,EACvB,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAKhF,OAAO,EACN,yBAAyB,EACzB,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,8CAA8C,CAAC"}