@sweidos/eidos 1.0.8 → 1.0.9
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 +10 -2
- package/dist/eidos.cjs.js +23 -15
- package/dist/eidos.cjs.js.map +1 -1
- package/dist/eidos.es.js +23 -15
- package/dist/eidos.es.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,13 +208,21 @@ const result = await createOrder(payload)
|
|
|
208
208
|
|
|
209
209
|
### `replayQueue()`
|
|
210
210
|
|
|
211
|
-
Manually trigger queue replay. Called automatically on reconnect when `autoReplay: true`.
|
|
211
|
+
Manually trigger queue replay. Called automatically on reconnect when `autoReplay: true`. Returns a `ReplayResult` summary.
|
|
212
212
|
|
|
213
213
|
```ts
|
|
214
214
|
import { replayQueue } from '@sweidos/eidos'
|
|
215
|
+
import type { ReplayResult } from '@sweidos/eidos'
|
|
215
216
|
|
|
216
217
|
// Manual trigger — e.g. after a user clicks "Retry"
|
|
217
|
-
await replayQueue()
|
|
218
|
+
const result: ReplayResult = await replayQueue()
|
|
219
|
+
// { attempted: 3, succeeded: 2, failed: 0, retrying: 1, skipped: 0 }
|
|
220
|
+
//
|
|
221
|
+
// attempted — items where the fn was found and called
|
|
222
|
+
// succeeded — resolved successfully
|
|
223
|
+
// failed — maxRetries exceeded, stays in queue
|
|
224
|
+
// retrying — failed, will retry later (nextRetryAt set)
|
|
225
|
+
// skipped — fn not in registry (module not imported yet)
|
|
218
226
|
```
|
|
219
227
|
|
|
220
228
|
---
|
package/dist/eidos.cjs.js
CHANGED
|
@@ -496,10 +496,12 @@ function backoffMs(retryCount) {
|
|
|
496
496
|
let _replaying = false;
|
|
497
497
|
async function replayQueue() {
|
|
498
498
|
const store = useEidosStore.getState();
|
|
499
|
-
if (!store.isOnline || _replaying)
|
|
499
|
+
if (!store.isOnline || _replaying) {
|
|
500
|
+
return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
|
|
501
|
+
}
|
|
500
502
|
_replaying = true;
|
|
501
503
|
try {
|
|
502
|
-
await _doReplayQueue(store);
|
|
504
|
+
return await _doReplayQueue(store);
|
|
503
505
|
} finally {
|
|
504
506
|
_replaying = false;
|
|
505
507
|
}
|
|
@@ -510,10 +512,11 @@ async function _doReplayQueue(store) {
|
|
|
510
512
|
const pending = candidates.filter(
|
|
511
513
|
(item) => !item.nextRetryAt || item.nextRetryAt <= now
|
|
512
514
|
);
|
|
513
|
-
|
|
515
|
+
const result = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
|
|
516
|
+
const outcomes = await Promise.allSettled(
|
|
514
517
|
pending.map(async (item) => {
|
|
515
518
|
const fn = _actionRegistry.get(item.actionId);
|
|
516
|
-
if (!fn) return;
|
|
519
|
+
if (!fn) return "skipped";
|
|
517
520
|
store.updateQueueItem(item.id, { status: "replaying" });
|
|
518
521
|
await idbUpdateQueueItem(item.id, { status: "replaying" });
|
|
519
522
|
try {
|
|
@@ -525,27 +528,32 @@ async function _doReplayQueue(store) {
|
|
|
525
528
|
store.removeQueueItem(item.id);
|
|
526
529
|
idbRemoveFromQueue(item.id);
|
|
527
530
|
}, 3e3);
|
|
531
|
+
return "succeeded";
|
|
528
532
|
} catch (err) {
|
|
529
533
|
const retryCount = item.retryCount + 1;
|
|
530
534
|
if (retryCount >= item.maxRetries) {
|
|
531
|
-
store.updateQueueItem(item.id, {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
retryCount
|
|
535
|
-
});
|
|
536
|
-
await idbUpdateQueueItem(item.id, {
|
|
537
|
-
status: "failed",
|
|
538
|
-
error: String(err),
|
|
539
|
-
retryCount
|
|
540
|
-
});
|
|
535
|
+
store.updateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
|
|
536
|
+
await idbUpdateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
|
|
537
|
+
return "failed";
|
|
541
538
|
} else {
|
|
542
539
|
const nextRetryAt = Date.now() + backoffMs(retryCount);
|
|
543
540
|
store.updateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
|
|
544
541
|
await idbUpdateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
|
|
542
|
+
return "retrying";
|
|
545
543
|
}
|
|
546
544
|
}
|
|
547
545
|
})
|
|
548
546
|
);
|
|
547
|
+
for (const o of outcomes) {
|
|
548
|
+
const outcome = o.status === "fulfilled" ? o.value : "failed";
|
|
549
|
+
if (outcome === "skipped") {
|
|
550
|
+
result.skipped++;
|
|
551
|
+
} else {
|
|
552
|
+
result.attempted++;
|
|
553
|
+
result[outcome]++;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return result;
|
|
549
557
|
}
|
|
550
558
|
async function clearQueue() {
|
|
551
559
|
await idbClearQueue();
|
|
@@ -606,7 +614,7 @@ function useEidosStatus() {
|
|
|
606
614
|
const swError = useEidosStore((s) => s.swError);
|
|
607
615
|
return { isOnline, swStatus, swError };
|
|
608
616
|
}
|
|
609
|
-
const VERSION = "1.0.
|
|
617
|
+
const VERSION = "1.0.9";
|
|
610
618
|
exports.EidosProvider = EidosProvider;
|
|
611
619
|
exports.VERSION = VERSION;
|
|
612
620
|
exports.action = action;
|
package/dist/eidos.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eidos.cjs.js","sources":["../src/store.ts","../src/sw-bridge.ts","../src/resource.ts","../src/idb.ts","../src/action.ts","../src/runtime.ts","../src/react/Provider.tsx","../src/react/hooks.ts","../src/version.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react'\nimport type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\n addQueueItem: (item: ActionQueueItem) => void\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void\n removeQueueItem: (id: string) => void\n hydrateQueue: (items: ActionQueueItem[]) => void\n}\n\ntype Listener = () => void\n\nlet _state: EidosStore\nconst _listeners = new Set<Listener>()\n\nfunction _notify() {\n _listeners.forEach((fn) => fn())\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) }\n _notify()\n}\n\n_state = {\n isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n registerResource: (url, entry) =>\n _set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n _set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n _set((s) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\n addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n _set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => _set(() => ({ queue: items })),\n}\n\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\n}\n\n// useSyncExternalStore-based hook — drop-in replacement for zustand's useStore.\n// Supports both bare call (full state) and selector call.\nfunction _useStore(): EidosStore\nfunction _useStore<T>(selector: (state: EidosStore) => T): T\nfunction _useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T)\n return useSyncExternalStore(_subscribe, () => fn(_getState()))\n}\n\n_useStore.getState = _getState\n_useStore.subscribe = _subscribe\n\n// Test/devtools helper — merges partial state, preserves action methods.\n_useStore.setState = (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial\n _state = { ..._state, ...update }\n _notify()\n}\n\nexport const useEidosStore = _useStore\n","import { useEidosStore } from './store'\n\nlet _registration: ServiceWorkerRegistration | null = null\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = []\n\nexport function getSwRegistration() {\n return _registration\n}\n\nexport async function registerServiceWorker(swPath: string): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported')\n return\n }\n\n const store = useEidosStore.getState()\n store.setSwStatus('registering')\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' })\n\n await waitForActivation(_registration)\n\n store.setSwStatus('active')\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage)\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true))\n window.addEventListener('offline', () => store.setOnline(false))\n\n flushPendingMessages()\n } catch (err) {\n store.setSwStatus('error', String(err))\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) { resolve(); return }\n const sw = reg.installing ?? reg.waiting\n if (!sw) { resolve(); return }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000)\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer)\n sw.removeEventListener('statechange', handler)\n resolve()\n }\n })\n })\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active\n if (sw) {\n sw.postMessage(message)\n } else {\n _pendingMessages.push(message)\n }\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as { type: string; url?: string; strategy?: string }\n if (!data?.type) return\n\n const store = useEidosStore.getState()\n const { type, url } = data\n\n if (!url) return\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n break\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n })\n break\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n })\n break\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled })\n useEidosStore.getState().setOnline(!enabled)\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active\n if (!sw) return\n for (const msg of _pendingMessages) sw.postMessage(msg)\n _pendingMessages = []\n}\n","import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ───\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ───────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url)\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n },\n\n json: async () => {\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => ({\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }),\n\n prefetch: async () => {\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n await Promise.all(\n keys\n .filter((r) => r.url === url || new URL(r.url).pathname === url)\n .map((r) => cache.delete(r)),\n )\n }\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n '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.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n","import type { ActionQueueItem } from './types'\n\nconst DB_NAME = 'eidos'\nconst DB_VERSION = 1\nconst QUEUE_STORE = 'action-queue'\n\nlet _db: IDBDatabase | null = null\n\nfunction openDB(): Promise<IDBDatabase> {\n if (_db) return Promise.resolve(_db)\n\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, DB_VERSION)\n\n req.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(QUEUE_STORE)) {\n const store = db.createObjectStore(QUEUE_STORE, { keyPath: 'id' })\n store.createIndex('status', 'status', { unique: false })\n store.createIndex('actionId', 'actionId', { unique: false })\n }\n }\n\n req.onsuccess = () => {\n _db = req.result\n resolve(req.result)\n }\n\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbAddToQueue(item: ActionQueueItem): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).add(item)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbGetQueue(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const req = tx.objectStore(QUEUE_STORE).getAll()\n req.onsuccess = () => resolve(req.result as ActionQueueItem[])\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbUpdateQueueItem(\n id: string,\n update: Partial<ActionQueueItem>,\n): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n const store = tx.objectStore(QUEUE_STORE)\n const get = store.get(id)\n get.onsuccess = () => {\n if (get.result) {\n store.put({ ...get.result, ...update })\n } else if (import.meta.env.DEV) {\n console.warn(`[eidos] idbUpdateQueueItem: item \"${id}\" not found — store/IDB may have diverged`)\n }\n }\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbRemoveFromQueue(id: string): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).delete(id)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\n// Uses the status index to fetch only pending/failed items — avoids a full\n// table scan when the queue has many succeeded/replaying entries.\nexport async function idbGetPendingItems(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const index = tx.objectStore(QUEUE_STORE).index('status')\n const results: ActionQueueItem[] = []\n\n let done = 0\n function finish(err?: DOMException | null) {\n if (err) { reject(err); return }\n if (++done === 2) resolve(results)\n }\n\n const pendingReq = index.openCursor(IDBKeyRange.only('pending'))\n pendingReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n pendingReq.onerror = () => finish(pendingReq.error)\n\n const failedReq = index.openCursor(IDBKeyRange.only('failed'))\n failedReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n failedReq.onerror = () => finish(failedReq.error)\n })\n}\n\nexport async function idbClearQueue(): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).clear()\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n","import { useEidosStore } from './store'\nimport {\n idbAddToQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport type {\n ActionConfig,\n ActionHandle,\n ActionFn,\n ActionQueueItem,\n QueuedResult,\n} from './types'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _actionRegistry = new Map<string, ActionFn<any[], any>>()\n\nfunction uid() {\n return crypto.randomUUID()\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function action<TArgs extends any[], TReturn>(\n fn: ActionFn<TArgs, TReturn>,\n config: ActionConfig,\n): ActionHandle<TArgs, TReturn> {\n // || not ?? — fn.name can be '' (anonymous arrow fn) which ?? treats as a\n // valid value, causing all anonymous actions to share actionId ''.\n const actionId = config.name || fn.name || uid()\n\n if (import.meta.env.DEV && config.reliability === 'neverLose' && !config.name && !fn.name) {\n console.warn(\n `[eidos] action() registered with neverLose but no stable name was found (fn.name=\"${fn.name}\"). Pass config.name so queued items survive a page reload and can be replayed.`,\n )\n }\n\n // Registering here means the function is available for replay after\n // the user refreshes the page (actions are defined at module scope).\n _actionRegistry.set(actionId, fn as ActionFn<unknown[], unknown>)\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n if (config.reliability === 'neverLose') {\n if (!isOnline) {\n return persistAndQueue(actionId, actionId, args, config)\n }\n // Online + neverLose: execute, queue on failure\n try {\n return await fn(...args)\n } catch {\n return persistAndQueue(actionId, actionId, args, config)\n }\n }\n\n // best-effort: execute directly, no queuing\n return fn(...args)\n }\n\n Object.defineProperty(wrapped, 'id', { value: actionId, writable: false })\n Object.defineProperty(wrapped, 'config', { value: config, writable: false })\n\n return wrapped as unknown as ActionHandle<TArgs, TReturn>\n}\n\nfunction isJsonSerializable(value: unknown): boolean {\n try {\n JSON.stringify(value)\n return true\n } catch {\n return false\n }\n}\n\nasync function persistAndQueue(\n actionId: string,\n actionName: string,\n args: unknown[],\n config: ActionConfig,\n): Promise<QueuedResult> {\n if (import.meta.env.DEV && !isJsonSerializable(args)) {\n console.warn(\n `[eidos] action \"${actionName}\" queued with non-JSON-serializable args. These args will be lost after a page reload. Use plain JSON values for neverLose actions.`,\n args,\n )\n }\n\n const id = uid()\n const item: ActionQueueItem = {\n id,\n actionId,\n actionName,\n args,\n queuedAt: Date.now(),\n retryCount: 0,\n maxRetries: config.maxRetries ?? 3,\n status: 'pending',\n }\n\n await idbAddToQueue(item)\n useEidosStore.getState().addQueueItem(item)\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\n// Base delay 2s, doubles per retry, capped at 5 minutes, ±20% jitter\nfunction backoffMs(retryCount: number): number {\n const base = Math.min(2000 * 2 ** retryCount, 300_000)\n return base * (0.8 + Math.random() * 0.4)\n}\n\nlet _replaying = false\n\nexport async function replayQueue(): Promise<void> {\n const store = useEidosStore.getState()\n if (!store.isOnline || _replaying) return\n _replaying = true\n try {\n await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<void> {\n\n const candidates = await idbGetPendingItems()\n const now = Date.now()\n const pending = candidates.filter(\n (item) => !item.nextRetryAt || item.nextRetryAt <= now,\n )\n\n await Promise.allSettled(\n pending.map(async (item) => {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return\n\n store.updateQueueItem(item.id, { status: 'replaying' })\n await idbUpdateQueueItem(item.id, { status: 'replaying' })\n\n try {\n await fn(...(item.args as unknown[]))\n const completedAt = Date.now()\n store.updateQueueItem(item.id, { status: 'succeeded', completedAt })\n await idbUpdateQueueItem(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a delay so the UI can show the success state\n setTimeout(() => {\n store.removeQueueItem(item.id)\n idbRemoveFromQueue(item.id)\n }, 3000)\n } catch (err) {\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, {\n status: 'failed',\n error: String(err),\n retryCount,\n })\n await idbUpdateQueueItem(item.id, {\n status: 'failed',\n error: String(err),\n retryCount,\n })\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await idbUpdateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n }\n }\n }),\n )\n}\n\n/** Remove all items from the action queue (IDB + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await idbClearQueue()\n useEidosStore.getState().hydrateQueue([])\n}\n","import { registerServiceWorker } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean\n}\n\nlet _initialized = false\nlet _unsubscribe: (() => void) | null = null\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n if (_initialized) return\n _initialized = true\n\n const swPath = config.swPath ?? '/eidos-sw.js'\n const autoReplay = config.autoReplay ?? true\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue()\n if (persisted.length > 0) {\n useEidosStore.getState().hydrateQueue(persisted)\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath)\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n if (autoReplay) {\n // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;font-weight:bold')\n console.log('SW path :', swPath)\n console.log('Auto-replay:', autoReplay)\n console.log('SW status :', store.swStatus)\n console.groupEnd()\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.()\n _unsubscribe = null\n _initialized = false\n}\n","import { useEffect, type ReactNode } from 'react'\nimport { initEidos, type EidosConfig } from '../runtime'\n\ninterface EidosProviderProps extends EidosConfig {\n children: ReactNode\n}\n\n/**\n * Mount once at the root of your application.\n * Registers the service worker and initialises the Eidos runtime.\n *\n * @example\n * <EidosProvider swPath=\"/eidos-sw.js\">\n * <App />\n * </EidosProvider>\n */\nexport function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps) {\n useEffect(() => {\n initEidos({ swPath, autoReplay })\n // Run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return <>{children}</>\n}\n","import { useEidosStore } from '../store'\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useEidosStore()\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useEidosStore((s) => s.resources[url])\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useEidosStore((s) => s.queue)\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useEidosStore((s) => s.isOnline)\n const swStatus = useEidosStore((s) => s.swStatus)\n const swError = useEidosStore((s) => s.swError)\n return { isOnline, swStatus, swError }\n}\n","export const VERSION = '1.0.8'\n"],"names":["useSyncExternalStore","useEffect"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"eidos.cjs.js","sources":["../src/store.ts","../src/sw-bridge.ts","../src/resource.ts","../src/idb.ts","../src/action.ts","../src/runtime.ts","../src/react/Provider.tsx","../src/react/hooks.ts","../src/version.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react'\nimport type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\n addQueueItem: (item: ActionQueueItem) => void\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void\n removeQueueItem: (id: string) => void\n hydrateQueue: (items: ActionQueueItem[]) => void\n}\n\ntype Listener = () => void\n\nlet _state: EidosStore\nconst _listeners = new Set<Listener>()\n\nfunction _notify() {\n _listeners.forEach((fn) => fn())\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) }\n _notify()\n}\n\n_state = {\n isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n registerResource: (url, entry) =>\n _set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n _set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n _set((s) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\n addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n _set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => _set(() => ({ queue: items })),\n}\n\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\n}\n\n// useSyncExternalStore-based hook — drop-in replacement for zustand's useStore.\n// Supports both bare call (full state) and selector call.\nfunction _useStore(): EidosStore\nfunction _useStore<T>(selector: (state: EidosStore) => T): T\nfunction _useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T)\n return useSyncExternalStore(_subscribe, () => fn(_getState()))\n}\n\n_useStore.getState = _getState\n_useStore.subscribe = _subscribe\n\n// Test/devtools helper — merges partial state, preserves action methods.\n_useStore.setState = (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial\n _state = { ..._state, ...update }\n _notify()\n}\n\nexport const useEidosStore = _useStore\n","import { useEidosStore } from './store'\n\nlet _registration: ServiceWorkerRegistration | null = null\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = []\n\nexport function getSwRegistration() {\n return _registration\n}\n\nexport async function registerServiceWorker(swPath: string): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported')\n return\n }\n\n const store = useEidosStore.getState()\n store.setSwStatus('registering')\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' })\n\n await waitForActivation(_registration)\n\n store.setSwStatus('active')\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage)\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true))\n window.addEventListener('offline', () => store.setOnline(false))\n\n flushPendingMessages()\n } catch (err) {\n store.setSwStatus('error', String(err))\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) { resolve(); return }\n const sw = reg.installing ?? reg.waiting\n if (!sw) { resolve(); return }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000)\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer)\n sw.removeEventListener('statechange', handler)\n resolve()\n }\n })\n })\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active\n if (sw) {\n sw.postMessage(message)\n } else {\n _pendingMessages.push(message)\n }\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as { type: string; url?: string; strategy?: string }\n if (!data?.type) return\n\n const store = useEidosStore.getState()\n const { type, url } = data\n\n if (!url) return\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n break\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n })\n break\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n })\n break\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled })\n useEidosStore.getState().setOnline(!enabled)\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active\n if (!sw) return\n for (const msg of _pendingMessages) sw.postMessage(msg)\n _pendingMessages = []\n}\n","import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ───\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ───────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url)\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n },\n\n json: async () => {\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => ({\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }),\n\n prefetch: async () => {\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n await Promise.all(\n keys\n .filter((r) => r.url === url || new URL(r.url).pathname === url)\n .map((r) => cache.delete(r)),\n )\n }\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n '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.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n","import type { ActionQueueItem } from './types'\n\nconst DB_NAME = 'eidos'\nconst DB_VERSION = 1\nconst QUEUE_STORE = 'action-queue'\n\nlet _db: IDBDatabase | null = null\n\nfunction openDB(): Promise<IDBDatabase> {\n if (_db) return Promise.resolve(_db)\n\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, DB_VERSION)\n\n req.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(QUEUE_STORE)) {\n const store = db.createObjectStore(QUEUE_STORE, { keyPath: 'id' })\n store.createIndex('status', 'status', { unique: false })\n store.createIndex('actionId', 'actionId', { unique: false })\n }\n }\n\n req.onsuccess = () => {\n _db = req.result\n resolve(req.result)\n }\n\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbAddToQueue(item: ActionQueueItem): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).add(item)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbGetQueue(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const req = tx.objectStore(QUEUE_STORE).getAll()\n req.onsuccess = () => resolve(req.result as ActionQueueItem[])\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbUpdateQueueItem(\n id: string,\n update: Partial<ActionQueueItem>,\n): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n const store = tx.objectStore(QUEUE_STORE)\n const get = store.get(id)\n get.onsuccess = () => {\n if (get.result) {\n store.put({ ...get.result, ...update })\n } else if (import.meta.env.DEV) {\n console.warn(`[eidos] idbUpdateQueueItem: item \"${id}\" not found — store/IDB may have diverged`)\n }\n }\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbRemoveFromQueue(id: string): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).delete(id)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\n// Uses the status index to fetch only pending/failed items — avoids a full\n// table scan when the queue has many succeeded/replaying entries.\nexport async function idbGetPendingItems(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const index = tx.objectStore(QUEUE_STORE).index('status')\n const results: ActionQueueItem[] = []\n\n let done = 0\n function finish(err?: DOMException | null) {\n if (err) { reject(err); return }\n if (++done === 2) resolve(results)\n }\n\n const pendingReq = index.openCursor(IDBKeyRange.only('pending'))\n pendingReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n pendingReq.onerror = () => finish(pendingReq.error)\n\n const failedReq = index.openCursor(IDBKeyRange.only('failed'))\n failedReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n failedReq.onerror = () => finish(failedReq.error)\n })\n}\n\nexport async function idbClearQueue(): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).clear()\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n","import { useEidosStore } from './store'\nimport {\n idbAddToQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport type {\n ActionConfig,\n ActionHandle,\n ActionFn,\n ActionQueueItem,\n QueuedResult,\n ReplayResult,\n} from './types'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _actionRegistry = new Map<string, ActionFn<any[], any>>()\n\nfunction uid() {\n return crypto.randomUUID()\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function action<TArgs extends any[], TReturn>(\n fn: ActionFn<TArgs, TReturn>,\n config: ActionConfig,\n): ActionHandle<TArgs, TReturn> {\n // || not ?? — fn.name can be '' (anonymous arrow fn) which ?? treats as a\n // valid value, causing all anonymous actions to share actionId ''.\n const actionId = config.name || fn.name || uid()\n\n if (import.meta.env.DEV && config.reliability === 'neverLose' && !config.name && !fn.name) {\n console.warn(\n `[eidos] action() registered with neverLose but no stable name was found (fn.name=\"${fn.name}\"). Pass config.name so queued items survive a page reload and can be replayed.`,\n )\n }\n\n // Registering here means the function is available for replay after\n // the user refreshes the page (actions are defined at module scope).\n _actionRegistry.set(actionId, fn as ActionFn<unknown[], unknown>)\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n if (config.reliability === 'neverLose') {\n if (!isOnline) {\n return persistAndQueue(actionId, actionId, args, config)\n }\n // Online + neverLose: execute, queue on failure\n try {\n return await fn(...args)\n } catch {\n return persistAndQueue(actionId, actionId, args, config)\n }\n }\n\n // best-effort: execute directly, no queuing\n return fn(...args)\n }\n\n Object.defineProperty(wrapped, 'id', { value: actionId, writable: false })\n Object.defineProperty(wrapped, 'config', { value: config, writable: false })\n\n return wrapped as unknown as ActionHandle<TArgs, TReturn>\n}\n\nfunction isJsonSerializable(value: unknown): boolean {\n try {\n JSON.stringify(value)\n return true\n } catch {\n return false\n }\n}\n\nasync function persistAndQueue(\n actionId: string,\n actionName: string,\n args: unknown[],\n config: ActionConfig,\n): Promise<QueuedResult> {\n if (import.meta.env.DEV && !isJsonSerializable(args)) {\n console.warn(\n `[eidos] action \"${actionName}\" queued with non-JSON-serializable args. These args will be lost after a page reload. Use plain JSON values for neverLose actions.`,\n args,\n )\n }\n\n const id = uid()\n const item: ActionQueueItem = {\n id,\n actionId,\n actionName,\n args,\n queuedAt: Date.now(),\n retryCount: 0,\n maxRetries: config.maxRetries ?? 3,\n status: 'pending',\n }\n\n await idbAddToQueue(item)\n useEidosStore.getState().addQueueItem(item)\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\n// Base delay 2s, doubles per retry, capped at 5 minutes, ±20% jitter\nfunction backoffMs(retryCount: number): number {\n const base = Math.min(2000 * 2 ** retryCount, 300_000)\n return base * (0.8 + Math.random() * 0.4)\n}\n\nlet _replaying = false\n\nexport async function replayQueue(): Promise<ReplayResult> {\n const store = useEidosStore.getState()\n if (!store.isOnline || _replaying) {\n return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 }\n }\n _replaying = true\n try {\n return await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<ReplayResult> {\n\n const candidates = await idbGetPendingItems()\n const now = Date.now()\n const pending = candidates.filter(\n (item) => !item.nextRetryAt || item.nextRetryAt <= now,\n )\n\n const result: ReplayResult = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 }\n\n const outcomes = await Promise.allSettled(\n pending.map(async (item): Promise<'succeeded' | 'failed' | 'retrying' | 'skipped'> => {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return 'skipped'\n\n store.updateQueueItem(item.id, { status: 'replaying' })\n await idbUpdateQueueItem(item.id, { status: 'replaying' })\n\n try {\n await fn(...(item.args as unknown[]))\n const completedAt = Date.now()\n store.updateQueueItem(item.id, { status: 'succeeded', completedAt })\n await idbUpdateQueueItem(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a delay so the UI can show the success state\n setTimeout(() => {\n store.removeQueueItem(item.id)\n idbRemoveFromQueue(item.id)\n }, 3000)\n return 'succeeded'\n } catch (err) {\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n await idbUpdateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n return 'failed'\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await idbUpdateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n return 'retrying'\n }\n }\n }),\n )\n\n for (const o of outcomes) {\n const outcome = o.status === 'fulfilled' ? o.value : 'failed'\n if (outcome === 'skipped') { result.skipped++ }\n else { result.attempted++; result[outcome]++ }\n }\n\n return result\n}\n\n/** Remove all items from the action queue (IDB + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await idbClearQueue()\n useEidosStore.getState().hydrateQueue([])\n}\n","import { registerServiceWorker } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean\n}\n\nlet _initialized = false\nlet _unsubscribe: (() => void) | null = null\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n if (_initialized) return\n _initialized = true\n\n const swPath = config.swPath ?? '/eidos-sw.js'\n const autoReplay = config.autoReplay ?? true\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue()\n if (persisted.length > 0) {\n useEidosStore.getState().hydrateQueue(persisted)\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath)\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n if (autoReplay) {\n // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;font-weight:bold')\n console.log('SW path :', swPath)\n console.log('Auto-replay:', autoReplay)\n console.log('SW status :', store.swStatus)\n console.groupEnd()\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.()\n _unsubscribe = null\n _initialized = false\n}\n","import { useEffect, type ReactNode } from 'react'\nimport { initEidos, type EidosConfig } from '../runtime'\n\ninterface EidosProviderProps extends EidosConfig {\n children: ReactNode\n}\n\n/**\n * Mount once at the root of your application.\n * Registers the service worker and initialises the Eidos runtime.\n *\n * @example\n * <EidosProvider swPath=\"/eidos-sw.js\">\n * <App />\n * </EidosProvider>\n */\nexport function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps) {\n useEffect(() => {\n initEidos({ swPath, autoReplay })\n // Run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return <>{children}</>\n}\n","import { useEidosStore } from '../store'\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useEidosStore()\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useEidosStore((s) => s.resources[url])\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useEidosStore((s) => s.queue)\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useEidosStore((s) => s.isOnline)\n const swStatus = useEidosStore((s) => s.swStatus)\n const swError = useEidosStore((s) => s.swError)\n return { isOnline, swStatus, swError }\n}\n","export const VERSION = '1.0.9'\n"],"names":["useSyncExternalStore","useEffect"],"mappings":"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"}
|
package/dist/eidos.es.js
CHANGED
|
@@ -494,10 +494,12 @@ function backoffMs(retryCount) {
|
|
|
494
494
|
let _replaying = false;
|
|
495
495
|
async function replayQueue() {
|
|
496
496
|
const store = useEidosStore.getState();
|
|
497
|
-
if (!store.isOnline || _replaying)
|
|
497
|
+
if (!store.isOnline || _replaying) {
|
|
498
|
+
return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
|
|
499
|
+
}
|
|
498
500
|
_replaying = true;
|
|
499
501
|
try {
|
|
500
|
-
await _doReplayQueue(store);
|
|
502
|
+
return await _doReplayQueue(store);
|
|
501
503
|
} finally {
|
|
502
504
|
_replaying = false;
|
|
503
505
|
}
|
|
@@ -508,10 +510,11 @@ async function _doReplayQueue(store) {
|
|
|
508
510
|
const pending = candidates.filter(
|
|
509
511
|
(item) => !item.nextRetryAt || item.nextRetryAt <= now
|
|
510
512
|
);
|
|
511
|
-
|
|
513
|
+
const result = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 };
|
|
514
|
+
const outcomes = await Promise.allSettled(
|
|
512
515
|
pending.map(async (item) => {
|
|
513
516
|
const fn = _actionRegistry.get(item.actionId);
|
|
514
|
-
if (!fn) return;
|
|
517
|
+
if (!fn) return "skipped";
|
|
515
518
|
store.updateQueueItem(item.id, { status: "replaying" });
|
|
516
519
|
await idbUpdateQueueItem(item.id, { status: "replaying" });
|
|
517
520
|
try {
|
|
@@ -523,27 +526,32 @@ async function _doReplayQueue(store) {
|
|
|
523
526
|
store.removeQueueItem(item.id);
|
|
524
527
|
idbRemoveFromQueue(item.id);
|
|
525
528
|
}, 3e3);
|
|
529
|
+
return "succeeded";
|
|
526
530
|
} catch (err) {
|
|
527
531
|
const retryCount = item.retryCount + 1;
|
|
528
532
|
if (retryCount >= item.maxRetries) {
|
|
529
|
-
store.updateQueueItem(item.id, {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
retryCount
|
|
533
|
-
});
|
|
534
|
-
await idbUpdateQueueItem(item.id, {
|
|
535
|
-
status: "failed",
|
|
536
|
-
error: String(err),
|
|
537
|
-
retryCount
|
|
538
|
-
});
|
|
533
|
+
store.updateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
|
|
534
|
+
await idbUpdateQueueItem(item.id, { status: "failed", error: String(err), retryCount });
|
|
535
|
+
return "failed";
|
|
539
536
|
} else {
|
|
540
537
|
const nextRetryAt = Date.now() + backoffMs(retryCount);
|
|
541
538
|
store.updateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
|
|
542
539
|
await idbUpdateQueueItem(item.id, { status: "pending", retryCount, nextRetryAt });
|
|
540
|
+
return "retrying";
|
|
543
541
|
}
|
|
544
542
|
}
|
|
545
543
|
})
|
|
546
544
|
);
|
|
545
|
+
for (const o of outcomes) {
|
|
546
|
+
const outcome = o.status === "fulfilled" ? o.value : "failed";
|
|
547
|
+
if (outcome === "skipped") {
|
|
548
|
+
result.skipped++;
|
|
549
|
+
} else {
|
|
550
|
+
result.attempted++;
|
|
551
|
+
result[outcome]++;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return result;
|
|
547
555
|
}
|
|
548
556
|
async function clearQueue() {
|
|
549
557
|
await idbClearQueue();
|
|
@@ -604,7 +612,7 @@ function useEidosStatus() {
|
|
|
604
612
|
const swError = useEidosStore((s) => s.swError);
|
|
605
613
|
return { isOnline, swStatus, swError };
|
|
606
614
|
}
|
|
607
|
-
const VERSION = "1.0.
|
|
615
|
+
const VERSION = "1.0.9";
|
|
608
616
|
export {
|
|
609
617
|
EidosProvider,
|
|
610
618
|
VERSION,
|
package/dist/eidos.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eidos.es.js","sources":["../src/store.ts","../src/sw-bridge.ts","../src/resource.ts","../src/idb.ts","../src/action.ts","../src/runtime.ts","../src/react/Provider.tsx","../src/react/hooks.ts","../src/version.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react'\nimport type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\n addQueueItem: (item: ActionQueueItem) => void\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void\n removeQueueItem: (id: string) => void\n hydrateQueue: (items: ActionQueueItem[]) => void\n}\n\ntype Listener = () => void\n\nlet _state: EidosStore\nconst _listeners = new Set<Listener>()\n\nfunction _notify() {\n _listeners.forEach((fn) => fn())\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) }\n _notify()\n}\n\n_state = {\n isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n registerResource: (url, entry) =>\n _set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n _set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n _set((s) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\n addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n _set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => _set(() => ({ queue: items })),\n}\n\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\n}\n\n// useSyncExternalStore-based hook — drop-in replacement for zustand's useStore.\n// Supports both bare call (full state) and selector call.\nfunction _useStore(): EidosStore\nfunction _useStore<T>(selector: (state: EidosStore) => T): T\nfunction _useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T)\n return useSyncExternalStore(_subscribe, () => fn(_getState()))\n}\n\n_useStore.getState = _getState\n_useStore.subscribe = _subscribe\n\n// Test/devtools helper — merges partial state, preserves action methods.\n_useStore.setState = (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial\n _state = { ..._state, ...update }\n _notify()\n}\n\nexport const useEidosStore = _useStore\n","import { useEidosStore } from './store'\n\nlet _registration: ServiceWorkerRegistration | null = null\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = []\n\nexport function getSwRegistration() {\n return _registration\n}\n\nexport async function registerServiceWorker(swPath: string): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported')\n return\n }\n\n const store = useEidosStore.getState()\n store.setSwStatus('registering')\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' })\n\n await waitForActivation(_registration)\n\n store.setSwStatus('active')\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage)\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true))\n window.addEventListener('offline', () => store.setOnline(false))\n\n flushPendingMessages()\n } catch (err) {\n store.setSwStatus('error', String(err))\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) { resolve(); return }\n const sw = reg.installing ?? reg.waiting\n if (!sw) { resolve(); return }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000)\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer)\n sw.removeEventListener('statechange', handler)\n resolve()\n }\n })\n })\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active\n if (sw) {\n sw.postMessage(message)\n } else {\n _pendingMessages.push(message)\n }\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as { type: string; url?: string; strategy?: string }\n if (!data?.type) return\n\n const store = useEidosStore.getState()\n const { type, url } = data\n\n if (!url) return\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n break\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n })\n break\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n })\n break\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled })\n useEidosStore.getState().setOnline(!enabled)\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active\n if (!sw) return\n for (const msg of _pendingMessages) sw.postMessage(msg)\n _pendingMessages = []\n}\n","import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ───\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ───────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url)\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n },\n\n json: async () => {\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => ({\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }),\n\n prefetch: async () => {\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n await Promise.all(\n keys\n .filter((r) => r.url === url || new URL(r.url).pathname === url)\n .map((r) => cache.delete(r)),\n )\n }\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n '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.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n","import type { ActionQueueItem } from './types'\n\nconst DB_NAME = 'eidos'\nconst DB_VERSION = 1\nconst QUEUE_STORE = 'action-queue'\n\nlet _db: IDBDatabase | null = null\n\nfunction openDB(): Promise<IDBDatabase> {\n if (_db) return Promise.resolve(_db)\n\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, DB_VERSION)\n\n req.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(QUEUE_STORE)) {\n const store = db.createObjectStore(QUEUE_STORE, { keyPath: 'id' })\n store.createIndex('status', 'status', { unique: false })\n store.createIndex('actionId', 'actionId', { unique: false })\n }\n }\n\n req.onsuccess = () => {\n _db = req.result\n resolve(req.result)\n }\n\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbAddToQueue(item: ActionQueueItem): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).add(item)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbGetQueue(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const req = tx.objectStore(QUEUE_STORE).getAll()\n req.onsuccess = () => resolve(req.result as ActionQueueItem[])\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbUpdateQueueItem(\n id: string,\n update: Partial<ActionQueueItem>,\n): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n const store = tx.objectStore(QUEUE_STORE)\n const get = store.get(id)\n get.onsuccess = () => {\n if (get.result) {\n store.put({ ...get.result, ...update })\n } else if (import.meta.env.DEV) {\n console.warn(`[eidos] idbUpdateQueueItem: item \"${id}\" not found — store/IDB may have diverged`)\n }\n }\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbRemoveFromQueue(id: string): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).delete(id)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\n// Uses the status index to fetch only pending/failed items — avoids a full\n// table scan when the queue has many succeeded/replaying entries.\nexport async function idbGetPendingItems(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const index = tx.objectStore(QUEUE_STORE).index('status')\n const results: ActionQueueItem[] = []\n\n let done = 0\n function finish(err?: DOMException | null) {\n if (err) { reject(err); return }\n if (++done === 2) resolve(results)\n }\n\n const pendingReq = index.openCursor(IDBKeyRange.only('pending'))\n pendingReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n pendingReq.onerror = () => finish(pendingReq.error)\n\n const failedReq = index.openCursor(IDBKeyRange.only('failed'))\n failedReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n failedReq.onerror = () => finish(failedReq.error)\n })\n}\n\nexport async function idbClearQueue(): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).clear()\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n","import { useEidosStore } from './store'\nimport {\n idbAddToQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport type {\n ActionConfig,\n ActionHandle,\n ActionFn,\n ActionQueueItem,\n QueuedResult,\n} from './types'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _actionRegistry = new Map<string, ActionFn<any[], any>>()\n\nfunction uid() {\n return crypto.randomUUID()\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function action<TArgs extends any[], TReturn>(\n fn: ActionFn<TArgs, TReturn>,\n config: ActionConfig,\n): ActionHandle<TArgs, TReturn> {\n // || not ?? — fn.name can be '' (anonymous arrow fn) which ?? treats as a\n // valid value, causing all anonymous actions to share actionId ''.\n const actionId = config.name || fn.name || uid()\n\n if (import.meta.env.DEV && config.reliability === 'neverLose' && !config.name && !fn.name) {\n console.warn(\n `[eidos] action() registered with neverLose but no stable name was found (fn.name=\"${fn.name}\"). Pass config.name so queued items survive a page reload and can be replayed.`,\n )\n }\n\n // Registering here means the function is available for replay after\n // the user refreshes the page (actions are defined at module scope).\n _actionRegistry.set(actionId, fn as ActionFn<unknown[], unknown>)\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n if (config.reliability === 'neverLose') {\n if (!isOnline) {\n return persistAndQueue(actionId, actionId, args, config)\n }\n // Online + neverLose: execute, queue on failure\n try {\n return await fn(...args)\n } catch {\n return persistAndQueue(actionId, actionId, args, config)\n }\n }\n\n // best-effort: execute directly, no queuing\n return fn(...args)\n }\n\n Object.defineProperty(wrapped, 'id', { value: actionId, writable: false })\n Object.defineProperty(wrapped, 'config', { value: config, writable: false })\n\n return wrapped as unknown as ActionHandle<TArgs, TReturn>\n}\n\nfunction isJsonSerializable(value: unknown): boolean {\n try {\n JSON.stringify(value)\n return true\n } catch {\n return false\n }\n}\n\nasync function persistAndQueue(\n actionId: string,\n actionName: string,\n args: unknown[],\n config: ActionConfig,\n): Promise<QueuedResult> {\n if (import.meta.env.DEV && !isJsonSerializable(args)) {\n console.warn(\n `[eidos] action \"${actionName}\" queued with non-JSON-serializable args. These args will be lost after a page reload. Use plain JSON values for neverLose actions.`,\n args,\n )\n }\n\n const id = uid()\n const item: ActionQueueItem = {\n id,\n actionId,\n actionName,\n args,\n queuedAt: Date.now(),\n retryCount: 0,\n maxRetries: config.maxRetries ?? 3,\n status: 'pending',\n }\n\n await idbAddToQueue(item)\n useEidosStore.getState().addQueueItem(item)\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\n// Base delay 2s, doubles per retry, capped at 5 minutes, ±20% jitter\nfunction backoffMs(retryCount: number): number {\n const base = Math.min(2000 * 2 ** retryCount, 300_000)\n return base * (0.8 + Math.random() * 0.4)\n}\n\nlet _replaying = false\n\nexport async function replayQueue(): Promise<void> {\n const store = useEidosStore.getState()\n if (!store.isOnline || _replaying) return\n _replaying = true\n try {\n await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<void> {\n\n const candidates = await idbGetPendingItems()\n const now = Date.now()\n const pending = candidates.filter(\n (item) => !item.nextRetryAt || item.nextRetryAt <= now,\n )\n\n await Promise.allSettled(\n pending.map(async (item) => {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return\n\n store.updateQueueItem(item.id, { status: 'replaying' })\n await idbUpdateQueueItem(item.id, { status: 'replaying' })\n\n try {\n await fn(...(item.args as unknown[]))\n const completedAt = Date.now()\n store.updateQueueItem(item.id, { status: 'succeeded', completedAt })\n await idbUpdateQueueItem(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a delay so the UI can show the success state\n setTimeout(() => {\n store.removeQueueItem(item.id)\n idbRemoveFromQueue(item.id)\n }, 3000)\n } catch (err) {\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, {\n status: 'failed',\n error: String(err),\n retryCount,\n })\n await idbUpdateQueueItem(item.id, {\n status: 'failed',\n error: String(err),\n retryCount,\n })\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await idbUpdateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n }\n }\n }),\n )\n}\n\n/** Remove all items from the action queue (IDB + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await idbClearQueue()\n useEidosStore.getState().hydrateQueue([])\n}\n","import { registerServiceWorker } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean\n}\n\nlet _initialized = false\nlet _unsubscribe: (() => void) | null = null\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n if (_initialized) return\n _initialized = true\n\n const swPath = config.swPath ?? '/eidos-sw.js'\n const autoReplay = config.autoReplay ?? true\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue()\n if (persisted.length > 0) {\n useEidosStore.getState().hydrateQueue(persisted)\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath)\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n if (autoReplay) {\n // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;font-weight:bold')\n console.log('SW path :', swPath)\n console.log('Auto-replay:', autoReplay)\n console.log('SW status :', store.swStatus)\n console.groupEnd()\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.()\n _unsubscribe = null\n _initialized = false\n}\n","import { useEffect, type ReactNode } from 'react'\nimport { initEidos, type EidosConfig } from '../runtime'\n\ninterface EidosProviderProps extends EidosConfig {\n children: ReactNode\n}\n\n/**\n * Mount once at the root of your application.\n * Registers the service worker and initialises the Eidos runtime.\n *\n * @example\n * <EidosProvider swPath=\"/eidos-sw.js\">\n * <App />\n * </EidosProvider>\n */\nexport function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps) {\n useEffect(() => {\n initEidos({ swPath, autoReplay })\n // Run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return <>{children}</>\n}\n","import { useEidosStore } from '../store'\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useEidosStore()\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useEidosStore((s) => s.resources[url])\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useEidosStore((s) => s.queue)\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useEidosStore((s) => s.isOnline)\n const swStatus = useEidosStore((s) => s.swStatus)\n const swError = useEidosStore((s) => s.swError)\n return { isOnline, swStatus, swError }\n}\n","export const VERSION = '1.0.8'\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"eidos.es.js","sources":["../src/store.ts","../src/sw-bridge.ts","../src/resource.ts","../src/idb.ts","../src/action.ts","../src/runtime.ts","../src/react/Provider.tsx","../src/react/hooks.ts","../src/version.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react'\nimport type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\n addQueueItem: (item: ActionQueueItem) => void\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void\n removeQueueItem: (id: string) => void\n hydrateQueue: (items: ActionQueueItem[]) => void\n}\n\ntype Listener = () => void\n\nlet _state: EidosStore\nconst _listeners = new Set<Listener>()\n\nfunction _notify() {\n _listeners.forEach((fn) => fn())\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) }\n _notify()\n}\n\n_state = {\n isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n registerResource: (url, entry) =>\n _set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n _set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n _set((s) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\n addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n _set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => _set(() => ({ queue: items })),\n}\n\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\n}\n\n// useSyncExternalStore-based hook — drop-in replacement for zustand's useStore.\n// Supports both bare call (full state) and selector call.\nfunction _useStore(): EidosStore\nfunction _useStore<T>(selector: (state: EidosStore) => T): T\nfunction _useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T)\n return useSyncExternalStore(_subscribe, () => fn(_getState()))\n}\n\n_useStore.getState = _getState\n_useStore.subscribe = _subscribe\n\n// Test/devtools helper — merges partial state, preserves action methods.\n_useStore.setState = (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial\n _state = { ..._state, ...update }\n _notify()\n}\n\nexport const useEidosStore = _useStore\n","import { useEidosStore } from './store'\n\nlet _registration: ServiceWorkerRegistration | null = null\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = []\n\nexport function getSwRegistration() {\n return _registration\n}\n\nexport async function registerServiceWorker(swPath: string): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported')\n return\n }\n\n const store = useEidosStore.getState()\n store.setSwStatus('registering')\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' })\n\n await waitForActivation(_registration)\n\n store.setSwStatus('active')\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage)\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true))\n window.addEventListener('offline', () => store.setOnline(false))\n\n flushPendingMessages()\n } catch (err) {\n store.setSwStatus('error', String(err))\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) { resolve(); return }\n const sw = reg.installing ?? reg.waiting\n if (!sw) { resolve(); return }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000)\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer)\n sw.removeEventListener('statechange', handler)\n resolve()\n }\n })\n })\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active\n if (sw) {\n sw.postMessage(message)\n } else {\n _pendingMessages.push(message)\n }\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as { type: string; url?: string; strategy?: string }\n if (!data?.type) return\n\n const store = useEidosStore.getState()\n const { type, url } = data\n\n if (!url) return\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n break\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n })\n break\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n })\n break\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled })\n useEidosStore.getState().setOnline(!enabled)\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active\n if (!sw) return\n for (const msg of _pendingMessages) sw.postMessage(msg)\n _pendingMessages = []\n}\n","import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ───\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ───────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url)\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n },\n\n json: async () => {\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => ({\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }),\n\n prefetch: async () => {\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n await Promise.all(\n keys\n .filter((r) => r.url === url || new URL(r.url).pathname === url)\n .map((r) => cache.delete(r)),\n )\n }\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n '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.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n","import type { ActionQueueItem } from './types'\n\nconst DB_NAME = 'eidos'\nconst DB_VERSION = 1\nconst QUEUE_STORE = 'action-queue'\n\nlet _db: IDBDatabase | null = null\n\nfunction openDB(): Promise<IDBDatabase> {\n if (_db) return Promise.resolve(_db)\n\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(DB_NAME, DB_VERSION)\n\n req.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(QUEUE_STORE)) {\n const store = db.createObjectStore(QUEUE_STORE, { keyPath: 'id' })\n store.createIndex('status', 'status', { unique: false })\n store.createIndex('actionId', 'actionId', { unique: false })\n }\n }\n\n req.onsuccess = () => {\n _db = req.result\n resolve(req.result)\n }\n\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbAddToQueue(item: ActionQueueItem): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).add(item)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbGetQueue(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const req = tx.objectStore(QUEUE_STORE).getAll()\n req.onsuccess = () => resolve(req.result as ActionQueueItem[])\n req.onerror = () => reject(req.error)\n })\n}\n\nexport async function idbUpdateQueueItem(\n id: string,\n update: Partial<ActionQueueItem>,\n): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n const store = tx.objectStore(QUEUE_STORE)\n const get = store.get(id)\n get.onsuccess = () => {\n if (get.result) {\n store.put({ ...get.result, ...update })\n } else if (import.meta.env.DEV) {\n console.warn(`[eidos] idbUpdateQueueItem: item \"${id}\" not found — store/IDB may have diverged`)\n }\n }\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\nexport async function idbRemoveFromQueue(id: string): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).delete(id)\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n\n// Uses the status index to fetch only pending/failed items — avoids a full\n// table scan when the queue has many succeeded/replaying entries.\nexport async function idbGetPendingItems(): Promise<ActionQueueItem[]> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readonly')\n const index = tx.objectStore(QUEUE_STORE).index('status')\n const results: ActionQueueItem[] = []\n\n let done = 0\n function finish(err?: DOMException | null) {\n if (err) { reject(err); return }\n if (++done === 2) resolve(results)\n }\n\n const pendingReq = index.openCursor(IDBKeyRange.only('pending'))\n pendingReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n pendingReq.onerror = () => finish(pendingReq.error)\n\n const failedReq = index.openCursor(IDBKeyRange.only('failed'))\n failedReq.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result\n if (cursor) { results.push(cursor.value as ActionQueueItem); cursor.continue() }\n else finish()\n }\n failedReq.onerror = () => finish(failedReq.error)\n })\n}\n\nexport async function idbClearQueue(): Promise<void> {\n const db = await openDB()\n return new Promise((resolve, reject) => {\n const tx = db.transaction(QUEUE_STORE, 'readwrite')\n tx.objectStore(QUEUE_STORE).clear()\n tx.oncomplete = () => resolve()\n tx.onerror = () => reject(tx.error)\n })\n}\n","import { useEidosStore } from './store'\nimport {\n idbAddToQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport type {\n ActionConfig,\n ActionHandle,\n ActionFn,\n ActionQueueItem,\n QueuedResult,\n ReplayResult,\n} from './types'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _actionRegistry = new Map<string, ActionFn<any[], any>>()\n\nfunction uid() {\n return crypto.randomUUID()\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function action<TArgs extends any[], TReturn>(\n fn: ActionFn<TArgs, TReturn>,\n config: ActionConfig,\n): ActionHandle<TArgs, TReturn> {\n // || not ?? — fn.name can be '' (anonymous arrow fn) which ?? treats as a\n // valid value, causing all anonymous actions to share actionId ''.\n const actionId = config.name || fn.name || uid()\n\n if (import.meta.env.DEV && config.reliability === 'neverLose' && !config.name && !fn.name) {\n console.warn(\n `[eidos] action() registered with neverLose but no stable name was found (fn.name=\"${fn.name}\"). Pass config.name so queued items survive a page reload and can be replayed.`,\n )\n }\n\n // Registering here means the function is available for replay after\n // the user refreshes the page (actions are defined at module scope).\n _actionRegistry.set(actionId, fn as ActionFn<unknown[], unknown>)\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n if (config.reliability === 'neverLose') {\n if (!isOnline) {\n return persistAndQueue(actionId, actionId, args, config)\n }\n // Online + neverLose: execute, queue on failure\n try {\n return await fn(...args)\n } catch {\n return persistAndQueue(actionId, actionId, args, config)\n }\n }\n\n // best-effort: execute directly, no queuing\n return fn(...args)\n }\n\n Object.defineProperty(wrapped, 'id', { value: actionId, writable: false })\n Object.defineProperty(wrapped, 'config', { value: config, writable: false })\n\n return wrapped as unknown as ActionHandle<TArgs, TReturn>\n}\n\nfunction isJsonSerializable(value: unknown): boolean {\n try {\n JSON.stringify(value)\n return true\n } catch {\n return false\n }\n}\n\nasync function persistAndQueue(\n actionId: string,\n actionName: string,\n args: unknown[],\n config: ActionConfig,\n): Promise<QueuedResult> {\n if (import.meta.env.DEV && !isJsonSerializable(args)) {\n console.warn(\n `[eidos] action \"${actionName}\" queued with non-JSON-serializable args. These args will be lost after a page reload. Use plain JSON values for neverLose actions.`,\n args,\n )\n }\n\n const id = uid()\n const item: ActionQueueItem = {\n id,\n actionId,\n actionName,\n args,\n queuedAt: Date.now(),\n retryCount: 0,\n maxRetries: config.maxRetries ?? 3,\n status: 'pending',\n }\n\n await idbAddToQueue(item)\n useEidosStore.getState().addQueueItem(item)\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\n// Base delay 2s, doubles per retry, capped at 5 minutes, ±20% jitter\nfunction backoffMs(retryCount: number): number {\n const base = Math.min(2000 * 2 ** retryCount, 300_000)\n return base * (0.8 + Math.random() * 0.4)\n}\n\nlet _replaying = false\n\nexport async function replayQueue(): Promise<ReplayResult> {\n const store = useEidosStore.getState()\n if (!store.isOnline || _replaying) {\n return { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 }\n }\n _replaying = true\n try {\n return await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<ReplayResult> {\n\n const candidates = await idbGetPendingItems()\n const now = Date.now()\n const pending = candidates.filter(\n (item) => !item.nextRetryAt || item.nextRetryAt <= now,\n )\n\n const result: ReplayResult = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0 }\n\n const outcomes = await Promise.allSettled(\n pending.map(async (item): Promise<'succeeded' | 'failed' | 'retrying' | 'skipped'> => {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return 'skipped'\n\n store.updateQueueItem(item.id, { status: 'replaying' })\n await idbUpdateQueueItem(item.id, { status: 'replaying' })\n\n try {\n await fn(...(item.args as unknown[]))\n const completedAt = Date.now()\n store.updateQueueItem(item.id, { status: 'succeeded', completedAt })\n await idbUpdateQueueItem(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a delay so the UI can show the success state\n setTimeout(() => {\n store.removeQueueItem(item.id)\n idbRemoveFromQueue(item.id)\n }, 3000)\n return 'succeeded'\n } catch (err) {\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n await idbUpdateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n return 'failed'\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await idbUpdateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n return 'retrying'\n }\n }\n }),\n )\n\n for (const o of outcomes) {\n const outcome = o.status === 'fulfilled' ? o.value : 'failed'\n if (outcome === 'skipped') { result.skipped++ }\n else { result.attempted++; result[outcome]++ }\n }\n\n return result\n}\n\n/** Remove all items from the action queue (IDB + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await idbClearQueue()\n useEidosStore.getState().hydrateQueue([])\n}\n","import { registerServiceWorker } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean\n}\n\nlet _initialized = false\nlet _unsubscribe: (() => void) | null = null\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n if (_initialized) return\n _initialized = true\n\n const swPath = config.swPath ?? '/eidos-sw.js'\n const autoReplay = config.autoReplay ?? true\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue()\n if (persisted.length > 0) {\n useEidosStore.getState().hydrateQueue(persisted)\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath)\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n if (autoReplay) {\n // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;font-weight:bold')\n console.log('SW path :', swPath)\n console.log('Auto-replay:', autoReplay)\n console.log('SW status :', store.swStatus)\n console.groupEnd()\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.()\n _unsubscribe = null\n _initialized = false\n}\n","import { useEffect, type ReactNode } from 'react'\nimport { initEidos, type EidosConfig } from '../runtime'\n\ninterface EidosProviderProps extends EidosConfig {\n children: ReactNode\n}\n\n/**\n * Mount once at the root of your application.\n * Registers the service worker and initialises the Eidos runtime.\n *\n * @example\n * <EidosProvider swPath=\"/eidos-sw.js\">\n * <App />\n * </EidosProvider>\n */\nexport function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps) {\n useEffect(() => {\n initEidos({ swPath, autoReplay })\n // Run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return <>{children}</>\n}\n","import { useEidosStore } from '../store'\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useEidosStore()\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useEidosStore((s) => s.resources[url])\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useEidosStore((s) => s.queue)\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useEidosStore((s) => s.isOnline)\n const swStatus = useEidosStore((s) => s.swStatus)\n const swError = useEidosStore((s) => s.swError)\n return { isOnline, swStatus, swError }\n}\n","export const VERSION = '1.0.9'\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -110,7 +110,21 @@ export declare interface QueuedResult {
|
|
|
110
110
|
readonly message: string;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
export declare function replayQueue(): Promise<
|
|
113
|
+
export declare function replayQueue(): Promise<ReplayResult>;
|
|
114
|
+
|
|
115
|
+
/** Summary returned by replayQueue(). */
|
|
116
|
+
export declare interface ReplayResult {
|
|
117
|
+
/** Items where the registered function was found and called. */
|
|
118
|
+
attempted: number;
|
|
119
|
+
/** Items that resolved successfully. */
|
|
120
|
+
succeeded: number;
|
|
121
|
+
/** Items that failed and have no retries remaining (status: 'failed'). */
|
|
122
|
+
failed: number;
|
|
123
|
+
/** Items that failed but will be retried later (nextRetryAt set). */
|
|
124
|
+
retrying: number;
|
|
125
|
+
/** Items whose actionId had no registered function — likely not yet imported. */
|
|
126
|
+
skipped: number;
|
|
127
|
+
}
|
|
114
128
|
|
|
115
129
|
export declare function resource<T = unknown>(url: string, config: ResourceConfig): ResourceHandle<T>;
|
|
116
130
|
|
|
@@ -190,7 +204,7 @@ declare namespace _useStore {
|
|
|
190
204
|
var setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
export declare const VERSION = "1.0.
|
|
207
|
+
export declare const VERSION = "1.0.9";
|
|
194
208
|
|
|
195
209
|
export { }
|
|
196
210
|
|