@sweidos/eidos 2.0.0 → 2.2.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 +53 -14
- package/dist/action.js +152 -134
- package/dist/action.js.map +1 -1
- package/dist/devtools.js +253 -20
- package/dist/eidos.cjs +2 -2
- package/dist/eidos.cjs.map +1 -1
- package/dist/index.d.ts +101 -13
- package/dist/index.js +40 -35
- package/dist/react/hooks.js +30 -27
- package/dist/react/hooks.js.map +1 -1
- package/dist/resource.js +22 -22
- package/dist/resource.js.map +1 -1
- package/dist/runtime.js +29 -24
- package/dist/runtime.js.map +1 -1
- package/dist/store-slices.js +31 -20
- package/dist/store-slices.js.map +1 -1
- package/dist/store.js +22 -19
- package/dist/store.js.map +1 -1
- package/dist/stores.js +31 -22
- package/dist/stores.js.map +1 -1
- package/dist/testing.cjs +3 -2
- package/dist/testing.js +3 -2
- package/dist/types.js +19 -8
- package/dist/types.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@sweidos/eidos)
|
|
5
5
|
[](https://bundlejs.com/?q=@sweidos/eidos)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
|
-
[](https://github.com/sweidos/eidos/actions)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
> **
|
|
10
|
+
> **Never lose a write.**
|
|
11
11
|
|
|
12
|
-
Declare what your app needs offline. Eidos picks the cache strategy, registers the Service Worker, and persists your action queue to IndexedDB —
|
|
12
|
+
Declare what your app needs offline. Eidos picks the cache strategy, registers the Service Worker, and persists your action queue to IndexedDB — with idempotency keys and cross-tab replay locks built in, so a queued mutation runs exactly once.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
15
|
import { resource, action } from '@sweidos/eidos';
|
|
@@ -135,16 +135,19 @@ if ('queued' in result) {
|
|
|
135
135
|
|
|
136
136
|
## Framework support
|
|
137
137
|
|
|
138
|
-
| Framework
|
|
139
|
-
|
|
|
140
|
-
| **React**
|
|
141
|
-
| **Next.js App Router**
|
|
142
|
-
| **
|
|
143
|
-
| **
|
|
144
|
-
| **
|
|
145
|
-
| **
|
|
146
|
-
| **
|
|
147
|
-
| **
|
|
138
|
+
| Framework | Import path | Notes |
|
|
139
|
+
| -------------------------- | ----------------------------- | -------------------------------------------------------------- |
|
|
140
|
+
| **React** | `@sweidos/eidos` | Hooks + `EidosProvider` |
|
|
141
|
+
| **Next.js App Router** | `@sweidos/eidos/nextjs` | Pre-marked `'use client'` — no wrapper needed |
|
|
142
|
+
| **Next.js Server Actions** | `@eidos/next` | `serverAction()` neverLose wrapper + idempotency context |
|
|
143
|
+
| **SvelteKit** | `@sweidos/eidos/sveltekit` | `initEidosSvelteKit()` in `onMount`, framework-agnostic stores |
|
|
144
|
+
| **Vue** | `@sweidos/eidos` | Framework-agnostic stores via `eidosStatus.subscribe()` |
|
|
145
|
+
| **React Native** | `@sweidos/eidos/react-native` | AsyncStorage-backed queue, same `action()` API |
|
|
146
|
+
| **Vanilla JS** | `@sweidos/eidos` | `eidosStatus`, `eidosQueue`, `eidosQueueStats` stores |
|
|
147
|
+
| **Vite** | `@sweidos/eidos/vite` | Plugin auto-copies `eidos-sw.js` on every build |
|
|
148
|
+
| **CRDT merge (Yjs)** | `@eidos/crdt-yjs` | `createYjsMergeResolver()` for `conflict.strategy: 'merge'` |
|
|
149
|
+
| **TanStack Query** | `@sweidos/eidos/query` | `useEidosQuery`, `useEidosMutation`, `withEidosQueryClient` |
|
|
150
|
+
| **Tauri / Electron** | `@eidos/sqlite-storage` | SQLite-backed `QueueStorage`, same `action()` API |
|
|
148
151
|
|
|
149
152
|
---
|
|
150
153
|
|
|
@@ -160,6 +163,9 @@ const products = resource('/api/products', {
|
|
|
160
163
|
strategy?: 'cache-first' | 'stale-while-revalidate' | 'network-first',
|
|
161
164
|
cacheName?: string, // custom cache bucket
|
|
162
165
|
maxAge?: number, // TTL in ms — re-fetch after expiry
|
|
166
|
+
version?: string | number, // bump when the response shape changes —
|
|
167
|
+
// appended to cacheName (e.g. 'eidos-resources-v1-v2')
|
|
168
|
+
// so old-shaped cache entries aren't served
|
|
163
169
|
})
|
|
164
170
|
|
|
165
171
|
await products.fetch() // Promise<Response>
|
|
@@ -211,6 +217,13 @@ const createOrder = action(async (payload: OrderPayload, ctx: ActionContext) =>
|
|
|
211
217
|
|
|
212
218
|
// ctx.idempotencyKey is stable across retries — forward as e.g. an
|
|
213
219
|
// `Idempotency-Key` header so the server can dedupe replayed writes.
|
|
220
|
+
|
|
221
|
+
// Module-level helpers (used by the devtools queue inspector, and usable
|
|
222
|
+
// directly): cancel/remove a queue item by idempotency key, or reset a
|
|
223
|
+
// 'failed' item back to 'pending' for the next replayQueue().
|
|
224
|
+
import { cancelByIdempotencyKey, requeueItem } from '@sweidos/eidos'
|
|
225
|
+
await cancelByIdempotencyKey(idempotencyKey) // true if cancelled/removed
|
|
226
|
+
await requeueItem(queueItemId) // true if it was 'failed'
|
|
214
227
|
```
|
|
215
228
|
|
|
216
229
|
### React hooks
|
|
@@ -221,6 +234,9 @@ const { pending, failed } = useEidosQueueStats();
|
|
|
221
234
|
const entry = useEidosResource('/api/products');
|
|
222
235
|
const item = useEidosAction(queuedResult.id);
|
|
223
236
|
useEidosOnDrain(() => toast('All offline actions synced!'));
|
|
237
|
+
|
|
238
|
+
// Cumulative neverLose outcome counters (queued/succeeded/failed/retried/conflicted/cancelled)
|
|
239
|
+
const { queued, succeeded, failed: failedCount } = useEidosReliabilityStats();
|
|
224
240
|
```
|
|
225
241
|
|
|
226
242
|
### Framework-agnostic stores
|
|
@@ -231,8 +247,25 @@ eidosStatus.subscribe(({ isOnline }) => { ... })
|
|
|
231
247
|
eidosQueue.subscribe((queue) => { ... })
|
|
232
248
|
eidosQueueStats.getState() // { pending, failed, replaying, total }
|
|
233
249
|
eidosResource('/api/products').getState() // ResourceEntry | undefined
|
|
250
|
+
onQueueDrain(() => toast('All offline actions synced!')) // returns unsubscribe
|
|
251
|
+
eidosReliabilityStats.getState() // { queued, succeeded, failed, retried, conflicted, cancelled }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Reliability telemetry
|
|
255
|
+
|
|
256
|
+
Opt in to periodic reporting of cumulative `neverLose` queue outcomes — wire it
|
|
257
|
+
up to your analytics backend:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
initEidos({
|
|
261
|
+
onReliabilityReport: (stats) => analytics.track('eidos_reliability', stats),
|
|
262
|
+
reliabilityReportInterval: 60_000, // default
|
|
263
|
+
});
|
|
234
264
|
```
|
|
235
265
|
|
|
266
|
+
The same counters are visible live in `<EidosDevtools />` under the
|
|
267
|
+
"Reliability" tab.
|
|
268
|
+
|
|
236
269
|
---
|
|
237
270
|
|
|
238
271
|
## TanStack Query
|
|
@@ -392,10 +425,16 @@ Panel shows: live queue state · cache entries · SW status · offline simulatio
|
|
|
392
425
|
|
|
393
426
|
**Next.js** — import from `@sweidos/eidos/nextjs`. Pre-marked `'use client'`, works in App Router layouts without a wrapper.
|
|
394
427
|
|
|
428
|
+
**Next.js Server Actions** — `@eidos/next`'s `serverAction()` wraps a `'use server'` function with `action()` (`reliability: 'neverLose'` by default), keyed by `config.name` + `config.namespace`. `getActionContext()` / `idempotencyHeaders()` recover the `idempotencyKey`/`attempt` inside the action body.
|
|
429
|
+
|
|
395
430
|
**SvelteKit** — `initEidosSvelteKit()` inside `onMount`. Framework-agnostic stores (`$eidosQueue`, `$eidosStatus`) work with Svelte's `$` auto-subscribe.
|
|
396
431
|
|
|
397
432
|
**React Native** — `@sweidos/eidos/react-native` with AsyncStorage-backed queue. Same `action()` API surface, no Service Worker dependency.
|
|
398
433
|
|
|
434
|
+
**Tauri / Electron** — `@eidos/sqlite-storage` with a SQLite-backed `QueueStorage`. Pass a `@tauri-apps/plugin-sql` `Database` directly, or wrap `better-sqlite3` with the `SqliteLike` interface. Same `action()` API surface, no Service Worker dependency.
|
|
435
|
+
|
|
436
|
+
**CRDT merge (Yjs)** — `@eidos/crdt-yjs`'s `createYjsMergeResolver()` builds a `conflict.resolve` for the `'merge'`/`'custom'` strategy that applies the server's Yjs state and the queued local update to a `Y.Doc`, then rewrites the queued args with the merged update — automatic, loss-free reconciliation of concurrent edits instead of a hand-written `resolve()`.
|
|
437
|
+
|
|
399
438
|
---
|
|
400
439
|
|
|
401
440
|
## Known limitations
|
|
@@ -418,7 +457,7 @@ Panel shows: live queue state · cache entries · SW status · offline simulatio
|
|
|
418
457
|
| Offline writes | IndexedDB queue, auto-replay + backoff via `action()` | Background Sync, you wire it | No built-in mutation queue |
|
|
419
458
|
| Framework support | React, Svelte, Vue, Next.js, React Native, vanilla JS | Framework-agnostic (SW only) | Per-library |
|
|
420
459
|
| TanStack Query bridge | `@sweidos/eidos/query` adapter | — | Native |
|
|
421
|
-
| Bundle size (core) | ~6 kB brotli
|
|
460
|
+
| Bundle size (core) | ~6.5 kB brotli | ~3-6 kB (modular) | ~13 kB |
|
|
422
461
|
|
|
423
462
|
Not a TanStack Query replacement — `@sweidos/eidos/query` is a thin adapter so
|
|
424
463
|
you keep TQ's cache/devtools while Eidos owns the offline layer. Workbox is a
|
package/dist/action.js
CHANGED
|
@@ -1,104 +1,120 @@
|
|
|
1
|
-
import { useEidosStore as
|
|
2
|
-
import { getSwRegistration as
|
|
3
|
-
import { idbQueueStorage as
|
|
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
4
|
import { _getQueueStorage as S } from "./queue-storage.js";
|
|
5
|
-
import { broadcastQueueSync as
|
|
6
|
-
var
|
|
5
|
+
import { broadcastQueueSync as s } from "./queue-sync.js";
|
|
6
|
+
var g = /* @__PURE__ */ new Map(), I = /* @__PURE__ */ new Map(), Q = /* @__PURE__ */ new Map(), k = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map();
|
|
7
7
|
function c() {
|
|
8
|
-
return S() ??
|
|
8
|
+
return S() ?? C;
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function m() {
|
|
11
11
|
return crypto.randomUUID();
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
return e(...t,
|
|
13
|
+
function v(e, t, n) {
|
|
14
|
+
return e(...t, n);
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const { isOnline:
|
|
22
|
-
let
|
|
16
|
+
function B(e, t) {
|
|
17
|
+
const n = t.name || e.name || m(), a = t.namespace ? `${t.namespace}::${n}` : n;
|
|
18
|
+
if (g.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
|
+
g.set(a, e), k.set(a, t), t.onRollback && I.set(a, t.onRollback), t.conflict && Q.set(a, t.conflict);
|
|
20
|
+
const i = async (...r) => {
|
|
21
|
+
const { isOnline: o } = l.getState(), u = m();
|
|
22
|
+
let b;
|
|
23
23
|
if (t.cancellable) {
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const d = new AbortController();
|
|
25
|
+
p.set(u, d), b = d.signal;
|
|
26
26
|
}
|
|
27
|
-
const
|
|
27
|
+
const y = {
|
|
28
28
|
idempotencyKey: u,
|
|
29
29
|
attempt: 0,
|
|
30
|
-
signal:
|
|
30
|
+
signal: b
|
|
31
31
|
};
|
|
32
|
-
t.onOptimistic?.(...
|
|
32
|
+
t.onOptimistic?.(...r, y);
|
|
33
33
|
try {
|
|
34
34
|
if (t.reliability === "neverLose") {
|
|
35
|
-
if (!
|
|
35
|
+
if (!o) return h(a, a, r, t, u);
|
|
36
36
|
try {
|
|
37
|
-
return await
|
|
38
|
-
} catch (
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
37
|
+
return await v(e, r, y);
|
|
38
|
+
} catch (d) {
|
|
39
|
+
if (x(d)) throw d;
|
|
40
|
+
return h(a, a, r, t, u);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
return await
|
|
45
|
-
} catch (
|
|
46
|
-
throw t.onRollback?.(...
|
|
44
|
+
return await v(e, r, y);
|
|
45
|
+
} catch (d) {
|
|
46
|
+
throw t.onRollback?.(...r, y), d;
|
|
47
47
|
}
|
|
48
48
|
} finally {
|
|
49
|
-
t.cancellable &&
|
|
49
|
+
t.cancellable && p.delete(u);
|
|
50
50
|
}
|
|
51
|
-
}, i = async (n) => {
|
|
52
|
-
const s = y.get(n);
|
|
53
|
-
if (s)
|
|
54
|
-
return s.abort(), !0;
|
|
55
|
-
const u = (await c().getAll()).find((p) => p.idempotencyKey === n && p.status === "pending");
|
|
56
|
-
return u ? (f.getState().removeQueueItem(u.id), d({
|
|
57
|
-
type: "remove",
|
|
58
|
-
id: u.id
|
|
59
|
-
}), await c().remove(u.id), !0) : !1;
|
|
60
51
|
};
|
|
61
|
-
return Object.defineProperty(
|
|
52
|
+
return Object.defineProperty(i, "id", {
|
|
62
53
|
value: a,
|
|
63
54
|
writable: !1
|
|
64
|
-
}), Object.defineProperty(
|
|
55
|
+
}), Object.defineProperty(i, "config", {
|
|
65
56
|
value: t,
|
|
66
57
|
writable: !1
|
|
67
|
-
}), Object.defineProperty(
|
|
68
|
-
value:
|
|
58
|
+
}), Object.defineProperty(i, "cancel", {
|
|
59
|
+
value: _,
|
|
69
60
|
writable: !1
|
|
70
|
-
}),
|
|
61
|
+
}), i;
|
|
62
|
+
}
|
|
63
|
+
async function _(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), s({
|
|
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), s({
|
|
83
|
+
type: "update",
|
|
84
|
+
id: e,
|
|
85
|
+
update: n
|
|
86
|
+
}), await c().update(e, n), !0;
|
|
71
87
|
}
|
|
72
|
-
async function
|
|
73
|
-
const
|
|
88
|
+
async function h(e, t, n, a, i) {
|
|
89
|
+
const r = m(), o = {
|
|
74
90
|
schemaVersion: 2,
|
|
75
|
-
id:
|
|
91
|
+
id: r,
|
|
76
92
|
actionId: e,
|
|
77
93
|
actionName: t,
|
|
78
|
-
idempotencyKey:
|
|
79
|
-
args:
|
|
94
|
+
idempotencyKey: i,
|
|
95
|
+
args: n,
|
|
80
96
|
queuedAt: Date.now(),
|
|
81
97
|
retryCount: 0,
|
|
82
98
|
maxRetries: a.maxRetries ?? 3,
|
|
83
99
|
status: "pending",
|
|
84
100
|
priority: a.priority ?? "normal"
|
|
85
101
|
};
|
|
86
|
-
await c().add(
|
|
102
|
+
await c().add(o), l.getState().addQueueItem(o), l.getState().recordReliabilityEvent("queued");
|
|
87
103
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
104
|
+
const u = A();
|
|
105
|
+
u && "sync" in u && await u.sync.register("eidos-queue-replay");
|
|
90
106
|
} catch {
|
|
91
107
|
}
|
|
92
108
|
return {
|
|
93
109
|
queued: !0,
|
|
94
|
-
id:
|
|
110
|
+
id: r,
|
|
95
111
|
message: `"${t}" queued — will execute when online`
|
|
96
112
|
};
|
|
97
113
|
}
|
|
98
|
-
function
|
|
114
|
+
function x(e) {
|
|
99
115
|
return e instanceof DOMException && e.name === "AbortError";
|
|
100
116
|
}
|
|
101
|
-
function
|
|
117
|
+
function E(e) {
|
|
102
118
|
if (e instanceof Response) return e.status >= 400 && e.status < 500;
|
|
103
119
|
if (typeof e == "object" && e !== null) {
|
|
104
120
|
const t = e.status;
|
|
@@ -106,10 +122,10 @@ function K(e) {
|
|
|
106
122
|
}
|
|
107
123
|
return !1;
|
|
108
124
|
}
|
|
109
|
-
function
|
|
125
|
+
function K(e) {
|
|
110
126
|
return Math.min(2e3 * 2 ** e, 3e5) * (0.8 + Math.random() * 0.4);
|
|
111
127
|
}
|
|
112
|
-
function
|
|
128
|
+
function f() {
|
|
113
129
|
return {
|
|
114
130
|
attempted: 0,
|
|
115
131
|
succeeded: 0,
|
|
@@ -120,171 +136,173 @@ function g() {
|
|
|
120
136
|
cancelled: 0
|
|
121
137
|
};
|
|
122
138
|
}
|
|
123
|
-
var
|
|
139
|
+
var w = !1, q = "eidos-queue-replay";
|
|
124
140
|
async function V() {
|
|
125
|
-
const e =
|
|
126
|
-
if (!e.isOnline) return
|
|
127
|
-
if (typeof navigator < "u" && navigator.locks) return navigator.locks.request(
|
|
128
|
-
if (
|
|
129
|
-
|
|
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 ? R(e) : f());
|
|
144
|
+
if (w) return f();
|
|
145
|
+
w = !0;
|
|
130
146
|
try {
|
|
131
|
-
return await
|
|
147
|
+
return await R(e);
|
|
132
148
|
} finally {
|
|
133
|
-
|
|
149
|
+
w = !1;
|
|
134
150
|
}
|
|
135
151
|
}
|
|
136
|
-
async function
|
|
137
|
-
const
|
|
152
|
+
async function M(e, t) {
|
|
153
|
+
const n = Date.now();
|
|
138
154
|
t.updateQueueItem(e.id, {
|
|
139
155
|
status: "succeeded",
|
|
140
|
-
completedAt:
|
|
141
|
-
}),
|
|
156
|
+
completedAt: n
|
|
157
|
+
}), t.recordReliabilityEvent("succeeded"), s({
|
|
142
158
|
type: "update",
|
|
143
159
|
id: e.id,
|
|
144
160
|
update: {
|
|
145
161
|
status: "succeeded",
|
|
146
|
-
completedAt:
|
|
162
|
+
completedAt: n
|
|
147
163
|
}
|
|
148
164
|
}), await c().update(e.id, {
|
|
149
165
|
status: "succeeded",
|
|
150
|
-
completedAt:
|
|
166
|
+
completedAt: n
|
|
151
167
|
}), setTimeout(() => {
|
|
152
|
-
t.removeQueueItem(e.id),
|
|
168
|
+
t.removeQueueItem(e.id), s({
|
|
153
169
|
type: "remove",
|
|
154
170
|
id: e.id
|
|
155
171
|
}), c().remove(e.id);
|
|
156
172
|
}, 3e3);
|
|
157
173
|
}
|
|
158
|
-
async function
|
|
174
|
+
async function O(e, t, n) {
|
|
159
175
|
const a = Q.get(e.actionId);
|
|
160
|
-
let
|
|
176
|
+
let i;
|
|
161
177
|
if (a) switch (a.strategy) {
|
|
162
178
|
case "serverWins":
|
|
163
|
-
|
|
179
|
+
i = "skip";
|
|
164
180
|
break;
|
|
165
181
|
case "clientWins":
|
|
166
|
-
|
|
182
|
+
i = "retry";
|
|
167
183
|
break;
|
|
168
184
|
case "merge":
|
|
169
185
|
case "custom": {
|
|
170
|
-
const
|
|
171
|
-
error:
|
|
186
|
+
const r = {
|
|
187
|
+
error: n,
|
|
172
188
|
args: e.args,
|
|
173
189
|
attempt: e.retryCount,
|
|
174
190
|
idempotencyKey: e.idempotencyKey
|
|
175
191
|
};
|
|
176
|
-
|
|
192
|
+
i = a.resolve?.(r) ?? "retry";
|
|
177
193
|
break;
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
|
-
if (
|
|
181
|
-
return t.removeQueueItem(e.id),
|
|
196
|
+
if (i === "skip")
|
|
197
|
+
return t.removeQueueItem(e.id), t.recordReliabilityEvent("conflicted"), s({
|
|
182
198
|
type: "remove",
|
|
183
199
|
id: e.id
|
|
184
200
|
}), await c().remove(e.id), "conflicted";
|
|
185
|
-
|
|
201
|
+
i && typeof i == "object" && (e.args = i.resolved, t.updateQueueItem(e.id, { args: i.resolved }), s({
|
|
186
202
|
type: "update",
|
|
187
203
|
id: e.id,
|
|
188
|
-
update: { args:
|
|
189
|
-
}), await c().update(e.id, { args:
|
|
204
|
+
update: { args: i.resolved }
|
|
205
|
+
}), await c().update(e.id, { args: i.resolved }));
|
|
190
206
|
}
|
|
191
|
-
async function P(e, t,
|
|
207
|
+
async function P(e, t, n) {
|
|
192
208
|
const a = e.retryCount + 1;
|
|
193
209
|
if (a >= e.maxRetries) {
|
|
194
|
-
const
|
|
210
|
+
const r = {
|
|
195
211
|
status: "failed",
|
|
196
|
-
error: String(
|
|
212
|
+
error: String(n),
|
|
197
213
|
retryCount: a
|
|
198
214
|
};
|
|
199
|
-
t.updateQueueItem(e.id,
|
|
215
|
+
t.updateQueueItem(e.id, r), t.recordReliabilityEvent("failed"), s({
|
|
200
216
|
type: "update",
|
|
201
217
|
id: e.id,
|
|
202
|
-
update:
|
|
203
|
-
}), await c().update(e.id,
|
|
204
|
-
const
|
|
218
|
+
update: r
|
|
219
|
+
}), await c().update(e.id, r);
|
|
220
|
+
const o = {
|
|
205
221
|
idempotencyKey: e.idempotencyKey,
|
|
206
222
|
attempt: a
|
|
207
223
|
};
|
|
208
|
-
return
|
|
224
|
+
return I.get(e.actionId)?.(...e.args, o), "failed";
|
|
209
225
|
}
|
|
210
|
-
const
|
|
226
|
+
const i = {
|
|
211
227
|
status: "pending",
|
|
212
228
|
retryCount: a,
|
|
213
|
-
nextRetryAt: Date.now() +
|
|
229
|
+
nextRetryAt: Date.now() + K(a)
|
|
214
230
|
};
|
|
215
|
-
return t.updateQueueItem(e.id,
|
|
231
|
+
return t.updateQueueItem(e.id, i), t.recordReliabilityEvent("retried"), s({
|
|
216
232
|
type: "update",
|
|
217
233
|
id: e.id,
|
|
218
|
-
update:
|
|
219
|
-
}), await c().update(e.id,
|
|
234
|
+
update: i
|
|
235
|
+
}), await c().update(e.id, i), "retrying";
|
|
220
236
|
}
|
|
221
237
|
async function D(e, t) {
|
|
222
|
-
const
|
|
223
|
-
if (!
|
|
224
|
-
const a =
|
|
225
|
-
let
|
|
238
|
+
const n = g.get(e.actionId);
|
|
239
|
+
if (!n) return "skipped";
|
|
240
|
+
const a = k.get(e.actionId)?.cancellable;
|
|
241
|
+
let i;
|
|
226
242
|
if (a) {
|
|
227
|
-
const
|
|
228
|
-
|
|
243
|
+
const o = new AbortController();
|
|
244
|
+
p.set(e.idempotencyKey, o), i = o.signal;
|
|
229
245
|
}
|
|
230
|
-
const
|
|
246
|
+
const r = {
|
|
231
247
|
idempotencyKey: e.idempotencyKey,
|
|
232
248
|
attempt: e.retryCount,
|
|
233
|
-
signal:
|
|
249
|
+
signal: i
|
|
234
250
|
};
|
|
235
251
|
try {
|
|
236
|
-
return await
|
|
237
|
-
} catch (
|
|
238
|
-
if (
|
|
239
|
-
return t.removeQueueItem(e.id),
|
|
252
|
+
return await v(n, e.args, r), await M(e, t), "succeeded";
|
|
253
|
+
} catch (o) {
|
|
254
|
+
if (x(o))
|
|
255
|
+
return t.removeQueueItem(e.id), t.recordReliabilityEvent("cancelled"), s({
|
|
240
256
|
type: "remove",
|
|
241
257
|
id: e.id
|
|
242
258
|
}), await c().remove(e.id), "cancelled";
|
|
243
|
-
if (
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
259
|
+
if (E(o)) {
|
|
260
|
+
const u = await O(e, t, o);
|
|
261
|
+
if (u) return u;
|
|
246
262
|
}
|
|
247
|
-
return P(e, t,
|
|
263
|
+
return P(e, t, o);
|
|
248
264
|
} finally {
|
|
249
|
-
a &&
|
|
265
|
+
a && p.delete(e.idempotencyKey);
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
|
-
async function j(e, t,
|
|
268
|
+
async function j(e, t, n) {
|
|
253
269
|
if (e.length === 0) return;
|
|
254
|
-
const a = e.filter((
|
|
255
|
-
if (
|
|
256
|
-
const
|
|
257
|
-
id:
|
|
270
|
+
const a = e.filter((r) => g.has(r.actionId));
|
|
271
|
+
if (n.skipped += e.length - a.length, a.length > 0) {
|
|
272
|
+
const r = a.map((o) => ({
|
|
273
|
+
id: o.id,
|
|
258
274
|
update: { status: "replaying" }
|
|
259
275
|
}));
|
|
260
|
-
t.batchUpdateQueueItems(
|
|
276
|
+
t.batchUpdateQueueItems(r), s({
|
|
261
277
|
type: "batchUpdate",
|
|
262
|
-
updates:
|
|
278
|
+
updates: r
|
|
263
279
|
});
|
|
264
|
-
for (const
|
|
280
|
+
for (const o of a) c().update(o.id, { status: "replaying" });
|
|
265
281
|
}
|
|
266
|
-
const
|
|
267
|
-
for (const
|
|
268
|
-
const
|
|
269
|
-
|
|
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]++);
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
|
-
async function
|
|
273
|
-
const t = await c().getPending(),
|
|
274
|
-
for (const
|
|
288
|
+
async function R(e) {
|
|
289
|
+
const t = await c().getPending(), n = Date.now(), a = t.filter((r) => r.retryCount < r.maxRetries && (!r.nextRetryAt || r.nextRetryAt <= n)), i = f();
|
|
290
|
+
for (const r of [
|
|
275
291
|
"high",
|
|
276
292
|
"normal",
|
|
277
293
|
"low"
|
|
278
|
-
]) await j(a.filter((
|
|
279
|
-
return
|
|
294
|
+
]) await j(a.filter((o) => (o.priority ?? "normal") === r), e, i);
|
|
295
|
+
return i;
|
|
280
296
|
}
|
|
281
297
|
async function Y() {
|
|
282
|
-
await c().clear(),
|
|
298
|
+
await c().clear(), l.getState().hydrateQueue([]);
|
|
283
299
|
}
|
|
284
300
|
export {
|
|
285
|
-
|
|
301
|
+
B as action,
|
|
302
|
+
_ as cancelByIdempotencyKey,
|
|
286
303
|
Y as clearQueue,
|
|
287
|
-
V as replayQueue
|
|
304
|
+
V as replayQueue,
|
|
305
|
+
F as requeueItem
|
|
288
306
|
};
|
|
289
307
|
|
|
290
308
|
//# sourceMappingURL=action.js.map
|