@sweidos/eidos 2.1.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
@@ -172,7 +172,7 @@ export declare function cancelByIdempotencyKey(idempotencyKey: string): Promise<
172
172
  /** Remove all items from the action queue (storage + in-memory store). */
173
173
  export declare function clearQueue(): Promise<void>;
174
174
 
175
- declare interface ConflictConfig {
175
+ export declare interface ConflictConfig {
176
176
  /**
177
177
  * - `'serverWins'`: drop the queued item, keeping the server's state.
178
178
  * - `'clientWins'`: keep retrying — the client's write should eventually
@@ -188,7 +188,7 @@ declare interface ConflictConfig {
188
188
  * Passed to `ConflictConfig.resolve` (for `'merge'`/`'custom'` strategies)
189
189
  * when a queued action's replay receives a 4xx response.
190
190
  */
191
- declare interface ConflictContext {
191
+ export declare interface ConflictContext {
192
192
  /** Whatever `fn` threw — typically a `Response` or an error with `.status`. */
193
193
  error: unknown;
194
194
  /** The original arguments the action was queued with. */
@@ -205,7 +205,7 @@ declare interface ConflictContext {
205
205
  * - `{ resolved: args }`: replace the queued args and retry immediately
206
206
  * on the next replay pass.
207
207
  */
208
- declare type ConflictResolution = 'retry' | 'skip' | {
208
+ export declare type ConflictResolution = 'retry' | 'skip' | {
209
209
  resolved: any[];
210
210
  };
211
211
 
@@ -224,6 +224,15 @@ export declare interface EidosConfig {
224
224
  swPath?: string;
225
225
  /** Automatically replay the action queue on reconnect. Default: true. */
226
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;
227
236
  }
228
237
 
229
238
  /**
@@ -262,6 +271,13 @@ export declare interface EidosReadable<T> {
262
271
  getState(): T;
263
272
  }
264
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
+
265
281
  /**
266
282
  * Live cache state for a single registered resource URL.
267
283
  * @example
@@ -277,6 +293,7 @@ export declare interface EidosState {
277
293
  swError?: string;
278
294
  resources: Record<string, ResourceEntry>;
279
295
  queue: ActionQueueItem[];
296
+ reliability: ReliabilityStats;
280
297
  }
281
298
 
282
299
  /**
@@ -289,7 +306,7 @@ export declare const eidosStatus: EidosReadable<{
289
306
  swError: string | undefined;
290
307
  }>;
291
308
 
292
- export declare interface EidosStore extends EidosState, ResourceActions, QueueActions {
309
+ export declare interface EidosStore extends EidosState, ResourceActions, QueueActions, ReliabilityActions {
293
310
  setOnline: (online: boolean) => void;
294
311
  setSwStatus: (status: EidosState['swStatus'], error?: string) => void;
295
312
  }
@@ -321,6 +338,17 @@ export declare function isBgSyncSupported(): boolean;
321
338
 
322
339
  declare type Listener = () => void;
323
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
+
324
352
  /**
325
353
  * Handle for a URL pattern (`/api/products/*`, `/api/users/:id`, `**`).
326
354
  * The SW intercepts all matching requests automatically — there is no single
@@ -372,6 +400,32 @@ export declare interface QueueStorage {
372
400
 
373
401
  export declare function registerPushCallbacks(handlers: PushHandlers): void;
374
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
+
375
429
  export declare function replayQueue(): Promise<ReplayResult>;
376
430
 
377
431
  /** Summary returned by replayQueue(). */
@@ -507,14 +561,6 @@ export declare function useEidos(): EidosStore;
507
561
  */
508
562
  export declare function useEidosAction(id: string): ActionQueueItem | undefined;
509
563
 
510
- /**
511
- * Calls `callback` once each time the action queue drains from non-empty → 0.
512
- * Stable callback reference not required — always calls the latest version.
513
- * Use for "all offline actions synced!" toasts.
514
- *
515
- * @example
516
- * useEidosOnDrain(() => toast.success('All offline actions synced!'))
517
- */
518
564
  export declare function useEidosOnDrain(callback: () => void): void;
519
565
 
520
566
  /** The current action queue. */
@@ -532,6 +578,21 @@ export declare function useEidosQueueStats(): {
532
578
  total: number;
533
579
  };
534
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
+
535
596
  /** Live state for a single registered resource URL. */
536
597
  export declare function useEidosResource(url: string): ResourceEntry;
537
598
 
@@ -555,7 +616,7 @@ export declare const useEidosStore: {
555
616
  setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
556
617
  };
557
618
 
558
- export declare const VERSION = "2.1.0";
619
+ export declare const VERSION = "2.2.0";
559
620
 
560
621
  /**
561
622
  * Bulk-prefetch an array of resource handles concurrently, warming the cache
package/dist/index.js CHANGED
@@ -1,51 +1,54 @@
1
1
  import { useEidosStore as o } from "./store.js";
2
- import { getSwRegistration as s, isBgSyncSupported as t, registerPushCallbacks as i, sendToWorker as u, setOfflineSimulation as m } from "./sw-bridge.js";
3
- import { resource as d, resourcePattern as c, setQueryInvalidator as n, warmCache as p } 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, cancelByIdempotencyKey as l, clearQueue as y, replayQueue as R, requeueItem as I } from "./action.js";
6
- import { subscribeReplayOnReconnect as b } from "./replay.js";
5
+ import { action as Q, cancelByIdempotencyKey as y, clearQueue as g, replayQueue as R, requeueItem as b } from "./action.js";
6
+ import { subscribeReplayOnReconnect as O } from "./replay.js";
7
7
  import { _resetEidos as P, initEidos as h } from "./runtime.js";
8
8
  import { AsyncStorageQueueStorage as v } from "./async-storage-adapter.js";
9
9
  import { EidosProvider as B } from "./react/Provider.js";
10
- import { useEidos as _, useEidosAction as q, useEidosOnDrain as x, useEidosQueue as D, useEidosQueueStats as K, 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 F, eidosQueue as G, eidosQueueStats as H, eidosResource as J, eidosStatus as L, eidosStore as M } from "./stores.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
14
  v as AsyncStorageQueueStorage,
15
15
  B as EidosProvider,
16
- j as VERSION,
16
+ Y as VERSION,
17
17
  f as _getQueueStorage,
18
18
  P as _resetEidos,
19
- g as action,
20
- l as cancelByIdempotencyKey,
21
- y as clearQueue,
22
- F as eidosAction,
23
- G as eidosQueue,
24
- H as eidosQueueStats,
25
- J as eidosResource,
26
- L as eidosStatus,
27
- M as eidosStore,
28
- s as getSwRegistration,
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,
29
30
  h as initEidos,
30
- t as isBgSyncSupported,
31
- i as registerPushCallbacks,
31
+ s as isBgSyncSupported,
32
+ V as onQueueDrain,
33
+ t as registerPushCallbacks,
32
34
  R as replayQueue,
33
- I as requeueItem,
35
+ b as requeueItem,
34
36
  d as resource,
35
- c as resourcePattern,
37
+ n as resourcePattern,
36
38
  u as sendToWorker,
37
- m as setOfflineSimulation,
38
- n as setQueryInvalidator,
39
+ a as setOfflineSimulation,
40
+ c as setQueryInvalidator,
39
41
  E as setQueueStorage,
40
- b as subscribeReplayOnReconnect,
41
- _ as useEidos,
42
- q as useEidosAction,
43
- x as useEidosOnDrain,
44
- D as useEidosQueue,
45
- K as useEidosQueueStats,
46
- N as useEidosResource,
47
- T as useEidosResources,
48
- V as useEidosStatus,
42
+ O as subscribeReplayOnReconnect,
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,
49
52
  o as useEidosStore,
50
53
  p as warmCache
51
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/runtime.js CHANGED
@@ -1,50 +1,55 @@
1
- import { useEidosStore as a } from "./store.js";
2
- import { registerBgSyncHandler as c, registerServiceWorker as m } from "./sw-bridge.js";
3
- import { idbGetQueue as y, idbQueueStorage as d } from "./idb.js";
4
- import { _getQueueStorage as p } from "./queue-storage.js";
5
- import { subscribeQueueSync as l } from "./queue-sync.js";
6
- import { replayQueue as f } from "./action.js";
7
- import { subscribeReplayOnReconnect as g } from "./replay.js";
8
- async function h(e) {
1
+ import { useEidosStore as n } from "./store.js";
2
+ import { registerBgSyncHandler as c, registerServiceWorker as y } from "./sw-bridge.js";
3
+ import { idbGetQueue as m, idbQueueStorage as p } from "./idb.js";
4
+ import { _getQueueStorage as d } from "./queue-storage.js";
5
+ import { subscribeQueueSync as f } from "./queue-sync.js";
6
+ import { replayQueue as b } from "./action.js";
7
+ import { subscribeReplayOnReconnect as h } from "./replay.js";
8
+ async function R(e) {
9
9
  if (e.schemaVersion === 2 && e.idempotencyKey) return e;
10
10
  const t = {
11
11
  ...e,
12
12
  schemaVersion: 2,
13
13
  idempotencyKey: e.idempotencyKey ?? crypto.randomUUID()
14
14
  };
15
- return await (p() ?? d).update(t.id, {
15
+ return await (d() ?? p).update(t.id, {
16
16
  schemaVersion: t.schemaVersion,
17
17
  idempotencyKey: t.idempotencyKey
18
18
  }).catch(() => {
19
19
  }), t;
20
20
  }
21
- var r = !1, i = null, o = null;
22
- async function R(e = {}) {
23
- if (typeof window > "u" || r) return;
24
- r = !0;
25
- const t = e.swPath ?? "/eidos-sw.js", s = e.autoReplay ?? !0;
21
+ var o = !1, s = null, u = null, i = null;
22
+ async function K(e = {}) {
23
+ if (typeof window > "u" || o) return;
24
+ o = !0;
25
+ const t = e.swPath ?? "/eidos-sw.js", l = e.autoReplay ?? !0;
26
26
  try {
27
- const n = await y();
28
- if (n.length > 0) {
29
- const u = await Promise.all(n.map(h));
30
- a.getState().hydrateQueue(u);
27
+ const r = await m();
28
+ if (r.length > 0) {
29
+ const a = await Promise.all(r.map(R));
30
+ n.getState().hydrateQueue(a);
31
31
  }
32
32
  } catch {
33
33
  }
34
34
  try {
35
- await m(t);
35
+ await y(t);
36
36
  } catch {
37
37
  }
38
- c(() => {
39
- a.getState().isOnline && setTimeout(f, 200);
40
- }), s && (i = g()), o = l();
38
+ if (c(() => {
39
+ n.getState().isOnline && setTimeout(b, 200);
40
+ }), l && (s = h()), u = f(), e.onReliabilityReport) {
41
+ const r = e.reliabilityReportInterval ?? 6e4, a = e.onReliabilityReport;
42
+ i = setInterval(() => {
43
+ a(n.getState().reliability);
44
+ }, r);
45
+ }
41
46
  }
42
47
  function V() {
43
- i?.(), i = null, o?.(), o = null, r = !1;
48
+ s?.(), s = null, u?.(), u = null, i && clearInterval(i), i = null, o = !1;
44
49
  }
45
50
  export {
46
51
  V as _resetEidos,
47
- R as initEidos
52
+ K as initEidos
48
53
  };
49
54
 
50
55
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import { registerServiceWorker, registerBgSyncHandler } from './sw-bridge';\nimport { replayQueue } from './action';\nimport { useEidosStore } from './store';\nimport { idbGetQueue, idbQueueStorage } from './idb';\nimport { _getQueueStorage } from './queue-storage';\nimport { subscribeReplayOnReconnect } from './replay';\nimport { subscribeQueueSync } from './queue-sync';\nimport { CURRENT_QUEUE_SCHEMA_VERSION } from './types';\nimport type { ActionQueueItem } from './types';\n\n// Items persisted before idempotencyKey/schemaVersion existed (v1) are migrated\n// in place: assign a fresh idempotencyKey and bump schemaVersion. A fresh key on\n// first replay after upgrade is correct — these items were never sent with one.\nasync function migrateQueueItem(item: ActionQueueItem): Promise<ActionQueueItem> {\n if (item.schemaVersion === CURRENT_QUEUE_SCHEMA_VERSION && item.idempotencyKey) {\n return item;\n }\n const migrated: ActionQueueItem = {\n ...item,\n schemaVersion: CURRENT_QUEUE_SCHEMA_VERSION,\n idempotencyKey: item.idempotencyKey ?? crypto.randomUUID(),\n };\n const storage = _getQueueStorage() ?? idbQueueStorage;\n await storage\n .update(migrated.id, {\n schemaVersion: migrated.schemaVersion,\n idempotencyKey: migrated.idempotencyKey,\n })\n .catch(() => {\n // Best-effort persist — item still gets the migrated shape in memory this session\n });\n return migrated;\n}\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string;\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean;\n}\n\nlet _initialized = false;\nlet _unsubscribe: (() => void) | null = null;\nlet _unsubscribeQueueSync: (() => void) | null = null;\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n // Skip silently during SSR — SW, IndexedDB, and window are browser-only.\n if (typeof window === 'undefined') return;\n if (_initialized) return;\n _initialized = true;\n\n const swPath = config.swPath ?? '/eidos-sw.js';\n const autoReplay = config.autoReplay ?? true;\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue();\n if (persisted.length > 0) {\n const migrated = await Promise.all(persisted.map(migrateQueueItem));\n useEidosStore.getState().hydrateQueue(migrated);\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath);\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n // When the SW fires the Background Sync tag, replay the queue in the main thread.\n // This path runs even if the user briefly navigated away and back — the browser\n // triggers the sync event on the SW, which wakes up all open clients.\n registerBgSyncHandler(() => {\n if (useEidosStore.getState().isOnline) {\n setTimeout(replayQueue, 200);\n }\n });\n\n if (autoReplay) {\n _unsubscribe = subscribeReplayOnReconnect();\n }\n\n // Apply queue-item status changes broadcast by the replay-lock holder so\n // non-leader tabs reflect live status without waiting for re-hydration.\n _unsubscribeQueueSync = subscribeQueueSync();\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState();\n console.groupCollapsed('%c⚡ Eidos', 'color:#22C55E;font-weight:bold');\n console.log('SW path :', swPath);\n console.log('Auto-replay:', autoReplay);\n console.log('SW status :', store.swStatus);\n console.groupEnd();\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.();\n _unsubscribe = null;\n _unsubscribeQueueSync?.();\n _unsubscribeQueueSync = null;\n _initialized = false;\n}\n"],"mappings":";;;;;;;AAaA,eAAe,EAAiB,GAAiD;AAC/E,MAAI,EAAK,kBAAA,KAAkD,EAAK,eAC9D,QAAO;AAET,QAAM,IAA4B;AAAA,IAChC,GAAG;AAAA,IACH,eAAA;AAAA,IACA,gBAAgB,EAAK,kBAAkB,OAAO,WAAW;AAAA,EAC3D;AAEA,gBADgB,EAAiB,KAAK,GAEnC,OAAO,EAAS,IAAI;AAAA,IACnB,eAAe,EAAS;AAAA,IACxB,gBAAgB,EAAS;AAAA,EAC3B,CAAC,EACA,MAAA,MAAY;AAAA,EAEb,CAAC,GACI;AACT;AASA,IAAI,IAAe,IACf,IAAoC,MACpC,IAA6C;AAEjD,eAAsB,EAAU,IAAsB,CAAC,GAAkB;AAGvE,MADI,OAAO,SAAW,OAClB,EAAc;AAClB,EAAA,IAAe;AAEf,QAAM,IAAS,EAAO,UAAU,gBAC1B,IAAa,EAAO,cAAc;AAGxC,MAAI;AACF,UAAM,IAAY,MAAM,EAAY;AACpC,QAAI,EAAU,SAAS,GAAG;AACxB,YAAM,IAAW,MAAM,QAAQ,IAAI,EAAU,IAAI,CAAgB,CAAC;AAClE,MAAA,EAAc,SAAS,EAAE,aAAa,CAAQ;AAAA,IAChD;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,EAAsB,CAAM;AAAA,EACpC,QAAQ;AAAA,EAER;AAKA,EAAA,EAAA,MAA4B;AAC1B,IAAI,EAAc,SAAS,EAAE,YAC3B,WAAW,GAAa,GAAG;AAAA,EAE/B,CAAC,GAEG,MACF,IAAe,EAA2B,IAK5C,IAAwB,EAAmB;AAU7C;AAEA,SAAgB,IAAc;AAC5B,EAAA,IAAe,GACf,IAAe,MACf,IAAwB,GACxB,IAAwB,MACxB,IAAe;AACjB"}
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import { registerServiceWorker, registerBgSyncHandler } from './sw-bridge';\nimport { replayQueue } from './action';\nimport { useEidosStore } from './store';\nimport { idbGetQueue, idbQueueStorage } from './idb';\nimport { _getQueueStorage } from './queue-storage';\nimport { subscribeReplayOnReconnect } from './replay';\nimport { subscribeQueueSync } from './queue-sync';\nimport { CURRENT_QUEUE_SCHEMA_VERSION } from './types';\nimport type { ActionQueueItem, ReliabilityStats } from './types';\n\n// Items persisted before idempotencyKey/schemaVersion existed (v1) are migrated\n// in place: assign a fresh idempotencyKey and bump schemaVersion. A fresh key on\n// first replay after upgrade is correct — these items were never sent with one.\nasync function migrateQueueItem(item: ActionQueueItem): Promise<ActionQueueItem> {\n if (item.schemaVersion === CURRENT_QUEUE_SCHEMA_VERSION && item.idempotencyKey) {\n return item;\n }\n const migrated: ActionQueueItem = {\n ...item,\n schemaVersion: CURRENT_QUEUE_SCHEMA_VERSION,\n idempotencyKey: item.idempotencyKey ?? crypto.randomUUID(),\n };\n const storage = _getQueueStorage() ?? idbQueueStorage;\n await storage\n .update(migrated.id, {\n schemaVersion: migrated.schemaVersion,\n idempotencyKey: migrated.idempotencyKey,\n })\n .catch(() => {\n // Best-effort persist — item still gets the migrated shape in memory this session\n });\n return migrated;\n}\n\nexport interface EidosConfig {\n /** Path to the eidos service worker. Defaults to '/eidos-sw.js'. */\n swPath?: string;\n /** Automatically replay the action queue on reconnect. Default: true. */\n autoReplay?: boolean;\n /**\n * Opt-in reliability telemetry. Called with a snapshot of cumulative\n * `neverLose` queue outcome counters (`ReliabilityStats`) every\n * `reliabilityReportInterval` ms — wire this up to your analytics backend.\n * Not called if omitted.\n */\n onReliabilityReport?: (stats: ReliabilityStats) => void;\n /** Interval (ms) between `onReliabilityReport` calls. Default: 60000. */\n reliabilityReportInterval?: number;\n}\n\nlet _initialized = false;\nlet _unsubscribe: (() => void) | null = null;\nlet _unsubscribeQueueSync: (() => void) | null = null;\nlet _reliabilityReportTimer: ReturnType<typeof setInterval> | null = null;\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\n // Skip silently during SSR — SW, IndexedDB, and window are browser-only.\n if (typeof window === 'undefined') return;\n if (_initialized) return;\n _initialized = true;\n\n const swPath = config.swPath ?? '/eidos-sw.js';\n const autoReplay = config.autoReplay ?? true;\n\n // Restore persisted queue from IndexedDB on startup\n try {\n const persisted = await idbGetQueue();\n if (persisted.length > 0) {\n const migrated = await Promise.all(persisted.map(migrateQueueItem));\n useEidosStore.getState().hydrateQueue(migrated);\n }\n } catch {\n // IndexedDB unavailable (Firefox private browsing) — silent fallback\n }\n\n try {\n await registerServiceWorker(swPath);\n } catch {\n // SW registration failed; app continues without offline support\n }\n\n // When the SW fires the Background Sync tag, replay the queue in the main thread.\n // This path runs even if the user briefly navigated away and back — the browser\n // triggers the sync event on the SW, which wakes up all open clients.\n registerBgSyncHandler(() => {\n if (useEidosStore.getState().isOnline) {\n setTimeout(replayQueue, 200);\n }\n });\n\n if (autoReplay) {\n _unsubscribe = subscribeReplayOnReconnect();\n }\n\n // Apply queue-item status changes broadcast by the replay-lock holder so\n // non-leader tabs reflect live status without waiting for re-hydration.\n _unsubscribeQueueSync = subscribeQueueSync();\n\n if (config.onReliabilityReport) {\n const interval = config.reliabilityReportInterval ?? 60_000;\n const report = config.onReliabilityReport;\n _reliabilityReportTimer = setInterval(() => {\n report(useEidosStore.getState().reliability);\n }, interval);\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState();\n console.groupCollapsed('%c⚡ Eidos', 'color:#22C55E;font-weight:bold');\n console.log('SW path :', swPath);\n console.log('Auto-replay:', autoReplay);\n console.log('SW status :', store.swStatus);\n console.groupEnd();\n }\n}\n\nexport function _resetEidos() {\n _unsubscribe?.();\n _unsubscribe = null;\n _unsubscribeQueueSync?.();\n _unsubscribeQueueSync = null;\n if (_reliabilityReportTimer) clearInterval(_reliabilityReportTimer);\n _reliabilityReportTimer = null;\n _initialized = false;\n}\n"],"mappings":";;;;;;;AAaA,eAAe,EAAiB,GAAiD;AAC/E,MAAI,EAAK,kBAAA,KAAkD,EAAK,eAC9D,QAAO;AAET,QAAM,IAA4B;AAAA,IAChC,GAAG;AAAA,IACH,eAAA;AAAA,IACA,gBAAgB,EAAK,kBAAkB,OAAO,WAAW;AAAA,EAC3D;AAEA,gBADgB,EAAiB,KAAK,GAEnC,OAAO,EAAS,IAAI;AAAA,IACnB,eAAe,EAAS;AAAA,IACxB,gBAAgB,EAAS;AAAA,EAC3B,CAAC,EACA,MAAA,MAAY;AAAA,EAEb,CAAC,GACI;AACT;AAkBA,IAAI,IAAe,IACf,IAAoC,MACpC,IAA6C,MAC7C,IAAiE;AAErE,eAAsB,EAAU,IAAsB,CAAC,GAAkB;AAGvE,MADI,OAAO,SAAW,OAClB,EAAc;AAClB,EAAA,IAAe;AAEf,QAAM,IAAS,EAAO,UAAU,gBAC1B,IAAa,EAAO,cAAc;AAGxC,MAAI;AACF,UAAM,IAAY,MAAM,EAAY;AACpC,QAAI,EAAU,SAAS,GAAG;AACxB,YAAM,IAAW,MAAM,QAAQ,IAAI,EAAU,IAAI,CAAgB,CAAC;AAClE,MAAA,EAAc,SAAS,EAAE,aAAa,CAAQ;AAAA,IAChD;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,EAAsB,CAAM;AAAA,EACpC,QAAQ;AAAA,EAER;AAmBA,MAdA,EAAA,MAA4B;AAC1B,IAAI,EAAc,SAAS,EAAE,YAC3B,WAAW,GAAa,GAAG;AAAA,EAE/B,CAAC,GAEG,MACF,IAAe,EAA2B,IAK5C,IAAwB,EAAmB,GAEvC,EAAO,qBAAqB;AAC9B,UAAM,IAAW,EAAO,6BAA6B,KAC/C,IAAS,EAAO;AACtB,IAAA,IAA0B,YAAA,MAAkB;AAC1C,MAAA,EAAO,EAAc,SAAS,EAAE,WAAW;AAAA,IAC7C,GAAG,CAAQ;AAAA,EACb;AAUF;AAEA,SAAgB,IAAc;AAC5B,EAAA,IAAe,GACf,IAAe,MACf,IAAwB,GACxB,IAAwB,MACpB,KAAyB,cAAc,CAAuB,GAClE,IAA0B,MAC1B,IAAe;AACjB"}
@@ -1,43 +1,54 @@
1
- function t(o) {
1
+ import { emptyReliabilityStats as o } from "./types.js";
2
+ function s(t) {
2
3
  return {
3
- registerResource: (e, r) => o((u) => ({ resources: {
4
+ registerResource: (e, r) => t((u) => ({ resources: {
4
5
  ...u.resources,
5
6
  [e]: r
6
7
  } })),
7
- updateResource: (e, r) => o((u) => ({ resources: {
8
+ updateResource: (e, r) => t((u) => ({ resources: {
8
9
  ...u.resources,
9
10
  [e]: u.resources[e] ? {
10
11
  ...u.resources[e],
11
12
  ...r
12
13
  } : u.resources[e]
13
14
  } })),
14
- unregisterResource: (e) => o((r) => ({ resources: Object.fromEntries(Object.entries(r.resources).filter(([u]) => u !== e)) }))
15
+ unregisterResource: (e) => t((r) => ({ resources: Object.fromEntries(Object.entries(r.resources).filter(([u]) => u !== e)) }))
15
16
  };
16
17
  }
17
- function n(o) {
18
+ function n(t) {
18
19
  return {
19
- addQueueItem: (e) => o((r) => ({ queue: [...r.queue, e] })),
20
- updateQueueItem: (e, r) => o((u) => ({ queue: u.queue.map((c) => c.id === e ? {
21
- ...c,
20
+ addQueueItem: (e) => t((r) => ({ queue: [...r.queue, e] })),
21
+ updateQueueItem: (e, r) => t((u) => ({ queue: u.queue.map((i) => i.id === e ? {
22
+ ...i,
22
23
  ...r
23
- } : c) })),
24
- batchUpdateQueueItems: (e) => o((r) => {
25
- const u = new Map(e.map((c) => [c.id, c.update]));
26
- return { queue: r.queue.map((c) => {
27
- const s = u.get(c.id);
28
- return s ? {
29
- ...c,
30
- ...s
31
- } : c;
24
+ } : i) })),
25
+ batchUpdateQueueItems: (e) => t((r) => {
26
+ const u = new Map(e.map((i) => [i.id, i.update]));
27
+ return { queue: r.queue.map((i) => {
28
+ const c = u.get(i.id);
29
+ return c ? {
30
+ ...i,
31
+ ...c
32
+ } : i;
32
33
  }) };
33
34
  }),
34
- removeQueueItem: (e) => o((r) => ({ queue: r.queue.filter((u) => u.id !== e) })),
35
- hydrateQueue: (e) => o(() => ({ queue: e }))
35
+ removeQueueItem: (e) => t((r) => ({ queue: r.queue.filter((u) => u.id !== e) })),
36
+ hydrateQueue: (e) => t(() => ({ queue: e }))
37
+ };
38
+ }
39
+ function l(t) {
40
+ return {
41
+ recordReliabilityEvent: (e) => t((r) => ({ reliability: {
42
+ ...r.reliability,
43
+ [e]: r.reliability[e] + 1
44
+ } })),
45
+ resetReliabilityStats: () => t(() => ({ reliability: o() }))
36
46
  };
37
47
  }
38
48
  export {
39
49
  n as createQueueActions,
40
- t as createResourceActions
50
+ l as createReliabilityActions,
51
+ s as createResourceActions
41
52
  };
42
53
 
43
54
  //# sourceMappingURL=store-slices.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-slices.js","names":[],"sources":["../src/store-slices.ts"],"sourcesContent":["import type { ResourceEntry, ActionQueueItem } from './types';\nimport type { EidosStore } from './store';\n\ntype Setter = (updater: (prev: EidosStore) => Partial<EidosStore>) => void;\n\n// ── Resource slice ────────────────────────────────────────────────────────────\n\nexport interface ResourceActions {\n registerResource: (url: string, entry: ResourceEntry) => void;\n updateResource: (url: string, update: Partial<ResourceEntry>) => void;\n unregisterResource: (url: string) => void;\n}\n\nexport function createResourceActions(set: Setter): ResourceActions {\n return {\n registerResource: (url, entry) => set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n set((s) => ({\n resources: Object.fromEntries(Object.entries(s.resources).filter(([k]) => k !== url)),\n })),\n };\n}\n\n// ── Queue slice ───────────────────────────────────────────────────────────────\n\nexport interface QueueActions {\n addQueueItem: (item: ActionQueueItem) => void;\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;\n batchUpdateQueueItems: (updates: Array<{ id: string; update: Partial<ActionQueueItem> }>) => void;\n removeQueueItem: (id: string) => void;\n hydrateQueue: (items: ActionQueueItem[]) => void;\n}\n\nexport function createQueueActions(set: Setter): QueueActions {\n return {\n addQueueItem: (item) => set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n batchUpdateQueueItems: (updates) =>\n set((s) => {\n const map = new Map(updates.map((u) => [u.id, u.update]));\n return {\n queue: s.queue.map((item) => {\n const u = map.get(item.id);\n return u ? { ...item, ...u } : item;\n }),\n };\n }),\n\n removeQueueItem: (id) => set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => set(() => ({ queue: items })),\n };\n}\n"],"mappings":"AAaA,SAAgB,EAAsB,GAA8B;AAClE,SAAO;AAAA,IACL,kBAAA,CAAmB,GAAK,MAAU,EAAA,CAAK,OAAO,EAAE,WAAW;AAAA,MAAE,GAAG,EAAE;AAAA,OAAY,CAAA,GAAM;AAAA,IAAM,EAAE,EAAE;AAAA,IAE9F,gBAAA,CAAiB,GAAK,MACpB,EAAA,CAAK,OAAO,EACV,WAAW;AAAA,MACT,GAAG,EAAE;AAAA,OACJ,CAAA,GAAM,EAAE,UAAU,CAAA,IAAO;AAAA,QAAE,GAAG,EAAE,UAAU,CAAA;AAAA,QAAM,GAAG;AAAA,MAAO,IAAI,EAAE,UAAU,CAAA;AAAA,IAC7E,EACF,EAAE;AAAA,IAEJ,oBAAA,CAAqB,MACnB,EAAA,CAAK,OAAO,EACV,WAAW,OAAO,YAAY,OAAO,QAAQ,EAAE,SAAS,EAAE,OAAA,CAAQ,CAAC,CAAA,MAAO,MAAM,CAAG,CAAC,EACtF,EAAE;AAAA,EACN;AACF;AAYA,SAAgB,EAAmB,GAA2B;AAC5D,SAAO;AAAA,IACL,cAAA,CAAe,MAAS,EAAA,CAAK,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAI,EAAE,EAAE;AAAA,IAElE,iBAAA,CAAkB,GAAI,MACpB,EAAA,CAAK,OAAO,EACV,OAAO,EAAE,MAAM,IAAA,CAAK,MAAU,EAAK,OAAO,IAAK;AAAA,MAAE,GAAG;AAAA,MAAM,GAAG;AAAA,IAAO,IAAI,CAAK,EAC/E,EAAE;AAAA,IAEJ,uBAAA,CAAwB,MACtB,EAAA,CAAK,MAAM;AACT,YAAM,IAAM,IAAI,IAAI,EAAQ,IAAA,CAAK,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACxD,aAAO,EACL,OAAO,EAAE,MAAM,IAAA,CAAK,MAAS;AAC3B,cAAM,IAAI,EAAI,IAAI,EAAK,EAAE;AACzB,eAAO,IAAI;AAAA,UAAE,GAAG;AAAA,UAAM,GAAG;AAAA,QAAE,IAAI;AAAA,MACjC,CAAC,EACH;AAAA,IACF,CAAC;AAAA,IAEH,iBAAA,CAAkB,MAAO,EAAA,CAAK,OAAO,EAAE,OAAO,EAAE,MAAM,OAAA,CAAQ,MAAS,EAAK,OAAO,CAAE,EAAE,EAAE;AAAA,IAEzF,cAAA,CAAe,MAAU,EAAA,OAAW,EAAE,OAAO,EAAM,EAAE;AAAA,EACvD;AACF"}
1
+ {"version":3,"file":"store-slices.js","names":[],"sources":["../src/store-slices.ts"],"sourcesContent":["import type { ResourceEntry, ActionQueueItem, ReliabilityStats } from './types';\nimport { emptyReliabilityStats } from './types';\nimport type { EidosStore } from './store';\n\ntype Setter = (updater: (prev: EidosStore) => Partial<EidosStore>) => void;\n\n// ── Resource slice ────────────────────────────────────────────────────────────\n\nexport interface ResourceActions {\n registerResource: (url: string, entry: ResourceEntry) => void;\n updateResource: (url: string, update: Partial<ResourceEntry>) => void;\n unregisterResource: (url: string) => void;\n}\n\nexport function createResourceActions(set: Setter): ResourceActions {\n return {\n registerResource: (url, entry) => set((s) => ({ resources: { ...s.resources, [url]: entry } })),\n\n updateResource: (url, update) =>\n set((s) => ({\n resources: {\n ...s.resources,\n [url]: s.resources[url] ? { ...s.resources[url], ...update } : s.resources[url],\n },\n })),\n\n unregisterResource: (url) =>\n set((s) => ({\n resources: Object.fromEntries(Object.entries(s.resources).filter(([k]) => k !== url)),\n })),\n };\n}\n\n// ── Queue slice ───────────────────────────────────────────────────────────────\n\nexport interface QueueActions {\n addQueueItem: (item: ActionQueueItem) => void;\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;\n batchUpdateQueueItems: (updates: Array<{ id: string; update: Partial<ActionQueueItem> }>) => void;\n removeQueueItem: (id: string) => void;\n hydrateQueue: (items: ActionQueueItem[]) => void;\n}\n\nexport function createQueueActions(set: Setter): QueueActions {\n return {\n addQueueItem: (item) => set((s) => ({ queue: [...s.queue, item] })),\n\n updateQueueItem: (id, update) =>\n set((s) => ({\n queue: s.queue.map((item) => (item.id === id ? { ...item, ...update } : item)),\n })),\n\n batchUpdateQueueItems: (updates) =>\n set((s) => {\n const map = new Map(updates.map((u) => [u.id, u.update]));\n return {\n queue: s.queue.map((item) => {\n const u = map.get(item.id);\n return u ? { ...item, ...u } : item;\n }),\n };\n }),\n\n removeQueueItem: (id) => set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => set(() => ({ queue: items })),\n };\n}\n\n// ── Reliability slice ─────────────────────────────────────────────────────────\n\nexport interface ReliabilityActions {\n recordReliabilityEvent: (event: keyof ReliabilityStats) => void;\n resetReliabilityStats: () => void;\n}\n\nexport function createReliabilityActions(set: Setter): ReliabilityActions {\n return {\n recordReliabilityEvent: (event) =>\n set((s) => ({ reliability: { ...s.reliability, [event]: s.reliability[event] + 1 } })),\n\n resetReliabilityStats: () => set(() => ({ reliability: emptyReliabilityStats() })),\n };\n}\n"],"mappings":";AAcA,SAAgB,EAAsB,GAA8B;AAClE,SAAO;AAAA,IACL,kBAAA,CAAmB,GAAK,MAAU,EAAA,CAAK,OAAO,EAAE,WAAW;AAAA,MAAE,GAAG,EAAE;AAAA,OAAY,CAAA,GAAM;AAAA,IAAM,EAAE,EAAE;AAAA,IAE9F,gBAAA,CAAiB,GAAK,MACpB,EAAA,CAAK,OAAO,EACV,WAAW;AAAA,MACT,GAAG,EAAE;AAAA,OACJ,CAAA,GAAM,EAAE,UAAU,CAAA,IAAO;AAAA,QAAE,GAAG,EAAE,UAAU,CAAA;AAAA,QAAM,GAAG;AAAA,MAAO,IAAI,EAAE,UAAU,CAAA;AAAA,IAC7E,EACF,EAAE;AAAA,IAEJ,oBAAA,CAAqB,MACnB,EAAA,CAAK,OAAO,EACV,WAAW,OAAO,YAAY,OAAO,QAAQ,EAAE,SAAS,EAAE,OAAA,CAAQ,CAAC,CAAA,MAAO,MAAM,CAAG,CAAC,EACtF,EAAE;AAAA,EACN;AACF;AAYA,SAAgB,EAAmB,GAA2B;AAC5D,SAAO;AAAA,IACL,cAAA,CAAe,MAAS,EAAA,CAAK,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAI,EAAE,EAAE;AAAA,IAElE,iBAAA,CAAkB,GAAI,MACpB,EAAA,CAAK,OAAO,EACV,OAAO,EAAE,MAAM,IAAA,CAAK,MAAU,EAAK,OAAO,IAAK;AAAA,MAAE,GAAG;AAAA,MAAM,GAAG;AAAA,IAAO,IAAI,CAAK,EAC/E,EAAE;AAAA,IAEJ,uBAAA,CAAwB,MACtB,EAAA,CAAK,MAAM;AACT,YAAM,IAAM,IAAI,IAAI,EAAQ,IAAA,CAAK,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACxD,aAAO,EACL,OAAO,EAAE,MAAM,IAAA,CAAK,MAAS;AAC3B,cAAM,IAAI,EAAI,IAAI,EAAK,EAAE;AACzB,eAAO,IAAI;AAAA,UAAE,GAAG;AAAA,UAAM,GAAG;AAAA,QAAE,IAAI;AAAA,MACjC,CAAC,EACH;AAAA,IACF,CAAC;AAAA,IAEH,iBAAA,CAAkB,MAAO,EAAA,CAAK,OAAO,EAAE,OAAO,EAAE,MAAM,OAAA,CAAQ,MAAS,EAAK,OAAO,CAAE,EAAE,EAAE;AAAA,IAEzF,cAAA,CAAe,MAAU,EAAA,OAAW,EAAE,OAAO,EAAM,EAAE;AAAA,EACvD;AACF;AASA,SAAgB,EAAyB,GAAiC;AACxE,SAAO;AAAA,IACL,wBAAA,CAAyB,MACvB,EAAA,CAAK,OAAO,EAAE,aAAa;AAAA,MAAE,GAAG,EAAE;AAAA,OAAc,CAAA,GAAQ,EAAE,YAAY,CAAA,IAAS;AAAA,IAAE,EAAE,EAAE;AAAA,IAEvF,uBAAA,MAA6B,EAAA,OAAW,EAAE,aAAa,EAAsB,EAAE,EAAE;AAAA,EACnF;AACF"}
package/dist/store.js CHANGED
@@ -1,9 +1,10 @@
1
- import { createQueueActions as u, createResourceActions as i } from "./store-slices.js";
2
- var t, s = /* @__PURE__ */ new Set();
1
+ import { emptyReliabilityStats as s } from "./types.js";
2
+ import { createQueueActions as a, createReliabilityActions as u, createResourceActions as c } from "./store-slices.js";
3
+ var t, o = /* @__PURE__ */ new Set();
3
4
  function r() {
4
- s.forEach((e) => e());
5
+ o.forEach((e) => e());
5
6
  }
6
- function n(e) {
7
+ function i(e) {
7
8
  t = {
8
9
  ...t,
9
10
  ...e(t)
@@ -15,35 +16,37 @@ t = {
15
16
  swError: void 0,
16
17
  resources: {},
17
18
  queue: [],
18
- setOnline: (e) => n(() => ({ isOnline: e })),
19
- setSwStatus: (e, o) => n(() => ({
19
+ reliability: s(),
20
+ setOnline: (e) => i(() => ({ isOnline: e })),
21
+ setSwStatus: (e, n) => i(() => ({
20
22
  swStatus: e,
21
- swError: o
23
+ swError: n
22
24
  })),
23
- ...i(n),
24
- ...u(n)
25
+ ...c(i),
26
+ ...a(i),
27
+ ...u(i)
25
28
  };
26
- function a() {
29
+ function f() {
27
30
  return t;
28
31
  }
29
- function c(e) {
30
- return s.add(e), () => {
31
- s.delete(e);
32
+ function d(e) {
33
+ return o.add(e), () => {
34
+ o.delete(e);
32
35
  };
33
36
  }
34
- var d = {
35
- getState: a,
36
- subscribe: c,
37
+ var b = {
38
+ getState: f,
39
+ subscribe: d,
37
40
  setState: (e) => {
38
- const o = typeof e == "function" ? e(t) : e;
41
+ const n = typeof e == "function" ? e(t) : e;
39
42
  t = {
40
43
  ...t,
41
- ...o
44
+ ...n
42
45
  }, r();
43
46
  }
44
47
  };
45
48
  export {
46
- d as useEidosStore
49
+ b as useEidosStore
47
50
  };
48
51
 
49
52
  //# sourceMappingURL=store.js.map