@pol-studios/powersync 1.0.7 → 1.0.10

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 (99) hide show
  1. package/README.md +933 -0
  2. package/dist/CacheSettingsManager-uz-kbnRH.d.ts +461 -0
  3. package/dist/attachments/index.d.ts +709 -6
  4. package/dist/attachments/index.js +133 -5
  5. package/dist/chunk-24RDMMCL.js +44 -0
  6. package/dist/chunk-24RDMMCL.js.map +1 -0
  7. package/dist/chunk-4TXTAEF2.js +2060 -0
  8. package/dist/chunk-4TXTAEF2.js.map +1 -0
  9. package/dist/chunk-63PXSPIN.js +358 -0
  10. package/dist/chunk-63PXSPIN.js.map +1 -0
  11. package/dist/chunk-654ERHA7.js +1 -0
  12. package/dist/{chunk-BREGB4WL.js → chunk-BRXQNASY.js} +287 -335
  13. package/dist/chunk-BRXQNASY.js.map +1 -0
  14. package/dist/{chunk-DHYUBVP7.js → chunk-CAB26E6F.js} +20 -9
  15. package/dist/chunk-CAB26E6F.js.map +1 -0
  16. package/dist/{chunk-H772V6XQ.js → chunk-CUCAYK7Z.js} +7 -43
  17. package/dist/chunk-CUCAYK7Z.js.map +1 -0
  18. package/dist/{chunk-4C3RY5SU.js → chunk-HWSNV45P.js} +76 -1
  19. package/dist/chunk-HWSNV45P.js.map +1 -0
  20. package/dist/{chunk-HFOFLW5F.js → chunk-KN2IZERF.js} +139 -6
  21. package/dist/chunk-KN2IZERF.js.map +1 -0
  22. package/dist/{chunk-UEYRTLKE.js → chunk-P4HZA6ZT.js} +20 -9
  23. package/dist/chunk-P4HZA6ZT.js.map +1 -0
  24. package/dist/chunk-T4AO7JIG.js +1 -0
  25. package/dist/{chunk-XQAJM2MW.js → chunk-VACPAAQZ.js} +33 -2
  26. package/dist/{chunk-XQAJM2MW.js.map → chunk-VACPAAQZ.js.map} +1 -1
  27. package/dist/{chunk-53WH2JJV.js → chunk-WN5ZJ3E2.js} +5 -8
  28. package/dist/chunk-WN5ZJ3E2.js.map +1 -0
  29. package/dist/chunk-XAEII4ZX.js +456 -0
  30. package/dist/chunk-XAEII4ZX.js.map +1 -0
  31. package/dist/chunk-XOY2CJ67.js +289 -0
  32. package/dist/chunk-XOY2CJ67.js.map +1 -0
  33. package/dist/chunk-YHTZ7VMV.js +1 -0
  34. package/dist/{chunk-MKD2VCX3.js → chunk-Z6VOBGTU.js} +8 -8
  35. package/dist/chunk-Z6VOBGTU.js.map +1 -0
  36. package/dist/chunk-ZM4ENYMF.js +230 -0
  37. package/dist/chunk-ZM4ENYMF.js.map +1 -0
  38. package/dist/connector/index.d.ts +56 -3
  39. package/dist/connector/index.js +8 -5
  40. package/dist/core/index.d.ts +12 -1
  41. package/dist/core/index.js +3 -2
  42. package/dist/error/index.js +0 -1
  43. package/dist/index.d.ts +12 -10
  44. package/dist/index.js +191 -29
  45. package/dist/index.native.d.ts +11 -9
  46. package/dist/index.native.js +191 -29
  47. package/dist/index.web.d.ts +11 -9
  48. package/dist/index.web.js +191 -29
  49. package/dist/maintenance/index.js +0 -1
  50. package/dist/platform/index.js +0 -2
  51. package/dist/platform/index.js.map +1 -1
  52. package/dist/platform/index.native.js +1 -2
  53. package/dist/platform/index.web.js +0 -1
  54. package/dist/pol-attachment-queue-BVAIueoP.d.ts +817 -0
  55. package/dist/provider/index.d.ts +38 -34
  56. package/dist/provider/index.js +11 -12
  57. package/dist/react/index.d.ts +372 -0
  58. package/dist/react/index.js +25 -0
  59. package/dist/storage/index.d.ts +3 -3
  60. package/dist/storage/index.js +22 -8
  61. package/dist/storage/index.native.d.ts +3 -3
  62. package/dist/storage/index.native.js +21 -7
  63. package/dist/storage/index.web.d.ts +3 -3
  64. package/dist/storage/index.web.js +21 -7
  65. package/dist/storage/upload/index.d.ts +7 -8
  66. package/dist/storage/upload/index.js +3 -3
  67. package/dist/storage/upload/index.native.d.ts +7 -8
  68. package/dist/storage/upload/index.native.js +4 -3
  69. package/dist/storage/upload/index.web.d.ts +1 -4
  70. package/dist/storage/upload/index.web.js +3 -3
  71. package/dist/supabase-connector-T9vHq_3i.d.ts +202 -0
  72. package/dist/sync/index.js +3 -3
  73. package/dist/{supabase-connector-qLm-WHkM.d.ts → types-B212hgfA.d.ts} +48 -170
  74. package/dist/{types-BVacP54t.d.ts → types-CyvBaAl8.d.ts} +12 -4
  75. package/dist/types-D0WcHrq6.d.ts +234 -0
  76. package/package.json +18 -4
  77. package/dist/CacheSettingsManager-1exbOC6S.d.ts +0 -261
  78. package/dist/chunk-4C3RY5SU.js.map +0 -1
  79. package/dist/chunk-53WH2JJV.js.map +0 -1
  80. package/dist/chunk-BREGB4WL.js.map +0 -1
  81. package/dist/chunk-DGUM43GV.js +0 -11
  82. package/dist/chunk-DHYUBVP7.js.map +0 -1
  83. package/dist/chunk-GKF7TOMT.js +0 -1
  84. package/dist/chunk-H772V6XQ.js.map +0 -1
  85. package/dist/chunk-HFOFLW5F.js.map +0 -1
  86. package/dist/chunk-KGSFAE5B.js +0 -1
  87. package/dist/chunk-LNL64IJZ.js +0 -1
  88. package/dist/chunk-MKD2VCX3.js.map +0 -1
  89. package/dist/chunk-UEYRTLKE.js.map +0 -1
  90. package/dist/chunk-WQ5MPAVC.js +0 -449
  91. package/dist/chunk-WQ5MPAVC.js.map +0 -1
  92. package/dist/chunk-ZEOKPWUC.js +0 -1165
  93. package/dist/chunk-ZEOKPWUC.js.map +0 -1
  94. package/dist/pol-attachment-queue-C7YNXXhK.d.ts +0 -676
  95. package/dist/types-Bgvx7-E8.d.ts +0 -187
  96. /package/dist/{chunk-DGUM43GV.js.map → chunk-654ERHA7.js.map} +0 -0
  97. /package/dist/{chunk-GKF7TOMT.js.map → chunk-T4AO7JIG.js.map} +0 -0
  98. /package/dist/{chunk-KGSFAE5B.js.map → chunk-YHTZ7VMV.js.map} +0 -0
  99. /package/dist/{chunk-LNL64IJZ.js.map → react/index.js.map} +0 -0
package/README.md ADDED
@@ -0,0 +1,933 @@
1
+ # @pol-studios/powersync
2
+
3
+ A comprehensive offline-first data synchronization library for React Native and Web apps, built on top of PowerSync with Supabase integration.
4
+
5
+ ## Features
6
+
7
+ - **Offline-First Database** - Full local SQLite database with automatic sync
8
+ - **Reactive Queries** - Live-updating queries that respond to local and remote changes
9
+ - **Durable Uploads** - CRUD operations queued and retried automatically
10
+ - **Attachment Management** - File uploads/downloads with caching and compression
11
+ - **Conflict Detection** - Field-level conflict detection with resolution options
12
+ - **Background Sync** - iOS/Android background sync support
13
+ - **Health Monitoring** - Connection health, metrics, and diagnostics
14
+ - **Platform Agnostic** - Works on React Native and Web with same API
15
+
16
+ ## Table of Contents
17
+
18
+ - [Installation](#installation)
19
+ - [Quick Start](#quick-start)
20
+ - [Provider Configuration](#provider-configuration)
21
+ - [React Hooks](#react-hooks)
22
+ - [Database Operations](#database-operations)
23
+ - [Sync Control](#sync-control)
24
+ - [Connection & Status](#connection--status)
25
+ - [Pending Mutations](#pending-mutations)
26
+ - [Failed Transactions](#failed-transactions)
27
+ - [Attachments](#attachments)
28
+ - [Conflict Handling](#conflict-handling)
29
+ - [Background Sync](#background-sync)
30
+ - [Cache Management](#cache-management)
31
+ - [Error Handling](#error-handling)
32
+ - [Platform Adapters](#platform-adapters)
33
+ - [API Reference](#api-reference)
34
+
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ # Core package
41
+ pnpm add @pol-studios/powersync
42
+
43
+ # Required peer dependencies
44
+ pnpm add @powersync/attachments @supabase/supabase-js @tanstack/react-query
45
+
46
+ # React Native specific
47
+ pnpm add @powersync/react-native @powersync/op-sqlite expo-file-system
48
+ pnpm add @react-native-community/netinfo react-native-background-upload
49
+ pnpm add @react-native-async-storage/async-storage
50
+
51
+ # Web specific
52
+ pnpm add @powersync/web
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Create Your Schema
60
+
61
+ ```typescript
62
+ // powersync-schema.ts
63
+ import { Schema, Table, column } from '@powersync/react-native';
64
+
65
+ export const AppSchema = new Schema([
66
+ new Table({
67
+ name: 'todos',
68
+ columns: [
69
+ column.text('title'),
70
+ column.text('description'),
71
+ column.integer('completed'),
72
+ column.text('created_at'),
73
+ ],
74
+ }),
75
+ ]);
76
+ ```
77
+
78
+ ### 2. Configure the Provider
79
+
80
+ ```tsx
81
+ import { OfflineDataProvider } from '@pol-studios/powersync';
82
+ import { createClient } from '@supabase/supabase-js';
83
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
84
+
85
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
86
+ const queryClient = new QueryClient();
87
+
88
+ function App() {
89
+ return (
90
+ <OfflineDataProvider
91
+ config={{
92
+ supabaseClient: supabase,
93
+ powerSyncUrl: 'https://your-instance.powersync.com',
94
+ schema: AppSchema,
95
+ queryClient, // Pass queryClient to enable cache invalidation on sync
96
+ dbFilename: 'myapp.db',
97
+ }}
98
+ >
99
+ <QueryClientProvider client={queryClient}>
100
+ <YourApp />
101
+ </QueryClientProvider>
102
+ </OfflineDataProvider>
103
+ );
104
+ }
105
+ ```
106
+
107
+ > **Note:** The same `queryClient` instance is passed to both `OfflineDataProvider` config and `QueryClientProvider`. This allows the sync system to invalidate React Query caches when data changes. The `QueryClientProvider` must be nested inside `OfflineDataProvider` so that child components can use both PowerSync hooks and React Query hooks.
108
+
109
+ ### 3. Use the Database
110
+
111
+ ```tsx
112
+ import { usePowerSync, useDatabase } from '@pol-studios/powersync';
113
+
114
+ function TodoList() {
115
+ const { db, isReady } = usePowerSync();
116
+ const [todos, setTodos] = useState([]);
117
+
118
+ useEffect(() => {
119
+ if (!db || !isReady) return;
120
+
121
+ // Reactive query - updates automatically
122
+ db.watch('SELECT * FROM todos ORDER BY created_at DESC', [], {
123
+ onResult: (result) => setTodos(result.rows._array),
124
+ });
125
+ }, [db, isReady]);
126
+
127
+ const addTodo = async (title: string) => {
128
+ await db.execute(
129
+ 'INSERT INTO todos (id, title, completed, created_at) VALUES (?, ?, 0, ?)',
130
+ [uuid(), title, new Date().toISOString()]
131
+ );
132
+ };
133
+
134
+ return (
135
+ <View>
136
+ {todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
137
+ </View>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Provider Configuration
145
+
146
+ ### OfflineDataProviderConfig
147
+
148
+ ```typescript
149
+ interface OfflineDataProviderConfig {
150
+ // Required
151
+ supabaseClient: SupabaseClient;
152
+ schema: Schema;
153
+ queryClient: QueryClient;
154
+
155
+ // Optional - PowerSync connection
156
+ powerSyncUrl?: string; // If omitted, runs in online-only mode
157
+ dbFilename?: string; // Default: 'powersync.db'
158
+
159
+ // Optional - Connector configuration
160
+ connector?: {
161
+ schemaRouter?: (table: string) => string; // Route tables to Supabase schemas
162
+ conflictDetection?: { enabled: boolean }; // Enable conflict detection
163
+ };
164
+
165
+ // Optional - Sync behavior
166
+ sync?: {
167
+ autoConnect?: boolean; // Default: true
168
+ enableHealthMonitoring?: boolean; // Default: true
169
+ enableMetrics?: boolean; // Default: true
170
+ };
171
+
172
+ // Optional - Attachments
173
+ attachments?: AttachmentConfig;
174
+
175
+ // Optional - Background sync (React Native only)
176
+ backgroundSync?: {
177
+ enabled: boolean;
178
+ minimumInterval?: number; // Minutes, default: 15
179
+ callbacks?: {
180
+ onSyncStart?: () => void;
181
+ onSyncComplete?: () => void;
182
+ onSyncError?: (error: Error) => void;
183
+ };
184
+ };
185
+
186
+ // Optional - Platform adapter (auto-detected if omitted)
187
+ platform?: PlatformAdapter;
188
+ }
189
+ ```
190
+
191
+ ### Provider Callbacks
192
+
193
+ ```tsx
194
+ <OfflineDataProvider
195
+ config={config}
196
+ onReady={() => console.log('Database ready')}
197
+ onError={(error) => console.error('Init failed:', error)}
198
+ onSyncStatusChange={(status) => console.log('Sync status:', status)}
199
+ onBackgroundSyncSystemReady={(system) => {
200
+ // Background sync system is ready
201
+ }}
202
+ >
203
+ {children}
204
+ </OfflineDataProvider>
205
+ ```
206
+
207
+ ---
208
+
209
+ ## React Hooks
210
+
211
+ ### Core Hooks
212
+
213
+ ```typescript
214
+ import {
215
+ usePowerSync,
216
+ useDatabase,
217
+ usePlatform,
218
+ useAttachmentQueue,
219
+ useAttachmentQueueReady,
220
+ } from '@pol-studios/powersync';
221
+
222
+ // Main context - database, connector, initialization state
223
+ const { db, connector, isReady, isInitializing, error } = usePowerSync();
224
+
225
+ // Get initialized database (throws if not ready)
226
+ const db = useDatabase();
227
+
228
+ // Access platform adapter
229
+ const platform = usePlatform();
230
+
231
+ // Attachment queue
232
+ const attachmentQueue = useAttachmentQueue();
233
+ const isQueueReady = useAttachmentQueueReady();
234
+ ```
235
+
236
+ ### Status Hooks (Recommended - Focused)
237
+
238
+ ```typescript
239
+ import {
240
+ useConnectionStatus,
241
+ useSyncActivityContext,
242
+ usePendingMutationsContext,
243
+ useFailedTransactionsContext,
244
+ useCompletedTransactionsContext,
245
+ useSyncModeContext,
246
+ useConnectionHealth,
247
+ useSyncMetrics,
248
+ } from '@pol-studios/powersync';
249
+
250
+ // Connection state only (minimal re-renders)
251
+ const { connected, connecting, hasSynced, lastSyncedAt } = useConnectionStatus();
252
+
253
+ // Upload/download activity
254
+ const { uploading, downloading, downloadProgress } = useSyncActivityContext();
255
+
256
+ // Pending mutations
257
+ const { pendingMutations, pendingCount, discardPendingMutation } = usePendingMutationsContext();
258
+
259
+ // Failed transactions with retry
260
+ const { failedTransactions, hasUploadErrors, retryFailure, clearFailure } = useFailedTransactionsContext();
261
+
262
+ // Completed transactions
263
+ const { completedTransactions, newCompletedTransactions, markNotificationsAsSeen } = useCompletedTransactionsContext();
264
+
265
+ // Sync mode control
266
+ const { syncMode, isPaused, setSyncMode, networkReachable } = useSyncModeContext();
267
+
268
+ // Health monitoring
269
+ const health = useConnectionHealth(); // Returns ConnectionHealth object directly
270
+
271
+ // Sync metrics
272
+ const { metrics } = useSyncMetrics(); // totalSyncs, successRate, avgDuration, etc.
273
+ ```
274
+
275
+ ### Convenience Hooks
276
+
277
+ ```typescript
278
+ import {
279
+ useOnlineStatus,
280
+ useSyncControl,
281
+ useSyncMode,
282
+ useIsSyncing,
283
+ useDownloadProgress,
284
+ useEntitySyncStatus,
285
+ useUploadStatus,
286
+ useSyncActivity,
287
+ } from '@pol-studios/powersync';
288
+
289
+ // Device online/offline
290
+ const { isOnline } = useOnlineStatus();
291
+
292
+ // Sync control actions
293
+ const { triggerSync, syncNow, pause, resume, disconnect, setSyncMode } = useSyncControl();
294
+
295
+ // Quick sync mode access
296
+ const { mode, setMode, canUpload, canDownload } = useSyncMode();
297
+
298
+ // Simple syncing check
299
+ const isSyncing = useIsSyncing();
300
+
301
+ // Download progress
302
+ const progress = useDownloadProgress(); // { current, target, percentage } | null
303
+
304
+ // Entity-specific sync state
305
+ const { state, error, pendingOperations, dismiss } = useEntitySyncStatus(entityId);
306
+ // state: 'idle' | 'saving' | 'syncing' | 'synced' | 'error'
307
+
308
+ // Overall upload status
309
+ const { pendingCount, failedCount, permanentFailureCount, retryAll } = useUploadStatus();
310
+
311
+ // Comprehensive activity tracking
312
+ const { pending, failed, completed, counts, hasActivity, retryAll, dismissFailure } = useSyncActivity();
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Database Operations
318
+
319
+ ### Reading Data
320
+
321
+ ```typescript
322
+ // One-time query
323
+ const todos = await db.getAll('SELECT * FROM todos WHERE completed = 0');
324
+
325
+ // Single row
326
+ const todo = await db.get('SELECT * FROM todos WHERE id = ?', [todoId]);
327
+
328
+ // Reactive watch (updates on changes)
329
+ db.watch('SELECT * FROM todos ORDER BY created_at DESC', [], {
330
+ onResult: (result) => {
331
+ const todos = result.rows._array;
332
+ setTodos(todos);
333
+ },
334
+ });
335
+
336
+ // Watch with abort signal for cleanup
337
+ const controller = new AbortController();
338
+ db.watch(query, params, { onResult }, { signal: controller.signal });
339
+ // Later: controller.abort();
340
+ ```
341
+
342
+ ### Writing Data
343
+
344
+ ```typescript
345
+ // Insert
346
+ await db.execute(
347
+ 'INSERT INTO todos (id, title, completed) VALUES (?, ?, ?)',
348
+ [uuid(), 'New todo', 0]
349
+ );
350
+
351
+ // Update
352
+ await db.execute(
353
+ 'UPDATE todos SET completed = 1 WHERE id = ?',
354
+ [todoId]
355
+ );
356
+
357
+ // Delete
358
+ await db.execute('DELETE FROM todos WHERE id = ?', [todoId]);
359
+
360
+ // Transaction
361
+ await db.writeTransaction(async (tx) => {
362
+ await tx.execute('INSERT INTO todos ...', [...]);
363
+ await tx.execute('INSERT INTO todo_items ...', [...]);
364
+ });
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Sync Control
370
+
371
+ ### Sync Modes
372
+
373
+ ```typescript
374
+ type SyncMode = 'push-pull' | 'pull-only' | 'offline';
375
+ ```
376
+
377
+ | Mode | Uploads | Downloads | Use Case |
378
+ |------|---------|-----------|----------|
379
+ | `push-pull` | Yes | Yes | Normal operation |
380
+ | `pull-only` | No | Yes | Read-only mode |
381
+ | `offline` | No | No | Fully offline |
382
+
383
+ ### Control Methods
384
+
385
+ ```typescript
386
+ const { triggerSync, syncNow, pause, resume, setSyncMode } = useSyncControl();
387
+
388
+ // Force fresh sync (disconnect + reconnect)
389
+ await triggerSync();
390
+
391
+ // Sync now regardless of mode (forces upload)
392
+ await syncNow();
393
+
394
+ // Pause syncing (sets offline mode)
395
+ await pause();
396
+
397
+ // Resume syncing (sets push-pull mode)
398
+ await resume();
399
+
400
+ // Set specific mode
401
+ await setSyncMode('pull-only');
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Connection & Status
407
+
408
+ ### Online/Offline Detection
409
+
410
+ ```tsx
411
+ import { useOnlineStatus, useConnectionStatus } from '@pol-studios/powersync';
412
+
413
+ function OfflineBanner() {
414
+ const { isOnline } = useOnlineStatus();
415
+ const { connected, connecting } = useConnectionStatus();
416
+
417
+ if (isOnline && connected) return null;
418
+
419
+ return (
420
+ <Banner>
421
+ {!isOnline ? 'You are offline' : connecting ? 'Reconnecting...' : 'Disconnected'}
422
+ </Banner>
423
+ );
424
+ }
425
+ ```
426
+
427
+ ### Connection Health
428
+
429
+ ```tsx
430
+ const health = useConnectionHealth();
431
+
432
+ // health.status: 'healthy' | 'degraded' | 'disconnected'
433
+ // health.latency: number | null
434
+ // health.lastHealthCheck: Date | null
435
+ // health.consecutiveFailures: number
436
+
437
+ const statusColor = {
438
+ healthy: 'green',
439
+ degraded: 'yellow',
440
+ disconnected: 'red',
441
+ }[health.status];
442
+ ```
443
+
444
+ ### Sync Metrics
445
+
446
+ ```tsx
447
+ const { metrics } = useSyncMetrics();
448
+
449
+ // metrics.totalSyncs: number
450
+ // metrics.successfulSyncs: number
451
+ // metrics.failedSyncs: number
452
+ // metrics.successRate: number (0-1)
453
+ // metrics.averageDurationMs: number
454
+ // metrics.totalDataDownloaded: number (bytes)
455
+ // metrics.totalDataUploaded: number (bytes)
456
+ // metrics.lastError: SyncError | null
457
+ ```
458
+
459
+ ---
460
+
461
+ ## Pending Mutations
462
+
463
+ Track local changes waiting to sync:
464
+
465
+ ```tsx
466
+ import { usePendingMutationsContext } from '@pol-studios/powersync';
467
+
468
+ function PendingChanges() {
469
+ const { pendingMutations, pendingCount, discardPendingMutation } = usePendingMutationsContext();
470
+
471
+ return (
472
+ <View>
473
+ <Text>{pendingCount} changes pending</Text>
474
+ {pendingMutations.map(mutation => (
475
+ <View key={mutation.id}>
476
+ <Text>{mutation.op} on {mutation.table}</Text>
477
+ <Button onPress={() => discardPendingMutation(mutation.id)}>
478
+ Discard
479
+ </Button>
480
+ </View>
481
+ ))}
482
+ </View>
483
+ );
484
+ }
485
+ ```
486
+
487
+ ### Mutation Types
488
+
489
+ ```typescript
490
+ interface CrudEntry {
491
+ id: string;
492
+ op: 'PUT' | 'PATCH' | 'DELETE';
493
+ table: string;
494
+ opData: Record<string, any>;
495
+ transactionId: number;
496
+ }
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Failed Transactions
502
+
503
+ Handle sync failures with retry capability:
504
+
505
+ ```tsx
506
+ import { useFailedTransactionsContext } from '@pol-studios/powersync';
507
+
508
+ function FailedUploads() {
509
+ const {
510
+ failedTransactions,
511
+ hasUploadErrors,
512
+ permanentErrorCount,
513
+ retryFailure,
514
+ clearFailure,
515
+ clearAllFailures,
516
+ } = useFailedTransactionsContext();
517
+
518
+ if (!hasUploadErrors) return null;
519
+
520
+ const retryAll = async () => {
521
+ for (const failure of failedTransactions) {
522
+ await retryFailure(failure.id);
523
+ }
524
+ };
525
+
526
+ return (
527
+ <View>
528
+ <Text>{failedTransactions.length} failed</Text>
529
+ <Button onPress={retryAll}>Retry All</Button>
530
+ {failedTransactions.map(failure => (
531
+ <View key={failure.id}>
532
+ <Text>{failure.error.userMessage}</Text>
533
+ <Text>Retried {failure.retryCount} times</Text>
534
+ <Button onPress={() => retryFailure(failure.id)}>Retry</Button>
535
+ <Button onPress={() => clearFailure(failure.id)}>Dismiss</Button>
536
+ </View>
537
+ ))}
538
+ </View>
539
+ );
540
+ }
541
+ ```
542
+
543
+ ### Failed Transaction Structure
544
+
545
+ ```typescript
546
+ interface FailedTransaction {
547
+ id: string;
548
+ affectedTables: string[];
549
+ affectedEntityIds: string[];
550
+ error: SyncError;
551
+ retryCount: number;
552
+ lastAttemptAt: Date;
553
+ createdAt: Date;
554
+ }
555
+
556
+ interface SyncError {
557
+ type: 'network' | 'auth' | 'server' | 'conflict' | 'validation' | 'quota' | 'unknown';
558
+ message: string;
559
+ userMessage: string;
560
+ isPermanent: boolean;
561
+ code?: string;
562
+ }
563
+ ```
564
+
565
+ ---
566
+
567
+ ## Attachments
568
+
569
+ For detailed attachment documentation, see [docs/ATTACHMENTS.md](./docs/ATTACHMENTS.md).
570
+
571
+ ### Quick Setup
572
+
573
+ ```tsx
574
+ <OfflineDataProvider
575
+ config={{
576
+ // ... other config
577
+ attachments: {
578
+ bucket: 'my-attachments',
579
+ watchIds: (db, onUpdate) => {
580
+ db.watch(
581
+ 'SELECT storage_path as id FROM photos WHERE storage_path IS NOT NULL',
582
+ [],
583
+ { onResult: (r) => onUpdate(r.rows._array.map(row => row.id)) }
584
+ );
585
+ },
586
+ skipDownload: async ({ ids }) => {
587
+ // Skip video files
588
+ return ids.filter(id => id.endsWith('.mp4'));
589
+ },
590
+ maxCacheBytes: CACHE_SIZE_PRESETS.GB_1,
591
+ },
592
+ }}
593
+ />
594
+ ```
595
+
596
+ ### Upload Files
597
+
598
+ ```tsx
599
+ const attachmentQueue = useAttachmentQueue();
600
+
601
+ await attachmentQueue.queueUpload({
602
+ storagePath: `photos/${uuid()}.jpg`,
603
+ sourceUri: localFileUri,
604
+ filename: 'photo.jpg',
605
+ mediaType: 'image/jpeg',
606
+ });
607
+ ```
608
+
609
+ ### Track Progress
610
+
611
+ ```tsx
612
+ const [stats, setStats] = useState(null);
613
+
614
+ useEffect(() => {
615
+ if (!attachmentQueue) return;
616
+ return attachmentQueue.onProgress(setStats);
617
+ }, [attachmentQueue]);
618
+
619
+ // stats.syncedCount - downloaded files
620
+ // stats.pendingCount - queued for download
621
+ // stats.pendingUploadCount - queued for upload
622
+ // stats.failedPermanentCount - failed uploads
623
+ ```
624
+
625
+ ---
626
+
627
+ ## Conflict Handling
628
+
629
+ Enable field-level conflict detection:
630
+
631
+ ```tsx
632
+ <OfflineDataProvider
633
+ config={{
634
+ connector: {
635
+ conflictDetection: { enabled: true },
636
+ },
637
+ }}
638
+ />
639
+ ```
640
+
641
+ ### Conflict Structure
642
+
643
+ ```typescript
644
+ interface ConflictCheckResult {
645
+ hasConflict: boolean;
646
+ conflicts: FieldConflict[];
647
+ nonConflictingChanges: string[];
648
+ table: string;
649
+ recordId: string;
650
+ }
651
+
652
+ interface FieldConflict {
653
+ field: string;
654
+ localValue: any;
655
+ serverValue: any;
656
+ changedBy: string;
657
+ changedAt: Date;
658
+ }
659
+ ```
660
+
661
+ ### Resolution Options
662
+
663
+ ```typescript
664
+ type ConflictResolution =
665
+ | { action: 'overwrite' } // Use local, overwrite server
666
+ | { action: 'keep-server' } // Discard local changes
667
+ | { action: 'partial'; fields: string[] }; // Sync specific fields only
668
+ ```
669
+
670
+ ---
671
+
672
+ ## Background Sync
673
+
674
+ Configure background sync for React Native:
675
+
676
+ ```tsx
677
+ <OfflineDataProvider
678
+ config={{
679
+ backgroundSync: {
680
+ enabled: true,
681
+ minimumInterval: 15, // minutes
682
+ callbacks: {
683
+ onSyncStart: () => console.log('Background sync starting'),
684
+ onSyncComplete: () => console.log('Background sync complete'),
685
+ onSyncError: (error) => console.error('Background sync failed:', error),
686
+ },
687
+ },
688
+ }}
689
+ onBackgroundSyncSystemReady={(system) => {
690
+ // Background sync system initialized
691
+ // Call system.signalReady() when app is ready
692
+ }}
693
+ />
694
+ ```
695
+
696
+ ---
697
+
698
+ ## Cache Management
699
+
700
+ ### Database Statistics
701
+
702
+ ```tsx
703
+ import { useDatabaseMaintenance } from '@pol-studios/powersync';
704
+
705
+ const { getCacheStats, compactDatabase, checkIntegrity, checkStorageQuota } = useDatabaseMaintenance();
706
+
707
+ // Get table statistics
708
+ const stats = await getCacheStats();
709
+ // stats.totalRows, stats.totalTables
710
+ // stats.tables: [{ name, rowCount, size, sizeFormatted }]
711
+ // stats.storage: { used, usedFormatted, reclaimable }
712
+
713
+ // Compact database (VACUUM)
714
+ const result = await compactDatabase();
715
+ // result.bytesReclaimed
716
+
717
+ // Check integrity
718
+ const integrity = await checkIntegrity();
719
+ // integrity.ok, integrity.issues
720
+
721
+ // Check device storage
722
+ const quota = await checkStorageQuota();
723
+ // quota.status: 'ok' | 'warning' | 'critical'
724
+ // quota.freeSpace, quota.threshold
725
+ ```
726
+
727
+ ### Reset Cache
728
+
729
+ ```tsx
730
+ import { useDatabase } from '@pol-studios/powersync';
731
+
732
+ const db = useDatabase();
733
+
734
+ // Clear synced data but keep local changes
735
+ await db.disconnectAndClear({ clearLocal: false });
736
+
737
+ // Then reconnect
738
+ await triggerSync();
739
+ ```
740
+
741
+ ---
742
+
743
+ ## Error Handling
744
+
745
+ ### Error Boundary
746
+
747
+ ```tsx
748
+ import { PowerSyncErrorBoundary } from '@pol-studios/powersync';
749
+
750
+ function Screen() {
751
+ return (
752
+ <PowerSyncErrorBoundary
753
+ fallback={(error, retry) => (
754
+ <View>
755
+ <Text>Something went wrong: {error.message}</Text>
756
+ <Button onPress={retry}>Retry</Button>
757
+ </View>
758
+ )}
759
+ >
760
+ <ScreenContent />
761
+ </PowerSyncErrorBoundary>
762
+ );
763
+ }
764
+ ```
765
+
766
+ ### Error Types
767
+
768
+ ```typescript
769
+ import {
770
+ PowerSyncError,
771
+ InitializationError,
772
+ SyncOperationError,
773
+ ConnectorError,
774
+ AttachmentError,
775
+ ConfigurationError,
776
+ } from '@pol-studios/powersync';
777
+
778
+ // Check error type
779
+ if (error instanceof SyncOperationError && error.retryable) {
780
+ // Can retry
781
+ }
782
+ ```
783
+
784
+ ### Error Classification
785
+
786
+ ```typescript
787
+ import { classifyError, classifySupabaseError } from '@pol-studios/powersync';
788
+
789
+ // Classify any error
790
+ const errorType = classifyError(error);
791
+ // 'network' | 'auth' | 'server' | 'conflict' | 'validation' | 'quota' | 'unknown'
792
+
793
+ // Classify Supabase/PostgreSQL error
794
+ const classified = classifySupabaseError(error);
795
+ // { type, isPermanent, userMessage }
796
+ ```
797
+
798
+ ---
799
+
800
+ ## Platform Adapters
801
+
802
+ ### Auto-Detection
803
+
804
+ The platform is auto-detected. Override if needed:
805
+
806
+ ```tsx
807
+ import { createNativePlatformAdapter } from '@pol-studios/powersync/platform';
808
+
809
+ const platform = createNativePlatformAdapter({
810
+ debug: (...args) => console.debug('[PowerSync]', ...args),
811
+ info: (...args) => console.info('[PowerSync]', ...args),
812
+ warn: (...args) => console.warn('[PowerSync]', ...args),
813
+ error: (...args) => console.error('[PowerSync]', ...args),
814
+ });
815
+
816
+ <OfflineDataProvider
817
+ config={{
818
+ platform,
819
+ // ... other config
820
+ }}
821
+ />
822
+ ```
823
+
824
+ ### Platform Features
825
+
826
+ ```typescript
827
+ interface PlatformAdapter {
828
+ createDatabase(options): PowerSyncDatabase;
829
+ fileSystem: FileSystemAdapter;
830
+ storage: AsyncStorageAdapter;
831
+ network: NetworkAdapter;
832
+ logger: LoggerAdapter;
833
+ imageProcessor?: ImageProcessorAdapter;
834
+ }
835
+ ```
836
+
837
+ ---
838
+
839
+ ## API Reference
840
+
841
+ ### Import Paths
842
+
843
+ ```typescript
844
+ // Main exports
845
+ import {
846
+ OfflineDataProvider,
847
+ usePowerSync,
848
+ useDatabase,
849
+ useConnectionStatus,
850
+ useSyncActivityContext,
851
+ usePendingMutationsContext,
852
+ useFailedTransactionsContext,
853
+ useSyncControl,
854
+ useAttachmentQueue,
855
+ CACHE_SIZE_PRESETS,
856
+ } from '@pol-studios/powersync';
857
+
858
+ // Attachment types
859
+ import type {
860
+ AttachmentConfig,
861
+ SkipDownloadContext,
862
+ PolAttachmentState,
863
+ AttachmentSyncStats,
864
+ } from '@pol-studios/powersync/attachments';
865
+
866
+ // Platform adapters
867
+ import {
868
+ createNativePlatformAdapter,
869
+ createWebPlatformAdapter,
870
+ } from '@pol-studios/powersync/platform';
871
+
872
+ // Storage utilities
873
+ import { createSupabaseStorage } from '@pol-studios/powersync/storage';
874
+
875
+ // Core types
876
+ import type {
877
+ SyncStatus,
878
+ SyncError,
879
+ CrudEntry,
880
+ FailedTransaction,
881
+ ConnectionHealth,
882
+ SyncMetrics,
883
+ } from '@pol-studios/powersync/core';
884
+ ```
885
+
886
+ ### Default Values
887
+
888
+ ```typescript
889
+ // Sync defaults
890
+ DEFAULT_SYNC_CONFIG = {
891
+ autoConnect: true,
892
+ syncInterval: 0,
893
+ enableHealthMonitoring: true,
894
+ enableMetrics: true,
895
+ };
896
+
897
+ // Cache presets
898
+ CACHE_SIZE_PRESETS = {
899
+ MB_250: 262144000,
900
+ MB_500: 524288000,
901
+ GB_1: 1073741824,
902
+ GB_2: 2147483648,
903
+ GB_5: 5368709120,
904
+ UNLIMITED: Number.MAX_SAFE_INTEGER,
905
+ };
906
+
907
+ // Upload retry defaults
908
+ DEFAULT_UPLOAD_CONFIG = {
909
+ concurrency: 5,
910
+ timeoutMs: 120000,
911
+ baseRetryDelayMs: 30000,
912
+ maxRetryDelayMs: 3600000,
913
+ maxRetryCount: 100,
914
+ };
915
+
916
+ // Download defaults
917
+ DEFAULT_DOWNLOAD_CONFIG = {
918
+ concurrency: 3,
919
+ timeoutMs: 120000,
920
+ };
921
+ ```
922
+
923
+ ---
924
+
925
+ ## Contributing
926
+
927
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
928
+
929
+ ## Related Documentation
930
+
931
+ - [Attachments Guide](./docs/ATTACHMENTS.md) - Detailed attachment system documentation
932
+ - [PowerSync Documentation](https://docs.powersync.com) - Official PowerSync docs
933
+ - [Supabase Documentation](https://supabase.com/docs) - Supabase integration