@sweidos/eidos 1.0.33 → 1.1.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 +108 -81
- package/dist/action.js +119 -86
- package/dist/async-storage-adapter.js +15 -12
- package/dist/devtools.js +953 -555
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +37 -15
- package/dist/index.js +42 -41
- package/dist/nextjs.js +1 -10
- package/dist/query.cjs +131 -0
- package/dist/query.js +121 -41
- package/dist/queue-storage.js +5 -4
- package/dist/react/Devtools.d.ts +1 -1
- package/dist/react/Provider.js +11 -7
- package/dist/react/hooks.js +48 -38
- package/dist/react-native.js +47 -53
- package/dist/replay.js +15 -0
- package/dist/resource.js +77 -79
- package/dist/runtime.js +22 -28
- package/dist/store-slices.js +43 -0
- package/dist/store.js +32 -49
- package/dist/stores.js +25 -22
- package/dist/sveltekit.js +22 -6
- package/dist/sw-bridge.js +48 -46
- package/dist/testing.cjs +165 -0
- package/dist/testing.js +140 -70
- package/dist/version.js +4 -3
- package/dist/vite.cjs +48 -0
- package/dist/vite.js +45 -29
- package/package.json +48 -27
- package/dist/action.js.map +0 -1
- package/dist/async-storage-adapter.js.map +0 -1
- package/dist/eidos.cjs.js +0 -14
- package/dist/eidos.cjs.js.map +0 -1
- package/dist/idb.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query.cjs.js +0 -48
- package/dist/queue-storage.js.map +0 -1
- package/dist/react/Provider.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/resource.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/stores.js.map +0 -1
- package/dist/sw-bridge.js.map +0 -1
- package/dist/testing.cjs.js +0 -86
- package/dist/version.js.map +0 -1
- package/dist/vite.cjs.js +0 -31
package/dist/sw-bridge.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { useEidosStore as
|
|
2
|
-
|
|
3
|
-
function
|
|
1
|
+
import { useEidosStore as i } from "./store.js";
|
|
2
|
+
var s = null, o = [];
|
|
3
|
+
function l() {
|
|
4
4
|
return s;
|
|
5
5
|
}
|
|
6
|
-
async function w(
|
|
6
|
+
async function w(e) {
|
|
7
7
|
if (typeof navigator > "u" || !("serviceWorker" in navigator)) {
|
|
8
|
-
|
|
8
|
+
i.getState().setSwStatus("unsupported");
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const t = i.getState();
|
|
12
|
+
t.setSwStatus("registering");
|
|
13
13
|
try {
|
|
14
|
-
s = await navigator.serviceWorker.register(
|
|
14
|
+
s = await navigator.serviceWorker.register(e, { scope: "/" }), await f(s), t.setSwStatus("active"), navigator.serviceWorker.addEventListener("message", d), window.addEventListener("online", () => t.setOnline(!0)), window.addEventListener("offline", () => t.setOnline(!1)), S();
|
|
15
15
|
} catch (n) {
|
|
16
|
-
|
|
16
|
+
t.setSwStatus("error", String(n));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
function f(
|
|
20
|
-
return new Promise((
|
|
21
|
-
if (
|
|
22
|
-
|
|
19
|
+
function f(e) {
|
|
20
|
+
return new Promise((t) => {
|
|
21
|
+
if (e.active) {
|
|
22
|
+
t();
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
const n =
|
|
25
|
+
const n = e.installing ?? e.waiting;
|
|
26
26
|
if (!n) {
|
|
27
|
-
|
|
27
|
+
t();
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const a = setTimeout(t, 1e4);
|
|
31
31
|
n.addEventListener("statechange", function r() {
|
|
32
|
-
n.state === "activated" && (clearTimeout(
|
|
32
|
+
n.state === "activated" && (clearTimeout(a), n.removeEventListener("statechange", r), t());
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
36
|
+
function g(e) {
|
|
37
|
+
const t = s?.active;
|
|
38
|
+
t ? t.postMessage(e) : o.push(e);
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
function E(
|
|
42
|
-
|
|
40
|
+
var c = null;
|
|
41
|
+
function E(e) {
|
|
42
|
+
c = e;
|
|
43
43
|
}
|
|
44
44
|
function p() {
|
|
45
45
|
try {
|
|
@@ -48,58 +48,60 @@ function p() {
|
|
|
48
48
|
return !1;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
const n =
|
|
55
|
-
if (
|
|
56
|
-
|
|
51
|
+
function d(e) {
|
|
52
|
+
const t = e.data;
|
|
53
|
+
if (!t?.type) return;
|
|
54
|
+
const n = i.getState(), { type: a, url: r } = t;
|
|
55
|
+
if (a === "EIDOS_BACKGROUND_SYNC") {
|
|
56
|
+
c?.();
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
if (r)
|
|
60
|
-
switch (
|
|
60
|
+
switch (a) {
|
|
61
61
|
case "EIDOS_CACHE_HIT": {
|
|
62
|
-
const
|
|
62
|
+
const u = n.resources[r];
|
|
63
63
|
n.updateResource(r, {
|
|
64
64
|
status: "fresh",
|
|
65
65
|
lastEvent: "cache-hit",
|
|
66
|
-
cacheHits: (
|
|
66
|
+
cacheHits: (u?.cacheHits ?? 0) + 1
|
|
67
67
|
});
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
|
-
case "EIDOS_CACHE_UPDATED":
|
|
70
|
+
case "EIDOS_CACHE_UPDATED":
|
|
71
71
|
n.updateResource(r, {
|
|
72
72
|
status: "fresh",
|
|
73
73
|
lastEvent: "cache-updated",
|
|
74
74
|
cachedAt: Date.now()
|
|
75
75
|
});
|
|
76
76
|
break;
|
|
77
|
-
|
|
78
|
-
case "EIDOS_NETWORK_ERROR": {
|
|
77
|
+
case "EIDOS_NETWORK_ERROR":
|
|
79
78
|
n.updateResource(r, {
|
|
80
79
|
status: "error",
|
|
81
80
|
lastEvent: "network-error"
|
|
82
81
|
});
|
|
83
82
|
break;
|
|
84
|
-
}
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
|
-
function h(
|
|
88
|
-
|
|
85
|
+
function h(e) {
|
|
86
|
+
g({
|
|
87
|
+
type: "EIDOS_SIMULATE_OFFLINE",
|
|
88
|
+
enabled: e
|
|
89
|
+
}), i.getState().setOnline(!e);
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
for (const
|
|
94
|
-
|
|
91
|
+
function S() {
|
|
92
|
+
const e = s?.active;
|
|
93
|
+
if (e) {
|
|
94
|
+
for (const t of o) e.postMessage(t);
|
|
95
|
+
o = [];
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
export {
|
|
98
|
-
|
|
99
|
+
l as getSwRegistration,
|
|
99
100
|
p as isBgSyncSupported,
|
|
100
101
|
E as registerBgSyncHandler,
|
|
101
102
|
w as registerServiceWorker,
|
|
102
|
-
|
|
103
|
+
g as sendToWorker,
|
|
103
104
|
h as setOfflineSimulation
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=sw-bridge.js.map
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _sweidos_eidos = require("@sweidos/eidos");
|
|
3
|
+
//#region src/testing.ts
|
|
4
|
+
/**
|
|
5
|
+
* @sweidos/eidos/testing
|
|
6
|
+
*
|
|
7
|
+
* Test helpers for Vitest, Jest, and Playwright.
|
|
8
|
+
* Import only in test files — never in production code.
|
|
9
|
+
*
|
|
10
|
+
* All helpers work at the JS layer (no real SW or Cache API required).
|
|
11
|
+
* Provide a `caches` mock when using getCachedEntry / clearEidosCache in jsdom.
|
|
12
|
+
*/
|
|
13
|
+
var _originalFetch = null;
|
|
14
|
+
/**
|
|
15
|
+
* Put Eidos into offline mode. Actions with `reliability: 'neverLose'` will
|
|
16
|
+
* be queued instead of executed. Call `mockOnline()` to restore.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* beforeEach(() => mockOffline())
|
|
20
|
+
* afterEach(() => mockOnline())
|
|
21
|
+
*/
|
|
22
|
+
function mockOffline(options = {}) {
|
|
23
|
+
_sweidos_eidos.useEidosStore.getState().setOnline(false);
|
|
24
|
+
if (options.stubFetch && _originalFetch === null) {
|
|
25
|
+
_originalFetch = globalThis.fetch;
|
|
26
|
+
globalThis.fetch = () => Promise.reject(/* @__PURE__ */ new TypeError("Network request failed (stubbed by @sweidos/eidos/testing)"));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Restore online mode. Removes any fetch stub installed by `mockOffline`.
|
|
31
|
+
*/
|
|
32
|
+
function mockOnline() {
|
|
33
|
+
_sweidos_eidos.useEidosStore.getState().setOnline(true);
|
|
34
|
+
if (_originalFetch !== null) {
|
|
35
|
+
globalThis.fetch = _originalFetch;
|
|
36
|
+
_originalFetch = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Replay the action queue immediately and return the result.
|
|
41
|
+
* Forces `isOnline = true` first so the replay is never skipped.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* mockOffline()
|
|
45
|
+
* await savePost(draft) // queued
|
|
46
|
+
* const result = await drainQueue()
|
|
47
|
+
* expect(result.succeeded).toBe(1)
|
|
48
|
+
*/
|
|
49
|
+
async function drainQueue() {
|
|
50
|
+
_sweidos_eidos.useEidosStore.getState().setOnline(true);
|
|
51
|
+
return (0, _sweidos_eidos.replayQueue)();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wait until the action queue contains no pending or replaying items.
|
|
55
|
+
* Resolves immediately if the queue is already clear.
|
|
56
|
+
* Rejects with a timeout error if items remain after `timeout` ms.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* mockOnline()
|
|
60
|
+
* await waitForQueueDrain()
|
|
61
|
+
* expect(getEidosState().queue).toHaveLength(0)
|
|
62
|
+
*/
|
|
63
|
+
function waitForQueueDrain(options = {}) {
|
|
64
|
+
const timeout = options.timeout ?? 5e3;
|
|
65
|
+
const interval = options.interval ?? 50;
|
|
66
|
+
const deadline = Date.now() + timeout;
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
function check() {
|
|
69
|
+
const { queue } = _sweidos_eidos.useEidosStore.getState();
|
|
70
|
+
const active = queue.filter((q) => q.status === "pending" || q.status === "replaying");
|
|
71
|
+
if (active.length === 0) {
|
|
72
|
+
resolve();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (Date.now() >= deadline) {
|
|
76
|
+
reject(/* @__PURE__ */ new Error(`[eidos/testing] waitForQueueDrain timed out after ${timeout}ms. ${active.length} item(s) still active (statuses: ${active.map((q) => q.status).join(", ")}).`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
setTimeout(check, interval);
|
|
80
|
+
}
|
|
81
|
+
check();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Default Eidos cache namespace written by the service worker. */
|
|
85
|
+
var EIDOS_CACHE_NAME = "eidos-resources-v1";
|
|
86
|
+
/**
|
|
87
|
+
* Read a cached Response for a URL from Eidos Cache Storage.
|
|
88
|
+
* Returns `undefined` if the entry is not cached.
|
|
89
|
+
*
|
|
90
|
+
* Requires the Cache API (`caches` global). In jsdom, provide a mock (e.g.
|
|
91
|
+
* `vitest-fetch-mock`, `jest-fetch-mock`, or a custom `setupFiles` polyfill).
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const res = await getCachedEntry('/api/user/1')
|
|
95
|
+
* expect(res).toBeDefined()
|
|
96
|
+
* expect(await res!.json()).toMatchObject({ id: 1 })
|
|
97
|
+
*/
|
|
98
|
+
async function getCachedEntry(url, cacheName = EIDOS_CACHE_NAME) {
|
|
99
|
+
if (typeof caches === "undefined") return void 0;
|
|
100
|
+
return await (await caches.open(cacheName)).match(url) ?? void 0;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Delete all entries from an Eidos cache namespace.
|
|
104
|
+
* Call in `afterEach` to prevent cache state leaking between tests.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* afterEach(() => clearEidosCache())
|
|
108
|
+
*/
|
|
109
|
+
async function clearEidosCache(cacheName = EIDOS_CACHE_NAME) {
|
|
110
|
+
if (typeof caches === "undefined") return;
|
|
111
|
+
await caches.delete(cacheName);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Full reset — call in `beforeEach` to start each test from a clean slate.
|
|
115
|
+
*
|
|
116
|
+
* - Resets runtime initialisation flag (`_resetEidos`) so `initEidos()` can
|
|
117
|
+
* be called again without the duplicate-init guard firing.
|
|
118
|
+
* - Clears IDB action queue and in-memory store queue.
|
|
119
|
+
* - Restores online state and removes any fetch stub from `mockOffline`.
|
|
120
|
+
* - Clears registered resource entries and resets SW status.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* beforeEach(async () => {
|
|
124
|
+
* await resetEidos()
|
|
125
|
+
* })
|
|
126
|
+
*/
|
|
127
|
+
async function resetEidos() {
|
|
128
|
+
(0, _sweidos_eidos._resetEidos)();
|
|
129
|
+
await (0, _sweidos_eidos.clearQueue)();
|
|
130
|
+
mockOnline();
|
|
131
|
+
_sweidos_eidos.useEidosStore.setState((s) => ({
|
|
132
|
+
...s,
|
|
133
|
+
resources: {},
|
|
134
|
+
swStatus: "idle",
|
|
135
|
+
swError: void 0
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Return a plain-object snapshot of the current Eidos store state.
|
|
140
|
+
* Useful for assertions without importing `useEidosStore` directly.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* expect(getEidosState().isOnline).toBe(false)
|
|
144
|
+
* expect(getEidosState().queue).toHaveLength(1)
|
|
145
|
+
*/
|
|
146
|
+
function getEidosState() {
|
|
147
|
+
const { isOnline, swStatus, swError, resources, queue } = _sweidos_eidos.useEidosStore.getState();
|
|
148
|
+
return {
|
|
149
|
+
isOnline,
|
|
150
|
+
swStatus,
|
|
151
|
+
swError,
|
|
152
|
+
resources,
|
|
153
|
+
queue
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
exports.EIDOS_CACHE_NAME = EIDOS_CACHE_NAME;
|
|
158
|
+
exports.clearEidosCache = clearEidosCache;
|
|
159
|
+
exports.drainQueue = drainQueue;
|
|
160
|
+
exports.getCachedEntry = getCachedEntry;
|
|
161
|
+
exports.getEidosState = getEidosState;
|
|
162
|
+
exports.mockOffline = mockOffline;
|
|
163
|
+
exports.mockOnline = mockOnline;
|
|
164
|
+
exports.resetEidos = resetEidos;
|
|
165
|
+
exports.waitForQueueDrain = waitForQueueDrain;
|
package/dist/testing.js
CHANGED
|
@@ -1,86 +1,156 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { _resetEidos, clearQueue, replayQueue, useEidosStore } from "@sweidos/eidos";
|
|
2
|
+
//#region src/testing.ts
|
|
3
|
+
/**
|
|
4
|
+
* @sweidos/eidos/testing
|
|
5
|
+
*
|
|
6
|
+
* Test helpers for Vitest, Jest, and Playwright.
|
|
7
|
+
* Import only in test files — never in production code.
|
|
8
|
+
*
|
|
9
|
+
* All helpers work at the JS layer (no real SW or Cache API required).
|
|
10
|
+
* Provide a `caches` mock when using getCachedEntry / clearEidosCache in jsdom.
|
|
11
|
+
*/
|
|
12
|
+
var _originalFetch = null;
|
|
13
|
+
/**
|
|
14
|
+
* Put Eidos into offline mode. Actions with `reliability: 'neverLose'` will
|
|
15
|
+
* be queued instead of executed. Call `mockOnline()` to restore.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* beforeEach(() => mockOffline())
|
|
19
|
+
* afterEach(() => mockOnline())
|
|
20
|
+
*/
|
|
3
21
|
function mockOffline(options = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
);
|
|
10
|
-
}
|
|
22
|
+
useEidosStore.getState().setOnline(false);
|
|
23
|
+
if (options.stubFetch && _originalFetch === null) {
|
|
24
|
+
_originalFetch = globalThis.fetch;
|
|
25
|
+
globalThis.fetch = () => Promise.reject(/* @__PURE__ */ new TypeError("Network request failed (stubbed by @sweidos/eidos/testing)"));
|
|
26
|
+
}
|
|
11
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Restore online mode. Removes any fetch stub installed by `mockOffline`.
|
|
30
|
+
*/
|
|
12
31
|
function mockOnline() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
useEidosStore.getState().setOnline(true);
|
|
33
|
+
if (_originalFetch !== null) {
|
|
34
|
+
globalThis.fetch = _originalFetch;
|
|
35
|
+
_originalFetch = null;
|
|
36
|
+
}
|
|
18
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Replay the action queue immediately and return the result.
|
|
40
|
+
* Forces `isOnline = true` first so the replay is never skipped.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* mockOffline()
|
|
44
|
+
* await savePost(draft) // queued
|
|
45
|
+
* const result = await drainQueue()
|
|
46
|
+
* expect(result.succeeded).toBe(1)
|
|
47
|
+
*/
|
|
19
48
|
async function drainQueue() {
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
useEidosStore.getState().setOnline(true);
|
|
50
|
+
return replayQueue();
|
|
22
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Wait until the action queue contains no pending or replaying items.
|
|
54
|
+
* Resolves immediately if the queue is already clear.
|
|
55
|
+
* Rejects with a timeout error if items remain after `timeout` ms.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* mockOnline()
|
|
59
|
+
* await waitForQueueDrain()
|
|
60
|
+
* expect(getEidosState().queue).toHaveLength(0)
|
|
61
|
+
*/
|
|
23
62
|
function waitForQueueDrain(options = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
setTimeout(check, interval);
|
|
46
|
-
}
|
|
47
|
-
check();
|
|
48
|
-
});
|
|
63
|
+
const timeout = options.timeout ?? 5e3;
|
|
64
|
+
const interval = options.interval ?? 50;
|
|
65
|
+
const deadline = Date.now() + timeout;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
function check() {
|
|
68
|
+
const { queue } = useEidosStore.getState();
|
|
69
|
+
const active = queue.filter((q) => q.status === "pending" || q.status === "replaying");
|
|
70
|
+
if (active.length === 0) {
|
|
71
|
+
resolve();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (Date.now() >= deadline) {
|
|
75
|
+
reject(/* @__PURE__ */ new Error(`[eidos/testing] waitForQueueDrain timed out after ${timeout}ms. ${active.length} item(s) still active (statuses: ${active.map((q) => q.status).join(", ")}).`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setTimeout(check, interval);
|
|
79
|
+
}
|
|
80
|
+
check();
|
|
81
|
+
});
|
|
49
82
|
}
|
|
50
|
-
|
|
83
|
+
/** Default Eidos cache namespace written by the service worker. */
|
|
84
|
+
var EIDOS_CACHE_NAME = "eidos-resources-v1";
|
|
85
|
+
/**
|
|
86
|
+
* Read a cached Response for a URL from Eidos Cache Storage.
|
|
87
|
+
* Returns `undefined` if the entry is not cached.
|
|
88
|
+
*
|
|
89
|
+
* Requires the Cache API (`caches` global). In jsdom, provide a mock (e.g.
|
|
90
|
+
* `vitest-fetch-mock`, `jest-fetch-mock`, or a custom `setupFiles` polyfill).
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* const res = await getCachedEntry('/api/user/1')
|
|
94
|
+
* expect(res).toBeDefined()
|
|
95
|
+
* expect(await res!.json()).toMatchObject({ id: 1 })
|
|
96
|
+
*/
|
|
51
97
|
async function getCachedEntry(url, cacheName = EIDOS_CACHE_NAME) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const match = await cache.match(url);
|
|
55
|
-
return match ?? void 0;
|
|
98
|
+
if (typeof caches === "undefined") return void 0;
|
|
99
|
+
return await (await caches.open(cacheName)).match(url) ?? void 0;
|
|
56
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Delete all entries from an Eidos cache namespace.
|
|
103
|
+
* Call in `afterEach` to prevent cache state leaking between tests.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* afterEach(() => clearEidosCache())
|
|
107
|
+
*/
|
|
57
108
|
async function clearEidosCache(cacheName = EIDOS_CACHE_NAME) {
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
if (typeof caches === "undefined") return;
|
|
110
|
+
await caches.delete(cacheName);
|
|
60
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Full reset — call in `beforeEach` to start each test from a clean slate.
|
|
114
|
+
*
|
|
115
|
+
* - Resets runtime initialisation flag (`_resetEidos`) so `initEidos()` can
|
|
116
|
+
* be called again without the duplicate-init guard firing.
|
|
117
|
+
* - Clears IDB action queue and in-memory store queue.
|
|
118
|
+
* - Restores online state and removes any fetch stub from `mockOffline`.
|
|
119
|
+
* - Clears registered resource entries and resets SW status.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* beforeEach(async () => {
|
|
123
|
+
* await resetEidos()
|
|
124
|
+
* })
|
|
125
|
+
*/
|
|
61
126
|
async function resetEidos() {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
127
|
+
_resetEidos();
|
|
128
|
+
await clearQueue();
|
|
129
|
+
mockOnline();
|
|
130
|
+
useEidosStore.setState((s) => ({
|
|
131
|
+
...s,
|
|
132
|
+
resources: {},
|
|
133
|
+
swStatus: "idle",
|
|
134
|
+
swError: void 0
|
|
135
|
+
}));
|
|
71
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Return a plain-object snapshot of the current Eidos store state.
|
|
139
|
+
* Useful for assertions without importing `useEidosStore` directly.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* expect(getEidosState().isOnline).toBe(false)
|
|
143
|
+
* expect(getEidosState().queue).toHaveLength(1)
|
|
144
|
+
*/
|
|
72
145
|
function getEidosState() {
|
|
73
|
-
|
|
74
|
-
|
|
146
|
+
const { isOnline, swStatus, swError, resources, queue } = useEidosStore.getState();
|
|
147
|
+
return {
|
|
148
|
+
isOnline,
|
|
149
|
+
swStatus,
|
|
150
|
+
swError,
|
|
151
|
+
resources,
|
|
152
|
+
queue
|
|
153
|
+
};
|
|
75
154
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
clearEidosCache,
|
|
79
|
-
drainQueue,
|
|
80
|
-
getCachedEntry,
|
|
81
|
-
getEidosState,
|
|
82
|
-
mockOffline,
|
|
83
|
-
mockOnline,
|
|
84
|
-
resetEidos,
|
|
85
|
-
waitForQueueDrain
|
|
86
|
-
};
|
|
155
|
+
//#endregion
|
|
156
|
+
export { EIDOS_CACHE_NAME, clearEidosCache, drainQueue, getCachedEntry, getEidosState, mockOffline, mockOnline, resetEidos, waitForQueueDrain };
|
package/dist/version.js
CHANGED
package/dist/vite.cjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let fs = require("fs");
|
|
3
|
+
let path = require("path");
|
|
4
|
+
//#region src/vite.ts
|
|
5
|
+
/**
|
|
6
|
+
* Vite plugin for Eidos.
|
|
7
|
+
*
|
|
8
|
+
* Automatically copies `eidos-sw.js` from the installed package into your
|
|
9
|
+
* `public/` directory on every build and dev-server start, so the service
|
|
10
|
+
* worker is always in sync with the installed package version.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // vite.config.ts
|
|
15
|
+
* import { eidos } from '@sweidos/eidos/vite'
|
|
16
|
+
* import { defineConfig } from 'vite'
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* plugins: [eidos()],
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function eidos(options) {
|
|
24
|
+
const swDest = options?.swDest ?? "public/eidos-sw.js";
|
|
25
|
+
function copySW(root) {
|
|
26
|
+
const src = (0, path.resolve)(root, "node_modules/@sweidos/eidos/dist/eidos-sw.js");
|
|
27
|
+
if (!(0, fs.existsSync)(src)) {
|
|
28
|
+
console.warn("[eidos-vite] Could not locate eidos-sw.js in node_modules. Make sure @sweidos/eidos is installed.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const dest = (0, path.resolve)(root, swDest);
|
|
32
|
+
const destDir = (0, path.dirname)(dest);
|
|
33
|
+
if (!(0, fs.existsSync)(destDir)) (0, fs.mkdirSync)(destDir, { recursive: true });
|
|
34
|
+
(0, fs.copyFileSync)(src, dest);
|
|
35
|
+
console.log(`[eidos-vite] eidos-sw.js → ${swDest} ✓`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
name: "eidos",
|
|
39
|
+
buildStart() {
|
|
40
|
+
copySW(process.cwd());
|
|
41
|
+
},
|
|
42
|
+
configureServer(server) {
|
|
43
|
+
copySW(server.config.root);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
exports.eidos = eidos;
|
package/dist/vite.js
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
import { existsSync, mkdirSync
|
|
2
|
-
import {
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { dirname, resolve } from "path";
|
|
3
|
+
//#region src/vite.ts
|
|
4
|
+
/**
|
|
5
|
+
* Vite plugin for Eidos.
|
|
6
|
+
*
|
|
7
|
+
* Automatically copies `eidos-sw.js` from the installed package into your
|
|
8
|
+
* `public/` directory on every build and dev-server start, so the service
|
|
9
|
+
* worker is always in sync with the installed package version.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // vite.config.ts
|
|
14
|
+
* import { eidos } from '@sweidos/eidos/vite'
|
|
15
|
+
* import { defineConfig } from 'vite'
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* plugins: [eidos()],
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
3
22
|
function eidos(options) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
};
|
|
23
|
+
const swDest = options?.swDest ?? "public/eidos-sw.js";
|
|
24
|
+
function copySW(root) {
|
|
25
|
+
const src = resolve(root, "node_modules/@sweidos/eidos/dist/eidos-sw.js");
|
|
26
|
+
if (!existsSync(src)) {
|
|
27
|
+
console.warn("[eidos-vite] Could not locate eidos-sw.js in node_modules. Make sure @sweidos/eidos is installed.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const dest = resolve(root, swDest);
|
|
31
|
+
const destDir = dirname(dest);
|
|
32
|
+
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
33
|
+
copyFileSync(src, dest);
|
|
34
|
+
console.log(`[eidos-vite] eidos-sw.js → ${swDest} ✓`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name: "eidos",
|
|
38
|
+
buildStart() {
|
|
39
|
+
copySW(process.cwd());
|
|
40
|
+
},
|
|
41
|
+
configureServer(server) {
|
|
42
|
+
copySW(server.config.root);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
28
45
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { eidos };
|