@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/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.0.0";
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 s, isBgSyncSupported as i, registerPushCallbacks as t, sendToWorker as u, setOfflineSimulation as m } from "./sw-bridge.js";
3
- import { resource as d, resourcePattern as n, setQueryInvalidator as p, warmCache as c } from "./resource.js";
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 g, clearQueue as l, replayQueue as R } from "./action.js";
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 A, initEidos as P } from "./runtime.js";
8
- import { AsyncStorageQueueStorage as k } from "./async-storage-adapter.js";
9
- import { EidosProvider as w } from "./react/Provider.js";
10
- import { useEidos as I, useEidosAction as _, useEidosOnDrain as x, useEidosQueue as B, useEidosQueueStats as D, useEidosResource as N, useEidosResources as T, useEidosStatus as V } from "./react/hooks.js";
11
- import { VERSION as j } from "./version.js";
12
- import { eidosAction as z, eidosQueue as F, eidosQueueStats as G, eidosResource as H, eidosStatus as J, eidosStore as K } from "./stores.js";
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
- k as AsyncStorageQueueStorage,
15
- w as EidosProvider,
16
- j as VERSION,
14
+ v as AsyncStorageQueueStorage,
15
+ B as EidosProvider,
16
+ Y as VERSION,
17
17
  f as _getQueueStorage,
18
- A as _resetEidos,
19
- g as action,
20
- l as clearQueue,
21
- z as eidosAction,
22
- F as eidosQueue,
23
- G as eidosQueueStats,
24
- H as eidosResource,
25
- J as eidosStatus,
26
- K as eidosStore,
27
- s as getSwRegistration,
28
- P as initEidos,
29
- i as isBgSyncSupported,
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
- m as setOfflineSimulation,
36
- p as setQueryInvalidator,
39
+ a as setOfflineSimulation,
40
+ c as setQueryInvalidator,
37
41
  E as setQueueStorage,
38
42
  O as subscribeReplayOnReconnect,
39
- I as useEidos,
40
- _ as useEidosAction,
41
- x as useEidosOnDrain,
42
- B as useEidosQueue,
43
- D as useEidosQueueStats,
44
- N as useEidosResource,
45
- T as useEidosResources,
46
- V as useEidosStatus,
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
- c as warmCache
53
+ p as warmCache
49
54
  };
@@ -1,23 +1,24 @@
1
- import { useEidosStore as o } from "../store.js";
2
1
  import { countQueueByStatus as l } from "../types.js";
3
- import { useEffect as i, useRef as c, useSyncExternalStore as p } from "react";
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 p(o.subscribe, () => t(o.getState()));
7
+ return S(r.subscribe, () => t(r.getState()));
7
8
  }
8
- function R() {
9
+ function b() {
9
10
  return u();
10
11
  }
11
- function q() {
12
+ function w() {
12
13
  return u((e) => e.resources);
13
14
  }
14
- function w(e) {
15
+ function Q(e) {
15
16
  return u((t) => t.resources[e]);
16
17
  }
17
- function y() {
18
+ function $() {
18
19
  return u((e) => e.queue);
19
20
  }
20
- function $(e) {
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 Q() {
31
- const [e, t, n, r] = u((s) => {
32
- const { pending: f, failed: a, replaying: d, total: E } = l(s.queue);
33
- return `${f},${a},${d},${E}`;
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: +r
40
+ total: +i
40
41
  };
41
42
  }
42
- function b(e) {
43
- const t = u((s) => s.queue.length), n = c(0), r = c(e);
44
- i(() => {
45
- r.current = e;
46
- }), i(() => {
47
- n.current > 0 && t === 0 && r.current(), n.current = t;
48
- }, [t]);
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
- R as useEidos,
52
- $ as useEidosAction,
53
- b as useEidosOnDrain,
54
- y as useEidosQueue,
55
- Q as useEidosQueueStats,
56
- w as useEidosResource,
57
- q as useEidosResources,
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
 
@@ -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 useEidosOnDrain(callback: () => void) {\n const total = useStore((s) => s.queue.length);\n const prevRef = useRef(0);\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n useEffect(() => {\n if (prevRef.current > 0 && total === 0) {\n callbackRef.current();\n }\n prevRef.current = total;\n }, [total]);\n}\n"],"mappings":";;;AAOA,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;AAUA,SAAgB,EAAgB,GAAsB;AACpD,QAAM,IAAQ,EAAA,CAAU,MAAM,EAAE,MAAM,MAAM,GACtC,IAAU,EAAO,CAAC,GAClB,IAAc,EAAO,CAAQ;AAEnC,EAAA,EAAA,MAAgB;AACd,IAAA,EAAY,UAAU;AAAA,EACxB,CAAC,GAED,EAAA,MAAgB;AACd,IAAI,EAAQ,UAAU,KAAK,MAAU,KACnC,EAAY,QAAQ,GAEtB,EAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,CAAK,CAAC;AACZ"}
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(), v = null;
3
+ var d = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), g = null;
4
4
  function _(e) {
5
- v = e;
5
+ g = e;
6
6
  }
7
- function u(e) {
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 = u(e) ? k(e) : void 0, r = {
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, f = new URL(o).pathname;
44
- return n ? n.test(c ? o : f) : c ? o === e : o === e || f === e;
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
- u(e) || h.getState().updateResource(e, {
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
- }), v?.(["eidos", e]);
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 (u(e)) throw new Error(`[eidos] resource('${e}') is a URL pattern — use resourcePattern('${e}', config) instead. Pattern handles only support invalidate()/unregister(); the SW intercepts matching requests automatically.`);
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 O(e, s) {
92
- if (!u(e)) throw new Error(`[eidos] resourcePattern('${e}') is not a URL pattern — use resource('${e}', config) instead.`);
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], f = s.maxAge !== void 0 && o?.cachedAt !== void 0 && Date.now() - o.cachedAt > s.maxAge;
113
- if (i && !f)
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 ? g(s ?? "stale-while-revalidate", e.cacheName) : g(s ?? "network-first", e.cacheName);
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 g(e, s) {
204
- const a = C[e];
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: s ?? "eidos-resources-v1",
209
- reasoning: a.reasoning,
210
- behavior: a.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 T(e) {
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
- O as resourcePattern,
224
+ $ as resourcePattern,
225
225
  _ as setQueryInvalidator,
226
- T as warmCache
226
+ O as warmCache
227
227
  };
228
228
 
229
229
  //# sourceMappingURL=resource.js.map