@doars/vroagn
v1.2.0
Published
A teensy-tiny library for managing network requests.
Downloads
3
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 full build 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.
import { create, cacheFetch } from '@doars/vroagn'
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 = 0
The 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 = null
The payload to be sent with the request, typically used withpost
orput
methods.{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 = null
The mode of the request. Options includecors
,no-cors
, orsame-origin
.{string} path = ''
The endpoint path to be appended to the base URL.{string} priority = null
The 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 = null
A list of additional custom parsers for when the build-in parsers aren't sufficient.{string} type = null
Specifies the expected response type for the request. This overrides the automatic type determination.{AbortController} abort = null
A customAbortController
instance 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 = fetch
Allows the setting of a custom fetch function to use.{number} debounce = 0
Time 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 = 0
Time in milliseconds to delay the start of the request.{number} retryAttempts = 0
The 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-After
header is given by the response it will be used if the retry after moment is later that the retry delay.{number} retryDelay = 500
Time 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 = 0
Time in milliseconds to throttle requests. Limits the number of requests that can be made within a specified time frame.{number} timeout = 0
Time 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
arrayBuffer
will be parsed as an array buffer. - The type
blob
will be parsed as a blob. - The type
formData
will be parsed as form data. - The type
text
will be parsed as text. AContent-Type
orAccepts
headers oftext/plain
or a file extension oftxt
are also parsed as text. - The type
json
will be parsed as JSON. AContent-Type
orAccepts
headers ofapplication/json
ortext/json
or a file extension ofjson
are also parsed as JSON. - The type
xml
will be parsed as XML. AContent-Type
orAccepts
headers ofapplication/xml
ortext/xml
or a file extension ofxml
are also parsed as XML. - The type
html
will be parsed as a HTML document. AContent-Type
orAccepts
headers oftext/html
or a file extension ofhtml
are also parsed as a HTML document. - The type
html-partial
will be parsed as partial HTML content. AContent-Type
orAccepts
headers oftext/html-partial
are also parsed as partial HTML content. - The type
svg
will be parsed as a SVG document. AContent-Type
orAccepts
headers ofimage/svg+xml
or a file extension ofsvg
are also parsed as a SVG document.
Installation
Via NPM
npm install @doars/vroagn
IIFE 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'