@affectively/dash 5.4.1 → 5.4.5

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 (92) hide show
  1. package/README.md +8 -189
  2. package/dist/automerge_wasm_bg-4hg5vg2g.wasm +0 -0
  3. package/dist/engine/sqlite.d.ts +30 -0
  4. package/dist/engine/vec_extension.d.ts +2 -0
  5. package/dist/index.d.ts +73 -0
  6. package/dist/index.js +53895 -0
  7. package/dist/middleware/errorHandler.d.ts +60 -0
  8. package/dist/{src/sync → sync}/AeonDurableSync.d.ts +7 -9
  9. package/dist/sync/AeonDurableSync.js +1984 -0
  10. package/dist/{src/sync → sync}/AutomergeProvider.d.ts +8 -8
  11. package/dist/sync/AutomergeProvider.js +4421 -0
  12. package/dist/sync/HybridProvider.d.ts +124 -0
  13. package/dist/sync/HybridProvider.js +8328 -0
  14. package/dist/sync/connection/WebRTCConnection.d.ts +23 -0
  15. package/dist/sync/connection/WebRTCConnection.js +59 -0
  16. package/dist/sync/index.d.ts +13 -0
  17. package/dist/sync/index.js +12773 -0
  18. package/dist/sync/provider/YjsSqliteProvider.d.ts +17 -0
  19. package/dist/sync/provider/YjsSqliteProvider.js +54 -0
  20. package/dist/sync/types.d.ts +74 -0
  21. package/dist/sync/webtransport/WebTransportProvider.d.ts +16 -0
  22. package/dist/sync/webtransport/WebTransportProvider.js +55 -0
  23. package/package.json +62 -70
  24. package/dist/src/api/firebase/auth/index.d.ts +0 -137
  25. package/dist/src/api/firebase/auth/index.js +0 -352
  26. package/dist/src/api/firebase/auth/providers.d.ts +0 -254
  27. package/dist/src/api/firebase/auth/providers.js +0 -518
  28. package/dist/src/api/firebase/database/index.d.ts +0 -108
  29. package/dist/src/api/firebase/database/index.js +0 -368
  30. package/dist/src/api/firebase/errors.d.ts +0 -15
  31. package/dist/src/api/firebase/errors.js +0 -215
  32. package/dist/src/api/firebase/firestore/data-types.d.ts +0 -116
  33. package/dist/src/api/firebase/firestore/data-types.js +0 -280
  34. package/dist/src/api/firebase/firestore/index.d.ts +0 -7
  35. package/dist/src/api/firebase/firestore/index.js +0 -13
  36. package/dist/src/api/firebase/firestore/listeners.d.ts +0 -20
  37. package/dist/src/api/firebase/firestore/listeners.js +0 -50
  38. package/dist/src/api/firebase/firestore/operations.d.ts +0 -123
  39. package/dist/src/api/firebase/firestore/operations.js +0 -490
  40. package/dist/src/api/firebase/firestore/query.d.ts +0 -118
  41. package/dist/src/api/firebase/firestore/query.js +0 -418
  42. package/dist/src/api/firebase/index.d.ts +0 -11
  43. package/dist/src/api/firebase/index.js +0 -17
  44. package/dist/src/api/firebase/storage/index.d.ts +0 -100
  45. package/dist/src/api/firebase/storage/index.js +0 -286
  46. package/dist/src/api/firebase/types.d.ts +0 -341
  47. package/dist/src/api/firebase/types.js +0 -4
  48. package/dist/src/auth/manager.d.ts +0 -182
  49. package/dist/src/auth/manager.js +0 -598
  50. package/dist/src/engine/ai.js +0 -76
  51. package/dist/src/engine/sqlite.d.ts +0 -353
  52. package/dist/src/engine/sqlite.js +0 -1328
  53. package/dist/src/engine/vec_extension.d.ts +0 -5
  54. package/dist/src/engine/vec_extension.js +0 -10
  55. package/dist/src/index.d.ts +0 -21
  56. package/dist/src/index.js +0 -26
  57. package/dist/src/mcp/server.js +0 -87
  58. package/dist/src/reactivity/signal.js +0 -31
  59. package/dist/src/schema/lens.d.ts +0 -29
  60. package/dist/src/schema/lens.js +0 -122
  61. package/dist/src/sync/AeonDurableSync.js +0 -133
  62. package/dist/src/sync/AutomergeProvider.js +0 -153
  63. package/dist/src/sync/aeon/config.d.ts +0 -21
  64. package/dist/src/sync/aeon/config.js +0 -14
  65. package/dist/src/sync/aeon/delta-adapter.d.ts +0 -62
  66. package/dist/src/sync/aeon/delta-adapter.js +0 -98
  67. package/dist/src/sync/aeon/index.d.ts +0 -18
  68. package/dist/src/sync/aeon/index.js +0 -19
  69. package/dist/src/sync/aeon/offline-adapter.d.ts +0 -110
  70. package/dist/src/sync/aeon/offline-adapter.js +0 -227
  71. package/dist/src/sync/aeon/presence-adapter.d.ts +0 -114
  72. package/dist/src/sync/aeon/presence-adapter.js +0 -157
  73. package/dist/src/sync/aeon/schema-adapter.d.ts +0 -95
  74. package/dist/src/sync/aeon/schema-adapter.js +0 -163
  75. package/dist/src/sync/backup.d.ts +0 -12
  76. package/dist/src/sync/backup.js +0 -44
  77. package/dist/src/sync/connection.d.ts +0 -20
  78. package/dist/src/sync/connection.js +0 -50
  79. package/dist/src/sync/d1-provider.d.ts +0 -103
  80. package/dist/src/sync/d1-provider.js +0 -418
  81. package/dist/src/sync/hybrid-provider.d.ts +0 -307
  82. package/dist/src/sync/hybrid-provider.js +0 -1353
  83. package/dist/src/sync/provider.d.ts +0 -11
  84. package/dist/src/sync/provider.js +0 -67
  85. package/dist/src/sync/types.d.ts +0 -32
  86. package/dist/src/sync/types.js +0 -4
  87. package/dist/src/sync/verify.d.ts +0 -1
  88. package/dist/src/sync/verify.js +0 -23
  89. package/dist/tsconfig.tsbuildinfo +0 -1
  90. /package/dist/{src/engine → engine}/ai.d.ts +0 -0
  91. /package/dist/{src/mcp → mcp}/server.d.ts +0 -0
  92. /package/dist/{src/reactivity → reactivity}/signal.d.ts +0 -0
@@ -1,418 +0,0 @@
1
- /**
2
- * D1 Sync Provider
3
- *
4
- * Provides HTTP-based sync between local Dash SQLite (WASM) and Cloudflare D1.
5
- *
6
- * Unlike the HybridProvider which syncs Yjs CRDT documents over WebSocket,
7
- * this provider syncs regular SQL tables via HTTP REST API.
8
- *
9
- * Architecture:
10
- * - Local changes are tracked in a sync_queue table
11
- * - Sync runs periodically or on-demand
12
- * - Conflicts are resolved using last-write-wins with _lastModified timestamps
13
- *
14
- * Security:
15
- * - Table/column names are validated against allowed patterns
16
- * - All user data uses parameterized queries
17
- */
18
- import { dash } from '../engine/sqlite.js';
19
- // Valid identifier pattern (prevents SQL injection)
20
- const VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
21
- /**
22
- * Validate and escape a SQL identifier (table or column name)
23
- * Throws if the identifier is invalid
24
- */
25
- function escapeIdentifier(name) {
26
- if (!VALID_IDENTIFIER.test(name)) {
27
- throw new Error(`Invalid SQL identifier: ${name}`);
28
- }
29
- // Double-quote the identifier for safety
30
- return `"${name}"`;
31
- }
32
- export class D1SyncProvider {
33
- config;
34
- syncTimer = null;
35
- isSyncing = false;
36
- lastSyncTime = 0;
37
- initialized = false;
38
- constructor(config) {
39
- this.config = {
40
- syncInterval: 30000,
41
- ...config,
42
- };
43
- }
44
- /**
45
- * Initialize the sync provider - must be called before syncing
46
- */
47
- async initialize() {
48
- if (this.initialized)
49
- return;
50
- await dash.ready();
51
- // Create sync metadata and queue tables
52
- dash.execute(`
53
- CREATE TABLE IF NOT EXISTS dash_sync_meta (
54
- key TEXT PRIMARY KEY,
55
- value TEXT
56
- )
57
- `);
58
- dash.execute(`
59
- CREATE TABLE IF NOT EXISTS dash_sync_queue (
60
- id INTEGER PRIMARY KEY AUTOINCREMENT,
61
- table_name TEXT NOT NULL,
62
- row_id TEXT NOT NULL,
63
- operation TEXT NOT NULL CHECK(operation IN ('create', 'update', 'delete')),
64
- data TEXT,
65
- created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000),
66
- synced INTEGER DEFAULT 0
67
- )
68
- `);
69
- // Create index for efficient querying
70
- dash.execute(`
71
- CREATE INDEX IF NOT EXISTS idx_sync_queue_synced
72
- ON dash_sync_queue(synced, created_at)
73
- `);
74
- // Load last sync time
75
- const meta = dash.execute("SELECT value FROM dash_sync_meta WHERE key = 'lastSyncTime'");
76
- if (meta.length > 0) {
77
- this.lastSyncTime = parseInt(meta[0].value, 10) || 0;
78
- }
79
- this.initialized = true;
80
- // Set up triggers for each table
81
- for (const table of this.config.tables) {
82
- this.setupTableTriggers(table);
83
- }
84
- // Start auto-sync if configured
85
- if (this.config.syncInterval && this.config.syncInterval > 0) {
86
- this.startAutoSync();
87
- }
88
- console.log('[D1SyncProvider] Initialized for tables:', this.config.tables);
89
- }
90
- /**
91
- * Set up SQLite triggers to track changes for a table
92
- * Uses validated identifiers to prevent SQL injection
93
- */
94
- setupTableTriggers(tableName) {
95
- // Validate table name to prevent SQL injection
96
- const safeTableName = escapeIdentifier(tableName);
97
- const triggerSuffix = tableName.replace(/[^a-zA-Z0-9_]/g, '_');
98
- // Clean up existing triggers first
99
- try {
100
- dash.execute(`DROP TRIGGER IF EXISTS dash_sync_${triggerSuffix}_insert`);
101
- dash.execute(`DROP TRIGGER IF EXISTS dash_sync_${triggerSuffix}_update`);
102
- dash.execute(`DROP TRIGGER IF EXISTS dash_sync_${triggerSuffix}_delete`);
103
- }
104
- catch {
105
- // Ignore errors from dropping non-existent triggers
106
- }
107
- // Create INSERT trigger with parameterized table name in data
108
- dash.execute(`
109
- CREATE TRIGGER IF NOT EXISTS dash_sync_${triggerSuffix}_insert
110
- AFTER INSERT ON ${safeTableName}
111
- BEGIN
112
- INSERT INTO dash_sync_queue (table_name, row_id, operation, data)
113
- VALUES (
114
- '${tableName.replace(/'/g, "''")}',
115
- NEW.id,
116
- 'create',
117
- json_object(
118
- 'id', NEW.id,
119
- '_lastModified', strftime('%s', 'now') * 1000,
120
- '_data', json(NEW)
121
- )
122
- );
123
- END
124
- `);
125
- // Create UPDATE trigger with timestamp for LWW
126
- dash.execute(`
127
- CREATE TRIGGER IF NOT EXISTS dash_sync_${triggerSuffix}_update
128
- AFTER UPDATE ON ${safeTableName}
129
- BEGIN
130
- INSERT INTO dash_sync_queue (table_name, row_id, operation, data)
131
- VALUES (
132
- '${tableName.replace(/'/g, "''")}',
133
- NEW.id,
134
- 'update',
135
- json_object(
136
- 'id', NEW.id,
137
- '_lastModified', strftime('%s', 'now') * 1000,
138
- '_data', json(NEW)
139
- )
140
- );
141
- END
142
- `);
143
- // Create DELETE trigger
144
- dash.execute(`
145
- CREATE TRIGGER IF NOT EXISTS dash_sync_${triggerSuffix}_delete
146
- AFTER DELETE ON ${safeTableName}
147
- BEGIN
148
- INSERT INTO dash_sync_queue (table_name, row_id, operation, data)
149
- VALUES (
150
- '${tableName.replace(/'/g, "''")}',
151
- OLD.id,
152
- 'delete',
153
- json_object('id', OLD.id, '_lastModified', strftime('%s', 'now') * 1000)
154
- );
155
- END
156
- `);
157
- }
158
- /**
159
- * Start automatic sync at the configured interval
160
- */
161
- startAutoSync() {
162
- if (this.syncTimer)
163
- return;
164
- this.syncTimer = setInterval(() => {
165
- this.sync().catch((err) => {
166
- console.error('[D1SyncProvider] Auto-sync failed:', err);
167
- this.config.onSyncError?.(err);
168
- });
169
- }, this.config.syncInterval);
170
- console.log('[D1SyncProvider] Auto-sync started, interval:', this.config.syncInterval, 'ms');
171
- }
172
- /**
173
- * Stop automatic sync
174
- */
175
- stopAutoSync() {
176
- if (this.syncTimer) {
177
- clearInterval(this.syncTimer);
178
- this.syncTimer = null;
179
- console.log('[D1SyncProvider] Auto-sync stopped');
180
- }
181
- }
182
- /**
183
- * Perform a sync operation
184
- */
185
- async sync() {
186
- if (!this.initialized) {
187
- await this.initialize();
188
- }
189
- if (this.isSyncing) {
190
- console.log('[D1SyncProvider] Sync already in progress, skipping');
191
- return { pushed: 0, pulled: 0, errors: ['Sync already in progress'], timestamp: Date.now() };
192
- }
193
- this.isSyncing = true;
194
- const result = { pushed: 0, pulled: 0, errors: [], timestamp: Date.now() };
195
- try {
196
- const token = await this.config.getAuthToken();
197
- if (!token) {
198
- throw new Error('No auth token available');
199
- }
200
- // Get pending changes from queue
201
- const pending = dash.execute(`
202
- SELECT * FROM dash_sync_queue
203
- WHERE synced = 0
204
- ORDER BY created_at ASC
205
- LIMIT 100
206
- `);
207
- // Group changes by table
208
- const changes = {};
209
- for (const entry of pending) {
210
- if (!changes[entry.table_name]) {
211
- changes[entry.table_name] = { creates: [], updates: [], deletes: [] };
212
- }
213
- const tableChanges = changes[entry.table_name];
214
- const data = entry.data ? JSON.parse(entry.data) : null;
215
- switch (entry.operation) {
216
- case 'create':
217
- if (data)
218
- tableChanges.creates.push(data);
219
- break;
220
- case 'update':
221
- if (data)
222
- tableChanges.updates.push({ id: entry.row_id, data });
223
- break;
224
- case 'delete':
225
- tableChanges.deletes.push(entry.row_id);
226
- break;
227
- }
228
- }
229
- // Sync each table
230
- for (const tableName of this.config.tables) {
231
- const tableChanges = changes[tableName] || { creates: [], updates: [], deletes: [] };
232
- try {
233
- const response = await fetch(`${this.config.baseUrl}/sync`, {
234
- method: 'POST',
235
- headers: {
236
- 'Content-Type': 'application/json',
237
- 'Authorization': `Bearer ${token}`,
238
- },
239
- body: JSON.stringify({
240
- table: tableName,
241
- creates: tableChanges.creates,
242
- updates: tableChanges.updates,
243
- deletes: tableChanges.deletes,
244
- lastSyncTime: this.lastSyncTime,
245
- }),
246
- });
247
- if (!response.ok) {
248
- const errorText = await response.text();
249
- result.errors.push(`${tableName}: ${response.status} ${errorText}`);
250
- continue;
251
- }
252
- const syncResponse = await response.json();
253
- result.pushed += tableChanges.creates.length + tableChanges.updates.length + tableChanges.deletes.length;
254
- result.pulled += syncResponse.serverChanges?.length || 0;
255
- // Apply server changes locally
256
- if (syncResponse.serverChanges && syncResponse.serverChanges.length > 0) {
257
- await this.applyServerChanges(tableName, syncResponse.serverChanges);
258
- }
259
- // Update sync time
260
- if (syncResponse.syncTime > this.lastSyncTime) {
261
- this.lastSyncTime = syncResponse.syncTime;
262
- dash.execute("INSERT OR REPLACE INTO dash_sync_meta (key, value) VALUES ('lastSyncTime', ?)", [String(this.lastSyncTime)]);
263
- }
264
- }
265
- catch (err) {
266
- result.errors.push(`${tableName}: ${err instanceof Error ? err.message : String(err)}`);
267
- }
268
- }
269
- // Mark synced entries
270
- if (pending.length > 0) {
271
- const ids = pending.map(e => e.id).join(',');
272
- dash.execute(`UPDATE dash_sync_queue SET synced = 1 WHERE id IN (${ids})`);
273
- }
274
- // Clean up old synced entries (keep last 1000)
275
- dash.execute(`
276
- DELETE FROM dash_sync_queue
277
- WHERE synced = 1
278
- AND id NOT IN (
279
- SELECT id FROM dash_sync_queue
280
- WHERE synced = 1
281
- ORDER BY id DESC
282
- LIMIT 1000
283
- )
284
- `);
285
- console.log('[D1SyncProvider] Sync complete:', result);
286
- this.config.onSyncComplete?.(result);
287
- }
288
- catch (err) {
289
- const error = err instanceof Error ? err : new Error(String(err));
290
- result.errors.push(error.message);
291
- console.error('[D1SyncProvider] Sync failed:', error);
292
- this.config.onSyncError?.(error);
293
- }
294
- finally {
295
- this.isSyncing = false;
296
- }
297
- return result;
298
- }
299
- /**
300
- * Apply changes received from the server with Last-Write-Wins (LWW) conflict resolution
301
- * Server changes only win if their _lastModified is newer than local
302
- */
303
- async applyServerChanges(tableName, changes) {
304
- const safeTableName = escapeIdentifier(tableName);
305
- for (const change of changes) {
306
- try {
307
- const serverTimestamp = change._lastModified || 0;
308
- const rowId = change.id;
309
- // Check if this is a delete (marked by a flag or null data)
310
- if (change.deleted || change._deleted) {
311
- // For deletes, check if local row exists and is older
312
- const local = dash.execute(`SELECT _lastModified FROM ${safeTableName} WHERE id = ?`, [rowId]);
313
- if (local.length === 0 || (local[0]._lastModified || 0) < serverTimestamp) {
314
- dash.execute(`DELETE FROM ${safeTableName} WHERE id = ?`, [rowId]);
315
- console.log(`[D1SyncProvider] LWW: Applied server delete for ${tableName}:${rowId}`);
316
- }
317
- else {
318
- console.log(`[D1SyncProvider] LWW: Skipped server delete (local is newer) for ${tableName}:${rowId}`);
319
- }
320
- continue;
321
- }
322
- // Check local record timestamp for LWW
323
- const local = dash.execute(`SELECT _lastModified FROM ${safeTableName} WHERE id = ?`, [rowId]);
324
- const localTimestamp = local.length > 0 ? (local[0]._lastModified || 0) : 0;
325
- // Only apply if server is newer (or record doesn't exist locally)
326
- if (localTimestamp >= serverTimestamp && local.length > 0) {
327
- console.log(`[D1SyncProvider] LWW: Skipped server change (local is newer) for ${tableName}:${rowId}`);
328
- continue;
329
- }
330
- // Get column names from the change object, excluding internal fields
331
- const data = change._data || change;
332
- const columns = Object.keys(data).filter(k => !k.startsWith('_'));
333
- // Add _lastModified column for tracking
334
- if (!columns.includes('_lastModified')) {
335
- columns.push('_lastModified');
336
- }
337
- const escapedColumns = columns.map(c => escapeIdentifier(c));
338
- const placeholders = columns.map(() => '?').join(', ');
339
- const values = columns.map(k => {
340
- if (k === '_lastModified')
341
- return serverTimestamp;
342
- const v = data[k];
343
- // Serialize objects/arrays to JSON
344
- if (v !== null && typeof v === 'object') {
345
- return JSON.stringify(v);
346
- }
347
- return v;
348
- });
349
- // Use INSERT OR REPLACE to handle both new and updated records
350
- const sql = `INSERT OR REPLACE INTO ${safeTableName} (${escapedColumns.join(', ')}) VALUES (${placeholders})`;
351
- dash.execute(sql, values);
352
- console.log(`[D1SyncProvider] LWW: Applied server change for ${tableName}:${rowId}`);
353
- }
354
- catch (err) {
355
- console.error(`[D1SyncProvider] Failed to apply change to ${tableName}:`, err, change);
356
- }
357
- }
358
- }
359
- /**
360
- * Queue a change manually (for cases where triggers can't be used)
361
- */
362
- queueChange(tableName, rowId, operation, data) {
363
- if (!this.initialized) {
364
- console.warn('[D1SyncProvider] Not initialized, change will not be queued');
365
- return;
366
- }
367
- dash.execute(`INSERT INTO dash_sync_queue (table_name, row_id, operation, data) VALUES (?, ?, ?, ?)`, [tableName, rowId, operation, data ? JSON.stringify(data) : null]);
368
- }
369
- /**
370
- * Get the current sync status
371
- */
372
- getStatus() {
373
- let pendingChanges = 0;
374
- if (this.initialized) {
375
- const result = dash.execute('SELECT COUNT(*) as count FROM dash_sync_queue WHERE synced = 0');
376
- pendingChanges = result[0]?.count || 0;
377
- }
378
- return {
379
- initialized: this.initialized,
380
- syncing: this.isSyncing,
381
- lastSyncTime: this.lastSyncTime,
382
- pendingChanges,
383
- };
384
- }
385
- /**
386
- * Force a full sync by resetting last sync time
387
- */
388
- async forceFullSync() {
389
- this.lastSyncTime = 0;
390
- dash.execute("INSERT OR REPLACE INTO dash_sync_meta (key, value) VALUES ('lastSyncTime', '0')");
391
- return this.sync();
392
- }
393
- /**
394
- * Destroy the provider and clean up
395
- */
396
- destroy() {
397
- this.stopAutoSync();
398
- this.initialized = false;
399
- console.log('[D1SyncProvider] Destroyed');
400
- }
401
- }
402
- // Export singleton factory for convenience
403
- let defaultProvider = null;
404
- export function getD1SyncProvider(config) {
405
- if (!defaultProvider && config) {
406
- defaultProvider = new D1SyncProvider(config);
407
- }
408
- if (!defaultProvider) {
409
- throw new Error('D1SyncProvider not configured. Call getD1SyncProvider(config) first.');
410
- }
411
- return defaultProvider;
412
- }
413
- export function resetD1SyncProvider() {
414
- if (defaultProvider) {
415
- defaultProvider.destroy();
416
- defaultProvider = null;
417
- }
418
- }