@sweidos/eidos 1.0.34 → 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 +96 -89
- 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/devtools.js
CHANGED
|
@@ -1,620 +1,1018 @@
|
|
|
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
|
-
|
|
84
|
-
}
|
|
111
|
+
return {
|
|
112
|
+
isOnline: useStore((s) => s.isOnline),
|
|
113
|
+
swStatus: useStore((s) => s.swStatus),
|
|
114
|
+
swError: useStore((s) => s.swError)
|
|
115
|
+
};
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
147
|
+
sendToWorker({
|
|
148
|
+
type: "EIDOS_SIMULATE_OFFLINE",
|
|
149
|
+
enabled
|
|
150
|
+
});
|
|
151
|
+
useEidosStore.getState().setOnline(!enabled);
|
|
152
|
+
}
|
|
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();
|
|
210
269
|
function qs() {
|
|
211
|
-
|
|
270
|
+
return _getQueueStorage() ?? idbQueueStorage;
|
|
212
271
|
}
|
|
213
272
|
function isClientError(err) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
if (err instanceof Response) return err.status >= 400 && err.status < 500;
|
|
274
|
+
if (typeof err === "object" && err !== null) {
|
|
275
|
+
const s = err.status;
|
|
276
|
+
if (typeof s === "number") return s >= 400 && s < 500;
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
220
279
|
}
|
|
221
280
|
function backoffMs(retryCount) {
|
|
222
|
-
|
|
223
|
-
return base * (0.8 + Math.random() * 0.4);
|
|
281
|
+
return Math.min(2e3 * 2 ** retryCount, 3e5) * (.8 + Math.random() * .4);
|
|
224
282
|
}
|
|
225
|
-
|
|
283
|
+
var _replaying = false;
|
|
226
284
|
async function replayQueue() {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
285
|
+
const store = useEidosStore.getState();
|
|
286
|
+
if (!store.isOnline || _replaying) return {
|
|
287
|
+
attempted: 0,
|
|
288
|
+
succeeded: 0,
|
|
289
|
+
failed: 0,
|
|
290
|
+
retrying: 0,
|
|
291
|
+
skipped: 0,
|
|
292
|
+
conflicted: 0
|
|
293
|
+
};
|
|
294
|
+
_replaying = true;
|
|
295
|
+
try {
|
|
296
|
+
return await _doReplayQueue(store);
|
|
297
|
+
} finally {
|
|
298
|
+
_replaying = false;
|
|
299
|
+
}
|
|
237
300
|
}
|
|
238
301
|
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
|
-
|
|
302
|
+
const fn = _actionRegistry.get(item.actionId);
|
|
303
|
+
if (!fn) return "skipped";
|
|
304
|
+
try {
|
|
305
|
+
await fn(...item.args);
|
|
306
|
+
const completedAt = Date.now();
|
|
307
|
+
store.updateQueueItem(item.id, {
|
|
308
|
+
status: "succeeded",
|
|
309
|
+
completedAt
|
|
310
|
+
});
|
|
311
|
+
await qs().update(item.id, {
|
|
312
|
+
status: "succeeded",
|
|
313
|
+
completedAt
|
|
314
|
+
});
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
store.removeQueueItem(item.id);
|
|
317
|
+
qs().remove(item.id);
|
|
318
|
+
}, 3e3);
|
|
319
|
+
return "succeeded";
|
|
320
|
+
} catch (err) {
|
|
321
|
+
if (isClientError(err)) {
|
|
322
|
+
const onConflict = _conflictRegistry.get(item.actionId);
|
|
323
|
+
if (onConflict) {
|
|
324
|
+
if (onConflict(err, item.args) === "skip") {
|
|
325
|
+
store.removeQueueItem(item.id);
|
|
326
|
+
await qs().remove(item.id);
|
|
327
|
+
return "conflicted";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const retryCount = item.retryCount + 1;
|
|
332
|
+
if (retryCount >= item.maxRetries) {
|
|
333
|
+
store.updateQueueItem(item.id, {
|
|
334
|
+
status: "failed",
|
|
335
|
+
error: String(err),
|
|
336
|
+
retryCount
|
|
337
|
+
});
|
|
338
|
+
await qs().update(item.id, {
|
|
339
|
+
status: "failed",
|
|
340
|
+
error: String(err),
|
|
341
|
+
retryCount
|
|
342
|
+
});
|
|
343
|
+
_rollbackRegistry.get(item.actionId)?.(...item.args);
|
|
344
|
+
return "failed";
|
|
345
|
+
} else {
|
|
346
|
+
const nextRetryAt = Date.now() + backoffMs(retryCount);
|
|
347
|
+
store.updateQueueItem(item.id, {
|
|
348
|
+
status: "pending",
|
|
349
|
+
retryCount,
|
|
350
|
+
nextRetryAt
|
|
351
|
+
});
|
|
352
|
+
await qs().update(item.id, {
|
|
353
|
+
status: "pending",
|
|
354
|
+
retryCount,
|
|
355
|
+
nextRetryAt
|
|
356
|
+
});
|
|
357
|
+
return "retrying";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
277
360
|
}
|
|
278
361
|
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
|
-
}
|
|
362
|
+
if (items.length === 0) return;
|
|
363
|
+
const replayable = items.filter((item) => _actionRegistry.has(item.actionId));
|
|
364
|
+
result.skipped += items.length - replayable.length;
|
|
365
|
+
if (replayable.length > 0) {
|
|
366
|
+
store.batchUpdateQueueItems(replayable.map((item) => ({
|
|
367
|
+
id: item.id,
|
|
368
|
+
update: { status: "replaying" }
|
|
369
|
+
})));
|
|
370
|
+
for (const item of replayable) qs().update(item.id, { status: "replaying" });
|
|
371
|
+
}
|
|
372
|
+
const outcomes = await Promise.allSettled(replayable.map((item) => _replayItem(item, store)));
|
|
373
|
+
for (const o of outcomes) {
|
|
374
|
+
const outcome = o.status === "fulfilled" ? o.value : "failed";
|
|
375
|
+
if (outcome === "skipped") result.skipped++;
|
|
376
|
+
else if (outcome === "conflicted") result.conflicted++;
|
|
377
|
+
else {
|
|
378
|
+
result.attempted++;
|
|
379
|
+
result[outcome]++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
300
382
|
}
|
|
301
383
|
async function _doReplayQueue(store) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
384
|
+
const candidates = await qs().getPending();
|
|
385
|
+
const now = Date.now();
|
|
386
|
+
const pending = candidates.filter((item) => item.retryCount < item.maxRetries && (!item.nextRetryAt || item.nextRetryAt <= now));
|
|
387
|
+
const result = {
|
|
388
|
+
attempted: 0,
|
|
389
|
+
succeeded: 0,
|
|
390
|
+
failed: 0,
|
|
391
|
+
retrying: 0,
|
|
392
|
+
skipped: 0,
|
|
393
|
+
conflicted: 0
|
|
394
|
+
};
|
|
395
|
+
for (const tier of [
|
|
396
|
+
"high",
|
|
397
|
+
"normal",
|
|
398
|
+
"low"
|
|
399
|
+
]) await _replayTier(pending.filter((item) => (item.priority ?? "normal") === tier), store, result);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
/** Remove all items from the action queue (storage + in-memory store). */
|
|
314
403
|
async function clearQueue() {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
404
|
+
await qs().clear();
|
|
405
|
+
useEidosStore.getState().hydrateQueue([]);
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/react/Devtools.tsx
|
|
409
|
+
var C = {
|
|
410
|
+
bg: "#0f1117",
|
|
411
|
+
surface: "#1a1d27",
|
|
412
|
+
border: "#2a2d3a",
|
|
413
|
+
text: "#e2e8f0",
|
|
414
|
+
muted: "#64748b",
|
|
415
|
+
green: "#22c55e",
|
|
416
|
+
red: "#ef4444",
|
|
417
|
+
yellow: "#f59e0b",
|
|
418
|
+
blue: "#3b82f6",
|
|
419
|
+
purple: "#a855f7",
|
|
420
|
+
cyan: "#06b6d4"
|
|
330
421
|
};
|
|
331
422
|
function pill(color) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
423
|
+
return {
|
|
424
|
+
display: "inline-flex",
|
|
425
|
+
alignItems: "center",
|
|
426
|
+
padding: "1px 7px",
|
|
427
|
+
borderRadius: 9999,
|
|
428
|
+
fontSize: 10,
|
|
429
|
+
fontWeight: 600,
|
|
430
|
+
background: `${color}22`,
|
|
431
|
+
color,
|
|
432
|
+
border: `1px solid ${color}44`,
|
|
433
|
+
fontFamily: "inherit"
|
|
434
|
+
};
|
|
344
435
|
}
|
|
345
436
|
function btn(variant = "ghost") {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
437
|
+
const base = {
|
|
438
|
+
cursor: "pointer",
|
|
439
|
+
border: "none",
|
|
440
|
+
borderRadius: 6,
|
|
441
|
+
padding: "3px 10px",
|
|
442
|
+
fontSize: 11,
|
|
443
|
+
fontWeight: 500,
|
|
444
|
+
fontFamily: "inherit",
|
|
445
|
+
display: "inline-flex",
|
|
446
|
+
alignItems: "center",
|
|
447
|
+
gap: 5,
|
|
448
|
+
transition: "background-color 0.15s, color 0.15s"
|
|
449
|
+
};
|
|
450
|
+
if (variant === "danger") return {
|
|
451
|
+
...base,
|
|
452
|
+
background: `${C.red}22`,
|
|
453
|
+
color: C.red
|
|
454
|
+
};
|
|
455
|
+
if (variant === "primary") return {
|
|
456
|
+
...base,
|
|
457
|
+
background: `${C.blue}22`,
|
|
458
|
+
color: C.blue
|
|
459
|
+
};
|
|
460
|
+
return {
|
|
461
|
+
...base,
|
|
462
|
+
background: C.surface,
|
|
463
|
+
color: C.muted
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
var focusRing = {
|
|
467
|
+
outline: `2px solid ${C.cyan}`,
|
|
468
|
+
outlineOffset: 1
|
|
469
|
+
};
|
|
470
|
+
function withFocusRing(handlers = {}) {
|
|
471
|
+
return {
|
|
472
|
+
onFocus: (e) => {
|
|
473
|
+
Object.assign(e.currentTarget.style, focusRing);
|
|
474
|
+
handlers.onFocus?.();
|
|
475
|
+
},
|
|
476
|
+
onBlur: (e) => {
|
|
477
|
+
e.currentTarget.style.outline = "none";
|
|
478
|
+
handlers.onBlur?.();
|
|
479
|
+
}
|
|
480
|
+
};
|
|
359
481
|
}
|
|
360
482
|
function swStatusColor(s) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
483
|
+
if (s === "active") return C.green;
|
|
484
|
+
if (s === "registering") return C.yellow;
|
|
485
|
+
if (s === "error" || s === "unsupported") return C.red;
|
|
486
|
+
return C.muted;
|
|
365
487
|
}
|
|
366
488
|
function queueStatusColor(s) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
489
|
+
if (s === "succeeded") return C.green;
|
|
490
|
+
if (s === "failed") return C.red;
|
|
491
|
+
if (s === "replaying") return C.yellow;
|
|
492
|
+
return C.blue;
|
|
371
493
|
}
|
|
372
494
|
function resourceStatusColor(s) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
495
|
+
if (s === "fresh") return C.green;
|
|
496
|
+
if (s === "stale" || s === "offline") return C.yellow;
|
|
497
|
+
if (s === "error") return C.red;
|
|
498
|
+
if (s === "fetching") return C.cyan;
|
|
499
|
+
return C.muted;
|
|
500
|
+
}
|
|
501
|
+
function Icon({ path, size = 12, strokeWidth = 2 }) {
|
|
502
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
503
|
+
width: size,
|
|
504
|
+
height: size,
|
|
505
|
+
viewBox: "0 0 24 24",
|
|
506
|
+
fill: "none",
|
|
507
|
+
stroke: "currentColor",
|
|
508
|
+
strokeWidth,
|
|
509
|
+
strokeLinecap: "round",
|
|
510
|
+
strokeLinejoin: "round",
|
|
511
|
+
"aria-hidden": "true",
|
|
512
|
+
style: { flexShrink: 0 },
|
|
513
|
+
children: /* @__PURE__ */ jsx("path", { d: path })
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
var ICONS = {
|
|
517
|
+
bolt: "M13 2 3 14h7l-1 8 10-12h-7l1-8z",
|
|
518
|
+
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",
|
|
519
|
+
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",
|
|
520
|
+
play: "M6 4l13 8-13 8V4z",
|
|
521
|
+
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",
|
|
522
|
+
arrowUp: "M12 19V5M5 12l7-7 7 7",
|
|
523
|
+
arrowDown: "M12 5v14M19 12l-7 7-7-7",
|
|
524
|
+
clock: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 6v6l4 2"
|
|
525
|
+
};
|
|
379
526
|
function positionStyle(p) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
527
|
+
const base = {
|
|
528
|
+
position: "fixed",
|
|
529
|
+
zIndex: 99999
|
|
530
|
+
};
|
|
531
|
+
if (p === "bottom-left") return {
|
|
532
|
+
...base,
|
|
533
|
+
bottom: 16,
|
|
534
|
+
left: 16
|
|
535
|
+
};
|
|
536
|
+
if (p === "top-right") return {
|
|
537
|
+
...base,
|
|
538
|
+
top: 16,
|
|
539
|
+
right: 16
|
|
540
|
+
};
|
|
541
|
+
if (p === "top-left") return {
|
|
542
|
+
...base,
|
|
543
|
+
top: 16,
|
|
544
|
+
left: 16
|
|
545
|
+
};
|
|
546
|
+
return {
|
|
547
|
+
...base,
|
|
548
|
+
bottom: 16,
|
|
549
|
+
right: 16
|
|
550
|
+
};
|
|
385
551
|
}
|
|
386
552
|
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
|
-
|
|
553
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
554
|
+
const [tab, setTab] = useState("queue");
|
|
555
|
+
const [simOffline, setSimOffline] = useState(false);
|
|
556
|
+
const { isOnline, swStatus, swError } = useEidosStatus();
|
|
557
|
+
const queue = useEidosQueue();
|
|
558
|
+
const { pending, failed, replaying } = useEidosQueueStats();
|
|
559
|
+
const resources = useEidosResources();
|
|
560
|
+
const resourceList = Object.values(resources);
|
|
561
|
+
const badgeCount = pending + failed + replaying;
|
|
562
|
+
const toggleOffline = useCallback(() => {
|
|
563
|
+
const next = !simOffline;
|
|
564
|
+
setSimOffline(next);
|
|
565
|
+
setOfflineSimulation(next);
|
|
566
|
+
}, [simOffline]);
|
|
567
|
+
const handleReplay = useCallback(() => {
|
|
568
|
+
replayQueue();
|
|
569
|
+
}, []);
|
|
570
|
+
const handleClear = useCallback(() => {
|
|
571
|
+
clearQueue();
|
|
572
|
+
}, []);
|
|
573
|
+
const toggleBtn = /* @__PURE__ */ jsxs("button", {
|
|
574
|
+
onClick: () => setOpen((v) => !v),
|
|
575
|
+
"aria-expanded": open,
|
|
576
|
+
"aria-label": open ? "Close Eidos Devtools" : "Open Eidos Devtools",
|
|
577
|
+
title: "Eidos Devtools",
|
|
578
|
+
...withFocusRing(),
|
|
579
|
+
style: {
|
|
580
|
+
display: "flex",
|
|
581
|
+
alignItems: "center",
|
|
582
|
+
gap: 6,
|
|
583
|
+
padding: "5px 10px",
|
|
584
|
+
background: C.bg,
|
|
585
|
+
border: `1px solid ${C.border}`,
|
|
586
|
+
borderRadius: 9999,
|
|
587
|
+
cursor: "pointer",
|
|
588
|
+
color: C.text,
|
|
589
|
+
fontSize: 11,
|
|
590
|
+
fontWeight: 600,
|
|
591
|
+
fontFamily: "ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace",
|
|
592
|
+
boxShadow: "0 2px 12px rgba(0,0,0,0.5)",
|
|
593
|
+
userSelect: "none",
|
|
594
|
+
minHeight: 32
|
|
595
|
+
},
|
|
596
|
+
children: [
|
|
597
|
+
/* @__PURE__ */ jsx("span", {
|
|
598
|
+
"aria-hidden": "true",
|
|
599
|
+
style: {
|
|
600
|
+
width: 7,
|
|
601
|
+
height: 7,
|
|
602
|
+
borderRadius: "50%",
|
|
603
|
+
background: isOnline ? C.green : C.red,
|
|
604
|
+
flexShrink: 0
|
|
605
|
+
}
|
|
606
|
+
}),
|
|
607
|
+
/* @__PURE__ */ jsx("span", {
|
|
608
|
+
style: {
|
|
609
|
+
color: C.cyan,
|
|
610
|
+
display: "inline-flex"
|
|
611
|
+
},
|
|
612
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
613
|
+
path: ICONS.bolt,
|
|
614
|
+
size: 12
|
|
615
|
+
})
|
|
616
|
+
}),
|
|
617
|
+
/* @__PURE__ */ jsx("span", { children: "eidos" }),
|
|
618
|
+
badgeCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
619
|
+
"aria-label": `${badgeCount} ${failed > 0 ? "failed" : "pending"} queue item${badgeCount !== 1 ? "s" : ""}`,
|
|
620
|
+
style: {
|
|
621
|
+
background: failed > 0 ? C.red : C.yellow,
|
|
622
|
+
color: "#fff",
|
|
623
|
+
borderRadius: 9999,
|
|
624
|
+
minWidth: 16,
|
|
625
|
+
height: 16,
|
|
626
|
+
display: "inline-flex",
|
|
627
|
+
alignItems: "center",
|
|
628
|
+
justifyContent: "center",
|
|
629
|
+
fontSize: 9,
|
|
630
|
+
fontWeight: 700,
|
|
631
|
+
padding: "0 4px"
|
|
632
|
+
},
|
|
633
|
+
children: badgeCount
|
|
634
|
+
})
|
|
635
|
+
]
|
|
636
|
+
});
|
|
637
|
+
if (!open) return /* @__PURE__ */ jsx("div", {
|
|
638
|
+
style: positionStyle(position),
|
|
639
|
+
children: toggleBtn
|
|
640
|
+
});
|
|
641
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
642
|
+
style: {
|
|
643
|
+
...positionStyle(position),
|
|
644
|
+
display: "flex",
|
|
645
|
+
flexDirection: "column",
|
|
646
|
+
alignItems: "flex-end",
|
|
647
|
+
gap: 6
|
|
648
|
+
},
|
|
649
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
650
|
+
style: {
|
|
651
|
+
width: 340,
|
|
652
|
+
maxHeight: 480,
|
|
653
|
+
display: "flex",
|
|
654
|
+
flexDirection: "column",
|
|
655
|
+
background: C.bg,
|
|
656
|
+
border: `1px solid ${C.border}`,
|
|
657
|
+
borderRadius: 10,
|
|
658
|
+
overflow: "hidden",
|
|
659
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.6)",
|
|
660
|
+
fontFamily: "ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace",
|
|
661
|
+
fontSize: 11,
|
|
662
|
+
color: C.text
|
|
663
|
+
},
|
|
664
|
+
children: [
|
|
665
|
+
/* @__PURE__ */ jsxs("div", {
|
|
666
|
+
style: {
|
|
667
|
+
display: "flex",
|
|
668
|
+
alignItems: "center",
|
|
669
|
+
gap: 8,
|
|
670
|
+
padding: "8px 12px",
|
|
671
|
+
background: C.surface,
|
|
672
|
+
borderBottom: `1px solid ${C.border}`,
|
|
673
|
+
flexShrink: 0
|
|
674
|
+
},
|
|
675
|
+
children: [
|
|
676
|
+
/* @__PURE__ */ jsx("span", {
|
|
677
|
+
style: {
|
|
678
|
+
color: C.cyan,
|
|
679
|
+
fontSize: 13
|
|
680
|
+
},
|
|
681
|
+
children: "⚡"
|
|
682
|
+
}),
|
|
683
|
+
/* @__PURE__ */ jsx("span", {
|
|
684
|
+
style: {
|
|
685
|
+
fontWeight: 700,
|
|
686
|
+
fontSize: 12,
|
|
687
|
+
color: C.text,
|
|
688
|
+
flex: 1
|
|
689
|
+
},
|
|
690
|
+
children: "Eidos Devtools"
|
|
691
|
+
}),
|
|
692
|
+
/* @__PURE__ */ jsxs("div", {
|
|
693
|
+
style: {
|
|
694
|
+
display: "flex",
|
|
695
|
+
alignItems: "center",
|
|
696
|
+
gap: 6
|
|
697
|
+
},
|
|
698
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
699
|
+
style: pill(isOnline ? C.green : C.red),
|
|
700
|
+
children: isOnline ? "online" : "offline"
|
|
701
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
702
|
+
onClick: toggleOffline,
|
|
703
|
+
"aria-pressed": simOffline,
|
|
704
|
+
title: simOffline ? "Disable offline simulation" : "Enable offline simulation",
|
|
705
|
+
...withFocusRing(),
|
|
706
|
+
style: {
|
|
707
|
+
...btn("ghost"),
|
|
708
|
+
background: simOffline ? `${C.yellow}22` : C.surface,
|
|
709
|
+
color: simOffline ? C.yellow : C.muted,
|
|
710
|
+
border: `1px solid ${simOffline ? C.yellow + "44" : C.border}`,
|
|
711
|
+
fontSize: 10,
|
|
712
|
+
minHeight: 22
|
|
713
|
+
},
|
|
714
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
715
|
+
path: simOffline ? ICONS.satelliteOff : ICONS.satellite,
|
|
716
|
+
size: 11
|
|
717
|
+
}), simOffline ? "simulating offline" : "sim offline"]
|
|
718
|
+
})]
|
|
719
|
+
})
|
|
720
|
+
]
|
|
721
|
+
}),
|
|
722
|
+
/* @__PURE__ */ jsxs("div", {
|
|
723
|
+
style: {
|
|
724
|
+
display: "flex",
|
|
725
|
+
alignItems: "center",
|
|
726
|
+
gap: 8,
|
|
727
|
+
padding: "6px 12px",
|
|
728
|
+
borderBottom: `1px solid ${C.border}`,
|
|
729
|
+
flexShrink: 0,
|
|
730
|
+
background: C.bg
|
|
731
|
+
},
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsx("span", {
|
|
734
|
+
style: {
|
|
735
|
+
color: C.muted,
|
|
736
|
+
fontSize: 10
|
|
737
|
+
},
|
|
738
|
+
children: "SW"
|
|
739
|
+
}),
|
|
740
|
+
/* @__PURE__ */ jsx("span", {
|
|
741
|
+
style: pill(swStatusColor(swStatus)),
|
|
742
|
+
children: swStatus
|
|
743
|
+
}),
|
|
744
|
+
swError && /* @__PURE__ */ jsx("span", {
|
|
745
|
+
style: {
|
|
746
|
+
color: C.red,
|
|
747
|
+
fontSize: 10,
|
|
748
|
+
flex: 1,
|
|
749
|
+
overflow: "hidden",
|
|
750
|
+
textOverflow: "ellipsis",
|
|
751
|
+
whiteSpace: "nowrap"
|
|
752
|
+
},
|
|
753
|
+
children: swError
|
|
754
|
+
})
|
|
755
|
+
]
|
|
756
|
+
}),
|
|
757
|
+
/* @__PURE__ */ jsx("div", {
|
|
758
|
+
style: {
|
|
759
|
+
display: "flex",
|
|
760
|
+
borderBottom: `1px solid ${C.border}`,
|
|
761
|
+
flexShrink: 0,
|
|
762
|
+
background: C.surface
|
|
763
|
+
},
|
|
764
|
+
children: ["queue", "cache"].map((t) => /* @__PURE__ */ jsx("button", {
|
|
765
|
+
role: "tab",
|
|
766
|
+
"aria-selected": tab === t,
|
|
767
|
+
onClick: () => setTab(t),
|
|
768
|
+
...withFocusRing(),
|
|
769
|
+
style: {
|
|
770
|
+
flex: 1,
|
|
771
|
+
padding: "6px 0",
|
|
772
|
+
minHeight: 32,
|
|
773
|
+
background: "none",
|
|
774
|
+
border: "none",
|
|
775
|
+
borderBottom: tab === t ? `2px solid ${C.cyan}` : "2px solid transparent",
|
|
776
|
+
cursor: "pointer",
|
|
777
|
+
color: tab === t ? C.cyan : C.muted,
|
|
778
|
+
fontSize: 11,
|
|
779
|
+
fontWeight: tab === t ? 600 : 400,
|
|
780
|
+
fontFamily: "inherit",
|
|
781
|
+
textTransform: "uppercase",
|
|
782
|
+
letterSpacing: "0.05em",
|
|
783
|
+
transition: "color 0.15s, border-color 0.15s"
|
|
784
|
+
},
|
|
785
|
+
children: t === "queue" ? `Queue (${queue.length})` : `Cache (${resourceList.length})`
|
|
786
|
+
}, t))
|
|
787
|
+
}),
|
|
788
|
+
/* @__PURE__ */ jsx("div", {
|
|
789
|
+
style: {
|
|
790
|
+
flex: 1,
|
|
791
|
+
overflowY: "auto",
|
|
792
|
+
minHeight: 0
|
|
793
|
+
},
|
|
794
|
+
children: tab === "queue" ? /* @__PURE__ */ jsx(QueueTab, {
|
|
795
|
+
queue,
|
|
796
|
+
onReplay: handleReplay,
|
|
797
|
+
onClear: handleClear
|
|
798
|
+
}) : /* @__PURE__ */ jsx(CacheTab, { resources: resourceList })
|
|
799
|
+
})
|
|
800
|
+
]
|
|
801
|
+
}), toggleBtn]
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function QueueTab({ queue, onReplay, onClear }) {
|
|
805
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
806
|
+
style: {
|
|
807
|
+
display: "flex",
|
|
808
|
+
flexDirection: "column",
|
|
809
|
+
height: "100%"
|
|
810
|
+
},
|
|
811
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
812
|
+
style: {
|
|
813
|
+
display: "flex",
|
|
814
|
+
gap: 6,
|
|
815
|
+
padding: "8px 12px",
|
|
816
|
+
borderBottom: `1px solid ${C.border}`,
|
|
817
|
+
flexShrink: 0
|
|
818
|
+
},
|
|
819
|
+
children: [
|
|
820
|
+
/* @__PURE__ */ jsxs("button", {
|
|
821
|
+
onClick: onReplay,
|
|
822
|
+
...withFocusRing(),
|
|
823
|
+
style: {
|
|
824
|
+
...btn("primary"),
|
|
825
|
+
minHeight: 24
|
|
826
|
+
},
|
|
827
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
828
|
+
path: ICONS.play,
|
|
829
|
+
size: 11
|
|
830
|
+
}), "Replay queue"]
|
|
831
|
+
}),
|
|
832
|
+
/* @__PURE__ */ jsxs("button", {
|
|
833
|
+
onClick: onClear,
|
|
834
|
+
...withFocusRing(),
|
|
835
|
+
style: {
|
|
836
|
+
...btn("danger"),
|
|
837
|
+
minHeight: 24
|
|
838
|
+
},
|
|
839
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
840
|
+
path: ICONS.trash,
|
|
841
|
+
size: 11
|
|
842
|
+
}), "Clear queue"]
|
|
843
|
+
}),
|
|
844
|
+
/* @__PURE__ */ jsxs("span", {
|
|
845
|
+
style: {
|
|
846
|
+
marginLeft: "auto",
|
|
847
|
+
color: C.muted,
|
|
848
|
+
fontSize: 10,
|
|
849
|
+
alignSelf: "center"
|
|
850
|
+
},
|
|
851
|
+
children: [
|
|
852
|
+
queue.length,
|
|
853
|
+
" item",
|
|
854
|
+
queue.length !== 1 ? "s" : ""
|
|
855
|
+
]
|
|
856
|
+
})
|
|
857
|
+
]
|
|
858
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
859
|
+
style: {
|
|
860
|
+
flex: 1,
|
|
861
|
+
overflowY: "auto"
|
|
862
|
+
},
|
|
863
|
+
children: queue.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
864
|
+
style: {
|
|
865
|
+
padding: "20px 12px",
|
|
866
|
+
textAlign: "center",
|
|
867
|
+
color: C.muted
|
|
868
|
+
},
|
|
869
|
+
children: "Queue empty"
|
|
870
|
+
}) : queue.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
871
|
+
style: {
|
|
872
|
+
display: "flex",
|
|
873
|
+
alignItems: "center",
|
|
874
|
+
gap: 8,
|
|
875
|
+
padding: "7px 12px",
|
|
876
|
+
borderBottom: `1px solid ${C.border}`
|
|
877
|
+
},
|
|
878
|
+
children: [
|
|
879
|
+
/* @__PURE__ */ jsx("span", {
|
|
880
|
+
style: pill(queueStatusColor(item.status)),
|
|
881
|
+
children: item.status
|
|
882
|
+
}),
|
|
883
|
+
item.priority && item.priority !== "normal" && /* @__PURE__ */ jsx("span", {
|
|
884
|
+
style: pill(item.priority === "high" ? C.purple : C.muted),
|
|
885
|
+
children: item.priority
|
|
886
|
+
}),
|
|
887
|
+
/* @__PURE__ */ jsx("span", {
|
|
888
|
+
style: {
|
|
889
|
+
flex: 1,
|
|
890
|
+
overflow: "hidden",
|
|
891
|
+
textOverflow: "ellipsis",
|
|
892
|
+
whiteSpace: "nowrap",
|
|
893
|
+
color: C.text
|
|
894
|
+
},
|
|
895
|
+
children: item.actionName
|
|
896
|
+
}),
|
|
897
|
+
item.retryCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
898
|
+
style: {
|
|
899
|
+
color: C.muted,
|
|
900
|
+
fontSize: 10,
|
|
901
|
+
flexShrink: 0
|
|
902
|
+
},
|
|
903
|
+
children: [
|
|
904
|
+
"×",
|
|
905
|
+
item.retryCount,
|
|
906
|
+
"/",
|
|
907
|
+
item.maxRetries
|
|
908
|
+
]
|
|
909
|
+
})
|
|
910
|
+
]
|
|
911
|
+
}, item.id))
|
|
912
|
+
})]
|
|
913
|
+
});
|
|
580
914
|
}
|
|
581
915
|
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
|
-
|
|
916
|
+
return /* @__PURE__ */ jsx("div", { children: resources.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
917
|
+
style: {
|
|
918
|
+
padding: "20px 12px",
|
|
919
|
+
textAlign: "center",
|
|
920
|
+
color: C.muted
|
|
921
|
+
},
|
|
922
|
+
children: "No resources registered"
|
|
923
|
+
}) : resources.map((res) => /* @__PURE__ */ jsxs("div", {
|
|
924
|
+
style: {
|
|
925
|
+
padding: "7px 12px",
|
|
926
|
+
borderBottom: `1px solid ${C.border}`
|
|
927
|
+
},
|
|
928
|
+
children: [
|
|
929
|
+
/* @__PURE__ */ jsxs("div", {
|
|
930
|
+
style: {
|
|
931
|
+
display: "flex",
|
|
932
|
+
alignItems: "center",
|
|
933
|
+
gap: 6,
|
|
934
|
+
marginBottom: 3
|
|
935
|
+
},
|
|
936
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
937
|
+
style: pill(resourceStatusColor(res.status)),
|
|
938
|
+
children: res.status
|
|
939
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
940
|
+
style: {
|
|
941
|
+
color: C.muted,
|
|
942
|
+
fontSize: 10
|
|
943
|
+
},
|
|
944
|
+
children: res.strategy.name
|
|
945
|
+
})]
|
|
946
|
+
}),
|
|
947
|
+
/* @__PURE__ */ jsx("div", {
|
|
948
|
+
style: {
|
|
949
|
+
color: C.text,
|
|
950
|
+
overflow: "hidden",
|
|
951
|
+
textOverflow: "ellipsis",
|
|
952
|
+
whiteSpace: "nowrap",
|
|
953
|
+
fontSize: 10,
|
|
954
|
+
marginBottom: 2
|
|
955
|
+
},
|
|
956
|
+
children: res.url
|
|
957
|
+
}),
|
|
958
|
+
/* @__PURE__ */ jsxs("div", {
|
|
959
|
+
style: {
|
|
960
|
+
display: "flex",
|
|
961
|
+
gap: 10,
|
|
962
|
+
color: C.muted,
|
|
963
|
+
fontSize: 10
|
|
964
|
+
},
|
|
965
|
+
children: [
|
|
966
|
+
/* @__PURE__ */ jsxs("span", {
|
|
967
|
+
title: "Cache hits",
|
|
968
|
+
style: {
|
|
969
|
+
display: "inline-flex",
|
|
970
|
+
alignItems: "center",
|
|
971
|
+
gap: 3
|
|
972
|
+
},
|
|
973
|
+
children: [
|
|
974
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
975
|
+
path: ICONS.arrowUp,
|
|
976
|
+
size: 10
|
|
977
|
+
}),
|
|
978
|
+
res.cacheHits,
|
|
979
|
+
" hit",
|
|
980
|
+
res.cacheHits !== 1 ? "s" : ""
|
|
981
|
+
]
|
|
982
|
+
}),
|
|
983
|
+
/* @__PURE__ */ jsxs("span", {
|
|
984
|
+
title: "Cache misses",
|
|
985
|
+
style: {
|
|
986
|
+
display: "inline-flex",
|
|
987
|
+
alignItems: "center",
|
|
988
|
+
gap: 3
|
|
989
|
+
},
|
|
990
|
+
children: [
|
|
991
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
992
|
+
path: ICONS.arrowDown,
|
|
993
|
+
size: 10
|
|
994
|
+
}),
|
|
995
|
+
res.cacheMisses,
|
|
996
|
+
" miss",
|
|
997
|
+
res.cacheMisses !== 1 ? "es" : ""
|
|
998
|
+
]
|
|
999
|
+
}),
|
|
1000
|
+
res.cachedAt && /* @__PURE__ */ jsxs("span", {
|
|
1001
|
+
title: "Cached at",
|
|
1002
|
+
style: {
|
|
1003
|
+
display: "inline-flex",
|
|
1004
|
+
alignItems: "center",
|
|
1005
|
+
gap: 3
|
|
1006
|
+
},
|
|
1007
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1008
|
+
path: ICONS.clock,
|
|
1009
|
+
size: 10
|
|
1010
|
+
}), new Date(res.cachedAt).toLocaleTimeString()]
|
|
1011
|
+
})
|
|
1012
|
+
]
|
|
1013
|
+
})
|
|
1014
|
+
]
|
|
1015
|
+
}, res.url)) });
|
|
1016
|
+
}
|
|
1017
|
+
//#endregion
|
|
1018
|
+
export { EidosDevtools };
|