@sweidos/eidos 1.0.19 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/eidos.cjs.js CHANGED
@@ -1,762 +1,14 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const react = require("react");
5
- let _state;
6
- const _listeners = /* @__PURE__ */ new Set();
7
- function _notify() {
8
- _listeners.forEach((fn) => fn());
9
- }
10
- function _set(updater) {
11
- _state = { ..._state, ...updater(_state) };
12
- _notify();
13
- }
14
- _state = {
15
- isOnline: typeof navigator !== "undefined" ? navigator.onLine : true,
16
- swStatus: "idle",
17
- swError: void 0,
18
- resources: {},
19
- queue: [],
20
- setOnline: (isOnline) => _set(() => ({ isOnline })),
21
- setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),
22
- registerResource: (url, entry) => _set((s) => ({ resources: { ...s.resources, [url]: entry } })),
23
- updateResource: (url, update) => _set((s) => ({
24
- resources: {
25
- ...s.resources,
26
- [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url]
27
- }
28
- })),
29
- unregisterResource: (url) => _set((s) => {
30
- const { [url]: _removed, ...rest } = s.resources;
31
- return { resources: rest };
32
- }),
33
- addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),
34
- updateQueueItem: (id, update) => _set((s) => ({
35
- queue: s.queue.map((item) => item.id === id ? { ...item, ...update } : item)
36
- })),
37
- removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),
38
- hydrateQueue: (items) => _set(() => ({ queue: items }))
39
- };
40
- function _getState() {
41
- return _state;
42
- }
43
- function _subscribe(listener) {
44
- _listeners.add(listener);
45
- return () => {
46
- _listeners.delete(listener);
47
- };
48
- }
49
- const useEidosStore = {
50
- getState: _getState,
51
- subscribe: _subscribe,
52
- // Test/devtools helper — merges partial state, preserves action methods.
53
- setState: (partial) => {
54
- const update = typeof partial === "function" ? partial(_state) : partial;
55
- _state = { ..._state, ...update };
56
- _notify();
57
- }
58
- };
59
- let _registration = null;
60
- let _pendingMessages = [];
61
- function getSwRegistration() {
62
- return _registration;
63
- }
64
- async function registerServiceWorker(swPath) {
65
- if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
66
- useEidosStore.getState().setSwStatus("unsupported");
67
- return;
68
- }
69
- const store = useEidosStore.getState();
70
- store.setSwStatus("registering");
71
- try {
72
- _registration = await navigator.serviceWorker.register(swPath, { scope: "/" });
73
- await waitForActivation(_registration);
74
- store.setSwStatus("active");
75
- navigator.serviceWorker.addEventListener("message", onSwMessage);
76
- window.addEventListener("online", () => store.setOnline(true));
77
- window.addEventListener("offline", () => store.setOnline(false));
78
- flushPendingMessages();
79
- } catch (err) {
80
- store.setSwStatus("error", String(err));
81
- }
82
- }
83
- function waitForActivation(reg) {
84
- return new Promise((resolve) => {
85
- if (reg.active) {
86
- resolve();
87
- return;
88
- }
89
- const sw = reg.installing ?? reg.waiting;
90
- if (!sw) {
91
- resolve();
92
- return;
93
- }
94
- const timer = setTimeout(resolve, 1e4);
95
- sw.addEventListener("statechange", function handler() {
96
- if (sw.state === "activated") {
97
- clearTimeout(timer);
98
- sw.removeEventListener("statechange", handler);
99
- resolve();
100
- }
101
- });
102
- });
103
- }
104
- function sendToWorker(message) {
105
- const sw = _registration == null ? void 0 : _registration.active;
106
- if (sw) {
107
- sw.postMessage(message);
108
- } else {
109
- _pendingMessages.push(message);
110
- }
111
- }
112
- let _bgSyncHandler = null;
113
- function registerBgSyncHandler(fn) {
114
- _bgSyncHandler = fn;
115
- }
116
- function isBgSyncSupported() {
117
- try {
118
- return typeof navigator !== "undefined" && "serviceWorker" in navigator && _registration !== null && "sync" in _registration;
119
- } catch {
120
- return false;
121
- }
122
- }
123
- function onSwMessage(event) {
124
- const data = event.data;
125
- if (!(data == null ? void 0 : data.type)) return;
126
- const store = useEidosStore.getState();
127
- const { type, url } = data;
128
- if (type === "EIDOS_BACKGROUND_SYNC") {
129
- _bgSyncHandler == null ? void 0 : _bgSyncHandler();
130
- return;
131
- }
132
- if (!url) return;
133
- switch (type) {
134
- case "EIDOS_CACHE_HIT": {
135
- const current = store.resources[url];
136
- store.updateResource(url, {
137
- status: "fresh",
138
- lastEvent: "cache-hit",
139
- cacheHits: ((current == null ? void 0 : current.cacheHits) ?? 0) + 1
140
- });
141
- break;
142
- }
143
- case "EIDOS_CACHE_UPDATED": {
144
- store.updateResource(url, {
145
- status: "fresh",
146
- lastEvent: "cache-updated",
147
- cachedAt: Date.now()
148
- });
149
- break;
150
- }
151
- case "EIDOS_NETWORK_ERROR": {
152
- store.updateResource(url, {
153
- status: "error",
154
- lastEvent: "network-error"
155
- });
156
- break;
157
- }
158
- }
159
- }
160
- function setOfflineSimulation(enabled) {
161
- sendToWorker({ type: "EIDOS_SIMULATE_OFFLINE", enabled });
162
- useEidosStore.getState().setOnline(!enabled);
163
- }
164
- function flushPendingMessages() {
165
- const sw = _registration == null ? void 0 : _registration.active;
166
- if (!sw) return;
167
- for (const msg of _pendingMessages) sw.postMessage(msg);
168
- _pendingMessages = [];
169
- }
170
- const _registry = /* @__PURE__ */ new Map();
171
- let _queryInvalidator = null;
172
- function setQueryInvalidator(fn) {
173
- _queryInvalidator = fn;
174
- }
175
- function isPattern(url) {
176
- return url.includes("*") || /:[^/]+/.test(url);
177
- }
178
- function patternToRegexStr(pattern) {
179
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
180
- return "^" + escaped.replace(/\*\*/g, ".+").replace(/\*/g, "[^/]+").replace(/:[^/]+/g, "[^/]+") + "$";
181
- }
182
- function _patternError(url, method) {
183
- return new Error(
184
- `[eidos] resource('${url}') is a URL pattern — ${method}() is not supported on pattern handles. The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`
185
- );
186
- }
187
- function resource(url, config) {
188
- if (_registry.has(url)) {
189
- return _registry.get(url);
190
- }
191
- const strategy = deriveStrategy(url, config);
192
- const regexStr = isPattern(url) ? patternToRegexStr(url) : void 0;
193
- const entry = {
194
- url,
195
- config,
196
- strategy,
197
- status: "idle",
198
- cacheHits: 0,
199
- cacheMisses: 0
200
- };
201
- useEidosStore.getState().registerResource(url, entry);
202
- sendToWorker({
203
- type: "EIDOS_REGISTER_RESOURCE",
204
- url,
205
- strategy: strategy.swStrategy,
206
- cacheName: strategy.cacheName,
207
- ...regexStr !== void 0 && { pattern: regexStr }
208
- });
209
- const handle = {
210
- url,
211
- config,
212
- strategy,
213
- fetch: async () => {
214
- if (isPattern(url)) throw _patternError(url, "fetch");
215
- const store = useEidosStore.getState();
216
- store.updateResource(url, { status: "fetching", fetchedAt: Date.now() });
217
- const cache = await caches.open(strategy.cacheName).catch(() => null);
218
- try {
219
- if (strategy.swStrategy !== "network-first") {
220
- const cached = cache ? await cache.match(url).catch(() => null) : null;
221
- const current = useEidosStore.getState().resources[url];
222
- const expired = config.maxAge !== void 0 && (current == null ? void 0 : current.cachedAt) !== void 0 && Date.now() - current.cachedAt > config.maxAge;
223
- if (cached && !expired) {
224
- store.updateResource(url, {
225
- status: "fresh",
226
- lastEvent: "cache-hit",
227
- cacheHits: ((current == null ? void 0 : current.cacheHits) ?? 0) + 1
228
- });
229
- if (strategy.swStrategy === "stale-while-revalidate") {
230
- fetch(url).then(async (resp) => {
231
- if (resp.ok && cache) {
232
- await cache.put(url, resp.clone());
233
- useEidosStore.getState().updateResource(url, {
234
- cachedAt: Date.now(),
235
- lastEvent: "cache-updated"
236
- });
237
- }
238
- }).catch(() => {
239
- });
240
- }
241
- return cached;
242
- }
243
- const storeEntry = useEidosStore.getState().resources[url];
244
- store.updateResource(url, {
245
- cacheMisses: ((storeEntry == null ? void 0 : storeEntry.cacheMisses) ?? 0) + 1
246
- });
247
- }
248
- const response = await fetch(url);
249
- if (response.ok) {
250
- if (cache) await cache.put(url, response.clone());
251
- store.updateResource(url, {
252
- status: "fresh",
253
- cachedAt: Date.now(),
254
- lastEvent: "cache-updated"
255
- });
256
- return response;
257
- }
258
- store.updateResource(url, { status: response.status === 503 ? "offline" : "error" });
259
- const isOffline = response.headers.get("X-Eidos-Offline") === "true";
260
- throw new Error(
261
- isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`
262
- );
263
- } catch (err) {
264
- const fallback = cache ? await cache.match(url).catch(() => null) : null;
265
- if (fallback) {
266
- const current = useEidosStore.getState().resources[url];
267
- store.updateResource(url, {
268
- status: "fresh",
269
- lastEvent: "cache-hit",
270
- cacheHits: ((current == null ? void 0 : current.cacheHits) ?? 0) + 1
271
- });
272
- return fallback;
273
- }
274
- store.updateResource(url, { status: "error" });
275
- throw err;
276
- }
277
- },
278
- json: async () => {
279
- if (isPattern(url)) throw _patternError(url, "json");
280
- const res = await handle.fetch();
281
- return res.json();
282
- },
283
- query: () => {
284
- if (isPattern(url)) throw _patternError(url, "query");
285
- return {
286
- queryKey: ["eidos", url],
287
- queryFn: () => handle.json()
288
- };
289
- },
290
- prefetch: async () => {
291
- if (isPattern(url)) throw _patternError(url, "prefetch");
292
- await handle.fetch();
293
- },
294
- invalidate: async () => {
295
- sendToWorker({ type: "EIDOS_CLEAR_CACHE", url });
296
- const cache = await caches.open(strategy.cacheName).catch(() => null);
297
- if (cache) {
298
- const keys = await cache.keys();
299
- const patternRe = regexStr ? new RegExp(regexStr) : null;
300
- const isCrossOrigin = url.startsWith("http");
301
- await Promise.all(
302
- keys.filter((r) => {
303
- const rUrl = r.url;
304
- const p = new URL(rUrl).pathname;
305
- if (patternRe) {
306
- return patternRe.test(isCrossOrigin ? rUrl : p);
307
- }
308
- return isCrossOrigin ? rUrl === url : rUrl === url || p === url;
309
- }).map((r) => cache.delete(r))
310
- );
311
- }
312
- if (!isPattern(url)) {
313
- useEidosStore.getState().updateResource(url, {
314
- status: "stale",
315
- cachedAt: void 0,
316
- lastEvent: "cache-cleared",
317
- cacheHits: 0,
318
- cacheMisses: 0
319
- });
320
- }
321
- _queryInvalidator == null ? void 0 : _queryInvalidator(["eidos", url]);
322
- },
323
- unregister: () => {
324
- _registry.delete(url);
325
- sendToWorker({ type: "EIDOS_UNREGISTER_RESOURCE", url });
326
- useEidosStore.getState().unregisterResource(url);
327
- }
328
- };
329
- _registry.set(url, handle);
330
- return handle;
331
- }
332
- function deriveStrategy(url, config) {
333
- const explicit = config.strategy;
334
- if (config.offline) return buildStrategy(explicit ?? "stale-while-revalidate", url, config.cacheName);
335
- return buildStrategy(explicit ?? "network-first", url, config.cacheName);
336
- }
337
- const STRATEGY_META = {
338
- "stale-while-revalidate": {
339
- name: "StaleWhileRevalidate",
340
- 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.",
341
- behavior: [
342
- "Cache hit → return immediately, kick off background revalidation",
343
- "Cache miss → fetch from network, cache the response, return it",
344
- "Offline → return cached version if available, 503 if not",
345
- "Reconnect → next request triggers a background refresh"
346
- ],
347
- equivalentCode: `// Workbox equivalent
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=require("react/jsx-runtime"),v=require("react");let y;const C=new Set;function U(){C.forEach(e=>e())}function g(e){y={...y,...e(y)},U()}y={isOnline:typeof navigator<"u"?navigator.onLine:!0,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>g(()=>({isOnline:e})),setSwStatus:(e,t)=>g(()=>({swStatus:e,swError:t})),registerResource:(e,t)=>g(s=>({resources:{...s.resources,[e]:t}})),updateResource:(e,t)=>g(s=>({resources:{...s.resources,[e]:s.resources[e]?{...s.resources[e],...t}:s.resources[e]}})),unregisterResource:e=>g(t=>{const{[e]:s,...r}=t.resources;return{resources:r}}),addQueueItem:e=>g(t=>({queue:[...t.queue,e]})),updateQueueItem:(e,t)=>g(s=>({queue:s.queue.map(r=>r.id===e?{...r,...t}:r)})),removeQueueItem:e=>g(t=>({queue:t.queue.filter(s=>s.id!==e)})),hydrateQueue:e=>g(()=>({queue:e}))};function L(){return y}function F(e){return C.add(e),()=>{C.delete(e)}}const c={getState:L,subscribe:F,setState:e=>{const t=typeof e=="function"?e(y):e;y={...y,...t},U()}};let w=null,D=[];function $(){return w}async function G(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){c.getState().setSwStatus("unsupported");return}const t=c.getState();t.setSwStatus("registering");try{w=await navigator.serviceWorker.register(e,{scope:"/"}),await K(w),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",z),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),J()}catch(s){t.setSwStatus("error",String(s))}}function K(e){return new Promise(t=>{if(e.active){t();return}const s=e.installing??e.waiting;if(!s){t();return}const r=setTimeout(t,1e4);s.addEventListener("statechange",function n(){s.state==="activated"&&(clearTimeout(r),s.removeEventListener("statechange",n),t())})})}function q(e){const t=w==null?void 0:w.active;t?t.postMessage(e):D.push(e)}let k=null;function V(e){k=e}function Y(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&w!==null&&"sync"in w}catch{return!1}}function z(e){const t=e.data;if(!(t!=null&&t.type))return;const s=c.getState(),{type:r,url:n}=t;if(r==="EIDOS_BACKGROUND_SYNC"){k==null||k();return}if(n)switch(r){case"EIDOS_CACHE_HIT":{const i=s.resources[n];s.updateResource(n,{status:"fresh",lastEvent:"cache-hit",cacheHits:((i==null?void 0:i.cacheHits)??0)+1});break}case"EIDOS_CACHE_UPDATED":{s.updateResource(n,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break}case"EIDOS_NETWORK_ERROR":{s.updateResource(n,{status:"error",lastEvent:"network-error"});break}}}function X(e){q({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),c.getState().setOnline(!e)}function J(){const e=w==null?void 0:w.active;if(e){for(const t of D)e.postMessage(t);D=[]}}const R=new Map,Q=new Map;let x=null;function Z(e){x=e}function S(e){return e.includes("*")||/:[^/]+/.test(e)}function ee(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function b(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 te(e,t){if(R.has(e))return R.get(e);const s=ne(e,t),r=S(e)?ee(e):void 0,n={url:e,config:t,strategy:s,status:"idle",cacheHits:0,cacheMisses:0};c.getState().registerResource(e,n),q({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:s.swStrategy,cacheName:s.cacheName,...r!==void 0&&{pattern:r}});const i={url:e,config:t,strategy:s,fetch:async()=>{if(S(e))throw b(e,"fetch");const a=Q.get(e);if(a)return a.then(u=>u.clone());const o=se(e,t,s);return Q.set(e,o),o.finally(()=>Q.delete(e)),o.then(u=>u.clone())},json:async()=>{if(S(e))throw b(e,"json");return(await i.fetch()).json()},query:()=>{if(S(e))throw b(e,"query");return{queryKey:["eidos",e],queryFn:()=>i.json()}},prefetch:async()=>{if(S(e))throw b(e,"prefetch");await i.fetch()},invalidate:async()=>{q({type:"EIDOS_CLEAR_CACHE",url:e});const a=await caches.open(s.cacheName).catch(()=>null);if(a){const o=await a.keys(),u=r?new RegExp(r):null,f=e.startsWith("http");await Promise.all(o.filter(d=>{const l=d.url,P=new URL(l).pathname;return u?u.test(f?l:P):f?l===e:l===e||P===e}).map(d=>a.delete(d)))}S(e)||c.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),x==null||x(["eidos",e])},unregister:()=>{R.delete(e),q({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),c.getState().unregisterResource(e)}};return R.set(e,i),i}async function se(e,t,s){const r=c.getState();r.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const n=await caches.open(s.cacheName).catch(()=>null);try{if(s.swStrategy!=="network-first"){const o=n?await n.match(e).catch(()=>null):null,u=c.getState().resources[e],f=t.maxAge!==void 0&&(u==null?void 0:u.cachedAt)!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(o&&!f)return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:((u==null?void 0:u.cacheHits)??0)+1}),s.swStrategy==="stale-while-revalidate"&&fetch(e).then(async l=>{l.ok&&n&&(await n.put(e,l.clone()),c.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),o;const d=c.getState().resources[e];r.updateResource(e,{cacheMisses:((d==null?void 0:d.cacheMisses)??0)+1})}const i=await fetch(e);if(i.ok)return n&&await n.put(e,i.clone()),r.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),i;r.updateResource(e,{status:i.status===503?"offline":"error"});const a=i.headers.get("X-Eidos-Offline")==="true";throw new Error(a?`offline: no cached response for ${e}`:`${i.status} ${i.statusText}`)}catch(i){const a=n?await n.match(e).catch(()=>null):null;if(a){const o=c.getState().resources[e];return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:((o==null?void 0:o.cacheHits)??0)+1}),a}throw r.updateResource(e,{status:"error"}),i}}function ne(e,t){const s=t.strategy;return t.offline?T(s??"stale-while-revalidate",e,t.cacheName):T(s??"network-first",e,t.cacheName)}const re={"stale-while-revalidate":{name:"StaleWhileRevalidate",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
348
2
  new StaleWhileRevalidate({
349
3
  cacheName: 'eidos-resources-v1',
350
4
  plugins: [new ExpirationPlugin({ maxEntries: 60 })],
351
- })`
352
- },
353
- "cache-first": {
354
- name: "CacheFirst",
355
- reasoning: "cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.",
356
- behavior: [
357
- "Cache hit → return immediately, no network request made",
358
- "Cache miss → fetch from network, cache the response, return it",
359
- "Offline → return cached version, 503 if cache is empty",
360
- "Cache never expires unless explicitly invalidated"
361
- ],
362
- equivalentCode: `// Workbox equivalent
5
+ })`},"cache-first":{name:"CacheFirst",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
363
6
  new CacheFirst({
364
7
  cacheName: 'eidos-resources-v1',
365
8
  plugins: [new ExpirationPlugin({ maxEntries: 60 })],
366
- })`
367
- },
368
- "network-first": {
369
- name: "NetworkFirst",
370
- reasoning: "network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.",
371
- behavior: [
372
- "Always try network first",
373
- "Network success → update cache, return fresh response",
374
- "Network failure → fall back to cached version",
375
- "Offline with empty cache → return 503 error response"
376
- ],
377
- equivalentCode: `// Workbox equivalent
9
+ })`},"network-first":{name:"NetworkFirst",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
378
10
  new NetworkFirst({
379
11
  cacheName: 'eidos-resources-v1',
380
12
  networkTimeoutSeconds: 3,
381
- })`
382
- }
383
- };
384
- function buildStrategy(swStrategy, _url, cacheName) {
385
- return {
386
- ...STRATEGY_META[swStrategy],
387
- swStrategy,
388
- cacheName: cacheName ?? "eidos-resources-v1"
389
- };
390
- }
391
- const DB_NAME = "eidos";
392
- const DB_VERSION = 1;
393
- const QUEUE_STORE = "action-queue";
394
- let _db = null;
395
- function openDB() {
396
- if (_db) return Promise.resolve(_db);
397
- return new Promise((resolve, reject) => {
398
- const req = indexedDB.open(DB_NAME, DB_VERSION);
399
- req.onupgradeneeded = (event) => {
400
- const db = event.target.result;
401
- if (!db.objectStoreNames.contains(QUEUE_STORE)) {
402
- const store = db.createObjectStore(QUEUE_STORE, { keyPath: "id" });
403
- store.createIndex("status", "status", { unique: false });
404
- store.createIndex("actionId", "actionId", { unique: false });
405
- }
406
- };
407
- req.onsuccess = () => {
408
- _db = req.result;
409
- resolve(req.result);
410
- };
411
- req.onerror = () => reject(req.error);
412
- });
413
- }
414
- async function idbAddToQueue(item) {
415
- const db = await openDB();
416
- return new Promise((resolve, reject) => {
417
- const tx = db.transaction(QUEUE_STORE, "readwrite");
418
- tx.objectStore(QUEUE_STORE).add(item);
419
- tx.oncomplete = () => resolve();
420
- tx.onerror = () => reject(tx.error);
421
- });
422
- }
423
- async function idbGetQueue() {
424
- const db = await openDB();
425
- return new Promise((resolve, reject) => {
426
- const tx = db.transaction(QUEUE_STORE, "readonly");
427
- const req = tx.objectStore(QUEUE_STORE).getAll();
428
- req.onsuccess = () => resolve(req.result);
429
- req.onerror = () => reject(req.error);
430
- });
431
- }
432
- async function idbUpdateQueueItem(id, update) {
433
- const db = await openDB();
434
- return new Promise((resolve, reject) => {
435
- const tx = db.transaction(QUEUE_STORE, "readwrite");
436
- const store = tx.objectStore(QUEUE_STORE);
437
- const get = store.get(id);
438
- get.onsuccess = () => {
439
- if (get.result) {
440
- store.put({ ...get.result, ...update });
441
- }
442
- };
443
- tx.oncomplete = () => resolve();
444
- tx.onerror = () => reject(tx.error);
445
- });
446
- }
447
- async function idbRemoveFromQueue(id) {
448
- const db = await openDB();
449
- return new Promise((resolve, reject) => {
450
- const tx = db.transaction(QUEUE_STORE, "readwrite");
451
- tx.objectStore(QUEUE_STORE).delete(id);
452
- tx.oncomplete = () => resolve();
453
- tx.onerror = () => reject(tx.error);
454
- });
455
- }
456
- async function idbGetPendingItems() {
457
- const db = await openDB();
458
- return new Promise((resolve, reject) => {
459
- const tx = db.transaction(QUEUE_STORE, "readonly");
460
- const index = tx.objectStore(QUEUE_STORE).index("status");
461
- const results = [];
462
- let done = 0;
463
- function finish(err) {
464
- if (err) {
465
- reject(err);
466
- return;
467
- }
468
- if (++done === 2) resolve(results);
469
- }
470
- const pendingReq = index.openCursor(IDBKeyRange.only("pending"));
471
- pendingReq.onsuccess = (e) => {
472
- const cursor = e.target.result;
473
- if (cursor) {
474
- results.push(cursor.value);
475
- cursor.continue();
476
- } else finish();
477
- };
478
- pendingReq.onerror = () => finish(pendingReq.error);
479
- const failedReq = index.openCursor(IDBKeyRange.only("failed"));
480
- failedReq.onsuccess = (e) => {
481
- const cursor = e.target.result;
482
- if (cursor) {
483
- results.push(cursor.value);
484
- cursor.continue();
485
- } else finish();
486
- };
487
- failedReq.onerror = () => finish(failedReq.error);
488
- });
489
- }
490
- async function idbClearQueue() {
491
- const db = await openDB();
492
- return new Promise((resolve, reject) => {
493
- const tx = db.transaction(QUEUE_STORE, "readwrite");
494
- tx.objectStore(QUEUE_STORE).clear();
495
- tx.oncomplete = () => resolve();
496
- tx.onerror = () => reject(tx.error);
497
- });
498
- }
499
- const _actionRegistry = /* @__PURE__ */ new Map();
500
- function uid() {
501
- return crypto.randomUUID();
502
- }
503
- function action(fn, config) {
504
- const actionId = config.name || fn.name || uid();
505
- _actionRegistry.set(actionId, fn);
506
- const wrapped = async (...args) => {
507
- const { isOnline } = useEidosStore.getState();
508
- if (config.reliability === "neverLose") {
509
- if (!isOnline) {
510
- return persistAndQueue(actionId, actionId, args, config);
511
- }
512
- try {
513
- return await fn(...args);
514
- } catch {
515
- return persistAndQueue(actionId, actionId, args, config);
516
- }
517
- }
518
- return fn(...args);
519
- };
520
- Object.defineProperty(wrapped, "id", { value: actionId, writable: false });
521
- Object.defineProperty(wrapped, "config", { value: config, writable: false });
522
- return wrapped;
523
- }
524
- async function persistAndQueue(actionId, actionName, args, config) {
525
- const id = uid();
526
- const item = {
527
- id,
528
- actionId,
529
- actionName,
530
- args,
531
- queuedAt: Date.now(),
532
- retryCount: 0,
533
- maxRetries: config.maxRetries ?? 3,
534
- status: "pending"
535
- };
536
- await idbAddToQueue(item);
537
- useEidosStore.getState().addQueueItem(item);
538
- try {
539
- const reg = getSwRegistration();
540
- if (reg && "sync" in reg) {
541
- await reg.sync.register("eidos-queue-replay");
542
- }
543
- } catch {
544
- }
545
- return {
546
- queued: true,
547
- id,
548
- message: `"${actionName}" queued — will execute when online`
549
- };
550
- }
551
- function backoffMs(retryCount) {
552
- const base = Math.min(2e3 * 2 ** retryCount, 3e5);
553
- return base * (0.8 + Math.random() * 0.4);
554
- }
555
- let _replaying = false;
556
- async function replayQueue() {
557
- const store = useEidosStore.getState();
558
- if (!store.isOnline || _replaying) {
559
- return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
560
- }
561
- _replaying = true;
562
- try {
563
- return await _doReplayQueue(store);
564
- } finally {
565
- _replaying = false;
566
- }
567
- }
568
- async function _doReplayQueue(store) {
569
- const candidates = await idbGetPendingItems();
570
- const now = Date.now();
571
- const pending = candidates.filter(
572
- (item) => !item.nextRetryAt || item.nextRetryAt <= now
573
- );
574
- const result = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
575
- const outcomes = await Promise.allSettled(
576
- pending.map(async (item) => {
577
- const fn = _actionRegistry.get(item.actionId);
578
- if (!fn) return "skipped";
579
- store.updateQueueItem(item.id, { status: "replaying" });
580
- await idbUpdateQueueItem(item.id, { status: "replaying" });
581
- try {
582
- await fn(...item.args);
583
- const completedAt = Date.now();
584
- store.updateQueueItem(item.id, { status: "succeeded", completedAt });
585
- await idbUpdateQueueItem(item.id, { status: "succeeded", completedAt });
586
- setTimeout(() => {
587
- store.removeQueueItem(item.id);
588
- idbRemoveFromQueue(item.id);
589
- }, 3e3);
590
- return "succeeded";
591
- } catch (err) {
592
- const retryCount = item.retryCount + 1;
593
- if (retryCount >= item.maxRetries) {
594
- store.updateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
595
- await idbUpdateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
596
- return "failed";
597
- } else {
598
- const nextRetryAt = Date.now() + backoffMs(retryCount);
599
- store.updateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
600
- await idbUpdateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
601
- return "retrying";
602
- }
603
- }
604
- })
605
- );
606
- for (const o of outcomes) {
607
- const outcome = o.status === "fulfilled" ? o.value : "failed";
608
- if (outcome === "skipped") {
609
- result.skipped++;
610
- } else {
611
- result.attempted++;
612
- result[outcome]++;
613
- }
614
- }
615
- return result;
616
- }
617
- async function clearQueue() {
618
- await idbClearQueue();
619
- useEidosStore.getState().hydrateQueue([]);
620
- }
621
- let _initialized = false;
622
- async function initEidos(config = {}) {
623
- if (_initialized) return;
624
- _initialized = true;
625
- const swPath = config.swPath ?? "/eidos-sw.js";
626
- const autoReplay = config.autoReplay ?? true;
627
- try {
628
- const persisted = await idbGetQueue();
629
- if (persisted.length > 0) {
630
- useEidosStore.getState().hydrateQueue(persisted);
631
- }
632
- } catch {
633
- }
634
- try {
635
- await registerServiceWorker(swPath);
636
- } catch {
637
- }
638
- registerBgSyncHandler(() => {
639
- if (useEidosStore.getState().isOnline) {
640
- setTimeout(replayQueue, 200);
641
- }
642
- });
643
- if (autoReplay) {
644
- let prevIsOnline = useEidosStore.getState().isOnline;
645
- useEidosStore.subscribe(() => {
646
- const { isOnline } = useEidosStore.getState();
647
- const justCameOnline = isOnline && !prevIsOnline;
648
- prevIsOnline = isOnline;
649
- if (justCameOnline) {
650
- setTimeout(replayQueue, 600);
651
- }
652
- });
653
- const store = useEidosStore.getState();
654
- const hasPending = store.queue.some((q) => q.status === "pending" || q.status === "failed");
655
- if (store.isOnline && hasPending) {
656
- setTimeout(replayQueue, 1200);
657
- }
658
- }
659
- }
660
- function EidosProvider({ children, swPath, autoReplay }) {
661
- react.useEffect(() => {
662
- initEidos({ swPath, autoReplay });
663
- }, []);
664
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
665
- }
666
- function useStore(selector) {
667
- const fn = selector ?? ((s) => s);
668
- return react.useSyncExternalStore(useEidosStore.subscribe, () => fn(useEidosStore.getState()));
669
- }
670
- function useEidos() {
671
- return useStore();
672
- }
673
- function useEidosResource(url) {
674
- return useStore((s) => s.resources[url]);
675
- }
676
- function useEidosQueue() {
677
- return useStore((s) => s.queue);
678
- }
679
- function useEidosAction(id) {
680
- return useStore((s) => s.queue.find((item) => item.id === id));
681
- }
682
- function useEidosStatus() {
683
- const isOnline = useStore((s) => s.isOnline);
684
- const swStatus = useStore((s) => s.swStatus);
685
- const swError = useStore((s) => s.swError);
686
- return { isOnline, swStatus, swError };
687
- }
688
- function useEidosQueueStats() {
689
- const pending = useStore((s) => s.queue.filter((q) => q.status === "pending").length);
690
- const failed = useStore((s) => s.queue.filter((q) => q.status === "failed").length);
691
- const replaying = useStore((s) => s.queue.filter((q) => q.status === "replaying").length);
692
- const total = useStore((s) => s.queue.length);
693
- return { pending, failed, replaying, total };
694
- }
695
- function useEidosOnDrain(callback) {
696
- const total = useStore((s) => s.queue.length);
697
- const prevRef = react.useRef(0);
698
- const callbackRef = react.useRef(callback);
699
- callbackRef.current = callback;
700
- react.useEffect(() => {
701
- if (prevRef.current > 0 && total === 0) {
702
- callbackRef.current();
703
- }
704
- prevRef.current = total;
705
- }, [total]);
706
- }
707
- const VERSION = "1.0.12";
708
- function readable(selector) {
709
- return {
710
- subscribe(run) {
711
- run(selector(useEidosStore.getState()));
712
- return useEidosStore.subscribe(() => run(selector(useEidosStore.getState())));
713
- },
714
- getState() {
715
- return selector(useEidosStore.getState());
716
- }
717
- };
718
- }
719
- const eidosStore = readable((s) => s);
720
- const eidosQueue = readable((s) => s.queue);
721
- const eidosStatus = readable((s) => ({
722
- isOnline: s.isOnline,
723
- swStatus: s.swStatus,
724
- swError: s.swError
725
- }));
726
- const eidosQueueStats = readable((s) => ({
727
- pending: s.queue.filter((q) => q.status === "pending").length,
728
- failed: s.queue.filter((q) => q.status === "failed").length,
729
- replaying: s.queue.filter((q) => q.status === "replaying").length,
730
- total: s.queue.length
731
- }));
732
- function eidosResource(url) {
733
- return readable((s) => s.resources[url]);
734
- }
735
- function eidosAction(id) {
736
- return readable((s) => s.queue.find((item) => item.id === id));
737
- }
738
- exports.EidosProvider = EidosProvider;
739
- exports.VERSION = VERSION;
740
- exports.action = action;
741
- exports.clearQueue = clearQueue;
742
- exports.eidosAction = eidosAction;
743
- exports.eidosQueue = eidosQueue;
744
- exports.eidosQueueStats = eidosQueueStats;
745
- exports.eidosResource = eidosResource;
746
- exports.eidosStatus = eidosStatus;
747
- exports.eidosStore = eidosStore;
748
- exports.initEidos = initEidos;
749
- exports.isBgSyncSupported = isBgSyncSupported;
750
- exports.replayQueue = replayQueue;
751
- exports.resource = resource;
752
- exports.setOfflineSimulation = setOfflineSimulation;
753
- exports.setQueryInvalidator = setQueryInvalidator;
754
- exports.useEidos = useEidos;
755
- exports.useEidosAction = useEidosAction;
756
- exports.useEidosOnDrain = useEidosOnDrain;
757
- exports.useEidosQueue = useEidosQueue;
758
- exports.useEidosQueueStats = useEidosQueueStats;
759
- exports.useEidosResource = useEidosResource;
760
- exports.useEidosStatus = useEidosStatus;
761
- exports.useEidosStore = useEidosStore;
13
+ })`}};function T(e,t,s){return{...re[e],swStrategy:e,cacheName:s??"eidos-resources-v1"}}const ae="eidos",ie=1,h="action-queue";let A=null;function m(){return A?Promise.resolve(A):new Promise((e,t)=>{const s=indexedDB.open(ae,ie);s.onupgradeneeded=r=>{const n=r.target.result;if(!n.objectStoreNames.contains(h)){const i=n.createObjectStore(h,{keyPath:"id"});i.createIndex("status","status",{unique:!1}),i.createIndex("actionId","actionId",{unique:!1})}},s.onsuccess=()=>{A=s.result,e(s.result)},s.onerror=()=>t(s.error)})}async function oe(e){const t=await m();return new Promise((s,r)=>{const n=t.transaction(h,"readwrite");n.objectStore(h).add(e),n.oncomplete=()=>s(),n.onerror=()=>r(n.error)})}async function ce(){const e=await m();return new Promise((t,s)=>{const n=e.transaction(h,"readonly").objectStore(h).getAll();n.onsuccess=()=>t(n.result),n.onerror=()=>s(n.error)})}async function O(e,t){const s=await m();return new Promise((r,n)=>{const i=s.transaction(h,"readwrite"),a=i.objectStore(h),o=a.get(e);o.onsuccess=()=>{o.result&&a.put({...o.result,...t})},i.oncomplete=()=>r(),i.onerror=()=>n(i.error)})}async function ue(e){const t=await m();return new Promise((s,r)=>{const n=t.transaction(h,"readwrite");n.objectStore(h).delete(e),n.oncomplete=()=>s(),n.onerror=()=>r(n.error)})}async function de(){const e=await m();return new Promise((t,s)=>{const n=e.transaction(h,"readonly").objectStore(h).index("status"),i=[];let a=0;function o(d){if(d){s(d);return}++a===2&&t(i)}const u=n.openCursor(IDBKeyRange.only("pending"));u.onsuccess=d=>{const l=d.target.result;l?(i.push(l.value),l.continue()):o()},u.onerror=()=>o(u.error);const f=n.openCursor(IDBKeyRange.only("failed"));f.onsuccess=d=>{const l=d.target.result;l?(i.push(l.value),l.continue()):o()},f.onerror=()=>o(f.error)})}async function fe(){const e=await m();return new Promise((t,s)=>{const r=e.transaction(h,"readwrite");r.objectStore(h).clear(),r.oncomplete=()=>t(),r.onerror=()=>s(r.error)})}const W=new Map;function B(){return crypto.randomUUID()}function le(e,t){const s=t.name||e.name||B();W.set(s,e);const r=async(...n)=>{const{isOnline:i}=c.getState();if(t.reliability==="neverLose"){if(!i)return j(s,s,n,t);try{return await e(...n)}catch{return j(s,s,n,t)}}return e(...n)};return Object.defineProperty(r,"id",{value:s,writable:!1}),Object.defineProperty(r,"config",{value:t,writable:!1}),r}async function j(e,t,s,r){const n=B(),i={id:n,actionId:e,actionName:t,args:s,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending"};await oe(i),c.getState().addQueueItem(i);try{const a=$();a&&"sync"in a&&await a.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:n,message:`"${t}" queued — will execute when online`}}function he(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}let _=!1;async function I(){const e=c.getState();if(!e.isOnline||_)return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0};_=!0;try{return await pe(e)}finally{_=!1}}async function pe(e){const t=await de(),s=Date.now(),r=t.filter(a=>!a.nextRetryAt||a.nextRetryAt<=s),n={attempted:0,succeeded:0,failed:0,retrying:0,skipped:0},i=await Promise.allSettled(r.map(async a=>{const o=W.get(a.actionId);if(!o)return"skipped";e.updateQueueItem(a.id,{status:"replaying"}),await O(a.id,{status:"replaying"});try{await o(...a.args);const u=Date.now();return e.updateQueueItem(a.id,{status:"succeeded",completedAt:u}),await O(a.id,{status:"succeeded",completedAt:u}),setTimeout(()=>{e.removeQueueItem(a.id),ue(a.id)},3e3),"succeeded"}catch(u){const f=a.retryCount+1;if(f>=a.maxRetries)return e.updateQueueItem(a.id,{status:"failed",error:String(u),retryCount:f}),await O(a.id,{status:"failed",error:String(u),retryCount:f}),"failed";{const d=Date.now()+he(f);return e.updateQueueItem(a.id,{status:"pending",retryCount:f,nextRetryAt:d}),await O(a.id,{status:"pending",retryCount:f,nextRetryAt:d}),"retrying"}}}));for(const a of i){const o=a.status==="fulfilled"?a.value:"failed";o==="skipped"?n.skipped++:(n.attempted++,n[o]++)}return n}async function we(){await fe(),c.getState().hydrateQueue([])}let M=!1;async function H(e={}){if(M)return;M=!0;const t=e.swPath??"/eidos-sw.js",s=e.autoReplay??!0;try{const r=await ce();r.length>0&&c.getState().hydrateQueue(r)}catch{}try{await G(t)}catch{}if(V(()=>{c.getState().isOnline&&setTimeout(I,200)}),s){let r=c.getState().isOnline;c.subscribe(()=>{const{isOnline:a}=c.getState(),o=a&&!r;r=a,o&&setTimeout(I,600)});const n=c.getState(),i=n.queue.some(a=>a.status==="pending"||a.status==="failed");n.isOnline&&i&&setTimeout(I,1200)}}function ge({children:e,swPath:t,autoReplay:s}){return v.useEffect(()=>{H({swPath:t,autoReplay:s})},[]),N.jsx(N.Fragment,{children:e})}function p(e){const t=e??(s=>s);return v.useSyncExternalStore(c.subscribe,()=>t(c.getState()))}function ye(){return p()}function Se(e){return p(t=>t.resources[e])}function me(){return p(e=>e.queue)}function Ee(e){return p(t=>t.queue.find(s=>s.id===e))}function ve(){const e=p(r=>r.isOnline),t=p(r=>r.swStatus),s=p(r=>r.swError);return{isOnline:e,swStatus:t,swError:s}}function Re(){const e=p(n=>n.queue.filter(i=>i.status==="pending").length),t=p(n=>n.queue.filter(i=>i.status==="failed").length),s=p(n=>n.queue.filter(i=>i.status==="replaying").length),r=p(n=>n.queue.length);return{pending:e,failed:t,replaying:s,total:r}}function be(e){const t=p(n=>n.queue.length),s=v.useRef(0),r=v.useRef(e);r.current=e,v.useEffect(()=>{s.current>0&&t===0&&r.current(),s.current=t},[t])}const Oe="1.0.12";function E(e){return{subscribe(t){return t(e(c.getState())),c.subscribe(()=>t(e(c.getState())))},getState(){return e(c.getState())}}}const qe=E(e=>e),ke=E(e=>e.queue),xe=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError})),Ie=E(e=>{let t=0,s=0,r=0;for(const n of e.queue)n.status==="pending"?t++:n.status==="failed"?s++:n.status==="replaying"&&r++;return{pending:t,failed:s,replaying:r,total:e.queue.length}});function Qe(e){return E(t=>t.resources[e])}function Ae(e){return E(t=>t.queue.find(s=>s.id===e))}exports.EidosProvider=ge;exports.VERSION=Oe;exports.action=le;exports.clearQueue=we;exports.eidosAction=Ae;exports.eidosQueue=ke;exports.eidosQueueStats=Ie;exports.eidosResource=Qe;exports.eidosStatus=xe;exports.eidosStore=qe;exports.initEidos=H;exports.isBgSyncSupported=Y;exports.replayQueue=I;exports.resource=te;exports.setOfflineSimulation=X;exports.setQueryInvalidator=Z;exports.useEidos=ye;exports.useEidosAction=Ee;exports.useEidosOnDrain=be;exports.useEidosQueue=me;exports.useEidosQueueStats=Re;exports.useEidosResource=Se;exports.useEidosStatus=ve;exports.useEidosStore=c;
762
14
  //# sourceMappingURL=eidos.cjs.js.map