@bennetgallein/mande
Version:
Some awesome description
167 lines (165 loc) • 5.65 kB
JavaScript
/*!
* @bennetgallein/mande v3.0.0-rc5
* (c) 2024 Eduardo San Martin Morote
* @license MIT
*/
function stringifyQuery(query) {
let searchParams = Object.keys(query)
.map((k) => [k, query[k]].map(encodeURIComponent).join('='))
.join('&');
return searchParams ? '?' + searchParams : '';
}
let trailingSlashRE = /\/+$/;
let leadingSlashRE = /^\/+/;
function joinURL(base, url) {
return (base.replace(trailingSlashRE, '') + '/' + url.replace(leadingSlashRE, ''));
}
function removeNullishValues(headers) {
return Object.keys(headers).reduce((newHeaders, headerName) => {
if (headers[headerName] != null) {
// @ts-ignore
newHeaders[headerName] = headers[headerName];
}
return newHeaders;
}, {});
}
/**
* Global default options as {@link Options} that are applied to **all** mande
* instances. Always contain an initialized `headers` property with the default
* headers:
* - Accept: 'application/json'
* - 'Content-Type': 'application/json'
*/
const defaults = {
responseAs: 'json',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
/**
* Create a Mande instance
*
* @example
* ```js
* const users = mande('/api/users')
* users.get('2').then(user => {
* // do something
* })
* ```
* @param baseURL - absolute url
* @param instanceOptions - optional options that will be applied to every
* other request for this instance
*/
function mande(baseURL, passedInstanceOptions = {}, fetchPolyfill) {
function _fetch(method, urlOrData, dataOrOptions, localOptions = {}) {
let url;
let data;
if (typeof urlOrData === 'object') {
url = '';
data = urlOrData;
localOptions = dataOrOptions || {};
}
else {
url = urlOrData;
data = dataOrOptions;
}
let mergedOptions = {
...defaults,
...instanceOptions,
method,
...localOptions,
// we need to ditch nullish headers
headers: removeNullishValues({
...defaults.headers,
...instanceOptions.headers,
...localOptions.headers,
}),
};
let query = {
...defaults.query,
...instanceOptions.query,
...localOptions.query,
};
let { responseAs } = mergedOptions;
url = joinURL(baseURL, typeof url === 'number' ? '' + url : url || '');
// TODO: warn about multiple queries provided not supported
// if (__DEV__ && query && urlInstance.search)
url += stringifyQuery(query);
if (data)
mergedOptions.body = JSON.stringify(data);
return localFetch(url, mergedOptions)
.then((response) => Promise.all([
response,
responseAs === 'response'
? response
: response[responseAs]().catch(() => null),
]))
.then(([response, data]) => {
if (response.status >= 200 && response.status < 300) {
// data is a raw response when responseAs is response
return responseAs !== 'response' && response.status == 204
? null
: data;
}
let err = new Error(response.statusText);
err.response = response;
err.body = data;
throw err;
});
}
const localFetch = typeof fetch != 'undefined' ? fetch : fetchPolyfill;
if (!localFetch) {
throw new Error('No fetch function exists. Make sure to include a polyfill on Node.js.');
}
const instanceOptions = {
query: {},
headers: {},
...passedInstanceOptions,
};
return {
options: instanceOptions,
post: _fetch.bind(null, 'POST'),
put: _fetch.bind(null, 'PUT'),
patch: _fetch.bind(null, 'PATCH'),
// these two have no body
get: (url, options) => _fetch('GET', url, null, options),
delete: (url, options) => _fetch('DELETE', url, null, options),
};
}
/**
* Creates an Nuxt SSR compatible function that automatically proxies cookies
* to requests and works transparently on the server and client (it still
* requires a fetch polyfill on Node).
* @example
* ```js
* import { mande, nuxtWrap } from 'mande'
*
* const fetchPolyfill = process.server ? require('node-fetch') : fetch
* const users = mande(BASE_URL + '/api/users', {}, fetchPolyfill)
*
* export const getUserById = nuxtWrap(users, (api, id: string) => api.get(id))
* ```
*
* @param api - Mande instance to wrap
* @param fn - function to be wrapped
*/
function nuxtWrap(api, fn) {
// args for the api call + 1 because of api parameter
const argsAmount = fn.length;
const wrappedCall = function _wrappedCall() {
let apiInstance = api;
let args = Array.from(arguments);
// call from nuxt server with a function to augment the api instance
if (arguments.length === argsAmount) {
apiInstance = { ...api };
// remove the first argument
const [augmentApiInstance] = args.splice(0, 1);
// let the caller augment the instance
augmentApiInstance(apiInstance);
}
return fn.call(null, apiInstance, ...args);
};
return wrappedCall;
}
export { defaults, mande, nuxtWrap };