@syncular/client 0.15.36 → 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 +36 -2
- package/dist/client.js +33 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/realtime-supervisor.d.ts +78 -0
- package/dist/realtime-supervisor.js +452 -0
- package/package.json +3 -3
- package/src/client.ts +39 -4
- package/src/index.ts +1 -0
- package/src/realtime-supervisor.ts +598 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.37",
|
|
4
4
|
"description": "Syncular TypeScript client core — offline-first sync over SQLite (WASM/OPFS, Bun, Node)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
84
|
-
"@syncular/core": "0.15.
|
|
84
|
+
"@syncular/core": "0.15.37"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"better-sqlite3": ">=11"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@syncular/server": "0.15.
|
|
95
|
+
"@syncular/server": "0.15.37",
|
|
96
96
|
"@types/better-sqlite3": "^7.6.13",
|
|
97
97
|
"better-sqlite3": "^12.11.1"
|
|
98
98
|
}
|
package/src/client.ts
CHANGED
|
@@ -545,6 +545,8 @@ export class SyncClient {
|
|
|
545
545
|
#conflicts: ConflictRecord[] = [];
|
|
546
546
|
#rejections: RejectionRecord[] = [];
|
|
547
547
|
#socket: RealtimeSocket | undefined;
|
|
548
|
+
#realtimeConnectPromise: Promise<void> | undefined;
|
|
549
|
+
#realtimeGeneration = 0;
|
|
548
550
|
#pendingRound: PendingRound | undefined;
|
|
549
551
|
#needsPull = false;
|
|
550
552
|
#syncing = false;
|
|
@@ -793,8 +795,7 @@ export class SyncClient {
|
|
|
793
795
|
async close(): Promise<void> {
|
|
794
796
|
this.#devtoolsUnregister?.();
|
|
795
797
|
this.#devtoolsUnregister = undefined;
|
|
796
|
-
this
|
|
797
|
-
this.#socket = undefined;
|
|
798
|
+
this.disconnectRealtime();
|
|
798
799
|
this.#abortPendingRound('client closed mid-round');
|
|
799
800
|
await this.#lease?.release();
|
|
800
801
|
this.#lease = undefined;
|
|
@@ -2758,10 +2759,32 @@ export class SyncClient {
|
|
|
2758
2759
|
// -- realtime (§8 client side) ----------------------------------------------
|
|
2759
2760
|
|
|
2760
2761
|
connectRealtime(): Promise<void> {
|
|
2761
|
-
|
|
2762
|
+
this.#requireActive();
|
|
2763
|
+
if (this.#socket !== undefined) return Promise.resolve();
|
|
2764
|
+
if (this.#realtimeConnectPromise !== undefined) {
|
|
2765
|
+
return this.#realtimeConnectPromise;
|
|
2766
|
+
}
|
|
2767
|
+
const generation = this.#realtimeGeneration;
|
|
2768
|
+
const task = this.#runProtectedAsync(() =>
|
|
2769
|
+
this.#connectRealtime(generation),
|
|
2770
|
+
);
|
|
2771
|
+
this.#realtimeConnectPromise = task;
|
|
2772
|
+
void task.then(
|
|
2773
|
+
() => {
|
|
2774
|
+
if (this.#realtimeConnectPromise === task) {
|
|
2775
|
+
this.#realtimeConnectPromise = undefined;
|
|
2776
|
+
}
|
|
2777
|
+
},
|
|
2778
|
+
() => {
|
|
2779
|
+
if (this.#realtimeConnectPromise === task) {
|
|
2780
|
+
this.#realtimeConnectPromise = undefined;
|
|
2781
|
+
}
|
|
2782
|
+
},
|
|
2783
|
+
);
|
|
2784
|
+
return task;
|
|
2762
2785
|
}
|
|
2763
2786
|
|
|
2764
|
-
async #connectRealtime(): Promise<void> {
|
|
2787
|
+
async #connectRealtime(generation: number): Promise<void> {
|
|
2765
2788
|
const connector = this.#config.realtime;
|
|
2766
2789
|
if (connector === undefined) {
|
|
2767
2790
|
throw new ClientSyncError(
|
|
@@ -2769,16 +2792,19 @@ export class SyncClient {
|
|
|
2769
2792
|
'no realtime connector configured',
|
|
2770
2793
|
);
|
|
2771
2794
|
}
|
|
2795
|
+
let openedSocket: RealtimeSocket | undefined;
|
|
2772
2796
|
const socket = await connector({
|
|
2773
2797
|
onText: (text) => this.#handleRealtimeText(text),
|
|
2774
2798
|
onBinary: (bytes) => this.#routeRealtimeBinary(bytes),
|
|
2775
2799
|
onClose: () => {
|
|
2800
|
+
if (openedSocket === undefined || this.#socket !== openedSocket) return;
|
|
2776
2801
|
this.#socket = undefined;
|
|
2777
2802
|
this.#presence.clear(); // §8.6.1: presence is per-connection
|
|
2778
2803
|
this.#abortPendingRound('realtime socket closed mid-round (§8.7)');
|
|
2779
2804
|
this.#emitDiagnostics();
|
|
2780
2805
|
},
|
|
2781
2806
|
});
|
|
2807
|
+
openedSocket = socket;
|
|
2782
2808
|
if (this.#securityLifecycle === 'preflight') {
|
|
2783
2809
|
socket.close();
|
|
2784
2810
|
throw new ClientSyncError(
|
|
@@ -2786,11 +2812,20 @@ export class SyncClient {
|
|
|
2786
2812
|
'realtime connected after the client entered security preflight',
|
|
2787
2813
|
);
|
|
2788
2814
|
}
|
|
2815
|
+
if (generation !== this.#realtimeGeneration || !this.#started) {
|
|
2816
|
+
socket.close();
|
|
2817
|
+
throw new ClientSyncError(
|
|
2818
|
+
'client.realtime_cancelled',
|
|
2819
|
+
'realtime connection was cancelled before activation',
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2789
2822
|
this.#socket = socket;
|
|
2790
2823
|
this.#emitDiagnostics();
|
|
2791
2824
|
}
|
|
2792
2825
|
|
|
2793
2826
|
disconnectRealtime(): void {
|
|
2827
|
+
this.#realtimeGeneration += 1;
|
|
2828
|
+
this.#realtimeConnectPromise = undefined;
|
|
2794
2829
|
this.#socket?.close();
|
|
2795
2830
|
this.#socket = undefined;
|
|
2796
2831
|
this.#presence.clear(); // §8.6.1: presence is per-connection
|
package/src/index.ts
CHANGED