@interocitor/core 0.0.0-beta.6 → 0.0.0-beta.7
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
|
```
|
|
@@ -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;
|
|
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;
|
|
@@ -250,6 +256,8 @@ export declare class Interocitor<S extends Record<string, Record<string, unknown
|
|
|
250
256
|
connect(): Promise<void>;
|
|
251
257
|
private tryConnectFastPath;
|
|
252
258
|
private doConnect;
|
|
259
|
+
private startVisibilityTracking;
|
|
260
|
+
private stopVisibilityTracking;
|
|
253
261
|
disconnect(): Promise<void>;
|
|
254
262
|
setRemoteStorage(adapter: StorageAdapter | null): Promise<void>;
|
|
255
263
|
setLocalStorage(local: LocalStoreAdapter): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,iBAAiB,EAEjB,QAAQ,EACR,GAAG,EAIH,iBAAiB,EAEjB,WAAW,EAGX,gBAAgB,EAEhB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,2BAA2B,EAC3B,aAAa,EACb,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/sync-engine.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
580
|
-
|
|
581
|
-
|
|
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);
|
|
@@ -1449,12 +1493,41 @@ export class Interocitor {
|
|
|
1449
1493
|
this.startRemoteInvalidations(adapter);
|
|
1450
1494
|
this.connected = true;
|
|
1451
1495
|
this.log('info', 'connect() — connected', { remotePath: this.config.remotePath, deviceId: this.deviceId, pollInterval: this.config.pollInterval });
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1496
|
+
this.startVisibilityTracking();
|
|
1497
|
+
}
|
|
1498
|
+
startVisibilityTracking() {
|
|
1499
|
+
this.stopVisibilityTracking();
|
|
1500
|
+
if (typeof document === 'undefined')
|
|
1501
|
+
return;
|
|
1502
|
+
const listener = () => {
|
|
1503
|
+
if (document.visibilityState === 'hidden') {
|
|
1504
|
+
// Tab backgrounded: flush pending writes and slow down polling 10×.
|
|
1505
|
+
this.doFlush().catch(() => { });
|
|
1506
|
+
if (this.pollBaseIntervalMs) {
|
|
1507
|
+
this.pollCurrentIntervalMs = Math.min(this.pollCurrentIntervalMs * POLL_BACKGROUND_MULTIPLIER, this.pollBaseIntervalMs * POLL_BACKGROUND_MULTIPLIER);
|
|
1508
|
+
this.log('debug', '[interocitor:poll] tab hidden, poll interval slowed', { intervalMs: this.pollCurrentIntervalMs });
|
|
1456
1509
|
}
|
|
1457
|
-
}
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
// Tab foregrounded: pull immediately then reset to base interval.
|
|
1513
|
+
this.log('debug', '[interocitor:poll] tab visible, forcing pull and resetting interval');
|
|
1514
|
+
if (this.pollBaseIntervalMs) {
|
|
1515
|
+
this.pollCurrentIntervalMs = this.pollBaseIntervalMs;
|
|
1516
|
+
}
|
|
1517
|
+
if (this.connected) {
|
|
1518
|
+
this.pull().catch(() => { });
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
this.visibilityChangeListener = listener;
|
|
1523
|
+
document.addEventListener('visibilitychange', listener);
|
|
1524
|
+
}
|
|
1525
|
+
stopVisibilityTracking() {
|
|
1526
|
+
if (this.visibilityChangeListener) {
|
|
1527
|
+
if (typeof document !== 'undefined') {
|
|
1528
|
+
document.removeEventListener('visibilitychange', this.visibilityChangeListener);
|
|
1529
|
+
}
|
|
1530
|
+
this.visibilityChangeListener = null;
|
|
1458
1531
|
}
|
|
1459
1532
|
}
|
|
1460
1533
|
async disconnect() {
|
|
@@ -1463,6 +1536,7 @@ export class Interocitor {
|
|
|
1463
1536
|
// poll/push/flush touches the adapter while we are killing the session.
|
|
1464
1537
|
this.stopPolling();
|
|
1465
1538
|
this.stopRemoteInvalidations();
|
|
1539
|
+
this.stopVisibilityTracking();
|
|
1466
1540
|
this.clearScheduledFlush();
|
|
1467
1541
|
this.clearCompactTimers();
|
|
1468
1542
|
this.clearBatchTimer();
|
package/package.json
CHANGED