@sweidos/eidos 1.2.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/dist/devtools.js CHANGED
@@ -89,6 +89,21 @@ var useEidosStore = {
89
89
  }
90
90
  };
91
91
  //#endregion
92
+ //#region src/types.ts
93
+ /** Single pass over the queue — avoids separate .filter() calls per status. */
94
+ function countQueueByStatus(queue) {
95
+ let pending = 0, failed = 0, replaying = 0;
96
+ for (const q of queue) if (q.status === "pending") pending++;
97
+ else if (q.status === "failed") failed++;
98
+ else if (q.status === "replaying") replaying++;
99
+ return {
100
+ pending,
101
+ failed,
102
+ replaying,
103
+ total: queue.length
104
+ };
105
+ }
106
+ //#endregion
92
107
  //#region src/react/hooks.ts
93
108
  function useStore(selector) {
94
109
  const fn = selector ?? ((s) => s);
@@ -121,11 +136,8 @@ function useEidosStatus() {
121
136
  */
122
137
  function useEidosQueueStats() {
123
138
  const [p, f, r, t] = useStore((s) => {
124
- let pending = 0, failed = 0, replaying = 0;
125
- for (const q of s.queue) if (q.status === "pending") pending++;
126
- else if (q.status === "failed") failed++;
127
- else if (q.status === "replaying") replaying++;
128
- return `${pending},${failed},${replaying},${s.queue.length}`;
139
+ const { pending, failed, replaying, total } = countQueueByStatus(s.queue);
140
+ return `${pending},${failed},${replaying},${total}`;
129
141
  }).split(",");
130
142
  return {
131
143
  pending: +p,
@@ -262,10 +274,29 @@ function _getQueueStorage() {
262
274
  return _storage;
263
275
  }
264
276
  //#endregion
277
+ //#region src/queue-sync.ts
278
+ var CHANNEL_NAME = "eidos-queue-sync";
279
+ var _channel;
280
+ function getChannel() {
281
+ if (_channel !== void 0) return _channel;
282
+ _channel = typeof BroadcastChannel === "undefined" ? null : new BroadcastChannel(CHANNEL_NAME);
283
+ return _channel;
284
+ }
285
+ /**
286
+ * Broadcasts a queue-item status change to other tabs sharing the same
287
+ * IndexedDB queue. The replay-lock holder (see `replayQueue` in action.ts)
288
+ * is the only tab that mutates queue-item status, so non-leader tabs would
289
+ * otherwise show stale status until their own store re-hydrates.
290
+ *
291
+ * No-ops in environments without BroadcastChannel (React Native, old Safari).
292
+ */
293
+ function broadcastQueueSync(message) {
294
+ getChannel()?.postMessage(message);
295
+ }
296
+ //#endregion
265
297
  //#region src/action.ts
266
298
  var _actionRegistry = /* @__PURE__ */ new Map();
267
299
  var _rollbackRegistry = /* @__PURE__ */ new Map();
268
- var _conflictRegistry = /* @__PURE__ */ new Map();
269
300
  var _conflictConfigRegistry = /* @__PURE__ */ new Map();
270
301
  var _configRegistry = /* @__PURE__ */ new Map();
271
302
  var _inflightControllers = /* @__PURE__ */ new Map();
@@ -317,6 +348,116 @@ async function replayQueue() {
317
348
  _replaying = false;
318
349
  }
319
350
  }
351
+ async function _markSucceeded(item, store) {
352
+ const completedAt = Date.now();
353
+ store.updateQueueItem(item.id, {
354
+ status: "succeeded",
355
+ completedAt
356
+ });
357
+ broadcastQueueSync({
358
+ type: "update",
359
+ id: item.id,
360
+ update: {
361
+ status: "succeeded",
362
+ completedAt
363
+ }
364
+ });
365
+ await qs().update(item.id, {
366
+ status: "succeeded",
367
+ completedAt
368
+ });
369
+ setTimeout(() => {
370
+ store.removeQueueItem(item.id);
371
+ broadcastQueueSync({
372
+ type: "remove",
373
+ id: item.id
374
+ });
375
+ qs().remove(item.id);
376
+ }, 3e3);
377
+ }
378
+ /**
379
+ * Resolves a 4xx error against the action's conflict strategy.
380
+ * Returns 'conflicted' if the item was dropped, undefined if normal
381
+ * retry/fail logic should run (possibly with `item.args` rewritten by `merge`).
382
+ */
383
+ async function _resolveConflict(item, store, err) {
384
+ const conflictConfig = _conflictConfigRegistry.get(item.actionId);
385
+ let resolution;
386
+ if (conflictConfig) switch (conflictConfig.strategy) {
387
+ case "serverWins":
388
+ resolution = "skip";
389
+ break;
390
+ case "clientWins":
391
+ resolution = "retry";
392
+ break;
393
+ case "merge":
394
+ case "custom": {
395
+ const ctx = {
396
+ error: err,
397
+ args: item.args,
398
+ attempt: item.retryCount,
399
+ idempotencyKey: item.idempotencyKey
400
+ };
401
+ resolution = conflictConfig.resolve?.(ctx) ?? "retry";
402
+ break;
403
+ }
404
+ }
405
+ if (resolution === "skip") {
406
+ store.removeQueueItem(item.id);
407
+ broadcastQueueSync({
408
+ type: "remove",
409
+ id: item.id
410
+ });
411
+ await qs().remove(item.id);
412
+ return "conflicted";
413
+ }
414
+ if (resolution && typeof resolution === "object") {
415
+ item.args = resolution.resolved;
416
+ store.updateQueueItem(item.id, { args: resolution.resolved });
417
+ broadcastQueueSync({
418
+ type: "update",
419
+ id: item.id,
420
+ update: { args: resolution.resolved }
421
+ });
422
+ await qs().update(item.id, { args: resolution.resolved });
423
+ }
424
+ }
425
+ async function _scheduleRetryOrFail(item, store, err) {
426
+ const retryCount = item.retryCount + 1;
427
+ if (retryCount >= item.maxRetries) {
428
+ const update = {
429
+ status: "failed",
430
+ error: String(err),
431
+ retryCount
432
+ };
433
+ store.updateQueueItem(item.id, update);
434
+ broadcastQueueSync({
435
+ type: "update",
436
+ id: item.id,
437
+ update
438
+ });
439
+ await qs().update(item.id, update);
440
+ const ctx = {
441
+ idempotencyKey: item.idempotencyKey,
442
+ attempt: retryCount
443
+ };
444
+ _rollbackRegistry.get(item.actionId)?.(...item.args, ctx);
445
+ return "failed";
446
+ }
447
+ const update = {
448
+ status: "pending",
449
+ retryCount,
450
+ nextRetryAt: Date.now() + backoffMs(retryCount)
451
+ };
452
+ store.updateQueueItem(item.id, update);
453
+ broadcastQueueSync({
454
+ type: "update",
455
+ id: item.id,
456
+ update
457
+ });
458
+ await qs().update(item.id, update);
459
+ return "retrying";
460
+ }
320
461
  async function _replayItem(item, store) {
321
462
  const fn = _actionRegistry.get(item.actionId);
322
463
  if (!fn) return "skipped";
@@ -334,92 +475,23 @@ async function _replayItem(item, store) {
334
475
  };
335
476
  try {
336
477
  await callWithContext(fn, item.args, ctx);
337
- const completedAt = Date.now();
338
- store.updateQueueItem(item.id, {
339
- status: "succeeded",
340
- completedAt
341
- });
342
- await qs().update(item.id, {
343
- status: "succeeded",
344
- completedAt
345
- });
346
- setTimeout(() => {
347
- store.removeQueueItem(item.id);
348
- qs().remove(item.id);
349
- }, 3e3);
478
+ await _markSucceeded(item, store);
350
479
  return "succeeded";
351
480
  } catch (err) {
352
481
  if (isAbortError(err)) {
353
482
  store.removeQueueItem(item.id);
483
+ broadcastQueueSync({
484
+ type: "remove",
485
+ id: item.id
486
+ });
354
487
  await qs().remove(item.id);
355
488
  return "cancelled";
356
489
  }
357
490
  if (isClientError(err)) {
358
- const conflictConfig = _conflictConfigRegistry.get(item.actionId);
359
- let resolution;
360
- if (conflictConfig) switch (conflictConfig.strategy) {
361
- case "serverWins":
362
- resolution = "skip";
363
- break;
364
- case "clientWins":
365
- case "lastWriteWins":
366
- resolution = "retry";
367
- break;
368
- case "merge":
369
- case "custom": {
370
- const ctx = {
371
- error: err,
372
- args: item.args,
373
- attempt: item.retryCount,
374
- idempotencyKey: item.idempotencyKey
375
- };
376
- resolution = conflictConfig.resolve?.(ctx) ?? "retry";
377
- break;
378
- }
379
- }
380
- else {
381
- const onConflict = _conflictRegistry.get(item.actionId);
382
- if (onConflict) resolution = onConflict(err, item.args);
383
- }
384
- if (resolution === "skip") {
385
- store.removeQueueItem(item.id);
386
- await qs().remove(item.id);
387
- return "conflicted";
388
- }
389
- if (resolution && typeof resolution === "object") {
390
- item.args = resolution.resolved;
391
- store.updateQueueItem(item.id, { args: resolution.resolved });
392
- await qs().update(item.id, { args: resolution.resolved });
393
- }
394
- }
395
- const retryCount = item.retryCount + 1;
396
- if (retryCount >= item.maxRetries) {
397
- store.updateQueueItem(item.id, {
398
- status: "failed",
399
- error: String(err),
400
- retryCount
401
- });
402
- await qs().update(item.id, {
403
- status: "failed",
404
- error: String(err),
405
- retryCount
406
- });
407
- _rollbackRegistry.get(item.actionId)?.(...item.args);
408
- return "failed";
409
- } else {
410
- const nextRetryAt = Date.now() + backoffMs(retryCount);
411
- store.updateQueueItem(item.id, {
412
- status: "pending",
413
- retryCount,
414
- nextRetryAt
415
- });
416
- await qs().update(item.id, {
417
- status: "pending",
418
- retryCount,
419
- nextRetryAt
420
- });
421
- return "retrying";
491
+ const outcome = await _resolveConflict(item, store, err);
492
+ if (outcome) return outcome;
422
493
  }
494
+ return _scheduleRetryOrFail(item, store, err);
423
495
  } finally {
424
496
  if (cancellable) _inflightControllers.delete(item.idempotencyKey);
425
497
  }
@@ -429,10 +501,15 @@ async function _replayTier(items, store, result) {
429
501
  const replayable = items.filter((item) => _actionRegistry.has(item.actionId));
430
502
  result.skipped += items.length - replayable.length;
431
503
  if (replayable.length > 0) {
432
- store.batchUpdateQueueItems(replayable.map((item) => ({
504
+ const updates = replayable.map((item) => ({
433
505
  id: item.id,
434
506
  update: { status: "replaying" }
435
- })));
507
+ }));
508
+ store.batchUpdateQueueItems(updates);
509
+ broadcastQueueSync({
510
+ type: "batchUpdate",
511
+ updates
512
+ });
436
513
  for (const item of replayable) qs().update(item.id, { status: "replaying" });
437
514
  }
438
515
  const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)));
package/dist/eidos-sw.js CHANGED
@@ -1,3 +1,11 @@
1
+ //#region ../core/src/internal/url-base64.ts
2
+ /** Decodes a base64url string (e.g. a VAPID public key) into raw bytes. */
3
+ function urlBase64ToUint8Array(base64Url) {
4
+ const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
5
+ const raw = atob(base64);
6
+ return Uint8Array.from(raw, (c) => c.charCodeAt(0));
7
+ }
8
+ //#endregion
1
9
  //#region src/sw.ts
2
10
  var CACHE_VERSION = "v1";
3
11
  var CACHE_PREFIX = "eidos";
@@ -267,11 +275,6 @@ self.addEventListener("pushsubscriptionchange", (event) => {
267
275
  });
268
276
  })());
269
277
  });
270
- function urlBase64ToUint8Array(base64Url) {
271
- const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
272
- const raw = atob(base64);
273
- return Uint8Array.from(raw, (c) => c.charCodeAt(0));
274
- }
275
278
  var META_DB = "eidos-sw-meta";
276
279
  var META_STORE = "kv";
277
280
  function openMetaDb() {
package/dist/eidos.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let m=require("react"),H=require("react/jsx-runtime");function ce(e){return{registerResource:(t,n)=>e(r=>({resources:{...r.resources,[t]:n}})),updateResource:(t,n)=>e(r=>({resources:{...r.resources,[t]:r.resources[t]?{...r.resources[t],...n}:r.resources[t]}})),unregisterResource:t=>e(n=>({resources:Object.fromEntries(Object.entries(n.resources).filter(([r])=>r!==t))}))}}function ue(e){return{addQueueItem:t=>e(n=>({queue:[...n.queue,t]})),updateQueueItem:(t,n)=>e(r=>({queue:r.queue.map(s=>s.id===t?{...s,...n}:s)})),batchUpdateQueueItems:t=>e(n=>{const r=new Map(t.map(s=>[s.id,s.update]));return{queue:n.queue.map(s=>{const a=r.get(s.id);return a?{...s,...a}:s})}}),removeQueueItem:t=>e(n=>({queue:n.queue.filter(r=>r.id!==t)})),hydrateQueue:t=>e(()=>({queue:t}))}}var w,D=new Set;function V(){D.forEach(e=>e())}function k(e){w={...w,...e(w)},V()}w={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>k(()=>({isOnline:e})),setSwStatus:(e,t)=>k(()=>({swStatus:e,swError:t})),...ce(k),...ue(k)};function de(){return w}function le(e){return D.add(e),()=>{D.delete(e)}}var u={getState:de,subscribe:le,setState:e=>{const t=typeof e=="function"?e(w):e;w={...w,...t},V()}},v=null,N=[];function G(){return v}async function fe(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){u.getState().setSwStatus("unsupported");return}const t=u.getState();t.setSwStatus("registering");try{v=await navigator.serviceWorker.register(e,{scope:"/"}),await pe(v),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",we),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),Se()}catch(n){t.setSwStatus("error",String(n))}}function pe(e){return new Promise(t=>{if(e.active){t();return}const n=e.installing??e.waiting;if(!n){t();return}const r=setTimeout(t,1e4);n.addEventListener("statechange",function s(){n.state==="activated"&&(clearTimeout(r),n.removeEventListener("statechange",s),t())})})}function R(e){const t=v?.active;t?t.postMessage(e):N.push(e)}var Y=null;function he(e){Y=e}function ye(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&v!==null&&"sync"in v}catch{return!1}}var T={};function ge(e){T=e}function we(e){const t=e.data;if(!t?.type)return;const n=u.getState(),{type:r,url:s}=t;if(r==="EIDOS_BACKGROUND_SYNC"){Y?.();return}if(r==="EIDOS_NOTIFICATION_CLICK"){T.onNotificationClick?.(t.data);return}if(r==="EIDOS_SUBSCRIPTION_EXPIRED"){T.onSubscriptionExpired?.(t.subscription);return}if(s)switch(r){case"EIDOS_CACHE_HIT":{const a=n.resources[s];n.updateResource(s,{status:"fresh",lastEvent:"cache-hit",cacheHits:(a?.cacheHits??0)+1});break}case"EIDOS_CACHE_UPDATED":n.updateResource(s,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break;case"EIDOS_NETWORK_ERROR":n.updateResource(s,{status:"error",lastEvent:"network-error"});break}}function ve(e){R({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),u.getState().setOnline(!e)}function Se(){const e=v?.active;if(e){for(const t of N)e.postMessage(t);N=[]}}var I=new Map,C=new Map,J=null;function me(e){J=e}function S(e){return e.includes("*")||/:[^/]+/.test(e)}function be(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function _(e,t){return new Error(`[eidos] resource('${e}') is a URL pattern — ${t}() is not supported on pattern handles. The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`)}function Ee(e,t){if(I.has(e))return I.get(e);const n=Oe(t),r=S(e)?be(e):void 0,s={url:e,config:t,strategy:n,status:"idle",cacheHits:0,cacheMisses:0};u.getState().registerResource(e,s),R({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:n.swStrategy,cacheName:n.cacheName,...r!==void 0&&{pattern:r}});const a={url:e,config:t,strategy:n,fetch:async()=>{if(S(e))throw _(e,"fetch");const i=C.get(e);if(i)return i.then(c=>c.clone());const o=Re(e,t,n);return C.set(e,o),o.finally(()=>C.delete(e)).catch(()=>{}),o.then(c=>c.clone())},json:async()=>{if(S(e))throw _(e,"json");return(await a.fetch()).json()},query:()=>{if(S(e))throw _(e,"query");return{queryKey:["eidos",e],queryFn:()=>a.json()}},prefetch:async()=>{if(S(e))throw _(e,"prefetch");await a.fetch()},invalidate:async()=>{R({type:"EIDOS_CLEAR_CACHE",url:e});const i=await caches.open(n.cacheName).catch(()=>null);if(i){const o=await i.keys(),c=r?new RegExp(r):null,d=e.startsWith("http");await Promise.all(o.filter(l=>{const f=l.url,y=new URL(f).pathname;return c?c.test(d?f:y):d?f===e:f===e||y===e}).map(l=>i.delete(l)))}S(e)||u.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),J?.(["eidos",e])},unregister:()=>{I.delete(e),R({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),u.getState().unregisterResource(e)}};return I.set(e,a),a}async function Re(e,t,n){const r=u.getState();r.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const s=await caches.open(n.cacheName).catch(()=>null);try{if(n.swStrategy!=="network-first"){const o=s?await s.match(e).catch(()=>null):null,c=u.getState().resources[e],d=t.maxAge!==void 0&&c?.cachedAt!==void 0&&Date.now()-c.cachedAt>t.maxAge;if(o&&!d)return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(c?.cacheHits??0)+1}),n.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async f=>{f.ok&&s&&(await s.put(e,f.clone()),u.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),o;const l=u.getState().resources[e];r.updateResource(e,{cacheMisses:(l?.cacheMisses??0)+1})}const a=await fetch(e);if(a.ok)return s&&await s.put(e,a.clone()),r.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),a;r.updateResource(e,{status:a.status===503?"offline":"error"});const i=a.headers.get("X-Eidos-Offline")==="true";throw new Error(i?`offline: no cached response for ${e}`:`${a.status} ${a.statusText}`)}catch(a){const i=s?await s.match(e).catch(()=>null):null;if(i){const o=u.getState().resources[e];return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(o?.cacheHits??0)+1}),i}throw r.updateResource(e,{status:"error"}),a}}function Oe(e){const t=e.strategy;return e.offline?$(t??"stale-while-revalidate",e.cacheName):$(t??"network-first",e.cacheName)}var ke={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},Ie={"stale-while-revalidate":{reasoning:"offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.",behavior:["Cache hit → return immediately, kick off background revalidation","Cache miss → fetch from network, cache the response, return it","Offline → return cached version if available, 503 if not","Reconnect → next request triggers a background refresh"],equivalentCode:`// Workbox equivalent
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let S=require("react"),H=require("react/jsx-runtime");function pe(e){return{registerResource:(t,r)=>e(n=>({resources:{...n.resources,[t]:r}})),updateResource:(t,r)=>e(n=>({resources:{...n.resources,[t]:n.resources[t]?{...n.resources[t],...r}:n.resources[t]}})),unregisterResource:t=>e(r=>({resources:Object.fromEntries(Object.entries(r.resources).filter(([n])=>n!==t))}))}}function he(e){return{addQueueItem:t=>e(r=>({queue:[...r.queue,t]})),updateQueueItem:(t,r)=>e(n=>({queue:n.queue.map(a=>a.id===t?{...a,...r}:a)})),batchUpdateQueueItems:t=>e(r=>{const n=new Map(t.map(a=>[a.id,a.update]));return{queue:r.queue.map(a=>{const s=n.get(a.id);return s?{...a,...s}:a})}}),removeQueueItem:t=>e(r=>({queue:r.queue.filter(n=>n.id!==t)})),hydrateQueue:t=>e(()=>({queue:t}))}}var w,D=new Set;function G(){D.forEach(e=>e())}function k(e){w={...w,...e(w)},G()}w={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>k(()=>({isOnline:e})),setSwStatus:(e,t)=>k(()=>({swStatus:e,swError:t})),...pe(k),...he(k)};function ye(){return w}function ge(e){return D.add(e),()=>{D.delete(e)}}var o={getState:ye,subscribe:ge,setState:e=>{const t=typeof e=="function"?e(w):e;w={...w,...t},G()}},m=null,T=[];function Y(){return m}async function we(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){o.getState().setSwStatus("unsupported");return}const t=o.getState();t.setSwStatus("registering");try{m=await navigator.serviceWorker.register(e,{scope:"/"}),await ve(m),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",Ee),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),_e()}catch(r){t.setSwStatus("error",String(r))}}function ve(e){return new Promise(t=>{if(e.active){t();return}const r=e.installing??e.waiting;if(!r){t();return}const n=setTimeout(t,1e4);r.addEventListener("statechange",function a(){r.state==="activated"&&(clearTimeout(n),r.removeEventListener("statechange",a),t())})})}function _(e){const t=m?.active;t?t.postMessage(e):T.push(e)}var J=null;function me(e){J=e}function Se(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&m!==null&&"sync"in m}catch{return!1}}var M={};function be(e){M=e}function Ee(e){const t=e.data;if(!t?.type)return;const r=o.getState(),{type:n,url:a}=t;if(n==="EIDOS_BACKGROUND_SYNC"){J?.();return}if(n==="EIDOS_NOTIFICATION_CLICK"){M.onNotificationClick?.(t.data);return}if(n==="EIDOS_SUBSCRIPTION_EXPIRED"){M.onSubscriptionExpired?.(t.subscription);return}if(a)switch(n){case"EIDOS_CACHE_HIT":{const s=r.resources[a];r.updateResource(a,{status:"fresh",lastEvent:"cache-hit",cacheHits:(s?.cacheHits??0)+1});break}case"EIDOS_CACHE_UPDATED":r.updateResource(a,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break;case"EIDOS_NETWORK_ERROR":r.updateResource(a,{status:"error",lastEvent:"network-error"});break}}function Re(e){_({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),o.getState().setOnline(!e)}function _e(){const e=m?.active;if(e){for(const t of T)e.postMessage(t);T=[]}}var v=new Map,C=new Map,X=null;function ke(e){X=e}function q(e){return e.includes("*")||/:[^/]+/.test(e)}function Ie(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function z(e,t){const r=qe(t),n=q(e)?Ie(e):void 0,a={url:e,config:t,strategy:r,status:"idle",cacheHits:0,cacheMisses:0};return o.getState().registerResource(e,a),_({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:r.swStrategy,cacheName:r.cacheName,...n!==void 0&&{pattern:n}}),{strategy:r,regexStr:n}}function Z(e,t,r){return async()=>{_({type:"EIDOS_CLEAR_CACHE",url:e});const n=await caches.open(t.cacheName).catch(()=>null);if(n){const a=await n.keys(),s=r?new RegExp(r):null,i=e.startsWith("http");await Promise.all(a.filter(c=>{const u=c.url,d=new URL(u).pathname;return s?s.test(i?u:d):i?u===e:u===e||d===e}).map(c=>n.delete(c)))}q(e)||o.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),X?.(["eidos",e])}}function ee(e){return()=>{v.delete(e),_({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),o.getState().unregisterResource(e)}}function Oe(e,t){if(q(e))throw new Error(`[eidos] resource('${e}') is a URL pattern — use resourcePattern('${e}', config) instead. Pattern handles only support invalidate()/unregister(); the SW intercepts matching requests automatically.`);if(v.has(e))return v.get(e);const{strategy:r}=z(e,t),n={url:e,config:t,strategy:r,fetch:async()=>{const a=C.get(e);if(a)return a.then(i=>i.clone());const s=Qe(e,t,r);return C.set(e,s),s.finally(()=>C.delete(e)).catch(()=>{}),s.then(i=>i.clone())},json:async()=>(await n.fetch()).json(),query:()=>({queryKey:["eidos",e],queryFn:()=>n.json()}),prefetch:async()=>{await n.fetch()},invalidate:Z(e,r,void 0),unregister:ee(e)};return v.set(e,n),n}function Ae(e,t){if(!q(e))throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);if(v.has(e))return v.get(e);const{strategy:r,regexStr:n}=z(e,t),a={url:e,config:t,strategy:r,invalidate:Z(e,r,n),unregister:ee(e)};return v.set(e,a),a}async function Qe(e,t,r){const n=o.getState();n.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const a=await caches.open(r.cacheName).catch(()=>null);try{if(r.swStrategy!=="network-first"){const c=a?await a.match(e).catch(()=>null):null,u=o.getState().resources[e],d=t.maxAge!==void 0&&u?.cachedAt!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(c&&!d)return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(u?.cacheHits??0)+1}),r.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async l=>{l.ok&&a&&(await a.put(e,l.clone()),o.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),c;const g=o.getState().resources[e];n.updateResource(e,{cacheMisses:(g?.cacheMisses??0)+1})}const s=await fetch(e);if(s.ok)return a&&await a.put(e,s.clone()),n.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),s;n.updateResource(e,{status:s.status===503?"offline":"error"});const i=s.headers.get("X-Eidos-Offline")==="true";throw new Error(i?`offline: no cached response for ${e}`:`${s.status} ${s.statusText}`)}catch(s){const i=a?await a.match(e).catch(()=>null):null;if(i){const c=o.getState().resources[e];return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(c?.cacheHits??0)+1}),i}throw n.updateResource(e,{status:"error"}),s}}function qe(e){const t=e.strategy;return e.offline?B(t??"stale-while-revalidate",e.cacheName):B(t??"network-first",e.cacheName)}var Ce={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},xe={"stale-while-revalidate":{reasoning:"offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.",behavior:["Cache hit → return immediately, kick off background revalidation","Cache miss → fetch from network, cache the response, return it","Offline → return cached version if available, 503 if not","Reconnect → next request triggers a background refresh"],equivalentCode:`// Workbox equivalent
2
2
  new StaleWhileRevalidate({
3
3
  cacheName: 'eidos-resources-v1',
4
4
  plugins: [new ExpirationPlugin({ maxEntries: 60 })],
@@ -10,6 +10,6 @@ new CacheFirst({
10
10
  new NetworkFirst({
11
11
  cacheName: 'eidos-resources-v1',
12
12
  networkTimeoutSeconds: 3,
13
- })`}};function $(e,t){const n=Ie[e];return{name:ke[e],swStrategy:e,cacheName:t??"eidos-resources-v1",reasoning:n.reasoning,behavior:n.behavior,equivalentCode:""}}async function _e(e){const t=await Promise.allSettled(e.map(r=>r.prefetch())),n=t.filter(r=>r.status==="rejected").map(r=>r.reason);return{warmed:t.filter(r=>r.status==="fulfilled").length,failed:n.length,errors:n}}var Ae="eidos",qe=1,p="action-queue",x=null;function b(){return x?Promise.resolve(x):new Promise((e,t)=>{const n=indexedDB.open(Ae,qe);n.onupgradeneeded=r=>{const s=r.target.result;if(!s.objectStoreNames.contains(p)){const a=s.createObjectStore(p,{keyPath:"id"});a.createIndex("status","status",{unique:!1}),a.createIndex("actionId","actionId",{unique:!1})}},n.onsuccess=()=>{x=n.result,e(n.result)},n.onerror=()=>t(n.error)})}async function Ce(e){const t=await b();return new Promise((n,r)=>{const s=t.transaction(p,"readwrite");s.objectStore(p).add(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function X(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(p,"readonly").objectStore(p).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function xe(e,t){const n=await b();return new Promise((r,s)=>{const a=n.transaction(p,"readwrite"),i=a.objectStore(p),o=i.get(e);o.onsuccess=()=>{o.result&&i.put({...o.result,...t})},a.oncomplete=()=>r(),a.onerror=()=>s(a.error)})}async function Qe(e){const t=await b();return new Promise((n,r)=>{const s=t.transaction(p,"readwrite");s.objectStore(p).delete(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function Pe(){const e=await b();function t(s){return new Promise((a,i)=>{const o=e.transaction(p,"readonly").objectStore(p).index("status"),c=[],d=o.openCursor(IDBKeyRange.only(s));d.onsuccess=l=>{const f=l.target.result;f?(c.push(f.value),f.continue()):a(c)},d.onerror=()=>i(d.error)})}const[n,r]=await Promise.all([t("pending"),t("failed")]);return[...n,...r]}async function De(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(p,"readwrite");r.objectStore(p).clear(),r.oncomplete=()=>t(),r.onerror=()=>n(r.error)})}var z={add:Ce,getAll:X,getPending:Pe,update:xe,remove:Qe,clear:De},Z=null;function Ne(e){Z=e}function K(){return Z}var L=new Map,ee=new Map,te=new Map,ne=new Map,re=new Map,O=new Map;function h(){return K()??z}function j(){return crypto.randomUUID()}function M(e,t,n){return e(...t,n)}function Te(e,t){const n=t.name||e.name||j(),r=t.namespace?`${t.namespace}::${n}`:n;L.set(r,e),re.set(r,t),t.onRollback&&ee.set(r,t.onRollback),t.onConflict&&te.set(r,t.onConflict),t.conflict&&ne.set(r,t.conflict);const s=async(...i)=>{const{isOnline:o}=u.getState(),c=t.reliability==="neverLose"||t.cancellable,d=c?j():"";let l;if(t.cancellable){const y=new AbortController;O.set(d,y),l=y.signal}const f={idempotencyKey:d,attempt:0,signal:l};t.onOptimistic?.(...i,f);try{if(t.reliability==="neverLose"){if(!o)return B(r,r,i,t,d);try{return await M(e,i,f)}catch(y){if(se(y))throw y;return B(r,r,i,t,d)}}try{return c?await M(e,i,f):await e(...i)}catch(y){throw t.onRollback?.(...i),y}}finally{t.cancellable&&O.delete(d)}},a=async i=>{const o=O.get(i);if(o)return o.abort(),!0;const c=(await h().getAll()).find(d=>d.idempotencyKey===i&&d.status==="pending");return c?(u.getState().removeQueueItem(c.id),await h().remove(c.id),!0):!1};return Object.defineProperty(s,"id",{value:r,writable:!1}),Object.defineProperty(s,"config",{value:t,writable:!1}),Object.defineProperty(s,"cancel",{value:a,writable:!1}),s}async function B(e,t,n,r,s){const a=j(),i={schemaVersion:2,id:a,actionId:e,actionName:t,idempotencyKey:s,args:n,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending",priority:r.priority??"normal"};await h().add(i),u.getState().addQueueItem(i);try{const o=G();o&&"sync"in o&&await o.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:a,message:`"${t}" queued — will execute when online`}}function se(e){return e instanceof DOMException&&e.name==="AbortError"}function je(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function Me(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}function A(){return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0,cancelled:0}}var Q=!1,Ue="eidos-queue-replay";async function q(){const e=u.getState();if(!e.isOnline)return A();if(typeof navigator<"u"&&navigator.locks)return navigator.locks.request(Ue,{ifAvailable:!0},async t=>t?F(e):A());if(Q)return A();Q=!0;try{return await F(e)}finally{Q=!1}}async function We(e,t){const n=L.get(e.actionId);if(!n)return"skipped";const r=re.get(e.actionId)?.cancellable;let s;if(r){const i=new AbortController;O.set(e.idempotencyKey,i),s=i.signal}const a={idempotencyKey:e.idempotencyKey,attempt:e.retryCount,signal:s};try{await M(n,e.args,a);const i=Date.now();return t.updateQueueItem(e.id,{status:"succeeded",completedAt:i}),await h().update(e.id,{status:"succeeded",completedAt:i}),setTimeout(()=>{t.removeQueueItem(e.id),h().remove(e.id)},3e3),"succeeded"}catch(i){if(se(i))return t.removeQueueItem(e.id),await h().remove(e.id),"cancelled";if(je(i)){const c=ne.get(e.actionId);let d;if(c)switch(c.strategy){case"serverWins":d="skip";break;case"clientWins":case"lastWriteWins":d="retry";break;case"merge":case"custom":{const l={error:i,args:e.args,attempt:e.retryCount,idempotencyKey:e.idempotencyKey};d=c.resolve?.(l)??"retry";break}}else{const l=te.get(e.actionId);l&&(d=l(i,e.args))}if(d==="skip")return t.removeQueueItem(e.id),await h().remove(e.id),"conflicted";d&&typeof d=="object"&&(e.args=d.resolved,t.updateQueueItem(e.id,{args:d.resolved}),await h().update(e.id,{args:d.resolved}))}const o=e.retryCount+1;if(o>=e.maxRetries)return t.updateQueueItem(e.id,{status:"failed",error:String(i),retryCount:o}),await h().update(e.id,{status:"failed",error:String(i),retryCount:o}),ee.get(e.actionId)?.(...e.args),"failed";{const c=Date.now()+Me(o);return t.updateQueueItem(e.id,{status:"pending",retryCount:o,nextRetryAt:c}),await h().update(e.id,{status:"pending",retryCount:o,nextRetryAt:c}),"retrying"}}finally{r&&O.delete(e.idempotencyKey)}}async function Ke(e,t,n){if(e.length===0)return;const r=e.filter(a=>L.has(a.actionId));if(n.skipped+=e.length-r.length,r.length>0){t.batchUpdateQueueItems(r.map(a=>({id:a.id,update:{status:"replaying"}})));for(const a of r)h().update(a.id,{status:"replaying"})}const s=await Promise.allSettled(r.map(a=>We(a,t)));for(const a of s){const i=a.status==="fulfilled"?a.value:"failed";i==="skipped"?n.skipped++:i==="conflicted"?n.conflicted++:i==="cancelled"?n.cancelled++:(n.attempted++,n[i]++)}}async function F(e){const t=await h().getPending(),n=Date.now(),r=t.filter(a=>a.retryCount<a.maxRetries&&(!a.nextRetryAt||a.nextRetryAt<=n)),s=A();for(const a of["high","normal","low"])await Ke(r.filter(i=>(i.priority??"normal")===a),e,s);return s}async function Le(){await h().clear(),u.getState().hydrateQueue([])}function ae(){let e=u.getState().isOnline;const t=u.subscribe(()=>{const{isOnline:s}=u.getState(),a=s&&!e;e=s,a&&setTimeout(q,600)}),n=u.getState(),r=n.queue.some(s=>s.status==="pending");return n.isOnline&&r&&setTimeout(q,1200),t}async function He(e){if(e.schemaVersion===2&&e.idempotencyKey)return e;const t={...e,schemaVersion:2,idempotencyKey:e.idempotencyKey??crypto.randomUUID()};return await(K()??z).update(t.id,{schemaVersion:t.schemaVersion,idempotencyKey:t.idempotencyKey}).catch(()=>{}),t}var U=!1,W=null;async function ie(e={}){if(typeof window>"u"||U)return;U=!0;const t=e.swPath??"/eidos-sw.js",n=e.autoReplay??!0;try{const r=await X();if(r.length>0){const s=await Promise.all(r.map(He));u.getState().hydrateQueue(s)}}catch{}try{await fe(t)}catch{}he(()=>{u.getState().isOnline&&setTimeout(q,200)}),n&&(W=ae())}function $e(){W?.(),W=null,U=!1}var P="@eidos:queue",Be=class{constructor(e){this.storage=e}async readAll(){try{const e=await this.storage.getItem(P);return e?JSON.parse(e):[]}catch{return[]}}async writeAll(e){await this.storage.setItem(P,JSON.stringify(e))}async add(e){const t=await this.readAll();t.push(e),await this.writeAll(t)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(e=>e.status==="pending"||e.status==="failed")}async update(e,t){const n=await this.readAll(),r=n.findIndex(s=>s.id===e);r!==-1&&(n[r]={...n[r],...t}),await this.writeAll(n)}async remove(e){const t=await this.readAll();await this.writeAll(t.filter(n=>n.id!==e))}async clear(){await this.storage.removeItem(P)}};function Fe({children:e,swPath:t,autoReplay:n}){return(0,m.useEffect)(()=>{ie({swPath:t,autoReplay:n})},[]),(0,H.jsx)(H.Fragment,{children:e})}function g(e){const t=e??(n=>n);return(0,m.useSyncExternalStore)(u.subscribe,()=>t(u.getState()))}function Ve(){return g()}function Ge(){return g(e=>e.resources)}function Ye(e){return g(t=>t.resources[e])}function Je(){return g(e=>e.queue)}function Xe(e){return g(t=>t.queue.find(n=>n.id===e))}function ze(){return{isOnline:g(e=>e.isOnline),swStatus:g(e=>e.swStatus),swError:g(e=>e.swError)}}function Ze(){const[e,t,n,r]=g(s=>{let a=0,i=0,o=0;for(const c of s.queue)c.status==="pending"?a++:c.status==="failed"?i++:c.status==="replaying"&&o++;return`${a},${i},${o},${s.queue.length}`}).split(",");return{pending:+e,failed:+t,replaying:+n,total:+r}}function et(e){const t=g(s=>s.queue.length),n=(0,m.useRef)(0),r=(0,m.useRef)(e);(0,m.useEffect)(()=>{r.current=e}),(0,m.useEffect)(()=>{n.current>0&&t===0&&r.current(),n.current=t},[t])}var tt="1.2.0";function nt(e,t){const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(e[r]!==t[r])return!1;return!0}function oe(e,t){return nt(e,t)}function E(e,t=Object.is){return{subscribe(n){let r=e(u.getState());return n(r),u.subscribe(()=>{const s=e(u.getState());t(r,s)||(r=s,n(s))})},getState(){return e(u.getState())}}}var rt=E(e=>e),st=E(e=>e.queue),at=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),oe),it=E(e=>{let t=0,n=0,r=0;for(const s of e.queue)s.status==="pending"?t++:s.status==="failed"?n++:s.status==="replaying"&&r++;return{pending:t,failed:n,replaying:r,total:e.queue.length}},oe);function ot(e){return E(t=>t.resources[e])}function ct(e){return E(t=>t.queue.find(n=>n.id===e))}exports.AsyncStorageQueueStorage=Be;exports.EidosProvider=Fe;exports.VERSION=tt;exports._getQueueStorage=K;exports._resetEidos=$e;exports.action=Te;exports.clearQueue=Le;exports.eidosAction=ct;exports.eidosQueue=st;exports.eidosQueueStats=it;exports.eidosResource=ot;exports.eidosStatus=at;exports.eidosStore=rt;exports.getSwRegistration=G;exports.initEidos=ie;exports.isBgSyncSupported=ye;exports.registerPushCallbacks=ge;exports.replayQueue=q;exports.resource=Ee;exports.sendToWorker=R;exports.setOfflineSimulation=ve;exports.setQueryInvalidator=me;exports.setQueueStorage=Ne;exports.subscribeReplayOnReconnect=ae;exports.useEidos=Ve;exports.useEidosAction=Xe;exports.useEidosOnDrain=et;exports.useEidosQueue=Je;exports.useEidosQueueStats=Ze;exports.useEidosResource=Ye;exports.useEidosResources=Ge;exports.useEidosStatus=ze;exports.useEidosStore=u;exports.warmCache=_e;
13
+ })`}};function B(e,t){const r=xe[e];return{name:Ce[e],swStrategy:e,cacheName:t??"eidos-resources-v1",reasoning:r.reasoning,behavior:r.behavior,equivalentCode:""}}async function Pe(e){const t=await Promise.allSettled(e.map(n=>n.prefetch())),r=t.filter(n=>n.status==="rejected").map(n=>n.reason);return{warmed:t.filter(n=>n.status==="fulfilled").length,failed:r.length,errors:r}}var Ne="eidos",De=1,f="action-queue",x=null;function b(){return x?Promise.resolve(x):new Promise((e,t)=>{const r=indexedDB.open(Ne,De);r.onupgradeneeded=n=>{const a=n.target.result;if(!a.objectStoreNames.contains(f)){const s=a.createObjectStore(f,{keyPath:"id"});s.createIndex("status","status",{unique:!1}),s.createIndex("actionId","actionId",{unique:!1})}},r.onsuccess=()=>{x=r.result,e(r.result)},r.onerror=()=>t(r.error)})}async function Te(e){const t=await b();return new Promise((r,n)=>{const a=t.transaction(f,"readwrite");a.objectStore(f).add(e),a.oncomplete=()=>r(),a.onerror=()=>n(a.error)})}async function te(){const e=await b();return new Promise((t,r)=>{const n=e.transaction(f,"readonly").objectStore(f).getAll();n.onsuccess=()=>t(n.result),n.onerror=()=>r(n.error)})}async function Me(e,t){const r=await b();return new Promise((n,a)=>{const s=r.transaction(f,"readwrite"),i=s.objectStore(f),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},s.oncomplete=()=>n(),s.onerror=()=>a(s.error)})}async function je(e){const t=await b();return new Promise((r,n)=>{const a=t.transaction(f,"readwrite");a.objectStore(f).delete(e),a.oncomplete=()=>r(),a.onerror=()=>n(a.error)})}async function Ue(){const e=await b();function t(a){return new Promise((s,i)=>{const c=e.transaction(f,"readonly").objectStore(f).index("status"),u=[],d=c.openCursor(IDBKeyRange.only(a));d.onsuccess=g=>{const l=g.target.result;l?(u.push(l.value),l.continue()):s(u)},d.onerror=()=>i(d.error)})}const[r,n]=await Promise.all([t("pending"),t("failed")]);return[...r,...n]}async function Ke(){const e=await b();return new Promise((t,r)=>{const n=e.transaction(f,"readwrite");n.objectStore(f).clear(),n.oncomplete=()=>t(),n.onerror=()=>r(n.error)})}var ne={add:Te,getAll:te,getPending:Ue,update:Me,remove:je,clear:Ke},re=null;function Le(e){re=e}function $(){return re}var We="eidos-queue-sync",I;function ae(){return I!==void 0||(I=typeof BroadcastChannel>"u"?null:new BroadcastChannel(We)),I}function y(e){ae()?.postMessage(e)}function $e(){const e=ae();if(!e)return()=>{};const t=r=>{const n=o.getState(),a=r.data;switch(a.type){case"update":n.updateQueueItem(a.id,a.update);break;case"batchUpdate":n.batchUpdateQueueItems(a.updates);break;case"remove":n.removeQueueItem(a.id);break}};return e.addEventListener("message",t),()=>e.removeEventListener("message",t)}function se(e){let t=0,r=0,n=0;for(const a of e)a.status==="pending"?t++:a.status==="failed"?r++:a.status==="replaying"&&n++;return{pending:t,failed:r,replaying:n,total:e.length}}var A=new Map,ie=new Map,oe=new Map,ce=new Map,R=new Map;function p(){return $()??ne}function j(){return crypto.randomUUID()}function U(e,t,r){return e(...t,r)}function He(e,t){const r=t.name||e.name||j(),n=t.namespace?`${t.namespace}::${r}`:r;if(A.has(n))throw new Error(`[eidos] duplicate action id "${n}" — an action with this id is already registered. Pass a unique config.name or config.namespace.`);A.set(n,e),ce.set(n,t),t.onRollback&&ie.set(n,t.onRollback),t.conflict&&oe.set(n,t.conflict);const a=async(...i)=>{const{isOnline:c}=o.getState(),u=j();let d;if(t.cancellable){const l=new AbortController;R.set(u,l),d=l.signal}const g={idempotencyKey:u,attempt:0,signal:d};t.onOptimistic?.(...i,g);try{if(t.reliability==="neverLose"){if(!c)return F(n,n,i,t,u);try{return await U(e,i,g)}catch(l){if(ue(l))throw l;return F(n,n,i,t,u)}}try{return await U(e,i,g)}catch(l){throw t.onRollback?.(...i,g),l}}finally{t.cancellable&&R.delete(u)}},s=async i=>{const c=R.get(i);if(c)return c.abort(),!0;const u=(await p().getAll()).find(d=>d.idempotencyKey===i&&d.status==="pending");return u?(o.getState().removeQueueItem(u.id),y({type:"remove",id:u.id}),await p().remove(u.id),!0):!1};return Object.defineProperty(a,"id",{value:n,writable:!1}),Object.defineProperty(a,"config",{value:t,writable:!1}),Object.defineProperty(a,"cancel",{value:s,writable:!1}),a}async function F(e,t,r,n,a){const s=j(),i={schemaVersion:2,id:s,actionId:e,actionName:t,idempotencyKey:a,args:r,queuedAt:Date.now(),retryCount:0,maxRetries:n.maxRetries??3,status:"pending",priority:n.priority??"normal"};await p().add(i),o.getState().addQueueItem(i);try{const c=Y();c&&"sync"in c&&await c.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:s,message:`"${t}" queued — will execute when online`}}function ue(e){return e instanceof DOMException&&e.name==="AbortError"}function Be(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function Fe(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}function O(){return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0,cancelled:0}}var P=!1,Ve="eidos-queue-replay";async function Q(){const e=o.getState();if(!e.isOnline)return O();if(typeof navigator<"u"&&navigator.locks)return navigator.locks.request(Ve,{ifAvailable:!0},async t=>t?V(e):O());if(P)return O();P=!0;try{return await V(e)}finally{P=!1}}async function Ge(e,t){const r=Date.now();t.updateQueueItem(e.id,{status:"succeeded",completedAt:r}),y({type:"update",id:e.id,update:{status:"succeeded",completedAt:r}}),await p().update(e.id,{status:"succeeded",completedAt:r}),setTimeout(()=>{t.removeQueueItem(e.id),y({type:"remove",id:e.id}),p().remove(e.id)},3e3)}async function Ye(e,t,r){const n=oe.get(e.actionId);let a;if(n)switch(n.strategy){case"serverWins":a="skip";break;case"clientWins":a="retry";break;case"merge":case"custom":{const s={error:r,args:e.args,attempt:e.retryCount,idempotencyKey:e.idempotencyKey};a=n.resolve?.(s)??"retry";break}}if(a==="skip")return t.removeQueueItem(e.id),y({type:"remove",id:e.id}),await p().remove(e.id),"conflicted";a&&typeof a=="object"&&(e.args=a.resolved,t.updateQueueItem(e.id,{args:a.resolved}),y({type:"update",id:e.id,update:{args:a.resolved}}),await p().update(e.id,{args:a.resolved}))}async function Je(e,t,r){const n=e.retryCount+1;if(n>=e.maxRetries){const s={status:"failed",error:String(r),retryCount:n};t.updateQueueItem(e.id,s),y({type:"update",id:e.id,update:s}),await p().update(e.id,s);const i={idempotencyKey:e.idempotencyKey,attempt:n};return ie.get(e.actionId)?.(...e.args,i),"failed"}const a={status:"pending",retryCount:n,nextRetryAt:Date.now()+Fe(n)};return t.updateQueueItem(e.id,a),y({type:"update",id:e.id,update:a}),await p().update(e.id,a),"retrying"}async function Xe(e,t){const r=A.get(e.actionId);if(!r)return"skipped";const n=ce.get(e.actionId)?.cancellable;let a;if(n){const i=new AbortController;R.set(e.idempotencyKey,i),a=i.signal}const s={idempotencyKey:e.idempotencyKey,attempt:e.retryCount,signal:a};try{return await U(r,e.args,s),await Ge(e,t),"succeeded"}catch(i){if(ue(i))return t.removeQueueItem(e.id),y({type:"remove",id:e.id}),await p().remove(e.id),"cancelled";if(Be(i)){const c=await Ye(e,t,i);if(c)return c}return Je(e,t,i)}finally{n&&R.delete(e.idempotencyKey)}}async function ze(e,t,r){if(e.length===0)return;const n=e.filter(s=>A.has(s.actionId));if(r.skipped+=e.length-n.length,n.length>0){const s=n.map(i=>({id:i.id,update:{status:"replaying"}}));t.batchUpdateQueueItems(s),y({type:"batchUpdate",updates:s});for(const i of n)p().update(i.id,{status:"replaying"})}const a=await Promise.allSettled(n.map(s=>Xe(s,t)));for(const s of a){const i=s.status==="fulfilled"?s.value:"failed";i==="skipped"?r.skipped++:i==="conflicted"?r.conflicted++:i==="cancelled"?r.cancelled++:(r.attempted++,r[i]++)}}async function V(e){const t=await p().getPending(),r=Date.now(),n=t.filter(s=>s.retryCount<s.maxRetries&&(!s.nextRetryAt||s.nextRetryAt<=r)),a=O();for(const s of["high","normal","low"])await ze(n.filter(i=>(i.priority??"normal")===s),e,a);return a}async function Ze(){await p().clear(),o.getState().hydrateQueue([])}function de(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:a}=o.getState(),s=a&&!e;e=a,s&&setTimeout(Q,600)}),r=o.getState(),n=r.queue.some(a=>a.status==="pending");return r.isOnline&&n&&setTimeout(Q,1200),t}async function et(e){if(e.schemaVersion===2&&e.idempotencyKey)return e;const t={...e,schemaVersion:2,idempotencyKey:e.idempotencyKey??crypto.randomUUID()};return await($()??ne).update(t.id,{schemaVersion:t.schemaVersion,idempotencyKey:t.idempotencyKey}).catch(()=>{}),t}var K=!1,L=null,W=null;async function le(e={}){if(typeof window>"u"||K)return;K=!0;const t=e.swPath??"/eidos-sw.js",r=e.autoReplay??!0;try{const n=await te();if(n.length>0){const a=await Promise.all(n.map(et));o.getState().hydrateQueue(a)}}catch{}try{await we(t)}catch{}me(()=>{o.getState().isOnline&&setTimeout(Q,200)}),r&&(L=de()),W=$e()}function tt(){L?.(),L=null,W?.(),W=null,K=!1}var N="@eidos:queue",nt=class{constructor(e){this.storage=e}async readAll(){try{const e=await this.storage.getItem(N);return e?JSON.parse(e):[]}catch{return[]}}async writeAll(e){await this.storage.setItem(N,JSON.stringify(e))}async add(e){const t=await this.readAll();t.push(e),await this.writeAll(t)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(e=>e.status==="pending"||e.status==="failed")}async update(e,t){const r=await this.readAll(),n=r.findIndex(a=>a.id===e);n!==-1&&(r[n]={...r[n],...t}),await this.writeAll(r)}async remove(e){const t=await this.readAll();await this.writeAll(t.filter(r=>r.id!==e))}async clear(){await this.storage.removeItem(N)}};function rt({children:e,swPath:t,autoReplay:r}){return(0,S.useEffect)(()=>{le({swPath:t,autoReplay:r})},[]),(0,H.jsx)(H.Fragment,{children:e})}function h(e){const t=e??(r=>r);return(0,S.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function at(){return h()}function st(){return h(e=>e.resources)}function it(e){return h(t=>t.resources[e])}function ot(){return h(e=>e.queue)}function ct(e){return h(t=>t.queue.find(r=>r.id===e))}function ut(){return{isOnline:h(e=>e.isOnline),swStatus:h(e=>e.swStatus),swError:h(e=>e.swError)}}function dt(){const[e,t,r,n]=h(a=>{const{pending:s,failed:i,replaying:c,total:u}=se(a.queue);return`${s},${i},${c},${u}`}).split(",");return{pending:+e,failed:+t,replaying:+r,total:+n}}function lt(e){const t=h(a=>a.queue.length),r=(0,S.useRef)(0),n=(0,S.useRef)(e);(0,S.useEffect)(()=>{n.current=e}),(0,S.useEffect)(()=>{r.current>0&&t===0&&n.current(),r.current=t},[t])}var ft="2.0.0";function pt(e,t){const r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(const n of r)if(e[n]!==t[n])return!1;return!0}function fe(e,t){return pt(e,t)}function E(e,t=Object.is){return{subscribe(r){let n=e(o.getState());return r(n),o.subscribe(()=>{const a=e(o.getState());t(n,a)||(n=a,r(a))})},getState(){return e(o.getState())}}}var ht=E(e=>e),yt=E(e=>e.queue),gt=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),fe),wt=E(e=>se(e.queue),fe);function vt(e){return E(t=>t.resources[e])}function mt(e){return E(t=>t.queue.find(r=>r.id===e))}exports.AsyncStorageQueueStorage=nt;exports.EidosProvider=rt;exports.VERSION=ft;exports._getQueueStorage=$;exports._resetEidos=tt;exports.action=He;exports.clearQueue=Ze;exports.eidosAction=mt;exports.eidosQueue=yt;exports.eidosQueueStats=wt;exports.eidosResource=vt;exports.eidosStatus=gt;exports.eidosStore=ht;exports.getSwRegistration=Y;exports.initEidos=le;exports.isBgSyncSupported=Se;exports.registerPushCallbacks=be;exports.replayQueue=Q;exports.resource=Oe;exports.resourcePattern=Ae;exports.sendToWorker=_;exports.setOfflineSimulation=Re;exports.setQueryInvalidator=ke;exports.setQueueStorage=Le;exports.subscribeReplayOnReconnect=de;exports.useEidos=at;exports.useEidosAction=ct;exports.useEidosOnDrain=lt;exports.useEidosQueue=ot;exports.useEidosQueueStats=dt;exports.useEidosResource=it;exports.useEidosResources=st;exports.useEidosStatus=ut;exports.useEidosStore=o;exports.warmCache=Pe;
14
14
 
15
15
  //# sourceMappingURL=eidos.cjs.map