@sweidos/eidos 1.0.25 → 1.0.31

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/resource.js CHANGED
@@ -5,24 +5,24 @@ let m = null;
5
5
  function N(e) {
6
6
  m = e;
7
7
  }
8
- function u(e) {
8
+ function l(e) {
9
9
  return e.includes("*") || /:[^/]+/.test(e);
10
10
  }
11
11
  function k(e) {
12
12
  return "^" + e.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]+").replace(/:[^/]+/g, "[^/]+") + "$";
13
13
  }
14
- function w(e, t) {
14
+ function w(e, a) {
15
15
  return new Error(
16
- `[eidos] resource('${e}') is a URL pattern — ${t}() is not supported on pattern handles. The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`
16
+ `[eidos] resource('${e}') is a URL pattern — ${a}() is not supported on pattern handles. The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`
17
17
  );
18
18
  }
19
- function q(e, t) {
19
+ function q(e, a) {
20
20
  if (p.has(e))
21
21
  return p.get(e);
22
- const a = S(e, t), n = u(e) ? k(e) : void 0, o = {
22
+ const s = E(e, a), t = l(e) ? k(e) : void 0, o = {
23
23
  url: e,
24
- config: t,
25
- strategy: a,
24
+ config: a,
25
+ strategy: s,
26
26
  status: "idle",
27
27
  cacheHits: 0,
28
28
  cacheMisses: 0
@@ -30,49 +30,50 @@ function q(e, t) {
30
30
  h.getState().registerResource(e, o), g({
31
31
  type: "EIDOS_REGISTER_RESOURCE",
32
32
  url: e,
33
- strategy: a.swStrategy,
34
- cacheName: a.cacheName,
35
- ...n !== void 0 && { pattern: n }
33
+ strategy: s.swStrategy,
34
+ cacheName: s.cacheName,
35
+ ...t !== void 0 && { pattern: t }
36
36
  });
37
- const s = {
37
+ const c = {
38
38
  url: e,
39
- config: t,
40
- strategy: a,
39
+ config: a,
40
+ strategy: s,
41
41
  fetch: async () => {
42
- if (u(e)) throw w(e, "fetch");
42
+ if (l(e)) throw w(e, "fetch");
43
43
  const r = y.get(e);
44
- if (r) return r.then((c) => c.clone());
45
- const i = E(e, t, a);
46
- return y.set(e, i), i.finally(() => y.delete(e)), i.then((c) => c.clone());
44
+ if (r) return r.then((n) => n.clone());
45
+ const i = S(e, a, s);
46
+ return y.set(e, i), i.finally(() => y.delete(e)).catch(() => {
47
+ }), i.then((n) => n.clone());
47
48
  },
48
49
  json: async () => {
49
- if (u(e)) throw w(e, "json");
50
- return (await s.fetch()).json();
50
+ if (l(e)) throw w(e, "json");
51
+ return (await c.fetch()).json();
51
52
  },
52
53
  query: () => {
53
- if (u(e)) throw w(e, "query");
54
+ if (l(e)) throw w(e, "query");
54
55
  return {
55
56
  queryKey: ["eidos", e],
56
- queryFn: () => s.json()
57
+ queryFn: () => c.json()
57
58
  };
58
59
  },
59
60
  prefetch: async () => {
60
- if (u(e)) throw w(e, "prefetch");
61
- await s.fetch();
61
+ if (l(e)) throw w(e, "prefetch");
62
+ await c.fetch();
62
63
  },
63
64
  invalidate: async () => {
64
65
  g({ type: "EIDOS_CLEAR_CACHE", url: e });
65
- const r = await caches.open(a.cacheName).catch(() => null);
66
+ const r = await caches.open(s.cacheName).catch(() => null);
66
67
  if (r) {
67
- const i = await r.keys(), c = n ? new RegExp(n) : null, l = e.startsWith("http");
68
+ const i = await r.keys(), n = t ? new RegExp(t) : null, u = e.startsWith("http");
68
69
  await Promise.all(
69
70
  i.filter((f) => {
70
71
  const d = f.url, v = new URL(d).pathname;
71
- return c ? c.test(l ? d : v) : l ? d === e : d === e || v === e;
72
+ return n ? n.test(u ? d : v) : u ? d === e : d === e || v === e;
72
73
  }).map((f) => r.delete(f))
73
74
  );
74
75
  }
75
- u(e) || h.getState().updateResource(e, {
76
+ l(e) || h.getState().updateResource(e, {
76
77
  status: "stale",
77
78
  cachedAt: void 0,
78
79
  lastEvent: "cache-cleared",
@@ -84,21 +85,21 @@ function q(e, t) {
84
85
  p.delete(e), g({ type: "EIDOS_UNREGISTER_RESOURCE", url: e }), h.getState().unregisterResource(e);
85
86
  }
86
87
  };
87
- return p.set(e, s), s;
88
+ return p.set(e, c), c;
88
89
  }
89
- async function E(e, t, a) {
90
- const n = h.getState();
91
- n.updateResource(e, { status: "fetching", fetchedAt: Date.now() });
92
- const o = await caches.open(a.cacheName).catch(() => null);
90
+ async function S(e, a, s) {
91
+ const t = h.getState();
92
+ t.updateResource(e, { status: "fetching", fetchedAt: Date.now() });
93
+ const o = await caches.open(s.cacheName).catch(() => null);
93
94
  try {
94
- if (a.swStrategy !== "network-first") {
95
- const i = o ? await o.match(e).catch(() => null) : null, c = h.getState().resources[e], l = t.maxAge !== void 0 && (c == null ? void 0 : c.cachedAt) !== void 0 && Date.now() - c.cachedAt > t.maxAge;
96
- if (i && !l)
97
- return n.updateResource(e, {
95
+ if (s.swStrategy !== "network-first") {
96
+ const i = o ? await o.match(e).catch(() => null) : null, n = h.getState().resources[e], u = a.maxAge !== void 0 && (n == null ? void 0 : n.cachedAt) !== void 0 && Date.now() - n.cachedAt > a.maxAge;
97
+ if (i && !u)
98
+ return t.updateResource(e, {
98
99
  status: "fresh",
99
100
  lastEvent: "cache-hit",
100
- cacheHits: ((c == null ? void 0 : c.cacheHits) ?? 0) + 1
101
- }), a.swStrategy === "stale-while-revalidate" && fetch(e, { signal: AbortSignal.timeout(5e3) }).then(async (d) => {
101
+ cacheHits: ((n == null ? void 0 : n.cacheHits) ?? 0) + 1
102
+ }), s.swStrategy === "stale-while-revalidate" && fetch(e, { signal: AbortSignal.timeout(5e3) }).then(async (d) => {
102
103
  d.ok && o && (await o.put(e, d.clone()), h.getState().updateResource(e, {
103
104
  cachedAt: Date.now(),
104
105
  lastEvent: "cache-updated"
@@ -106,38 +107,38 @@ async function E(e, t, a) {
106
107
  }).catch(() => {
107
108
  }), i;
108
109
  const f = h.getState().resources[e];
109
- n.updateResource(e, {
110
+ t.updateResource(e, {
110
111
  cacheMisses: ((f == null ? void 0 : f.cacheMisses) ?? 0) + 1
111
112
  });
112
113
  }
113
- const s = await fetch(e);
114
- if (s.ok)
115
- return o && await o.put(e, s.clone()), n.updateResource(e, {
114
+ const c = await fetch(e);
115
+ if (c.ok)
116
+ return o && await o.put(e, c.clone()), t.updateResource(e, {
116
117
  status: "fresh",
117
118
  cachedAt: Date.now(),
118
119
  lastEvent: "cache-updated"
119
- }), s;
120
- n.updateResource(e, { status: s.status === 503 ? "offline" : "error" });
121
- const r = s.headers.get("X-Eidos-Offline") === "true";
120
+ }), c;
121
+ t.updateResource(e, { status: c.status === 503 ? "offline" : "error" });
122
+ const r = c.headers.get("X-Eidos-Offline") === "true";
122
123
  throw new Error(
123
- r ? `offline: no cached response for ${e}` : `${s.status} ${s.statusText}`
124
+ r ? `offline: no cached response for ${e}` : `${c.status} ${c.statusText}`
124
125
  );
125
- } catch (s) {
126
+ } catch (c) {
126
127
  const r = o ? await o.match(e).catch(() => null) : null;
127
128
  if (r) {
128
129
  const i = h.getState().resources[e];
129
- return n.updateResource(e, {
130
+ return t.updateResource(e, {
130
131
  status: "fresh",
131
132
  lastEvent: "cache-hit",
132
133
  cacheHits: ((i == null ? void 0 : i.cacheHits) ?? 0) + 1
133
134
  }), r;
134
135
  }
135
- throw n.updateResource(e, { status: "error" }), s;
136
+ throw t.updateResource(e, { status: "error" }), c;
136
137
  }
137
138
  }
138
- function S(e, t) {
139
- const a = t.strategy;
140
- return t.offline ? R(a ?? "stale-while-revalidate", e, t.cacheName) : R(a ?? "network-first", e, t.cacheName);
139
+ function E(e, a) {
140
+ const s = a.strategy;
141
+ return a.offline ? R(s ?? "stale-while-revalidate", e, a.cacheName) : R(s ?? "network-first", e, a.cacheName);
141
142
  }
142
143
  const x = {
143
144
  "stale-while-revalidate": {
@@ -186,15 +187,24 @@ new NetworkFirst({
186
187
  })`
187
188
  }
188
189
  };
189
- function R(e, t, a) {
190
+ function R(e, a, s) {
190
191
  return {
191
192
  ...x[e],
192
193
  swStrategy: e,
193
- cacheName: a ?? "eidos-resources-v1"
194
+ cacheName: s ?? "eidos-resources-v1"
195
+ };
196
+ }
197
+ async function A(e) {
198
+ const a = await Promise.allSettled(e.map((t) => t.prefetch())), s = a.filter((t) => t.status === "rejected").map((t) => t.reason);
199
+ return {
200
+ warmed: a.filter((t) => t.status === "fulfilled").length,
201
+ failed: s.length,
202
+ errors: s
194
203
  };
195
204
  }
196
205
  export {
197
206
  q as resource,
198
- N as setQueryInvalidator
207
+ N as setQueryInvalidator,
208
+ A as warmCache
199
209
  };
200
210
  //# sourceMappingURL=resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.js","sources":["../src/resource.ts"],"sourcesContent":["import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\n// ── Request deduplication ─────────────────────────────────────────────────────\n// If multiple callers invoke handle.fetch() simultaneously for the same URL,\n// only one network request is made. Each caller gets its own cloned Response.\n// Keyed by URL; entry is deleted when the request settles.\nconst _inflightRequests = /* @__PURE__ */ new Map<string, Promise<Response>>()\n\n// ── TanStack Query bridge (optional) ─────────────────────────────────────────\n// Set by @sweidos/eidos/query when withEidosQueryClient() is called.\n// Lets handle.invalidate() also invalidate the matching TQ cache entry.\ntype QueryInvalidator = (queryKey: [string, string]) => void\nlet _queryInvalidator: QueryInvalidator | null = null\n\n/** @internal Called by @sweidos/eidos/query. */\nexport function setQueryInvalidator(fn: QueryInvalidator): void {\n _queryInvalidator = fn\n}\n\n// ── URL pattern helpers ───────────────────────────────────────────────────────\n\n/** Returns true if `url` contains wildcard or :param segments. */\nfunction isPattern(url: string): boolean {\n return url.includes('*') || /:[^/]+/.test(url)\n}\n\n/**\n * Converts a URL pattern to a regex source string for SW fetch matching.\n * `**` → multi-segment wildcard (`.+`)\n * `*` → single-segment wildcard (`[^/]+`)\n * `:param` → named single segment (`[^/]+`)\n *\n * Special regex characters in the pattern (e.g. `.`) are escaped first so\n * they match literally.\n *\n * @example\n * patternToRegexStr('/api/products/*') // '^/api/products/[^/]+$'\n * patternToRegexStr('/api/products/**') // '^/api/products/.+$'\n * patternToRegexStr('/api/users/:id') // '^/api/users/[^/]+$'\n */\nfunction patternToRegexStr(pattern: string): string {\n // Escape all regex-special chars except `*`, `/`, `:` (handled below)\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n return (\n '^' +\n escaped\n .replace(/\\*\\*/g, '.+') // ** → multi-segment wildcard\n .replace(/\\*/g, '[^/]+') // * → single-segment wildcard\n .replace(/:[^/]+/g, '[^/]+') // :param → single-segment wildcard\n + '$'\n )\n}\n\nfunction _patternError(url: string, method: string): Error {\n return new Error(\n `[eidos] resource('${url}') is a URL pattern — ${method}() is not supported on pattern handles. ` +\n `The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`,\n )\n}\n\n// ── resource() ────────────────────────────────────────────────────────────────\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n const regexStr = isPattern(url) ? patternToRegexStr(url) : undefined\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n ...(regexStr !== undefined && { pattern: regexStr }),\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n if (isPattern(url)) throw _patternError(url, 'fetch')\n\n // ── Deduplication: coalesce concurrent fetches for the same URL ─────\n // If a request is already in-flight, piggyback on it and return a clone\n // so each caller gets an independent readable Response body.\n const existing = _inflightRequests.get(url)\n if (existing) return existing.then((r) => r.clone())\n\n // Store the raw-response promise. All callers (including the primary)\n // receive a clone — the raw response stays unconsumed in the map so\n // any caller arriving while the promise is still pending can clone it.\n const task = _fetchResource(url, config, strategy)\n _inflightRequests.set(url, task)\n task.finally(() => _inflightRequests.delete(url))\n return task.then((r) => r.clone())\n },\n\n json: async () => {\n if (isPattern(url)) throw _patternError(url, 'json')\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => {\n if (isPattern(url)) throw _patternError(url, 'query')\n return {\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }\n },\n\n prefetch: async () => {\n if (isPattern(url)) throw _patternError(url, 'prefetch')\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n const patternRe = regexStr ? new RegExp(regexStr) : null\n const isCrossOrigin = url.startsWith('http')\n await Promise.all(\n keys\n .filter((r) => {\n const rUrl = r.url\n const p = new URL(rUrl).pathname\n if (patternRe) {\n // Cross-origin patterns were compiled from absolute URLs; test full URL.\n return patternRe.test(isCrossOrigin ? rUrl : p)\n }\n return isCrossOrigin ? rUrl === url : (rUrl === url || p === url)\n })\n .map((r) => cache.delete(r)),\n )\n }\n // For exact-URL resources update the store entry; patterns don't have a\n // single entry to update (individual URLs are not tracked per-pattern).\n if (!isPattern(url)) {\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n }\n // Notify TanStack Query bridge if registered.\n _queryInvalidator?.(['eidos', url])\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ── _fetchResource ─────────────────────────────────────────────────────────────\n// The actual network/cache implementation. Separated from handle.fetch() so the\n// deduplication wrapper can store the Promise and share it across concurrent callers.\n// Returns the raw (unconsumed) Response — callers MUST .clone() before reading body.\nasync function _fetchResource(\n url: string,\n config: ResourceConfig,\n strategy: GeneratedStrategy,\n): Promise<Response> {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ─────────\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ─────────────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url, { signal: AbortSignal.timeout(5000) })\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline or timed out — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n 'offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n"],"names":["_registry","_inflightRequests","_queryInvalidator","setQueryInvalidator","fn","isPattern","url","patternToRegexStr","pattern","_patternError","method","resource","config","strategy","deriveStrategy","regexStr","entry","useEidosStore","sendToWorker","handle","existing","r","task","_fetchResource","cache","keys","patternRe","isCrossOrigin","rUrl","p","store","cached","current","expired","resp","storeEntry","response","isOffline","err","fallback","explicit","buildStrategy","STRATEGY_META","swStrategy","_url","cacheName"],"mappings":";;AAUA,MAAMA,wBAAgB,IAAA,GAMhBC,wBAAwC,IAAA;AAM9C,IAAIC,IAA6C;AAG1C,SAASC,EAAoBC,GAA4B;AAC9D,EAAAF,IAAoBE;AACtB;AAKA,SAASC,EAAUC,GAAsB;AACvC,SAAOA,EAAI,SAAS,GAAG,KAAK,SAAS,KAAKA,CAAG;AAC/C;AAgBA,SAASC,EAAkBC,GAAyB;AAGlD,SACE,MAFcA,EAAQ,QAAQ,sBAAsB,MAAM,EAIvD,QAAQ,SAAS,IAAI,EACrB,QAAQ,OAAO,OAAO,EACtB,QAAQ,WAAW,OAAO,IAC3B;AAEN;AAEA,SAASC,EAAcH,GAAaI,GAAuB;AACzD,SAAO,IAAI;AAAA,IACT,qBAAqBJ,CAAG,yBAAyBI,CAAM;AAAA,EAAA;AAG3D;AAIO,SAASC,EACdL,GACAM,GACmB;AACnB,MAAIZ,EAAU,IAAIM,CAAG;AAenB,WAAON,EAAU,IAAIM,CAAG;AAG1B,QAAMO,IAAWC,EAAeR,GAAKM,CAAM,GACrCG,IAAWV,EAAUC,CAAG,IAAIC,EAAkBD,CAAG,IAAI,QAErDU,IAAuB;AAAA,IAC3B,KAAAV;AAAA,IACA,QAAAM;AAAA,IACA,UAAAC;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,aAAa;AAAA,EAAA;AAGf,EAAAI,EAAc,SAAA,EAAW,iBAAiBX,GAAKU,CAAK,GAEpDE,EAAa;AAAA,IACX,MAAM;AAAA,IACN,KAAAZ;AAAA,IACA,UAAUO,EAAS;AAAA,IACnB,WAAWA,EAAS;AAAA,IACpB,GAAIE,MAAa,UAAa,EAAE,SAASA,EAAA;AAAA,EAAS,CACnD;AAED,QAAMI,IAA4B;AAAA,IAChC,KAAAb;AAAA,IACA,QAAAM;AAAA,IACA,UAAAC;AAAA,IAEA,OAAO,YAAY;AACjB,UAAIR,EAAUC,CAAG,EAAG,OAAMG,EAAcH,GAAK,OAAO;AAKpD,YAAMc,IAAWnB,EAAkB,IAAIK,CAAG;AAC1C,UAAIc,UAAiBA,EAAS,KAAK,CAACC,MAAMA,EAAE,OAAO;AAKnD,YAAMC,IAAOC,EAAejB,GAAKM,GAAQC,CAAQ;AACjD,aAAAZ,EAAkB,IAAIK,GAAKgB,CAAI,GAC/BA,EAAK,QAAQ,MAAMrB,EAAkB,OAAOK,CAAG,CAAC,GACzCgB,EAAK,KAAK,CAACD,MAAMA,EAAE,OAAO;AAAA,IACnC;AAAA,IAEA,MAAM,YAAY;AAChB,UAAIhB,EAAUC,CAAG,EAAG,OAAMG,EAAcH,GAAK,MAAM;AAEnD,cADY,MAAMa,EAAO,MAAA,GACd,KAAA;AAAA,IACb;AAAA,IAEA,OAAO,MAAM;AACX,UAAId,EAAUC,CAAG,EAAG,OAAMG,EAAcH,GAAK,OAAO;AACpD,aAAO;AAAA,QACL,UAAU,CAAC,SAASA,CAAG;AAAA,QACvB,SAAS,MAAMa,EAAO,KAAA;AAAA,MAAK;AAAA,IAE/B;AAAA,IAEA,UAAU,YAAY;AACpB,UAAId,EAAUC,CAAG,EAAG,OAAMG,EAAcH,GAAK,UAAU;AACvD,YAAMa,EAAO,MAAA;AAAA,IACf;AAAA,IAEA,YAAY,YAAY;AACtB,MAAAD,EAAa,EAAE,MAAM,qBAAqB,KAAAZ,EAAA,CAAK;AAC/C,YAAMkB,IAAQ,MAAM,OAAO,KAAKX,EAAS,SAAS,EAAE,MAAM,MAAM,IAAI;AACpE,UAAIW,GAAO;AACT,cAAMC,IAAO,MAAMD,EAAM,KAAA,GACnBE,IAAYX,IAAW,IAAI,OAAOA,CAAQ,IAAI,MAC9CY,IAAgBrB,EAAI,WAAW,MAAM;AAC3C,cAAM,QAAQ;AAAA,UACZmB,EACG,OAAO,CAACJ,MAAM;AACb,kBAAMO,IAAOP,EAAE,KACTQ,IAAI,IAAI,IAAID,CAAI,EAAE;AACxB,mBAAIF,IAEKA,EAAU,KAAKC,IAAgBC,IAAOC,CAAC,IAEzCF,IAAgBC,MAAStB,IAAOsB,MAAStB,KAAOuB,MAAMvB;AAAA,UAC/D,CAAC,EACA,IAAI,CAACe,MAAMG,EAAM,OAAOH,CAAC,CAAC;AAAA,QAAA;AAAA,MAEjC;AAGA,MAAKhB,EAAUC,CAAG,KAChBW,EAAc,SAAA,EAAW,eAAeX,GAAK;AAAA,QAC3C,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,WAAW;AAAA,QACX,WAAW;AAAA,QACX,aAAa;AAAA,MAAA,CACd,GAGHJ,KAAA,QAAAA,EAAoB,CAAC,SAASI,CAAG;AAAA,IACnC;AAAA,IAEA,YAAY,MAAM;AAChB,MAAAN,EAAU,OAAOM,CAAG,GACpBY,EAAa,EAAE,MAAM,6BAA6B,KAAAZ,EAAA,CAAK,GACvDW,EAAc,SAAA,EAAW,mBAAmBX,CAAG;AAAA,IACjD;AAAA,EAAA;AAGF,SAAAN,EAAU,IAAIM,GAAKa,CAAM,GAClBA;AACT;AAMA,eAAeI,EACbjB,GACAM,GACAC,GACmB;AACnB,QAAMiB,IAAQb,EAAc,SAAA;AAC5B,EAAAa,EAAM,eAAexB,GAAK,EAAE,QAAQ,YAAY,WAAW,KAAK,IAAA,GAAO;AAIvE,QAAMkB,IAAQ,MAAM,OAAO,KAAKX,EAAS,SAAS,EAAE,MAAM,MAAM,IAAI;AAEpE,MAAI;AAKF,QAAIA,EAAS,eAAe,iBAAiB;AAK3C,YAAMkB,IAASP,IAAQ,MAAMA,EAAM,MAAMlB,CAAG,EAAE,MAAM,MAAM,IAAI,IAAI,MAG5D0B,IAAUf,EAAc,SAAA,EAAW,UAAUX,CAAG,GAChD2B,IACJrB,EAAO,WAAW,WAClBoB,KAAA,gBAAAA,EAAS,cAAa,UACtB,KAAK,IAAA,IAAQA,EAAQ,WAAWpB,EAAO;AAEzC,UAAImB,KAAU,CAACE;AACb,eAAAH,EAAM,eAAexB,GAAK;AAAA,UACxB,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,aAAY0B,KAAA,gBAAAA,EAAS,cAAa,KAAK;AAAA,QAAA,CACxC,GAGGnB,EAAS,eAAe,4BAC1B,MAAMP,GAAK,EAAE,QAAQ,YAAY,QAAQ,GAAI,GAAG,EAC7C,KAAK,OAAO4B,MAAS;AACpB,UAAIA,EAAK,MAAMV,MACb,MAAMA,EAAM,IAAIlB,GAAK4B,EAAK,OAAO,GACjCjB,EAAc,SAAA,EAAW,eAAeX,GAAK;AAAA,YAC3C,UAAU,KAAK,IAAA;AAAA,YACf,WAAW;AAAA,UAAA,CACZ;AAAA,QAEL,CAAC,EACA,MAAM,MAAM;AAAA,QAEb,CAAC,GAGEyB;AAIT,YAAMI,IAAalB,EAAc,SAAA,EAAW,UAAUX,CAAG;AACzD,MAAAwB,EAAM,eAAexB,GAAK;AAAA,QACxB,eAAc6B,KAAA,gBAAAA,EAAY,gBAAe,KAAK;AAAA,MAAA,CAC/C;AAAA,IACH;AAEA,UAAMC,IAAW,MAAM,MAAM9B,CAAG;AAEhC,QAAI8B,EAAS;AACX,aAAIZ,KAAO,MAAMA,EAAM,IAAIlB,GAAK8B,EAAS,OAAO,GAChDN,EAAM,eAAexB,GAAK;AAAA,QACxB,QAAQ;AAAA,QACR,UAAU,KAAK,IAAA;AAAA,QACf,WAAW;AAAA,MAAA,CACZ,GACM8B;AAKT,IAAAN,EAAM,eAAexB,GAAK,EAAE,QAAQ8B,EAAS,WAAW,MAAM,YAAY,SAAS;AAGnF,UAAMC,IAAYD,EAAS,QAAQ,IAAI,iBAAiB,MAAM;AAC9D,UAAM,IAAI;AAAA,MACRC,IAAY,mCAAmC/B,CAAG,KAAK,GAAG8B,EAAS,MAAM,IAAIA,EAAS,UAAU;AAAA,IAAA;AAAA,EAEpG,SAASE,GAAK;AAEZ,UAAMC,IAAWf,IAAQ,MAAMA,EAAM,MAAMlB,CAAG,EAAE,MAAM,MAAM,IAAI,IAAI;AAEpE,QAAIiC,GAAU;AACZ,YAAMP,IAAUf,EAAc,SAAA,EAAW,UAAUX,CAAG;AACtD,aAAAwB,EAAM,eAAexB,GAAK;AAAA,QACxB,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,aAAY0B,KAAA,gBAAAA,EAAS,cAAa,KAAK;AAAA,MAAA,CACxC,GACMO;AAAA,IACT;AAEA,UAAAT,EAAM,eAAexB,GAAK,EAAE,QAAQ,SAAS,GACvCgC;AAAA,EACR;AACF;AAMA,SAASxB,EAAeR,GAAaM,GAA2C;AAC9E,QAAM4B,IAAW5B,EAAO;AACxB,SAAIA,EAAO,UAAgB6B,EAAcD,KAAY,0BAA0BlC,GAAKM,EAAO,SAAS,IAC7F6B,EAAcD,KAAY,iBAAiBlC,GAAKM,EAAO,SAAS;AACzE;AAEA,MAAM8B,IAA4F;AAAA,EAChG,0BAA0B;AAAA,IACxB,MAAM;AAAA,IACN,WACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA;AAAA,EAMlB,eAAe;AAAA,IACb,MAAM;AAAA,IACN,WACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA;AAAA,EAMlB,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,WACE;AAAA,IACF,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA;AAMpB;AAEA,SAASD,EAAcE,GAA2BC,GAAcC,GAAuC;AACrG,SAAO;AAAA,IACL,GAAGH,EAAcC,CAAU;AAAA,IAC3B,YAAAA;AAAA,IACA,WAAWE,KAAa;AAAA,EAAA;AAE5B;"}
1
+ {"version":3,"file":"resource.js","sources":["../src/resource.ts"],"sourcesContent":["import { useEidosStore } from './store'\nimport { sendToWorker } from './sw-bridge'\nimport type {\n ResourceConfig,\n ResourceHandle,\n ResourceEntry,\n GeneratedStrategy,\n CacheStrategy,\n WarmCacheResult,\n} from './types'\n\nconst _registry = new Map<string, ResourceHandle>()\n\n// ── Request deduplication ─────────────────────────────────────────────────────\n// If multiple callers invoke handle.fetch() simultaneously for the same URL,\n// only one network request is made. Each caller gets its own cloned Response.\n// Keyed by URL; entry is deleted when the request settles.\nconst _inflightRequests = /* @__PURE__ */ new Map<string, Promise<Response>>()\n\n// ── TanStack Query bridge (optional) ─────────────────────────────────────────\n// Set by @sweidos/eidos/query when withEidosQueryClient() is called.\n// Lets handle.invalidate() also invalidate the matching TQ cache entry.\ntype QueryInvalidator = (queryKey: [string, string]) => void\nlet _queryInvalidator: QueryInvalidator | null = null\n\n/** @internal Called by @sweidos/eidos/query. */\nexport function setQueryInvalidator(fn: QueryInvalidator): void {\n _queryInvalidator = fn\n}\n\n// ── URL pattern helpers ───────────────────────────────────────────────────────\n\n/** Returns true if `url` contains wildcard or :param segments. */\nfunction isPattern(url: string): boolean {\n return url.includes('*') || /:[^/]+/.test(url)\n}\n\n/**\n * Converts a URL pattern to a regex source string for SW fetch matching.\n * `**` → multi-segment wildcard (`.+`)\n * `*` → single-segment wildcard (`[^/]+`)\n * `:param` → named single segment (`[^/]+`)\n *\n * Special regex characters in the pattern (e.g. `.`) are escaped first so\n * they match literally.\n *\n * @example\n * patternToRegexStr('/api/products/*') // '^/api/products/[^/]+$'\n * patternToRegexStr('/api/products/**') // '^/api/products/.+$'\n * patternToRegexStr('/api/users/:id') // '^/api/users/[^/]+$'\n */\nfunction patternToRegexStr(pattern: string): string {\n // Escape all regex-special chars except `*`, `/`, `:` (handled below)\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n return (\n '^' +\n escaped\n .replace(/\\*\\*/g, '.+') // ** → multi-segment wildcard\n .replace(/\\*/g, '[^/]+') // * → single-segment wildcard\n .replace(/:[^/]+/g, '[^/]+') // :param → single-segment wildcard\n + '$'\n )\n}\n\nfunction _patternError(url: string, method: string): Error {\n return new Error(\n `[eidos] resource('${url}') is a URL pattern — ${method}() is not supported on pattern handles. ` +\n `The SW intercepts matching requests automatically; call fetch(specificUrl) directly in your app code.`,\n )\n}\n\n// ── resource() ────────────────────────────────────────────────────────────────\n\nexport function resource<T = unknown>(\n url: string,\n config: ResourceConfig,\n): ResourceHandle<T> {\n if (_registry.has(url)) {\n if (import.meta.env.DEV) {\n const existing = _registry.get(url)!\n const existingCfg = existing.config\n if (\n existingCfg.offline !== config.offline ||\n existingCfg.strategy !== config.strategy ||\n existingCfg.cacheName !== config.cacheName\n ) {\n console.warn(\n `[eidos] resource('${url}') already registered with a different config — returning cached handle. Call resource.unregister() first to re-register.`,\n { registered: existingCfg, ignored: config },\n )\n }\n }\n return _registry.get(url) as ResourceHandle<T>\n }\n\n const strategy = deriveStrategy(url, config)\n const regexStr = isPattern(url) ? patternToRegexStr(url) : undefined\n\n const entry: ResourceEntry = {\n url,\n config,\n strategy,\n status: 'idle',\n cacheHits: 0,\n cacheMisses: 0,\n }\n\n useEidosStore.getState().registerResource(url, entry)\n\n sendToWorker({\n type: 'EIDOS_REGISTER_RESOURCE',\n url,\n strategy: strategy.swStrategy,\n cacheName: strategy.cacheName,\n ...(regexStr !== undefined && { pattern: regexStr }),\n })\n\n const handle: ResourceHandle<T> = {\n url,\n config,\n strategy,\n\n fetch: async () => {\n if (isPattern(url)) throw _patternError(url, 'fetch')\n\n // ── Deduplication: coalesce concurrent fetches for the same URL ─────\n // If a request is already in-flight, piggyback on it and return a clone\n // so each caller gets an independent readable Response body.\n const existing = _inflightRequests.get(url)\n if (existing) return existing.then((r) => r.clone())\n\n // Store the raw-response promise. All callers (including the primary)\n // receive a clone — the raw response stays unconsumed in the map so\n // any caller arriving while the promise is still pending can clone it.\n const task = _fetchResource(url, config, strategy)\n _inflightRequests.set(url, task)\n // .catch() silences the unhandled-rejection on the cleanup promise;\n // the error still propagates to callers via task.then() below.\n task.finally(() => _inflightRequests.delete(url)).catch(() => {})\n return task.then((r) => r.clone())\n },\n\n json: async () => {\n if (isPattern(url)) throw _patternError(url, 'json')\n const res = await handle.fetch()\n return res.json() as Promise<T>\n },\n\n query: () => {\n if (isPattern(url)) throw _patternError(url, 'query')\n return {\n queryKey: ['eidos', url] as [string, string],\n queryFn: () => handle.json(),\n }\n },\n\n prefetch: async () => {\n if (isPattern(url)) throw _patternError(url, 'prefetch')\n await handle.fetch()\n },\n\n invalidate: async () => {\n sendToWorker({ type: 'EIDOS_CLEAR_CACHE', url })\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n if (cache) {\n const keys = await cache.keys()\n const patternRe = regexStr ? new RegExp(regexStr) : null\n const isCrossOrigin = url.startsWith('http')\n await Promise.all(\n keys\n .filter((r) => {\n const rUrl = r.url\n const p = new URL(rUrl).pathname\n if (patternRe) {\n // Cross-origin patterns were compiled from absolute URLs; test full URL.\n return patternRe.test(isCrossOrigin ? rUrl : p)\n }\n return isCrossOrigin ? rUrl === url : (rUrl === url || p === url)\n })\n .map((r) => cache.delete(r)),\n )\n }\n // For exact-URL resources update the store entry; patterns don't have a\n // single entry to update (individual URLs are not tracked per-pattern).\n if (!isPattern(url)) {\n useEidosStore.getState().updateResource(url, {\n status: 'stale',\n cachedAt: undefined,\n lastEvent: 'cache-cleared',\n cacheHits: 0,\n cacheMisses: 0,\n })\n }\n // Notify TanStack Query bridge if registered.\n _queryInvalidator?.(['eidos', url])\n },\n\n unregister: () => {\n _registry.delete(url)\n sendToWorker({ type: 'EIDOS_UNREGISTER_RESOURCE', url })\n useEidosStore.getState().unregisterResource(url)\n },\n }\n\n _registry.set(url, handle)\n return handle\n}\n\n// ── _fetchResource ─────────────────────────────────────────────────────────────\n// The actual network/cache implementation. Separated from handle.fetch() so the\n// deduplication wrapper can store the Promise and share it across concurrent callers.\n// Returns the raw (unconsumed) Response — callers MUST .clone() before reading body.\nasync function _fetchResource(\n url: string,\n config: ResourceConfig,\n strategy: GeneratedStrategy,\n): Promise<Response> {\n const store = useEidosStore.getState()\n store.updateResource(url, { status: 'fetching', fetchedAt: Date.now() })\n\n // Open cache once and reuse across try/catch — avoids a redundant\n // caches.open() call in the error fallback path.\n const cache = await caches.open(strategy.cacheName).catch(() => null)\n\n try {\n // ── network-first: skip cache check, go straight to network ─────────\n // For cache-first / SWR the cache check below is correct. For\n // network-first, reading cache first and returning early would\n // contradict the strategy — fresh data is the priority.\n if (strategy.swStrategy !== 'network-first') {\n // ── Direct Cache API check ─────────────────────────────────────────\n // We read the cache in the main thread rather than waiting for\n // an async SW postMessage. This gives instant, reliable status\n // updates regardless of SW message timing.\n const cached = cache ? await cache.match(url).catch(() => null) : null\n\n // Treat cache as miss if maxAge exceeded\n const current = useEidosStore.getState().resources[url]\n const expired =\n config.maxAge !== undefined &&\n current?.cachedAt !== undefined &&\n Date.now() - current.cachedAt > config.maxAge\n\n if (cached && !expired) {\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n\n // Background revalidation for SWR (stale-while-revalidate)\n if (strategy.swStrategy === 'stale-while-revalidate') {\n fetch(url, { signal: AbortSignal.timeout(5000) })\n .then(async (resp) => {\n if (resp.ok && cache) {\n await cache.put(url, resp.clone())\n useEidosStore.getState().updateResource(url, {\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n }\n })\n .catch(() => {\n /* offline or timed out — cached version stays valid */\n })\n }\n\n return cached\n }\n\n // Cache miss (or expired)\n const storeEntry = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n cacheMisses: (storeEntry?.cacheMisses ?? 0) + 1,\n })\n }\n\n const response = await fetch(url)\n\n if (response.ok) {\n if (cache) await cache.put(url, response.clone())\n store.updateResource(url, {\n status: 'fresh',\n cachedAt: Date.now(),\n lastEvent: 'cache-updated',\n })\n return response\n }\n\n // Non-2xx response (e.g. 503 from offline SW) — update status and throw\n // so callers get a proper error instead of a plain-object body they can't use.\n store.updateResource(url, { status: response.status === 503 ? 'offline' : 'error' })\n\n // Check if the SW tagged this as an offline response\n const isOffline = response.headers.get('X-Eidos-Offline') === 'true'\n throw new Error(\n isOffline ? `offline: no cached response for ${url}` : `${response.status} ${response.statusText}`,\n )\n } catch (err) {\n // Network failure — try cache one more time as fallback\n const fallback = cache ? await cache.match(url).catch(() => null) : null\n\n if (fallback) {\n const current = useEidosStore.getState().resources[url]\n store.updateResource(url, {\n status: 'fresh',\n lastEvent: 'cache-hit',\n cacheHits: (current?.cacheHits ?? 0) + 1,\n })\n return fallback\n }\n\n store.updateResource(url, { status: 'error' })\n throw err\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Strategy derivation — intent → deterministic caching strategy\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction deriveStrategy(url: string, config: ResourceConfig): GeneratedStrategy {\n const explicit = config.strategy\n if (config.offline) return buildStrategy(explicit ?? 'stale-while-revalidate', url, config.cacheName)\n return buildStrategy(explicit ?? 'network-first', url, config.cacheName)\n}\n\nconst STRATEGY_META: Record<CacheStrategy, Omit<GeneratedStrategy, 'swStrategy' | 'cacheName'>> = {\n 'stale-while-revalidate': {\n name: 'StaleWhileRevalidate',\n reasoning:\n 'offline: true signals resilience. SWR returns cached data instantly while revalidating in the background — the best tradeoff between speed and freshness for offline-capable resources.',\n behavior: [\n 'Cache hit → return immediately, kick off background revalidation',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version if available, 503 if not',\n 'Reconnect → next request triggers a background refresh',\n ],\n equivalentCode: `// Workbox equivalent\nnew StaleWhileRevalidate({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'cache-first': {\n name: 'CacheFirst',\n reasoning:\n 'cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.',\n behavior: [\n 'Cache hit → return immediately, no network request made',\n 'Cache miss → fetch from network, cache the response, return it',\n 'Offline → return cached version, 503 if cache is empty',\n 'Cache never expires unless explicitly invalidated',\n ],\n equivalentCode: `// Workbox equivalent\nnew CacheFirst({\n cacheName: 'eidos-resources-v1',\n plugins: [new ExpirationPlugin({ maxEntries: 60 })],\n})`,\n },\n 'network-first': {\n name: 'NetworkFirst',\n reasoning:\n 'network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.',\n behavior: [\n 'Always try network first',\n 'Network success → update cache, return fresh response',\n 'Network failure → fall back to cached version',\n 'Offline with empty cache → return 503 error response',\n ],\n equivalentCode: `// Workbox equivalent\nnew NetworkFirst({\n cacheName: 'eidos-resources-v1',\n networkTimeoutSeconds: 3,\n})`,\n },\n}\n\nfunction buildStrategy(swStrategy: CacheStrategy, _url: string, cacheName?: string): GeneratedStrategy {\n return {\n ...STRATEGY_META[swStrategy],\n swStrategy,\n cacheName: cacheName ?? 'eidos-resources-v1',\n }\n}\n\n// ── warmCache ─────────────────────────────────────────────────────────────────\n\n/**\n * Bulk-prefetch an array of resource handles concurrently, warming the cache\n * for each one. Useful on login / app init when you know which resources the\n * user will need offline.\n *\n * Pattern handles (containing `*`, `**`, or `:param`) are silently skipped —\n * they match multiple URLs so there is no single URL to prefetch.\n *\n * @example\n * import { warmCache } from '@sweidos/eidos'\n *\n * // In EidosProvider's onReady, or after login:\n * const { warmed, failed } = await warmCache([products, userProfile, settings])\n */\nexport async function warmCache(handles: ResourceHandle[]): Promise<WarmCacheResult> {\n const results = await Promise.allSettled(handles.map((h) => h.prefetch()))\n const errors = results\n .filter((r): r is PromiseRejectedResult => r.status === 'rejected')\n .map((r) => r.reason)\n\n if (import.meta.env.DEV && errors.length > 0) {\n console.warn(`[eidos] warmCache: ${errors.length} handle(s) failed to prefetch`, errors)\n }\n\n return {\n warmed: results.filter((r) => r.status === 'fulfilled').length,\n failed: errors.length,\n errors,\n }\n}\n"],"names":["_registry","_inflightRequests","_queryInvalidator","setQueryInvalidator","fn","isPattern","url","patternToRegexStr","pattern","_patternError","method","resource","config","strategy","deriveStrategy","regexStr","entry","useEidosStore","sendToWorker","handle","existing","r","task","_fetchResource","cache","keys","patternRe","isCrossOrigin","rUrl","p","store","cached","current","expired","resp","storeEntry","response","isOffline","err","fallback","explicit","buildStrategy","STRATEGY_META","swStrategy","_url","cacheName","warmCache","handles","results","h","errors"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { AsyncStorageLike } from './index.ts';
2
+
3
+ export interface EidosRNConfig {
4
+ /** AsyncStorage singleton (or any AsyncStorageLike key-value store). */
5
+ storage: AsyncStorageLike;
6
+ /** Replay the queue automatically when the app comes back online. Default: true. */
7
+ autoReplay?: boolean;
8
+ }
9
+ /**
10
+ * Initialize Eidos for React Native.
11
+ * Call this once at app startup (outside any component) before rendering.
12
+ *
13
+ * @example
14
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
15
+ * await initEidosRN({ storage: AsyncStorage })
16
+ */
17
+ export declare function initEidosRN(config: EidosRNConfig): Promise<void>;
18
+ export declare function _resetEidosRN(): void;
package/dist/runtime.js CHANGED
@@ -1,10 +1,10 @@
1
- import { registerServiceWorker as m, registerBgSyncHandler as d } from "./sw-bridge.js";
1
+ import { registerServiceWorker as d, registerBgSyncHandler as m } from "./sw-bridge.js";
2
2
  import { replayQueue as n } from "./action.js";
3
3
  import { useEidosStore as e } from "./store.js";
4
4
  import { idbGetQueue as p } from "./idb.js";
5
5
  let o = !1, s = null;
6
- async function O(r = {}) {
7
- if (o) return;
6
+ async function w(r = {}) {
7
+ if (typeof window > "u" || o) return;
8
8
  o = !0;
9
9
  const u = r.swPath ?? "/eidos-sw.js", l = r.autoReplay ?? !0;
10
10
  try {
@@ -13,26 +13,26 @@ async function O(r = {}) {
13
13
  } catch {
14
14
  }
15
15
  try {
16
- await m(u);
16
+ await d(u);
17
17
  } catch {
18
18
  }
19
- if (d(() => {
19
+ if (m(() => {
20
20
  e.getState().isOnline && setTimeout(n, 200);
21
21
  }), l) {
22
22
  let t = e.getState().isOnline;
23
23
  s = e.subscribe(() => {
24
- const { isOnline: i } = e.getState(), f = i && !t;
25
- t = i, f && setTimeout(n, 600);
24
+ const { isOnline: i } = e.getState(), c = i && !t;
25
+ t = i, c && setTimeout(n, 600);
26
26
  });
27
- const a = e.getState(), c = a.queue.some((i) => i.status === "pending" || i.status === "failed");
28
- a.isOnline && c && setTimeout(n, 1200);
27
+ const a = e.getState(), f = a.queue.some((i) => i.status === "pending" || i.status === "failed");
28
+ a.isOnline && f && setTimeout(n, 1200);
29
29
  }
30
30
  }
31
- function w() {
31
+ function O() {
32
32
  s == null || s(), s = null, o = !1;
33
33
  }
34
34
  export {
35
- w as _resetEidos,
36
- O as initEidos
35
+ O as _resetEidos,
36
+ w as initEidos
37
37
  };
38
38
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sources":["../src/runtime.ts"],"sourcesContent":["import { registerServiceWorker, registerBgSyncHandler } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\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\n\nexport async function initEidos(config: EidosConfig = {}): Promise<void> {\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 useEidosStore.getState().hydrateQueue(persisted)\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 // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;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 _initialized = false\n}\n"],"names":["_initialized","_unsubscribe","initEidos","config","swPath","autoReplay","persisted","idbGetQueue","useEidosStore","registerServiceWorker","registerBgSyncHandler","replayQueue","prevIsOnline","isOnline","justCameOnline","store","hasPending","q","_resetEidos"],"mappings":";;;;AAYA,IAAIA,IAAe,IACfC,IAAoC;AAExC,eAAsBC,EAAUC,IAAsB,IAAmB;AACvE,MAAIH,EAAc;AAClB,EAAAA,IAAe;AAEf,QAAMI,IAASD,EAAO,UAAU,gBAC1BE,IAAaF,EAAO,cAAc;AAGxC,MAAI;AACF,UAAMG,IAAY,MAAMC,EAAA;AACxB,IAAID,EAAU,SAAS,KACrBE,EAAc,SAAA,EAAW,aAAaF,CAAS;AAAA,EAEnD,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAMG,EAAsBL,CAAM;AAAA,EACpC,QAAQ;AAAA,EAER;AAWA,MANAM,EAAsB,MAAM;AAC1B,IAAIF,EAAc,SAAA,EAAW,YAC3B,WAAWG,GAAa,GAAG;AAAA,EAE/B,CAAC,GAEGN,GAAY;AAQd,QAAIO,IAAeJ,EAAc,SAAA,EAAW;AAE5C,IAAAP,IAAeO,EAAc,UAAU,MAAM;AAC3C,YAAM,EAAE,UAAAK,EAAA,IAAaL,EAAc,SAAA,GAC7BM,IAAiBD,KAAY,CAACD;AACpC,MAAAA,IAAeC,GAEXC,KAEF,WAAWH,GAAa,GAAG;AAAA,IAE/B,CAAC;AAGD,UAAMI,IAAQP,EAAc,SAAA,GACtBQ,IAAaD,EAAM,MAAM,KAAK,CAACE,MAAMA,EAAE,WAAW,aAAaA,EAAE,WAAW,QAAQ;AAC1F,IAAIF,EAAM,YAAYC,KACpB,WAAWL,GAAa,IAAI;AAAA,EAEhC;AAUF;AAEO,SAASO,IAAc;AAC5B,EAAAjB,KAAA,QAAAA,KACAA,IAAe,MACfD,IAAe;AACjB;"}
1
+ {"version":3,"file":"runtime.js","sources":["../src/runtime.ts"],"sourcesContent":["import { registerServiceWorker, registerBgSyncHandler } from './sw-bridge'\nimport { replayQueue } from './action'\nimport { useEidosStore } from './store'\nimport { idbGetQueue } from './idb'\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\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 useEidosStore.getState().hydrateQueue(persisted)\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 // ── Subscribe to the store instead of window.addEventListener('online')\n //\n // WHY: setOfflineSimulation() updates the store directly but never fires a\n // real browser `online` event. Watching the store catches both:\n // • Real network reconnects (sw-bridge updates store on window.online)\n // • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))\n //\n let prevIsOnline = useEidosStore.getState().isOnline\n\n _unsubscribe = useEidosStore.subscribe(() => {\n const { isOnline } = useEidosStore.getState()\n const justCameOnline = isOnline && !prevIsOnline\n prevIsOnline = isOnline\n\n if (justCameOnline) {\n // Small delay so the connection (or simulation reset) settles first\n setTimeout(replayQueue, 600)\n }\n })\n\n // Replay any pending items that survived a page reload\n const store = useEidosStore.getState()\n const hasPending = store.queue.some((q) => q.status === 'pending' || q.status === 'failed')\n if (store.isOnline && hasPending) {\n setTimeout(replayQueue, 1200)\n }\n }\n\n if (import.meta.env.DEV) {\n const store = useEidosStore.getState()\n console.groupCollapsed('%c⚡ Eidos', 'color:#38bdf8;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 _initialized = false\n}\n"],"names":["_initialized","_unsubscribe","initEidos","config","swPath","autoReplay","persisted","idbGetQueue","useEidosStore","registerServiceWorker","registerBgSyncHandler","replayQueue","prevIsOnline","isOnline","justCameOnline","store","hasPending","q","_resetEidos"],"mappings":";;;;AAYA,IAAIA,IAAe,IACfC,IAAoC;AAExC,eAAsBC,EAAUC,IAAsB,IAAmB;AAGvE,MADI,OAAO,SAAW,OAClBH,EAAc;AAClB,EAAAA,IAAe;AAEf,QAAMI,IAASD,EAAO,UAAU,gBAC1BE,IAAaF,EAAO,cAAc;AAGxC,MAAI;AACF,UAAMG,IAAY,MAAMC,EAAA;AACxB,IAAID,EAAU,SAAS,KACrBE,EAAc,SAAA,EAAW,aAAaF,CAAS;AAAA,EAEnD,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAMG,EAAsBL,CAAM;AAAA,EACpC,QAAQ;AAAA,EAER;AAWA,MANAM,EAAsB,MAAM;AAC1B,IAAIF,EAAc,SAAA,EAAW,YAC3B,WAAWG,GAAa,GAAG;AAAA,EAE/B,CAAC,GAEGN,GAAY;AAQd,QAAIO,IAAeJ,EAAc,SAAA,EAAW;AAE5C,IAAAP,IAAeO,EAAc,UAAU,MAAM;AAC3C,YAAM,EAAE,UAAAK,EAAA,IAAaL,EAAc,SAAA,GAC7BM,IAAiBD,KAAY,CAACD;AACpC,MAAAA,IAAeC,GAEXC,KAEF,WAAWH,GAAa,GAAG;AAAA,IAE/B,CAAC;AAGD,UAAMI,IAAQP,EAAc,SAAA,GACtBQ,IAAaD,EAAM,MAAM,KAAK,CAACE,MAAMA,EAAE,WAAW,aAAaA,EAAE,WAAW,QAAQ;AAC1F,IAAIF,EAAM,YAAYC,KACpB,WAAWL,GAAa,IAAI;AAAA,EAEhC;AAUF;AAEO,SAASO,IAAc;AAC5B,EAAAjB,KAAA,QAAAA,KACAA,IAAe,MACfD,IAAe;AACjB;"}
package/dist/store.js CHANGED
@@ -1,13 +1,14 @@
1
- let s;
1
+ let o;
2
2
  const n = /* @__PURE__ */ new Set();
3
- function c() {
3
+ function a() {
4
4
  n.forEach((e) => e());
5
5
  }
6
6
  function r(e) {
7
- s = { ...s, ...e(s) }, c();
7
+ o = { ...o, ...e(o) }, a();
8
8
  }
9
- s = {
10
- isOnline: typeof navigator < "u" ? navigator.onLine : !0,
9
+ o = {
10
+ // navigator.onLine is undefined in React Native — default to true unless explicitly false
11
+ isOnline: typeof navigator > "u" || navigator.onLine !== !1,
11
12
  swStatus: "idle",
12
13
  swError: void 0,
13
14
  resources: {},
@@ -22,34 +23,43 @@ s = {
22
23
  }
23
24
  })),
24
25
  unregisterResource: (e) => r((u) => {
25
- const { [e]: t, ...o } = u.resources;
26
- return { resources: o };
26
+ const { [e]: t, ...s } = u.resources;
27
+ return { resources: s };
27
28
  }),
28
29
  addQueueItem: (e) => r((u) => ({ queue: [...u.queue, e] })),
29
30
  updateQueueItem: (e, u) => r((t) => ({
30
- queue: t.queue.map((o) => o.id === e ? { ...o, ...u } : o)
31
+ queue: t.queue.map((s) => s.id === e ? { ...s, ...u } : s)
31
32
  })),
33
+ batchUpdateQueueItems: (e) => r((u) => {
34
+ const t = new Map(e.map((s) => [s.id, s.update]));
35
+ return {
36
+ queue: u.queue.map((s) => {
37
+ const c = t.get(s.id);
38
+ return c ? { ...s, ...c } : s;
39
+ })
40
+ };
41
+ }),
32
42
  removeQueueItem: (e) => r((u) => ({ queue: u.queue.filter((t) => t.id !== e) })),
33
43
  hydrateQueue: (e) => r(() => ({ queue: e }))
34
44
  };
35
- function i() {
36
- return s;
45
+ function d() {
46
+ return o;
37
47
  }
38
- function d(e) {
48
+ function i(e) {
39
49
  return n.add(e), () => {
40
50
  n.delete(e);
41
51
  };
42
52
  }
43
- const a = {
44
- getState: i,
45
- subscribe: d,
53
+ const f = {
54
+ getState: d,
55
+ subscribe: i,
46
56
  // Test/devtools helper — merges partial state, preserves action methods.
47
57
  setState: (e) => {
48
- const u = typeof e == "function" ? e(s) : e;
49
- s = { ...s, ...u }, c();
58
+ const u = typeof e == "function" ? e(o) : e;
59
+ o = { ...o, ...u }, a();
50
60
  }
51
61
  };
52
62
  export {
53
- a as useEidosStore
63
+ f as useEidosStore
54
64
  };
55
65
  //# sourceMappingURL=store.js.map
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sources":["../src/store.ts"],"sourcesContent":["import type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\n addQueueItem: (item: ActionQueueItem) => void\n updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void\n removeQueueItem: (id: string) => void\n hydrateQueue: (items: ActionQueueItem[]) => 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 isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\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 registerResource: (url, entry) =>\n _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 // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\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 removeQueueItem: (id) => _set((s) => ({ queue: s.queue.filter((item) => item.id !== id) })),\n\n hydrateQueue: (items) => _set(() => ({ queue: items })),\n}\n\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\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"],"names":["_state","_listeners","_notify","fn","_set","updater","isOnline","swStatus","swError","url","entry","s","update","_removed","rest","item","id","items","_getState","_subscribe","listener","useEidosStore","partial"],"mappings":"AAoBA,IAAIA;AACJ,MAAMC,wBAAiB,IAAA;AAEvB,SAASC,IAAU;AACjB,EAAAD,EAAW,QAAQ,CAACE,MAAOA,EAAA,CAAI;AACjC;AAEA,SAASC,EAAKC,GAAoD;AAChE,EAAAL,IAAS,EAAE,GAAGA,GAAQ,GAAGK,EAAQL,CAAM,EAAA,GACvCE,EAAA;AACF;AAEAF,IAAS;AAAA,EACP,UAAU,OAAO,YAAc,MAAc,UAAU,SAAS;AAAA,EAChE,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW,CAAA;AAAA,EACX,OAAO,CAAA;AAAA,EAEP,WAAW,CAACM,MAAaF,EAAK,OAAO,EAAE,UAAAE,IAAW;AAAA,EAElD,aAAa,CAACC,GAAUC,MAAYJ,EAAK,OAAO,EAAE,UAAAG,GAAU,SAAAC,EAAA,EAAU;AAAA,EAEtE,kBAAkB,CAACC,GAAKC,MACtBN,EAAK,CAACO,OAAO,EAAE,WAAW,EAAE,GAAGA,EAAE,WAAW,CAACF,CAAG,GAAGC,EAAA,IAAU;AAAA,EAE/D,gBAAgB,CAACD,GAAKG,MACpBR,EAAK,CAACO,OAAO;AAAA,IACX,WAAW;AAAA,MACT,GAAGA,EAAE;AAAA,MACL,CAACF,CAAG,GAAGE,EAAE,UAAUF,CAAG,IAAI,EAAE,GAAGE,EAAE,UAAUF,CAAG,GAAG,GAAGG,MAAWD,EAAE,UAAUF,CAAG;AAAA,IAAA;AAAA,EAChF,EACA;AAAA,EAEJ,oBAAoB,CAACA,MACnBL,EAAK,CAACO,MAAM;AAEV,UAAM,EAAE,CAACF,CAAG,GAAGI,GAAU,GAAGC,EAAA,IAASH,EAAE;AACvC,WAAO,EAAE,WAAWG,EAAA;AAAA,EACtB,CAAC;AAAA,EAEH,cAAc,CAACC,MAASX,EAAK,CAACO,OAAO,EAAE,OAAO,CAAC,GAAGA,EAAE,OAAOI,CAAI,IAAI;AAAA,EAEnE,iBAAiB,CAACC,GAAIJ,MACpBR,EAAK,CAACO,OAAO;AAAA,IACX,OAAOA,EAAE,MAAM,IAAI,CAACI,MAAUA,EAAK,OAAOC,IAAK,EAAE,GAAGD,GAAM,GAAGH,EAAA,IAAWG,CAAK;AAAA,EAAA,EAC7E;AAAA,EAEJ,iBAAiB,CAACC,MAAOZ,EAAK,CAACO,OAAO,EAAE,OAAOA,EAAE,MAAM,OAAO,CAACI,MAASA,EAAK,OAAOC,CAAE,IAAI;AAAA,EAE1F,cAAc,CAACC,MAAUb,EAAK,OAAO,EAAE,OAAOa,IAAQ;AACxD;AAEA,SAASC,IAAY;AACnB,SAAOlB;AACT;AAEA,SAASmB,EAAWC,GAAoB;AACtC,SAAAnB,EAAW,IAAImB,CAAQ,GAChB,MAAM;AAAE,IAAAnB,EAAW,OAAOmB,CAAQ;AAAA,EAAE;AAC7C;AAEO,MAAMC,IAAgB;AAAA,EAC3B,UAAUH;AAAA,EACV,WAAWC;AAAA;AAAA,EAEX,UAAU,CAACG,MAA4E;AACrF,UAAMV,IAAS,OAAOU,KAAY,aAAaA,EAAQtB,CAAM,IAAIsB;AACjE,IAAAtB,IAAS,EAAE,GAAGA,GAAQ,GAAGY,EAAA,GACzBV,EAAA;AAAA,EACF;AACF;"}
1
+ {"version":3,"file":"store.js","sources":["../src/store.ts"],"sourcesContent":["import type { EidosState, ResourceEntry, ActionQueueItem } from './types'\n\nexport interface EidosStore extends EidosState {\n // Online\n setOnline: (online: boolean) => void\n // SW\n setSwStatus: (status: EidosState['swStatus'], error?: string) => void\n // Resources\n registerResource: (url: string, entry: ResourceEntry) => void\n updateResource: (url: string, update: Partial<ResourceEntry>) => void\n unregisterResource: (url: string) => void\n // Queue\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\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 registerResource: (url, entry) =>\n _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 // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { [url]: _removed, ...rest } = s.resources\n return { resources: rest }\n }),\n\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\nfunction _getState() {\n return _state\n}\n\nfunction _subscribe(listener: Listener) {\n _listeners.add(listener)\n return () => { _listeners.delete(listener) }\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"],"names":["_state","_listeners","_notify","fn","_set","updater","isOnline","swStatus","swError","url","entry","s","update","_removed","rest","item","id","updates","map","u","items","_getState","_subscribe","listener","useEidosStore","partial"],"mappings":"AAqBA,IAAIA;AACJ,MAAMC,wBAAiB,IAAA;AAEvB,SAASC,IAAU;AACjB,EAAAD,EAAW,QAAQ,CAACE,MAAOA,EAAA,CAAI;AACjC;AAEA,SAASC,EAAKC,GAAoD;AAChE,EAAAL,IAAS,EAAE,GAAGA,GAAQ,GAAGK,EAAQL,CAAM,EAAA,GACvCE,EAAA;AACF;AAEAF,IAAS;AAAA;AAAA,EAEP,UAAU,OAAO,YAAc,OAAe,UAAU,WAAW;AAAA,EACnE,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW,CAAA;AAAA,EACX,OAAO,CAAA;AAAA,EAEP,WAAW,CAACM,MAAaF,EAAK,OAAO,EAAE,UAAAE,IAAW;AAAA,EAElD,aAAa,CAACC,GAAUC,MAAYJ,EAAK,OAAO,EAAE,UAAAG,GAAU,SAAAC,EAAA,EAAU;AAAA,EAEtE,kBAAkB,CAACC,GAAKC,MACtBN,EAAK,CAACO,OAAO,EAAE,WAAW,EAAE,GAAGA,EAAE,WAAW,CAACF,CAAG,GAAGC,EAAA,IAAU;AAAA,EAE/D,gBAAgB,CAACD,GAAKG,MACpBR,EAAK,CAACO,OAAO;AAAA,IACX,WAAW;AAAA,MACT,GAAGA,EAAE;AAAA,MACL,CAACF,CAAG,GAAGE,EAAE,UAAUF,CAAG,IAAI,EAAE,GAAGE,EAAE,UAAUF,CAAG,GAAG,GAAGG,MAAWD,EAAE,UAAUF,CAAG;AAAA,IAAA;AAAA,EAChF,EACA;AAAA,EAEJ,oBAAoB,CAACA,MACnBL,EAAK,CAACO,MAAM;AAEV,UAAM,EAAE,CAACF,CAAG,GAAGI,GAAU,GAAGC,EAAA,IAASH,EAAE;AACvC,WAAO,EAAE,WAAWG,EAAA;AAAA,EACtB,CAAC;AAAA,EAEH,cAAc,CAACC,MAASX,EAAK,CAACO,OAAO,EAAE,OAAO,CAAC,GAAGA,EAAE,OAAOI,CAAI,IAAI;AAAA,EAEnE,iBAAiB,CAACC,GAAIJ,MACpBR,EAAK,CAACO,OAAO;AAAA,IACX,OAAOA,EAAE,MAAM,IAAI,CAACI,MAAUA,EAAK,OAAOC,IAAK,EAAE,GAAGD,GAAM,GAAGH,EAAA,IAAWG,CAAK;AAAA,EAAA,EAC7E;AAAA,EAEJ,uBAAuB,CAACE,MACtBb,EAAK,CAACO,MAAM;AACV,UAAMO,IAAM,IAAI,IAAID,EAAQ,IAAI,CAACE,MAAM,CAACA,EAAE,IAAIA,EAAE,MAAM,CAAC,CAAC;AACxD,WAAO;AAAA,MACL,OAAOR,EAAE,MAAM,IAAI,CAACI,MAAS;AAC3B,cAAMI,IAAID,EAAI,IAAIH,EAAK,EAAE;AACzB,eAAOI,IAAI,EAAE,GAAGJ,GAAM,GAAGI,MAAMJ;AAAA,MACjC,CAAC;AAAA,IAAA;AAAA,EAEL,CAAC;AAAA,EAEH,iBAAiB,CAACC,MAAOZ,EAAK,CAACO,OAAO,EAAE,OAAOA,EAAE,MAAM,OAAO,CAACI,MAASA,EAAK,OAAOC,CAAE,IAAI;AAAA,EAE1F,cAAc,CAACI,MAAUhB,EAAK,OAAO,EAAE,OAAOgB,IAAQ;AACxD;AAEA,SAASC,IAAY;AACnB,SAAOrB;AACT;AAEA,SAASsB,EAAWC,GAAoB;AACtC,SAAAtB,EAAW,IAAIsB,CAAQ,GAChB,MAAM;AAAE,IAAAtB,EAAW,OAAOsB,CAAQ;AAAA,EAAE;AAC7C;AAEO,MAAMC,IAAgB;AAAA,EAC3B,UAAUH;AAAA,EACV,WAAWC;AAAA;AAAA,EAEX,UAAU,CAACG,MAA4E;AACrF,UAAMb,IAAS,OAAOa,KAAY,aAAaA,EAAQzB,CAAM,IAAIyB;AACjE,IAAAzB,IAAS,EAAE,GAAGA,GAAQ,GAAGY,EAAA,GACzBV,EAAA;AAAA,EACF;AACF;"}
package/dist/stores.js CHANGED
@@ -1,36 +1,49 @@
1
1
  import { useEidosStore as i } from "./store.js";
2
- function s(e) {
2
+ function o(e, t) {
3
+ const s = Object.keys(e);
4
+ if (s.length !== Object.keys(t).length) return !1;
5
+ for (const n of s)
6
+ if (e[n] !== t[n]) return !1;
7
+ return !0;
8
+ }
9
+ function u(e, t = Object.is) {
3
10
  return {
4
- subscribe(t) {
5
- return t(e(i.getState())), i.subscribe(() => t(e(i.getState())));
11
+ subscribe(s) {
12
+ let n = e(i.getState());
13
+ return s(n), i.subscribe(() => {
14
+ const r = e(i.getState());
15
+ t(n, r) || (n = r, s(r));
16
+ });
6
17
  },
7
18
  getState() {
8
19
  return e(i.getState());
9
20
  }
10
21
  };
11
22
  }
12
- const a = s((e) => e), d = s((e) => e.queue), f = s((e) => ({
13
- isOnline: e.isOnline,
14
- swStatus: e.swStatus,
15
- swError: e.swError
16
- })), c = s((e) => {
17
- let t = 0, u = 0, r = 0;
18
- for (const n of e.queue)
19
- n.status === "pending" ? t++ : n.status === "failed" ? u++ : n.status === "replaying" && r++;
20
- return { pending: t, failed: u, replaying: r, total: e.queue.length };
21
- });
22
- function l(e) {
23
- return s((t) => t.resources[e]);
23
+ const f = u((e) => e), l = u((e) => e.queue), c = u(
24
+ (e) => ({ isOnline: e.isOnline, swStatus: e.swStatus, swError: e.swError }),
25
+ o
26
+ ), d = u(
27
+ (e) => {
28
+ let t = 0, s = 0, n = 0;
29
+ for (const r of e.queue)
30
+ r.status === "pending" ? t++ : r.status === "failed" ? s++ : r.status === "replaying" && n++;
31
+ return { pending: t, failed: s, replaying: n, total: e.queue.length };
32
+ },
33
+ o
34
+ );
35
+ function g(e) {
36
+ return u((t) => t.resources[e]);
24
37
  }
25
38
  function S(e) {
26
- return s((t) => t.queue.find((u) => u.id === e));
39
+ return u((t) => t.queue.find((s) => s.id === e));
27
40
  }
28
41
  export {
29
42
  S as eidosAction,
30
- d as eidosQueue,
31
- c as eidosQueueStats,
32
- l as eidosResource,
33
- f as eidosStatus,
34
- a as eidosStore
43
+ l as eidosQueue,
44
+ d as eidosQueueStats,
45
+ g as eidosResource,
46
+ c as eidosStatus,
47
+ f as eidosStore
35
48
  };
36
49
  //# sourceMappingURL=stores.js.map