@pol-studios/powersync 1.0.6 → 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.
- package/README.md +933 -0
- package/dist/CacheSettingsManager-uz-kbnRH.d.ts +461 -0
- package/dist/attachments/index.d.ts +745 -332
- package/dist/attachments/index.js +152 -6
- package/dist/{types-Cd7RhNqf.d.ts → background-sync-ChCXW-EV.d.ts} +53 -2
- 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-A4IBBWGO.js +377 -0
- package/dist/chunk-A4IBBWGO.js.map +1 -0
- package/dist/chunk-BRXQNASY.js +1720 -0
- package/dist/chunk-BRXQNASY.js.map +1 -0
- package/dist/chunk-CAB26E6F.js +142 -0
- package/dist/chunk-CAB26E6F.js.map +1 -0
- package/dist/{chunk-EJ23MXPQ.js → chunk-CGL33PL4.js} +3 -1
- package/dist/chunk-CGL33PL4.js.map +1 -0
- package/dist/{chunk-R4YFWQ3Q.js → chunk-CUCAYK7Z.js} +309 -92
- package/dist/chunk-CUCAYK7Z.js.map +1 -0
- package/dist/chunk-FV2HXEIY.js +124 -0
- package/dist/chunk-FV2HXEIY.js.map +1 -0
- package/dist/chunk-HWSNV45P.js +279 -0
- package/dist/chunk-HWSNV45P.js.map +1 -0
- package/dist/{chunk-62J2DPKX.js → chunk-KN2IZERF.js} +530 -413
- package/dist/chunk-KN2IZERF.js.map +1 -0
- package/dist/{chunk-7EMDVIZX.js → chunk-N75DEF5J.js} +19 -1
- package/dist/chunk-N75DEF5J.js.map +1 -0
- package/dist/chunk-P4HZA6ZT.js +83 -0
- package/dist/chunk-P4HZA6ZT.js.map +1 -0
- package/dist/chunk-P6WOZO7H.js +49 -0
- package/dist/chunk-P6WOZO7H.js.map +1 -0
- package/dist/chunk-T4AO7JIG.js +1 -0
- package/dist/chunk-TGBT5XBE.js +1 -0
- package/dist/{chunk-FPTDATY5.js → chunk-VACPAAQZ.js} +54 -12
- package/dist/chunk-VACPAAQZ.js.map +1 -0
- package/dist/chunk-WGHNIAF7.js +329 -0
- package/dist/chunk-WGHNIAF7.js.map +1 -0
- package/dist/{chunk-3AYXHQ4W.js → chunk-WN5ZJ3E2.js} +108 -47
- 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-YSTEESEG.js +676 -0
- package/dist/chunk-YSTEESEG.js.map +1 -0
- package/dist/chunk-Z6VOBGTU.js +32 -0
- 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 +236 -4
- package/dist/connector/index.js +15 -4
- package/dist/core/index.d.ts +16 -3
- package/dist/core/index.js +6 -2
- package/dist/error/index.d.ts +54 -0
- package/dist/error/index.js +7 -0
- package/dist/error/index.js.map +1 -0
- package/dist/index.d.ts +102 -12
- package/dist/index.js +309 -37
- package/dist/index.native.d.ts +22 -10
- package/dist/index.native.js +309 -38
- package/dist/index.web.d.ts +22 -10
- package/dist/index.web.js +310 -38
- package/dist/maintenance/index.d.ts +118 -0
- package/dist/maintenance/index.js +16 -0
- package/dist/maintenance/index.js.map +1 -0
- package/dist/platform/index.d.ts +16 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/index.native.d.ts +2 -2
- package/dist/platform/index.native.js +1 -1
- package/dist/platform/index.web.d.ts +1 -1
- package/dist/platform/index.web.js +1 -1
- package/dist/pol-attachment-queue-BVAIueoP.d.ts +817 -0
- package/dist/provider/index.d.ts +451 -21
- package/dist/provider/index.js +32 -13
- package/dist/react/index.d.ts +372 -0
- package/dist/react/index.js +25 -0
- package/dist/react/index.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.js +42 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.native.d.ts +6 -0
- package/dist/storage/index.native.js +40 -0
- package/dist/storage/index.native.js.map +1 -0
- package/dist/storage/index.web.d.ts +6 -0
- package/dist/storage/index.web.js +40 -0
- package/dist/storage/index.web.js.map +1 -0
- package/dist/storage/upload/index.d.ts +54 -0
- package/dist/storage/upload/index.js +15 -0
- package/dist/storage/upload/index.js.map +1 -0
- package/dist/storage/upload/index.native.d.ts +56 -0
- package/dist/storage/upload/index.native.js +15 -0
- package/dist/storage/upload/index.native.js.map +1 -0
- package/dist/storage/upload/index.web.d.ts +2 -0
- package/dist/storage/upload/index.web.js +14 -0
- package/dist/storage/upload/index.web.js.map +1 -0
- package/dist/supabase-connector-T9vHq_3i.d.ts +202 -0
- package/dist/sync/index.d.ts +288 -23
- package/dist/sync/index.js +22 -10
- package/dist/{index-l3iL9Jte.d.ts → types-B212hgfA.d.ts} +101 -158
- package/dist/{types-afHtE1U_.d.ts → types-CDqWh56B.d.ts} +2 -0
- package/dist/types-CyvBaAl8.d.ts +60 -0
- package/dist/types-D0WcHrq6.d.ts +234 -0
- package/package.json +89 -5
- package/dist/chunk-32OLICZO.js +0 -1
- package/dist/chunk-3AYXHQ4W.js.map +0 -1
- package/dist/chunk-5FIMA26D.js +0 -1
- package/dist/chunk-62J2DPKX.js.map +0 -1
- package/dist/chunk-7EMDVIZX.js.map +0 -1
- package/dist/chunk-EJ23MXPQ.js.map +0 -1
- package/dist/chunk-FPTDATY5.js.map +0 -1
- package/dist/chunk-KCDG2MNP.js +0 -1431
- package/dist/chunk-KCDG2MNP.js.map +0 -1
- package/dist/chunk-OLHGI472.js +0 -1
- package/dist/chunk-PAFBKNL3.js +0 -99
- package/dist/chunk-PAFBKNL3.js.map +0 -1
- package/dist/chunk-R4YFWQ3Q.js.map +0 -1
- package/dist/chunk-V6LJ6MR2.js +0 -740
- package/dist/chunk-V6LJ6MR2.js.map +0 -1
- package/dist/chunk-VJCL2SWD.js +0 -1
- package/dist/failed-upload-store-C0cLxxPz.d.ts +0 -33
- /package/dist/{chunk-32OLICZO.js.map → chunk-654ERHA7.js.map} +0 -0
- /package/dist/{chunk-5FIMA26D.js.map → chunk-T4AO7JIG.js.map} +0 -0
- /package/dist/{chunk-OLHGI472.js.map → chunk-TGBT5XBE.js.map} +0 -0
- /package/dist/{chunk-VJCL2SWD.js.map → chunk-YHTZ7VMV.js.map} +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// src/sync/dead-letter-queue.ts
|
|
2
|
+
var DEFAULT_STORAGE_KEY = "@pol-powersync:dead_letter_queue";
|
|
3
|
+
var DEFAULT_MAX_ENTRIES = 100;
|
|
4
|
+
var DEFAULT_TTL_MS = 0;
|
|
5
|
+
var DeadLetterQueue = class {
|
|
6
|
+
storage;
|
|
7
|
+
logger;
|
|
8
|
+
maxEntries;
|
|
9
|
+
entryTTLMs;
|
|
10
|
+
storageKey;
|
|
11
|
+
entries = [];
|
|
12
|
+
listeners = /* @__PURE__ */ new Set();
|
|
13
|
+
initialized = false;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.storage = options.storage;
|
|
16
|
+
this.logger = options.logger;
|
|
17
|
+
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
18
|
+
this.entryTTLMs = options.entryTTLMs ?? DEFAULT_TTL_MS;
|
|
19
|
+
this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
20
|
+
}
|
|
21
|
+
// ─── Initialization ──────────────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the DLQ by loading persisted entries
|
|
24
|
+
*/
|
|
25
|
+
async init() {
|
|
26
|
+
if (this.initialized) return;
|
|
27
|
+
try {
|
|
28
|
+
const json = await this.storage.getItem(this.storageKey);
|
|
29
|
+
if (json) {
|
|
30
|
+
const parsed = JSON.parse(json);
|
|
31
|
+
this.entries = parsed.map((item) => ({
|
|
32
|
+
...item,
|
|
33
|
+
movedAt: new Date(item.movedAt),
|
|
34
|
+
error: {
|
|
35
|
+
...item.error,
|
|
36
|
+
timestamp: new Date(item.error.timestamp)
|
|
37
|
+
}
|
|
38
|
+
})).filter((entry) => !isNaN(entry.movedAt.getTime()));
|
|
39
|
+
this.logger.debug("[DLQ] Loaded", this.entries.length, "entries");
|
|
40
|
+
}
|
|
41
|
+
} catch (err) {
|
|
42
|
+
this.logger.warn("[DLQ] Failed to load entries:", err);
|
|
43
|
+
}
|
|
44
|
+
this.cleanupExpired();
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Dispose the DLQ (cleanup)
|
|
49
|
+
*/
|
|
50
|
+
dispose() {
|
|
51
|
+
this.listeners.clear();
|
|
52
|
+
}
|
|
53
|
+
// ─── Queue Operations ────────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Add an entry to the dead letter queue
|
|
56
|
+
*/
|
|
57
|
+
async add(entry) {
|
|
58
|
+
const existingIndex = this.entries.findIndex((e) => e.id === entry.id);
|
|
59
|
+
if (existingIndex !== -1) {
|
|
60
|
+
this.entries[existingIndex] = entry;
|
|
61
|
+
this.logger.debug("[DLQ] Updated existing entry:", entry.id);
|
|
62
|
+
} else {
|
|
63
|
+
this.entries.push(entry);
|
|
64
|
+
this.logger.info("[DLQ] Added entry:", entry.id, "reason:", entry.reason);
|
|
65
|
+
}
|
|
66
|
+
if (this.entries.length > this.maxEntries) {
|
|
67
|
+
const removed = this.entries.shift();
|
|
68
|
+
this.logger.debug("[DLQ] Removed oldest entry due to max limit:", removed?.id);
|
|
69
|
+
}
|
|
70
|
+
await this.persist();
|
|
71
|
+
this.notifyListeners();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all entries in the DLQ
|
|
75
|
+
*/
|
|
76
|
+
getAll() {
|
|
77
|
+
this.cleanupExpired();
|
|
78
|
+
return [...this.entries];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get an entry by ID
|
|
82
|
+
*/
|
|
83
|
+
get(id) {
|
|
84
|
+
return this.entries.find((e) => e.id === id) ?? null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get entries affecting a specific entity
|
|
88
|
+
*/
|
|
89
|
+
getByEntityId(entityId) {
|
|
90
|
+
return this.entries.filter((e) => e.affectedEntityIds.includes(entityId));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get entries for a specific table
|
|
94
|
+
*/
|
|
95
|
+
getByTable(tableName) {
|
|
96
|
+
return this.entries.filter((e) => e.affectedTables.includes(tableName));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get entry count
|
|
100
|
+
*/
|
|
101
|
+
get count() {
|
|
102
|
+
return this.entries.length;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if DLQ has any entries
|
|
106
|
+
*/
|
|
107
|
+
get hasEntries() {
|
|
108
|
+
return this.entries.length > 0;
|
|
109
|
+
}
|
|
110
|
+
// ─── Entry Management ────────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Remove an entry from the DLQ (e.g., user discards it)
|
|
113
|
+
*/
|
|
114
|
+
async remove(id) {
|
|
115
|
+
const index = this.entries.findIndex((e) => e.id === id);
|
|
116
|
+
if (index === -1) {
|
|
117
|
+
this.logger.warn("[DLQ] Entry not found for removal:", id);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const [removed] = this.entries.splice(index, 1);
|
|
121
|
+
this.logger.info("[DLQ] Removed entry:", id);
|
|
122
|
+
await this.persist();
|
|
123
|
+
this.notifyListeners();
|
|
124
|
+
return removed;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Retry an entry - removes it from DLQ and returns it for resubmission
|
|
128
|
+
*
|
|
129
|
+
* @returns The entry that was removed (caller should resubmit to PowerSync)
|
|
130
|
+
*/
|
|
131
|
+
async retry(id) {
|
|
132
|
+
const entry = await this.remove(id);
|
|
133
|
+
if (entry) {
|
|
134
|
+
this.logger.info("[DLQ] Entry marked for retry:", id);
|
|
135
|
+
}
|
|
136
|
+
return entry;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Clear all entries from the DLQ
|
|
140
|
+
*/
|
|
141
|
+
async clear() {
|
|
142
|
+
if (this.entries.length === 0) return;
|
|
143
|
+
const count = this.entries.length;
|
|
144
|
+
this.entries = [];
|
|
145
|
+
this.logger.info("[DLQ] Cleared all", count, "entries");
|
|
146
|
+
await this.persist();
|
|
147
|
+
this.notifyListeners();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Update user note on an entry
|
|
151
|
+
*/
|
|
152
|
+
async updateNote(id, note) {
|
|
153
|
+
const entry = this.entries.find((e) => e.id === id);
|
|
154
|
+
if (!entry) {
|
|
155
|
+
this.logger.warn("[DLQ] Entry not found for note update:", id);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
entry.userNote = note;
|
|
159
|
+
await this.persist();
|
|
160
|
+
this.notifyListeners();
|
|
161
|
+
}
|
|
162
|
+
// ─── Subscriptions ───────────────────────────────────────────────────────────
|
|
163
|
+
/**
|
|
164
|
+
* Subscribe to DLQ changes
|
|
165
|
+
* @returns Unsubscribe function
|
|
166
|
+
*/
|
|
167
|
+
onQueueChange(listener) {
|
|
168
|
+
this.listeners.add(listener);
|
|
169
|
+
listener(this.getAll());
|
|
170
|
+
return () => {
|
|
171
|
+
this.listeners.delete(listener);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// ─── Statistics ──────────────────────────────────────────────────────────────
|
|
175
|
+
/**
|
|
176
|
+
* Get statistics about the DLQ
|
|
177
|
+
*/
|
|
178
|
+
getStats() {
|
|
179
|
+
const byReason = {};
|
|
180
|
+
const byTable = {};
|
|
181
|
+
for (const entry of this.entries) {
|
|
182
|
+
byReason[entry.reason] = (byReason[entry.reason] ?? 0) + 1;
|
|
183
|
+
for (const table of entry.affectedTables) {
|
|
184
|
+
byTable[table] = (byTable[table] ?? 0) + 1;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const sortedByDate = [...this.entries].sort((a, b) => a.movedAt.getTime() - b.movedAt.getTime());
|
|
188
|
+
return {
|
|
189
|
+
totalEntries: this.entries.length,
|
|
190
|
+
byReason,
|
|
191
|
+
byTable,
|
|
192
|
+
oldestEntry: sortedByDate[0]?.movedAt ?? null,
|
|
193
|
+
newestEntry: sortedByDate[sortedByDate.length - 1]?.movedAt ?? null
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// ─── Private Methods ─────────────────────────────────────────────────────────
|
|
197
|
+
async persist() {
|
|
198
|
+
try {
|
|
199
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.entries));
|
|
200
|
+
} catch (err) {
|
|
201
|
+
this.logger.warn("[DLQ] Failed to persist entries:", err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
notifyListeners() {
|
|
205
|
+
const entries = this.getAll();
|
|
206
|
+
for (const listener of this.listeners) {
|
|
207
|
+
try {
|
|
208
|
+
listener(entries);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
this.logger.warn("[DLQ] Listener error:", err);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
cleanupExpired() {
|
|
215
|
+
if (this.entryTTLMs <= 0) return;
|
|
216
|
+
const cutoff = Date.now() - this.entryTTLMs;
|
|
217
|
+
const initialCount = this.entries.length;
|
|
218
|
+
this.entries = this.entries.filter((e) => e.movedAt.getTime() > cutoff);
|
|
219
|
+
const removed = initialCount - this.entries.length;
|
|
220
|
+
if (removed > 0) {
|
|
221
|
+
this.logger.debug("[DLQ] Cleaned up", removed, "expired entries");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
function generateDLQEntryId(entries) {
|
|
226
|
+
const entryIds = entries.map((e) => e.id).sort().join(",");
|
|
227
|
+
const timestamp = Date.now().toString(36);
|
|
228
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
229
|
+
return `dlq_${timestamp}_${random}_${entryIds.slice(0, 20)}`;
|
|
230
|
+
}
|
|
231
|
+
function createDeadLetterEntry(entries, error, reason, retryAttempts = 0) {
|
|
232
|
+
return {
|
|
233
|
+
id: generateDLQEntryId(entries),
|
|
234
|
+
entries,
|
|
235
|
+
error,
|
|
236
|
+
reason,
|
|
237
|
+
movedAt: /* @__PURE__ */ new Date(),
|
|
238
|
+
affectedTables: [...new Set(entries.map((e) => e.table))],
|
|
239
|
+
affectedEntityIds: [...new Set(entries.map((e) => e.id))],
|
|
240
|
+
retryAttempts
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/sync/background-sync.ts
|
|
245
|
+
import * as BackgroundTask from "expo-background-task";
|
|
246
|
+
import * as TaskManager from "expo-task-manager";
|
|
247
|
+
import { AppState } from "react-native";
|
|
248
|
+
var BACKGROUND_SYNC_TASK = "powersync-background-sync";
|
|
249
|
+
var DEFAULT_MINIMUM_INTERVAL = 15;
|
|
250
|
+
function defineBackgroundSyncTask(getSystem, options = {}) {
|
|
251
|
+
const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;
|
|
252
|
+
TaskManager.defineTask(taskName, async () => {
|
|
253
|
+
const system = getSystem();
|
|
254
|
+
try {
|
|
255
|
+
console.log(`[Background Sync] Starting at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
256
|
+
if (!system) {
|
|
257
|
+
console.warn("[Background Sync] System not available");
|
|
258
|
+
return BackgroundTask.BackgroundTaskResult.Failed;
|
|
259
|
+
}
|
|
260
|
+
if (system.isConnected()) {
|
|
261
|
+
console.log("[Background Sync] Already connected, skipping");
|
|
262
|
+
return BackgroundTask.BackgroundTaskResult.Success;
|
|
263
|
+
}
|
|
264
|
+
if (options.onSyncStart) {
|
|
265
|
+
await options.onSyncStart();
|
|
266
|
+
}
|
|
267
|
+
console.log("[Background Sync] Initializing PowerSync");
|
|
268
|
+
await system.init();
|
|
269
|
+
await new Promise((resolve, reject) => {
|
|
270
|
+
const timeout = setTimeout(() => {
|
|
271
|
+
reject(new Error("Background sync timeout after 25s"));
|
|
272
|
+
}, 25e3);
|
|
273
|
+
const unsubscribe = system.onStatusChange((status) => {
|
|
274
|
+
const hasSynced = Boolean(status.lastSyncedAt);
|
|
275
|
+
const {
|
|
276
|
+
downloading,
|
|
277
|
+
uploading
|
|
278
|
+
} = status;
|
|
279
|
+
if (hasSynced && !downloading && !uploading) {
|
|
280
|
+
console.log("[Background Sync] Sync complete");
|
|
281
|
+
clearTimeout(timeout);
|
|
282
|
+
unsubscribe();
|
|
283
|
+
resolve();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
if (options.onSyncComplete) {
|
|
288
|
+
options.onSyncComplete();
|
|
289
|
+
}
|
|
290
|
+
return BackgroundTask.BackgroundTaskResult.Success;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error("[Background Sync] Failed:", error);
|
|
293
|
+
if (options.onError) {
|
|
294
|
+
options.onError(error);
|
|
295
|
+
}
|
|
296
|
+
return BackgroundTask.BackgroundTaskResult.Failed;
|
|
297
|
+
} finally {
|
|
298
|
+
try {
|
|
299
|
+
await system?.disconnect();
|
|
300
|
+
} catch (e) {
|
|
301
|
+
console.warn("[Background Sync] Disconnect failed:", e);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
return taskName;
|
|
306
|
+
}
|
|
307
|
+
async function initializeBackgroundSync(appReadyPromise, options = {}) {
|
|
308
|
+
const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;
|
|
309
|
+
const minimumInterval = options.minimumInterval ?? DEFAULT_MINIMUM_INTERVAL;
|
|
310
|
+
await appReadyPromise;
|
|
311
|
+
const handleAppStateChange = async (nextAppState) => {
|
|
312
|
+
try {
|
|
313
|
+
console.log("[Background Sync] App state changed:", nextAppState);
|
|
314
|
+
if (nextAppState === "active") {
|
|
315
|
+
const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
|
|
316
|
+
if (isRegistered) {
|
|
317
|
+
console.log("[Background Sync] Unregistering task (app active)");
|
|
318
|
+
await BackgroundTask.unregisterTaskAsync(taskName);
|
|
319
|
+
}
|
|
320
|
+
} else if (nextAppState === "background") {
|
|
321
|
+
const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
|
|
322
|
+
if (!isRegistered) {
|
|
323
|
+
console.log("[Background Sync] Registering task (app backgrounded)");
|
|
324
|
+
await BackgroundTask.registerTaskAsync(taskName, {
|
|
325
|
+
minimumInterval
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.warn("[Background Sync] AppState handler error:", error);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const subscription = AppState.addEventListener("change", handleAppStateChange);
|
|
334
|
+
if (AppState.currentState === "background") {
|
|
335
|
+
const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
|
|
336
|
+
if (!isRegistered) {
|
|
337
|
+
await BackgroundTask.registerTaskAsync(taskName, {
|
|
338
|
+
minimumInterval
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return () => {
|
|
343
|
+
subscription.remove();
|
|
344
|
+
unregisterBackgroundSync(taskName).catch((e) => console.warn("[Background Sync] Cleanup unregister failed:", e));
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async function registerBackgroundSync(options = {}) {
|
|
348
|
+
const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;
|
|
349
|
+
const minimumInterval = options.minimumInterval ?? DEFAULT_MINIMUM_INTERVAL;
|
|
350
|
+
const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
|
|
351
|
+
if (!isRegistered) {
|
|
352
|
+
await BackgroundTask.registerTaskAsync(taskName, {
|
|
353
|
+
minimumInterval
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function unregisterBackgroundSync(taskName = BACKGROUND_SYNC_TASK) {
|
|
358
|
+
const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
|
|
359
|
+
if (isRegistered) {
|
|
360
|
+
await BackgroundTask.unregisterTaskAsync(taskName);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function isBackgroundSyncRegistered(taskName = BACKGROUND_SYNC_TASK) {
|
|
364
|
+
return TaskManager.isTaskRegisteredAsync(taskName);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export {
|
|
368
|
+
DeadLetterQueue,
|
|
369
|
+
generateDLQEntryId,
|
|
370
|
+
createDeadLetterEntry,
|
|
371
|
+
defineBackgroundSyncTask,
|
|
372
|
+
initializeBackgroundSync,
|
|
373
|
+
registerBackgroundSync,
|
|
374
|
+
unregisterBackgroundSync,
|
|
375
|
+
isBackgroundSyncRegistered
|
|
376
|
+
};
|
|
377
|
+
//# sourceMappingURL=chunk-A4IBBWGO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sync/dead-letter-queue.ts","../src/sync/background-sync.ts"],"sourcesContent":["/**\n * Dead Letter Queue (DLQ) for PowerSync\n *\n * Handles mutations that have permanently failed and cannot be automatically retried.\n * These are operations that need manual intervention (data fix, user action, etc.).\n *\n * Use cases:\n * - RLS policy violations (user doesn't have permission)\n * - Foreign key constraint violations (referenced record doesn't exist)\n * - Validation errors (data doesn't match schema)\n * - Max retry exceeded for transient errors\n *\n * The DLQ gives users a way to:\n * 1. See mutations that will never succeed automatically\n * 2. Understand why they failed\n * 3. Manually fix the underlying data issue\n * 4. Retry after fixing\n * 5. Permanently discard if no longer needed\n */\n\nimport type { CrudEntry, SyncError } from '../core/types';\nimport type { AsyncStorageAdapter, LoggerAdapter } from '../platform/types';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Reason why a mutation was moved to the dead letter queue\n */\nexport type DeadLetterReason = 'permanent_error' // Error that will never succeed (RLS, constraints)\n| 'max_retries_exceeded' // Exceeded retry limit for transient errors\n| 'manual_move' // User explicitly moved to DLQ\n| 'conflict_unresolved' // Conflict that couldn't be auto-resolved\n| 'data_corruption'; // Data integrity issues\n\n/**\n * A single entry in the dead letter queue\n */\nexport interface DeadLetterEntry {\n /** Unique identifier for this DLQ entry */\n id: string;\n /** The CRUD entries that failed */\n entries: CrudEntry[];\n /** The error that caused the failure */\n error: SyncError;\n /** Why this was moved to the DLQ */\n reason: DeadLetterReason;\n /** When this was moved to the DLQ */\n movedAt: Date;\n /** Table names affected */\n affectedTables: string[];\n /** Entity IDs affected */\n affectedEntityIds: string[];\n /** Number of retry attempts before being moved to DLQ */\n retryAttempts: number;\n /** Optional user note (e.g., why they're keeping it) */\n userNote?: string;\n}\n\n/**\n * Options for creating a DeadLetterQueue\n */\nexport interface DeadLetterQueueOptions {\n /** Async storage adapter for persistence */\n storage: AsyncStorageAdapter;\n /** Logger for debugging */\n logger: LoggerAdapter;\n /** Maximum entries to keep in DLQ */\n maxEntries?: number;\n /** Time-to-live for entries in ms (0 = never expire) */\n entryTTLMs?: number;\n /** Storage key for persistence */\n storageKey?: string;\n}\n\n/**\n * Listener for DLQ changes\n */\nexport type DeadLetterQueueListener = (entries: DeadLetterEntry[]) => void;\n\n// ─── Default Config ──────────────────────────────────────────────────────────\n\nconst DEFAULT_STORAGE_KEY = '@pol-powersync:dead_letter_queue';\nconst DEFAULT_MAX_ENTRIES = 100;\nconst DEFAULT_TTL_MS = 0; // Never expire by default\n\n// ─── DeadLetterQueue Implementation ──────────────────────────────────────────\n\n/**\n * Dead Letter Queue for permanently failed mutations\n *\n * @example\n * ```typescript\n * const dlq = new DeadLetterQueue({\n * storage: asyncStorage,\n * logger: console,\n * });\n *\n * await dlq.init();\n *\n * // Add a failed mutation\n * await dlq.add({\n * id: 'dlq_123',\n * entries: failedCrudEntries,\n * error: { type: 'validation', message: 'RLS policy violation', ... },\n * reason: 'permanent_error',\n * movedAt: new Date(),\n * affectedTables: ['projects'],\n * affectedEntityIds: ['project-456'],\n * retryAttempts: 3,\n * });\n *\n * // Subscribe to changes\n * const unsubscribe = dlq.onQueueChange((entries) => {\n * console.log('DLQ has', entries.length, 'entries');\n * });\n *\n * // Retry an entry (removes from DLQ)\n * const entry = await dlq.retry('dlq_123');\n * // Now re-submit entry.entries to PowerSync\n * ```\n */\nexport class DeadLetterQueue {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly maxEntries: number;\n private readonly entryTTLMs: number;\n private readonly storageKey: string;\n private entries: DeadLetterEntry[] = [];\n private listeners = new Set<DeadLetterQueueListener>();\n private initialized = false;\n constructor(options: DeadLetterQueueOptions) {\n this.storage = options.storage;\n this.logger = options.logger;\n this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n this.entryTTLMs = options.entryTTLMs ?? DEFAULT_TTL_MS;\n this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n }\n\n // ─── Initialization ──────────────────────────────────────────────────────────\n\n /**\n * Initialize the DLQ by loading persisted entries\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n try {\n const json = await this.storage.getItem(this.storageKey);\n if (json) {\n const parsed = JSON.parse(json) as Array<Omit<DeadLetterEntry, 'movedAt' | 'error'> & {\n movedAt: string;\n error: Omit<SyncError, 'timestamp'> & {\n timestamp: string;\n };\n }>;\n this.entries = parsed.map(item => ({\n ...item,\n movedAt: new Date(item.movedAt),\n error: {\n ...item.error,\n timestamp: new Date(item.error.timestamp)\n }\n })).filter(entry => !isNaN(entry.movedAt.getTime()));\n this.logger.debug('[DLQ] Loaded', this.entries.length, 'entries');\n }\n } catch (err) {\n this.logger.warn('[DLQ] Failed to load entries:', err);\n }\n\n // Cleanup expired entries\n this.cleanupExpired();\n this.initialized = true;\n }\n\n /**\n * Dispose the DLQ (cleanup)\n */\n dispose(): void {\n this.listeners.clear();\n }\n\n // ─── Queue Operations ────────────────────────────────────────────────────────\n\n /**\n * Add an entry to the dead letter queue\n */\n async add(entry: DeadLetterEntry): Promise<void> {\n // Check if entry with same ID already exists\n const existingIndex = this.entries.findIndex(e => e.id === entry.id);\n if (existingIndex !== -1) {\n // Update existing entry\n this.entries[existingIndex] = entry;\n this.logger.debug('[DLQ] Updated existing entry:', entry.id);\n } else {\n // Add new entry\n this.entries.push(entry);\n this.logger.info('[DLQ] Added entry:', entry.id, 'reason:', entry.reason);\n }\n\n // Enforce max entries (remove oldest)\n if (this.entries.length > this.maxEntries) {\n const removed = this.entries.shift();\n this.logger.debug('[DLQ] Removed oldest entry due to max limit:', removed?.id);\n }\n await this.persist();\n this.notifyListeners();\n }\n\n /**\n * Get all entries in the DLQ\n */\n getAll(): DeadLetterEntry[] {\n this.cleanupExpired();\n return [...this.entries];\n }\n\n /**\n * Get an entry by ID\n */\n get(id: string): DeadLetterEntry | null {\n return this.entries.find(e => e.id === id) ?? null;\n }\n\n /**\n * Get entries affecting a specific entity\n */\n getByEntityId(entityId: string): DeadLetterEntry[] {\n return this.entries.filter(e => e.affectedEntityIds.includes(entityId));\n }\n\n /**\n * Get entries for a specific table\n */\n getByTable(tableName: string): DeadLetterEntry[] {\n return this.entries.filter(e => e.affectedTables.includes(tableName));\n }\n\n /**\n * Get entry count\n */\n get count(): number {\n return this.entries.length;\n }\n\n /**\n * Check if DLQ has any entries\n */\n get hasEntries(): boolean {\n return this.entries.length > 0;\n }\n\n // ─── Entry Management ────────────────────────────────────────────────────────\n\n /**\n * Remove an entry from the DLQ (e.g., user discards it)\n */\n async remove(id: string): Promise<DeadLetterEntry | null> {\n const index = this.entries.findIndex(e => e.id === id);\n if (index === -1) {\n this.logger.warn('[DLQ] Entry not found for removal:', id);\n return null;\n }\n const [removed] = this.entries.splice(index, 1);\n this.logger.info('[DLQ] Removed entry:', id);\n await this.persist();\n this.notifyListeners();\n return removed;\n }\n\n /**\n * Retry an entry - removes it from DLQ and returns it for resubmission\n *\n * @returns The entry that was removed (caller should resubmit to PowerSync)\n */\n async retry(id: string): Promise<DeadLetterEntry | null> {\n const entry = await this.remove(id);\n if (entry) {\n this.logger.info('[DLQ] Entry marked for retry:', id);\n }\n return entry;\n }\n\n /**\n * Clear all entries from the DLQ\n */\n async clear(): Promise<void> {\n if (this.entries.length === 0) return;\n const count = this.entries.length;\n this.entries = [];\n this.logger.info('[DLQ] Cleared all', count, 'entries');\n await this.persist();\n this.notifyListeners();\n }\n\n /**\n * Update user note on an entry\n */\n async updateNote(id: string, note: string | undefined): Promise<void> {\n const entry = this.entries.find(e => e.id === id);\n if (!entry) {\n this.logger.warn('[DLQ] Entry not found for note update:', id);\n return;\n }\n entry.userNote = note;\n await this.persist();\n this.notifyListeners();\n }\n\n // ─── Subscriptions ───────────────────────────────────────────────────────────\n\n /**\n * Subscribe to DLQ changes\n * @returns Unsubscribe function\n */\n onQueueChange(listener: DeadLetterQueueListener): () => void {\n this.listeners.add(listener);\n // Immediately call with current state\n listener(this.getAll());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // ─── Statistics ──────────────────────────────────────────────────────────────\n\n /**\n * Get statistics about the DLQ\n */\n getStats(): {\n totalEntries: number;\n byReason: Record<DeadLetterReason, number>;\n byTable: Record<string, number>;\n oldestEntry: Date | null;\n newestEntry: Date | null;\n } {\n const byReason = {} as Record<DeadLetterReason, number>;\n const byTable = {} as Record<string, number>;\n for (const entry of this.entries) {\n byReason[entry.reason] = (byReason[entry.reason] ?? 0) + 1;\n for (const table of entry.affectedTables) {\n byTable[table] = (byTable[table] ?? 0) + 1;\n }\n }\n const sortedByDate = [...this.entries].sort((a, b) => a.movedAt.getTime() - b.movedAt.getTime());\n return {\n totalEntries: this.entries.length,\n byReason,\n byTable,\n oldestEntry: sortedByDate[0]?.movedAt ?? null,\n newestEntry: sortedByDate[sortedByDate.length - 1]?.movedAt ?? null\n };\n }\n\n // ─── Private Methods ─────────────────────────────────────────────────────────\n\n private async persist(): Promise<void> {\n try {\n await this.storage.setItem(this.storageKey, JSON.stringify(this.entries));\n } catch (err) {\n this.logger.warn('[DLQ] Failed to persist entries:', err);\n }\n }\n private notifyListeners(): void {\n const entries = this.getAll();\n for (const listener of this.listeners) {\n try {\n listener(entries);\n } catch (err) {\n this.logger.warn('[DLQ] Listener error:', err);\n }\n }\n }\n private cleanupExpired(): void {\n if (this.entryTTLMs <= 0) return;\n const cutoff = Date.now() - this.entryTTLMs;\n const initialCount = this.entries.length;\n this.entries = this.entries.filter(e => e.movedAt.getTime() > cutoff);\n const removed = initialCount - this.entries.length;\n if (removed > 0) {\n this.logger.debug('[DLQ] Cleaned up', removed, 'expired entries');\n }\n }\n}\n\n// ─── Helper Functions ────────────────────────────────────────────────────────\n\n/**\n * Generate a unique ID for a DLQ entry based on CRUD entries\n */\nexport function generateDLQEntryId(entries: CrudEntry[]): string {\n const entryIds = entries.map(e => e.id).sort().join(',');\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).slice(2, 8);\n return `dlq_${timestamp}_${random}_${entryIds.slice(0, 20)}`;\n}\n\n/**\n * Create a DeadLetterEntry from a failed transaction\n */\nexport function createDeadLetterEntry(entries: CrudEntry[], error: SyncError, reason: DeadLetterReason, retryAttempts: number = 0): DeadLetterEntry {\n return {\n id: generateDLQEntryId(entries),\n entries,\n error,\n reason,\n movedAt: new Date(),\n affectedTables: [...new Set(entries.map(e => e.table))],\n affectedEntityIds: [...new Set(entries.map(e => e.id))],\n retryAttempts\n };\n}","import * as BackgroundTask from \"expo-background-task\";\nimport * as TaskManager from \"expo-task-manager\";\nimport { AppState, type AppStateStatus } from \"react-native\";\nconst BACKGROUND_SYNC_TASK = \"powersync-background-sync\";\nconst DEFAULT_MINIMUM_INTERVAL = 15; // 15 minutes\n\nexport interface BackgroundSyncOptions {\n /** Task name (default: \"powersync-background-sync\") */\n taskName?: string;\n /** Minimum interval in minutes (default: 15) */\n minimumInterval?: number;\n /** Called when background sync starts */\n onSyncStart?: () => Promise<void>;\n /** Called when background sync completes */\n onSyncComplete?: () => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\nexport interface BackgroundSyncSystem {\n /** Initialize PowerSync and connect */\n init: () => Promise<void>;\n /** Disconnect PowerSync */\n disconnect: () => Promise<void>;\n /** Check if PowerSync is connected */\n isConnected: () => boolean;\n /** Get the PowerSync database instance */\n getDatabase: () => any; // AbstractPowerSyncDatabase\n /** Register status change listener, returns unsubscribe function (REQUIRED) */\n onStatusChange: (callback: (status: {\n lastSyncedAt?: Date;\n downloading: boolean;\n uploading: boolean;\n }) => void) => () => void;\n}\n\n/**\n * Define the background sync task.\n * Must be called at module load time (outside of components).\n */\nexport function defineBackgroundSyncTask(getSystem: () => BackgroundSyncSystem | null, options: BackgroundSyncOptions = {}): string {\n const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;\n TaskManager.defineTask(taskName, async () => {\n const system = getSystem();\n try {\n console.log(`[Background Sync] Starting at ${new Date().toISOString()}`);\n if (!system) {\n console.warn(\"[Background Sync] System not available\");\n return BackgroundTask.BackgroundTaskResult.Failed;\n }\n\n // Don't create another connection if already connected\n if (system.isConnected()) {\n console.log(\"[Background Sync] Already connected, skipping\");\n return BackgroundTask.BackgroundTaskResult.Success;\n }\n if (options.onSyncStart) {\n await options.onSyncStart();\n }\n console.log(\"[Background Sync] Initializing PowerSync\");\n await system.init();\n\n // Wait for sync to complete\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"Background sync timeout after 25s\"));\n }, 25000); // iOS ~30s limit\n\n const unsubscribe = system.onStatusChange(status => {\n const hasSynced = Boolean(status.lastSyncedAt);\n const {\n downloading,\n uploading\n } = status;\n if (hasSynced && !downloading && !uploading) {\n console.log(\"[Background Sync] Sync complete\");\n clearTimeout(timeout);\n unsubscribe();\n resolve();\n }\n });\n });\n if (options.onSyncComplete) {\n options.onSyncComplete();\n }\n return BackgroundTask.BackgroundTaskResult.Success;\n } catch (error) {\n console.error(\"[Background Sync] Failed:\", error);\n if (options.onError) {\n options.onError(error as Error);\n }\n return BackgroundTask.BackgroundTaskResult.Failed;\n } finally {\n // Always disconnect after background sync to free resources\n try {\n await system?.disconnect();\n } catch (e) {\n console.warn(\"[Background Sync] Disconnect failed:\", e);\n }\n }\n });\n return taskName;\n}\n\n/**\n * Initialize background sync with AppState management.\n * Registers task when app goes to background, unregisters when active.\n */\nexport async function initializeBackgroundSync(appReadyPromise: Promise<void>, options: BackgroundSyncOptions = {}): Promise<() => void> {\n const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;\n const minimumInterval = options.minimumInterval ?? DEFAULT_MINIMUM_INTERVAL;\n\n // Wait for app to be ready\n await appReadyPromise;\n const handleAppStateChange = async (nextAppState: AppStateStatus) => {\n try {\n console.log(\"[Background Sync] App state changed:\", nextAppState);\n if (nextAppState === \"active\") {\n // App is in foreground, unregister background task\n const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);\n if (isRegistered) {\n console.log(\"[Background Sync] Unregistering task (app active)\");\n await BackgroundTask.unregisterTaskAsync(taskName);\n }\n } else if (nextAppState === \"background\") {\n // App is in background, register task\n const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);\n if (!isRegistered) {\n console.log(\"[Background Sync] Registering task (app backgrounded)\");\n await BackgroundTask.registerTaskAsync(taskName, {\n minimumInterval\n });\n }\n }\n } catch (error) {\n console.warn(\"[Background Sync] AppState handler error:\", error);\n }\n };\n\n // Subscribe to app state changes\n const subscription = AppState.addEventListener(\"change\", handleAppStateChange);\n\n // Check initial state (in case app starts in background)\n if (AppState.currentState === \"background\") {\n const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);\n if (!isRegistered) {\n await BackgroundTask.registerTaskAsync(taskName, {\n minimumInterval\n });\n }\n }\n\n // Return cleanup function\n return () => {\n subscription.remove();\n // Fire and forget the unregister\n unregisterBackgroundSync(taskName).catch(e => console.warn(\"[Background Sync] Cleanup unregister failed:\", e));\n };\n}\n\n/**\n * Manually register the background sync task.\n */\nexport async function registerBackgroundSync(options: BackgroundSyncOptions = {}): Promise<void> {\n const taskName = options.taskName ?? BACKGROUND_SYNC_TASK;\n const minimumInterval = options.minimumInterval ?? DEFAULT_MINIMUM_INTERVAL;\n const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);\n if (!isRegistered) {\n await BackgroundTask.registerTaskAsync(taskName, {\n minimumInterval\n });\n }\n}\n\n/**\n * Manually unregister the background sync task.\n */\nexport async function unregisterBackgroundSync(taskName: string = BACKGROUND_SYNC_TASK): Promise<void> {\n const isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);\n if (isRegistered) {\n await BackgroundTask.unregisterTaskAsync(taskName);\n }\n}\n\n/**\n * Check if background sync task is registered.\n */\nexport async function isBackgroundSyncRegistered(taskName: string = BACKGROUND_SYNC_TASK): Promise<boolean> {\n return TaskManager.isTaskRegisteredAsync(taskName);\n}"],"mappings":"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","names":[]}
|