@sweidos/eidos 1.0.34 → 1.2.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 +171 -89
- package/dist/action.js +197 -91
- package/dist/async-storage-adapter.js +15 -12
- package/dist/cli.js +102 -0
- package/dist/devtools.js +1009 -551
- package/dist/eidos-sw.js +280 -188
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +135 -18
- package/dist/index.js +46 -42
- package/dist/nextjs.js +1 -10
- package/dist/push.cjs +120 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +113 -0
- 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 +39 -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 +64 -49
- 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 +57 -28
- 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/devtools.js
CHANGED
|
@@ -1,620 +1,1078 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useCallback, useState, useSyncExternalStore } from "react";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
//#region src/store-slices.ts
|
|
5
|
+
function createResourceActions(set) {
|
|
6
|
+
return {
|
|
7
|
+
registerResource: (url, entry) => set((s) => ({ resources: {
|
|
8
|
+
...s.resources,
|
|
9
|
+
[url]: entry
|
|
10
|
+
} })),
|
|
11
|
+
updateResource: (url, update) => set((s) => ({ resources: {
|
|
12
|
+
...s.resources,
|
|
13
|
+
[url]: s.resources[url] ? {
|
|
14
|
+
...s.resources[url],
|
|
15
|
+
...update
|
|
16
|
+
} : s.resources[url]
|
|
17
|
+
} })),
|
|
18
|
+
unregisterResource: (url) => set((s) => ({ resources: Object.fromEntries(Object.entries(s.resources).filter(([k]) => k !== url)) }))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createQueueActions(set) {
|
|
22
|
+
return {
|
|
23
|
+
addQueueItem: (item) => set((s) => ({ queue: [...s.queue, item] })),
|
|
24
|
+
updateQueueItem: (id, update) => set((s) => ({ queue: s.queue.map((item) => item.id === id ? {
|
|
25
|
+
...item,
|
|
26
|
+
...update
|
|
27
|
+
} : item) })),
|
|
28
|
+
batchUpdateQueueItems: (updates) => set((s) => {
|
|
29
|
+
const map = new Map(updates.map((u) => [u.id, u.update]));
|
|
30
|
+
return { queue: s.queue.map((item) => {
|
|
31
|
+
const u = map.get(item.id);
|
|
32
|
+
return u ? {
|
|
33
|
+
...item,
|
|
34
|
+
...u
|
|
35
|
+
} : item;
|
|
36
|
+
}) };
|
|
37
|
+
}),
|
|
38
|
+
removeQueueItem: (id) => set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),
|
|
39
|
+
hydrateQueue: (items) => set(() => ({ queue: items }))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/store.ts
|
|
44
|
+
var _state;
|
|
45
|
+
var _listeners = /* @__PURE__ */ new Set();
|
|
6
46
|
function _notify() {
|
|
7
|
-
|
|
47
|
+
_listeners.forEach((fn) => fn());
|
|
8
48
|
}
|
|
9
49
|
function _set(updater) {
|
|
10
|
-
|
|
11
|
-
|
|
50
|
+
_state = {
|
|
51
|
+
..._state,
|
|
52
|
+
...updater(_state)
|
|
53
|
+
};
|
|
54
|
+
_notify();
|
|
12
55
|
}
|
|
13
56
|
_state = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url]
|
|
27
|
-
}
|
|
28
|
-
})),
|
|
29
|
-
unregisterResource: (url) => _set((s) => ({
|
|
30
|
-
resources: Object.fromEntries(
|
|
31
|
-
Object.entries(s.resources).filter(([k]) => k !== url)
|
|
32
|
-
)
|
|
33
|
-
})),
|
|
34
|
-
addQueueItem: (item) => _set((s) => ({ queue: [...s.queue, item] })),
|
|
35
|
-
updateQueueItem: (id, update) => _set((s) => ({
|
|
36
|
-
queue: s.queue.map((item) => item.id === id ? { ...item, ...update } : item)
|
|
37
|
-
})),
|
|
38
|
-
batchUpdateQueueItems: (updates) => _set((s) => {
|
|
39
|
-
const map = new Map(updates.map((u) => [u.id, u.update]));
|
|
40
|
-
return {
|
|
41
|
-
queue: s.queue.map((item) => {
|
|
42
|
-
const u = map.get(item.id);
|
|
43
|
-
return u ? { ...item, ...u } : item;
|
|
44
|
-
})
|
|
45
|
-
};
|
|
46
|
-
}),
|
|
47
|
-
removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),
|
|
48
|
-
hydrateQueue: (items) => _set(() => ({ queue: items }))
|
|
57
|
+
isOnline: typeof navigator === "undefined" || navigator.onLine !== false,
|
|
58
|
+
swStatus: "idle",
|
|
59
|
+
swError: void 0,
|
|
60
|
+
resources: {},
|
|
61
|
+
queue: [],
|
|
62
|
+
setOnline: (isOnline) => _set(() => ({ isOnline })),
|
|
63
|
+
setSwStatus: (swStatus, swError) => _set(() => ({
|
|
64
|
+
swStatus,
|
|
65
|
+
swError
|
|
66
|
+
})),
|
|
67
|
+
...createResourceActions(_set),
|
|
68
|
+
...createQueueActions(_set)
|
|
49
69
|
};
|
|
50
70
|
function _getState() {
|
|
51
|
-
|
|
71
|
+
return _state;
|
|
52
72
|
}
|
|
53
73
|
function _subscribe(listener) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
_listeners.add(listener);
|
|
75
|
+
return () => {
|
|
76
|
+
_listeners.delete(listener);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var useEidosStore = {
|
|
80
|
+
getState: _getState,
|
|
81
|
+
subscribe: _subscribe,
|
|
82
|
+
setState: (partial) => {
|
|
83
|
+
const update = typeof partial === "function" ? partial(_state) : partial;
|
|
84
|
+
_state = {
|
|
85
|
+
..._state,
|
|
86
|
+
...update
|
|
87
|
+
};
|
|
88
|
+
_notify();
|
|
89
|
+
}
|
|
68
90
|
};
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/react/hooks.ts
|
|
69
93
|
function useStore(selector) {
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
const fn = selector ?? ((s) => s);
|
|
95
|
+
return useSyncExternalStore(useEidosStore.subscribe, () => fn(useEidosStore.getState()));
|
|
72
96
|
}
|
|
97
|
+
/** All registered resources — only re-renders when the resources map changes, not on queue mutations. */
|
|
73
98
|
function useEidosResources() {
|
|
74
|
-
|
|
99
|
+
return useStore((s) => s.resources);
|
|
75
100
|
}
|
|
101
|
+
/** The current action queue. */
|
|
76
102
|
function useEidosQueue() {
|
|
77
|
-
|
|
103
|
+
return useStore((s) => s.queue);
|
|
78
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Online + SW status — cheap subscription, safe to use in header components.
|
|
107
|
+
* Three separate primitive selectors so each only triggers a re-render when
|
|
108
|
+
* its own value changes (no object-reference churn from a combined selector).
|
|
109
|
+
*/
|
|
79
110
|
function useEidosStatus() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
return {
|
|
112
|
+
isOnline: useStore((s) => s.isOnline),
|
|
113
|
+
swStatus: useStore((s) => s.swStatus),
|
|
114
|
+
swError: useStore((s) => s.swError)
|
|
115
|
+
};
|
|
84
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Queue counts — single subscription, single loop. Re-renders only when a
|
|
119
|
+
* count changes, not on every queue mutation. Use for badges and status bars
|
|
120
|
+
* instead of `useEidosQueue()` when you only need numbers, not full items.
|
|
121
|
+
*/
|
|
85
122
|
function useEidosQueueStats() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
const [p, f, r, t] = useStore((s) => {
|
|
124
|
+
let pending = 0, failed = 0, replaying = 0;
|
|
125
|
+
for (const q of s.queue) if (q.status === "pending") pending++;
|
|
126
|
+
else if (q.status === "failed") failed++;
|
|
127
|
+
else if (q.status === "replaying") replaying++;
|
|
128
|
+
return `${pending},${failed},${replaying},${s.queue.length}`;
|
|
129
|
+
}).split(",");
|
|
130
|
+
return {
|
|
131
|
+
pending: +p,
|
|
132
|
+
failed: +f,
|
|
133
|
+
replaying: +r,
|
|
134
|
+
total: +t
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/sw-bridge.ts
|
|
139
|
+
var _registration = null;
|
|
140
|
+
var _pendingMessages = [];
|
|
141
|
+
function sendToWorker(message) {
|
|
142
|
+
const sw = _registration?.active;
|
|
143
|
+
if (sw) sw.postMessage(message);
|
|
144
|
+
else _pendingMessages.push(message);
|
|
97
145
|
}
|
|
98
146
|
function setOfflineSimulation(enabled) {
|
|
99
|
-
|
|
147
|
+
sendToWorker({
|
|
148
|
+
type: "EIDOS_SIMULATE_OFFLINE",
|
|
149
|
+
enabled
|
|
150
|
+
});
|
|
151
|
+
useEidosStore.getState().setOnline(!enabled);
|
|
100
152
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/idb.ts
|
|
155
|
+
var DB_NAME = "eidos";
|
|
156
|
+
var DB_VERSION = 1;
|
|
157
|
+
var QUEUE_STORE = "action-queue";
|
|
158
|
+
var _db = null;
|
|
105
159
|
function openDB() {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
160
|
+
if (_db) return Promise.resolve(_db);
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
163
|
+
req.onupgradeneeded = (event) => {
|
|
164
|
+
const db = event.target.result;
|
|
165
|
+
if (!db.objectStoreNames.contains(QUEUE_STORE)) {
|
|
166
|
+
const store = db.createObjectStore(QUEUE_STORE, { keyPath: "id" });
|
|
167
|
+
store.createIndex("status", "status", { unique: false });
|
|
168
|
+
store.createIndex("actionId", "actionId", { unique: false });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
req.onsuccess = () => {
|
|
172
|
+
_db = req.result;
|
|
173
|
+
resolve(req.result);
|
|
174
|
+
};
|
|
175
|
+
req.onerror = () => reject(req.error);
|
|
176
|
+
});
|
|
123
177
|
}
|
|
124
178
|
async function idbAddToQueue(item) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
179
|
+
const db = await openDB();
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const tx = db.transaction(QUEUE_STORE, "readwrite");
|
|
182
|
+
tx.objectStore(QUEUE_STORE).add(item);
|
|
183
|
+
tx.oncomplete = () => resolve();
|
|
184
|
+
tx.onerror = () => reject(tx.error);
|
|
185
|
+
});
|
|
132
186
|
}
|
|
133
187
|
async function idbGetQueue() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
188
|
+
const db = await openDB();
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const req = db.transaction(QUEUE_STORE, "readonly").objectStore(QUEUE_STORE).getAll();
|
|
191
|
+
req.onsuccess = () => resolve(req.result);
|
|
192
|
+
req.onerror = () => reject(req.error);
|
|
193
|
+
});
|
|
141
194
|
}
|
|
142
195
|
async function idbUpdateQueueItem(id, update) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
const db = await openDB();
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const tx = db.transaction(QUEUE_STORE, "readwrite");
|
|
199
|
+
const store = tx.objectStore(QUEUE_STORE);
|
|
200
|
+
const get = store.get(id);
|
|
201
|
+
get.onsuccess = () => {
|
|
202
|
+
if (get.result) store.put({
|
|
203
|
+
...get.result,
|
|
204
|
+
...update
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
tx.oncomplete = () => resolve();
|
|
208
|
+
tx.onerror = () => reject(tx.error);
|
|
209
|
+
});
|
|
156
210
|
}
|
|
157
211
|
async function idbRemoveFromQueue(id) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
212
|
+
const db = await openDB();
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const tx = db.transaction(QUEUE_STORE, "readwrite");
|
|
215
|
+
tx.objectStore(QUEUE_STORE).delete(id);
|
|
216
|
+
tx.oncomplete = () => resolve();
|
|
217
|
+
tx.onerror = () => reject(tx.error);
|
|
218
|
+
});
|
|
165
219
|
}
|
|
166
220
|
async function idbGetPendingItems() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
cursorToArray("pending"),
|
|
186
|
-
cursorToArray("failed")
|
|
187
|
-
]);
|
|
188
|
-
return [...pending, ...failed];
|
|
221
|
+
const db = await openDB();
|
|
222
|
+
function cursorToArray(status) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const index = db.transaction(QUEUE_STORE, "readonly").objectStore(QUEUE_STORE).index("status");
|
|
225
|
+
const items = [];
|
|
226
|
+
const req = index.openCursor(IDBKeyRange.only(status));
|
|
227
|
+
req.onsuccess = (e) => {
|
|
228
|
+
const cursor = e.target.result;
|
|
229
|
+
if (cursor) {
|
|
230
|
+
items.push(cursor.value);
|
|
231
|
+
cursor.continue();
|
|
232
|
+
} else resolve(items);
|
|
233
|
+
};
|
|
234
|
+
req.onerror = () => reject(req.error);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const [pending, failed] = await Promise.all([cursorToArray("pending"), cursorToArray("failed")]);
|
|
238
|
+
return [...pending, ...failed];
|
|
189
239
|
}
|
|
190
240
|
async function idbClearQueue() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
remove: (id) => idbRemoveFromQueue(id),
|
|
208
|
-
clear: () => idbClearQueue()
|
|
241
|
+
const db = await openDB();
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
const tx = db.transaction(QUEUE_STORE, "readwrite");
|
|
244
|
+
tx.objectStore(QUEUE_STORE).clear();
|
|
245
|
+
tx.oncomplete = () => resolve();
|
|
246
|
+
tx.onerror = () => reject(tx.error);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** IndexedDB-backed QueueStorage implementation (default for browser environments). */
|
|
250
|
+
var idbQueueStorage = {
|
|
251
|
+
add: idbAddToQueue,
|
|
252
|
+
getAll: idbGetQueue,
|
|
253
|
+
getPending: idbGetPendingItems,
|
|
254
|
+
update: idbUpdateQueueItem,
|
|
255
|
+
remove: idbRemoveFromQueue,
|
|
256
|
+
clear: idbClearQueue
|
|
209
257
|
};
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/queue-storage.ts
|
|
260
|
+
var _storage = null;
|
|
261
|
+
function _getQueueStorage() {
|
|
262
|
+
return _storage;
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/action.ts
|
|
266
|
+
var _actionRegistry = /* @__PURE__ */ new Map();
|
|
267
|
+
var _rollbackRegistry = /* @__PURE__ */ new Map();
|
|
268
|
+
var _conflictRegistry = /* @__PURE__ */ new Map();
|
|
269
|
+
var _conflictConfigRegistry = /* @__PURE__ */ new Map();
|
|
270
|
+
var _configRegistry = /* @__PURE__ */ new Map();
|
|
271
|
+
var _inflightControllers = /* @__PURE__ */ new Map();
|
|
210
272
|
function qs() {
|
|
211
|
-
|
|
273
|
+
return _getQueueStorage() ?? idbQueueStorage;
|
|
274
|
+
}
|
|
275
|
+
function callWithContext(fn, args, ctx) {
|
|
276
|
+
return fn(...args, ctx);
|
|
277
|
+
}
|
|
278
|
+
function isAbortError(err) {
|
|
279
|
+
return err instanceof DOMException && err.name === "AbortError";
|
|
212
280
|
}
|
|
213
281
|
function isClientError(err) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
282
|
+
if (err instanceof Response) return err.status >= 400 && err.status < 500;
|
|
283
|
+
if (typeof err === "object" && err !== null) {
|
|
284
|
+
const s = err.status;
|
|
285
|
+
if (typeof s === "number") return s >= 400 && s < 500;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
220
288
|
}
|
|
221
289
|
function backoffMs(retryCount) {
|
|
222
|
-
|
|
223
|
-
|
|
290
|
+
return Math.min(2e3 * 2 ** retryCount, 3e5) * (.8 + Math.random() * .4);
|
|
291
|
+
}
|
|
292
|
+
function emptyReplayResult() {
|
|
293
|
+
return {
|
|
294
|
+
attempted: 0,
|
|
295
|
+
succeeded: 0,
|
|
296
|
+
failed: 0,
|
|
297
|
+
retrying: 0,
|
|
298
|
+
skipped: 0,
|
|
299
|
+
conflicted: 0,
|
|
300
|
+
cancelled: 0
|
|
301
|
+
};
|
|
224
302
|
}
|
|
225
|
-
|
|
303
|
+
var _replaying = false;
|
|
304
|
+
var REPLAY_LOCK_NAME = "eidos-queue-replay";
|
|
226
305
|
async function replayQueue() {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
306
|
+
const store = useEidosStore.getState();
|
|
307
|
+
if (!store.isOnline) return emptyReplayResult();
|
|
308
|
+
if (typeof navigator !== "undefined" && navigator.locks) return navigator.locks.request(REPLAY_LOCK_NAME, { ifAvailable: true }, async (lock) => {
|
|
309
|
+
if (!lock) return emptyReplayResult();
|
|
310
|
+
return _doReplayQueue(store);
|
|
311
|
+
});
|
|
312
|
+
if (_replaying) return emptyReplayResult();
|
|
313
|
+
_replaying = true;
|
|
314
|
+
try {
|
|
315
|
+
return await _doReplayQueue(store);
|
|
316
|
+
} finally {
|
|
317
|
+
_replaying = false;
|
|
318
|
+
}
|
|
237
319
|
}
|
|
238
320
|
async function _replayItem(item, store) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
321
|
+
const fn = _actionRegistry.get(item.actionId);
|
|
322
|
+
if (!fn) return "skipped";
|
|
323
|
+
const cancellable = _configRegistry.get(item.actionId)?.cancellable;
|
|
324
|
+
let signal;
|
|
325
|
+
if (cancellable) {
|
|
326
|
+
const controller = new AbortController();
|
|
327
|
+
_inflightControllers.set(item.idempotencyKey, controller);
|
|
328
|
+
signal = controller.signal;
|
|
329
|
+
}
|
|
330
|
+
const ctx = {
|
|
331
|
+
idempotencyKey: item.idempotencyKey,
|
|
332
|
+
attempt: item.retryCount,
|
|
333
|
+
signal
|
|
334
|
+
};
|
|
335
|
+
try {
|
|
336
|
+
await callWithContext(fn, item.args, ctx);
|
|
337
|
+
const completedAt = Date.now();
|
|
338
|
+
store.updateQueueItem(item.id, {
|
|
339
|
+
status: "succeeded",
|
|
340
|
+
completedAt
|
|
341
|
+
});
|
|
342
|
+
await qs().update(item.id, {
|
|
343
|
+
status: "succeeded",
|
|
344
|
+
completedAt
|
|
345
|
+
});
|
|
346
|
+
setTimeout(() => {
|
|
347
|
+
store.removeQueueItem(item.id);
|
|
348
|
+
qs().remove(item.id);
|
|
349
|
+
}, 3e3);
|
|
350
|
+
return "succeeded";
|
|
351
|
+
} catch (err) {
|
|
352
|
+
if (isAbortError(err)) {
|
|
353
|
+
store.removeQueueItem(item.id);
|
|
354
|
+
await qs().remove(item.id);
|
|
355
|
+
return "cancelled";
|
|
356
|
+
}
|
|
357
|
+
if (isClientError(err)) {
|
|
358
|
+
const conflictConfig = _conflictConfigRegistry.get(item.actionId);
|
|
359
|
+
let resolution;
|
|
360
|
+
if (conflictConfig) switch (conflictConfig.strategy) {
|
|
361
|
+
case "serverWins":
|
|
362
|
+
resolution = "skip";
|
|
363
|
+
break;
|
|
364
|
+
case "clientWins":
|
|
365
|
+
case "lastWriteWins":
|
|
366
|
+
resolution = "retry";
|
|
367
|
+
break;
|
|
368
|
+
case "merge":
|
|
369
|
+
case "custom": {
|
|
370
|
+
const ctx = {
|
|
371
|
+
error: err,
|
|
372
|
+
args: item.args,
|
|
373
|
+
attempt: item.retryCount,
|
|
374
|
+
idempotencyKey: item.idempotencyKey
|
|
375
|
+
};
|
|
376
|
+
resolution = conflictConfig.resolve?.(ctx) ?? "retry";
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
const onConflict = _conflictRegistry.get(item.actionId);
|
|
382
|
+
if (onConflict) resolution = onConflict(err, item.args);
|
|
383
|
+
}
|
|
384
|
+
if (resolution === "skip") {
|
|
385
|
+
store.removeQueueItem(item.id);
|
|
386
|
+
await qs().remove(item.id);
|
|
387
|
+
return "conflicted";
|
|
388
|
+
}
|
|
389
|
+
if (resolution && typeof resolution === "object") {
|
|
390
|
+
item.args = resolution.resolved;
|
|
391
|
+
store.updateQueueItem(item.id, { args: resolution.resolved });
|
|
392
|
+
await qs().update(item.id, { args: resolution.resolved });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const retryCount = item.retryCount + 1;
|
|
396
|
+
if (retryCount >= item.maxRetries) {
|
|
397
|
+
store.updateQueueItem(item.id, {
|
|
398
|
+
status: "failed",
|
|
399
|
+
error: String(err),
|
|
400
|
+
retryCount
|
|
401
|
+
});
|
|
402
|
+
await qs().update(item.id, {
|
|
403
|
+
status: "failed",
|
|
404
|
+
error: String(err),
|
|
405
|
+
retryCount
|
|
406
|
+
});
|
|
407
|
+
_rollbackRegistry.get(item.actionId)?.(...item.args);
|
|
408
|
+
return "failed";
|
|
409
|
+
} else {
|
|
410
|
+
const nextRetryAt = Date.now() + backoffMs(retryCount);
|
|
411
|
+
store.updateQueueItem(item.id, {
|
|
412
|
+
status: "pending",
|
|
413
|
+
retryCount,
|
|
414
|
+
nextRetryAt
|
|
415
|
+
});
|
|
416
|
+
await qs().update(item.id, {
|
|
417
|
+
status: "pending",
|
|
418
|
+
retryCount,
|
|
419
|
+
nextRetryAt
|
|
420
|
+
});
|
|
421
|
+
return "retrying";
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
if (cancellable) _inflightControllers.delete(item.idempotencyKey);
|
|
425
|
+
}
|
|
277
426
|
}
|
|
278
427
|
async function _replayTier(items, store, result) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
428
|
+
if (items.length === 0) return;
|
|
429
|
+
const replayable = items.filter((item) => _actionRegistry.has(item.actionId));
|
|
430
|
+
result.skipped += items.length - replayable.length;
|
|
431
|
+
if (replayable.length > 0) {
|
|
432
|
+
store.batchUpdateQueueItems(replayable.map((item) => ({
|
|
433
|
+
id: item.id,
|
|
434
|
+
update: { status: "replaying" }
|
|
435
|
+
})));
|
|
436
|
+
for (const item of replayable) qs().update(item.id, { status: "replaying" });
|
|
437
|
+
}
|
|
438
|
+
const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)));
|
|
439
|
+
for (const o of outcomes) {
|
|
440
|
+
const outcome = o.status === "fulfilled" ? o.value : "failed";
|
|
441
|
+
if (outcome === "skipped") result.skipped++;
|
|
442
|
+
else if (outcome === "conflicted") result.conflicted++;
|
|
443
|
+
else if (outcome === "cancelled") result.cancelled++;
|
|
444
|
+
else {
|
|
445
|
+
result.attempted++;
|
|
446
|
+
result[outcome]++;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
300
449
|
}
|
|
301
450
|
async function _doReplayQueue(store) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return result;
|
|
451
|
+
const candidates = await qs().getPending();
|
|
452
|
+
const now = Date.now();
|
|
453
|
+
const pending = candidates.filter((item) => item.retryCount < item.maxRetries && (!item.nextRetryAt || item.nextRetryAt <= now));
|
|
454
|
+
const result = emptyReplayResult();
|
|
455
|
+
for (const tier of [
|
|
456
|
+
"high",
|
|
457
|
+
"normal",
|
|
458
|
+
"low"
|
|
459
|
+
]) await _replayTier(pending.filter((item) => (item.priority ?? "normal") === tier), store, result);
|
|
460
|
+
return result;
|
|
313
461
|
}
|
|
462
|
+
/** Remove all items from the action queue (storage + in-memory store). */
|
|
314
463
|
async function clearQueue() {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
464
|
+
await qs().clear();
|
|
465
|
+
useEidosStore.getState().hydrateQueue([]);
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/react/Devtools.tsx
|
|
469
|
+
var C = {
|
|
470
|
+
bg: "#0f1117",
|
|
471
|
+
surface: "#1a1d27",
|
|
472
|
+
border: "#2a2d3a",
|
|
473
|
+
text: "#e2e8f0",
|
|
474
|
+
muted: "#64748b",
|
|
475
|
+
green: "#22c55e",
|
|
476
|
+
red: "#ef4444",
|
|
477
|
+
yellow: "#f59e0b",
|
|
478
|
+
blue: "#3b82f6",
|
|
479
|
+
purple: "#a855f7",
|
|
480
|
+
cyan: "#06b6d4"
|
|
330
481
|
};
|
|
331
482
|
function pill(color) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
483
|
+
return {
|
|
484
|
+
display: "inline-flex",
|
|
485
|
+
alignItems: "center",
|
|
486
|
+
padding: "1px 7px",
|
|
487
|
+
borderRadius: 9999,
|
|
488
|
+
fontSize: 10,
|
|
489
|
+
fontWeight: 600,
|
|
490
|
+
background: `${color}22`,
|
|
491
|
+
color,
|
|
492
|
+
border: `1px solid ${color}44`,
|
|
493
|
+
fontFamily: "inherit"
|
|
494
|
+
};
|
|
344
495
|
}
|
|
345
496
|
function btn(variant = "ghost") {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
497
|
+
const base = {
|
|
498
|
+
cursor: "pointer",
|
|
499
|
+
border: "none",
|
|
500
|
+
borderRadius: 6,
|
|
501
|
+
padding: "3px 10px",
|
|
502
|
+
fontSize: 11,
|
|
503
|
+
fontWeight: 500,
|
|
504
|
+
fontFamily: "inherit",
|
|
505
|
+
display: "inline-flex",
|
|
506
|
+
alignItems: "center",
|
|
507
|
+
gap: 5,
|
|
508
|
+
transition: "background-color 0.15s, color 0.15s"
|
|
509
|
+
};
|
|
510
|
+
if (variant === "danger") return {
|
|
511
|
+
...base,
|
|
512
|
+
background: `${C.red}22`,
|
|
513
|
+
color: C.red
|
|
514
|
+
};
|
|
515
|
+
if (variant === "primary") return {
|
|
516
|
+
...base,
|
|
517
|
+
background: `${C.blue}22`,
|
|
518
|
+
color: C.blue
|
|
519
|
+
};
|
|
520
|
+
return {
|
|
521
|
+
...base,
|
|
522
|
+
background: C.surface,
|
|
523
|
+
color: C.muted
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
var focusRing = {
|
|
527
|
+
outline: `2px solid ${C.cyan}`,
|
|
528
|
+
outlineOffset: 1
|
|
529
|
+
};
|
|
530
|
+
function withFocusRing(handlers = {}) {
|
|
531
|
+
return {
|
|
532
|
+
onFocus: (e) => {
|
|
533
|
+
Object.assign(e.currentTarget.style, focusRing);
|
|
534
|
+
handlers.onFocus?.();
|
|
535
|
+
},
|
|
536
|
+
onBlur: (e) => {
|
|
537
|
+
e.currentTarget.style.outline = "none";
|
|
538
|
+
handlers.onBlur?.();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
359
541
|
}
|
|
360
542
|
function swStatusColor(s) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
543
|
+
if (s === "active") return C.green;
|
|
544
|
+
if (s === "registering") return C.yellow;
|
|
545
|
+
if (s === "error" || s === "unsupported") return C.red;
|
|
546
|
+
return C.muted;
|
|
365
547
|
}
|
|
366
548
|
function queueStatusColor(s) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
549
|
+
if (s === "succeeded") return C.green;
|
|
550
|
+
if (s === "failed") return C.red;
|
|
551
|
+
if (s === "replaying") return C.yellow;
|
|
552
|
+
return C.blue;
|
|
371
553
|
}
|
|
372
554
|
function resourceStatusColor(s) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
555
|
+
if (s === "fresh") return C.green;
|
|
556
|
+
if (s === "stale" || s === "offline") return C.yellow;
|
|
557
|
+
if (s === "error") return C.red;
|
|
558
|
+
if (s === "fetching") return C.cyan;
|
|
559
|
+
return C.muted;
|
|
560
|
+
}
|
|
561
|
+
function Icon({ path, size = 12, strokeWidth = 2 }) {
|
|
562
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
563
|
+
width: size,
|
|
564
|
+
height: size,
|
|
565
|
+
viewBox: "0 0 24 24",
|
|
566
|
+
fill: "none",
|
|
567
|
+
stroke: "currentColor",
|
|
568
|
+
strokeWidth,
|
|
569
|
+
strokeLinecap: "round",
|
|
570
|
+
strokeLinejoin: "round",
|
|
571
|
+
"aria-hidden": "true",
|
|
572
|
+
style: { flexShrink: 0 },
|
|
573
|
+
children: /* @__PURE__ */ jsx("path", { d: path })
|
|
574
|
+
});
|
|
378
575
|
}
|
|
576
|
+
var ICONS = {
|
|
577
|
+
bolt: "M13 2 3 14h7l-1 8 10-12h-7l1-8z",
|
|
578
|
+
satellite: "M5 13a8.5 8.5 0 0 0 8 8M11 3a12 12 0 0 1 10 10M5 13l-3 3 6 6 3-3M14 6l4 4M9.5 8.5l6 6",
|
|
579
|
+
satelliteOff: "M2 2l20 20M5 13a8.5 8.5 0 0 0 8 8M14 6l4 4M9.5 8.5l6 6M5 13l-3 3 6 6 3-3",
|
|
580
|
+
play: "M6 4l13 8-13 8V4z",
|
|
581
|
+
trash: "M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m3 0-1 14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1L4 6h16z",
|
|
582
|
+
arrowUp: "M12 19V5M5 12l7-7 7 7",
|
|
583
|
+
arrowDown: "M12 5v14M19 12l-7 7-7-7",
|
|
584
|
+
clock: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 6v6l4 2"
|
|
585
|
+
};
|
|
379
586
|
function positionStyle(p) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
587
|
+
const base = {
|
|
588
|
+
position: "fixed",
|
|
589
|
+
zIndex: 99999
|
|
590
|
+
};
|
|
591
|
+
if (p === "bottom-left") return {
|
|
592
|
+
...base,
|
|
593
|
+
bottom: 16,
|
|
594
|
+
left: 16
|
|
595
|
+
};
|
|
596
|
+
if (p === "top-right") return {
|
|
597
|
+
...base,
|
|
598
|
+
top: 16,
|
|
599
|
+
right: 16
|
|
600
|
+
};
|
|
601
|
+
if (p === "top-left") return {
|
|
602
|
+
...base,
|
|
603
|
+
top: 16,
|
|
604
|
+
left: 16
|
|
605
|
+
};
|
|
606
|
+
return {
|
|
607
|
+
...base,
|
|
608
|
+
bottom: 16,
|
|
609
|
+
right: 16
|
|
610
|
+
};
|
|
385
611
|
}
|
|
386
612
|
function EidosDevtools({ position = "bottom-right", defaultOpen = false }) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
613
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
614
|
+
const [tab, setTab] = useState("queue");
|
|
615
|
+
const [simOffline, setSimOffline] = useState(false);
|
|
616
|
+
const { isOnline, swStatus, swError } = useEidosStatus();
|
|
617
|
+
const queue = useEidosQueue();
|
|
618
|
+
const { pending, failed, replaying } = useEidosQueueStats();
|
|
619
|
+
const resources = useEidosResources();
|
|
620
|
+
const resourceList = Object.values(resources);
|
|
621
|
+
const badgeCount = pending + failed + replaying;
|
|
622
|
+
const toggleOffline = useCallback(() => {
|
|
623
|
+
const next = !simOffline;
|
|
624
|
+
setSimOffline(next);
|
|
625
|
+
setOfflineSimulation(next);
|
|
626
|
+
}, [simOffline]);
|
|
627
|
+
const handleReplay = useCallback(() => {
|
|
628
|
+
replayQueue();
|
|
629
|
+
}, []);
|
|
630
|
+
const handleClear = useCallback(() => {
|
|
631
|
+
clearQueue();
|
|
632
|
+
}, []);
|
|
633
|
+
const toggleBtn = /* @__PURE__ */ jsxs("button", {
|
|
634
|
+
onClick: () => setOpen((v) => !v),
|
|
635
|
+
"aria-expanded": open,
|
|
636
|
+
"aria-label": open ? "Close Eidos Devtools" : "Open Eidos Devtools",
|
|
637
|
+
title: "Eidos Devtools",
|
|
638
|
+
...withFocusRing(),
|
|
639
|
+
style: {
|
|
640
|
+
display: "flex",
|
|
641
|
+
alignItems: "center",
|
|
642
|
+
gap: 6,
|
|
643
|
+
padding: "5px 10px",
|
|
644
|
+
background: C.bg,
|
|
645
|
+
border: `1px solid ${C.border}`,
|
|
646
|
+
borderRadius: 9999,
|
|
647
|
+
cursor: "pointer",
|
|
648
|
+
color: C.text,
|
|
649
|
+
fontSize: 11,
|
|
650
|
+
fontWeight: 600,
|
|
651
|
+
fontFamily: "ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace",
|
|
652
|
+
boxShadow: "0 2px 12px rgba(0,0,0,0.5)",
|
|
653
|
+
userSelect: "none",
|
|
654
|
+
minHeight: 32
|
|
655
|
+
},
|
|
656
|
+
children: [
|
|
657
|
+
/* @__PURE__ */ jsx("span", {
|
|
658
|
+
"aria-hidden": "true",
|
|
659
|
+
style: {
|
|
660
|
+
width: 7,
|
|
661
|
+
height: 7,
|
|
662
|
+
borderRadius: "50%",
|
|
663
|
+
background: isOnline ? C.green : C.red,
|
|
664
|
+
flexShrink: 0
|
|
665
|
+
}
|
|
666
|
+
}),
|
|
667
|
+
/* @__PURE__ */ jsx("span", {
|
|
668
|
+
style: {
|
|
669
|
+
color: C.cyan,
|
|
670
|
+
display: "inline-flex"
|
|
671
|
+
},
|
|
672
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
673
|
+
path: ICONS.bolt,
|
|
674
|
+
size: 12
|
|
675
|
+
})
|
|
676
|
+
}),
|
|
677
|
+
/* @__PURE__ */ jsx("span", { children: "eidos" }),
|
|
678
|
+
badgeCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
679
|
+
"aria-label": `${badgeCount} ${failed > 0 ? "failed" : "pending"} queue item${badgeCount !== 1 ? "s" : ""}`,
|
|
680
|
+
style: {
|
|
681
|
+
background: failed > 0 ? C.red : C.yellow,
|
|
682
|
+
color: "#fff",
|
|
683
|
+
borderRadius: 9999,
|
|
684
|
+
minWidth: 16,
|
|
685
|
+
height: 16,
|
|
686
|
+
display: "inline-flex",
|
|
687
|
+
alignItems: "center",
|
|
688
|
+
justifyContent: "center",
|
|
689
|
+
fontSize: 9,
|
|
690
|
+
fontWeight: 700,
|
|
691
|
+
padding: "0 4px"
|
|
692
|
+
},
|
|
693
|
+
children: badgeCount
|
|
694
|
+
})
|
|
695
|
+
]
|
|
696
|
+
});
|
|
697
|
+
if (!open) return /* @__PURE__ */ jsx("div", {
|
|
698
|
+
style: positionStyle(position),
|
|
699
|
+
children: toggleBtn
|
|
700
|
+
});
|
|
701
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
702
|
+
style: {
|
|
703
|
+
...positionStyle(position),
|
|
704
|
+
display: "flex",
|
|
705
|
+
flexDirection: "column",
|
|
706
|
+
alignItems: "flex-end",
|
|
707
|
+
gap: 6
|
|
708
|
+
},
|
|
709
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
710
|
+
style: {
|
|
711
|
+
width: 340,
|
|
712
|
+
maxHeight: 480,
|
|
713
|
+
display: "flex",
|
|
714
|
+
flexDirection: "column",
|
|
715
|
+
background: C.bg,
|
|
716
|
+
border: `1px solid ${C.border}`,
|
|
717
|
+
borderRadius: 10,
|
|
718
|
+
overflow: "hidden",
|
|
719
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.6)",
|
|
720
|
+
fontFamily: "ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace",
|
|
721
|
+
fontSize: 11,
|
|
722
|
+
color: C.text
|
|
723
|
+
},
|
|
724
|
+
children: [
|
|
725
|
+
/* @__PURE__ */ jsxs("div", {
|
|
726
|
+
style: {
|
|
727
|
+
display: "flex",
|
|
728
|
+
alignItems: "center",
|
|
729
|
+
gap: 8,
|
|
730
|
+
padding: "8px 12px",
|
|
731
|
+
background: C.surface,
|
|
732
|
+
borderBottom: `1px solid ${C.border}`,
|
|
733
|
+
flexShrink: 0
|
|
734
|
+
},
|
|
735
|
+
children: [
|
|
736
|
+
/* @__PURE__ */ jsx("span", {
|
|
737
|
+
style: {
|
|
738
|
+
color: C.cyan,
|
|
739
|
+
fontSize: 13
|
|
740
|
+
},
|
|
741
|
+
children: "⚡"
|
|
742
|
+
}),
|
|
743
|
+
/* @__PURE__ */ jsx("span", {
|
|
744
|
+
style: {
|
|
745
|
+
fontWeight: 700,
|
|
746
|
+
fontSize: 12,
|
|
747
|
+
color: C.text,
|
|
748
|
+
flex: 1
|
|
749
|
+
},
|
|
750
|
+
children: "Eidos Devtools"
|
|
751
|
+
}),
|
|
752
|
+
/* @__PURE__ */ jsxs("div", {
|
|
753
|
+
style: {
|
|
754
|
+
display: "flex",
|
|
755
|
+
alignItems: "center",
|
|
756
|
+
gap: 6
|
|
757
|
+
},
|
|
758
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
759
|
+
style: pill(isOnline ? C.green : C.red),
|
|
760
|
+
children: isOnline ? "online" : "offline"
|
|
761
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
762
|
+
onClick: toggleOffline,
|
|
763
|
+
"aria-pressed": simOffline,
|
|
764
|
+
title: simOffline ? "Disable offline simulation" : "Enable offline simulation",
|
|
765
|
+
...withFocusRing(),
|
|
766
|
+
style: {
|
|
767
|
+
...btn("ghost"),
|
|
768
|
+
background: simOffline ? `${C.yellow}22` : C.surface,
|
|
769
|
+
color: simOffline ? C.yellow : C.muted,
|
|
770
|
+
border: `1px solid ${simOffline ? C.yellow + "44" : C.border}`,
|
|
771
|
+
fontSize: 10,
|
|
772
|
+
minHeight: 22
|
|
773
|
+
},
|
|
774
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
775
|
+
path: simOffline ? ICONS.satelliteOff : ICONS.satellite,
|
|
776
|
+
size: 11
|
|
777
|
+
}), simOffline ? "simulating offline" : "sim offline"]
|
|
778
|
+
})]
|
|
779
|
+
})
|
|
780
|
+
]
|
|
781
|
+
}),
|
|
782
|
+
/* @__PURE__ */ jsxs("div", {
|
|
783
|
+
style: {
|
|
784
|
+
display: "flex",
|
|
785
|
+
alignItems: "center",
|
|
786
|
+
gap: 8,
|
|
787
|
+
padding: "6px 12px",
|
|
788
|
+
borderBottom: `1px solid ${C.border}`,
|
|
789
|
+
flexShrink: 0,
|
|
790
|
+
background: C.bg
|
|
791
|
+
},
|
|
792
|
+
children: [
|
|
793
|
+
/* @__PURE__ */ jsx("span", {
|
|
794
|
+
style: {
|
|
795
|
+
color: C.muted,
|
|
796
|
+
fontSize: 10
|
|
797
|
+
},
|
|
798
|
+
children: "SW"
|
|
799
|
+
}),
|
|
800
|
+
/* @__PURE__ */ jsx("span", {
|
|
801
|
+
style: pill(swStatusColor(swStatus)),
|
|
802
|
+
children: swStatus
|
|
803
|
+
}),
|
|
804
|
+
swError && /* @__PURE__ */ jsx("span", {
|
|
805
|
+
style: {
|
|
806
|
+
color: C.red,
|
|
807
|
+
fontSize: 10,
|
|
808
|
+
flex: 1,
|
|
809
|
+
overflow: "hidden",
|
|
810
|
+
textOverflow: "ellipsis",
|
|
811
|
+
whiteSpace: "nowrap"
|
|
812
|
+
},
|
|
813
|
+
children: swError
|
|
814
|
+
})
|
|
815
|
+
]
|
|
816
|
+
}),
|
|
817
|
+
/* @__PURE__ */ jsx("div", {
|
|
818
|
+
style: {
|
|
819
|
+
display: "flex",
|
|
820
|
+
borderBottom: `1px solid ${C.border}`,
|
|
821
|
+
flexShrink: 0,
|
|
822
|
+
background: C.surface
|
|
823
|
+
},
|
|
824
|
+
children: ["queue", "cache"].map((t) => /* @__PURE__ */ jsx("button", {
|
|
825
|
+
role: "tab",
|
|
826
|
+
"aria-selected": tab === t,
|
|
827
|
+
onClick: () => setTab(t),
|
|
828
|
+
...withFocusRing(),
|
|
829
|
+
style: {
|
|
830
|
+
flex: 1,
|
|
831
|
+
padding: "6px 0",
|
|
832
|
+
minHeight: 32,
|
|
833
|
+
background: "none",
|
|
834
|
+
border: "none",
|
|
835
|
+
borderBottom: tab === t ? `2px solid ${C.cyan}` : "2px solid transparent",
|
|
836
|
+
cursor: "pointer",
|
|
837
|
+
color: tab === t ? C.cyan : C.muted,
|
|
838
|
+
fontSize: 11,
|
|
839
|
+
fontWeight: tab === t ? 600 : 400,
|
|
840
|
+
fontFamily: "inherit",
|
|
841
|
+
textTransform: "uppercase",
|
|
842
|
+
letterSpacing: "0.05em",
|
|
843
|
+
transition: "color 0.15s, border-color 0.15s"
|
|
844
|
+
},
|
|
845
|
+
children: t === "queue" ? `Queue (${queue.length})` : `Cache (${resourceList.length})`
|
|
846
|
+
}, t))
|
|
847
|
+
}),
|
|
848
|
+
/* @__PURE__ */ jsx("div", {
|
|
849
|
+
style: {
|
|
850
|
+
flex: 1,
|
|
851
|
+
overflowY: "auto",
|
|
852
|
+
minHeight: 0
|
|
853
|
+
},
|
|
854
|
+
children: tab === "queue" ? /* @__PURE__ */ jsx(QueueTab, {
|
|
855
|
+
queue,
|
|
856
|
+
onReplay: handleReplay,
|
|
857
|
+
onClear: handleClear
|
|
858
|
+
}) : /* @__PURE__ */ jsx(CacheTab, { resources: resourceList })
|
|
859
|
+
})
|
|
860
|
+
]
|
|
861
|
+
}), toggleBtn]
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
function QueueTab({ queue, onReplay, onClear }) {
|
|
865
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
866
|
+
style: {
|
|
867
|
+
display: "flex",
|
|
868
|
+
flexDirection: "column",
|
|
869
|
+
height: "100%"
|
|
870
|
+
},
|
|
871
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
872
|
+
style: {
|
|
873
|
+
display: "flex",
|
|
874
|
+
gap: 6,
|
|
875
|
+
padding: "8px 12px",
|
|
876
|
+
borderBottom: `1px solid ${C.border}`,
|
|
877
|
+
flexShrink: 0
|
|
878
|
+
},
|
|
879
|
+
children: [
|
|
880
|
+
/* @__PURE__ */ jsxs("button", {
|
|
881
|
+
onClick: onReplay,
|
|
882
|
+
...withFocusRing(),
|
|
883
|
+
style: {
|
|
884
|
+
...btn("primary"),
|
|
885
|
+
minHeight: 24
|
|
886
|
+
},
|
|
887
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
888
|
+
path: ICONS.play,
|
|
889
|
+
size: 11
|
|
890
|
+
}), "Replay queue"]
|
|
891
|
+
}),
|
|
892
|
+
/* @__PURE__ */ jsxs("button", {
|
|
893
|
+
onClick: onClear,
|
|
894
|
+
...withFocusRing(),
|
|
895
|
+
style: {
|
|
896
|
+
...btn("danger"),
|
|
897
|
+
minHeight: 24
|
|
898
|
+
},
|
|
899
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
900
|
+
path: ICONS.trash,
|
|
901
|
+
size: 11
|
|
902
|
+
}), "Clear queue"]
|
|
903
|
+
}),
|
|
904
|
+
/* @__PURE__ */ jsxs("span", {
|
|
905
|
+
style: {
|
|
906
|
+
marginLeft: "auto",
|
|
907
|
+
color: C.muted,
|
|
908
|
+
fontSize: 10,
|
|
909
|
+
alignSelf: "center"
|
|
910
|
+
},
|
|
911
|
+
children: [
|
|
912
|
+
queue.length,
|
|
913
|
+
" item",
|
|
914
|
+
queue.length !== 1 ? "s" : ""
|
|
915
|
+
]
|
|
916
|
+
})
|
|
917
|
+
]
|
|
918
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
919
|
+
style: {
|
|
920
|
+
flex: 1,
|
|
921
|
+
overflowY: "auto"
|
|
922
|
+
},
|
|
923
|
+
children: queue.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
924
|
+
style: {
|
|
925
|
+
padding: "20px 12px",
|
|
926
|
+
textAlign: "center",
|
|
927
|
+
color: C.muted
|
|
928
|
+
},
|
|
929
|
+
children: "Queue empty"
|
|
930
|
+
}) : queue.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
931
|
+
style: {
|
|
932
|
+
display: "flex",
|
|
933
|
+
alignItems: "center",
|
|
934
|
+
gap: 8,
|
|
935
|
+
padding: "7px 12px",
|
|
936
|
+
borderBottom: `1px solid ${C.border}`
|
|
937
|
+
},
|
|
938
|
+
children: [
|
|
939
|
+
/* @__PURE__ */ jsx("span", {
|
|
940
|
+
style: pill(queueStatusColor(item.status)),
|
|
941
|
+
children: item.status
|
|
942
|
+
}),
|
|
943
|
+
item.priority && item.priority !== "normal" && /* @__PURE__ */ jsx("span", {
|
|
944
|
+
style: pill(item.priority === "high" ? C.purple : C.muted),
|
|
945
|
+
children: item.priority
|
|
946
|
+
}),
|
|
947
|
+
/* @__PURE__ */ jsx("span", {
|
|
948
|
+
style: {
|
|
949
|
+
flex: 1,
|
|
950
|
+
overflow: "hidden",
|
|
951
|
+
textOverflow: "ellipsis",
|
|
952
|
+
whiteSpace: "nowrap",
|
|
953
|
+
color: C.text
|
|
954
|
+
},
|
|
955
|
+
children: item.actionName
|
|
956
|
+
}),
|
|
957
|
+
item.retryCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
958
|
+
style: {
|
|
959
|
+
color: C.muted,
|
|
960
|
+
fontSize: 10,
|
|
961
|
+
flexShrink: 0
|
|
962
|
+
},
|
|
963
|
+
children: [
|
|
964
|
+
"×",
|
|
965
|
+
item.retryCount,
|
|
966
|
+
"/",
|
|
967
|
+
item.maxRetries
|
|
968
|
+
]
|
|
969
|
+
})
|
|
970
|
+
]
|
|
971
|
+
}, item.id))
|
|
972
|
+
})]
|
|
973
|
+
});
|
|
580
974
|
}
|
|
581
975
|
function CacheTab({ resources }) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
976
|
+
return /* @__PURE__ */ jsx("div", { children: resources.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
977
|
+
style: {
|
|
978
|
+
padding: "20px 12px",
|
|
979
|
+
textAlign: "center",
|
|
980
|
+
color: C.muted
|
|
981
|
+
},
|
|
982
|
+
children: "No resources registered"
|
|
983
|
+
}) : resources.map((res) => /* @__PURE__ */ jsxs("div", {
|
|
984
|
+
style: {
|
|
985
|
+
padding: "7px 12px",
|
|
986
|
+
borderBottom: `1px solid ${C.border}`
|
|
987
|
+
},
|
|
988
|
+
children: [
|
|
989
|
+
/* @__PURE__ */ jsxs("div", {
|
|
990
|
+
style: {
|
|
991
|
+
display: "flex",
|
|
992
|
+
alignItems: "center",
|
|
993
|
+
gap: 6,
|
|
994
|
+
marginBottom: 3
|
|
995
|
+
},
|
|
996
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
997
|
+
style: pill(resourceStatusColor(res.status)),
|
|
998
|
+
children: res.status
|
|
999
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1000
|
+
style: {
|
|
1001
|
+
color: C.muted,
|
|
1002
|
+
fontSize: 10
|
|
1003
|
+
},
|
|
1004
|
+
children: res.strategy.name
|
|
1005
|
+
})]
|
|
1006
|
+
}),
|
|
1007
|
+
/* @__PURE__ */ jsx("div", {
|
|
1008
|
+
style: {
|
|
1009
|
+
color: C.text,
|
|
1010
|
+
overflow: "hidden",
|
|
1011
|
+
textOverflow: "ellipsis",
|
|
1012
|
+
whiteSpace: "nowrap",
|
|
1013
|
+
fontSize: 10,
|
|
1014
|
+
marginBottom: 2
|
|
1015
|
+
},
|
|
1016
|
+
children: res.url
|
|
1017
|
+
}),
|
|
1018
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1019
|
+
style: {
|
|
1020
|
+
display: "flex",
|
|
1021
|
+
gap: 10,
|
|
1022
|
+
color: C.muted,
|
|
1023
|
+
fontSize: 10
|
|
1024
|
+
},
|
|
1025
|
+
children: [
|
|
1026
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1027
|
+
title: "Cache hits",
|
|
1028
|
+
style: {
|
|
1029
|
+
display: "inline-flex",
|
|
1030
|
+
alignItems: "center",
|
|
1031
|
+
gap: 3
|
|
1032
|
+
},
|
|
1033
|
+
children: [
|
|
1034
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1035
|
+
path: ICONS.arrowUp,
|
|
1036
|
+
size: 10
|
|
1037
|
+
}),
|
|
1038
|
+
res.cacheHits,
|
|
1039
|
+
" hit",
|
|
1040
|
+
res.cacheHits !== 1 ? "s" : ""
|
|
1041
|
+
]
|
|
1042
|
+
}),
|
|
1043
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1044
|
+
title: "Cache misses",
|
|
1045
|
+
style: {
|
|
1046
|
+
display: "inline-flex",
|
|
1047
|
+
alignItems: "center",
|
|
1048
|
+
gap: 3
|
|
1049
|
+
},
|
|
1050
|
+
children: [
|
|
1051
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1052
|
+
path: ICONS.arrowDown,
|
|
1053
|
+
size: 10
|
|
1054
|
+
}),
|
|
1055
|
+
res.cacheMisses,
|
|
1056
|
+
" miss",
|
|
1057
|
+
res.cacheMisses !== 1 ? "es" : ""
|
|
1058
|
+
]
|
|
1059
|
+
}),
|
|
1060
|
+
res.cachedAt && /* @__PURE__ */ jsxs("span", {
|
|
1061
|
+
title: "Cached at",
|
|
1062
|
+
style: {
|
|
1063
|
+
display: "inline-flex",
|
|
1064
|
+
alignItems: "center",
|
|
1065
|
+
gap: 3
|
|
1066
|
+
},
|
|
1067
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1068
|
+
path: ICONS.clock,
|
|
1069
|
+
size: 10
|
|
1070
|
+
}), new Date(res.cachedAt).toLocaleTimeString()]
|
|
1071
|
+
})
|
|
1072
|
+
]
|
|
1073
|
+
})
|
|
1074
|
+
]
|
|
1075
|
+
}, res.url)) });
|
|
1076
|
+
}
|
|
1077
|
+
//#endregion
|
|
1078
|
+
export { EidosDevtools };
|