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 CHANGED
@@ -48,8 +48,9 @@ await store.save('forms', {
48
48
  timestamp: new Date().toISOString()
49
49
  });
50
50
 
51
- // Sync all pending records to server
52
- await store.flush();
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.flush()` → sync all pending items
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.flush()` | Sync all pending queue items |
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().flush();
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** — `flush()` POSTs all pending to your server
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
- flush(): Promise<void>;
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
- flush(): Promise<void>;
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.flush();
224
+ void this.flushWithResult();
225
225
  }, DEBOUNCE_MS);
226
226
  }
227
- async flush() {
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
- return;
259
+ result.remainingPending = 0;
260
+ return result;
239
261
  }
240
262
  for (const item of pending) {
241
- await this.syncItem(item);
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 flush() {
490
- await this.engine.flush();
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.flush();
202
+ void this.flushWithResult();
203
203
  }, DEBOUNCE_MS);
204
204
  }
205
- async flush() {
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
- return;
237
+ result.remainingPending = 0;
238
+ return result;
217
239
  }
218
240
  for (const item of pending) {
219
- await this.syncItem(item);
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 flush() {
468
- await this.engine.flush();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "async-storage-sync",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Offline-first data layer for React Native with local-first storage and automatic sync",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",