@edgebasejs/client-core 0.1.7 → 0.1.9
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 +15 -0
- package/dist/client-core/src/index.d.ts +4 -0
- package/dist/client-core/src/index.d.ts.map +1 -1
- package/dist/client-core/src/index.js +4 -0
- package/dist/client-core/src/index.js.map +1 -1
- package/dist/client-core/src/mutations/batch-processor-client.d.ts +67 -0
- package/dist/client-core/src/mutations/batch-processor-client.d.ts.map +1 -0
- package/dist/client-core/src/mutations/batch-processor-client.js +64 -0
- package/dist/client-core/src/mutations/batch-processor-client.js.map +1 -0
- package/dist/client-core/src/mutations/transaction-hook.d.ts +80 -0
- package/dist/client-core/src/mutations/transaction-hook.d.ts.map +1 -0
- package/dist/client-core/src/mutations/transaction-hook.js +204 -0
- package/dist/client-core/src/mutations/transaction-hook.js.map +1 -0
- package/dist/client-core/src/realtime/realtime-sync-manager.d.ts +55 -0
- package/dist/client-core/src/realtime/realtime-sync-manager.d.ts.map +1 -0
- package/dist/client-core/src/realtime/realtime-sync-manager.js +208 -0
- package/dist/client-core/src/realtime/realtime-sync-manager.js.map +1 -0
- package/dist/client-core/src/realtime/subscription-handler.d.ts +74 -0
- package/dist/client-core/src/realtime/subscription-handler.d.ts.map +1 -0
- package/dist/client-core/src/realtime/subscription-handler.js +224 -0
- package/dist/client-core/src/realtime/subscription-handler.js.map +1 -0
- package/dist/client-core/src/sync/sync-engine.d.ts +10 -0
- package/dist/client-core/src/sync/sync-engine.d.ts.map +1 -1
- package/dist/client-core/src/sync/sync-engine.js +37 -5
- package/dist/client-core/src/sync/sync-engine.js.map +1 -1
- package/dist/core/src/access-rules/column-security.d.ts +80 -0
- package/dist/core/src/access-rules/column-security.d.ts.map +1 -0
- package/dist/core/src/access-rules/column-security.js +191 -0
- package/dist/core/src/access-rules/column-security.js.map +1 -0
- package/dist/core/src/access-rules/engine.d.ts +26 -0
- package/dist/core/src/access-rules/engine.d.ts.map +1 -0
- package/dist/core/src/access-rules/engine.js +76 -0
- package/dist/core/src/access-rules/engine.js.map +1 -0
- package/dist/core/src/access-rules/index.d.ts +3 -0
- package/dist/core/src/access-rules/index.d.ts.map +1 -0
- package/dist/core/src/access-rules/index.js +3 -0
- package/dist/core/src/access-rules/index.js.map +1 -0
- package/dist/core/src/audit/audit-manager.d.ts +108 -0
- package/dist/core/src/audit/audit-manager.d.ts.map +1 -0
- package/dist/core/src/audit/audit-manager.js +265 -0
- package/dist/core/src/audit/audit-manager.js.map +1 -0
- package/dist/core/src/auth/auth-service.d.ts +71 -0
- package/dist/core/src/auth/auth-service.d.ts.map +1 -0
- package/dist/core/src/auth/auth-service.js +177 -0
- package/dist/core/src/auth/auth-service.js.map +1 -0
- package/dist/core/src/auth/index.d.ts +4 -0
- package/dist/core/src/auth/index.d.ts.map +1 -0
- package/dist/core/src/auth/index.js +4 -0
- package/dist/core/src/auth/index.js.map +1 -0
- package/dist/core/src/encryption/encryption-manager.d.ts +97 -0
- package/dist/core/src/encryption/encryption-manager.d.ts.map +1 -0
- package/dist/core/src/encryption/encryption-manager.js +224 -0
- package/dist/core/src/encryption/encryption-manager.js.map +1 -0
- package/dist/core/src/index.d.ts +16 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +16 -0
- package/dist/core/src/index.js.map +1 -0
- package/dist/core/src/realtime/change-notifier.d.ts +50 -0
- package/dist/core/src/realtime/change-notifier.d.ts.map +1 -0
- package/dist/core/src/realtime/change-notifier.js +145 -0
- package/dist/core/src/realtime/change-notifier.js.map +1 -0
- package/dist/core/src/realtime/message-types.d.ts +39 -0
- package/dist/core/src/realtime/message-types.d.ts.map +1 -0
- package/dist/core/src/realtime/message-types.js +5 -0
- package/dist/core/src/realtime/message-types.js.map +1 -0
- package/dist/core/src/realtime/subscription-manager.d.ts +67 -0
- package/dist/core/src/realtime/subscription-manager.d.ts.map +1 -0
- package/dist/core/src/realtime/subscription-manager.js +229 -0
- package/dist/core/src/realtime/subscription-manager.js.map +1 -0
- package/dist/core/src/search/search-manager.d.ts +93 -0
- package/dist/core/src/search/search-manager.d.ts.map +1 -0
- package/dist/core/src/search/search-manager.js +258 -0
- package/dist/core/src/search/search-manager.js.map +1 -0
- package/dist/core/src/storage/file-manager.d.ts +138 -0
- package/dist/core/src/storage/file-manager.d.ts.map +1 -0
- package/dist/core/src/storage/file-manager.js +224 -0
- package/dist/core/src/storage/file-manager.js.map +1 -0
- package/dist/core/src/sync/batch-processor.d.ts +97 -0
- package/dist/core/src/sync/batch-processor.d.ts.map +1 -0
- package/dist/core/src/sync/batch-processor.js +313 -0
- package/dist/core/src/sync/batch-processor.js.map +1 -0
- package/dist/core/src/sync/csv-processor.d.ts +66 -0
- package/dist/core/src/sync/csv-processor.d.ts.map +1 -0
- package/dist/core/src/sync/csv-processor.js +223 -0
- package/dist/core/src/sync/csv-processor.js.map +1 -0
- package/dist/core/src/sync/index.d.ts +3 -0
- package/dist/core/src/sync/index.d.ts.map +1 -0
- package/dist/core/src/sync/index.js +3 -0
- package/dist/core/src/sync/index.js.map +1 -0
- package/dist/core/src/sync/sync-engine.d.ts +68 -0
- package/dist/core/src/sync/sync-engine.d.ts.map +1 -0
- package/dist/core/src/sync/sync-engine.js +317 -0
- package/dist/core/src/sync/sync-engine.js.map +1 -0
- package/dist/core/src/sync/transaction-manager.d.ts +83 -0
- package/dist/core/src/sync/transaction-manager.d.ts.map +1 -0
- package/dist/core/src/sync/transaction-manager.js +227 -0
- package/dist/core/src/sync/transaction-manager.js.map +1 -0
- package/dist/core/src/webhooks/webhook-manager.d.ts +137 -0
- package/dist/core/src/webhooks/webhook-manager.d.ts.map +1 -0
- package/dist/core/src/webhooks/webhook-manager.js +334 -0
- package/dist/core/src/webhooks/webhook-manager.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/package.json +2 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages real-time sync between WebSocket changes and local storage
|
|
3
|
+
* Handles merging of server changes with local data
|
|
4
|
+
*/
|
|
5
|
+
export class RealtimeSyncManager {
|
|
6
|
+
constructor(storage) {
|
|
7
|
+
this.storage = storage;
|
|
8
|
+
this.changeListeners = new Map();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to changes for a specific entity
|
|
12
|
+
*/
|
|
13
|
+
onEntityChange(entity, listener) {
|
|
14
|
+
if (!this.changeListeners.has(entity)) {
|
|
15
|
+
this.changeListeners.set(entity, new Set());
|
|
16
|
+
}
|
|
17
|
+
this.changeListeners.get(entity).add(listener);
|
|
18
|
+
// Return unsubscribe function
|
|
19
|
+
return () => {
|
|
20
|
+
this.changeListeners.get(entity)?.delete(listener);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Handle incoming WebSocket change message
|
|
25
|
+
* Merges with local storage using server-authoritative conflict resolution
|
|
26
|
+
*/
|
|
27
|
+
async handleChange(change) {
|
|
28
|
+
try {
|
|
29
|
+
const { entity, operation, record, recordId, version, timestamp } = change;
|
|
30
|
+
switch (operation) {
|
|
31
|
+
case 'create':
|
|
32
|
+
case 'update':
|
|
33
|
+
if (!record || !recordId)
|
|
34
|
+
break;
|
|
35
|
+
// Store in local storage with new version
|
|
36
|
+
const key = `entity:${entity}:${recordId}`;
|
|
37
|
+
const storedData = {
|
|
38
|
+
...record,
|
|
39
|
+
_version: version || 0,
|
|
40
|
+
_syncedAt: timestamp || Date.now(),
|
|
41
|
+
_synced: true, // Mark as synced from server
|
|
42
|
+
};
|
|
43
|
+
await this.storage.setItem(key, JSON.stringify(storedData));
|
|
44
|
+
// Notify listeners
|
|
45
|
+
this.notifyListeners(entity, change);
|
|
46
|
+
break;
|
|
47
|
+
case 'delete':
|
|
48
|
+
if (!recordId)
|
|
49
|
+
break;
|
|
50
|
+
// Remove from local storage
|
|
51
|
+
const deleteKey = `entity:${entity}:${recordId}`;
|
|
52
|
+
await this.storage.removeItem(deleteKey);
|
|
53
|
+
// Notify listeners
|
|
54
|
+
this.notifyListeners(entity, change);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('Failed to handle real-time change:', error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Notify listeners about a change
|
|
64
|
+
*/
|
|
65
|
+
notifyListeners(entity, change) {
|
|
66
|
+
const listeners = this.changeListeners.get(entity);
|
|
67
|
+
if (listeners) {
|
|
68
|
+
for (const listener of listeners) {
|
|
69
|
+
try {
|
|
70
|
+
listener(change);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Listener error:', error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all data for an entity from local storage
|
|
80
|
+
*/
|
|
81
|
+
async getEntityData(entity) {
|
|
82
|
+
try {
|
|
83
|
+
const allKeys = await this.storage.getAllKeys();
|
|
84
|
+
const entityKeys = allKeys.filter((key) => key.startsWith(`entity:${entity}:`));
|
|
85
|
+
const results = [];
|
|
86
|
+
for (const key of entityKeys) {
|
|
87
|
+
const data = await this.storage.getItem(key);
|
|
88
|
+
if (data) {
|
|
89
|
+
results.push(JSON.parse(data));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Failed to get entity data:', error);
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get a single record by ID
|
|
101
|
+
*/
|
|
102
|
+
async getRecord(entity, recordId) {
|
|
103
|
+
try {
|
|
104
|
+
const key = `entity:${entity}:${recordId}`;
|
|
105
|
+
const data = await this.storage.getItem(key);
|
|
106
|
+
if (data) {
|
|
107
|
+
return JSON.parse(data);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('Failed to get record:', error);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Merge outbox changes with server updates
|
|
118
|
+
* Used during sync to detect conflicts
|
|
119
|
+
*/
|
|
120
|
+
async mergeWithOutbox(entity, recordId, serverRecord, serverVersion) {
|
|
121
|
+
try {
|
|
122
|
+
const key = `entity:${entity}:${recordId}`;
|
|
123
|
+
const stored = await this.storage.getItem(key);
|
|
124
|
+
let localData = stored ? JSON.parse(stored) : null;
|
|
125
|
+
if (!localData) {
|
|
126
|
+
// No local data, use server data
|
|
127
|
+
return {
|
|
128
|
+
resolved: true,
|
|
129
|
+
data: serverRecord,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Check if there are local changes (indicated by _outbox flag)
|
|
133
|
+
if (localData._outbox) {
|
|
134
|
+
// Conflict: local changes conflict with server update
|
|
135
|
+
return {
|
|
136
|
+
resolved: false,
|
|
137
|
+
data: serverRecord, // Server wins (authoritative)
|
|
138
|
+
conflict: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// No local changes, use server data
|
|
142
|
+
return {
|
|
143
|
+
resolved: true,
|
|
144
|
+
data: serverRecord,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error('Failed to merge with outbox:', error);
|
|
149
|
+
return {
|
|
150
|
+
resolved: true,
|
|
151
|
+
data: serverRecord,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Clear all data for an entity
|
|
157
|
+
*/
|
|
158
|
+
async clearEntity(entity) {
|
|
159
|
+
try {
|
|
160
|
+
const allKeys = await this.storage.getAllKeys();
|
|
161
|
+
const entityKeys = allKeys.filter((key) => key.startsWith(`entity:${entity}:`));
|
|
162
|
+
for (const key of entityKeys) {
|
|
163
|
+
await this.storage.removeItem(key);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error('Failed to clear entity:', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get sync metadata for entity
|
|
172
|
+
* Used to track which records have been synced
|
|
173
|
+
*/
|
|
174
|
+
async getSyncStatus(entity) {
|
|
175
|
+
try {
|
|
176
|
+
const data = await this.getEntityData(entity);
|
|
177
|
+
return {
|
|
178
|
+
entity,
|
|
179
|
+
totalRecords: data.length,
|
|
180
|
+
syncedRecords: data.filter((d) => d._synced).length,
|
|
181
|
+
pendingRecords: data.filter((d) => d._outbox).length,
|
|
182
|
+
lastUpdate: Math.max(...data.map((d) => d._syncedAt || 0), 0),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('Failed to get sync status:', error);
|
|
187
|
+
return {
|
|
188
|
+
entity,
|
|
189
|
+
totalRecords: 0,
|
|
190
|
+
syncedRecords: 0,
|
|
191
|
+
pendingRecords: 0,
|
|
192
|
+
lastUpdate: 0,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Listen for all changes across entities
|
|
198
|
+
*/
|
|
199
|
+
onAnyChange(listener) {
|
|
200
|
+
const allListener = (change) => {
|
|
201
|
+
listener(change.entity, change);
|
|
202
|
+
};
|
|
203
|
+
// This is a catch-all listener that would need to be managed separately
|
|
204
|
+
// For now, return a no-op unsubscribe since we don't have a global listener yet
|
|
205
|
+
return () => { };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=realtime-sync-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-sync-manager.js","sourceRoot":"","sources":["../../../../src/realtime/realtime-sync-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ChangeMessage } from '@edgebasejs/core';
|
|
2
|
+
export interface SubscriptionOptions {
|
|
3
|
+
entity: string;
|
|
4
|
+
filters?: Record<string, unknown>;
|
|
5
|
+
onData?: (change: ChangeMessage) => void;
|
|
6
|
+
onError?: (error: Error) => void;
|
|
7
|
+
onSubscribed?: (subscriptionId: string) => void;
|
|
8
|
+
onUnsubscribed?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ConnectionConfig {
|
|
11
|
+
url: string;
|
|
12
|
+
token: string;
|
|
13
|
+
reconnectAttempts?: number;
|
|
14
|
+
reconnectDelay?: number;
|
|
15
|
+
heartbeatInterval?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Manages WebSocket connection and subscriptions on the client side
|
|
19
|
+
*/
|
|
20
|
+
export declare class ClientSubscriptionHandler {
|
|
21
|
+
private config;
|
|
22
|
+
private ws;
|
|
23
|
+
private subscriptions;
|
|
24
|
+
private reconnectAttempts;
|
|
25
|
+
private reconnectTimeout;
|
|
26
|
+
private heartbeatTimeout;
|
|
27
|
+
private messageHandlers;
|
|
28
|
+
constructor(config: ConnectionConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Connect to WebSocket server
|
|
31
|
+
*/
|
|
32
|
+
connect(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to entity changes
|
|
35
|
+
*/
|
|
36
|
+
subscribe(options: SubscriptionOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* Unsubscribe from entity changes
|
|
39
|
+
*/
|
|
40
|
+
unsubscribe(subscriptionId: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Handle incoming WebSocket messages
|
|
43
|
+
*/
|
|
44
|
+
private handleMessage;
|
|
45
|
+
/**
|
|
46
|
+
* Send heartbeat to server
|
|
47
|
+
*/
|
|
48
|
+
private sendHeartbeat;
|
|
49
|
+
/**
|
|
50
|
+
* Setup periodic heartbeat
|
|
51
|
+
*/
|
|
52
|
+
private setupHeartbeat;
|
|
53
|
+
/**
|
|
54
|
+
* Clear heartbeat
|
|
55
|
+
*/
|
|
56
|
+
private clearHeartbeat;
|
|
57
|
+
/**
|
|
58
|
+
* Attempt to reconnect with exponential backoff
|
|
59
|
+
*/
|
|
60
|
+
private attemptReconnect;
|
|
61
|
+
/**
|
|
62
|
+
* Disconnect from WebSocket
|
|
63
|
+
*/
|
|
64
|
+
disconnect(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Check if connected
|
|
67
|
+
*/
|
|
68
|
+
isConnected(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Get subscription count
|
|
71
|
+
*/
|
|
72
|
+
getSubscriptionCount(): number;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=subscription-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-handler.d.ts","sourceRoot":"","sources":["../../../../src/realtime/subscription-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,kBAAkB,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,yBAAyB;IAQxB,OAAO,CAAC,MAAM;IAP1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,eAAe,CAA6C;gBAEhD,MAAM,EAAE,gBAAgB;IAE5C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC9B;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM;IAsC/C;;OAEG;IACH,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAkBzC;;OAEG;IACH,OAAO,CAAC,aAAa;IAsDrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;OAEG;IACH,UAAU,IAAI,IAAI;IAiBlB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,oBAAoB,IAAI,MAAM;CAG/B"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages WebSocket connection and subscriptions on the client side
|
|
3
|
+
*/
|
|
4
|
+
export class ClientSubscriptionHandler {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.ws = null;
|
|
8
|
+
this.subscriptions = new Map();
|
|
9
|
+
this.reconnectAttempts = 0;
|
|
10
|
+
this.reconnectTimeout = null;
|
|
11
|
+
this.heartbeatTimeout = null;
|
|
12
|
+
this.messageHandlers = new Map();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Connect to WebSocket server
|
|
16
|
+
*/
|
|
17
|
+
async connect() {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
try {
|
|
20
|
+
const wsUrl = this.config.url.replace(/^http/, 'ws');
|
|
21
|
+
this.ws = new WebSocket(wsUrl);
|
|
22
|
+
this.ws.onopen = () => {
|
|
23
|
+
console.log('WebSocket connected');
|
|
24
|
+
this.reconnectAttempts = 0;
|
|
25
|
+
this.setupHeartbeat();
|
|
26
|
+
resolve();
|
|
27
|
+
};
|
|
28
|
+
this.ws.onmessage = (event) => {
|
|
29
|
+
this.handleMessage(event.data);
|
|
30
|
+
};
|
|
31
|
+
this.ws.onerror = (error) => {
|
|
32
|
+
console.error('WebSocket error:', error);
|
|
33
|
+
reject(new Error('WebSocket connection failed'));
|
|
34
|
+
};
|
|
35
|
+
this.ws.onclose = () => {
|
|
36
|
+
console.log('WebSocket disconnected');
|
|
37
|
+
this.clearHeartbeat();
|
|
38
|
+
this.attemptReconnect();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
reject(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to entity changes
|
|
48
|
+
*/
|
|
49
|
+
subscribe(options) {
|
|
50
|
+
const subscriptionId = `csub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
51
|
+
// Store subscription locally
|
|
52
|
+
this.subscriptions.set(subscriptionId, options);
|
|
53
|
+
// Register message handler for this subscription
|
|
54
|
+
this.messageHandlers.set(subscriptionId, (message) => {
|
|
55
|
+
if (message.entity === options.entity) {
|
|
56
|
+
// Check if filters match
|
|
57
|
+
if (options.filters && message.record) {
|
|
58
|
+
let matches = true;
|
|
59
|
+
for (const [key, value] of Object.entries(options.filters)) {
|
|
60
|
+
if (message.record[key] !== value) {
|
|
61
|
+
matches = false;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!matches)
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
options.onData?.(message);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Send subscription message to server
|
|
72
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
73
|
+
const message = {
|
|
74
|
+
type: 'subscribe',
|
|
75
|
+
entity: options.entity,
|
|
76
|
+
filters: options.filters,
|
|
77
|
+
};
|
|
78
|
+
this.ws.send(JSON.stringify(message));
|
|
79
|
+
}
|
|
80
|
+
return subscriptionId;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Unsubscribe from entity changes
|
|
84
|
+
*/
|
|
85
|
+
unsubscribe(subscriptionId) {
|
|
86
|
+
const options = this.subscriptions.get(subscriptionId);
|
|
87
|
+
if (!options)
|
|
88
|
+
return;
|
|
89
|
+
// Remove subscription
|
|
90
|
+
this.subscriptions.delete(subscriptionId);
|
|
91
|
+
this.messageHandlers.delete(subscriptionId);
|
|
92
|
+
// Send unsubscribe message to server
|
|
93
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
94
|
+
const message = {
|
|
95
|
+
type: 'unsubscribe',
|
|
96
|
+
entity: options.entity,
|
|
97
|
+
};
|
|
98
|
+
this.ws.send(JSON.stringify(message));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Handle incoming WebSocket messages
|
|
103
|
+
*/
|
|
104
|
+
handleMessage(data) {
|
|
105
|
+
try {
|
|
106
|
+
const message = JSON.parse(data);
|
|
107
|
+
switch (message.type) {
|
|
108
|
+
case 'change':
|
|
109
|
+
// Broadcast to all registered handlers
|
|
110
|
+
for (const handler of this.messageHandlers.values()) {
|
|
111
|
+
handler(message);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'subscribed':
|
|
115
|
+
// Find the subscription that was just created
|
|
116
|
+
const subOptions = Array.from(this.subscriptions.values()).find((sub) => sub.entity === message.entity);
|
|
117
|
+
if (subOptions) {
|
|
118
|
+
subOptions.onSubscribed?.(message.subscriptionId);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'unsubscribed':
|
|
122
|
+
const unsubOptions = Array.from(this.subscriptions.values()).find((sub) => sub.entity === message.entity);
|
|
123
|
+
if (unsubOptions) {
|
|
124
|
+
unsubOptions.onUnsubscribed?.();
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'error':
|
|
128
|
+
console.error(`Server error: ${message.code} - ${message.message}`);
|
|
129
|
+
for (const sub of this.subscriptions.values()) {
|
|
130
|
+
sub.onError?.(new Error(message.message));
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'connected':
|
|
134
|
+
console.log(`Connected to server with connectionId: ${message.connectionId}`);
|
|
135
|
+
break;
|
|
136
|
+
case 'heartbeat':
|
|
137
|
+
// Server heartbeat - no action needed
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
console.warn(`Unknown message type: ${message.type}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error('Failed to parse WebSocket message:', error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Send heartbeat to server
|
|
149
|
+
*/
|
|
150
|
+
sendHeartbeat() {
|
|
151
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
152
|
+
const message = {
|
|
153
|
+
type: 'heartbeat',
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
};
|
|
156
|
+
this.ws.send(JSON.stringify(message));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Setup periodic heartbeat
|
|
161
|
+
*/
|
|
162
|
+
setupHeartbeat() {
|
|
163
|
+
const interval = this.config.heartbeatInterval || 30000;
|
|
164
|
+
this.heartbeatTimeout = setInterval(() => {
|
|
165
|
+
this.sendHeartbeat();
|
|
166
|
+
}, interval);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Clear heartbeat
|
|
170
|
+
*/
|
|
171
|
+
clearHeartbeat() {
|
|
172
|
+
if (this.heartbeatTimeout) {
|
|
173
|
+
clearInterval(this.heartbeatTimeout);
|
|
174
|
+
this.heartbeatTimeout = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Attempt to reconnect with exponential backoff
|
|
179
|
+
*/
|
|
180
|
+
attemptReconnect() {
|
|
181
|
+
const maxAttempts = this.config.reconnectAttempts || 5;
|
|
182
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
183
|
+
console.error('Max reconnection attempts reached');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.reconnectAttempts++;
|
|
187
|
+
const delay = (this.config.reconnectDelay || 1000) * Math.pow(2, this.reconnectAttempts - 1);
|
|
188
|
+
console.log(`Attempting to reconnect in ${delay}ms...`);
|
|
189
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
190
|
+
this.connect().catch((error) => {
|
|
191
|
+
console.error('Reconnection failed:', error);
|
|
192
|
+
});
|
|
193
|
+
}, delay);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Disconnect from WebSocket
|
|
197
|
+
*/
|
|
198
|
+
disconnect() {
|
|
199
|
+
if (this.reconnectTimeout) {
|
|
200
|
+
clearTimeout(this.reconnectTimeout);
|
|
201
|
+
this.reconnectTimeout = null;
|
|
202
|
+
}
|
|
203
|
+
this.clearHeartbeat();
|
|
204
|
+
if (this.ws) {
|
|
205
|
+
this.ws.close();
|
|
206
|
+
this.ws = null;
|
|
207
|
+
}
|
|
208
|
+
this.subscriptions.clear();
|
|
209
|
+
this.messageHandlers.clear();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Check if connected
|
|
213
|
+
*/
|
|
214
|
+
isConnected() {
|
|
215
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get subscription count
|
|
219
|
+
*/
|
|
220
|
+
getSubscriptionCount() {
|
|
221
|
+
return this.subscriptions.size;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=subscription-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-handler.js","sourceRoot":"","sources":["../../../../src/realtime/subscription-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ export interface ClientSyncOptions {
|
|
|
5
5
|
accessToken: string;
|
|
6
6
|
storage: IStorageAdapter;
|
|
7
7
|
outboxManager: OutboxManager;
|
|
8
|
+
autoSync?: boolean;
|
|
8
9
|
onConflict?: (entity: string, recordId: string, serverData: Record<string, any>) => void;
|
|
9
10
|
onError?: (error: Error) => void;
|
|
10
11
|
}
|
|
@@ -24,6 +25,7 @@ export declare class ClientSyncEngine {
|
|
|
24
25
|
private outboxManager;
|
|
25
26
|
private onConflict?;
|
|
26
27
|
private onError?;
|
|
28
|
+
private autoSync;
|
|
27
29
|
private isSyncing;
|
|
28
30
|
private lastSyncTimestamp;
|
|
29
31
|
private syncStatusKey;
|
|
@@ -36,6 +38,10 @@ export declare class ClientSyncEngine {
|
|
|
36
38
|
* Update access token (for when user logs in)
|
|
37
39
|
*/
|
|
38
40
|
setAccessToken(token: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Enable or disable automatic synchronization
|
|
43
|
+
*/
|
|
44
|
+
setAutoSync(enabled: boolean): void;
|
|
39
45
|
/**
|
|
40
46
|
* Get current sync status
|
|
41
47
|
*/
|
|
@@ -44,6 +50,10 @@ export declare class ClientSyncEngine {
|
|
|
44
50
|
* Sync with server
|
|
45
51
|
*/
|
|
46
52
|
sync(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Force a full sync (ignore lastSyncTimestamp)
|
|
55
|
+
*/
|
|
56
|
+
syncFull(): Promise<void>;
|
|
47
57
|
/**
|
|
48
58
|
* Send sync request to server
|
|
49
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../../../src/sync/sync-engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAoB,MAAM,0BAA0B,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IACzF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAC,CAA8E;IACjG,OAAO,CAAC,OAAO,CAAC,CAAyB;IACzC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,aAAa,CAAwB;gBAEjC,OAAO,EAAE,iBAAiB;
|
|
1
|
+
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../../../src/sync/sync-engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAoB,MAAM,0BAA0B,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IACzF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAC,CAA8E;IACjG,OAAO,CAAC,OAAO,CAAC,CAAyB;IACzC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,aAAa,CAAwB;gBAEjC,OAAO,EAAE,iBAAiB;IAUtC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAInC;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAStC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;YACW,SAAS;IAiBvB;;OAEG;YACW,eAAe;IAiG7B;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,OAAO,GAAE,MAAU,GAClB,OAAO,CAAC,IAAI,CAAC;IAoBhB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B;AAED,eAAe,gBAAgB,CAAC"}
|
|
@@ -14,6 +14,7 @@ export class ClientSyncEngine {
|
|
|
14
14
|
this.outboxManager = options.outboxManager;
|
|
15
15
|
this.onConflict = options.onConflict;
|
|
16
16
|
this.onError = options.onError;
|
|
17
|
+
this.autoSync = options.autoSync ?? true;
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Initialize sync engine
|
|
@@ -29,6 +30,12 @@ export class ClientSyncEngine {
|
|
|
29
30
|
setAccessToken(token) {
|
|
30
31
|
this.accessToken = token;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Enable or disable automatic synchronization
|
|
35
|
+
*/
|
|
36
|
+
setAutoSync(enabled) {
|
|
37
|
+
this.autoSync = enabled;
|
|
38
|
+
}
|
|
32
39
|
/**
|
|
33
40
|
* Get current sync status
|
|
34
41
|
*/
|
|
@@ -80,6 +87,14 @@ export class ClientSyncEngine {
|
|
|
80
87
|
this.isSyncing = false;
|
|
81
88
|
}
|
|
82
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Force a full sync (ignore lastSyncTimestamp)
|
|
92
|
+
*/
|
|
93
|
+
async syncFull() {
|
|
94
|
+
this.lastSyncTimestamp = null;
|
|
95
|
+
await this.storage.removeItem(this.syncStatusKey);
|
|
96
|
+
await this.sync();
|
|
97
|
+
}
|
|
83
98
|
/**
|
|
84
99
|
* Send sync request to server
|
|
85
100
|
*/
|
|
@@ -111,7 +126,19 @@ export class ClientSyncEngine {
|
|
|
111
126
|
}
|
|
112
127
|
// Update local storage with server version
|
|
113
128
|
const storageKey = `entity:${change.entity}:${change.id}`;
|
|
114
|
-
|
|
129
|
+
if (change.operation === 'delete') {
|
|
130
|
+
await this.storage.removeItem(storageKey);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const existingRaw = await this.storage.getItem(storageKey);
|
|
134
|
+
const existing = existingRaw ? JSON.parse(existingRaw) : {};
|
|
135
|
+
const payload = {
|
|
136
|
+
...existing,
|
|
137
|
+
...(change.data || {}),
|
|
138
|
+
_version: change.version ?? (change.operation === 'create' ? 1 : 0),
|
|
139
|
+
};
|
|
140
|
+
await this.storage.setItem(storageKey, JSON.stringify(payload));
|
|
141
|
+
}
|
|
115
142
|
}
|
|
116
143
|
// Process conflicts
|
|
117
144
|
for (const conflict of response.conflicts) {
|
|
@@ -126,7 +153,10 @@ export class ClientSyncEngine {
|
|
|
126
153
|
});
|
|
127
154
|
// Update local storage with resolved data
|
|
128
155
|
const storageKey = `entity:${conflict.entity}:${conflict.id}`;
|
|
129
|
-
await this.storage.setItem(storageKey, JSON.stringify(
|
|
156
|
+
await this.storage.setItem(storageKey, JSON.stringify({
|
|
157
|
+
...resolution.resolvedData,
|
|
158
|
+
_version: conflict.serverVersion,
|
|
159
|
+
}));
|
|
130
160
|
// Notify caller of conflict
|
|
131
161
|
this.onConflict?.(conflict.entity, conflict.id, conflict.serverData);
|
|
132
162
|
// Remove from outbox since server won
|
|
@@ -173,9 +203,11 @@ export class ClientSyncEngine {
|
|
|
173
203
|
});
|
|
174
204
|
// Try to sync (will be handled by background sync if fails)
|
|
175
205
|
// Don't await to allow optimistic updates
|
|
176
|
-
this.
|
|
177
|
-
|
|
178
|
-
|
|
206
|
+
if (this.autoSync) {
|
|
207
|
+
this.sync().catch(() => {
|
|
208
|
+
// Sync failed - will retry in background
|
|
209
|
+
});
|
|
210
|
+
}
|
|
179
211
|
}
|
|
180
212
|
/**
|
|
181
213
|
* Clear sync state (for logout/reset)
|