@doars/vroagn
v1.5.1
Published
A teensy-tiny library for managing network requests.
Readme
vroagn
A teensy-tiny library for managing network requests.
- Comes in at two kilobytes 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.
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: 'https://api.example.com',
path: '/v1/item',
method: 'get',
retryAttempts: 3,
retryDelay: 1000,
})Here, we set up a GET request to https://api.example.com/v1/item with a policy allowing up to three retries after the initial request, starting with a one-second delay. 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 parsed correctly.
But what if you need to parse other data types? Say no more! The base library contains a handful of common parsing methods, while the full library adds CSV/TSV, INI, TOML, and even YAML.
import {
create,
csvParser,
iniParser,
tomlParser,
yamlParser,
} from '@doars/vroagn'
const request = create({
domain: 'https://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 = (
options = {},
) => ({
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 option. The examples directory contains an additional fetch function that writes successful requests to the browser's Cache API, keeping them available between page reloads independently of the browser's built-in HTTP cache.
import { create } from '@doars/vroagn'
import { cacheFetch } from '[...]/exm/cache.js'
const request = create({
domain: 'https://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: 'https://api.example.com',
path: '/v1/item',
maxRequests: 1,
retryAttempts: 4,
})
const [, , state] = mount(
'#app',
(state) => {
return node('div', (
(state.data ?? []).map(
(item) => node('p', item.title),
),
))
},
{ data: null },
)
requestItems().then(([error, response, result]) => {
state.data = error ? [] : result
})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 its behaviour. Any send option can also be specified here and overridden by options passed to the returned method. Headers are the exception: initial and per-call headers are merged.
The full list of create options:
{number} maxConcurrencyThe maximum number of concurrent requests allowed. It must be a positive integer. Omit it for unlimited concurrency. Initial and per-call limits share one strict FIFO queue.{number} maxRequestsThe maximum number of fetches started by this request instance. Debounced and queued calls consume the limit when their fetch starts.{function} createTrustedHTMLConverts the complete response string before anhtmlorhtml-partialparsing sink. The callback may returnTrustedHTMLfrom an application policy.{object} trustedTypesPolicyA Trusted Types policy with a callablecreateHTMLmethod. A per-call callback takes precedence over a per-call policy, then the initial callback and policy.
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 Fetch priority hint. Options includehigh,normal, orlow. This does not reorder vroagn's FIFO scheduler.{object} queryParams = {}Query parameters to append to the request URL.{string} redirect = nullThe way to handle redirect responses. Options includeerror,follow, ormanual.{Parser[]} parsers = nullA list of additional custom parsers for when the built-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} maximumRetryDelayOptional upper bound for retry delays.{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 returned method resolves to [error, response, result], containing an error when one occurred, the raw response when available, and the parsed body. Expected request failures are returned in this tuple. Invalid creation options, such as a non-positive maxConcurrency, can throw synchronously.
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. AnAcceptorContent-Typeheader oftext/plain, or a file extension oftxt, is also parsed as text. - The type
jsonwill be parsed as JSON. AnAcceptorContent-Typeheader ofapplication/jsonortext/json, or a file extension ofjson, is also parsed as JSON. - The type
xmlwill be parsed as XML. AnAcceptorContent-Typeheader ofapplication/xmlortext/xml, or a file extension ofxml, is also parsed as XML. - The type
htmlwill be parsed as an HTML document. AnAcceptorContent-Typeheader oftext/html, or a file extension ofhtml, is also parsed as an HTML document. - The type
html-partialwill be parsed as partial HTML content. AnAcceptorContent-Typeheader oftext/html-partialis also parsed as partial HTML content. - The type
svgwill be parsed as an SVG document. AnAcceptorContent-Typeheader ofimage/svg+xml, or a file extension ofsvg, is also parsed as an SVG document.
htmlandhtml-partialparse trusted content and do not sanitize it. Using either mode asserts that you trust the response origin and content. For untrusted HTML, sanitize with a maintained sanitizer inside your application's Trusted Types policy and pass that policy withtrustedTypesPolicy. ATrustedHTMLvalue only reflects the policy that created it; it does not sanitize content by itself.
const policy = trustedTypes.createPolicy('vroagn', {
createHTML: (html) => DOMPurify.sanitize(html, {
RETURN_TRUSTED_TYPE: false,
}),
})
const request = create({
trustedTypesPolicy: policy,
type: 'html-partial',
})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'For compact language-model documentation, see llms.txt.
