@gdwc/drupal-state
Version:
A simple data store to manage application state sourced from Drupal's JSON:API.
33 lines (27 loc) • 8.3 kB
JavaScript
'use strict';
var M = require('zustand/vanilla');
var jsona = require('jsona');
var drupalJsonapiParams = require('drupal-jsonapi-params');
var K = require('deepmerge');
var _ = require('isomorphic-fetch');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var M__default = /*#__PURE__*/_interopDefault(M);
var K__default = /*#__PURE__*/_interopDefault(K);
var ___default = /*#__PURE__*/_interopDefault(_);
var j=(a,e={},t)=>___default.default(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);}),E=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);}),w=O;var U=async(a,e,t={},n=!1,s=f,i=r=>{throw r})=>await w(a+"?path="+e+"&_format=json",t,i,n,s),v=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);})},$=H;var F=a=>typeof a=="string"||"href"in a;var B=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:m=!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=m,this.dataFormatter=new jsona.Jsona,this.noStore=x,!this.debug||console.log("Debug mode:",m),this.store=M__default.default(()=>({}));let{getState:P,setState:k,subscribe:S,destroy:u}=this.store;this.getState=P,this.setState=k,this.subscribe=S,this.destroy=u;let g=l=>{throw l};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.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 $(`${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(e,this.fetchAdapter,this.onError)}async fetchJsonapiEndpoint(e,t={},n,s){return await w(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:m=!1}){m&&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={},l="";if(this.clientId&&this.clientSecret&&!r){let d=new Headers;l=await this.getAuthHeader(),d.append("Authorization",l),g={headers:d};}let c=await v(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 h={...d,[t]:c};this.setState({dsPathTranslations:h});}else {let h={[t]:c};this.setState({dsPathTranslations:h});}}else return}let S=this.getState().dsPathTranslations[`${t}`].entity.uuid;return await this.getObject({objectName:e,id:S,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:m=!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.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(),S=typeof s=="string"?s:s?.getQueryString(),u=S?`${e}-${S}`:e,g=`${e}Resources`,l=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),h=await this.fetchData(d,n,m);return this.dataFormatter.deserialize(h)}if(t){let c=S?`${t}-${S}`: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(l?.data&&!r){let p=l.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 h=await this.getApiIndex();if(!h){this.onError(x);return}if(!h[e]){this.onError(P);return}let R=this.assembleEndpoint(h[e].href,t,s),y=await this.fetchData(R,n,m),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||!l||l.links?.next&&!l.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),h=await this.fetchData(d,n,m);this.debug&&!this.noStore&&console.log(`Add Collection ${e} to state`);let R={};if(R[u]=h,this.setState(R),i){let y=h?.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__default.default(J[u],D);return J[u]=q,this.setState(J),D.links},G=p,b;do{let I=await o(G);b=this.getState(),y=I,G=T(I);}while(y.next);let C=b[u];return this.noStore&&delete b[u],this.dataFormatter.deserialize(C)}}return this.dataFormatter.deserialize(h)}else return !this.debug||console.log(`Matched collection ${e} in state`),this.dataFormatter.deserialize(l)}},V=B;
exports.DrupalState = V;
exports.fetchApiIndex = E;
exports.fetchJsonapiEndpoint = w;
exports.fetchToken = $;
exports.translatePath = v;