@syncular/client 0.15.47 → 0.16.1
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 +59 -0
- package/dist/bun-database.d.ts +5 -0
- package/dist/bun-database.js +5 -0
- package/dist/client.d.ts +17 -20
- package/dist/client.js +152 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/outbox.d.ts +5 -1
- package/dist/outbox.js +26 -3
- package/dist/reactive-store.d.ts +12 -6
- package/dist/reactive-store.js +219 -45
- package/dist/realtime-supervisor.d.ts +1 -1
- package/dist/realtime-supervisor.js +2 -8
- package/dist/remote.d.ts +2 -0
- package/dist/remote.js +10 -2
- package/dist/sync-scheduler.d.ts +23 -0
- package/dist/sync-scheduler.js +122 -0
- package/dist/window.d.ts +5 -0
- package/dist/window.js +39 -0
- package/dist/worker-entry.js +8 -19
- package/dist/worker-host.d.ts +4 -7
- package/dist/worker-host.js +3 -15
- package/dist/worker-protocol.d.ts +4 -18
- package/package.json +3 -11
- package/src/bun-database.ts +11 -0
- package/src/client.ts +203 -63
- package/src/index.ts +1 -0
- package/src/outbox.ts +39 -8
- package/src/reactive-store.ts +226 -56
- package/src/realtime-supervisor.ts +2 -11
- package/src/remote.ts +11 -2
- package/src/sync-scheduler.ts +154 -0
- package/src/window.ts +65 -0
- package/src/worker-entry.ts +10 -21
- package/src/worker-host.ts +5 -22
- package/src/worker-protocol.ts +7 -33
- package/dist/realtime-supervisor-observation.d.ts +0 -8
- package/dist/realtime-supervisor-observation.js +0 -15
- package/src/realtime-supervisor-observation.ts +0 -21
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { WakeReason } from '@syncular/core';
|
|
2
|
+
import type { SecurityLifecycle } from './client';
|
|
3
|
+
import type { SyncIntent } from './invalidation';
|
|
4
|
+
|
|
5
|
+
export interface SyncSchedulerClient {
|
|
6
|
+
readonly syncNeeded: boolean;
|
|
7
|
+
readonly securityLifecycle: SecurityLifecycle;
|
|
8
|
+
syncUntilIdle(maxRounds?: number): Promise<unknown>;
|
|
9
|
+
onSyncNeeded(
|
|
10
|
+
listener: (reason: 'startup' | 'hello' | WakeReason) => void,
|
|
11
|
+
): () => void;
|
|
12
|
+
onSyncIntent(listener: (intent: SyncIntent) => void): () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SyncSchedulerOptions {
|
|
16
|
+
readonly maxRounds?: number;
|
|
17
|
+
readonly onError?: (error: unknown) => void;
|
|
18
|
+
readonly now?: () => number;
|
|
19
|
+
readonly queueMicrotask?: (callback: () => void) => void;
|
|
20
|
+
readonly schedule?: (callback: () => void, delayMs: number) => () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SyncScheduler {
|
|
24
|
+
readonly stopped: boolean;
|
|
25
|
+
stop(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Install the event-driven single-flight host loop for a direct client. */
|
|
29
|
+
export function installSyncScheduler(
|
|
30
|
+
client: SyncSchedulerClient,
|
|
31
|
+
options: SyncSchedulerOptions = {},
|
|
32
|
+
): SyncScheduler {
|
|
33
|
+
const now = options.now ?? Date.now;
|
|
34
|
+
const enqueue = options.queueMicrotask ?? globalThis.queueMicrotask;
|
|
35
|
+
const schedule =
|
|
36
|
+
options.schedule ??
|
|
37
|
+
((callback: () => void, delayMs: number): (() => void) => {
|
|
38
|
+
const timer = globalThis.setTimeout(callback, delayMs);
|
|
39
|
+
return () => globalThis.clearTimeout(timer);
|
|
40
|
+
});
|
|
41
|
+
let stopped = false;
|
|
42
|
+
let running = false;
|
|
43
|
+
let immediatePending = false;
|
|
44
|
+
let immediateQueued = false;
|
|
45
|
+
let backgroundReady = false;
|
|
46
|
+
let backgroundDue = Number.POSITIVE_INFINITY;
|
|
47
|
+
let cancelBackground: (() => void) | undefined;
|
|
48
|
+
|
|
49
|
+
const report = (error: unknown): void => {
|
|
50
|
+
if (options.onError !== undefined) {
|
|
51
|
+
options.onError(error);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const root: typeof globalThis & {
|
|
55
|
+
reportError?: (error: unknown) => void;
|
|
56
|
+
} = globalThis;
|
|
57
|
+
if (root.reportError !== undefined) root.reportError(error);
|
|
58
|
+
else console.error(error);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const clearBackground = (): void => {
|
|
62
|
+
cancelBackground?.();
|
|
63
|
+
cancelBackground = undefined;
|
|
64
|
+
backgroundReady = false;
|
|
65
|
+
backgroundDue = Number.POSITIVE_INFINITY;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const queueImmediate = (): void => {
|
|
69
|
+
immediatePending = true;
|
|
70
|
+
if (stopped || running || immediateQueued) return;
|
|
71
|
+
immediateQueued = true;
|
|
72
|
+
enqueue(() => {
|
|
73
|
+
immediateQueued = false;
|
|
74
|
+
if (stopped || !immediatePending) return;
|
|
75
|
+
immediatePending = false;
|
|
76
|
+
run();
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const run = (): void => {
|
|
81
|
+
if (stopped || running) return;
|
|
82
|
+
if (client.securityLifecycle === 'preflight') {
|
|
83
|
+
immediatePending = false;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
running = true;
|
|
87
|
+
backgroundReady = false;
|
|
88
|
+
void client
|
|
89
|
+
.syncUntilIdle(options.maxRounds)
|
|
90
|
+
.catch((error: unknown) => {
|
|
91
|
+
if (!stopped) report(error);
|
|
92
|
+
})
|
|
93
|
+
.finally(() => {
|
|
94
|
+
running = false;
|
|
95
|
+
if (stopped) return;
|
|
96
|
+
if (immediatePending || backgroundReady) {
|
|
97
|
+
queueImmediate();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (cancelBackground !== undefined && backgroundDue <= now()) {
|
|
101
|
+
clearBackground();
|
|
102
|
+
queueImmediate();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const consume = (intent: SyncIntent): void => {
|
|
108
|
+
if (stopped) return;
|
|
109
|
+
if (intent.kind === 'none') {
|
|
110
|
+
clearBackground();
|
|
111
|
+
immediatePending = false;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (intent.kind === 'interactive') {
|
|
115
|
+
clearBackground();
|
|
116
|
+
queueImmediate();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (immediatePending || immediateQueued) return;
|
|
120
|
+
clearBackground();
|
|
121
|
+
backgroundDue = now() + Math.max(0, intent.delayMs);
|
|
122
|
+
cancelBackground = schedule(
|
|
123
|
+
() => {
|
|
124
|
+
cancelBackground = undefined;
|
|
125
|
+
backgroundDue = Number.POSITIVE_INFINITY;
|
|
126
|
+
backgroundReady = true;
|
|
127
|
+
if (!running) queueImmediate();
|
|
128
|
+
},
|
|
129
|
+
Math.max(0, intent.delayMs),
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const unsubscribeNeeded = client.onSyncNeeded(() => {
|
|
134
|
+
consume({ kind: 'interactive' });
|
|
135
|
+
});
|
|
136
|
+
const unsubscribeIntent = client.onSyncIntent(consume);
|
|
137
|
+
if (client.syncNeeded && client.securityLifecycle === 'active') {
|
|
138
|
+
consume({ kind: 'interactive' });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
get stopped() {
|
|
143
|
+
return stopped;
|
|
144
|
+
},
|
|
145
|
+
stop() {
|
|
146
|
+
if (stopped) return;
|
|
147
|
+
stopped = true;
|
|
148
|
+
clearBackground();
|
|
149
|
+
immediatePending = false;
|
|
150
|
+
unsubscribeNeeded();
|
|
151
|
+
unsubscribeIntent();
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
package/src/window.ts
CHANGED
|
@@ -14,6 +14,71 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { canonicalScopeJson, type ScopeMap } from '@syncular/core';
|
|
16
16
|
import type { ClientDatabase } from './database';
|
|
17
|
+
import { ClientSyncError } from './errors';
|
|
18
|
+
|
|
19
|
+
export type TimeBucketUnit = 'month';
|
|
20
|
+
|
|
21
|
+
const MAX_TIME_BUCKET_MS = 253_402_300_799_999;
|
|
22
|
+
|
|
23
|
+
function monthBucket(year: number, month: number): string {
|
|
24
|
+
return `${year.toString().padStart(4, '0')}-${month.toString().padStart(2, '0')}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Derive the immutable UTC scope value stored when a row is created. */
|
|
28
|
+
export function creationTimeBucket(
|
|
29
|
+
createdAtMs: number,
|
|
30
|
+
unit: TimeBucketUnit,
|
|
31
|
+
): string {
|
|
32
|
+
if (
|
|
33
|
+
unit !== 'month' ||
|
|
34
|
+
!Number.isSafeInteger(createdAtMs) ||
|
|
35
|
+
createdAtMs < 0 ||
|
|
36
|
+
createdAtMs > MAX_TIME_BUCKET_MS
|
|
37
|
+
) {
|
|
38
|
+
throw new ClientSyncError(
|
|
39
|
+
'sync.invalid_request',
|
|
40
|
+
'creationTimeBucket requires a supported unit and a UTC timestamp from 1970 through 9999',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const date = new Date(createdAtMs);
|
|
44
|
+
return monthBucket(date.getUTCFullYear(), date.getUTCMonth() + 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Return a rolling UTC month window ordered from oldest to newest. */
|
|
48
|
+
export function last(
|
|
49
|
+
count: number,
|
|
50
|
+
unit: TimeBucketUnit,
|
|
51
|
+
nowMs = Date.now(),
|
|
52
|
+
): string[] {
|
|
53
|
+
if (
|
|
54
|
+
unit !== 'month' ||
|
|
55
|
+
!Number.isSafeInteger(count) ||
|
|
56
|
+
count < 1 ||
|
|
57
|
+
count > 1_200 ||
|
|
58
|
+
!Number.isSafeInteger(nowMs) ||
|
|
59
|
+
nowMs < 0 ||
|
|
60
|
+
nowMs > MAX_TIME_BUCKET_MS
|
|
61
|
+
) {
|
|
62
|
+
throw new ClientSyncError(
|
|
63
|
+
'sync.invalid_request',
|
|
64
|
+
'last requires a supported unit, a count from 1 through 1200, and a UTC timestamp from 1970 through 9999',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const date = new Date(nowMs);
|
|
68
|
+
const current = date.getUTCFullYear() * 12 + date.getUTCMonth();
|
|
69
|
+
if (current - (count - 1) < 1970 * 12) {
|
|
70
|
+
throw new ClientSyncError(
|
|
71
|
+
'sync.invalid_request',
|
|
72
|
+
'last requires every returned UTC month to fall from 1970 through 9999',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const units: string[] = [];
|
|
76
|
+
for (let offset = count - 1; offset >= 0; offset -= 1) {
|
|
77
|
+
const value = current - offset;
|
|
78
|
+
units.push(monthBucket(Math.floor(value / 12), (value % 12) + 1));
|
|
79
|
+
}
|
|
80
|
+
return units;
|
|
81
|
+
}
|
|
17
82
|
|
|
18
83
|
/**
|
|
19
84
|
* A window base: one table, one variable whose values are the window
|
package/src/worker-entry.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
httpSyncTransport,
|
|
28
28
|
webSocketRealtimeConnector,
|
|
29
29
|
} from './http';
|
|
30
|
-
import type {
|
|
30
|
+
import type { SyncIntent } from './invalidation';
|
|
31
31
|
import type {
|
|
32
32
|
RealtimeConnector,
|
|
33
33
|
SegmentDownloader,
|
|
@@ -151,7 +151,7 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
151
151
|
if (
|
|
152
152
|
closed ||
|
|
153
153
|
client === undefined ||
|
|
154
|
-
client.securityLifecycle === 'preflight'
|
|
154
|
+
client.securityLifecycle() === 'preflight'
|
|
155
155
|
) {
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
@@ -175,7 +175,7 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
175
175
|
!autoSync ||
|
|
176
176
|
closed ||
|
|
177
177
|
client === undefined ||
|
|
178
|
-
client.securityLifecycle === 'preflight' ||
|
|
178
|
+
client.securityLifecycle() === 'preflight' ||
|
|
179
179
|
intent.kind === 'none'
|
|
180
180
|
) {
|
|
181
181
|
return;
|
|
@@ -211,10 +211,6 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
211
211
|
queueMicrotask(runAutoSync);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
function consumeEffects(effects: CommandEffects): void {
|
|
215
|
-
consumeSyncIntent(effects.sync);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
214
|
function requireClient(): SyncClient {
|
|
219
215
|
if (client === undefined) {
|
|
220
216
|
throw new ClientSyncError(
|
|
@@ -375,12 +371,13 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
375
371
|
// `start()` may discover persisted subscriptions/outbox work. Its callback
|
|
376
372
|
// fires before this worker publishes the initialized client, so consume
|
|
377
373
|
// the durable state once here as well; coalescing makes this a single task.
|
|
378
|
-
if (started.syncNeeded)
|
|
374
|
+
if (started.statusSnapshot().syncNeeded)
|
|
375
|
+
consumeSyncIntent({ kind: 'interactive' });
|
|
379
376
|
return { clientId: started.clientId };
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
const api: WorkerApi = {
|
|
383
|
-
securityLifecycle: () => requireClient().securityLifecycle,
|
|
380
|
+
securityLifecycle: () => requireClient().securityLifecycle(),
|
|
384
381
|
beginSecurityPreflight: async () => {
|
|
385
382
|
if (backgroundTimer !== undefined) clearTimeout(backgroundTimer);
|
|
386
383
|
backgroundTimer = undefined;
|
|
@@ -398,14 +395,11 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
398
395
|
subscribe: (input) => requireClient().subscribe(input),
|
|
399
396
|
unsubscribe: (id) => requireClient().unsubscribe(id),
|
|
400
397
|
setWindow: async (base, units) => {
|
|
401
|
-
|
|
402
|
-
consumeEffects(result.effects);
|
|
398
|
+
await requireClient().setWindowCommand(base, units);
|
|
403
399
|
},
|
|
404
400
|
windowState: (base) => requireClient().windowState(base),
|
|
405
401
|
mutate: (mutations) => {
|
|
406
|
-
|
|
407
|
-
consumeEffects(result.effects);
|
|
408
|
-
return result.value;
|
|
402
|
+
return requireClient().mutateCommand(mutations).value;
|
|
409
403
|
},
|
|
410
404
|
patch: (table, rowId, partial, options) => {
|
|
411
405
|
// Same §8.4 rule as `mutate`: a local write must push without the app
|
|
@@ -416,7 +410,6 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
416
410
|
partial,
|
|
417
411
|
options,
|
|
418
412
|
);
|
|
419
|
-
consumeEffects(result.effects);
|
|
420
413
|
return result.value;
|
|
421
414
|
},
|
|
422
415
|
purgeLocalData: (input) => requireClient().purgeLocalData(input),
|
|
@@ -443,17 +436,13 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
443
436
|
realtime: snapshot.host.realtime,
|
|
444
437
|
});
|
|
445
438
|
},
|
|
446
|
-
conflicts: () => requireClient().conflicts,
|
|
447
|
-
rejections: () => requireClient().rejections,
|
|
439
|
+
conflicts: () => requireClient().conflicts(),
|
|
440
|
+
rejections: () => requireClient().rejections(),
|
|
448
441
|
commitOutcome: (clientCommitId) =>
|
|
449
442
|
requireClient().commitOutcome(clientCommitId),
|
|
450
443
|
commitOutcomes: (query) => requireClient().commitOutcomes(query),
|
|
451
444
|
resolveCommitOutcome: (input) =>
|
|
452
445
|
requireClient().resolveCommitOutcome(input),
|
|
453
|
-
schemaFloor: () => requireClient().schemaFloor,
|
|
454
|
-
leaseState: () => requireClient().leaseState,
|
|
455
|
-
upgrading: () => requireClient().upgrading,
|
|
456
|
-
syncNeeded: () => requireClient().syncNeeded,
|
|
457
446
|
pendingCommits: () => requireClient().pendingCommits(),
|
|
458
447
|
subscriptions: () => requireClient().subscriptions(),
|
|
459
448
|
subscription: (id) => requireClient().subscription(id),
|
package/src/worker-host.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PromiseMethods } from './client';
|
|
1
2
|
/**
|
|
2
3
|
* Main-thread side of the worker mode and the
|
|
3
4
|
* multi-tab topology.
|
|
@@ -25,13 +26,11 @@ import type { WakeReason } from '@syncular/core';
|
|
|
25
26
|
import type { BlobRef, CachedBlob } from './blob';
|
|
26
27
|
import type {
|
|
27
28
|
ConflictRecord,
|
|
28
|
-
LeaseState,
|
|
29
29
|
MutationInput,
|
|
30
30
|
PresencePeer,
|
|
31
31
|
QueryReadSpec,
|
|
32
32
|
QuerySnapshot,
|
|
33
33
|
RejectionRecord,
|
|
34
|
-
SchemaFloor,
|
|
35
34
|
SecurityLifecycle,
|
|
36
35
|
SubscribeInput,
|
|
37
36
|
SyncClientLimits,
|
|
@@ -244,7 +243,7 @@ interface LeaderCore {
|
|
|
244
243
|
* promise. `role` is `'leader'` (owns the worker) or `'follower'` (proxies to
|
|
245
244
|
* the leader over the channel). Constructed via {@link createSyncClientHandle}.
|
|
246
245
|
*/
|
|
247
|
-
export class SyncClientHandle {
|
|
246
|
+
export class SyncClientHandle implements PromiseMethods<WorkerApi> {
|
|
248
247
|
/** True only for a leader handle. Kept for the pre-multiTab contract. */
|
|
249
248
|
get isLeader(): boolean {
|
|
250
249
|
return this.#role === 'leader';
|
|
@@ -324,12 +323,12 @@ export class SyncClientHandle {
|
|
|
324
323
|
ref: this,
|
|
325
324
|
clientId: () => this.#clientId,
|
|
326
325
|
role: () => this.#role,
|
|
327
|
-
outbox: async () => (await this.
|
|
326
|
+
outbox: async () => (await this.statusSnapshot()).outbox,
|
|
328
327
|
subscriptions: () => this.subscriptions(),
|
|
329
328
|
conflicts: async () => (await this.conflicts()).length,
|
|
330
329
|
rejections: async () => (await this.rejections()).length,
|
|
331
|
-
syncNeeded: () => this.
|
|
332
|
-
upgrading: () => this.
|
|
330
|
+
syncNeeded: async () => (await this.statusSnapshot()).syncNeeded,
|
|
331
|
+
upgrading: async () => (await this.statusSnapshot()).upgrading,
|
|
333
332
|
onInvalidate: (listener) => this.onInvalidate(listener),
|
|
334
333
|
});
|
|
335
334
|
}
|
|
@@ -593,23 +592,7 @@ export class SyncClientHandle {
|
|
|
593
592
|
return this.#call('resolveCommitOutcome', [input]);
|
|
594
593
|
}
|
|
595
594
|
|
|
596
|
-
schemaFloor(): Promise<SchemaFloor | undefined> {
|
|
597
|
-
return this.#call('schemaFloor', []);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
leaseState(): Promise<LeaseState | undefined> {
|
|
601
|
-
return this.#call('leaseState', []);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
595
|
/** §7.4.5: true while a schema-bump reset + first re-bootstrap runs. */
|
|
605
|
-
upgrading(): Promise<boolean> {
|
|
606
|
-
return this.#call('upgrading', []);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
syncNeeded(): Promise<boolean> {
|
|
610
|
-
return this.#call('syncNeeded', []);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
596
|
pendingCommits(): Promise<OutboxCommit[]> {
|
|
614
597
|
return this.#call('pendingCommits', []);
|
|
615
598
|
}
|
package/src/worker-protocol.ts
CHANGED
|
@@ -20,13 +20,11 @@ import type { WakeReason } from '@syncular/core';
|
|
|
20
20
|
import type { BlobRef, CachedBlob } from './blob';
|
|
21
21
|
import type {
|
|
22
22
|
ConflictRecord,
|
|
23
|
-
|
|
23
|
+
ClientSnapshotMethods,
|
|
24
24
|
MutationInput,
|
|
25
25
|
PresencePeer,
|
|
26
26
|
QueryReadSpec,
|
|
27
27
|
QuerySnapshot,
|
|
28
|
-
RejectionRecord,
|
|
29
|
-
SchemaFloor,
|
|
30
28
|
SecurityLifecycle,
|
|
31
29
|
SubscribeInput,
|
|
32
30
|
SyncClientLimits,
|
|
@@ -34,27 +32,15 @@ import type {
|
|
|
34
32
|
WindowState,
|
|
35
33
|
} from './client';
|
|
36
34
|
import type { SqlRow, SqlValue } from './database';
|
|
37
|
-
import type {
|
|
38
|
-
ClientDiagnosticsRequest,
|
|
39
|
-
ClientDiagnosticsSnapshot,
|
|
40
|
-
} from './diagnostics';
|
|
35
|
+
import type { ClientDiagnosticsSnapshot } from './diagnostics';
|
|
41
36
|
import type { EncryptionKeyringConfig } from './encryption';
|
|
42
|
-
import type {
|
|
43
|
-
ClientChangeBatch,
|
|
44
|
-
LocalRevision,
|
|
45
|
-
SyncStatusSnapshot,
|
|
46
|
-
} from './invalidation';
|
|
37
|
+
import type { ClientChangeBatch, LocalRevision } from './invalidation';
|
|
47
38
|
import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge';
|
|
48
39
|
import type {
|
|
49
40
|
LocalDataRebootstrapInput,
|
|
50
41
|
LocalDataRebootstrapResult,
|
|
51
42
|
} from './local-rebootstrap';
|
|
52
43
|
import type { OutboxCommit } from './outbox';
|
|
53
|
-
import type {
|
|
54
|
-
CommitOutcome,
|
|
55
|
-
CommitOutcomeQuery,
|
|
56
|
-
ResolveCommitOutcomeInput,
|
|
57
|
-
} from './outcomes';
|
|
58
44
|
import type { ClientSchema } from './schema';
|
|
59
45
|
import type { SubscriptionRecord } from './state';
|
|
60
46
|
import type { WindowBase } from './window';
|
|
@@ -137,7 +123,10 @@ export interface WorkerSecurityActivation {
|
|
|
137
123
|
// The logical API — the one shared shape (worker implements, handle projects)
|
|
138
124
|
// ---------------------------------------------------------------------------
|
|
139
125
|
|
|
140
|
-
export interface WorkerApi
|
|
126
|
+
export interface WorkerApi extends Omit<
|
|
127
|
+
ClientSnapshotMethods,
|
|
128
|
+
'querySnapshot'
|
|
129
|
+
> {
|
|
141
130
|
securityLifecycle(): SecurityLifecycle;
|
|
142
131
|
beginSecurityPreflight(): Promise<void>;
|
|
143
132
|
activateSecurity(options?: WorkerSecurityActivation): Promise<void>;
|
|
@@ -166,21 +155,6 @@ export interface WorkerApi {
|
|
|
166
155
|
query(sql: string, params?: readonly SqlValue[]): SqlRow[];
|
|
167
156
|
querySnapshot(spec: QueryReadSpec): QuerySnapshot;
|
|
168
157
|
localRevision(): LocalRevision;
|
|
169
|
-
statusSnapshot(): SyncStatusSnapshot;
|
|
170
|
-
diagnosticsSnapshot(
|
|
171
|
-
request?: ClientDiagnosticsRequest,
|
|
172
|
-
): ClientDiagnosticsSnapshot;
|
|
173
|
-
conflicts(): readonly ConflictRecord[];
|
|
174
|
-
rejections(): readonly RejectionRecord[];
|
|
175
|
-
commitOutcome(clientCommitId: string): CommitOutcome | undefined;
|
|
176
|
-
commitOutcomes(query?: CommitOutcomeQuery): readonly CommitOutcome[];
|
|
177
|
-
resolveCommitOutcome(input: ResolveCommitOutcomeInput): CommitOutcome;
|
|
178
|
-
schemaFloor(): SchemaFloor | undefined;
|
|
179
|
-
/** §7.3.5: the opaque auth-lease state, or undefined. */
|
|
180
|
-
leaseState(): LeaseState | undefined;
|
|
181
|
-
/** §7.4.5: true while a schema-bump reset + first re-bootstrap runs. */
|
|
182
|
-
upgrading(): boolean;
|
|
183
|
-
syncNeeded(): boolean;
|
|
184
158
|
pendingCommits(): OutboxCommit[];
|
|
185
159
|
subscriptions(): SubscriptionRecord[];
|
|
186
160
|
subscription(id: string): SubscriptionRecord | undefined;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preserve supervisor observation across a facade without transferring
|
|
3
|
-
* transport ownership or exposing the source client. Binding packages use
|
|
4
|
-
* this when they normalize a client into another object identity.
|
|
5
|
-
*/
|
|
6
|
-
export declare function linkRealtimeSupervisorObservation<Target extends object>(target: Target, source: object): Target;
|
|
7
|
-
/** @internal Resolve one facade hop for the supervisor attachment lookup. */
|
|
8
|
-
export declare function realtimeSupervisorObservationSource(target: object): object | undefined;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const observationSources = new WeakMap();
|
|
2
|
-
/**
|
|
3
|
-
* Preserve supervisor observation across a facade without transferring
|
|
4
|
-
* transport ownership or exposing the source client. Binding packages use
|
|
5
|
-
* this when they normalize a client into another object identity.
|
|
6
|
-
*/
|
|
7
|
-
export function linkRealtimeSupervisorObservation(target, source) {
|
|
8
|
-
if (target !== source)
|
|
9
|
-
observationSources.set(target, source);
|
|
10
|
-
return target;
|
|
11
|
-
}
|
|
12
|
-
/** @internal Resolve one facade hop for the supervisor attachment lookup. */
|
|
13
|
-
export function realtimeSupervisorObservationSource(target) {
|
|
14
|
-
return observationSources.get(target);
|
|
15
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const observationSources = new WeakMap<object, object>();
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Preserve supervisor observation across a facade without transferring
|
|
5
|
-
* transport ownership or exposing the source client. Binding packages use
|
|
6
|
-
* this when they normalize a client into another object identity.
|
|
7
|
-
*/
|
|
8
|
-
export function linkRealtimeSupervisorObservation<Target extends object>(
|
|
9
|
-
target: Target,
|
|
10
|
-
source: object,
|
|
11
|
-
): Target {
|
|
12
|
-
if (target !== source) observationSources.set(target, source);
|
|
13
|
-
return target;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** @internal Resolve one facade hop for the supervisor attachment lookup. */
|
|
17
|
-
export function realtimeSupervisorObservationSource(
|
|
18
|
-
target: object,
|
|
19
|
-
): object | undefined {
|
|
20
|
-
return observationSources.get(target);
|
|
21
|
-
}
|