@sweidos/eidos 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -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 +60 -19
- package/dist/eidos.cjs +5 -5
- 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 +80 -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 +305 -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 +7 -5
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,79 +1,81 @@
|
|
|
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 g } from "./sw-bridge.js";
|
|
3
|
+
var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), v = null;
|
|
4
|
+
function q(e) {
|
|
5
|
+
v = e;
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function u(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 w(e, a) {
|
|
14
|
+
const s = k(a), t = u(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), g({
|
|
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 }
|
|
28
30
|
}), {
|
|
29
|
-
strategy:
|
|
31
|
+
strategy: s,
|
|
30
32
|
regexStr: t
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
|
-
function y(e,
|
|
35
|
+
function y(e, a, s) {
|
|
34
36
|
return async () => {
|
|
35
|
-
|
|
37
|
+
g({
|
|
36
38
|
type: "EIDOS_CLEAR_CACHE",
|
|
37
39
|
url: e
|
|
38
40
|
});
|
|
39
|
-
const t = await caches.open(
|
|
41
|
+
const t = await caches.open(a.cacheName).catch(() => null);
|
|
40
42
|
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((
|
|
43
|
+
const r = await t.keys(), n = s ? new RegExp(s) : null, i = e.startsWith("http");
|
|
44
|
+
await Promise.all(r.filter((c) => {
|
|
45
|
+
const o = c.url, f = new URL(o).pathname;
|
|
46
|
+
return n ? n.test(i ? o : f) : i ? o === e : o === e || f === e;
|
|
47
|
+
}).map((c) => t.delete(c)));
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
u(e) || h.getState().updateResource(e, {
|
|
48
50
|
status: "stale",
|
|
49
51
|
cachedAt: void 0,
|
|
50
52
|
lastEvent: "cache-cleared",
|
|
51
53
|
cacheHits: 0,
|
|
52
54
|
cacheMisses: 0
|
|
53
|
-
}),
|
|
55
|
+
}), v?.(["eidos", e]);
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
function E(e) {
|
|
57
59
|
return () => {
|
|
58
|
-
d.delete(e),
|
|
60
|
+
d.delete(e), g({
|
|
59
61
|
type: "EIDOS_UNREGISTER_RESOURCE",
|
|
60
62
|
url: e
|
|
61
63
|
}), h.getState().unregisterResource(e);
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
|
-
function
|
|
65
|
-
if (
|
|
66
|
+
function _(e, a) {
|
|
67
|
+
if (u(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
68
|
if (d.has(e)) return d.get(e);
|
|
67
|
-
const { strategy:
|
|
69
|
+
const { strategy: s } = w(e, a), t = {
|
|
68
70
|
url: e,
|
|
69
|
-
config:
|
|
70
|
-
strategy:
|
|
71
|
+
config: a,
|
|
72
|
+
strategy: s,
|
|
71
73
|
fetch: async () => {
|
|
72
74
|
const r = l.get(e);
|
|
73
|
-
if (r) return r.then((
|
|
74
|
-
const n = S(e,
|
|
75
|
+
if (r) return r.then((i) => i.clone());
|
|
76
|
+
const n = S(e, a, s);
|
|
75
77
|
return l.set(e, n), n.finally(() => l.delete(e)).catch(() => {
|
|
76
|
-
}), n.then((
|
|
78
|
+
}), n.then((i) => i.clone());
|
|
77
79
|
},
|
|
78
80
|
json: async () => (await t.fetch()).json(),
|
|
79
81
|
query: () => ({
|
|
@@ -83,47 +85,47 @@ function A(e, s) {
|
|
|
83
85
|
prefetch: async () => {
|
|
84
86
|
await t.fetch();
|
|
85
87
|
},
|
|
86
|
-
invalidate: y(e,
|
|
88
|
+
invalidate: y(e, s, void 0),
|
|
87
89
|
unregister: E(e)
|
|
88
90
|
};
|
|
89
91
|
return d.set(e, t), t;
|
|
90
92
|
}
|
|
91
|
-
function $(e,
|
|
92
|
-
if (!
|
|
93
|
+
function $(e, a) {
|
|
94
|
+
if (!u(e)) throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);
|
|
93
95
|
if (d.has(e)) return d.get(e);
|
|
94
|
-
const { strategy:
|
|
96
|
+
const { strategy: s, regexStr: t } = w(e, a), r = {
|
|
95
97
|
url: e,
|
|
96
|
-
config:
|
|
97
|
-
strategy:
|
|
98
|
-
invalidate: y(e,
|
|
98
|
+
config: a,
|
|
99
|
+
strategy: s,
|
|
100
|
+
invalidate: y(e, s, t),
|
|
99
101
|
unregister: E(e)
|
|
100
102
|
};
|
|
101
103
|
return d.set(e, r), r;
|
|
102
104
|
}
|
|
103
|
-
async function S(e,
|
|
105
|
+
async function S(e, a, s) {
|
|
104
106
|
const t = h.getState();
|
|
105
107
|
t.updateResource(e, {
|
|
106
108
|
status: "fetching",
|
|
107
109
|
fetchedAt: Date.now()
|
|
108
110
|
});
|
|
109
|
-
const r = await caches.open(
|
|
111
|
+
const r = await caches.open(s.cacheName).catch(() => null);
|
|
110
112
|
try {
|
|
111
|
-
if (
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
113
|
+
if (s.swStrategy !== "network-first") {
|
|
114
|
+
const c = r ? await r.match(e).catch(() => null) : null, o = h.getState().resources[e], f = a.maxAge !== void 0 && o?.cachedAt !== void 0 && Date.now() - o.cachedAt > a.maxAge;
|
|
115
|
+
if (c && !f)
|
|
114
116
|
return t.updateResource(e, {
|
|
115
117
|
status: "fresh",
|
|
116
118
|
lastEvent: "cache-hit",
|
|
117
119
|
cacheHits: (o?.cacheHits ?? 0) + 1
|
|
118
|
-
}),
|
|
119
|
-
|
|
120
|
+
}), s.swStrategy === "stale-while-revalidate" && fetch(e, { signal: AbortSignal.timeout(5e3) }).then(async (m) => {
|
|
121
|
+
m.ok && r && (await r.put(e, m.clone()), h.getState().updateResource(e, {
|
|
120
122
|
cachedAt: Date.now(),
|
|
121
123
|
lastEvent: "cache-updated"
|
|
122
124
|
}));
|
|
123
125
|
}).catch(() => {
|
|
124
|
-
}),
|
|
125
|
-
const
|
|
126
|
-
t.updateResource(e, { cacheMisses: (
|
|
126
|
+
}), c;
|
|
127
|
+
const x = h.getState().resources[e];
|
|
128
|
+
t.updateResource(e, { cacheMisses: (x?.cacheMisses ?? 0) + 1 });
|
|
127
129
|
}
|
|
128
130
|
const n = await fetch(e);
|
|
129
131
|
if (n.ok)
|
|
@@ -133,30 +135,30 @@ async function S(e, s, a) {
|
|
|
133
135
|
lastEvent: "cache-updated"
|
|
134
136
|
}), n;
|
|
135
137
|
t.updateResource(e, { status: n.status === 503 ? "offline" : "error" });
|
|
136
|
-
const
|
|
137
|
-
throw new Error(
|
|
138
|
+
const i = n.headers.get("X-Eidos-Offline") === "true";
|
|
139
|
+
throw new Error(i ? `offline: no cached response for ${e}` : `${n.status} ${n.statusText}`);
|
|
138
140
|
} catch (n) {
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
141
|
-
const
|
|
141
|
+
const i = r ? await r.match(e).catch(() => null) : null;
|
|
142
|
+
if (i) {
|
|
143
|
+
const c = h.getState().resources[e];
|
|
142
144
|
return t.updateResource(e, {
|
|
143
145
|
status: "fresh",
|
|
144
146
|
lastEvent: "cache-hit",
|
|
145
|
-
cacheHits: (
|
|
146
|
-
}),
|
|
147
|
+
cacheHits: (c?.cacheHits ?? 0) + 1
|
|
148
|
+
}), i;
|
|
147
149
|
}
|
|
148
150
|
throw t.updateResource(e, { status: "error" }), n;
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
return e.offline ?
|
|
153
|
+
function k(e) {
|
|
154
|
+
const a = e.strategy;
|
|
155
|
+
return e.offline ? p(a ?? "stale-while-revalidate", e.cacheName, e.version) : p(a ?? "network-first", e.cacheName, e.version);
|
|
154
156
|
}
|
|
155
|
-
var
|
|
157
|
+
var A = {
|
|
156
158
|
"stale-while-revalidate": "StaleWhileRevalidate",
|
|
157
159
|
"cache-first": "CacheFirst",
|
|
158
160
|
"network-first": "NetworkFirst"
|
|
159
|
-
},
|
|
161
|
+
}, b = {
|
|
160
162
|
"stale-while-revalidate": {
|
|
161
163
|
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
164
|
behavior: [
|
|
@@ -165,10 +167,10 @@ var x = {
|
|
|
165
167
|
"Offline → return cached version if available, 503 if not",
|
|
166
168
|
"Reconnect → next request triggers a background refresh"
|
|
167
169
|
],
|
|
168
|
-
equivalentCode: `// Workbox equivalent
|
|
170
|
+
equivalentCode: `// Workbox equivalent (maxEntries/maxAge are configured via ResourceConfig)
|
|
169
171
|
new StaleWhileRevalidate({
|
|
170
172
|
cacheName: 'eidos-resources-v1',
|
|
171
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
173
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
172
174
|
})`
|
|
173
175
|
},
|
|
174
176
|
"cache-first": {
|
|
@@ -177,12 +179,12 @@ new StaleWhileRevalidate({
|
|
|
177
179
|
"Cache hit → return immediately, no network request made",
|
|
178
180
|
"Cache miss → fetch from network, cache the response, return it",
|
|
179
181
|
"Offline → return cached version, 503 if cache is empty",
|
|
180
|
-
"Cache never expires unless explicitly invalidated"
|
|
182
|
+
"Cache never expires unless maxAge is set or explicitly invalidated"
|
|
181
183
|
],
|
|
182
|
-
equivalentCode: `// Workbox equivalent
|
|
184
|
+
equivalentCode: `// Workbox equivalent (maxEntries/maxAge are configured via ResourceConfig)
|
|
183
185
|
new CacheFirst({
|
|
184
186
|
cacheName: 'eidos-resources-v1',
|
|
185
|
-
plugins: [new ExpirationPlugin({ maxEntries:
|
|
187
|
+
plugins: [new ExpirationPlugin({ maxEntries: config.maxEntries, maxAgeSeconds: config.maxAge && config.maxAge / 1000 })],
|
|
186
188
|
})`
|
|
187
189
|
},
|
|
188
190
|
"network-first": {
|
|
@@ -200,29 +202,29 @@ new NetworkFirst({
|
|
|
200
202
|
})`
|
|
201
203
|
}
|
|
202
204
|
};
|
|
203
|
-
function
|
|
204
|
-
const t =
|
|
205
|
+
function p(e, a, s) {
|
|
206
|
+
const t = b[e], r = a ?? "eidos-resources-v1";
|
|
205
207
|
return {
|
|
206
|
-
name:
|
|
208
|
+
name: A[e],
|
|
207
209
|
swStrategy: e,
|
|
208
|
-
cacheName:
|
|
210
|
+
cacheName: s !== void 0 ? `${r}-v${s}` : r,
|
|
209
211
|
reasoning: t.reasoning,
|
|
210
212
|
behavior: t.behavior,
|
|
211
213
|
equivalentCode: ""
|
|
212
214
|
};
|
|
213
215
|
}
|
|
214
216
|
async function O(e) {
|
|
215
|
-
const
|
|
217
|
+
const a = await Promise.allSettled(e.map((t) => t.prefetch())), s = a.filter((t) => t.status === "rejected").map((t) => t.reason);
|
|
216
218
|
return {
|
|
217
|
-
warmed:
|
|
218
|
-
failed:
|
|
219
|
-
errors:
|
|
219
|
+
warmed: a.filter((t) => t.status === "fulfilled").length,
|
|
220
|
+
failed: s.length,
|
|
221
|
+
errors: s
|
|
220
222
|
};
|
|
221
223
|
}
|
|
222
224
|
export {
|
|
223
|
-
|
|
225
|
+
_ as resource,
|
|
224
226
|
$ as resourcePattern,
|
|
225
|
-
|
|
227
|
+
q as setQueryInvalidator,
|
|
226
228
|
O as warmCache
|
|
227
229
|
};
|
|
228
230
|
|