async-storage-sync 1.0.6 → 1.0.8
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 +130 -149
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -44
- package/dist/index.mjs +85 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# async-storage-sync
|
|
2
2
|
|
|
3
|
-
**Offline-first data layer for React Native** —
|
|
3
|
+
**Offline-first data layer for React Native** — save locally, sync to your server when connected.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,209 +8,190 @@
|
|
|
8
8
|
npm install async-storage-sync @react-native-async-storage/async-storage @react-native-community/netinfo
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Initialize once in your app (no separate files required):
|
|
13
|
+
**1. Initialize once in `App.tsx`:**
|
|
16
14
|
|
|
17
15
|
```ts
|
|
18
|
-
// App.tsx
|
|
19
|
-
import React, { useEffect } from 'react';
|
|
20
16
|
import { initSyncQueue } from 'async-storage-sync';
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return <YourApp />;
|
|
34
|
-
}
|
|
18
|
+
initSyncQueue({
|
|
19
|
+
driver: 'asyncstorage',
|
|
20
|
+
serverUrl: 'https://api.example.com',
|
|
21
|
+
credentials: {
|
|
22
|
+
Authorization: 'Token abc123',
|
|
23
|
+
'x-api-key': 'my-custom-key',
|
|
24
|
+
},
|
|
25
|
+
endpoint: '/submit',
|
|
26
|
+
autoSync: true,
|
|
27
|
+
});
|
|
35
28
|
```
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
**2. Save data anywhere in your app:**
|
|
38
31
|
|
|
39
32
|
```ts
|
|
40
33
|
import { getSyncQueue } from 'async-storage-sync';
|
|
41
34
|
|
|
42
35
|
const store = getSyncQueue();
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
timestamp: new Date().toISOString()
|
|
37
|
+
await store.save('submissions', {
|
|
38
|
+
formId: '123',
|
|
39
|
+
name: 'John',
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
49
41
|
});
|
|
50
|
-
|
|
51
|
-
// Sync and get summary result
|
|
52
|
-
const result = await store.flushWithResult();
|
|
53
|
-
console.log(`Synced: ${result.synced}, Failed: ${result.failed}, Remaining: ${result.remainingPending}`);
|
|
54
|
-
|
|
55
|
-
// List pending records
|
|
56
|
-
const pending = await store.getAll('forms');
|
|
57
|
-
console.log(`${pending.length} forms waiting to sync`);
|
|
58
42
|
```
|
|
59
43
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `autoSync: true` (default): when the app starts, the package checks connectivity and attempts sync if online.
|
|
63
|
-
- `autoSync: true` also listens for reconnect events and retries pending items automatically.
|
|
64
|
-
- `autoSync: false`: no automatic syncing; call sync methods manually when you choose.
|
|
65
|
-
- Manual methods:
|
|
66
|
-
- `store.flushWithResult()` → sync all pending and return summary counts
|
|
67
|
-
- `store.syncWithResult(collection)` → sync one collection and return summary counts
|
|
68
|
-
- `store.syncById(collection, id)` → sync one record
|
|
69
|
-
- Sync destination is controlled by your config: `serverUrl + endpoint`.
|
|
44
|
+
**3. Sync and check the result:**
|
|
70
45
|
|
|
71
|
-
## API Reference
|
|
72
|
-
|
|
73
|
-
### Setup
|
|
74
|
-
|
|
75
|
-
| Function | Purpose |
|
|
76
|
-
|--------|---------|
|
|
77
|
-
| `initSyncQueue(config)` | Initialize singleton once at app startup (safe to call repeatedly) |
|
|
78
|
-
| `getSyncQueue()` | Get initialized singleton instance |
|
|
79
|
-
| `setStorageDriver(storage)` | Inject storage client explicitly (useful for symlink/local package setups) |
|
|
80
|
-
|
|
81
|
-
### Store Methods (`const store = getSyncQueue()`)
|
|
82
|
-
|
|
83
|
-
| Method | Purpose |
|
|
84
|
-
|--------|---------|
|
|
85
|
-
| `store.save(collection, data, options?)` | Save one record locally and enqueue for sync |
|
|
86
|
-
| `store.getAll(collection)` | Get all records from one collection |
|
|
87
|
-
| `store.getById(collection, id)` | Get one record by internal `_id` |
|
|
88
|
-
| `store.deleteById(collection, id)` | Delete one record by internal `_id` |
|
|
89
|
-
| `store.deleteCollection(collection)` | Delete all records in one collection |
|
|
90
|
-
| `store.flushWithResult()` | Sync all pending and return detailed summary (`attempted`, `synced`, `failed`, `retried`, `remainingPending`, `items`) |
|
|
91
|
-
| `store.syncWithResult(collection)` | Sync collection and return detailed summary (same format as `flushWithResult()`) |
|
|
92
|
-
| `store.syncById(collection, id)` | Sync one specific record by internal `_id` |
|
|
93
|
-
| `store.requeueFailed()` | Move `failed` records back to pending queue for retry |
|
|
94
|
-
| `store.onSynced(callback)` | Event callback for successful sync of each item |
|
|
95
|
-
| `store.onAuthError(callback)` | Event callback when sync returns `401` or `403` |
|
|
96
|
-
| `store.onStorageFull(callback)` | Event callback when local storage is full on save |
|
|
97
|
-
| `store.getQueue()` | Inspect in-memory queue items (debug/metrics) |
|
|
98
|
-
| `store.destroy()` | Stop engine, clear queue/storage, and reset singleton |
|
|
99
|
-
|
|
100
|
-
## Quick Examples
|
|
101
|
-
|
|
102
|
-
**Auto-sync when internet reconnects:**
|
|
103
46
|
```ts
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
NetInfo.addEventListener(state => {
|
|
108
|
-
if (state.isConnected) {
|
|
109
|
-
void getSyncQueue().flushWithResult();
|
|
110
|
-
}
|
|
111
|
-
});
|
|
47
|
+
const result = await store.flushWithResult('submissions');
|
|
48
|
+
console.log(`Synced: ${result.synced}, Failed: ${result.failed}, Remaining: ${result.remainingPending}`);
|
|
112
49
|
```
|
|
113
50
|
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
const store = getSyncQueue();
|
|
51
|
+
---
|
|
117
52
|
|
|
118
|
-
|
|
119
|
-
if (statusCode === 401 || statusCode === 403) {
|
|
120
|
-
console.log('Session expired, re-login needed');
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
```
|
|
53
|
+
## Configuration
|
|
124
54
|
|
|
125
|
-
**Transform data before sending to server:**
|
|
126
55
|
```ts
|
|
127
56
|
initSyncQueue({
|
|
57
|
+
// required
|
|
128
58
|
driver: 'asyncstorage',
|
|
129
59
|
serverUrl: 'https://api.example.com',
|
|
130
|
-
credentials: {
|
|
60
|
+
credentials: {
|
|
61
|
+
Authorization: 'Token abc123', // sent as-is in request headers
|
|
62
|
+
'x-api-key': 'my-custom-key', // any key/value pair works
|
|
63
|
+
},
|
|
131
64
|
endpoint: '/submit',
|
|
132
|
-
|
|
65
|
+
|
|
66
|
+
// optional
|
|
67
|
+
autoSync: true, // auto-flush on app open + reconnect (default: true)
|
|
68
|
+
autoSyncCollections: ['submissions'], // limit auto-sync to these collections; empty = all
|
|
69
|
+
onSyncSuccess: 'delete', // what to do after a successful sync: 'keep' | 'delete' | 'ttl'
|
|
70
|
+
ttl: 7 * 24 * 60 * 60 * 1000, // used only when onSyncSuccess is 'ttl'
|
|
71
|
+
duplicateStrategy: 'append', // 'append' (default) | 'overwrite'
|
|
72
|
+
payloadTransformer: (record) => { // strip internal fields before sending to server
|
|
133
73
|
const { _id, _ts, _synced, _retries, ...payload } = record;
|
|
134
74
|
return payload;
|
|
135
75
|
},
|
|
136
76
|
});
|
|
137
77
|
```
|
|
138
78
|
|
|
139
|
-
|
|
79
|
+
> `credentials` values are merged directly into request headers. Any key/value pair is supported.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### Setup
|
|
86
|
+
|
|
140
87
|
```ts
|
|
141
|
-
initSyncQueue(
|
|
142
|
-
|
|
143
|
-
serverUrl: 'https://api.example.com',
|
|
144
|
-
credentials: {
|
|
145
|
-
Authorization: 'Token abc123',
|
|
146
|
-
'x-api-key': 'my-custom-key',
|
|
147
|
-
},
|
|
148
|
-
endpoint: '/submit',
|
|
149
|
-
});
|
|
88
|
+
initSyncQueue(config) // call once at app startup — safe to call again, won't re-init
|
|
89
|
+
getSyncQueue() // get the instance anywhere in your app
|
|
150
90
|
```
|
|
151
91
|
|
|
152
|
-
|
|
92
|
+
### Write
|
|
93
|
+
|
|
153
94
|
```ts
|
|
154
|
-
//
|
|
155
|
-
|
|
95
|
+
store.save(collection, data, options?) // save locally and enqueue for sync
|
|
96
|
+
```
|
|
156
97
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
98
|
+
`options` (all optional, override global config for this call only):
|
|
99
|
+
|
|
100
|
+
| Option | Values | Description |
|
|
101
|
+
|--------|--------|-------------|
|
|
102
|
+
| `type` | `string` | Labels the record — used by `overwrite` strategy to find and replace |
|
|
103
|
+
| `onSyncSuccess` | `'keep'` \| `'delete'` \| `'ttl'` | What happens to the local copy after sync |
|
|
104
|
+
| `duplicateStrategy` | `'append'` \| `'overwrite'` | Whether to add a new record or replace an existing one of the same `type` |
|
|
105
|
+
|
|
106
|
+
### Read
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
store.getAll(collection) // all records in a collection
|
|
110
|
+
store.getById(collection, id) // one record by its _id
|
|
162
111
|
```
|
|
163
112
|
|
|
164
|
-
|
|
113
|
+
### Delete
|
|
114
|
+
|
|
165
115
|
```ts
|
|
166
|
-
|
|
167
|
-
|
|
116
|
+
store.deleteById(collection, id) // remove one record
|
|
117
|
+
store.deleteCollection(collection) // wipe entire collection — call on logout
|
|
168
118
|
```
|
|
169
119
|
|
|
170
|
-
|
|
120
|
+
### Sync
|
|
171
121
|
|
|
172
122
|
```ts
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
endpoint?: '/submit', // route to POST data
|
|
178
|
-
autoSync?: false, // auto-sync on reconnect
|
|
179
|
-
onSyncSuccess?: 'keep', // after sync: keep|delete|ttl
|
|
180
|
-
ttl?: 7 * 24 * 60 * 60 * 1000, // if ttl mode, keep duration
|
|
181
|
-
duplicateStrategy?: 'append', // append or overwrite
|
|
182
|
-
payloadTransformer?: (r) => r, // optional: shape before send
|
|
183
|
-
});
|
|
123
|
+
store.flushWithResult(collection) // sync one collection, get a result summary
|
|
124
|
+
store.syncManyWithResult(collections[]) // sync multiple collections, merged result summary
|
|
125
|
+
store.syncById(collection, id) // sync one specific record
|
|
126
|
+
store.requeueFailed() // move 'failed' records back to pending for retry
|
|
184
127
|
```
|
|
185
128
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
129
|
+
**Result summary shape:**
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
{
|
|
133
|
+
attempted, synced, failed, retried,
|
|
134
|
+
deferred, networkErrors, remainingPending,
|
|
135
|
+
skippedAlreadyFlushing, items
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Events
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
store.onSynced(callback) // fires after each record syncs successfully
|
|
143
|
+
store.onAuthError(callback) // fires on 401/403 — use to trigger re-login
|
|
144
|
+
store.onStorageFull(callback) // fires when device storage is full
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Debug
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
store.getQueue() // inspect the in-memory queue
|
|
151
|
+
store.destroy() // stop engine, clear everything, reset singleton
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
189
155
|
|
|
190
156
|
## How It Works
|
|
191
157
|
|
|
192
|
-
1.
|
|
193
|
-
2.
|
|
194
|
-
3.
|
|
195
|
-
4.
|
|
196
|
-
5.
|
|
197
|
-
6.
|
|
158
|
+
1. `save()` writes the record to AsyncStorage immediately — no network needed.
|
|
159
|
+
2. The record is added to a queue with `_synced: 'pending'`.
|
|
160
|
+
3. On `flushWithResult()` (or automatically on reconnect if `autoSync: true`), queued records are POSTed to your server.
|
|
161
|
+
4. On `200 OK` — the record is marked synced, then kept/deleted based on `onSyncSuccess`.
|
|
162
|
+
5. On `5xx` — retried up to 5 times with exponential backoff.
|
|
163
|
+
6. On `4xx` — marked `failed`, never retried. Fires `onAuthError` on 401/403.
|
|
164
|
+
7. Everything persists across app restarts — the queue survives crashes.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Stored Record Shape
|
|
198
169
|
|
|
199
|
-
|
|
170
|
+
Every saved record gets these fields added automatically:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
{
|
|
174
|
+
_id: string // uuid v4
|
|
175
|
+
_ts: number // Date.now() at save time
|
|
176
|
+
_synced: 'pending' | 'synced' | 'failed'
|
|
177
|
+
_type: string // from save() options.type, or ''
|
|
178
|
+
_retries: number // sync attempt count
|
|
179
|
+
|
|
180
|
+
...yourData // everything you passed to save()
|
|
181
|
+
}
|
|
182
|
+
```
|
|
200
183
|
|
|
201
|
-
|
|
202
|
-
- `asyncstorage::__queue__` — Sync queue
|
|
184
|
+
---
|
|
203
185
|
|
|
204
186
|
## Limits
|
|
205
187
|
|
|
206
|
-
|
|
207
|
-
|
|
188
|
+
- Max 5 sync retries per record
|
|
189
|
+
- Not a full database — designed for queuing records, not complex queries
|
|
190
|
+
- Config is locked after `initSyncQueue()` is called
|
|
208
191
|
|
|
209
192
|
## Production Checklist
|
|
210
193
|
|
|
211
|
-
- [ ] Use `payloadTransformer` to
|
|
212
|
-
- [ ] Handle `onAuthError`
|
|
213
|
-
- [ ]
|
|
214
|
-
- [ ]
|
|
215
|
-
- [ ] Call `deleteCollection()` on logout
|
|
216
|
-
- [ ] Monitor queue with `getQueue()` for ops metrics
|
|
194
|
+
- [ ] Use `payloadTransformer` to strip `_` fields before they reach your server
|
|
195
|
+
- [ ] Handle `onAuthError` to catch expired tokens
|
|
196
|
+
- [ ] Call `deleteCollection()` on logout to clear user data
|
|
197
|
+
- [ ] Set `autoSyncCollections` to limit which collections sync automatically
|
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,7 @@ interface InitConfig {
|
|
|
13
13
|
*/
|
|
14
14
|
payloadTransformer?: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
15
15
|
autoSync?: boolean;
|
|
16
|
+
autoSyncCollections?: string[];
|
|
16
17
|
endpoint?: string;
|
|
17
18
|
onSyncSuccess?: OnSyncSuccess;
|
|
18
19
|
ttl?: number;
|
|
@@ -81,6 +82,7 @@ declare class AsyncStorageSync {
|
|
|
81
82
|
deleteById(name: string, id: string): Promise<void>;
|
|
82
83
|
deleteCollection(name: string): Promise<void>;
|
|
83
84
|
syncWithResult(name: string): Promise<FlushResult>;
|
|
85
|
+
syncManyWithResult(names: string[]): Promise<FlushResult>;
|
|
84
86
|
syncById(_name: string, id: string): Promise<void>;
|
|
85
87
|
flushWithResult(): Promise<FlushResult>;
|
|
86
88
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface InitConfig {
|
|
|
13
13
|
*/
|
|
14
14
|
payloadTransformer?: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
15
15
|
autoSync?: boolean;
|
|
16
|
+
autoSyncCollections?: string[];
|
|
16
17
|
endpoint?: string;
|
|
17
18
|
onSyncSuccess?: OnSyncSuccess;
|
|
18
19
|
ttl?: number;
|
|
@@ -81,6 +82,7 @@ declare class AsyncStorageSync {
|
|
|
81
82
|
deleteById(name: string, id: string): Promise<void>;
|
|
82
83
|
deleteCollection(name: string): Promise<void>;
|
|
83
84
|
syncWithResult(name: string): Promise<FlushResult>;
|
|
85
|
+
syncManyWithResult(names: string[]): Promise<FlushResult>;
|
|
84
86
|
syncById(_name: string, id: string): Promise<void>;
|
|
85
87
|
flushWithResult(): Promise<FlushResult>;
|
|
86
88
|
/**
|
package/dist/index.js
CHANGED
|
@@ -221,9 +221,49 @@ var SyncEngine = class {
|
|
|
221
221
|
clearTimeout(this.debounceTimer);
|
|
222
222
|
}
|
|
223
223
|
this.debounceTimer = setTimeout(() => {
|
|
224
|
-
void this.
|
|
224
|
+
void this.flushAutoSyncTargetWithResult();
|
|
225
225
|
}, DEBOUNCE_MS);
|
|
226
226
|
}
|
|
227
|
+
async flushAutoSyncTargetWithResult() {
|
|
228
|
+
const configuredCollections = this.config.autoSyncCollections?.map((name) => name.trim()).filter((name) => name.length > 0);
|
|
229
|
+
if (!configuredCollections || configuredCollections.length === 0) {
|
|
230
|
+
return this.flushWithResult();
|
|
231
|
+
}
|
|
232
|
+
return this.flushCollectionsWithResult(configuredCollections);
|
|
233
|
+
}
|
|
234
|
+
createEmptyResult() {
|
|
235
|
+
return {
|
|
236
|
+
attempted: 0,
|
|
237
|
+
synced: 0,
|
|
238
|
+
failed: 0,
|
|
239
|
+
retried: 0,
|
|
240
|
+
deferred: 0,
|
|
241
|
+
networkErrors: 0,
|
|
242
|
+
remainingPending: 0,
|
|
243
|
+
skippedAlreadyFlushing: false,
|
|
244
|
+
items: []
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
mergeResult(target, source) {
|
|
248
|
+
target.attempted += source.attempted;
|
|
249
|
+
target.synced += source.synced;
|
|
250
|
+
target.failed += source.failed;
|
|
251
|
+
target.retried += source.retried;
|
|
252
|
+
target.deferred += source.deferred;
|
|
253
|
+
target.networkErrors += source.networkErrors;
|
|
254
|
+
target.items.push(...source.items);
|
|
255
|
+
target.remainingPending = source.remainingPending;
|
|
256
|
+
target.skippedAlreadyFlushing = target.skippedAlreadyFlushing || source.skippedAlreadyFlushing;
|
|
257
|
+
}
|
|
258
|
+
async flushCollectionsWithResult(collectionNames) {
|
|
259
|
+
const result = this.createEmptyResult();
|
|
260
|
+
for (const collectionName of collectionNames) {
|
|
261
|
+
const collectionResult = await this.flushCollectionWithResult(collectionName);
|
|
262
|
+
this.mergeResult(result, collectionResult);
|
|
263
|
+
}
|
|
264
|
+
result.remainingPending = this.queue.getPending().length;
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
227
267
|
async flushWithResult() {
|
|
228
268
|
if (this.isFlushing) {
|
|
229
269
|
console.log("[SyncEngine] flush() skipped \u2014 already flushing");
|
|
@@ -240,17 +280,7 @@ var SyncEngine = class {
|
|
|
240
280
|
};
|
|
241
281
|
}
|
|
242
282
|
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
|
-
};
|
|
283
|
+
const result = this.createEmptyResult();
|
|
254
284
|
try {
|
|
255
285
|
const pending = this.queue.getPending();
|
|
256
286
|
console.log("[SyncEngine] flush() \u2014 pending items:", pending.length);
|
|
@@ -282,40 +312,48 @@ var SyncEngine = class {
|
|
|
282
312
|
}
|
|
283
313
|
}
|
|
284
314
|
async flushCollectionWithResult(collectionName) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (pending.length === 0) {
|
|
298
|
-
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
299
|
-
return result;
|
|
315
|
+
if (this.isFlushing) {
|
|
316
|
+
return {
|
|
317
|
+
attempted: 0,
|
|
318
|
+
synced: 0,
|
|
319
|
+
failed: 0,
|
|
320
|
+
retried: 0,
|
|
321
|
+
deferred: 0,
|
|
322
|
+
networkErrors: 0,
|
|
323
|
+
remainingPending: this.queue.getPendingForCollection(collectionName).length,
|
|
324
|
+
skippedAlreadyFlushing: true,
|
|
325
|
+
items: []
|
|
326
|
+
};
|
|
300
327
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
result.
|
|
307
|
-
|
|
308
|
-
result.failed += 1;
|
|
309
|
-
} else if (itemResult.status === "retried") {
|
|
310
|
-
result.retried += 1;
|
|
311
|
-
} else if (itemResult.status === "deferred-backoff") {
|
|
312
|
-
result.deferred += 1;
|
|
313
|
-
} else if (itemResult.status === "network-error") {
|
|
314
|
-
result.networkErrors += 1;
|
|
328
|
+
this.isFlushing = true;
|
|
329
|
+
const result = this.createEmptyResult();
|
|
330
|
+
try {
|
|
331
|
+
const pending = this.queue.getPendingForCollection(collectionName);
|
|
332
|
+
if (pending.length === 0) {
|
|
333
|
+
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
334
|
+
return result;
|
|
315
335
|
}
|
|
336
|
+
for (const item of pending) {
|
|
337
|
+
result.attempted += 1;
|
|
338
|
+
const itemResult = await this.syncItem(item);
|
|
339
|
+
result.items.push(itemResult);
|
|
340
|
+
if (itemResult.status === "synced") {
|
|
341
|
+
result.synced += 1;
|
|
342
|
+
} else if (itemResult.status === "failed") {
|
|
343
|
+
result.failed += 1;
|
|
344
|
+
} else if (itemResult.status === "retried") {
|
|
345
|
+
result.retried += 1;
|
|
346
|
+
} else if (itemResult.status === "deferred-backoff") {
|
|
347
|
+
result.deferred += 1;
|
|
348
|
+
} else if (itemResult.status === "network-error") {
|
|
349
|
+
result.networkErrors += 1;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
353
|
+
return result;
|
|
354
|
+
} finally {
|
|
355
|
+
this.isFlushing = false;
|
|
316
356
|
}
|
|
317
|
-
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
318
|
-
return result;
|
|
319
357
|
}
|
|
320
358
|
async flushRecord(recordId) {
|
|
321
359
|
const item = this.queue.getPendingForRecord(recordId);
|
|
@@ -501,7 +539,7 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
501
539
|
console.log("[AsyncStorageSync] autoSync enabled \u2014 starting sync engine");
|
|
502
540
|
instance.engine.start();
|
|
503
541
|
} else {
|
|
504
|
-
console.log("[AsyncStorageSync] autoSync disabled \u2014 call
|
|
542
|
+
console.log("[AsyncStorageSync] autoSync disabled \u2014 call flushWithResult() or syncWithResult(collection) manually");
|
|
505
543
|
}
|
|
506
544
|
_AsyncStorageSync.instance = instance;
|
|
507
545
|
return instance;
|
|
@@ -589,6 +627,9 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
589
627
|
async syncWithResult(name) {
|
|
590
628
|
return this.engine.flushCollectionWithResult(name);
|
|
591
629
|
}
|
|
630
|
+
async syncManyWithResult(names) {
|
|
631
|
+
return this.engine.flushCollectionsWithResult(names);
|
|
632
|
+
}
|
|
592
633
|
async syncById(_name, id) {
|
|
593
634
|
await this.engine.flushRecord(id);
|
|
594
635
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -199,9 +199,49 @@ var SyncEngine = class {
|
|
|
199
199
|
clearTimeout(this.debounceTimer);
|
|
200
200
|
}
|
|
201
201
|
this.debounceTimer = setTimeout(() => {
|
|
202
|
-
void this.
|
|
202
|
+
void this.flushAutoSyncTargetWithResult();
|
|
203
203
|
}, DEBOUNCE_MS);
|
|
204
204
|
}
|
|
205
|
+
async flushAutoSyncTargetWithResult() {
|
|
206
|
+
const configuredCollections = this.config.autoSyncCollections?.map((name) => name.trim()).filter((name) => name.length > 0);
|
|
207
|
+
if (!configuredCollections || configuredCollections.length === 0) {
|
|
208
|
+
return this.flushWithResult();
|
|
209
|
+
}
|
|
210
|
+
return this.flushCollectionsWithResult(configuredCollections);
|
|
211
|
+
}
|
|
212
|
+
createEmptyResult() {
|
|
213
|
+
return {
|
|
214
|
+
attempted: 0,
|
|
215
|
+
synced: 0,
|
|
216
|
+
failed: 0,
|
|
217
|
+
retried: 0,
|
|
218
|
+
deferred: 0,
|
|
219
|
+
networkErrors: 0,
|
|
220
|
+
remainingPending: 0,
|
|
221
|
+
skippedAlreadyFlushing: false,
|
|
222
|
+
items: []
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
mergeResult(target, source) {
|
|
226
|
+
target.attempted += source.attempted;
|
|
227
|
+
target.synced += source.synced;
|
|
228
|
+
target.failed += source.failed;
|
|
229
|
+
target.retried += source.retried;
|
|
230
|
+
target.deferred += source.deferred;
|
|
231
|
+
target.networkErrors += source.networkErrors;
|
|
232
|
+
target.items.push(...source.items);
|
|
233
|
+
target.remainingPending = source.remainingPending;
|
|
234
|
+
target.skippedAlreadyFlushing = target.skippedAlreadyFlushing || source.skippedAlreadyFlushing;
|
|
235
|
+
}
|
|
236
|
+
async flushCollectionsWithResult(collectionNames) {
|
|
237
|
+
const result = this.createEmptyResult();
|
|
238
|
+
for (const collectionName of collectionNames) {
|
|
239
|
+
const collectionResult = await this.flushCollectionWithResult(collectionName);
|
|
240
|
+
this.mergeResult(result, collectionResult);
|
|
241
|
+
}
|
|
242
|
+
result.remainingPending = this.queue.getPending().length;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
205
245
|
async flushWithResult() {
|
|
206
246
|
if (this.isFlushing) {
|
|
207
247
|
console.log("[SyncEngine] flush() skipped \u2014 already flushing");
|
|
@@ -218,17 +258,7 @@ var SyncEngine = class {
|
|
|
218
258
|
};
|
|
219
259
|
}
|
|
220
260
|
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
|
-
};
|
|
261
|
+
const result = this.createEmptyResult();
|
|
232
262
|
try {
|
|
233
263
|
const pending = this.queue.getPending();
|
|
234
264
|
console.log("[SyncEngine] flush() \u2014 pending items:", pending.length);
|
|
@@ -260,40 +290,48 @@ var SyncEngine = class {
|
|
|
260
290
|
}
|
|
261
291
|
}
|
|
262
292
|
async flushCollectionWithResult(collectionName) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (pending.length === 0) {
|
|
276
|
-
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
277
|
-
return result;
|
|
293
|
+
if (this.isFlushing) {
|
|
294
|
+
return {
|
|
295
|
+
attempted: 0,
|
|
296
|
+
synced: 0,
|
|
297
|
+
failed: 0,
|
|
298
|
+
retried: 0,
|
|
299
|
+
deferred: 0,
|
|
300
|
+
networkErrors: 0,
|
|
301
|
+
remainingPending: this.queue.getPendingForCollection(collectionName).length,
|
|
302
|
+
skippedAlreadyFlushing: true,
|
|
303
|
+
items: []
|
|
304
|
+
};
|
|
278
305
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
result.
|
|
285
|
-
|
|
286
|
-
result.failed += 1;
|
|
287
|
-
} else if (itemResult.status === "retried") {
|
|
288
|
-
result.retried += 1;
|
|
289
|
-
} else if (itemResult.status === "deferred-backoff") {
|
|
290
|
-
result.deferred += 1;
|
|
291
|
-
} else if (itemResult.status === "network-error") {
|
|
292
|
-
result.networkErrors += 1;
|
|
306
|
+
this.isFlushing = true;
|
|
307
|
+
const result = this.createEmptyResult();
|
|
308
|
+
try {
|
|
309
|
+
const pending = this.queue.getPendingForCollection(collectionName);
|
|
310
|
+
if (pending.length === 0) {
|
|
311
|
+
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
312
|
+
return result;
|
|
293
313
|
}
|
|
314
|
+
for (const item of pending) {
|
|
315
|
+
result.attempted += 1;
|
|
316
|
+
const itemResult = await this.syncItem(item);
|
|
317
|
+
result.items.push(itemResult);
|
|
318
|
+
if (itemResult.status === "synced") {
|
|
319
|
+
result.synced += 1;
|
|
320
|
+
} else if (itemResult.status === "failed") {
|
|
321
|
+
result.failed += 1;
|
|
322
|
+
} else if (itemResult.status === "retried") {
|
|
323
|
+
result.retried += 1;
|
|
324
|
+
} else if (itemResult.status === "deferred-backoff") {
|
|
325
|
+
result.deferred += 1;
|
|
326
|
+
} else if (itemResult.status === "network-error") {
|
|
327
|
+
result.networkErrors += 1;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
331
|
+
return result;
|
|
332
|
+
} finally {
|
|
333
|
+
this.isFlushing = false;
|
|
294
334
|
}
|
|
295
|
-
result.remainingPending = this.queue.getPendingForCollection(collectionName).length;
|
|
296
|
-
return result;
|
|
297
335
|
}
|
|
298
336
|
async flushRecord(recordId) {
|
|
299
337
|
const item = this.queue.getPendingForRecord(recordId);
|
|
@@ -479,7 +517,7 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
479
517
|
console.log("[AsyncStorageSync] autoSync enabled \u2014 starting sync engine");
|
|
480
518
|
instance.engine.start();
|
|
481
519
|
} else {
|
|
482
|
-
console.log("[AsyncStorageSync] autoSync disabled \u2014 call
|
|
520
|
+
console.log("[AsyncStorageSync] autoSync disabled \u2014 call flushWithResult() or syncWithResult(collection) manually");
|
|
483
521
|
}
|
|
484
522
|
_AsyncStorageSync.instance = instance;
|
|
485
523
|
return instance;
|
|
@@ -567,6 +605,9 @@ var _AsyncStorageSync = class _AsyncStorageSync {
|
|
|
567
605
|
async syncWithResult(name) {
|
|
568
606
|
return this.engine.flushCollectionWithResult(name);
|
|
569
607
|
}
|
|
608
|
+
async syncManyWithResult(names) {
|
|
609
|
+
return this.engine.flushCollectionsWithResult(names);
|
|
610
|
+
}
|
|
570
611
|
async syncById(_name, id) {
|
|
571
612
|
await this.engine.flushRecord(id);
|
|
572
613
|
}
|
package/package.json
CHANGED