fetchja 1.2.1 → 1.4.0

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/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import x from"pluralize";function p(t){if(Array.isArray(t))return t.map(p);let e={id:t.id,type:t.type};for(let r in t.attributes)e[r]=t.attributes[r];for(let r in t.relationships)t.relationships[r].data&&(e[r]=t.relationships[r].data);return e}function g(t){let e={};for(let r of t)e[r.type]||(e[r.type]={}),e[r.type][r.id]=p(r);return e}function f(t){return typeof t=="object"&&t!==null}function d(t){let e={};if(t.data&&(e.data=p(t.data)),t.meta&&(e.meta=t.meta),t.included){let r=g(t.included),a=s=>s.type in r?r[s.type][s.id]:s,i=s=>Array.isArray(s)?s.map(a):a(s);for(let s in r)for(let o in r[s])for(let c in r[s][o]){let u=r[s][o][c];f(u)&&(r[s][o][c]=i(u))}if(Array.isArray(e.data))for(let s of e.data)for(let o in s)f(s[o])&&(s[o]=i(s[o]));else if(f(e.data))for(let s in e.data)f(e.data[s])&&(e.data[s]=i(e.data[s]))}return e}function n(t){if(t.response){let{data:e}=t.response;e?.errors&&(t.errors=e.errors)}throw t}function q(t,e,r){return typeof t=="object"&&t!==null?(r.relationships||(r.relationships={}),r.relationships[e]={data:t.id?{id:t.id,type:t.type||e}:t,links:t.links,meta:t.meta}):(r.attributes||(r.attributes={}),r.attributes[e]=t),r}function C(t,e,r={camelCaseTypes:a=>a,pluralTypes:a=>a}){try{if(e===null||Array.isArray(e)&&!e.length)return{data:e};let a={type:r.pluralTypes(r.camelCaseTypes(t))};e.id&&(a.id=String(e.id));for(let i in e)["id","type"].includes(i)||q(e[i],i,a);return JSON.stringify({data:a})}catch(a){n(a)}}function b(t,e={},r=""){let a=Array.isArray(e);for(let i in e){let s=e[i],o=r?`${r}[${a?"":i}]`:i;s instanceof Object?b(t,s,o):t.append(o,s)}}function T(t={}){let e=new URLSearchParams;return b(e,t),e}function w(t,e={resourceCase:r=>r,pluralize:r=>r}){let r=t.split("/"),a=r.pop(),i=r.join("/");return[a,`${i}/${e.pluralize(e.resourceCase(a))}`]}function h(t){return t.toLowerCase().replace(/[-_](.)/g,(e,r)=>r.toUpperCase()).replace(/^(.)/,e=>e.toLowerCase())}function k(t){return t.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/_/g,"-").toLowerCase()}function z(t){return t.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase()}var y="application/vnd.api+json",m=class{constructor(e={headers:{}}){this.baseURL=e.baseURL,this.headers={Accept:y,"Content-Type":y,...e.headers},this.queryFormatter=typeof e.queryFormatter=="function"?e.queryFormatter:a=>T(a),this.camelCaseTypes=e.camelCaseTypes===!1?a=>a:h;let r={camel:h,kebab:k,snake:z,default:a=>a};this.resourceCase=r[e.resourceCase]||r.default,this.pluralize=e.pluralize===!1?a=>a:x,this.onResponseError=a=>a,this.fetch=this.get,this.update=this.patch,this.create=this.post,this.remove=this.delete}#e(e){return w(e,{resourceCase:this.resourceCase,pluralize:this.pluralize})}async request(e={method:"GET",headers:{}}){let r=this.baseURL||e.baseURL,a=new URL(e.url.startsWith("/")?e.url.slice(1):e.url,r.endsWith("/")?r:r+"/");e.params&&(a.search=this.queryFormatter(e.params)),e.body&&(e.body=C(e.type,e.body,{camelCaseTypes:this.camelCaseTypes,pluralTypes:this.pluralize}));let i=()=>{let s=new Headers({...this.headers,...e.headers});return fetch(a,{method:e.method,body:e.body,headers:s})};try{let s=await i();if(s.ok){if(!s.ok)throw new Error(s.statusText)}else{s.replayRequest=i;let l=await this.onResponseError(s);l instanceof Response&&(s=l)}let o={};for(let[l,A]of s.headers.entries())o[l]=A;let c=o["content-type"],u=c&&c.includes(y)?await s.json():{};return{...d(u),status:s.status,statusText:s.statusText,headers:o}}catch(s){throw s}}get(e,r={method:"GET"}){try{return r.url=e.split("/").map(a=>this.resourceCase(a)).filter(Boolean).join("/"),this.request(r)}catch(a){throw n(a)}}patch(e,r,a={method:"PATCH"}){try{let[i,s]=this.#e(e);return this.request({url:r?.id?`${s}/${r.id}`:s,body:r,type:i,...a})}catch(i){throw n(i)}}post(e,r,a={method:"POST"}){try{let[i,s]=this.#e(e);return this.request({url:s,body:r,type:i,...a})}catch(i){throw n(i)}}delete(e,r,a={method:"DELETE"}){try{let[i,s]=this.#e(e);return this.request({url:`${s}/${r}`,body:{id:r},type:i,...a})}catch(i){throw n(i)}}};export{m as default};
1
+ import g from"pluralize";function f(s){if(Array.isArray(s))return s.map(f);let e={type:s.type,id:s.id};for(let t in s.attributes)e[t]=s.attributes[t];for(let t in s.relationships)s.relationships[t].data&&(e[t]=s.relationships[t].data);return e}function q(s){let e={};for(let t of s)e[t.type]||(e[t.type]={}),e[t.type][t.id]=f(t);return e}function h(s){return typeof s=="object"&&s!==null}function b(s){let e={};if(s.data&&(e.data=f(s.data)),s.meta&&(e.meta=s.meta),s.included){let t=q(s.included),a=r=>r.type in t?t[r.type][r.id]:r,o=r=>Array.isArray(r)?r.map(a):a(r);for(let r in t)for(let i in t[r])for(let u in t[r][i]){let n=t[r][i][u];h(n)&&(t[r][i][u]=o(n))}if(Array.isArray(e.data))for(let r of e.data)for(let i in r)h(r[i])&&(r[i]=o(r[i]));else if(h(e.data))for(let r in e.data)h(e.data[r])&&(e.data[r]=o(e.data[r]))}return e}function p(s){if(s.response){let{data:e}=s.response;e?.errors&&(s.errors=e.errors)}throw s}function R(s){return typeof s=="object"&&s!==null}function w(s,e,t={camelCaseTypes:a=>a,pluralTypes:a=>a}){let a=[];function o(i,u){if(R(i)){if(!i.id)throw new Error("All included resources must have an ID.");a.find(n=>n.id===i.id)||a.push(r(i,u))}}function r(i,u){let n={type:t.pluralTypes(t.camelCaseTypes(u))};for(let c in i){if(c==="type")continue;if(c==="id"){n.id=String(i.id);continue}let l=i[c];if(typeof l=="object"){if(n.relationships=n.relationships||{},Array.isArray(l)){n.relationships[c]={data:l.map(d=>(o(d,c),{type:d.type||c,id:d.id}))};continue}n.relationships[c]={data:{type:l.type||c,id:l.id}},o(l,c);continue}n.attributes=n.attributes||{},n.attributes[c]=i[c]}return n}try{let i=r(e,s);return JSON.stringify({data:i,included:a})}catch(i){p(i)}}function T(s,e={},t=""){let a=Array.isArray(e);for(let o in e){let r=e[o],i=t?`${t}[${a?"":o}]`:o;r instanceof Object?T(s,r,i):s.append(i,r)}}function k(s={}){let e=new URLSearchParams;return T(e,s),e}function A(s,e={resourceCase:t=>t,pluralize:t=>t}){let t=s.split("/"),a=t.pop(),o=t.join("/");return[a,`${o}/${e.pluralize(e.resourceCase(a))}`]}function y(s){return s.toLowerCase().replace(/[-_](.)/g,(e,t)=>t.toUpperCase()).replace(/^(.)/,e=>e.toLowerCase())}function x(s){return s.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/_/g,"-").toLowerCase()}function z(s){return s.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase()}var m="application/vnd.api+json",C=class{constructor(e={headers:{}}){this.baseURL=e.baseURL,this.headers={Accept:m,"Content-Type":m,...e.headers},this.fetchFunction=e.fetchFunction,this.queryFormatter=typeof e.queryFormatter=="function"?e.queryFormatter:a=>k(a),this.camelCaseTypes=e.camelCaseTypes===!1?a=>a:y;let t={camel:y,kebab:x,snake:z,default:a=>a};this.resourceCase=t[e.resourceCase]||t.default,this.pluralize=e.pluralize===!1?a=>a:g,this.onResponseError=a=>a,this.fetch=this.get,this.update=this.patch,this.create=this.post,this.remove=this.delete}#e(e){return A(e,{resourceCase:this.resourceCase,pluralize:this.pluralize})}async request(e={method:"GET",headers:{}}){let t=this.baseURL||e.baseURL,a=new URL(e.url.startsWith("/")?e.url.slice(1):e.url,t.endsWith("/")?t:t+"/");e.params&&(a.search=this.queryFormatter(e.params)),e.body&&(e.body=w(e.type,e.body,{camelCaseTypes:this.camelCaseTypes,pluralTypes:this.pluralize}));let o=()=>{let r=new Headers({...this.headers,...e.headers}),i={method:e.method,body:e.body,headers:r};return typeof this.fetchFunction=="function"?this.fetchFunction(a,i):fetch(a,i)};try{let r=await o();if(!r.ok){r.replayRequest=o;let c=await this.onResponseError(r);if(c instanceof Response&&(r=c),!r.ok)throw new Error(r.statusText)}let i={};for(let[c,l]of r.headers.entries())i[c]=l;let u=i["content-type"],n=u&&u.includes(m)?await r.json():{};return{...n.errors?n:b(n),status:r.status,statusText:r.statusText,headers:i}}catch(r){throw r}}get(e,t={method:"GET"}){try{return t.url=e.split("/").map(a=>this.resourceCase(a)).filter(Boolean).join("/"),this.request(t)}catch(a){throw p(a)}}patch(e,t,a={method:"PATCH"}){try{let[o,r]=this.#e(e);return this.request({url:t?.id?`${r}/${t.id}`:r,body:t,type:o,...a})}catch(o){throw p(o)}}post(e,t,a={method:"POST"}){try{let[o,r]=this.#e(e);return this.request({url:r,body:t,type:o,...a})}catch(o){throw p(o)}}delete(e,t,a={method:"DELETE"}){try{let[o,r]=this.#e(e);return this.request({url:`${r}/${t}`,body:{id:t},type:o,...a})}catch(o){throw p(o)}}};export{C as default};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.js", "../src/utils/deattribute.js", "../src/utils/deserialize.js", "../src/utils/error-parser.js", "../src/utils/serialize.js", "../src/utils/query-formatter.js", "../src/utils/split-model.js", "../src/utils/camel-case.js", "../src/utils/kebab-case.js", "../src/utils/snake-case.js"],
4
- "sourcesContent": ["import pluralize from 'pluralize'\n\nimport { deserialize } from './utils/deserialize.js'\nimport { serialize } from './utils/serialize.js'\n\nimport { errorParser } from './utils/error-parser.js'\nimport { queryFormatter } from './utils/query-formatter.js'\nimport { splitModel } from './utils/split-model.js'\n\nimport { camelCase } from './utils/camel-case.js'\nimport { kebabCase } from './utils/kebab-case.js'\nimport { snakeCase } from './utils/snake-case.js'\n\nconst jsonType = 'application/vnd.api+json'\n\n/**\n * Options for Fetchja.\n * \n * @typedef {Object} FetchjaOptions\n * @property {string} baseURL The base URL for all requests.\n * @property {Object} headers The headers to include in all requests.\n * @property {Function} queryFormatter A function to format query parameters.\n * @property {string} resourceCase The case to use for resource names.\n * @property {boolean} pluralize Pluralize resource names.\n */\n\n/**\n * Fetchja is a simple wrapper around the Fetch API.\n * \n * @class Fetchja\n * @param {FetchjaOptions} [options] Options for Fetchja.\n */\nexport default class Fetchja {\n constructor (options = {\n headers: {}\n }) {\n this.baseURL = options.baseURL\n\n // Headers\n this.headers = {\n Accept: jsonType,\n 'Content-Type': jsonType,\n ...options.headers\n }\n\n // Query\n this.queryFormatter = typeof options.queryFormatter === 'function'\n ? options.queryFormatter\n : object => queryFormatter(object)\n\n // Camel Case Types\n this.camelCaseTypes = options.camelCaseTypes === false\n ? string => string\n : camelCase\n\n // Resource Case\n const cases = {\n camel: camelCase,\n kebab: kebabCase,\n snake: snakeCase,\n\n default: string => string\n }\n\n this.resourceCase = cases[options.resourceCase] || cases.default\n \n // Pluralise\n this.pluralize = options.pluralize === false\n ? string => string\n : pluralize\n \n // Interceptors\n this.onResponseError = error => error\n\n // Alias\n this.fetch = this.get\n this.update = this.patch\n this.create = this.post\n this.remove = this.delete\n }\n\n #splitModel (model) {\n return splitModel(model, {\n resourceCase: this.resourceCase,\n pluralize: this.pluralize\n })\n }\n\n async request (options = {\n method: 'GET',\n headers: {}\n }) {\n const baseURL = this.baseURL || options.baseURL\n\n const url = new URL(\n options.url.startsWith('/') ? options.url.slice(1) : options.url,\n baseURL.endsWith('/') ? baseURL : baseURL + '/'\n )\n\n // Params\n if (options.params) {\n url.search = this.queryFormatter(options.params)\n }\n\n // Body\n if (options.body) {\n options.body = serialize(options.type, options.body, {\n camelCaseTypes: this.camelCaseTypes,\n pluralTypes: this.pluralize\n })\n }\n\n // Request\n const makeRequest = () => {\n // Headers\n const headers = new Headers({\n ...this.headers,\n ...options.headers\n })\n\n // Fetch\n return fetch(url, {\n method: options.method,\n body: options.body,\n headers\n })\n }\n\n try {\n let response = await makeRequest()\n\n if (!response.ok) {\n response.replayRequest = makeRequest\n const replayedResponse = await this.onResponseError(response)\n\n if (replayedResponse instanceof Response) {\n response = replayedResponse\n }\n } else if (!response.ok) {\n throw new Error(response.statusText)\n }\n\n // Response Headers\n const responseHeaders = {}\n\n for (const [key, value] of response.headers.entries()) {\n responseHeaders[key] = value\n }\n\n const contentType = responseHeaders['content-type']\n\n // Response Data\n const data = contentType && contentType.includes(jsonType)\n ? await response.json()\n : {}\n\n // Return\n return {\n ...deserialize(data),\n\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders\n }\n } catch (error) {\n throw error\n }\n }\n\n get (model, options = { method: 'GET' }) {\n try {\n options.url = model.split('/')\n .map(part => this.resourceCase(part))\n .filter(Boolean)\n .join('/')\n\n return this.request(options)\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n patch (model, body, options = { method: 'PATCH' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url: body?.id ? `${url}/${body.id}` : url,\n body,\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n post (model, body, options = { method: 'POST' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url,\n body,\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n delete (model, id, options = { method: 'DELETE' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url: `${url}/${id}`,\n body: { id },\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n}\n", "/**\n * Deattribute JSON:API data.\n *\n * @param {Object|Object[]} data The JSON:API data to deattribute.\n * @returns {Object} The deattributed data.\n */\nexport function deattribute (data) {\n if (Array.isArray(data)) {\n return data.map(deattribute)\n }\n\n const output = {\n id: data.id,\n type: data.type\n }\n\n for (const key in data.attributes) {\n output[key] = data.attributes[key]\n }\n\n for (const key in data.relationships) {\n if (data.relationships[key].data) {\n output[key] = data.relationships[key].data\n }\n }\n\n return output\n}", "import { deattribute } from './deattribute.js'\n\n/**\n * Group included JSON:API data by type and ID.\n *\n * @param {Object[]} included The included JSON:API data.\n * @returns {Object} The grouped included data.\n */\nfunction groupIncluded (included) {\n const groups = {}\n\n for (const item of included) {\n if (!groups[item.type]) {\n groups[item.type] = {}\n }\n\n groups[item.type][item.id] = deattribute(item)\n }\n\n return groups\n}\n\n/**\n * Checks if a value is an object.\n *\n * @param {*} object The value to check.\n * @returns {boolean} Whether the value is an object.\n */\nfunction hasObject (object) {\n return typeof object === 'object' && object !== null\n}\n\n/**\n * Deserialises a JSON-API response.\n *\n * @param {Object} response The JSON-API response.\n * @returns {Object} The deserialised response.\n */\nexport function deserialize (response) {\n const output = {}\n\n if (response.data) {\n output.data = deattribute(response.data)\n }\n\n if (response.meta) {\n output.meta = response.meta\n }\n\n if (response.included) {\n const included = groupIncluded(response.included)\n\n const getIncluded = item => item.type in included\n ? included[item.type][item.id]\n : item\n\n const replace = item => Array.isArray(item)\n ? item.map(getIncluded)\n : getIncluded(item)\n\n // Replace relationships with included data.\n for (const type in included) {\n for (const id in included[type]) {\n for (const key in included[type][id]) {\n const item = included[type][id][key]\n\n if (hasObject(item)) {\n included[type][id][key] = replace(item)\n }\n }\n }\n }\n\n // Replace relationships in the main data with included data.\n if (Array.isArray(output.data)) {\n for (const item of output.data) {\n for (const key in item) {\n if (hasObject(item[key])) {\n item[key] = replace(item[key])\n }\n }\n }\n } else if (hasObject(output.data)) {\n for (const key in output.data) {\n if (hasObject(output.data[key])) {\n output.data[key] = replace(output.data[key])\n }\n }\n }\n }\n\n return output\n}", "/**\n * Parse the error response from the API.\n *\n * @param {Object} error The error object.\n * @throws {Error} The parsed error object.\n */\nexport function errorParser (error) {\n if (error.response) {\n const { data } = error.response\n\n if (data?.errors) {\n error.errors = data.errors\n }\n }\n\n throw error\n}\n", "import { errorParser } from './error-parser.js'\n\nfunction serializeNode (node, key, data) {\n if (typeof node === 'object' && node !== null) {\n if (!data.relationships) {\n data.relationships = {}\n }\n\n data.relationships[key] = {\n data: node.id ? { id: node.id, type: node.type || key } : node,\n links: node.links,\n meta: node.meta\n }\n } else {\n if (!data.attributes) {\n data.attributes = {}\n }\n\n data.attributes[key] = node\n }\n\n return data\n}\n\nexport function serialize (type, data, options = {\n camelCaseTypes: string => string,\n pluralTypes: string => string\n}) {\n try {\n if (data === null || (Array.isArray(data) && !data.length)) {\n return { data }\n }\n\n const output = {\n type: options.pluralTypes(options.camelCaseTypes(type))\n }\n\n if (data.id) {\n output.id = String(data.id)\n }\n\n for (const key in data) {\n if (['id', 'type'].includes(key)) {\n continue\n }\n\n serializeNode(data[key], key, output)\n }\n\n return JSON.stringify({ data: output })\n } catch (error) {\n errorParser(error)\n }\n}", "/**\n * Loop through an object and build a query string.\n * \n * @param {URLSearchParams} query The query to append to.\n * @param {Object} object The object to loop through.\n * @param {string} prefix The prefix to use.\n * @returns {void}\n * @private\n */\nfunction buildQuery (query, object = {}, prefix = '') {\n const isArray = Array.isArray(object)\n\n for (const key in object) {\n const value = object[key]\n const withPrefix = prefix ? `${prefix}[${isArray ? '' : key}]` : key\n\n value instanceof Object\n ? buildQuery(query, value, withPrefix)\n : query.append(withPrefix, value)\n }\n}\n\n/**\n * Format query parameters.\n * \n * @param {Object} parameters The parameters to format.\n * @returns {URLSearchParams} The formatted query.\n */\nexport function queryFormatter (parameters = {}) {\n const query = new URLSearchParams()\n buildQuery(query, parameters)\n\n return query\n}\n", "/**\n * Split a model name from a URL.\n *\n * @param {string} url The URL to split.\n * @param {Object} options The options to use.\n * @returns {string[]} The model and resource.\n */\nexport function splitModel (url, options = {\n resourceCase: string => string,\n pluralize: string => string\n}) {\n const parts = url.split('/')\n const model = parts.pop()\n const resource = parts.join('/')\n\n return [\n model,\n `${resource}/${options.pluralize(options.resourceCase(model))}`\n ]\n}\n", "/**\n * Convert a string from snake_case and kebab-case to camelCase.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function camelCase (input) {\n return input\n .toLowerCase()\n .replace(/[-_](.)/g, (_, char) => char.toUpperCase())\n .replace(/^(.)/, (char) => char.toLowerCase())\n}\n", "/**\n * Convert a string from camelCase and snake_case to kebab-case.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function kebabCase (input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/_/g, '-')\n .toLowerCase()\n}\n", "/**\n * Converts a string from camelCase and kebab-case to snake_case.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function snakeCase (input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .replace(/-/g, '_')\n .toLowerCase()\n}\n"],
5
- "mappings": "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",
6
- "names": ["pluralize", "deattribute", "data", "output", "key", "groupIncluded", "included", "groups", "item", "deattribute", "hasObject", "object", "deserialize", "response", "output", "getIncluded", "replace", "type", "id", "key", "errorParser", "error", "data", "serializeNode", "node", "key", "data", "serialize", "type", "options", "string", "output", "error", "errorParser", "buildQuery", "query", "object", "prefix", "isArray", "key", "value", "withPrefix", "queryFormatter", "parameters", "splitModel", "url", "options", "string", "parts", "model", "resource", "camelCase", "input", "_", "char", "kebabCase", "input", "snakeCase", "input", "jsonType", "Fetchja", "options", "object", "queryFormatter", "string", "camelCase", "cases", "kebabCase", "snakeCase", "pluralize", "error", "#splitModel", "model", "splitModel", "baseURL", "url", "serialize", "makeRequest", "headers", "response", "replayedResponse", "responseHeaders", "key", "value", "contentType", "data", "deserialize", "part", "errorParser", "body", "type", "id"]
4
+ "sourcesContent": ["import pluralize from 'pluralize'\n\nimport { deserialize } from './utils/deserialize.js'\nimport { serialize } from './utils/serialize.js'\n\nimport { errorParser } from './utils/error-parser.js'\nimport { queryFormatter } from './utils/query-formatter.js'\nimport { splitModel } from './utils/split-model.js'\n\nimport { camelCase } from './utils/camel-case.js'\nimport { kebabCase } from './utils/kebab-case.js'\nimport { snakeCase } from './utils/snake-case.js'\n\nconst jsonType = 'application/vnd.api+json'\n\n/**\n * Options for Fetchja.\n * \n * @typedef {Object} FetchjaOptions\n * @property {string} baseURL The base URL for all requests.\n * @property {Function} fetchFunction A custom fetch function to use in request.\n * @property {Object} headers The headers to include in all requests.\n * @property {Function} queryFormatter A function to format query parameters.\n * @property {string} resourceCase The case to use for resource names.\n * @property {boolean} pluralize Pluralize resource names.\n */\n\n/**\n * Fetchja is a simple wrapper around the Fetch API.\n * \n * @class Fetchja\n * @param {FetchjaOptions} [options] Options for Fetchja.\n */\nexport default class Fetchja {\n constructor (options = {\n headers: {}\n }) {\n this.baseURL = options.baseURL\n\n // Headers\n this.headers = {\n Accept: jsonType,\n 'Content-Type': jsonType,\n ...options.headers\n }\n\n // Fetch Function\n this.fetchFunction = options.fetchFunction\n\n // Query\n this.queryFormatter = typeof options.queryFormatter === 'function'\n ? options.queryFormatter\n : object => queryFormatter(object)\n\n // Camel Case Types\n this.camelCaseTypes = options.camelCaseTypes === false\n ? string => string\n : camelCase\n\n // Resource Case\n const cases = {\n camel: camelCase,\n kebab: kebabCase,\n snake: snakeCase,\n\n default: string => string\n }\n\n this.resourceCase = cases[options.resourceCase] || cases.default\n \n // Pluralise\n this.pluralize = options.pluralize === false\n ? string => string\n : pluralize\n\n // Interceptors\n this.onResponseError = error => error\n\n // Alias\n this.fetch = this.get\n this.update = this.patch\n this.create = this.post\n this.remove = this.delete\n }\n\n #splitModel (model) {\n return splitModel(model, {\n resourceCase: this.resourceCase,\n pluralize: this.pluralize\n })\n }\n\n async request (options = {\n method: 'GET',\n headers: {}\n }) {\n const baseURL = this.baseURL || options.baseURL\n\n const url = new URL(\n options.url.startsWith('/') ? options.url.slice(1) : options.url,\n baseURL.endsWith('/') ? baseURL : baseURL + '/'\n )\n\n // Params\n if (options.params) {\n url.search = this.queryFormatter(options.params)\n }\n\n // Body\n if (options.body) {\n options.body = serialize(options.type, options.body, {\n camelCaseTypes: this.camelCaseTypes,\n pluralTypes: this.pluralize\n })\n }\n\n // Request\n const makeRequest = () => {\n // Headers\n const headers = new Headers({\n ...this.headers,\n ...options.headers\n })\n\n // Fetch\n const fetchOptions = {\n method: options.method,\n body: options.body,\n headers\n }\n\n if (typeof this.fetchFunction === 'function') {\n return this.fetchFunction(url, fetchOptions)\n }\n\n return fetch(url, fetchOptions)\n }\n\n try {\n let response = await makeRequest()\n\n if (!response.ok) {\n response.replayRequest = makeRequest\n\n const replayedResponse = await this.onResponseError(response)\n\n if (replayedResponse instanceof Response) {\n response = replayedResponse\n }\n\n if (!response.ok) {\n throw new Error(response.statusText)\n }\n }\n\n // Response Headers\n const responseHeaders = {}\n\n for (const [key, value] of response.headers.entries()) {\n responseHeaders[key] = value\n }\n\n const contentType = responseHeaders['content-type']\n\n // Response Data\n const data = contentType && contentType.includes(jsonType)\n ? await response.json()\n : {}\n\n // Return\n return {\n ...(data.errors ? data : deserialize(data)),\n\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders\n }\n } catch (error) {\n throw error\n }\n }\n\n get (model, options = { method: 'GET' }) {\n try {\n options.url = model.split('/')\n .map(part => this.resourceCase(part))\n .filter(Boolean)\n .join('/')\n\n return this.request(options)\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n patch (model, body, options = { method: 'PATCH' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url: body?.id ? `${url}/${body.id}` : url,\n body,\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n post (model, body, options = { method: 'POST' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url,\n body,\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n\n delete (model, id, options = { method: 'DELETE' }) {\n try {\n const [type, url] = this.#splitModel(model)\n\n return this.request({\n url: `${url}/${id}`,\n body: { id },\n type,\n\n ...options\n })\n } catch (error) {\n throw errorParser(error)\n }\n }\n}\n", "/**\n * Deattribute JSON:API data.\n *\n * @param {Object|Object[]} data The JSON:API data to deattribute.\n * @returns {Object} The deattributed data.\n */\nexport function deattribute (data) {\n if (Array.isArray(data)) {\n return data.map(deattribute)\n }\n\n const output = {\n type: data.type,\n id: data.id\n }\n\n for (const key in data.attributes) {\n output[key] = data.attributes[key]\n }\n\n for (const key in data.relationships) {\n if (data.relationships[key].data) {\n output[key] = data.relationships[key].data\n }\n }\n\n return output\n}", "import { deattribute } from './deattribute.js'\n\n/**\n * Group included JSON:API data by type and ID.\n *\n * @param {Object[]} included The included JSON:API data.\n * @returns {Object} The grouped included data.\n */\nfunction groupIncluded (included) {\n const groups = {}\n\n for (const item of included) {\n if (!groups[item.type]) {\n groups[item.type] = {}\n }\n\n groups[item.type][item.id] = deattribute(item)\n }\n\n return groups\n}\n\n/**\n * Checks if a value is an object.\n *\n * @param {*} object The value to check.\n * @returns {boolean} Whether the value is an object.\n */\nfunction hasObject (object) {\n return typeof object === 'object' && object !== null\n}\n\n/**\n * Deserialises a JSON-API response.\n *\n * @param {Object} response The JSON-API response.\n * @returns {Object} The deserialised response.\n */\nexport function deserialize (response) {\n const output = {}\n\n if (response.data) {\n output.data = deattribute(response.data)\n }\n\n if (response.meta) {\n output.meta = response.meta\n }\n\n if (response.included) {\n const included = groupIncluded(response.included)\n\n const getIncluded = item => item.type in included\n ? included[item.type][item.id]\n : item\n\n const replace = item => Array.isArray(item)\n ? item.map(getIncluded)\n : getIncluded(item)\n\n // Replace relationships with included data.\n for (const type in included) {\n for (const id in included[type]) {\n for (const key in included[type][id]) {\n const item = included[type][id][key]\n\n if (hasObject(item)) {\n included[type][id][key] = replace(item)\n }\n }\n }\n }\n\n // Replace relationships in the main data with included data.\n if (Array.isArray(output.data)) {\n for (const item of output.data) {\n for (const key in item) {\n if (hasObject(item[key])) {\n item[key] = replace(item[key])\n }\n }\n }\n } else if (hasObject(output.data)) {\n for (const key in output.data) {\n if (hasObject(output.data[key])) {\n output.data[key] = replace(output.data[key])\n }\n }\n }\n }\n\n return output\n}", "/**\n * Parse the error response from the API.\n *\n * @param {Object} error The error object.\n * @throws {Error} The parsed error object.\n */\nexport function errorParser (error) {\n if (error.response) {\n const { data } = error.response\n\n if (data?.errors) {\n error.errors = data.errors\n }\n }\n\n throw error\n}\n", "import { errorParser } from './error-parser.js'\n\n/**\n * Checks if a value is an object.\n *\n * @param {*} object The object to check.\n * @returns {boolean} Whether the value is an object.\n */\nfunction hasObject (object) {\n return typeof object === 'object' && object !== null\n}\n\n/**\n * Serialises a JSON-API request.\n * \n * @param {string} type The entity name.\n * @param {Object} request The request to serialise.\n * @param {Object} options The serialisation options.\n * @returns {string} The JSON serialised request.\n */\nexport function serialize (type, request, options = {\n camelCaseTypes: string => string,\n pluralTypes: string => string\n}) {\n const included = []\n\n function include (node, subtype) {\n if (!hasObject(node)) {\n return\n }\n\n if (!node.id) {\n throw new Error('All included resources must have an ID.')\n }\n\n if (!included.find(item => item.id === node.id)) {\n included.push(extractData(node, subtype))\n }\n }\n\n function extractData (node, subtype) {\n const data = {\n type: options.pluralTypes(options.camelCaseTypes(subtype))\n }\n\n for (const key in node) {\n if (key === 'type') {\n continue\n }\n\n if (key === 'id') {\n data.id = String(node.id)\n continue\n }\n \n const value = node[key]\n\n // Is this a relationship?\n if (typeof value === 'object') {\n data.relationships = data.relationships || {}\n\n // One-To-Many / Many-To-Many\n if (Array.isArray(value)) {\n data.relationships[key] = {\n data: value.map(item => {\n include(item, key)\n return { type: item.type || key, id: item.id }\n })\n }\n\n continue\n }\n\n // One-To-One\n data.relationships[key] = {\n data: { type: value.type || key, id: value.id }\n }\n\n include(value, key)\n\n continue\n }\n\n // Is this an attribute?\n data.attributes = data.attributes || {}\n data.attributes[key] = node[key]\n }\n\n return data\n }\n\n try {\n const data = extractData(request, type)\n return JSON.stringify({ data, included })\n } catch (error) {\n errorParser(error)\n }\n}\n", "/**\n * Loop through an object and build a query string.\n * \n * @param {URLSearchParams} query The query to append to.\n * @param {Object} object The object to loop through.\n * @param {string} prefix The prefix to use.\n * @returns {void}\n * @private\n */\nfunction buildQuery (query, object = {}, prefix = '') {\n const isArray = Array.isArray(object)\n\n for (const key in object) {\n const value = object[key]\n const withPrefix = prefix ? `${prefix}[${isArray ? '' : key}]` : key\n\n value instanceof Object\n ? buildQuery(query, value, withPrefix)\n : query.append(withPrefix, value)\n }\n}\n\n/**\n * Format query parameters.\n * \n * @param {Object} parameters The parameters to format.\n * @returns {URLSearchParams} The formatted query.\n */\nexport function queryFormatter (parameters = {}) {\n const query = new URLSearchParams()\n buildQuery(query, parameters)\n\n return query\n}\n", "/**\n * Split a model name from a URL.\n *\n * @param {string} url The URL to split.\n * @param {Object} options The options to use.\n * @returns {string[]} The model and resource.\n */\nexport function splitModel (url, options = {\n resourceCase: string => string,\n pluralize: string => string\n}) {\n const parts = url.split('/')\n const model = parts.pop()\n const resource = parts.join('/')\n\n return [\n model,\n `${resource}/${options.pluralize(options.resourceCase(model))}`\n ]\n}\n", "/**\n * Convert a string from snake_case and kebab-case to camelCase.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function camelCase (input) {\n return input\n .toLowerCase()\n .replace(/[-_](.)/g, (_, char) => char.toUpperCase())\n .replace(/^(.)/, (char) => char.toLowerCase())\n}\n", "/**\n * Convert a string from camelCase and snake_case to kebab-case.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function kebabCase (input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/_/g, '-')\n .toLowerCase()\n}\n", "/**\n * Converts a string from camelCase and kebab-case to snake_case.\n *\n * @param {string} input The string to convert.\n * @returns {string} The converted string.\n */\nexport function snakeCase (input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .replace(/-/g, '_')\n .toLowerCase()\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["pluralize", "deattribute", "data", "output", "key", "groupIncluded", "included", "groups", "item", "deattribute", "hasObject", "object", "deserialize", "response", "output", "getIncluded", "replace", "type", "id", "key", "errorParser", "error", "data", "hasObject", "object", "serialize", "type", "request", "options", "string", "included", "include", "node", "subtype", "item", "extractData", "data", "key", "value", "error", "errorParser", "buildQuery", "query", "object", "prefix", "isArray", "key", "value", "withPrefix", "queryFormatter", "parameters", "splitModel", "url", "options", "string", "parts", "model", "resource", "camelCase", "input", "_", "char", "kebabCase", "input", "snakeCase", "input", "jsonType", "Fetchja", "options", "object", "queryFormatter", "string", "camelCase", "cases", "kebabCase", "snakeCase", "pluralize", "error", "#splitModel", "model", "splitModel", "baseURL", "url", "serialize", "makeRequest", "headers", "fetchOptions", "response", "replayedResponse", "responseHeaders", "key", "value", "contentType", "data", "deserialize", "part", "errorParser", "body", "type", "id"]
7
7
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "fetchja",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "esbuild src/index.js --bundle --format=esm --packages=external --minify --sourcemap --outfile=dist/index.js",
8
- "test": "echo \"Error: no test specified\" && exit 1"
8
+ "test": "node --test tests/*"
9
9
  },
10
10
  "author": "Caio Tarifa <caio@yahoo.com>",
11
11
  "license": "ISC",
@@ -14,7 +14,7 @@
14
14
  "pluralize": "^8.0.0"
15
15
  },
16
16
  "devDependencies": {
17
- "esbuild": "0.21.5"
17
+ "esbuild": "0.23.0"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
package/readme.md CHANGED
@@ -21,7 +21,7 @@ $ npm install fetchja pluralize
21
21
  Here's a quick example to get you up and running with Fetchja:
22
22
 
23
23
  ```javascript
24
- import { Fetchja } from 'fetchja'
24
+ import Fetchja from 'fetchja'
25
25
 
26
26
  const api = new Fetchja({
27
27
  baseURL: 'https://api.example.com'
@@ -34,3 +34,12 @@ try {
34
34
  console.error(error)
35
35
  }
36
36
  ```
37
+
38
+ ### Using a custom fetch function.
39
+
40
+ ```javascript
41
+ const api = new Fetchja({
42
+ baseURL: 'https://api.example.com',
43
+ fetchFunction: (url, options) => myCustomFetch(url.href, options)
44
+ })
45
+ ```
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ const jsonType = 'application/vnd.api+json'
18
18
  *
19
19
  * @typedef {Object} FetchjaOptions
20
20
  * @property {string} baseURL The base URL for all requests.
21
+ * @property {Function} fetchFunction A custom fetch function to use in request.
21
22
  * @property {Object} headers The headers to include in all requests.
22
23
  * @property {Function} queryFormatter A function to format query parameters.
23
24
  * @property {string} resourceCase The case to use for resource names.
@@ -43,6 +44,9 @@ export default class Fetchja {
43
44
  ...options.headers
44
45
  }
45
46
 
47
+ // Fetch Function
48
+ this.fetchFunction = options.fetchFunction
49
+
46
50
  // Query
47
51
  this.queryFormatter = typeof options.queryFormatter === 'function'
48
52
  ? options.queryFormatter
@@ -68,7 +72,7 @@ export default class Fetchja {
68
72
  this.pluralize = options.pluralize === false
69
73
  ? string => string
70
74
  : pluralize
71
-
75
+
72
76
  // Interceptors
73
77
  this.onResponseError = error => error
74
78
 
@@ -119,11 +123,17 @@ export default class Fetchja {
119
123
  })
120
124
 
121
125
  // Fetch
122
- return fetch(url, {
126
+ const fetchOptions = {
123
127
  method: options.method,
124
128
  body: options.body,
125
129
  headers
126
- })
130
+ }
131
+
132
+ if (typeof this.fetchFunction === 'function') {
133
+ return this.fetchFunction(url, fetchOptions)
134
+ }
135
+
136
+ return fetch(url, fetchOptions)
127
137
  }
128
138
 
129
139
  try {
@@ -131,13 +141,16 @@ export default class Fetchja {
131
141
 
132
142
  if (!response.ok) {
133
143
  response.replayRequest = makeRequest
144
+
134
145
  const replayedResponse = await this.onResponseError(response)
135
146
 
136
147
  if (replayedResponse instanceof Response) {
137
148
  response = replayedResponse
138
149
  }
139
- } else if (!response.ok) {
140
- throw new Error(response.statusText)
150
+
151
+ if (!response.ok) {
152
+ throw new Error(response.statusText)
153
+ }
141
154
  }
142
155
 
143
156
  // Response Headers
@@ -156,7 +169,7 @@ export default class Fetchja {
156
169
 
157
170
  // Return
158
171
  return {
159
- ...deserialize(data),
172
+ ...(data.errors ? data : deserialize(data)),
160
173
 
161
174
  status: response.status,
162
175
  statusText: response.statusText,
@@ -10,8 +10,8 @@ export function deattribute (data) {
10
10
  }
11
11
 
12
12
  const output = {
13
- id: data.id,
14
- type: data.type
13
+ type: data.type,
14
+ id: data.id
15
15
  }
16
16
 
17
17
  for (const key in data.attributes) {
@@ -1,54 +1,98 @@
1
1
  import { errorParser } from './error-parser.js'
2
2
 
3
- function serializeNode (node, key, data) {
4
- if (typeof node === 'object' && node !== null) {
5
- if (!data.relationships) {
6
- data.relationships = {}
7
- }
8
-
9
- data.relationships[key] = {
10
- data: node.id ? { id: node.id, type: node.type || key } : node,
11
- links: node.links,
12
- meta: node.meta
13
- }
14
- } else {
15
- if (!data.attributes) {
16
- data.attributes = {}
17
- }
18
-
19
- data.attributes[key] = node
20
- }
21
-
22
- return data
3
+ /**
4
+ * Checks if a value is an object.
5
+ *
6
+ * @param {*} object The object to check.
7
+ * @returns {boolean} Whether the value is an object.
8
+ */
9
+ function hasObject (object) {
10
+ return typeof object === 'object' && object !== null
23
11
  }
24
12
 
25
- export function serialize (type, data, options = {
13
+ /**
14
+ * Serialises a JSON-API request.
15
+ *
16
+ * @param {string} type The entity name.
17
+ * @param {Object} request The request to serialise.
18
+ * @param {Object} options The serialisation options.
19
+ * @returns {string} The JSON serialised request.
20
+ */
21
+ export function serialize (type, request, options = {
26
22
  camelCaseTypes: string => string,
27
23
  pluralTypes: string => string
28
24
  }) {
29
- try {
30
- if (data === null || (Array.isArray(data) && !data.length)) {
31
- return { data }
25
+ const included = []
26
+
27
+ function include (node, subtype) {
28
+ if (!hasObject(node)) {
29
+ return
32
30
  }
33
31
 
34
- const output = {
35
- type: options.pluralTypes(options.camelCaseTypes(type))
32
+ if (!node.id) {
33
+ throw new Error('All included resources must have an ID.')
36
34
  }
37
35
 
38
- if (data.id) {
39
- output.id = String(data.id)
36
+ if (!included.find(item => item.id === node.id)) {
37
+ included.push(extractData(node, subtype))
40
38
  }
39
+ }
41
40
 
42
- for (const key in data) {
43
- if (['id', 'type'].includes(key)) {
41
+ function extractData (node, subtype) {
42
+ const data = {
43
+ type: options.pluralTypes(options.camelCaseTypes(subtype))
44
+ }
45
+
46
+ for (const key in node) {
47
+ if (key === 'type') {
44
48
  continue
45
49
  }
46
50
 
47
- serializeNode(data[key], key, output)
51
+ if (key === 'id') {
52
+ data.id = String(node.id)
53
+ continue
54
+ }
55
+
56
+ const value = node[key]
57
+
58
+ // Is this a relationship?
59
+ if (typeof value === 'object') {
60
+ data.relationships = data.relationships || {}
61
+
62
+ // One-To-Many / Many-To-Many
63
+ if (Array.isArray(value)) {
64
+ data.relationships[key] = {
65
+ data: value.map(item => {
66
+ include(item, key)
67
+ return { type: item.type || key, id: item.id }
68
+ })
69
+ }
70
+
71
+ continue
72
+ }
73
+
74
+ // One-To-One
75
+ data.relationships[key] = {
76
+ data: { type: value.type || key, id: value.id }
77
+ }
78
+
79
+ include(value, key)
80
+
81
+ continue
82
+ }
83
+
84
+ // Is this an attribute?
85
+ data.attributes = data.attributes || {}
86
+ data.attributes[key] = node[key]
48
87
  }
49
88
 
50
- return JSON.stringify({ data: output })
89
+ return data
90
+ }
91
+
92
+ try {
93
+ const data = extractData(request, type)
94
+ return JSON.stringify({ data, included })
51
95
  } catch (error) {
52
96
  errorParser(error)
53
97
  }
54
- }
98
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export function snakeCase (input) {
8
8
  return input
9
- .replace(/([a-z])([A-Z])/g, '$1_$2')
10
- .replace(/-/g, '_')
11
- .toLowerCase()
9
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
10
+ .replace(/-/g, '_')
11
+ .toLowerCase()
12
12
  }
@@ -0,0 +1,30 @@
1
+ import assert from 'assert'
2
+ import { camelCase } from '../../src/utils/camel-case.js'
3
+
4
+ // Test 1: snake_case to camelCase.
5
+ assert.strictEqual(
6
+ camelCase('hello_world'),
7
+ 'helloWorld',
8
+ 'Should convert snake_case to camelCase.'
9
+ )
10
+
11
+ // Test 2: kebab-case to camelCase.
12
+ assert.strictEqual(
13
+ camelCase('hello-world'),
14
+ 'helloWorld',
15
+ 'Should convert kebab-case to camelCase.'
16
+ )
17
+
18
+ // Test 3: Mixed case to camelCase.
19
+ assert.strictEqual(
20
+ camelCase('HELLO_WORLD_123'),
21
+ 'helloWorld123',
22
+ 'Should convert mixed case to camelCase.'
23
+ )
24
+
25
+ // Test 4: Empty string.
26
+ assert.strictEqual(
27
+ camelCase(''),
28
+ '',
29
+ 'Should return an empty string.'
30
+ )
@@ -0,0 +1,30 @@
1
+ import assert from 'assert'
2
+ import { kebabCase } from '../../src/utils/kebab-case.js'
3
+
4
+ // Test 1: camelCase to kebab-case.
5
+ assert.strictEqual(
6
+ kebabCase('helloWorld'),
7
+ 'hello-world',
8
+ 'Should convert camelCase to kebab-case.'
9
+ )
10
+
11
+ // Test 2: snake_case to kebab-case.
12
+ assert.strictEqual(
13
+ kebabCase('hello_world'),
14
+ 'hello-world',
15
+ 'Should convert snake_case to kebab-case.'
16
+ )
17
+
18
+ // Test 3: Mixed case to kebab-case.
19
+ assert.strictEqual(
20
+ kebabCase('HELLO_WORLD_123'),
21
+ 'hello-world-123',
22
+ 'Should convert mixed case to kebab-case.'
23
+ )
24
+
25
+ // Test 4: Empty string.
26
+ assert.strictEqual(
27
+ kebabCase(''),
28
+ '',
29
+ 'Should return an empty string.'
30
+ )
@@ -0,0 +1,30 @@
1
+ import assert from 'assert'
2
+ import { snakeCase } from '../../src/utils/snake-case.js'
3
+
4
+ // Test 1: camelCase to snake_case.
5
+ assert.strictEqual(
6
+ snakeCase('helloWorld'),
7
+ 'hello_world',
8
+ 'Should convert camelCase to snake_case.'
9
+ )
10
+
11
+ // Test 2: kebab-case to snake_case.
12
+ assert.strictEqual(
13
+ snakeCase('hello-world'),
14
+ 'hello_world',
15
+ 'Should convert kebab-case to snake_case.'
16
+ )
17
+
18
+ // Test 3: Mixed case to snake_case.
19
+ assert.strictEqual(
20
+ snakeCase('HELLO_WORLD_123'),
21
+ 'hello_world_123',
22
+ 'Should convert mixed case to snake_case.'
23
+ )
24
+
25
+ // Test 4: Empty string.
26
+ assert.strictEqual(
27
+ snakeCase(''),
28
+ '',
29
+ 'Should return an empty string.'
30
+ )