@prabhask5/stellar-engine 1.0.3

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.
Files changed (138) hide show
  1. package/README.md +295 -0
  2. package/dist/actions/remoteChange.d.ts +79 -0
  3. package/dist/actions/remoteChange.d.ts.map +1 -0
  4. package/dist/actions/remoteChange.js +300 -0
  5. package/dist/actions/remoteChange.js.map +1 -0
  6. package/dist/auth/admin.d.ts +12 -0
  7. package/dist/auth/admin.d.ts.map +1 -0
  8. package/dist/auth/admin.js +23 -0
  9. package/dist/auth/admin.js.map +1 -0
  10. package/dist/auth/offlineCredentials.d.ts +41 -0
  11. package/dist/auth/offlineCredentials.d.ts.map +1 -0
  12. package/dist/auth/offlineCredentials.js +121 -0
  13. package/dist/auth/offlineCredentials.js.map +1 -0
  14. package/dist/auth/offlineLogin.d.ts +34 -0
  15. package/dist/auth/offlineLogin.d.ts.map +1 -0
  16. package/dist/auth/offlineLogin.js +75 -0
  17. package/dist/auth/offlineLogin.js.map +1 -0
  18. package/dist/auth/offlineSession.d.ts +22 -0
  19. package/dist/auth/offlineSession.d.ts.map +1 -0
  20. package/dist/auth/offlineSession.js +54 -0
  21. package/dist/auth/offlineSession.js.map +1 -0
  22. package/dist/auth/resolveAuthState.d.ts +24 -0
  23. package/dist/auth/resolveAuthState.d.ts.map +1 -0
  24. package/dist/auth/resolveAuthState.js +69 -0
  25. package/dist/auth/resolveAuthState.js.map +1 -0
  26. package/dist/config.d.ts +53 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +55 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/conflicts.d.ts +70 -0
  31. package/dist/conflicts.d.ts.map +1 -0
  32. package/dist/conflicts.js +321 -0
  33. package/dist/conflicts.js.map +1 -0
  34. package/dist/data.d.ts +77 -0
  35. package/dist/data.d.ts.map +1 -0
  36. package/dist/data.js +360 -0
  37. package/dist/data.js.map +1 -0
  38. package/dist/database.d.ts +31 -0
  39. package/dist/database.d.ts.map +1 -0
  40. package/dist/database.js +51 -0
  41. package/dist/database.js.map +1 -0
  42. package/dist/debug.d.ts +11 -0
  43. package/dist/debug.d.ts.map +1 -0
  44. package/dist/debug.js +48 -0
  45. package/dist/debug.js.map +1 -0
  46. package/dist/deviceId.d.ts +16 -0
  47. package/dist/deviceId.d.ts.map +1 -0
  48. package/dist/deviceId.js +48 -0
  49. package/dist/deviceId.js.map +1 -0
  50. package/dist/engine.d.ts +14 -0
  51. package/dist/engine.d.ts.map +1 -0
  52. package/dist/engine.js +1903 -0
  53. package/dist/engine.js.map +1 -0
  54. package/dist/entries/actions.d.ts +2 -0
  55. package/dist/entries/actions.d.ts.map +1 -0
  56. package/dist/entries/actions.js +3 -0
  57. package/dist/entries/actions.js.map +1 -0
  58. package/dist/entries/auth.d.ts +7 -0
  59. package/dist/entries/auth.d.ts.map +1 -0
  60. package/dist/entries/auth.js +6 -0
  61. package/dist/entries/auth.js.map +1 -0
  62. package/dist/entries/config.d.ts +3 -0
  63. package/dist/entries/config.d.ts.map +1 -0
  64. package/dist/entries/config.js +3 -0
  65. package/dist/entries/config.js.map +1 -0
  66. package/dist/entries/stores.d.ts +9 -0
  67. package/dist/entries/stores.d.ts.map +1 -0
  68. package/dist/entries/stores.js +9 -0
  69. package/dist/entries/stores.js.map +1 -0
  70. package/dist/entries/types.d.ts +11 -0
  71. package/dist/entries/types.d.ts.map +1 -0
  72. package/dist/entries/types.js +2 -0
  73. package/dist/entries/types.js.map +1 -0
  74. package/dist/entries/utils.d.ts +3 -0
  75. package/dist/entries/utils.d.ts.map +1 -0
  76. package/dist/entries/utils.js +4 -0
  77. package/dist/entries/utils.js.map +1 -0
  78. package/dist/index.d.ts +32 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +39 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/operations.d.ts +73 -0
  83. package/dist/operations.d.ts.map +1 -0
  84. package/dist/operations.js +227 -0
  85. package/dist/operations.js.map +1 -0
  86. package/dist/queue.d.ts +32 -0
  87. package/dist/queue.d.ts.map +1 -0
  88. package/dist/queue.js +377 -0
  89. package/dist/queue.js.map +1 -0
  90. package/dist/realtime.d.ts +57 -0
  91. package/dist/realtime.d.ts.map +1 -0
  92. package/dist/realtime.js +491 -0
  93. package/dist/realtime.js.map +1 -0
  94. package/dist/reconnectHandler.d.ts +16 -0
  95. package/dist/reconnectHandler.d.ts.map +1 -0
  96. package/dist/reconnectHandler.js +21 -0
  97. package/dist/reconnectHandler.js.map +1 -0
  98. package/dist/runtime/runtimeConfig.d.ts +27 -0
  99. package/dist/runtime/runtimeConfig.d.ts.map +1 -0
  100. package/dist/runtime/runtimeConfig.js +133 -0
  101. package/dist/runtime/runtimeConfig.js.map +1 -0
  102. package/dist/stores/authState.d.ts +57 -0
  103. package/dist/stores/authState.d.ts.map +1 -0
  104. package/dist/stores/authState.js +154 -0
  105. package/dist/stores/authState.js.map +1 -0
  106. package/dist/stores/network.d.ts +9 -0
  107. package/dist/stores/network.d.ts.map +1 -0
  108. package/dist/stores/network.js +97 -0
  109. package/dist/stores/network.js.map +1 -0
  110. package/dist/stores/remoteChanges.d.ts +142 -0
  111. package/dist/stores/remoteChanges.d.ts.map +1 -0
  112. package/dist/stores/remoteChanges.js +353 -0
  113. package/dist/stores/remoteChanges.js.map +1 -0
  114. package/dist/stores/sync.d.ts +35 -0
  115. package/dist/stores/sync.d.ts.map +1 -0
  116. package/dist/stores/sync.js +115 -0
  117. package/dist/stores/sync.js.map +1 -0
  118. package/dist/supabase/auth.d.ts +60 -0
  119. package/dist/supabase/auth.d.ts.map +1 -0
  120. package/dist/supabase/auth.js +298 -0
  121. package/dist/supabase/auth.js.map +1 -0
  122. package/dist/supabase/client.d.ts +15 -0
  123. package/dist/supabase/client.d.ts.map +1 -0
  124. package/dist/supabase/client.js +149 -0
  125. package/dist/supabase/client.js.map +1 -0
  126. package/dist/supabase/validate.d.ts +11 -0
  127. package/dist/supabase/validate.d.ts.map +1 -0
  128. package/dist/supabase/validate.js +38 -0
  129. package/dist/supabase/validate.js.map +1 -0
  130. package/dist/types.d.ts +78 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/types.js +16 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/utils.d.ts +24 -0
  135. package/dist/utils.d.ts.map +1 -0
  136. package/dist/utils.js +56 -0
  137. package/dist/utils.js.map +1 -0
  138. package/package.json +84 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Real-Time Subscription Manager
3
+ *
4
+ * Phase 5 of multi-device sync: Implements Supabase Realtime subscriptions
5
+ * for instant multi-device synchronization.
6
+ *
7
+ * Design decisions:
8
+ * - Uses Supabase Realtime PostgreSQL Changes for all entity tables
9
+ * - Skips echo (own changes) by comparing device_id in the payload
10
+ * - Tracks recently processed entities to prevent duplicate processing with polling
11
+ * - Applies changes through existing conflict resolution engine
12
+ * - Falls back to polling if WebSocket connection fails (max 5 reconnect attempts)
13
+ * - Single channel per user with filter by user_id for efficiency
14
+ * - Pauses reconnection attempts while offline (waits for online event)
15
+ * - Uses reconnectScheduled flag to prevent duplicate reconnect attempts
16
+ */
17
+ export type RealtimeConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
18
+ /**
19
+ * Subscribe to connection state changes
20
+ */
21
+ export declare function onConnectionStateChange(callback: (state: RealtimeConnectionState) => void): () => void;
22
+ /**
23
+ * Subscribe to data update notifications (called after local DB is updated)
24
+ */
25
+ export declare function onRealtimeDataUpdate(callback: (table: string, entityId: string) => void): () => void;
26
+ /**
27
+ * Get current realtime connection state.
28
+ * Used by debug utilities exposed on the window object.
29
+ */
30
+ export declare function getConnectionState(): RealtimeConnectionState;
31
+ /**
32
+ * Check if an entity was recently processed via realtime
33
+ * Used by engine.ts to prevent duplicate processing during polling
34
+ */
35
+ export declare function wasRecentlyProcessedByRealtime(entityId: string): boolean;
36
+ /**
37
+ * Start realtime subscriptions for a user
38
+ */
39
+ export declare function startRealtimeSubscriptions(userId: string): Promise<void>;
40
+ /**
41
+ * Stop realtime subscriptions (public API)
42
+ */
43
+ export declare function stopRealtimeSubscriptions(): Promise<void>;
44
+ /**
45
+ * Pause realtime (when going offline) - stops reconnection attempts
46
+ * Called by sync engine when offline event fires
47
+ */
48
+ export declare function pauseRealtime(): void;
49
+ /**
50
+ * Check if realtime is healthy (connected and not in error state)
51
+ */
52
+ export declare function isRealtimeHealthy(): boolean;
53
+ /**
54
+ * Clean up expired entries from recently processed tracking
55
+ */
56
+ export declare function cleanupRealtimeTracking(): void;
57
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkBH,MAAM,MAAM,uBAAuB,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;AAqC5F;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GACjD,MAAM,IAAI,CAKZ;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAClD,MAAM,IAAI,CAGZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,uBAAuB,CAE5D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUxE;AAiUD;;GAEG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2G9E;AAED;;GAEG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB/D;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAWpC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAO9C"}
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Real-Time Subscription Manager
3
+ *
4
+ * Phase 5 of multi-device sync: Implements Supabase Realtime subscriptions
5
+ * for instant multi-device synchronization.
6
+ *
7
+ * Design decisions:
8
+ * - Uses Supabase Realtime PostgreSQL Changes for all entity tables
9
+ * - Skips echo (own changes) by comparing device_id in the payload
10
+ * - Tracks recently processed entities to prevent duplicate processing with polling
11
+ * - Applies changes through existing conflict resolution engine
12
+ * - Falls back to polling if WebSocket connection fails (max 5 reconnect attempts)
13
+ * - Single channel per user with filter by user_id for efficiency
14
+ * - Pauses reconnection attempts while offline (waits for online event)
15
+ * - Uses reconnectScheduled flag to prevent duplicate reconnect attempts
16
+ */
17
+ import { debugLog, debugWarn, debugError } from './debug';
18
+ import { getEngineConfig } from './config';
19
+ import { getDeviceId } from './deviceId';
20
+ import { resolveConflicts, storeConflictHistory, getPendingOpsForEntity } from './conflicts';
21
+ import { getPendingEntityIds } from './queue';
22
+ import { remoteChangesStore } from './stores/remoteChanges';
23
+ // Protection window for recently modified entities (matches engine.ts)
24
+ const RECENTLY_MODIFIED_TTL_MS = 2000;
25
+ // Track entities that realtime has just processed (to prevent duplicate processing with polling)
26
+ // This is separate from engine.ts's recentlyModifiedEntities (which tracks local writes)
27
+ const recentlyProcessedByRealtime = new Map();
28
+ const state = {
29
+ channel: null,
30
+ connectionState: 'disconnected',
31
+ userId: null,
32
+ deviceId: '',
33
+ lastError: null,
34
+ reconnectAttempts: 0,
35
+ reconnectTimeout: null
36
+ };
37
+ // Callbacks for state changes and data updates
38
+ const connectionCallbacks = new Set();
39
+ const dataUpdateCallbacks = new Set();
40
+ // Maximum reconnect attempts before giving up (will fall back to polling)
41
+ const MAX_RECONNECT_ATTEMPTS = 5;
42
+ // Base delay for exponential backoff (ms)
43
+ const RECONNECT_BASE_DELAY = 1000;
44
+ // Lock to prevent concurrent start/stop operations
45
+ let operationInProgress = false;
46
+ // Flag to track if reconnect is already scheduled (prevents duplicate scheduling)
47
+ let reconnectScheduled = false;
48
+ /**
49
+ * Subscribe to connection state changes
50
+ */
51
+ export function onConnectionStateChange(callback) {
52
+ connectionCallbacks.add(callback);
53
+ // Immediately call with current state
54
+ callback(state.connectionState);
55
+ return () => connectionCallbacks.delete(callback);
56
+ }
57
+ /**
58
+ * Subscribe to data update notifications (called after local DB is updated)
59
+ */
60
+ export function onRealtimeDataUpdate(callback) {
61
+ dataUpdateCallbacks.add(callback);
62
+ return () => dataUpdateCallbacks.delete(callback);
63
+ }
64
+ /**
65
+ * Get current realtime connection state.
66
+ * Used by debug utilities exposed on the window object.
67
+ */
68
+ export function getConnectionState() {
69
+ return state.connectionState;
70
+ }
71
+ /**
72
+ * Check if an entity was recently processed via realtime
73
+ * Used by engine.ts to prevent duplicate processing during polling
74
+ */
75
+ export function wasRecentlyProcessedByRealtime(entityId) {
76
+ const processedAt = recentlyProcessedByRealtime.get(entityId);
77
+ if (!processedAt)
78
+ return false;
79
+ const age = Date.now() - processedAt;
80
+ if (age > RECENTLY_MODIFIED_TTL_MS) {
81
+ recentlyProcessedByRealtime.delete(entityId);
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+ /**
87
+ * Update connection state and notify subscribers
88
+ */
89
+ function setConnectionState(newState, error) {
90
+ state.connectionState = newState;
91
+ state.lastError = error || null;
92
+ for (const callback of connectionCallbacks) {
93
+ try {
94
+ callback(newState);
95
+ }
96
+ catch (e) {
97
+ debugError('[Realtime] Connection callback error:', e);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Notify data update subscribers
103
+ */
104
+ function notifyDataUpdate(table, entityId) {
105
+ debugLog(`[Realtime] Notifying ${dataUpdateCallbacks.size} subscribers of update: ${table}/${entityId}`);
106
+ for (const callback of dataUpdateCallbacks) {
107
+ try {
108
+ callback(table, entityId);
109
+ }
110
+ catch (e) {
111
+ debugError('[Realtime] Data update callback error:', e);
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Check if this change came from our own device (skip to prevent echo)
117
+ */
118
+ function isOwnDeviceChange(record) {
119
+ if (!record)
120
+ return false;
121
+ const recordDeviceId = record.device_id;
122
+ return recordDeviceId === state.deviceId;
123
+ }
124
+ /**
125
+ * Check if entity was recently processed by realtime (prevent duplicate processing)
126
+ */
127
+ function wasRecentlyProcessed(entityId) {
128
+ const processedAt = recentlyProcessedByRealtime.get(entityId);
129
+ if (!processedAt)
130
+ return false;
131
+ const age = Date.now() - processedAt;
132
+ if (age > RECENTLY_MODIFIED_TTL_MS) {
133
+ recentlyProcessedByRealtime.delete(entityId);
134
+ return false;
135
+ }
136
+ return true;
137
+ }
138
+ /**
139
+ * Handle incoming realtime change
140
+ */
141
+ async function handleRealtimeChange(table, payload) {
142
+ const eventType = payload.eventType;
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ const newRecord = payload.new;
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ const oldRecord = payload.old;
147
+ // Determine entity ID
148
+ const entityId = (newRecord?.id || oldRecord?.id);
149
+ debugLog(`[Realtime] Received ${eventType} on ${table}:`, entityId);
150
+ if (!entityId) {
151
+ debugWarn('[Realtime] Change without entity ID:', table, eventType);
152
+ return;
153
+ }
154
+ // Skip if this change came from our own device (prevents echo)
155
+ if (isOwnDeviceChange(newRecord)) {
156
+ debugLog(`[Realtime] Skipping own device change: ${table}/${entityId}`);
157
+ return;
158
+ }
159
+ // Skip if we just processed this entity (prevents rapid duplicate processing)
160
+ if (wasRecentlyProcessed(entityId)) {
161
+ debugLog(`[Realtime] Skipping recently processed: ${table}/${entityId}`);
162
+ return;
163
+ }
164
+ debugLog(`[Realtime] Processing remote change: ${eventType} ${table}/${entityId}`);
165
+ const dexieTable = getEngineConfig().tables.find(t => t.supabaseName === table)?.dexieTable;
166
+ if (!dexieTable) {
167
+ debugWarn('[Realtime] Unknown table:', table);
168
+ return;
169
+ }
170
+ try {
171
+ switch (eventType) {
172
+ case 'INSERT':
173
+ case 'UPDATE': {
174
+ if (!newRecord)
175
+ return;
176
+ // Check if entity is being edited in a manual-save form
177
+ const _isBeingEdited = remoteChangesStore.isEditing(entityId, table);
178
+ // Get local entity if it exists
179
+ const localEntity = await getEngineConfig().db.table(dexieTable).get(entityId);
180
+ // Determine which fields changed
181
+ const changedFields = [];
182
+ if (localEntity && newRecord) {
183
+ for (const key of Object.keys(newRecord)) {
184
+ if (key === 'updated_at' || key === '_version')
185
+ continue;
186
+ if (JSON.stringify(localEntity[key]) !== JSON.stringify(newRecord[key])) {
187
+ changedFields.push(key);
188
+ }
189
+ }
190
+ }
191
+ // Soft delete: UPDATE with deleted=true is treated as a deletion
192
+ // Play the delete animation BEFORE writing to DB so stores don't filter it out instantly
193
+ const isSoftDelete = newRecord.deleted === true && localEntity && !localEntity.deleted;
194
+ if (isSoftDelete) {
195
+ debugLog(`[Realtime] Soft delete detected for ${table}/${entityId}`);
196
+ // Record delete animation and wait for it to play
197
+ remoteChangesStore.recordRemoteChange(entityId, table, ['*'], true, 'DELETE');
198
+ await remoteChangesStore.markPendingDelete(entityId, table);
199
+ // Now write the soft-deleted record to DB (triggers reactive store refresh)
200
+ await getEngineConfig().db.table(dexieTable).put(newRecord);
201
+ recentlyProcessedByRealtime.set(entityId, Date.now());
202
+ notifyDataUpdate(table, entityId);
203
+ break;
204
+ }
205
+ // Check for pending operations
206
+ const pendingEntityIds = await getPendingEntityIds();
207
+ const hasPendingOps = pendingEntityIds.has(entityId);
208
+ let applied = false;
209
+ if (!localEntity) {
210
+ // New entity - just insert it
211
+ await getEngineConfig().db.table(dexieTable).put(newRecord);
212
+ applied = true;
213
+ }
214
+ else if (!hasPendingOps) {
215
+ // No pending ops - check if remote is newer
216
+ const localUpdatedAt = new Date(localEntity.updated_at).getTime();
217
+ const remoteUpdatedAt = new Date(newRecord.updated_at).getTime();
218
+ if (remoteUpdatedAt > localUpdatedAt) {
219
+ // Remote is newer, accept it
220
+ await getEngineConfig().db.table(dexieTable).put(newRecord);
221
+ applied = true;
222
+ }
223
+ }
224
+ else {
225
+ // Has pending operations - use conflict resolution
226
+ const pendingOps = await getPendingOpsForEntity(entityId);
227
+ const resolution = await resolveConflicts(table, entityId, localEntity, newRecord, pendingOps);
228
+ // Store merged entity
229
+ await getEngineConfig().db.table(dexieTable).put(resolution.mergedEntity);
230
+ applied = true;
231
+ // Store conflict history if there were conflicts
232
+ if (resolution.hasConflicts) {
233
+ await storeConflictHistory(resolution);
234
+ }
235
+ }
236
+ // Calculate value delta for increment/decrement detection
237
+ let valueDelta;
238
+ if (changedFields.includes('current_value') && localEntity && newRecord) {
239
+ const oldValue = localEntity.current_value || 0;
240
+ const newValue = newRecord.current_value || 0;
241
+ valueDelta = newValue - oldValue;
242
+ }
243
+ // Record the remote change for UI animation
244
+ // If entity is being edited in a form, the change will be deferred
245
+ // We pass the eventType so the store can detect the action type
246
+ if (changedFields.length > 0 || !localEntity) {
247
+ remoteChangesStore.recordRemoteChange(entityId, table, changedFields.length > 0 ? changedFields : ['*'], applied, eventType, valueDelta);
248
+ // Call table-specific onRemoteChange hook if configured
249
+ const tblConfig = getEngineConfig().tables.find(t => t.supabaseName === table);
250
+ if (tblConfig?.onRemoteChange) {
251
+ tblConfig.onRemoteChange(table, newRecord);
252
+ }
253
+ }
254
+ // Mark as recently processed to prevent duplicate processing by polling
255
+ recentlyProcessedByRealtime.set(entityId, Date.now());
256
+ // Notify subscribers
257
+ notifyDataUpdate(table, entityId);
258
+ break;
259
+ }
260
+ case 'DELETE': {
261
+ // For soft-delete systems, this would be an UPDATE with deleted=true
262
+ // But if hard delete happens, we should remove locally too
263
+ if (oldRecord) {
264
+ // Record the delete for UI animation before removing
265
+ remoteChangesStore.recordRemoteChange(entityId, table, ['*'], true, 'DELETE');
266
+ // Mark as pending delete and wait for animation to complete
267
+ // This allows the UI to play the delete animation before DOM removal
268
+ await remoteChangesStore.markPendingDelete(entityId, table);
269
+ // Now actually delete from database (triggers reactive DOM removal)
270
+ await getEngineConfig().db.table(dexieTable).delete(entityId);
271
+ // Mark as recently processed
272
+ recentlyProcessedByRealtime.set(entityId, Date.now());
273
+ notifyDataUpdate(table, entityId);
274
+ }
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ catch (error) {
280
+ debugError(`[Realtime] Error handling ${eventType} on ${table}:`, error);
281
+ }
282
+ }
283
+ /**
284
+ * Schedule reconnection with exponential backoff
285
+ * Only schedules if online - no point reconnecting while offline
286
+ */
287
+ function scheduleReconnect() {
288
+ // Prevent duplicate scheduling from multiple event callbacks
289
+ if (reconnectScheduled) {
290
+ return;
291
+ }
292
+ if (state.reconnectTimeout) {
293
+ clearTimeout(state.reconnectTimeout);
294
+ state.reconnectTimeout = null;
295
+ }
296
+ // Don't attempt reconnection while offline - wait for online event
297
+ if (typeof navigator !== 'undefined' && !navigator.onLine) {
298
+ debugLog('[Realtime] Offline - waiting for online event to reconnect');
299
+ setConnectionState('disconnected');
300
+ return;
301
+ }
302
+ if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
303
+ debugLog('[Realtime] Max reconnect attempts reached, falling back to polling');
304
+ setConnectionState('error', 'Max reconnection attempts reached');
305
+ return;
306
+ }
307
+ reconnectScheduled = true;
308
+ const delay = RECONNECT_BASE_DELAY * Math.pow(2, state.reconnectAttempts);
309
+ debugLog(`[Realtime] Scheduling reconnect attempt ${state.reconnectAttempts + 1} in ${delay}ms`);
310
+ state.reconnectTimeout = setTimeout(async () => {
311
+ reconnectScheduled = false;
312
+ // Double-check we're still online before attempting
313
+ if (typeof navigator !== 'undefined' && !navigator.onLine) {
314
+ debugLog('[Realtime] Went offline during backoff, cancelling reconnect');
315
+ return;
316
+ }
317
+ state.reconnectAttempts++;
318
+ if (state.userId) {
319
+ await startRealtimeSubscriptions(state.userId);
320
+ }
321
+ }, delay);
322
+ }
323
+ /**
324
+ * Internal stop function (doesn't check operation lock)
325
+ */
326
+ async function stopRealtimeSubscriptionsInternal() {
327
+ // Clear reconnect timeout and flag
328
+ if (state.reconnectTimeout) {
329
+ clearTimeout(state.reconnectTimeout);
330
+ state.reconnectTimeout = null;
331
+ }
332
+ reconnectScheduled = false;
333
+ // Unsubscribe from channel
334
+ if (state.channel) {
335
+ try {
336
+ await getEngineConfig().supabase.removeChannel(state.channel);
337
+ }
338
+ catch (error) {
339
+ debugError('[Realtime] Error removing channel:', error);
340
+ }
341
+ state.channel = null;
342
+ }
343
+ state.reconnectAttempts = 0;
344
+ setConnectionState('disconnected');
345
+ }
346
+ /**
347
+ * Start realtime subscriptions for a user
348
+ */
349
+ export async function startRealtimeSubscriptions(userId) {
350
+ if (typeof window === 'undefined')
351
+ return;
352
+ // Don't start if offline - wait for online event
353
+ if (!navigator.onLine) {
354
+ debugLog('[Realtime] Offline - skipping subscription start');
355
+ return;
356
+ }
357
+ // Don't start if already connected with same user
358
+ if (state.channel && state.userId === userId && state.connectionState === 'connected') {
359
+ return;
360
+ }
361
+ // Prevent concurrent start/stop operations
362
+ if (operationInProgress) {
363
+ debugLog('[Realtime] Operation already in progress, skipping');
364
+ return;
365
+ }
366
+ operationInProgress = true;
367
+ try {
368
+ // Stop existing subscriptions first
369
+ await stopRealtimeSubscriptionsInternal();
370
+ state.userId = userId;
371
+ state.deviceId = getDeviceId();
372
+ setConnectionState('connecting');
373
+ const config = getEngineConfig();
374
+ const realtimeTables = config.tables.map(t => t.supabaseName);
375
+ // Create a single channel for all tables
376
+ // Using a unique channel name per user
377
+ const channelName = `${config.prefix}_sync_${userId}`;
378
+ state.channel = config.supabase.channel(channelName);
379
+ // Subscribe to all tables without user_id filter
380
+ // RLS (Row Level Security) policies handle security at the database level
381
+ debugLog(`[Realtime] Setting up subscriptions for ${realtimeTables.length} tables`);
382
+ for (const table of realtimeTables) {
383
+ state.channel = state.channel.on('postgres_changes', {
384
+ event: '*',
385
+ schema: 'public',
386
+ table: table
387
+ }, (payload) => {
388
+ debugLog(`[Realtime] Raw payload received for ${table}:`, payload.eventType);
389
+ handleRealtimeChange(table, payload).catch((error) => {
390
+ debugError(`[Realtime] Error processing ${table} change:`, error);
391
+ });
392
+ });
393
+ }
394
+ // Subscribe to the channel
395
+ state.channel.subscribe((status, err) => {
396
+ // Release the operation lock once we get any response
397
+ operationInProgress = false;
398
+ switch (status) {
399
+ case 'SUBSCRIBED':
400
+ debugLog('[Realtime] Connected and subscribed');
401
+ state.reconnectAttempts = 0;
402
+ reconnectScheduled = false;
403
+ setConnectionState('connected');
404
+ break;
405
+ case 'CHANNEL_ERROR':
406
+ if (err?.message) {
407
+ debugError('[Realtime] Channel error:', err?.message);
408
+ }
409
+ setConnectionState('error', err?.message || 'Channel error');
410
+ scheduleReconnect();
411
+ break;
412
+ case 'TIMED_OUT':
413
+ debugWarn('[Realtime] Connection timed out');
414
+ setConnectionState('error', 'Connection timed out');
415
+ scheduleReconnect();
416
+ break;
417
+ case 'CLOSED':
418
+ debugLog('[Realtime] Channel closed');
419
+ // Only try to reconnect if:
420
+ // 1. We weren't intentionally disconnected
421
+ // 2. We have a user
422
+ // 3. We're not already scheduled for reconnect (prevents duplicate from CHANNEL_ERROR + CLOSED)
423
+ if (state.connectionState !== 'disconnected' && state.userId && !reconnectScheduled) {
424
+ setConnectionState('disconnected');
425
+ scheduleReconnect();
426
+ }
427
+ break;
428
+ }
429
+ });
430
+ }
431
+ catch (error) {
432
+ operationInProgress = false;
433
+ debugError('[Realtime] Failed to start subscriptions:', error);
434
+ setConnectionState('error', error instanceof Error ? error.message : 'Failed to connect');
435
+ scheduleReconnect();
436
+ }
437
+ }
438
+ /**
439
+ * Stop realtime subscriptions (public API)
440
+ */
441
+ export async function stopRealtimeSubscriptions() {
442
+ // Prevent concurrent operations
443
+ if (operationInProgress) {
444
+ debugLog('[Realtime] Operation already in progress, skipping stop');
445
+ return;
446
+ }
447
+ operationInProgress = true;
448
+ try {
449
+ await stopRealtimeSubscriptionsInternal();
450
+ state.userId = null;
451
+ // Clear recently processed tracking
452
+ recentlyProcessedByRealtime.clear();
453
+ }
454
+ finally {
455
+ operationInProgress = false;
456
+ }
457
+ }
458
+ /**
459
+ * Pause realtime (when going offline) - stops reconnection attempts
460
+ * Called by sync engine when offline event fires
461
+ */
462
+ export function pauseRealtime() {
463
+ // Clear any pending reconnect attempts and reset flags
464
+ if (state.reconnectTimeout) {
465
+ clearTimeout(state.reconnectTimeout);
466
+ state.reconnectTimeout = null;
467
+ }
468
+ reconnectScheduled = false;
469
+ // Reset reconnect attempts so we get fresh attempts when coming online
470
+ state.reconnectAttempts = 0;
471
+ setConnectionState('disconnected');
472
+ debugLog('[Realtime] Paused - waiting for online event');
473
+ }
474
+ /**
475
+ * Check if realtime is healthy (connected and not in error state)
476
+ */
477
+ export function isRealtimeHealthy() {
478
+ return state.connectionState === 'connected';
479
+ }
480
+ /**
481
+ * Clean up expired entries from recently processed tracking
482
+ */
483
+ export function cleanupRealtimeTracking() {
484
+ const now = Date.now();
485
+ for (const [entityId, processedAt] of recentlyProcessedByRealtime) {
486
+ if (now - processedAt > RECENTLY_MODIFIED_TTL_MS) {
487
+ recentlyProcessedByRealtime.delete(entityId);
488
+ }
489
+ }
490
+ }
491
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Reconnect Handler Registry
3
+ * Allows layout components to register callbacks for reconnection events
4
+ */
5
+ type ReconnectHandler = () => Promise<void>;
6
+ /**
7
+ * Set the reconnect handler (called from layout.svelte)
8
+ */
9
+ export declare function setReconnectHandler(handler: ReconnectHandler | null): void;
10
+ /**
11
+ * Call the reconnect handler if one is registered
12
+ * Called from the network store on reconnection
13
+ */
14
+ export declare function callReconnectHandler(): Promise<void>;
15
+ export {};
16
+ //# sourceMappingURL=reconnectHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconnectHandler.d.ts","sourceRoot":"","sources":["../src/reconnectHandler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,gBAAgB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAI5C;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAE1E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1D"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Reconnect Handler Registry
3
+ * Allows layout components to register callbacks for reconnection events
4
+ */
5
+ let reconnectHandler = null;
6
+ /**
7
+ * Set the reconnect handler (called from layout.svelte)
8
+ */
9
+ export function setReconnectHandler(handler) {
10
+ reconnectHandler = handler;
11
+ }
12
+ /**
13
+ * Call the reconnect handler if one is registered
14
+ * Called from the network store on reconnection
15
+ */
16
+ export async function callReconnectHandler() {
17
+ if (reconnectHandler) {
18
+ await reconnectHandler();
19
+ }
20
+ }
21
+ //# sourceMappingURL=reconnectHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconnectHandler.js","sourceRoot":"","sources":["../src/reconnectHandler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,IAAI,gBAAgB,GAA4B,IAAI,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgC;IAClE,gBAAgB,GAAG,OAAO,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Runtime Configuration Module
3
+ *
4
+ * Replaces build-time $env/static/public with runtime config fetched from the server.
5
+ * Config is cached in localStorage for instant subsequent loads and offline PWA support.
6
+ */
7
+ export interface AppConfig {
8
+ supabaseUrl: string;
9
+ supabaseAnonKey: string;
10
+ configured: boolean;
11
+ }
12
+ export declare function _setConfigPrefix(prefix: string): void;
13
+ /**
14
+ * Initialize config: tries localStorage first (instant), then validates against server.
15
+ * Returns the config if configured, null if not.
16
+ */
17
+ export declare function initConfig(): Promise<AppConfig | null>;
18
+ /**
19
+ * Get config synchronously. Returns cached config or null.
20
+ * Call initConfig() first to ensure config is loaded.
21
+ */
22
+ export declare function getConfig(): AppConfig | null;
23
+ /**
24
+ * Set config directly (used after setup wizard completes)
25
+ */
26
+ export declare function setConfig(config: AppConfig): void;
27
+ //# sourceMappingURL=runtimeConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtimeConfig.d.ts","sourceRoot":"","sources":["../../src/runtime/runtimeConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAID,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,QAE9C;AAuCD;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA6C5D;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,SAAS,GAAG,IAAI,CAQ5C;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAGjD"}