@doars/vroagn
v1.4.3
Published
A teensy-tiny library for managing network requests.
Readme
vroagn
A teensy-tiny library for managing network requests.
- Comes in at a kilobyte and half in size when compressed. Due to the minimal philosophy of the library and the simple concepts within the total size is tiny as well.
- Has features like throttling, debouncing, delays, retries, and more, making it easier to manage and control network traffic.
- Designed to be used with front-end frameworks such as
@doars/staark. - Written in TypeScript.
The heart of vroagn is the create function, it helps you set up a reusable request configuration. Think of it like setting the table before a big feast—you get everything ready, and then just dig in whenever you're hungry (or in our case, need to make a request).
import { create } from '@doars/vroagn'
const request = create({
domain: '//api.example.com',
path: '/v1/item',
method: 'get',
retryAttempts: 3,
retryDelay: 1000,
})Here, we set up a GET request to https://api.example.com/data with a retry policy that attempts the request up to three times, waiting one second between attempts. You can add more options, like custom headers, query parameters, or even timeout settings.
Once your request is created, sending it is as easy as pie. Just call the function with any additional options you need, and the library takes care of the rest.
const [error, response, result] = await request()
console.log(error, response, result)This sends the request and returns the response and parsed result, depending on the response type. It will automatically retry the request if it fails with specific HTTP status codes like 429 Too Many Requests or 503 Service Unavailable.
The library tries to be smart, it knows that different content types need different handling. If you're fetching JSON, XML, HTML, or even SVG, the data will automatically be parses correctly.
But, what if you need to parse other data types? Say no more! The base library only contains a hand full of common parsing methods, but the full library has a few additional ones. For example parsing CSV/TSV, INI, TOML and even YAML.
import {
create,
csvParser,
iniParser,
tomlParser,
yamlParser,
} from '@doars/vroagn'
const request = create({
domain: '//api.example.com',
parsers: [
csvParser(),
iniParser(),
tomlParser(),
yamlParser(),
],
})Two of the aforementioned parsers, TOML and YAML, are rather simple implementations as they are optimized for size not features. Therefore they will not support everything for example parsing complex data types and proper error handling. If you do need to support the full specification I recommend creating a function that wraps an existing parser.
The example below is a wrapper function for the smol-toml library.
import { parse } from 'smol-toml'
const tomlParser = (
) => ({
types: options.types || ['toml', 'application/toml', 'text/toml'],
parser: async (
response,
requestOptions,
type,
) => {
const text = await response.text()
return parse(text)
},
})And the example below is a wrapper for the js-yaml library.
import { load } from 'js-yaml'
const yamlParser = (
options = {},
) => ({
types: options.types || ['yaml', 'application/yaml', 'text/yaml'],
parser: async (
response,
requestOptions,
) => {
const text = await response.text()
return load(text, options)
},
})You can of course also write an entirely new parser based on a custom data specification, this is perfect for those quirky APIs with their own data formats.
A custom fetch function can also be specified using the fetch options. The examples directory of the library contains an additional fetch function. This function writes successful requests to the browser's caches ensuring the cache is maintained between page reloads outside of the browsers build-in cache.
import { create } from '@doars/vroagn'
import { cacheFetch } from '[...]/exm/cache.js'
const request = create({
domain: '//api.example.com',
fetch: cacheFetch({
name: 'vroagn-cache', // Optional.
ttl: 60 * 60 * 1000, // One hour, optional.
}),
})
const [error, response, result] = await request()
console.log(error, response, result)Using vroagn with staark
vroagn and staark are like peanut butter and jelly. They're great on their own, but together they're unstoppable. Let's see how you can combine them to fetch data dynamically in your staark powered application.
import { mount, node } from '@doars/staark'
import { create } from '@doars/vroagn'
const requestItems = create({
domain: '//api.example.com',
path: '/v1/item',
maxRequests: 1,
retryAttempts: 4,
})
mount(
document.body.firstElementSibling,
(state) => {
if (!state.data) {
requestItems()
.then(
([error, response, result]) => state.data => result,
)
}
return node('div', (
(state.data ?? []).map(
(item) => node('p', item.title),
),
))
},
{ data: null },
)In this example, staark and vroagn team up to fetch and display a list of items. The data is only fetched once, and vroagn handles any retry logic if the request fails. This approach ensures that your application remains responsive and resilient, even when the network isn't.
Request options
When creating a request instance, you can pass an options object to configure the request behaviour. Note that any send options can also be specified here, but will be overwritten with by the options given to the returned method. With one exception, the headers object will be merged with one anther.
The full list of create options:
{number} maxConcurrency = 0The maximum number of concurrent requests allowed. Requests exceeding this limit are queued until one completes. Zero means unlimited.
The full list of send options:
{object} body = nullThe payload to be sent with the request, typically used withpostorputmethods.{string} credentials = 'omit'The credentials mode to use for the request. Options includeomit,same-origin, orinclude.{string} domain = ''The base URL for the API. This URL is prefixed to all request paths.{object} headers = {}Custom headers to include with each request. These headers are merged with any headers specified at the time of the request.{string} method = 'get'The HTTP method to use for the request. Common options includeget,post,put, anddelete.{string} mode = nullThe mode of the request. Options includecors,no-cors, orsame-origin.{string} path = ''The endpoint path to be appended to the base URL.{string} priority = nullThe priority of the request. Options includehigh,normal, orlow. Higher priority requests are executed first.{object} queryParams = {}Query parameters to append to the request URL.{string} redirect = {}The way to handle redirect responses. Options includeerror,follow, ormanual.{Parser[]} parsers = nullA list of additional custom parsers for when the build-in parsers aren't sufficient.{string} type = nullSpecifies the expected response type for the request. This overrides the automatic type determination.{AbortController} abort = nullA customAbortControllerinstance for cancelling the request.{string} cache = 'default'The cache mode for the request. Options includedefault,no-store,reload,no-cache,force-cache, oronly-if-cached.{function} fetch = fetchAllows the setting of a custom fetch function to use.{number} debounce = 0Time in milliseconds to delay the request after it has been triggered. If a new request is triggered within this time, the timer resets.{number} delay = 0Time in milliseconds to delay the start of the request.{number} retryAttempts = 0The number of retry attempts allowed before giving up on the request.{number[]} retryCodes = [429, 503, 504,]An array of HTTP status codes that will trigger a retry if encountered. If aRetry-Afterheader is given by the response it will be used if the retry after moment is later that the retry delay.{number} retryDelay = 500Time in milliseconds to wait between retry attempts if retries are enabled. The delay between retries will increase exponentially, doubling after each failed attempt. This can help to reduce the load on the server during periods of high failure rates.{number} throttle = 0Time in milliseconds to throttle requests. Limits the number of requests that can be made within a specified time frame.{number} timeout = 0Time in milliseconds before the request times out. If the request exceeds this duration, it will be aborted.
The send method returns a promise that resolves to a tuple containing the raw response object and the parsed response body. If the response is JSON and accepts is set to json, it will be parsed automatically.
Parsing
The following types will be automatically parsed and determined.
- The type
arrayBufferwill be parsed as an array buffer. - The type
blobwill be parsed as a blob. - The type
formDatawill be parsed as form data. - The type
textwill be parsed as text. AContent-TypeorAcceptsheaders oftext/plainor a file extension oftxtare also parsed as text. - The type
jsonwill be parsed as JSON. AContent-TypeorAcceptsheaders ofapplication/jsonortext/jsonor a file extension ofjsonare also parsed as JSON. - The type
xmlwill be parsed as XML. AContent-TypeorAcceptsheaders ofapplication/xmlortext/xmlor a file extension ofxmlare also parsed as XML. - The type
htmlwill be parsed as a HTML document. AContent-TypeorAcceptsheaders oftext/htmlor a file extension ofhtmlare also parsed as a HTML document. - The type
html-partialwill be parsed as partial HTML content. AContent-TypeorAcceptsheaders oftext/html-partialare also parsed as partial HTML content. - The type
svgwill be parsed as a SVG document. AContent-TypeorAcceptsheaders ofimage/svg+xmlor a file extension ofsvgare also parsed as a SVG document.
Installation
Via NPM
npm install @doars/vroagnIIFE build via a CDN
<!-- Base bundle -->
<script src="https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.base.iife.js"></script>
<!-- Base bundle minified -->
<script src="https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.base.iife.min.js"></script>
<!-- Full bundle -->
<script src="https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.iife.js"></script>
<!-- Full bundle minified -->
<script src="https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.iife.min.js"></script>ESM build via a CDN
// Base bundle.
import { create } from 'https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.base.js'
// Base bundle minified.
import { create } from 'https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.base.min.js'
// Full bundle.
import { create } from 'https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.js'
// Full bundle minified.
import { create } from 'https://cdn.jsdelivr.net/npm/@doars/vroagn@1/dst/vroagn.min.js'