@sweidos/eidos 1.2.0 → 2.1.0
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 +62 -32
- package/dist/action.js +208 -152
- package/dist/action.js.map +1 -0
- package/dist/async-storage-adapter.js.map +1 -0
- package/dist/devtools.js +247 -86
- package/dist/eidos-sw.js +8 -5
- package/dist/eidos.cjs +2 -2
- package/dist/eidos.cjs.map +1 -0
- package/dist/idb.js.map +1 -0
- package/dist/index.d.ts +101 -35
- package/dist/index.js +44 -41
- package/dist/push.cjs +8 -5
- package/dist/push.js +8 -5
- package/dist/query.cjs +1 -1
- package/dist/query.d.ts +1 -2
- package/dist/query.js +1 -1
- package/dist/queue-storage.js.map +1 -0
- package/dist/queue-sync.js +34 -0
- package/dist/queue-sync.js.map +1 -0
- package/dist/react/Provider.js.map +1 -0
- package/dist/react/hooks.js +23 -23
- package/dist/react/hooks.js.map +1 -0
- package/dist/replay.js.map +1 -0
- package/dist/resource.js +121 -107
- package/dist/resource.js.map +1 -0
- package/dist/runtime.js +23 -22
- package/dist/runtime.js.map +1 -0
- package/dist/store-slices.js.map +1 -0
- package/dist/store.js.map +1 -0
- package/dist/stores.js +23 -31
- package/dist/stores.js.map +1 -0
- package/dist/sw-bridge.js.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -114,20 +114,22 @@ if ('queued' in result) {
|
|
|
114
114
|
|
|
115
115
|
## What you get
|
|
116
116
|
|
|
117
|
-
| Feature | Description
|
|
118
|
-
| --------------------------- |
|
|
119
|
-
| **Auto strategy selection** | `offline: true` → StaleWhileRevalidate. No config needed. Override when you want.
|
|
120
|
-
| **Persistent action queue** | Failed writes go to IndexedDB and replay with exponential backoff on reconnect.
|
|
121
|
-
| **Request deduplication** | Concurrent `resource.fetch()` calls share one in-flight request.
|
|
122
|
-
| **Optimistic updates** | `onOptimistic` / `onRollback` callbacks for instant UI feedback.
|
|
123
|
-
| **Conflict resolution** | `
|
|
124
|
-
| **
|
|
125
|
-
| **
|
|
126
|
-
| **
|
|
127
|
-
| **
|
|
128
|
-
| **
|
|
129
|
-
| **
|
|
130
|
-
| **
|
|
117
|
+
| Feature | Description |
|
|
118
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| **Auto strategy selection** | `offline: true` → StaleWhileRevalidate. No config needed. Override when you want. |
|
|
120
|
+
| **Persistent action queue** | Failed writes go to IndexedDB and replay with exponential backoff on reconnect. |
|
|
121
|
+
| **Request deduplication** | Concurrent `resource.fetch()` calls share one in-flight request. |
|
|
122
|
+
| **Optimistic updates** | `onOptimistic` / `onRollback` callbacks for instant UI feedback. |
|
|
123
|
+
| **Conflict resolution** | `conflict: { strategy: 'serverWins' \| 'clientWins' \| 'merge' \| 'custom' }` on 4xx replay responses. |
|
|
124
|
+
| **Idempotent replay** | Stable `idempotencyKey` per invocation, forwarded to `fn` via `ActionContext` — safe retries even after a dropped response. |
|
|
125
|
+
| **Cancellable actions** | `cancellable: true` → `AbortSignal` per call, plus `handle.cancel(idempotencyKey)`. |
|
|
126
|
+
| **Queue prioritization** | `priority: 'high' \| 'normal' \| 'low'` — high items replay before normal. |
|
|
127
|
+
| **Cache warming** | `warmCache(handles[])` bulk-prefetches resources on login/init. |
|
|
128
|
+
| **URL patterns** | `/api/products/*`, `/api/users/:id`, `**` wildcards — SW intercepts all matches. |
|
|
129
|
+
| **Background Sync** | Registers a `sync` tag so queued actions replay even after tab close. |
|
|
130
|
+
| **Devtools panel** | `<EidosDevtools />` — live queue, cache state, offline toggle, no CSS import. |
|
|
131
|
+
| **Testing helpers** | `mockOffline`, `drainQueue`, `resetEidos`, `getCachedEntry` for Vitest/Jest. |
|
|
132
|
+
| **OpenAPI codegen** | `npx eidos-gen openapi.json` generates typed `resource()` + `action()` declarations. |
|
|
131
133
|
|
|
132
134
|
---
|
|
133
135
|
|
|
@@ -158,6 +160,9 @@ const products = resource('/api/products', {
|
|
|
158
160
|
strategy?: 'cache-first' | 'stale-while-revalidate' | 'network-first',
|
|
159
161
|
cacheName?: string, // custom cache bucket
|
|
160
162
|
maxAge?: number, // TTL in ms — re-fetch after expiry
|
|
163
|
+
version?: string | number, // bump when the response shape changes —
|
|
164
|
+
// appended to cacheName (e.g. 'eidos-resources-v1-v2')
|
|
165
|
+
// so old-shaped cache entries aren't served
|
|
161
166
|
})
|
|
162
167
|
|
|
163
168
|
await products.fetch() // Promise<Response>
|
|
@@ -175,20 +180,47 @@ products.query() // { queryKey, queryFn } for useQuery
|
|
|
175
180
|
| `offline: true, strategy: 'cache-first'` | CacheFirst | Static assets, config data |
|
|
176
181
|
| `offline: true, strategy: 'network-first'` | NetworkFirst | Always-fresh with offline fallback |
|
|
177
182
|
|
|
178
|
-
|
|
183
|
+
### `resourcePattern(pattern, config)`
|
|
184
|
+
|
|
185
|
+
For URL patterns — `/api/products/*`, `/api/users/:id`, `**` — the SW intercepts
|
|
186
|
+
all matching requests automatically, so there's no single URL to fetch. Use
|
|
187
|
+
`resourcePattern()` instead of `resource()`; it returns a handle with only
|
|
188
|
+
`invalidate()` and `unregister()`:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const productPattern = resourcePattern('/api/products/*', { offline: true });
|
|
192
|
+
|
|
193
|
+
await productPattern.invalidate(); // clear all cached entries matching the pattern
|
|
194
|
+
productPattern.unregister();
|
|
195
|
+
```
|
|
179
196
|
|
|
180
197
|
### `action(fn, config)`
|
|
181
198
|
|
|
182
199
|
```ts
|
|
183
|
-
const createOrder = action(async (payload: OrderPayload) => { ... }, {
|
|
200
|
+
const createOrder = action(async (payload: OrderPayload, ctx: ActionContext) => { ... }, {
|
|
184
201
|
reliability: 'neverLose', // persist to IDB + replay on reconnect
|
|
185
202
|
name: 'createOrder', // stable name for post-reload replay
|
|
203
|
+
namespace?: string, // prefix actionId — avoids collisions across modules
|
|
186
204
|
maxRetries?: number, // default: 3
|
|
187
205
|
priority?: 'high' | 'normal' | 'low',
|
|
206
|
+
cancellable?: boolean, // adds AbortSignal to ctx, enables handle.cancel(key)
|
|
188
207
|
onOptimistic?: (...args) => void, // instant UI update
|
|
189
208
|
onRollback?: (...args) => void, // revert on permanent failure
|
|
190
|
-
|
|
209
|
+
conflict?: { // 4xx replay handling
|
|
210
|
+
strategy: 'serverWins' | 'clientWins' | 'merge' | 'custom',
|
|
211
|
+
resolve?: (ctx) => 'retry' | 'skip' | { resolved: args },
|
|
212
|
+
},
|
|
191
213
|
})
|
|
214
|
+
|
|
215
|
+
// ctx.idempotencyKey is stable across retries — forward as e.g. an
|
|
216
|
+
// `Idempotency-Key` header so the server can dedupe replayed writes.
|
|
217
|
+
|
|
218
|
+
// Module-level helpers (used by the devtools queue inspector, and usable
|
|
219
|
+
// directly): cancel/remove a queue item by idempotency key, or reset a
|
|
220
|
+
// 'failed' item back to 'pending' for the next replayQueue().
|
|
221
|
+
import { cancelByIdempotencyKey, requeueItem } from '@sweidos/eidos'
|
|
222
|
+
await cancelByIdempotencyKey(idempotencyKey) // true if cancelled/removed
|
|
223
|
+
await requeueItem(queueItemId) // true if it was 'failed'
|
|
192
224
|
```
|
|
193
225
|
|
|
194
226
|
### React hooks
|
|
@@ -389,21 +421,19 @@ Panel shows: live queue state · cache entries · SW status · offline simulatio
|
|
|
389
421
|
|
|
390
422
|
## How it compares
|
|
391
423
|
|
|
392
|
-
|
|
|
393
|
-
|
|
|
394
|
-
| Service worker setup
|
|
395
|
-
| Caching strategy
|
|
396
|
-
| Offline writes
|
|
397
|
-
| Framework support
|
|
398
|
-
| TanStack Query bridge
|
|
399
|
-
| Bundle size (core
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
`resource()`/`action()` declarations instead of you writing `workbox-*` config
|
|
406
|
-
by hand.
|
|
424
|
+
| | **Eidos** | Workbox | RTK Query / TanStack Query |
|
|
425
|
+
| --------------------- | ----------------------------------------------------- | ---------------------------- | -------------------------- |
|
|
426
|
+
| Service worker setup | Generated from `resource()`/`action()` declarations | Hand-written routing config | None — no SW |
|
|
427
|
+
| Caching strategy | Auto-derived from intent, inspectable via devtools | Manually chosen per route | `staleTime`/`gcTime` only |
|
|
428
|
+
| Offline writes | IndexedDB queue, auto-replay + backoff via `action()` | Background Sync, you wire it | No built-in mutation queue |
|
|
429
|
+
| Framework support | React, Svelte, Vue, Next.js, React Native, vanilla JS | Framework-agnostic (SW only) | Per-library |
|
|
430
|
+
| TanStack Query bridge | `@sweidos/eidos/query` adapter | — | Native |
|
|
431
|
+
| Bundle size (core) | ~6.3 kB brotli | ~3-6 kB (modular) | ~13 kB |
|
|
432
|
+
|
|
433
|
+
Not a TanStack Query replacement — `@sweidos/eidos/query` is a thin adapter so
|
|
434
|
+
you keep TQ's cache/devtools while Eidos owns the offline layer. Workbox is a
|
|
435
|
+
lower-level toolkit; Eidos picks and configures strategies for you instead of
|
|
436
|
+
hand-written `workbox-*` config.
|
|
407
437
|
|
|
408
438
|
---
|
|
409
439
|
|
package/dist/action.js
CHANGED
|
@@ -1,86 +1,107 @@
|
|
|
1
|
-
import { useEidosStore as
|
|
2
|
-
import { getSwRegistration as
|
|
3
|
-
import { idbQueueStorage as
|
|
4
|
-
import { _getQueueStorage as
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { useEidosStore as l } from "./store.js";
|
|
2
|
+
import { getSwRegistration as A } from "./sw-bridge.js";
|
|
3
|
+
import { idbQueueStorage as C } from "./idb.js";
|
|
4
|
+
import { _getQueueStorage as _ } from "./queue-storage.js";
|
|
5
|
+
import { broadcastQueueSync as u } from "./queue-sync.js";
|
|
6
|
+
var w = /* @__PURE__ */ new Map(), R = /* @__PURE__ */ new Map(), Q = /* @__PURE__ */ new Map(), k = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map();
|
|
7
|
+
function c() {
|
|
8
|
+
return _() ?? C;
|
|
8
9
|
}
|
|
9
10
|
function m() {
|
|
10
11
|
return crypto.randomUUID();
|
|
11
12
|
}
|
|
12
|
-
function v(e, t,
|
|
13
|
-
return e(...t,
|
|
13
|
+
function v(e, t, n) {
|
|
14
|
+
return e(...t, n);
|
|
14
15
|
}
|
|
15
|
-
function
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
function B(e, t) {
|
|
17
|
+
const n = t.name || e.name || m(), a = t.namespace ? `${t.namespace}::${n}` : n;
|
|
18
|
+
if (w.has(a)) throw new Error(`[eidos] duplicate action id "${a}" — an action with this id is already registered. Pass a unique config.name or config.namespace.`);
|
|
19
|
+
w.set(a, e), k.set(a, t), t.onRollback && R.set(a, t.onRollback), t.conflict && Q.set(a, t.conflict);
|
|
20
|
+
const i = async (...r) => {
|
|
21
|
+
const { isOnline: o } = l.getState(), s = m();
|
|
22
|
+
let b;
|
|
21
23
|
if (t.cancellable) {
|
|
22
|
-
const
|
|
23
|
-
p.set(
|
|
24
|
+
const d = new AbortController();
|
|
25
|
+
p.set(s, d), b = d.signal;
|
|
24
26
|
}
|
|
25
|
-
const
|
|
26
|
-
idempotencyKey:
|
|
27
|
+
const y = {
|
|
28
|
+
idempotencyKey: s,
|
|
27
29
|
attempt: 0,
|
|
28
|
-
signal:
|
|
30
|
+
signal: b
|
|
29
31
|
};
|
|
30
|
-
t.onOptimistic?.(...
|
|
32
|
+
t.onOptimistic?.(...r, y);
|
|
31
33
|
try {
|
|
32
34
|
if (t.reliability === "neverLose") {
|
|
33
|
-
if (!
|
|
35
|
+
if (!o) return h(a, a, r, t, s);
|
|
34
36
|
try {
|
|
35
|
-
return await v(e,
|
|
36
|
-
} catch (
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
37
|
+
return await v(e, r, y);
|
|
38
|
+
} catch (d) {
|
|
39
|
+
if (x(d)) throw d;
|
|
40
|
+
return h(a, a, r, t, s);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
try {
|
|
42
|
-
return
|
|
43
|
-
} catch (
|
|
44
|
-
throw t.onRollback?.(...
|
|
44
|
+
return await v(e, r, y);
|
|
45
|
+
} catch (d) {
|
|
46
|
+
throw t.onRollback?.(...r, y), d;
|
|
45
47
|
}
|
|
46
48
|
} finally {
|
|
47
|
-
t.cancellable && p.delete(
|
|
49
|
+
t.cancellable && p.delete(s);
|
|
48
50
|
}
|
|
49
|
-
}, r = async (n) => {
|
|
50
|
-
const s = p.get(n);
|
|
51
|
-
if (s)
|
|
52
|
-
return s.abort(), !0;
|
|
53
|
-
const l = (await u().getAll()).find((o) => o.idempotencyKey === n && o.status === "pending");
|
|
54
|
-
return l ? (y.getState().removeQueueItem(l.id), await u().remove(l.id), !0) : !1;
|
|
55
51
|
};
|
|
56
|
-
return Object.defineProperty(
|
|
52
|
+
return Object.defineProperty(i, "id", {
|
|
57
53
|
value: a,
|
|
58
54
|
writable: !1
|
|
59
|
-
}), Object.defineProperty(
|
|
55
|
+
}), Object.defineProperty(i, "config", {
|
|
60
56
|
value: t,
|
|
61
57
|
writable: !1
|
|
62
|
-
}), Object.defineProperty(
|
|
63
|
-
value:
|
|
58
|
+
}), Object.defineProperty(i, "cancel", {
|
|
59
|
+
value: S,
|
|
64
60
|
writable: !1
|
|
65
|
-
}),
|
|
61
|
+
}), i;
|
|
66
62
|
}
|
|
67
|
-
async function
|
|
68
|
-
const
|
|
63
|
+
async function S(e) {
|
|
64
|
+
const t = p.get(e);
|
|
65
|
+
if (t)
|
|
66
|
+
return t.abort(), !0;
|
|
67
|
+
const n = (await c().getAll()).find((a) => a.idempotencyKey === e && a.status === "pending");
|
|
68
|
+
return n ? (l.getState().removeQueueItem(n.id), u({
|
|
69
|
+
type: "remove",
|
|
70
|
+
id: n.id
|
|
71
|
+
}), await c().remove(n.id), !0) : !1;
|
|
72
|
+
}
|
|
73
|
+
async function F(e) {
|
|
74
|
+
const t = (await c().getAll()).find((a) => a.id === e);
|
|
75
|
+
if (!t || t.status !== "failed") return !1;
|
|
76
|
+
const n = {
|
|
77
|
+
status: "pending",
|
|
78
|
+
error: void 0,
|
|
79
|
+
nextRetryAt: void 0,
|
|
80
|
+
retryCount: 0
|
|
81
|
+
};
|
|
82
|
+
return l.getState().updateQueueItem(e, n), u({
|
|
83
|
+
type: "update",
|
|
84
|
+
id: e,
|
|
85
|
+
update: n
|
|
86
|
+
}), await c().update(e, n), !0;
|
|
87
|
+
}
|
|
88
|
+
async function h(e, t, n, a, i) {
|
|
89
|
+
const r = m(), o = {
|
|
69
90
|
schemaVersion: 2,
|
|
70
91
|
id: r,
|
|
71
92
|
actionId: e,
|
|
72
93
|
actionName: t,
|
|
73
|
-
idempotencyKey:
|
|
74
|
-
args:
|
|
94
|
+
idempotencyKey: i,
|
|
95
|
+
args: n,
|
|
75
96
|
queuedAt: Date.now(),
|
|
76
97
|
retryCount: 0,
|
|
77
98
|
maxRetries: a.maxRetries ?? 3,
|
|
78
99
|
status: "pending",
|
|
79
100
|
priority: a.priority ?? "normal"
|
|
80
101
|
};
|
|
81
|
-
await
|
|
102
|
+
await c().add(o), l.getState().addQueueItem(o);
|
|
82
103
|
try {
|
|
83
|
-
const s =
|
|
104
|
+
const s = A();
|
|
84
105
|
s && "sync" in s && await s.sync.register("eidos-queue-replay");
|
|
85
106
|
} catch {
|
|
86
107
|
}
|
|
@@ -90,7 +111,7 @@ async function R(e, t, i, a, c) {
|
|
|
90
111
|
message: `"${t}" queued — will execute when online`
|
|
91
112
|
};
|
|
92
113
|
}
|
|
93
|
-
function
|
|
114
|
+
function x(e) {
|
|
94
115
|
return e instanceof DOMException && e.name === "AbortError";
|
|
95
116
|
}
|
|
96
117
|
function K(e) {
|
|
@@ -101,10 +122,10 @@ function K(e) {
|
|
|
101
122
|
}
|
|
102
123
|
return !1;
|
|
103
124
|
}
|
|
104
|
-
function
|
|
125
|
+
function M(e) {
|
|
105
126
|
return Math.min(2e3 * 2 ** e, 3e5) * (0.8 + Math.random() * 0.4);
|
|
106
127
|
}
|
|
107
|
-
function
|
|
128
|
+
function f() {
|
|
108
129
|
return {
|
|
109
130
|
attempted: 0,
|
|
110
131
|
succeeded: 0,
|
|
@@ -115,138 +136,173 @@ function w() {
|
|
|
115
136
|
cancelled: 0
|
|
116
137
|
};
|
|
117
138
|
}
|
|
118
|
-
var
|
|
119
|
-
async function
|
|
120
|
-
const e =
|
|
121
|
-
if (!e.isOnline) return
|
|
122
|
-
if (typeof navigator < "u" && navigator.locks) return navigator.locks.request(q, { ifAvailable: !0 }, async (t) => t ? I(e) :
|
|
123
|
-
if (
|
|
124
|
-
|
|
139
|
+
var g = !1, q = "eidos-queue-replay";
|
|
140
|
+
async function V() {
|
|
141
|
+
const e = l.getState();
|
|
142
|
+
if (!e.isOnline) return f();
|
|
143
|
+
if (typeof navigator < "u" && navigator.locks) return navigator.locks.request(q, { ifAvailable: !0 }, async (t) => t ? I(e) : f());
|
|
144
|
+
if (g) return f();
|
|
145
|
+
g = !0;
|
|
125
146
|
try {
|
|
126
147
|
return await I(e);
|
|
127
148
|
} finally {
|
|
128
|
-
|
|
149
|
+
g = !1;
|
|
129
150
|
}
|
|
130
151
|
}
|
|
131
|
-
async function
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
async function O(e, t) {
|
|
153
|
+
const n = Date.now();
|
|
154
|
+
t.updateQueueItem(e.id, {
|
|
155
|
+
status: "succeeded",
|
|
156
|
+
completedAt: n
|
|
157
|
+
}), u({
|
|
158
|
+
type: "update",
|
|
159
|
+
id: e.id,
|
|
160
|
+
update: {
|
|
161
|
+
status: "succeeded",
|
|
162
|
+
completedAt: n
|
|
163
|
+
}
|
|
164
|
+
}), await c().update(e.id, {
|
|
165
|
+
status: "succeeded",
|
|
166
|
+
completedAt: n
|
|
167
|
+
}), setTimeout(() => {
|
|
168
|
+
t.removeQueueItem(e.id), u({
|
|
169
|
+
type: "remove",
|
|
170
|
+
id: e.id
|
|
171
|
+
}), c().remove(e.id);
|
|
172
|
+
}, 3e3);
|
|
173
|
+
}
|
|
174
|
+
async function E(e, t, n) {
|
|
175
|
+
const a = Q.get(e.actionId);
|
|
176
|
+
let i;
|
|
177
|
+
if (a) switch (a.strategy) {
|
|
178
|
+
case "serverWins":
|
|
179
|
+
i = "skip";
|
|
180
|
+
break;
|
|
181
|
+
case "clientWins":
|
|
182
|
+
i = "retry";
|
|
183
|
+
break;
|
|
184
|
+
case "merge":
|
|
185
|
+
case "custom": {
|
|
186
|
+
const r = {
|
|
187
|
+
error: n,
|
|
188
|
+
args: e.args,
|
|
189
|
+
attempt: e.retryCount,
|
|
190
|
+
idempotencyKey: e.idempotencyKey
|
|
191
|
+
};
|
|
192
|
+
i = a.resolve?.(r) ?? "retry";
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (i === "skip")
|
|
197
|
+
return t.removeQueueItem(e.id), u({
|
|
198
|
+
type: "remove",
|
|
199
|
+
id: e.id
|
|
200
|
+
}), await c().remove(e.id), "conflicted";
|
|
201
|
+
i && typeof i == "object" && (e.args = i.resolved, t.updateQueueItem(e.id, { args: i.resolved }), u({
|
|
202
|
+
type: "update",
|
|
203
|
+
id: e.id,
|
|
204
|
+
update: { args: i.resolved }
|
|
205
|
+
}), await c().update(e.id, { args: i.resolved }));
|
|
206
|
+
}
|
|
207
|
+
async function P(e, t, n) {
|
|
208
|
+
const a = e.retryCount + 1;
|
|
209
|
+
if (a >= e.maxRetries) {
|
|
210
|
+
const r = {
|
|
211
|
+
status: "failed",
|
|
212
|
+
error: String(n),
|
|
213
|
+
retryCount: a
|
|
214
|
+
};
|
|
215
|
+
t.updateQueueItem(e.id, r), u({
|
|
216
|
+
type: "update",
|
|
217
|
+
id: e.id,
|
|
218
|
+
update: r
|
|
219
|
+
}), await c().update(e.id, r);
|
|
220
|
+
const o = {
|
|
221
|
+
idempotencyKey: e.idempotencyKey,
|
|
222
|
+
attempt: a
|
|
223
|
+
};
|
|
224
|
+
return R.get(e.actionId)?.(...e.args, o), "failed";
|
|
225
|
+
}
|
|
226
|
+
const i = {
|
|
227
|
+
status: "pending",
|
|
228
|
+
retryCount: a,
|
|
229
|
+
nextRetryAt: Date.now() + M(a)
|
|
230
|
+
};
|
|
231
|
+
return t.updateQueueItem(e.id, i), u({
|
|
232
|
+
type: "update",
|
|
233
|
+
id: e.id,
|
|
234
|
+
update: i
|
|
235
|
+
}), await c().update(e.id, i), "retrying";
|
|
236
|
+
}
|
|
237
|
+
async function D(e, t) {
|
|
238
|
+
const n = w.get(e.actionId);
|
|
239
|
+
if (!n) return "skipped";
|
|
240
|
+
const a = k.get(e.actionId)?.cancellable;
|
|
241
|
+
let i;
|
|
136
242
|
if (a) {
|
|
137
|
-
const
|
|
138
|
-
p.set(e.idempotencyKey,
|
|
243
|
+
const o = new AbortController();
|
|
244
|
+
p.set(e.idempotencyKey, o), i = o.signal;
|
|
139
245
|
}
|
|
140
246
|
const r = {
|
|
141
247
|
idempotencyKey: e.idempotencyKey,
|
|
142
248
|
attempt: e.retryCount,
|
|
143
|
-
signal:
|
|
249
|
+
signal: i
|
|
144
250
|
};
|
|
145
251
|
try {
|
|
146
|
-
await v(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}, 3e3), "succeeded";
|
|
157
|
-
} catch (n) {
|
|
158
|
-
if (A(n))
|
|
159
|
-
return t.removeQueueItem(e.id), await u().remove(e.id), "cancelled";
|
|
160
|
-
if (K(n)) {
|
|
161
|
-
const l = Q.get(e.actionId);
|
|
162
|
-
let o;
|
|
163
|
-
if (l) switch (l.strategy) {
|
|
164
|
-
case "serverWins":
|
|
165
|
-
o = "skip";
|
|
166
|
-
break;
|
|
167
|
-
case "clientWins":
|
|
168
|
-
case "lastWriteWins":
|
|
169
|
-
o = "retry";
|
|
170
|
-
break;
|
|
171
|
-
case "merge":
|
|
172
|
-
case "custom": {
|
|
173
|
-
const d = {
|
|
174
|
-
error: n,
|
|
175
|
-
args: e.args,
|
|
176
|
-
attempt: e.retryCount,
|
|
177
|
-
idempotencyKey: e.idempotencyKey
|
|
178
|
-
};
|
|
179
|
-
o = l.resolve?.(d) ?? "retry";
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
const d = C.get(e.actionId);
|
|
185
|
-
d && (o = d(n, e.args));
|
|
186
|
-
}
|
|
187
|
-
if (o === "skip")
|
|
188
|
-
return t.removeQueueItem(e.id), await u().remove(e.id), "conflicted";
|
|
189
|
-
o && typeof o == "object" && (e.args = o.resolved, t.updateQueueItem(e.id, { args: o.resolved }), await u().update(e.id, { args: o.resolved }));
|
|
190
|
-
}
|
|
191
|
-
const s = e.retryCount + 1;
|
|
192
|
-
if (s >= e.maxRetries)
|
|
193
|
-
return t.updateQueueItem(e.id, {
|
|
194
|
-
status: "failed",
|
|
195
|
-
error: String(n),
|
|
196
|
-
retryCount: s
|
|
197
|
-
}), await u().update(e.id, {
|
|
198
|
-
status: "failed",
|
|
199
|
-
error: String(n),
|
|
200
|
-
retryCount: s
|
|
201
|
-
}), k.get(e.actionId)?.(...e.args), "failed";
|
|
202
|
-
{
|
|
203
|
-
const l = Date.now() + O(s);
|
|
204
|
-
return t.updateQueueItem(e.id, {
|
|
205
|
-
status: "pending",
|
|
206
|
-
retryCount: s,
|
|
207
|
-
nextRetryAt: l
|
|
208
|
-
}), await u().update(e.id, {
|
|
209
|
-
status: "pending",
|
|
210
|
-
retryCount: s,
|
|
211
|
-
nextRetryAt: l
|
|
212
|
-
}), "retrying";
|
|
252
|
+
return await v(n, e.args, r), await O(e, t), "succeeded";
|
|
253
|
+
} catch (o) {
|
|
254
|
+
if (x(o))
|
|
255
|
+
return t.removeQueueItem(e.id), u({
|
|
256
|
+
type: "remove",
|
|
257
|
+
id: e.id
|
|
258
|
+
}), await c().remove(e.id), "cancelled";
|
|
259
|
+
if (K(o)) {
|
|
260
|
+
const s = await E(e, t, o);
|
|
261
|
+
if (s) return s;
|
|
213
262
|
}
|
|
263
|
+
return P(e, t, o);
|
|
214
264
|
} finally {
|
|
215
265
|
a && p.delete(e.idempotencyKey);
|
|
216
266
|
}
|
|
217
267
|
}
|
|
218
|
-
async function
|
|
268
|
+
async function j(e, t, n) {
|
|
219
269
|
if (e.length === 0) return;
|
|
220
|
-
const a = e.filter((r) =>
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
id:
|
|
270
|
+
const a = e.filter((r) => w.has(r.actionId));
|
|
271
|
+
if (n.skipped += e.length - a.length, a.length > 0) {
|
|
272
|
+
const r = a.map((o) => ({
|
|
273
|
+
id: o.id,
|
|
224
274
|
update: { status: "replaying" }
|
|
225
|
-
}))
|
|
226
|
-
|
|
275
|
+
}));
|
|
276
|
+
t.batchUpdateQueueItems(r), u({
|
|
277
|
+
type: "batchUpdate",
|
|
278
|
+
updates: r
|
|
279
|
+
});
|
|
280
|
+
for (const o of a) c().update(o.id, { status: "replaying" });
|
|
227
281
|
}
|
|
228
|
-
const
|
|
229
|
-
for (const r of
|
|
230
|
-
const
|
|
231
|
-
|
|
282
|
+
const i = await Promise.allSettled(a.map((r) => D(r, t)));
|
|
283
|
+
for (const r of i) {
|
|
284
|
+
const o = r.status === "fulfilled" ? r.value : "failed";
|
|
285
|
+
o === "skipped" ? n.skipped++ : o === "conflicted" ? n.conflicted++ : o === "cancelled" ? n.cancelled++ : (n.attempted++, n[o]++);
|
|
232
286
|
}
|
|
233
287
|
}
|
|
234
288
|
async function I(e) {
|
|
235
|
-
const t = await
|
|
289
|
+
const t = await c().getPending(), n = Date.now(), a = t.filter((r) => r.retryCount < r.maxRetries && (!r.nextRetryAt || r.nextRetryAt <= n)), i = f();
|
|
236
290
|
for (const r of [
|
|
237
291
|
"high",
|
|
238
292
|
"normal",
|
|
239
293
|
"low"
|
|
240
|
-
]) await
|
|
241
|
-
return
|
|
294
|
+
]) await j(a.filter((o) => (o.priority ?? "normal") === r), e, i);
|
|
295
|
+
return i;
|
|
242
296
|
}
|
|
243
|
-
async function
|
|
244
|
-
await
|
|
297
|
+
async function Y() {
|
|
298
|
+
await c().clear(), l.getState().hydrateQueue([]);
|
|
245
299
|
}
|
|
246
300
|
export {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
301
|
+
B as action,
|
|
302
|
+
S as cancelByIdempotencyKey,
|
|
303
|
+
Y as clearQueue,
|
|
304
|
+
V as replayQueue,
|
|
305
|
+
F as requeueItem
|
|
250
306
|
};
|
|
251
307
|
|
|
252
308
|
//# sourceMappingURL=action.js.map
|