UNPKG

@shopify/storefront-api-client

Version:

Shopify Storefront API Client - A lightweight JS client to interact with Shopify's Storefront API

4 lines (3 loc) 9.45 kB
/*! shopify/storefront-api-client@1.0.7 -- Copyright (c) 2023-present, Shopify Inc. -- license (MIT): https://github.com/Shopify/shopify-app-js/blob/main/LICENSE.md */ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).ShopifyStorefrontAPIClient={})}(this,(function(e){"use strict";const r="GraphQL Client",t="An error occurred while fetching from the API. Review 'graphQLErrors' for details.",n="Response returned unexpected Content-Type:",o="An unknown error has occurred. The API did not return a data object or any errors in its response.",s="application/json",i="multipart/mixed",a="X-SDK-Variant",c="X-SDK-Version",u="shopify-graphql-client",l="1.3.2",p=[429,503],d=/@(defer)\b/i,f=/boundary="?([^=";]+)"?/i,h="\r\n\r\n";function y(e,t=r){return e.startsWith(`${t}`)?e:`${t}: ${e}`}function m(e){return e instanceof Error?e.message:JSON.stringify(e)}function g(e){return e instanceof Error&&e.cause?e.cause:void 0}function w(e){return e.flatMap((({errors:e})=>e??[]))}function b({client:e,retries:r}){if(void 0!==r&&("number"!=typeof r||r<0||r>3))throw new Error(`${e}: The provided "retries" value (${r}) is invalid - it cannot be less than 0 or greater than 3`)}function $(e,r){return r&&("object"!=typeof r||Array.isArray(r)||"object"==typeof r&&Object.keys(r).length>0)?{[e]:r}:{}}function A(e,r){if(0===e.length)return r;const t=e.pop(),n={[t]:r};return 0===e.length?n:A(e,n)}function S(e,r){return Object.keys(r||{}).reduce(((t,n)=>("object"==typeof r[n]||Array.isArray(r[n]))&&e[n]?(t[n]=S(e[n],r[n]),t):(t[n]=r[n],t)),Array.isArray(e)?[...e]:{...e})}function v([e,...r]){return r.reduce(S,{...e})}function x({clientLogger:e,customFetchApi:t=fetch,client:n=r,defaultRetryWaitTime:o=1e3,retriableCodes:s=p}){const i=async(r,a,c)=>{const u=a+1,l=c+1;let p;try{if(p=await t(...r),e({type:"HTTP-Response",content:{requestParams:r,response:p}}),!p.ok&&s.includes(p.status)&&u<=l)throw new Error;return p}catch(t){if(u<=l){const t=p?.headers.get("Retry-After");return await async function(e){return new Promise((r=>setTimeout(r,e)))}(t?parseInt(t,10):o),e({type:"HTTP-Retry",content:{requestParams:r,lastResponse:p,retryAttempt:a,maxRetries:c}}),i(r,u,c)}throw new Error(y(`${c>0?`Attempted maximum number of ${c} network retries. Last message - `:""}${m(t)}`,n))}};return i}function E({headers:e,url:t,customFetchApi:o=fetch,retries:p=0,logger:A}){b({client:r,retries:p});const S={headers:e,url:t,retries:p},E=function(e){return r=>{e&&e(r)}}(A),V=function(e,{url:t,headers:n,retries:o}){return async(s,i={})=>{const{variables:p,headers:d,url:f,retries:h,keepalive:y,signal:m}=i,g=JSON.stringify({query:s,variables:p});b({client:r,retries:h});const w=Object.entries({...n,...d}).reduce(((e,[r,t])=>(e[r]=Array.isArray(t)?t.join(", "):t.toString(),e)),{});w[a]||w[c]||(w[a]=u,w[c]=l);return e([f??t,{method:"POST",headers:w,body:g,signal:m,keepalive:y}],1,h??o)}}(x({customFetchApi:o,clientLogger:E,defaultRetryWaitTime:1e3}),S),q=function(e){return async(...r)=>{if(d.test(r[0]))throw new Error(y("This operation will result in a streamable response - use requestStream() instead."));try{const t=await e(...r),{status:o,statusText:i}=t,a=t.headers.get("content-type")||"";return t.ok?a.includes(s)?k(t):{errors:{networkStatusCode:o,message:y(`${n} ${a}`),response:t}}:{errors:{networkStatusCode:o,message:y(i),response:t}}}catch(e){return{errors:{message:m(e)}}}}}(V),C=function(e){return async(...r)=>{if(!d.test(r[0]))throw new Error(y("This operation does not result in a streamable response - use request() instead."));try{const t=await e(...r),{statusText:o}=t;if(!t.ok)throw new Error(o,{cause:t});const a=t.headers.get("content-type")||"";switch(!0){case a.includes(s):return function(e){return{async*[Symbol.asyncIterator](){const r=await k(e);yield{...r,hasNext:!1}}}}(t);case a.includes(i):return function(e,r){const t=(r??"").match(f),n=`--${t?t[1]:"-"}`;if(!e.body?.getReader&&!e.body?.[Symbol.asyncIterator])throw new Error("API multipart response did not return an iterable body",{cause:e});const o=async function*(e){const r=new TextDecoder;if(e.body[Symbol.asyncIterator])for await(const t of e.body)yield r.decode(t);else{const t=e.body.getReader();let n;try{for(;!(n=await t.read()).done;)yield r.decode(n.value)}finally{t.cancel()}}}(e);let s,i={};return{async*[Symbol.asyncIterator](){try{let e=!0;for await(const r of function(e,r){return{async*[Symbol.asyncIterator](){try{let t="";for await(const n of e)if(t+=n,t.indexOf(r)>-1){const e=t.lastIndexOf(r),n=t.slice(0,e).split(r).filter((e=>e.trim().length>0)).map((e=>e.slice(e.indexOf(h)+4).trim()));n.length>0&&(yield n),t=t.slice(e+r.length),"--"===t.trim()&&(t="")}}catch(e){throw new Error(`Error occured while processing stream payload - ${m(e)}`)}}}}(o,n)){const t=T(r);s=t.find((e=>e.extensions))?.extensions??s;const n=w(t);i=v([i,...t.map((({data:e})=>e))]),e=t.slice(-1)[0].hasNext,j(n,i),yield{...$("data",i),...$("extensions",s),hasNext:e}}if(e)throw new Error("Response stream terminated unexpectedly")}catch(r){const t=g(r);yield{...$("data",i),...$("extensions",s),errors:{message:y(m(r)),networkStatusCode:e.status,...$("graphQLErrors",t?.graphQLErrors),response:e},hasNext:!1}}}}}(t,a);default:throw new Error(`${n} ${a}`,{cause:t})}}catch(e){return{async*[Symbol.asyncIterator](){const r=g(e);yield{errors:{message:y(m(e)),...$("networkStatusCode",r?.status),...$("response",r)},hasNext:!1}}}}}}(V);return{config:S,fetch:V,request:q,requestStream:C}}async function k(e){const{errors:r,data:n,extensions:s}=await e.json();return{...$("data",n),...$("extensions",s),headers:e.headers,...r||!n?{errors:{networkStatusCode:e.status,message:y(r?t:o),...$("graphQLErrors",r),response:e}}:{}}}function T(e){return e.map((e=>{try{return JSON.parse(e)}catch(e){throw new Error(`Error in parsing multipart response - ${m(e)}`)}})).map((e=>{const{data:r,incremental:t,hasNext:n,extensions:o,errors:s}=e;if(!t)return{data:r||{},...$("errors",s),...$("extensions",o),hasNext:n};const i=t.map((({data:e,path:r,errors:t})=>({data:e&&r?A(r,e):{},...$("errors",t)})));return{data:1===i.length?i[0].data:v([...i.map((({data:e})=>e))]),...$("errors",w(i)),hasNext:n}}))}function j(e,r){if(e.length>0)throw new Error(t,{cause:{graphQLErrors:e}});if(0===Object.keys(r).length)throw new Error(o)}function V({client:e,currentSupportedApiVersions:r,apiVersion:t,logger:n}){const o=`${e}: the provided apiVersion ("${t}")`,s=`Currently supported API versions: ${r.join(", ")}`;if(!t||"string"!=typeof t)throw new Error(`${o} is invalid. ${s}`);const i=t.trim();r.includes(i)||(n?n({type:"Unsupported_Api_Version",content:{apiVersion:t,supportedApiVersions:r}}):console.warn(`${o} is likely deprecated or not supported. ${s}`))}function q(e){const r=3*e-2;return 10===r?r:`0${r}`}function C(e,r,t){const n=r-t;return n<=0?`${e-1}-${q(n+4)}`:`${e}-${q(n)}`}function I(){const{year:e,quarter:r,version:t}=function(){const e=new Date,r=e.getUTCMonth(),t=e.getUTCFullYear(),n=Math.floor(r/3+1);return{year:t,quarter:n,version:`${t}-${q(n)}`}}(),n=4===r?`${e+1}-01`:`${e}-${q(r+1)}`;return[C(e,r,3),C(e,r,2),C(e,r,1),t,n,"unstable"]}const P="application/json",N="X-Shopify-Storefront-Access-Token",O="Shopify-Storefront-Private-Token",R="X-SDK-Variant",D="X-SDK-Version",U="X-SDK-Variant-Source",L="Storefront API Client";e.createStorefrontApiClient=function({storeDomain:e,apiVersion:r,publicAccessToken:t,privateAccessToken:n,clientName:o,retries:s=0,customFetchApi:i,logger:a}){const c=I(),u=function({client:e,storeDomain:r}){try{if(!r||"string"!=typeof r)throw new Error;const e=r.trim(),t=e.match(/^https?:/)?e:`https://${e}`,n=new URL(t);return n.protocol="https",n.origin}catch(t){throw new Error(`${e}: a valid store domain ("${r}") must be provided`,{cause:t})}}({client:L,storeDomain:e}),l={client:L,currentSupportedApiVersions:c,logger:a};V({...l,apiVersion:r}),function(e,r){if(!e&&!r)throw new Error(`${L}: a public or private access token must be provided`);if(e&&r)throw new Error(`${L}: only provide either a public or private access token`)}(t,n),function(e){if(e&&"undefined"!=typeof window)throw new Error(`${L}: private access tokens and headers should only be used in a server-to-server implementation. Use the public API access token in nonserver environments.`)}(n);const p=function(e,r,t){return n=>{n&&V({...t,apiVersion:n});const o=(n??r).trim();return`${e}/api/${o}/graphql.json`}}(u,r,l),d={storeDomain:u,apiVersion:r,...t?{publicAccessToken:t}:{privateAccessToken:n},headers:{"Content-Type":P,Accept:P,[R]:"storefront-api-client",[D]:"1.0.7",...o?{[U]:o}:{},...t?{[N]:t}:{[O]:n}},apiUrl:p(),clientName:o},f=E({headers:d.headers,url:d.apiUrl,retries:s,customFetchApi:i,logger:a}),h=function(e){return r=>({...r??{},...e.headers})}(d),y=function(e,r){return t=>t?r(t):e.apiUrl}(d,p),m=function({getHeaders:e,getApiUrl:r}){return(t,n)=>{const o=[t];if(n&&Object.keys(n).length>0){const{variables:t,apiVersion:s,headers:i,retries:a}=n;o.push({...t?{variables:t}:{},...i?{headers:e(i)}:{},...s?{url:r(s)}:{},...a?{retries:a}:{}})}return o}}({getHeaders:h,getApiUrl:y}),g={config:d,getHeaders:h,getApiUrl:y,fetch:(...e)=>f.fetch(...m(...e)),request:(...e)=>f.request(...m(...e)),requestStream:(...e)=>f.requestStream(...m(...e))};return Object.freeze(g)}})); //# sourceMappingURL=storefront-api-client.min.js.map