@pol-studios/powersync 1.0.7 → 1.0.11
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 +933 -0
- package/dist/CacheSettingsManager-uz-kbnRH.d.ts +461 -0
- package/dist/attachments/index.d.ts +709 -6
- package/dist/attachments/index.js +133 -5
- package/dist/chunk-24RDMMCL.js +44 -0
- package/dist/chunk-24RDMMCL.js.map +1 -0
- package/dist/chunk-4TXTAEF2.js +2060 -0
- package/dist/chunk-4TXTAEF2.js.map +1 -0
- package/dist/chunk-63PXSPIN.js +358 -0
- package/dist/chunk-63PXSPIN.js.map +1 -0
- package/dist/chunk-654ERHA7.js +1 -0
- package/dist/{chunk-BREGB4WL.js → chunk-BRXQNASY.js} +287 -335
- package/dist/chunk-BRXQNASY.js.map +1 -0
- package/dist/{chunk-DHYUBVP7.js → chunk-CAB26E6F.js} +20 -9
- package/dist/chunk-CAB26E6F.js.map +1 -0
- package/dist/{chunk-H772V6XQ.js → chunk-CUCAYK7Z.js} +7 -43
- package/dist/chunk-CUCAYK7Z.js.map +1 -0
- package/dist/{chunk-4C3RY5SU.js → chunk-HWSNV45P.js} +76 -1
- package/dist/chunk-HWSNV45P.js.map +1 -0
- package/dist/{chunk-HFOFLW5F.js → chunk-KN2IZERF.js} +139 -6
- package/dist/chunk-KN2IZERF.js.map +1 -0
- package/dist/{chunk-UEYRTLKE.js → chunk-P4HZA6ZT.js} +20 -9
- package/dist/chunk-P4HZA6ZT.js.map +1 -0
- package/dist/chunk-T4AO7JIG.js +1 -0
- package/dist/{chunk-XQAJM2MW.js → chunk-VACPAAQZ.js} +33 -2
- package/dist/{chunk-XQAJM2MW.js.map → chunk-VACPAAQZ.js.map} +1 -1
- package/dist/{chunk-53WH2JJV.js → chunk-WN5ZJ3E2.js} +5 -8
- package/dist/chunk-WN5ZJ3E2.js.map +1 -0
- package/dist/chunk-XAEII4ZX.js +456 -0
- package/dist/chunk-XAEII4ZX.js.map +1 -0
- package/dist/chunk-XOY2CJ67.js +289 -0
- package/dist/chunk-XOY2CJ67.js.map +1 -0
- package/dist/chunk-YHTZ7VMV.js +1 -0
- package/dist/{chunk-MKD2VCX3.js → chunk-Z6VOBGTU.js} +8 -8
- package/dist/chunk-Z6VOBGTU.js.map +1 -0
- package/dist/chunk-ZM4ENYMF.js +230 -0
- package/dist/chunk-ZM4ENYMF.js.map +1 -0
- package/dist/connector/index.d.ts +56 -3
- package/dist/connector/index.js +8 -5
- package/dist/core/index.d.ts +12 -1
- package/dist/core/index.js +3 -2
- package/dist/error/index.js +0 -1
- package/dist/generator/cli.js +527 -0
- package/dist/generator/index.d.ts +168 -0
- package/dist/generator/index.js +370 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.js +191 -29
- package/dist/index.native.d.ts +11 -9
- package/dist/index.native.js +191 -29
- package/dist/index.web.d.ts +11 -9
- package/dist/index.web.js +191 -29
- package/dist/maintenance/index.js +0 -1
- package/dist/platform/index.js +0 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/index.native.js +1 -2
- package/dist/platform/index.web.js +0 -1
- package/dist/pol-attachment-queue-BVAIueoP.d.ts +817 -0
- package/dist/provider/index.d.ts +38 -34
- package/dist/provider/index.js +11 -12
- package/dist/react/index.d.ts +372 -0
- package/dist/react/index.js +25 -0
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +22 -8
- package/dist/storage/index.native.d.ts +3 -3
- package/dist/storage/index.native.js +21 -7
- package/dist/storage/index.web.d.ts +3 -3
- package/dist/storage/index.web.js +21 -7
- package/dist/storage/upload/index.d.ts +7 -8
- package/dist/storage/upload/index.js +3 -3
- package/dist/storage/upload/index.native.d.ts +7 -8
- package/dist/storage/upload/index.native.js +4 -3
- package/dist/storage/upload/index.web.d.ts +1 -4
- package/dist/storage/upload/index.web.js +3 -3
- package/dist/supabase-connector-T9vHq_3i.d.ts +202 -0
- package/dist/sync/index.js +3 -3
- package/dist/{supabase-connector-qLm-WHkM.d.ts → types-B212hgfA.d.ts} +48 -170
- package/dist/{types-BVacP54t.d.ts → types-CyvBaAl8.d.ts} +12 -4
- package/dist/types-D0WcHrq6.d.ts +234 -0
- package/package.json +28 -4
- package/dist/CacheSettingsManager-1exbOC6S.d.ts +0 -261
- package/dist/chunk-4C3RY5SU.js.map +0 -1
- package/dist/chunk-53WH2JJV.js.map +0 -1
- package/dist/chunk-BREGB4WL.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DHYUBVP7.js.map +0 -1
- package/dist/chunk-GKF7TOMT.js +0 -1
- package/dist/chunk-H772V6XQ.js.map +0 -1
- package/dist/chunk-HFOFLW5F.js.map +0 -1
- package/dist/chunk-KGSFAE5B.js +0 -1
- package/dist/chunk-LNL64IJZ.js +0 -1
- package/dist/chunk-MKD2VCX3.js.map +0 -1
- package/dist/chunk-UEYRTLKE.js.map +0 -1
- package/dist/chunk-WQ5MPAVC.js +0 -449
- package/dist/chunk-WQ5MPAVC.js.map +0 -1
- package/dist/chunk-ZEOKPWUC.js +0 -1165
- package/dist/chunk-ZEOKPWUC.js.map +0 -1
- package/dist/pol-attachment-queue-C7YNXXhK.d.ts +0 -676
- package/dist/types-Bgvx7-E8.d.ts +0 -187
- /package/dist/{chunk-DGUM43GV.js.map → chunk-654ERHA7.js.map} +0 -0
- /package/dist/{chunk-GKF7TOMT.js.map → chunk-T4AO7JIG.js.map} +0 -0
- /package/dist/{chunk-KGSFAE5B.js.map → chunk-YHTZ7VMV.js.map} +0 -0
- /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
|