@pol-studios/powersync 1.0.3 → 1.0.6
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/dist/chunk-5FIMA26D.js +1 -0
- package/dist/{chunk-C2RSTGDC.js → chunk-62J2DPKX.js} +497 -43
- package/dist/chunk-62J2DPKX.js.map +1 -0
- package/dist/{chunk-GMFDCVMZ.js → chunk-KCDG2MNP.js} +161 -15
- package/dist/chunk-KCDG2MNP.js.map +1 -0
- package/dist/chunk-PAFBKNL3.js +99 -0
- package/dist/chunk-PAFBKNL3.js.map +1 -0
- package/dist/{chunk-OTJXIRWX.js → chunk-R4YFWQ3Q.js} +1 -1
- package/dist/chunk-R4YFWQ3Q.js.map +1 -0
- package/dist/connector/index.d.ts +2 -1
- package/dist/connector/index.js +4 -1
- package/dist/failed-upload-store-C0cLxxPz.d.ts +33 -0
- package/dist/{index-Cb-NI0Ct.d.ts → index-l3iL9Jte.d.ts} +72 -1
- package/dist/index.d.ts +142 -4
- package/dist/index.js +52 -6
- package/dist/index.native.d.ts +4 -3
- package/dist/index.native.js +52 -6
- package/dist/index.web.d.ts +4 -3
- package/dist/index.web.js +52 -6
- package/dist/provider/index.d.ts +258 -3
- package/dist/provider/index.js +28 -3
- package/dist/sync/index.d.ts +1 -0
- package/dist/sync/index.js +9 -3
- package/package.json +1 -1
- package/dist/chunk-7JQZBZ5N.js +0 -1
- package/dist/chunk-C2RSTGDC.js.map +0 -1
- package/dist/chunk-GMFDCVMZ.js.map +0 -1
- package/dist/chunk-OTJXIRWX.js.map +0 -1
- /package/dist/{chunk-7JQZBZ5N.js.map → chunk-5FIMA26D.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-5FIMA26D.js.map
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
failedUploadStore
|
|
3
|
+
} from "./chunk-PAFBKNL3.js";
|
|
1
4
|
import {
|
|
2
5
|
classifySupabaseError
|
|
3
6
|
} from "./chunk-FPTDATY5.js";
|
|
4
7
|
|
|
5
8
|
// src/connector/types.ts
|
|
6
9
|
var defaultSchemaRouter = () => "public";
|
|
10
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
11
|
+
transient: {
|
|
12
|
+
maxRetries: 3,
|
|
13
|
+
baseDelayMs: 1e3,
|
|
14
|
+
maxDelayMs: 3e4,
|
|
15
|
+
backoffMultiplier: 2
|
|
16
|
+
},
|
|
17
|
+
permanent: {
|
|
18
|
+
maxRetries: 2,
|
|
19
|
+
baseDelayMs: 5e3,
|
|
20
|
+
maxDelayMs: 3e5,
|
|
21
|
+
backoffMultiplier: 3
|
|
22
|
+
}
|
|
23
|
+
};
|
|
7
24
|
|
|
8
25
|
// src/conflicts/detect.ts
|
|
9
26
|
var TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
@@ -112,6 +129,120 @@ async function getLocalVersion(table, recordId, db) {
|
|
|
112
129
|
return result?._version ?? null;
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
// src/utils/retry.ts
|
|
133
|
+
var AbortError = class extends Error {
|
|
134
|
+
constructor(message = "Operation aborted") {
|
|
135
|
+
super(message);
|
|
136
|
+
this.name = "AbortError";
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var RetryExhaustedError = class extends Error {
|
|
140
|
+
/** The last error that caused the final retry to fail */
|
|
141
|
+
cause;
|
|
142
|
+
/** Total number of attempts made */
|
|
143
|
+
attempts;
|
|
144
|
+
constructor(cause, attempts) {
|
|
145
|
+
super(`Retry exhausted after ${attempts} attempt(s): ${cause.message}`);
|
|
146
|
+
this.name = "RetryExhaustedError";
|
|
147
|
+
this.cause = cause;
|
|
148
|
+
this.attempts = attempts;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function calculateBackoffDelay(attempt, config) {
|
|
152
|
+
const {
|
|
153
|
+
baseDelayMs,
|
|
154
|
+
maxDelayMs,
|
|
155
|
+
backoffMultiplier
|
|
156
|
+
} = config;
|
|
157
|
+
const safeAttempt = Math.max(0, attempt);
|
|
158
|
+
const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, safeAttempt);
|
|
159
|
+
return Math.min(exponentialDelay, maxDelayMs);
|
|
160
|
+
}
|
|
161
|
+
function addJitter(delay) {
|
|
162
|
+
const jitterFactor = 0.9 + Math.random() * 0.2;
|
|
163
|
+
return Math.round(delay * jitterFactor);
|
|
164
|
+
}
|
|
165
|
+
function sleep(ms, signal) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
if (signal?.aborted) {
|
|
168
|
+
reject(new AbortError());
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (ms <= 0) {
|
|
172
|
+
resolve();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let timeoutId;
|
|
176
|
+
const handleAbort = () => {
|
|
177
|
+
if (timeoutId !== void 0) {
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
}
|
|
180
|
+
reject(new AbortError());
|
|
181
|
+
};
|
|
182
|
+
if (signal) {
|
|
183
|
+
signal.addEventListener("abort", handleAbort, {
|
|
184
|
+
once: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
timeoutId = setTimeout(() => {
|
|
188
|
+
if (signal) {
|
|
189
|
+
signal.removeEventListener("abort", handleAbort);
|
|
190
|
+
}
|
|
191
|
+
resolve();
|
|
192
|
+
}, ms);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
var DEFAULT_BACKOFF_CONFIG = {
|
|
196
|
+
maxRetries: 3,
|
|
197
|
+
baseDelayMs: 1e3,
|
|
198
|
+
maxDelayMs: 3e4,
|
|
199
|
+
backoffMultiplier: 2
|
|
200
|
+
};
|
|
201
|
+
async function withExponentialBackoff(fn, config, options) {
|
|
202
|
+
const {
|
|
203
|
+
maxRetries,
|
|
204
|
+
baseDelayMs,
|
|
205
|
+
maxDelayMs,
|
|
206
|
+
backoffMultiplier
|
|
207
|
+
} = config;
|
|
208
|
+
const {
|
|
209
|
+
signal,
|
|
210
|
+
onRetry
|
|
211
|
+
} = options ?? {};
|
|
212
|
+
if (signal?.aborted) {
|
|
213
|
+
throw new AbortError();
|
|
214
|
+
}
|
|
215
|
+
const safeMaxRetries = Math.max(0, Math.floor(maxRetries));
|
|
216
|
+
const totalAttempts = safeMaxRetries + 1;
|
|
217
|
+
let lastError;
|
|
218
|
+
for (let attempt = 0; attempt < totalAttempts; attempt++) {
|
|
219
|
+
if (signal?.aborted) {
|
|
220
|
+
throw new AbortError();
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
return await fn();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
226
|
+
const isLastAttempt = attempt === totalAttempts - 1;
|
|
227
|
+
if (isLastAttempt) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
if (signal?.aborted) {
|
|
231
|
+
throw new AbortError();
|
|
232
|
+
}
|
|
233
|
+
const baseDelay = calculateBackoffDelay(attempt, {
|
|
234
|
+
baseDelayMs,
|
|
235
|
+
maxDelayMs,
|
|
236
|
+
backoffMultiplier
|
|
237
|
+
});
|
|
238
|
+
const delayWithJitter = addJitter(baseDelay);
|
|
239
|
+
onRetry?.(attempt + 1, delayWithJitter, lastError);
|
|
240
|
+
await sleep(delayWithJitter, signal);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
throw new RetryExhaustedError(lastError, totalAttempts);
|
|
244
|
+
}
|
|
245
|
+
|
|
115
246
|
// src/connector/supabase-connector.ts
|
|
116
247
|
var SupabaseConnector = class {
|
|
117
248
|
supabase;
|
|
@@ -136,6 +267,9 @@ var SupabaseConnector = class {
|
|
|
136
267
|
resolvedConflicts = /* @__PURE__ */ new Map();
|
|
137
268
|
// Cleanup function for resolution listener subscription
|
|
138
269
|
unsubscribeResolution;
|
|
270
|
+
// Retry configuration
|
|
271
|
+
retryConfig;
|
|
272
|
+
autoRetryPaused = false;
|
|
139
273
|
constructor(options) {
|
|
140
274
|
this.supabase = options.supabaseClient;
|
|
141
275
|
this.powerSyncUrl = options.powerSyncUrl;
|
|
@@ -149,6 +283,16 @@ var SupabaseConnector = class {
|
|
|
149
283
|
this.conflictDetection = options.conflictDetection;
|
|
150
284
|
this.conflictHandler = options.conflictHandler;
|
|
151
285
|
this.conflictBus = options.conflictBus;
|
|
286
|
+
this.retryConfig = {
|
|
287
|
+
transient: {
|
|
288
|
+
...DEFAULT_RETRY_CONFIG.transient,
|
|
289
|
+
...options.retryConfig?.transient
|
|
290
|
+
},
|
|
291
|
+
permanent: {
|
|
292
|
+
...DEFAULT_RETRY_CONFIG.permanent,
|
|
293
|
+
...options.retryConfig?.permanent
|
|
294
|
+
}
|
|
295
|
+
};
|
|
152
296
|
if (this.conflictBus) {
|
|
153
297
|
this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {
|
|
154
298
|
const key = `${table}:${recordId}`;
|
|
@@ -175,6 +319,196 @@ var SupabaseConnector = class {
|
|
|
175
319
|
}
|
|
176
320
|
this.resolvedConflicts.clear();
|
|
177
321
|
}
|
|
322
|
+
// ─── Retry Control Methods ─────────────────────────────────────────────────
|
|
323
|
+
/**
|
|
324
|
+
* Pause automatic retry of failed uploads.
|
|
325
|
+
* Use this when the user goes offline intentionally or wants manual control.
|
|
326
|
+
*/
|
|
327
|
+
pauseAutoRetry() {
|
|
328
|
+
this.autoRetryPaused = true;
|
|
329
|
+
if (__DEV__) {
|
|
330
|
+
console.log("[Connector] Auto-retry paused");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Resume automatic retry of failed uploads.
|
|
335
|
+
*/
|
|
336
|
+
resumeAutoRetry() {
|
|
337
|
+
this.autoRetryPaused = false;
|
|
338
|
+
if (__DEV__) {
|
|
339
|
+
console.log("[Connector] Auto-retry resumed");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Manually retry all failed uploads that are ready for retry.
|
|
344
|
+
* This processes entries from the failed upload store.
|
|
345
|
+
*/
|
|
346
|
+
async retryFailedUploads() {
|
|
347
|
+
const retryableUploads = failedUploadStore.getRetryable();
|
|
348
|
+
if (retryableUploads.length === 0) {
|
|
349
|
+
if (__DEV__) {
|
|
350
|
+
console.log("[Connector] No failed uploads ready for retry");
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (__DEV__) {
|
|
355
|
+
console.log("[Connector] Manually retrying failed uploads:", {
|
|
356
|
+
count: retryableUploads.length,
|
|
357
|
+
entries: retryableUploads.map((u) => ({
|
|
358
|
+
table: u.table,
|
|
359
|
+
id: u.id,
|
|
360
|
+
operation: u.operation
|
|
361
|
+
}))
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
for (const upload of retryableUploads) {
|
|
365
|
+
try {
|
|
366
|
+
const entry = {
|
|
367
|
+
table: upload.table,
|
|
368
|
+
op: upload.operation,
|
|
369
|
+
id: upload.id,
|
|
370
|
+
clientId: Date.now(),
|
|
371
|
+
// Synthetic clientId for retry
|
|
372
|
+
opData: upload.data
|
|
373
|
+
};
|
|
374
|
+
await this.processWithRetry(entry);
|
|
375
|
+
failedUploadStore.remove(upload.id);
|
|
376
|
+
if (__DEV__) {
|
|
377
|
+
console.log("[Connector] Retry succeeded for:", {
|
|
378
|
+
table: upload.table,
|
|
379
|
+
id: upload.id
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (__DEV__) {
|
|
384
|
+
console.log("[Connector] Retry failed again for:", {
|
|
385
|
+
table: upload.table,
|
|
386
|
+
id: upload.id,
|
|
387
|
+
error: error instanceof Error ? error.message : String(error)
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Clear all failed uploads from the store.
|
|
395
|
+
* Use with caution - this discards all pending retries.
|
|
396
|
+
*/
|
|
397
|
+
clearFailedUploads() {
|
|
398
|
+
failedUploadStore.clear();
|
|
399
|
+
if (__DEV__) {
|
|
400
|
+
console.log("[Connector] Failed uploads cleared");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get all failed uploads from the store.
|
|
405
|
+
*/
|
|
406
|
+
getFailedUploads() {
|
|
407
|
+
return failedUploadStore.getAll();
|
|
408
|
+
}
|
|
409
|
+
// ─── Private Retry Logic ───────────────────────────────────────────────────
|
|
410
|
+
/**
|
|
411
|
+
* Process a single CRUD entry with exponential backoff retry.
|
|
412
|
+
*
|
|
413
|
+
* @param entry - The CRUD entry to process
|
|
414
|
+
* @throws Error if all retries exhausted (for critical failures)
|
|
415
|
+
*/
|
|
416
|
+
async processWithRetry(entry) {
|
|
417
|
+
const classified = {
|
|
418
|
+
isPermanent: false,
|
|
419
|
+
pgCode: void 0,
|
|
420
|
+
userMessage: ""
|
|
421
|
+
};
|
|
422
|
+
let selectedConfig = this.retryConfig.transient;
|
|
423
|
+
let lastError;
|
|
424
|
+
try {
|
|
425
|
+
await withExponentialBackoff(async () => {
|
|
426
|
+
try {
|
|
427
|
+
await this.processCrudEntry(entry);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
const classifiedError = classifySupabaseError(error);
|
|
430
|
+
classified.isPermanent = classifiedError.isPermanent;
|
|
431
|
+
classified.pgCode = classifiedError.pgCode;
|
|
432
|
+
classified.userMessage = classifiedError.userMessage;
|
|
433
|
+
if (classifiedError.isPermanent) {
|
|
434
|
+
selectedConfig = this.retryConfig.permanent;
|
|
435
|
+
}
|
|
436
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
437
|
+
if (__DEV__) {
|
|
438
|
+
console.log("[Connector] CRUD operation failed, will retry:", {
|
|
439
|
+
table: entry.table,
|
|
440
|
+
op: entry.op,
|
|
441
|
+
id: entry.id,
|
|
442
|
+
isPermanent: classifiedError.isPermanent,
|
|
443
|
+
pgCode: classifiedError.pgCode,
|
|
444
|
+
userMessage: classifiedError.userMessage
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}, selectedConfig, {
|
|
450
|
+
onRetry: (attempt, delay, error) => {
|
|
451
|
+
this.logger?.debug("[Connector] Retry attempt:", {
|
|
452
|
+
table: entry.table,
|
|
453
|
+
op: entry.op,
|
|
454
|
+
id: entry.id,
|
|
455
|
+
attempt,
|
|
456
|
+
delay,
|
|
457
|
+
error: error.message
|
|
458
|
+
});
|
|
459
|
+
if (__DEV__) {
|
|
460
|
+
console.log("[Connector] Retry attempt:", {
|
|
461
|
+
table: entry.table,
|
|
462
|
+
op: entry.op,
|
|
463
|
+
id: entry.id,
|
|
464
|
+
attempt,
|
|
465
|
+
delayMs: delay,
|
|
466
|
+
errorMessage: error.message
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
} catch (error) {
|
|
472
|
+
const finalError = lastError ?? (error instanceof Error ? error : new Error(String(error)));
|
|
473
|
+
const category = classified.isPermanent ? "permanent" : classified.pgCode ? "transient" : "unknown";
|
|
474
|
+
const retryConfig = classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;
|
|
475
|
+
const nextRetryDelay = calculateBackoffDelay(retryConfig.maxRetries, retryConfig);
|
|
476
|
+
const nextRetryAt = Date.now() + nextRetryDelay;
|
|
477
|
+
failedUploadStore.add({
|
|
478
|
+
table: entry.table,
|
|
479
|
+
operation: entry.op,
|
|
480
|
+
data: entry.opData ?? {},
|
|
481
|
+
error: {
|
|
482
|
+
message: finalError.message,
|
|
483
|
+
code: classified.pgCode,
|
|
484
|
+
category
|
|
485
|
+
},
|
|
486
|
+
retryCount: retryConfig.maxRetries,
|
|
487
|
+
lastAttempt: Date.now(),
|
|
488
|
+
nextRetryAt
|
|
489
|
+
});
|
|
490
|
+
if (__DEV__) {
|
|
491
|
+
console.log("[Connector] Entry added to failed upload store:", {
|
|
492
|
+
table: entry.table,
|
|
493
|
+
op: entry.op,
|
|
494
|
+
id: entry.id,
|
|
495
|
+
category,
|
|
496
|
+
nextRetryAt: new Date(nextRetryAt).toISOString()
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
this.logger?.error("[Connector] CRUD entry failed after retries:", {
|
|
500
|
+
table: entry.table,
|
|
501
|
+
op: entry.op,
|
|
502
|
+
id: entry.id,
|
|
503
|
+
error: finalError.message,
|
|
504
|
+
category,
|
|
505
|
+
nextRetryAt
|
|
506
|
+
});
|
|
507
|
+
if (classified.isPermanent) {
|
|
508
|
+
throw finalError;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
178
512
|
/**
|
|
179
513
|
* Set the active project IDs for scoped sync.
|
|
180
514
|
* Call this when user selects/opens projects.
|
|
@@ -237,6 +571,44 @@ var SupabaseConnector = class {
|
|
|
237
571
|
this.logger?.debug("[Connector] Upload skipped - sync mode does not allow uploads");
|
|
238
572
|
return;
|
|
239
573
|
}
|
|
574
|
+
if (!this.autoRetryPaused) {
|
|
575
|
+
const retryableUploads = failedUploadStore.getRetryable();
|
|
576
|
+
if (retryableUploads.length > 0) {
|
|
577
|
+
if (__DEV__) {
|
|
578
|
+
console.log("[Connector] Processing retryable failed uploads:", {
|
|
579
|
+
count: retryableUploads.length
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
for (const upload of retryableUploads) {
|
|
583
|
+
try {
|
|
584
|
+
const entry = {
|
|
585
|
+
table: upload.table,
|
|
586
|
+
op: upload.operation,
|
|
587
|
+
id: upload.id,
|
|
588
|
+
clientId: Date.now(),
|
|
589
|
+
// Synthetic clientId for retry
|
|
590
|
+
opData: upload.data
|
|
591
|
+
};
|
|
592
|
+
await this.processWithRetry(entry);
|
|
593
|
+
failedUploadStore.remove(upload.id);
|
|
594
|
+
if (__DEV__) {
|
|
595
|
+
console.log("[Connector] Retried upload succeeded:", {
|
|
596
|
+
table: upload.table,
|
|
597
|
+
id: upload.id
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (__DEV__) {
|
|
602
|
+
console.log("[Connector] Retried upload failed again:", {
|
|
603
|
+
table: upload.table,
|
|
604
|
+
id: upload.id,
|
|
605
|
+
error: error instanceof Error ? error.message : String(error)
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
240
612
|
if (__DEV__) {
|
|
241
613
|
console.log("[Connector] uploadData called, fetching next CRUD transaction...");
|
|
242
614
|
}
|
|
@@ -400,25 +772,62 @@ var SupabaseConnector = class {
|
|
|
400
772
|
}
|
|
401
773
|
return;
|
|
402
774
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
console.log("[Connector] Processing CRUD entry:", {
|
|
407
|
-
table: entry.table,
|
|
408
|
-
op: entry.op,
|
|
409
|
-
id: entry.id,
|
|
410
|
-
opData: entry.opData
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
await this.processCrudEntry(entry);
|
|
414
|
-
}
|
|
775
|
+
const criticalFailures = [];
|
|
776
|
+
const successfulEntries = [];
|
|
777
|
+
for (const entry of entriesToProcess) {
|
|
415
778
|
if (__DEV__) {
|
|
416
|
-
console.log("[Connector]
|
|
779
|
+
console.log("[Connector] Processing CRUD entry with retry:", {
|
|
780
|
+
table: entry.table,
|
|
781
|
+
op: entry.op,
|
|
782
|
+
id: entry.id,
|
|
783
|
+
opData: entry.opData
|
|
784
|
+
});
|
|
417
785
|
}
|
|
786
|
+
try {
|
|
787
|
+
await this.processWithRetry(entry);
|
|
788
|
+
successfulEntries.push(entry);
|
|
789
|
+
} catch (error) {
|
|
790
|
+
criticalFailures.push({
|
|
791
|
+
entry,
|
|
792
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (criticalFailures.length > 0) {
|
|
797
|
+
const firstFailure = criticalFailures[0];
|
|
798
|
+
const classified = classifySupabaseError(firstFailure.error);
|
|
799
|
+
console.error("[PowerSync Connector] Critical upload failure:", {
|
|
800
|
+
errorMessage: firstFailure.error.message,
|
|
801
|
+
classified,
|
|
802
|
+
isPermanent: classified.isPermanent,
|
|
803
|
+
criticalCount: criticalFailures.length,
|
|
804
|
+
successCount: successfulEntries.length,
|
|
805
|
+
entries: criticalFailures.map((f) => ({
|
|
806
|
+
table: f.entry.table,
|
|
807
|
+
op: f.entry.op,
|
|
808
|
+
id: f.entry.id
|
|
809
|
+
}))
|
|
810
|
+
});
|
|
811
|
+
this.logger?.error("[Connector] Critical upload failure:", {
|
|
812
|
+
error: firstFailure.error,
|
|
813
|
+
classified,
|
|
814
|
+
entries: criticalFailures.map((f) => ({
|
|
815
|
+
table: f.entry.table,
|
|
816
|
+
op: f.entry.op,
|
|
817
|
+
id: f.entry.id
|
|
818
|
+
}))
|
|
819
|
+
});
|
|
820
|
+
this.onTransactionFailure?.(criticalFailures.map((f) => f.entry), firstFailure.error, classified);
|
|
821
|
+
throw firstFailure.error;
|
|
822
|
+
}
|
|
823
|
+
if (__DEV__) {
|
|
824
|
+
console.log("[Connector] All CRUD entries processed, completing transaction...");
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
418
827
|
await transaction.complete();
|
|
419
828
|
if (__DEV__) {
|
|
420
829
|
console.log("[Connector] Transaction completed successfully:", {
|
|
421
|
-
entriesCount:
|
|
830
|
+
entriesCount: successfulEntries.length,
|
|
422
831
|
discardedCount: entriesDiscarded.length
|
|
423
832
|
});
|
|
424
833
|
}
|
|
@@ -448,32 +857,32 @@ var SupabaseConnector = class {
|
|
|
448
857
|
}
|
|
449
858
|
}
|
|
450
859
|
}
|
|
451
|
-
this.onTransactionSuccess?.(
|
|
452
|
-
this.onTransactionComplete?.(
|
|
860
|
+
this.onTransactionSuccess?.(successfulEntries);
|
|
861
|
+
this.onTransactionComplete?.(successfulEntries);
|
|
453
862
|
} catch (error) {
|
|
454
863
|
const classified = classifySupabaseError(error);
|
|
455
|
-
console.error("[PowerSync Connector]
|
|
864
|
+
console.error("[PowerSync Connector] Transaction completion FAILED:", {
|
|
456
865
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
457
866
|
errorKeys: error && typeof error === "object" ? Object.keys(error) : [],
|
|
458
867
|
errorObject: JSON.stringify(error, null, 2),
|
|
459
868
|
classified,
|
|
460
869
|
isPermanent: classified.isPermanent,
|
|
461
|
-
entries:
|
|
870
|
+
entries: successfulEntries.map((e) => ({
|
|
462
871
|
table: e.table,
|
|
463
872
|
op: e.op,
|
|
464
873
|
id: e.id
|
|
465
874
|
}))
|
|
466
875
|
});
|
|
467
|
-
this.logger?.error("[Connector]
|
|
876
|
+
this.logger?.error("[Connector] Transaction completion error:", {
|
|
468
877
|
error,
|
|
469
878
|
classified,
|
|
470
|
-
entries:
|
|
879
|
+
entries: successfulEntries.map((e) => ({
|
|
471
880
|
table: e.table,
|
|
472
881
|
op: e.op,
|
|
473
882
|
id: e.id
|
|
474
883
|
}))
|
|
475
884
|
});
|
|
476
|
-
this.onTransactionFailure?.(
|
|
885
|
+
this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);
|
|
477
886
|
throw error;
|
|
478
887
|
}
|
|
479
888
|
}
|
|
@@ -482,53 +891,90 @@ var SupabaseConnector = class {
|
|
|
482
891
|
* Used when conflict detection is disabled.
|
|
483
892
|
*/
|
|
484
893
|
async processTransaction(transaction, _database) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
console.log("[Connector] Processing CRUD entry:", {
|
|
489
|
-
table: entry.table,
|
|
490
|
-
op: entry.op,
|
|
491
|
-
id: entry.id,
|
|
492
|
-
opData: entry.opData
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
await this.processCrudEntry(entry);
|
|
496
|
-
}
|
|
894
|
+
const criticalFailures = [];
|
|
895
|
+
const successfulEntries = [];
|
|
896
|
+
for (const entry of transaction.crud) {
|
|
497
897
|
if (__DEV__) {
|
|
498
|
-
console.log("[Connector]
|
|
898
|
+
console.log("[Connector] Processing CRUD entry with retry:", {
|
|
899
|
+
table: entry.table,
|
|
900
|
+
op: entry.op,
|
|
901
|
+
id: entry.id,
|
|
902
|
+
opData: entry.opData
|
|
903
|
+
});
|
|
499
904
|
}
|
|
905
|
+
try {
|
|
906
|
+
await this.processWithRetry(entry);
|
|
907
|
+
successfulEntries.push(entry);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
criticalFailures.push({
|
|
910
|
+
entry,
|
|
911
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (criticalFailures.length > 0) {
|
|
916
|
+
const firstFailure = criticalFailures[0];
|
|
917
|
+
const classified = classifySupabaseError(firstFailure.error);
|
|
918
|
+
console.error("[PowerSync Connector] Critical upload failure:", {
|
|
919
|
+
errorMessage: firstFailure.error.message,
|
|
920
|
+
classified,
|
|
921
|
+
isPermanent: classified.isPermanent,
|
|
922
|
+
criticalCount: criticalFailures.length,
|
|
923
|
+
successCount: successfulEntries.length,
|
|
924
|
+
entries: criticalFailures.map((f) => ({
|
|
925
|
+
table: f.entry.table,
|
|
926
|
+
op: f.entry.op,
|
|
927
|
+
id: f.entry.id
|
|
928
|
+
}))
|
|
929
|
+
});
|
|
930
|
+
this.logger?.error("[Connector] Critical upload failure:", {
|
|
931
|
+
error: firstFailure.error,
|
|
932
|
+
classified,
|
|
933
|
+
entries: criticalFailures.map((f) => ({
|
|
934
|
+
table: f.entry.table,
|
|
935
|
+
op: f.entry.op,
|
|
936
|
+
id: f.entry.id
|
|
937
|
+
}))
|
|
938
|
+
});
|
|
939
|
+
this.onTransactionFailure?.(criticalFailures.map((f) => f.entry), firstFailure.error, classified);
|
|
940
|
+
throw firstFailure.error;
|
|
941
|
+
}
|
|
942
|
+
if (__DEV__) {
|
|
943
|
+
console.log("[Connector] All CRUD entries processed, completing transaction...");
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
500
946
|
await transaction.complete();
|
|
501
947
|
if (__DEV__) {
|
|
502
948
|
console.log("[Connector] Transaction completed successfully:", {
|
|
503
|
-
entriesCount:
|
|
949
|
+
entriesCount: successfulEntries.length
|
|
504
950
|
});
|
|
505
951
|
}
|
|
506
|
-
this.onTransactionSuccess?.(
|
|
507
|
-
this.onTransactionComplete?.(
|
|
952
|
+
this.onTransactionSuccess?.(successfulEntries);
|
|
953
|
+
this.onTransactionComplete?.(successfulEntries);
|
|
508
954
|
} catch (error) {
|
|
509
955
|
const classified = classifySupabaseError(error);
|
|
510
|
-
console.error("[PowerSync Connector]
|
|
956
|
+
console.error("[PowerSync Connector] Transaction completion FAILED:", {
|
|
511
957
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
512
958
|
errorKeys: error && typeof error === "object" ? Object.keys(error) : [],
|
|
513
959
|
errorObject: JSON.stringify(error, null, 2),
|
|
514
960
|
classified,
|
|
515
961
|
isPermanent: classified.isPermanent,
|
|
516
|
-
entries:
|
|
962
|
+
entries: successfulEntries.map((e) => ({
|
|
517
963
|
table: e.table,
|
|
518
964
|
op: e.op,
|
|
519
965
|
id: e.id
|
|
520
966
|
}))
|
|
521
967
|
});
|
|
522
|
-
this.logger?.error("[Connector]
|
|
968
|
+
this.logger?.error("[Connector] Transaction completion error:", {
|
|
523
969
|
error,
|
|
524
970
|
classified,
|
|
525
|
-
entries:
|
|
971
|
+
entries: successfulEntries.map((e) => ({
|
|
526
972
|
table: e.table,
|
|
527
973
|
op: e.op,
|
|
528
974
|
id: e.id
|
|
529
975
|
}))
|
|
530
976
|
});
|
|
531
|
-
this.onTransactionFailure?.(
|
|
977
|
+
this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);
|
|
532
978
|
throw error;
|
|
533
979
|
}
|
|
534
980
|
}
|
|
@@ -717,10 +1163,18 @@ var SupabaseConnector = class {
|
|
|
717
1163
|
|
|
718
1164
|
export {
|
|
719
1165
|
defaultSchemaRouter,
|
|
1166
|
+
DEFAULT_RETRY_CONFIG,
|
|
720
1167
|
detectConflicts,
|
|
721
1168
|
hasVersionColumn,
|
|
722
1169
|
fetchServerVersion,
|
|
723
1170
|
getLocalVersion,
|
|
1171
|
+
AbortError,
|
|
1172
|
+
RetryExhaustedError,
|
|
1173
|
+
calculateBackoffDelay,
|
|
1174
|
+
addJitter,
|
|
1175
|
+
sleep,
|
|
1176
|
+
DEFAULT_BACKOFF_CONFIG,
|
|
1177
|
+
withExponentialBackoff,
|
|
724
1178
|
SupabaseConnector
|
|
725
1179
|
};
|
|
726
|
-
//# sourceMappingURL=chunk-
|
|
1180
|
+
//# sourceMappingURL=chunk-62J2DPKX.js.map
|