@sweidos/eidos 2.0.0 → 2.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 +53 -14
- package/dist/action.js +152 -134
- package/dist/action.js.map +1 -1
- package/dist/devtools.js +253 -20
- package/dist/eidos.cjs +2 -2
- package/dist/eidos.cjs.map +1 -1
- package/dist/index.d.ts +101 -13
- package/dist/index.js +40 -35
- package/dist/react/hooks.js +30 -27
- package/dist/react/hooks.js.map +1 -1
- package/dist/resource.js +22 -22
- package/dist/resource.js.map +1 -1
- package/dist/runtime.js +29 -24
- package/dist/runtime.js.map +1 -1
- package/dist/store-slices.js +31 -20
- package/dist/store-slices.js.map +1 -1
- package/dist/store.js +22 -19
- package/dist/store.js.map +1 -1
- package/dist/stores.js +31 -22
- package/dist/stores.js.map +1 -1
- package/dist/testing.cjs +3 -2
- package/dist/testing.js +3 -2
- package/dist/types.js +19 -8
- package/dist/types.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -158,10 +158,21 @@ export declare class AsyncStorageQueueStorage implements QueueStorage {
|
|
|
158
158
|
|
|
159
159
|
export declare type CacheStrategy = 'cache-first' | 'stale-while-revalidate' | 'network-first';
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Cancel an invocation by its `idempotencyKey` (from `ActionContext` /
|
|
163
|
+
* `onOptimistic`). Aborts the in-flight call if `cancellable: true` and
|
|
164
|
+
* still running, otherwise removes a not-yet-replayed queued item.
|
|
165
|
+
* Returns `true` if something was cancelled/removed.
|
|
166
|
+
*
|
|
167
|
+
* Shared by every `ActionHandle.cancel()` and by devtools, which can't
|
|
168
|
+
* address a specific handle from a queue item alone.
|
|
169
|
+
*/
|
|
170
|
+
export declare function cancelByIdempotencyKey(idempotencyKey: string): Promise<boolean>;
|
|
171
|
+
|
|
161
172
|
/** Remove all items from the action queue (storage + in-memory store). */
|
|
162
173
|
export declare function clearQueue(): Promise<void>;
|
|
163
174
|
|
|
164
|
-
declare interface ConflictConfig {
|
|
175
|
+
export declare interface ConflictConfig {
|
|
165
176
|
/**
|
|
166
177
|
* - `'serverWins'`: drop the queued item, keeping the server's state.
|
|
167
178
|
* - `'clientWins'`: keep retrying — the client's write should eventually
|
|
@@ -177,7 +188,7 @@ declare interface ConflictConfig {
|
|
|
177
188
|
* Passed to `ConflictConfig.resolve` (for `'merge'`/`'custom'` strategies)
|
|
178
189
|
* when a queued action's replay receives a 4xx response.
|
|
179
190
|
*/
|
|
180
|
-
declare interface ConflictContext {
|
|
191
|
+
export declare interface ConflictContext {
|
|
181
192
|
/** Whatever `fn` threw — typically a `Response` or an error with `.status`. */
|
|
182
193
|
error: unknown;
|
|
183
194
|
/** The original arguments the action was queued with. */
|
|
@@ -194,7 +205,7 @@ declare interface ConflictContext {
|
|
|
194
205
|
* - `{ resolved: args }`: replace the queued args and retry immediately
|
|
195
206
|
* on the next replay pass.
|
|
196
207
|
*/
|
|
197
|
-
declare type ConflictResolution = 'retry' | 'skip' | {
|
|
208
|
+
export declare type ConflictResolution = 'retry' | 'skip' | {
|
|
198
209
|
resolved: any[];
|
|
199
210
|
};
|
|
200
211
|
|
|
@@ -213,6 +224,15 @@ export declare interface EidosConfig {
|
|
|
213
224
|
swPath?: string;
|
|
214
225
|
/** Automatically replay the action queue on reconnect. Default: true. */
|
|
215
226
|
autoReplay?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Opt-in reliability telemetry. Called with a snapshot of cumulative
|
|
229
|
+
* `neverLose` queue outcome counters (`ReliabilityStats`) every
|
|
230
|
+
* `reliabilityReportInterval` ms — wire this up to your analytics backend.
|
|
231
|
+
* Not called if omitted.
|
|
232
|
+
*/
|
|
233
|
+
onReliabilityReport?: (stats: ReliabilityStats) => void;
|
|
234
|
+
/** Interval (ms) between `onReliabilityReport` calls. Default: 60000. */
|
|
235
|
+
reliabilityReportInterval?: number;
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
/**
|
|
@@ -251,6 +271,13 @@ export declare interface EidosReadable<T> {
|
|
|
251
271
|
getState(): T;
|
|
252
272
|
}
|
|
253
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Cumulative, session-scoped `neverLose` queue outcome counters — opt-in
|
|
276
|
+
* reliability telemetry. Re-emits only when a counter changes. See
|
|
277
|
+
* `EidosConfig.onReliabilityReport` to forward these to an analytics backend.
|
|
278
|
+
*/
|
|
279
|
+
export declare const eidosReliabilityStats: EidosReadable<ReliabilityStats>;
|
|
280
|
+
|
|
254
281
|
/**
|
|
255
282
|
* Live cache state for a single registered resource URL.
|
|
256
283
|
* @example
|
|
@@ -266,6 +293,7 @@ export declare interface EidosState {
|
|
|
266
293
|
swError?: string;
|
|
267
294
|
resources: Record<string, ResourceEntry>;
|
|
268
295
|
queue: ActionQueueItem[];
|
|
296
|
+
reliability: ReliabilityStats;
|
|
269
297
|
}
|
|
270
298
|
|
|
271
299
|
/**
|
|
@@ -278,7 +306,7 @@ export declare const eidosStatus: EidosReadable<{
|
|
|
278
306
|
swError: string | undefined;
|
|
279
307
|
}>;
|
|
280
308
|
|
|
281
|
-
export declare interface EidosStore extends EidosState, ResourceActions, QueueActions {
|
|
309
|
+
export declare interface EidosStore extends EidosState, ResourceActions, QueueActions, ReliabilityActions {
|
|
282
310
|
setOnline: (online: boolean) => void;
|
|
283
311
|
setSwStatus: (status: EidosState['swStatus'], error?: string) => void;
|
|
284
312
|
}
|
|
@@ -310,6 +338,17 @@ export declare function isBgSyncSupported(): boolean;
|
|
|
310
338
|
|
|
311
339
|
declare type Listener = () => void;
|
|
312
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Calls `callback` once each time the action queue drains from non-empty → 0.
|
|
343
|
+
* Framework-agnostic equivalent of `useEidosOnDrain` for Svelte/Vue/vanilla.
|
|
344
|
+
* Returns an unsubscribe function.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* // Svelte
|
|
348
|
+
* onMount(() => onQueueDrain(() => toast.success('All offline actions synced!')))
|
|
349
|
+
*/
|
|
350
|
+
export declare function onQueueDrain(callback: () => void): () => void;
|
|
351
|
+
|
|
313
352
|
/**
|
|
314
353
|
* Handle for a URL pattern (`/api/products/*`, `/api/users/:id`, `**`).
|
|
315
354
|
* The SW intercepts all matching requests automatically — there is no single
|
|
@@ -361,6 +400,32 @@ export declare interface QueueStorage {
|
|
|
361
400
|
|
|
362
401
|
export declare function registerPushCallbacks(handlers: PushHandlers): void;
|
|
363
402
|
|
|
403
|
+
declare interface ReliabilityActions {
|
|
404
|
+
recordReliabilityEvent: (event: keyof ReliabilityStats) => void;
|
|
405
|
+
resetReliabilityStats: () => void;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Cumulative, session-scoped counters for `neverLose` queue outcomes — opt-in
|
|
410
|
+
* telemetry surfaced via `eidosReliabilityStats` / `useEidosReliabilityStats()`
|
|
411
|
+
* and `EidosConfig.onReliabilityReport`. Reset on page reload (not persisted).
|
|
412
|
+
*/
|
|
413
|
+
export declare interface ReliabilityStats {
|
|
414
|
+
[key: string]: number;
|
|
415
|
+
/** Actions persisted to the queue (offline, or online call that threw). */
|
|
416
|
+
queued: number;
|
|
417
|
+
/** Queue items that executed successfully (first attempt or a retry). */
|
|
418
|
+
succeeded: number;
|
|
419
|
+
/** Queue items that exhausted `maxRetries` and moved to `'failed'`. */
|
|
420
|
+
failed: number;
|
|
421
|
+
/** Replay attempts that failed but will retry (haven't exhausted `maxRetries`). */
|
|
422
|
+
retried: number;
|
|
423
|
+
/** Queue items dropped by a `serverWins`/`merge`/`custom` conflict resolution. */
|
|
424
|
+
conflicted: number;
|
|
425
|
+
/** Queue items removed via `handle.cancel(idempotencyKey)` before replay completed. */
|
|
426
|
+
cancelled: number;
|
|
427
|
+
}
|
|
428
|
+
|
|
364
429
|
export declare function replayQueue(): Promise<ReplayResult>;
|
|
365
430
|
|
|
366
431
|
/** Summary returned by replayQueue(). */
|
|
@@ -381,6 +446,14 @@ export declare interface ReplayResult {
|
|
|
381
446
|
cancelled: number;
|
|
382
447
|
}
|
|
383
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Reset a `'failed'` queue item back to `'pending'` so the next
|
|
451
|
+
* `replayQueue()` retries it — clears `error`/`nextRetryAt` and resets
|
|
452
|
+
* `retryCount` to 0. Returns `true` if the item existed and was failed.
|
|
453
|
+
* Used by devtools' per-item "Retry" action.
|
|
454
|
+
*/
|
|
455
|
+
export declare function requeueItem(id: string): Promise<boolean>;
|
|
456
|
+
|
|
384
457
|
export declare function _resetEidos(): void;
|
|
385
458
|
|
|
386
459
|
/**
|
|
@@ -402,6 +475,14 @@ export declare interface ResourceConfig {
|
|
|
402
475
|
strategy?: CacheStrategy;
|
|
403
476
|
/** Custom cache bucket name. Defaults to 'eidos-resources-v1'. */
|
|
404
477
|
cacheName?: string;
|
|
478
|
+
/**
|
|
479
|
+
* Cache schema version. Bump when the response shape changes so old
|
|
480
|
+
* cached entries (a different shape) aren't served from a stale bucket.
|
|
481
|
+
* Appended to `cacheName` as a suffix (e.g. `eidos-resources-v1-v2`).
|
|
482
|
+
* Old-versioned buckets are left in Cache Storage — clear them via
|
|
483
|
+
* `caches.delete()` if needed.
|
|
484
|
+
*/
|
|
485
|
+
version?: string | number;
|
|
405
486
|
/** Max age of cached response in milliseconds. Expired entries trigger a network fetch. */
|
|
406
487
|
maxAge?: number;
|
|
407
488
|
}
|
|
@@ -480,14 +561,6 @@ export declare function useEidos(): EidosStore;
|
|
|
480
561
|
*/
|
|
481
562
|
export declare function useEidosAction(id: string): ActionQueueItem | undefined;
|
|
482
563
|
|
|
483
|
-
/**
|
|
484
|
-
* Calls `callback` once each time the action queue drains from non-empty → 0.
|
|
485
|
-
* Stable callback reference not required — always calls the latest version.
|
|
486
|
-
* Use for "all offline actions synced!" toasts.
|
|
487
|
-
*
|
|
488
|
-
* @example
|
|
489
|
-
* useEidosOnDrain(() => toast.success('All offline actions synced!'))
|
|
490
|
-
*/
|
|
491
564
|
export declare function useEidosOnDrain(callback: () => void): void;
|
|
492
565
|
|
|
493
566
|
/** The current action queue. */
|
|
@@ -505,6 +578,21 @@ export declare function useEidosQueueStats(): {
|
|
|
505
578
|
total: number;
|
|
506
579
|
};
|
|
507
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Calls `callback` once each time the action queue drains from non-empty → 0.
|
|
583
|
+
* Stable callback reference not required — always calls the latest version.
|
|
584
|
+
* Use for "all offline actions synced!" toasts.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* useEidosOnDrain(() => toast.success('All offline actions synced!'))
|
|
588
|
+
*/
|
|
589
|
+
/**
|
|
590
|
+
* Cumulative, session-scoped `neverLose` queue outcome counters — opt-in
|
|
591
|
+
* reliability telemetry for dashboards/devtools. Re-renders only when a
|
|
592
|
+
* counter changes.
|
|
593
|
+
*/
|
|
594
|
+
export declare function useEidosReliabilityStats(): ReliabilityStats;
|
|
595
|
+
|
|
508
596
|
/** Live state for a single registered resource URL. */
|
|
509
597
|
export declare function useEidosResource(url: string): ResourceEntry;
|
|
510
598
|
|
|
@@ -528,7 +616,7 @@ export declare const useEidosStore: {
|
|
|
528
616
|
setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
|
|
529
617
|
};
|
|
530
618
|
|
|
531
|
-
export declare const VERSION = "2.
|
|
619
|
+
export declare const VERSION = "2.2.0";
|
|
532
620
|
|
|
533
621
|
/**
|
|
534
622
|
* Bulk-prefetch an array of resource handles concurrently, warming the cache
|
package/dist/index.js
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
1
|
import { useEidosStore as o } from "./store.js";
|
|
2
|
-
import { getSwRegistration as
|
|
3
|
-
import { resource as d, resourcePattern as n, setQueryInvalidator as
|
|
2
|
+
import { getSwRegistration as i, isBgSyncSupported as s, registerPushCallbacks as t, sendToWorker as u, setOfflineSimulation as a } from "./sw-bridge.js";
|
|
3
|
+
import { resource as d, resourcePattern as n, setQueryInvalidator as c, warmCache as p } from "./resource.js";
|
|
4
4
|
import { _getQueueStorage as f, setQueueStorage as E } from "./queue-storage.js";
|
|
5
|
-
import { action as
|
|
5
|
+
import { action as Q, cancelByIdempotencyKey as y, clearQueue as g, replayQueue as R, requeueItem as b } from "./action.js";
|
|
6
6
|
import { subscribeReplayOnReconnect as O } from "./replay.js";
|
|
7
|
-
import { _resetEidos as
|
|
8
|
-
import { AsyncStorageQueueStorage as
|
|
9
|
-
import { EidosProvider as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { _resetEidos as P, initEidos as h } from "./runtime.js";
|
|
8
|
+
import { AsyncStorageQueueStorage as v } from "./async-storage-adapter.js";
|
|
9
|
+
import { EidosProvider as B } from "./react/Provider.js";
|
|
10
|
+
import { eidosAction as D, eidosQueue as _, eidosQueueStats as q, eidosReliabilityStats as x, eidosResource as K, eidosStatus as N, eidosStore as T, onQueueDrain as V } from "./stores.js";
|
|
11
|
+
import { useEidos as j, useEidosAction as z, useEidosOnDrain as F, useEidosQueue as G, useEidosQueueStats as H, useEidosReliabilityStats as J, useEidosResource as L, useEidosResources as M, useEidosStatus as U } from "./react/hooks.js";
|
|
12
|
+
import { VERSION as Y } from "./version.js";
|
|
13
13
|
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
v as AsyncStorageQueueStorage,
|
|
15
|
+
B as EidosProvider,
|
|
16
|
+
Y as VERSION,
|
|
17
17
|
f as _getQueueStorage,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
K as
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
i as
|
|
18
|
+
P as _resetEidos,
|
|
19
|
+
Q as action,
|
|
20
|
+
y as cancelByIdempotencyKey,
|
|
21
|
+
g as clearQueue,
|
|
22
|
+
D as eidosAction,
|
|
23
|
+
_ as eidosQueue,
|
|
24
|
+
q as eidosQueueStats,
|
|
25
|
+
x as eidosReliabilityStats,
|
|
26
|
+
K as eidosResource,
|
|
27
|
+
N as eidosStatus,
|
|
28
|
+
T as eidosStore,
|
|
29
|
+
i as getSwRegistration,
|
|
30
|
+
h as initEidos,
|
|
31
|
+
s as isBgSyncSupported,
|
|
32
|
+
V as onQueueDrain,
|
|
30
33
|
t as registerPushCallbacks,
|
|
31
34
|
R as replayQueue,
|
|
35
|
+
b as requeueItem,
|
|
32
36
|
d as resource,
|
|
33
37
|
n as resourcePattern,
|
|
34
38
|
u as sendToWorker,
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
a as setOfflineSimulation,
|
|
40
|
+
c as setQueryInvalidator,
|
|
37
41
|
E as setQueueStorage,
|
|
38
42
|
O as subscribeReplayOnReconnect,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
j as useEidos,
|
|
44
|
+
z as useEidosAction,
|
|
45
|
+
F as useEidosOnDrain,
|
|
46
|
+
G as useEidosQueue,
|
|
47
|
+
H as useEidosQueueStats,
|
|
48
|
+
J as useEidosReliabilityStats,
|
|
49
|
+
L as useEidosResource,
|
|
50
|
+
M as useEidosResources,
|
|
51
|
+
U as useEidosStatus,
|
|
47
52
|
o as useEidosStore,
|
|
48
|
-
|
|
53
|
+
p as warmCache
|
|
49
54
|
};
|
package/dist/react/hooks.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { useEidosStore as o } from "../store.js";
|
|
2
1
|
import { countQueueByStatus as l } from "../types.js";
|
|
3
|
-
import {
|
|
2
|
+
import { useEidosStore as r } from "../store.js";
|
|
3
|
+
import { onQueueDrain as E } from "../stores.js";
|
|
4
|
+
import { useEffect as s, useRef as p, useSyncExternalStore as S } from "react";
|
|
4
5
|
function u(e) {
|
|
5
6
|
const t = e ?? ((n) => n);
|
|
6
|
-
return
|
|
7
|
+
return S(r.subscribe, () => t(r.getState()));
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
+
function b() {
|
|
9
10
|
return u();
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
+
function w() {
|
|
12
13
|
return u((e) => e.resources);
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
+
function Q(e) {
|
|
15
16
|
return u((t) => t.resources[e]);
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
+
function $() {
|
|
18
19
|
return u((e) => e.queue);
|
|
19
20
|
}
|
|
20
|
-
function
|
|
21
|
+
function q(e) {
|
|
21
22
|
return u((t) => t.queue.find((n) => n.id === e));
|
|
22
23
|
}
|
|
23
24
|
function O() {
|
|
@@ -27,34 +28,36 @@ function O() {
|
|
|
27
28
|
swError: u((e) => e.swError)
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
|
-
function
|
|
31
|
-
const [e, t, n,
|
|
32
|
-
const { pending:
|
|
33
|
-
return `${f},${a},${d}
|
|
31
|
+
function x() {
|
|
32
|
+
const [e, t, n, i] = u((o) => {
|
|
33
|
+
const { pending: c, failed: f, replaying: a, total: d } = l(o.queue);
|
|
34
|
+
return `${c},${f},${a},${d}`;
|
|
34
35
|
}).split(",");
|
|
35
36
|
return {
|
|
36
37
|
pending: +e,
|
|
37
38
|
failed: +t,
|
|
38
39
|
replaying: +n,
|
|
39
|
-
total: +
|
|
40
|
+
total: +i
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
function D() {
|
|
44
|
+
return u((e) => e.reliability);
|
|
45
|
+
}
|
|
46
|
+
function A(e) {
|
|
47
|
+
const t = p(e);
|
|
48
|
+
s(() => {
|
|
49
|
+
t.current = e;
|
|
50
|
+
}), s(() => E(() => t.current()), []);
|
|
49
51
|
}
|
|
50
52
|
export {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
b as useEidos,
|
|
54
|
+
q as useEidosAction,
|
|
55
|
+
A as useEidosOnDrain,
|
|
56
|
+
$ as useEidosQueue,
|
|
57
|
+
x as useEidosQueueStats,
|
|
58
|
+
D as useEidosReliabilityStats,
|
|
59
|
+
Q as useEidosResource,
|
|
60
|
+
w as useEidosResources,
|
|
58
61
|
O as useEidosStatus
|
|
59
62
|
};
|
|
60
63
|
|
package/dist/react/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":[],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import { useEffect, useRef, useSyncExternalStore } from 'react';\nimport { useEidosStore } from '../store';\nimport type { EidosStore } from '../store';\nimport { countQueueByStatus } from '../types';\n\nfunction useStore(): EidosStore;\nfunction useStore<T>(selector: (state: EidosStore) => T): T;\nfunction useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T);\n return useSyncExternalStore(useEidosStore.subscribe, () => fn(useEidosStore.getState()));\n}\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useStore();\n}\n\n/** All registered resources — only re-renders when the resources map changes, not on queue mutations. */\nexport function useEidosResources() {\n return useStore((s) => s.resources);\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useStore((s) => s.resources[url]);\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useStore((s) => s.queue);\n}\n\n/**\n * Live state for a single queue item by ID. Only re-renders when that specific\n * item changes — cheaper than `useEidosQueue().find(id)` which re-renders on\n * any queue mutation.\n */\nexport function useEidosAction(id: string) {\n return useStore((s) => s.queue.find((item) => item.id === id));\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useStore((s) => s.isOnline);\n const swStatus = useStore((s) => s.swStatus);\n const swError = useStore((s) => s.swError);\n return { isOnline, swStatus, swError };\n}\n\n/**\n * Queue counts — single subscription, single loop. Re-renders only when a\n * count changes, not on every queue mutation. Use for badges and status bars\n * instead of `useEidosQueue()` when you only need numbers, not full items.\n */\nexport function useEidosQueueStats() {\n // Encode as a comma-separated string so useSyncExternalStore's Object.is\n // comparison bails out correctly when counts haven't changed. One loop,\n // one subscription — cheaper than four separate filter() passes.\n const encoded = useStore((s) => {\n const { pending, failed, replaying, total } = countQueueByStatus(s.queue);\n return `${pending},${failed},${replaying},${total}`;\n });\n const [p, f, r, t] = encoded.split(',');\n return { pending: +p, failed: +f, replaying: +r, total: +t };\n}\n\n/**\n * Calls `callback` once each time the action queue drains from non-empty → 0.\n * Stable callback reference not required — always calls the latest version.\n * Use for \"all offline actions synced!\" toasts.\n *\n * @example\n * useEidosOnDrain(() => toast.success('All offline actions synced!'))\n */\nexport function
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import { useEffect, useRef, useSyncExternalStore } from 'react';\nimport { useEidosStore } from '../store';\nimport type { EidosStore } from '../store';\nimport { countQueueByStatus } from '../types';\nimport { onQueueDrain } from '../stores';\n\nfunction useStore(): EidosStore;\nfunction useStore<T>(selector: (state: EidosStore) => T): T;\nfunction useStore<T = EidosStore>(selector?: (state: EidosStore) => T): T {\n const fn = selector ?? ((s: EidosStore) => s as unknown as T);\n return useSyncExternalStore(useEidosStore.subscribe, () => fn(useEidosStore.getState()));\n}\n\n/** Full Eidos store — prefer the narrower hooks below for performance. */\nexport function useEidos() {\n return useStore();\n}\n\n/** All registered resources — only re-renders when the resources map changes, not on queue mutations. */\nexport function useEidosResources() {\n return useStore((s) => s.resources);\n}\n\n/** Live state for a single registered resource URL. */\nexport function useEidosResource(url: string) {\n return useStore((s) => s.resources[url]);\n}\n\n/** The current action queue. */\nexport function useEidosQueue() {\n return useStore((s) => s.queue);\n}\n\n/**\n * Live state for a single queue item by ID. Only re-renders when that specific\n * item changes — cheaper than `useEidosQueue().find(id)` which re-renders on\n * any queue mutation.\n */\nexport function useEidosAction(id: string) {\n return useStore((s) => s.queue.find((item) => item.id === id));\n}\n\n/**\n * Online + SW status — cheap subscription, safe to use in header components.\n * Three separate primitive selectors so each only triggers a re-render when\n * its own value changes (no object-reference churn from a combined selector).\n */\nexport function useEidosStatus() {\n const isOnline = useStore((s) => s.isOnline);\n const swStatus = useStore((s) => s.swStatus);\n const swError = useStore((s) => s.swError);\n return { isOnline, swStatus, swError };\n}\n\n/**\n * Queue counts — single subscription, single loop. Re-renders only when a\n * count changes, not on every queue mutation. Use for badges and status bars\n * instead of `useEidosQueue()` when you only need numbers, not full items.\n */\nexport function useEidosQueueStats() {\n // Encode as a comma-separated string so useSyncExternalStore's Object.is\n // comparison bails out correctly when counts haven't changed. One loop,\n // one subscription — cheaper than four separate filter() passes.\n const encoded = useStore((s) => {\n const { pending, failed, replaying, total } = countQueueByStatus(s.queue);\n return `${pending},${failed},${replaying},${total}`;\n });\n const [p, f, r, t] = encoded.split(',');\n return { pending: +p, failed: +f, replaying: +r, total: +t };\n}\n\n/**\n * Calls `callback` once each time the action queue drains from non-empty → 0.\n * Stable callback reference not required — always calls the latest version.\n * Use for \"all offline actions synced!\" toasts.\n *\n * @example\n * useEidosOnDrain(() => toast.success('All offline actions synced!'))\n */\n/**\n * Cumulative, session-scoped `neverLose` queue outcome counters — opt-in\n * reliability telemetry for dashboards/devtools. Re-renders only when a\n * counter changes.\n */\nexport function useEidosReliabilityStats() {\n return useStore((s) => s.reliability);\n}\n\nexport function useEidosOnDrain(callback: () => void) {\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n useEffect(() => onQueueDrain(() => callbackRef.current()), []);\n}\n"],"mappings":";;;;AAQA,SAAS,EAAyB,GAAwC;AACxE,QAAM,IAAK,MAAA,CAAc,MAAkB;AAC3C,SAAO,EAAqB,EAAc,WAAA,MAAiB,EAAG,EAAc,SAAS,CAAC,CAAC;AACzF;AAGA,SAAgB,IAAW;AACzB,SAAO,EAAS;AAClB;AAGA,SAAgB,IAAoB;AAClC,SAAO,EAAA,CAAU,MAAM,EAAE,SAAS;AACpC;AAGA,SAAgB,EAAiB,GAAa;AAC5C,SAAO,EAAA,CAAU,MAAM,EAAE,UAAU,CAAA,CAAI;AACzC;AAGA,SAAgB,IAAgB;AAC9B,SAAO,EAAA,CAAU,MAAM,EAAE,KAAK;AAChC;AAOA,SAAgB,EAAe,GAAY;AACzC,SAAO,EAAA,CAAU,MAAM,EAAE,MAAM,KAAA,CAAM,MAAS,EAAK,OAAO,CAAE,CAAC;AAC/D;AAOA,SAAgB,IAAiB;AAI/B,SAAO;AAAA,IAAE,UAHQ,EAAA,CAAU,MAAM,EAAE,QAG1B;AAAA,IAAU,UAFF,EAAA,CAAU,MAAM,EAAE,QAEhB;AAAA,IAAU,SADb,EAAA,CAAU,MAAM,EAAE,OACL;AAAA,EAAQ;AACvC;AAOA,SAAgB,IAAqB;AAQnC,QAAM,CAAC,GAAG,GAAG,GAAG,CAAA,IAJA,EAAA,CAAU,MAAM;AAC9B,UAAM,EAAE,SAAA,GAAS,QAAA,GAAQ,WAAA,GAAW,OAAA,EAAA,IAAU,EAAmB,EAAE,KAAK;AACxE,WAAO,GAAG,CAAA,IAAW,CAAA,IAAU,CAAA,IAAa,CAAA;AAAA,EAC9C,CACqB,EAAQ,MAAM,GAAG;AACtC,SAAO;AAAA,IAAE,SAAS,CAAC;AAAA,IAAG,QAAQ,CAAC;AAAA,IAAG,WAAW,CAAC;AAAA,IAAG,OAAO,CAAC;AAAA,EAAE;AAC7D;AAeA,SAAgB,IAA2B;AACzC,SAAO,EAAA,CAAU,MAAM,EAAE,WAAW;AACtC;AAEA,SAAgB,EAAgB,GAAsB;AACpD,QAAM,IAAc,EAAO,CAAQ;AAEnC,EAAA,EAAA,MAAgB;AACd,IAAA,EAAY,UAAU;AAAA,EACxB,CAAC,GAED,EAAA,MAAgB,EAAA,MAAmB,EAAY,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC/D"}
|
package/dist/resource.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useEidosStore as h } from "./store.js";
|
|
2
2
|
import { sendToWorker as p } from "./sw-bridge.js";
|
|
3
|
-
var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(),
|
|
3
|
+
var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), g = null;
|
|
4
4
|
function _(e) {
|
|
5
|
-
|
|
5
|
+
g = e;
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function f(e) {
|
|
8
8
|
return e.includes("*") || /:[^/]+/.test(e);
|
|
9
9
|
}
|
|
10
10
|
function k(e) {
|
|
11
11
|
return "^" + e.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]+").replace(/:[^/]+/g, "[^/]+") + "$";
|
|
12
12
|
}
|
|
13
13
|
function m(e, s) {
|
|
14
|
-
const a = b(s), t =
|
|
14
|
+
const a = b(s), t = f(e) ? k(e) : void 0, r = {
|
|
15
15
|
url: e,
|
|
16
16
|
config: s,
|
|
17
17
|
strategy: a,
|
|
@@ -40,17 +40,17 @@ function y(e, s, a) {
|
|
|
40
40
|
if (t) {
|
|
41
41
|
const r = await t.keys(), n = a ? new RegExp(a) : null, c = e.startsWith("http");
|
|
42
42
|
await Promise.all(r.filter((i) => {
|
|
43
|
-
const o = i.url,
|
|
44
|
-
return n ? n.test(c ? o :
|
|
43
|
+
const o = i.url, u = new URL(o).pathname;
|
|
44
|
+
return n ? n.test(c ? o : u) : c ? o === e : o === e || u === e;
|
|
45
45
|
}).map((i) => t.delete(i)));
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
f(e) || h.getState().updateResource(e, {
|
|
48
48
|
status: "stale",
|
|
49
49
|
cachedAt: void 0,
|
|
50
50
|
lastEvent: "cache-cleared",
|
|
51
51
|
cacheHits: 0,
|
|
52
52
|
cacheMisses: 0
|
|
53
|
-
}),
|
|
53
|
+
}), g?.(["eidos", e]);
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
function E(e) {
|
|
@@ -62,7 +62,7 @@ function E(e) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
function A(e, s) {
|
|
65
|
-
if (
|
|
65
|
+
if (f(e)) throw new Error(`[eidos] resource('${e}') is a URL pattern — use resourcePattern('${e}', config) instead. Pattern handles only support invalidate()/unregister(); the SW intercepts matching requests automatically.`);
|
|
66
66
|
if (d.has(e)) return d.get(e);
|
|
67
67
|
const { strategy: a } = m(e, s), t = {
|
|
68
68
|
url: e,
|
|
@@ -88,8 +88,8 @@ function A(e, s) {
|
|
|
88
88
|
};
|
|
89
89
|
return d.set(e, t), t;
|
|
90
90
|
}
|
|
91
|
-
function
|
|
92
|
-
if (!
|
|
91
|
+
function $(e, s) {
|
|
92
|
+
if (!f(e)) throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);
|
|
93
93
|
if (d.has(e)) return d.get(e);
|
|
94
94
|
const { strategy: a, regexStr: t } = m(e, s), r = {
|
|
95
95
|
url: e,
|
|
@@ -109,8 +109,8 @@ async function S(e, s, a) {
|
|
|
109
109
|
const r = await caches.open(a.cacheName).catch(() => null);
|
|
110
110
|
try {
|
|
111
111
|
if (a.swStrategy !== "network-first") {
|
|
112
|
-
const i = r ? await r.match(e).catch(() => null) : null, o = h.getState().resources[e],
|
|
113
|
-
if (i && !
|
|
112
|
+
const i = r ? await r.match(e).catch(() => null) : null, o = h.getState().resources[e], u = s.maxAge !== void 0 && o?.cachedAt !== void 0 && Date.now() - o.cachedAt > s.maxAge;
|
|
113
|
+
if (i && !u)
|
|
114
114
|
return t.updateResource(e, {
|
|
115
115
|
status: "fresh",
|
|
116
116
|
lastEvent: "cache-hit",
|
|
@@ -150,7 +150,7 @@ async function S(e, s, a) {
|
|
|
150
150
|
}
|
|
151
151
|
function b(e) {
|
|
152
152
|
const s = e.strategy;
|
|
153
|
-
return e.offline ?
|
|
153
|
+
return e.offline ? v(s ?? "stale-while-revalidate", e.cacheName, e.version) : v(s ?? "network-first", e.cacheName, e.version);
|
|
154
154
|
}
|
|
155
155
|
var x = {
|
|
156
156
|
"stale-while-revalidate": "StaleWhileRevalidate",
|
|
@@ -200,18 +200,18 @@ new NetworkFirst({
|
|
|
200
200
|
})`
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
|
-
function
|
|
204
|
-
const
|
|
203
|
+
function v(e, s, a) {
|
|
204
|
+
const t = C[e], r = s ?? "eidos-resources-v1";
|
|
205
205
|
return {
|
|
206
206
|
name: x[e],
|
|
207
207
|
swStrategy: e,
|
|
208
|
-
cacheName:
|
|
209
|
-
reasoning:
|
|
210
|
-
behavior:
|
|
208
|
+
cacheName: a !== void 0 ? `${r}-v${a}` : r,
|
|
209
|
+
reasoning: t.reasoning,
|
|
210
|
+
behavior: t.behavior,
|
|
211
211
|
equivalentCode: ""
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
|
-
async function
|
|
214
|
+
async function O(e) {
|
|
215
215
|
const s = await Promise.allSettled(e.map((t) => t.prefetch())), a = s.filter((t) => t.status === "rejected").map((t) => t.reason);
|
|
216
216
|
return {
|
|
217
217
|
warmed: s.filter((t) => t.status === "fulfilled").length,
|
|
@@ -221,9 +221,9 @@ async function T(e) {
|
|
|
221
221
|
}
|
|
222
222
|
export {
|
|
223
223
|
A as resource,
|
|
224
|
-
|
|
224
|
+
$ as resourcePattern,
|
|
225
225
|
_ as setQueryInvalidator,
|
|
226
|
-
|
|
226
|
+
O as warmCache
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
//# sourceMappingURL=resource.js.map
|