@quereus/sync-client 0.1.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/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +30 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/serialization.d.ts +37 -0
- package/dist/src/serialization.d.ts.map +1 -0
- package/dist/src/serialization.js +96 -0
- package/dist/src/serialization.js.map +1 -0
- package/dist/src/sync-client.d.ts +71 -0
- package/dist/src/sync-client.d.ts.map +1 -0
- package/dist/src/sync-client.js +342 -0
- package/dist/src/sync-client.js.map +1 -0
- package/dist/src/types.d.ts +178 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quereus/sync-client - WebSocket sync client for Quereus
|
|
3
|
+
*
|
|
4
|
+
* This package provides a WebSocket-based sync client that connects to a
|
|
5
|
+
* sync server and handles bidirectional synchronization of changes.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic reconnection with exponential backoff
|
|
9
|
+
* - Local change batching with debouncing
|
|
10
|
+
* - Delta sync tracking (only send/receive changes since last sync)
|
|
11
|
+
* - Event callbacks for status changes, remote changes, and errors
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { SyncClient } from '@quereus/sync-client';
|
|
16
|
+
*
|
|
17
|
+
* const client = new SyncClient({
|
|
18
|
+
* syncManager,
|
|
19
|
+
* onStatusChange: (status) => console.log('Status:', status),
|
|
20
|
+
* onRemoteChanges: (result) => console.log('Applied:', result.applied),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* await client.connect('ws://localhost:8080/sync');
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
export { SyncClient } from './sync-client.js';
|
|
29
|
+
export { serializeChangeSet, deserializeChangeSet, serializeHLCForTransport, deserializeHLCFromTransport, } from './serialization.js';
|
|
30
|
+
export type { SyncStatus, SyncEvent, SyncEventType, SyncClientOptions, SerializedChangeSet, SerializedChange, SerializedSchemaMigration, ClientMessage, ServerMessage, HandshakeMessage, HandshakeAckMessage, GetChangesMessage, ChangesMessage, PushChangesMessage, ApplyChangesMessage, ApplyResultMessage, GetSnapshotMessage, PingMessage, PongMessage, ErrorMessage, } from './types.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,yBAAyB,EAEzB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quereus/sync-client - WebSocket sync client for Quereus
|
|
3
|
+
*
|
|
4
|
+
* This package provides a WebSocket-based sync client that connects to a
|
|
5
|
+
* sync server and handles bidirectional synchronization of changes.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic reconnection with exponential backoff
|
|
9
|
+
* - Local change batching with debouncing
|
|
10
|
+
* - Delta sync tracking (only send/receive changes since last sync)
|
|
11
|
+
* - Event callbacks for status changes, remote changes, and errors
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { SyncClient } from '@quereus/sync-client';
|
|
16
|
+
*
|
|
17
|
+
* const client = new SyncClient({
|
|
18
|
+
* syncManager,
|
|
19
|
+
* onStatusChange: (status) => console.log('Status:', status),
|
|
20
|
+
* onRemoteChanges: (result) => console.log('Applied:', result.applied),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* await client.connect('ws://localhost:8080/sync');
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
export { SyncClient } from './sync-client.js';
|
|
29
|
+
export { serializeChangeSet, deserializeChangeSet, serializeHLCForTransport, deserializeHLCFromTransport, } from './serialization.js';
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers for ChangeSet transport over JSON.
|
|
3
|
+
*
|
|
4
|
+
* - SiteIds use base64url encoding (via siteIdToBase64/siteIdFromBase64)
|
|
5
|
+
* - HLCs use standard base64 encoding (via btoa/atob in browser, Buffer in Node)
|
|
6
|
+
*/
|
|
7
|
+
import { type ChangeSet, type HLC } from '@quereus/plugin-sync';
|
|
8
|
+
import type { SerializedChangeSet } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Serialize a ChangeSet for JSON transport.
|
|
11
|
+
*
|
|
12
|
+
* @param cs - The ChangeSet to serialize
|
|
13
|
+
* @returns A JSON-serializable object
|
|
14
|
+
*/
|
|
15
|
+
export declare function serializeChangeSet(cs: ChangeSet): SerializedChangeSet;
|
|
16
|
+
/**
|
|
17
|
+
* Deserialize a ChangeSet from JSON transport format.
|
|
18
|
+
*
|
|
19
|
+
* @param obj - The serialized ChangeSet object
|
|
20
|
+
* @returns The deserialized ChangeSet
|
|
21
|
+
*/
|
|
22
|
+
export declare function deserializeChangeSet(obj: SerializedChangeSet): ChangeSet;
|
|
23
|
+
/**
|
|
24
|
+
* Serialize an HLC for transport (base64 encoding).
|
|
25
|
+
*
|
|
26
|
+
* @param hlc - The HLC to serialize
|
|
27
|
+
* @returns Base64-encoded string
|
|
28
|
+
*/
|
|
29
|
+
export declare function serializeHLCForTransport(hlc: HLC): string;
|
|
30
|
+
/**
|
|
31
|
+
* Deserialize an HLC from transport format.
|
|
32
|
+
*
|
|
33
|
+
* @param str - Base64-encoded HLC string
|
|
34
|
+
* @returns The deserialized HLC
|
|
35
|
+
*/
|
|
36
|
+
export declare function deserializeHLCFromTransport(str: string): HLC;
|
|
37
|
+
//# sourceMappingURL=serialization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../src/serialization.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAKL,KAAK,SAAS,EAGd,KAAK,GAAG,EACT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAkCtD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,GAAG,mBAAmB,CAcrE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,mBAAmB,GAAG,SAAS,CAcxE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAE5D"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers for ChangeSet transport over JSON.
|
|
3
|
+
*
|
|
4
|
+
* - SiteIds use base64url encoding (via siteIdToBase64/siteIdFromBase64)
|
|
5
|
+
* - HLCs use standard base64 encoding (via btoa/atob in browser, Buffer in Node)
|
|
6
|
+
*/
|
|
7
|
+
import { serializeHLC, deserializeHLC, siteIdToBase64, siteIdFromBase64, } from '@quereus/plugin-sync';
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Base64 helpers (work in both browser and Node.js)
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Encode bytes to base64 string.
|
|
13
|
+
*/
|
|
14
|
+
function bytesToBase64(bytes) {
|
|
15
|
+
// Browser: use btoa
|
|
16
|
+
if (typeof btoa === 'function') {
|
|
17
|
+
return btoa(String.fromCharCode(...bytes));
|
|
18
|
+
}
|
|
19
|
+
// Node.js: use Buffer
|
|
20
|
+
return Buffer.from(bytes).toString('base64');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Decode base64 string to bytes.
|
|
24
|
+
*/
|
|
25
|
+
function base64ToBytes(str) {
|
|
26
|
+
// Browser: use atob
|
|
27
|
+
if (typeof atob === 'function') {
|
|
28
|
+
return Uint8Array.from(atob(str), c => c.charCodeAt(0));
|
|
29
|
+
}
|
|
30
|
+
// Node.js: use Buffer
|
|
31
|
+
return new Uint8Array(Buffer.from(str, 'base64'));
|
|
32
|
+
}
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// ChangeSet Serialization
|
|
35
|
+
// ============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Serialize a ChangeSet for JSON transport.
|
|
38
|
+
*
|
|
39
|
+
* @param cs - The ChangeSet to serialize
|
|
40
|
+
* @returns A JSON-serializable object
|
|
41
|
+
*/
|
|
42
|
+
export function serializeChangeSet(cs) {
|
|
43
|
+
return {
|
|
44
|
+
siteId: siteIdToBase64(cs.siteId),
|
|
45
|
+
transactionId: cs.transactionId,
|
|
46
|
+
hlc: bytesToBase64(serializeHLC(cs.hlc)),
|
|
47
|
+
changes: cs.changes.map(c => ({
|
|
48
|
+
...c,
|
|
49
|
+
hlc: bytesToBase64(serializeHLC(c.hlc)),
|
|
50
|
+
})),
|
|
51
|
+
schemaMigrations: cs.schemaMigrations.map(m => ({
|
|
52
|
+
...m,
|
|
53
|
+
hlc: bytesToBase64(serializeHLC(m.hlc)),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Deserialize a ChangeSet from JSON transport format.
|
|
59
|
+
*
|
|
60
|
+
* @param obj - The serialized ChangeSet object
|
|
61
|
+
* @returns The deserialized ChangeSet
|
|
62
|
+
*/
|
|
63
|
+
export function deserializeChangeSet(obj) {
|
|
64
|
+
return {
|
|
65
|
+
siteId: siteIdFromBase64(obj.siteId),
|
|
66
|
+
transactionId: obj.transactionId,
|
|
67
|
+
hlc: deserializeHLC(base64ToBytes(obj.hlc)),
|
|
68
|
+
changes: obj.changes.map(c => ({
|
|
69
|
+
...c,
|
|
70
|
+
hlc: deserializeHLC(base64ToBytes(c.hlc)),
|
|
71
|
+
})),
|
|
72
|
+
schemaMigrations: obj.schemaMigrations.map(m => ({
|
|
73
|
+
...m,
|
|
74
|
+
hlc: deserializeHLC(base64ToBytes(m.hlc)),
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Serialize an HLC for transport (base64 encoding).
|
|
80
|
+
*
|
|
81
|
+
* @param hlc - The HLC to serialize
|
|
82
|
+
* @returns Base64-encoded string
|
|
83
|
+
*/
|
|
84
|
+
export function serializeHLCForTransport(hlc) {
|
|
85
|
+
return bytesToBase64(serializeHLC(hlc));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Deserialize an HLC from transport format.
|
|
89
|
+
*
|
|
90
|
+
* @param str - Base64-encoded HLC string
|
|
91
|
+
* @returns The deserialized HLC
|
|
92
|
+
*/
|
|
93
|
+
export function deserializeHLCFromTransport(str) {
|
|
94
|
+
return deserializeHLC(base64ToBytes(str));
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=serialization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialization.js","sourceRoot":"","sources":["../../src/serialization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncClient - WebSocket-based sync client for Quereus.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - WebSocket connection and handshake
|
|
6
|
+
* - Message dispatch (changes, push_changes, apply_result, error, pong)
|
|
7
|
+
* - Reconnection with exponential backoff
|
|
8
|
+
* - Local change debouncing
|
|
9
|
+
* - Delta sync tracking (lastSentHLC, pendingSentHLC)
|
|
10
|
+
*/
|
|
11
|
+
import type { SyncClientOptions, SyncStatus } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* WebSocket sync client for Quereus.
|
|
14
|
+
*
|
|
15
|
+
* Connects to a sync server and handles bidirectional synchronization
|
|
16
|
+
* of changes with automatic reconnection and local change batching.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SyncClient {
|
|
19
|
+
private readonly syncManager;
|
|
20
|
+
private readonly syncEvents;
|
|
21
|
+
private readonly options;
|
|
22
|
+
private ws;
|
|
23
|
+
private serverSiteId;
|
|
24
|
+
private _status;
|
|
25
|
+
private connectionUrl;
|
|
26
|
+
private connectionToken;
|
|
27
|
+
private reconnectAttempts;
|
|
28
|
+
private reconnectTimer;
|
|
29
|
+
private intentionalDisconnect;
|
|
30
|
+
private lastSentHLC;
|
|
31
|
+
private pendingSentHLC;
|
|
32
|
+
private debounceTimer;
|
|
33
|
+
private pendingLocalChangeCount;
|
|
34
|
+
private localChangeUnsubscribe;
|
|
35
|
+
constructor(options: SyncClientOptions);
|
|
36
|
+
/** Current connection status */
|
|
37
|
+
get status(): SyncStatus;
|
|
38
|
+
/** Whether the client is connected and synced */
|
|
39
|
+
get isConnected(): boolean;
|
|
40
|
+
/** Whether the client is fully synced */
|
|
41
|
+
get isSynced(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Connect to a sync server.
|
|
44
|
+
*
|
|
45
|
+
* @param url - WebSocket URL of the sync server
|
|
46
|
+
* @param token - Optional authentication token
|
|
47
|
+
* @returns Promise that resolves when connected (not yet synced)
|
|
48
|
+
*/
|
|
49
|
+
connect(url: string, token?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Disconnect from the sync server.
|
|
52
|
+
* Stops reconnection attempts.
|
|
53
|
+
*/
|
|
54
|
+
disconnect(): Promise<void>;
|
|
55
|
+
private handleMessage;
|
|
56
|
+
private handleHandshakeAck;
|
|
57
|
+
private handleChanges;
|
|
58
|
+
private handleApplyResult;
|
|
59
|
+
private sendHandshake;
|
|
60
|
+
private requestChangesFromServer;
|
|
61
|
+
private send;
|
|
62
|
+
private subscribeToLocalChanges;
|
|
63
|
+
private debouncePushLocalChanges;
|
|
64
|
+
private pushLocalChanges;
|
|
65
|
+
private scheduleReconnect;
|
|
66
|
+
private clearReconnectTimer;
|
|
67
|
+
private clearDebounceTimer;
|
|
68
|
+
private setStatus;
|
|
69
|
+
private emitSyncEvent;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=sync-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../../src/sync-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAaH,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EAGX,MAAM,YAAY,CAAC;AAapB;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAED;IAGvB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,YAAY,CAAuB;IAG3C,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAAiC;IAGxD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAAS;IAGtC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,cAAc,CAAoB;IAG1C,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,uBAAuB,CAAK;IAGpC,OAAO,CAAC,sBAAsB,CAA6B;gBAE/C,OAAO,EAAE,iBAAiB;IAYtC,gCAAgC;IAChC,IAAI,MAAM,IAAI,UAAU,CAEvB;IAED,iDAAiD;IACjD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,yCAAyC;IACzC,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;;;OAMG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4DzD;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YA8BnB,aAAa;YA+Bb,kBAAkB;YAiBlB,aAAa;IAgC3B,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,aAAa;YASP,wBAAwB;IAatC,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,uBAAuB;IAa/B,OAAO,CAAC,wBAAwB;YASlB,gBAAgB;IAuC9B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,aAAa;CAatB"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncClient - WebSocket-based sync client for Quereus.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - WebSocket connection and handshake
|
|
6
|
+
* - Message dispatch (changes, push_changes, apply_result, error, pong)
|
|
7
|
+
* - Reconnection with exponential backoff
|
|
8
|
+
* - Local change debouncing
|
|
9
|
+
* - Delta sync tracking (lastSentHLC, pendingSentHLC)
|
|
10
|
+
*/
|
|
11
|
+
import { siteIdToBase64, siteIdFromBase64, compareHLC, } from '@quereus/plugin-sync';
|
|
12
|
+
import { serializeChangeSet, deserializeChangeSet, serializeHLCForTransport, } from './serialization.js';
|
|
13
|
+
// Default configuration values
|
|
14
|
+
const DEFAULT_RECONNECT_DELAY_MS = 1000;
|
|
15
|
+
const DEFAULT_MAX_RECONNECT_DELAY_MS = 60_000;
|
|
16
|
+
const DEFAULT_LOCAL_CHANGE_DEBOUNCE_MS = 50;
|
|
17
|
+
/**
|
|
18
|
+
* WebSocket sync client for Quereus.
|
|
19
|
+
*
|
|
20
|
+
* Connects to a sync server and handles bidirectional synchronization
|
|
21
|
+
* of changes with automatic reconnection and local change batching.
|
|
22
|
+
*/
|
|
23
|
+
export class SyncClient {
|
|
24
|
+
syncManager;
|
|
25
|
+
syncEvents;
|
|
26
|
+
options;
|
|
27
|
+
// WebSocket state
|
|
28
|
+
ws = null;
|
|
29
|
+
serverSiteId = null;
|
|
30
|
+
// Connection state
|
|
31
|
+
_status = { status: 'disconnected' };
|
|
32
|
+
connectionUrl = null;
|
|
33
|
+
connectionToken = undefined;
|
|
34
|
+
// Reconnection state
|
|
35
|
+
reconnectAttempts = 0;
|
|
36
|
+
reconnectTimer = null;
|
|
37
|
+
intentionalDisconnect = false;
|
|
38
|
+
// Delta sync tracking
|
|
39
|
+
lastSentHLC = null;
|
|
40
|
+
pendingSentHLC = null;
|
|
41
|
+
// Local change debouncing
|
|
42
|
+
debounceTimer = null;
|
|
43
|
+
pendingLocalChangeCount = 0;
|
|
44
|
+
// Local change listener cleanup
|
|
45
|
+
localChangeUnsubscribe = null;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
this.syncManager = options.syncManager;
|
|
48
|
+
this.syncEvents = options.syncEvents;
|
|
49
|
+
this.options = {
|
|
50
|
+
...options,
|
|
51
|
+
autoReconnect: options.autoReconnect ?? true,
|
|
52
|
+
reconnectDelayMs: options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS,
|
|
53
|
+
maxReconnectDelayMs: options.maxReconnectDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS,
|
|
54
|
+
localChangeDebounceMs: options.localChangeDebounceMs ?? DEFAULT_LOCAL_CHANGE_DEBOUNCE_MS,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Current connection status */
|
|
58
|
+
get status() {
|
|
59
|
+
return this._status;
|
|
60
|
+
}
|
|
61
|
+
/** Whether the client is connected and synced */
|
|
62
|
+
get isConnected() {
|
|
63
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
64
|
+
}
|
|
65
|
+
/** Whether the client is fully synced */
|
|
66
|
+
get isSynced() {
|
|
67
|
+
return this._status.status === 'synced';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Connect to a sync server.
|
|
71
|
+
*
|
|
72
|
+
* @param url - WebSocket URL of the sync server
|
|
73
|
+
* @param token - Optional authentication token
|
|
74
|
+
* @returns Promise that resolves when connected (not yet synced)
|
|
75
|
+
*/
|
|
76
|
+
async connect(url, token) {
|
|
77
|
+
// Store connection params for reconnection
|
|
78
|
+
this.connectionUrl = url;
|
|
79
|
+
this.connectionToken = token;
|
|
80
|
+
this.intentionalDisconnect = false;
|
|
81
|
+
// Clear any pending reconnect timer
|
|
82
|
+
this.clearReconnectTimer();
|
|
83
|
+
// Close existing connection
|
|
84
|
+
if (this.ws) {
|
|
85
|
+
this.ws.close();
|
|
86
|
+
this.ws = null;
|
|
87
|
+
}
|
|
88
|
+
this.setStatus({ status: 'connecting' });
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
try {
|
|
91
|
+
const wsUrl = token ? `${url}?token=${encodeURIComponent(token)}` : url;
|
|
92
|
+
this.ws = new WebSocket(wsUrl);
|
|
93
|
+
this.ws.onopen = () => {
|
|
94
|
+
this.reconnectAttempts = 0;
|
|
95
|
+
this.sendHandshake(token);
|
|
96
|
+
this.setStatus({ status: 'syncing', progress: 0 });
|
|
97
|
+
this.emitSyncEvent('state-change', 'Connected to sync server, handshake sent');
|
|
98
|
+
resolve();
|
|
99
|
+
};
|
|
100
|
+
this.ws.onclose = () => {
|
|
101
|
+
this.setStatus({ status: 'disconnected' });
|
|
102
|
+
this.emitSyncEvent('state-change', 'Disconnected from sync server');
|
|
103
|
+
this.scheduleReconnect();
|
|
104
|
+
};
|
|
105
|
+
this.ws.onerror = () => {
|
|
106
|
+
const error = new Error('WebSocket connection failed');
|
|
107
|
+
this.setStatus({ status: 'error', message: error.message });
|
|
108
|
+
this.emitSyncEvent('error', error.message);
|
|
109
|
+
// Only reject on first attempt
|
|
110
|
+
if (this.reconnectAttempts === 0) {
|
|
111
|
+
reject(error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
this.ws.onmessage = (event) => {
|
|
115
|
+
this.handleMessage(event.data).catch(err => {
|
|
116
|
+
console.error('Error handling sync message:', err);
|
|
117
|
+
this.emitSyncEvent('error', `Sync error: ${err instanceof Error ? err.message : 'Unknown'}`);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const msg = error instanceof Error ? error.message : 'Connection failed';
|
|
123
|
+
this.setStatus({ status: 'error', message: msg });
|
|
124
|
+
reject(error);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Disconnect from the sync server.
|
|
130
|
+
* Stops reconnection attempts.
|
|
131
|
+
*/
|
|
132
|
+
async disconnect() {
|
|
133
|
+
this.intentionalDisconnect = true;
|
|
134
|
+
// Clear timers
|
|
135
|
+
this.clearReconnectTimer();
|
|
136
|
+
this.clearDebounceTimer();
|
|
137
|
+
// Remove local change listener
|
|
138
|
+
if (this.localChangeUnsubscribe) {
|
|
139
|
+
this.localChangeUnsubscribe();
|
|
140
|
+
this.localChangeUnsubscribe = null;
|
|
141
|
+
}
|
|
142
|
+
// Close WebSocket
|
|
143
|
+
if (this.ws) {
|
|
144
|
+
this.ws.close();
|
|
145
|
+
this.ws = null;
|
|
146
|
+
}
|
|
147
|
+
this.serverSiteId = null;
|
|
148
|
+
this.lastSentHLC = null;
|
|
149
|
+
this.pendingSentHLC = null;
|
|
150
|
+
this.setStatus({ status: 'disconnected' });
|
|
151
|
+
this.emitSyncEvent('state-change', 'Disconnected from sync server (manual)');
|
|
152
|
+
}
|
|
153
|
+
// ==========================================================================
|
|
154
|
+
// Private: Message Handlers
|
|
155
|
+
// ==========================================================================
|
|
156
|
+
async handleMessage(data) {
|
|
157
|
+
const message = JSON.parse(data);
|
|
158
|
+
switch (message.type) {
|
|
159
|
+
case 'handshake_ack':
|
|
160
|
+
await this.handleHandshakeAck(message);
|
|
161
|
+
break;
|
|
162
|
+
case 'changes':
|
|
163
|
+
case 'push_changes':
|
|
164
|
+
await this.handleChanges(message.changeSets || []);
|
|
165
|
+
break;
|
|
166
|
+
case 'apply_result':
|
|
167
|
+
this.handleApplyResult(message);
|
|
168
|
+
break;
|
|
169
|
+
case 'error':
|
|
170
|
+
this.emitSyncEvent('error', `Server error: ${message.message} (${message.code})`);
|
|
171
|
+
this.options.onError?.(new Error(message.message));
|
|
172
|
+
break;
|
|
173
|
+
case 'pong':
|
|
174
|
+
// Heartbeat response - no action needed
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
console.warn('Unknown sync message type:', message.type);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async handleHandshakeAck(message) {
|
|
181
|
+
if (message.serverSiteId) {
|
|
182
|
+
this.serverSiteId = siteIdFromBase64(message.serverSiteId);
|
|
183
|
+
}
|
|
184
|
+
this.emitSyncEvent('state-change', `Authenticated with server (connection: ${message.connectionId?.slice(0, 8) ?? 'unknown'})`);
|
|
185
|
+
// Request changes from server since our last sync with this peer
|
|
186
|
+
await this.requestChangesFromServer();
|
|
187
|
+
// Subscribe to local changes for pushing to server
|
|
188
|
+
this.subscribeToLocalChanges();
|
|
189
|
+
}
|
|
190
|
+
async handleChanges(serializedChangeSets) {
|
|
191
|
+
const changeSets = serializedChangeSets.map(cs => deserializeChangeSet(cs));
|
|
192
|
+
const result = await this.syncManager.applyChanges(changeSets);
|
|
193
|
+
// Update peer sync state with the max HLC from received changes
|
|
194
|
+
if (changeSets.length > 0 && this.serverSiteId) {
|
|
195
|
+
let maxHLC;
|
|
196
|
+
for (const cs of changeSets) {
|
|
197
|
+
if (!maxHLC || compareHLC(cs.hlc, maxHLC) > 0) {
|
|
198
|
+
maxHLC = cs.hlc;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (maxHLC) {
|
|
202
|
+
await this.syncManager.updatePeerSyncState(this.serverSiteId, maxHLC);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Emit events
|
|
206
|
+
if (result.applied > 0 || result.conflicts > 0 || result.skipped > 0) {
|
|
207
|
+
const conflictText = result.conflicts > 0 ? ` (${result.conflicts} conflicts resolved)` : '';
|
|
208
|
+
const skippedText = result.skipped > 0 ? `, ${result.skipped} skipped` : '';
|
|
209
|
+
this.emitSyncEvent('remote-change', `Applied ${result.applied} column changes${conflictText}${skippedText}`, { changeCount: result.applied, conflicts: result.conflicts, skipped: result.skipped });
|
|
210
|
+
}
|
|
211
|
+
this.options.onRemoteChanges?.(result, changeSets);
|
|
212
|
+
this.setStatus({ status: 'synced', lastSyncTime: Date.now() });
|
|
213
|
+
}
|
|
214
|
+
handleApplyResult(message) {
|
|
215
|
+
// Update lastSentHLC to enable delta sync on next send
|
|
216
|
+
if (this.pendingSentHLC) {
|
|
217
|
+
this.lastSentHLC = this.pendingSentHLC;
|
|
218
|
+
this.pendingSentHLC = null;
|
|
219
|
+
}
|
|
220
|
+
this.emitSyncEvent('info', `Server applied ${message.applied ?? 0} change(s)`);
|
|
221
|
+
}
|
|
222
|
+
// ==========================================================================
|
|
223
|
+
// Private: Message Sending
|
|
224
|
+
// ==========================================================================
|
|
225
|
+
sendHandshake(token) {
|
|
226
|
+
const siteId = this.syncManager.getSiteId();
|
|
227
|
+
this.send({
|
|
228
|
+
type: 'handshake',
|
|
229
|
+
siteId: siteIdToBase64(siteId),
|
|
230
|
+
token,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async requestChangesFromServer() {
|
|
234
|
+
if (!this.serverSiteId)
|
|
235
|
+
return;
|
|
236
|
+
const lastSyncHLC = await this.syncManager.getPeerSyncState(this.serverSiteId);
|
|
237
|
+
const msg = { type: 'get_changes' };
|
|
238
|
+
if (lastSyncHLC) {
|
|
239
|
+
msg.sinceHLC = serializeHLCForTransport(lastSyncHLC);
|
|
240
|
+
}
|
|
241
|
+
this.send(msg);
|
|
242
|
+
}
|
|
243
|
+
send(message) {
|
|
244
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
245
|
+
this.ws.send(JSON.stringify(message));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ==========================================================================
|
|
249
|
+
// Private: Local Change Handling
|
|
250
|
+
// ==========================================================================
|
|
251
|
+
subscribeToLocalChanges() {
|
|
252
|
+
// Unsubscribe from previous if any
|
|
253
|
+
if (this.localChangeUnsubscribe) {
|
|
254
|
+
this.localChangeUnsubscribe();
|
|
255
|
+
}
|
|
256
|
+
// Subscribe to local changes via SyncEventEmitter
|
|
257
|
+
this.localChangeUnsubscribe = this.syncEvents.onLocalChange(() => {
|
|
258
|
+
this.pendingLocalChangeCount++;
|
|
259
|
+
this.debouncePushLocalChanges();
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
debouncePushLocalChanges() {
|
|
263
|
+
this.clearDebounceTimer();
|
|
264
|
+
this.debounceTimer = setTimeout(async () => {
|
|
265
|
+
this.debounceTimer = null;
|
|
266
|
+
await this.pushLocalChanges();
|
|
267
|
+
}, this.options.localChangeDebounceMs);
|
|
268
|
+
}
|
|
269
|
+
async pushLocalChanges() {
|
|
270
|
+
if (!this.isConnected)
|
|
271
|
+
return;
|
|
272
|
+
// Get changes since lastSentHLC (delta sync)
|
|
273
|
+
const changes = await this.syncManager.getChangesSince(this.syncManager.getSiteId(), this.lastSentHLC ?? undefined);
|
|
274
|
+
if (changes.length === 0)
|
|
275
|
+
return;
|
|
276
|
+
// Track the max HLC we're sending for delta sync
|
|
277
|
+
let maxHLC;
|
|
278
|
+
for (const cs of changes) {
|
|
279
|
+
if (!maxHLC || compareHLC(cs.hlc, maxHLC) > 0) {
|
|
280
|
+
maxHLC = cs.hlc;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this.pendingSentHLC = maxHLC ?? null;
|
|
284
|
+
// Serialize and send
|
|
285
|
+
const serialized = changes.map(cs => serializeChangeSet(cs));
|
|
286
|
+
this.emitSyncEvent('local-change', `Sending ${changes.length} change set(s) to server`, {
|
|
287
|
+
changeCount: changes.length,
|
|
288
|
+
});
|
|
289
|
+
this.send({
|
|
290
|
+
type: 'apply_changes',
|
|
291
|
+
changes: serialized,
|
|
292
|
+
});
|
|
293
|
+
this.pendingLocalChangeCount = 0;
|
|
294
|
+
}
|
|
295
|
+
// ==========================================================================
|
|
296
|
+
// Private: Reconnection
|
|
297
|
+
// ==========================================================================
|
|
298
|
+
scheduleReconnect() {
|
|
299
|
+
if (this.intentionalDisconnect || !this.connectionUrl || !this.options.autoReconnect) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, ... up to max
|
|
303
|
+
const delay = Math.min(this.options.reconnectDelayMs * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectDelayMs);
|
|
304
|
+
this.reconnectAttempts++;
|
|
305
|
+
this.emitSyncEvent('state-change', `Reconnecting in ${Math.round(delay / 1000)}s (attempt ${this.reconnectAttempts})`);
|
|
306
|
+
this.reconnectTimer = setTimeout(() => {
|
|
307
|
+
this.reconnectTimer = null;
|
|
308
|
+
this.connect(this.connectionUrl, this.connectionToken).catch(() => {
|
|
309
|
+
// Error already handled in connect, reconnect will be scheduled by onclose
|
|
310
|
+
});
|
|
311
|
+
}, delay);
|
|
312
|
+
}
|
|
313
|
+
clearReconnectTimer() {
|
|
314
|
+
if (this.reconnectTimer) {
|
|
315
|
+
clearTimeout(this.reconnectTimer);
|
|
316
|
+
this.reconnectTimer = null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
clearDebounceTimer() {
|
|
320
|
+
if (this.debounceTimer) {
|
|
321
|
+
clearTimeout(this.debounceTimer);
|
|
322
|
+
this.debounceTimer = null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// ==========================================================================
|
|
326
|
+
// Private: Status & Events
|
|
327
|
+
// ==========================================================================
|
|
328
|
+
setStatus(status) {
|
|
329
|
+
this._status = status;
|
|
330
|
+
this.options.onStatusChange?.(status);
|
|
331
|
+
}
|
|
332
|
+
emitSyncEvent(type, message, details) {
|
|
333
|
+
const event = {
|
|
334
|
+
type,
|
|
335
|
+
timestamp: Date.now(),
|
|
336
|
+
message,
|
|
337
|
+
details,
|
|
338
|
+
};
|
|
339
|
+
this.options.onSyncEvent?.(event);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=sync-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-client.js","sourceRoot":"","sources":["../../src/sync-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the WebSocket sync client.
|
|
3
|
+
*/
|
|
4
|
+
import type { ApplyResult, ChangeSet, SyncManager, SyncEventEmitter } from '@quereus/plugin-sync';
|
|
5
|
+
/**
|
|
6
|
+
* Sync connection status.
|
|
7
|
+
*/
|
|
8
|
+
export type SyncStatus = {
|
|
9
|
+
status: 'disconnected';
|
|
10
|
+
} | {
|
|
11
|
+
status: 'connecting';
|
|
12
|
+
} | {
|
|
13
|
+
status: 'syncing';
|
|
14
|
+
progress: number;
|
|
15
|
+
} | {
|
|
16
|
+
status: 'synced';
|
|
17
|
+
lastSyncTime: number;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'error';
|
|
20
|
+
message: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Sync event types for logging/UI notifications.
|
|
24
|
+
*/
|
|
25
|
+
export type SyncEventType = 'remote-change' | 'local-change' | 'conflict' | 'state-change' | 'error' | 'info';
|
|
26
|
+
/**
|
|
27
|
+
* Sync event for logging/UI display.
|
|
28
|
+
*/
|
|
29
|
+
export interface SyncEvent {
|
|
30
|
+
type: SyncEventType;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
message: string;
|
|
33
|
+
details?: {
|
|
34
|
+
table?: string;
|
|
35
|
+
changeCount?: number;
|
|
36
|
+
conflicts?: number;
|
|
37
|
+
skipped?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Options for configuring the SyncClient.
|
|
42
|
+
*/
|
|
43
|
+
export interface SyncClientOptions {
|
|
44
|
+
/**
|
|
45
|
+
* The SyncManager instance to use for sync operations.
|
|
46
|
+
*/
|
|
47
|
+
syncManager: SyncManager;
|
|
48
|
+
/**
|
|
49
|
+
* The SyncEventEmitter to subscribe to local change events.
|
|
50
|
+
* Required for automatic pushing of local changes to the server.
|
|
51
|
+
*/
|
|
52
|
+
syncEvents: SyncEventEmitter;
|
|
53
|
+
/**
|
|
54
|
+
* Callback when connection status changes.
|
|
55
|
+
*/
|
|
56
|
+
onStatusChange?: (status: SyncStatus) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Callback when remote changes are applied.
|
|
59
|
+
*/
|
|
60
|
+
onRemoteChanges?: (result: ApplyResult, changeSets: ChangeSet[]) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Callback when a sync event occurs (for logging/UI).
|
|
63
|
+
*/
|
|
64
|
+
onSyncEvent?: (event: SyncEvent) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Callback when an error occurs.
|
|
67
|
+
*/
|
|
68
|
+
onError?: (error: Error) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Whether to automatically reconnect on disconnect.
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
autoReconnect?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Initial delay for reconnection (milliseconds).
|
|
76
|
+
* @default 1000
|
|
77
|
+
*/
|
|
78
|
+
reconnectDelayMs?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Maximum delay for reconnection (milliseconds).
|
|
81
|
+
* @default 60000
|
|
82
|
+
*/
|
|
83
|
+
maxReconnectDelayMs?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Debounce delay for batching local changes (milliseconds).
|
|
86
|
+
* @default 50
|
|
87
|
+
*/
|
|
88
|
+
localChangeDebounceMs?: number;
|
|
89
|
+
}
|
|
90
|
+
/** Client → Server: Handshake */
|
|
91
|
+
export interface HandshakeMessage {
|
|
92
|
+
type: 'handshake';
|
|
93
|
+
siteId: string;
|
|
94
|
+
token?: string;
|
|
95
|
+
}
|
|
96
|
+
/** Client → Server: Request changes */
|
|
97
|
+
export interface GetChangesMessage {
|
|
98
|
+
type: 'get_changes';
|
|
99
|
+
sinceHLC?: string;
|
|
100
|
+
}
|
|
101
|
+
/** Client → Server: Apply local changes */
|
|
102
|
+
export interface ApplyChangesMessage {
|
|
103
|
+
type: 'apply_changes';
|
|
104
|
+
changes: SerializedChangeSet[];
|
|
105
|
+
}
|
|
106
|
+
/** Client → Server: Request snapshot */
|
|
107
|
+
export interface GetSnapshotMessage {
|
|
108
|
+
type: 'get_snapshot';
|
|
109
|
+
}
|
|
110
|
+
/** Client → Server: Heartbeat */
|
|
111
|
+
export interface PingMessage {
|
|
112
|
+
type: 'ping';
|
|
113
|
+
}
|
|
114
|
+
export type ClientMessage = HandshakeMessage | GetChangesMessage | ApplyChangesMessage | GetSnapshotMessage | PingMessage;
|
|
115
|
+
/** Server → Client: Handshake acknowledgment */
|
|
116
|
+
export interface HandshakeAckMessage {
|
|
117
|
+
type: 'handshake_ack';
|
|
118
|
+
serverSiteId: string;
|
|
119
|
+
connectionId?: string;
|
|
120
|
+
}
|
|
121
|
+
/** Server → Client: Changes response */
|
|
122
|
+
export interface ChangesMessage {
|
|
123
|
+
type: 'changes';
|
|
124
|
+
changeSets: SerializedChangeSet[];
|
|
125
|
+
}
|
|
126
|
+
/** Server → Client: Pushed changes from another peer */
|
|
127
|
+
export interface PushChangesMessage {
|
|
128
|
+
type: 'push_changes';
|
|
129
|
+
changeSets: SerializedChangeSet[];
|
|
130
|
+
}
|
|
131
|
+
/** Server → Client: Apply result */
|
|
132
|
+
export interface ApplyResultMessage {
|
|
133
|
+
type: 'apply_result';
|
|
134
|
+
applied: number;
|
|
135
|
+
skipped: number;
|
|
136
|
+
conflicts: number;
|
|
137
|
+
transactions: number;
|
|
138
|
+
}
|
|
139
|
+
/** Server → Client: Error */
|
|
140
|
+
export interface ErrorMessage {
|
|
141
|
+
type: 'error';
|
|
142
|
+
code: string;
|
|
143
|
+
message: string;
|
|
144
|
+
}
|
|
145
|
+
/** Server → Client: Heartbeat response */
|
|
146
|
+
export interface PongMessage {
|
|
147
|
+
type: 'pong';
|
|
148
|
+
}
|
|
149
|
+
export type ServerMessage = HandshakeAckMessage | ChangesMessage | PushChangesMessage | ApplyResultMessage | ErrorMessage | PongMessage;
|
|
150
|
+
/**
|
|
151
|
+
* A ChangeSet serialized for JSON transport.
|
|
152
|
+
* SiteIds are base64url-encoded, HLCs are base64-encoded.
|
|
153
|
+
*/
|
|
154
|
+
export interface SerializedChangeSet {
|
|
155
|
+
siteId: string;
|
|
156
|
+
transactionId: string;
|
|
157
|
+
hlc: string;
|
|
158
|
+
changes: SerializedChange[];
|
|
159
|
+
schemaMigrations: SerializedSchemaMigration[];
|
|
160
|
+
}
|
|
161
|
+
export interface SerializedChange {
|
|
162
|
+
type: 'column' | 'delete';
|
|
163
|
+
schema: string;
|
|
164
|
+
table: string;
|
|
165
|
+
pk: unknown[];
|
|
166
|
+
column?: string;
|
|
167
|
+
value?: unknown;
|
|
168
|
+
hlc: string;
|
|
169
|
+
}
|
|
170
|
+
export interface SerializedSchemaMigration {
|
|
171
|
+
type: string;
|
|
172
|
+
schema: string;
|
|
173
|
+
table: string;
|
|
174
|
+
ddl: string;
|
|
175
|
+
hlc: string;
|
|
176
|
+
schemaVersion: number;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quereus/sync-client",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "WebSocket sync client for Quereus - handles connection, reconnection, and change synchronization",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"quereus",
|
|
8
|
+
"sync",
|
|
9
|
+
"websocket",
|
|
10
|
+
"crdt",
|
|
11
|
+
"replication",
|
|
12
|
+
"offline-first",
|
|
13
|
+
"client"
|
|
14
|
+
],
|
|
15
|
+
"publisher": "Got Choices Foundation",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/gotchoices/quereus.git",
|
|
19
|
+
"directory": "packages/quereus-sync-client"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"main": "dist/src/index.js",
|
|
23
|
+
"types": "./dist/src/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"!**/*.tsbuildinfo"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/src/index.d.ts",
|
|
31
|
+
"import": "./dist/src/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"clean": "rimraf dist",
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "cd ../.. && node --import ./packages/quereus-sync-client/register.mjs node_modules/mocha/bin/mocha.js \"packages/quereus-sync-client/test/**/*.spec.ts\" --colors",
|
|
39
|
+
"test:single": "cd ../.. && node --import ./packages/quereus-sync-client/register.mjs node_modules/mocha/bin/mocha.js --bail"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@quereus/plugin-sync": "*"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@quereus/plugin-sync": "*",
|
|
46
|
+
"@types/mocha": "^10.0.10",
|
|
47
|
+
"@types/node": "^22.15.29",
|
|
48
|
+
"chai": "^5.2.0",
|
|
49
|
+
"mocha": "^11.5.0",
|
|
50
|
+
"rimraf": "^6.1.2",
|
|
51
|
+
"ts-node": "^10.9.2",
|
|
52
|
+
"typescript": "^5.8.3"
|
|
53
|
+
},
|
|
54
|
+
"quereus": {
|
|
55
|
+
"provides": {
|
|
56
|
+
"sync-client": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|