@spooky-sync/core 0.0.1-canary.161 → 0.0.1-canary.162
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.d.ts +294 -4
- package/dist/index.js +1033 -293
- package/dist/otel/index.d.ts +1 -1
- package/dist/types.d.ts +98 -1
- package/package.json +3 -3
- package/src/modules/crdt/crdt-hydration.test.ts +4 -0
- package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
- package/src/modules/crdt/index.ts +109 -7
- package/src/modules/data/data.membership.test.ts +297 -0
- package/src/modules/data/id-set-plan.test.ts +122 -0
- package/src/modules/data/index.ts +291 -39
- package/src/modules/data/window-query.ts +53 -13
- package/src/modules/sync/sync.live-removal.test.ts +134 -0
- package/src/modules/sync/sync.reconnect.test.ts +145 -0
- package/src/modules/sync/sync.ts +143 -43
- package/src/services/database/connection-supervisor.test.ts +255 -0
- package/src/services/database/connection-supervisor.ts +351 -0
- package/src/services/database/database.query-timeout.test.ts +83 -0
- package/src/services/database/database.ts +21 -1
- package/src/services/database/index.ts +1 -0
- package/src/services/database/local-migrator.ts +1 -0
- package/src/services/database/remote.ts +99 -3
- package/src/services/database/sqlite-cache-engine.ts +1 -0
- package/src/sp00ky.ts +32 -1
- package/src/types.ts +99 -0
- package/src/utils/index.ts +38 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as Sp00kyQueryResultPromise, B as LocalStore, C as ReconnectConfig, D as RunOptions, E as RegistrationTimings, F as SyncHealthConfig, G as SyncEventSystem, H as DatabaseEventSystem, I as SyncHealthStatus, K as EventDefinition, L as TimingPhase, M as StorageHealthStatus, N as StoreType, O as Sp00kyConfig, P as SyncHealth, R as UpdateOptions, S as QueryUpdateCallback, T as RecordVersionDiff, U as DatabaseEventTypes, V as SealedQuery, W as Logger$1, _ as QueryState, a as MATERIALIZATION_SAMPLE_WINDOW, b as QueryTimeToLive, c as MutationEventType, d as PinoTransmit, f as PreloadOptions, g as QueryHash, h as QueryConfigRecord, i as Level, j as StorageHealth, k as Sp00kyQueryResult, l as PersistenceClient, m as QueryConfig, n as DebounceOptions, o as MutationCallback, p as PreloadRefresh, q as EventSystem, r as EventSubscriptionOptions, s as MutationEvent, t as ConnectionState, u as PhaseStat, v as QueryStatus, w as RecordVersionArray, x as QueryTimings, y as QueryStatusCallback, z as UpEvent } from "./types.js";
|
|
2
2
|
import * as surrealdb0 from "surrealdb";
|
|
3
|
-
import { Duration, RecordId, Surreal as Surreal$1, SurrealTransaction } from "surrealdb";
|
|
3
|
+
import { Duration, RecordId, Surreal as Surreal$1, SurrealEvents, SurrealTransaction } from "surrealdb";
|
|
4
4
|
import { AccessDefinition, BackendNames, BackendRoutes, BucketNames, ColumnSchema, FinalQuery, GetTable, QueryBuilder, QueryOptions, QueryPlan, RoutePayload, SchemaStructure, TableModel, TableNames, TypeNameToTypeMap } from "@spooky-sync/query-builder";
|
|
5
5
|
import { Logger } from "pino";
|
|
6
6
|
import { LoroDoc } from "loro-crdt";
|
|
@@ -10,6 +10,13 @@ declare abstract class AbstractDatabaseService {
|
|
|
10
10
|
protected client: Surreal$1;
|
|
11
11
|
protected logger: Logger$1;
|
|
12
12
|
protected events: DatabaseEventSystem;
|
|
13
|
+
/**
|
|
14
|
+
* Per-query deadline in ms; `0` disables. Only the remote service sets this
|
|
15
|
+
* (see `RemoteDatabaseService`) — a local query can be legitimately slow and
|
|
16
|
+
* has its own retry ladders, and there is no half-open-socket failure mode
|
|
17
|
+
* for an in-process engine.
|
|
18
|
+
*/
|
|
19
|
+
protected queryTimeoutMs: number;
|
|
13
20
|
protected abstract eventType: typeof DatabaseEventTypes.LocalQuery | typeof DatabaseEventTypes.RemoteQuery;
|
|
14
21
|
constructor(client: Surreal$1, logger: Logger$1, events: DatabaseEventSystem);
|
|
15
22
|
abstract connect(): Promise<void>;
|
|
@@ -19,6 +26,11 @@ declare abstract class AbstractDatabaseService {
|
|
|
19
26
|
private queryQueue;
|
|
20
27
|
/**
|
|
21
28
|
* Execute a query with serialized execution to prevent WASM transaction issues.
|
|
29
|
+
*
|
|
30
|
+
* Serialization means every query waits on the previous one, so a call that
|
|
31
|
+
* never settles blocks the whole chain forever. {@link queryTimeoutMs} bounds
|
|
32
|
+
* each link: on expiry this promise rejects and the chain moves on, even
|
|
33
|
+
* though the underlying RPC is still parked in the SDK's pending map.
|
|
22
34
|
*/
|
|
23
35
|
query<T extends unknown[]>(query: string, vars?: Record<string, unknown>): Promise<T>;
|
|
24
36
|
execute<T>(query: SealedQuery<T>, vars?: Record<string, unknown>): Promise<T>;
|
|
@@ -26,18 +38,144 @@ declare abstract class AbstractDatabaseService {
|
|
|
26
38
|
}
|
|
27
39
|
//#endregion
|
|
28
40
|
//#region src/services/database/remote.d.ts
|
|
41
|
+
/** Transport events the SDK publishes, mapped 1:1 to {@link ConnectionState}. */
|
|
42
|
+
type RemoteConnectionEvent = ConnectionState | 'error';
|
|
29
43
|
declare class RemoteDatabaseService extends AbstractDatabaseService {
|
|
30
44
|
private config;
|
|
31
45
|
protected eventType: "DATABASE_REMOTE_QUERY";
|
|
46
|
+
private readonly reconnectConfig;
|
|
47
|
+
/**
|
|
48
|
+
* In-flight `connect()`, so concurrent callers (boot + supervisor revive +
|
|
49
|
+
* an `online` event landing at the same moment) share one attempt instead of
|
|
50
|
+
* racing two sockets. Cleared on settle, so a later call always reconnects.
|
|
51
|
+
*/
|
|
52
|
+
private connecting;
|
|
32
53
|
constructor(config: Sp00kyConfig<any>['database'], logger: Logger$1);
|
|
33
54
|
getConfig(): Sp00kyConfig<any>['database'];
|
|
55
|
+
/** Resolved reconnect tunables; the supervisor reads its own knobs here. */
|
|
56
|
+
getReconnectConfig(): Required<ReconnectConfig>;
|
|
57
|
+
/** Current transport state as reported by the SDK. */
|
|
58
|
+
getStatus(): ConnectionState;
|
|
59
|
+
/**
|
|
60
|
+
* Observe transport events. Thin passthrough so callers (the supervisor,
|
|
61
|
+
* sync, CRDT) don't have to reach through `getClient()`.
|
|
62
|
+
*/
|
|
63
|
+
subscribeConnection<K extends RemoteConnectionEvent>(event: K, cb: (...payload: SurrealEvents[K]) => void): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Tear the socket down on purpose. Used by the heartbeat watchdog when a
|
|
66
|
+
* socket stops answering but never closes: `close()` makes the SDK publish
|
|
67
|
+
* `disconnected`, which is what drives the supervisor's revive loop.
|
|
68
|
+
*/
|
|
69
|
+
forceClose(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Open (or re-open) the remote connection.
|
|
72
|
+
*
|
|
73
|
+
* Safe to call repeatedly: concurrent calls share the in-flight attempt, and
|
|
74
|
+
* a call after a `disconnected` builds a fresh socket. `use()` and
|
|
75
|
+
* `authenticate()` are re-applied here for the cold path; the SDK also
|
|
76
|
+
* replays them itself on its own internal reconnects.
|
|
77
|
+
*/
|
|
34
78
|
connect(): Promise<void>;
|
|
79
|
+
private doConnect;
|
|
35
80
|
signin(params: any): Promise<any>;
|
|
36
81
|
signup(params: any): Promise<any>;
|
|
37
82
|
authenticate(token: string): Promise<any>;
|
|
38
83
|
invalidate(): Promise<void>;
|
|
39
84
|
}
|
|
40
85
|
//#endregion
|
|
86
|
+
//#region src/services/database/connection-supervisor.d.ts
|
|
87
|
+
/**
|
|
88
|
+
* Keeps the remote WebSocket alive for the whole life of the page.
|
|
89
|
+
*
|
|
90
|
+
* The SurrealDB SDK reconnects on its own after a socket `close`, but that
|
|
91
|
+
* covers only one of three ways the connection dies:
|
|
92
|
+
*
|
|
93
|
+
* 1. **Socket closes, SDK recovers.** Handled entirely by the SDK. This
|
|
94
|
+
* supervisor only observes it (to report `reconnecting` upward).
|
|
95
|
+
* 2. **Socket closes, SDK gives up.** With `attempts: -1` this shouldn't happen
|
|
96
|
+
* from exhaustion — but the SDK also terminates the engine permanently when
|
|
97
|
+
* its post-reconnect handshake throws (it re-runs `version()`, `use()`,
|
|
98
|
+
* `authenticate()` on every reconnect and closes the engine on any error).
|
|
99
|
+
* One transient hiccup there would otherwise kill the page's connection for
|
|
100
|
+
* good. The revive loop re-opens from scratch.
|
|
101
|
+
* 3. **Socket never closes at all.** A half-open connection: the peer is gone
|
|
102
|
+
* (NAT timeout, wifi switch, laptop sleep) but no FIN ever arrives, so
|
|
103
|
+
* `readyState` stays OPEN and the SDK's own 30s ping — fire-and-forget, no
|
|
104
|
+
* response deadline — never notices. Nothing ever fires a `close` event, so
|
|
105
|
+
* nothing ever triggers a reconnect. The heartbeat detects this and forces
|
|
106
|
+
* the teardown that case 2's loop then repairs.
|
|
107
|
+
*
|
|
108
|
+
* Plus wake triggers: coming back `online` or un-hiding the tab probes
|
|
109
|
+
* immediately rather than waiting out a backoff that was scheduled while the
|
|
110
|
+
* network was known-down.
|
|
111
|
+
*/
|
|
112
|
+
declare class ConnectionSupervisor {
|
|
113
|
+
private readonly remote;
|
|
114
|
+
private readonly logger;
|
|
115
|
+
private readonly config;
|
|
116
|
+
private state;
|
|
117
|
+
private subscribers;
|
|
118
|
+
private started;
|
|
119
|
+
private disposed;
|
|
120
|
+
private heartbeatTimer;
|
|
121
|
+
private heartbeatInFlight;
|
|
122
|
+
private reviveTimer;
|
|
123
|
+
private reviveAttempts;
|
|
124
|
+
private reviving;
|
|
125
|
+
/**
|
|
126
|
+
* Set while the browser reports itself offline. Retrying a socket against a
|
|
127
|
+
* down interface only burns backoff, so the loop parks until `online` fires.
|
|
128
|
+
*/
|
|
129
|
+
private suspended;
|
|
130
|
+
private teardown;
|
|
131
|
+
private static readonly REVIVE_BASE_MS;
|
|
132
|
+
constructor(remote: RemoteDatabaseService, logger: Logger$1, config?: Required<ReconnectConfig>);
|
|
133
|
+
/** Latest observed transport state. */
|
|
134
|
+
get connection(): ConnectionState;
|
|
135
|
+
/**
|
|
136
|
+
* Observe transport state. Fires immediately with the current value and again
|
|
137
|
+
* on every change. Returns an unsubscribe.
|
|
138
|
+
*/
|
|
139
|
+
subscribe(cb: (state: ConnectionState) => void): () => void;
|
|
140
|
+
/**
|
|
141
|
+
* Begin supervising. Call once, after the initial {@link
|
|
142
|
+
* RemoteDatabaseService.connect}. Idempotent.
|
|
143
|
+
*/
|
|
144
|
+
start(): void;
|
|
145
|
+
/** Stop all timers and listeners. Safe to call more than once. */
|
|
146
|
+
dispose(): void;
|
|
147
|
+
private setState;
|
|
148
|
+
private clearReviveTimer;
|
|
149
|
+
/**
|
|
150
|
+
* Queue the next `connect()` attempt on exponential backoff, capped at
|
|
151
|
+
* `superviseRetryDelayMaxMs`. Never gives up — the page is expected to
|
|
152
|
+
* outlive any outage.
|
|
153
|
+
*/
|
|
154
|
+
private scheduleRevive;
|
|
155
|
+
private revive;
|
|
156
|
+
private stopHeartbeat;
|
|
157
|
+
private startHeartbeat;
|
|
158
|
+
/**
|
|
159
|
+
* Probe the server end-to-end. Deliberately goes through
|
|
160
|
+
* `remote.query` — the same serialized queue every other remote call uses —
|
|
161
|
+
* so a queue wedged behind a stuck RPC also fails the heartbeat instead of
|
|
162
|
+
* being invisible to it.
|
|
163
|
+
*/
|
|
164
|
+
private beat;
|
|
165
|
+
/**
|
|
166
|
+
* A restored network or an un-hidden tab is the strongest available hint that
|
|
167
|
+
* a reconnect will now succeed, so probe immediately instead of waiting out a
|
|
168
|
+
* backoff scheduled under worse conditions.
|
|
169
|
+
*/
|
|
170
|
+
private installWakeTriggers;
|
|
171
|
+
/**
|
|
172
|
+
* Reset the backoff and act on whichever problem is present: reconnect if the
|
|
173
|
+
* socket is gone, otherwise probe it (it may be half-open — which is exactly
|
|
174
|
+
* what a sleep/wake cycle produces).
|
|
175
|
+
*/
|
|
176
|
+
private wake;
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
41
179
|
//#region src/modules/sync/queue/queue-down.d.ts
|
|
42
180
|
type RegisterEvent = {
|
|
43
181
|
type: 'register';
|
|
@@ -488,7 +626,55 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
488
626
|
* await so a concurrently-firing timer can't process it twice.
|
|
489
627
|
*/
|
|
490
628
|
flushPendingStreamUpdate(queryHash: string): Promise<void>;
|
|
629
|
+
/**
|
|
630
|
+
* Materialize a query's result rows from the local store.
|
|
631
|
+
*
|
|
632
|
+
* A query's rows are its MEMBERSHIP — the id-set the server put in
|
|
633
|
+
* `_00_list_ref` (`remoteArray`) — not "every local body that matches the
|
|
634
|
+
* WHERE". Those two disagree, and the disagreement was the bug: when a row
|
|
635
|
+
* leaves a query's window but still exists upstream, `handleRemovedRecords`
|
|
636
|
+
* keeps its local body and never re-fetches it, so a predicate re-scan finds
|
|
637
|
+
* that stale body still matching and keeps rendering the row. Selecting the
|
|
638
|
+
* id-set directly is also the only correct thing for a windowed query, where
|
|
639
|
+
* re-applying `START m` against the shared local store skips the window's own
|
|
640
|
+
* rows entirely (sparse windowing) and returns nothing.
|
|
641
|
+
*
|
|
642
|
+
* The rendered set is:
|
|
643
|
+
*
|
|
644
|
+
* (membership ∪ (pendingWrites ∩ localArray)) − pendingDeletes
|
|
645
|
+
*
|
|
646
|
+
* The middle term keeps optimistic writes visible without re-admitting stale
|
|
647
|
+
* rows. Every local write is fed to the SSP (`cache.saveBatch` →
|
|
648
|
+
* `ingestMany`), so `localArray` answers "does this row match the predicate
|
|
649
|
+
* per LOCAL truth". A pending write that moves a row into the window is in
|
|
650
|
+
* `localArray` and shows; one that moves a row out is absent and does not; a
|
|
651
|
+
* stale body the server dropped has no pending write at all, so it stays out.
|
|
652
|
+
* `pendingDeletes` covers the reverse lag — the server still lists a row whose
|
|
653
|
+
* DELETE is sitting in our outbox.
|
|
654
|
+
*
|
|
655
|
+
* Falls back to the predicate scan only when membership has never been
|
|
656
|
+
* established (a query first run on this device), so an offline first paint
|
|
657
|
+
* still shows something.
|
|
658
|
+
*/
|
|
491
659
|
private materializeRecords;
|
|
660
|
+
/**
|
|
661
|
+
* The materialization itself, without the DevTools timing wrapper. Split out so
|
|
662
|
+
* cold-start seeding can use it before a `QueryState` exists.
|
|
663
|
+
*/
|
|
664
|
+
private materializeFromConfig;
|
|
665
|
+
/**
|
|
666
|
+
* The authoritative membership list to render from, or `null` when membership
|
|
667
|
+
* has never been established and the caller must fall back to a scan.
|
|
668
|
+
*
|
|
669
|
+
* A windowed query has no usable fallback — re-running its `START m` locally
|
|
670
|
+
* returns the wrong rows — so it renders from whatever id-set is on hand
|
|
671
|
+
* (SSP's included) rather than degrading to a scan. That is the pre-existing
|
|
672
|
+
* behavior for windows and is preserved.
|
|
673
|
+
*/
|
|
674
|
+
private resolveMembership;
|
|
675
|
+
/** Apply the pending-write union and pending-delete subtraction, and map to
|
|
676
|
+
* RecordIds for the engines' id-set path. */
|
|
677
|
+
private buildRenderIds;
|
|
492
678
|
private processStreamUpdate;
|
|
493
679
|
/**
|
|
494
680
|
* Compute p55/p90/p99 from a rolling window of materialization samples.
|
|
@@ -585,6 +771,32 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
585
771
|
} | null>;
|
|
586
772
|
/** Stamp the preload freshness marker after a successful snapshot fetch. */
|
|
587
773
|
writePreloadMarker(hash: string, rowCount: number): Promise<void>;
|
|
774
|
+
/**
|
|
775
|
+
* Read the durable membership row, or `null` if this query has never had
|
|
776
|
+
* authoritative membership on this device. Any read error is treated as
|
|
777
|
+
* "unknown" so a broken row degrades to the predicate scan rather than
|
|
778
|
+
* rendering an empty list.
|
|
779
|
+
*/
|
|
780
|
+
getWindowMembership(key: string): Promise<RecordVersionArray | null>;
|
|
781
|
+
/** Persist the durable membership row. Best-effort: callers must not fail a
|
|
782
|
+
* sync round because the mirror write failed. */
|
|
783
|
+
writeWindowMembership(key: string, ids: RecordVersionArray): Promise<void>;
|
|
784
|
+
/**
|
|
785
|
+
* Record ids with a mutation still in the outbox, split by direction.
|
|
786
|
+
*
|
|
787
|
+
* Both halves feed {@link materializeRecords}: `writes` keeps optimistic
|
|
788
|
+
* creates/updates visible before the server has acknowledged them, and
|
|
789
|
+
* `deletes` suppresses rows the server still lists because our DELETE hasn't
|
|
790
|
+
* been processed yet. Reading `_00_pending_mutations` (rather than tracking
|
|
791
|
+
* ids in memory) is what makes both survive a reload.
|
|
792
|
+
*
|
|
793
|
+
* On failure returns empty sets: membership alone then decides, which can
|
|
794
|
+
* briefly hide an optimistic write but never resurrects a deleted row.
|
|
795
|
+
*/
|
|
796
|
+
getPendingRecordIds(): Promise<{
|
|
797
|
+
writes: Set<string>;
|
|
798
|
+
deletes: Set<string>;
|
|
799
|
+
}>;
|
|
588
800
|
/** True while ≥1 live subscriber is watching this query (refcount guard). */
|
|
589
801
|
hasSubscribers(hash: string): boolean;
|
|
590
802
|
/**
|
|
@@ -679,6 +891,18 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
679
891
|
private createAndRegisterQuery;
|
|
680
892
|
private createNewQuery;
|
|
681
893
|
private calculateHash;
|
|
894
|
+
/**
|
|
895
|
+
* Session-independent counterpart of {@link calculateHash}: the key for a
|
|
896
|
+
* query's durable `_00_window` membership row.
|
|
897
|
+
*
|
|
898
|
+
* Deliberately the SAME inputs minus the `session::id()` salt, so the two keys
|
|
899
|
+
* can never drift apart. The salt is right for `_00_query` (two tabs must not
|
|
900
|
+
* fight over one row) and wrong for membership, which has to be recognizable
|
|
901
|
+
* after a reload — a reload mints a new session id, and offline the salt is
|
|
902
|
+
* `''`, so a salted key can never match what the previous session wrote.
|
|
903
|
+
*/
|
|
904
|
+
private calculateMembershipKey;
|
|
905
|
+
private sha256;
|
|
682
906
|
private startTTLHeartbeat;
|
|
683
907
|
private replaceRecordInQueries;
|
|
684
908
|
}
|
|
@@ -808,6 +1032,13 @@ interface Sp00kySyncOptions {
|
|
|
808
1032
|
* up-queue for the session. Defaults to 30000; `0` disables the timeout.
|
|
809
1033
|
*/
|
|
810
1034
|
pushTimeoutMs?: number;
|
|
1035
|
+
/**
|
|
1036
|
+
* Transport supervisor. Sync reads its state to report `connection` in
|
|
1037
|
+
* {@link SyncHealth} so a UI can show "reconnecting…" the instant the socket
|
|
1038
|
+
* drops, without waiting for the degrade threshold. Optional: omitted in
|
|
1039
|
+
* tests, where `connection` then reports `connected`.
|
|
1040
|
+
*/
|
|
1041
|
+
connectionSupervisor?: ConnectionSupervisor;
|
|
811
1042
|
}
|
|
812
1043
|
/**
|
|
813
1044
|
* The main synchronization engine for Sp00ky.
|
|
@@ -831,7 +1062,12 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
831
1062
|
* `SYNC_QUERY_UPDATED` and `SYNC_MUTATION_ROLLED_BACK`. */
|
|
832
1063
|
get engineEvents(): SyncEventSystem;
|
|
833
1064
|
private scheduler;
|
|
834
|
-
|
|
1065
|
+
/**
|
|
1066
|
+
* Set by any event that means the socket we registered on is gone, so the
|
|
1067
|
+
* next `connected` knows it must re-subscribe rather than treat itself as the
|
|
1068
|
+
* initial connect. See {@link subscribeToReconnect}.
|
|
1069
|
+
*/
|
|
1070
|
+
private needsResubscribe;
|
|
835
1071
|
events: SyncEventSystem;
|
|
836
1072
|
private currentUserId;
|
|
837
1073
|
private tabRole;
|
|
@@ -866,6 +1102,17 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
866
1102
|
private selfHealAttempts;
|
|
867
1103
|
private static readonly SELF_HEAL_BASE_MS;
|
|
868
1104
|
private static readonly SELF_HEAL_MAX_MS;
|
|
1105
|
+
/**
|
|
1106
|
+
* Transport supervisor, when one was supplied. Sync only reads state from it;
|
|
1107
|
+
* it never drives reconnects itself.
|
|
1108
|
+
*/
|
|
1109
|
+
private readonly connectionSupervisor?;
|
|
1110
|
+
/**
|
|
1111
|
+
* Mirror of the supervisor's state. Defaults to `connected` so a client
|
|
1112
|
+
* constructed without a supervisor (tests, embedders) reports the same health
|
|
1113
|
+
* shape it always has rather than a permanent false "disconnected".
|
|
1114
|
+
*/
|
|
1115
|
+
private connectionState;
|
|
869
1116
|
/** Current sync-health snapshot. */
|
|
870
1117
|
get syncHealth(): SyncHealth;
|
|
871
1118
|
/**
|
|
@@ -875,6 +1122,17 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
875
1122
|
*/
|
|
876
1123
|
subscribeToSyncHealth(cb: (health: SyncHealth) => void): () => void;
|
|
877
1124
|
private emitSyncHealth;
|
|
1125
|
+
/**
|
|
1126
|
+
* Mirror the supervisor's transport state into {@link SyncHealth} and emit on
|
|
1127
|
+
* every change, so a UI can react to a dropped socket immediately instead of
|
|
1128
|
+
* waiting for `degradeAfterFailures` failed rounds. `status` is untouched:
|
|
1129
|
+
* a brief reconnect is not a degradation.
|
|
1130
|
+
*
|
|
1131
|
+
* No explicit unsubscribe: the supervisor is owned by the same client and
|
|
1132
|
+
* drops all subscribers in its own `dispose()`, which `Sp00kyClient.close()`
|
|
1133
|
+
* calls first.
|
|
1134
|
+
*/
|
|
1135
|
+
private subscribeToConnectionState;
|
|
878
1136
|
/**
|
|
879
1137
|
* Fed by the scheduler once per drained sync round. Individual failures are
|
|
880
1138
|
* absorbed by the queue's retry; only a run of `degradeAfterFailures`
|
|
@@ -999,6 +1257,16 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
999
1257
|
listRefTable(): string;
|
|
1000
1258
|
private killRefLiveQuery;
|
|
1001
1259
|
private restartRefLiveQuery;
|
|
1260
|
+
/**
|
|
1261
|
+
* Drop local LIVE bookkeeping without issuing a `KILL`.
|
|
1262
|
+
*
|
|
1263
|
+
* Called when the socket dies. The server-side subscription is scoped to that
|
|
1264
|
+
* WebSocket session and died with it, so there is nothing left to kill — and
|
|
1265
|
+
* by the time the reconnect handler runs, the client reports `connected`
|
|
1266
|
+
* again, which would otherwise send a `KILL` for a stale uuid on the *new*
|
|
1267
|
+
* session and hold up the restart queued behind it.
|
|
1268
|
+
*/
|
|
1269
|
+
private invalidateRefLiveQuery;
|
|
1002
1270
|
private subscribeToReconnect;
|
|
1003
1271
|
private startRefLiveQueries;
|
|
1004
1272
|
private handleRemoteListRefChange;
|
|
@@ -1247,9 +1515,30 @@ declare class CrdtManager {
|
|
|
1247
1515
|
private fields;
|
|
1248
1516
|
private liveByTable;
|
|
1249
1517
|
private pendingLive;
|
|
1518
|
+
private staleTables;
|
|
1519
|
+
private connectionGeneration;
|
|
1520
|
+
private connectionUnsubscribes;
|
|
1250
1521
|
private logger;
|
|
1251
1522
|
private sessionId;
|
|
1252
1523
|
constructor(schema: SchemaStructure, local: LocalStore, remote: RemoteDatabaseService, logger: Logger$1, debounceMs?: number);
|
|
1524
|
+
/**
|
|
1525
|
+
* Re-establish table LIVEs after a socket drop.
|
|
1526
|
+
*
|
|
1527
|
+
* A LIVE subscription lives and dies with its WebSocket session, and
|
|
1528
|
+
* `ensureTableSubscription` is memoized on `liveByTable` — so without this,
|
|
1529
|
+
* the first reconnect leaves CRDT realtime permanently dead: the map still
|
|
1530
|
+
* holds a uuid for a subscription the server has forgotten, so every later
|
|
1531
|
+
* `open()` short-circuits and no LIVE is ever re-issued.
|
|
1532
|
+
*
|
|
1533
|
+
* Both drop events matter: the SDK publishes `reconnecting` (not
|
|
1534
|
+
* `disconnected`) when it intends to recover on its own, and `disconnected`
|
|
1535
|
+
* only once it has given up.
|
|
1536
|
+
*/
|
|
1537
|
+
private subscribeToReconnect;
|
|
1538
|
+
/** Stop observing transport events. Separate from {@link closeAll}, which also
|
|
1539
|
+
* runs on a bucket switch where the manager keeps being used. */
|
|
1540
|
+
dispose(): void;
|
|
1541
|
+
private hasOpenFieldFor;
|
|
1253
1542
|
/** Set the session id that scopes this client's cursor entries. Must be
|
|
1254
1543
|
* called before `open()` for cursors to be pushed under a stable key.
|
|
1255
1544
|
* Passed in from `sp00ky.ts` at boot (it already fetches `session::id()`
|
|
@@ -1441,6 +1730,7 @@ declare class Sp00kyClient<S extends SchemaStructure> {
|
|
|
1441
1730
|
private config;
|
|
1442
1731
|
private local;
|
|
1443
1732
|
private remote;
|
|
1733
|
+
private connectionSupervisor;
|
|
1444
1734
|
private persistenceClient;
|
|
1445
1735
|
private migrator;
|
|
1446
1736
|
private cache;
|
|
@@ -1650,4 +1940,4 @@ declare function textToHtml(text: string): string;
|
|
|
1650
1940
|
*/
|
|
1651
1941
|
|
|
1652
1942
|
//#endregion
|
|
1653
|
-
export { AppReleaseHandle, AppReleaseModule, type AppReleaseOptions, type AppReleaseSnapshot, AuthEventSystem, AuthEventTypeMap, AuthEventTypes, AuthService, BucketHandle, CURSOR_COLORS, CrdtField, CrdtManager, DebounceOptions, EventSubscriptionOptions, FeatureFlagHandle, FeatureFlagModule, type FeatureFlagOptions, type FeatureFlagSnapshot, Level, MATERIALIZATION_SAMPLE_WINDOW, MutationCallback, MutationEvent, MutationEventType, PersistenceClient, PhaseStat, PinoTransmit, PreloadOptions, PreloadRefresh, QueryConfig, QueryConfigRecord, QueryHash, QueryState, QueryStatus, QueryStatusCallback, QueryTimeToLive, QueryTimings, QueryUpdateCallback, RecordVersionArray, RecordVersionDiff, RegistrationTimings, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResult, Sp00kyQueryResultPromise, StorageHealth, StorageHealthStatus, StoreType, SyncHealth, SyncHealthConfig, SyncHealthStatus, TimingPhase, UpdateOptions, createAuthEventSystem, cursorColorFromName, fileToUint8Array, semverGt, textToHtml };
|
|
1943
|
+
export { AppReleaseHandle, AppReleaseModule, type AppReleaseOptions, type AppReleaseSnapshot, AuthEventSystem, AuthEventTypeMap, AuthEventTypes, AuthService, BucketHandle, CURSOR_COLORS, ConnectionState, CrdtField, CrdtManager, DebounceOptions, EventSubscriptionOptions, FeatureFlagHandle, FeatureFlagModule, type FeatureFlagOptions, type FeatureFlagSnapshot, Level, MATERIALIZATION_SAMPLE_WINDOW, MutationCallback, MutationEvent, MutationEventType, PersistenceClient, PhaseStat, PinoTransmit, PreloadOptions, PreloadRefresh, QueryConfig, QueryConfigRecord, QueryHash, QueryState, QueryStatus, QueryStatusCallback, QueryTimeToLive, QueryTimings, QueryUpdateCallback, ReconnectConfig, RecordVersionArray, RecordVersionDiff, RegistrationTimings, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResult, Sp00kyQueryResultPromise, StorageHealth, StorageHealthStatus, StoreType, SyncHealth, SyncHealthConfig, SyncHealthStatus, TimingPhase, UpdateOptions, createAuthEventSystem, cursorColorFromName, fileToUint8Array, semverGt, textToHtml };
|