@spooky-sync/core 0.0.1-canary.221 → 0.0.1-canary.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +12 -4
- package/package.json +3 -3
- package/src/modules/data/index.ts +28 -1
package/dist/index.js
CHANGED
|
@@ -3473,6 +3473,14 @@ function phaseStatOf(samples, lastMs) {
|
|
|
3473
3473
|
count: samples.length
|
|
3474
3474
|
};
|
|
3475
3475
|
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Fraction of a query's TTL after which the client refreshes `lastActiveAt`.
|
|
3478
|
+
*
|
|
3479
|
+
* Must leave room for at least one retry: the server sweeps a view — and its
|
|
3480
|
+
* `_00_list_ref` edges — the moment `lastActiveAt + ttl` passes, and the client
|
|
3481
|
+
* is LIVE on those edges, so a swept-but-still-watched view empties the UI.
|
|
3482
|
+
*/
|
|
3483
|
+
const TTL_HEARTBEAT_FRACTION = .5;
|
|
3476
3484
|
var DataModule = class DataModule {
|
|
3477
3485
|
/** Tab identity baked into mutation ids (shared-tabs rollback routing);
|
|
3478
3486
|
* undefined in solo mode, where mutation-id falls back to a session id. */
|
|
@@ -5023,7 +5031,7 @@ var DataModule = class DataModule {
|
|
|
5023
5031
|
}
|
|
5024
5032
|
startTTLHeartbeat(queryState, hash) {
|
|
5025
5033
|
if (queryState.ttlTimer) return;
|
|
5026
|
-
const heartbeatTime = Math.floor(queryState.ttlDurationMs *
|
|
5034
|
+
const heartbeatTime = Math.floor(queryState.ttlDurationMs * TTL_HEARTBEAT_FRACTION);
|
|
5027
5035
|
queryState.ttlTimer = setTimeout(() => {
|
|
5028
5036
|
queryState.ttlTimer = null;
|
|
5029
5037
|
if ((this.subscriptions.get(hash)?.size ?? 0) === 0) {
|
|
@@ -7735,8 +7743,8 @@ function selfAllowlistedVariant(flag, userId) {
|
|
|
7735
7743
|
|
|
7736
7744
|
//#endregion
|
|
7737
7745
|
//#region src/modules/devtools/index.ts
|
|
7738
|
-
const CORE_VERSION = "0.0.1-canary.
|
|
7739
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
7746
|
+
const CORE_VERSION = "0.0.1-canary.222";
|
|
7747
|
+
const WASM_VERSION = "0.0.1-canary.222";
|
|
7740
7748
|
const SURREAL_VERSION = "3.0.3";
|
|
7741
7749
|
var DevToolsService = class DevToolsService {
|
|
7742
7750
|
eventsHistory = [];
|
|
@@ -12751,7 +12759,7 @@ var Sp00kyClient = class {
|
|
|
12751
12759
|
return new TabsCoordinator({
|
|
12752
12760
|
tabId,
|
|
12753
12761
|
fingerprint: computeTabsFingerprint({
|
|
12754
|
-
coreVersion: "0.0.1-canary.
|
|
12762
|
+
coreVersion: "0.0.1-canary.222",
|
|
12755
12763
|
schemaHash: hash53(this.config.schemaSurql),
|
|
12756
12764
|
endpoint: this.config.database.endpoint ?? "",
|
|
12757
12765
|
namespace: this.config.database.namespace,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/core",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.222",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@spooky-sync/query-builder": "0.0.1-canary.
|
|
64
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
63
|
+
"@spooky-sync/query-builder": "0.0.1-canary.222",
|
|
64
|
+
"@spooky-sync/ssp-wasm": "0.0.1-canary.222",
|
|
65
65
|
"@sqlite.org/sqlite-wasm": "3.53.0-build1",
|
|
66
66
|
"@surrealdb/wasm": "^3.0.3",
|
|
67
67
|
"blurhash": "^2.0.5",
|
|
@@ -90,6 +90,15 @@ export interface DurableMembership {
|
|
|
90
90
|
confirmed: boolean;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Fraction of a query's TTL after which the client refreshes `lastActiveAt`.
|
|
95
|
+
*
|
|
96
|
+
* Must leave room for at least one retry: the server sweeps a view — and its
|
|
97
|
+
* `_00_list_ref` edges — the moment `lastActiveAt + ttl` passes, and the client
|
|
98
|
+
* is LIVE on those edges, so a swept-but-still-watched view empties the UI.
|
|
99
|
+
*/
|
|
100
|
+
const TTL_HEARTBEAT_FRACTION = 0.5;
|
|
101
|
+
|
|
93
102
|
export class DataModule<S extends SchemaStructure> {
|
|
94
103
|
/** Tab identity baked into mutation ids (shared-tabs rollback routing);
|
|
95
104
|
* undefined in solo mode, where mutation-id falls back to a session id. */
|
|
@@ -2452,7 +2461,25 @@ export class DataModule<S extends SchemaStructure> {
|
|
|
2452
2461
|
private startTTLHeartbeat(queryState: QueryState, hash: QueryHash): void {
|
|
2453
2462
|
if (queryState.ttlTimer) return;
|
|
2454
2463
|
|
|
2455
|
-
|
|
2464
|
+
// Half the TTL, not 0.9 of it, so ONE failed beat is survivable.
|
|
2465
|
+
//
|
|
2466
|
+
// The timer re-arms after each beat, so expiry sits a full TTL after the
|
|
2467
|
+
// last SUCCESSFUL refresh. At 0.9 a beat that fails — the SSP restarting,
|
|
2468
|
+
// a reconnect, a slow database — left 10% of the TTL before the sweep
|
|
2469
|
+
// deleted the row AND its `_00_list_ref` edges, with no second attempt in
|
|
2470
|
+
// between. On a 10m TTL that is 60 seconds of slack against an SSP
|
|
2471
|
+
// bootstrap that takes ~2 minutes on a large tenant, so the view lost a
|
|
2472
|
+
// race it could not win.
|
|
2473
|
+
//
|
|
2474
|
+
// The user-visible form is content vanishing and coming back: the client
|
|
2475
|
+
// is LIVE on those edges, so it sees the sweep's deletes immediately, and
|
|
2476
|
+
// only restores them once its heartbeat notices the row is gone and
|
|
2477
|
+
// re-registers. Reported as chat messages disappearing for 10+ seconds.
|
|
2478
|
+
//
|
|
2479
|
+
// At 0.5 a single failure still leaves another attempt and half the TTL.
|
|
2480
|
+
// The cost is one extra beat per view per TTL, which is a single
|
|
2481
|
+
// `UPDATE ... SET lastActiveAt`.
|
|
2482
|
+
const heartbeatTime = Math.floor(queryState.ttlDurationMs * TTL_HEARTBEAT_FRACTION);
|
|
2456
2483
|
|
|
2457
2484
|
queryState.ttlTimer = setTimeout(() => {
|
|
2458
2485
|
queryState.ttlTimer = null;
|