storyblok-js-client
Version:
Universal JavaScript SDK for Storyblok's API
8 lines (7 loc) • 13.2 kB
JavaScript
/**
* name: storyblok-js-client
* (c) 2025
* description: Universal JavaScript SDK for Storyblok's API
* author: Alexander Feiglstorfer <delooks@gmail.com>
*/
(function(y,f){typeof exports=="object"&&typeof module<"u"?module.exports=f():typeof define=="function"&&define.amd?define(f):(y=typeof globalThis<"u"?globalThis:y||self,y.StoryblokJSClient=f())})(this,function(){"use strict";var H=Object.defineProperty;var U=(y,f,g)=>f in y?H(y,f,{enumerable:!0,configurable:!0,writable:!0,value:g}):y[f]=g;var u=(y,f,g)=>U(y,typeof f!="symbol"?f+"":f,g);class y extends Error{constructor(e){super(e),this.name="AbortError"}}function f(l,e,t){if(!Number.isFinite(e))throw new TypeError("Expected `limit` to be a finite number");if(!Number.isFinite(t))throw new TypeError("Expected `interval` to be a finite number");const i=[];let s=[],r=0,n=!1;const o=async()=>{r++;const a=i.shift();if(a)try{const d=await l(...a.args);a.resolve(d)}catch(d){a.reject(d)}const h=setTimeout(()=>{r--,i.length>0&&o(),s=s.filter(d=>d!==h)},t);s.includes(h)||s.push(h)},c=(...a)=>n?Promise.reject(new Error("Throttled function is already aborted and not accepting new promises")):new Promise((h,d)=>{i.push({resolve:h,reject:d,args:a}),r<e&&o()});return c.abort=()=>{n=!0,s.forEach(clearTimeout),s=[],i.forEach(a=>a.reject(()=>new y("Throttle function aborted"))),i.length=0},c}const g=(l="")=>l.includes("/cdn/"),A=(l,e=25,t=1)=>({...l,per_page:e,page:t}),w=l=>new Promise(e=>setTimeout(e,l)),T=(l=0,e)=>Array.from({length:l},e),S=(l=0,e=l)=>{const t=Math.abs(e-l)||0,i=l<e?1:-1;return T(t,(s,r)=>r*i+l)},P=async(l,e)=>Promise.all(l.map(e)),C=(l=[],e)=>l.map(e).reduce((t,i)=>[...t,...i],[]),v=(l,e,t)=>{const i=[];for(const s in l){if(!Object.prototype.hasOwnProperty.call(l,s))continue;const r=l[s];if(r==null)continue;const n=t?"":encodeURIComponent(s);let o;typeof r=="object"?o=v(r,e?e+encodeURIComponent(`[${n}]`):n,Array.isArray(r)):o=`${e?e+encodeURIComponent(`[${n}]`):n}=${encodeURIComponent(r)}`,i.push(o)}return i.join("&")},R=l=>{const e={eu:"api.storyblok.com",us:"api-us.storyblok.com",cn:"app.storyblokchina.cn",ap:"api-ap.storyblok.com",ca:"api-ca.storyblok.com"};return e[l]??e.eu},L=Object.freeze(Object.defineProperty({__proto__:null,arrayFrom:T,asyncMap:P,delay:w,escapeHTML:l=>{const e={"&":"&","<":"<",">":">",'"':""","'":"'"},t=/[&<>"']/g,i=new RegExp(t.source);return l&&i.test(l)?l.replace(t,s=>e[s]):l},flatMap:C,getOptionsPage:A,getRegionURL:R,isCDNUrl:g,range:S,stringify:v},Symbol.toStringTag,{value:"Module"}));class ${constructor(e){u(this,"baseURL");u(this,"timeout");u(this,"headers");u(this,"responseInterceptor");u(this,"fetch");u(this,"ejectInterceptor");u(this,"url");u(this,"parameters");u(this,"fetchOptions");this.baseURL=e.baseURL,this.headers=e.headers||new Headers,this.timeout=e!=null&&e.timeout?e.timeout*1e3:0,this.responseInterceptor=e.responseInterceptor,this.fetch=(...t)=>e.fetch?e.fetch(...t):fetch(...t),this.ejectInterceptor=!1,this.url="",this.parameters={},this.fetchOptions={}}get(e,t){return this.url=e,this.parameters=t,this._methodHandler("get")}post(e,t){return this.url=e,this.parameters=t,this._methodHandler("post")}put(e,t){return this.url=e,this.parameters=t,this._methodHandler("put")}delete(e,t){return this.url=e,this.parameters=t??{},this._methodHandler("delete")}async _responseHandler(e){const t=[],i={data:{},headers:{},status:0,statusText:""};e.status!==204&&await e.json().then(s=>{i.data=s});for(const s of e.headers.entries())t[s[0]]=s[1];return i.headers={...t},i.status=e.status,i.statusText=e.statusText,i}async _methodHandler(e){let t=`${this.baseURL}${this.url}`,i=null;e==="get"?t=`${this.baseURL}${this.url}?${v(this.parameters)}`:i=JSON.stringify(this.parameters);const s=new URL(t),r=new AbortController,{signal:n}=r;let o;this.timeout&&(o=setTimeout(()=>r.abort(),this.timeout));try{const c=await this.fetch(`${s}`,{method:e,headers:this.headers,body:i,signal:n,...this.fetchOptions});this.timeout&&clearTimeout(o);const a=await this._responseHandler(c);return this.responseInterceptor&&!this.ejectInterceptor?this._statusHandler(this.responseInterceptor(a)):this._statusHandler(a)}catch(c){return{message:c}}}setFetchOptions(e={}){Object.keys(e).length>0&&"method"in e&&delete e.method,this.fetchOptions={...e}}eject(){this.ejectInterceptor=!0}_normalizeErrorMessage(e){if(Array.isArray(e))return e[0]||"Unknown error";if(e&&typeof e=="object"){if(e.error)return e.error;for(const t in e){if(Array.isArray(e[t]))return`${t}: ${e[t][0]}`;if(typeof e[t]=="string")return`${t}: ${e[t]}`}if(e.slug)return e.slug}return"Unknown error"}_statusHandler(e){const t=/20[0-6]/g;return new Promise((i,s)=>{if(t.test(`${e.status}`))return i(e);const r={message:this._normalizeErrorMessage(e.data),status:e.status,response:e};s(r)})}}const E="SB-Agent",m={defaultAgentName:"SB-JS-CLIENT",defaultAgentVersion:"SB-Agent-Version",packageVersion:"6.0.0"},N={DRAFT:"draft"};let _={};const p={};class b{constructor(e,t){u(this,"client");u(this,"maxRetries");u(this,"retriesDelay");u(this,"throttle");u(this,"accessToken");u(this,"cache");u(this,"resolveCounter");u(this,"relations");u(this,"links");u(this,"version");u(this,"richTextResolver");u(this,"resolveNestedRelations");u(this,"stringifiedStoriesCache");u(this,"inlineAssets");let i=e.endpoint||t;if(!i){const n=e.https===!1?"http":"https";e.oauthToken?i=`${n}://${R(e.region)}/v1`:i=`${n}://${R(e.region)}/v2`}const s=new Headers;s.set("Content-Type","application/json"),s.set("Accept","application/json"),e.headers&&(e.headers.constructor.name==="Headers"?e.headers.entries().toArray():Object.entries(e.headers)).forEach(([o,c])=>{s.set(o,c)}),s.has(E)||(s.set(E,m.defaultAgentName),s.set(m.defaultAgentVersion,m.packageVersion));let r=5;e.oauthToken&&(s.set("Authorization",e.oauthToken),r=3),e.rateLimit&&(r=e.rateLimit),this.maxRetries=e.maxRetries||10,this.retriesDelay=300,this.throttle=f(this.throttledRequest.bind(this),r,1e3),this.accessToken=e.accessToken||"",this.relations={},this.links={},this.cache=e.cache||{clear:"manual"},this.resolveCounter=0,this.resolveNestedRelations=e.resolveNestedRelations||!0,this.stringifiedStoriesCache={},this.version=e.version||N.DRAFT,this.inlineAssets=e.inlineAssets||!1,this.client=new $({baseURL:i,timeout:e.timeout||0,headers:s,responseInterceptor:e.responseInterceptor,fetch:e.fetch})}parseParams(e){return e.token||(e.token=this.getToken()),e.cv||(e.cv=p[e.token]),Array.isArray(e.resolve_relations)&&(e.resolve_relations=e.resolve_relations.join(",")),typeof e.resolve_relations<"u"&&(e.resolve_level=2),e}factoryParamOptions(e,t){return g(e)?this.parseParams(t):t}makeRequest(e,t,i,s,r){const n=this.factoryParamOptions(e,A(t,i,s));return this.cacheResponse(e,n,void 0,r)}get(e,t={},i){t||(t={});const s=`/${e}`;t.version=t.version||this.version;const r=this.factoryParamOptions(s,t);return this.cacheResponse(s,r,void 0,i)}async getAll(e,t={},i,s){const r=(t==null?void 0:t.per_page)||25,n=`/${e}`.replace(/\/$/,""),o=i??n.substring(n.lastIndexOf("/")+1);t.version=t.version||this.version;const c=1,a=await this.makeRequest(n,t,r,c,s),h=a.total?Math.ceil(a.total/r):1,d=await P(S(c,h),k=>this.makeRequest(n,t,r,k+1,s));return C([a,...d],k=>Object.values(k.data[o]))}post(e,t={},i){const s=`/${e}`;return this.throttle("post",s,t,i)}put(e,t={},i){const s=`/${e}`;return this.throttle("put",s,t,i)}delete(e,t={},i){t||(t={});const s=`/${e}`;return this.throttle("delete",s,t,i)}getStories(e={},t){return this._addResolveLevel(e),this.get("cdn/stories",e,t)}getStory(e,t={},i){return this._addResolveLevel(t),this.get(`cdn/stories/${e}`,t,i)}getToken(){return this.accessToken}ejectInterceptor(){this.client.eject()}_addResolveLevel(e){typeof e.resolve_relations<"u"&&(e.resolve_level=2)}_cleanCopy(e){return JSON.parse(JSON.stringify(e))}_insertLinks(e,t,i){const s=e[t];s&&s.fieldtype==="multilink"&&s.linktype==="story"&&typeof s.id=="string"&&this.links[i][s.id]?s.story=this._cleanCopy(this.links[i][s.id]):s&&s.linktype==="story"&&typeof s.uuid=="string"&&this.links[i][s.uuid]&&(s.story=this._cleanCopy(this.links[i][s.uuid]))}getStoryReference(e,t){return this.relations[e][t]?JSON.parse(this.stringifiedStoriesCache[t]||JSON.stringify(this.relations[e][t])):t}_resolveField(e,t,i){const s=e[t];typeof s=="string"?e[t]=this.getStoryReference(i,s):Array.isArray(s)&&(e[t]=s.map(r=>this.getStoryReference(i,r)).filter(Boolean))}_insertRelations(e,t,i,s){if(Array.isArray(i)?i.find(o=>o.endsWith(`.${t}`)):i.endsWith(`.${t}`)){this._resolveField(e,t,s);return}const n=e.component?`${e.component}.${t}`:t;(Array.isArray(i)?i.includes(n):i===n)&&this._resolveField(e,t,s)}iterateTree(e,t,i){const s=(r,n="")=>{if(!(!r||r._stopResolving)){if(Array.isArray(r))r.forEach((o,c)=>s(o,`${n}[${c}]`));else if(typeof r=="object")for(const o in r){const c=n?`${n}.${o}`:o;(r.component&&r._uid||r.type==="link")&&(this._insertRelations(r,o,t,i),this._insertLinks(r,o,i)),s(r[o],c)}}};s(e.content)}async resolveLinks(e,t,i){let s=[];if(e.link_uuids){const r=e.link_uuids.length,n=[],o=50;for(let c=0;c<r;c+=o){const a=Math.min(r,c+o);n.push(e.link_uuids.slice(c,a))}for(let c=0;c<n.length;c++)(await this.getStories({per_page:o,language:t.language,version:t.version,starts_with:t.starts_with,by_uuids:n[c].join(",")})).data.stories.forEach(h=>{s.push(h)})}else s=e.links;s.forEach(r=>{this.links[i][r.uuid]={...r,_stopResolving:!0}})}async resolveRelations(e,t,i){let s=[];if(e.rel_uuids){const r=e.rel_uuids.length,n=[],o=50;for(let c=0;c<r;c+=o){const a=Math.min(r,c+o);n.push(e.rel_uuids.slice(c,a))}for(let c=0;c<n.length;c++)(await this.getStories({per_page:o,language:t.language,version:t.version,starts_with:t.starts_with,by_uuids:n[c].join(","),excluding_fields:t.excluding_fields})).data.stories.forEach(h=>{s.push(h)});s.length>0&&(e.rels=s,delete e.rel_uuids)}else s=e.rels;s&&s.length>0&&s.forEach(r=>{this.relations[i][r.uuid]={...r,_stopResolving:!0}})}async resolveStories(e,t,i){var r,n;let s=[];if(this.links[i]={},this.relations[i]={},typeof t.resolve_relations<"u"&&t.resolve_relations.length>0&&(typeof t.resolve_relations=="string"&&(s=t.resolve_relations.split(",")),await this.resolveRelations(e,t,i)),t.resolve_links&&["1","story","url","link"].includes(t.resolve_links)&&((r=e.links)!=null&&r.length||(n=e.link_uuids)!=null&&n.length)&&await this.resolveLinks(e,t,i),this.resolveNestedRelations)for(const o in this.relations[i])this.iterateTree(this.relations[i][o],s,i);e.story?this.iterateTree(e.story,s,i):e.stories.forEach(o=>{this.iterateTree(o,s,i)}),this.stringifiedStoriesCache={},delete this.links[i],delete this.relations[i]}async cacheResponse(e,t,i,s){const r=v({url:e,params:t}),n=this.cacheProvider();if(t.version==="published"&&e!=="/cdn/spaces/me"){const o=await n.get(r);if(o)return Promise.resolve(o)}return new Promise(async(o,c)=>{var a;try{const h=await this.throttle("get",e,t,s);if(h.status!==200)return c(h);let d={data:h.data,headers:h.headers};if((a=h.headers)!=null&&a["per-page"]&&(d=Object.assign({},d,{perPage:h.headers["per-page"]?Number.parseInt(h.headers["per-page"]):0,total:h.headers["per-page"]?Number.parseInt(h.headers.total):0})),d.data.story||d.data.stories){const x=this.resolveCounter=++this.resolveCounter%1e3;await this.resolveStories(d.data,t,`${x}`),d=await this.processInlineAssets(d)}t.version==="published"&&e!=="/cdn/spaces/me"&&await n.set(r,d);const k=this.cache.clear==="onpreview"&&t.version==="draft"||this.cache.clear==="auto";return t.token&&d.data.cv&&(k&&p[t.token]&&p[t.token]!==d.data.cv&&await this.flushCache(),p[t.token]=d.data.cv),o(d)}catch(h){if(h.response&&h.status===429&&(i=typeof i>"u"?0:i+1,i<this.maxRetries))return console.log(`Hit rate limit. Retrying in ${this.retriesDelay/1e3} seconds.`),await w(this.retriesDelay),this.cacheResponse(e,t,i).then(o).catch(c);c(h)}})}throttledRequest(e,t,i,s){return this.client.setFetchOptions(s),this.client[e](t,i)}cacheVersions(){return p}cacheVersion(){return p[this.accessToken]}setCacheVersion(e){this.accessToken&&(p[this.accessToken]=e)}clearCacheVersion(){this.accessToken&&(p[this.accessToken]=0)}cacheProvider(){switch(this.cache.type){case"memory":return{get(e){return Promise.resolve(_[e])},getAll(){return Promise.resolve(_)},set(e,t){return _[e]=t,Promise.resolve(void 0)},flush(){return _={},Promise.resolve(void 0)}};case"custom":if(this.cache.custom)return this.cache.custom;default:return{get(){return Promise.resolve()},getAll(){return Promise.resolve(void 0)},set(){return Promise.resolve(void 0)},flush(){return Promise.resolve(void 0)}}}}async flushCache(){return await this.cacheProvider().flush(),this.clearCacheVersion(),this}async processInlineAssets(e){if(!this.inlineAssets)return e;const t=i=>{if(!i||typeof i!="object")return i;if(Array.isArray(i))return i.map(r=>t(r));let s={...i};s.fieldtype==="asset"&&Array.isArray(e.data.assets)&&(s={...s,...e.data.assets.find(r=>r.id===s.id)});for(const r in s)typeof s[r]=="object"&&(s[r]=t(s[r]));return s};return e.data.story&&(e.data.story.content=t(e.data.story.content)),e.data.stories&&(e.data.stories=e.data.stories.map(i=>(i.content=t(i.content),i))),e}}const O=(l,e)=>{for(const t in e)l[t]=e[t]};return O(b,{SbFetch:$}),O(b,L),b});