@sweidos/eidos 1.1.0 → 2.0.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 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** | `onConflict` decides per 4xx whether to retry or drop a queued action. |
124
- | **Queue prioritization** | `priority: 'high' \| 'normal' \| 'low'` — high items replay before normal. |
125
- | **Cache warming** | `warmCache(handles[])` bulk-prefetches resources on login/init. |
126
- | **URL patterns** | `/api/products/*`, `/api/users/:id`, `**` wildcardsSW intercepts all matches. |
127
- | **Background Sync** | Registers a `sync` tag so queued actions replay even after tab close. |
128
- | **Devtools panel** | `<EidosDevtools />` live queue, cache state, offline toggle, no CSS import. |
129
- | **Testing helpers** | `mockOffline`, `drainQueue`, `resetEidos`, `getCachedEntry` for Vitest/Jest. |
130
- | **OpenAPI codegen** | `npx eidos-gen openapi.json` generates typed `resource()` + `action()` declarations. |
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
 
@@ -175,20 +177,40 @@ products.query() // { queryKey, queryFn } for useQuery
175
177
  | `offline: true, strategy: 'cache-first'` | CacheFirst | Static assets, config data |
176
178
  | `offline: true, strategy: 'network-first'` | NetworkFirst | Always-fresh with offline fallback |
177
179
 
178
- URL patterns work on any handle: `/api/products/*`, `/api/users/:id`, `**`
180
+ ### `resourcePattern(pattern, config)`
181
+
182
+ For URL patterns — `/api/products/*`, `/api/users/:id`, `**` — the SW intercepts
183
+ all matching requests automatically, so there's no single URL to fetch. Use
184
+ `resourcePattern()` instead of `resource()`; it returns a handle with only
185
+ `invalidate()` and `unregister()`:
186
+
187
+ ```ts
188
+ const productPattern = resourcePattern('/api/products/*', { offline: true });
189
+
190
+ await productPattern.invalidate(); // clear all cached entries matching the pattern
191
+ productPattern.unregister();
192
+ ```
179
193
 
180
194
  ### `action(fn, config)`
181
195
 
182
196
  ```ts
183
- const createOrder = action(async (payload: OrderPayload) => { ... }, {
197
+ const createOrder = action(async (payload: OrderPayload, ctx: ActionContext) => { ... }, {
184
198
  reliability: 'neverLose', // persist to IDB + replay on reconnect
185
199
  name: 'createOrder', // stable name for post-reload replay
200
+ namespace?: string, // prefix actionId — avoids collisions across modules
186
201
  maxRetries?: number, // default: 3
187
202
  priority?: 'high' | 'normal' | 'low',
203
+ cancellable?: boolean, // adds AbortSignal to ctx, enables handle.cancel(key)
188
204
  onOptimistic?: (...args) => void, // instant UI update
189
205
  onRollback?: (...args) => void, // revert on permanent failure
190
- onConflict?: (error, args) => 'retry' | 'skip', // 4xx handler
206
+ conflict?: { // 4xx replay handling
207
+ strategy: 'serverWins' | 'clientWins' | 'merge' | 'custom',
208
+ resolve?: (ctx) => 'retry' | 'skip' | { resolved: args },
209
+ },
191
210
  })
211
+
212
+ // ctx.idempotencyKey is stable across retries — forward as e.g. an
213
+ // `Idempotency-Key` header so the server can dedupe replayed writes.
192
214
  ```
193
215
 
194
216
  ### React hooks
@@ -233,6 +255,81 @@ const mutation = useEidosMutation(createOrder, {
233
255
 
234
256
  ---
235
257
 
258
+ ## Push Notifications
259
+
260
+ Headless, framework-agnostic Web Push. Tree-shaken via a separate subpath — adds zero bytes unless imported.
261
+
262
+ **1. Generate VAPID keys (one-time):**
263
+
264
+ ```sh
265
+ npx @sweidos/eidos generate-vapid-keys
266
+ ```
267
+
268
+ Detects your framework (Vite/Next/SvelteKit/Nuxt) and writes a correctly-prefixed
269
+ public key + an unprefixed private key to `.env.local`:
270
+
271
+ ```
272
+ VITE_EIDOS_VAPID_PUBLIC_KEY=...
273
+ EIDOS_VAPID_PRIVATE_KEY=...
274
+ ```
275
+
276
+ Give `EIDOS_VAPID_PRIVATE_KEY` (and the public key) to your backend. What the
277
+ backend does with them — language, storage, send timing — is entirely its own
278
+ concern; Eidos never talks to it directly.
279
+
280
+ **2. Register handlers once at app init (any tab, no permission prompt):**
281
+
282
+ ```ts
283
+ import { registerPushHandlers } from '@sweidos/eidos/push';
284
+
285
+ registerPushHandlers({
286
+ onNotificationClick: (data) => router.push(data.url),
287
+ onSubscriptionExpired: (sub) =>
288
+ fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
289
+ });
290
+ ```
291
+
292
+ **3. Subscribe from a user gesture (e.g. an "Enable notifications" button):**
293
+
294
+ ```ts
295
+ import { subscribeToPush, isPushSupported, getPushPermissionState } from '@sweidos/eidos/push';
296
+
297
+ async function onEnableClick() {
298
+ const result = await subscribeToPush({
299
+ vapidPublicKey: import.meta.env.VITE_EIDOS_VAPID_PUBLIC_KEY,
300
+ onSubscribe: (sub) =>
301
+ fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
302
+ });
303
+
304
+ if (result.status === 'subscribed') toast('Notifications enabled');
305
+ else if (result.status === 'denied') toast('Permission denied');
306
+ }
307
+ ```
308
+
309
+ `isPushSupported()` / `getPushPermissionState()` / `getPushUnsupportedReason()`
310
+ let you hide the button when push is unavailable (e.g. iOS Safari outside an
311
+ installed PWA returns `'ios-not-installed'`).
312
+
313
+ ### Server payload schema
314
+
315
+ The service worker shows whatever your server sends — Eidos never renders UI:
316
+
317
+ ```json
318
+ {
319
+ "title": "Order shipped",
320
+ "body": "Your order #1234 is on its way",
321
+ "icon": "/icon.png",
322
+ "badge": "/badge.png",
323
+ "tag": "order-1234",
324
+ "data": { "url": "/orders/1234" }
325
+ }
326
+ ```
327
+
328
+ Click behavior: if the app is open, `data` is delivered to `onNotificationClick`
329
+ for client-side routing; otherwise the SW opens `data.url` directly.
330
+
331
+ ---
332
+
236
333
  ## Testing
237
334
 
238
335
  ```ts
@@ -314,21 +411,19 @@ Panel shows: live queue state · cache entries · SW status · offline simulatio
314
411
 
315
412
  ## How it compares
316
413
 
317
- | | **Eidos** | Workbox | RTK Query / TanStack Query |
318
- | -------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------- |
319
- | Service worker setup | Generated for you — `resource()`/`action()` declarations drive the SW | Hand-write `routing` + `strategies` config | None — no SW |
320
- | Caching strategy | Auto-derived from intent (`offline: true` → SWR, etc.), inspectable via devtools | Manually chosen per route | Configurable `staleTime`/`gcTime`, no Cache Storage integration |
321
- | Offline writes | `action()` + `reliability: 'neverLose'` → IndexedDB queue, auto-replay, exponential backoff | Background Sync plugin, you wire the queue | No built-in offline mutation queue |
322
- | Framework support | React, Svelte, Vue, Next.js, React Native, vanilla JS | Framework-agnostic (SW only) | Per-library (RTK Query = Redux, TanStack = many) |
323
- | TanStack Query bridge | `@sweidos/eidos/query` — drop-in `useEidosQuery`/`useEidosMutation` | — | Native |
324
- | Bundle size (core, brotli) | ~5.4 kB | ~3-6 kB (modular) | ~13 kB (TanStack Query core) |
325
-
326
- Eidos isn't a replacement for TanStack Query — `@sweidos/eidos/query` is a thin
327
- adapter so you keep TQ's cache/devtools while Eidos owns the offline layer
328
- (SW caching + IndexedDB write queue). Workbox is a lower-level toolkit Eidos
329
- generates strategies _for_; Eidos picks and configures the strategy from your
330
- `resource()`/`action()` declarations instead of you writing `workbox-*` config
331
- by hand.
414
+ | | **Eidos** | Workbox | RTK Query / TanStack Query |
415
+ | --------------------- | ----------------------------------------------------- | ---------------------------- | -------------------------- |
416
+ | Service worker setup | Generated from `resource()`/`action()` declarations | Hand-written routing config | None — no SW |
417
+ | Caching strategy | Auto-derived from intent, inspectable via devtools | Manually chosen per route | `staleTime`/`gcTime` only |
418
+ | Offline writes | IndexedDB queue, auto-replay + backoff via `action()` | Background Sync, you wire it | No built-in mutation queue |
419
+ | Framework support | React, Svelte, Vue, Next.js, React Native, vanilla JS | Framework-agnostic (SW only) | Per-library |
420
+ | TanStack Query bridge | `@sweidos/eidos/query` adapter | — | Native |
421
+ | Bundle size (core) | ~6 kB brotli | ~3-6 kB (modular) | ~13 kB |
422
+
423
+ Not a TanStack Query replacement — `@sweidos/eidos/query` is a thin adapter so
424
+ you keep TQ's cache/devtools while Eidos owns the offline layer. Workbox is a
425
+ lower-level toolkit; Eidos picks and configures strategies for you instead of
426
+ hand-written `workbox-*` config.
332
427
 
333
428
  ---
334
429
 
package/dist/action.js CHANGED
@@ -1,57 +1,92 @@
1
- import { useEidosStore as s } from "./store.js";
2
- import { getSwRegistration as w } from "./sw-bridge.js";
3
- import { idbQueueStorage as g } from "./idb.js";
4
- import { _getQueueStorage as m } from "./queue-storage.js";
5
- var d = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map(), f = /* @__PURE__ */ new Map();
6
- function u() {
7
- return m() ?? g;
8
- }
9
- function y() {
1
+ import { useEidosStore as f } from "./store.js";
2
+ import { getSwRegistration as _ } from "./sw-bridge.js";
3
+ import { idbQueueStorage as A } from "./idb.js";
4
+ import { _getQueueStorage as S } from "./queue-storage.js";
5
+ import { broadcastQueueSync as d } from "./queue-sync.js";
6
+ var m = /* @__PURE__ */ new Map(), k = /* @__PURE__ */ new Map(), Q = /* @__PURE__ */ new Map(), x = /* @__PURE__ */ new Map(), y = /* @__PURE__ */ new Map();
7
+ function c() {
8
+ return S() ?? A;
9
+ }
10
+ function b() {
10
11
  return crypto.randomUUID();
11
12
  }
12
- function C(e, t) {
13
- const r = t.name || e.name || y();
14
- d.set(r, e), t.onRollback && p.set(r, t.onRollback), t.onConflict && f.set(r, t.onConflict);
15
- const a = async (...i) => {
16
- const { isOnline: n } = s.getState();
17
- if (t.onOptimistic?.(...i), t.reliability === "neverLose") {
18
- if (!n) return l(r, r, i, t);
19
- try {
20
- return await e(...i);
21
- } catch {
22
- return l(r, r, i, t);
23
- }
13
+ function h(e, t, o) {
14
+ return e(...t, o);
15
+ }
16
+ function F(e, t) {
17
+ const o = t.name || e.name || b(), a = t.namespace ? `${t.namespace}::${o}` : o;
18
+ if (m.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
+ m.set(a, e), x.set(a, t), t.onRollback && k.set(a, t.onRollback), t.conflict && Q.set(a, t.conflict);
20
+ const r = async (...n) => {
21
+ const { isOnline: s } = f.getState(), u = b();
22
+ let p;
23
+ if (t.cancellable) {
24
+ const l = new AbortController();
25
+ y.set(u, l), p = l.signal;
24
26
  }
27
+ const w = {
28
+ idempotencyKey: u,
29
+ attempt: 0,
30
+ signal: p
31
+ };
32
+ t.onOptimistic?.(...n, w);
25
33
  try {
26
- return await e(...i);
27
- } catch (o) {
28
- throw t.onRollback?.(...i), o;
34
+ if (t.reliability === "neverLose") {
35
+ if (!s) return R(a, a, n, t, u);
36
+ try {
37
+ return await h(e, n, w);
38
+ } catch (l) {
39
+ if (C(l)) throw l;
40
+ return R(a, a, n, t, u);
41
+ }
42
+ }
43
+ try {
44
+ return await h(e, n, w);
45
+ } catch (l) {
46
+ throw t.onRollback?.(...n, w), l;
47
+ }
48
+ } finally {
49
+ t.cancellable && y.delete(u);
29
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;
30
60
  };
31
- return Object.defineProperty(a, "id", {
32
- value: r,
61
+ return Object.defineProperty(r, "id", {
62
+ value: a,
33
63
  writable: !1
34
- }), Object.defineProperty(a, "config", {
64
+ }), Object.defineProperty(r, "config", {
35
65
  value: t,
36
66
  writable: !1
37
- }), a;
67
+ }), Object.defineProperty(r, "cancel", {
68
+ value: i,
69
+ writable: !1
70
+ }), r;
38
71
  }
39
- async function l(e, t, r, a) {
40
- const i = y(), n = {
72
+ async function R(e, t, o, a, r) {
73
+ const i = b(), n = {
74
+ schemaVersion: 2,
41
75
  id: i,
42
76
  actionId: e,
43
77
  actionName: t,
44
- args: r,
78
+ idempotencyKey: r,
79
+ args: o,
45
80
  queuedAt: Date.now(),
46
81
  retryCount: 0,
47
82
  maxRetries: a.maxRetries ?? 3,
48
83
  status: "pending",
49
84
  priority: a.priority ?? "normal"
50
85
  };
51
- await u().add(n), s.getState().addQueueItem(n);
86
+ await c().add(n), f.getState().addQueueItem(n);
52
87
  try {
53
- const o = w();
54
- o && "sync" in o && await o.sync.register("eidos-queue-replay");
88
+ const s = _();
89
+ s && "sync" in s && await s.sync.register("eidos-queue-replay");
55
90
  } catch {
56
91
  }
57
92
  return {
@@ -60,7 +95,10 @@ async function l(e, t, r, a) {
60
95
  message: `"${t}" queued — will execute when online`
61
96
  };
62
97
  }
63
- function h(e) {
98
+ function C(e) {
99
+ return e instanceof DOMException && e.name === "AbortError";
100
+ }
101
+ function K(e) {
64
102
  if (e instanceof Response) return e.status >= 400 && e.status < 500;
65
103
  if (typeof e == "object" && e !== null) {
66
104
  const t = e.status;
@@ -68,112 +106,185 @@ function h(e) {
68
106
  }
69
107
  return !1;
70
108
  }
71
- function R(e) {
109
+ function M(e) {
72
110
  return Math.min(2e3 * 2 ** e, 3e5) * (0.8 + Math.random() * 0.4);
73
111
  }
74
- var c = !1;
75
- async function _() {
76
- const e = s.getState();
77
- if (!e.isOnline || c) return {
112
+ function g() {
113
+ return {
78
114
  attempted: 0,
79
115
  succeeded: 0,
80
116
  failed: 0,
81
117
  retrying: 0,
82
118
  skipped: 0,
83
- conflicted: 0
119
+ conflicted: 0,
120
+ cancelled: 0
84
121
  };
85
- c = !0;
122
+ }
123
+ var v = !1, O = "eidos-queue-replay";
124
+ async function V() {
125
+ const e = f.getState();
126
+ if (!e.isOnline) return g();
127
+ if (typeof navigator < "u" && navigator.locks) return navigator.locks.request(O, { ifAvailable: !0 }, async (t) => t ? I(e) : g());
128
+ if (v) return g();
129
+ v = !0;
86
130
  try {
87
- return await Q(e);
131
+ return await I(e);
88
132
  } finally {
89
- c = !1;
133
+ v = !1;
90
134
  }
91
135
  }
92
- async function b(e, t) {
93
- const r = d.get(e.actionId);
94
- if (!r) return "skipped";
95
- try {
96
- await r(...e.args);
97
- const a = Date.now();
98
- return t.updateQueueItem(e.id, {
136
+ async function q(e, t) {
137
+ const o = Date.now();
138
+ t.updateQueueItem(e.id, {
139
+ status: "succeeded",
140
+ completedAt: o
141
+ }), d({
142
+ type: "update",
143
+ id: e.id,
144
+ update: {
99
145
  status: "succeeded",
100
- completedAt: a
101
- }), await u().update(e.id, {
102
- status: "succeeded",
103
- completedAt: a
104
- }), setTimeout(() => {
105
- t.removeQueueItem(e.id), u().remove(e.id);
106
- }, 3e3), "succeeded";
107
- } catch (a) {
108
- if (h(a)) {
109
- const n = f.get(e.actionId);
110
- if (n && n(a, e.args) === "skip")
111
- return t.removeQueueItem(e.id), await u().remove(e.id), "conflicted";
146
+ completedAt: o
112
147
  }
113
- const i = e.retryCount + 1;
114
- if (i >= e.maxRetries)
115
- return t.updateQueueItem(e.id, {
116
- status: "failed",
117
- error: String(a),
118
- retryCount: i
119
- }), await u().update(e.id, {
120
- status: "failed",
121
- error: String(a),
122
- retryCount: i
123
- }), p.get(e.actionId)?.(...e.args), "failed";
124
- {
125
- const n = Date.now() + R(i);
126
- return t.updateQueueItem(e.id, {
127
- status: "pending",
128
- retryCount: i,
129
- nextRetryAt: n
130
- }), await u().update(e.id, {
131
- status: "pending",
132
- retryCount: i,
133
- nextRetryAt: n
134
- }), "retrying";
148
+ }), await c().update(e.id, {
149
+ status: "succeeded",
150
+ completedAt: o
151
+ }), setTimeout(() => {
152
+ t.removeQueueItem(e.id), d({
153
+ type: "remove",
154
+ id: e.id
155
+ }), c().remove(e.id);
156
+ }, 3e3);
157
+ }
158
+ async function E(e, t, o) {
159
+ const a = Q.get(e.actionId);
160
+ let r;
161
+ if (a) switch (a.strategy) {
162
+ case "serverWins":
163
+ r = "skip";
164
+ break;
165
+ case "clientWins":
166
+ r = "retry";
167
+ break;
168
+ case "merge":
169
+ case "custom": {
170
+ const i = {
171
+ error: o,
172
+ args: e.args,
173
+ attempt: e.retryCount,
174
+ idempotencyKey: e.idempotencyKey
175
+ };
176
+ r = a.resolve?.(i) ?? "retry";
177
+ break;
135
178
  }
136
179
  }
180
+ if (r === "skip")
181
+ return t.removeQueueItem(e.id), d({
182
+ type: "remove",
183
+ id: e.id
184
+ }), await c().remove(e.id), "conflicted";
185
+ r && typeof r == "object" && (e.args = r.resolved, t.updateQueueItem(e.id, { args: r.resolved }), d({
186
+ type: "update",
187
+ id: e.id,
188
+ update: { args: r.resolved }
189
+ }), await c().update(e.id, { args: r.resolved }));
137
190
  }
138
- async function I(e, t, r) {
191
+ async function P(e, t, o) {
192
+ const a = e.retryCount + 1;
193
+ if (a >= e.maxRetries) {
194
+ const i = {
195
+ status: "failed",
196
+ error: String(o),
197
+ retryCount: a
198
+ };
199
+ t.updateQueueItem(e.id, i), d({
200
+ type: "update",
201
+ id: e.id,
202
+ update: i
203
+ }), await c().update(e.id, i);
204
+ const n = {
205
+ idempotencyKey: e.idempotencyKey,
206
+ attempt: a
207
+ };
208
+ return k.get(e.actionId)?.(...e.args, n), "failed";
209
+ }
210
+ const r = {
211
+ status: "pending",
212
+ retryCount: a,
213
+ nextRetryAt: Date.now() + M(a)
214
+ };
215
+ return t.updateQueueItem(e.id, r), d({
216
+ type: "update",
217
+ id: e.id,
218
+ update: r
219
+ }), await c().update(e.id, r), "retrying";
220
+ }
221
+ async function D(e, t) {
222
+ const o = m.get(e.actionId);
223
+ if (!o) return "skipped";
224
+ const a = x.get(e.actionId)?.cancellable;
225
+ let r;
226
+ if (a) {
227
+ const n = new AbortController();
228
+ y.set(e.idempotencyKey, n), r = n.signal;
229
+ }
230
+ const i = {
231
+ idempotencyKey: e.idempotencyKey,
232
+ attempt: e.retryCount,
233
+ signal: r
234
+ };
235
+ try {
236
+ return await h(o, e.args, i), await q(e, t), "succeeded";
237
+ } catch (n) {
238
+ if (C(n))
239
+ return t.removeQueueItem(e.id), d({
240
+ type: "remove",
241
+ id: e.id
242
+ }), await c().remove(e.id), "cancelled";
243
+ if (K(n)) {
244
+ const s = await E(e, t, n);
245
+ if (s) return s;
246
+ }
247
+ return P(e, t, n);
248
+ } finally {
249
+ a && y.delete(e.idempotencyKey);
250
+ }
251
+ }
252
+ async function j(e, t, o) {
139
253
  if (e.length === 0) return;
140
- const a = e.filter((n) => d.has(n.actionId));
141
- if (r.skipped += e.length - a.length, a.length > 0) {
142
- t.batchUpdateQueueItems(a.map((n) => ({
254
+ const a = e.filter((i) => m.has(i.actionId));
255
+ if (o.skipped += e.length - a.length, a.length > 0) {
256
+ const i = a.map((n) => ({
143
257
  id: n.id,
144
258
  update: { status: "replaying" }
145
- })));
146
- for (const n of a) u().update(n.id, { status: "replaying" });
259
+ }));
260
+ t.batchUpdateQueueItems(i), d({
261
+ type: "batchUpdate",
262
+ updates: i
263
+ });
264
+ for (const n of a) c().update(n.id, { status: "replaying" });
147
265
  }
148
- const i = await Promise.allSettled(a.map((n) => b(n, t)));
149
- for (const n of i) {
150
- const o = n.status === "fulfilled" ? n.value : "failed";
151
- o === "skipped" ? r.skipped++ : o === "conflicted" ? r.conflicted++ : (r.attempted++, r[o]++);
266
+ const r = await Promise.allSettled(a.map((i) => D(i, t)));
267
+ for (const i of r) {
268
+ const n = i.status === "fulfilled" ? i.value : "failed";
269
+ n === "skipped" ? o.skipped++ : n === "conflicted" ? o.conflicted++ : n === "cancelled" ? o.cancelled++ : (o.attempted++, o[n]++);
152
270
  }
153
271
  }
154
- async function Q(e) {
155
- const t = await u().getPending(), r = Date.now(), a = t.filter((n) => n.retryCount < n.maxRetries && (!n.nextRetryAt || n.nextRetryAt <= r)), i = {
156
- attempted: 0,
157
- succeeded: 0,
158
- failed: 0,
159
- retrying: 0,
160
- skipped: 0,
161
- conflicted: 0
162
- };
163
- for (const n of [
272
+ async function I(e) {
273
+ const t = await c().getPending(), o = Date.now(), a = t.filter((i) => i.retryCount < i.maxRetries && (!i.nextRetryAt || i.nextRetryAt <= o)), r = g();
274
+ for (const i of [
164
275
  "high",
165
276
  "normal",
166
277
  "low"
167
- ]) await I(a.filter((o) => (o.priority ?? "normal") === n), e, i);
168
- return i;
278
+ ]) await j(a.filter((n) => (n.priority ?? "normal") === i), e, r);
279
+ return r;
169
280
  }
170
- async function A() {
171
- await u().clear(), s.getState().hydrateQueue([]);
281
+ async function Y() {
282
+ await c().clear(), f.getState().hydrateQueue([]);
172
283
  }
173
284
  export {
174
- C as action,
175
- A as clearQueue,
176
- _ as replayQueue
285
+ F as action,
286
+ Y as clearQueue,
287
+ V as replayQueue
177
288
  };
178
289
 
179
290
  //# sourceMappingURL=action.js.map