@sweidos/eidos 1.0.31 → 1.0.33
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 +146 -848
- package/dist/action.js +11 -9
- package/dist/action.js.map +1 -1
- package/dist/devtools.js +29 -36
- package/dist/eidos.cjs.js +4 -4
- package/dist/eidos.cjs.js.map +1 -1
- package/dist/idb.js +56 -63
- package/dist/idb.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/resource.js +29 -22
- package/dist/resource.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/store.js +9 -8
- package/dist/store.js.map +1 -1
- package/dist/stores.js +15 -13
- package/dist/stores.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +21 -4
package/dist/action.js
CHANGED
|
@@ -63,7 +63,7 @@ async function p(e, t, r, u) {
|
|
|
63
63
|
message: `"${t}" queued — will execute when online`
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function x(e) {
|
|
67
67
|
if (e instanceof Response) return e.status >= 400 && e.status < 500;
|
|
68
68
|
if (typeof e == "object" && e !== null) {
|
|
69
69
|
const t = e.status;
|
|
@@ -71,7 +71,7 @@ function _(e) {
|
|
|
71
71
|
}
|
|
72
72
|
return !1;
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function C(e) {
|
|
75
75
|
return Math.min(2e3 * 2 ** e, 3e5) * (0.8 + Math.random() * 0.4);
|
|
76
76
|
}
|
|
77
77
|
let c = !1;
|
|
@@ -86,7 +86,7 @@ async function j() {
|
|
|
86
86
|
c = !1;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
async function
|
|
89
|
+
async function _(e, t) {
|
|
90
90
|
var u;
|
|
91
91
|
const r = l.get(e.actionId);
|
|
92
92
|
if (!r) return "skipped";
|
|
@@ -97,7 +97,7 @@ async function A(e, t) {
|
|
|
97
97
|
t.removeQueueItem(e.id), o().remove(e.id);
|
|
98
98
|
}, 3e3), "succeeded";
|
|
99
99
|
} catch (a) {
|
|
100
|
-
if (
|
|
100
|
+
if (x(a)) {
|
|
101
101
|
const i = y.get(e.actionId);
|
|
102
102
|
if (i && i(a, e.args) === "skip")
|
|
103
103
|
return t.removeQueueItem(e.id), await o().remove(e.id), "conflicted";
|
|
@@ -106,12 +106,12 @@ async function A(e, t) {
|
|
|
106
106
|
if (n >= e.maxRetries)
|
|
107
107
|
return t.updateQueueItem(e.id, { status: "failed", error: String(a), retryCount: n }), await o().update(e.id, { status: "failed", error: String(a), retryCount: n }), (u = f.get(e.actionId)) == null || u(...e.args), "failed";
|
|
108
108
|
{
|
|
109
|
-
const i = Date.now() +
|
|
109
|
+
const i = Date.now() + C(n);
|
|
110
110
|
return t.updateQueueItem(e.id, { status: "pending", retryCount: n, nextRetryAt: i }), await o().update(e.id, { status: "pending", retryCount: n, nextRetryAt: i }), "retrying";
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
async function
|
|
114
|
+
async function A(e, t, r) {
|
|
115
115
|
if (e.length === 0) return;
|
|
116
116
|
const u = e.filter((n) => l.has(n.actionId));
|
|
117
117
|
if (r.skipped += e.length - u.length, u.length > 0) {
|
|
@@ -119,17 +119,19 @@ async function C(e, t, r) {
|
|
|
119
119
|
for (const n of u)
|
|
120
120
|
o().update(n.id, { status: "replaying" });
|
|
121
121
|
}
|
|
122
|
-
const a = await Promise.allSettled(u.map((n) =>
|
|
122
|
+
const a = await Promise.allSettled(u.map((n) => _(n, t)));
|
|
123
123
|
for (const n of a) {
|
|
124
124
|
const i = n.status === "fulfilled" ? n.value : "failed";
|
|
125
125
|
i === "skipped" ? r.skipped++ : i === "conflicted" ? r.conflicted++ : (r.attempted++, r[i]++);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
async function M(e) {
|
|
129
|
-
const t = await o().getPending(), r = Date.now(), u = t.filter(
|
|
129
|
+
const t = await o().getPending(), r = Date.now(), u = t.filter(
|
|
130
|
+
(n) => n.retryCount < n.maxRetries && (!n.nextRetryAt || n.nextRetryAt <= r)
|
|
131
|
+
), a = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 };
|
|
130
132
|
for (const n of ["high", "normal", "low"]) {
|
|
131
133
|
const i = u.filter((s) => (s.priority ?? "normal") === n);
|
|
132
|
-
await
|
|
134
|
+
await A(i, e, a);
|
|
133
135
|
}
|
|
134
136
|
return a;
|
|
135
137
|
}
|
package/dist/action.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.js","sources":["../src/action.ts"],"sourcesContent":["import { useEidosStore } from './store'\nimport { getSwRegistration } from './sw-bridge'\nimport {\n idbAddToQueue,\n idbGetQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport { _getQueueStorage } from './queue-storage'\nimport type { QueueStorage } from './queue-storage'\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// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _rollbackRegistry = new Map<string, (...args: any[]) => void>()\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _conflictRegistry = new Map<string, (error: unknown, args: any[]) => 'retry' | 'skip'>()\n\n// IDB fallback — used when no custom storage is set (default browser behavior).\nconst _idbFallback: QueueStorage = {\n add: (item) => idbAddToQueue(item),\n getAll: () => idbGetQueue(),\n getPending: () => idbGetPendingItems(),\n update: (id, patch) => idbUpdateQueueItem(id, patch),\n remove: (id) => idbRemoveFromQueue(id),\n clear: () => idbClearQueue(),\n}\n\nfunction qs(): QueueStorage {\n return _getQueueStorage() ?? _idbFallback\n}\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 if (config.onRollback) {\n _rollbackRegistry.set(actionId, config.onRollback)\n }\n\n if (config.onConflict) {\n _conflictRegistry.set(actionId, config.onConflict)\n }\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n config.onOptimistic?.(...args)\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, rollback on failure\n try {\n return await fn(...args)\n } catch (err) {\n config.onRollback?.(...args)\n throw err\n }\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 priority: config.priority ?? 'normal',\n }\n\n await qs().add(item)\n useEidosStore.getState().addQueueItem(item)\n\n // Register Background Sync tag so the browser can wake up open clients\n // when connectivity returns, even if the user navigated away briefly.\n // Graceful no-op when Background Sync is unsupported.\n try {\n const reg = getSwRegistration()\n if (reg && 'sync' in reg) {\n await (reg as unknown as { sync: { register(tag: string): Promise<void> } }).sync.register('eidos-queue-replay')\n }\n } catch {\n // Background Sync not available — online-event replay remains the fallback\n }\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\nfunction isClientError(err: unknown): boolean {\n if (err instanceof Response) return err.status >= 400 && err.status < 500\n if (typeof err === 'object' && err !== null) {\n const s = (err as Record<string, unknown>).status\n if (typeof s === 'number') return s >= 400 && s < 500\n }\n return false\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, conflicted: 0 }\n }\n _replaying = true\n try {\n return await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\ntype ItemOutcome = 'succeeded' | 'failed' | 'retrying' | 'skipped' | 'conflicted'\n\nasync function _replayItem(\n item: ActionQueueItem,\n store: ReturnType<typeof useEidosStore.getState>,\n): Promise<ItemOutcome> {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return 'skipped'\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 qs().update(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a short delay so UI can show the success state briefly\n setTimeout(() => {\n store.removeQueueItem(item.id)\n qs().remove(item.id)\n }, 3000)\n return 'succeeded'\n } catch (err) {\n // 4xx: give onConflict a chance to decide before normal retry/fail logic\n if (isClientError(err)) {\n const onConflict = _conflictRegistry.get(item.actionId)\n if (onConflict) {\n const resolution = onConflict(err, item.args as unknown[])\n if (resolution === 'skip') {\n store.removeQueueItem(item.id)\n await qs().remove(item.id)\n return 'conflicted'\n }\n // 'retry' falls through to normal retry/fail logic below\n }\n }\n\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n await qs().update(item.id, { status: 'failed', error: String(err), retryCount })\n _rollbackRegistry.get(item.actionId)?.(...(item.args as unknown[]))\n return 'failed'\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await qs().update(item.id, { status: 'pending', retryCount, nextRetryAt })\n return 'retrying'\n }\n }\n}\n\nasync function _replayTier(\n items: ActionQueueItem[],\n store: ReturnType<typeof useEidosStore.getState>,\n result: ReplayResult,\n): Promise<void> {\n if (items.length === 0) return\n\n // Batch 'replaying' status update — N items → 1 store notify.\n // IDB write is fire-and-forget: on reload items stay 'pending', safe to re-replay.\n const replayable = items.filter((item) => _actionRegistry.has(item.actionId))\n result.skipped += items.length - replayable.length\n\n if (replayable.length > 0) {\n store.batchUpdateQueueItems(replayable.map((item) => ({ id: item.id, update: { status: 'replaying' } })))\n for (const item of replayable) {\n qs().update(item.id, { status: 'replaying' })\n }\n }\n\n const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)))\n\n for (const o of outcomes) {\n const outcome = o.status === 'fulfilled' ? o.value : 'failed'\n if (outcome === 'skipped') { result.skipped++ }\n else if (outcome === 'conflicted') { result.conflicted++ }\n else { result.attempted++; result[outcome]++ }\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<ReplayResult> {\n const candidates = await qs().getPending()\n const now = Date.now()\n const pending = candidates.filter((item) => !item.nextRetryAt || item.nextRetryAt <= now)\n\n const result: ReplayResult = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 }\n\n // Process tiers sequentially: high items complete before normal, normal before low.\n // Within each tier items run in parallel via Promise.allSettled.\n for (const tier of ['high', 'normal', 'low'] as const) {\n const tierItems = pending.filter((item) => (item.priority ?? 'normal') === tier)\n await _replayTier(tierItems, store, result)\n }\n\n return result\n}\n\n/** Remove all items from the action queue (storage + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await qs().clear()\n useEidosStore.getState().hydrateQueue([])\n}\n"],"names":["_actionRegistry","_rollbackRegistry","_conflictRegistry","_idbFallback","item","idbAddToQueue","idbGetQueue","idbGetPendingItems","id","patch","idbUpdateQueueItem","idbRemoveFromQueue","idbClearQueue","qs","_getQueueStorage","uid","action","fn","config","actionId","wrapped","args","isOnline","useEidosStore","_a","persistAndQueue","err","_b","actionName","reg","getSwRegistration","isClientError","s","backoffMs","retryCount","_replaying","replayQueue","store","_doReplayQueue","_replayItem","completedAt","onConflict","nextRetryAt","_replayTier","items","result","replayable","outcomes","o","outcome","candidates","now","pending","tier","tierItems","clearQueue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"action.js","sources":["../src/action.ts"],"sourcesContent":["import { useEidosStore } from './store'\nimport { getSwRegistration } from './sw-bridge'\nimport {\n idbAddToQueue,\n idbGetQueue,\n idbGetPendingItems,\n idbUpdateQueueItem,\n idbRemoveFromQueue,\n idbClearQueue,\n} from './idb'\nimport { _getQueueStorage } from './queue-storage'\nimport type { QueueStorage } from './queue-storage'\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// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _rollbackRegistry = new Map<string, (...args: any[]) => void>()\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _conflictRegistry = new Map<string, (error: unknown, args: any[]) => 'retry' | 'skip'>()\n\n// IDB fallback — used when no custom storage is set (default browser behavior).\nconst _idbFallback: QueueStorage = {\n add: (item) => idbAddToQueue(item),\n getAll: () => idbGetQueue(),\n getPending: () => idbGetPendingItems(),\n update: (id, patch) => idbUpdateQueueItem(id, patch),\n remove: (id) => idbRemoveFromQueue(id),\n clear: () => idbClearQueue(),\n}\n\nfunction qs(): QueueStorage {\n return _getQueueStorage() ?? _idbFallback\n}\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 if (config.onRollback) {\n _rollbackRegistry.set(actionId, config.onRollback)\n }\n\n if (config.onConflict) {\n _conflictRegistry.set(actionId, config.onConflict)\n }\n\n const wrapped = async (...args: TArgs): Promise<TReturn | QueuedResult> => {\n const { isOnline } = useEidosStore.getState()\n\n config.onOptimistic?.(...args)\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, rollback on failure\n try {\n return await fn(...args)\n } catch (err) {\n config.onRollback?.(...args)\n throw err\n }\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 priority: config.priority ?? 'normal',\n }\n\n await qs().add(item)\n useEidosStore.getState().addQueueItem(item)\n\n // Register Background Sync tag so the browser can wake up open clients\n // when connectivity returns, even if the user navigated away briefly.\n // Graceful no-op when Background Sync is unsupported.\n try {\n const reg = getSwRegistration()\n if (reg && 'sync' in reg) {\n await (reg as unknown as { sync: { register(tag: string): Promise<void> } }).sync.register('eidos-queue-replay')\n }\n } catch {\n // Background Sync not available — online-event replay remains the fallback\n }\n\n return {\n queued: true,\n id,\n message: `\"${actionName}\" queued — will execute when online`,\n }\n}\n\nfunction isClientError(err: unknown): boolean {\n if (err instanceof Response) return err.status >= 400 && err.status < 500\n if (typeof err === 'object' && err !== null) {\n const s = (err as Record<string, unknown>).status\n if (typeof s === 'number') return s >= 400 && s < 500\n }\n return false\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, conflicted: 0 }\n }\n _replaying = true\n try {\n return await _doReplayQueue(store)\n } finally {\n _replaying = false\n }\n}\n\ntype ItemOutcome = 'succeeded' | 'failed' | 'retrying' | 'skipped' | 'conflicted'\n\nasync function _replayItem(\n item: ActionQueueItem,\n store: ReturnType<typeof useEidosStore.getState>,\n): Promise<ItemOutcome> {\n const fn = _actionRegistry.get(item.actionId)\n if (!fn) return 'skipped'\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 qs().update(item.id, { status: 'succeeded', completedAt })\n\n // Remove from queue after a short delay so UI can show the success state briefly\n setTimeout(() => {\n store.removeQueueItem(item.id)\n qs().remove(item.id)\n }, 3000)\n return 'succeeded'\n } catch (err) {\n // 4xx: give onConflict a chance to decide before normal retry/fail logic\n if (isClientError(err)) {\n const onConflict = _conflictRegistry.get(item.actionId)\n if (onConflict) {\n const resolution = onConflict(err, item.args as unknown[])\n if (resolution === 'skip') {\n store.removeQueueItem(item.id)\n await qs().remove(item.id)\n return 'conflicted'\n }\n // 'retry' falls through to normal retry/fail logic below\n }\n }\n\n const retryCount = item.retryCount + 1\n if (retryCount >= item.maxRetries) {\n store.updateQueueItem(item.id, { status: 'failed', error: String(err), retryCount })\n await qs().update(item.id, { status: 'failed', error: String(err), retryCount })\n _rollbackRegistry.get(item.actionId)?.(...(item.args as unknown[]))\n return 'failed'\n } else {\n const nextRetryAt = Date.now() + backoffMs(retryCount)\n store.updateQueueItem(item.id, { status: 'pending', retryCount, nextRetryAt })\n await qs().update(item.id, { status: 'pending', retryCount, nextRetryAt })\n return 'retrying'\n }\n }\n}\n\nasync function _replayTier(\n items: ActionQueueItem[],\n store: ReturnType<typeof useEidosStore.getState>,\n result: ReplayResult,\n): Promise<void> {\n if (items.length === 0) return\n\n // Batch 'replaying' status update — N items → 1 store notify.\n // IDB write is fire-and-forget: on reload items stay 'pending', safe to re-replay.\n const replayable = items.filter((item) => _actionRegistry.has(item.actionId))\n result.skipped += items.length - replayable.length\n\n if (replayable.length > 0) {\n store.batchUpdateQueueItems(replayable.map((item) => ({ id: item.id, update: { status: 'replaying' } })))\n for (const item of replayable) {\n qs().update(item.id, { status: 'replaying' })\n }\n }\n\n const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)))\n\n for (const o of outcomes) {\n const outcome = o.status === 'fulfilled' ? o.value : 'failed'\n if (outcome === 'skipped') { result.skipped++ }\n else if (outcome === 'conflicted') { result.conflicted++ }\n else { result.attempted++; result[outcome]++ }\n }\n}\n\nasync function _doReplayQueue(store: ReturnType<typeof useEidosStore.getState>): Promise<ReplayResult> {\n const candidates = await qs().getPending()\n const now = Date.now()\n // getPending() includes 'failed' items (for UI/queue-stats visibility), but\n // items that already exhausted maxRetries must not be auto-replayed again —\n // otherwise every reconnect re-executes the action and re-fires onRollback.\n // Those items stay 'failed' until the host app explicitly clears/re-queues them.\n const pending = candidates.filter(\n (item) => item.retryCount < item.maxRetries && (!item.nextRetryAt || item.nextRetryAt <= now),\n )\n\n const result: ReplayResult = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 }\n\n // Process tiers sequentially: high items complete before normal, normal before low.\n // Within each tier items run in parallel via Promise.allSettled.\n for (const tier of ['high', 'normal', 'low'] as const) {\n const tierItems = pending.filter((item) => (item.priority ?? 'normal') === tier)\n await _replayTier(tierItems, store, result)\n }\n\n return result\n}\n\n/** Remove all items from the action queue (storage + in-memory store). */\nexport async function clearQueue(): Promise<void> {\n await qs().clear()\n useEidosStore.getState().hydrateQueue([])\n}\n"],"names":["_actionRegistry","_rollbackRegistry","_conflictRegistry","_idbFallback","item","idbAddToQueue","idbGetQueue","idbGetPendingItems","id","patch","idbUpdateQueueItem","idbRemoveFromQueue","idbClearQueue","qs","_getQueueStorage","uid","action","fn","config","actionId","wrapped","args","isOnline","useEidosStore","_a","persistAndQueue","err","_b","actionName","reg","getSwRegistration","isClientError","s","backoffMs","retryCount","_replaying","replayQueue","store","_doReplayQueue","_replayItem","completedAt","onConflict","nextRetryAt","_replayTier","items","result","replayable","outcomes","o","outcome","candidates","now","pending","tier","tierItems","clearQueue"],"mappings":"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"}
|
package/dist/devtools.js
CHANGED
|
@@ -26,10 +26,11 @@ _state = {
|
|
|
26
26
|
[url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url]
|
|
27
27
|
}
|
|
28
28
|
})),
|
|
29
|
-
unregisterResource: (url) => _set((s) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
unregisterResource: (url) => _set((s) => ({
|
|
30
|
+
resources: Object.fromEntries(
|
|
31
|
+
Object.entries(s.resources).filter(([k]) => k !== url)
|
|
32
|
+
)
|
|
33
|
+
})),
|
|
33
34
|
addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),
|
|
34
35
|
updateQueueItem: (id, update) => _set((s) => ({
|
|
35
36
|
queue: s.queue.map((item) => item.id === id ? { ...item, ...update } : item)
|
|
@@ -164,37 +165,27 @@ async function idbRemoveFromQueue(id) {
|
|
|
164
165
|
}
|
|
165
166
|
async function idbGetPendingItems() {
|
|
166
167
|
const db = await openDB();
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const failedReq = index.openCursor(IDBKeyRange.only("failed"));
|
|
189
|
-
failedReq.onsuccess = (e) => {
|
|
190
|
-
const cursor = e.target.result;
|
|
191
|
-
if (cursor) {
|
|
192
|
-
results.push(cursor.value);
|
|
193
|
-
cursor.continue();
|
|
194
|
-
} else finish();
|
|
195
|
-
};
|
|
196
|
-
failedReq.onerror = () => finish(failedReq.error);
|
|
197
|
-
});
|
|
168
|
+
function cursorToArray(status) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const tx = db.transaction(QUEUE_STORE, "readonly");
|
|
171
|
+
const index = tx.objectStore(QUEUE_STORE).index("status");
|
|
172
|
+
const items = [];
|
|
173
|
+
const req = index.openCursor(IDBKeyRange.only(status));
|
|
174
|
+
req.onsuccess = (e) => {
|
|
175
|
+
const cursor = e.target.result;
|
|
176
|
+
if (cursor) {
|
|
177
|
+
items.push(cursor.value);
|
|
178
|
+
cursor.continue();
|
|
179
|
+
} else resolve(items);
|
|
180
|
+
};
|
|
181
|
+
req.onerror = () => reject(req.error);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const [pending, failed] = await Promise.all([
|
|
185
|
+
cursorToArray("pending"),
|
|
186
|
+
cursorToArray("failed")
|
|
187
|
+
]);
|
|
188
|
+
return [...pending, ...failed];
|
|
198
189
|
}
|
|
199
190
|
async function idbClearQueue() {
|
|
200
191
|
const db = await openDB();
|
|
@@ -310,7 +301,9 @@ async function _replayTier(items, store, result) {
|
|
|
310
301
|
async function _doReplayQueue(store) {
|
|
311
302
|
const candidates = await qs().getPending();
|
|
312
303
|
const now = Date.now();
|
|
313
|
-
const pending = candidates.filter(
|
|
304
|
+
const pending = candidates.filter(
|
|
305
|
+
(item) => item.retryCount < item.maxRetries && (!item.nextRetryAt || item.nextRetryAt <= now)
|
|
306
|
+
);
|
|
314
307
|
const result = { attempted: 0, succeeded: 0, failed: 0, retrying: 0, skipped: 0, conflicted: 0 };
|
|
315
308
|
for (const tier of ["high", "normal", "low"]) {
|
|
316
309
|
const tierItems = pending.filter((item) => (item.priority ?? "normal") === tier);
|
package/dist/eidos.cjs.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const U=require("react/jsx-runtime"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const U=require("react/jsx-runtime"),k=require("react");let S;const N=new Set;function H(){N.forEach(e=>e())}function p(e){S={...S,...e(S)},H()}S={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>p(()=>({isOnline:e})),setSwStatus:(e,t)=>p(()=>({swStatus:e,swError:t})),registerResource:(e,t)=>p(s=>({resources:{...s.resources,[e]:t}})),updateResource:(e,t)=>p(s=>({resources:{...s.resources,[e]:s.resources[e]?{...s.resources[e],...t}:s.resources[e]}})),unregisterResource:e=>p(t=>({resources:Object.fromEntries(Object.entries(t.resources).filter(([s])=>s!==e))})),addQueueItem:e=>p(t=>({queue:[...t.queue,e]})),updateQueueItem:(e,t)=>p(s=>({queue:s.queue.map(n=>n.id===e?{...n,...t}:n)})),batchUpdateQueueItems:e=>p(t=>{const s=new Map(e.map(n=>[n.id,n.update]));return{queue:t.queue.map(n=>{const r=s.get(n.id);return r?{...n,...r}:n})}}),removeQueueItem:e=>p(t=>({queue:t.queue.filter(s=>s.id!==e)})),hydrateQueue:e=>p(()=>({queue:e}))};function z(){return S}function X(e){return N.add(e),()=>{N.delete(e)}}const o={getState:z,subscribe:X,setState:e=>{const t=typeof e=="function"?e(S):e;S={...S,...t},H()}};let h=null,T=[];function Z(){return h}async function ee(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){o.getState().setSwStatus("unsupported");return}const t=o.getState();t.setSwStatus("registering");try{h=await navigator.serviceWorker.register(e,{scope:"/"}),await te(h),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",re),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),ie()}catch(s){t.setSwStatus("error",String(s))}}function te(e){return new Promise(t=>{if(e.active){t();return}const s=e.installing??e.waiting;if(!s){t();return}const n=setTimeout(t,1e4);s.addEventListener("statechange",function r(){s.state==="activated"&&(clearTimeout(n),s.removeEventListener("statechange",r),t())})})}function A(e){const t=h==null?void 0:h.active;t?t.postMessage(e):T.push(e)}let I=null;function se(e){I=e}function ne(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&h!==null&&"sync"in h}catch{return!1}}function re(e){const t=e.data;if(!(t!=null&&t.type))return;const s=o.getState(),{type:n,url:r}=t;if(n==="EIDOS_BACKGROUND_SYNC"){I==null||I();return}if(r)switch(n){case"EIDOS_CACHE_HIT":{const a=s.resources[r];s.updateResource(r,{status:"fresh",lastEvent:"cache-hit",cacheHits:((a==null?void 0:a.cacheHits)??0)+1});break}case"EIDOS_CACHE_UPDATED":{s.updateResource(r,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break}case"EIDOS_NETWORK_ERROR":{s.updateResource(r,{status:"error",lastEvent:"network-error"});break}}}function ae(e){A({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),o.getState().setOnline(!e)}function ie(){const e=h==null?void 0:h.active;if(e){for(const t of T)e.postMessage(t);T=[]}}const O=new Map,Q=new Map;let x=null;function oe(e){x=e}function E(e){return e.includes("*")||/:[^/]+/.test(e)}function ce(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function q(e,t){return new Error(`[eidos] resource('${e}') is a URL pattern — ${t}() is not supported on pattern handles. The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`)}function ue(e,t){if(O.has(e))return O.get(e);const s=le(e,t),n=E(e)?ce(e):void 0,r={url:e,config:t,strategy:s,status:"idle",cacheHits:0,cacheMisses:0};o.getState().registerResource(e,r),A({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:s.swStrategy,cacheName:s.cacheName,...n!==void 0&&{pattern:n}});const a={url:e,config:t,strategy:s,fetch:async()=>{if(E(e))throw q(e,"fetch");const i=Q.get(e);if(i)return i.then(u=>u.clone());const c=de(e,t,s);return Q.set(e,c),c.finally(()=>Q.delete(e)).catch(()=>{}),c.then(u=>u.clone())},json:async()=>{if(E(e))throw q(e,"json");return(await a.fetch()).json()},query:()=>{if(E(e))throw q(e,"query");return{queryKey:["eidos",e],queryFn:()=>a.json()}},prefetch:async()=>{if(E(e))throw q(e,"prefetch");await a.fetch()},invalidate:async()=>{A({type:"EIDOS_CLEAR_CACHE",url:e});const i=await caches.open(s.cacheName).catch(()=>null);if(i){const c=await i.keys(),u=n?new RegExp(n):null,l=e.startsWith("http");await Promise.all(c.filter(f=>{const g=f.url,m=new URL(g).pathname;return u?u.test(l?g:m):l?g===e:g===e||m===e}).map(f=>i.delete(f)))}E(e)||o.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),x==null||x(["eidos",e])},unregister:()=>{O.delete(e),A({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),o.getState().unregisterResource(e)}};return O.set(e,a),a}async function de(e,t,s){const n=o.getState();n.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const r=await caches.open(s.cacheName).catch(()=>null);try{if(s.swStrategy!=="network-first"){const c=r?await r.match(e).catch(()=>null):null,u=o.getState().resources[e],l=t.maxAge!==void 0&&(u==null?void 0:u.cachedAt)!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(c&&!l)return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:((u==null?void 0:u.cacheHits)??0)+1}),s.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async g=>{g.ok&&r&&(await r.put(e,g.clone()),o.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),c;const f=o.getState().resources[e];n.updateResource(e,{cacheMisses:((f==null?void 0:f.cacheMisses)??0)+1})}const a=await fetch(e);if(a.ok)return r&&await r.put(e,a.clone()),n.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),a;n.updateResource(e,{status:a.status===503?"offline":"error"});const i=a.headers.get("X-Eidos-Offline")==="true";throw new Error(i?`offline: no cached response for ${e}`:`${a.status} ${a.statusText}`)}catch(a){const i=r?await r.match(e).catch(()=>null):null;if(i){const c=o.getState().resources[e];return n.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:((c==null?void 0:c.cacheHits)??0)+1}),i}throw n.updateResource(e,{status:"error"}),a}}function le(e,t){const s=t.strategy;return t.offline?W(s??"stale-while-revalidate",e,t.cacheName):W(s??"network-first",e,t.cacheName)}const fe={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},he={"stale-while-revalidate":{reasoning:"offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.",behavior:["Cache hit → return immediately, kick off background revalidation","Cache miss → fetch from network, cache the response, return it","Offline → return cached version if available, 503 if not","Reconnect → next request triggers a background refresh"],equivalentCode:`// Workbox equivalent
|
|
2
2
|
new StaleWhileRevalidate({
|
|
3
3
|
cacheName: 'eidos-resources-v1',
|
|
4
4
|
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
5
|
-
})`},"cache-first":{
|
|
5
|
+
})`},"cache-first":{reasoning:"cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.",behavior:["Cache hit → return immediately, no network request made","Cache miss → fetch from network, cache the response, return it","Offline → return cached version, 503 if cache is empty","Cache never expires unless explicitly invalidated"],equivalentCode:`// Workbox equivalent
|
|
6
6
|
new CacheFirst({
|
|
7
7
|
cacheName: 'eidos-resources-v1',
|
|
8
8
|
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
9
|
-
})`},"network-first":{
|
|
9
|
+
})`},"network-first":{reasoning:"network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.",behavior:["Always try network first","Network success → update cache, return fresh response","Network failure → fall back to cached version","Offline with empty cache → return 503 error response"],equivalentCode:`// Workbox equivalent
|
|
10
10
|
new NetworkFirst({
|
|
11
11
|
cacheName: 'eidos-resources-v1',
|
|
12
12
|
networkTimeoutSeconds: 3,
|
|
13
|
-
})`}};function W(e,t,s){return{
|
|
13
|
+
})`}};function W(e,t,s){const n=he[e];return{name:fe[e],swStrategy:e,cacheName:s??"eidos-resources-v1",reasoning:n.reasoning,behavior:n.behavior,equivalentCode:""}}async function pe(e){const t=await Promise.allSettled(e.map(n=>n.prefetch())),s=t.filter(n=>n.status==="rejected").map(n=>n.reason);return{warmed:t.filter(n=>n.status==="fulfilled").length,failed:s.length,errors:s}}const we="eidos",ge=1,d="action-queue";let C=null;function v(){return C?Promise.resolve(C):new Promise((e,t)=>{const s=indexedDB.open(we,ge);s.onupgradeneeded=n=>{const r=n.target.result;if(!r.objectStoreNames.contains(d)){const a=r.createObjectStore(d,{keyPath:"id"});a.createIndex("status","status",{unique:!1}),a.createIndex("actionId","actionId",{unique:!1})}},s.onsuccess=()=>{C=s.result,e(s.result)},s.onerror=()=>t(s.error)})}async function ye(e){const t=await v();return new Promise((s,n)=>{const r=t.transaction(d,"readwrite");r.objectStore(d).add(e),r.oncomplete=()=>s(),r.onerror=()=>n(r.error)})}async function L(){const e=await v();return new Promise((t,s)=>{const r=e.transaction(d,"readonly").objectStore(d).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>s(r.error)})}async function Se(e,t){const s=await v();return new Promise((n,r)=>{const a=s.transaction(d,"readwrite"),i=a.objectStore(d),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},a.oncomplete=()=>n(),a.onerror=()=>r(a.error)})}async function me(e){const t=await v();return new Promise((s,n)=>{const r=t.transaction(d,"readwrite");r.objectStore(d).delete(e),r.oncomplete=()=>s(),r.onerror=()=>n(r.error)})}async function Ee(){const e=await v();function t(r){return new Promise((a,i)=>{const u=e.transaction(d,"readonly").objectStore(d).index("status"),l=[],f=u.openCursor(IDBKeyRange.only(r));f.onsuccess=g=>{const m=g.target.result;m?(l.push(m.value),m.continue()):a(l)},f.onerror=()=>i(f.error)})}const[s,n]=await Promise.all([t("pending"),t("failed")]);return[...s,...n]}async function ve(){const e=await v();return new Promise((t,s)=>{const n=e.transaction(d,"readwrite");n.objectStore(d).clear(),n.oncomplete=()=>t(),n.onerror=()=>s(n.error)})}let B=null;function be(e){B=e}function F(){return B}const M=new Map,G=new Map,K=new Map,Re={add:e=>ye(e),getAll:()=>L(),getPending:()=>Ee(),update:(e,t)=>Se(e,t),remove:e=>me(e),clear:()=>ve()};function y(){return F()??Re}function V(){return crypto.randomUUID()}function ke(e,t){const s=t.name||e.name||V();M.set(s,e),t.onRollback&&G.set(s,t.onRollback),t.onConflict&&K.set(s,t.onConflict);const n=async(...r)=>{var i,c;const{isOnline:a}=o.getState();if((i=t.onOptimistic)==null||i.call(t,...r),t.reliability==="neverLose"){if(!a)return $(s,s,r,t);try{return await e(...r)}catch{return $(s,s,r,t)}}try{return await e(...r)}catch(u){throw(c=t.onRollback)==null||c.call(t,...r),u}};return Object.defineProperty(n,"id",{value:s,writable:!1}),Object.defineProperty(n,"config",{value:t,writable:!1}),n}async function $(e,t,s,n){const r=V(),a={id:r,actionId:e,actionName:t,args:s,queuedAt:Date.now(),retryCount:0,maxRetries:n.maxRetries??3,status:"pending",priority:n.priority??"normal"};await y().add(a),o.getState().addQueueItem(a);try{const i=Z();i&&"sync"in i&&await i.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:r,message:`"${t}" queued — will execute when online`}}function Oe(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function qe(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}let P=!1;async function _(){const e=o.getState();if(!e.isOnline||P)return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};P=!0;try{return await xe(e)}finally{P=!1}}async function Ae(e,t){var n;const s=M.get(e.actionId);if(!s)return"skipped";try{await s(...e.args);const r=Date.now();return t.updateQueueItem(e.id,{status:"succeeded",completedAt:r}),await y().update(e.id,{status:"succeeded",completedAt:r}),setTimeout(()=>{t.removeQueueItem(e.id),y().remove(e.id)},3e3),"succeeded"}catch(r){if(Oe(r)){const i=K.get(e.actionId);if(i&&i(r,e.args)==="skip")return t.removeQueueItem(e.id),await y().remove(e.id),"conflicted"}const a=e.retryCount+1;if(a>=e.maxRetries)return t.updateQueueItem(e.id,{status:"failed",error:String(r),retryCount:a}),await y().update(e.id,{status:"failed",error:String(r),retryCount:a}),(n=G.get(e.actionId))==null||n(...e.args),"failed";{const i=Date.now()+qe(a);return t.updateQueueItem(e.id,{status:"pending",retryCount:a,nextRetryAt:i}),await y().update(e.id,{status:"pending",retryCount:a,nextRetryAt:i}),"retrying"}}}async function Ie(e,t,s){if(e.length===0)return;const n=e.filter(a=>M.has(a.actionId));if(s.skipped+=e.length-n.length,n.length>0){t.batchUpdateQueueItems(n.map(a=>({id:a.id,update:{status:"replaying"}})));for(const a of n)y().update(a.id,{status:"replaying"})}const r=await Promise.allSettled(n.map(a=>Ae(a,t)));for(const a of r){const i=a.status==="fulfilled"?a.value:"failed";i==="skipped"?s.skipped++:i==="conflicted"?s.conflicted++:(s.attempted++,s[i]++)}}async function xe(e){const t=await y().getPending(),s=Date.now(),n=t.filter(a=>a.retryCount<a.maxRetries&&(!a.nextRetryAt||a.nextRetryAt<=s)),r={attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};for(const a of["high","normal","low"]){const i=n.filter(c=>(c.priority??"normal")===a);await Ie(i,e,r)}return r}async function _e(){await y().clear(),o.getState().hydrateQueue([])}let j=!1,R=null;async function Y(e={}){if(typeof window>"u"||j)return;j=!0;const t=e.swPath??"/eidos-sw.js",s=e.autoReplay??!0;try{const n=await L();n.length>0&&o.getState().hydrateQueue(n)}catch{}try{await ee(t)}catch{}if(se(()=>{o.getState().isOnline&&setTimeout(_,200)}),s){let n=o.getState().isOnline;R=o.subscribe(()=>{const{isOnline:i}=o.getState(),c=i&&!n;n=i,c&&setTimeout(_,600)});const r=o.getState(),a=r.queue.some(i=>i.status==="pending"||i.status==="failed");r.isOnline&&a&&setTimeout(_,1200)}}function Qe(){R==null||R(),R=null,j=!1}const D="@eidos:queue";class Ce{constructor(t){this.storage=t}async readAll(){try{const t=await this.storage.getItem(D);return t?JSON.parse(t):[]}catch{return[]}}async writeAll(t){await this.storage.setItem(D,JSON.stringify(t))}async add(t){const s=await this.readAll();s.push(t),await this.writeAll(s)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(s=>s.status==="pending"||s.status==="failed")}async update(t,s){const n=await this.readAll(),r=n.findIndex(a=>a.id===t);r!==-1&&(n[r]={...n[r],...s}),await this.writeAll(n)}async remove(t){const s=await this.readAll();await this.writeAll(s.filter(n=>n.id!==t))}async clear(){await this.storage.removeItem(D)}}function Pe({children:e,swPath:t,autoReplay:s}){return k.useEffect(()=>{Y({swPath:t,autoReplay:s})},[]),U.jsx(U.Fragment,{children:e})}function w(e){const t=e??(s=>s);return k.useSyncExternalStore(o.subscribe,()=>t(o.getState()))}function De(){return w()}function Ne(){return w(e=>e.resources)}function Te(e){return w(t=>t.resources[e])}function je(){return w(e=>e.queue)}function Me(e){return w(t=>t.queue.find(s=>s.id===e))}function Ue(){const e=w(n=>n.isOnline),t=w(n=>n.swStatus),s=w(n=>n.swError);return{isOnline:e,swStatus:t,swError:s}}function We(){const e=w(a=>{let i=0,c=0,u=0;for(const l of a.queue)l.status==="pending"?i++:l.status==="failed"?c++:l.status==="replaying"&&u++;return`${i},${c},${u},${a.queue.length}`}),[t,s,n,r]=e.split(",");return{pending:+t,failed:+s,replaying:+n,total:+r}}function $e(e){const t=w(r=>r.queue.length),s=k.useRef(0),n=k.useRef(e);n.current=e,k.useEffect(()=>{s.current>0&&t===0&&n.current(),s.current=t},[t])}const He="1.0.33";function Le(e,t){const s=Object.keys(e);if(s.length!==Object.keys(t).length)return!1;for(const n of s)if(e[n]!==t[n])return!1;return!0}function J(e,t){return Le(e,t)}function b(e,t=Object.is){return{subscribe(s){let n=e(o.getState());return s(n),o.subscribe(()=>{const r=e(o.getState());t(n,r)||(n=r,s(r))})},getState(){return e(o.getState())}}}const Be=b(e=>e),Fe=b(e=>e.queue),Ge=b(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),J),Ke=b(e=>{let t=0,s=0,n=0;for(const r of e.queue)r.status==="pending"?t++:r.status==="failed"?s++:r.status==="replaying"&&n++;return{pending:t,failed:s,replaying:n,total:e.queue.length}},J);function Ve(e){return b(t=>t.resources[e])}function Ye(e){return b(t=>t.queue.find(s=>s.id===e))}exports.AsyncStorageQueueStorage=Ce;exports.EidosProvider=Pe;exports.VERSION=He;exports._getQueueStorage=F;exports._resetEidos=Qe;exports.action=ke;exports.clearQueue=_e;exports.eidosAction=Ye;exports.eidosQueue=Fe;exports.eidosQueueStats=Ke;exports.eidosResource=Ve;exports.eidosStatus=Ge;exports.eidosStore=Be;exports.initEidos=Y;exports.isBgSyncSupported=ne;exports.replayQueue=_;exports.resource=ue;exports.setOfflineSimulation=ae;exports.setQueryInvalidator=oe;exports.setQueueStorage=be;exports.useEidos=De;exports.useEidosAction=Me;exports.useEidosOnDrain=$e;exports.useEidosQueue=je;exports.useEidosQueueStats=We;exports.useEidosResource=Te;exports.useEidosResources=Ne;exports.useEidosStatus=Ue;exports.useEidosStore=o;exports.warmCache=pe;
|
|
14
14
|
//# sourceMappingURL=eidos.cjs.js.map
|