@syncular/client 0.15.35 → 0.15.37
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 +45 -5
- package/dist/client.js +43 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-rebootstrap-receipt.d.ts +12 -0
- package/dist/local-rebootstrap-receipt.js +59 -0
- package/dist/local-rebootstrap.d.ts +5 -1
- package/dist/realtime-supervisor.d.ts +78 -0
- package/dist/realtime-supervisor.js +452 -0
- package/package.json +3 -3
- package/src/client.ts +56 -8
- package/src/index.ts +1 -0
- package/src/local-rebootstrap-receipt.ts +79 -0
- package/src/local-rebootstrap.ts +5 -1
- package/src/realtime-supervisor.ts +598 -0
package/README.md
CHANGED
|
@@ -54,14 +54,48 @@ The handle exposes the same logical API as `SyncClient` (subscribe /
|
|
|
54
54
|
mutate / sync / query / conflicts / …), every method a promise. It
|
|
55
55
|
acquires the Web Locks leader lock *before* spawning the worker — one
|
|
56
56
|
core per origin. Wake-ups are handled inside the worker (`autoSync`,
|
|
57
|
-
SPEC §8.4
|
|
58
|
-
|
|
57
|
+
SPEC §8.4); the supported page-level realtime supervisor owns reconnect and
|
|
58
|
+
resume policy. The main thread gets `onSyncNeeded` / `onConflict` / `onSynced`
|
|
59
59
|
events for rendering.
|
|
60
60
|
|
|
61
61
|
**Ephemeral in-memory mode is EXPLICIT.** `openWasmDatabase()` returns an
|
|
62
62
|
in-memory sqlite-wasm database for tests, demos and SSR. Nothing
|
|
63
63
|
persists, on purpose, and that is the only main-thread mode.
|
|
64
64
|
|
|
65
|
+
## Supported realtime lifecycle
|
|
66
|
+
|
|
67
|
+
Register subscriptions, then install the cross-host supervisor:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import {
|
|
71
|
+
browserConnectivitySignal,
|
|
72
|
+
documentLifecycleSignal,
|
|
73
|
+
installRealtimeSupervisor,
|
|
74
|
+
} from '@syncular/client';
|
|
75
|
+
|
|
76
|
+
await handle.subscribe({ id: 'todos', table: 'todos', scopes });
|
|
77
|
+
installRealtimeSupervisor(handle, {
|
|
78
|
+
connectivity: browserConnectivitySignal(),
|
|
79
|
+
lifecycle: documentLifecycleSignal(),
|
|
80
|
+
// Encrypted/locked apps also pass their active/preflight signal.
|
|
81
|
+
protection,
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It owns one connection attempt, retries initial failure and later socket loss
|
|
86
|
+
with bounded exponential jitter, suspends while offline/background/protected,
|
|
87
|
+
runs `syncUntilIdle()` before reporting `connected`, and cancels before
|
|
88
|
+
`close()`. `realtimeSupervisorSnapshot()` and
|
|
89
|
+
`subscribeRealtimeSupervisor()` expose only `phase`, `attempt`, and the bounded
|
|
90
|
+
library delay for UI/diagnostics—never raw transport prose or identities.
|
|
91
|
+
|
|
92
|
+
The lower-level `connectRealtime()` is idempotent while connected and
|
|
93
|
+
single-flight while connecting; disconnect invalidates an in-flight result so
|
|
94
|
+
it cannot install a stale socket. It remains available for custom host loops.
|
|
95
|
+
HTTP rounds still work with no socket, but do not imply continuous convergence:
|
|
96
|
+
a host trigger must actually run them. See the complete
|
|
97
|
+
[realtime lifecycle guide](https://syncular.dev/concepts-realtime/).
|
|
98
|
+
|
|
65
99
|
## Multi-tab followers (TODO 3.2, REVISE B3)
|
|
66
100
|
|
|
67
101
|
By default, every tab of the same origin shares ONE core:
|
|
@@ -312,9 +346,15 @@ const result = await client.rebootstrapLocalData({
|
|
|
312
346
|
The reset, durable idempotency marker, and optimistic outbox replay are one
|
|
313
347
|
SQLite transaction. An interruption therefore leaves either the old
|
|
314
348
|
projection or the fully reset projection with pending offline work still
|
|
315
|
-
visible. Reusing the same id returns `alreadyApplied: true
|
|
316
|
-
|
|
317
|
-
|
|
349
|
+
visible. Reusing the same id returns `alreadyApplied: true` together with the
|
|
350
|
+
original `retainedCommits` and `resetSubscriptions` counts. The core stores
|
|
351
|
+
that counts-only receipt atomically with the reset, so an application crash
|
|
352
|
+
after the SQLite commit but before its own acknowledgement does not turn the
|
|
353
|
+
retry into a misleading zero-impact result. Markers written before Syncular
|
|
354
|
+
0.15.36 cannot reconstruct their historical counts and preserve the former
|
|
355
|
+
zero-count replay behavior. A malformed or unreadable persisted receipt fails
|
|
356
|
+
closed with the sanitized `sync.local_corrupt` client-local code and performs
|
|
357
|
+
no reset. No receipt exposes ids, rows, scopes, or clinical values.
|
|
318
358
|
|
|
319
359
|
Worker, Tauri, and React Native adapters strictly decode the exact result
|
|
320
360
|
shape before returning it. Missing or additional fields, a non-boolean
|
package/dist/client.js
CHANGED
|
@@ -17,6 +17,7 @@ import { ChangeAccumulator, ChangeEmitter, InvalidationEmitter, invalidationFrom
|
|
|
17
17
|
import { singleOwnerLock, } from './leader-lock.js';
|
|
18
18
|
import { compileLocalDataPurge, localDataPurgeMetaKey, localDataPurgeTargetMatches, } from './local-purge.js';
|
|
19
19
|
import { compileLocalDataRebootstrap, localDataRebootstrapMetaKey, } from './local-rebootstrap.js';
|
|
20
|
+
import { decodeLocalDataRebootstrapReceipt, encodeLocalDataRebootstrapReceipt, } from './local-rebootstrap-receipt.js';
|
|
20
21
|
import { appendOutboxCommit, deleteOutboxCommit, dropOutboxCommitsInScope, encodeOutboxCommit, listOutbox, listOutboxBeforeImages, OutboxEncodeError, replaceOutboxBeforeImages, } from './outbox.js';
|
|
21
22
|
import { activeFailureRecords, listCommitOutcomes, persistCommitOutcomeResolution, pruneCommitOutcomes, commitOutcome as readCommitOutcome, recordCommitOutcome, } from './outcomes.js';
|
|
22
23
|
import { assertReadOnlyQuery } from './query-guard.js';
|
|
@@ -96,6 +97,8 @@ export class SyncClient {
|
|
|
96
97
|
#conflicts = [];
|
|
97
98
|
#rejections = [];
|
|
98
99
|
#socket;
|
|
100
|
+
#realtimeConnectPromise;
|
|
101
|
+
#realtimeGeneration = 0;
|
|
99
102
|
#pendingRound;
|
|
100
103
|
#needsPull = false;
|
|
101
104
|
#syncing = false;
|
|
@@ -314,8 +317,7 @@ export class SyncClient {
|
|
|
314
317
|
async close() {
|
|
315
318
|
this.#devtoolsUnregister?.();
|
|
316
319
|
this.#devtoolsUnregister = undefined;
|
|
317
|
-
this
|
|
318
|
-
this.#socket = undefined;
|
|
320
|
+
this.disconnectRealtime();
|
|
319
321
|
this.#abortPendingRound('client closed mid-round');
|
|
320
322
|
await this.#lease?.release();
|
|
321
323
|
this.#lease = undefined;
|
|
@@ -1509,11 +1511,13 @@ export class SyncClient {
|
|
|
1509
1511
|
this.#requireActive();
|
|
1510
1512
|
const rebootstrapId = compileLocalDataRebootstrap(input);
|
|
1511
1513
|
const metaKey = localDataRebootstrapMetaKey(rebootstrapId);
|
|
1512
|
-
|
|
1514
|
+
const persistedReceipt = getMeta(this.#db, metaKey);
|
|
1515
|
+
if (persistedReceipt !== undefined) {
|
|
1516
|
+
const receipt = decodeLocalDataRebootstrapReceipt(persistedReceipt);
|
|
1513
1517
|
return {
|
|
1514
1518
|
alreadyApplied: true,
|
|
1515
|
-
retainedCommits:
|
|
1516
|
-
resetSubscriptions:
|
|
1519
|
+
retainedCommits: receipt.retainedCommits,
|
|
1520
|
+
resetSubscriptions: receipt.resetSubscriptions,
|
|
1517
1521
|
};
|
|
1518
1522
|
}
|
|
1519
1523
|
if (this.#schemaFloor !== undefined) {
|
|
@@ -1536,7 +1540,10 @@ export class SyncClient {
|
|
|
1536
1540
|
for (const commit of pending) {
|
|
1537
1541
|
this.#applyOperationsLocally(commit.operations, batch);
|
|
1538
1542
|
}
|
|
1539
|
-
setMeta(this.#db, metaKey,
|
|
1543
|
+
setMeta(this.#db, metaKey, encodeLocalDataRebootstrapReceipt({
|
|
1544
|
+
retainedCommits: pending.length,
|
|
1545
|
+
resetSubscriptions,
|
|
1546
|
+
}));
|
|
1540
1547
|
});
|
|
1541
1548
|
}
|
|
1542
1549
|
catch (error) {
|
|
@@ -1933,31 +1940,59 @@ export class SyncClient {
|
|
|
1933
1940
|
}
|
|
1934
1941
|
// -- realtime (§8 client side) ----------------------------------------------
|
|
1935
1942
|
connectRealtime() {
|
|
1936
|
-
|
|
1943
|
+
this.#requireActive();
|
|
1944
|
+
if (this.#socket !== undefined)
|
|
1945
|
+
return Promise.resolve();
|
|
1946
|
+
if (this.#realtimeConnectPromise !== undefined) {
|
|
1947
|
+
return this.#realtimeConnectPromise;
|
|
1948
|
+
}
|
|
1949
|
+
const generation = this.#realtimeGeneration;
|
|
1950
|
+
const task = this.#runProtectedAsync(() => this.#connectRealtime(generation));
|
|
1951
|
+
this.#realtimeConnectPromise = task;
|
|
1952
|
+
void task.then(() => {
|
|
1953
|
+
if (this.#realtimeConnectPromise === task) {
|
|
1954
|
+
this.#realtimeConnectPromise = undefined;
|
|
1955
|
+
}
|
|
1956
|
+
}, () => {
|
|
1957
|
+
if (this.#realtimeConnectPromise === task) {
|
|
1958
|
+
this.#realtimeConnectPromise = undefined;
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1961
|
+
return task;
|
|
1937
1962
|
}
|
|
1938
|
-
async #connectRealtime() {
|
|
1963
|
+
async #connectRealtime(generation) {
|
|
1939
1964
|
const connector = this.#config.realtime;
|
|
1940
1965
|
if (connector === undefined) {
|
|
1941
1966
|
throw new ClientSyncError('sync.invalid_request', 'no realtime connector configured');
|
|
1942
1967
|
}
|
|
1968
|
+
let openedSocket;
|
|
1943
1969
|
const socket = await connector({
|
|
1944
1970
|
onText: (text) => this.#handleRealtimeText(text),
|
|
1945
1971
|
onBinary: (bytes) => this.#routeRealtimeBinary(bytes),
|
|
1946
1972
|
onClose: () => {
|
|
1973
|
+
if (openedSocket === undefined || this.#socket !== openedSocket)
|
|
1974
|
+
return;
|
|
1947
1975
|
this.#socket = undefined;
|
|
1948
1976
|
this.#presence.clear(); // §8.6.1: presence is per-connection
|
|
1949
1977
|
this.#abortPendingRound('realtime socket closed mid-round (§8.7)');
|
|
1950
1978
|
this.#emitDiagnostics();
|
|
1951
1979
|
},
|
|
1952
1980
|
});
|
|
1981
|
+
openedSocket = socket;
|
|
1953
1982
|
if (this.#securityLifecycle === 'preflight') {
|
|
1954
1983
|
socket.close();
|
|
1955
1984
|
throw new ClientSyncError(SECURITY_PREFLIGHT_REQUIRED_CODE, 'realtime connected after the client entered security preflight');
|
|
1956
1985
|
}
|
|
1986
|
+
if (generation !== this.#realtimeGeneration || !this.#started) {
|
|
1987
|
+
socket.close();
|
|
1988
|
+
throw new ClientSyncError('client.realtime_cancelled', 'realtime connection was cancelled before activation');
|
|
1989
|
+
}
|
|
1957
1990
|
this.#socket = socket;
|
|
1958
1991
|
this.#emitDiagnostics();
|
|
1959
1992
|
}
|
|
1960
1993
|
disconnectRealtime() {
|
|
1994
|
+
this.#realtimeGeneration += 1;
|
|
1995
|
+
this.#realtimeConnectPromise = undefined;
|
|
1961
1996
|
this.#socket?.close();
|
|
1962
1997
|
this.#socket = undefined;
|
|
1963
1998
|
this.#presence.clear(); // §8.6.1: presence is per-connection
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from './outbox.js';
|
|
|
29
29
|
export * from './outcomes.js';
|
|
30
30
|
export * from './query-guard.js';
|
|
31
31
|
export * from './reactive-store.js';
|
|
32
|
+
export * from './realtime-supervisor.js';
|
|
32
33
|
export * from './schema.js';
|
|
33
34
|
export * from './sql-tag.js';
|
|
34
35
|
export * from './state.js';
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export * from './outbox.js';
|
|
|
29
29
|
export * from './outcomes.js';
|
|
30
30
|
export * from './query-guard.js';
|
|
31
31
|
export * from './reactive-store.js';
|
|
32
|
+
export * from './realtime-supervisor.js';
|
|
32
33
|
export * from './schema.js';
|
|
33
34
|
export * from './sql-tag.js';
|
|
34
35
|
export * from './state.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LocalDataRebootstrapReceipt {
|
|
2
|
+
readonly retainedCommits: number;
|
|
3
|
+
readonly resetSubscriptions: number;
|
|
4
|
+
}
|
|
5
|
+
/** Encode only the bounded counts that are safe to replay outside the core. */
|
|
6
|
+
export declare function encodeLocalDataRebootstrapReceipt(receipt: LocalDataRebootstrapReceipt): string;
|
|
7
|
+
/**
|
|
8
|
+
* Decode a committed receipt without leaking its application-owned key. The
|
|
9
|
+
* original counts were not retained by pre-0.15.36 `v1` markers, so those
|
|
10
|
+
* historical repairs keep their former zero-count replay behavior.
|
|
11
|
+
*/
|
|
12
|
+
export declare function decodeLocalDataRebootstrapReceipt(value: string): LocalDataRebootstrapReceipt;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ClientSyncError } from './errors.js';
|
|
2
|
+
const LEGACY_MARKER = 'v1';
|
|
3
|
+
const RECEIPT_VERSION = 2;
|
|
4
|
+
const RECEIPT_KEYS = [
|
|
5
|
+
'resetSubscriptions',
|
|
6
|
+
'retainedCommits',
|
|
7
|
+
'version',
|
|
8
|
+
];
|
|
9
|
+
function invalidReceipt() {
|
|
10
|
+
throw new ClientSyncError('sync.local_corrupt', 'persisted local rebootstrap receipt is invalid');
|
|
11
|
+
}
|
|
12
|
+
function isCount(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
14
|
+
}
|
|
15
|
+
/** Encode only the bounded counts that are safe to replay outside the core. */
|
|
16
|
+
export function encodeLocalDataRebootstrapReceipt(receipt) {
|
|
17
|
+
if (!isCount(receipt.retainedCommits) ||
|
|
18
|
+
!isCount(receipt.resetSubscriptions)) {
|
|
19
|
+
return invalidReceipt();
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify({
|
|
22
|
+
version: RECEIPT_VERSION,
|
|
23
|
+
retainedCommits: receipt.retainedCommits,
|
|
24
|
+
resetSubscriptions: receipt.resetSubscriptions,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decode a committed receipt without leaking its application-owned key. The
|
|
29
|
+
* original counts were not retained by pre-0.15.36 `v1` markers, so those
|
|
30
|
+
* historical repairs keep their former zero-count replay behavior.
|
|
31
|
+
*/
|
|
32
|
+
export function decodeLocalDataRebootstrapReceipt(value) {
|
|
33
|
+
if (value === LEGACY_MARKER) {
|
|
34
|
+
return { retainedCommits: 0, resetSubscriptions: 0 };
|
|
35
|
+
}
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(value);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return invalidReceipt();
|
|
42
|
+
}
|
|
43
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
44
|
+
return invalidReceipt();
|
|
45
|
+
}
|
|
46
|
+
const source = parsed;
|
|
47
|
+
const keys = Object.keys(source).sort();
|
|
48
|
+
if (keys.length !== RECEIPT_KEYS.length ||
|
|
49
|
+
keys.some((key, index) => key !== RECEIPT_KEYS[index]) ||
|
|
50
|
+
source.version !== RECEIPT_VERSION ||
|
|
51
|
+
!isCount(source.retainedCommits) ||
|
|
52
|
+
!isCount(source.resetSubscriptions)) {
|
|
53
|
+
return invalidReceipt();
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
retainedCommits: source.retainedCommits,
|
|
57
|
+
resetSubscriptions: source.resetSubscriptions,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -12,7 +12,11 @@ export declare const INVALID_HOST_RESPONSE_CODE = "client.invalid_host_response"
|
|
|
12
12
|
export interface LocalDataRebootstrapInput {
|
|
13
13
|
readonly rebootstrapId: string;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Privacy-safe acknowledgement; no row or subscription identifiers escape.
|
|
17
|
+
* An idempotent replay sets `alreadyApplied` while retaining the first call's
|
|
18
|
+
* exact counts when that repair was first applied by Syncular 0.15.36 or later.
|
|
19
|
+
*/
|
|
16
20
|
export interface LocalDataRebootstrapResult {
|
|
17
21
|
readonly alreadyApplied: boolean;
|
|
18
22
|
readonly retainedCommits: number;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { SecurityLifecycle } from './client.js';
|
|
2
|
+
import type { ClientDiagnosticsConnectivity, ClientDiagnosticsListener, ClientDiagnosticsSnapshot } from './diagnostics.js';
|
|
3
|
+
type CancelTimer = () => void;
|
|
4
|
+
export interface RealtimeSupervisorClient {
|
|
5
|
+
connectRealtime(): Promise<void>;
|
|
6
|
+
disconnectRealtime(): void | Promise<void>;
|
|
7
|
+
syncUntilIdle(maxRounds?: number): unknown | Promise<unknown>;
|
|
8
|
+
diagnosticsSnapshot(): ClientDiagnosticsSnapshot | Promise<ClientDiagnosticsSnapshot>;
|
|
9
|
+
onDiagnostics(listener: ClientDiagnosticsListener): () => void;
|
|
10
|
+
close(): void | Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface RealtimeSupervisorSignal<State> {
|
|
13
|
+
current(): State;
|
|
14
|
+
subscribe(listener: (state: State) => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
export type RealtimeSupervisorLifecycleState = 'active' | 'background' | 'unknown';
|
|
17
|
+
export type RealtimeSupervisorProtectionState = SecurityLifecycle | 'unknown';
|
|
18
|
+
type RealtimeSuspendedPhase = 'offline' | 'background' | 'protected';
|
|
19
|
+
export type RealtimeSupervisorPhase = 'idle' | 'connecting' | 'connected' | 'retrying' | RealtimeSuspendedPhase | 'unsupported' | 'stopped';
|
|
20
|
+
export interface RealtimeSupervisorSnapshot {
|
|
21
|
+
readonly phase: RealtimeSupervisorPhase;
|
|
22
|
+
/** One-based for retries; zero for the initial connection. */
|
|
23
|
+
readonly attempt: number;
|
|
24
|
+
/** Bounded host-policy delay, never server or transport prose. */
|
|
25
|
+
readonly retryDelayMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface RealtimeSupervisorOptions {
|
|
28
|
+
/** Host online/offline evidence. Unknown remains connectable and observable. */
|
|
29
|
+
readonly connectivity?: RealtimeSupervisorSignal<ClientDiagnosticsConnectivity>;
|
|
30
|
+
/** Browser/native foreground evidence. Background always suspends the socket. */
|
|
31
|
+
readonly lifecycle?: RealtimeSupervisorSignal<RealtimeSupervisorLifecycleState>;
|
|
32
|
+
/** Publish preflight before draining keys so reconnect stops in the same turn. */
|
|
33
|
+
readonly protection?: RealtimeSupervisorSignal<RealtimeSupervisorProtectionState>;
|
|
34
|
+
/** Deterministic test/host timer seam. */
|
|
35
|
+
readonly schedule?: (callback: () => void, delayMs: number) => CancelTimer;
|
|
36
|
+
readonly random?: () => number;
|
|
37
|
+
readonly initialDelayMs?: number;
|
|
38
|
+
readonly maximumDelayMs?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RealtimeSupervisorEventTarget {
|
|
41
|
+
addEventListener(type: string, listener: () => void): void;
|
|
42
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
43
|
+
}
|
|
44
|
+
export interface BrowserConnectivitySignalOptions {
|
|
45
|
+
readonly events?: RealtimeSupervisorEventTarget;
|
|
46
|
+
readonly network?: {
|
|
47
|
+
readonly onLine?: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface DocumentLifecycleSignalOptions {
|
|
51
|
+
readonly events?: RealtimeSupervisorEventTarget;
|
|
52
|
+
readonly document?: {
|
|
53
|
+
readonly visibilityState?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Browser/Tauri-webview connectivity evidence without importing DOM types. */
|
|
57
|
+
export declare function browserConnectivitySignal(options?: BrowserConnectivitySignalOptions): RealtimeSupervisorSignal<ClientDiagnosticsConnectivity>;
|
|
58
|
+
/** Browser/Tauri-webview visibility evidence without importing DOM types. */
|
|
59
|
+
export declare function documentLifecycleSignal(options?: DocumentLifecycleSignalOptions): RealtimeSupervisorSignal<RealtimeSupervisorLifecycleState>;
|
|
60
|
+
/**
|
|
61
|
+
* Supported host policy for Syncular's explicit realtime transport. It owns
|
|
62
|
+
* exactly one connect attempt, runs an explicit catch-up round before claiming
|
|
63
|
+
* connected, retries transient loss with bounded exponential jitter, and
|
|
64
|
+
* suspends across offline, background, or protected-preflight state.
|
|
65
|
+
*/
|
|
66
|
+
export declare class RealtimeSupervisor {
|
|
67
|
+
#private;
|
|
68
|
+
constructor(client: RealtimeSupervisorClient, options?: RealtimeSupervisorOptions);
|
|
69
|
+
snapshot(): RealtimeSupervisorSnapshot;
|
|
70
|
+
subscribe(listener: () => void): () => void;
|
|
71
|
+
start(): void;
|
|
72
|
+
stop(): void;
|
|
73
|
+
}
|
|
74
|
+
/** Install one supervisor and make client disposal cancel it before close. */
|
|
75
|
+
export declare function installRealtimeSupervisor<T extends RealtimeSupervisorClient>(client: T, options?: RealtimeSupervisorOptions): T;
|
|
76
|
+
export declare function realtimeSupervisorSnapshot(client: object): RealtimeSupervisorSnapshot;
|
|
77
|
+
export declare function subscribeRealtimeSupervisor(client: object, listener: () => void): () => void;
|
|
78
|
+
export {};
|