@sweidos/eidos 2.2.0 → 2.3.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 +254 -15
- package/dist/action.d.ts +22 -0
- package/dist/async-storage-adapter.d.ts +25 -0
- package/dist/debug.d.ts +46 -0
- package/dist/debug.js +43 -0
- package/dist/debug.js.map +1 -0
- package/dist/devtools.js +185 -5
- package/dist/eidos-sw.js +60 -19
- package/dist/eidos.cjs +5 -5
- package/dist/eidos.cjs.map +1 -1
- package/dist/idb.d.ts +10 -0
- package/dist/index.d.ts +20 -647
- package/dist/index.js +37 -34
- package/dist/internal/url-base64.d.ts +2 -0
- package/dist/query.d.ts +1 -2
- package/dist/queue-storage.d.ts +12 -0
- package/dist/queue-sync.d.ts +32 -0
- package/dist/react/Provider.d.ts +16 -0
- package/dist/react/ProviderRN.d.ts +0 -1
- package/dist/react/hooks.d.ts +51 -0
- package/dist/replay.d.ts +15 -0
- package/dist/resource.d.ts +32 -0
- package/dist/resource.js +80 -78
- package/dist/resource.js.map +1 -1
- package/dist/runtime-rn.d.ts +0 -1
- package/dist/runtime.d.ts +39 -0
- package/dist/runtime.js +24 -21
- package/dist/runtime.js.map +1 -1
- package/dist/store-slices.d.ts +26 -0
- package/dist/store.d.ts +15 -0
- package/dist/stores.d.ts +64 -0
- package/dist/sveltekit.d.ts +0 -1
- package/dist/sw-bridge.d.ts +24 -0
- package/dist/sw-bridge.js +69 -54
- package/dist/sw-bridge.js.map +1 -1
- package/dist/testing.d.ts +1 -2
- package/dist/types.d.ts +305 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/vite.d.ts +0 -1
- package/package.json +7 -5
package/dist/devtools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useCallback, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/types.ts
|
|
5
5
|
function emptyReliabilityStats() {
|
|
@@ -187,6 +187,9 @@ function useEidosReliabilityStats() {
|
|
|
187
187
|
//#region src/sw-bridge.ts
|
|
188
188
|
var _registration = null;
|
|
189
189
|
var _pendingMessages = [];
|
|
190
|
+
function getSwRegistration() {
|
|
191
|
+
return _registration;
|
|
192
|
+
}
|
|
190
193
|
function sendToWorker(message) {
|
|
191
194
|
const sw = _registration?.active;
|
|
192
195
|
if (sw) sw.postMessage(message);
|
|
@@ -199,6 +202,14 @@ function setOfflineSimulation(enabled) {
|
|
|
199
202
|
});
|
|
200
203
|
useEidosStore.getState().setOnline(!enabled);
|
|
201
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Tells the waiting service worker to activate immediately, then reloads the page.
|
|
207
|
+
* Only relevant when `skipWaiting: false` — call this after the user confirms
|
|
208
|
+
* a "reload to update" toast shown via `onUpdateAvailable`.
|
|
209
|
+
*/
|
|
210
|
+
function triggerSwUpdate() {
|
|
211
|
+
_registration?.waiting?.postMessage({ type: "EIDOS_SKIP_WAITING" });
|
|
212
|
+
}
|
|
202
213
|
//#endregion
|
|
203
214
|
//#region src/idb.ts
|
|
204
215
|
var DB_NAME = "eidos";
|
|
@@ -752,7 +763,9 @@ var ICONS = {
|
|
|
752
763
|
clock: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 6v6l4 2",
|
|
753
764
|
x: "M18 6 6 18M6 6l12 12",
|
|
754
765
|
rotateCcw: "M3 12a9 9 0 1 0 2.6-6.4M3 12V5m0 7h7",
|
|
755
|
-
activity: "M22 12h-4l-3 9L9 3l-3 9H2"
|
|
766
|
+
activity: "M22 12h-4l-3 9L9 3l-3 9H2",
|
|
767
|
+
shield: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",
|
|
768
|
+
refreshCw: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8M21 3v5h-5M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16M3 21v-5h5"
|
|
756
769
|
};
|
|
757
770
|
function positionStyle(p) {
|
|
758
771
|
const base = {
|
|
@@ -996,7 +1009,8 @@ function EidosDevtools({ position = "bottom-right", defaultOpen = false }) {
|
|
|
996
1009
|
children: [
|
|
997
1010
|
"queue",
|
|
998
1011
|
"cache",
|
|
999
|
-
"reliability"
|
|
1012
|
+
"reliability",
|
|
1013
|
+
"sw"
|
|
1000
1014
|
].map((t) => /* @__PURE__ */ jsx("button", {
|
|
1001
1015
|
role: "tab",
|
|
1002
1016
|
"aria-selected": tab === t,
|
|
@@ -1018,7 +1032,7 @@ function EidosDevtools({ position = "bottom-right", defaultOpen = false }) {
|
|
|
1018
1032
|
letterSpacing: "0.05em",
|
|
1019
1033
|
transition: "color 0.15s, border-color 0.15s"
|
|
1020
1034
|
},
|
|
1021
|
-
children: t === "queue" ? `Queue (${queue.length})` : t === "cache" ? `Cache (${resourceList.length})` : "Reliability"
|
|
1035
|
+
children: t === "queue" ? `Queue (${queue.length})` : t === "cache" ? `Cache (${resourceList.length})` : t === "reliability" ? "Reliability" : "SW"
|
|
1022
1036
|
}, t))
|
|
1023
1037
|
}),
|
|
1024
1038
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -1031,7 +1045,7 @@ function EidosDevtools({ position = "bottom-right", defaultOpen = false }) {
|
|
|
1031
1045
|
queue,
|
|
1032
1046
|
onReplay: handleReplay,
|
|
1033
1047
|
onClear: handleClear
|
|
1034
|
-
}) : tab === "cache" ? /* @__PURE__ */ jsx(CacheTab, { resources: resourceList }) : /* @__PURE__ */ jsx(ReliabilityTab, { stats: reliability })
|
|
1048
|
+
}) : tab === "cache" ? /* @__PURE__ */ jsx(CacheTab, { resources: resourceList }) : tab === "reliability" ? /* @__PURE__ */ jsx(ReliabilityTab, { stats: reliability }) : /* @__PURE__ */ jsx(SwTab, { resources: resourceList })
|
|
1035
1049
|
})
|
|
1036
1050
|
]
|
|
1037
1051
|
}), toggleBtn]
|
|
@@ -1384,5 +1398,171 @@ function ReliabilityTab({ stats }) {
|
|
|
1384
1398
|
})
|
|
1385
1399
|
] });
|
|
1386
1400
|
}
|
|
1401
|
+
function readSwSnapshot() {
|
|
1402
|
+
const reg = getSwRegistration();
|
|
1403
|
+
if (!reg) return {
|
|
1404
|
+
activeUrl: null,
|
|
1405
|
+
hasWaiting: false,
|
|
1406
|
+
hasInstalling: false
|
|
1407
|
+
};
|
|
1408
|
+
return {
|
|
1409
|
+
activeUrl: reg.active?.scriptURL ?? null,
|
|
1410
|
+
hasWaiting: reg.waiting !== null,
|
|
1411
|
+
hasInstalling: reg.installing !== null
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function SwTab({ resources }) {
|
|
1415
|
+
const [snap, setSnap] = useState(readSwSnapshot);
|
|
1416
|
+
useEffect(() => {
|
|
1417
|
+
const id = setInterval(() => setSnap(readSwSnapshot()), 1e3);
|
|
1418
|
+
return () => clearInterval(id);
|
|
1419
|
+
}, []);
|
|
1420
|
+
const buckets = resources.reduce((acc, res) => {
|
|
1421
|
+
const name = res.strategy.cacheName;
|
|
1422
|
+
acc[name] = (acc[name] ?? 0) + 1;
|
|
1423
|
+
return acc;
|
|
1424
|
+
}, {});
|
|
1425
|
+
const bucketList = Object.entries(buckets);
|
|
1426
|
+
const swState = snap.hasInstalling ? "installing" : snap.hasWaiting ? "waiting" : snap.activeUrl ? "active" : "none";
|
|
1427
|
+
const stateColor = swState === "active" ? C.green : swState === "waiting" || swState === "installing" ? C.yellow : C.muted;
|
|
1428
|
+
const shortUrl = snap.activeUrl ? snap.activeUrl.replace(/^https?:\/\/[^/]+/, "") || snap.activeUrl : null;
|
|
1429
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1430
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1431
|
+
style: {
|
|
1432
|
+
display: "flex",
|
|
1433
|
+
alignItems: "center",
|
|
1434
|
+
gap: 8,
|
|
1435
|
+
padding: "8px 12px",
|
|
1436
|
+
borderBottom: `1px solid ${C.border}`
|
|
1437
|
+
},
|
|
1438
|
+
children: [
|
|
1439
|
+
/* @__PURE__ */ jsx("span", {
|
|
1440
|
+
style: {
|
|
1441
|
+
color: C.muted,
|
|
1442
|
+
display: "inline-flex"
|
|
1443
|
+
},
|
|
1444
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1445
|
+
path: ICONS.shield,
|
|
1446
|
+
size: 12
|
|
1447
|
+
})
|
|
1448
|
+
}),
|
|
1449
|
+
/* @__PURE__ */ jsx("span", {
|
|
1450
|
+
style: pill(stateColor),
|
|
1451
|
+
children: swState
|
|
1452
|
+
}),
|
|
1453
|
+
shortUrl !== null ? /* @__PURE__ */ jsx("span", {
|
|
1454
|
+
title: snap.activeUrl ?? void 0,
|
|
1455
|
+
style: {
|
|
1456
|
+
flex: 1,
|
|
1457
|
+
color: C.muted,
|
|
1458
|
+
fontSize: 10,
|
|
1459
|
+
overflow: "hidden",
|
|
1460
|
+
textOverflow: "ellipsis",
|
|
1461
|
+
whiteSpace: "nowrap"
|
|
1462
|
+
},
|
|
1463
|
+
children: shortUrl
|
|
1464
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
1465
|
+
style: {
|
|
1466
|
+
flex: 1,
|
|
1467
|
+
color: C.muted,
|
|
1468
|
+
fontSize: 10
|
|
1469
|
+
},
|
|
1470
|
+
children: "No SW registered"
|
|
1471
|
+
}),
|
|
1472
|
+
/* @__PURE__ */ jsx("button", {
|
|
1473
|
+
onClick: () => setSnap(readSwSnapshot()),
|
|
1474
|
+
title: "Refresh SW state",
|
|
1475
|
+
"aria-label": "Refresh SW state",
|
|
1476
|
+
...withFocusRing(),
|
|
1477
|
+
style: {
|
|
1478
|
+
...btn("ghost"),
|
|
1479
|
+
padding: "2px 6px",
|
|
1480
|
+
minHeight: 20
|
|
1481
|
+
},
|
|
1482
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1483
|
+
path: ICONS.refreshCw,
|
|
1484
|
+
size: 10
|
|
1485
|
+
})
|
|
1486
|
+
})
|
|
1487
|
+
]
|
|
1488
|
+
}),
|
|
1489
|
+
snap.hasWaiting ? /* @__PURE__ */ jsxs("div", {
|
|
1490
|
+
style: {
|
|
1491
|
+
display: "flex",
|
|
1492
|
+
alignItems: "center",
|
|
1493
|
+
gap: 8,
|
|
1494
|
+
padding: "8px 12px",
|
|
1495
|
+
borderBottom: `1px solid ${C.border}`,
|
|
1496
|
+
background: `${C.yellow}11`
|
|
1497
|
+
},
|
|
1498
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1499
|
+
style: {
|
|
1500
|
+
color: C.yellow,
|
|
1501
|
+
fontSize: 10,
|
|
1502
|
+
flex: 1
|
|
1503
|
+
},
|
|
1504
|
+
children: "Update ready — new SW is waiting to activate."
|
|
1505
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1506
|
+
onClick: triggerSwUpdate,
|
|
1507
|
+
title: "Activate the waiting SW now",
|
|
1508
|
+
...withFocusRing(),
|
|
1509
|
+
style: {
|
|
1510
|
+
...btn("primary"),
|
|
1511
|
+
minHeight: 22,
|
|
1512
|
+
fontSize: 10
|
|
1513
|
+
},
|
|
1514
|
+
children: "Force update"
|
|
1515
|
+
})]
|
|
1516
|
+
}) : null,
|
|
1517
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1518
|
+
style: {
|
|
1519
|
+
padding: "6px 12px",
|
|
1520
|
+
color: C.muted,
|
|
1521
|
+
fontSize: 10,
|
|
1522
|
+
borderBottom: `1px solid ${C.border}`
|
|
1523
|
+
},
|
|
1524
|
+
children: [
|
|
1525
|
+
"Cache buckets (",
|
|
1526
|
+
bucketList.length,
|
|
1527
|
+
")"
|
|
1528
|
+
]
|
|
1529
|
+
}),
|
|
1530
|
+
bucketList.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
1531
|
+
style: {
|
|
1532
|
+
padding: "16px 12px",
|
|
1533
|
+
textAlign: "center",
|
|
1534
|
+
color: C.muted,
|
|
1535
|
+
fontSize: 10
|
|
1536
|
+
},
|
|
1537
|
+
children: "No resources registered"
|
|
1538
|
+
}) : bucketList.map(([name, count]) => /* @__PURE__ */ jsxs("div", {
|
|
1539
|
+
style: {
|
|
1540
|
+
display: "flex",
|
|
1541
|
+
alignItems: "center",
|
|
1542
|
+
justifyContent: "space-between",
|
|
1543
|
+
padding: "7px 12px",
|
|
1544
|
+
borderBottom: `1px solid ${C.border}`
|
|
1545
|
+
},
|
|
1546
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1547
|
+
style: {
|
|
1548
|
+
color: C.text,
|
|
1549
|
+
fontSize: 10,
|
|
1550
|
+
overflow: "hidden",
|
|
1551
|
+
textOverflow: "ellipsis",
|
|
1552
|
+
whiteSpace: "nowrap",
|
|
1553
|
+
flex: 1
|
|
1554
|
+
},
|
|
1555
|
+
children: name
|
|
1556
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1557
|
+
style: pill(C.blue),
|
|
1558
|
+
children: [
|
|
1559
|
+
count,
|
|
1560
|
+
" resource",
|
|
1561
|
+
count !== 1 ? "s" : ""
|
|
1562
|
+
]
|
|
1563
|
+
})]
|
|
1564
|
+
}, name))
|
|
1565
|
+
] });
|
|
1566
|
+
}
|
|
1387
1567
|
//#endregion
|
|
1388
1568
|
export { EidosDevtools };
|
package/dist/eidos-sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region src/internal/url-base64.ts
|
|
2
2
|
/** Decodes a base64url string (e.g. a VAPID public key) into raw bytes. */
|
|
3
3
|
function urlBase64ToUint8Array(base64Url) {
|
|
4
4
|
const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -13,9 +13,7 @@ var runtimeConfig = {
|
|
|
13
13
|
resources: /* @__PURE__ */ new Map(),
|
|
14
14
|
simulateOffline: false
|
|
15
15
|
};
|
|
16
|
-
self.addEventListener("install", (
|
|
17
|
-
event.waitUntil(self.skipWaiting());
|
|
18
|
-
});
|
|
16
|
+
self.addEventListener("install", () => {});
|
|
19
17
|
self.addEventListener("activate", (event) => {
|
|
20
18
|
event.waitUntil(Promise.all([self.clients.claim(), caches.keys().then((keys) => Promise.all(keys.filter((k) => k.startsWith(CACHE_PREFIX) && !k.endsWith(CACHE_VERSION)).map((k) => caches.delete(k))))]));
|
|
21
19
|
});
|
|
@@ -29,7 +27,9 @@ self.addEventListener("message", (event) => {
|
|
|
29
27
|
runtimeConfig.resources.set(url, {
|
|
30
28
|
strategy: data.strategy,
|
|
31
29
|
cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
|
|
32
|
-
...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
|
|
30
|
+
...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) },
|
|
31
|
+
...data.maxAge !== void 0 && { maxAge: data.maxAge },
|
|
32
|
+
...data.maxEntries !== void 0 && { maxEntries: data.maxEntries }
|
|
33
33
|
});
|
|
34
34
|
event.source?.postMessage({
|
|
35
35
|
type: "EIDOS_RESOURCE_REGISTERED",
|
|
@@ -65,6 +65,9 @@ self.addEventListener("message", (event) => {
|
|
|
65
65
|
});
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
|
+
case "EIDOS_SKIP_WAITING":
|
|
69
|
+
self.skipWaiting();
|
|
70
|
+
break;
|
|
68
71
|
case "EIDOS_PING":
|
|
69
72
|
event.source?.postMessage({ type: "EIDOS_PONG" });
|
|
70
73
|
break;
|
|
@@ -88,7 +91,7 @@ self.addEventListener("fetch", (event) => {
|
|
|
88
91
|
}
|
|
89
92
|
if (!reg) return;
|
|
90
93
|
if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
|
|
91
|
-
event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg
|
|
94
|
+
event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg));
|
|
92
95
|
return;
|
|
93
96
|
}
|
|
94
97
|
event.respondWith(handleFetch(event.request, pathname, reg));
|
|
@@ -96,16 +99,45 @@ self.addEventListener("fetch", (event) => {
|
|
|
96
99
|
async function handleFetch(request, pathname, reg) {
|
|
97
100
|
if (runtimeConfig.simulateOffline) return serveOffline(request, pathname, reg.cacheName);
|
|
98
101
|
switch (reg.strategy) {
|
|
99
|
-
case "cache-first": return cacheFirst(request, pathname, reg
|
|
100
|
-
case "stale-while-revalidate": return staleWhileRevalidate(null, request, pathname, reg
|
|
101
|
-
case "network-first": return networkFirst(request, pathname, reg
|
|
102
|
+
case "cache-first": return cacheFirst(request, pathname, reg);
|
|
103
|
+
case "stale-while-revalidate": return staleWhileRevalidate(null, request, pathname, reg);
|
|
104
|
+
case "network-first": return networkFirst(request, pathname, reg);
|
|
102
105
|
default: return fetch(request);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
var CACHED_AT_HEADER = "X-Eidos-Cached-At";
|
|
109
|
+
/**
|
|
110
|
+
* Puts a response into cache with a `X-Eidos-Cached-At` timestamp header so
|
|
111
|
+
* the SW can enforce `maxAge` on subsequent cache hits.
|
|
112
|
+
* Caller must pass a clone of the response — `response.body` is consumed here.
|
|
113
|
+
*/
|
|
114
|
+
async function putCached(cache, request, response) {
|
|
115
|
+
const headers = new Headers(response.headers);
|
|
116
|
+
headers.set(CACHED_AT_HEADER, String(Date.now()));
|
|
117
|
+
await cache.put(request, new Response(response.body, {
|
|
118
|
+
status: response.status,
|
|
119
|
+
statusText: response.statusText,
|
|
120
|
+
headers
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
/** Returns true if the cached response has exceeded `maxAge`. */
|
|
124
|
+
function isExpired(cached, maxAge) {
|
|
125
|
+
if (maxAge === void 0) return false;
|
|
126
|
+
const cachedAt = Number(cached.headers.get(CACHED_AT_HEADER) ?? "0");
|
|
127
|
+
return cachedAt > 0 && Date.now() - cachedAt > maxAge;
|
|
128
|
+
}
|
|
129
|
+
/** Evicts the oldest (first-inserted) entries when the cache exceeds `maxEntries`. */
|
|
130
|
+
async function evictIfNeeded(cache, maxEntries) {
|
|
131
|
+
if (maxEntries === void 0) return;
|
|
132
|
+
const keys = await cache.keys();
|
|
133
|
+
const overflow = keys.length - maxEntries;
|
|
134
|
+
if (overflow > 0) await Promise.all(keys.slice(0, overflow).map((k) => cache.delete(k)));
|
|
135
|
+
}
|
|
136
|
+
async function cacheFirst(request, pathname, reg) {
|
|
137
|
+
const { cacheName, maxAge, maxEntries } = reg;
|
|
106
138
|
const cache = await caches.open(cacheName);
|
|
107
139
|
const cached = await cache.match(request);
|
|
108
|
-
if (cached) {
|
|
140
|
+
if (cached && !isExpired(cached, maxAge)) {
|
|
109
141
|
notifyClients({
|
|
110
142
|
type: "EIDOS_CACHE_HIT",
|
|
111
143
|
url: pathname,
|
|
@@ -113,10 +145,12 @@ async function cacheFirst(request, pathname, cacheName) {
|
|
|
113
145
|
});
|
|
114
146
|
return cached;
|
|
115
147
|
}
|
|
148
|
+
if (cached) await cache.delete(request);
|
|
116
149
|
try {
|
|
117
150
|
const response = await fetch(request);
|
|
118
151
|
if (response.ok) {
|
|
119
|
-
await cache
|
|
152
|
+
await putCached(cache, request, response.clone());
|
|
153
|
+
await evictIfNeeded(cache, maxEntries);
|
|
120
154
|
notifyClients({
|
|
121
155
|
type: "EIDOS_CACHE_UPDATED",
|
|
122
156
|
url: pathname,
|
|
@@ -132,12 +166,17 @@ async function cacheFirst(request, pathname, cacheName) {
|
|
|
132
166
|
return offlineErrorResponse(pathname);
|
|
133
167
|
}
|
|
134
168
|
}
|
|
135
|
-
async function staleWhileRevalidate(event, request, pathname,
|
|
169
|
+
async function staleWhileRevalidate(event, request, pathname, reg) {
|
|
170
|
+
const { cacheName, maxAge, maxEntries } = reg;
|
|
136
171
|
const cache = await caches.open(cacheName);
|
|
137
172
|
const cached = await cache.match(request);
|
|
173
|
+
const expired = cached ? isExpired(cached, maxAge) : false;
|
|
174
|
+
if (expired) await cache.delete(request);
|
|
175
|
+
const effectiveCached = expired ? null : cached;
|
|
138
176
|
const revalidatePromise = fetch(request).then(async (response) => {
|
|
139
177
|
if (response.ok) {
|
|
140
|
-
await cache
|
|
178
|
+
await putCached(cache, request, response.clone());
|
|
179
|
+
await evictIfNeeded(cache, maxEntries);
|
|
141
180
|
notifyClients({
|
|
142
181
|
type: "EIDOS_CACHE_UPDATED",
|
|
143
182
|
url: pathname,
|
|
@@ -152,23 +191,25 @@ async function staleWhileRevalidate(event, request, pathname, cacheName) {
|
|
|
152
191
|
strategy: "stale-while-revalidate"
|
|
153
192
|
});
|
|
154
193
|
});
|
|
155
|
-
if (
|
|
194
|
+
if (effectiveCached) {
|
|
156
195
|
event?.waitUntil(revalidatePromise);
|
|
157
196
|
notifyClients({
|
|
158
197
|
type: "EIDOS_CACHE_HIT",
|
|
159
198
|
url: pathname,
|
|
160
199
|
strategy: "stale-while-revalidate"
|
|
161
200
|
});
|
|
162
|
-
return
|
|
201
|
+
return effectiveCached;
|
|
163
202
|
}
|
|
164
203
|
return await revalidatePromise ?? offlineErrorResponse(pathname);
|
|
165
204
|
}
|
|
166
|
-
async function networkFirst(request, pathname,
|
|
205
|
+
async function networkFirst(request, pathname, reg) {
|
|
206
|
+
const { cacheName, maxAge, maxEntries } = reg;
|
|
167
207
|
const cache = await caches.open(cacheName);
|
|
168
208
|
try {
|
|
169
209
|
const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
|
|
170
210
|
if (response.ok) {
|
|
171
|
-
await cache
|
|
211
|
+
await putCached(cache, request, response.clone());
|
|
212
|
+
await evictIfNeeded(cache, maxEntries);
|
|
172
213
|
notifyClients({
|
|
173
214
|
type: "EIDOS_CACHE_UPDATED",
|
|
174
215
|
url: pathname,
|
|
@@ -178,7 +219,7 @@ async function networkFirst(request, pathname, cacheName) {
|
|
|
178
219
|
return response;
|
|
179
220
|
} catch {
|
|
180
221
|
const cached = await cache.match(request);
|
|
181
|
-
if (cached) {
|
|
222
|
+
if (cached && !isExpired(cached, maxAge)) {
|
|
182
223
|
notifyClients({
|
|
183
224
|
type: "EIDOS_CACHE_HIT",
|
|
184
225
|
url: pathname,
|
package/dist/eidos.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let R=require("react"),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let R=require("react"),V=require("react/jsx-runtime");function X(){return{queued:0,succeeded:0,failed:0,retried:0,conflicted:0,cancelled:0}}function z(e){let t=0,n=0,r=0;for(const a of e)a.status==="pending"?t++:a.status==="failed"?n++:a.status==="replaying"&&r++;return{pending:t,failed:n,replaying:r,total:e.length}}function ve(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 me(e){return{addQueueItem:t=>e(n=>({queue:[...n.queue,t]})),updateQueueItem:(t,n)=>e(r=>({queue:r.queue.map(a=>a.id===t?{...a,...n}:a)})),batchUpdateQueueItems:t=>e(n=>{const r=new Map(t.map(a=>[a.id,a.update]));return{queue:n.queue.map(a=>{const s=r.get(a.id);return s?{...a,...s}:a})}}),removeQueueItem:t=>e(n=>({queue:n.queue.filter(r=>r.id!==t)})),hydrateQueue:t=>e(()=>({queue:t}))}}function Se(e){return{recordReliabilityEvent:t=>e(n=>({reliability:{...n.reliability,[t]:n.reliability[t]+1}})),resetReliabilityStats:()=>e(()=>({reliability:X()}))}}var w,M=new Set;function Z(){M.forEach(e=>e())}function E(e){w={...w,...e(w)},Z()}w={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],reliability:X(),setOnline:e=>E(()=>({isOnline:e})),setSwStatus:(e,t)=>E(()=>({swStatus:e,swError:t})),...ve(E),...me(E),...Se(E)};function be(){return w}function Ee(e){return M.add(e),()=>{M.delete(e)}}var o={getState:be,subscribe:Ee,setState:e=>{const t=typeof e=="function"?e(w):e;w={...w,...t},Z()}},h=null,T=[];function H(){return h}async function Re(e,t={skipWaiting:!0}){if(typeof navigator>"u"||!("serviceWorker"in navigator)){o.getState().setSwStatus("unsupported");return}const n=o.getState();n.setSwStatus("registering");try{h=await navigator.serviceWorker.register(e,{scope:"/"}),await Ae(h),n.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",Oe),window.addEventListener("online",()=>n.setOnline(!0)),window.addEventListener("offline",()=>n.setOnline(!1)),qe(),Ce(h,t)}catch(r){n.setSwStatus("error",String(r))}}function Ae(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 a(){n.state==="activated"&&(clearTimeout(r),n.removeEventListener("statechange",a),t())})})}function I(e){const t=h?.active;t?t.postMessage(e):T.push(e)}var ee=null;function Ie(e){ee=e}function _e(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&h!==null&&"sync"in h}catch{return!1}}var U={};function ke(e){U=e}function Oe(e){const t=e.data;if(!t?.type)return;const n=o.getState(),{type:r,url:a}=t;if(r==="EIDOS_BACKGROUND_SYNC"){ee?.();return}if(r==="EIDOS_NOTIFICATION_CLICK"){U.onNotificationClick?.(t.data);return}if(r==="EIDOS_SUBSCRIPTION_EXPIRED"){U.onSubscriptionExpired?.(t.subscription);return}if(a)switch(r){case"EIDOS_CACHE_HIT":{const s=n.resources[a];n.updateResource(a,{status:"fresh",lastEvent:"cache-hit",cacheHits:(s?.cacheHits??0)+1});break}case"EIDOS_CACHE_UPDATED":n.updateResource(a,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break;case"EIDOS_NETWORK_ERROR":n.updateResource(a,{status:"error",lastEvent:"network-error"});break}}function xe(e){I({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),o.getState().setOnline(!e)}function qe(){const e=h?.active;if(e){for(const t of T)e.postMessage(t);T=[]}}function Ce(e,t){const n=r=>{t.skipWaiting?r.waiting?.postMessage({type:"EIDOS_SKIP_WAITING"}):t.onUpdateAvailable?.(r)};e.waiting&&navigator.serviceWorker.controller&&n(e),e.addEventListener("updatefound",()=>{const r=e.installing;r&&r.addEventListener("statechange",()=>{r.state==="installed"&&navigator.serviceWorker.controller&&n(e)})})}function Qe(){h?.waiting?.postMessage({type:"EIDOS_SKIP_WAITING"})}var v=new Map,Q=new Map,te=null;function Pe(e){te=e}function C(e){return e.includes("*")||/:[^/]+/.test(e)}function De(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function ne(e,t){const n=Ue(t),r=C(e)?De(e):void 0,a={url:e,config:t,strategy:n,status:"idle",cacheHits:0,cacheMisses:0};return o.getState().registerResource(e,a),I({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:n.swStrategy,cacheName:n.cacheName,...r!==void 0&&{pattern:r},...t.maxAge!==void 0&&{maxAge:t.maxAge},...t.maxEntries!==void 0&&{maxEntries:t.maxEntries}}),{strategy:n,regexStr:r}}function re(e,t,n){return async()=>{I({type:"EIDOS_CLEAR_CACHE",url:e});const r=await caches.open(t.cacheName).catch(()=>null);if(r){const a=await r.keys(),s=n?new RegExp(n):null,i=e.startsWith("http");await Promise.all(a.filter(c=>{const u=c.url,f=new URL(u).pathname;return s?s.test(i?u:f):i?u===e:u===e||f===e}).map(c=>r.delete(c)))}C(e)||o.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),te?.(["eidos",e])}}function ae(e){return()=>{v.delete(e),I({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),o.getState().unregisterResource(e)}}function Ne(e,t){if(C(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:n}=ne(e,t),r={url:e,config:t,strategy:n,fetch:async()=>{const a=Q.get(e);if(a)return a.then(i=>i.clone());const s=Te(e,t,n);return Q.set(e,s),s.finally(()=>Q.delete(e)).catch(()=>{}),s.then(i=>i.clone())},json:async()=>(await r.fetch()).json(),query:()=>({queryKey:["eidos",e],queryFn:()=>r.json()}),prefetch:async()=>{await r.fetch()},invalidate:re(e,n,void 0),unregister:ae(e)};return v.set(e,r),r}function Me(e,t){if(!C(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:n,regexStr:r}=ne(e,t),a={url:e,config:t,strategy:n,invalidate:re(e,n,r),unregister:ae(e)};return v.set(e,a),a}async function Te(e,t,n){const r=o.getState();r.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const a=await caches.open(n.cacheName).catch(()=>null);try{if(n.swStrategy!=="network-first"){const c=a?await a.match(e).catch(()=>null):null,u=o.getState().resources[e],f=t.maxAge!==void 0&&u?.cachedAt!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(c&&!f)return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(u?.cacheHits??0)+1}),n.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async S=>{S.ok&&a&&(await a.put(e,S.clone()),o.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),c;const p=o.getState().resources[e];r.updateResource(e,{cacheMisses:(p?.cacheMisses??0)+1})}const s=await fetch(e);if(s.ok)return a&&await a.put(e,s.clone()),r.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),s;r.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 r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(c?.cacheHits??0)+1}),i}throw r.updateResource(e,{status:"error"}),s}}function Ue(e){const t=e.strategy;return e.offline?G(t??"stale-while-revalidate",e.cacheName,e.version):G(t??"network-first",e.cacheName,e.version)}var je={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},Ke={"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 (maxEntries/maxAge are configured via ResourceConfig)
|
|
2
2
|
new StaleWhileRevalidate({
|
|
3
3
|
cacheName: 'eidos-resources-v1',
|
|
4
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
5
|
-
})`},"cache-first":{reasoning:"cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.",behavior:["Cache hit → return immediately, no network request made","Cache miss → fetch from network, cache the response, return it","Offline → return cached version, 503 if cache is empty","Cache never expires unless explicitly invalidated"],equivalentCode:`// Workbox equivalent
|
|
4
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
5
|
+
})`},"cache-first":{reasoning:"cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.",behavior:["Cache hit → return immediately, no network request made","Cache miss → fetch from network, cache the response, return it","Offline → return cached version, 503 if cache is empty","Cache never expires unless maxAge is set or explicitly invalidated"],equivalentCode:`// Workbox equivalent (maxEntries/maxAge are configured via ResourceConfig)
|
|
6
6
|
new CacheFirst({
|
|
7
7
|
cacheName: 'eidos-resources-v1',
|
|
8
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
8
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
9
9
|
})`},"network-first":{reasoning:"network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.",behavior:["Always try network first","Network success → update cache, return fresh response","Network failure → fall back to cached version","Offline with empty cache → return 503 error response"],equivalentCode:`// Workbox equivalent
|
|
10
10
|
new NetworkFirst({
|
|
11
11
|
cacheName: 'eidos-resources-v1',
|
|
12
12
|
networkTimeoutSeconds: 3,
|
|
13
|
-
})`}};function V(e,t,n){const r=Me[e],a=t??"eidos-resources-v1";return{name:Te[e],swStrategy:e,cacheName:n!==void 0?`${a}-v${n}`:a,reasoning:r.reasoning,behavior:r.behavior,equivalentCode:""}}async function je(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 Ue="eidos",Ke=1,l="action-queue",P=null;function b(){return P?Promise.resolve(P):new Promise((e,t)=>{const n=indexedDB.open(Ue,Ke);n.onupgradeneeded=r=>{const a=r.target.result;if(!a.objectStoreNames.contains(l)){const s=a.createObjectStore(l,{keyPath:"id"});s.createIndex("status","status",{unique:!1}),s.createIndex("actionId","actionId",{unique:!1})}},n.onsuccess=()=>{P=n.result,e(n.result)},n.onerror=()=>t(n.error)})}async function $e(e){const t=await b();return new Promise((n,r)=>{const a=t.transaction(l,"readwrite");a.objectStore(l).add(e),a.oncomplete=()=>n(),a.onerror=()=>r(a.error)})}async function se(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(l,"readonly").objectStore(l).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function Le(e,t){const n=await b();return new Promise((r,a)=>{const s=n.transaction(l,"readwrite"),i=s.objectStore(l),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},s.oncomplete=()=>r(),s.onerror=()=>a(s.error)})}async function We(e){const t=await b();return new Promise((n,r)=>{const a=t.transaction(l,"readwrite");a.objectStore(l).delete(e),a.oncomplete=()=>n(),a.onerror=()=>r(a.error)})}async function Be(){const e=await b();function t(a){return new Promise((s,i)=>{const c=e.transaction(l,"readonly").objectStore(l).index("status"),u=[],f=c.openCursor(IDBKeyRange.only(a));f.onsuccess=p=>{const S=p.target.result;S?(u.push(S.value),S.continue()):s(u)},f.onerror=()=>i(f.error)})}const[n,r]=await Promise.all([t("pending"),t("failed")]);return[...n,...r]}async function He(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(l,"readwrite");r.objectStore(l).clear(),r.oncomplete=()=>t(),r.onerror=()=>n(r.error)})}var ie={add:$e,getAll:se,getPending:Be,update:Le,remove:We,clear:He},oe=null;function Fe(e){oe=e}function B(){return oe}var Ve="eidos-queue-sync",k;function ce(){return k!==void 0||(k=typeof BroadcastChannel>"u"?null:new BroadcastChannel(Ve)),k}function h(e){ce()?.postMessage(e)}function Ge(){const e=ce();if(!e)return()=>{};const t=n=>{const r=o.getState(),a=n.data;switch(a.type){case"update":r.updateQueueItem(a.id,a.update);break;case"batchUpdate":r.batchUpdateQueueItems(a.updates);break;case"remove":r.removeQueueItem(a.id);break}};return e.addEventListener("message",t),()=>e.removeEventListener("message",t)}var q=new Map,ue=new Map,de=new Map,le=new Map,I=new Map;function d(){return B()??ie}function U(){return crypto.randomUUID()}function K(e,t,n){return e(...t,n)}function Ye(e,t){const n=t.name||e.name||U(),r=t.namespace?`${t.namespace}::${n}`:n;if(q.has(r))throw new Error(`[eidos] duplicate action id "${r}" — an action with this id is already registered. Pass a unique config.name or config.namespace.`);q.set(r,e),le.set(r,t),t.onRollback&&ue.set(r,t.onRollback),t.conflict&&de.set(r,t.conflict);const a=async(...s)=>{const{isOnline:i}=o.getState(),c=U();let u;if(t.cancellable){const p=new AbortController;I.set(c,p),u=p.signal}const f={idempotencyKey:c,attempt:0,signal:u};t.onOptimistic?.(...s,f);try{if(t.reliability==="neverLose"){if(!i)return G(r,r,s,t,c);try{return await K(e,s,f)}catch(p){if(pe(p))throw p;return G(r,r,s,t,c)}}try{return await K(e,s,f)}catch(p){throw t.onRollback?.(...s,f),p}}finally{t.cancellable&&I.delete(c)}};return Object.defineProperty(a,"id",{value:r,writable:!1}),Object.defineProperty(a,"config",{value:t,writable:!1}),Object.defineProperty(a,"cancel",{value:fe,writable:!1}),a}async function fe(e){const t=I.get(e);if(t)return t.abort(),!0;const n=(await d().getAll()).find(r=>r.idempotencyKey===e&&r.status==="pending");return n?(o.getState().removeQueueItem(n.id),h({type:"remove",id:n.id}),await d().remove(n.id),!0):!1}async function Je(e){const t=(await d().getAll()).find(r=>r.id===e);if(!t||t.status!=="failed")return!1;const n={status:"pending",error:void 0,nextRetryAt:void 0,retryCount:0};return o.getState().updateQueueItem(e,n),h({type:"update",id:e,update:n}),await d().update(e,n),!0}async function G(e,t,n,r,a){const s=U(),i={schemaVersion:2,id:s,actionId:e,actionName:t,idempotencyKey:a,args:n,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending",priority:r.priority??"normal"};await d().add(i),o.getState().addQueueItem(i),o.getState().recordReliabilityEvent("queued");try{const c=Z();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 pe(e){return e instanceof DOMException&&e.name==="AbortError"}function Xe(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 ze(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 D=!1,Ze="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(Ze,{ifAvailable:!0},async t=>t?Y(e):O());if(D)return O();D=!0;try{return await Y(e)}finally{D=!1}}async function et(e,t){const n=Date.now();t.updateQueueItem(e.id,{status:"succeeded",completedAt:n}),t.recordReliabilityEvent("succeeded"),h({type:"update",id:e.id,update:{status:"succeeded",completedAt:n}}),await d().update(e.id,{status:"succeeded",completedAt:n}),setTimeout(()=>{t.removeQueueItem(e.id),h({type:"remove",id:e.id}),d().remove(e.id)},3e3)}async function tt(e,t,n){const r=de.get(e.actionId);let a;if(r)switch(r.strategy){case"serverWins":a="skip";break;case"clientWins":a="retry";break;case"merge":case"custom":{const s={error:n,args:e.args,attempt:e.retryCount,idempotencyKey:e.idempotencyKey};a=r.resolve?.(s)??"retry";break}}if(a==="skip")return t.removeQueueItem(e.id),t.recordReliabilityEvent("conflicted"),h({type:"remove",id:e.id}),await d().remove(e.id),"conflicted";a&&typeof a=="object"&&(e.args=a.resolved,t.updateQueueItem(e.id,{args:a.resolved}),h({type:"update",id:e.id,update:{args:a.resolved}}),await d().update(e.id,{args:a.resolved}))}async function nt(e,t,n){const r=e.retryCount+1;if(r>=e.maxRetries){const s={status:"failed",error:String(n),retryCount:r};t.updateQueueItem(e.id,s),t.recordReliabilityEvent("failed"),h({type:"update",id:e.id,update:s}),await d().update(e.id,s);const i={idempotencyKey:e.idempotencyKey,attempt:r};return ue.get(e.actionId)?.(...e.args,i),"failed"}const a={status:"pending",retryCount:r,nextRetryAt:Date.now()+ze(r)};return t.updateQueueItem(e.id,a),t.recordReliabilityEvent("retried"),h({type:"update",id:e.id,update:a}),await d().update(e.id,a),"retrying"}async function rt(e,t){const n=q.get(e.actionId);if(!n)return"skipped";const r=le.get(e.actionId)?.cancellable;let a;if(r){const i=new AbortController;I.set(e.idempotencyKey,i),a=i.signal}const s={idempotencyKey:e.idempotencyKey,attempt:e.retryCount,signal:a};try{return await K(n,e.args,s),await et(e,t),"succeeded"}catch(i){if(pe(i))return t.removeQueueItem(e.id),t.recordReliabilityEvent("cancelled"),h({type:"remove",id:e.id}),await d().remove(e.id),"cancelled";if(Xe(i)){const c=await tt(e,t,i);if(c)return c}return nt(e,t,i)}finally{r&&I.delete(e.idempotencyKey)}}async function at(e,t,n){if(e.length===0)return;const r=e.filter(s=>q.has(s.actionId));if(n.skipped+=e.length-r.length,r.length>0){const s=r.map(i=>({id:i.id,update:{status:"replaying"}}));t.batchUpdateQueueItems(s),h({type:"batchUpdate",updates:s});for(const i of r)d().update(i.id,{status:"replaying"})}const a=await Promise.allSettled(r.map(s=>rt(s,t)));for(const s of a){const i=s.status==="fulfilled"?s.value:"failed";i==="skipped"?n.skipped++:i==="conflicted"?n.conflicted++:i==="cancelled"?n.cancelled++:(n.attempted++,n[i]++)}}async function Y(e){const t=await d().getPending(),n=Date.now(),r=t.filter(s=>s.retryCount<s.maxRetries&&(!s.nextRetryAt||s.nextRetryAt<=n)),a=O();for(const s of["high","normal","low"])await at(r.filter(i=>(i.priority??"normal")===s),e,a);return a}async function st(){await d().clear(),o.getState().hydrateQueue([])}function ye(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:a}=o.getState(),s=a&&!e;e=a,s&&setTimeout(Q,600)}),n=o.getState(),r=n.queue.some(a=>a.status==="pending");return n.isOnline&&r&&setTimeout(Q,1200),t}async function it(e){if(e.schemaVersion===2&&e.idempotencyKey)return e;const t={...e,schemaVersion:2,idempotencyKey:e.idempotencyKey??crypto.randomUUID()};return await(B()??ie).update(t.id,{schemaVersion:t.schemaVersion,idempotencyKey:t.idempotencyKey}).catch(()=>{}),t}var $=!1,L=null,W=null,A=null;async function he(e={}){if(typeof window>"u"||$)return;$=!0;const t=e.swPath??"/eidos-sw.js",n=e.autoReplay??!0;try{const r=await se();if(r.length>0){const a=await Promise.all(r.map(it));o.getState().hydrateQueue(a)}}catch{}try{await Ee(t)}catch{}if(Ie(()=>{o.getState().isOnline&&setTimeout(Q,200)}),n&&(L=ye()),W=Ge(),e.onReliabilityReport){const r=e.reliabilityReportInterval??6e4,a=e.onReliabilityReport;A=setInterval(()=>{a(o.getState().reliability)},r)}}function ot(){L?.(),L=null,W?.(),W=null,A&&clearInterval(A),A=null,$=!1}var N="@eidos:queue",ct=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 n=await this.readAll(),r=n.findIndex(a=>a.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(N)}};function ut({children:e,swPath:t,autoReplay:n}){return(0,R.useEffect)(()=>{he({swPath:t,autoReplay:n})},[]),(0,F.jsx)(F.Fragment,{children:e})}function dt(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 H(e,t){return dt(e,t)}function m(e,t=Object.is){return{subscribe(n){let r=e(o.getState());return n(r),o.subscribe(()=>{const a=e(o.getState());t(r,a)||(r=a,n(a))})},getState(){return e(o.getState())}}}var lt=m(e=>e),ft=m(e=>e.queue),pt=m(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),H),yt=m(e=>X(e.queue),H),ht=m(e=>e.reliability,H);function gt(e){return m(t=>t.resources[e])}function wt(e){return m(t=>t.queue.find(n=>n.id===e))}function ge(e){let t=o.getState().queue.length;return o.subscribe(()=>{const n=o.getState().queue.length;t>0&&n===0&&e(),t=n})}function y(e){const t=e??(n=>n);return(0,R.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function vt(){return y()}function mt(){return y(e=>e.resources)}function St(e){return y(t=>t.resources[e])}function bt(){return y(e=>e.queue)}function Et(e){return y(t=>t.queue.find(n=>n.id===e))}function Rt(){return{isOnline:y(e=>e.isOnline),swStatus:y(e=>e.swStatus),swError:y(e=>e.swError)}}function It(){const[e,t,n,r]=y(a=>{const{pending:s,failed:i,replaying:c,total:u}=X(a.queue);return`${s},${i},${c},${u}`}).split(",");return{pending:+e,failed:+t,replaying:+n,total:+r}}function _t(){return y(e=>e.reliability)}function kt(e){const t=(0,R.useRef)(e);(0,R.useEffect)(()=>{t.current=e}),(0,R.useEffect)(()=>ge(()=>t.current()),[])}var Ot="2.2.0";exports.AsyncStorageQueueStorage=ct;exports.EidosProvider=ut;exports.VERSION=Ot;exports._getQueueStorage=B;exports._resetEidos=ot;exports.action=Ye;exports.cancelByIdempotencyKey=fe;exports.clearQueue=st;exports.eidosAction=wt;exports.eidosQueue=ft;exports.eidosQueueStats=yt;exports.eidosReliabilityStats=ht;exports.eidosResource=gt;exports.eidosStatus=pt;exports.eidosStore=lt;exports.getSwRegistration=Z;exports.initEidos=he;exports.isBgSyncSupported=_e;exports.onQueueDrain=ge;exports.registerPushCallbacks=ke;exports.replayQueue=Q;exports.requeueItem=Je;exports.resource=xe;exports.resourcePattern=Pe;exports.sendToWorker=_;exports.setOfflineSimulation=Ae;exports.setQueryInvalidator=Qe;exports.setQueueStorage=Fe;exports.subscribeReplayOnReconnect=ye;exports.useEidos=vt;exports.useEidosAction=Et;exports.useEidosOnDrain=kt;exports.useEidosQueue=bt;exports.useEidosQueueStats=It;exports.useEidosReliabilityStats=_t;exports.useEidosResource=St;exports.useEidosResources=mt;exports.useEidosStatus=Rt;exports.useEidosStore=o;exports.warmCache=je;
|
|
13
|
+
})`}};function G(e,t,n){const r=Ke[e],a=t??"eidos-resources-v1";return{name:je[e],swStrategy:e,cacheName:n!==void 0?`${a}-v${n}`:a,reasoning:r.reasoning,behavior:r.behavior,equivalentCode:""}}async function We(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 Le="eidos",$e=1,l="action-queue",P=null;function b(){return P?Promise.resolve(P):new Promise((e,t)=>{const n=indexedDB.open(Le,$e);n.onupgradeneeded=r=>{const a=r.target.result;if(!a.objectStoreNames.contains(l)){const s=a.createObjectStore(l,{keyPath:"id"});s.createIndex("status","status",{unique:!1}),s.createIndex("actionId","actionId",{unique:!1})}},n.onsuccess=()=>{P=n.result,e(n.result)},n.onerror=()=>t(n.error)})}async function He(e){const t=await b();return new Promise((n,r)=>{const a=t.transaction(l,"readwrite");a.objectStore(l).add(e),a.oncomplete=()=>n(),a.onerror=()=>r(a.error)})}async function se(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(l,"readonly").objectStore(l).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function Be(e,t){const n=await b();return new Promise((r,a)=>{const s=n.transaction(l,"readwrite"),i=s.objectStore(l),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},s.oncomplete=()=>r(),s.onerror=()=>a(s.error)})}async function Fe(e){const t=await b();return new Promise((n,r)=>{const a=t.transaction(l,"readwrite");a.objectStore(l).delete(e),a.oncomplete=()=>n(),a.onerror=()=>r(a.error)})}async function Ve(){const e=await b();function t(a){return new Promise((s,i)=>{const c=e.transaction(l,"readonly").objectStore(l).index("status"),u=[],f=c.openCursor(IDBKeyRange.only(a));f.onsuccess=p=>{const S=p.target.result;S?(u.push(S.value),S.continue()):s(u)},f.onerror=()=>i(f.error)})}const[n,r]=await Promise.all([t("pending"),t("failed")]);return[...n,...r]}async function Ge(){const e=await b();return new Promise((t,n)=>{const r=e.transaction(l,"readwrite");r.objectStore(l).clear(),r.oncomplete=()=>t(),r.onerror=()=>n(r.error)})}var ie={add:He,getAll:se,getPending:Ve,update:Be,remove:Fe,clear:Ge},oe=null;function Ye(e){oe=e}function B(){return oe}var Je="eidos-queue-sync",_;function ce(){return _!==void 0||(_=typeof BroadcastChannel>"u"?null:new BroadcastChannel(Je)),_}function g(e){ce()?.postMessage(e)}function Xe(){const e=ce();if(!e)return()=>{};const t=n=>{const r=o.getState(),a=n.data;switch(a.type){case"update":r.updateQueueItem(a.id,a.update);break;case"batchUpdate":r.batchUpdateQueueItems(a.updates);break;case"remove":r.removeQueueItem(a.id);break}};return e.addEventListener("message",t),()=>e.removeEventListener("message",t)}var x=new Map,ue=new Map,de=new Map,le=new Map,A=new Map;function d(){return B()??ie}function j(){return crypto.randomUUID()}function K(e,t,n){return e(...t,n)}function ze(e,t){const n=t.name||e.name||j(),r=t.namespace?`${t.namespace}::${n}`:n;if(x.has(r))throw new Error(`[eidos] duplicate action id "${r}" — an action with this id is already registered. Pass a unique config.name or config.namespace.`);x.set(r,e),le.set(r,t),t.onRollback&&ue.set(r,t.onRollback),t.conflict&&de.set(r,t.conflict);const a=async(...s)=>{const{isOnline:i}=o.getState(),c=j();let u;if(t.cancellable){const p=new AbortController;A.set(c,p),u=p.signal}const f={idempotencyKey:c,attempt:0,signal:u};t.onOptimistic?.(...s,f);try{if(t.reliability==="neverLose"){if(!i)return Y(r,r,s,t,c);try{return await K(e,s,f)}catch(p){if(pe(p))throw p;return Y(r,r,s,t,c)}}try{return await K(e,s,f)}catch(p){throw t.onRollback?.(...s,f),p}}finally{t.cancellable&&A.delete(c)}};return Object.defineProperty(a,"id",{value:r,writable:!1}),Object.defineProperty(a,"config",{value:t,writable:!1}),Object.defineProperty(a,"cancel",{value:fe,writable:!1}),a}async function fe(e){const t=A.get(e);if(t)return t.abort(),!0;const n=(await d().getAll()).find(r=>r.idempotencyKey===e&&r.status==="pending");return n?(o.getState().removeQueueItem(n.id),g({type:"remove",id:n.id}),await d().remove(n.id),!0):!1}async function Ze(e){const t=(await d().getAll()).find(r=>r.id===e);if(!t||t.status!=="failed")return!1;const n={status:"pending",error:void 0,nextRetryAt:void 0,retryCount:0};return o.getState().updateQueueItem(e,n),g({type:"update",id:e,update:n}),await d().update(e,n),!0}async function Y(e,t,n,r,a){const s=j(),i={schemaVersion:2,id:s,actionId:e,actionName:t,idempotencyKey:a,args:n,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending",priority:r.priority??"normal"};await d().add(i),o.getState().addQueueItem(i),o.getState().recordReliabilityEvent("queued");try{const c=H();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 pe(e){return e instanceof DOMException&&e.name==="AbortError"}function et(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 tt(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}function k(){return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0,cancelled:0}}var D=!1,nt="eidos-queue-replay";async function q(){const e=o.getState();if(!e.isOnline)return k();if(typeof navigator<"u"&&navigator.locks)return navigator.locks.request(nt,{ifAvailable:!0},async t=>t?J(e):k());if(D)return k();D=!0;try{return await J(e)}finally{D=!1}}async function rt(e,t){const n=Date.now();t.updateQueueItem(e.id,{status:"succeeded",completedAt:n}),t.recordReliabilityEvent("succeeded"),g({type:"update",id:e.id,update:{status:"succeeded",completedAt:n}}),await d().update(e.id,{status:"succeeded",completedAt:n}),setTimeout(()=>{t.removeQueueItem(e.id),g({type:"remove",id:e.id}),d().remove(e.id)},3e3)}async function at(e,t,n){const r=de.get(e.actionId);let a;if(r)switch(r.strategy){case"serverWins":a="skip";break;case"clientWins":a="retry";break;case"merge":case"custom":{const s={error:n,args:e.args,attempt:e.retryCount,idempotencyKey:e.idempotencyKey};a=r.resolve?.(s)??"retry";break}}if(a==="skip")return t.removeQueueItem(e.id),t.recordReliabilityEvent("conflicted"),g({type:"remove",id:e.id}),await d().remove(e.id),"conflicted";a&&typeof a=="object"&&(e.args=a.resolved,t.updateQueueItem(e.id,{args:a.resolved}),g({type:"update",id:e.id,update:{args:a.resolved}}),await d().update(e.id,{args:a.resolved}))}async function st(e,t,n){const r=e.retryCount+1;if(r>=e.maxRetries){const s={status:"failed",error:String(n),retryCount:r};t.updateQueueItem(e.id,s),t.recordReliabilityEvent("failed"),g({type:"update",id:e.id,update:s}),await d().update(e.id,s);const i={idempotencyKey:e.idempotencyKey,attempt:r};return ue.get(e.actionId)?.(...e.args,i),"failed"}const a={status:"pending",retryCount:r,nextRetryAt:Date.now()+tt(r)};return t.updateQueueItem(e.id,a),t.recordReliabilityEvent("retried"),g({type:"update",id:e.id,update:a}),await d().update(e.id,a),"retrying"}async function it(e,t){const n=x.get(e.actionId);if(!n)return"skipped";const r=le.get(e.actionId)?.cancellable;let a;if(r){const i=new AbortController;A.set(e.idempotencyKey,i),a=i.signal}const s={idempotencyKey:e.idempotencyKey,attempt:e.retryCount,signal:a};try{return await K(n,e.args,s),await rt(e,t),"succeeded"}catch(i){if(pe(i))return t.removeQueueItem(e.id),t.recordReliabilityEvent("cancelled"),g({type:"remove",id:e.id}),await d().remove(e.id),"cancelled";if(et(i)){const c=await at(e,t,i);if(c)return c}return st(e,t,i)}finally{r&&A.delete(e.idempotencyKey)}}async function ot(e,t,n){if(e.length===0)return;const r=e.filter(s=>x.has(s.actionId));if(n.skipped+=e.length-r.length,r.length>0){const s=r.map(i=>({id:i.id,update:{status:"replaying"}}));t.batchUpdateQueueItems(s),g({type:"batchUpdate",updates:s});for(const i of r)d().update(i.id,{status:"replaying"})}const a=await Promise.allSettled(r.map(s=>it(s,t)));for(const s of a){const i=s.status==="fulfilled"?s.value:"failed";i==="skipped"?n.skipped++:i==="conflicted"?n.conflicted++:i==="cancelled"?n.cancelled++:(n.attempted++,n[i]++)}}async function J(e){const t=await d().getPending(),n=Date.now(),r=t.filter(s=>s.retryCount<s.maxRetries&&(!s.nextRetryAt||s.nextRetryAt<=n)),a=k();for(const s of["high","normal","low"])await ot(r.filter(i=>(i.priority??"normal")===s),e,a);return a}async function ct(){await d().clear(),o.getState().hydrateQueue([])}function ye(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:a}=o.getState(),s=a&&!e;e=a,s&&setTimeout(q,600)}),n=o.getState(),r=n.queue.some(a=>a.status==="pending");return n.isOnline&&r&&setTimeout(q,1200),t}async function ut(e){if(e.schemaVersion===2&&e.idempotencyKey)return e;const t={...e,schemaVersion:2,idempotencyKey:e.idempotencyKey??crypto.randomUUID()};return await(B()??ie).update(t.id,{schemaVersion:t.schemaVersion,idempotencyKey:t.idempotencyKey}).catch(()=>{}),t}var W=!1,L=null,$=null,O=null;async function ge(e={}){if(typeof window>"u"||W)return;W=!0;const t=e.swPath??"/eidos-sw.js",n=e.autoReplay??!0,r=e.skipWaiting??!0;try{const a=await se();if(a.length>0){const s=await Promise.all(a.map(ut));o.getState().hydrateQueue(s)}}catch{}try{await Re(t,{skipWaiting:r,onUpdateAvailable:e.onUpdateAvailable})}catch{}if(Ie(()=>{o.getState().isOnline&&setTimeout(q,200)}),n&&(L=ye()),$=Xe(),e.onReliabilityReport){const a=e.reliabilityReportInterval??6e4,s=e.onReliabilityReport;O=setInterval(()=>{s(o.getState().reliability)},a)}}function dt(){L?.(),L=null,$?.(),$=null,O&&clearInterval(O),O=null,W=!1}var N="@eidos:queue",lt=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 n=await this.readAll(),r=n.findIndex(a=>a.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(N)}};function ft({children:e,swPath:t,autoReplay:n}){return(0,R.useEffect)(()=>{ge({swPath:t,autoReplay:n})},[]),(0,V.jsx)(V.Fragment,{children:e})}function pt(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 F(e,t){return pt(e,t)}function m(e,t=Object.is){return{subscribe(n){let r=e(o.getState());return n(r),o.subscribe(()=>{const a=e(o.getState());t(r,a)||(r=a,n(a))})},getState(){return e(o.getState())}}}var yt=m(e=>e),gt=m(e=>e.queue),ht=m(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),F),wt=m(e=>z(e.queue),F),vt=m(e=>e.reliability,F);function mt(e){return m(t=>t.resources[e])}function St(e){return m(t=>t.queue.find(n=>n.id===e))}function he(e){let t=o.getState().queue.length;return o.subscribe(()=>{const n=o.getState().queue.length;t>0&&n===0&&e(),t=n})}function y(e){const t=e??(n=>n);return(0,R.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function bt(){return y()}function Et(){return y(e=>e.resources)}function Rt(e){return y(t=>t.resources[e])}function At(){return y(e=>e.queue)}function It(e){return y(t=>t.queue.find(n=>n.id===e))}function _t(){return{isOnline:y(e=>e.isOnline),swStatus:y(e=>e.swStatus),swError:y(e=>e.swError)}}function kt(){const[e,t,n,r]=y(a=>{const{pending:s,failed:i,replaying:c,total:u}=z(a.queue);return`${s},${i},${c},${u}`}).split(",");return{pending:+e,failed:+t,replaying:+n,total:+r}}function Ot(){return y(e=>e.reliability)}function xt(e){const t=(0,R.useRef)(e);(0,R.useEffect)(()=>{t.current=e}),(0,R.useEffect)(()=>he(()=>t.current()),[])}var we="2.3.0";function qt(){const e=o.getState(),t=H();return{version:we,swStatus:e.swStatus,...e.swError!==void 0&&{swError:e.swError},isOnline:e.isOnline,resourceCount:Object.keys(e.resources).length,resources:Object.fromEntries(Object.entries(e.resources).map(([n,r])=>[n,{url:r.url,strategy:r.strategy.swStrategy,status:r.status,cacheHits:r.cacheHits,cacheMisses:r.cacheMisses,...r.cachedAt!==void 0&&{cachedAt:r.cachedAt}}])),queue:e.queue.map(n=>({id:n.id,actionId:n.actionId,actionName:n.actionName,status:n.status,retryCount:n.retryCount,maxRetries:n.maxRetries,idempotencyKey:n.idempotencyKey,schemaVersion:n.schemaVersion,queuedAt:n.queuedAt})),reliability:{...e.reliability},swRegistration:t?{scope:t.scope,scriptURL:(t.active??t.waiting??t.installing)?.scriptURL??"",state:t.installing?"installing":t.waiting?"waiting":t.active?"active":null}:null}}exports.AsyncStorageQueueStorage=lt;exports.EidosProvider=ft;exports.VERSION=we;exports._getQueueStorage=B;exports._resetEidos=dt;exports.action=ze;exports.cancelByIdempotencyKey=fe;exports.clearQueue=ct;exports.eidosAction=St;exports.eidosDebug=qt;exports.eidosQueue=gt;exports.eidosQueueStats=wt;exports.eidosReliabilityStats=vt;exports.eidosResource=mt;exports.eidosStatus=ht;exports.eidosStore=yt;exports.getSwRegistration=H;exports.initEidos=ge;exports.isBgSyncSupported=_e;exports.onQueueDrain=he;exports.registerPushCallbacks=ke;exports.replayQueue=q;exports.requeueItem=Ze;exports.resource=Ne;exports.resourcePattern=Me;exports.sendToWorker=I;exports.setOfflineSimulation=xe;exports.setQueryInvalidator=Pe;exports.setQueueStorage=Ye;exports.subscribeReplayOnReconnect=ye;exports.triggerSwUpdate=Qe;exports.useEidos=bt;exports.useEidosAction=It;exports.useEidosOnDrain=xt;exports.useEidosQueue=At;exports.useEidosQueueStats=kt;exports.useEidosReliabilityStats=Ot;exports.useEidosResource=Rt;exports.useEidosResources=Et;exports.useEidosStatus=_t;exports.useEidosStore=o;exports.warmCache=We;
|
|
14
14
|
|
|
15
15
|
//# sourceMappingURL=eidos.cjs.map
|