@gdwc/drupal-state
Version:
A simple data store to manage application state sourced from Drupal's JSON:API.
21 lines (18 loc) • 7.84 kB
JavaScript
import M from 'zustand/vanilla';
import { Jsona } from 'jsona';
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
import K from 'deepmerge';
import _ from 'isomorphic-fetch';
var j=(a,e={},t)=>_(a,e),f=j;var z=(a,e=f,t=n=>{throw n})=>e(a).then(s=>{if(!s.ok)throw new Error(`Failed to fetch API index.
Tried fetching: ${a}
Server responded with status code: ${s.status}`);return s.json()}).then(s=>s.links||!1).catch(s=>{t(s);}),$=z;var O=(a,e={},t=i=>{throw i},n,s=f)=>s(a,e,n).then(r=>{if(!r.ok)throw new Error(`Failed to fetch JSON:API endpoint.
Tried fetching: ${a}
Server responded with status code: ${r.status}`);return r.json()}).then(r=>r).catch(r=>{t(r);}),R=O;var U=async(a,e,t={},n=!1,s=f,i=r=>{throw r})=>await R(a+"?path="+e+"&_format=json",t,i,n,s),B=U;var H=(a,e,t=f,n=s=>{throw s})=>{let s=Object.keys(e).map(r=>`${r}=${e[r]}`).join("&");return t(a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s}).then(r=>{if(!r.ok)throw new Error(`Unable to fetch token.
The server responded with status code ${r.status}`);return r.ok&&r.json()}).then(r=>r).catch(r=>{n(r);})},G=H;var F=a=>typeof a=="string"||"href"in a;var E=class{apiBase;apiPrefix;defaultLocale;apiRoot;clientId;clientSecret;fetchAdapter;auth;token={accessToken:"",validUntil:0,tokenType:""};debug;store;getState;setState;subscribe;destroy;dataFormatter;onError;noStore;constructor({apiBase:e,apiPrefix:t="jsonapi",defaultLocale:n,clientId:s,clientSecret:i,fetchAdapter:r=f,debug:S=!1,onError:A,noStore:x=!1}){this.apiBase=e,this.apiPrefix=t,this.defaultLocale=n,this.apiRoot=this.assembleApiRoot(),this.clientId=s,this.clientSecret=i,this.auth=!!(this.clientId&&this.clientSecret),this.fetchAdapter=r,this.debug=S,this.dataFormatter=new Jsona,this.noStore=x,!this.debug||console.log("Debug mode:",S),this.store=M(()=>({}));let{getState:P,setState:k,subscribe:m,destroy:u}=this.store;this.getState=P,this.setState=k,this.subscribe=m,this.destroy=u;let g=h=>{throw h};this.onError=A||g;}assembleApiRoot(){return this.apiBase=this.apiBase.replace(/\/\s*$/,""),this.apiPrefix=this.apiPrefix.replace(/^\s*\//,""),this.apiPrefix=this.apiPrefix.slice(-1)==="/"?this.apiPrefix:`${this.apiPrefix}/`,this.defaultLocale?`${this.apiBase}/${this.defaultLocale}/${this.apiPrefix}`:`${this.apiBase}/${this.apiPrefix}`}assembleEndpoint(e,t="",n){let s=new DrupalJsonApiParams,i="";if(n){if(typeof n=="string"&&n[0]==="?")return this.onError(new Error(`Invalid params: Params must not start with "?".
Remove the preceding "?" or use https://www.npmjs.com/package/drupal-jsonapi-params`)),"";s.initialize(n);}return !F(e)||!e?(this.onError(new Error(`Could not assemble endpoint. Check the hrefIndex, and id.
apiBase: ${JSON.stringify(this.apiBase??null)}
index: ${JSON.stringify(e)??null}
id: ${t??null}`)),""):(typeof e=="string"&&(i=e),t&&(i+=`/${t}`),s.getQueryString()&&(i+=`?${s.getQueryString()}`),i)}async getAuthHeader(){if(this.token.validUntil-10*1e3>Date.now())!this.debug||console.log("Using existing auth token");else {!this.debug||console.log("Fetching new auth token");let e={grant_type:"client_credentials",client_id:this.clientId,client_secret:this.clientSecret},t=await G(`${this.apiBase}/oauth/token`,e,this.fetchAdapter,this.onError);this.token={accessToken:t.access_token,validUntil:Date.now()+t.expires_in*1e3,tokenType:t.token_type};}return `${this.token.tokenType} ${this.token.accessToken}`}async fetchApiIndex(e){return await $(e,this.fetchAdapter,this.onError)}async fetchJsonapiEndpoint(e,t={},n,s){return await R(e,t,n,s,this.fetchAdapter)}async fetchData(e,t=!1,n=!1){let s={},i="";if(this.clientId&&this.clientSecret&&!n){let r=new Headers;i=await this.getAuthHeader(),r.append("Authorization",i),s={headers:r};}return await this.fetchJsonapiEndpoint(e,s,this.onError,t)}async getApiIndex(){let t=this.getState().dsApiIndex;if(!t){let n=await this.fetchApiIndex(this.apiRoot);return this.setState({dsApiIndex:n}),this.getState().dsApiIndex}return t}async getObjectByPath({objectName:e,path:t,res:n,params:s,refresh:i=!1,anon:r=!1,query:S=!1}){S&&console.warn("The `query` option is deprecated; Fetching without query...");let A=this.getState(),x=A.dsPathTranslations;if(i||!x?.[`${t}`]||this.noStore){!this.debug||console.log(`No match for ${t} in dsPathTranslations - calling translatePath.`);let g={},h="";if(this.clientId&&this.clientSecret&&!r){let d=new Headers;h=await this.getAuthHeader(),d.append("Authorization",h),g={headers:d};}let c=await B(this.apiBase+"/router/translate-path",t,g,!1,this.fetchAdapter,this.onError);if(c){let d=A.dsPathTranslations;if(d){i&&d[`${t}`]&&delete d[`${t}`];let l={...d,[t]:c};this.setState({dsPathTranslations:l});}else {let l={[t]:c};this.setState({dsPathTranslations:l});}}else return}let m=this.getState().dsPathTranslations[`${t}`].entity.uuid;return await this.getObject({objectName:e,id:m,res:n,params:s,refresh:i,anon:r})}async getObject({objectName:e,id:t,res:n=!1,params:s,all:i=!1,refresh:r=!1,anon:S=!1,query:A=!1}){if(A&&console.warn("The `query` option is deprecated; Fetching without query..."),s!==void 0&&typeof s!="string"&&!(s instanceof DrupalJsonApiParams)){this.onError(new Error("Invalid params: Params must be a string or instance of DrupalJsonApiParams (https://www.npmjs.com/package/drupal-jsonapi-params)"));return}let x=new Error(`Unable to fetch the API Index.
Check that ${this.apiRoot} is a valid jsonapi index`),P=new Error(`Invalid objectName.
Check that ${e} is a valid node in your Drupal instance`),k=this.getState(),m=typeof s=="string"?s:s?.getQueryString(),u=m?`${e}-${m}`:e,g=`${e}Resources`,h=k[u];if(this.noStore&&!i){!this.debug||console.log(`Fetch Resource ${e}`);let c=await this.getApiIndex();if(!c){this.onError(x);return}if(!c[e]){this.onError(P);return}let d=this.assembleEndpoint(c[e].href,t,s),l=await this.fetchData(d,n,S);return this.dataFormatter.deserialize(l)}if(t){let c=m?`${t}-${m}`:t,d=r?!1:k[g];if(d){let p=d[c];if(p)return !this.debug||console.log(`Matched resource ${t} in state`),p?.graphql?p.graphql:this.dataFormatter.deserialize(p)}if(h?.data&&!r){let p=h.data.filter(o=>o.id===t);if(p){!this.debug||console.log(`Matched resource ${t} in collection`);let o={data:p.pop()};return this.dataFormatter.deserialize(o)}}!this.debug||console.log(`Fetch Resource ${t} and add to state`);let l=await this.getApiIndex();if(!l){this.onError(x);return}if(!l[e]){this.onError(P);return}let w=this.assembleEndpoint(l[e].href,t,s),y=await this.fetchData(w,n,S),T=k[g];if(T){let p={...T,[c]:y};this.setState({[g]:p});}else {let p={[c]:y};this.setState({[g]:p});}return this.dataFormatter.deserialize(y)}if(r||!h||h.links?.next&&!h.links?.last&&i||this.noStore){this.debug&&!this.noStore&&console.log(`Fetch Collection ${e} and add to state`);let c=await this.getApiIndex();if(!c){this.onError(x);return}if(!c[e]){this.onError(P);return}let d=this.assembleEndpoint(c[e].href,t,s),l=await this.fetchData(d,n,S);this.debug&&!this.noStore&&console.log(`Add Collection ${e} to state`);let w={};if(w[u]=l,this.setState(w),i){let y=l?.links,T=o=>o===void 0||!("next"in o)?"":"next"in o&&typeof o.next=="string"?o.next:o.next!==null&&o.next!==void 0&&typeof o.next!="string"&&"href"in o?.next&&typeof o.next.href=="string"?o.next.href:"",p=T(y);if(p){!this.debug||console.log(`Found 'next' link - attempting to fetch next page of results for ${e}`);let o=async I=>{if(I==="")return {};let D=await this.fetchData(I,n),J=this.getState(),q=K(J[u],D);return J[u]=q,this.setState(J),D.links},v=p,b;do{let I=await o(v);b=this.getState(),y=I,v=T(I);}while(y.next);let C=b[u];return this.noStore&&delete b[u],this.dataFormatter.deserialize(C)}}return this.dataFormatter.deserialize(l)}else return !this.debug||console.log(`Matched collection ${e} in state`),this.dataFormatter.deserialize(h)}},Le=E;
export { Le as default };