@sweidos/eidos 2.2.0 → 2.3.1
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 +261 -15
- package/dist/action.d.ts +22 -0
- package/dist/async-storage-adapter.d.ts +25 -0
- package/dist/debug.d.ts +46 -0
- package/dist/debug.js +43 -0
- package/dist/debug.js.map +1 -0
- package/dist/devtools.js +185 -5
- package/dist/eidos-sw.js +62 -20
- package/dist/eidos.cjs +6 -6
- package/dist/eidos.cjs.map +1 -1
- package/dist/idb.d.ts +10 -0
- package/dist/index.d.ts +20 -647
- package/dist/index.js +37 -34
- package/dist/internal/url-base64.d.ts +2 -0
- package/dist/query.d.ts +1 -2
- package/dist/queue-storage.d.ts +12 -0
- package/dist/queue-sync.d.ts +32 -0
- package/dist/react/Provider.d.ts +16 -0
- package/dist/react/ProviderRN.d.ts +0 -1
- package/dist/react/hooks.d.ts +51 -0
- package/dist/replay.d.ts +15 -0
- package/dist/resource.d.ts +32 -0
- package/dist/resource.js +81 -78
- package/dist/resource.js.map +1 -1
- package/dist/runtime-rn.d.ts +0 -1
- package/dist/runtime.d.ts +39 -0
- package/dist/runtime.js +24 -21
- package/dist/runtime.js.map +1 -1
- package/dist/store-slices.d.ts +26 -0
- package/dist/store.d.ts +15 -0
- package/dist/stores.d.ts +64 -0
- package/dist/sveltekit.d.ts +0 -1
- package/dist/sw-bridge.d.ts +24 -0
- package/dist/sw-bridge.js +69 -54
- package/dist/sw-bridge.js.map +1 -1
- package/dist/testing.d.ts +1 -2
- package/dist/types.d.ts +311 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/vite.d.ts +0 -1
- package/package.json +11 -7
package/dist/index.js
CHANGED
|
@@ -1,45 +1,48 @@
|
|
|
1
1
|
import { useEidosStore as o } from "./store.js";
|
|
2
|
-
import { getSwRegistration as i, isBgSyncSupported as
|
|
3
|
-
import { resource as
|
|
4
|
-
import { _getQueueStorage as
|
|
5
|
-
import { action as Q, cancelByIdempotencyKey as y, clearQueue as
|
|
6
|
-
import { subscribeReplayOnReconnect as
|
|
7
|
-
import { _resetEidos as
|
|
8
|
-
import { AsyncStorageQueueStorage as
|
|
2
|
+
import { getSwRegistration as i, isBgSyncSupported as t, registerPushCallbacks as s, sendToWorker as u, setOfflineSimulation as m, triggerSwUpdate as a } from "./sw-bridge.js";
|
|
3
|
+
import { resource as n, resourcePattern as p, setQueryInvalidator as c, warmCache as S } from "./resource.js";
|
|
4
|
+
import { _getQueueStorage as E, setQueueStorage as l } from "./queue-storage.js";
|
|
5
|
+
import { action as Q, cancelByIdempotencyKey as y, clearQueue as R, replayQueue as b, requeueItem as I } from "./action.js";
|
|
6
|
+
import { subscribeReplayOnReconnect as w } from "./replay.js";
|
|
7
|
+
import { _resetEidos as D, initEidos as P } from "./runtime.js";
|
|
8
|
+
import { AsyncStorageQueueStorage as k } from "./async-storage-adapter.js";
|
|
9
9
|
import { EidosProvider as B } from "./react/Provider.js";
|
|
10
|
-
import { eidosAction as
|
|
11
|
-
import { useEidos as j, useEidosAction as z, useEidosOnDrain as F, useEidosQueue as G, useEidosQueueStats as H, useEidosReliabilityStats as J, useEidosResource as L, useEidosResources as M, useEidosStatus as
|
|
12
|
-
import { VERSION as
|
|
10
|
+
import { eidosAction as _, eidosQueue as q, eidosQueueStats as x, eidosReliabilityStats as K, eidosResource as N, eidosStatus as T, eidosStore as U, onQueueDrain as V } from "./stores.js";
|
|
11
|
+
import { useEidos as j, useEidosAction as z, useEidosOnDrain as F, useEidosQueue as G, useEidosQueueStats as H, useEidosReliabilityStats as J, useEidosResource as L, useEidosResources as M, useEidosStatus as X } from "./react/hooks.js";
|
|
12
|
+
import { VERSION as Z } from "./version.js";
|
|
13
|
+
import { eidosDebug as ee } from "./debug.js";
|
|
13
14
|
export {
|
|
14
|
-
|
|
15
|
+
k as AsyncStorageQueueStorage,
|
|
15
16
|
B as EidosProvider,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Z as VERSION,
|
|
18
|
+
E as _getQueueStorage,
|
|
19
|
+
D as _resetEidos,
|
|
19
20
|
Q as action,
|
|
20
21
|
y as cancelByIdempotencyKey,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
q as
|
|
25
|
-
x as
|
|
26
|
-
K as
|
|
27
|
-
N as
|
|
28
|
-
T as
|
|
22
|
+
R as clearQueue,
|
|
23
|
+
_ as eidosAction,
|
|
24
|
+
ee as eidosDebug,
|
|
25
|
+
q as eidosQueue,
|
|
26
|
+
x as eidosQueueStats,
|
|
27
|
+
K as eidosReliabilityStats,
|
|
28
|
+
N as eidosResource,
|
|
29
|
+
T as eidosStatus,
|
|
30
|
+
U as eidosStore,
|
|
29
31
|
i as getSwRegistration,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
P as initEidos,
|
|
33
|
+
t as isBgSyncSupported,
|
|
32
34
|
V as onQueueDrain,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
s as registerPushCallbacks,
|
|
36
|
+
b as replayQueue,
|
|
37
|
+
I as requeueItem,
|
|
38
|
+
n as resource,
|
|
39
|
+
p as resourcePattern,
|
|
38
40
|
u as sendToWorker,
|
|
39
|
-
|
|
41
|
+
m as setOfflineSimulation,
|
|
40
42
|
c as setQueryInvalidator,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
l as setQueueStorage,
|
|
44
|
+
w as subscribeReplayOnReconnect,
|
|
45
|
+
a as triggerSwUpdate,
|
|
43
46
|
j as useEidos,
|
|
44
47
|
z as useEidosAction,
|
|
45
48
|
F as useEidosOnDrain,
|
|
@@ -48,7 +51,7 @@ export {
|
|
|
48
51
|
J as useEidosReliabilityStats,
|
|
49
52
|
L as useEidosResource,
|
|
50
53
|
M as useEidosResources,
|
|
51
|
-
|
|
54
|
+
X as useEidosStatus,
|
|
52
55
|
o as useEidosStore,
|
|
53
|
-
|
|
56
|
+
S as warmCache
|
|
54
57
|
};
|
package/dist/query.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { UseQueryOptions, UseQueryResult, UseMutationOptions, UseMutationResult, QueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { ResourceHandle, AnyResourceHandle, ActionHandle, QueuedResult } from '
|
|
3
|
-
|
|
2
|
+
import { ResourceHandle, AnyResourceHandle, ActionHandle, QueuedResult } from './index.ts';
|
|
4
3
|
/**
|
|
5
4
|
* Register a QueryClient with Eidos.
|
|
6
5
|
*
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ActionQueueItem } from './types';
|
|
2
|
+
export interface QueueStorage {
|
|
3
|
+
add(item: ActionQueueItem): Promise<void>;
|
|
4
|
+
getAll(): Promise<ActionQueueItem[]>;
|
|
5
|
+
getPending(): Promise<ActionQueueItem[]>;
|
|
6
|
+
update(id: string, patch: Partial<ActionQueueItem>): Promise<void>;
|
|
7
|
+
remove(id: string): Promise<void>;
|
|
8
|
+
clear(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/** Override the default IndexedDB queue with a custom storage backend (e.g. AsyncStorage for React Native). */
|
|
11
|
+
export declare function setQueueStorage(s: QueueStorage): void;
|
|
12
|
+
export declare function _getQueueStorage(): QueueStorage | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ActionQueueItem } from './types';
|
|
2
|
+
type QueueSyncMessage = {
|
|
3
|
+
type: 'update';
|
|
4
|
+
id: string;
|
|
5
|
+
update: Partial<ActionQueueItem>;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'batchUpdate';
|
|
8
|
+
updates: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
update: Partial<ActionQueueItem>;
|
|
11
|
+
}>;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'remove';
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Broadcasts a queue-item status change to other tabs sharing the same
|
|
18
|
+
* IndexedDB queue. The replay-lock holder (see `replayQueue` in action.ts)
|
|
19
|
+
* is the only tab that mutates queue-item status, so non-leader tabs would
|
|
20
|
+
* otherwise show stale status until their own store re-hydrates.
|
|
21
|
+
*
|
|
22
|
+
* No-ops in environments without BroadcastChannel (React Native, old Safari).
|
|
23
|
+
*/
|
|
24
|
+
export declare function broadcastQueueSync(message: QueueSyncMessage): void;
|
|
25
|
+
/**
|
|
26
|
+
* Applies queue-item status updates broadcast by the replay-lock holder to
|
|
27
|
+
* this tab's store. Returns an unsubscribe function.
|
|
28
|
+
*/
|
|
29
|
+
export declare function subscribeQueueSync(): () => void;
|
|
30
|
+
/** Test-only: reset the cached channel so each test gets a fresh instance. */
|
|
31
|
+
export declare function _resetQueueSyncChannel(): void;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { EidosConfig } from '../runtime';
|
|
3
|
+
interface EidosProviderProps extends EidosConfig {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Mount once at the root of your application.
|
|
8
|
+
* Registers the service worker and initialises the Eidos runtime.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <EidosProvider swPath="/eidos-sw.js">
|
|
12
|
+
* <App />
|
|
13
|
+
* </EidosProvider>
|
|
14
|
+
*/
|
|
15
|
+
export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { EidosStore } from '../store';
|
|
2
|
+
/** Full Eidos store — prefer the narrower hooks below for performance. */
|
|
3
|
+
export declare function useEidos(): EidosStore;
|
|
4
|
+
/** All registered resources — only re-renders when the resources map changes, not on queue mutations. */
|
|
5
|
+
export declare function useEidosResources(): Record<string, import('..').ResourceEntry>;
|
|
6
|
+
/** Live state for a single registered resource URL. */
|
|
7
|
+
export declare function useEidosResource(url: string): import('..').ResourceEntry;
|
|
8
|
+
/** The current action queue. */
|
|
9
|
+
export declare function useEidosQueue(): import('..').ActionQueueItem[];
|
|
10
|
+
/**
|
|
11
|
+
* Live state for a single queue item by ID. Only re-renders when that specific
|
|
12
|
+
* item changes — cheaper than `useEidosQueue().find(id)` which re-renders on
|
|
13
|
+
* any queue mutation.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useEidosAction(id: string): import('..').ActionQueueItem | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Online + SW status — cheap subscription, safe to use in header components.
|
|
18
|
+
* Three separate primitive selectors so each only triggers a re-render when
|
|
19
|
+
* its own value changes (no object-reference churn from a combined selector).
|
|
20
|
+
*/
|
|
21
|
+
export declare function useEidosStatus(): {
|
|
22
|
+
isOnline: boolean;
|
|
23
|
+
swStatus: "idle" | "error" | "registering" | "active" | "unsupported";
|
|
24
|
+
swError: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Queue counts — single subscription, single loop. Re-renders only when a
|
|
28
|
+
* count changes, not on every queue mutation. Use for badges and status bars
|
|
29
|
+
* instead of `useEidosQueue()` when you only need numbers, not full items.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useEidosQueueStats(): {
|
|
32
|
+
pending: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
replaying: number;
|
|
35
|
+
total: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Calls `callback` once each time the action queue drains from non-empty → 0.
|
|
39
|
+
* Stable callback reference not required — always calls the latest version.
|
|
40
|
+
* Use for "all offline actions synced!" toasts.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* useEidosOnDrain(() => toast.success('All offline actions synced!'))
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Cumulative, session-scoped `neverLose` queue outcome counters — opt-in
|
|
47
|
+
* reliability telemetry for dashboards/devtools. Re-renders only when a
|
|
48
|
+
* counter changes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function useEidosReliabilityStats(): import('..').ReliabilityStats;
|
|
51
|
+
export declare function useEidosOnDrain(callback: () => void): void;
|
package/dist/replay.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscribe to online/offline transitions and trigger replayQueue() on
|
|
3
|
+
* reconnect, plus replay any pending items left over from a previous session.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the web (runtime.ts) and React Native (runtime-rn.ts) init paths.
|
|
6
|
+
*
|
|
7
|
+
* WHY subscribe to the store instead of window.addEventListener('online'):
|
|
8
|
+
* setOfflineSimulation() updates the store directly but never fires a real
|
|
9
|
+
* browser `online` event. Watching the store catches both:
|
|
10
|
+
* • Real network reconnects (sw-bridge updates store on window.online)
|
|
11
|
+
* • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))
|
|
12
|
+
*
|
|
13
|
+
* Returns an unsubscribe function.
|
|
14
|
+
*/
|
|
15
|
+
export declare function subscribeReplayOnReconnect(): () => void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ResourceConfig, ResourceHandle, PatternResourceHandle, WarmCacheResult } from './types';
|
|
2
|
+
type QueryInvalidator = (queryKey: [string, string]) => void;
|
|
3
|
+
/** @internal Called by @sweidos/eidos/query. */
|
|
4
|
+
export declare function setQueryInvalidator(fn: QueryInvalidator): void;
|
|
5
|
+
/**
|
|
6
|
+
* Registers a concrete-URL resource. For URL patterns (`/api/products/*`,
|
|
7
|
+
* `/api/users/:id`, `**`), use `resourcePattern()` instead.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resource<T = unknown>(url: string, config: ResourceConfig): ResourceHandle<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Registers a URL pattern (`/api/products/*`, `/api/users/:id`, `**`). The SW
|
|
12
|
+
* intercepts all matching requests automatically — there is no single URL to
|
|
13
|
+
* fetch/cache directly, so the returned handle only supports cache management
|
|
14
|
+
* (`invalidate`/`unregister`). For a fetchable resource, use `resource()`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resourcePattern(url: string, config: ResourceConfig): PatternResourceHandle;
|
|
17
|
+
/**
|
|
18
|
+
* Bulk-prefetch an array of resource handles concurrently, warming the cache
|
|
19
|
+
* for each one. Useful on login / app init when you know which resources the
|
|
20
|
+
* user will need offline.
|
|
21
|
+
*
|
|
22
|
+
* Pattern handles (containing `*`, `**`, or `:param`) are silently skipped —
|
|
23
|
+
* they match multiple URLs so there is no single URL to prefetch.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* import { warmCache } from '@sweidos/eidos'
|
|
27
|
+
*
|
|
28
|
+
* // In EidosProvider's onReady, or after login:
|
|
29
|
+
* const { warmed, failed } = await warmCache([products, userProfile, settings])
|
|
30
|
+
*/
|
|
31
|
+
export declare function warmCache(handles: ResourceHandle[]): Promise<WarmCacheResult>;
|
|
32
|
+
export {};
|
package/dist/resource.js
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
1
|
import { useEidosStore as h } from "./store.js";
|
|
2
|
-
import { sendToWorker as
|
|
3
|
-
var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(),
|
|
4
|
-
function
|
|
5
|
-
|
|
2
|
+
import { sendToWorker as m } from "./sw-bridge.js";
|
|
3
|
+
var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), w = null;
|
|
4
|
+
function q(e) {
|
|
5
|
+
w = e;
|
|
6
6
|
}
|
|
7
7
|
function f(e) {
|
|
8
8
|
return e.includes("*") || /:[^/]+/.test(e);
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function R(e) {
|
|
11
11
|
return "^" + e.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]+").replace(/:[^/]+/g, "[^/]+") + "$";
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
const
|
|
13
|
+
function v(e, a) {
|
|
14
|
+
const s = S(a), t = f(e) ? R(e) : void 0, r = {
|
|
15
15
|
url: e,
|
|
16
|
-
config:
|
|
17
|
-
strategy:
|
|
16
|
+
config: a,
|
|
17
|
+
strategy: s,
|
|
18
18
|
status: "idle",
|
|
19
19
|
cacheHits: 0,
|
|
20
20
|
cacheMisses: 0
|
|
21
21
|
};
|
|
22
|
-
return h.getState().registerResource(e, r),
|
|
22
|
+
return h.getState().registerResource(e, r), m({
|
|
23
23
|
type: "EIDOS_REGISTER_RESOURCE",
|
|
24
24
|
url: e,
|
|
25
|
-
strategy:
|
|
26
|
-
cacheName:
|
|
27
|
-
...t !== void 0 && { pattern: t }
|
|
25
|
+
strategy: s.swStrategy,
|
|
26
|
+
cacheName: s.cacheName,
|
|
27
|
+
...t !== void 0 && { pattern: t },
|
|
28
|
+
...a.maxAge !== void 0 && { maxAge: a.maxAge },
|
|
29
|
+
...a.maxEntries !== void 0 && { maxEntries: a.maxEntries },
|
|
30
|
+
...a.networkTimeoutMs !== void 0 && { networkTimeoutMs: a.networkTimeoutMs }
|
|
28
31
|
}), {
|
|
29
|
-
strategy:
|
|
32
|
+
strategy: s,
|
|
30
33
|
regexStr: t
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
|
-
function y(e,
|
|
36
|
+
function y(e, a, s) {
|
|
34
37
|
return async () => {
|
|
35
|
-
|
|
38
|
+
m({
|
|
36
39
|
type: "EIDOS_CLEAR_CACHE",
|
|
37
40
|
url: e
|
|
38
41
|
});
|
|
39
|
-
const t = await caches.open(
|
|
42
|
+
const t = await caches.open(a.cacheName).catch(() => null);
|
|
40
43
|
if (t) {
|
|
41
|
-
const r = await t.keys(), n =
|
|
42
|
-
await Promise.all(r.filter((
|
|
43
|
-
const o =
|
|
44
|
-
return n ? n.test(
|
|
45
|
-
}).map((
|
|
44
|
+
const r = await t.keys(), n = s ? new RegExp(s) : null, i = e.startsWith("http");
|
|
45
|
+
await Promise.all(r.filter((c) => {
|
|
46
|
+
const o = c.url, u = new URL(o).pathname;
|
|
47
|
+
return n ? n.test(i ? o : u) : i ? o === e : o === e || u === e;
|
|
48
|
+
}).map((c) => t.delete(c)));
|
|
46
49
|
}
|
|
47
50
|
f(e) || h.getState().updateResource(e, {
|
|
48
51
|
status: "stale",
|
|
@@ -50,30 +53,30 @@ function y(e, s, a) {
|
|
|
50
53
|
lastEvent: "cache-cleared",
|
|
51
54
|
cacheHits: 0,
|
|
52
55
|
cacheMisses: 0
|
|
53
|
-
}),
|
|
56
|
+
}), w?.(["eidos", e]);
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
59
|
function E(e) {
|
|
57
60
|
return () => {
|
|
58
|
-
d.delete(e),
|
|
61
|
+
d.delete(e), m({
|
|
59
62
|
type: "EIDOS_UNREGISTER_RESOURCE",
|
|
60
63
|
url: e
|
|
61
64
|
}), h.getState().unregisterResource(e);
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
|
-
function
|
|
67
|
+
function _(e, a) {
|
|
65
68
|
if (f(e)) throw new Error(`[eidos] resource('${e}') is a URL pattern — use resourcePattern('${e}', config) instead. Pattern handles only support invalidate()/unregister(); the SW intercepts matching requests automatically.`);
|
|
66
69
|
if (d.has(e)) return d.get(e);
|
|
67
|
-
const { strategy:
|
|
70
|
+
const { strategy: s } = v(e, a), t = {
|
|
68
71
|
url: e,
|
|
69
|
-
config:
|
|
70
|
-
strategy:
|
|
72
|
+
config: a,
|
|
73
|
+
strategy: s,
|
|
71
74
|
fetch: async () => {
|
|
72
75
|
const r = l.get(e);
|
|
73
|
-
if (r) return r.then((
|
|
74
|
-
const n =
|
|
76
|
+
if (r) return r.then((i) => i.clone());
|
|
77
|
+
const n = k(e, a, s);
|
|
75
78
|
return l.set(e, n), n.finally(() => l.delete(e)).catch(() => {
|
|
76
|
-
}), n.then((
|
|
79
|
+
}), n.then((i) => i.clone());
|
|
77
80
|
},
|
|
78
81
|
json: async () => (await t.fetch()).json(),
|
|
79
82
|
query: () => ({
|
|
@@ -83,47 +86,47 @@ function A(e, s) {
|
|
|
83
86
|
prefetch: async () => {
|
|
84
87
|
await t.fetch();
|
|
85
88
|
},
|
|
86
|
-
invalidate: y(e,
|
|
89
|
+
invalidate: y(e, s, void 0),
|
|
87
90
|
unregister: E(e)
|
|
88
91
|
};
|
|
89
92
|
return d.set(e, t), t;
|
|
90
93
|
}
|
|
91
|
-
function
|
|
94
|
+
function T(e, a) {
|
|
92
95
|
if (!f(e)) throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);
|
|
93
96
|
if (d.has(e)) return d.get(e);
|
|
94
|
-
const { strategy:
|
|
97
|
+
const { strategy: s, regexStr: t } = v(e, a), r = {
|
|
95
98
|
url: e,
|
|
96
|
-
config:
|
|
97
|
-
strategy:
|
|
98
|
-
invalidate: y(e,
|
|
99
|
+
config: a,
|
|
100
|
+
strategy: s,
|
|
101
|
+
invalidate: y(e, s, t),
|
|
99
102
|
unregister: E(e)
|
|
100
103
|
};
|
|
101
104
|
return d.set(e, r), r;
|
|
102
105
|
}
|
|
103
|
-
async function
|
|
106
|
+
async function k(e, a, s) {
|
|
104
107
|
const t = h.getState();
|
|
105
108
|
t.updateResource(e, {
|
|
106
109
|
status: "fetching",
|
|
107
110
|
fetchedAt: Date.now()
|
|
108
111
|
});
|
|
109
|
-
const r = await caches.open(
|
|
112
|
+
const r = await caches.open(s.cacheName).catch(() => null);
|
|
110
113
|
try {
|
|
111
|
-
if (
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
+
if (s.swStrategy !== "network-first") {
|
|
115
|
+
const c = r ? await r.match(e).catch(() => null) : null, o = h.getState().resources[e], u = a.maxAge !== void 0 && o?.cachedAt !== void 0 && Date.now() - o.cachedAt > a.maxAge;
|
|
116
|
+
if (c && !u)
|
|
114
117
|
return t.updateResource(e, {
|
|
115
118
|
status: "fresh",
|
|
116
119
|
lastEvent: "cache-hit",
|
|
117
120
|
cacheHits: (o?.cacheHits ?? 0) + 1
|
|
118
|
-
}),
|
|
119
|
-
|
|
121
|
+
}), s.swStrategy === "stale-while-revalidate" && fetch(e, { signal: AbortSignal.timeout(a.networkTimeoutMs ?? 3e3) }).then(async (g) => {
|
|
122
|
+
g.ok && r && (await r.put(e, g.clone()), h.getState().updateResource(e, {
|
|
120
123
|
cachedAt: Date.now(),
|
|
121
124
|
lastEvent: "cache-updated"
|
|
122
125
|
}));
|
|
123
126
|
}).catch(() => {
|
|
124
|
-
}),
|
|
125
|
-
const
|
|
126
|
-
t.updateResource(e, { cacheMisses: (
|
|
127
|
+
}), c;
|
|
128
|
+
const x = h.getState().resources[e];
|
|
129
|
+
t.updateResource(e, { cacheMisses: (x?.cacheMisses ?? 0) + 1 });
|
|
127
130
|
}
|
|
128
131
|
const n = await fetch(e);
|
|
129
132
|
if (n.ok)
|
|
@@ -133,30 +136,30 @@ async function S(e, s, a) {
|
|
|
133
136
|
lastEvent: "cache-updated"
|
|
134
137
|
}), n;
|
|
135
138
|
t.updateResource(e, { status: n.status === 503 ? "offline" : "error" });
|
|
136
|
-
const
|
|
137
|
-
throw new Error(
|
|
139
|
+
const i = n.headers.get("X-Eidos-Offline") === "true";
|
|
140
|
+
throw new Error(i ? `offline: no cached response for ${e}` : `${n.status} ${n.statusText}`);
|
|
138
141
|
} catch (n) {
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
141
|
-
const
|
|
142
|
+
const i = r ? await r.match(e).catch(() => null) : null;
|
|
143
|
+
if (i) {
|
|
144
|
+
const c = h.getState().resources[e];
|
|
142
145
|
return t.updateResource(e, {
|
|
143
146
|
status: "fresh",
|
|
144
147
|
lastEvent: "cache-hit",
|
|
145
|
-
cacheHits: (
|
|
146
|
-
}),
|
|
148
|
+
cacheHits: (c?.cacheHits ?? 0) + 1
|
|
149
|
+
}), i;
|
|
147
150
|
}
|
|
148
151
|
throw t.updateResource(e, { status: "error" }), n;
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
return e.offline ?
|
|
154
|
+
function S(e) {
|
|
155
|
+
const a = e.strategy;
|
|
156
|
+
return e.offline ? p(a ?? "stale-while-revalidate", e.cacheName, e.version) : p(a ?? "network-first", e.cacheName, e.version);
|
|
154
157
|
}
|
|
155
|
-
var
|
|
158
|
+
var A = {
|
|
156
159
|
"stale-while-revalidate": "StaleWhileRevalidate",
|
|
157
160
|
"cache-first": "CacheFirst",
|
|
158
161
|
"network-first": "NetworkFirst"
|
|
159
|
-
},
|
|
162
|
+
}, b = {
|
|
160
163
|
"stale-while-revalidate": {
|
|
161
164
|
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.",
|
|
162
165
|
behavior: [
|
|
@@ -165,10 +168,10 @@ var x = {
|
|
|
165
168
|
"Offline → return cached version if available, 503 if not",
|
|
166
169
|
"Reconnect → next request triggers a background refresh"
|
|
167
170
|
],
|
|
168
|
-
equivalentCode: `// Workbox equivalent
|
|
171
|
+
equivalentCode: `// Workbox equivalent (maxEntries/maxAge are configured via ResourceConfig)
|
|
169
172
|
new StaleWhileRevalidate({
|
|
170
173
|
cacheName: 'eidos-resources-v1',
|
|
171
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
174
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
172
175
|
})`
|
|
173
176
|
},
|
|
174
177
|
"cache-first": {
|
|
@@ -177,12 +180,12 @@ new StaleWhileRevalidate({
|
|
|
177
180
|
"Cache hit → return immediately, no network request made",
|
|
178
181
|
"Cache miss → fetch from network, cache the response, return it",
|
|
179
182
|
"Offline → return cached version, 503 if cache is empty",
|
|
180
|
-
"Cache never expires unless explicitly invalidated"
|
|
183
|
+
"Cache never expires unless maxAge is set or explicitly invalidated"
|
|
181
184
|
],
|
|
182
|
-
equivalentCode: `// Workbox equivalent
|
|
185
|
+
equivalentCode: `// Workbox equivalent (maxEntries/maxAge are configured via ResourceConfig)
|
|
183
186
|
new CacheFirst({
|
|
184
187
|
cacheName: 'eidos-resources-v1',
|
|
185
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
188
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
186
189
|
})`
|
|
187
190
|
},
|
|
188
191
|
"network-first": {
|
|
@@ -196,34 +199,34 @@ new CacheFirst({
|
|
|
196
199
|
equivalentCode: `// Workbox equivalent
|
|
197
200
|
new NetworkFirst({
|
|
198
201
|
cacheName: 'eidos-resources-v1',
|
|
199
|
-
networkTimeoutSeconds: 3,
|
|
202
|
+
networkTimeoutSeconds: 3, // controlled via ResourceConfig.networkTimeoutMs (default 3000)
|
|
200
203
|
})`
|
|
201
204
|
}
|
|
202
205
|
};
|
|
203
|
-
function
|
|
204
|
-
const t =
|
|
206
|
+
function p(e, a, s) {
|
|
207
|
+
const t = b[e], r = a ?? "eidos-resources-v1";
|
|
205
208
|
return {
|
|
206
|
-
name:
|
|
209
|
+
name: A[e],
|
|
207
210
|
swStrategy: e,
|
|
208
|
-
cacheName:
|
|
211
|
+
cacheName: s !== void 0 ? `${r}-v${s}` : r,
|
|
209
212
|
reasoning: t.reasoning,
|
|
210
213
|
behavior: t.behavior,
|
|
211
214
|
equivalentCode: ""
|
|
212
215
|
};
|
|
213
216
|
}
|
|
214
|
-
async function
|
|
215
|
-
const
|
|
217
|
+
async function M(e) {
|
|
218
|
+
const a = await Promise.allSettled(e.map((t) => t.prefetch())), s = a.filter((t) => t.status === "rejected").map((t) => t.reason);
|
|
216
219
|
return {
|
|
217
|
-
warmed:
|
|
218
|
-
failed:
|
|
219
|
-
errors:
|
|
220
|
+
warmed: a.filter((t) => t.status === "fulfilled").length,
|
|
221
|
+
failed: s.length,
|
|
222
|
+
errors: s
|
|
220
223
|
};
|
|
221
224
|
}
|
|
222
225
|
export {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
_ as resource,
|
|
227
|
+
T as resourcePattern,
|
|
228
|
+
q as setQueryInvalidator,
|
|
229
|
+
M as warmCache
|
|
227
230
|
};
|
|
228
231
|
|
|
229
232
|
//# sourceMappingURL=resource.js.map
|