npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

adnf

v0.0.38

Published

**A Dog Named Fetch** - A strict, tiny typescript fetch wrapper.

Readme

🐕 ADNF

A Dog Named Fetch - A strict, tiny typescript fetch wrapper.

Overview

import { fetch } from 'adnf'

const result = await fetch<User>('/me') // Result<User, unknown>

result.user // undefined | User

if (result.ok) {
  result.user satisfies User
}

The FetchResult

The fetch function has 3 result types. The FetchResult<V, E> type describes all three result types:

  • Ok<V>: Fetch was successful
  • Except<E>: Fetch returned response but with error status code
  • NoResponse: 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') // FetchResult<Flower[], "NoFlower">

// Unwrap your value
result.unwrap() // (throws Error) | Flower | null
result.notNullable() // (throws Error) | Flower

// Response data, irregardless of result type
result.data // Flower | null | 'NoFlower' | undefined

// Success data
if (result.ok) {
  result // Ok<Flower[]>
  result.value // Flower[]
}

// Error cases
if (result.failed) {
  if (result.response) {
    result // Except<'NoFlower'>
    result.except // "NoFlower" | null
    return
  }

  result // NoResponse
  result.error // the thrown Error object
  result.message // string
}

result.aborted // fetch was aborted
result.timeout // fetch was aborted due to a timeout
result.resolved // fetch was able to resolve to a request

Extend fetchers

You can use the maker functions withOptions, withResource, withBase and withMiddleware to sequentially extend a fetch.

import { fetch, withBase } from 'adnf'

// base fetch config
const baseFetch = withOptions(fetch, { cache: 'no-cache' })
const apiFetch = withBase(baseFetch, '/api')
const authFetch = withOptions(apiFetch, options => ({
  headers: { Authorization: localStorage.get('token') },
}))

// Use `withMethods` to extend your fetch with http methods
const auth = withMethods(authFetch)

await auth.get('/me')

Reference

Fetchers

fetch

The ResultFetch returning a FetchResult

import { fetch } from "adnf"

// ResultFetch
const result = fetch(
  resource: string,
  options: RequestInit & {
    fetch // fetch implementation, default: window.fetch
    strict: boolean // default: true
    timeout: number // timeout in ms. returns "NoResponse" with timeout set to true
    group: AbortControllerGroup
    abortPrevious: boolean // aborts all previous fetches in provided AbortControllerGroup
    data: object // json body data
    params: Record<string, any> // search params
    form: FormData | FormDataRecord
    files: FormDataRecord
  }
)


result satisfies FetchResult

Makers

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('/') // fetch("/", { method: "get" })
methods.post('/') // fetch("/", { method: "post" })

withResource & withBase

Rewrite your fetch resource

withResource(fetch, '/workspace')
// same as: withResource(fetch, (resource) => resource + "/workspace")

withBase(fetch, '/api')
// same as: withResource(fetch, (resource) => "/api" + resource)

withOptions

Extend your fetch options

const noCacheFetch = withOptions(fetch, { cache: 'no-cache' })
const cacheFetch = withOptions(noCacheFetch, { cache: 'default' }) // overwrites cache

// pass a callback for fresh on-fetch options
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 cache identifier key.

import { fetch, withDeclarations, params } from 'adnf'

const declare = withDeclarations(fetch)

// Declare GET fetch

const getUser = (id: string) => declare<{}, 'Unauthorized'>('/user', { params: { id } })

const declaration = getUser('a')

declaration.key // /user?id=a
declaration.fetch() // run fetch as usual

declare('/user', { params: { id } }).key // /user?id=a
declare('/user', () => ({ params: { id } })).key // /user
declare(params('/user', { id })).key // /user?id=a
declare(`/user/${id}`).key // /user/a
declare(['/user', id]).key // /user/a

// Declare mutative fetch

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 // /flower?id=tulip

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 // "@"/user",#params,,"

const declaration = fetchUser.fetch({ id: 'a' }) // fetch('/user', { method: "post", params: { id: 'a' }, ... })

withMiddleware

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 = withMiddleware(
  fetch,
  fetch => (resource, options) => fetch(resource, { ...options })
)

Helpers

createAbortGroup

Creates a grouped abort controller.

import { createAbortGroup } from 'adnf'

const group = createAbortGroup()

// use abortPrevious for grouped fetched before fetch
fetch.post('/upload', { abortPrevious: true, group }) // NoResponse
fetch.post('/upload', { abortPrevious: true, group }) // NoResponse
fetch.post('/upload', { abortPrevious: true, group }) // Ok

// or manually
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' })
// /user?id=a
params('https://github.com/user?id=a&for=b', { id: 'b' })
// https://github.com/user?id=b&for=b
params('https://github.com/user?id=a&for=b', { id: 'b' }, true)
// https://github.com/user?id=b

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 withMiddleware 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 = withMiddleware(fetch, fetch => {
  console.log('init: a')
  return (resource, options) => {
    console.log('fetch: a')
    return fetch(resource, options)
  }
})

const b = withMiddleware(a, fetch => {
  console.log('init: b')
  return (resource, options) => {
    console.log('fetch: b')
    return fetch(resource, options)
  }
})

const c = withMiddleware(c, fetch => {
  console.log('init: c')
  return (resource, options) => {
    console.log('fetch: c')
    return fetch(resource, options)
  }
})

c()
// init: c
// init: b
// init: a
// fetch: a
// fetch: b
// fetch: c

ADNF + SWR

See repo resources