@symbo.ls/fetch 3.14.663 → 3.14.665
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 +18 -1
- package/adapters/rest.js +79 -11
- package/dist/cjs/adapters/rest.js +1 -1
- package/dist/cjs/early.js +1 -1
- package/dist/cjs/earlyScript.js +15 -16
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/inflight.js +1 -0
- package/dist/esm/adapters/rest.js +1 -1
- package/dist/esm/early.js +1 -1
- package/dist/esm/earlyScript.js +14 -15
- package/dist/esm/index.js +1 -1
- package/dist/esm/inflight.js +1 -0
- package/early.js +104 -37
- package/earlyScript.js +16 -19
- package/index.js +95 -56
- package/inflight.js +39 -0
- package/package.json +3 -3
package/dist/esm/earlyScript.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`),
|
|
1
|
+
import{polyglotLangStorageKey as l,polyglotFallbackLang as f}from"@symbo.ls/utils";import{buildUrl as g,selectQuery as y,withQuery as h}from"./adapters/rest.js";import{EARLY_KEY as m,earlyKey as p,isScalarParams as d,matchEarlyRoute as v,withReads as q,queryHeaderEntries as S}from"./early.js";const b=["credentials","mode","cache","redirect","referrerPolicy","priority"],i=r=>{try{return new Function(r),!0}catch{return!1}},w=(r,t)=>{const e=typeof r=="function"?r.toString():String(r).trim();if(i("return ("+e+")"))return"("+e+")";if(i("return ({"+e+"})"))return"Object.values({"+e+"})[0]";throw new Error(`@symbo.ls/fetch early: the request function of ${t} does not compile on its own`)},u=(r,t)=>typeof r=="function"||typeof r=="string"?w(r,t):JSON.stringify(Array.isArray(r)?r:[]),O=r=>{const t=r.state&&typeof r.state.lang=="string"?r.state.lang:"",e=r.polyglot;return e&&typeof e=="object"?{storage:l(e),fallback:f(t,e)}:{storage:"",fallback:t}},$=(r,t)=>{const e={};for(const{name:a,param:o,found:n,value:s}of S(r,t)){if(!n)throw new Error(`@symbo.ls/fetch early.query: "${a}" is not one of the adapter's configured headers`);e[o]=s}return e},k=r=>{const t={};for(const e of b)r&&r[e]!==void 0&&(t[e]=r[e]);return t.cache===void 0&&(t.cache="no-store"),t},E=(r={})=>{const t=r&&r.fetch,e=t&&q(t.early,t.reads);if(!e||typeof e!="object"||t.adapter!=="rest"||!t.url)return"";const a=e.routes&&typeof e.routes=="object"?Object.keys(e.routes):[];if(!a.length&&!e.always)return"";const o={url:t.url,query:$(e.query,t.headers),rpc:e.rpc==="get",fetchOptions:k(t.fetchOptions),lang:O(r)},n=a.map(c=>`[${JSON.stringify(c)}, ${u(e.routes[c],JSON.stringify(c))}]`).join(`,
|
|
2
|
+
`),s=e.always?u(e.always,"`always`"):"[]";return`/* @symbo.ls/fetch \u2014 fetch.early: the first view's requests, sent before the app boots */
|
|
3
3
|
;(function () {
|
|
4
4
|
var g = globalThis
|
|
5
|
-
var K = Symbol.for(${JSON.stringify(
|
|
5
|
+
var K = Symbol.for(${JSON.stringify(m)})
|
|
6
6
|
if (g[K] || typeof g.fetch !== 'function' || !g.location) return
|
|
7
|
-
var registry = g[K] = {
|
|
7
|
+
var registry = g[K] = { entries: {} }
|
|
8
8
|
var config = ${JSON.stringify(o)}
|
|
9
|
-
var buildUrl = ${
|
|
10
|
-
var selectQuery = ${
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
9
|
+
var buildUrl = ${g.toString()}
|
|
10
|
+
var selectQuery = ${y.toString()}
|
|
11
|
+
var withQuery = ${h.toString()}
|
|
12
|
+
var earlyKey = ${p.toString()}
|
|
13
|
+
var isScalarParams = ${d.toString()}
|
|
14
|
+
var matchEarlyRoute = ${v.toString()}
|
|
15
|
+
var always = ${s}
|
|
15
16
|
var routes = [
|
|
16
|
-
${
|
|
17
|
+
${n}
|
|
17
18
|
]
|
|
18
19
|
var storage = function (key) {
|
|
19
20
|
try { return key ? g.localStorage.getItem(key) : null } catch (e) { return null }
|
|
@@ -42,12 +43,10 @@ import{buildUrl as l,selectQuery as f}from"./adapters/rest.js";import{EARLY_KEY
|
|
|
42
43
|
var language = request.lang === false ? '' : lang
|
|
43
44
|
var key = earlyKey(url, language)
|
|
44
45
|
if (registry.entries[key]) continue
|
|
45
|
-
var address = new URL(url)
|
|
46
|
-
for (var name in config.query) address.searchParams.set(name, config.query[name])
|
|
47
46
|
var options = Object.assign({}, config.fetchOptions, { method: 'GET' })
|
|
48
47
|
if (language) options.headers = { 'Accept-Language': language }
|
|
49
|
-
registry.entries[key] = { time: Date.now(), answer: g.fetch(
|
|
48
|
+
registry.entries[key] = { time: Date.now(), answer: g.fetch(withQuery(url, config.query), options).catch(function () { return null }) }
|
|
50
49
|
} catch (e) {}
|
|
51
50
|
}
|
|
52
51
|
})()
|
|
53
|
-
`.replace(/<\/script/gi,"<\\/script")};var
|
|
52
|
+
`.replace(/<\/script/gi,"<\\/script")};var A=E;export{E as createEarlyScript,A as default};
|
package/dist/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isArray as Q,isFunction as l,isObject as Z,isString as _e,exec as Be}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"],Ge={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]||Ge[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]||Ge[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,Y=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=Y.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)=>(Y.has(e)||Y.set(e,new Set),Y.get(e).add(t),()=>{const r=Y.get(e);r&&(r.delete(t),r.size===0&&Y.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},x=new Set,Ye=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=Y.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 x)Ye(t)||x.delete(t)},3e4),we.unref&&we.unref())};ht();const xe=3,Re=e=>Math.min(1e3*2**e,3e4),Ve=(e,t)=>{const r=e.retry;return r===void 0&&t?{count:0}:r===!1?{count:0}:r===!0||r===void 0?{count:xe,delay:Re}:typeof r=="number"?{count:r,delay:Re}:{count:r.count??xe,delay:l(r.delay)?r.delay:typeof r.delay=="number"?()=>r.delay:Re}},We=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},E=(e,t,...r)=>{const s=l(e[t])?e[t]:null;s&&s.call(e,...r,e,e.state,e.context)},F=(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},Ee=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:V,as:L,limit:z,offset:U,order:b,headers:re,baseUrl:m,responseType:W,page:T,cursor:M,infinite:K,placeholderData:J,initialData:H,select:g,keepPreviousData:k}=e;let j=_e(g)?g:void 0;if(D&&l(t.getQuery)){const p=t.getQuery(d.name||"paths");p&&(j=p.select||p.length&&p.join(",")||j)}const B=ve(De,t,r),A=ie(T,t),me=ie(M,t),le=Ie(e),de=Ee(e)||le?null:Oe(Pe),nt=Ve(e,le||Xe(h)),Ke=ae(t,r),y=de?Le(e,B,i,Ke,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);K&&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"}),E(t,"onFetchError",c),F(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=!K&&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),E(t,"onFetchComplete",f),F(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"}),E(t,"onFetchStart"),F(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:Ke});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"}),E(t,"onFetchError",S),F(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:j,params:B,single:ce,limit:z,offset:U,order:b,headers:re,baseUrl:m};W!==void 0&&(c.responseType=W);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(V===!0||e.on==="submit")f=ue(t);else if(Q(V)){const S=ue(t);f={};for(const G of V)f[G]=S[G]}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=B;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:B,on:e.subscribeOn},(S,G,je)=>{a()&&($(t,S,L),E(t,"onFetchComplete",S),F(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=We(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"}),E(t,"onFetchError",w),F(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 G=!K&&be(y,X);if(G)f=G.data;else{const je=oe(y,c,null,X,ke(y,X));je.gcTime=de?.gcTime||3e5}}let S=f;if(K&&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),E(t,"onFetchComplete",S),F(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"}),E(t,"onFetchError",p),F(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=Ee(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 x){if(!Ye(r)){x.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)))},Et=e=>{for(const[t]of v)e(t)&&ge(t);for(const[t]of R)e(t)&&q.set(t,++I)},Ft=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:V,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(V)&&V(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"}),E(t,"onFetchStart"),F(e,t,"onFetchStart");const m=r?.analyze?.emitNetwork,W=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 j=await o.getSession();if(D===!0&&!j){re!==void 0&&$e(t,e);const B={message:"Not authenticated"};E(t,"onFetchError",B),F(e,t,"onFetchError",B);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 K=ae(t,r);K&&(M.headers={...M.headers,"Accept-Language":K});const J=Ve(e,!0),H=await We(()=>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"}),E(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())-W});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 j=_?{[_]:g}:g;t.state?.update&&t.state.update(j,{preventFetch:!0})}E(t,"onFetchComplete",g),F(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())-W}),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"}),E(t,"onFetchError",T),F(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())-W})}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||[])x.delete(r);e.__fetchCleanups=[],e.__fetchListeners=[],e.__unsubscribe=null,e.__fetchMounted=[]},rt=(e,t,r,s)=>{if(!e||!s?.fetch)return;const n=Be(e,t);if(!n)return;const i=Q(n)?n.map(a=>He(Be(a,t),t)).filter(Boolean):[He(n,t)].filter(Boolean),u=t.__ref;if(u.__fetchMounted)for(const a of u.__fetchMounted)x.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?Ft: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),!Ee(a))){const D={element:t,ref:u,config:a,context:s};x.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})}},Fe={invalidateQueries:e=>et(ze(e)),removeQueries:e=>{if(!e){ge();return}Et(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&&!Ee(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 Fe.invalidateQueries(e)},removeQueries(e){return Fe.removeQueries(e)},getQueryData(e){return Fe.getQueryData(st(e,this))},setQueryData(e,t){return Fe.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))}};import{earlyKey as Ut,EARLY_KEY as Qt,createEarlySeedScript as $t}from"./early.js";var It=rt;export{Qt as EARLY_KEY,Lt as createAdapter,$t as createEarlySeedScript,It as default,Ut as earlyKey,rt as executeFetch,Ot as fetchFunctions,Rt as fetchPlugin,Mt as hasAdapter,Ne as initAdapterAuth,se as parseDuration,Fe as queryClient,Ct as registerAdapter,dt as resolveDb};
|
|
1
|
+
import{isArray as q,isFunction as f,isObject as H,isString as _e,exec as He}from"@symbo.ls/utils";import{createEffect as yt}from"@symbo.ls/signal";import{dropEarly as bt}from"./early.js";import*as St from"./adapters/rest.js";const wt=["select","rpc","insert","update","upsert","delete","subscribe"],Xe={rest:()=>Promise.resolve(St),local:()=>import("./adapters/local.js")},ye=Symbol.for("@symbo.ls/fetch:CUSTOM_ADAPTERS"),X=(()=>{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})(),be=Symbol.for("@symbo.ls/fetch:PENDING_ADAPTERS"),Oe=(()=>{if(typeof globalThis<"u"&&globalThis[be])return globalThis[be];const e={};if(typeof globalThis<"u")try{return Object.defineProperty(globalThis,be,{value:e,writable:!1,configurable:!1,enumerable:!1}),globalThis[be]}catch{}return e})(),gt=5e3,Tt=e=>new Promise(t=>{if(X[e])return t(X[e]);const r=Oe[e]||=[];let s=!1;const o=()=>{s||(s=!0,t(X[e]||null))};r.push(o),setTimeout(o,gt)}),Ut=(e,t)=>{X[e]=t;const r=Oe[e];if(r&&r.length){Oe[e]=[];for(const s of r)s()}},Qt=e=>typeof e=="string"&&!!(X[e]||Xe[e]),$t=e=>{const t={};for(const r of wt)e[r]&&(t[r]=e[r]);return t.name=e.name||"custom",t},mt=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=X[s]||Xe[s];if(!o&&(o=await Tt(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)},re=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},m=new Map,x=new Map,I=new Map;let Se=0;const we=new Map,Le=new WeakMap,Re=(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,l=r?`${r}:`:"",h=s?`:${s}`:"",p=o?":p"+JSON.stringify(o):"",v=n!=null?":c"+JSON.stringify(n):"";return`${i}:${u}:${l}${JSON.stringify(t||"")}${a?":infinite":""}${p}${v}${h}`},se=e=>m.get(e)||null,ne=(e,t,r,s=++Se,o=!1)=>{const n={data:t,error:r,time:Date.now(),stale:o,seq:s};m.set(e,n);const i=x.get(e);if(i)for(const u of i)f(u)&&u(n);return n},ge=(e,t)=>{const r=e?m.get(e):null;return r&&!r.error&&r.seq>t?r:null},qe=(e,t)=>(we.get(e)||0)>t,Te=e=>{we.set(e,++Se)},Ze=e=>{m.delete(e),Te(e)},zt=(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))}),Ie=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:re(e),gcTime:3e5}:{staleTime:re(e.stale||e.staleTime)||6e4,gcTime:re(e.gc||e.gcTime||e.expire)||3e5,key:e.key},N=new Set,et=e=>!!e.ref&&e.element.__ref===e.ref,vt=({config:e,element:t,context:r})=>{try{return Qe(e,t,r)}catch{return null}};let me=null;const Et=()=>{me||(me=setInterval(()=>{const e=Date.now();for(const[t,r]of m){const s=x.get(t);!(s&&s.size>0)&&e-r.time>(r.gcTime||3e5)&&m.delete(t)}for(const[t]of we)!m.has(t)&&!I.has(t)&&we.delete(t);for(const t of N)et(t)||N.delete(t)},3e4),me.unref&&me.unref())};Et();const tt=3,Ne=e=>Math.min(1e3*2**e,3e4),rt=(e,t)=>{const r=e.retry;return r===void 0&&t?{count:0}:r===!1?{count:0}:r===!0||r===void 0?{count:tt,delay:Ne}:typeof r=="number"?{count:r,delay:Ne}:{count:r.count??tt,delay:f(r.delay)?r.delay:typeof r.delay=="number"?()=>r.delay:Ne}},st=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=f(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=f(s)?s(n,i):s;await new Promise(a=>setTimeout(a,u));continue}throw i}return{data:null,error:o}},Z={focus:new Set,online:new Set};let nt=!1;const Dt=()=>{if(nt||typeof window>"u")return;nt=!0;const e=()=>{for(const r of Z.focus)r()},t=()=>{for(const r of Z.online)r()};window.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&e()}),window.addEventListener("focus",e),window.addEventListener("online",t)},ot=(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(H(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},Pt=["insert","update","upsert","delete"],at=e=>Pt.includes(e),Ue=e=>e.mutation===!0?!0:e.mutation===!1||e.method!=="rpc"?!1:e.on==="submit"||e.on==="click"||e.invalidates!=null,oe=(e,t)=>{const r=e?.state?.root||t?.state?.root;if(r?.lang)return r.lang},ve=(e,t)=>{const s=(e?.state?.root||t?.state?.root)?.__fetchScope;return s?String(s):void 0},Ee=(e,t,r)=>{let s;if(!e)s=void 0;else if(f(e))s=e(t,t.state);else{s={};for(const o in e){const n=e[o];s[o]=f(n)?n(t,t.state):n}}return s},ae=(e,t)=>f(e)?e(t,t.state):e,Qe=(e,t,r)=>Re(e,Ee(e.params,t,r),ve(t,r),oe(t,r),ae(e.page,t),ae(e.cursor,t)),$e=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)})},ze=async(e,t)=>{if(f(e.select))return e.auth!==!1&&await $e(e,t),e;if(e.__resolved)return e.auth!==!1&&await $e(e.__resolved,t),e.__resolved;if(e.__resolving)return e.__resolving;e.__resolving=mt(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 $e(r,t),r):null},F=(e,t,...r)=>{const s=f(e[t])?e[t]:null;s&&s.call(e,...r,e,e.state,e.context)},k=(e,t,r,...s)=>{const o=e&&f(e[r])?e[r]:null;o&&o.call(t,...s,t,t.state,t.context)},ie=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},De=e=>e.responseType==="stream"||e.responseType==="response",Ft=["Blob","File","ArrayBuffer","ReadableStream","Response"],je=e=>e!==null&&typeof e=="object"&&(ArrayBuffer.isView(e)||Ft.includes(Object.prototype.toString.call(e).slice(8,-1))),U=(e,t,r,s={})=>{if(!r&&je(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)&&!je(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})}},Be=Object.freeze({isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!0,error:null,status:"success",fetchStatus:"idle"}),Ke=e=>({isFetching:!1,isLoading:!1,isStale:!1,isSuccess:!1,error:e,status:"error",fetchStatus:"idle"}),Q=(e,t)=>{const r=e?.__ref;r&&(r.__fetchStatus={...t,isError:!!t.error},r.__fetching=t.isFetching,r.__fetchError=t.error)},it=(e,t,r)=>{const s=e.__pages&&e.__pages.length?e.__pages:[r];f(t.getNextPageParam)&&(e.__nextPageParam=t.getNextPageParam(s[s.length-1],s),e.__hasNextPage=e.__nextPageParam!=null),f(t.getPreviousPageParam)&&(e.__prevPageParam=t.getPreviousPageParam(s[0],s),e.__hasPreviousPage=e.__prevPageParam!=null)},O=async(e,t,r,s={})=>{const o=r?.fetch;if(!o||!t||!t.__ref)return;const n=t.__ref,i=ve(t,r);(!n.__fetchScopeOwner||n.__fetchScopeOwner.scope!==i)&&(n.__fetchScopeOwner={scope:i});const u=n.__fetchScopeOwner,a=()=>t.__ref===n&&n.__fetchScopeOwner===u&&ve(t,r)===i;if(e.enabled===!1||f(e.enabled)&&!e.enabled(t,t.state)||e.skip===!0||f(e.skip)&&e.skip(t,t.state))return;const l=await ze(o,r);if(!l||!a())return;const{from:h,method:p,query:v,params:Fe,cache:Ae,transform:$,single:ue,auth:ce,fields:Y,as:A,limit:z,offset:L,order:S,headers:te,baseUrl:E,responseType:G,page:g,cursor:P,infinite:j,placeholderData:W,initialData:V,select:T,keepPreviousData:C}=e,B=c=>(f(T)&&(c=T(c,t,t.state)),$&&(c=$(c,t,t.state)),c),R=c=>{Q(t,Ke(c)),F(t,"onFetchError",c),k(e,t,"onFetchError",c)};let Ce=_e(T)?T:void 0;if(v&&f(t.getQuery)){const c=t.getQuery(l.name||"paths");c&&(Ce=c.select||c.length&&c.join(",")||Ce)}const fe=Ee(Fe,t,r),D=ae(g,t),Me=ae(P,t),le=Ue(e),de=De(e)||le?null:Ie(Ae),pt=rt(e,le||at(p)),Ve=oe(t,r),y=de?Re(e,fe,i,Ve,D,Me):null;if(!n.__fetchInitialized&&y&&(n.__fetchInitialized=!0,V!==void 0)){const c=f(V)?V():V;ne(y,c,null)}if((p==="select"||p==="rpc")&&y&&de&&!s.refresh&&!s.force){const c=se(y);if(c&&!c.error){const d=Date.now()-c.time,w=c.stale||d>(de.staleTime||0);j&&q(c.data)&&!n.__pages&&(n.__pages=[c.data],it(n,e,c.data));const _=B(c.data);if(!a())return;if(!w){Q(t,Be),U(t,_,A);return}U(t,_,A)}else if(W!==void 0){const d=f(W)?W(t,t.state):W;d!==void 0&&U(t,d,A)}}const pe=y&&!s.force?I.get(y):null;if(pe&&!qe(y,Le.get(pe)||0)){try{const c=await Promise.race([pe,new Promise((d,w)=>setTimeout(()=>w("DEDUP_TIMEOUT"),15e3))]);if(!a())return;if(c?.error)R(c.error);else if(c?.data!==void 0){const d=!j&&ge(y,Le.get(pe)||0),w=B(d?d.data:c.data);if(!a())return;U(t,w,A),F(t,"onFetchComplete",w),k(e,t,"onFetchComplete",w)}}catch(c){if(!a())return;c!=="DEDUP_TIMEOUT"&&R(c)}return}const ht=!se(y)?.data;Q(t,{isFetching:!0,isLoading:ht,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),F(t,"onFetchStart"),k(e,t,"onFetchStart");const M=r?.analyze?.emitNetwork,ke=M?typeof performance<"u"?performance.now():Date.now():0;M&&M({phase:"start",mode:"query",from:h,method:p,cacheKey:y,lang:Ve});const _t=async()=>{if(!a())return;if(ce!==!1&&l.auth!==!1&&l.getSession){const _=await l.getSession();if(!a())return;if(ce===!0&&!_){const b={message:"Not authenticated"};return R(b),{data:null,error:b}}}const c=l[p];if(!f(c))return{data:null,error:{message:`Method "${p}" not found on adapter`}};const d={from:h,select:Ce,params:fe,single:ue,limit:z,offset:L,order:S,headers:te,baseUrl:E};G!==void 0&&(d.responseType=G);const w=oe(t,r);if(w&&(d.headers={...d.headers,"Accept-Language":w}),D!==void 0){if(H(D))D.offset!==void 0&&(d.offset=D.offset),D.limit!==void 0&&(d.limit=D.limit),D.cursor!==void 0&&(d.params={...d.params,cursor:D.cursor});else if(typeof D=="number"){const _=e.pageSize||z||20;d.offset=(D-1)*_,d.limit=_}}if(Me!==void 0&&(d.params={...d.params,cursor:Me}),p==="insert"||p==="update"||p==="upsert"){let _;if(Y===!0||e.on==="submit")_=ie(t);else if(q(Y)){const b=ie(t);_={};for(const K of Y)_[K]=b[K]}else t.state?.parse?_=t.state.parse():H(t.state)&&(_={...t.state});$&&(_=$(_,t,t.state)),d.data=_}if(p==="rpc"){let _=fe;if(e.on==="submit"){const b=ie(t);_=e.transformParams?e.transformParams(b,t,t.state):b}d.params=_,d.read=!le&&e.read!==!1}if(p==="subscribe"){if(f(n.__unsubscribe)){const b=n.__unsubscribe;n.__unsubscribe=null;try{b()}catch{}}const _=l.subscribe({from:h,params:fe,on:e.subscribeOn},(b,K,Je)=>{a()&&(U(t,b,A),F(t,"onFetchComplete",b),k(e,t,"onFetchComplete",b))});return a()?(n.__unsubscribe=_,{data:null,error:null,subscribed:!0}):(f(_)&&_(),{data:null,error:null,subscribed:!0})}return c(d)},J=++Se,he=st(_t,pt);Le.set(he,J),y&&I.set(y,he);try{const c=await he;if(le&&e.invalidates&&c&&!c.error&&!c.subscribed&&ct(e.invalidates,h),!a()||c?.subscribed)return;const{data:d,error:w}=c||{};if(w){y&&!ge(y,J)&&ne(y,null,w,J),R(w),M&&M({phase:"error",mode:"query",from:h,method:p,cacheKey:y,error:w.message||w,durationMs:(typeof performance<"u"?performance.now():Date.now())-ke});return}if(d!==void 0){let _=d;if((p==="select"||p==="rpc")&&y){const K=!j&&ge(y,J);if(K)_=K.data;else{const Je=ne(y,d,null,J,qe(y,J));Je.gcTime=de?.gcTime||3e5}}let b=_;if(j&&q(d)&&((!n.__pages||!s.direction)&&(n.__pages=[]),s.direction==="previous"?n.__pages.unshift(d):n.__pages.push(d),b=n.__pages.flat(),it(n,e,d)),b=B(b),!a())return;Q(t,Be),(!C||b!==void 0)&&U(t,b,A),F(t,"onFetchComplete",b),k(e,t,"onFetchComplete",b),M&&M({phase:"success",mode:"query",ok:!0,from:h,method:p,cacheKey:y,durationMs:(typeof performance<"u"?performance.now():Date.now())-ke})}}catch(c){if(!a())return;R(c),M&&M({phase:"error",mode:"query",from:h,method:p,cacheKey:y,error:c.message||String(c),durationMs:(typeof performance<"u"?performance.now():Date.now())-ke})}finally{y&&I.get(y)===he&&I.delete(y)}},At=(e,t,r)=>{const s=e.on,o=t.__ref;if(!o)return;o.__fetchListeners||(o.__fetchListeners=[]);const n=e.__runner||O;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"&&Ct(e,t,r,n)},Ct=(e,t,r,s)=>{const o=t.__ref;let n;const i=yt(()=>{let u;try{u=JSON.stringify([Ee(e.params,t,r)??null,f(e.enabled)?!!e.enabled(t,t.state):e.enabled!==!1,f(e.skip)?!!e.skip(t,t.state):e.skip===!0,ae(e.page,t)??null])}catch{return}if(n===void 0){n=u;return}u!==n&&(n=u,queueMicrotask(()=>s(e,t,r)))});Array.isArray(o.__effects)?o.__effects.push(i):o.__fetchListeners.push(()=>i.dispose())},Mt=(e,t,r)=>{const s=t.__ref;s.__fetchCleanups||(s.__fetchCleanups=[]),Dt();const o=e.refetchInterval;if(o){const a=typeof o=="number"?o:re(o);if(a>0){const l=setInterval(()=>{(e.refetchIntervalInBackground||typeof document>"u"||document.visibilityState==="visible")&&O(e,t,r,{force:!0})},a);s.__fetchCleanups.push(()=>clearInterval(l))}}const n=De(e),i=e.refetchOnWindowFocus===void 0?!n:e.refetchOnWindowFocus!==!1,u=e.refetchOnReconnect===void 0?!n:e.refetchOnReconnect!==!1;if(i){const a=()=>{const l=Qe(e,t,r),h=se(l),p=Ie(e.cache);(!h||h.stale||Date.now()-h.time>(p?.staleTime||0))&&O(e,t,r,{force:!0})};Z.focus.add(a),s.__fetchCleanups.push(()=>Z.focus.delete(a))}if(u){const a=()=>O(e,t,r,{force:!0});Z.online.add(a),s.__fetchCleanups.push(()=>Z.online.delete(a))}},kt=(e,t,r)=>{if(!t.optimistic)return null;const s=e.__ref,o=t.as,n=e.state?.parse?e.state.parse():H(e.state)?{...e.state}:e.state;s.__optimisticSnapshot=n;const i=f(t.optimistic)?t.optimistic(r,n,e):t.optimistic;return i!==void 0&&U(e,i,o),n},xe=(e,t)=>{const r=e.__ref;if(!r||!t.optimistic)return;const s=r.__optimisticSnapshot;s!==void 0&&(U(e,s,t.as),delete r.__optimisticSnapshot)},Ye=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)):f(e)?t=>{try{return!!e(t)}catch{return!1}}:()=>!1},Ot=e=>{const t=[];for(const r of N){if(!et(r)){N.delete(r);continue}const s=vt(r);s&&e(s)&&t.push(O(r.config,r.element,r.context,{refresh:!0}))}return Promise.allSettled(t).then(()=>{})},ut=e=>{bt();for(const[t,r]of m)e(t)&&(r.stale=!0,Te(t));for(const[t]of I)!m.has(t)&&e(t)&&Te(t);return Ot(e)},ct=(e,t)=>{const r=(q(e)?e:[e]).map(s=>s===!0||s==="*"?o=>o.startsWith(t+":"):s?Ye(s):()=>!1);return ut(s=>r.some(o=>o(s)))},Ge=e=>{for(const[t]of m)e(t)&&Ze(t);for(const[t]of I)e(t)&&Te(t)},We=()=>!0,ft=new WeakSet,ee=e=>{!e||!e.isolateFetchCache||ft.has(e)||(ft.add(e),Ge(We),N.clear())},Lt=async(e,t,r)=>{const s=r?.fetch;if(!s)return;const o=await ze(s,r);if(!o)return;const n=t.__ref,{method:i,from:u,fields:a,transform:l,as:h,on:p,auth:v,headers:Fe,baseUrl:Ae,responseType:$,invalidates:ue,optimistic:ce,onMutate:Y,onSuccess:A,onError:z,onSettled:L}=e;let S;if(a===!0||p==="submit")S=ie(t);else if(q(a)){const g=ie(t);S={};for(const P of a)S[P]=g[P]}else t.state?.parse?S=t.state.parse():H(t.state)&&(S={...t.state});l&&(S=l(S,t,t.state)),f(Y)&&Y(S,t,t.state);const te=ce?kt(t,e,S):null;Q(t,{isFetching:!0,isLoading:!1,isStale:!1,isSuccess:!1,error:null,status:"pending",fetchStatus:"fetching"}),F(t,"onFetchStart"),k(e,t,"onFetchStart");const E=r?.analyze?.emitNetwork,G=E?typeof performance<"u"?performance.now():Date.now():0;E&&E({phase:"start",mode:"mutation",from:u,method:i});try{if(v!==!1&&o.auth!==!1&&o.getSession){const B=await o.getSession();if(v===!0&&!B){te!==void 0&&xe(t,e);const R={message:"Not authenticated"};F(t,"onFetchError",R),k(e,t,"onFetchError",R);return}}const g=o[i];if(!f(g))return;const P={from:u,data:S,headers:Fe,baseUrl:Ae};$!==void 0&&(P.responseType=$),e.params&&(P.params=Ee(e.params,t,r));const j=oe(t,r);j&&(P.headers={...P.headers,"Accept-Language":j});const W=rt(e,!0),V=await st(()=>g(P),W),{data:T,error:C}=V||{};if(C){te!==void 0&&xe(t,e),Q(t,Ke(C)),F(t,"onFetchError",C),f(z)&&z(C,S,t),f(L)&&L(null,C,S,t),E&&E({phase:"error",mode:"mutation",from:u,method:i,error:C.message||String(C),durationMs:(typeof performance<"u"?performance.now():Date.now())-G});return}if(delete n.__optimisticSnapshot,Q(t,Be),T!==void 0&&(h||!je(T))){const B=h?{[h]:T}:T;t.state?.update&&t.state.update(B,{preventFetch:!0})}F(t,"onFetchComplete",T),k(e,t,"onFetchComplete",T),f(A)&&A(T,S,t),f(L)&&L(T,null,S,t),E&&E({phase:"success",mode:"mutation",ok:!0,from:u,method:i,durationMs:(typeof performance<"u"?performance.now():Date.now())-G}),ue&&ct(ue,u)}catch(g){te!==void 0&&xe(t,e),Q(t,Ke(g)),F(t,"onFetchError",g),k(e,t,"onFetchError",g),f(z)&&z(g,S,t),f(L)&&L(null,g,S,t),E&&E({phase:"error",mode:"mutation",from:u,method:i,error:g.message||String(g),durationMs:(typeof performance<"u"?performance.now():Date.now())-G})}finally{n.__fetching=!1}},Rt=e=>{const t=r=>{try{r()}catch{}};for(const r of e.__fetchCleanups||[])t(r);for(const r of e.__fetchListeners||[])t(r);f(e.__unsubscribe)&&t(e.__unsubscribe);for(const r of e.__fetchMounted||[])N.delete(r);e.__fetchCleanups=[],e.__fetchListeners=[],e.__unsubscribe=null,e.__fetchMounted=[]},lt=(e,t,r,s)=>{if(!e||!s?.fetch)return;ee(s);const n=He(e,t);if(!n)return;const i=q(n)?n.map(a=>ot(He(a,t),t)).filter(Boolean):[ot(n,t)].filter(Boolean),u=t.__ref;if(u.__fetchMounted)for(const a of u.__fetchMounted)N.delete(a);u.__fetchMounted=[],!u.__fetchDisposer&&q(u.__effects)&&(u.__fetchDisposer={dispose:()=>Rt(u)},u.__effects.push(u.__fetchDisposer));for(const a of i){const l=at(a.method),h=l?Lt:O,p=!l&&!Ue(a)&&a.method!=="subscribe";if(a.on==="create"?h(a,t,s):queueMicrotask(()=>At({...a,__runner:h},t,s)),p&&a.on==="create"&&((a.refetchInterval||a.refetchOnWindowFocus!==!1||a.refetchOnReconnect!==!1)&&Mt(a,t,s),!De(a))){const v={element:t,ref:u,config:a,context:s};N.add(v),u.__fetchMounted.push(v)}}u.refetch=a=>{for(const l of i)O(l,t,s,{force:!0,...a})},u.fetchNextPage=()=>{const a=i[0];if(!a||!a.infinite)return;const l=u.__nextPageParam;if(l==null)return;const h={...a,cursor:l};O(h,t,s,{direction:"next",force:!0})},u.fetchPreviousPage=()=>{const a=i[0];if(!a||!a.infinite)return;const l=u.__prevPageParam;if(l==null)return;const h={...a,cursor:l};O(h,t,s,{direction:"previous",force:!0})}},Pe={invalidateQueries:e=>ut(Ye(e)),removeQueries:e=>{Ge(e?Ye(e):We)},getQueryData:e=>se(e)?.data??void 0,setQueryData:(e,t)=>{const r=se(e),s=f(t)?t(r?.data):t;ne(e,s,null)},prefetchQuery:async(e,t)=>{const r=t?.fetch;if(!r)return;ee(t);const s=await ze(r,t);if(!s)return;const o=e.method||"select",n=ve(null,t),i=oe(null,t),u=Re({...e,method:o},e.params,n,i),a=s[o];if(!f(a))return;const l={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&&(l.headers={...l.headers,"Accept-Language":i}),e.responseType!==void 0&&(l.responseType=e.responseType),o==="rpc"&&(l.read=!Ue(e)&&e.read!==!1);const h=++Se,p=await a(l);if(p?.data!==void 0&&!p?.error&&!De(e)&&!ge(u,h)){const v=ne(u,p.data,null,h,qe(u,h));v.gcTime=Ie(e.cache)?.gcTime||3e5}return p},getCache:()=>m,clear:()=>Ge(We),dropNonLang:e=>{if(!e)return;const t=`:${e}`;for(const[r]of m)r.match(/:([a-z]{2,3})$/i)&&(r.endsWith(t)||Ze(r))}},dt=(e,t)=>H(e)?Qe({method:"select",...e},t,t?.context):e,jt={invalidateQueries(e){return ee(this?.context),Pe.invalidateQueries(e)},removeQueries(e){return ee(this?.context),Pe.removeQueries(e)},getQueryData(e){return ee(this?.context),Pe.getQueryData(dt(e,this))},setQueryData(e,t){return ee(this?.context),Pe.setQueryData(dt(e,this),t)}},Bt={name:"fetch",create(e){const t=e.fetch;t&&(e.parent||I.clear(),lt(t,e,e.state,e.context))}};import{earlyKey as Gt,EARLY_KEY as Wt,createEarlySeedScript as Vt,createEarlySeedBlock as Jt,SEED_BLOCK as Ht}from"./early.js";import{requestsInFlight as Zt,whenRequestsIdle as er}from"./inflight.js";var Kt=lt;export{Wt as EARLY_KEY,Ht as SEED_BLOCK,$t as createAdapter,Jt as createEarlySeedBlock,Vt as createEarlySeedScript,Kt as default,Gt as earlyKey,lt as executeFetch,jt as fetchFunctions,Bt as fetchPlugin,Qt as hasAdapter,$e as initAdapterAuth,re as parseDuration,Pe as queryClient,Ut as registerAdapter,Zt as requestsInFlight,mt as resolveDb,er as whenRequestsIdle};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=Symbol.for("@symbo.ls/fetch:IN_FLIGHT"),s=()=>globalThis[o]||(globalThis[o]={count:0,waiters:[]}),r=()=>{s().count++},c=()=>{const t=s();if(t.count=t.count>0?t.count-1:0,t.count===0&&t.waiters.length){const e=t.waiters;t.waiters=[];for(const n of e)n()}},i=()=>s().count,u=()=>{const t=s();return t.count===0?Promise.resolve():new Promise(e=>{t.waiters.push(e)})};export{c as requestFinished,r as requestStarted,i as requestsInFlight,u as whenRequestsIdle};
|
package/early.js
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
// the pre-boot script runs before any module, so a global is the only thing
|
|
14
14
|
// it can hand over through; the Symbol keeps it off the visible namespace and
|
|
15
15
|
// shared by every copy of this package (same rationale as CUSTOM_ADAPTERS).
|
|
16
|
-
// {
|
|
17
|
-
// The script creates it; an adapter that finds none creates it
|
|
18
|
-
//
|
|
19
|
-
//
|
|
16
|
+
// { entries: { [earlyKey]: { time, answer: Promise<Response|null> } }, seeded? }
|
|
17
|
+
// The script creates it; an adapter that finds none creates it, so a pre-boot
|
|
18
|
+
// script that arrives after the app booted (an async script on a cold cache)
|
|
19
|
+
// finds one and sends nothing: no call would be left to adopt its answers.
|
|
20
20
|
//
|
|
21
21
|
// The pure helpers below (earlyKey, isScalarParams, matchEarlyRoute) are ALSO
|
|
22
22
|
// embedded into the pre-boot script by source (`fn.toString()`), so the script
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
// self-contained: no reference to anything outside their own parameters and
|
|
25
25
|
// the platform's built-ins (URL, decodeURIComponent, …).
|
|
26
26
|
|
|
27
|
+
import { jsonForScript, readJsonDataBlock } from '@symbo.ls/utils'
|
|
28
|
+
|
|
27
29
|
export const EARLY_KEY = '@symbo.ls/fetch:EARLY'
|
|
28
30
|
|
|
29
31
|
// How long an early answer stays adoptable, counted from when the pre-boot
|
|
@@ -97,6 +99,30 @@ export const matchEarlyRoute = (patterns, path) => {
|
|
|
97
99
|
|
|
98
100
|
const registryKey = () => Symbol.for(EARLY_KEY)
|
|
99
101
|
|
|
102
|
+
// The configured headers a `query` mapping (header name → query parameter —
|
|
103
|
+
// `reads.query`, `early.query`) moves into the URL: one entry per mapped name,
|
|
104
|
+
// `{ name, header: lower-cased name, param, found, value }`, matched against
|
|
105
|
+
// `headers` case-insensitively (of two spellings the later wins, as the
|
|
106
|
+
// adapter's mergeHeaders does). One reading for the three that need it: the
|
|
107
|
+
// adapter's anonymous reads (rest.js openReads), the early hand-off
|
|
108
|
+
// (openEarly) and the pre-boot script's build (earlyScript.js queryParams) —
|
|
109
|
+
// so the key the script sends is the key the adapter expects.
|
|
110
|
+
export const queryHeaderEntries = (mapping, headers) => {
|
|
111
|
+
const entries = []
|
|
112
|
+
for (const name in mapping || {}) {
|
|
113
|
+
const header = name.toLowerCase()
|
|
114
|
+
const entry = { name, header, param: mapping[name], found: false, value: undefined }
|
|
115
|
+
for (const key in headers || {}) {
|
|
116
|
+
if (key.toLowerCase() === header) {
|
|
117
|
+
entry.found = true
|
|
118
|
+
entry.value = headers[key]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
entries.push(entry)
|
|
122
|
+
}
|
|
123
|
+
return entries
|
|
124
|
+
}
|
|
125
|
+
|
|
100
126
|
// `fetch.early` with its `query` and `rpc` defaulting to the adapter's
|
|
101
127
|
// `reads` (adapters/rest.js): the same two options, declared once, drive both
|
|
102
128
|
// the pre-boot script and the adapter's own GETs. Used by both sides — the
|
|
@@ -118,30 +144,28 @@ export const withReads = (early, reads) => {
|
|
|
118
144
|
// No `early` config → null, and the adapter never looks (the opt-in).
|
|
119
145
|
export const openEarly = (config, headers) => {
|
|
120
146
|
const key = registryKey()
|
|
147
|
+
// The page's seed block, if it carries one, fills the registry first.
|
|
148
|
+
readSeedBlock()
|
|
121
149
|
if (!config || typeof config !== 'object') {
|
|
122
|
-
// A server-seeded registry (
|
|
123
|
-
//
|
|
124
|
-
// the page carries the answers,
|
|
125
|
-
//
|
|
126
|
-
//
|
|
150
|
+
// A server-seeded registry (the answers a brender pre-render already
|
|
151
|
+
// got: createEarlySeedBlock, or an older page's createEarlySeedScript)
|
|
152
|
+
// is adoptable without `fetch.early`: the page carries the answers,
|
|
153
|
+
// nothing is sent early. Only a call that sends exactly what the server
|
|
154
|
+
// recorded — an anonymous GET, no header beyond Accept-Language — can
|
|
155
|
+
// take one.
|
|
127
156
|
const seeded = globalThis[key]
|
|
128
|
-
if (seeded && seeded.seeded) return { carried: {}, rpc: false, maxAge: EARLY_MAX_AGE }
|
|
157
|
+
if (seeded && seeded.seeded) return { carried: {}, rpc: false, maxAge: EARLY_MAX_AGE, seeded: true }
|
|
129
158
|
return null
|
|
130
159
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
globalThis[key] = registry
|
|
135
|
-
}
|
|
136
|
-
registry.closed = true
|
|
160
|
+
// Any registry closes the hand-off: the pre-boot script sends nothing once
|
|
161
|
+
// one exists (earlyScript.js).
|
|
162
|
+
if (!globalThis[key]) globalThis[key] = { entries: {} }
|
|
137
163
|
const carried = {}
|
|
138
|
-
for (const
|
|
139
|
-
|
|
140
|
-
if (header.toLowerCase() === name.toLowerCase()) carried[header.toLowerCase()] = headers[header]
|
|
141
|
-
}
|
|
164
|
+
for (const { header, found, value } of queryHeaderEntries(config.query, headers)) {
|
|
165
|
+
if (found) carried[header] = value
|
|
142
166
|
}
|
|
143
167
|
const maxAge = typeof config.maxAge === 'number' && config.maxAge >= 0 ? config.maxAge : EARLY_MAX_AGE
|
|
144
|
-
return { carried, rpc: config.rpc === 'get', maxAge }
|
|
168
|
+
return { carried, rpc: config.rpc === 'get', maxAge, seeded: !!globalThis[key].seeded }
|
|
145
169
|
}
|
|
146
170
|
|
|
147
171
|
// The waiting answer for `key`, taken ONCE (a body can be read once; a second
|
|
@@ -149,12 +173,18 @@ export const openEarly = (config, headers) => {
|
|
|
149
173
|
// the early request failed, was refused (a 4xx/5xx may be the early form's
|
|
150
174
|
// own problem — a query-string key or a GET the backend does not serve — so
|
|
151
175
|
// the call's own request decides), or is older than `maxAge`.
|
|
176
|
+
//
|
|
177
|
+
// A seed block's answer (readSeedBlock) resolves to the answer itself —
|
|
178
|
+
// `{ seeded: true, status, type, body }`, always an ok one — which the REST
|
|
179
|
+
// adapter reads without a Response: no body stream, so the data lands in the
|
|
180
|
+
// same task the app booted in, before the first frame paints.
|
|
152
181
|
export const takeEarly = (key, maxAge = EARLY_MAX_AGE) => {
|
|
153
182
|
const registry = globalThis[registryKey()]
|
|
154
183
|
const entry = registry && registry.entries && registry.entries[key]
|
|
155
184
|
if (!entry) return null
|
|
156
185
|
delete registry.entries[key]
|
|
157
186
|
if (!(Date.now() - entry.time <= maxAge)) return null
|
|
187
|
+
if (entry.seeded) return Promise.resolve(entry.seeded)
|
|
158
188
|
return Promise.resolve(entry.answer).then(
|
|
159
189
|
(res) => (res && res.ok && !res.bodyUsed ? res : null),
|
|
160
190
|
() => null
|
|
@@ -170,25 +200,62 @@ export const dropEarly = () => {
|
|
|
170
200
|
|
|
171
201
|
// --- Server-seeded answers (FW-BRENDER-HYDRATION-AND-CF-DEPLOY-PROVIDER-1) ---
|
|
172
202
|
// A @symbo.ls/brender pre-render records the anonymous GETs its fetch
|
|
173
|
-
// adapter made (url, Accept-Language, status, content type, body).
|
|
174
|
-
// carries them
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// `
|
|
179
|
-
// `entries`: [{ url, lang, status, type, body }].
|
|
180
|
-
|
|
203
|
+
// adapter made (url as sent, Accept-Language, status, content type, body).
|
|
204
|
+
// The page carries them, and they fill the same registry a `fetch.early`
|
|
205
|
+
// pre-boot script fills — keyed with earlyKey, so the REST adapter adopts
|
|
206
|
+
// each answer for the first call that sends exactly that request instead of
|
|
207
|
+
// asking the network again. Marked `seeded`, so an adapter without
|
|
208
|
+
// `fetch.early` adopts them too (openEarly).
|
|
209
|
+
// `entries`: [{ url, lang, status, type, body }].
|
|
210
|
+
|
|
211
|
+
// The recorded answers as the page carries them: `{ k: earlyKey, s: status,
|
|
212
|
+
// t: content type, b: body }`.
|
|
213
|
+
const seedList = (entries) => (Array.isArray(entries) ? entries : [])
|
|
214
|
+
.filter((e) => e && typeof e.url === 'string' && typeof e.body === 'string')
|
|
215
|
+
.map((e) => ({ k: earlyKey(e.url, e.lang || ''), s: e.status || 200, t: e.type || 'application/json', b: e.body }))
|
|
216
|
+
|
|
217
|
+
// The page's answers as a data block — `<script type="application/json"
|
|
218
|
+
// data-fetch-seed>` — which no Content-Security-Policy blocks: XMA's
|
|
219
|
+
// `script-src 'self'` (no 'unsafe-inline') refused the inline
|
|
220
|
+
// createEarlySeedScript below, so a pre-rendered page under it had no seed
|
|
221
|
+
// (XMA report, brender items 1 and 3). The REST adapter reads the block when
|
|
222
|
+
// it opens (readSeedBlock). JSON is embedded with utils jsonForScript, so a
|
|
223
|
+
// body can never close the block.
|
|
224
|
+
export const SEED_BLOCK = 'script[type="application/json"][data-fetch-seed]'
|
|
225
|
+
|
|
226
|
+
export const createEarlySeedBlock = (entries) => {
|
|
227
|
+
const list = seedList(entries)
|
|
228
|
+
if (!list.length) return ''
|
|
229
|
+
return '<script type="application/json" data-fetch-seed>' + jsonForScript(list) + '</script>'
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Fill the registry from the page's seed block — once: the block goes when
|
|
233
|
+
// read. An answer already waiting under the same key (a pre-boot script's)
|
|
234
|
+
// is kept. Each seeded answer is adoptable for EARLY_MAX_AGE from now.
|
|
235
|
+
export const readSeedBlock = () => {
|
|
236
|
+
const read = readJsonDataBlock(globalThis.document, SEED_BLOCK, { remove: true })
|
|
237
|
+
const list = read && read.value
|
|
238
|
+
if (!Array.isArray(list) || !list.length) return
|
|
239
|
+
const key = registryKey()
|
|
240
|
+
const registry = globalThis[key] || (globalThis[key] = { entries: {} })
|
|
241
|
+
if (!registry.entries) registry.entries = {}
|
|
242
|
+
registry.seeded = true
|
|
243
|
+
const time = Date.now()
|
|
244
|
+
for (const e of list) {
|
|
245
|
+
if (!e || typeof e.k !== 'string' || typeof e.b !== 'string' || registry.entries[e.k]) continue
|
|
246
|
+
registry.entries[e.k] = { time, seeded: { seeded: true, status: e.s || 200, type: e.t || 'application/json', body: e.b } }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// The same answers as an inline script (pages from brender up to 3.14.664;
|
|
251
|
+
// the platform's renderer still ships it). Kept for those: a page under a
|
|
252
|
+
// strict Content-Security-Policy needs createEarlySeedBlock.
|
|
181
253
|
export const createEarlySeedScript = (entries) => {
|
|
182
|
-
const list = (
|
|
254
|
+
const list = seedList(entries)
|
|
183
255
|
if (!list.length) return ''
|
|
184
|
-
const json =
|
|
185
|
-
k: earlyKey(e.url, e.lang || ''),
|
|
186
|
-
s: e.status || 200,
|
|
187
|
-
t: e.type || 'application/json',
|
|
188
|
-
b: e.body
|
|
189
|
-
}))).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')
|
|
256
|
+
const json = jsonForScript(list)
|
|
190
257
|
return '<script>(function(){try{var k=Symbol.for(' + JSON.stringify(EARLY_KEY) + '),' +
|
|
191
|
-
'r=globalThis[k]||(globalThis[k]={
|
|
258
|
+
'r=globalThis[k]||(globalThis[k]={entries:{}}),t=Date.now(),l=' + json + ';' +
|
|
192
259
|
'r.seeded=true;r.entries=r.entries||{};' +
|
|
193
260
|
'for(var i=0;i<l.length;i++){var e=l[i];r.entries[e.k]={time:t,answer:Promise.resolve(new Response(e.b,{status:e.s,headers:{"content-type":e.t}}))}}' +
|
|
194
261
|
'}catch(e){}})()</script>'
|
package/earlyScript.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// before any module, so it carries everything itself: the adapter's address,
|
|
9
9
|
// the configured headers it sends as query parameters, the language rule and
|
|
10
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 /
|
|
11
|
+
// adapter's own buildUrl / selectQuery / withQuery and early.js's earlyKey /
|
|
12
12
|
// isScalarParams / matchEarlyRoute — so an early request has exactly the
|
|
13
13
|
// address and identity the adapter computes for the call that adopts it.
|
|
14
14
|
//
|
|
@@ -36,13 +36,9 @@
|
|
|
36
36
|
// makes (they send none). `ctx` is { path, query (URLSearchParams), lang,
|
|
37
37
|
// storage(key) → localStorage value or null }.
|
|
38
38
|
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
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'
|
|
39
|
+
import { polyglotLangStorageKey, polyglotFallbackLang } from '@symbo.ls/utils'
|
|
40
|
+
import { buildUrl, selectQuery, withQuery } from './adapters/rest.js'
|
|
41
|
+
import { EARLY_KEY, earlyKey, isScalarParams, matchEarlyRoute, withReads, queryHeaderEntries } from './early.js'
|
|
46
42
|
|
|
47
43
|
// The fetch() options an early GET may copy from the adapter's — the ones
|
|
48
44
|
// that change what comes back. Headers never: every header but the language
|
|
@@ -78,13 +74,16 @@ const requestsSource = (requests, where) => {
|
|
|
78
74
|
|
|
79
75
|
// The language the app will ask in on its first view: polyglot's stored
|
|
80
76
|
// choice, else the project's `state.lang`, else polyglot's default — the
|
|
81
|
-
// order polyglot's initPolyglot
|
|
82
|
-
//
|
|
77
|
+
// slot and the order polyglot's initPolyglot uses, from the same utils
|
|
78
|
+
// helpers (polyglotLangStorageKey, polyglotFallbackLang): the script must
|
|
79
|
+
// read what the app will, or its requests ask in another language than the
|
|
80
|
+
// app's. No polyglot: `state.lang`, or none (a declarative fetch then sends
|
|
81
|
+
// no Accept-Language either).
|
|
83
82
|
const languageRule = (project) => {
|
|
84
83
|
const stateLang = project.state && typeof project.state.lang === 'string' ? project.state.lang : ''
|
|
85
84
|
const polyglot = project.polyglot
|
|
86
85
|
if (polyglot && typeof polyglot === 'object') {
|
|
87
|
-
return { storage: polyglot
|
|
86
|
+
return { storage: polyglotLangStorageKey(polyglot), fallback: polyglotFallbackLang(stateLang, polyglot) }
|
|
88
87
|
}
|
|
89
88
|
return { storage: '', fallback: stateLang }
|
|
90
89
|
}
|
|
@@ -94,10 +93,9 @@ const languageRule = (project) => {
|
|
|
94
93
|
// for: those GETs would go out without the key and be refused, every time.
|
|
95
94
|
const queryParams = (mapping, headers) => {
|
|
96
95
|
const params = {}
|
|
97
|
-
for (const name
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
params[mapping[name]] = headers[header]
|
|
96
|
+
for (const { name, param, found, value } of queryHeaderEntries(mapping, headers)) {
|
|
97
|
+
if (!found) throw new Error(`@symbo.ls/fetch early.query: "${name}" is not one of the adapter's configured headers`)
|
|
98
|
+
params[param] = value
|
|
101
99
|
}
|
|
102
100
|
return params
|
|
103
101
|
}
|
|
@@ -150,10 +148,11 @@ export const createEarlyScript = (project = {}) => {
|
|
|
150
148
|
var g = globalThis
|
|
151
149
|
var K = Symbol.for(${JSON.stringify(EARLY_KEY)})
|
|
152
150
|
if (g[K] || typeof g.fetch !== 'function' || !g.location) return
|
|
153
|
-
var registry = g[K] = {
|
|
151
|
+
var registry = g[K] = { entries: {} }
|
|
154
152
|
var config = ${JSON.stringify(config)}
|
|
155
153
|
var buildUrl = ${buildUrl.toString()}
|
|
156
154
|
var selectQuery = ${selectQuery.toString()}
|
|
155
|
+
var withQuery = ${withQuery.toString()}
|
|
157
156
|
var earlyKey = ${earlyKey.toString()}
|
|
158
157
|
var isScalarParams = ${isScalarParams.toString()}
|
|
159
158
|
var matchEarlyRoute = ${matchEarlyRoute.toString()}
|
|
@@ -188,11 +187,9 @@ export const createEarlyScript = (project = {}) => {
|
|
|
188
187
|
var language = request.lang === false ? '' : lang
|
|
189
188
|
var key = earlyKey(url, language)
|
|
190
189
|
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
190
|
var options = Object.assign({}, config.fetchOptions, { method: 'GET' })
|
|
194
191
|
if (language) options.headers = { 'Accept-Language': language }
|
|
195
|
-
registry.entries[key] = { time: Date.now(), answer: g.fetch(
|
|
192
|
+
registry.entries[key] = { time: Date.now(), answer: g.fetch(withQuery(url, config.query), options).catch(function () { return null }) }
|
|
196
193
|
} catch (e) {}
|
|
197
194
|
}
|
|
198
195
|
})()
|