async-storage-sync 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +84 -8
- package/dist/index.mjs +84 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,8 +48,9 @@ await store.save('forms', {
|
|
|
48
48
|
timestamp: new Date().toISOString()
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// Sync
|
|
52
|
-
await store.
|
|
51
|
+
// Sync and get summary result
|
|
52
|
+
const result = await store.flushWithResult();
|
|
53
|
+
console.log(`Synced: ${result.synced}, Failed: ${result.failed}, Remaining: ${result.remainingPending}`);
|
|
53
54
|
|
|
54
55
|
// List pending records
|
|
55
56
|
const pending = await store.getAll('forms');
|
|
@@ -62,7 +63,7 @@ console.log(`${pending.length} forms waiting to sync`);
|
|
|
62
63
|
- `autoSync: true` also listens for reconnect events and retries pending items automatically.
|
|
63
64
|
- `autoSync: false`: no automatic syncing; call sync methods manually when you choose.
|
|
64
65
|
- Manual methods:
|
|
65
|
-
- `store.
|
|
66
|
+
- `store.flushWithResult()` → sync all pending and return summary counts
|
|
66
67
|
- `store.sync(collection)` → sync one collection
|
|
67
68
|
- `store.syncById(collection, id)` → sync one record
|
|
68
69
|
- Sync destination is controlled by your config: `serverUrl + endpoint`.
|
|
@@ -86,7 +87,7 @@ console.log(`${pending.length} forms waiting to sync`);
|
|
|
86
87
|
| `store.getById(collection, id)` | Get one record by internal `_id` |
|
|
87
88
|
| `store.deleteById(collection, id)` | Delete one record by internal `_id` |
|
|
88
89
|
| `store.deleteCollection(collection)` | Delete all records in one collection |
|
|
89
|
-
| `store.
|
|
90
|
+
| `store.flushWithResult()` | Sync all pending and return detailed summary (`attempted`, `synced`, `failed`, `retried`, `remainingPending`, `items`) |
|
|
90
91
|
| `store.sync(collection)` | Sync pending items for one collection only |
|
|
91
92
|
| `store.syncById(collection, id)` | Sync one specific record by internal `_id` |
|
|
92
93
|
| `store.requeueFailed()` | Move `failed` records back to pending queue for retry |
|
|
@@ -105,7 +106,7 @@ import { getSyncQueue } from 'async-storage-sync';
|
|
|
105
106
|
|
|
106
107
|
NetInfo.addEventListener(state => {
|
|
107
108
|
if (state.isConnected) {
|
|
108
|
-
void getSyncQueue().
|
|
109
|
+
void getSyncQueue().flushWithResult();
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
112
|
```
|
|
@@ -190,7 +191,7 @@ Notes:
|
|
|
190
191
|
|
|
191
192
|
1. **Save** — Records written to AsyncStorage immediately
|
|
192
193
|
2. **Queue** — Each save queued for syncing
|
|
193
|
-
3. **Sync** — `
|
|
194
|
+
3. **Sync** — `flushWithResult()` POSTs all pending to your server and returns summary output
|
|
194
195
|
4. **Status** — Records marked synced, then kept or deleted
|
|
195
196
|
5. **Retry** — Failed syncs retry automatically (max 5x)
|
|
196
197
|
6. **Persist** — Everything survives app restart
|
package/dist/index.d.mts
CHANGED
|
@@ -42,6 +42,25 @@ interface SaveOptions {
|
|
|
42
42
|
onSyncSuccess?: OnSyncSuccess;
|
|
43
43
|
duplicateStrategy?: DuplicateStrategy;
|
|
44
44
|
}
|
|
45
|
+
type FlushItemStatus = 'synced' | 'failed' | 'retried' | 'deferred-backoff' | 'network-error';
|
|
46
|
+
interface FlushItemResult {
|
|
47
|
+
itemId: string;
|
|
48
|
+
collection: string;
|
|
49
|
+
recordId: string;
|
|
50
|
+
status: FlushItemStatus;
|
|
51
|
+
httpStatus?: number;
|
|
52
|
+
}
|
|
53
|
+
interface FlushResult {
|
|
54
|
+
attempted: number;
|
|
55
|
+
synced: number;
|
|
56
|
+
failed: number;
|
|
57
|
+
retried: number;
|
|
58
|
+
deferred: number;
|
|
59
|
+
networkErrors: number;
|
|
60
|
+
remainingPending: number;
|
|
61
|
+
skippedAlreadyFlushing: boolean;
|
|
62
|
+
items: FlushItemResult[];
|
|
63
|
+
}
|
|
45
64
|
type SyncedCallback = (item: QueueItem) => void;
|
|
46
65
|
type AuthErrorCallback = (statusCode: number, item: QueueItem) => void;
|
|
47
66
|
type StorageFullCallback = () => void;
|
|
@@ -63,7 +82,7 @@ declare class AsyncStorageSync {
|
|
|
63
82
|
deleteCollection(name: string): Promise<void>;
|
|
64
83
|
sync(name: string): Promise<void>;
|
|
65
84
|
syncById(_name: string, id: string): Promise<void>;
|
|
66
|
-
|
|
85
|
+
flushWithResult(): Promise<FlushResult>;
|
|
67
86
|
/**
|
|
68
87
|
* Re-enqueue any records marked as 'failed' so they are retried on next flush.
|
|
69
88
|
* Called automatically on init to recover from previous 4xx/500 failures.
|
|
@@ -99,4 +118,4 @@ declare function getSyncQueue(): AsyncStorageSync;
|
|
|
99
118
|
*/
|
|
100
119
|
declare function setStorageDriver(storage: AsyncStorageClient): void;
|
|
101
120
|
|
|
102
|
-
export { AsyncStorageSync, type AuthErrorCallback, type DriverName, type DuplicateStrategy, type InitConfig, type OnSyncSuccess, type QueueItem, type RecordMeta, type SaveOptions, type StorageFullCallback, type StoredRecord, type SyncStatus, type SyncedCallback, getSyncQueue, initSyncQueue, setStorageDriver };
|
|
121
|
+
export { AsyncStorageSync, type AuthErrorCallback, type DriverName, type DuplicateStrategy, type FlushItemResult, type FlushItemStatus, type FlushResult, type InitConfig, type OnSyncSuccess, type QueueItem, type RecordMeta, type SaveOptions, type StorageFullCallback, type StoredRecord, type SyncStatus, type SyncedCallback, getSyncQueue, initSyncQueue, setStorageDriver };
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,25 @@ interface SaveOptions {
|
|
|
42
42
|
onSyncSuccess?: OnSyncSuccess;
|
|
43
43
|
duplicateStrategy?: DuplicateStrategy;
|
|
44
44
|
}
|
|
45
|
+
type FlushItemStatus = 'synced' | 'failed' | 'retried' | 'deferred-backoff' | 'network-error';
|
|
46
|
+
interface FlushItemResult {
|
|
47
|
+
itemId: string;
|
|
48
|
+
collection: string;
|
|
49
|
+
recordId: string;
|
|
50
|
+
status: FlushItemStatus;
|
|
51
|
+
httpStatus?: number;
|
|
52
|
+
}
|
|
53
|
+
interface FlushResult {
|
|
54
|
+
attempted: number;
|
|
55
|
+
synced: number;
|
|
56
|
+
failed: number;
|
|
57
|
+
retried: number;
|
|
58
|
+
deferred: number;
|
|
59
|
+
networkErrors: number;
|
|
60
|
+
remainingPending: number;
|
|
61
|
+
skippedAlreadyFlushing: boolean;
|
|
62
|
+
items: FlushItemResult[];
|
|
63
|
+
}
|
|
45
64
|
type SyncedCallback = (item: QueueItem) => void;
|
|
46
65
|
type AuthErrorCallback = (statusCode: number, item: QueueItem) => void;
|
|
47
66
|
type StorageFullCallback = () => void;
|
|
@@ -63,7 +82,7 @@ declare class AsyncStorageSync {
|
|
|
63
82
|
deleteCollection(name: string): Promise<void>;
|
|
64
83
|
sync(name: string): Promise<void>;
|
|
65
84
|
syncById(_name: string, id: string): Promise<void>;
|
|
66
|
-
|
|
85
|
+
flushWithResult(): Promise<FlushResult>;
|
|
67
86
|
/**
|
|
68
87
|
* Re-enqueue any records marked as 'failed' so they are retried on next flush.
|
|
69
88
|
* Called automatically on init to recover from previous 4xx/500 failures.
|
|
@@ -99,4 +118,4 @@ declare function getSyncQueue(): AsyncStorageSync;
|
|
|
99
118
|
*/
|
|
100
119
|
declare function setStorageDriver(storage: AsyncStorageClient): void;
|
|
101
120
|
|
|
102
|
-
export { AsyncStorageSync, type AuthErrorCallback, type DriverName, type DuplicateStrategy, type InitConfig, type OnSyncSuccess, type QueueItem, type RecordMeta, type SaveOptions, type StorageFullCallback, type StoredRecord, type SyncStatus, type SyncedCallback, getSyncQueue, initSyncQueue, setStorageDriver };
|
|
121
|
+
export { AsyncStorageSync, type AuthErrorCallback, type DriverName, type DuplicateStrategy, type FlushItemResult, type FlushItemStatus, type FlushResult, type InitConfig, type OnSyncSuccess, type QueueItem, type RecordMeta, type SaveOptions, type StorageFullCallback, type StoredRecord, type SyncStatus, type SyncedCallback, getSyncQueue, initSyncQueue, setStorageDriver };
|
package/dist/index.js
CHANGED
|
@@ -221,25 +221,62 @@ var SyncEngine = class {
|
|
|
221
221
|
clearTimeout(this.debounceTimer);
|
|
222
222
|
}
|
|
223
223
|
this.debounceTimer = setTimeout(() => {
|
|
224
|
-
void this.
|
|
224
|
+
void this.flushWithResult();
|
|
225
225
|
}, DEBOUNCE_MS);
|
|
226
226
|
}
|
|
227
|
-
async
|
|
227
|
+
async flushWithResult() {
|
|
228
228
|
if (this.isFlushing) {
|
|
229
229
|
console.log("[SyncEngine] flush() skipped \u2014 already flushing");
|
|
230
|
-
return
|
|
230
|
+
return {
|
|
231
|
+
attempted: 0,
|
|
232
|
+
synced: 0,
|
|
233
|
+
failed: 0,
|
|
234
|
+
retried: 0,
|
|
235
|
+
deferred: 0,
|
|
236
|
+
networkErrors: 0,
|
|
237
|
+
remainingPending: this.queue.getPending().length,
|
|
238
|
+
skippedAlreadyFlushing: true,
|
|
239
|
+
items: []
|
|
240
|
+
};
|
|
231
241
|
}
|
|
232
242
|
this.isFlushing = true;
|
|
243
|
+
const result = {
|
|
244
|
+
attempted: 0,
|
|
245
|
+
synced: 0,
|
|
246
|
+
failed: 0,
|
|
247
|
+
retried: 0,
|
|
248
|
+
deferred: 0,
|
|
249
|
+
networkErrors: 0,
|
|
250
|
+
remainingPending: 0,
|
|
251
|
+
skippedAlreadyFlushing: false,
|
|
252
|
+
items: []
|
|
253
|
+
};
|
|
233
254
|
try {
|
|
234
255
|
const pending = this.queue.getPending();
|
|
235
256
|
console.log("[SyncEngine] flush() \u2014 pending items:", pending.length);
|
|
236
257
|
if (pending.length === 0) {
|
|
237
258
|
console.log("[SyncEngine] Nothing to sync.");
|
|
238
|
-
|
|
259
|
+
result.remainingPending = 0;
|
|
260
|
+
return result;
|
|
239
261
|
}
|
|
240
262
|
for (const item of pending) {
|
|
241
|
-
|
|
263
|
+
result.attempted += 1;
|
|
264
|
+
const itemResult = await this.syncItem(item);
|
|
265
|
+
result.items.push(itemResult);
|
|
266
|
+
if (itemResult.status === "synced") {
|
|
267
|
+
result.synced += 1;
|
|
268
|
+
} else if (itemResult.status === "failed") {
|
|
269
|
+
result.failed += 1;
|
|
270
|
+
} else if (itemResult.status === "retried") {
|
|
271
|
+
result.retried += 1;
|
|
272
|
+
} else if (itemResult.status === "deferred-backoff") {
|
|
273
|
+
result.deferred += 1;
|
|
274
|
+
} else if (itemResult.status === "network-error") {
|
|
275
|
+
result.networkErrors += 1;
|
|
276
|
+
}
|
|
242
277
|
}
|
|
278
|
+
result.remainingPending = this.queue.getPending().length;
|
|
279
|
+
return result;
|
|
243
280
|
} finally {
|
|
244
281
|
this.isFlushing = false;
|
|
245
282
|
}
|
|
@@ -262,7 +299,12 @@ var SyncEngine = class {
|
|
|
262
299
|
const elapsed = Date.now() - item.ts;
|
|
263
300
|
if (elapsed < backoffMs) {
|
|
264
301
|
console.log(`[SyncEngine] syncItem backoff \u2014 retries: ${item.retries}, wait: ${backoffMs - elapsed}ms remaining`);
|
|
265
|
-
return
|
|
302
|
+
return {
|
|
303
|
+
itemId: item.id,
|
|
304
|
+
collection: item.key,
|
|
305
|
+
recordId: item.recordId,
|
|
306
|
+
status: "deferred-backoff"
|
|
307
|
+
};
|
|
266
308
|
}
|
|
267
309
|
}
|
|
268
310
|
try {
|
|
@@ -281,13 +323,47 @@ var SyncEngine = class {
|
|
|
281
323
|
console.log(`[SyncEngine] Response: ${response.status} ${response.statusText}`);
|
|
282
324
|
if (response.ok) {
|
|
283
325
|
await this.handleSuccess(item);
|
|
326
|
+
return {
|
|
327
|
+
itemId: item.id,
|
|
328
|
+
collection: item.key,
|
|
329
|
+
recordId: item.recordId,
|
|
330
|
+
status: "synced",
|
|
331
|
+
httpStatus: response.status
|
|
332
|
+
};
|
|
284
333
|
} else if (response.status >= 400 && response.status < 500) {
|
|
285
334
|
await this.handleClientError(item, response.status);
|
|
335
|
+
return {
|
|
336
|
+
itemId: item.id,
|
|
337
|
+
collection: item.key,
|
|
338
|
+
recordId: item.recordId,
|
|
339
|
+
status: "failed",
|
|
340
|
+
httpStatus: response.status
|
|
341
|
+
};
|
|
286
342
|
} else if (response.status >= 500) {
|
|
287
343
|
await this.handleServerError(item);
|
|
344
|
+
return {
|
|
345
|
+
itemId: item.id,
|
|
346
|
+
collection: item.key,
|
|
347
|
+
recordId: item.recordId,
|
|
348
|
+
status: "retried",
|
|
349
|
+
httpStatus: response.status
|
|
350
|
+
};
|
|
288
351
|
}
|
|
352
|
+
return {
|
|
353
|
+
itemId: item.id,
|
|
354
|
+
collection: item.key,
|
|
355
|
+
recordId: item.recordId,
|
|
356
|
+
status: "failed",
|
|
357
|
+
httpStatus: response.status
|
|
358
|
+
};
|
|
289
359
|
} catch (e) {
|
|
290
360
|
console.warn("[SyncEngine] \u{1F50C} Network error (offline?) \u2014 will retry on next flush:", e);
|
|
361
|
+
return {
|
|
362
|
+
itemId: item.id,
|
|
363
|
+
collection: item.key,
|
|
364
|
+
recordId: item.recordId,
|
|
365
|
+
status: "network-error"
|
|
366
|
+
};
|
|
291
367
|
}
|
|
292
368
|
}
|
|
293
369
|
async handleSuccess(item) {
|
|
@@ -486,8 +562,8 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
486
562
|
async syncById(_name, id) {
|
|
487
563
|
await this.engine.flushRecord(id);
|
|
488
564
|
}
|
|
489
|
-
async
|
|
490
|
-
|
|
565
|
+
async flushWithResult() {
|
|
566
|
+
return this.engine.flushWithResult();
|
|
491
567
|
}
|
|
492
568
|
/**
|
|
493
569
|
* Re-enqueue any records marked as 'failed' so they are retried on next flush.
|
package/dist/index.mjs
CHANGED
|
@@ -199,25 +199,62 @@ var SyncEngine = class {
|
|
|
199
199
|
clearTimeout(this.debounceTimer);
|
|
200
200
|
}
|
|
201
201
|
this.debounceTimer = setTimeout(() => {
|
|
202
|
-
void this.
|
|
202
|
+
void this.flushWithResult();
|
|
203
203
|
}, DEBOUNCE_MS);
|
|
204
204
|
}
|
|
205
|
-
async
|
|
205
|
+
async flushWithResult() {
|
|
206
206
|
if (this.isFlushing) {
|
|
207
207
|
console.log("[SyncEngine] flush() skipped \u2014 already flushing");
|
|
208
|
-
return
|
|
208
|
+
return {
|
|
209
|
+
attempted: 0,
|
|
210
|
+
synced: 0,
|
|
211
|
+
failed: 0,
|
|
212
|
+
retried: 0,
|
|
213
|
+
deferred: 0,
|
|
214
|
+
networkErrors: 0,
|
|
215
|
+
remainingPending: this.queue.getPending().length,
|
|
216
|
+
skippedAlreadyFlushing: true,
|
|
217
|
+
items: []
|
|
218
|
+
};
|
|
209
219
|
}
|
|
210
220
|
this.isFlushing = true;
|
|
221
|
+
const result = {
|
|
222
|
+
attempted: 0,
|
|
223
|
+
synced: 0,
|
|
224
|
+
failed: 0,
|
|
225
|
+
retried: 0,
|
|
226
|
+
deferred: 0,
|
|
227
|
+
networkErrors: 0,
|
|
228
|
+
remainingPending: 0,
|
|
229
|
+
skippedAlreadyFlushing: false,
|
|
230
|
+
items: []
|
|
231
|
+
};
|
|
211
232
|
try {
|
|
212
233
|
const pending = this.queue.getPending();
|
|
213
234
|
console.log("[SyncEngine] flush() \u2014 pending items:", pending.length);
|
|
214
235
|
if (pending.length === 0) {
|
|
215
236
|
console.log("[SyncEngine] Nothing to sync.");
|
|
216
|
-
|
|
237
|
+
result.remainingPending = 0;
|
|
238
|
+
return result;
|
|
217
239
|
}
|
|
218
240
|
for (const item of pending) {
|
|
219
|
-
|
|
241
|
+
result.attempted += 1;
|
|
242
|
+
const itemResult = await this.syncItem(item);
|
|
243
|
+
result.items.push(itemResult);
|
|
244
|
+
if (itemResult.status === "synced") {
|
|
245
|
+
result.synced += 1;
|
|
246
|
+
} else if (itemResult.status === "failed") {
|
|
247
|
+
result.failed += 1;
|
|
248
|
+
} else if (itemResult.status === "retried") {
|
|
249
|
+
result.retried += 1;
|
|
250
|
+
} else if (itemResult.status === "deferred-backoff") {
|
|
251
|
+
result.deferred += 1;
|
|
252
|
+
} else if (itemResult.status === "network-error") {
|
|
253
|
+
result.networkErrors += 1;
|
|
254
|
+
}
|
|
220
255
|
}
|
|
256
|
+
result.remainingPending = this.queue.getPending().length;
|
|
257
|
+
return result;
|
|
221
258
|
} finally {
|
|
222
259
|
this.isFlushing = false;
|
|
223
260
|
}
|
|
@@ -240,7 +277,12 @@ var SyncEngine = class {
|
|
|
240
277
|
const elapsed = Date.now() - item.ts;
|
|
241
278
|
if (elapsed < backoffMs) {
|
|
242
279
|
console.log(`[SyncEngine] syncItem backoff \u2014 retries: ${item.retries}, wait: ${backoffMs - elapsed}ms remaining`);
|
|
243
|
-
return
|
|
280
|
+
return {
|
|
281
|
+
itemId: item.id,
|
|
282
|
+
collection: item.key,
|
|
283
|
+
recordId: item.recordId,
|
|
284
|
+
status: "deferred-backoff"
|
|
285
|
+
};
|
|
244
286
|
}
|
|
245
287
|
}
|
|
246
288
|
try {
|
|
@@ -259,13 +301,47 @@ var SyncEngine = class {
|
|
|
259
301
|
console.log(`[SyncEngine] Response: ${response.status} ${response.statusText}`);
|
|
260
302
|
if (response.ok) {
|
|
261
303
|
await this.handleSuccess(item);
|
|
304
|
+
return {
|
|
305
|
+
itemId: item.id,
|
|
306
|
+
collection: item.key,
|
|
307
|
+
recordId: item.recordId,
|
|
308
|
+
status: "synced",
|
|
309
|
+
httpStatus: response.status
|
|
310
|
+
};
|
|
262
311
|
} else if (response.status >= 400 && response.status < 500) {
|
|
263
312
|
await this.handleClientError(item, response.status);
|
|
313
|
+
return {
|
|
314
|
+
itemId: item.id,
|
|
315
|
+
collection: item.key,
|
|
316
|
+
recordId: item.recordId,
|
|
317
|
+
status: "failed",
|
|
318
|
+
httpStatus: response.status
|
|
319
|
+
};
|
|
264
320
|
} else if (response.status >= 500) {
|
|
265
321
|
await this.handleServerError(item);
|
|
322
|
+
return {
|
|
323
|
+
itemId: item.id,
|
|
324
|
+
collection: item.key,
|
|
325
|
+
recordId: item.recordId,
|
|
326
|
+
status: "retried",
|
|
327
|
+
httpStatus: response.status
|
|
328
|
+
};
|
|
266
329
|
}
|
|
330
|
+
return {
|
|
331
|
+
itemId: item.id,
|
|
332
|
+
collection: item.key,
|
|
333
|
+
recordId: item.recordId,
|
|
334
|
+
status: "failed",
|
|
335
|
+
httpStatus: response.status
|
|
336
|
+
};
|
|
267
337
|
} catch (e) {
|
|
268
338
|
console.warn("[SyncEngine] \u{1F50C} Network error (offline?) \u2014 will retry on next flush:", e);
|
|
339
|
+
return {
|
|
340
|
+
itemId: item.id,
|
|
341
|
+
collection: item.key,
|
|
342
|
+
recordId: item.recordId,
|
|
343
|
+
status: "network-error"
|
|
344
|
+
};
|
|
269
345
|
}
|
|
270
346
|
}
|
|
271
347
|
async handleSuccess(item) {
|
|
@@ -464,8 +540,8 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
464
540
|
async syncById(_name, id) {
|
|
465
541
|
await this.engine.flushRecord(id);
|
|
466
542
|
}
|
|
467
|
-
async
|
|
468
|
-
|
|
543
|
+
async flushWithResult() {
|
|
544
|
+
return this.engine.flushWithResult();
|
|
469
545
|
}
|
|
470
546
|
/**
|
|
471
547
|
* Re-enqueue any records marked as 'failed' so they are retried on next flush.
|
package/package.json
CHANGED