@sweidos/eidos 1.0.34 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +171 -89
  2. package/dist/action.js +197 -91
  3. package/dist/async-storage-adapter.js +15 -12
  4. package/dist/cli.js +102 -0
  5. package/dist/devtools.js +1009 -551
  6. package/dist/eidos-sw.js +280 -188
  7. package/dist/eidos.cjs +15 -0
  8. package/dist/idb.js +59 -56
  9. package/dist/index.d.ts +135 -18
  10. package/dist/index.js +46 -42
  11. package/dist/nextjs.js +1 -10
  12. package/dist/push.cjs +120 -0
  13. package/dist/push.d.ts +28 -0
  14. package/dist/push.js +113 -0
  15. package/dist/query.cjs +131 -0
  16. package/dist/query.js +121 -41
  17. package/dist/queue-storage.js +5 -4
  18. package/dist/react/Devtools.d.ts +1 -1
  19. package/dist/react/Provider.js +11 -7
  20. package/dist/react/hooks.js +48 -38
  21. package/dist/react-native.js +47 -53
  22. package/dist/replay.js +15 -0
  23. package/dist/resource.js +77 -79
  24. package/dist/runtime.js +39 -28
  25. package/dist/store-slices.js +43 -0
  26. package/dist/store.js +32 -49
  27. package/dist/stores.js +25 -22
  28. package/dist/sveltekit.js +22 -6
  29. package/dist/sw-bridge.js +64 -49
  30. package/dist/testing.cjs +165 -0
  31. package/dist/testing.js +140 -70
  32. package/dist/version.js +4 -3
  33. package/dist/vite.cjs +48 -0
  34. package/dist/vite.js +45 -29
  35. package/package.json +57 -28
  36. package/dist/action.js.map +0 -1
  37. package/dist/async-storage-adapter.js.map +0 -1
  38. package/dist/eidos.cjs.js +0 -14
  39. package/dist/eidos.cjs.js.map +0 -1
  40. package/dist/idb.js.map +0 -1
  41. package/dist/index.js.map +0 -1
  42. package/dist/query.cjs.js +0 -48
  43. package/dist/queue-storage.js.map +0 -1
  44. package/dist/react/Provider.js.map +0 -1
  45. package/dist/react/hooks.js.map +0 -1
  46. package/dist/resource.js.map +0 -1
  47. package/dist/runtime.js.map +0 -1
  48. package/dist/store.js.map +0 -1
  49. package/dist/stores.js.map +0 -1
  50. package/dist/sw-bridge.js.map +0 -1
  51. package/dist/testing.cjs.js +0 -86
  52. package/dist/version.js.map +0 -1
  53. package/dist/vite.cjs.js +0 -31
package/dist/eidos-sw.js CHANGED
@@ -1,213 +1,305 @@
1
- const CACHE_VERSION = "v1";
2
- const CACHE_PREFIX = "eidos";
3
- const runtimeConfig = {
4
- resources: /* @__PURE__ */ new Map(),
5
- simulateOffline: false
1
+ //#region src/sw.ts
2
+ var CACHE_VERSION = "v1";
3
+ var CACHE_PREFIX = "eidos";
4
+ var runtimeConfig = {
5
+ resources: /* @__PURE__ */ new Map(),
6
+ simulateOffline: false
6
7
  };
7
8
  self.addEventListener("install", (event) => {
8
- event.waitUntil(self.skipWaiting());
9
+ event.waitUntil(self.skipWaiting());
9
10
  });
10
11
  self.addEventListener("activate", (event) => {
11
- event.waitUntil(
12
- Promise.all([
13
- self.clients.claim(),
14
- // Purge stale caches from previous versions
15
- caches.keys().then(
16
- (keys) => Promise.all(
17
- keys.filter((k) => k.startsWith(CACHE_PREFIX) && !k.endsWith(CACHE_VERSION)).map((k) => caches.delete(k))
18
- )
19
- )
20
- ])
21
- );
12
+ 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))))]));
22
13
  });
23
14
  self.addEventListener("message", (event) => {
24
- const data = event.data;
25
- if (!data?.type) return;
26
- switch (data.type) {
27
- case "EIDOS_REGISTER_RESOURCE": {
28
- const url = data.url;
29
- const patternSrc = data.pattern;
30
- runtimeConfig.resources.set(url, {
31
- strategy: data.strategy,
32
- cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
33
- ...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
34
- });
35
- event.source?.postMessage({ type: "EIDOS_RESOURCE_REGISTERED", url });
36
- break;
37
- }
38
- case "EIDOS_UNREGISTER_RESOURCE": {
39
- runtimeConfig.resources.delete(data.url);
40
- break;
41
- }
42
- case "EIDOS_SIMULATE_OFFLINE": {
43
- runtimeConfig.simulateOffline = data.enabled;
44
- break;
45
- }
46
- case "EIDOS_CLEAR_CACHE": {
47
- const targetUrl = data.url;
48
- const reg = targetUrl ? runtimeConfig.resources.get(targetUrl) : void 0;
49
- const cacheName = reg?.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`;
50
- caches.open(cacheName).then(async (cache) => {
51
- if (targetUrl) {
52
- const keys = await cache.keys();
53
- const isCrossOrigin = targetUrl.startsWith("http");
54
- await Promise.all(
55
- keys.filter((req) => {
56
- const reqUrl = req.url;
57
- const p = new URL(reqUrl).pathname;
58
- if (reg?.pattern) {
59
- return reg.pattern.test(isCrossOrigin ? reqUrl : p);
60
- }
61
- return isCrossOrigin ? reqUrl === targetUrl : p === targetUrl;
62
- }).map((req) => cache.delete(req))
63
- );
64
- } else {
65
- await cache.keys().then((keys) => Promise.all(keys.map((k) => cache.delete(k))));
66
- }
67
- notifyClients({ type: "EIDOS_CACHE_CLEARED", url: targetUrl });
68
- });
69
- break;
70
- }
71
- case "EIDOS_PING":
72
- event.source?.postMessage({ type: "EIDOS_PONG" });
73
- break;
74
- }
15
+ const data = event.data;
16
+ if (!data?.type) return;
17
+ switch (data.type) {
18
+ case "EIDOS_REGISTER_RESOURCE": {
19
+ const url = data.url;
20
+ const patternSrc = data.pattern;
21
+ runtimeConfig.resources.set(url, {
22
+ strategy: data.strategy,
23
+ cacheName: data.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`,
24
+ ...patternSrc !== void 0 && { pattern: new RegExp(patternSrc) }
25
+ });
26
+ event.source?.postMessage({
27
+ type: "EIDOS_RESOURCE_REGISTERED",
28
+ url
29
+ });
30
+ break;
31
+ }
32
+ case "EIDOS_UNREGISTER_RESOURCE":
33
+ runtimeConfig.resources.delete(data.url);
34
+ break;
35
+ case "EIDOS_SIMULATE_OFFLINE":
36
+ runtimeConfig.simulateOffline = data.enabled;
37
+ break;
38
+ case "EIDOS_CLEAR_CACHE": {
39
+ const targetUrl = data.url;
40
+ const reg = targetUrl ? runtimeConfig.resources.get(targetUrl) : void 0;
41
+ const cacheName = reg?.cacheName ?? `${CACHE_PREFIX}-resources-${CACHE_VERSION}`;
42
+ caches.open(cacheName).then(async (cache) => {
43
+ if (targetUrl) {
44
+ const keys = await cache.keys();
45
+ const isCrossOrigin = targetUrl.startsWith("http");
46
+ await Promise.all(keys.filter((req) => {
47
+ const reqUrl = req.url;
48
+ const p = new URL(reqUrl).pathname;
49
+ if (reg?.pattern) return reg.pattern.test(isCrossOrigin ? reqUrl : p);
50
+ return isCrossOrigin ? reqUrl === targetUrl : p === targetUrl;
51
+ }).map((req) => cache.delete(req)));
52
+ } else await cache.keys().then((keys) => Promise.all(keys.map((k) => cache.delete(k))));
53
+ notifyClients({
54
+ type: "EIDOS_CACHE_CLEARED",
55
+ url: targetUrl
56
+ });
57
+ });
58
+ break;
59
+ }
60
+ case "EIDOS_PING":
61
+ event.source?.postMessage({ type: "EIDOS_PONG" });
62
+ break;
63
+ case "EIDOS_CACHE_VAPID_KEY":
64
+ idbSet("vapidPublicKey", data.key);
65
+ break;
66
+ }
75
67
  });
76
68
  self.addEventListener("fetch", (event) => {
77
- if (event.request.method !== "GET") return;
78
- const requestUrl = event.request.url;
79
- const pathname = new URL(requestUrl).pathname;
80
- let reg = runtimeConfig.resources.get(requestUrl) ?? runtimeConfig.resources.get(pathname);
81
- if (!reg) {
82
- for (const [key, registration] of runtimeConfig.resources) {
83
- if (!registration.pattern) continue;
84
- const target = key.startsWith("http") ? requestUrl : pathname;
85
- if (registration.pattern.test(target)) {
86
- reg = registration;
87
- break;
88
- }
89
- }
90
- }
91
- if (!reg) return;
92
- if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
93
- event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg.cacheName));
94
- return;
95
- }
96
- event.respondWith(handleFetch(event.request, pathname, reg));
69
+ if (event.request.method !== "GET") return;
70
+ const requestUrl = event.request.url;
71
+ const pathname = new URL(requestUrl).pathname;
72
+ let reg = runtimeConfig.resources.get(requestUrl) ?? runtimeConfig.resources.get(pathname);
73
+ if (!reg) for (const [key, registration] of runtimeConfig.resources) {
74
+ if (!registration.pattern) continue;
75
+ const target = key.startsWith("http") ? requestUrl : pathname;
76
+ if (registration.pattern.test(target)) {
77
+ reg = registration;
78
+ break;
79
+ }
80
+ }
81
+ if (!reg) return;
82
+ if (reg.strategy === "stale-while-revalidate" && !runtimeConfig.simulateOffline) {
83
+ event.respondWith(staleWhileRevalidate(event, event.request, pathname, reg.cacheName));
84
+ return;
85
+ }
86
+ event.respondWith(handleFetch(event.request, pathname, reg));
97
87
  });
98
88
  async function handleFetch(request, pathname, reg) {
99
- if (runtimeConfig.simulateOffline) {
100
- return serveOffline(request, pathname, reg.cacheName);
101
- }
102
- switch (reg.strategy) {
103
- case "cache-first":
104
- return cacheFirst(request, pathname, reg.cacheName);
105
- case "stale-while-revalidate":
106
- return staleWhileRevalidate(null, request, pathname, reg.cacheName);
107
- case "network-first":
108
- return networkFirst(request, pathname, reg.cacheName);
109
- default:
110
- return fetch(request);
111
- }
89
+ if (runtimeConfig.simulateOffline) return serveOffline(request, pathname, reg.cacheName);
90
+ switch (reg.strategy) {
91
+ case "cache-first": return cacheFirst(request, pathname, reg.cacheName);
92
+ case "stale-while-revalidate": return staleWhileRevalidate(null, request, pathname, reg.cacheName);
93
+ case "network-first": return networkFirst(request, pathname, reg.cacheName);
94
+ default: return fetch(request);
95
+ }
112
96
  }
113
97
  async function cacheFirst(request, pathname, cacheName) {
114
- const cache = await caches.open(cacheName);
115
- const cached = await cache.match(request);
116
- if (cached) {
117
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "cache-first" });
118
- return cached;
119
- }
120
- try {
121
- const response = await fetch(request);
122
- if (response.ok) {
123
- await cache.put(request, response.clone());
124
- notifyClients({ type: "EIDOS_CACHE_UPDATED", url: pathname, strategy: "cache-first" });
125
- }
126
- return response;
127
- } catch {
128
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname });
129
- return offlineErrorResponse(pathname);
130
- }
98
+ const cache = await caches.open(cacheName);
99
+ const cached = await cache.match(request);
100
+ if (cached) {
101
+ notifyClients({
102
+ type: "EIDOS_CACHE_HIT",
103
+ url: pathname,
104
+ strategy: "cache-first"
105
+ });
106
+ return cached;
107
+ }
108
+ try {
109
+ const response = await fetch(request);
110
+ if (response.ok) {
111
+ await cache.put(request, response.clone());
112
+ notifyClients({
113
+ type: "EIDOS_CACHE_UPDATED",
114
+ url: pathname,
115
+ strategy: "cache-first"
116
+ });
117
+ }
118
+ return response;
119
+ } catch {
120
+ notifyClients({
121
+ type: "EIDOS_NETWORK_ERROR",
122
+ url: pathname
123
+ });
124
+ return offlineErrorResponse(pathname);
125
+ }
131
126
  }
132
127
  async function staleWhileRevalidate(event, request, pathname, cacheName) {
133
- const cache = await caches.open(cacheName);
134
- const cached = await cache.match(request);
135
- const revalidatePromise = fetch(request).then(async (response) => {
136
- if (response.ok) {
137
- await cache.put(request, response.clone());
138
- notifyClients({
139
- type: "EIDOS_CACHE_UPDATED",
140
- url: pathname,
141
- strategy: "stale-while-revalidate"
142
- });
143
- }
144
- return response;
145
- }).catch(() => {
146
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname, strategy: "stale-while-revalidate" });
147
- });
148
- if (cached) {
149
- event?.waitUntil(revalidatePromise);
150
- notifyClients({
151
- type: "EIDOS_CACHE_HIT",
152
- url: pathname,
153
- strategy: "stale-while-revalidate"
154
- });
155
- return cached;
156
- }
157
- const fresh = await revalidatePromise;
158
- return fresh ?? offlineErrorResponse(pathname);
128
+ const cache = await caches.open(cacheName);
129
+ const cached = await cache.match(request);
130
+ const revalidatePromise = fetch(request).then(async (response) => {
131
+ if (response.ok) {
132
+ await cache.put(request, response.clone());
133
+ notifyClients({
134
+ type: "EIDOS_CACHE_UPDATED",
135
+ url: pathname,
136
+ strategy: "stale-while-revalidate"
137
+ });
138
+ }
139
+ return response;
140
+ }).catch(() => {
141
+ notifyClients({
142
+ type: "EIDOS_NETWORK_ERROR",
143
+ url: pathname,
144
+ strategy: "stale-while-revalidate"
145
+ });
146
+ });
147
+ if (cached) {
148
+ event?.waitUntil(revalidatePromise);
149
+ notifyClients({
150
+ type: "EIDOS_CACHE_HIT",
151
+ url: pathname,
152
+ strategy: "stale-while-revalidate"
153
+ });
154
+ return cached;
155
+ }
156
+ return await revalidatePromise ?? offlineErrorResponse(pathname);
159
157
  }
160
158
  async function networkFirst(request, pathname, cacheName) {
161
- const cache = await caches.open(cacheName);
162
- try {
163
- const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
164
- if (response.ok) {
165
- await cache.put(request, response.clone());
166
- notifyClients({ type: "EIDOS_CACHE_UPDATED", url: pathname, strategy: "network-first" });
167
- }
168
- return response;
169
- } catch {
170
- const cached = await cache.match(request);
171
- if (cached) {
172
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "network-first" });
173
- return cached;
174
- }
175
- notifyClients({ type: "EIDOS_NETWORK_ERROR", url: pathname });
176
- return offlineErrorResponse(pathname);
177
- }
159
+ const cache = await caches.open(cacheName);
160
+ try {
161
+ const response = await fetch(request, { signal: AbortSignal.timeout(3e3) });
162
+ if (response.ok) {
163
+ await cache.put(request, response.clone());
164
+ notifyClients({
165
+ type: "EIDOS_CACHE_UPDATED",
166
+ url: pathname,
167
+ strategy: "network-first"
168
+ });
169
+ }
170
+ return response;
171
+ } catch {
172
+ const cached = await cache.match(request);
173
+ if (cached) {
174
+ notifyClients({
175
+ type: "EIDOS_CACHE_HIT",
176
+ url: pathname,
177
+ strategy: "network-first"
178
+ });
179
+ return cached;
180
+ }
181
+ notifyClients({
182
+ type: "EIDOS_NETWORK_ERROR",
183
+ url: pathname
184
+ });
185
+ return offlineErrorResponse(pathname);
186
+ }
178
187
  }
179
188
  async function serveOffline(request, pathname, cacheName) {
180
- const cache = await caches.open(cacheName);
181
- const cached = await cache.match(request);
182
- if (cached) {
183
- notifyClients({ type: "EIDOS_CACHE_HIT", url: pathname, strategy: "offline-simulation", simulated: true });
184
- return cached;
185
- }
186
- return offlineErrorResponse(pathname);
189
+ const cached = await (await caches.open(cacheName)).match(request);
190
+ if (cached) {
191
+ notifyClients({
192
+ type: "EIDOS_CACHE_HIT",
193
+ url: pathname,
194
+ strategy: "offline-simulation",
195
+ simulated: true
196
+ });
197
+ return cached;
198
+ }
199
+ return offlineErrorResponse(pathname);
187
200
  }
188
201
  function offlineErrorResponse(pathname) {
189
- return new Response(
190
- JSON.stringify({
191
- error: "offline",
192
- message: `No cached response available for ${pathname}`,
193
- eidos: true
194
- }),
195
- {
196
- status: 503,
197
- headers: {
198
- "Content-Type": "application/json",
199
- "X-Eidos-Offline": "true"
200
- }
201
- }
202
- );
202
+ return new Response(JSON.stringify({
203
+ error: "offline",
204
+ message: `No cached response available for ${pathname}`,
205
+ eidos: true
206
+ }), {
207
+ status: 503,
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ "X-Eidos-Offline": "true"
211
+ }
212
+ });
203
213
  }
204
214
  async function notifyClients(message) {
205
- const clients = await self.clients.matchAll({ includeUncontrolled: true });
206
- clients.forEach((client) => client.postMessage(message));
215
+ (await self.clients.matchAll({ includeUncontrolled: true })).forEach((client) => client.postMessage(message));
207
216
  }
208
217
  self.addEventListener("sync", (event) => {
209
- const syncEvent = event;
210
- if (syncEvent.tag === "eidos-queue-replay") {
211
- syncEvent.waitUntil(notifyClients({ type: "EIDOS_BACKGROUND_SYNC" }));
212
- }
218
+ const syncEvent = event;
219
+ if (syncEvent.tag === "eidos-queue-replay") syncEvent.waitUntil(notifyClients({ type: "EIDOS_BACKGROUND_SYNC" }));
213
220
  });
221
+ self.addEventListener("push", (event) => {
222
+ let payload = null;
223
+ try {
224
+ payload = event.data?.json();
225
+ } catch {
226
+ return;
227
+ }
228
+ if (!payload?.title) return;
229
+ event.waitUntil(self.registration.showNotification(payload.title, {
230
+ body: payload.body,
231
+ icon: payload.icon,
232
+ badge: payload.badge,
233
+ tag: payload.tag,
234
+ data: payload.data
235
+ }));
236
+ });
237
+ self.addEventListener("notificationclick", (event) => {
238
+ event.notification.close();
239
+ const data = event.notification.data ?? {};
240
+ event.waitUntil(self.clients.matchAll({
241
+ type: "window",
242
+ includeUncontrolled: true
243
+ }).then((clients) => {
244
+ const client = clients[0];
245
+ if (client) {
246
+ client.focus();
247
+ client.postMessage({
248
+ type: "EIDOS_NOTIFICATION_CLICK",
249
+ data
250
+ });
251
+ return;
252
+ }
253
+ if (data.url) return self.clients.openWindow(data.url);
254
+ }));
255
+ });
256
+ self.addEventListener("pushsubscriptionchange", (event) => {
257
+ const psEvent = event;
258
+ psEvent.waitUntil((async () => {
259
+ const vapidPublicKey = await idbGet("vapidPublicKey");
260
+ if (!vapidPublicKey) return;
261
+ await notifyClients({
262
+ type: "EIDOS_SUBSCRIPTION_EXPIRED",
263
+ subscription: (psEvent.newSubscription ?? await self.registration.pushManager.subscribe({
264
+ userVisibleOnly: true,
265
+ applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
266
+ })).toJSON()
267
+ });
268
+ })());
269
+ });
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
+ var META_DB = "eidos-sw-meta";
276
+ var META_STORE = "kv";
277
+ function openMetaDb() {
278
+ return new Promise((resolve, reject) => {
279
+ const req = indexedDB.open(META_DB, 1);
280
+ req.onupgradeneeded = () => req.result.createObjectStore(META_STORE);
281
+ req.onsuccess = () => resolve(req.result);
282
+ req.onerror = () => reject(req.error);
283
+ });
284
+ }
285
+ async function idbSet(key, value) {
286
+ const db = await openMetaDb();
287
+ await new Promise((resolve, reject) => {
288
+ const tx = db.transaction(META_STORE, "readwrite");
289
+ tx.objectStore(META_STORE).put(value, key);
290
+ tx.oncomplete = () => resolve();
291
+ tx.onerror = () => reject(tx.error);
292
+ });
293
+ db.close();
294
+ }
295
+ async function idbGet(key) {
296
+ const db = await openMetaDb();
297
+ const value = await new Promise((resolve, reject) => {
298
+ const req = db.transaction(META_STORE, "readonly").objectStore(META_STORE).get(key);
299
+ req.onsuccess = () => resolve(req.result);
300
+ req.onerror = () => reject(req.error);
301
+ });
302
+ db.close();
303
+ return value;
304
+ }
305
+ //#endregion
package/dist/eidos.cjs ADDED
@@ -0,0 +1,15 @@
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
2
+ new StaleWhileRevalidate({
3
+ cacheName: 'eidos-resources-v1',
4
+ plugins: [new ExpirationPlugin({ maxEntries: 60 })],
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
6
+ new CacheFirst({
7
+ cacheName: 'eidos-resources-v1',
8
+ plugins: [new ExpirationPlugin({ maxEntries: 60 })],
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
+ new NetworkFirst({
11
+ cacheName: 'eidos-resources-v1',
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;
14
+
15
+ //# sourceMappingURL=eidos.cjs.map