@symbo.ls/fetch 3.14.661 → 3.14.662
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/CHANGELOG.md +36 -0
- package/README.md +221 -35
- package/adapters/rest.js +165 -26
- package/dist/cjs/adapters/rest.js +1 -1
- package/dist/cjs/early.js +1 -0
- package/dist/cjs/earlyScript.js +53 -0
- package/dist/cjs/index.js +1 -1
- package/dist/esm/adapters/rest.js +1 -1
- package/dist/esm/early.js +1 -0
- package/dist/esm/earlyScript.js +53 -0
- package/dist/esm/index.js +1 -1
- package/early.js +160 -0
- package/earlyScript.js +204 -0
- package/index.js +490 -119
- package/package.json +9 -2
package/dist/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isArray as Q,isFunction as l,isObject as V,isString as H,exec as Pt}from"@symbo.ls/utils";const zt=["select","rpc","insert","update","upsert","delete","subscribe"],jt={rest:()=>import("./adapters/rest.js"),local:()=>import("./adapters/local.js")},ut=Symbol.for("@symbo.ls/fetch:CUSTOM_ADAPTERS"),X=(()=>{if(typeof globalThis<"u"&&globalThis[ut])return globalThis[ut];const t={};if(typeof globalThis<"u")try{return Object.defineProperty(globalThis,ut,{value:t,writable:!1,configurable:!1,enumerable:!1}),globalThis[ut]}catch{}return t})(),lt=Symbol.for("@symbo.ls/fetch:PENDING_ADAPTERS"),bt=(()=>{if(typeof globalThis<"u"&&globalThis[lt])return globalThis[lt];const t={};if(typeof globalThis<"u")try{return Object.defineProperty(globalThis,lt,{value:t,writable:!1,configurable:!1,enumerable:!1}),globalThis[lt]}catch{}return t})(),Bt=5e3,Gt=t=>new Promise(e=>{if(X[t])return e(X[t]);const s=bt[t]||=[];let r=!1;const o=()=>{r||(r=!0,e(X[t]||null))};s.push(o),setTimeout(o,Bt)}),re=(t,e)=>{X[t]=e;const s=bt[t];if(s&&s.length){bt[t]=[];for(const r of s)r()}},ne=t=>{const e={};for(const s of zt)t[s]&&(e[s]=t[s]);return e.name=t.name||"custom",e},Qt=async t=>{if(!t)return null;if(typeof t.select=="function")return t;const{adapter:e,...s}=t,r=typeof e=="string"?e:typeof t=="string"?t:null;if(!r)return null;let o=X[r]||jt[r];if(!o&&(o=await Gt(r),!o))throw new Error(`Unknown db adapter: "${r}". Use registerAdapter() for adapters with optional deps.`);const n=await o();if(!n||typeof n.setup!="function")throw new Error(`Adapter "${r}" loader returned a module without a setup() function. Got keys: [${Object.keys(n||{}).join(", ")}]. Likely a bundler chunk-resolution issue.`);return n.setup(s)},Z=t=>{if(!t)return 0;if(typeof t=="number")return t;const e=t.match(/^(\d+)(ms|s|m|h)$/);if(!e)return 0;const s=parseInt(e[1]),r=e[2];return r==="ms"?s:r==="s"?s*1e3:r==="m"?s*6e4:r==="h"?s*36e5:0},T=new Map,U=new Map,W=new Map,Wt=(t,e,s)=>{if(t.cache?.key)return t.cache.key;const r=Kt(t.params),o=s?`${s}:`:"",n=`${t.from}:${t.method}:${o}${JSON.stringify(r||"")}`;return e?`${n}:${e}`:n},tt=t=>T.get(t)||null,et=(t,e,s)=>{const r=T.get(t),o={data:e,error:s,time:Date.now(),stale:!1};T.set(t,o);const n=U.get(t);if(n)for(const a of n)l(a)&&a(o);return o},st=t=>{if(t){const e=T.get(t);e&&(e.stale=!0);return}for(const[,e]of T)e.stale=!0},dt=t=>{if(t){T.delete(t);return}T.clear()},oe=(t,e)=>(U.has(t)||U.set(t,new Set),U.get(t).add(e),()=>{const s=U.get(t);s&&(s.delete(e),s.size===0&&U.delete(t))}),wt=t=>!t&&t!==!1?{staleTime:6e4,gcTime:3e5}:t===!1?null:t===!0?{staleTime:6e4,gcTime:3e5}:typeof t=="number"?{staleTime:t,gcTime:Math.max(t*5,3e5)}:H(t)?{staleTime:Z(t),gcTime:3e5}:{staleTime:Z(t.stale||t.staleTime)||6e4,gcTime:Z(t.gc||t.gcTime||t.expire)||3e5,key:t.key};let ht=null;const Yt=()=>{ht||(ht=setInterval(()=>{const t=Date.now();for(const[e,s]of T){const r=U.get(e);!(r&&r.size>0)&&t-s.time>(s.gcTime||3e5)&&T.delete(e)}},3e4),ht.unref&&ht.unref())};Yt();const At=3,Tt=t=>Math.min(1e3*2**t,3e4),Lt=t=>{const e=t.retry;return e===!1?{count:0}:e===!0||e===void 0?{count:At,delay:Tt}:typeof e=="number"?{count:e,delay:Tt}:{count:e.count??At,delay:l(e.delay)?e.delay:typeof e.delay=="number"?()=>e.delay:Tt}},Ot=async(t,e)=>{const{count:s,delay:r}=e;let o;for(let n=0;n<=s;n++)try{const a=await t();if(a?.error){if(o=a.error,n<s){const c=l(r)?r(n,o):r;await new Promise(i=>setTimeout(i,c));continue}return a}return a}catch(a){if(o=a,n<s){const c=l(r)?r(n,a):r;await new Promise(i=>setTimeout(i,c));continue}throw a}return{data:null,error:o}},Y={focus:new Set,online:new Set};let Rt=!1;const xt=()=>{if(Rt||typeof window>"u")return;Rt=!0;const t=()=>{for(const s of Y.focus)s()},e=()=>{for(const s of Y.online)s()};window.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&t()}),window.addEventListener("focus",t),window.addEventListener("online",e)},kt=(t,e)=>{const s=e.__ref;if(t===!0)return{from:s.__state||e.key,method:"select",query:!0,on:"create"};if(H(t))return{from:t,method:"select",query:!0,on:"create"};if(V(t)){const r={...t};return r.from=r.from||s.__state||e.key,r.method=r.method||"select",r.on=r.on||"create",r.query===void 0&&(r.query=!0),r}return null},Kt=t=>(!t||l(t),t),pt=(t,e)=>{const s=t?.state?.root||e?.state?.root;if(s?.lang)return s.lang},mt=(t,e)=>{const r=(t?.state?.root||e?.state?.root)?.__fetchScope;return r?String(r):void 0},$t=(t,e,s)=>{let r;if(!t)r=void 0;else if(l(t))r=t(e,e.state);else{r={};for(const o in t){const n=t[o];r[o]=l(n)?n(e,e.state):n}}return r},vt=async(t,e)=>{if(!t||t.__authInitialized||(t.__authInitialized=!0,!t.getSession))return;const s=(r,o)=>{const n=e.state?.root;n?.update&&n.update({auth:{user:r,session:o,loading:!1}})};try{const r=await t.getSession();s(r?.user||null,r)}catch(r){console.warn("[fetch] Failed to restore auth session:",r.message)}t.onAuthStateChange&&t.onAuthStateChange((r,o)=>{s(o?.user||null,o)})},Ft=async(t,e)=>{if(l(t.select))return t.auth!==!1&&await vt(t,e),t;if(t.__resolved)return t.auth!==!1&&await vt(t.__resolved,e),t.__resolved;if(t.__resolving)return t.__resolving;t.__resolving=Qt(t);const s=await t.__resolving;return delete t.__resolving,s?(s.auth=t.auth,s.url=t.url,s.key=t.key,t.__resolved=s,e.fetch=s,t.auth!==!1&&await vt(s,e),s):null},E=(t,e,...s)=>{const r=l(t[e])?t[e]:null;r&&r.call(t,...s,t,t.state,t.context)},A=(t,e,s,...r)=>{const o=t&&l(t[s])?t[s]:null;o&&o.call(e,...r,e,e.state,e.context)},rt=t=>{const e={},s=t.node;if(!s)return e;if(s.tagName==="FORM"){const o=new FormData(s);for(const[n,a]of o.entries())e[n]=a;return e}const r=s.querySelectorAll("input, textarea, select");for(let o=0;o<r.length;o++){const n=r[o],a=n.name||n.getAttribute("name");a&&(n.type==="checkbox"?e[a]=n.checked:n.type==="file"?e[a]=n.files:e[a]=n.value)}return e},Et=t=>t.responseType==="stream"||t.responseType==="response",Jt=["Blob","File","ArrayBuffer","ReadableStream","Response"],Dt=t=>t!==null&&typeof t=="object"&&(ArrayBuffer.isView(t)||Jt.includes(Object.prototype.toString.call(t).slice(8,-1))),q=(t,e,s,r={})=>{if(!s&&Dt(e))return;const o=s&&s.startsWith("~/"),n=o&&t.state?.root||t.state;if(n?.update)if(o){const a=s.slice(2);e&&typeof e=="object"&&!Array.isArray(e)&&!Dt(e)&&a in e?n.update(e,{preventFetch:!0,...r}):n.update({[a]:e},{preventFetch:!0,...r})}else{const a=s?{[s]:e}:e;n.update(a,{preventFetch:!0,...r})}},L=(t,e)=>{const s=t?.__ref;s&&(s.__fetchStatus=e,s.__fetching=e.isFetching,s.__fetchError=e.error)},N=async(t,e,s,r={})=>{const o=s?.fetch;if(!o||!e||!e.__ref)return;const n=e.__ref,a=mt(e,s);(!n.__fetchScopeOwner||n.__fetchScopeOwner.scope!==a)&&(n.__fetchScopeOwner={scope:a});const c=n.__fetchScopeOwner,i=()=>e.__ref===n&&n.__fetchScopeOwner===c&&mt(e,s)===a;if(t.enabled===!1||l(t.enabled)&&!t.enabled(e,e.state)||t.skip===!0||l(t.skip)&&t.skip(e,e.state))return;const h=await Ft(o,s);if(!h||!i())return;const{from:g,method:S,query:R,params:x,cache:nt,transform:D,single:z,auth:ot,fields:j,as:k,limit:I,offset:$,order:b,headers:K,baseUrl:C,responseType:B,page:p,cursor:P,getNextPageParam:J,getPreviousPageParam:it,infinite:at,placeholderData:v,initialData:F,select:m,keepPreviousData:M}=t;let G=H(m)?m:void 0;if(R&&l(e.getQuery)){const d=e.getQuery(h.name||"paths");d&&(G=d.select||d.length&&d.join(",")||G)}const ft=$t(x,e,s),ct=Et(t)?null:wt(nt),Nt=Lt(t),_t=pt(e,s),It=_t?`:${_t}`:"",Ut=a?`${a}:`:"",_=ct?nt?.key||`${g}:${S}:${Ut}${JSON.stringify(ft||"")}${at?":infinite":""}${p?":p"+JSON.stringify(p):""}${It}`:null;if(!n.__fetchInitialized&&_&&(n.__fetchInitialized=!0,F!==void 0)){const d=l(F)?F():F;et(_,d,null)}if((S==="select"||S==="rpc")&&_&&ct){const d=tt(_);if(d&&!d.error){const u=Date.now()-d.time,y=d.stale||u>(ct.staleTime||0);let f=d.data;if(l(m)&&(f=m(f,e,e.state)),D&&(f=D(f,e,e.state)),!i())return;if(q(e,f,k),!y){L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"});return}}else if(v!==void 0){const u=l(v)?v(e,e.state):v;u!==void 0&&q(e,u,k)}}if(_&&W.has(_)&&!r.force){const d=W.get(_);try{const u=await Promise.race([d,new Promise((y,f)=>setTimeout(()=>f("DEDUP_TIMEOUT"),15e3))]);if(!i())return;if(u?.data!==void 0){let y=u.data;if(l(m)&&(y=m(y,e,e.state)),D&&(y=D(y,e,e.state)),!i())return;q(e,y,k),E(e,"onFetchComplete",y),A(t,e,"onFetchComplete",y)}}catch{if(!i())return}return}const qt=!tt(_)?.data;L(e,{isFetching:!0,isLoading:qt,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),E(e,"onFetchStart"),A(t,e,"onFetchStart");const O=s?.analyze?.emitNetwork,yt=O?typeof performance<"u"?performance.now():Date.now():0;O&&O({phase:"start",mode:"query",from:g,method:S,cacheKey:_,lang:_t});const gt=Ot(async()=>{if(!i())return;if(ot!==!1&&h.auth!==!1&&h.getSession){const f=await h.getSession();if(!i())return;if(ot===!0&&!f){const w={message:"Not authenticated"};return L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:w,status:"error",fetchStatus:"idle"}),E(e,"onFetchError",w),A(t,e,"onFetchError",w),{data:null,error:w}}}const d=h[S];if(!l(d))return{data:null,error:{message:`Method "${S}" not found on adapter`}};const u={from:g,select:G,params:ft,single:z,limit:I,offset:$,order:b,headers:K,baseUrl:C};B!==void 0&&(u.responseType=B);const y=pt(e,s);if(y&&(u.headers={...u.headers,"Accept-Language":y}),p!==void 0){if(V(p))p.offset!==void 0&&(u.offset=p.offset),p.limit!==void 0&&(u.limit=p.limit),p.cursor!==void 0&&(u.params={...u.params,cursor:p.cursor});else if(typeof p=="number"){const f=t.pageSize||I||20;u.offset=(p-1)*f,u.limit=f}}if(P!==void 0&&(u.params={...u.params,cursor:P}),S==="insert"||S==="update"||S==="upsert"){let f;if(j===!0||t.on==="submit")f=rt(e);else if(Q(j)){const w=rt(e);f={};for(const St of j)f[St]=w[St]}else e.state?.parse?f=e.state.parse():V(e.state)&&(f={...e.state});D&&(f=D(f,e,e.state)),u.data=f}if(S==="rpc"){let f=ft;if(t.on==="submit"){const w=rt(e);f=t.transformParams?t.transformParams(w,e,e.state):w}u.params=f}if(S==="subscribe"){const f=h.subscribe({from:g,params:ft,on:t.subscribeOn},(w,St,ee)=>{i()&&(q(e,w,k),E(e,"onFetchComplete",w),A(t,e,"onFetchComplete",w))});return i()?(n.__unsubscribe=f,{data:null,error:null,subscribed:!0}):(l(f)&&f(),{data:null,error:null,subscribed:!0})}return d(u)},Nt);_&&W.set(_,gt);try{const d=await gt;if(!i()||d?.subscribed)return;const{data:u,error:y}=d||{};if(y){_&&et(_,null,y),L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:y,status:"error",fetchStatus:"idle"}),E(e,"onFetchError",y),A(t,e,"onFetchError",y),O&&O({phase:"error",mode:"query",from:g,method:S,cacheKey:_,error:y.message||y,durationMs:(typeof performance<"u"?performance.now():Date.now())-yt});return}if(u!==void 0){if((S==="select"||S==="rpc")&&_){const w=et(_,u,null);w.gcTime=ct?.gcTime||3e5}let f=u;if(at&&Q(u)&&(n.__pages||(n.__pages=[]),r.direction==="previous"?n.__pages.unshift(u):n.__pages.push(u),f=n.__pages.flat(),J&&(n.__nextPageParam=J(u,n.__pages),n.__hasNextPage=n.__nextPageParam!=null),it&&(n.__prevPageParam=it(u,n.__pages),n.__hasPreviousPage=n.__prevPageParam!=null)),l(m)&&(f=m(f,e,e.state)),D&&(f=D(f,e,e.state)),!i())return;(!M||f!==void 0)&&q(e,f,k),L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),E(e,"onFetchComplete",f),A(t,e,"onFetchComplete",f),O&&O({phase:"success",mode:"query",ok:!0,from:g,method:S,cacheKey:_,durationMs:(typeof performance<"u"?performance.now():Date.now())-yt})}}catch(d){if(!i())return;L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:d,status:"error",fetchStatus:"idle"}),E(e,"onFetchError",d),A(t,e,"onFetchError",d),O&&O({phase:"error",mode:"query",from:g,method:S,cacheKey:_,error:d.message||String(d),durationMs:(typeof performance<"u"?performance.now():Date.now())-yt})}finally{_&&W.get(_)===gt&&W.delete(_)}},Vt=(t,e,s)=>{const r=t.on,o=e.__ref;o.__fetchListeners||(o.__fetchListeners=[]);const n=t.__runner||N;if(r==="submit"){const a=i=>{i.preventDefault(),n(t,e,s)},c=e.node;c&&(c.addEventListener("submit",a),o.__fetchListeners.push(()=>c.removeEventListener("submit",a)))}else if(r==="click"){const a=()=>n(t,e,s),c=e.node;c&&(c.addEventListener("click",a),o.__fetchListeners.push(()=>c.removeEventListener("click",a)))}else r==="stateChange"&&(o.__fetchOnStateChange=()=>n(t,e,s))},Ht=(t,e,s)=>{const r=e.__ref;r.__fetchCleanups||(r.__fetchCleanups=[]),xt();const o=t.refetchInterval;if(o){const i=typeof o=="number"?o:Z(o);if(i>0){const h=setInterval(()=>{(t.refetchIntervalInBackground||typeof document>"u"||document.visibilityState==="visible")&&N(t,e,s,{force:!0})},i);r.__fetchCleanups.push(()=>clearInterval(h))}}const n=Et(t),a=t.refetchOnWindowFocus===void 0?!n:t.refetchOnWindowFocus!==!1,c=t.refetchOnReconnect===void 0?!n:t.refetchOnReconnect!==!1;if(a){const i=()=>{const h=pt(e,s),g=mt(e,s),S=Wt(t,h,g),R=tt(S),x=wt(t.cache);(!R||R.stale||Date.now()-R.time>(x?.staleTime||0))&&N(t,e,s,{force:!0})};Y.focus.add(i),r.__fetchCleanups.push(()=>Y.focus.delete(i))}if(c){const i=()=>N(t,e,s,{force:!0});Y.online.add(i),r.__fetchCleanups.push(()=>Y.online.delete(i))}},Xt=(t,e,s)=>{if(!e.optimistic)return null;const r=t.__ref,o=e.as,n=t.state?.parse?t.state.parse():V(t.state)?{...t.state}:t.state;r.__optimisticSnapshot=n;const a=l(e.optimistic)?e.optimistic(s,n,t):e.optimistic;return a!==void 0&&q(t,a,o),n},Ct=(t,e)=>{const s=t.__ref,r=s.__optimisticSnapshot;r!==void 0&&(q(t,r,e.as),delete s.__optimisticSnapshot)},Zt=async(t,e,s)=>{const r=s?.fetch;if(!r)return;const o=await Ft(r,s);if(!o)return;const n=e.__ref,{method:a,from:c,fields:i,transform:h,as:g,on:S,auth:R,headers:x,baseUrl:nt,responseType:D,invalidates:z,optimistic:ot,onMutate:j,onSuccess:k,onError:I,onSettled:$}=t;let b;if(i===!0||S==="submit")b=rt(e);else if(Q(i)){const p=rt(e);b={};for(const P of i)b[P]=p[P]}else e.state?.parse?b=e.state.parse():V(e.state)&&(b={...e.state});h&&(b=h(b,e,e.state)),l(j)&&j(b,e,e.state);const K=ot?Xt(e,t,b):null;n.__fetching=!0,L(e,{isFetching:!0,isLoading:!1,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),E(e,"onFetchStart"),A(t,e,"onFetchStart");const C=s?.analyze?.emitNetwork,B=C?typeof performance<"u"?performance.now():Date.now():0;C&&C({phase:"start",mode:"mutation",from:c,method:a});try{if(R!==!1&&o.auth!==!1&&o.getSession){const m=await o.getSession();if(R===!0&&!m){K!==void 0&&Ct(e,t);const M={message:"Not authenticated"};E(e,"onFetchError",M),A(t,e,"onFetchError",M);return}}const p=o[a];if(!l(p))return;const P={from:c,data:b,headers:x,baseUrl:nt};D!==void 0&&(P.responseType=D),t.params&&(P.params=$t(t.params,e,s));const J=pt(e,s);J&&(P.headers={...P.headers,"Accept-Language":J});const it=Lt(t),at=await Ot(()=>p(P),it),{data:v,error:F}=at||{};if(F){K!==void 0&&Ct(e,t),L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:F,status:"error",fetchStatus:"idle"}),E(e,"onFetchError",F),l(I)&&I(F,b,e),l($)&&$(null,F,b,e),C&&C({phase:"error",mode:"mutation",from:c,method:a,error:F.message||String(F),durationMs:(typeof performance<"u"?performance.now():Date.now())-B});return}if(delete n.__optimisticSnapshot,v!==void 0&&(g||!Dt(v))){const m=g?{[g]:v}:v;e.state?.update&&e.state.update(m,{preventFetch:!0})}if(L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),E(e,"onFetchComplete",v),A(t,e,"onFetchComplete",v),l(k)&&k(v,b,e),l($)&&$(v,null,b,e),C&&C({phase:"success",mode:"mutation",ok:!0,from:c,method:a,durationMs:(typeof performance<"u"?performance.now():Date.now())-B}),z){const m=Q(z)?z:[z];for(const M of m)if(M===!0||M==="*")for(const[G]of T)G.startsWith(c+":")&&st(G);else st(M)}}catch(p){K!==void 0&&Ct(e,t),L(e,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:p,status:"error",fetchStatus:"idle"}),E(e,"onFetchError",p),A(t,e,"onFetchError",p),l(I)&&I(p,b,e),l($)&&$(null,p,b,e),C&&C({phase:"error",mode:"mutation",from:c,method:a,error:p.message||String(p),durationMs:(typeof performance<"u"?performance.now():Date.now())-B})}finally{n.__fetching=!1}},Mt=(t,e,s,r)=>{if(!t||!r?.fetch)return;const n=Pt(t,e);if(!n)return;const a=Q(n)?n.map(i=>kt(Pt(i,e),e)).filter(Boolean):[kt(n,e)].filter(Boolean);for(const i of a){const h=i.method==="insert"||i.method==="update"||i.method==="upsert"||i.method==="delete",g=h?Zt:N;i.on==="create"?g(i,e,r):Promise.resolve().then(()=>{Vt({...i,__runner:g},e,r)}),!h&&i.on==="create"&&(i.refetchInterval||i.refetchOnWindowFocus!==!1||i.refetchOnReconnect!==!1)&&Ht(i,e,r)}const c=e.__ref;c.refetch=i=>{for(const h of a)N(h,e,r,{force:!0,...i})},c.fetchNextPage=()=>{const i=a[0];if(!i||!i.infinite)return;const h=c.__nextPageParam;if(h==null)return;const g={...i,cursor:h};N(g,e,r,{direction:"next",force:!0})},c.fetchPreviousPage=()=>{const i=a[0];if(!i||!i.infinite)return;const h=c.__prevPageParam;if(h==null)return;const g={...i,cursor:h};N(g,e,r,{direction:"previous",force:!0})}},ie={invalidateQueries:t=>{if(!t){st();return}if(H(t))for(const[e]of T)(e.startsWith(t)||e.includes(t))&&st(e);else if(Q(t)){const e=t.join(":");for(const[s]of T)s.includes(e)&&st(s)}},removeQueries:t=>{if(!t){dt();return}if(H(t))for(const[e]of T)(e.startsWith(t)||e.includes(t))&&dt(e)},getQueryData:t=>tt(t)?.data??void 0,setQueryData:(t,e)=>{const s=tt(t),r=l(e)?e(s?.data):e;et(t,r,null)},prefetchQuery:async(t,e)=>{const s=e?.fetch;if(!s)return;const r=await Ft(s,e);if(!r)return;const o=t.cache?.key||`${t.from}:${t.method||"select"}:${JSON.stringify(t.params||"")}`,n=r[t.method||"select"];if(!l(n))return;const a={from:t.from,params:t.params,limit:t.limit,offset:t.offset,order:t.order,single:t.single,headers:t.headers,baseUrl:t.baseUrl};t.responseType!==void 0&&(a.responseType=t.responseType);const c=await n(a);if(c?.data!==void 0&&!c?.error&&!Et(t)){const i=et(o,c.data,null);i.gcTime=wt(t.cache)?.gcTime||3e5}return c},getCache:()=>T,clear:()=>dt(),dropNonLang:t=>{if(!t)return;const e=`:${t}`;for(const[s]of T)s.match(/:([a-z]{2,3})$/i)&&(s.endsWith(e)||dt(s))}},ae={name:"fetch",create(t){const e=t.fetch;e&&(t.parent||W.clear(),Mt(e,t,t.state,t.context))}};var fe=Mt;export{ne as createAdapter,fe as default,Mt as executeFetch,ae as fetchPlugin,vt as initAdapterAuth,Z as parseDuration,ie as queryClient,re as registerAdapter,Qt as resolveDb};
|
|
1
|
+
import{isArray as Q,isFunction as l,isObject as Z,isString as _e,exec as Ge}from"@symbo.ls/utils";import{createEffect as it}from"@symbo.ls/signal";import{dropEarly as ut}from"./early.js";const ct=["select","rpc","insert","update","upsert","delete","subscribe"],Ke={rest:()=>import("./adapters/rest.js"),local:()=>import("./adapters/local.js")},ye=Symbol.for("@symbo.ls/fetch:CUSTOM_ADAPTERS"),ee=(()=>{if(typeof globalThis<"u"&&globalThis[ye])return globalThis[ye];const e={};if(typeof globalThis<"u")try{return Object.defineProperty(globalThis,ye,{value:e,writable:!1,configurable:!1,enumerable:!1}),globalThis[ye]}catch{}return e})(),Se=Symbol.for("@symbo.ls/fetch:PENDING_ADAPTERS"),Ce=(()=>{if(typeof globalThis<"u"&&globalThis[Se])return globalThis[Se];const e={};if(typeof globalThis<"u")try{return Object.defineProperty(globalThis,Se,{value:e,writable:!1,configurable:!1,enumerable:!1}),globalThis[Se]}catch{}return e})(),ft=5e3,lt=e=>new Promise(t=>{if(ee[e])return t(ee[e]);const r=Ce[e]||=[];let s=!1;const o=()=>{s||(s=!0,t(ee[e]||null))};r.push(o),setTimeout(o,ft)}),Ct=(e,t)=>{ee[e]=t;const r=Ce[e];if(r&&r.length){Ce[e]=[];for(const s of r)s()}},Mt=e=>typeof e=="string"&&!!(ee[e]||Ke[e]),Lt=e=>{const t={};for(const r of ct)e[r]&&(t[r]=e[r]);return t.name=e.name||"custom",t},dt=async e=>{if(!e)return null;if(typeof e.select=="function")return e;const{adapter:t,...r}=e,s=typeof t=="string"?t:typeof e=="string"?e:null;if(!s)return null;let o=ee[s]||Ke[s];if(!o&&(o=await lt(s),!o))throw new Error(`Unknown db adapter: "${s}". Use registerAdapter() for adapters with optional deps.`);const n=await o();if(!n||typeof n.setup!="function")throw new Error(`Adapter "${s}" loader returned a module without a setup() function. Got keys: [${Object.keys(n||{}).join(", ")}]. Likely a bundler chunk-resolution issue.`);return n.setup(r)},se=e=>{if(!e)return 0;if(typeof e=="number")return e;const t=e.match(/^(\d+)(ms|s|m|h)$/);if(!t)return 0;const r=parseInt(t[1]),s=t[2];return s==="ms"?r:s==="s"?r*1e3:s==="m"?r*6e4:s==="h"?r*36e5:0},v=new Map,x=new Map,R=new Map;let I=0;const q=new Map,Me=new WeakMap,Le=(e,t,r,s,o=e.page,n=e.cursor)=>{if(e.cache?.key)return e.cache.key;const{from:i,method:u,infinite:a}=e,d=r?`${r}:`:"",_=s?`:${s}`:"",h=o?":p"+JSON.stringify(o):"",D=n!=null?":c"+JSON.stringify(n):"";return`${i}:${u}:${d}${JSON.stringify(t||"")}${a?":infinite":""}${h}${D}${_}`},ne=e=>v.get(e)||null,oe=(e,t,r,s=++I,o=!1)=>{const n={data:t,error:r,time:Date.now(),stale:o,seq:s};v.set(e,n);const i=x.get(e);if(i)for(const u of i)l(u)&&u(n);return n},be=(e,t)=>{const r=e?v.get(e):null;return r&&!r.error&&r.seq>t?r:null},ke=(e,t)=>(q.get(e)||0)>t,ge=e=>{if(e){v.delete(e),q.set(e,++I);return}for(const[t]of v)q.set(t,++I);for(const[t]of R)q.set(t,++I);v.clear()},kt=(e,t)=>(x.has(e)||x.set(e,new Set),x.get(e).add(t),()=>{const r=x.get(e);r&&(r.delete(t),r.size===0&&x.delete(e))}),Oe=e=>!e&&e!==!1?{staleTime:6e4,gcTime:3e5}:e===!1?null:e===!0?{staleTime:6e4,gcTime:3e5}:typeof e=="number"?{staleTime:e,gcTime:Math.max(e*5,3e5)}:_e(e)?{staleTime:se(e),gcTime:3e5}:{staleTime:se(e.stale||e.staleTime)||6e4,gcTime:se(e.gc||e.gcTime||e.expire)||3e5,key:e.key},V=new Set,xe=e=>!!e.ref&&e.element.__ref===e.ref,pt=({config:e,element:t,context:r})=>{try{return qe(e,t,r)}catch{return null}};let we=null;const ht=()=>{we||(we=setInterval(()=>{const e=Date.now();for(const[t,r]of v){const s=x.get(t);!(s&&s.size>0)&&e-r.time>(r.gcTime||3e5)&&v.delete(t)}for(const[t]of q)!v.has(t)&&!R.has(t)&&q.delete(t);for(const t of V)xe(t)||V.delete(t)},3e4),we.unref&&we.unref())};ht();const Ve=3,Re=e=>Math.min(1e3*2**e,3e4),We=(e,t)=>{const r=e.retry;return r===void 0&&t?{count:0}:r===!1?{count:0}:r===!0||r===void 0?{count:Ve,delay:Re}:typeof r=="number"?{count:r,delay:Re}:{count:r.count??Ve,delay:l(r.delay)?r.delay:typeof r.delay=="number"?()=>r.delay:Re}},Ye=async(e,t)=>{const{count:r,delay:s}=t;let o;for(let n=0;n<=r;n++)try{const i=await e();if(i?.error){if(o=i.error,n<r){const u=l(s)?s(n,o):s;await new Promise(a=>setTimeout(a,u));continue}return i}return i}catch(i){if(o=i,n<r){const u=l(s)?s(n,i):s;await new Promise(a=>setTimeout(a,u));continue}throw i}return{data:null,error:o}},te={focus:new Set,online:new Set};let Je=!1;const _t=()=>{if(Je||typeof window>"u")return;Je=!0;const e=()=>{for(const r of te.focus)r()},t=()=>{for(const r of te.online)r()};window.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&e()}),window.addEventListener("focus",e),window.addEventListener("online",t)},He=(e,t)=>{const r=t.__ref;if(e===!0)return{from:r.__state||t.key,method:"select",query:!0,on:"create"};if(_e(e))return{from:e,method:"select",query:!0,on:"create"};if(Z(e)){const s={...e};return s.from=s.from||r.__state||t.key,s.method=s.method||"select",s.on=s.on||"create",s.query===void 0&&(s.query=!0),s}return null},yt=["insert","update","upsert","delete"],Xe=e=>yt.includes(e),Ie=e=>e.mutation===!0?!0:e.mutation===!1||e.method!=="rpc"?!1:e.on==="submit"||e.on==="click"||e.invalidates!=null,ae=(e,t)=>{const r=e?.state?.root||t?.state?.root;if(r?.lang)return r.lang},Te=(e,t)=>{const s=(e?.state?.root||t?.state?.root)?.__fetchScope;return s?String(s):void 0},ve=(e,t,r)=>{let s;if(!e)s=void 0;else if(l(e))s=e(t,t.state);else{s={};for(const o in e){const n=e[o];s[o]=l(n)?n(t,t.state):n}}return s},ie=(e,t)=>l(e)?e(t,t.state):e,qe=(e,t,r)=>Le(e,ve(e.params,t,r),Te(t,r),ae(t,r),ie(e.page,t),ie(e.cursor,t)),Ne=async(e,t)=>{if(!e||e.__authInitialized||(e.__authInitialized=!0,!e.getSession))return;const r=(s,o)=>{const n=t.state?.root;n?.update&&n.update({auth:{user:s,session:o,loading:!1}})};try{const s=await e.getSession();r(s?.user||null,s)}catch(s){console.warn("[fetch] Failed to restore auth session:",s.message)}e.onAuthStateChange&&e.onAuthStateChange((s,o)=>{r(o?.user||null,o)})},Ue=async(e,t)=>{if(l(e.select))return e.auth!==!1&&await Ne(e,t),e;if(e.__resolved)return e.auth!==!1&&await Ne(e.__resolved,t),e.__resolved;if(e.__resolving)return e.__resolving;e.__resolving=dt(e);const r=await e.__resolving;return delete e.__resolving,r?(r.auth=e.auth,r.url=e.url,r.key=e.key,e.__resolved=r,t.fetch=r,e.auth!==!1&&await Ne(r,t),r):null},F=(e,t,...r)=>{const s=l(e[t])?e[t]:null;s&&s.call(e,...r,e,e.state,e.context)},E=(e,t,r,...s)=>{const o=e&&l(e[r])?e[r]:null;o&&o.call(t,...s,t,t.state,t.context)},ue=e=>{const t={},r=e.node;if(!r)return t;if(r.tagName==="FORM"){const o=new FormData(r);for(const[n,i]of o.entries())t[n]=i;return t}const s=r.querySelectorAll("input, textarea, select");for(let o=0;o<s.length;o++){const n=s[o],i=n.name||n.getAttribute("name");i&&(n.type==="checkbox"?t[i]=n.checked:n.type==="file"?t[i]=n.files:t[i]=n.value)}return t},Fe=e=>e.responseType==="stream"||e.responseType==="response",St=["Blob","File","ArrayBuffer","ReadableStream","Response"],Qe=e=>e!==null&&typeof e=="object"&&(ArrayBuffer.isView(e)||St.includes(Object.prototype.toString.call(e).slice(8,-1))),$=(e,t,r,s={})=>{if(!r&&Qe(t))return;const o=r&&r.startsWith("~/"),n=o&&e.state?.root||e.state;if(n?.update)if(o){const i=r.slice(2);t&&typeof t=="object"&&!Array.isArray(t)&&!Qe(t)&&i in t?n.update(t,{preventFetch:!0,...s}):n.update({[i]:t},{preventFetch:!0,...s})}else{const i=r?{[r]:t}:t;n.update(i,{preventFetch:!0,...s})}},C=(e,t)=>{const r=e?.__ref;r&&(r.__fetchStatus={...t,isError:!!t.error},r.__fetching=t.isFetching,r.__fetchError=t.error)},Ze=(e,t,r)=>{const s=e.__pages&&e.__pages.length?e.__pages:[r];l(t.getNextPageParam)&&(e.__nextPageParam=t.getNextPageParam(s[s.length-1],s),e.__hasNextPage=e.__nextPageParam!=null),l(t.getPreviousPageParam)&&(e.__prevPageParam=t.getPreviousPageParam(s[0],s),e.__hasPreviousPage=e.__prevPageParam!=null)},N=async(e,t,r,s={})=>{const o=r?.fetch;if(!o||!t||!t.__ref)return;const n=t.__ref,i=Te(t,r);(!n.__fetchScopeOwner||n.__fetchScopeOwner.scope!==i)&&(n.__fetchScopeOwner={scope:i});const u=n.__fetchScopeOwner,a=()=>t.__ref===n&&n.__fetchScopeOwner===u&&Te(t,r)===i;if(e.enabled===!1||l(e.enabled)&&!e.enabled(t,t.state)||e.skip===!0||l(e.skip)&&e.skip(t,t.state))return;const d=await Ue(o,r);if(!d||!a())return;const{from:_,method:h,query:D,params:De,cache:Pe,transform:P,single:ce,auth:fe,fields:W,as:L,limit:z,offset:U,order:b,headers:re,baseUrl:m,responseType:Y,page:T,cursor:M,infinite:j,placeholderData:J,initialData:H,select:g,keepPreviousData:k}=e;let B=_e(g)?g:void 0;if(D&&l(t.getQuery)){const p=t.getQuery(d.name||"paths");p&&(B=p.select||p.length&&p.join(",")||B)}const G=ve(De,t,r),A=ie(T,t),me=ie(M,t),le=Ie(e),de=Fe(e)||le?null:Oe(Pe),nt=We(e,le||Xe(h)),je=ae(t,r),y=de?Le(e,G,i,je,A,me):null;if(!n.__fetchInitialized&&y&&(n.__fetchInitialized=!0,H!==void 0)){const p=l(H)?H():H;oe(y,p,null)}if((h==="select"||h==="rpc")&&y&&de&&!s.refresh&&!s.force){const p=ne(y);if(p&&!p.error){const c=Date.now()-p.time,w=p.stale||c>(de.staleTime||0);j&&Q(p.data)&&!n.__pages&&(n.__pages=[p.data],Ze(n,e,p.data));let f=p.data;if(l(g)&&(f=g(f,t,t.state)),P&&(f=P(f,t,t.state)),!a())return;if(!w){C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),$(t,f,L);return}$(t,f,L)}else if(J!==void 0){const c=l(J)?J(t,t.state):J;c!==void 0&&$(t,c,L)}}const pe=y&&!s.force?R.get(y):null;if(pe&&!ke(y,Me.get(pe)||0)){const p=c=>{C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:c,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",c),E(e,t,"onFetchError",c)};try{const c=await Promise.race([pe,new Promise((w,f)=>setTimeout(()=>f("DEDUP_TIMEOUT"),15e3))]);if(!a())return;if(c?.error)p(c.error);else if(c?.data!==void 0){const w=!j&&be(y,Me.get(pe)||0);let f=w?w.data:c.data;if(l(g)&&(f=g(f,t,t.state)),P&&(f=P(f,t,t.state)),!a())return;$(t,f,L),F(t,"onFetchComplete",f),E(e,t,"onFetchComplete",f)}}catch(c){if(!a())return;c!=="DEDUP_TIMEOUT"&&p(c)}return}const ot=!ne(y)?.data;C(t,{isFetching:!0,isLoading:ot,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),F(t,"onFetchStart"),E(e,t,"onFetchStart");const O=r?.analyze?.emitNetwork,Ae=O?typeof performance<"u"?performance.now():Date.now():0;O&&O({phase:"start",mode:"query",from:_,method:h,cacheKey:y,lang:je});const at=async()=>{if(!a())return;if(fe!==!1&&d.auth!==!1&&d.getSession){const f=await d.getSession();if(!a())return;if(fe===!0&&!f){const S={message:"Not authenticated"};return C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:S,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",S),E(e,t,"onFetchError",S),{data:null,error:S}}}const p=d[h];if(!l(p))return{data:null,error:{message:`Method "${h}" not found on adapter`}};const c={from:_,select:B,params:G,single:ce,limit:z,offset:U,order:b,headers:re,baseUrl:m};Y!==void 0&&(c.responseType=Y);const w=ae(t,r);if(w&&(c.headers={...c.headers,"Accept-Language":w}),A!==void 0){if(Z(A))A.offset!==void 0&&(c.offset=A.offset),A.limit!==void 0&&(c.limit=A.limit),A.cursor!==void 0&&(c.params={...c.params,cursor:A.cursor});else if(typeof A=="number"){const f=e.pageSize||z||20;c.offset=(A-1)*f,c.limit=f}}if(me!==void 0&&(c.params={...c.params,cursor:me}),h==="insert"||h==="update"||h==="upsert"){let f;if(W===!0||e.on==="submit")f=ue(t);else if(Q(W)){const S=ue(t);f={};for(const K of W)f[K]=S[K]}else t.state?.parse?f=t.state.parse():Z(t.state)&&(f={...t.state});P&&(f=P(f,t,t.state)),c.data=f}if(h==="rpc"){let f=G;if(e.on==="submit"){const S=ue(t);f=e.transformParams?e.transformParams(S,t,t.state):S}c.params=f,c.read=!le&&e.read!==!1}if(h==="subscribe"){if(l(n.__unsubscribe)){const S=n.__unsubscribe;n.__unsubscribe=null;try{S()}catch{}}const f=d.subscribe({from:_,params:G,on:e.subscribeOn},(S,K,Be)=>{a()&&($(t,S,L),F(t,"onFetchComplete",S),E(e,t,"onFetchComplete",S))});return a()?(n.__unsubscribe=f,{data:null,error:null,subscribed:!0}):(l(f)&&f(),{data:null,error:null,subscribed:!0})}return p(c)},X=++I,he=Ye(at,nt);Me.set(he,X),y&&R.set(y,he);try{const p=await he;if(le&&e.invalidates&&p&&!p.error&&!p.subscribed&&tt(e.invalidates,_),!a()||p?.subscribed)return;const{data:c,error:w}=p||{};if(w){y&&!be(y,X)&&oe(y,null,w,X),C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:w,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",w),E(e,t,"onFetchError",w),O&&O({phase:"error",mode:"query",from:_,method:h,cacheKey:y,error:w.message||w,durationMs:(typeof performance<"u"?performance.now():Date.now())-Ae});return}if(c!==void 0){let f=c;if((h==="select"||h==="rpc")&&y){const K=!j&&be(y,X);if(K)f=K.data;else{const Be=oe(y,c,null,X,ke(y,X));Be.gcTime=de?.gcTime||3e5}}let S=f;if(j&&Q(c)&&((!n.__pages||!s.direction)&&(n.__pages=[]),s.direction==="previous"?n.__pages.unshift(c):n.__pages.push(c),S=n.__pages.flat(),Ze(n,e,c)),l(g)&&(S=g(S,t,t.state)),P&&(S=P(S,t,t.state)),!a())return;C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),(!k||S!==void 0)&&$(t,S,L),F(t,"onFetchComplete",S),E(e,t,"onFetchComplete",S),O&&O({phase:"success",mode:"query",ok:!0,from:_,method:h,cacheKey:y,durationMs:(typeof performance<"u"?performance.now():Date.now())-Ae})}}catch(p){if(!a())return;C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:p,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",p),E(e,t,"onFetchError",p),O&&O({phase:"error",mode:"query",from:_,method:h,cacheKey:y,error:p.message||String(p),durationMs:(typeof performance<"u"?performance.now():Date.now())-Ae})}finally{y&&R.get(y)===he&&R.delete(y)}},bt=(e,t,r)=>{const s=e.on,o=t.__ref;if(!o)return;o.__fetchListeners||(o.__fetchListeners=[]);const n=e.__runner||N;if(s==="submit"){const i=a=>{a.preventDefault(),n(e,t,r)},u=t.node;u&&(u.addEventListener("submit",i),o.__fetchListeners.push(()=>u.removeEventListener("submit",i)))}else if(s==="click"){const i=()=>n(e,t,r),u=t.node;u&&(u.addEventListener("click",i),o.__fetchListeners.push(()=>u.removeEventListener("click",i)))}else s==="stateChange"&>(e,t,r,n)},gt=(e,t,r,s)=>{const o=t.__ref;let n;const i=it(()=>{let u;try{u=JSON.stringify([ve(e.params,t,r)??null,l(e.enabled)?!!e.enabled(t,t.state):e.enabled!==!1,l(e.skip)?!!e.skip(t,t.state):e.skip===!0,ie(e.page,t)??null])}catch{return}if(n===void 0){n=u;return}u!==n&&(n=u,Promise.resolve().then(()=>s(e,t,r)))});Array.isArray(o.__effects)?o.__effects.push(i):o.__fetchListeners.push(()=>i.dispose())},wt=(e,t,r)=>{const s=t.__ref;s.__fetchCleanups||(s.__fetchCleanups=[]),_t();const o=e.refetchInterval;if(o){const a=typeof o=="number"?o:se(o);if(a>0){const d=setInterval(()=>{(e.refetchIntervalInBackground||typeof document>"u"||document.visibilityState==="visible")&&N(e,t,r,{force:!0})},a);s.__fetchCleanups.push(()=>clearInterval(d))}}const n=Fe(e),i=e.refetchOnWindowFocus===void 0?!n:e.refetchOnWindowFocus!==!1,u=e.refetchOnReconnect===void 0?!n:e.refetchOnReconnect!==!1;if(i){const a=()=>{const d=qe(e,t,r),_=ne(d),h=Oe(e.cache);(!_||_.stale||Date.now()-_.time>(h?.staleTime||0))&&N(e,t,r,{force:!0})};te.focus.add(a),s.__fetchCleanups.push(()=>te.focus.delete(a))}if(u){const a=()=>N(e,t,r,{force:!0});te.online.add(a),s.__fetchCleanups.push(()=>te.online.delete(a))}},Tt=(e,t,r)=>{if(!t.optimistic)return null;const s=e.__ref,o=t.as,n=e.state?.parse?e.state.parse():Z(e.state)?{...e.state}:e.state;s.__optimisticSnapshot=n;const i=l(t.optimistic)?t.optimistic(r,n,e):t.optimistic;return i!==void 0&&$(e,i,o),n},$e=(e,t)=>{const r=e.__ref;if(!r||!t.optimistic)return;const s=r.__optimisticSnapshot;s!==void 0&&($(e,s,t.as),delete r.__optimisticSnapshot)},ze=e=>{if(!e)return()=>!0;if(_e(e))return t=>t.includes(e);if(Q(e)){const t=e.join(":");return r=>r.includes(t)}return e instanceof RegExp?t=>(e.lastIndex=0,e.test(t)):l(e)?t=>{try{return!!e(t)}catch{return!1}}:()=>!1},vt=e=>{const t=[];for(const r of V){if(!xe(r)){V.delete(r);continue}const s=pt(r);s&&e(s)&&t.push(N(r.config,r.element,r.context,{refresh:!0}))}return Promise.allSettled(t).then(()=>{})},et=e=>{ut();for(const[t,r]of v)e(t)&&(r.stale=!0,q.set(t,++I));for(const[t]of R)!v.has(t)&&e(t)&&q.set(t,++I);return vt(e)},tt=(e,t)=>{const r=(Q(e)?e:[e]).map(s=>s===!0||s==="*"?o=>o.startsWith(t+":"):s?ze(s):()=>!1);return et(s=>r.some(o=>o(s)))},Ft=e=>{for(const[t]of v)e(t)&&ge(t);for(const[t]of R)e(t)&&q.set(t,++I)},Et=async(e,t,r)=>{const s=r?.fetch;if(!s)return;const o=await Ue(s,r);if(!o)return;const n=t.__ref,{method:i,from:u,fields:a,transform:d,as:_,on:h,auth:D,headers:De,baseUrl:Pe,responseType:P,invalidates:ce,optimistic:fe,onMutate:W,onSuccess:L,onError:z,onSettled:U}=e;let b;if(a===!0||h==="submit")b=ue(t);else if(Q(a)){const T=ue(t);b={};for(const M of a)b[M]=T[M]}else t.state?.parse?b=t.state.parse():Z(t.state)&&(b={...t.state});d&&(b=d(b,t,t.state)),l(W)&&W(b,t,t.state);const re=fe?Tt(t,e,b):null;n.__fetching=!0,C(t,{isFetching:!0,isLoading:!1,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),F(t,"onFetchStart"),E(e,t,"onFetchStart");const m=r?.analyze?.emitNetwork,Y=m?typeof performance<"u"?performance.now():Date.now():0;m&&m({phase:"start",mode:"mutation",from:u,method:i});try{if(D!==!1&&o.auth!==!1&&o.getSession){const B=await o.getSession();if(D===!0&&!B){re!==void 0&&$e(t,e);const G={message:"Not authenticated"};F(t,"onFetchError",G),E(e,t,"onFetchError",G);return}}const T=o[i];if(!l(T))return;const M={from:u,data:b,headers:De,baseUrl:Pe};P!==void 0&&(M.responseType=P),e.params&&(M.params=ve(e.params,t,r));const j=ae(t,r);j&&(M.headers={...M.headers,"Accept-Language":j});const J=We(e,!0),H=await Ye(()=>T(M),J),{data:g,error:k}=H||{};if(k){re!==void 0&&$e(t,e),C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:k,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",k),l(z)&&z(k,b,t),l(U)&&U(null,k,b,t),m&&m({phase:"error",mode:"mutation",from:u,method:i,error:k.message||String(k),durationMs:(typeof performance<"u"?performance.now():Date.now())-Y});return}if(delete n.__optimisticSnapshot,C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),g!==void 0&&(_||!Qe(g))){const B=_?{[_]:g}:g;t.state?.update&&t.state.update(B,{preventFetch:!0})}F(t,"onFetchComplete",g),E(e,t,"onFetchComplete",g),l(L)&&L(g,b,t),l(U)&&U(g,null,b,t),m&&m({phase:"success",mode:"mutation",ok:!0,from:u,method:i,durationMs:(typeof performance<"u"?performance.now():Date.now())-Y}),ce&&tt(ce,u)}catch(T){re!==void 0&&$e(t,e),C(t,{isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:T,status:"error",fetchStatus:"idle"}),F(t,"onFetchError",T),E(e,t,"onFetchError",T),l(z)&&z(T,b,t),l(U)&&U(null,T,b,t),m&&m({phase:"error",mode:"mutation",from:u,method:i,error:T.message||String(T),durationMs:(typeof performance<"u"?performance.now():Date.now())-Y})}finally{n.__fetching=!1}},Dt=e=>{const t=r=>{try{r()}catch{}};for(const r of e.__fetchCleanups||[])t(r);for(const r of e.__fetchListeners||[])t(r);l(e.__unsubscribe)&&t(e.__unsubscribe);for(const r of e.__fetchMounted||[])V.delete(r);e.__fetchCleanups=[],e.__fetchListeners=[],e.__unsubscribe=null,e.__fetchMounted=[]},rt=(e,t,r,s)=>{if(!e||!s?.fetch)return;const n=Ge(e,t);if(!n)return;const i=Q(n)?n.map(a=>He(Ge(a,t),t)).filter(Boolean):[He(n,t)].filter(Boolean),u=t.__ref;if(u.__fetchMounted)for(const a of u.__fetchMounted)V.delete(a);u.__fetchMounted=[],!u.__fetchDisposer&&Q(u.__effects)&&(u.__fetchDisposer={dispose:()=>Dt(u)},u.__effects.push(u.__fetchDisposer));for(const a of i){const d=Xe(a.method),_=d?Et:N,h=!d&&!Ie(a)&&a.method!=="subscribe";if(a.on==="create"?_(a,t,s):Promise.resolve().then(()=>{bt({...a,__runner:_},t,s)}),h&&a.on==="create"&&((a.refetchInterval||a.refetchOnWindowFocus!==!1||a.refetchOnReconnect!==!1)&&wt(a,t,s),!Fe(a))){const D={element:t,ref:u,config:a,context:s};V.add(D),u.__fetchMounted.push(D)}}u.refetch=a=>{for(const d of i)N(d,t,s,{force:!0,...a})},u.fetchNextPage=()=>{const a=i[0];if(!a||!a.infinite)return;const d=u.__nextPageParam;if(d==null)return;const _={...a,cursor:d};N(_,t,s,{direction:"next",force:!0})},u.fetchPreviousPage=()=>{const a=i[0];if(!a||!a.infinite)return;const d=u.__prevPageParam;if(d==null)return;const _={...a,cursor:d};N(_,t,s,{direction:"previous",force:!0})}},Ee={invalidateQueries:e=>et(ze(e)),removeQueries:e=>{if(!e){ge();return}Ft(ze(e))},getQueryData:e=>ne(e)?.data??void 0,setQueryData:(e,t)=>{const r=ne(e),s=l(t)?t(r?.data):t;oe(e,s,null)},prefetchQuery:async(e,t)=>{const r=t?.fetch;if(!r)return;const s=await Ue(r,t);if(!s)return;const o=e.method||"select",n=Te(null,t),i=ae(null,t),u=Le({...e,method:o},e.params,n,i),a=s[o];if(!l(a))return;const d={from:e.from,params:e.params,limit:e.limit,offset:e.offset,order:e.order,single:e.single,headers:e.headers,baseUrl:e.baseUrl};i&&(d.headers={...d.headers,"Accept-Language":i}),e.responseType!==void 0&&(d.responseType=e.responseType),o==="rpc"&&(d.read=!Ie(e)&&e.read!==!1);const _=++I,h=await a(d);if(h?.data!==void 0&&!h?.error&&!Fe(e)&&!be(u,_)){const D=oe(u,h.data,null,_,ke(u,_));D.gcTime=Oe(e.cache)?.gcTime||3e5}return h},getCache:()=>v,clear:()=>ge(),dropNonLang:e=>{if(!e)return;const t=`:${e}`;for(const[r]of v)r.match(/:([a-z]{2,3})$/i)&&(r.endsWith(t)||ge(r))}},st=(e,t)=>Z(e)?qe({method:"select",...e},t,t?.context):e,Ot={invalidateQueries(e){return Ee.invalidateQueries(e)},removeQueries(e){return Ee.removeQueries(e)},getQueryData(e){return Ee.getQueryData(st(e,this))},setQueryData(e,t){return Ee.setQueryData(st(e,this),t)}},Rt={name:"fetch",create(e){const t=e.fetch;t&&(e.parent||R.clear(),rt(t,e,e.state,e.context))}};var It=rt;export{Lt as createAdapter,It as default,rt as executeFetch,Ot as fetchFunctions,Rt as fetchPlugin,Mt as hasAdapter,Ne as initAdapterAuth,se as parseDuration,Ee as queryClient,Ct as registerAdapter,dt as resolveDb};
|
package/early.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// --- Early requests (`fetch.early`) ---
|
|
4
|
+
//
|
|
5
|
+
// A page's first view waits for its data twice over: the app bundle downloads,
|
|
6
|
+
// boots and mounts, and only THEN do its queries ask. `fetch.early` (opt-in)
|
|
7
|
+
// lets the build emit a pre-boot script (earlyScript.js createEarlyScript)
|
|
8
|
+
// that starts the first view's GETs while the HTML is still parsing, and lets
|
|
9
|
+
// the REST adapter ADOPT each answer for the first call that asks for exactly
|
|
10
|
+
// that request instead of sending it again.
|
|
11
|
+
//
|
|
12
|
+
// The two sides share a registry on globalThis under a `Symbol.for` slot —
|
|
13
|
+
// the pre-boot script runs before any module, so a global is the only thing
|
|
14
|
+
// it can hand over through; the Symbol keeps it off the visible namespace and
|
|
15
|
+
// shared by every copy of this package (same rationale as CUSTOM_ADAPTERS).
|
|
16
|
+
// { time, closed, entries: { [earlyKey]: { time, answer: Promise<Response|null> } } }
|
|
17
|
+
// The script creates it; an adapter that finds none creates it CLOSED, so a
|
|
18
|
+
// pre-boot script that arrives after the app booted (an async script on a
|
|
19
|
+
// cold cache) sends nothing: no call would be left to adopt its answers.
|
|
20
|
+
//
|
|
21
|
+
// The pure helpers below (earlyKey, isScalarParams, matchEarlyRoute) are ALSO
|
|
22
|
+
// embedded into the pre-boot script by source (`fn.toString()`), so the script
|
|
23
|
+
// and the adapter key requests with the same code. They must stay
|
|
24
|
+
// self-contained: no reference to anything outside their own parameters and
|
|
25
|
+
// the platform's built-ins (URL, decodeURIComponent, …).
|
|
26
|
+
|
|
27
|
+
export const EARLY_KEY = '@symbo.ls/fetch:EARLY'
|
|
28
|
+
|
|
29
|
+
// How long an early answer stays adoptable, counted from when the pre-boot
|
|
30
|
+
// script sent the request: long enough for a slow app bundle on a slow
|
|
31
|
+
// network to boot and ask, short enough that an adopted answer is never older
|
|
32
|
+
// than a default-cache query answer (staleTime 1m) may be.
|
|
33
|
+
export const EARLY_MAX_AGE = 30000
|
|
34
|
+
|
|
35
|
+
// The identity of a GET: its URL with the query sorted (the order of params
|
|
36
|
+
// keys is the caller's accident, not a different request) and the language
|
|
37
|
+
// it asked in — the only header an early GET sends, and one a backend may
|
|
38
|
+
// answer differently by.
|
|
39
|
+
export const earlyKey = (url, lang) => {
|
|
40
|
+
const parsed = new URL(url)
|
|
41
|
+
parsed.searchParams.sort()
|
|
42
|
+
return parsed.toString() + ' ' + (lang || '')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// An RPC goes early as a GET with its arguments in the query string, and only
|
|
46
|
+
// a string, a finite number or a boolean reads back the same from there: a
|
|
47
|
+
// JSON body's null (sent as null, but dropped from a query), an object or an
|
|
48
|
+
// array would reach the function as something else. `undefined` is dropped
|
|
49
|
+
// by both forms.
|
|
50
|
+
export const isScalarParams = (params) => {
|
|
51
|
+
if (params === undefined || params === null) return true
|
|
52
|
+
if (typeof params !== 'object' || Array.isArray(params)) return false
|
|
53
|
+
// A plain object only: a FormData or Blob body is no argument list.
|
|
54
|
+
const proto = Object.getPrototypeOf(params)
|
|
55
|
+
if (proto !== Object.prototype && proto !== null) return false
|
|
56
|
+
for (const key in params) {
|
|
57
|
+
const value = params[key]
|
|
58
|
+
if (value === undefined || typeof value === 'string' || typeof value === 'boolean') continue
|
|
59
|
+
if (typeof value === 'number' && isFinite(value)) continue
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The route pattern a path opens, scored like the router's matchRoute: a
|
|
66
|
+
// literal segment beats a `:param`, which beats a trailing `*`. Stricter in
|
|
67
|
+
// one way — a pattern must cover every segment — because a wrong guess here
|
|
68
|
+
// costs a wasted request, never a wrong answer (adoption is by request
|
|
69
|
+
// identity, not by route). Returns { index, params } or null.
|
|
70
|
+
export const matchEarlyRoute = (patterns, path) => {
|
|
71
|
+
const split = (value) => String(value).split('/').filter(Boolean)
|
|
72
|
+
const segments = split(path)
|
|
73
|
+
let best = null
|
|
74
|
+
for (let index = 0; index < patterns.length; index++) {
|
|
75
|
+
const parts = split(patterns[index])
|
|
76
|
+
const wildcard = parts[parts.length - 1] === '*'
|
|
77
|
+
const fixed = wildcard ? parts.length - 1 : parts.length
|
|
78
|
+
if (wildcard ? segments.length < fixed : segments.length !== fixed) continue
|
|
79
|
+
const params = {}
|
|
80
|
+
let score = wildcard ? -1 : 0
|
|
81
|
+
let matched = true
|
|
82
|
+
for (let i = 0; i < fixed; i++) {
|
|
83
|
+
if (parts[i].charAt(0) === ':') {
|
|
84
|
+
try { params[parts[i].slice(1)] = decodeURIComponent(segments[i]) } catch (e) { matched = false; break }
|
|
85
|
+
score += 1
|
|
86
|
+
} else if (parts[i] === segments[i]) {
|
|
87
|
+
score += 3
|
|
88
|
+
} else {
|
|
89
|
+
matched = false
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (matched && (!best || score > best.score)) best = { index, params, score }
|
|
94
|
+
}
|
|
95
|
+
return best && { index: best.index, params: best.params }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const registryKey = () => Symbol.for(EARLY_KEY)
|
|
99
|
+
|
|
100
|
+
// `fetch.early` with its `query` and `rpc` defaulting to the adapter's
|
|
101
|
+
// `reads` (adapters/rest.js): the same two options, declared once, drive both
|
|
102
|
+
// the pre-boot script and the adapter's own GETs. Used by both sides — the
|
|
103
|
+
// build (earlyScript.js) and the adapter (openEarly) — so they agree. An
|
|
104
|
+
// `early` that sets its own keeps them; no `early` stays no `early`.
|
|
105
|
+
export const withReads = (early, reads) => {
|
|
106
|
+
if (!early || typeof early !== 'object' || !reads || typeof reads !== 'object') return early
|
|
107
|
+
const merged = { ...early }
|
|
108
|
+
if (merged.query === undefined && reads.query !== undefined) merged.query = reads.query
|
|
109
|
+
if (merged.rpc === undefined && reads.rpc !== undefined) merged.rpc = reads.rpc
|
|
110
|
+
return merged
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The adapter's side of the hand-off, called once when a REST adapter with
|
|
114
|
+
// `early` is set up. Closes the registry against late pre-boot scripts and
|
|
115
|
+
// returns what the adapter needs to recognise an early request: the
|
|
116
|
+
// configured headers the script carried as query parameters (lower-cased
|
|
117
|
+
// name → configured value), whether RPCs went as GETs, and the answer age.
|
|
118
|
+
// No `early` config → null, and the adapter never looks (the opt-in).
|
|
119
|
+
export const openEarly = (config, headers) => {
|
|
120
|
+
if (!config || typeof config !== 'object') return null
|
|
121
|
+
const key = registryKey()
|
|
122
|
+
let registry = globalThis[key]
|
|
123
|
+
if (!registry) {
|
|
124
|
+
registry = { time: Date.now(), entries: {} }
|
|
125
|
+
globalThis[key] = registry
|
|
126
|
+
}
|
|
127
|
+
registry.closed = true
|
|
128
|
+
const carried = {}
|
|
129
|
+
for (const name in config.query || {}) {
|
|
130
|
+
for (const header in headers || {}) {
|
|
131
|
+
if (header.toLowerCase() === name.toLowerCase()) carried[header.toLowerCase()] = headers[header]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const maxAge = typeof config.maxAge === 'number' && config.maxAge >= 0 ? config.maxAge : EARLY_MAX_AGE
|
|
135
|
+
return { carried, rpc: config.rpc === 'get', maxAge }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The waiting answer for `key`, taken ONCE (a body can be read once; a second
|
|
139
|
+
// identical call asks the network). Resolves to an ok Response, or null when
|
|
140
|
+
// the early request failed, was refused (a 4xx/5xx may be the early form's
|
|
141
|
+
// own problem — a query-string key or a GET the backend does not serve — so
|
|
142
|
+
// the call's own request decides), or is older than `maxAge`.
|
|
143
|
+
export const takeEarly = (key, maxAge = EARLY_MAX_AGE) => {
|
|
144
|
+
const registry = globalThis[registryKey()]
|
|
145
|
+
const entry = registry && registry.entries && registry.entries[key]
|
|
146
|
+
if (!entry) return null
|
|
147
|
+
delete registry.entries[key]
|
|
148
|
+
if (!(Date.now() - entry.time <= maxAge)) return null
|
|
149
|
+
return Promise.resolve(entry.answer).then(
|
|
150
|
+
(res) => (res && res.ok && !res.bodyUsed ? res : null),
|
|
151
|
+
() => null
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Forget every answer still waiting. An invalidation means a write happened:
|
|
156
|
+
// an answer asked for before it may predate it.
|
|
157
|
+
export const dropEarly = () => {
|
|
158
|
+
const registry = globalThis[registryKey()]
|
|
159
|
+
if (registry && registry.entries) registry.entries = {}
|
|
160
|
+
}
|
package/earlyScript.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// --- The pre-boot script of `fetch.early` (build side) ---
|
|
4
|
+
//
|
|
5
|
+
// createEarlyScript(project) returns the source of a classic script that the
|
|
6
|
+
// build writes next to the page and references from its <head>
|
|
7
|
+
// (@symbo.ls/cli helpers/earlyScript.js). It runs while the HTML parses,
|
|
8
|
+
// before any module, so it carries everything itself: the adapter's address,
|
|
9
|
+
// the configured headers it sends as query parameters, the language rule and
|
|
10
|
+
// the declared requests as data, and the address helpers BY SOURCE — the REST
|
|
11
|
+
// adapter's own buildUrl / selectQuery and early.js's earlyKey /
|
|
12
|
+
// isScalarParams / matchEarlyRoute — so an early request has exactly the
|
|
13
|
+
// address and identity the adapter computes for the call that adopts it.
|
|
14
|
+
//
|
|
15
|
+
// Build-time only: a separate entry (`@symbo.ls/fetch/early-script`) that
|
|
16
|
+
// the runtime bundle never imports.
|
|
17
|
+
//
|
|
18
|
+
// Declaration (the REST adapter's config, `fetch` in config.js):
|
|
19
|
+
//
|
|
20
|
+
// fetch: {
|
|
21
|
+
// adapter: 'rest', url, headers: { apikey: '…' },
|
|
22
|
+
// reads: { query: …, rpc: … }, // the adapter's own anonymous GETs (rest.js)
|
|
23
|
+
// early: {
|
|
24
|
+
// query: { apikey: 'apikey' }, // configured header → query parameter (default: reads.query)
|
|
25
|
+
// rpc: 'get', // the backend serves read-only RPCs as GETs (default: reads.rpc)
|
|
26
|
+
// maxAge: 30000, // adapter side: how old an answer may be
|
|
27
|
+
// always: [ …requests ] | (params, ctx) => [ …requests ],
|
|
28
|
+
// routes: { '/blog/:id': [ …requests ] | ({ id }, ctx) => [ …requests ] }
|
|
29
|
+
// }
|
|
30
|
+
// }
|
|
31
|
+
//
|
|
32
|
+
// A request is the adapter call it stands for — `{ method: 'select' | 'rpc',
|
|
33
|
+
// from, params, select, limit, offset, order, single }` — plus `lang`: by
|
|
34
|
+
// default it asks in the active language (Accept-Language), as a declarative
|
|
35
|
+
// `fetch` does; `lang: false` for one a getDB() call or polyglot's loader
|
|
36
|
+
// makes (they send none). `ctx` is { path, query (URLSearchParams), lang,
|
|
37
|
+
// storage(key) → localStorage value or null }.
|
|
38
|
+
|
|
39
|
+
import { buildUrl, selectQuery } from './adapters/rest.js'
|
|
40
|
+
import { EARLY_KEY, earlyKey, isScalarParams, matchEarlyRoute, withReads } from './early.js'
|
|
41
|
+
|
|
42
|
+
// polyglot's own default for where it keeps the chosen language
|
|
43
|
+
// (@symbo.ls/polyglot STORAGE_KEY_LANG): the script must read the same slot
|
|
44
|
+
// the app will, or its requests ask in another language than the app's.
|
|
45
|
+
const POLYGLOT_LANG_KEY = 'smbls_lang'
|
|
46
|
+
|
|
47
|
+
// The fetch() options an early GET may copy from the adapter's — the ones
|
|
48
|
+
// that change what comes back. Headers never: every header but the language
|
|
49
|
+
// would make the request non-simple (a CORS preflight); the configured ones
|
|
50
|
+
// travel as query parameters (`early.query`) instead.
|
|
51
|
+
const FETCH_OPTION_KEYS = ['credentials', 'mode', 'cache', 'redirect', 'referrerPolicy', 'priority']
|
|
52
|
+
|
|
53
|
+
const compiles = (body) => {
|
|
54
|
+
try {
|
|
55
|
+
new Function(body)
|
|
56
|
+
return true
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A request function as an expression the script can hold: its own source
|
|
63
|
+
// (a function, or the string frank serialized it to). A method shorthand —
|
|
64
|
+
// `'/blog/:id' ({ id }) { … }` — is no expression, so it is read back out of
|
|
65
|
+
// an object literal. A function must stand alone: it runs before the app, with
|
|
66
|
+
// nothing but its arguments and the browser's globals.
|
|
67
|
+
const functionSource = (fn, where) => {
|
|
68
|
+
const source = typeof fn === 'function' ? fn.toString() : String(fn).trim()
|
|
69
|
+
if (compiles('return (' + source + ')')) return '(' + source + ')'
|
|
70
|
+
if (compiles('return ({' + source + '})')) return 'Object.values({' + source + '})[0]'
|
|
71
|
+
throw new Error(`@symbo.ls/fetch early: the request function of ${where} does not compile on its own`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const requestsSource = (requests, where) => {
|
|
75
|
+
if (typeof requests === 'function' || typeof requests === 'string') return functionSource(requests, where)
|
|
76
|
+
return JSON.stringify(Array.isArray(requests) ? requests : [])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The language the app will ask in on its first view: polyglot's stored
|
|
80
|
+
// choice, else the project's `state.lang`, else polyglot's default — the
|
|
81
|
+
// order polyglot's initPolyglot resolves it in. No polyglot: `state.lang`, or
|
|
82
|
+
// none (a declarative fetch then sends no Accept-Language either).
|
|
83
|
+
const languageRule = (project) => {
|
|
84
|
+
const stateLang = project.state && typeof project.state.lang === 'string' ? project.state.lang : ''
|
|
85
|
+
const polyglot = project.polyglot
|
|
86
|
+
if (polyglot && typeof polyglot === 'object') {
|
|
87
|
+
return { storage: polyglot.storageLangKey || POLYGLOT_LANG_KEY, fallback: stateLang || polyglot.defaultLang || 'en' }
|
|
88
|
+
}
|
|
89
|
+
return { storage: '', fallback: stateLang }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// `early.query` names configured headers; the script needs their values. A
|
|
93
|
+
// name the adapter does not configure is a mistake worth stopping the build
|
|
94
|
+
// for: those GETs would go out without the key and be refused, every time.
|
|
95
|
+
const queryParams = (mapping, headers) => {
|
|
96
|
+
const params = {}
|
|
97
|
+
for (const name in mapping || {}) {
|
|
98
|
+
const header = Object.keys(headers || {}).find((key) => key.toLowerCase() === name.toLowerCase())
|
|
99
|
+
if (!header) throw new Error(`@symbo.ls/fetch early.query: "${name}" is not one of the adapter's configured headers`)
|
|
100
|
+
params[mapping[name]] = headers[header]
|
|
101
|
+
}
|
|
102
|
+
return params
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const fetchOptions = (options) => {
|
|
106
|
+
const picked = {}
|
|
107
|
+
for (const key of FETCH_OPTION_KEYS) {
|
|
108
|
+
if (options && options[key] !== undefined) picked[key] = options[key]
|
|
109
|
+
}
|
|
110
|
+
// An early answer must be a fresh one, as the adapter's own request is (an
|
|
111
|
+
// RPC is a POST, which nothing caches): unless the adapter configures a
|
|
112
|
+
// cache mode, the early GET skips the HTTP cache.
|
|
113
|
+
if (picked.cache === undefined) picked.cache = 'no-store'
|
|
114
|
+
return picked
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The pre-boot script for a project, or '' when it declares no early
|
|
119
|
+
* requests (or its adapter is not REST — the script can only build REST
|
|
120
|
+
* addresses).
|
|
121
|
+
*
|
|
122
|
+
* @param {Object} project `{ fetch, polyglot, state }` — config.js and
|
|
123
|
+
* state.js, as source modules or frank JSON (request functions may be
|
|
124
|
+
* strings).
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
export const createEarlyScript = (project = {}) => {
|
|
128
|
+
const fetch = project && project.fetch
|
|
129
|
+
// `early.query` / `early.rpc` default to the adapter's `reads` (early.js
|
|
130
|
+
// withReads), exactly as the adapter resolves them.
|
|
131
|
+
const early = fetch && withReads(fetch.early, fetch.reads)
|
|
132
|
+
if (!early || typeof early !== 'object' || fetch.adapter !== 'rest' || !fetch.url) return ''
|
|
133
|
+
const routes = early.routes && typeof early.routes === 'object' ? Object.keys(early.routes) : []
|
|
134
|
+
if (!routes.length && !early.always) return ''
|
|
135
|
+
|
|
136
|
+
const config = {
|
|
137
|
+
url: fetch.url,
|
|
138
|
+
query: queryParams(early.query, fetch.headers),
|
|
139
|
+
rpc: early.rpc === 'get',
|
|
140
|
+
fetchOptions: fetchOptions(fetch.fetchOptions),
|
|
141
|
+
lang: languageRule(project)
|
|
142
|
+
}
|
|
143
|
+
const routeTable = routes
|
|
144
|
+
.map((pattern) => `[${JSON.stringify(pattern)}, ${requestsSource(early.routes[pattern], JSON.stringify(pattern))}]`)
|
|
145
|
+
.join(',\n ')
|
|
146
|
+
const always = early.always ? requestsSource(early.always, '`always`') : '[]'
|
|
147
|
+
|
|
148
|
+
const source = `/* @symbo.ls/fetch — fetch.early: the first view's requests, sent before the app boots */
|
|
149
|
+
;(function () {
|
|
150
|
+
var g = globalThis
|
|
151
|
+
var K = Symbol.for(${JSON.stringify(EARLY_KEY)})
|
|
152
|
+
if (g[K] || typeof g.fetch !== 'function' || !g.location) return
|
|
153
|
+
var registry = g[K] = { time: Date.now(), entries: {} }
|
|
154
|
+
var config = ${JSON.stringify(config)}
|
|
155
|
+
var buildUrl = ${buildUrl.toString()}
|
|
156
|
+
var selectQuery = ${selectQuery.toString()}
|
|
157
|
+
var earlyKey = ${earlyKey.toString()}
|
|
158
|
+
var isScalarParams = ${isScalarParams.toString()}
|
|
159
|
+
var matchEarlyRoute = ${matchEarlyRoute.toString()}
|
|
160
|
+
var always = ${always}
|
|
161
|
+
var routes = [
|
|
162
|
+
${routeTable}
|
|
163
|
+
]
|
|
164
|
+
var storage = function (key) {
|
|
165
|
+
try { return key ? g.localStorage.getItem(key) : null } catch (e) { return null }
|
|
166
|
+
}
|
|
167
|
+
var lang = (config.lang.storage && storage(config.lang.storage)) || config.lang.fallback || ''
|
|
168
|
+
var ctx = { path: g.location.pathname, query: new URLSearchParams(g.location.search), lang: lang, storage: storage }
|
|
169
|
+
var list = function (requests, params) {
|
|
170
|
+
try {
|
|
171
|
+
var out = typeof requests === 'function' ? requests(params, ctx) : requests
|
|
172
|
+
return Array.isArray(out) ? out : []
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return []
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
var match = matchEarlyRoute(routes.map(function (route) { return route[0] }), ctx.path)
|
|
178
|
+
var requests = list(always, {}).concat(match ? list(routes[match.index][1], match.params) : [])
|
|
179
|
+
for (var i = 0; i < requests.length; i++) {
|
|
180
|
+
try {
|
|
181
|
+
var request = requests[i]
|
|
182
|
+
if (!request || typeof request !== 'object' || !request.from) continue
|
|
183
|
+
var method = request.method || 'select'
|
|
184
|
+
var url
|
|
185
|
+
if (method === 'select') url = buildUrl(config.url, request.from, selectQuery(request))
|
|
186
|
+
else if (method === 'rpc' && config.rpc && isScalarParams(request.params)) url = buildUrl(config.url, 'rpc/' + request.from, request.params)
|
|
187
|
+
else continue
|
|
188
|
+
var language = request.lang === false ? '' : lang
|
|
189
|
+
var key = earlyKey(url, language)
|
|
190
|
+
if (registry.entries[key]) continue
|
|
191
|
+
var address = new URL(url)
|
|
192
|
+
for (var name in config.query) address.searchParams.set(name, config.query[name])
|
|
193
|
+
var options = Object.assign({}, config.fetchOptions, { method: 'GET' })
|
|
194
|
+
if (language) options.headers = { 'Accept-Language': language }
|
|
195
|
+
registry.entries[key] = { time: Date.now(), answer: g.fetch(address.toString(), options).catch(function () { return null }) }
|
|
196
|
+
} catch (e) {}
|
|
197
|
+
}
|
|
198
|
+
})()
|
|
199
|
+
`
|
|
200
|
+
// Safe to inline too: no literal `</script` can end an inline tag early.
|
|
201
|
+
return source.replace(/<\/script/gi, '<\\/script')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default createEarlyScript
|