@interocitor/core 0.0.0-beta.6 → 0.0.0-beta.8

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
@@ -163,6 +163,32 @@ What we do **not** guarantee:
163
163
  manifest). See `docs/security-model.md`.
164
164
  - Recovery if the passphrase is lost — see "New device / restore".
165
165
 
166
+ ### Sync cadence
167
+
168
+ The engine adapts how often it polls the remote so it does useful work
169
+ when there is data to sync, and stays out of the way otherwise. This is
170
+ all automatic — there is no configuration knob.
171
+
172
+ - **Adaptive backoff.** Polling starts at `pollInterval` (30 s by
173
+ default). After every poll that merges zero entries the interval
174
+ doubles, capped at 60 s. Any poll that merges ≥ 1 entry resets the
175
+ interval back to base. The intent is to absorb idle bursts of clients
176
+ without hammering the remote, while still recovering immediately when
177
+ data starts flowing.
178
+ - **Tab visibility.** When the host page is hidden
179
+ (`document.visibilityState === 'hidden'`) the current poll interval is
180
+ multiplied by 10 — backgrounded tabs poll lazily. When the tab becomes
181
+ visible again the interval is reset to base **and** an immediate
182
+ `pull()` is fired so foregrounded data jumps in without waiting for
183
+ the next tick. This is a standard SWR‑style refresh‑on‑focus pattern.
184
+ The listener is wired during `connect()` and torn down by
185
+ `disconnect()`; environments without `document` (e.g. Node) skip it.
186
+ - **Push fallback.** Adapters that push invalidations (Cloudflare relay)
187
+ trigger an immediate pull and bypass the polling cadence entirely.
188
+ Polling is the safety net when the push channel is unavailable; the
189
+ WS connection itself uses bounded retries with a long cooldown after
190
+ repeated failed upgrades, so a misbehaving relay cannot DDoS itself.
191
+
166
192
  ## Setup lifecycle
167
193
 
168
194
  ```
@@ -210,15 +236,56 @@ Joining a new device to an existing mesh requires three things:
210
236
  3. Access to the same **remote mailbox** (the same `remotePath` on a
211
237
  storage backend the new device can reach).
212
238
 
213
- The pairing flow ships these three over an ECDH relay handshake (see
214
- `generateShareQR` / `handleScannedQR` in the handshake module). After
215
- the handshake the new device:
239
+ A production app should keep CRUD, sync lifecycle, and pairing separate:
240
+
241
+ ```text
242
+ lib/interocitor-db.ts engine, schema, local repository, credential primitives
243
+ lib/interocitor-sync.ts mesh id lifecycle, adapter, connect/disconnect, recovery
244
+ lib/interocitor-pairing.ts QR handshake only
245
+ ```
246
+
247
+ The pairing flow ships credentials over an ECDH relay handshake. The handshake relay base and sync adapter base are different concepts: a relay base is the temporary handshake-file path, such as `/Taska`; the Cloudflare adapter base URL is the concrete Worker route, such as `/sync/io/{meshId}`. The engine `remotePath` is still the mesh folder path used inside that adapter, such as `/Taska`.
248
+
249
+ ### Pairing intents
250
+
251
+ **Join QR** is for a device that does not have credentials yet:
252
+
253
+ 1. Joiner mints a fresh mesh id.
254
+ 2. Joiner creates a Cloudflare adapter with base URL `/sync/io/{meshId}`.
255
+ 3. Joiner calls `generateJoinQR()`.
256
+ 4. Existing paired device scans and pushes credentials.
257
+ 5. Joiner receives credentials from `credentials` on the result.
258
+ 6. Joiner applies the passphrase and connects to the minted mesh.
259
+
260
+ **Share QR** is for an existing mesh member inviting a new device:
261
+
262
+ 1. Existing device connects to the active mesh.
263
+ 2. Existing device calls `generateShareQR()` with `remotePath` and `passphrase`.
264
+ 3. New device scans and receives credentials from `handleScannedQR()`.
265
+ 4. New device applies credentials and connects using the adapter config from the payload.
266
+
267
+ `handleScannedQR()` returns `null` for join intent because the scanner pushed its own credentials. It returns credentials for share intent because the scanner received credentials. Always handle the return value:
268
+
269
+ ```ts
270
+ import { decodeQRPayload, handleScannedQR, parseQRFromUrl } from '@interocitor/core';
271
+ // Raw QR payload decoder is also available from '@interocitor/core/handshake/qr'.
272
+
273
+ const payload = parseQRFromUrl(location.hash) ?? decodeQRPayload(rawPastedPayload);
274
+ const received = await handleScannedQR({ adapter, relayBase: '/Taska', payload });
275
+
276
+ if (received) {
277
+ if (received.passphrase) db.setPassphrase(received.passphrase);
278
+ await connectFromPayload(received.remotePath);
279
+ }
280
+ ```
281
+
282
+ After the handshake the new device:
216
283
 
217
284
  ```ts
218
285
  const db = new Interocitor(adapter, {
219
286
  dbName: 'my-app',
220
287
  appName: 'My App',
221
- remotePath: '/MyApp',
288
+ remotePath: '/Taska',
222
289
  passphrase: 'base58-from-handshake',
223
290
  encrypted: true,
224
291
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEtH,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAUD;;;;;;;;;;;;;GAaG;AACH,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,aAAa,CAAS;IAM9B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,MAAM,EAAE,uBAAuB;IAI3C,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,YAAY;IAWpB,OAAO,KAAK,SAAS,GAMpB;IAED,OAAO,KAAK,SAAS,GAWpB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,OAAO;IAMT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAK5B,eAAe,IAAI,OAAO;IAI1B,wBAAwB,CACtB,YAAY,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,EAC1D,KAAK,CAAC,EAAE,uBAAuB,GAC9B,MAAM,IAAI;IA+DP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/C;;0BAEsB;IACtB,gBAAgB,IAAI,IAAI;IAIlB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqB7C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAe5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAa3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAsB/D"}
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEtH,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAUD;;;;;;;;;;;;;GAaG;AACH,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,aAAa,CAAS;IAM9B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,MAAM,EAAE,uBAAuB;IAI3C,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,YAAY;IAWpB,OAAO,KAAK,SAAS,GAMpB;IAED,OAAO,KAAK,SAAS,GAWpB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,OAAO;IAMT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAK5B,eAAe,IAAI,OAAO;IAI1B,wBAAwB,CACtB,YAAY,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,EAC1D,KAAK,CAAC,EAAE,uBAAuB,GAC9B,MAAM,IAAI;IAsFP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/C;;0BAEsB;IACtB,gBAAgB,IAAI,IAAI;IAIlB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqB7C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAe5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAa3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAsB/D"}
@@ -101,18 +101,29 @@ export class CloudflareAdapter {
101
101
  let cancelled = false;
102
102
  let backoffMs = 1000;
103
103
  const MAX_BACKOFF_MS = 30000;
104
+ // Attempts where the socket closed before ever opening (failed upgrade).
105
+ // After MAX_FAILED_UPGRADES consecutive such failures we back off to a long
106
+ // cooldown interval rather than hammering (e.g. DO free-tier exhaustion).
107
+ // Once the cooldown elapses we try again — self-healing if the server recovers.
108
+ let failedUpgradeStreak = 0;
109
+ const MAX_FAILED_UPGRADES = 5;
110
+ const COOLDOWN_MS = 60 * 60 * 1000; // 1 hour
104
111
  const connect = () => {
105
112
  if (cancelled)
106
113
  return;
114
+ let opened = false;
107
115
  try {
108
116
  ws = new WebSocket(this.notifyUrl);
109
117
  }
110
118
  catch (error) {
111
119
  hooks?.onError?.(error);
120
+ failedUpgradeStreak++;
112
121
  scheduleReconnect();
113
122
  return;
114
123
  }
115
124
  ws.onopen = () => {
125
+ opened = true;
126
+ failedUpgradeStreak = 0;
116
127
  backoffMs = 1000;
117
128
  hooks?.onReady?.();
118
129
  };
@@ -131,8 +142,20 @@ export class CloudflareAdapter {
131
142
  hooks?.onError?.(event);
132
143
  };
133
144
  ws.onclose = () => {
145
+ if (!opened) {
146
+ // The upgrade itself failed (server returned non-101, e.g. 500/503).
147
+ failedUpgradeStreak++;
148
+ }
134
149
  if (!cancelled) {
135
150
  hooks?.onClose?.();
151
+ if (failedUpgradeStreak >= MAX_FAILED_UPGRADES) {
152
+ // Back off to a long cooldown then try again — self-healing if the
153
+ // server recovers (e.g. DO free-tier resets).
154
+ failedUpgradeStreak = 0;
155
+ backoffMs = 1000;
156
+ setTimeout(() => connect(), COOLDOWN_MS);
157
+ return;
158
+ }
136
159
  scheduleReconnect();
137
160
  }
138
161
  };
@@ -69,6 +69,10 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
69
69
  private batchTimer;
70
70
  private pendingBatch;
71
71
  private pollTimer;
72
+ private pollBaseIntervalMs;
73
+ private pollCurrentIntervalMs;
74
+ private pollGeneration;
75
+ private visibilityChangeListener;
72
76
  private unsubscribeRemoteInvalidations;
73
77
  private remoteInvalidationPullPromise;
74
78
  private remoteInvalidationPullQueued;
@@ -176,6 +180,8 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
176
180
  private jitterDelay;
177
181
  private stopPolling;
178
182
  private startPolling;
183
+ /** Called by emit() to adapt the poll interval based on pull activity. */
184
+ private adaptPollInterval;
179
185
  private stopRemoteInvalidations;
180
186
  private startRemoteInvalidations;
181
187
  private getRowHlc;
@@ -202,8 +208,12 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
202
208
  private applyInitialState;
203
209
  configureMesh(state: SyncInitialState): void;
204
210
  /**
205
- * Set the mesh encryption passphrase.
206
- * Call before init() or between disconnect() and init().
211
+ * Set or replace the mesh encryption passphrase before connecting.
212
+ *
213
+ * Apps may call this before init(), after init(), or after credentials
214
+ * arrive from pairing. It intentionally does not rebuild the engine or
215
+ * change remotePath/deviceId; it only invalidates the derived key so the
216
+ * next connect/flush uses the new passphrase.
207
217
  */
208
218
  setPassphrase(passphrase: string): void;
209
219
  /**
@@ -250,6 +260,8 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
250
260
  connect(): Promise<void>;
251
261
  private tryConnectFastPath;
252
262
  private doConnect;
263
+ private startVisibilityTracking;
264
+ private stopVisibilityTracking;
253
265
  disconnect(): Promise<void>;
254
266
  setRemoteStorage(adapter: StorageAdapter | null): Promise<void>;
255
267
  setLocalStorage(local: LocalStoreAdapter): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,7 @@ import { flushToAdapter } from "./flush.js";
23
23
  import { pull as doPull } from "./pull.js";
24
24
  import { compact as doCompact, rehydrate as doRehydrate } from "./compaction.js";
25
25
  const DEFAULT_COMPACT_WARNING_THRESHOLD = 50;
26
+ const POLL_BACKGROUND_MULTIPLIER = 10;
26
27
  const DEFAULT_COMPACT_AUTO_THRESHOLD = 50;
27
28
  const DEFAULT_COMPACT_AUTO_SAMPLE_NUMERATOR = 10;
28
29
  const DEFAULT_COMPACT_AUTO_DEVICE_COUNT = 1;
@@ -56,6 +57,10 @@ export class Interocitor {
56
57
  this.pendingBatch = null;
57
58
  // Poll / push invalidation management
58
59
  this.pollTimer = null;
60
+ this.pollBaseIntervalMs = 0;
61
+ this.pollCurrentIntervalMs = 0;
62
+ this.pollGeneration = {};
63
+ this.visibilityChangeListener = null;
59
64
  this.unsubscribeRemoteInvalidations = null;
60
65
  this.remoteInvalidationPullPromise = null;
61
66
  this.remoteInvalidationPullQueued = false;
@@ -570,15 +575,51 @@ export class Interocitor {
570
575
  }
571
576
  stopPolling() {
572
577
  if (this.pollTimer) {
573
- clearInterval(this.pollTimer);
578
+ clearTimeout(this.pollTimer);
574
579
  this.pollTimer = null;
575
580
  }
581
+ this.pollGeneration = {}; // invalidate any in-flight pull's generation
582
+ this.pollBaseIntervalMs = 0;
576
583
  }
577
584
  startPolling(intervalMs = this.config.pollInterval) {
578
585
  this.stopPolling();
579
- this.pollTimer = setInterval(() => {
580
- this.pull().catch(() => { });
581
- }, intervalMs);
586
+ this.pollBaseIntervalMs = intervalMs;
587
+ this.pollCurrentIntervalMs = intervalMs;
588
+ // Each startPolling call gets its own generation token so that in-flight
589
+ // pulls from a previous polling session don't reschedule after stopPolling.
590
+ const generation = {};
591
+ this.pollGeneration = generation;
592
+ const schedule = () => {
593
+ this.pollTimer = setTimeout(() => {
594
+ this.pollTimer = null;
595
+ this.pull().catch(() => { }).finally(() => {
596
+ if (this.pollGeneration === generation) {
597
+ schedule();
598
+ }
599
+ });
600
+ }, this.pollCurrentIntervalMs);
601
+ };
602
+ schedule();
603
+ }
604
+ /** Called by emit() to adapt the poll interval based on pull activity. */
605
+ adaptPollInterval(entriesMerged) {
606
+ if (!this.pollBaseIntervalMs)
607
+ return; // not polling
608
+ const MAX_POLL_INTERVAL_MS = 60000;
609
+ if (entriesMerged > 0) {
610
+ // Activity — reset to base interval.
611
+ if (this.pollCurrentIntervalMs !== this.pollBaseIntervalMs) {
612
+ this.pollCurrentIntervalMs = this.pollBaseIntervalMs;
613
+ this.log('debug', '[interocitor:poll] activity detected, poll interval reset', { intervalMs: this.pollCurrentIntervalMs });
614
+ }
615
+ }
616
+ else {
617
+ // Idle — back off toward max.
618
+ if (this.pollCurrentIntervalMs < MAX_POLL_INTERVAL_MS) {
619
+ this.pollCurrentIntervalMs = Math.min(this.pollCurrentIntervalMs * 2, MAX_POLL_INTERVAL_MS);
620
+ this.log('debug', '[interocitor:poll] idle, poll interval backed off', { intervalMs: this.pollCurrentIntervalMs });
621
+ }
622
+ }
582
623
  }
583
624
  stopRemoteInvalidations() {
584
625
  if (this.unsubscribeRemoteInvalidations) {
@@ -777,6 +818,9 @@ export class Interocitor {
777
818
  this.invalidateQueryCacheForTable(event.table);
778
819
  this.invalidateRowCacheForTable(event.table);
779
820
  }
821
+ if (event.type === 'sync:complete') {
822
+ this.adaptPollInterval(event.entriesMerged);
823
+ }
780
824
  for (const listener of this.listeners) {
781
825
  try {
782
826
  listener(event);
@@ -953,11 +997,27 @@ export class Interocitor {
953
997
  });
954
998
  }
955
999
  /**
956
- * Set the mesh encryption passphrase.
957
- * Call before init() or between disconnect() and init().
1000
+ * Set or replace the mesh encryption passphrase before connecting.
1001
+ *
1002
+ * Apps may call this before init(), after init(), or after credentials
1003
+ * arrive from pairing. It intentionally does not rebuild the engine or
1004
+ * change remotePath/deviceId; it only invalidates the derived key so the
1005
+ * next connect/flush uses the new passphrase.
958
1006
  */
959
1007
  setPassphrase(passphrase) {
960
- this.configureMesh({ passphrase, encrypted: true });
1008
+ if (this.connected)
1009
+ throw new Error('Cannot set passphrase while connected; disconnect first');
1010
+ this.passphrase = passphrase;
1011
+ this.encryptionKey = null;
1012
+ this.encrypted = true;
1013
+ this.emit({
1014
+ type: 'mesh:configured',
1015
+ dbName: this.dbName,
1016
+ remotePath: this.config.remotePath,
1017
+ deviceId: this.deviceId,
1018
+ encrypted: this.encrypted,
1019
+ hadPassphrase: true,
1020
+ });
961
1021
  }
962
1022
  /**
963
1023
  * Get the current mesh passphrase.
@@ -1211,6 +1271,8 @@ export class Interocitor {
1211
1271
  hasInFlight: !!this.connectPromise,
1212
1272
  });
1213
1273
  await this.ensureReady();
1274
+ if (this.encrypted && !this.encryptionKey)
1275
+ await this.resolveEncryption();
1214
1276
  if (!this.config.remotePath)
1215
1277
  throw new Error('connect() requires remotePath; configure mesh before connecting');
1216
1278
  // Idempotent. If we are already connected to a live mesh on this
@@ -1449,12 +1511,41 @@ export class Interocitor {
1449
1511
  this.startRemoteInvalidations(adapter);
1450
1512
  this.connected = true;
1451
1513
  this.log('info', 'connect() — connected', { remotePath: this.config.remotePath, deviceId: this.deviceId, pollInterval: this.config.pollInterval });
1452
- if (typeof window !== 'undefined') {
1453
- window.addEventListener('visibilitychange', () => {
1454
- if (document.visibilityState === 'hidden') {
1455
- this.doFlush().catch(() => { });
1514
+ this.startVisibilityTracking();
1515
+ }
1516
+ startVisibilityTracking() {
1517
+ this.stopVisibilityTracking();
1518
+ if (typeof document === 'undefined')
1519
+ return;
1520
+ const listener = () => {
1521
+ if (document.visibilityState === 'hidden') {
1522
+ // Tab backgrounded: flush pending writes and slow down polling 10×.
1523
+ this.doFlush().catch(() => { });
1524
+ if (this.pollBaseIntervalMs) {
1525
+ this.pollCurrentIntervalMs = Math.min(this.pollCurrentIntervalMs * POLL_BACKGROUND_MULTIPLIER, this.pollBaseIntervalMs * POLL_BACKGROUND_MULTIPLIER);
1526
+ this.log('debug', '[interocitor:poll] tab hidden, poll interval slowed', { intervalMs: this.pollCurrentIntervalMs });
1456
1527
  }
1457
- });
1528
+ }
1529
+ else {
1530
+ // Tab foregrounded: pull immediately then reset to base interval.
1531
+ this.log('debug', '[interocitor:poll] tab visible, forcing pull and resetting interval');
1532
+ if (this.pollBaseIntervalMs) {
1533
+ this.pollCurrentIntervalMs = this.pollBaseIntervalMs;
1534
+ }
1535
+ if (this.connected) {
1536
+ this.pull().catch(() => { });
1537
+ }
1538
+ }
1539
+ };
1540
+ this.visibilityChangeListener = listener;
1541
+ document.addEventListener('visibilitychange', listener);
1542
+ }
1543
+ stopVisibilityTracking() {
1544
+ if (this.visibilityChangeListener) {
1545
+ if (typeof document !== 'undefined') {
1546
+ document.removeEventListener('visibilitychange', this.visibilityChangeListener);
1547
+ }
1548
+ this.visibilityChangeListener = null;
1458
1549
  }
1459
1550
  }
1460
1551
  async disconnect() {
@@ -1463,6 +1554,7 @@ export class Interocitor {
1463
1554
  // poll/push/flush touches the adapter while we are killing the session.
1464
1555
  this.stopPolling();
1465
1556
  this.stopRemoteInvalidations();
1557
+ this.stopVisibilityTracking();
1466
1558
  this.clearScheduledFlush();
1467
1559
  this.clearCompactTimers();
1468
1560
  this.clearBatchTimer();
@@ -0,0 +1,3 @@
1
+ export { buildPairUrl, decodeQRPayload, encodeQRPayload, parseQRFromUrl, } from './qr.ts';
2
+ export type { HandshakeIntent, HandshakeQRPayload, } from './qr.ts';
3
+ //# sourceMappingURL=qr-public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr-public.d.ts","sourceRoot":"","sources":["../../src/handshake/qr-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,eAAe,EACf,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export { buildPairUrl, decodeQRPayload, encodeQRPayload, parseQRFromUrl, } from "./qr.js";
package/dist/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export { generateShareQR, generateJoinQR, handleScannedQR, buildPairUrl, parseQR
36
36
  export type { HandshakeCredentials, HandshakeQRPayload, HandshakeIntent, GenerateShareQROptions, GenerateShareQRResult, GenerateJoinQROptions, GenerateJoinQRResult, HandleScannedQROptions, } from './handshake/index.ts';
37
37
  export { Interocitor, type InterocitorInitContext, } from './core/sync-engine.ts';
38
38
  export { LocalStore } from './storage/local-store.ts';
39
+ export { resetLocalDatabase } from './storage/reset.ts';
39
40
  export { type CredentialStore, type StoredCredentials, LocalStorageCredentialStore, WebAuthnCredentialStore, createCredentialStore, } from './storage/credential-store.ts';
40
41
  export { Table, QueryResult, RowResult } from './core/table.ts';
41
42
  export { types } from './core/schema-types.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EACL,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,WAAW,EACX,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAI/C,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,YAAY,EAEV,cAAc,EACd,SAAS,EAGT,iBAAiB,EACjB,iBAAiB,EAGjB,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,2BAA2B,EAC3B,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,aAAa,EAGb,SAAS,EACT,iBAAiB,EAGjB,GAAG,EAGH,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EACL,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,WAAW,EACX,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAI/C,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,MAAM,EACN,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,YAAY,EAEV,cAAc,EACd,SAAS,EAGT,iBAAiB,EACjB,iBAAiB,EAGjB,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,2BAA2B,EAC3B,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,aAAa,EAGb,SAAS,EACT,iBAAiB,EAGjB,GAAG,EAGH,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ export { generateShareQR, generateJoinQR, handleScannedQR, buildPairUrl, parseQR
37
37
  // ─── Engine ───────────────────────────────────────────────────────────
38
38
  export { Interocitor, } from "./core/sync-engine.js";
39
39
  export { LocalStore } from "./storage/local-store.js";
40
+ export { resetLocalDatabase } from "./storage/reset.js";
40
41
  export { LocalStorageCredentialStore, WebAuthnCredentialStore, createCredentialStore, } from "./storage/credential-store.js";
41
42
  export { Table, QueryResult, RowResult } from "./core/table.js";
42
43
  export { types } from "./core/schema-types.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Delete an Interocitor IndexedDB database after callers have disconnected
3
+ * and cleared credentials.
4
+ *
5
+ * This is intentionally low-level: it only deletes the local IndexedDB
6
+ * database named by `dbName`. Apps should call it as the final destructive
7
+ * local reset step, then reload before creating or joining a new mesh.
8
+ */
9
+ export declare function resetLocalDatabase(dbName?: string): Promise<void>;
10
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/storage/reset.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASxE"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Delete an Interocitor IndexedDB database after callers have disconnected
3
+ * and cleared credentials.
4
+ *
5
+ * This is intentionally low-level: it only deletes the local IndexedDB
6
+ * database named by `dbName`. Apps should call it as the final destructive
7
+ * local reset step, then reload before creating or joining a new mesh.
8
+ */
9
+ export function resetLocalDatabase(dbName = 'interocitor') {
10
+ return new Promise((resolve, reject) => {
11
+ const req = indexedDB.deleteDatabase(dbName);
12
+ req.onsuccess = () => resolve();
13
+ req.onerror = () => reject(req.error ?? new Error(`Failed to delete IndexedDB database "${dbName}"`));
14
+ req.onblocked = () => {
15
+ reject(new Error(`Cannot delete IndexedDB database "${dbName}" while another connection is open`));
16
+ };
17
+ });
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interocitor/core",
3
- "version": "0.0.0-beta.6",
3
+ "version": "0.0.0-beta.8",
4
4
  "description": "Encrypted local-first CRDT database that syncs over cloud storage without a purpose-built backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,14 @@
30
30
  "import": "./dist/storage/local-store.js",
31
31
  "types": "./dist/storage/local-store.d.ts"
32
32
  },
33
+ "./storage/reset": {
34
+ "import": "./dist/storage/reset.js",
35
+ "types": "./dist/storage/reset.d.ts"
36
+ },
37
+ "./handshake/qr": {
38
+ "import": "./dist/handshake/qr-public.js",
39
+ "types": "./dist/handshake/qr-public.d.ts"
40
+ },
33
41
  "./crypto/keys": {
34
42
  "import": "./dist/crypto/keys.js",
35
43
  "types": "./dist/crypto/keys.d.ts"