🐕 ADNF
A Dog Named Fetch - A strict, tiny typescript fetch wrapper.
Overview
import { fetch } from 'adnf'
const result = await fetch<User>('/me')
result.user
if (result.success) {
result.user satisfies User
}
The FetchResult
The fetch
function has 3 result types. The FetchResult<V, E>
type describes all three result types:
Success<V>
: Fetch was successfulErrResponse<E>
: Fetch returned response but with error status codeErr
: No response. Strict error thrown, network error, fetch aborted
Additionally FetchResult
extends a rust inspired Result
wrapper proving a useful API.
const result = await fetch<Flower | null, 'NoFlower'>('/flower')
result.errorType
result.type
result.aborted
result.timeout
result.resolved
result.unwrap()
result.notNullable()
result.log()
if (result.success) {
result
result.value
}
if (result.failed) {
if (result.response) return result
result
result.error
result.message
}
Extend fetchers
You can use the maker functions withOptions
, withResource
, withBase
and withFetch
to sequentially extend a fetch.
import { fetch, withBase } from 'adnf'
const baseFetch = withOptions(fetch, { cache: 'no-cache' })
const apiFetch = withBase(baseFetch, '/api')
const authFetch = withOptions(apiFetch, options => ({
headers: { Authorization: localStorage.get('token') },
}))
const auth = withMethods(authFetch)
await auth.get('/me')
Reference
fetch
The ResultFetch
returning a FetchResult
import { fetch } from "adnf"
const result = fetch(
resource: string,
options: RequestInit & {
fetch
strict: boolean
timeout: number
group: AbortControllerGroup
abortPrevious: boolean
data: object
params: Record<string, any>
form: FormData | FormDataRecord
files: FormDataRecord
}
)
result satisfies FetchResult
debugFetch
A fetch function that logs fetches. Does not perform the fetch.
import { debugFetch } from 'adnf'
debugFetch('/user')
withMethods
Extends your fetch with http methods. Note that this does not return a fetch signature but an object of fetches, meaning it can not be passed to other makers. Do this last.
import { fetch, withMethods } from 'adnf'
const methods = withMethods(fetch)
methods.get('/')
methods.post('/')
withResource
& withBase
Rewrite your fetch resource
withResource(fetch, '/workspace')
withBase(fetch, '/api')
withOptions
Extend your fetch options
const noCacheFetch = withOptions(fetch, { cache: 'no-cache' })
const cacheFetch = withOptions(noCacheFetch, { cache: 'default' })
const auth = withOptions(noCacheFetch, options => ({
headers: { Authorization: localStorage.get('token') },
}))
withDeclarations
Declares fetches instead of running them immediately. Helps with prepared fetches, creating services and generating an identifier hash key
.
import { fetch, withDeclarations, params } from 'adnf'
const declare = withDeclarations(fetch)
const getUser = (id: string) => declare<{}, 'Unauthorized'>('/user', { params: { id } })
const declaration = getUser('a')
declaration.key
declaration.fetch()
declare('/user', { params: { id } }).key
declare('/user', () => ({ params: { id } })).key
declare(params('/user', { id })).key
declare(`/user/${id}`).key
declare(['/user', id]).key
import { params } from 'adnf'
const editFlower = (id: string) =>
declare<{}, 'Unauthorized', Partial<Flower>>(params('/flower', { id }), flower => ({
method: 'put',
data: flower,
}))
const declaration = editFlower('tulip')
declaration.key
For mutations where some arguments should not be part of the cache key, declare can be provided a function that will build options after the key was generated. Note that this will force your fetch to be a mutate method i.e. post
, put
, delete
or patch
.
const fetchUser = declare<User, void, { id: string }>('/user', args => ({
params: { id: args.id },
}))
declaration.key
const declaration = fetchUser.fetch({ id: 'a' })
withFetch
Create fetch creators that run sequentially when initiating a fetch. Used to create a fetch that is dependent on the next fetch. Used internally to implement other makers. Read more about fetch dependency below.
const newFetch = withFetch(fetch, fetch => (resource, options) => fetch(resource, { ...options }))
createAbortGroup
Creates a grouped abort controller.
import { createAbortGroup } from 'adnf'
const group = createAbortGroup()
fetch.post('/upload', { abortPrevious: true, group })
fetch.post('/upload', { abortPrevious: true, group })
fetch.post('/upload', { abortPrevious: true, group })
group.cancel()
params
Merge/replace search params to resource or complete URL. Will respect provided format.
params(path: string, params, replace: boolean)
params('/user', { id: 'a' })
params('https://github.com/user?id=a&for=b', { id: 'b' })
params('https://github.com/user?id=a&for=b', { id: 'b' }, true)
Recipes
Fetch logger
import { fetch, useFetch } from 'adnf'
const loggedFetch = useFetch(fetch, fetch => (resource, options) => {
console.log(options.method ?? 'get', resource, options)
return fetch(resource, options)
})
Resources
Fetch dependency
Dependent fetches follow other fetches. Maker functions return a special fetch that maintains a specific order. When using withFetch
the fetch provided in the creator is the next fetch. Your fetch creators are made "dependent" and run in sequence once you have initiated a fetch.
const a = withFetch(fetch, fetch => {
console.log('init: a')
return (resource, options) => {
console.log('fetch: a')
return fetch(resource, options)
}
})
const b = withFetch(a, fetch => {
console.log('init: b')
return (resource, options) => {
console.log('fetch: b')
return fetch(resource, options)
}
})
const c = withFetch(c, fetch => {
console.log('init: c')
return (resource, options) => {
console.log('fetch: c')
return fetch(resource, options)
}
})
c()
ADNF + SWR
See repo resources