@sweidos/eidos 1.0.33 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -81
- package/dist/action.js +119 -86
- package/dist/async-storage-adapter.js +15 -12
- package/dist/devtools.js +953 -555
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +37 -15
- package/dist/index.js +42 -41
- package/dist/nextjs.js +1 -10
- package/dist/query.cjs +131 -0
- package/dist/query.js +121 -41
- package/dist/queue-storage.js +5 -4
- package/dist/react/Devtools.d.ts +1 -1
- package/dist/react/Provider.js +11 -7
- package/dist/react/hooks.js +48 -38
- package/dist/react-native.js +47 -53
- package/dist/replay.js +15 -0
- package/dist/resource.js +77 -79
- package/dist/runtime.js +22 -28
- package/dist/store-slices.js +43 -0
- package/dist/store.js +32 -49
- package/dist/stores.js +25 -22
- package/dist/sveltekit.js +22 -6
- package/dist/sw-bridge.js +48 -46
- package/dist/testing.cjs +165 -0
- package/dist/testing.js +140 -70
- package/dist/version.js +4 -3
- package/dist/vite.cjs +48 -0
- package/dist/vite.js +45 -29
- package/package.json +48 -27
- package/dist/action.js.map +0 -1
- package/dist/async-storage-adapter.js.map +0 -1
- package/dist/eidos.cjs.js +0 -14
- package/dist/eidos.cjs.js.map +0 -1
- package/dist/idb.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query.cjs.js +0 -48
- package/dist/queue-storage.js.map +0 -1
- package/dist/react/Provider.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/resource.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/stores.js.map +0 -1
- package/dist/sw-bridge.js.map +0 -1
- package/dist/testing.cjs.js +0 -86
- package/dist/version.js.map +0 -1
- package/dist/vite.cjs.js +0 -31
package/dist/eidos.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let v=require("react"),j=require("react/jsx-runtime");function X(e){return{registerResource:(t,n)=>e(r=>({resources:{...r.resources,[t]:n}})),updateResource:(t,n)=>e(r=>({resources:{...r.resources,[t]:r.resources[t]?{...r.resources[t],...n}:r.resources[t]}})),unregisterResource:t=>e(n=>({resources:Object.fromEntries(Object.entries(n.resources).filter(([r])=>r!==t))}))}}function Z(e){return{addQueueItem:t=>e(n=>({queue:[...n.queue,t]})),updateQueueItem:(t,n)=>e(r=>({queue:r.queue.map(s=>s.id===t?{...s,...n}:s)})),batchUpdateQueueItems:t=>e(n=>{const r=new Map(t.map(s=>[s.id,s.update]));return{queue:n.queue.map(s=>{const a=r.get(s.id);return a?{...s,...a}:s})}}),removeQueueItem:t=>e(n=>({queue:n.queue.filter(r=>r.id!==t)})),hydrateQueue:t=>e(()=>({queue:t}))}}var g,x=new Set;function W(){x.forEach(e=>e())}function b(e){g={...g,...e(g)},W()}g={isOnline:typeof navigator>"u"||navigator.onLine!==!1,swStatus:"idle",swError:void 0,resources:{},queue:[],setOnline:e=>b(()=>({isOnline:e})),setSwStatus:(e,t)=>b(()=>({swStatus:e,swError:t})),...X(b),...Z(b)};function ee(){return g}function te(e){return x.add(e),()=>{x.delete(e)}}var o={getState:ee,subscribe:te,setState:e=>{const t=typeof e=="function"?e(g):e;g={...g,...t},W()}},y=null,C=[];function ne(){return y}async function re(e){if(typeof navigator>"u"||!("serviceWorker"in navigator)){o.getState().setSwStatus("unsupported");return}const t=o.getState();t.setSwStatus("registering");try{y=await navigator.serviceWorker.register(e,{scope:"/"}),await se(y),t.setSwStatus("active"),navigator.serviceWorker.addEventListener("message",oe),window.addEventListener("online",()=>t.setOnline(!0)),window.addEventListener("offline",()=>t.setOnline(!1)),ue()}catch(n){t.setSwStatus("error",String(n))}}function se(e){return new Promise(t=>{if(e.active){t();return}const n=e.installing??e.waiting;if(!n){t();return}const r=setTimeout(t,1e4);n.addEventListener("statechange",function s(){n.state==="activated"&&(clearTimeout(r),n.removeEventListener("statechange",s),t())})})}function k(e){const t=y?.active;t?t.postMessage(e):C.push(e)}var H=null;function ae(e){H=e}function ie(){try{return typeof navigator<"u"&&"serviceWorker"in navigator&&y!==null&&"sync"in y}catch{return!1}}function oe(e){const t=e.data;if(!t?.type)return;const n=o.getState(),{type:r,url:s}=t;if(r==="EIDOS_BACKGROUND_SYNC"){H?.();return}if(s)switch(r){case"EIDOS_CACHE_HIT":{const a=n.resources[s];n.updateResource(s,{status:"fresh",lastEvent:"cache-hit",cacheHits:(a?.cacheHits??0)+1});break}case"EIDOS_CACHE_UPDATED":n.updateResource(s,{status:"fresh",lastEvent:"cache-updated",cachedAt:Date.now()});break;case"EIDOS_NETWORK_ERROR":n.updateResource(s,{status:"error",lastEvent:"network-error"});break}}function ce(e){k({type:"EIDOS_SIMULATE_OFFLINE",enabled:e}),o.getState().setOnline(!e)}function ue(){const e=y?.active;if(e){for(const t of C)e.postMessage(t);C=[]}}var R=new Map,_=new Map,$=null;function de(e){$=e}function S(e){return e.includes("*")||/:[^/]+/.test(e)}function le(e){return"^"+e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".+").replace(/\*/g,"[^/]+").replace(/:[^/]+/g,"[^/]+")+"$"}function O(e,t){return new Error(`[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.`)}function fe(e,t){if(R.has(e))return R.get(e);const n=pe(t),r=S(e)?le(e):void 0,s={url:e,config:t,strategy:n,status:"idle",cacheHits:0,cacheMisses:0};o.getState().registerResource(e,s),k({type:"EIDOS_REGISTER_RESOURCE",url:e,strategy:n.swStrategy,cacheName:n.cacheName,...r!==void 0&&{pattern:r}});const a={url:e,config:t,strategy:n,fetch:async()=>{if(S(e))throw O(e,"fetch");const i=_.get(e);if(i)return i.then(u=>u.clone());const c=he(e,t,n);return _.set(e,c),c.finally(()=>_.delete(e)).catch(()=>{}),c.then(u=>u.clone())},json:async()=>{if(S(e))throw O(e,"json");return(await a.fetch()).json()},query:()=>{if(S(e))throw O(e,"query");return{queryKey:["eidos",e],queryFn:()=>a.json()}},prefetch:async()=>{if(S(e))throw O(e,"prefetch");await a.fetch()},invalidate:async()=>{k({type:"EIDOS_CLEAR_CACHE",url:e});const i=await caches.open(n.cacheName).catch(()=>null);if(i){const c=await i.keys(),u=r?new RegExp(r):null,p=e.startsWith("http");await Promise.all(c.filter(w=>{const l=w.url,T=new URL(l).pathname;return u?u.test(p?l:T):p?l===e:l===e||T===e}).map(w=>i.delete(w)))}S(e)||o.getState().updateResource(e,{status:"stale",cachedAt:void 0,lastEvent:"cache-cleared",cacheHits:0,cacheMisses:0}),$?.(["eidos",e])},unregister:()=>{R.delete(e),k({type:"EIDOS_UNREGISTER_RESOURCE",url:e}),o.getState().unregisterResource(e)}};return R.set(e,a),a}async function he(e,t,n){const r=o.getState();r.updateResource(e,{status:"fetching",fetchedAt:Date.now()});const s=await caches.open(n.cacheName).catch(()=>null);try{if(n.swStrategy!=="network-first"){const c=s?await s.match(e).catch(()=>null):null,u=o.getState().resources[e],p=t.maxAge!==void 0&&u?.cachedAt!==void 0&&Date.now()-u.cachedAt>t.maxAge;if(c&&!p)return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(u?.cacheHits??0)+1}),n.swStrategy==="stale-while-revalidate"&&fetch(e,{signal:AbortSignal.timeout(5e3)}).then(async l=>{l.ok&&s&&(await s.put(e,l.clone()),o.getState().updateResource(e,{cachedAt:Date.now(),lastEvent:"cache-updated"}))}).catch(()=>{}),c;const w=o.getState().resources[e];r.updateResource(e,{cacheMisses:(w?.cacheMisses??0)+1})}const a=await fetch(e);if(a.ok)return s&&await s.put(e,a.clone()),r.updateResource(e,{status:"fresh",cachedAt:Date.now(),lastEvent:"cache-updated"}),a;r.updateResource(e,{status:a.status===503?"offline":"error"});const i=a.headers.get("X-Eidos-Offline")==="true";throw new Error(i?`offline: no cached response for ${e}`:`${a.status} ${a.statusText}`)}catch(a){const i=s?await s.match(e).catch(()=>null):null;if(i){const c=o.getState().resources[e];return r.updateResource(e,{status:"fresh",lastEvent:"cache-hit",cacheHits:(c?.cacheHits??0)+1}),i}throw r.updateResource(e,{status:"error"}),a}}function pe(e){const t=e.strategy;return e.offline?M(t??"stale-while-revalidate",e.cacheName):M(t??"network-first",e.cacheName)}var ge={"stale-while-revalidate":"StaleWhileRevalidate","cache-first":"CacheFirst","network-first":"NetworkFirst"},we={"stale-while-revalidate":{reasoning:"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.",behavior:["Cache hit → return immediately, kick off background revalidation","Cache miss → fetch from network, cache the response, return it","Offline → return cached version if available, 503 if not","Reconnect → next request triggers a background refresh"],equivalentCode:`// Workbox equivalent
|
|
2
|
+
new StaleWhileRevalidate({
|
|
3
|
+
cacheName: 'eidos-resources-v1',
|
|
4
|
+
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
5
|
+
})`},"cache-first":{reasoning:"cache-first maximises speed and offline availability. Network is consulted only on cache miss. Best for static or infrequently-updated data.",behavior:["Cache hit → return immediately, no network request made","Cache miss → fetch from network, cache the response, return it","Offline → return cached version, 503 if cache is empty","Cache never expires unless explicitly invalidated"],equivalentCode:`// Workbox equivalent
|
|
6
|
+
new CacheFirst({
|
|
7
|
+
cacheName: 'eidos-resources-v1',
|
|
8
|
+
plugins: [new ExpirationPlugin({ maxEntries: 60 })],
|
|
9
|
+
})`},"network-first":{reasoning:"network-first prioritises fresh data. Cache acts as a safety net when offline. Best for frequently-updated resources where stale data causes problems.",behavior:["Always try network first","Network success → update cache, return fresh response","Network failure → fall back to cached version","Offline with empty cache → return 503 error response"],equivalentCode:`// Workbox equivalent
|
|
10
|
+
new NetworkFirst({
|
|
11
|
+
cacheName: 'eidos-resources-v1',
|
|
12
|
+
networkTimeoutSeconds: 3,
|
|
13
|
+
})`}};function M(e,t){const n=we[e];return{name:ge[e],swStrategy:e,cacheName:t??"eidos-resources-v1",reasoning:n.reasoning,behavior:n.behavior,equivalentCode:""}}async function ye(e){const t=await Promise.allSettled(e.map(r=>r.prefetch())),n=t.filter(r=>r.status==="rejected").map(r=>r.reason);return{warmed:t.filter(r=>r.status==="fulfilled").length,failed:n.length,errors:n}}var Se="eidos",ve=1,d="action-queue",A=null;function m(){return A?Promise.resolve(A):new Promise((e,t)=>{const n=indexedDB.open(Se,ve);n.onupgradeneeded=r=>{const s=r.target.result;if(!s.objectStoreNames.contains(d)){const a=s.createObjectStore(d,{keyPath:"id"});a.createIndex("status","status",{unique:!1}),a.createIndex("actionId","actionId",{unique:!1})}},n.onsuccess=()=>{A=n.result,e(n.result)},n.onerror=()=>t(n.error)})}async function me(e){const t=await m();return new Promise((n,r)=>{const s=t.transaction(d,"readwrite");s.objectStore(d).add(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function L(){const e=await m();return new Promise((t,n)=>{const r=e.transaction(d,"readonly").objectStore(d).getAll();r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function Ee(e,t){const n=await m();return new Promise((r,s)=>{const a=n.transaction(d,"readwrite"),i=a.objectStore(d),c=i.get(e);c.onsuccess=()=>{c.result&&i.put({...c.result,...t})},a.oncomplete=()=>r(),a.onerror=()=>s(a.error)})}async function be(e){const t=await m();return new Promise((n,r)=>{const s=t.transaction(d,"readwrite");s.objectStore(d).delete(e),s.oncomplete=()=>n(),s.onerror=()=>r(s.error)})}async function Re(){const e=await m();function t(s){return new Promise((a,i)=>{const c=e.transaction(d,"readonly").objectStore(d).index("status"),u=[],p=c.openCursor(IDBKeyRange.only(s));p.onsuccess=w=>{const l=w.target.result;l?(u.push(l.value),l.continue()):a(u)},p.onerror=()=>i(p.error)})}const[n,r]=await Promise.all([t("pending"),t("failed")]);return[...n,...r]}async function Oe(){const e=await m();return new Promise((t,n)=>{const r=e.transaction(d,"readwrite");r.objectStore(d).clear(),r.oncomplete=()=>t(),r.onerror=()=>n(r.error)})}var ke={add:me,getAll:L,getPending:Re,update:Ee,remove:be,clear:Oe},B=null;function qe(e){B=e}function F(){return B}var N=new Map,G=new Map,K=new Map;function h(){return F()??ke}function V(){return crypto.randomUUID()}function _e(e,t){const n=t.name||e.name||V();N.set(n,e),t.onRollback&&G.set(n,t.onRollback),t.onConflict&&K.set(n,t.onConflict);const r=async(...s)=>{const{isOnline:a}=o.getState();if(t.onOptimistic?.(...s),t.reliability==="neverLose"){if(!a)return U(n,n,s,t);try{return await e(...s)}catch{return U(n,n,s,t)}}try{return await e(...s)}catch(i){throw t.onRollback?.(...s),i}};return Object.defineProperty(r,"id",{value:n,writable:!1}),Object.defineProperty(r,"config",{value:t,writable:!1}),r}async function U(e,t,n,r){const s=V(),a={id:s,actionId:e,actionName:t,args:n,queuedAt:Date.now(),retryCount:0,maxRetries:r.maxRetries??3,status:"pending",priority:r.priority??"normal"};await h().add(a),o.getState().addQueueItem(a);try{const i=ne();i&&"sync"in i&&await i.sync.register("eidos-queue-replay")}catch{}return{queued:!0,id:s,message:`"${t}" queued — will execute when online`}}function Ae(e){if(e instanceof Response)return e.status>=400&&e.status<500;if(typeof e=="object"&&e!==null){const t=e.status;if(typeof t=="number")return t>=400&&t<500}return!1}function Ie(e){return Math.min(2e3*2**e,3e5)*(.8+Math.random()*.4)}var I=!1;async function q(){const e=o.getState();if(!e.isOnline||I)return{attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};I=!0;try{return await Ce(e)}finally{I=!1}}async function Qe(e,t){const n=N.get(e.actionId);if(!n)return"skipped";try{await n(...e.args);const r=Date.now();return t.updateQueueItem(e.id,{status:"succeeded",completedAt:r}),await h().update(e.id,{status:"succeeded",completedAt:r}),setTimeout(()=>{t.removeQueueItem(e.id),h().remove(e.id)},3e3),"succeeded"}catch(r){if(Ae(r)){const a=K.get(e.actionId);if(a&&a(r,e.args)==="skip")return t.removeQueueItem(e.id),await h().remove(e.id),"conflicted"}const s=e.retryCount+1;if(s>=e.maxRetries)return t.updateQueueItem(e.id,{status:"failed",error:String(r),retryCount:s}),await h().update(e.id,{status:"failed",error:String(r),retryCount:s}),G.get(e.actionId)?.(...e.args),"failed";{const a=Date.now()+Ie(s);return t.updateQueueItem(e.id,{status:"pending",retryCount:s,nextRetryAt:a}),await h().update(e.id,{status:"pending",retryCount:s,nextRetryAt:a}),"retrying"}}}async function xe(e,t,n){if(e.length===0)return;const r=e.filter(a=>N.has(a.actionId));if(n.skipped+=e.length-r.length,r.length>0){t.batchUpdateQueueItems(r.map(a=>({id:a.id,update:{status:"replaying"}})));for(const a of r)h().update(a.id,{status:"replaying"})}const s=await Promise.allSettled(r.map(a=>Qe(a,t)));for(const a of s){const i=a.status==="fulfilled"?a.value:"failed";i==="skipped"?n.skipped++:i==="conflicted"?n.conflicted++:(n.attempted++,n[i]++)}}async function Ce(e){const t=await h().getPending(),n=Date.now(),r=t.filter(a=>a.retryCount<a.maxRetries&&(!a.nextRetryAt||a.nextRetryAt<=n)),s={attempted:0,succeeded:0,failed:0,retrying:0,skipped:0,conflicted:0};for(const a of["high","normal","low"])await xe(r.filter(i=>(i.priority??"normal")===a),e,s);return s}async function Pe(){await h().clear(),o.getState().hydrateQueue([])}function Y(){let e=o.getState().isOnline;const t=o.subscribe(()=>{const{isOnline:s}=o.getState(),a=s&&!e;e=s,a&&setTimeout(q,600)}),n=o.getState(),r=n.queue.some(s=>s.status==="pending");return n.isOnline&&r&&setTimeout(q,1200),t}var P=!1,D=null;async function J(e={}){if(typeof window>"u"||P)return;P=!0;const t=e.swPath??"/eidos-sw.js",n=e.autoReplay??!0;try{const r=await L();r.length>0&&o.getState().hydrateQueue(r)}catch{}try{await re(t)}catch{}ae(()=>{o.getState().isOnline&&setTimeout(q,200)}),n&&(D=Y())}function De(){D?.(),D=null,P=!1}var Q="@eidos:queue",Ne=class{constructor(e){this.storage=e}async readAll(){try{const e=await this.storage.getItem(Q);return e?JSON.parse(e):[]}catch{return[]}}async writeAll(e){await this.storage.setItem(Q,JSON.stringify(e))}async add(e){const t=await this.readAll();t.push(e),await this.writeAll(t)}async getAll(){return this.readAll()}async getPending(){return(await this.readAll()).filter(e=>e.status==="pending"||e.status==="failed")}async update(e,t){const n=await this.readAll(),r=n.findIndex(s=>s.id===e);r!==-1&&(n[r]={...n[r],...t}),await this.writeAll(n)}async remove(e){const t=await this.readAll();await this.writeAll(t.filter(n=>n.id!==e))}async clear(){await this.storage.removeItem(Q)}};function Te({children:e,swPath:t,autoReplay:n}){return(0,v.useEffect)(()=>{J({swPath:t,autoReplay:n})},[]),(0,j.jsx)(j.Fragment,{children:e})}function f(e){const t=e??(n=>n);return(0,v.useSyncExternalStore)(o.subscribe,()=>t(o.getState()))}function je(){return f()}function Me(){return f(e=>e.resources)}function Ue(e){return f(t=>t.resources[e])}function We(){return f(e=>e.queue)}function He(e){return f(t=>t.queue.find(n=>n.id===e))}function $e(){return{isOnline:f(e=>e.isOnline),swStatus:f(e=>e.swStatus),swError:f(e=>e.swError)}}function Le(){const[e,t,n,r]=f(s=>{let a=0,i=0,c=0;for(const u of s.queue)u.status==="pending"?a++:u.status==="failed"?i++:u.status==="replaying"&&c++;return`${a},${i},${c},${s.queue.length}`}).split(",");return{pending:+e,failed:+t,replaying:+n,total:+r}}function Be(e){const t=f(s=>s.queue.length),n=(0,v.useRef)(0),r=(0,v.useRef)(e);(0,v.useEffect)(()=>{r.current=e}),(0,v.useEffect)(()=>{n.current>0&&t===0&&r.current(),n.current=t},[t])}var Fe="1.1.0";function Ge(e,t){const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(e[r]!==t[r])return!1;return!0}function z(e,t){return Ge(e,t)}function E(e,t=Object.is){return{subscribe(n){let r=e(o.getState());return n(r),o.subscribe(()=>{const s=e(o.getState());t(r,s)||(r=s,n(s))})},getState(){return e(o.getState())}}}var Ke=E(e=>e),Ve=E(e=>e.queue),Ye=E(e=>({isOnline:e.isOnline,swStatus:e.swStatus,swError:e.swError}),z),Je=E(e=>{let t=0,n=0,r=0;for(const s of e.queue)s.status==="pending"?t++:s.status==="failed"?n++:s.status==="replaying"&&r++;return{pending:t,failed:n,replaying:r,total:e.queue.length}},z);function ze(e){return E(t=>t.resources[e])}function Xe(e){return E(t=>t.queue.find(n=>n.id===e))}exports.AsyncStorageQueueStorage=Ne;exports.EidosProvider=Te;exports.VERSION=Fe;exports._getQueueStorage=F;exports._resetEidos=De;exports.action=_e;exports.clearQueue=Pe;exports.eidosAction=Xe;exports.eidosQueue=Ve;exports.eidosQueueStats=Je;exports.eidosResource=ze;exports.eidosStatus=Ye;exports.eidosStore=Ke;exports.initEidos=J;exports.isBgSyncSupported=ie;exports.replayQueue=q;exports.resource=fe;exports.setOfflineSimulation=ce;exports.setQueryInvalidator=de;exports.setQueueStorage=qe;exports.subscribeReplayOnReconnect=Y;exports.useEidos=je;exports.useEidosAction=He;exports.useEidosOnDrain=Be;exports.useEidosQueue=We;exports.useEidosQueueStats=Le;exports.useEidosResource=Ue;exports.useEidosResources=Me;exports.useEidosStatus=$e;exports.useEidosStore=o;exports.warmCache=ye;
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=eidos.cjs.map
|
package/dist/idb.js
CHANGED
|
@@ -1,80 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
if (!
|
|
10
|
-
const
|
|
11
|
-
|
|
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
|
-
},
|
|
14
|
-
b =
|
|
15
|
-
},
|
|
11
|
+
}, r.onsuccess = () => {
|
|
12
|
+
b = r.result, s(r.result);
|
|
13
|
+
}, r.onerror = () => n(r.error);
|
|
16
14
|
});
|
|
17
15
|
}
|
|
18
|
-
async function
|
|
19
|
-
const n = await
|
|
20
|
-
return new Promise((
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
|
27
|
-
return new Promise((n,
|
|
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 = () =>
|
|
27
|
+
e.onsuccess = () => n(e.result), e.onerror = () => r(e.error);
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
async function P(s, n) {
|
|
33
|
-
const
|
|
34
|
-
return new Promise((
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
43
|
-
return new Promise((
|
|
44
|
-
const
|
|
45
|
-
|
|
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
|
|
49
|
-
const s = await
|
|
50
|
-
function n(
|
|
51
|
-
return new Promise((
|
|
52
|
-
const
|
|
53
|
-
d.onsuccess = (
|
|
54
|
-
const l =
|
|
55
|
-
l ? (m.push(l.value), l.continue()) :
|
|
56
|
-
}, d.onerror = () =>
|
|
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 [
|
|
60
|
-
|
|
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
|
|
66
|
-
const s = await
|
|
67
|
-
return new Promise((n,
|
|
68
|
-
const
|
|
69
|
-
|
|
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
|
-
|
|
78
|
-
P as idbUpdateQueueItem
|
|
80
|
+
j as idbQueueStorage
|
|
79
81
|
};
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=idb.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JSX
|
|
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>;
|
|
@@ -131,7 +131,7 @@ export declare interface EidosConfig {
|
|
|
131
131
|
* <App />
|
|
132
132
|
* </EidosProvider>
|
|
133
133
|
*/
|
|
134
|
-
export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps):
|
|
134
|
+
export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps): JSX.Element;
|
|
135
135
|
|
|
136
136
|
declare interface EidosProviderProps extends EidosConfig {
|
|
137
137
|
children: ReactNode;
|
|
@@ -185,20 +185,9 @@ export declare const eidosStatus: EidosReadable<{
|
|
|
185
185
|
swError: string | undefined;
|
|
186
186
|
}>;
|
|
187
187
|
|
|
188
|
-
export declare interface EidosStore extends EidosState {
|
|
188
|
+
export declare interface EidosStore extends EidosState, ResourceActions, QueueActions {
|
|
189
189
|
setOnline: (online: boolean) => void;
|
|
190
190
|
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
191
|
}
|
|
203
192
|
|
|
204
193
|
/** Full Eidos state snapshot. Prefer the narrower stores below. */
|
|
@@ -228,6 +217,17 @@ declare type Listener = () => void;
|
|
|
228
217
|
|
|
229
218
|
declare type QueryInvalidator = (queryKey: [string, string]) => void;
|
|
230
219
|
|
|
220
|
+
declare interface QueueActions {
|
|
221
|
+
addQueueItem: (item: ActionQueueItem) => void;
|
|
222
|
+
updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;
|
|
223
|
+
batchUpdateQueueItems: (updates: Array<{
|
|
224
|
+
id: string;
|
|
225
|
+
update: Partial<ActionQueueItem>;
|
|
226
|
+
}>) => void;
|
|
227
|
+
removeQueueItem: (id: string) => void;
|
|
228
|
+
hydrateQueue: (items: ActionQueueItem[]) => void;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
231
|
export declare interface QueuedResult {
|
|
232
232
|
readonly queued: true;
|
|
233
233
|
readonly id: string;
|
|
@@ -265,6 +265,12 @@ export declare function _resetEidos(): void;
|
|
|
265
265
|
|
|
266
266
|
export declare function resource<T = unknown>(url: string, config: ResourceConfig): ResourceHandle<T>;
|
|
267
267
|
|
|
268
|
+
declare interface ResourceActions {
|
|
269
|
+
registerResource: (url: string, entry: ResourceEntry) => void;
|
|
270
|
+
updateResource: (url: string, update: Partial<ResourceEntry>) => void;
|
|
271
|
+
unregisterResource: (url: string) => void;
|
|
272
|
+
}
|
|
273
|
+
|
|
268
274
|
export declare interface ResourceConfig {
|
|
269
275
|
/** Make this resource available when the device is offline. */
|
|
270
276
|
offline: boolean;
|
|
@@ -314,6 +320,22 @@ export declare function setQueueStorage(s: QueueStorage): void;
|
|
|
314
320
|
|
|
315
321
|
declare function _subscribe(listener: Listener): () => void;
|
|
316
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Subscribe to online/offline transitions and trigger replayQueue() on
|
|
325
|
+
* reconnect, plus replay any pending items left over from a previous session.
|
|
326
|
+
*
|
|
327
|
+
* Shared by the web (runtime.ts) and React Native (runtime-rn.ts) init paths.
|
|
328
|
+
*
|
|
329
|
+
* WHY subscribe to the store instead of window.addEventListener('online'):
|
|
330
|
+
* setOfflineSimulation() updates the store directly but never fires a real
|
|
331
|
+
* browser `online` event. Watching the store catches both:
|
|
332
|
+
* • Real network reconnects (sw-bridge updates store on window.online)
|
|
333
|
+
* • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))
|
|
334
|
+
*
|
|
335
|
+
* Returns an unsubscribe function.
|
|
336
|
+
*/
|
|
337
|
+
export declare function subscribeReplayOnReconnect(): () => void;
|
|
338
|
+
|
|
317
339
|
/** Full Eidos store — prefer the narrower hooks below for performance. */
|
|
318
340
|
export declare function useEidos(): EidosStore;
|
|
319
341
|
|
|
@@ -372,7 +394,7 @@ export declare const useEidosStore: {
|
|
|
372
394
|
setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
|
|
373
395
|
};
|
|
374
396
|
|
|
375
|
-
export declare const VERSION = "1.0
|
|
397
|
+
export declare const VERSION = "1.1.0";
|
|
376
398
|
|
|
377
399
|
/**
|
|
378
400
|
* Bulk-prefetch an array of resource handles concurrently, warming the cache
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { _getQueueStorage as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { useEidosStore as o } from "./store.js";
|
|
2
|
+
import { isBgSyncSupported as s, setOfflineSimulation as i } from "./sw-bridge.js";
|
|
3
|
+
import { resource as u, setQueryInvalidator as m, warmCache as d } from "./resource.js";
|
|
4
|
+
import { _getQueueStorage as p, setQueueStorage as c } from "./queue-storage.js";
|
|
5
|
+
import { action as n, clearQueue as S, replayQueue as E } from "./action.js";
|
|
6
|
+
import { subscribeReplayOnReconnect as g } from "./replay.js";
|
|
7
|
+
import { _resetEidos as R, initEidos as y } from "./runtime.js";
|
|
8
|
+
import { AsyncStorageQueueStorage as A } from "./async-storage-adapter.js";
|
|
9
|
+
import { EidosProvider as v } from "./react/Provider.js";
|
|
10
|
+
import { useEidos as _, useEidosAction as h, useEidosOnDrain as w, useEidosQueue as x, useEidosQueueStats as B, useEidosResource as C, useEidosResources as D, useEidosStatus as N } from "./react/hooks.js";
|
|
11
|
+
import { VERSION as V } from "./version.js";
|
|
12
|
+
import { eidosAction as k, eidosQueue as q, eidosQueueStats as z, eidosResource as F, eidosStatus as G, eidosStore as H } from "./stores.js";
|
|
12
13
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
A as AsyncStorageQueueStorage,
|
|
15
|
+
v as EidosProvider,
|
|
16
|
+
V as VERSION,
|
|
17
|
+
p as _getQueueStorage,
|
|
18
|
+
R as _resetEidos,
|
|
19
|
+
n as action,
|
|
20
|
+
S as clearQueue,
|
|
21
|
+
k as eidosAction,
|
|
22
|
+
q as eidosQueue,
|
|
23
|
+
z as eidosQueueStats,
|
|
24
|
+
F as eidosResource,
|
|
25
|
+
G as eidosStatus,
|
|
26
|
+
H as eidosStore,
|
|
27
|
+
y as initEidos,
|
|
28
|
+
s as isBgSyncSupported,
|
|
29
|
+
E as replayQueue,
|
|
30
|
+
u as resource,
|
|
31
|
+
i as setOfflineSimulation,
|
|
32
|
+
m as setQueryInvalidator,
|
|
32
33
|
c as setQueueStorage,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
g as subscribeReplayOnReconnect,
|
|
35
|
+
_ as useEidos,
|
|
36
|
+
h as useEidosAction,
|
|
37
|
+
w as useEidosOnDrain,
|
|
38
|
+
x as useEidosQueue,
|
|
39
|
+
B as useEidosQueueStats,
|
|
40
|
+
C as useEidosResource,
|
|
41
|
+
D as useEidosResources,
|
|
42
|
+
N as useEidosStatus,
|
|
43
|
+
o as useEidosStore,
|
|
44
|
+
d as warmCache
|
|
43
45
|
};
|
|
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/query.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _tanstack_react_query = require("@tanstack/react-query");
|
|
3
|
+
let _sweidos_eidos = require("@sweidos/eidos");
|
|
4
|
+
//#region src/query.ts
|
|
5
|
+
/**
|
|
6
|
+
* @sweidos/eidos/query
|
|
7
|
+
*
|
|
8
|
+
* TanStack Query (React Query) integration for Eidos.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Register once (e.g. alongside new QueryClient())
|
|
13
|
+
* import { withEidosQueryClient } from '@sweidos/eidos/query'
|
|
14
|
+
* withEidosQueryClient(queryClient)
|
|
15
|
+
*
|
|
16
|
+
* // In components
|
|
17
|
+
* import { useEidosQuery, useEidosMutation } from '@sweidos/eidos/query'
|
|
18
|
+
*
|
|
19
|
+
* const { data, isPending } = useEidosQuery(products)
|
|
20
|
+
*
|
|
21
|
+
* const mutation = useEidosMutation(createOrder, {
|
|
22
|
+
* invalidates: [products],
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
var _globalClient = null;
|
|
27
|
+
/**
|
|
28
|
+
* Register a QueryClient with Eidos.
|
|
29
|
+
*
|
|
30
|
+
* Once called, `handle.invalidate()` will also call
|
|
31
|
+
* `queryClient.invalidateQueries({ queryKey: ['eidos', url] })`, keeping
|
|
32
|
+
* TanStack Query's cache in sync with Eidos's Cache Storage.
|
|
33
|
+
*
|
|
34
|
+
* Call this once, before rendering — e.g. alongside `new QueryClient()`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const queryClient = new QueryClient()
|
|
39
|
+
* withEidosQueryClient(queryClient)
|
|
40
|
+
*
|
|
41
|
+
* // Wrap your app as usual
|
|
42
|
+
* <QueryClientProvider client={queryClient}>
|
|
43
|
+
* <App />
|
|
44
|
+
* </QueryClientProvider>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function withEidosQueryClient(client) {
|
|
48
|
+
_globalClient = client;
|
|
49
|
+
(0, _sweidos_eidos.setQueryInvalidator)((queryKey) => {
|
|
50
|
+
client.invalidateQueries({ queryKey });
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wraps `useQuery` with Eidos-smart defaults.
|
|
55
|
+
*
|
|
56
|
+
* Key differences from plain `useQuery`:
|
|
57
|
+
* - `networkMode: 'always'` — Eidos owns offline logic; queries run even when
|
|
58
|
+
* `navigator.onLine` is false (the SW cache or IndexedDB serves the data).
|
|
59
|
+
* - `retry: false` — Eidos handles retries at the SW / replay layer; TQ
|
|
60
|
+
* retrying on top would double-fire and fight Eidos's backoff.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const products = resource('/api/products', { offline: true })
|
|
65
|
+
*
|
|
66
|
+
* // Automatically typed as UseQueryResult<Product[]>
|
|
67
|
+
* const { data, isPending, isError } = useEidosQuery<Product[]>(products)
|
|
68
|
+
*
|
|
69
|
+
* // Override any TQ option
|
|
70
|
+
* const { data } = useEidosQuery(products, { staleTime: 30_000 })
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function useEidosQuery(handle, options) {
|
|
74
|
+
return (0, _tanstack_react_query.useQuery)({
|
|
75
|
+
networkMode: "always",
|
|
76
|
+
retry: false,
|
|
77
|
+
...options,
|
|
78
|
+
...handle.query()
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Wraps `useMutation` for a single-argument Eidos action handle.
|
|
83
|
+
*
|
|
84
|
+
* Key differences from plain `useMutation`:
|
|
85
|
+
* - `networkMode: 'always'` — action executes (or queues) even when offline.
|
|
86
|
+
* - `invalidates` — shorthand to clear resource caches on success. Triggers
|
|
87
|
+
* both Eidos Cache Storage and TanStack Query invalidation (requires
|
|
88
|
+
* `withEidosQueryClient` for the TQ half).
|
|
89
|
+
* - Return type is `TData | QueuedResult`. Narrow with `'queued' in data` to
|
|
90
|
+
* detect the offline-queued case.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const mutation = useEidosMutation(createOrder, {
|
|
95
|
+
* invalidates: [products],
|
|
96
|
+
* onSuccess(data) {
|
|
97
|
+
* if ('queued' in data) toast('Saved offline — will sync when back online')
|
|
98
|
+
* else toast(`Order #${data.id} created!`)
|
|
99
|
+
* },
|
|
100
|
+
* })
|
|
101
|
+
*
|
|
102
|
+
* // Trigger
|
|
103
|
+
* mutation.mutate({ productId: 1, qty: 2 })
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
function useEidosMutation(handle, options) {
|
|
107
|
+
let contextClient = null;
|
|
108
|
+
try {
|
|
109
|
+
contextClient = (0, _tanstack_react_query.useQueryClient)();
|
|
110
|
+
} catch {}
|
|
111
|
+
const { invalidates, onSuccess, ...rest } = options ?? {};
|
|
112
|
+
return (0, _tanstack_react_query.useMutation)({
|
|
113
|
+
networkMode: "always",
|
|
114
|
+
...rest,
|
|
115
|
+
mutationFn: (arg) => handle(arg),
|
|
116
|
+
onSuccess: async (...args) => {
|
|
117
|
+
const [data] = args;
|
|
118
|
+
if (invalidates?.length) {
|
|
119
|
+
await Promise.all(invalidates.map((h) => h.invalidate()));
|
|
120
|
+
if (!_globalClient && contextClient) invalidates.forEach((h) => {
|
|
121
|
+
contextClient.invalidateQueries({ queryKey: h.query().queryKey });
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (onSuccess) await onSuccess(...args);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
exports.useEidosMutation = useEidosMutation;
|
|
130
|
+
exports.useEidosQuery = useEidosQuery;
|
|
131
|
+
exports.withEidosQueryClient = withEidosQueryClient;
|