@sweidos/eidos 1.0.34 → 1.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.
Files changed (53) hide show
  1. package/README.md +171 -89
  2. package/dist/action.js +197 -91
  3. package/dist/async-storage-adapter.js +15 -12
  4. package/dist/cli.js +102 -0
  5. package/dist/devtools.js +1009 -551
  6. package/dist/eidos-sw.js +280 -188
  7. package/dist/eidos.cjs +15 -0
  8. package/dist/idb.js +59 -56
  9. package/dist/index.d.ts +135 -18
  10. package/dist/index.js +46 -42
  11. package/dist/nextjs.js +1 -10
  12. package/dist/push.cjs +120 -0
  13. package/dist/push.d.ts +28 -0
  14. package/dist/push.js +113 -0
  15. package/dist/query.cjs +131 -0
  16. package/dist/query.js +121 -41
  17. package/dist/queue-storage.js +5 -4
  18. package/dist/react/Devtools.d.ts +1 -1
  19. package/dist/react/Provider.js +11 -7
  20. package/dist/react/hooks.js +48 -38
  21. package/dist/react-native.js +47 -53
  22. package/dist/replay.js +15 -0
  23. package/dist/resource.js +77 -79
  24. package/dist/runtime.js +39 -28
  25. package/dist/store-slices.js +43 -0
  26. package/dist/store.js +32 -49
  27. package/dist/stores.js +25 -22
  28. package/dist/sveltekit.js +22 -6
  29. package/dist/sw-bridge.js +64 -49
  30. package/dist/testing.cjs +165 -0
  31. package/dist/testing.js +140 -70
  32. package/dist/version.js +4 -3
  33. package/dist/vite.cjs +48 -0
  34. package/dist/vite.js +45 -29
  35. package/package.json +57 -28
  36. package/dist/action.js.map +0 -1
  37. package/dist/async-storage-adapter.js.map +0 -1
  38. package/dist/eidos.cjs.js +0 -14
  39. package/dist/eidos.cjs.js.map +0 -1
  40. package/dist/idb.js.map +0 -1
  41. package/dist/index.js.map +0 -1
  42. package/dist/query.cjs.js +0 -48
  43. package/dist/queue-storage.js.map +0 -1
  44. package/dist/react/Provider.js.map +0 -1
  45. package/dist/react/hooks.js.map +0 -1
  46. package/dist/resource.js.map +0 -1
  47. package/dist/runtime.js.map +0 -1
  48. package/dist/store.js.map +0 -1
  49. package/dist/stores.js.map +0 -1
  50. package/dist/sw-bridge.js.map +0 -1
  51. package/dist/testing.cjs.js +0 -86
  52. package/dist/version.js.map +0 -1
  53. package/dist/vite.cjs.js +0 -31
package/dist/idb.js CHANGED
@@ -1,80 +1,83 @@
1
- const p = "eidos";
2
- const o = "action-queue";
3
- let b = null;
4
- function a() {
1
+ var f = "eidos", p = 1, o = "action-queue", b = null;
2
+ function c() {
5
3
  return b ? Promise.resolve(b) : new Promise((s, n) => {
6
- const t = indexedDB.open(p, 1);
7
- t.onupgradeneeded = (r) => {
8
- const e = r.target.result;
9
- if (!e.objectStoreNames.contains(o)) {
10
- const c = e.createObjectStore(o, { keyPath: "id" });
11
- c.createIndex("status", "status", { unique: !1 }), c.createIndex("actionId", "actionId", { unique: !1 });
4
+ const r = indexedDB.open(f, p);
5
+ r.onupgradeneeded = (e) => {
6
+ const t = e.target.result;
7
+ if (!t.objectStoreNames.contains(o)) {
8
+ const a = t.createObjectStore(o, { keyPath: "id" });
9
+ a.createIndex("status", "status", { unique: !1 }), a.createIndex("actionId", "actionId", { unique: !1 });
12
10
  }
13
- }, t.onsuccess = () => {
14
- b = t.result, s(t.result);
15
- }, t.onerror = () => n(t.error);
11
+ }, r.onsuccess = () => {
12
+ b = r.result, s(r.result);
13
+ }, r.onerror = () => n(r.error);
16
14
  });
17
15
  }
18
- async function x(s) {
19
- const n = await a();
20
- return new Promise((t, r) => {
21
- const e = n.transaction(o, "readwrite");
22
- e.objectStore(o).add(s), e.oncomplete = () => t(), e.onerror = () => r(e.error);
16
+ async function g(s) {
17
+ const n = await c();
18
+ return new Promise((r, e) => {
19
+ const t = n.transaction(o, "readwrite");
20
+ t.objectStore(o).add(s), t.oncomplete = () => r(), t.onerror = () => e(t.error);
23
21
  });
24
22
  }
25
23
  async function y() {
26
- const s = await a();
27
- return new Promise((n, t) => {
24
+ const s = await c();
25
+ return new Promise((n, r) => {
28
26
  const e = s.transaction(o, "readonly").objectStore(o).getAll();
29
- e.onsuccess = () => n(e.result), e.onerror = () => t(e.error);
27
+ e.onsuccess = () => n(e.result), e.onerror = () => r(e.error);
30
28
  });
31
29
  }
32
30
  async function P(s, n) {
33
- const t = await a();
34
- return new Promise((r, e) => {
35
- const c = t.transaction(o, "readwrite"), i = c.objectStore(o), u = i.get(s);
36
- u.onsuccess = () => {
37
- u.result && i.put({ ...u.result, ...n });
38
- }, c.oncomplete = () => r(), c.onerror = () => e(c.error);
31
+ const r = await c();
32
+ return new Promise((e, t) => {
33
+ const a = r.transaction(o, "readwrite"), u = a.objectStore(o), i = u.get(s);
34
+ i.onsuccess = () => {
35
+ i.result && u.put({
36
+ ...i.result,
37
+ ...n
38
+ });
39
+ }, a.oncomplete = () => e(), a.onerror = () => t(a.error);
39
40
  });
40
41
  }
41
42
  async function S(s) {
42
- const n = await a();
43
- return new Promise((t, r) => {
44
- const e = n.transaction(o, "readwrite");
45
- e.objectStore(o).delete(s), e.oncomplete = () => t(), e.onerror = () => r(e.error);
43
+ const n = await c();
44
+ return new Promise((r, e) => {
45
+ const t = n.transaction(o, "readwrite");
46
+ t.objectStore(o).delete(s), t.oncomplete = () => r(), t.onerror = () => e(t.error);
46
47
  });
47
48
  }
48
- async function g() {
49
- const s = await a();
50
- function n(e) {
51
- return new Promise((c, i) => {
52
- const w = s.transaction(o, "readonly").objectStore(o).index("status"), m = [], d = w.openCursor(IDBKeyRange.only(e));
53
- d.onsuccess = (f) => {
54
- const l = f.target.result;
55
- l ? (m.push(l.value), l.continue()) : c(m);
56
- }, d.onerror = () => i(d.error);
49
+ async function x() {
50
+ const s = await c();
51
+ function n(t) {
52
+ return new Promise((a, u) => {
53
+ const i = s.transaction(o, "readonly").objectStore(o).index("status"), m = [], d = i.openCursor(IDBKeyRange.only(t));
54
+ d.onsuccess = (w) => {
55
+ const l = w.target.result;
56
+ l ? (m.push(l.value), l.continue()) : a(m);
57
+ }, d.onerror = () => u(d.error);
57
58
  });
58
59
  }
59
- const [t, r] = await Promise.all([
60
- n("pending"),
61
- n("failed")
62
- ]);
63
- return [...t, ...r];
60
+ const [r, e] = await Promise.all([n("pending"), n("failed")]);
61
+ return [...r, ...e];
64
62
  }
65
- async function I() {
66
- const s = await a();
67
- return new Promise((n, t) => {
68
- const r = s.transaction(o, "readwrite");
69
- r.objectStore(o).clear(), r.oncomplete = () => n(), r.onerror = () => t(r.error);
63
+ async function v() {
64
+ const s = await c();
65
+ return new Promise((n, r) => {
66
+ const e = s.transaction(o, "readwrite");
67
+ e.objectStore(o).clear(), e.oncomplete = () => n(), e.onerror = () => r(e.error);
70
68
  });
71
69
  }
70
+ var j = {
71
+ add: g,
72
+ getAll: y,
73
+ getPending: x,
74
+ update: P,
75
+ remove: S,
76
+ clear: v
77
+ };
72
78
  export {
73
- x as idbAddToQueue,
74
- I as idbClearQueue,
75
- g as idbGetPendingItems,
76
79
  y as idbGetQueue,
77
- S as idbRemoveFromQueue,
78
- P as idbUpdateQueueItem
80
+ j as idbQueueStorage
79
81
  };
80
- //# sourceMappingURL=idb.js.map
82
+
83
+ //# sourceMappingURL=idb.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { JSX as JSX_2 } from 'react';
1
+ import { JSX } from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  export declare function action<TArgs extends any[], TReturn>(fn: ActionFn<TArgs, TReturn>, config: ActionConfig): ActionHandle<TArgs, TReturn>;
@@ -13,6 +13,13 @@ export declare interface ActionConfig {
13
13
  maxRetries?: number;
14
14
  /** Human-readable name for the action (used in devtools). */
15
15
  name?: string;
16
+ /**
17
+ * Prefixes the registered action id (`namespace::name`). Use to avoid
18
+ * collisions when two actions share a name (e.g. across micro-frontends,
19
+ * or two `createOrder` actions in different modules) — without a
20
+ * namespace, the second registration silently overwrites the first.
21
+ */
22
+ namespace?: string;
16
23
  /**
17
24
  * Replay order when multiple queued actions are pending.
18
25
  * `'high'` items replay before `'normal'`, which replay before `'low'`.
@@ -21,9 +28,11 @@ export declare interface ActionConfig {
21
28
  */
22
29
  priority?: 'high' | 'normal' | 'low';
23
30
  /**
24
- * Called immediately before the async function executes, with the same args.
25
- * Use to apply an optimistic UI update (add item, mark as pending, etc.).
26
- * Called on every invocation — online, offline, and during queue replay.
31
+ * Called immediately before the async function executes, with the same args
32
+ * plus a trailing `ActionContext`. Use to apply an optimistic UI update (add
33
+ * item, mark as pending, etc.) and to capture `idempotencyKey` for later
34
+ * `handle.cancel(idempotencyKey)` calls. Called on every invocation —
35
+ * online, offline, and during queue replay.
27
36
  */
28
37
  onOptimistic?: (...args: any[]) => void;
29
38
  /**
@@ -46,7 +55,25 @@ export declare interface ActionConfig {
46
55
  * The `error` argument is whatever `fn` threw — typically a `Response` object
47
56
  * or a custom error with a `.status` property.
48
57
  */
58
+ /**
59
+ * @deprecated Use `conflict` instead. If both are set, `conflict` wins.
60
+ * Return `'retry'` to keep the item in the queue and retry per normal
61
+ * backoff, or `'skip'` to silently remove the item.
62
+ */
49
63
  onConflict?: (error: unknown, args: any[]) => 'retry' | 'skip';
64
+ /**
65
+ * Declarative conflict-resolution strategy used during queue replay when
66
+ * the server responds with a 4xx status (conflict, gone, unprocessable,
67
+ * etc.). Replaces `onConflict` for new code — see `ConflictConfig`.
68
+ */
69
+ conflict?: ConflictConfig;
70
+ /**
71
+ * When `true`, each invocation gets an `AbortController` whose `signal` is
72
+ * passed via `ActionContext.signal`. Forward it to `fetch`/etc. so
73
+ * `handle.cancel(idempotencyKey)` can abort an in-flight call, or remove a
74
+ * not-yet-replayed queued item.
75
+ */
76
+ cancellable?: boolean;
50
77
  }
51
78
 
52
79
  declare type ActionFn<TArgs extends any[], TReturn> = (...args: TArgs) => Promise<TReturn>;
@@ -55,13 +82,28 @@ export declare interface ActionHandle<TArgs extends any[], TReturn> {
55
82
  (...args: TArgs): Promise<TReturn | QueuedResult>;
56
83
  readonly id: string;
57
84
  readonly config: ActionConfig;
85
+ /**
86
+ * Cancel an invocation by its `idempotencyKey` (from `ActionContext` /
87
+ * `onOptimistic`). Aborts the in-flight call if `cancellable: true` and
88
+ * still running, otherwise removes a not-yet-replayed queued item.
89
+ * Returns `true` if something was cancelled/removed.
90
+ */
91
+ cancel: (idempotencyKey: string) => Promise<boolean>;
58
92
  }
59
93
 
60
94
  export declare interface ActionQueueItem {
95
+ /** Shape version this item was persisted with. Items from before v2 are migrated on load. */
96
+ schemaVersion: number;
61
97
  id: string;
62
98
  /** ID of the registered action (maps to the function in the registry). */
63
99
  actionId: string;
64
100
  actionName: string;
101
+ /**
102
+ * Stable per-invocation key, generated once and reused across every retry/replay
103
+ * of this item. Pass to your server as an idempotency key so retries that reach
104
+ * the server after a dropped response don't double-execute.
105
+ */
106
+ idempotencyKey: string;
65
107
  args: unknown[];
66
108
  queuedAt: number;
67
109
  retryCount: number;
@@ -105,6 +147,46 @@ export declare type CacheStrategy = 'cache-first' | 'stale-while-revalidate' | '
105
147
  /** Remove all items from the action queue (storage + in-memory store). */
106
148
  export declare function clearQueue(): Promise<void>;
107
149
 
150
+ declare interface ConflictConfig {
151
+ /**
152
+ * - `'serverWins'`: drop the queued item, keeping the server's state.
153
+ * - `'clientWins'`: keep retrying — the client's write should eventually
154
+ * be accepted (e.g. once the server-side conflict is cleared).
155
+ * - `'lastWriteWins'`: same as `'clientWins'` for now — requires a
156
+ * server-supplied timestamp contract to compare against `queuedAt`
157
+ * (see Phase 3 of the roadmap). Treated as `'clientWins'` until then.
158
+ * - `'merge'` / `'custom'`: call `resolve` to decide.
159
+ */
160
+ strategy: 'serverWins' | 'clientWins' | 'lastWriteWins' | 'merge' | 'custom';
161
+ /** Required for `'merge'` and `'custom'`. */
162
+ resolve?: (ctx: ConflictContext) => ConflictResolution;
163
+ }
164
+
165
+ /**
166
+ * Passed to `ConflictConfig.resolve` (for `'merge'`/`'custom'` strategies)
167
+ * when a queued action's replay receives a 4xx response.
168
+ */
169
+ declare interface ConflictContext {
170
+ /** Whatever `fn` threw — typically a `Response` or an error with `.status`. */
171
+ error: unknown;
172
+ /** The original arguments the action was queued with. */
173
+ args: any[];
174
+ /** Number of replay attempts so far (0 on first replay). */
175
+ attempt: number;
176
+ idempotencyKey: string;
177
+ }
178
+
179
+ /**
180
+ * Outcome of `ConflictConfig.resolve`:
181
+ * - `'retry'`: keep the item queued, retry per normal backoff.
182
+ * - `'skip'`: silently remove the item (no `onRollback`).
183
+ * - `{ resolved: args }`: replace the queued args and retry immediately
184
+ * on the next replay pass.
185
+ */
186
+ declare type ConflictResolution = 'retry' | 'skip' | {
187
+ resolved: any[];
188
+ };
189
+
108
190
  /**
109
191
  * Live state for a single queue item by ID. Returns `undefined` once the item
110
192
  * is removed from the queue (after a successful replay or `clearQueue()`).
@@ -131,7 +213,7 @@ export declare interface EidosConfig {
131
213
  * <App />
132
214
  * </EidosProvider>
133
215
  */
134
- export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps): JSX_2.Element;
216
+ export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps): JSX.Element;
135
217
 
136
218
  declare interface EidosProviderProps extends EidosConfig {
137
219
  children: ReactNode;
@@ -185,20 +267,9 @@ export declare const eidosStatus: EidosReadable<{
185
267
  swError: string | undefined;
186
268
  }>;
187
269
 
188
- export declare interface EidosStore extends EidosState {
270
+ export declare interface EidosStore extends EidosState, ResourceActions, QueueActions {
189
271
  setOnline: (online: boolean) => void;
190
272
  setSwStatus: (status: EidosState['swStatus'], error?: string) => void;
191
- registerResource: (url: string, entry: ResourceEntry) => void;
192
- updateResource: (url: string, update: Partial<ResourceEntry>) => void;
193
- unregisterResource: (url: string) => void;
194
- addQueueItem: (item: ActionQueueItem) => void;
195
- updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;
196
- batchUpdateQueueItems: (updates: Array<{
197
- id: string;
198
- update: Partial<ActionQueueItem>;
199
- }>) => void;
200
- removeQueueItem: (id: string) => void;
201
- hydrateQueue: (items: ActionQueueItem[]) => void;
202
273
  }
203
274
 
204
275
  /** Full Eidos state snapshot. Prefer the narrower stores below. */
@@ -220,14 +291,32 @@ export declare function _getQueueStorage(): QueueStorage | null;
220
291
 
221
292
  declare function _getState(): EidosStore;
222
293
 
294
+ export declare function getSwRegistration(): ServiceWorkerRegistration | null;
295
+
223
296
  export declare function initEidos(config?: EidosConfig): Promise<void>;
224
297
 
225
298
  export declare function isBgSyncSupported(): boolean;
226
299
 
227
300
  declare type Listener = () => void;
228
301
 
302
+ declare interface PushHandlers {
303
+ onNotificationClick?: (data: unknown) => void;
304
+ onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;
305
+ }
306
+
229
307
  declare type QueryInvalidator = (queryKey: [string, string]) => void;
230
308
 
309
+ declare interface QueueActions {
310
+ addQueueItem: (item: ActionQueueItem) => void;
311
+ updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;
312
+ batchUpdateQueueItems: (updates: Array<{
313
+ id: string;
314
+ update: Partial<ActionQueueItem>;
315
+ }>) => void;
316
+ removeQueueItem: (id: string) => void;
317
+ hydrateQueue: (items: ActionQueueItem[]) => void;
318
+ }
319
+
231
320
  export declare interface QueuedResult {
232
321
  readonly queued: true;
233
322
  readonly id: string;
@@ -243,6 +332,8 @@ export declare interface QueueStorage {
243
332
  clear(): Promise<void>;
244
333
  }
245
334
 
335
+ export declare function registerPushCallbacks(handlers: PushHandlers): void;
336
+
246
337
  export declare function replayQueue(): Promise<ReplayResult>;
247
338
 
248
339
  /** Summary returned by replayQueue(). */
@@ -259,12 +350,20 @@ export declare interface ReplayResult {
259
350
  skipped: number;
260
351
  /** Items that received a 4xx response and were dropped via `onConflict: () => 'skip'`. */
261
352
  conflicted: number;
353
+ /** Items removed via `handle.cancel(idempotencyKey)` before/during replay. */
354
+ cancelled: number;
262
355
  }
263
356
 
264
357
  export declare function _resetEidos(): void;
265
358
 
266
359
  export declare function resource<T = unknown>(url: string, config: ResourceConfig): ResourceHandle<T>;
267
360
 
361
+ declare interface ResourceActions {
362
+ registerResource: (url: string, entry: ResourceEntry) => void;
363
+ updateResource: (url: string, update: Partial<ResourceEntry>) => void;
364
+ unregisterResource: (url: string) => void;
365
+ }
366
+
268
367
  export declare interface ResourceConfig {
269
368
  /** Make this resource available when the device is offline. */
270
369
  offline: boolean;
@@ -305,6 +404,8 @@ export declare interface ResourceHandle<T = unknown> {
305
404
  unregister: () => void;
306
405
  }
307
406
 
407
+ export declare function sendToWorker(message: Record<string, unknown>): void;
408
+
308
409
  export declare function setOfflineSimulation(enabled: boolean): void;
309
410
 
310
411
  /* Excluded from this release type: setQueryInvalidator */
@@ -314,6 +415,22 @@ export declare function setQueueStorage(s: QueueStorage): void;
314
415
 
315
416
  declare function _subscribe(listener: Listener): () => void;
316
417
 
418
+ /**
419
+ * Subscribe to online/offline transitions and trigger replayQueue() on
420
+ * reconnect, plus replay any pending items left over from a previous session.
421
+ *
422
+ * Shared by the web (runtime.ts) and React Native (runtime-rn.ts) init paths.
423
+ *
424
+ * WHY subscribe to the store instead of window.addEventListener('online'):
425
+ * setOfflineSimulation() updates the store directly but never fires a real
426
+ * browser `online` event. Watching the store catches both:
427
+ * • Real network reconnects (sw-bridge updates store on window.online)
428
+ * • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))
429
+ *
430
+ * Returns an unsubscribe function.
431
+ */
432
+ export declare function subscribeReplayOnReconnect(): () => void;
433
+
317
434
  /** Full Eidos store — prefer the narrower hooks below for performance. */
318
435
  export declare function useEidos(): EidosStore;
319
436
 
@@ -372,7 +489,7 @@ export declare const useEidosStore: {
372
489
  setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
373
490
  };
374
491
 
375
- export declare const VERSION = "1.0.34";
492
+ export declare const VERSION = "1.2.0";
376
493
 
377
494
  /**
378
495
  * Bulk-prefetch an array of resource handles concurrently, warming the cache
package/dist/index.js CHANGED
@@ -1,44 +1,48 @@
1
- import { resource as r, setQueryInvalidator as s, warmCache as t } from "./resource.js";
2
- import { action as i, clearQueue as d, replayQueue as a } from "./action.js";
3
- import { _resetEidos as S, initEidos as f } from "./runtime.js";
4
- import { _getQueueStorage as E, setQueueStorage as c } from "./queue-storage.js";
5
- import { AsyncStorageQueueStorage as x } from "./async-storage-adapter.js";
6
- import { EidosProvider as g } from "./react/Provider.js";
7
- import { useEidos as y, useEidosAction as R, useEidosOnDrain as A, useEidosQueue as O, useEidosQueueStats as v, useEidosResource as I, useEidosResources as _, useEidosStatus as h } from "./react/hooks.js";
8
- import { VERSION as B } from "./version.js";
9
- import { isBgSyncSupported as D, setOfflineSimulation as N } from "./sw-bridge.js";
10
- import { useEidosStore as V } from "./store.js";
11
- import { eidosAction as j, eidosQueue as k, eidosQueueStats as q, eidosResource as z, eidosStatus as F, eidosStore as G } from "./stores.js";
1
+ import { useEidosStore as o } from "./store.js";
2
+ import { getSwRegistration as s, isBgSyncSupported as i, registerPushCallbacks as t, sendToWorker as u, setOfflineSimulation as m } from "./sw-bridge.js";
3
+ import { resource as a, setQueryInvalidator as p, warmCache as n } from "./resource.js";
4
+ import { _getQueueStorage as S, setQueueStorage as f } from "./queue-storage.js";
5
+ import { action as Q, clearQueue as g, replayQueue as l } from "./action.js";
6
+ import { subscribeReplayOnReconnect as y } from "./replay.js";
7
+ import { _resetEidos as b, initEidos as A } from "./runtime.js";
8
+ import { AsyncStorageQueueStorage as k } from "./async-storage-adapter.js";
9
+ import { EidosProvider as w } from "./react/Provider.js";
10
+ import { useEidos as I, useEidosAction as P, useEidosOnDrain as _, useEidosQueue as x, useEidosQueueStats as B, useEidosResource as D, useEidosResources as N, useEidosStatus as T } from "./react/hooks.js";
11
+ import { VERSION as W } from "./version.js";
12
+ import { eidosAction as q, eidosQueue as z, eidosQueueStats as F, eidosResource as G, eidosStatus as H, eidosStore as J } from "./stores.js";
12
13
  export {
13
- x as AsyncStorageQueueStorage,
14
- g as EidosProvider,
15
- B as VERSION,
16
- E as _getQueueStorage,
17
- S as _resetEidos,
18
- i as action,
19
- d as clearQueue,
20
- j as eidosAction,
21
- k as eidosQueue,
22
- q as eidosQueueStats,
23
- z as eidosResource,
24
- F as eidosStatus,
25
- G as eidosStore,
26
- f as initEidos,
27
- D as isBgSyncSupported,
28
- a as replayQueue,
29
- r as resource,
30
- N as setOfflineSimulation,
31
- s as setQueryInvalidator,
32
- c as setQueueStorage,
33
- y as useEidos,
34
- R as useEidosAction,
35
- A as useEidosOnDrain,
36
- O as useEidosQueue,
37
- v as useEidosQueueStats,
38
- I as useEidosResource,
39
- _ as useEidosResources,
40
- h as useEidosStatus,
41
- V as useEidosStore,
42
- t as warmCache
14
+ k as AsyncStorageQueueStorage,
15
+ w as EidosProvider,
16
+ W as VERSION,
17
+ S as _getQueueStorage,
18
+ b as _resetEidos,
19
+ Q as action,
20
+ g as clearQueue,
21
+ q as eidosAction,
22
+ z as eidosQueue,
23
+ F as eidosQueueStats,
24
+ G as eidosResource,
25
+ H as eidosStatus,
26
+ J as eidosStore,
27
+ s as getSwRegistration,
28
+ A as initEidos,
29
+ i as isBgSyncSupported,
30
+ t as registerPushCallbacks,
31
+ l as replayQueue,
32
+ a as resource,
33
+ u as sendToWorker,
34
+ m as setOfflineSimulation,
35
+ p as setQueryInvalidator,
36
+ f as setQueueStorage,
37
+ y as subscribeReplayOnReconnect,
38
+ I as useEidos,
39
+ P as useEidosAction,
40
+ _ as useEidosOnDrain,
41
+ x as useEidosQueue,
42
+ B as useEidosQueueStats,
43
+ D as useEidosResource,
44
+ N as useEidosResources,
45
+ T as useEidosStatus,
46
+ o as useEidosStore,
47
+ n as warmCache
43
48
  };
44
- //# sourceMappingURL=index.js.map
package/dist/nextjs.js CHANGED
@@ -1,12 +1,3 @@
1
1
  "use client";
2
2
  import { EidosProvider, useEidos, useEidosAction, useEidosOnDrain, useEidosQueue, useEidosQueueStats, useEidosResource, useEidosStatus } from "@sweidos/eidos";
3
- export {
4
- EidosProvider,
5
- useEidos,
6
- useEidosAction,
7
- useEidosOnDrain,
8
- useEidosQueue,
9
- useEidosQueueStats,
10
- useEidosResource,
11
- useEidosStatus
12
- };
3
+ export { EidosProvider, useEidos, useEidosAction, useEidosOnDrain, useEidosQueue, useEidosQueueStats, useEidosResource, useEidosStatus };
package/dist/push.cjs ADDED
@@ -0,0 +1,120 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _sweidos_eidos = require("@sweidos/eidos");
3
+ //#region src/push.ts
4
+ /**
5
+ * @sweidos/eidos/push
6
+ *
7
+ * Web Push integration. Framework-agnostic: register click/expiry handlers
8
+ * once at app init (any tab), and trigger subscription from a user gesture.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { registerPushHandlers, subscribeToPush } from '@sweidos/eidos/push'
13
+ *
14
+ * // App init — every tab, no permission prompt
15
+ * registerPushHandlers({
16
+ * onNotificationClick: (data) => router.push(data.url),
17
+ * onSubscriptionExpired: (sub) => fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
18
+ * })
19
+ *
20
+ * // User gesture (button click)
21
+ * const result = await subscribeToPush({
22
+ * vapidPublicKey: import.meta.env.VITE_EIDOS_VAPID_PUBLIC_KEY,
23
+ * onSubscribe: (sub) => fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
24
+ * })
25
+ * ```
26
+ */
27
+ /** Why push is unavailable on this device, or null if it's supported. */
28
+ function getPushUnsupportedReason() {
29
+ if (typeof window === "undefined") return "no-push-api";
30
+ if (/iPhone|iPad|iPod/.test(navigator.userAgent) && !navigator.standalone) return "ios-not-installed";
31
+ if (!("serviceWorker" in navigator) || !("PushManager" in window) || !("Notification" in window)) return "no-push-api";
32
+ return null;
33
+ }
34
+ function isPushSupported() {
35
+ return getPushUnsupportedReason() === null;
36
+ }
37
+ function getPushPermissionState() {
38
+ if (!isPushSupported()) return "unsupported";
39
+ return Notification.permission;
40
+ }
41
+ function registerPushHandlers(handlers) {
42
+ (0, _sweidos_eidos.registerPushCallbacks)(handlers);
43
+ }
44
+ async function subscribeToPush(config) {
45
+ if (!isPushSupported()) return { status: "unsupported" };
46
+ let permission;
47
+ try {
48
+ permission = await Notification.requestPermission();
49
+ } catch (error) {
50
+ return {
51
+ status: "error",
52
+ error
53
+ };
54
+ }
55
+ if (permission !== "granted") return { status: "denied" };
56
+ const registration = (0, _sweidos_eidos.getSwRegistration)();
57
+ if (!registration) return { status: "sw-not-ready" };
58
+ try {
59
+ const applicationServerKey = urlBase64ToUint8Array(config.vapidPublicKey);
60
+ let subscription = await registration.pushManager.getSubscription();
61
+ if (subscription && !subscriptionKeyMatches(subscription, config.vapidPublicKey)) {
62
+ await subscription.unsubscribe();
63
+ subscription = null;
64
+ }
65
+ if (!subscription) subscription = await registration.pushManager.subscribe({
66
+ userVisibleOnly: true,
67
+ applicationServerKey
68
+ });
69
+ (0, _sweidos_eidos.sendToWorker)({
70
+ type: "EIDOS_CACHE_VAPID_KEY",
71
+ key: config.vapidPublicKey
72
+ });
73
+ const json = subscription.toJSON();
74
+ config.onSubscribe?.(json);
75
+ return {
76
+ status: "subscribed",
77
+ subscription: json
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ status: "error",
82
+ error
83
+ };
84
+ }
85
+ }
86
+ async function unsubscribeFromPush() {
87
+ const registration = (0, _sweidos_eidos.getSwRegistration)();
88
+ if (!registration) return;
89
+ await (await registration.pushManager.getSubscription())?.unsubscribe();
90
+ }
91
+ async function getCurrentPushSubscription() {
92
+ const registration = (0, _sweidos_eidos.getSwRegistration)();
93
+ if (!registration) return null;
94
+ const subscription = await registration.pushManager.getSubscription();
95
+ return subscription ? subscription.toJSON() : null;
96
+ }
97
+ function urlBase64ToUint8Array(base64Url) {
98
+ const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
99
+ const raw = atob(base64);
100
+ return Uint8Array.from(raw, (c) => c.charCodeAt(0));
101
+ }
102
+ function uint8ArrayToUrlBase64(bytes) {
103
+ let binary = "";
104
+ for (const byte of bytes) binary += String.fromCharCode(byte);
105
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
106
+ }
107
+ /** Compares an existing subscription's key against the configured VAPID public key. */
108
+ function subscriptionKeyMatches(subscription, vapidPublicKey) {
109
+ const key = subscription.options.applicationServerKey;
110
+ if (!key) return false;
111
+ return uint8ArrayToUrlBase64(new Uint8Array(key)) === uint8ArrayToUrlBase64(urlBase64ToUint8Array(vapidPublicKey));
112
+ }
113
+ //#endregion
114
+ exports.getCurrentPushSubscription = getCurrentPushSubscription;
115
+ exports.getPushPermissionState = getPushPermissionState;
116
+ exports.getPushUnsupportedReason = getPushUnsupportedReason;
117
+ exports.isPushSupported = isPushSupported;
118
+ exports.registerPushHandlers = registerPushHandlers;
119
+ exports.subscribeToPush = subscribeToPush;
120
+ exports.unsubscribeFromPush = unsubscribeFromPush;
package/dist/push.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ export interface PushHandlers {
2
+ /** Fired when the user clicks a notification while a tab is open. */
3
+ onNotificationClick?: (data: unknown) => void;
4
+ /** Fired when the browser silently rotates the push subscription. Re-send to your backend. */
5
+ onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;
6
+ }
7
+ export interface PushConfig {
8
+ /** Base64url-encoded VAPID public key. Generate with `npx @sweidos/eidos generate-vapid-keys`. */
9
+ vapidPublicKey: string;
10
+ /** Called with the new subscription right after a successful subscribe. Send this to your backend. */
11
+ onSubscribe?: (sub: PushSubscriptionJSON) => void;
12
+ }
13
+ export type PushResult = {
14
+ status: 'subscribed';
15
+ subscription: PushSubscriptionJSON;
16
+ } | {
17
+ status: 'denied' | 'unsupported' | 'sw-not-ready' | 'error';
18
+ error?: unknown;
19
+ };
20
+ export type PushUnsupportedReason = 'no-push-api' | 'ios-not-installed' | null;
21
+ /** Why push is unavailable on this device, or null if it's supported. */
22
+ export declare function getPushUnsupportedReason(): PushUnsupportedReason;
23
+ export declare function isPushSupported(): boolean;
24
+ export declare function getPushPermissionState(): NotificationPermission | 'unsupported';
25
+ export declare function registerPushHandlers(handlers: PushHandlers): void;
26
+ export declare function subscribeToPush(config: PushConfig): Promise<PushResult>;
27
+ export declare function unsubscribeFromPush(): Promise<void>;
28
+ export declare function getCurrentPushSubscription(): Promise<PushSubscriptionJSON | null>;