@sweidos/eidos 2.1.0 → 2.3.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.
Files changed (56) hide show
  1. package/README.md +290 -22
  2. package/dist/action.d.ts +22 -0
  3. package/dist/action.js +47 -47
  4. package/dist/action.js.map +1 -1
  5. package/dist/async-storage-adapter.d.ts +25 -0
  6. package/dist/debug.d.ts +46 -0
  7. package/dist/debug.js +43 -0
  8. package/dist/debug.js.map +1 -0
  9. package/dist/devtools.js +350 -21
  10. package/dist/eidos-sw.js +60 -19
  11. package/dist/eidos.cjs +5 -5
  12. package/dist/eidos.cjs.map +1 -1
  13. package/dist/idb.d.ts +10 -0
  14. package/dist/index.d.ts +20 -586
  15. package/dist/index.js +47 -41
  16. package/dist/internal/url-base64.d.ts +2 -0
  17. package/dist/query.d.ts +1 -2
  18. package/dist/queue-storage.d.ts +12 -0
  19. package/dist/queue-sync.d.ts +32 -0
  20. package/dist/react/Provider.d.ts +16 -0
  21. package/dist/react/ProviderRN.d.ts +0 -1
  22. package/dist/react/hooks.d.ts +51 -0
  23. package/dist/react/hooks.js +30 -27
  24. package/dist/react/hooks.js.map +1 -1
  25. package/dist/replay.d.ts +15 -0
  26. package/dist/resource.d.ts +32 -0
  27. package/dist/resource.js +80 -78
  28. package/dist/resource.js.map +1 -1
  29. package/dist/runtime-rn.d.ts +0 -1
  30. package/dist/runtime.d.ts +39 -0
  31. package/dist/runtime.js +32 -24
  32. package/dist/runtime.js.map +1 -1
  33. package/dist/store-slices.d.ts +26 -0
  34. package/dist/store-slices.js +31 -20
  35. package/dist/store-slices.js.map +1 -1
  36. package/dist/store.d.ts +15 -0
  37. package/dist/store.js +22 -19
  38. package/dist/store.js.map +1 -1
  39. package/dist/stores.d.ts +64 -0
  40. package/dist/stores.js +31 -22
  41. package/dist/stores.js.map +1 -1
  42. package/dist/sveltekit.d.ts +0 -1
  43. package/dist/sw-bridge.d.ts +24 -0
  44. package/dist/sw-bridge.js +69 -54
  45. package/dist/sw-bridge.js.map +1 -1
  46. package/dist/testing.cjs +3 -2
  47. package/dist/testing.d.ts +1 -2
  48. package/dist/testing.js +3 -2
  49. package/dist/types.d.ts +305 -0
  50. package/dist/types.js +19 -8
  51. package/dist/types.js.map +1 -1
  52. package/dist/version.d.ts +1 -0
  53. package/dist/version.js +1 -1
  54. package/dist/version.js.map +1 -1
  55. package/dist/vite.d.ts +0 -1
  56. package/package.json +9 -7
@@ -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"}
@@ -0,0 +1,15 @@
1
+ import { EidosState } from './types';
2
+ import { ResourceActions, QueueActions, ReliabilityActions } from './store-slices';
3
+ export interface EidosStore extends EidosState, ResourceActions, QueueActions, ReliabilityActions {
4
+ setOnline: (online: boolean) => void;
5
+ setSwStatus: (status: EidosState['swStatus'], error?: string) => void;
6
+ }
7
+ type Listener = () => void;
8
+ declare function _getState(): EidosStore;
9
+ declare function _subscribe(listener: Listener): () => void;
10
+ export declare const useEidosStore: {
11
+ getState: typeof _getState;
12
+ subscribe: typeof _subscribe;
13
+ setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
14
+ };
15
+ export {};
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
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { EidosState } from './types';\nimport { createResourceActions, createQueueActions } from './store-slices';\nimport type { ResourceActions, QueueActions } from './store-slices';\n\nexport interface EidosStore extends EidosState, ResourceActions, QueueActions {\n // Online\n setOnline: (online: boolean) => void;\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void;\n}\n\ntype Listener = () => void;\n\nlet _state: EidosStore;\nconst _listeners = new Set<Listener>();\n\nfunction _notify() {\n _listeners.forEach((fn) => fn());\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) };\n _notify();\n}\n\n_state = {\n // navigator.onLine is undefined in React Native — default to true unless explicitly false\n isOnline: typeof navigator === 'undefined' || navigator.onLine !== false,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n ...createResourceActions(_set),\n ...createQueueActions(_set),\n};\n\nfunction _getState() {\n return _state;\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener);\n return () => {\n _listeners.delete(listener);\n };\n}\n\nexport const useEidosStore = {\n getState: _getState,\n subscribe: _subscribe,\n // Test/devtools helper — merges partial state, preserves action methods.\n setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial;\n _state = { ..._state, ...update };\n _notify();\n },\n};\n"],"mappings":";AAaA,IAAI,GACE,IAAa,oBAAI,IAAc;AAErC,SAAS,IAAU;AACjB,EAAA,EAAW,QAAA,CAAS,MAAO,EAAG,CAAC;AACjC;AAEA,SAAS,EAAK,GAAoD;AAChE,EAAA,IAAS;AAAA,IAAE,GAAG;AAAA,IAAQ,GAAG,EAAQ,CAAM;AAAA,EAAE,GACzC,EAAQ;AACV;AAEA,IAAS;AAAA,EAEP,UAAU,OAAO,YAAc,OAAe,UAAU,WAAW;AAAA,EACnE,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW,CAAC;AAAA,EACZ,OAAO,CAAC;AAAA,EAER,WAAA,CAAY,MAAa,EAAA,OAAY,EAAE,UAAA,EAAS,EAAE;AAAA,EAElD,aAAA,CAAc,GAAU,MAAY,EAAA,OAAY;AAAA,IAAE,UAAA;AAAA,IAAU,SAAA;AAAA,EAAQ,EAAE;AAAA,EAEtE,GAAG,EAAsB,CAAI;AAAA,EAC7B,GAAG,EAAmB,CAAI;AAC5B;AAEA,SAAS,IAAY;AACnB,SAAO;AACT;AAEA,SAAS,EAAW,GAAoB;AACtC,SAAA,EAAW,IAAI,CAAQ,GACvB,MAAa;AACX,IAAA,EAAW,OAAO,CAAQ;AAAA,EAC5B;AACF;AAEA,IAAa,IAAgB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EAEX,UAAA,CAAW,MAA4E;AACrF,UAAM,IAAS,OAAO,KAAY,aAAa,EAAQ,CAAM,IAAI;AACjE,IAAA,IAAS;AAAA,MAAE,GAAG;AAAA,MAAQ,GAAG;AAAA,IAAO,GAChC,EAAQ;AAAA,EACV;AACF"}
1
+ {"version":3,"file":"store.js","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { EidosState } from './types';\nimport { emptyReliabilityStats } from './types';\nimport {\n createResourceActions,\n createQueueActions,\n createReliabilityActions,\n} from './store-slices';\nimport type { ResourceActions, QueueActions, ReliabilityActions } from './store-slices';\n\nexport interface EidosStore extends EidosState, ResourceActions, QueueActions, ReliabilityActions {\n // Online\n setOnline: (online: boolean) => void;\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void;\n}\n\ntype Listener = () => void;\n\nlet _state: EidosStore;\nconst _listeners = new Set<Listener>();\n\nfunction _notify() {\n _listeners.forEach((fn) => fn());\n}\n\nfunction _set(updater: (prev: EidosStore) => Partial<EidosStore>) {\n _state = { ..._state, ...updater(_state) };\n _notify();\n}\n\n_state = {\n // navigator.onLine is undefined in React Native — default to true unless explicitly false\n isOnline: typeof navigator === 'undefined' || navigator.onLine !== false,\n swStatus: 'idle',\n swError: undefined,\n resources: {},\n queue: [],\n reliability: emptyReliabilityStats(),\n\n setOnline: (isOnline) => _set(() => ({ isOnline })),\n\n setSwStatus: (swStatus, swError) => _set(() => ({ swStatus, swError })),\n\n ...createResourceActions(_set),\n ...createQueueActions(_set),\n ...createReliabilityActions(_set),\n};\n\nfunction _getState() {\n return _state;\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener);\n return () => {\n _listeners.delete(listener);\n };\n}\n\nexport const useEidosStore = {\n getState: _getState,\n subscribe: _subscribe,\n // Test/devtools helper — merges partial state, preserves action methods.\n setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => {\n const update = typeof partial === 'function' ? partial(_state) : partial;\n _state = { ..._state, ...update };\n _notify();\n },\n};\n"],"mappings":";;AAkBA,IAAI,GACE,IAAa,oBAAI,IAAc;AAErC,SAAS,IAAU;AACjB,EAAA,EAAW,QAAA,CAAS,MAAO,EAAG,CAAC;AACjC;AAEA,SAAS,EAAK,GAAoD;AAChE,EAAA,IAAS;AAAA,IAAE,GAAG;AAAA,IAAQ,GAAG,EAAQ,CAAM;AAAA,EAAE,GACzC,EAAQ;AACV;AAEA,IAAS;AAAA,EAEP,UAAU,OAAO,YAAc,OAAe,UAAU,WAAW;AAAA,EACnE,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW,CAAC;AAAA,EACZ,OAAO,CAAC;AAAA,EACR,aAAa,EAAsB;AAAA,EAEnC,WAAA,CAAY,MAAa,EAAA,OAAY,EAAE,UAAA,EAAS,EAAE;AAAA,EAElD,aAAA,CAAc,GAAU,MAAY,EAAA,OAAY;AAAA,IAAE,UAAA;AAAA,IAAU,SAAA;AAAA,EAAQ,EAAE;AAAA,EAEtE,GAAG,EAAsB,CAAI;AAAA,EAC7B,GAAG,EAAmB,CAAI;AAAA,EAC1B,GAAG,EAAyB,CAAI;AAClC;AAEA,SAAS,IAAY;AACnB,SAAO;AACT;AAEA,SAAS,EAAW,GAAoB;AACtC,SAAA,EAAW,IAAI,CAAQ,GACvB,MAAa;AACX,IAAA,EAAW,OAAO,CAAQ;AAAA,EAC5B;AACF;AAEA,IAAa,IAAgB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EAEX,UAAA,CAAW,MAA4E;AACrF,UAAM,IAAS,OAAO,KAAY,aAAa,EAAQ,CAAM,IAAI;AACjE,IAAA,IAAS;AAAA,MAAE,GAAG;AAAA,MAAQ,GAAG;AAAA,IAAO,GAChC,EAAQ;AAAA,EACV;AACF"}
@@ -0,0 +1,64 @@
1
+ import { EidosStore } from './store';
2
+ import { ActionQueueItem, ResourceEntry, ReliabilityStats } from './types';
3
+ export interface EidosReadable<T> {
4
+ /** Subscribe to value changes. Returns an unsubscribe function. */
5
+ subscribe(run: (value: T) => void): () => void;
6
+ /** Read the current value synchronously without subscribing. */
7
+ getState(): T;
8
+ }
9
+ /** Full Eidos state snapshot. Prefer the narrower stores below. */
10
+ export declare const eidosStore: EidosReadable<EidosStore>;
11
+ /** The action queue. Re-notifies on every queue mutation. */
12
+ export declare const eidosQueue: EidosReadable<ActionQueueItem[]>;
13
+ /**
14
+ * Online status + SW lifecycle.
15
+ * Only re-emits when isOnline, swStatus, or swError actually changes.
16
+ */
17
+ export declare const eidosStatus: EidosReadable<{
18
+ isOnline: boolean;
19
+ swStatus: EidosStore['swStatus'];
20
+ swError: string | undefined;
21
+ }>;
22
+ /**
23
+ * Queue counts. Re-emits only when a count actually changes, not on every
24
+ * queue mutation (e.g. a status transition that doesn't change counts is skipped).
25
+ */
26
+ export declare const eidosQueueStats: EidosReadable<{
27
+ pending: number;
28
+ failed: number;
29
+ replaying: number;
30
+ total: number;
31
+ }>;
32
+ /**
33
+ * Cumulative, session-scoped `neverLose` queue outcome counters — opt-in
34
+ * reliability telemetry. Re-emits only when a counter changes. See
35
+ * `EidosConfig.onReliabilityReport` to forward these to an analytics backend.
36
+ */
37
+ export declare const eidosReliabilityStats: EidosReadable<ReliabilityStats>;
38
+ /**
39
+ * Live cache state for a single registered resource URL.
40
+ * @example
41
+ * // Svelte
42
+ * const entry = eidosResource('/api/products')
43
+ * $: hits = $entry?.cacheHits ?? 0
44
+ */
45
+ export declare function eidosResource(url: string): EidosReadable<ResourceEntry | undefined>;
46
+ /**
47
+ * Live state for a single queue item by ID. Returns `undefined` once the item
48
+ * is removed from the queue (after a successful replay or `clearQueue()`).
49
+ * @example
50
+ * // Svelte
51
+ * const item = eidosAction(queuedResult.id)
52
+ * $: status = $item?.status // 'pending' | 'replaying' | 'succeeded' | 'failed' | undefined
53
+ */
54
+ export declare function eidosAction(id: string): EidosReadable<ActionQueueItem | undefined>;
55
+ /**
56
+ * Calls `callback` once each time the action queue drains from non-empty → 0.
57
+ * Framework-agnostic equivalent of `useEidosOnDrain` for Svelte/Vue/vanilla.
58
+ * Returns an unsubscribe function.
59
+ *
60
+ * @example
61
+ * // Svelte
62
+ * onMount(() => onQueueDrain(() => toast.success('All offline actions synced!')))
63
+ */
64
+ export declare function onQueueDrain(callback: () => void): () => void;
package/dist/stores.js CHANGED
@@ -1,46 +1,55 @@
1
- import { useEidosStore as n } from "./store.js";
2
1
  import { countQueueByStatus as a } from "./types.js";
3
- function c(e, t) {
2
+ import { useEidosStore as u } from "./store.js";
3
+ function l(e, t) {
4
4
  const r = Object.keys(e);
5
5
  if (r.length !== Object.keys(t).length) return !1;
6
- for (const u of r) if (e[u] !== t[u]) return !1;
6
+ for (const s of r) if (e[s] !== t[s]) return !1;
7
7
  return !0;
8
8
  }
9
- function i(e, t) {
10
- return c(e, t);
9
+ function o(e, t) {
10
+ return l(e, t);
11
11
  }
12
- function s(e, t = Object.is) {
12
+ function n(e, t = Object.is) {
13
13
  return {
14
14
  subscribe(r) {
15
- let u = e(n.getState());
16
- return r(u), n.subscribe(() => {
17
- const o = e(n.getState());
18
- t(u, o) || (u = o, r(o));
15
+ let s = e(u.getState());
16
+ return r(s), u.subscribe(() => {
17
+ const i = e(u.getState());
18
+ t(s, i) || (s = i, r(i));
19
19
  });
20
20
  },
21
21
  getState() {
22
- return e(n.getState());
22
+ return e(u.getState());
23
23
  }
24
24
  };
25
25
  }
26
- var S = s((e) => e), d = s((e) => e.queue), b = s((e) => ({
26
+ var S = n((e) => e), b = n((e) => e.queue), d = n((e) => ({
27
27
  isOnline: e.isOnline,
28
28
  swStatus: e.swStatus,
29
29
  swError: e.swError
30
- }), i), g = s((e) => a(e.queue), i);
31
- function w(e) {
32
- return s((t) => t.resources[e]);
30
+ }), o), g = n((e) => a(e.queue), o), q = n((e) => e.reliability, o);
31
+ function h(e) {
32
+ return n((t) => t.resources[e]);
33
+ }
34
+ function v(e) {
35
+ return n((t) => t.queue.find((r) => r.id === e));
33
36
  }
34
- function q(e) {
35
- return s((t) => t.queue.find((r) => r.id === e));
37
+ function w(e) {
38
+ let t = u.getState().queue.length;
39
+ return u.subscribe(() => {
40
+ const r = u.getState().queue.length;
41
+ t > 0 && r === 0 && e(), t = r;
42
+ });
36
43
  }
37
44
  export {
38
- q as eidosAction,
39
- d as eidosQueue,
45
+ v as eidosAction,
46
+ b as eidosQueue,
40
47
  g as eidosQueueStats,
41
- w as eidosResource,
42
- b as eidosStatus,
43
- S as eidosStore
48
+ q as eidosReliabilityStats,
49
+ h as eidosResource,
50
+ d as eidosStatus,
51
+ S as eidosStore,
52
+ w as onQueueDrain
44
53
  };
45
54
 
46
55
  //# sourceMappingURL=stores.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stores.js","names":[],"sources":["../src/stores.ts"],"sourcesContent":["/**\n * Framework-agnostic reactive stores — compatible with Svelte's store protocol,\n * Vue's watchEffect, RxJS, and vanilla JS. Zero framework dependencies.\n *\n * Svelte: use the `$` prefix — `$eidosQueue`, `$eidosStatus`, etc.\n * Vue: call `.subscribe()` inside a composable with `onUnmounted` cleanup.\n * Vanilla: call `.subscribe(run)` directly; the return value unsubscribes.\n *\n * Each store calls its subscriber whenever any part of the Eidos state changes.\n * For fine-grained subscriptions, use `.getState()` to read the current snapshot\n * and compare manually in the subscriber callback.\n */\n\nimport { useEidosStore } from './store';\nimport type { EidosStore } from './store';\nimport { countQueueByStatus } from './types';\nimport type { ActionQueueItem, ResourceEntry } from './types';\n\n// ── Readable<T> — compatible with Svelte's Readable interface ─────────────────\n\nexport interface EidosReadable<T> {\n /** Subscribe to value changes. Returns an unsubscribe function. */\n subscribe(run: (value: T) => void): () => void;\n /** Read the current value synchronously without subscribing. */\n getState(): T;\n}\n\nfunction shallowEqual<T extends Record<string, unknown>>(a: T, b: T): boolean {\n const keys = Object.keys(a) as (keyof T)[];\n if (keys.length !== Object.keys(b).length) return false;\n for (const k of keys) if (a[k] !== b[k]) return false;\n return true;\n}\n\n// Typed comparator alias so call sites don't need inline casts.\nfunction shallowEq<T extends Record<string, unknown>>(a: T, b: T): boolean {\n return shallowEqual(a, b);\n}\n\nfunction readable<T>(\n selector: (s: EidosStore) => T,\n equal: (a: T, b: T) => boolean = Object.is,\n): EidosReadable<T> {\n return {\n subscribe(run) {\n // Emit current value immediately (Svelte store contract)\n let last = selector(useEidosStore.getState());\n run(last);\n return useEidosStore.subscribe(() => {\n const next = selector(useEidosStore.getState());\n if (!equal(last, next)) {\n last = next;\n run(next);\n }\n });\n },\n getState() {\n return selector(useEidosStore.getState());\n },\n };\n}\n\n// ── Static stores (created once at module scope) ──────────────────────────────\n\n/** Full Eidos state snapshot. Prefer the narrower stores below. */\nexport const eidosStore: EidosReadable<EidosStore> = readable((s) => s);\n\n/** The action queue. Re-notifies on every queue mutation. */\nexport const eidosQueue: EidosReadable<ActionQueueItem[]> = readable((s) => s.queue);\n\n/**\n * Online status + SW lifecycle.\n * Only re-emits when isOnline, swStatus, or swError actually changes.\n */\nexport const eidosStatus: EidosReadable<{\n isOnline: boolean;\n swStatus: EidosStore['swStatus'];\n swError: string | undefined;\n}> = readable(\n (s) => ({ isOnline: s.isOnline, swStatus: s.swStatus, swError: s.swError }),\n shallowEq,\n);\n\n/**\n * Queue counts. Re-emits only when a count actually changes, not on every\n * queue mutation (e.g. a status transition that doesn't change counts is skipped).\n */\nexport const eidosQueueStats: EidosReadable<{\n pending: number;\n failed: number;\n replaying: number;\n total: number;\n}> = readable((s) => countQueueByStatus(s.queue), shallowEq);\n\n// ── Dynamic stores (created per URL / ID) ─────────────────────────────────────\n\n/**\n * Live cache state for a single registered resource URL.\n * @example\n * // Svelte\n * const entry = eidosResource('/api/products')\n * $: hits = $entry?.cacheHits ?? 0\n */\nexport function eidosResource(url: string): EidosReadable<ResourceEntry | undefined> {\n return readable((s) => s.resources[url]);\n}\n\n/**\n * Live state for a single queue item by ID. Returns `undefined` once the item\n * is removed from the queue (after a successful replay or `clearQueue()`).\n * @example\n * // Svelte\n * const item = eidosAction(queuedResult.id)\n * $: status = $item?.status // 'pending' | 'replaying' | 'succeeded' | 'failed' | undefined\n */\nexport function eidosAction(id: string): EidosReadable<ActionQueueItem | undefined> {\n return readable((s) => s.queue.find((item) => item.id === id));\n}\n"],"mappings":";;AA2BA,SAAS,EAAgD,GAAM,GAAe;AAC5E,QAAM,IAAO,OAAO,KAAK,CAAC;AAC1B,MAAI,EAAK,WAAW,OAAO,KAAK,CAAC,EAAE,OAAQ,QAAO;AAClD,aAAW,KAAK,EAAM,KAAI,EAAE,CAAA,MAAO,EAAE,CAAA,EAAI,QAAO;AAChD,SAAO;AACT;AAGA,SAAS,EAA6C,GAAM,GAAe;AACzE,SAAO,EAAa,GAAG,CAAC;AAC1B;AAEA,SAAS,EACP,GACA,IAAiC,OAAO,IACtB;AAClB,SAAO;AAAA,IACL,UAAU,GAAK;AAEb,UAAI,IAAO,EAAS,EAAc,SAAS,CAAC;AAC5C,aAAA,EAAI,CAAI,GACD,EAAc,UAAA,MAAgB;AACnC,cAAM,IAAO,EAAS,EAAc,SAAS,CAAC;AAC9C,QAAK,EAAM,GAAM,CAAI,MACnB,IAAO,GACP,EAAI,CAAI;AAAA,MAEZ,CAAC;AAAA,IACH;AAAA,IACA,WAAW;AACT,aAAO,EAAS,EAAc,SAAS,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;AAKA,IAAa,IAAwC,EAAA,CAAU,MAAM,CAAC,GAGzD,IAA+C,EAAA,CAAU,MAAM,EAAE,KAAK,GAMtE,IAIR,EAAA,CACF,OAAO;AAAA,EAAE,UAAU,EAAE;AAAA,EAAU,UAAU,EAAE;AAAA,EAAU,SAAS,EAAE;AAAQ,IACzE,CACF,GAMa,IAKR,EAAA,CAAU,MAAM,EAAmB,EAAE,KAAK,GAAG,CAAS;AAW3D,SAAgB,EAAc,GAAuD;AACnF,SAAO,EAAA,CAAU,MAAM,EAAE,UAAU,CAAA,CAAI;AACzC;AAUA,SAAgB,EAAY,GAAwD;AAClF,SAAO,EAAA,CAAU,MAAM,EAAE,MAAM,KAAA,CAAM,MAAS,EAAK,OAAO,CAAE,CAAC;AAC/D"}
1
+ {"version":3,"file":"stores.js","names":[],"sources":["../src/stores.ts"],"sourcesContent":["/**\n * Framework-agnostic reactive stores — compatible with Svelte's store protocol,\n * Vue's watchEffect, RxJS, and vanilla JS. Zero framework dependencies.\n *\n * Svelte: use the `$` prefix — `$eidosQueue`, `$eidosStatus`, etc.\n * Vue: call `.subscribe()` inside a composable with `onUnmounted` cleanup.\n * Vanilla: call `.subscribe(run)` directly; the return value unsubscribes.\n *\n * Each store calls its subscriber whenever any part of the Eidos state changes.\n * For fine-grained subscriptions, use `.getState()` to read the current snapshot\n * and compare manually in the subscriber callback.\n */\n\nimport { useEidosStore } from './store';\nimport type { EidosStore } from './store';\nimport { countQueueByStatus } from './types';\nimport type { ActionQueueItem, ResourceEntry, ReliabilityStats } from './types';\n\n// ── Readable<T> — compatible with Svelte's Readable interface ─────────────────\n\nexport interface EidosReadable<T> {\n /** Subscribe to value changes. Returns an unsubscribe function. */\n subscribe(run: (value: T) => void): () => void;\n /** Read the current value synchronously without subscribing. */\n getState(): T;\n}\n\nfunction shallowEqual<T extends Record<string, unknown>>(a: T, b: T): boolean {\n const keys = Object.keys(a) as (keyof T)[];\n if (keys.length !== Object.keys(b).length) return false;\n for (const k of keys) if (a[k] !== b[k]) return false;\n return true;\n}\n\n// Typed comparator alias so call sites don't need inline casts.\nfunction shallowEq<T extends Record<string, unknown>>(a: T, b: T): boolean {\n return shallowEqual(a, b);\n}\n\nfunction readable<T>(\n selector: (s: EidosStore) => T,\n equal: (a: T, b: T) => boolean = Object.is,\n): EidosReadable<T> {\n return {\n subscribe(run) {\n // Emit current value immediately (Svelte store contract)\n let last = selector(useEidosStore.getState());\n run(last);\n return useEidosStore.subscribe(() => {\n const next = selector(useEidosStore.getState());\n if (!equal(last, next)) {\n last = next;\n run(next);\n }\n });\n },\n getState() {\n return selector(useEidosStore.getState());\n },\n };\n}\n\n// ── Static stores (created once at module scope) ──────────────────────────────\n\n/** Full Eidos state snapshot. Prefer the narrower stores below. */\nexport const eidosStore: EidosReadable<EidosStore> = readable((s) => s);\n\n/** The action queue. Re-notifies on every queue mutation. */\nexport const eidosQueue: EidosReadable<ActionQueueItem[]> = readable((s) => s.queue);\n\n/**\n * Online status + SW lifecycle.\n * Only re-emits when isOnline, swStatus, or swError actually changes.\n */\nexport const eidosStatus: EidosReadable<{\n isOnline: boolean;\n swStatus: EidosStore['swStatus'];\n swError: string | undefined;\n}> = readable(\n (s) => ({ isOnline: s.isOnline, swStatus: s.swStatus, swError: s.swError }),\n shallowEq,\n);\n\n/**\n * Queue counts. Re-emits only when a count actually changes, not on every\n * queue mutation (e.g. a status transition that doesn't change counts is skipped).\n */\nexport const eidosQueueStats: EidosReadable<{\n pending: number;\n failed: number;\n replaying: number;\n total: number;\n}> = readable((s) => countQueueByStatus(s.queue), shallowEq);\n\n/**\n * Cumulative, session-scoped `neverLose` queue outcome counters — opt-in\n * reliability telemetry. Re-emits only when a counter changes. See\n * `EidosConfig.onReliabilityReport` to forward these to an analytics backend.\n */\nexport const eidosReliabilityStats: EidosReadable<ReliabilityStats> = readable(\n (s) => s.reliability,\n shallowEq,\n);\n\n// ── Dynamic stores (created per URL / ID) ─────────────────────────────────────\n\n/**\n * Live cache state for a single registered resource URL.\n * @example\n * // Svelte\n * const entry = eidosResource('/api/products')\n * $: hits = $entry?.cacheHits ?? 0\n */\nexport function eidosResource(url: string): EidosReadable<ResourceEntry | undefined> {\n return readable((s) => s.resources[url]);\n}\n\n/**\n * Live state for a single queue item by ID. Returns `undefined` once the item\n * is removed from the queue (after a successful replay or `clearQueue()`).\n * @example\n * // Svelte\n * const item = eidosAction(queuedResult.id)\n * $: status = $item?.status // 'pending' | 'replaying' | 'succeeded' | 'failed' | undefined\n */\nexport function eidosAction(id: string): EidosReadable<ActionQueueItem | undefined> {\n return readable((s) => s.queue.find((item) => item.id === id));\n}\n\n/**\n * Calls `callback` once each time the action queue drains from non-empty → 0.\n * Framework-agnostic equivalent of `useEidosOnDrain` for Svelte/Vue/vanilla.\n * Returns an unsubscribe function.\n *\n * @example\n * // Svelte\n * onMount(() => onQueueDrain(() => toast.success('All offline actions synced!')))\n */\nexport function onQueueDrain(callback: () => void): () => void {\n let prev = useEidosStore.getState().queue.length;\n return useEidosStore.subscribe(() => {\n const total = useEidosStore.getState().queue.length;\n if (prev > 0 && total === 0) callback();\n prev = total;\n });\n}\n"],"mappings":";;AA2BA,SAAS,EAAgD,GAAM,GAAe;AAC5E,QAAM,IAAO,OAAO,KAAK,CAAC;AAC1B,MAAI,EAAK,WAAW,OAAO,KAAK,CAAC,EAAE,OAAQ,QAAO;AAClD,aAAW,KAAK,EAAM,KAAI,EAAE,CAAA,MAAO,EAAE,CAAA,EAAI,QAAO;AAChD,SAAO;AACT;AAGA,SAAS,EAA6C,GAAM,GAAe;AACzE,SAAO,EAAa,GAAG,CAAC;AAC1B;AAEA,SAAS,EACP,GACA,IAAiC,OAAO,IACtB;AAClB,SAAO;AAAA,IACL,UAAU,GAAK;AAEb,UAAI,IAAO,EAAS,EAAc,SAAS,CAAC;AAC5C,aAAA,EAAI,CAAI,GACD,EAAc,UAAA,MAAgB;AACnC,cAAM,IAAO,EAAS,EAAc,SAAS,CAAC;AAC9C,QAAK,EAAM,GAAM,CAAI,MACnB,IAAO,GACP,EAAI,CAAI;AAAA,MAEZ,CAAC;AAAA,IACH;AAAA,IACA,WAAW;AACT,aAAO,EAAS,EAAc,SAAS,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;AAKA,IAAa,IAAwC,EAAA,CAAU,MAAM,CAAC,GAGzD,IAA+C,EAAA,CAAU,MAAM,EAAE,KAAK,GAMtE,IAIR,EAAA,CACF,OAAO;AAAA,EAAE,UAAU,EAAE;AAAA,EAAU,UAAU,EAAE;AAAA,EAAU,SAAS,EAAE;AAAQ,IACzE,CACF,GAMa,IAKR,EAAA,CAAU,MAAM,EAAmB,EAAE,KAAK,GAAG,CAAS,GAO9C,IAAyD,EAAA,CACnE,MAAM,EAAE,aACT,CACF;AAWA,SAAgB,EAAc,GAAuD;AACnF,SAAO,EAAA,CAAU,MAAM,EAAE,UAAU,CAAA,CAAI;AACzC;AAUA,SAAgB,EAAY,GAAwD;AAClF,SAAO,EAAA,CAAU,MAAM,EAAE,MAAM,KAAA,CAAM,MAAS,EAAK,OAAO,CAAE,CAAC;AAC/D;AAWA,SAAgB,EAAa,GAAkC;AAC7D,MAAI,IAAO,EAAc,SAAS,EAAE,MAAM;AAC1C,SAAO,EAAc,UAAA,MAAgB;AACnC,UAAM,IAAQ,EAAc,SAAS,EAAE,MAAM;AAC7C,IAAI,IAAO,KAAK,MAAU,KAAG,EAAS,GACtC,IAAO;AAAA,EACT,CAAC;AACH"}
@@ -1,5 +1,4 @@
1
1
  import { EidosConfig } from './index.ts';
2
-
3
2
  /**
4
3
  * Returns an `onMount`-compatible callback that initialises the Eidos runtime
5
4
  * on the client only. Prevents SSR crashes caused by accessing `indexedDB` or
@@ -0,0 +1,24 @@
1
+ export declare function getSwRegistration(): ServiceWorkerRegistration | null;
2
+ interface SwRegistrationOptions {
3
+ skipWaiting: boolean;
4
+ onUpdateAvailable?: (registration: ServiceWorkerRegistration) => void;
5
+ }
6
+ export declare function registerServiceWorker(swPath: string, options?: SwRegistrationOptions): Promise<void>;
7
+ export declare function sendToWorker(message: Record<string, unknown>): void;
8
+ export declare function registerBgSyncHandler(fn: () => void): void;
9
+ export declare function isBgSyncSupported(): boolean;
10
+ interface PushHandlers {
11
+ onNotificationClick?: (data: unknown) => void;
12
+ onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;
13
+ }
14
+ export declare function registerPushCallbacks(handlers: PushHandlers): void;
15
+ export declare function setOfflineSimulation(enabled: boolean): void;
16
+ /**
17
+ * Tells the waiting service worker to activate immediately, then reloads the page.
18
+ * Only relevant when `skipWaiting: false` — call this after the user confirms
19
+ * a "reload to update" toast shown via `onUpdateAvailable`.
20
+ */
21
+ export declare function triggerSwUpdate(): void;
22
+ /** Test-only: resets module-level state between test cases. */
23
+ export declare function _resetSwBridgeForTests(): void;
24
+ export {};
package/dist/sw-bridge.js CHANGED
@@ -1,78 +1,78 @@
1
1
  import { useEidosStore as a } from "./store.js";
2
- var s = null, o = [];
2
+ var i = null, o = [];
3
3
  function E() {
4
- return s;
4
+ return i;
5
5
  }
6
- async function p(e) {
6
+ async function w(t, e = { skipWaiting: !0 }) {
7
7
  if (typeof navigator > "u" || !("serviceWorker" in navigator)) {
8
8
  a.getState().setSwStatus("unsupported");
9
9
  return;
10
10
  }
11
- const t = a.getState();
12
- t.setSwStatus("registering");
11
+ const n = a.getState();
12
+ n.setSwStatus("registering");
13
13
  try {
14
- s = await navigator.serviceWorker.register(e, { scope: "/" }), await d(s), t.setSwStatus("active"), navigator.serviceWorker.addEventListener("message", g), window.addEventListener("online", () => t.setOnline(!0)), window.addEventListener("offline", () => t.setOnline(!1)), l();
15
- } catch (n) {
16
- t.setSwStatus("error", String(n));
14
+ i = await navigator.serviceWorker.register(t, { scope: "/" }), await d(i), n.setSwStatus("active"), navigator.serviceWorker.addEventListener("message", S), window.addEventListener("online", () => n.setOnline(!0)), window.addEventListener("offline", () => n.setOnline(!1)), l(), v(i, e);
15
+ } catch (r) {
16
+ n.setSwStatus("error", String(r));
17
17
  }
18
18
  }
19
- function d(e) {
20
- return new Promise((t) => {
21
- if (e.active) {
22
- t();
19
+ function d(t) {
20
+ return new Promise((e) => {
21
+ if (t.active) {
22
+ e();
23
23
  return;
24
24
  }
25
- const n = e.installing ?? e.waiting;
25
+ const n = t.installing ?? t.waiting;
26
26
  if (!n) {
27
- t();
27
+ e();
28
28
  return;
29
29
  }
30
- const i = setTimeout(t, 1e4);
31
- n.addEventListener("statechange", function r() {
32
- n.state === "activated" && (clearTimeout(i), n.removeEventListener("statechange", r), t());
30
+ const r = setTimeout(e, 1e4);
31
+ n.addEventListener("statechange", function s() {
32
+ n.state === "activated" && (clearTimeout(r), n.removeEventListener("statechange", s), e());
33
33
  });
34
34
  });
35
35
  }
36
- function S(e) {
37
- const t = s?.active;
38
- t ? t.postMessage(e) : o.push(e);
36
+ function g(t) {
37
+ const e = i?.active;
38
+ e ? e.postMessage(t) : o.push(t);
39
39
  }
40
40
  var u = null;
41
- function w(e) {
42
- u = e;
41
+ function I(t) {
42
+ u = t;
43
43
  }
44
- function h() {
44
+ function _() {
45
45
  try {
46
- return typeof navigator < "u" && "serviceWorker" in navigator && s !== null && "sync" in s;
46
+ return typeof navigator < "u" && "serviceWorker" in navigator && i !== null && "sync" in i;
47
47
  } catch {
48
48
  return !1;
49
49
  }
50
50
  }
51
51
  var c = {};
52
- function O(e) {
53
- c = e;
52
+ function h(t) {
53
+ c = t;
54
54
  }
55
- function g(e) {
56
- const t = e.data;
57
- if (!t?.type) return;
58
- const n = a.getState(), { type: i, url: r } = t;
59
- if (i === "EIDOS_BACKGROUND_SYNC") {
55
+ function S(t) {
56
+ const e = t.data;
57
+ if (!e?.type) return;
58
+ const n = a.getState(), { type: r, url: s } = e;
59
+ if (r === "EIDOS_BACKGROUND_SYNC") {
60
60
  u?.();
61
61
  return;
62
62
  }
63
- if (i === "EIDOS_NOTIFICATION_CLICK") {
64
- c.onNotificationClick?.(t.data);
63
+ if (r === "EIDOS_NOTIFICATION_CLICK") {
64
+ c.onNotificationClick?.(e.data);
65
65
  return;
66
66
  }
67
- if (i === "EIDOS_SUBSCRIPTION_EXPIRED") {
68
- c.onSubscriptionExpired?.(t.subscription);
67
+ if (r === "EIDOS_SUBSCRIPTION_EXPIRED") {
68
+ c.onSubscriptionExpired?.(e.subscription);
69
69
  return;
70
70
  }
71
- if (r)
72
- switch (i) {
71
+ if (s)
72
+ switch (r) {
73
73
  case "EIDOS_CACHE_HIT": {
74
- const f = n.resources[r];
75
- n.updateResource(r, {
74
+ const f = n.resources[s];
75
+ n.updateResource(s, {
76
76
  status: "fresh",
77
77
  lastEvent: "cache-hit",
78
78
  cacheHits: (f?.cacheHits ?? 0) + 1
@@ -80,41 +80,56 @@ function g(e) {
80
80
  break;
81
81
  }
82
82
  case "EIDOS_CACHE_UPDATED":
83
- n.updateResource(r, {
83
+ n.updateResource(s, {
84
84
  status: "fresh",
85
85
  lastEvent: "cache-updated",
86
86
  cachedAt: Date.now()
87
87
  });
88
88
  break;
89
89
  case "EIDOS_NETWORK_ERROR":
90
- n.updateResource(r, {
90
+ n.updateResource(s, {
91
91
  status: "error",
92
92
  lastEvent: "network-error"
93
93
  });
94
94
  break;
95
95
  }
96
96
  }
97
- function _(e) {
98
- S({
97
+ function O(t) {
98
+ g({
99
99
  type: "EIDOS_SIMULATE_OFFLINE",
100
- enabled: e
101
- }), a.getState().setOnline(!e);
100
+ enabled: t
101
+ }), a.getState().setOnline(!t);
102
102
  }
103
103
  function l() {
104
- const e = s?.active;
105
- if (e) {
106
- for (const t of o) e.postMessage(t);
104
+ const t = i?.active;
105
+ if (t) {
106
+ for (const e of o) t.postMessage(e);
107
107
  o = [];
108
108
  }
109
109
  }
110
+ function v(t, e) {
111
+ const n = (r) => {
112
+ e.skipWaiting ? r.waiting?.postMessage({ type: "EIDOS_SKIP_WAITING" }) : e.onUpdateAvailable?.(r);
113
+ };
114
+ t.waiting && navigator.serviceWorker.controller && n(t), t.addEventListener("updatefound", () => {
115
+ const r = t.installing;
116
+ r && r.addEventListener("statechange", () => {
117
+ r.state === "installed" && navigator.serviceWorker.controller && n(t);
118
+ });
119
+ });
120
+ }
121
+ function k() {
122
+ i?.waiting?.postMessage({ type: "EIDOS_SKIP_WAITING" });
123
+ }
110
124
  export {
111
125
  E as getSwRegistration,
112
- h as isBgSyncSupported,
113
- w as registerBgSyncHandler,
114
- O as registerPushCallbacks,
115
- p as registerServiceWorker,
116
- S as sendToWorker,
117
- _ as setOfflineSimulation
126
+ _ as isBgSyncSupported,
127
+ I as registerBgSyncHandler,
128
+ h as registerPushCallbacks,
129
+ w as registerServiceWorker,
130
+ g as sendToWorker,
131
+ O as setOfflineSimulation,
132
+ k as triggerSwUpdate
118
133
  };
119
134
 
120
135
  //# sourceMappingURL=sw-bridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sw-bridge.js","names":[],"sources":["../src/sw-bridge.ts"],"sourcesContent":["import { useEidosStore } from './store';\n\nlet _registration: ServiceWorkerRegistration | null = null;\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = [];\n\nexport function getSwRegistration() {\n return _registration;\n}\n\nexport async function registerServiceWorker(swPath: string): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported');\n return;\n }\n\n const store = useEidosStore.getState();\n store.setSwStatus('registering');\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' });\n\n await waitForActivation(_registration);\n\n store.setSwStatus('active');\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage);\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true));\n window.addEventListener('offline', () => store.setOnline(false));\n\n flushPendingMessages();\n } catch (err) {\n store.setSwStatus('error', String(err));\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) {\n resolve();\n return;\n }\n const sw = reg.installing ?? reg.waiting;\n if (!sw) {\n resolve();\n return;\n }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000);\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer);\n sw.removeEventListener('statechange', handler);\n resolve();\n }\n });\n });\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active;\n if (sw) {\n sw.postMessage(message);\n } else {\n _pendingMessages.push(message);\n }\n}\n\nlet _bgSyncHandler: (() => void) | null = null;\n\nexport function registerBgSyncHandler(fn: () => void): void {\n _bgSyncHandler = fn;\n}\n\nexport function isBgSyncSupported(): boolean {\n try {\n return (\n typeof navigator !== 'undefined' &&\n 'serviceWorker' in navigator &&\n _registration !== null &&\n 'sync' in _registration\n );\n } catch {\n return false;\n }\n}\n\ninterface PushHandlers {\n onNotificationClick?: (data: unknown) => void;\n onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;\n}\n\nlet _pushHandlers: PushHandlers = {};\n\nexport function registerPushCallbacks(handlers: PushHandlers): void {\n _pushHandlers = handlers;\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as {\n type: string;\n url?: string;\n strategy?: string;\n data?: unknown;\n subscription?: unknown;\n };\n if (!data?.type) return;\n\n const store = useEidosStore.getState();\n const { type, url } = data;\n\n if (type === 'EIDOS_BACKGROUND_SYNC') {\n _bgSyncHandler?.();\n return;\n }\n\n if (type === 'EIDOS_NOTIFICATION_CLICK') {\n _pushHandlers.onNotificationClick?.(data.data);\n return;\n }\n\n if (type === 'EIDOS_SUBSCRIPTION_EXPIRED') {\n _pushHandlers.onSubscriptionExpired?.(data.subscription as PushSubscriptionJSON);\n return;\n }\n\n if (!url) return;\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url];\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n });\n break;\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n });\n break;\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n });\n break;\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled });\n useEidosStore.getState().setOnline(!enabled);\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active;\n if (!sw) return;\n for (const msg of _pendingMessages) sw.postMessage(msg);\n _pendingMessages = [];\n}\n\n/** Test-only: resets module-level state between test cases. */\nexport function _resetSwBridgeForTests(): void {\n _registration = null;\n _pendingMessages = [];\n _bgSyncHandler = null;\n _pushHandlers = {};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sw-bridge.js","names":[],"sources":["../src/sw-bridge.ts"],"sourcesContent":["import { useEidosStore } from './store';\n\nlet _registration: ServiceWorkerRegistration | null = null;\n// Messages sent before the SW activates are buffered here and flushed once\n// the SW is ready. Covers resource registrations, cache clears, offline\n// simulation — anything sent at module scope before EidosProvider mounts.\nlet _pendingMessages: Record<string, unknown>[] = [];\n\nexport function getSwRegistration() {\n return _registration;\n}\n\ninterface SwRegistrationOptions {\n skipWaiting: boolean;\n onUpdateAvailable?: (registration: ServiceWorkerRegistration) => void;\n}\n\nexport async function registerServiceWorker(\n swPath: string,\n options: SwRegistrationOptions = { skipWaiting: true },\n): Promise<void> {\n if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {\n useEidosStore.getState().setSwStatus('unsupported');\n if (import.meta.env.DEV) {\n console.warn(\n '[eidos] Service workers are not supported in this context. ' +\n 'Offline support and SW-side caching are disabled. ' +\n 'Service workers require a modern browser with a secure context (HTTPS or localhost).',\n );\n }\n return;\n }\n\n // Warn early when the browser will reject registration regardless — saves a round-trip\n // and gives a clearer message than the browser's generic SecurityError.\n if (import.meta.env.DEV && typeof window !== 'undefined' && !window.isSecureContext) {\n console.warn(\n `[eidos] Service workers require a secure context (HTTPS or localhost). ` +\n `initEidos() was called on \"${window.location.origin}\" — ` +\n `the browser will silently disable offline support. ` +\n `Switch to localhost for development or deploy to HTTPS.`,\n );\n }\n\n const store = useEidosStore.getState();\n store.setSwStatus('registering');\n\n try {\n _registration = await navigator.serviceWorker.register(swPath, { scope: '/' });\n\n await waitForActivation(_registration);\n\n store.setSwStatus('active');\n\n // Receive messages from SW\n navigator.serviceWorker.addEventListener('message', onSwMessage);\n\n // Track online/offline\n window.addEventListener('online', () => store.setOnline(true));\n window.addEventListener('offline', () => store.setOnline(false));\n\n flushPendingMessages();\n\n // Handle SW updates — the new SW waits for EIDOS_SKIP_WAITING from the page.\n _watchForUpdate(_registration, options);\n } catch (err) {\n store.setSwStatus('error', String(err));\n if (import.meta.env.DEV) {\n const errMsg = String(err).toLowerCase();\n const isNotFound =\n errMsg.includes('404') ||\n errMsg.includes('bad http response') ||\n errMsg.includes('not found') ||\n errMsg.includes('failed to load');\n if (isNotFound) {\n console.warn(\n `[eidos] Service worker file not found at \"${swPath}\". ` +\n `Did you add the eidos() plugin to your vite.config.ts? ` +\n `If you're not using Vite, copy the file manually: ` +\n `node_modules/@sweidos/eidos/dist/eidos-sw.js → public/eidos-sw.js`,\n );\n } else {\n console.warn(`[eidos] Service worker registration failed: ${err}`);\n }\n }\n }\n}\n\nfunction waitForActivation(reg: ServiceWorkerRegistration): Promise<void> {\n return new Promise((resolve) => {\n if (reg.active) {\n resolve();\n return;\n }\n const sw = reg.installing ?? reg.waiting;\n if (!sw) {\n resolve();\n return;\n }\n\n // Resolve after 10s regardless — another tab may be blocking activation\n const timer = setTimeout(resolve, 10_000);\n\n sw.addEventListener('statechange', function handler() {\n if (sw.state === 'activated') {\n clearTimeout(timer);\n sw.removeEventListener('statechange', handler);\n resolve();\n }\n });\n });\n}\n\nexport function sendToWorker(message: Record<string, unknown>): void {\n const sw = _registration?.active;\n if (sw) {\n sw.postMessage(message);\n } else {\n _pendingMessages.push(message);\n }\n}\n\nlet _bgSyncHandler: (() => void) | null = null;\n\nexport function registerBgSyncHandler(fn: () => void): void {\n _bgSyncHandler = fn;\n}\n\nexport function isBgSyncSupported(): boolean {\n try {\n return (\n typeof navigator !== 'undefined' &&\n 'serviceWorker' in navigator &&\n _registration !== null &&\n 'sync' in _registration\n );\n } catch {\n return false;\n }\n}\n\ninterface PushHandlers {\n onNotificationClick?: (data: unknown) => void;\n onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;\n}\n\nlet _pushHandlers: PushHandlers = {};\n\nexport function registerPushCallbacks(handlers: PushHandlers): void {\n _pushHandlers = handlers;\n}\n\nfunction onSwMessage(event: MessageEvent): void {\n const data = event.data as {\n type: string;\n url?: string;\n strategy?: string;\n data?: unknown;\n subscription?: unknown;\n };\n if (!data?.type) return;\n\n const store = useEidosStore.getState();\n const { type, url } = data;\n\n if (type === 'EIDOS_BACKGROUND_SYNC') {\n _bgSyncHandler?.();\n return;\n }\n\n if (type === 'EIDOS_NOTIFICATION_CLICK') {\n _pushHandlers.onNotificationClick?.(data.data);\n return;\n }\n\n if (type === 'EIDOS_SUBSCRIPTION_EXPIRED') {\n _pushHandlers.onSubscriptionExpired?.(data.subscription as PushSubscriptionJSON);\n return;\n }\n\n if (!url) return;\n\n switch (type) {\n case 'EIDOS_CACHE_HIT': {\n const current = store.resources[url];\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n });\n break;\n }\n case 'EIDOS_CACHE_UPDATED': {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-updated',\n cachedAt: Date.now(),\n });\n break;\n }\n case 'EIDOS_NETWORK_ERROR': {\n store.updateResource(url, {\n status: 'error',\n lastEvent: 'network-error',\n });\n break;\n }\n }\n}\n\nexport function setOfflineSimulation(enabled: boolean): void {\n sendToWorker({ type: 'EIDOS_SIMULATE_OFFLINE', enabled });\n useEidosStore.getState().setOnline(!enabled);\n}\n\nfunction flushPendingMessages(): void {\n const sw = _registration?.active;\n if (!sw) return;\n for (const msg of _pendingMessages) sw.postMessage(msg);\n _pendingMessages = [];\n}\n\nfunction _watchForUpdate(reg: ServiceWorkerRegistration, options: SwRegistrationOptions): void {\n const notify = (r: ServiceWorkerRegistration) => {\n if (options.skipWaiting) {\n r.waiting?.postMessage({ type: 'EIDOS_SKIP_WAITING' });\n } else {\n options.onUpdateAvailable?.(r);\n }\n };\n\n // A SW may already be waiting on startup (installed across a previous page load\n // but blocked because another tab held the old SW active).\n if (reg.waiting && navigator.serviceWorker.controller) {\n notify(reg);\n }\n\n reg.addEventListener('updatefound', () => {\n const newSw = reg.installing;\n if (!newSw) return;\n newSw.addEventListener('statechange', () => {\n if (newSw.state === 'installed' && navigator.serviceWorker.controller) {\n notify(reg);\n }\n });\n });\n}\n\n/**\n * Tells the waiting service worker to activate immediately, then reloads the page.\n * Only relevant when `skipWaiting: false` — call this after the user confirms\n * a \"reload to update\" toast shown via `onUpdateAvailable`.\n */\nexport function triggerSwUpdate(): void {\n _registration?.waiting?.postMessage({ type: 'EIDOS_SKIP_WAITING' });\n}\n\n/** Test-only: resets module-level state between test cases. */\nexport function _resetSwBridgeForTests(): void {\n _registration = null;\n _pendingMessages = [];\n _bgSyncHandler = null;\n _pushHandlers = {};\n}\n"],"mappings":"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"}