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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@braedonsaunders/appkit-errors

v0.2.0

Published

Universal action-error handling: a typed refusal taxonomy, a response read path that cannot throw, a persistent alert, and a busy lifecycle that always releases.

Downloads

10,461

Readme

@braedonsaunders/appkit-errors

The universal action-error layer: one vocabulary and one path for "the user did something and the server said no". Every drawer, form and list row that hand-rolls its own error handling eventually produces the same defect — the button spins forever, a toast flashes and vanishes, or the reason lands only in the console. This package is the shared path that makes that class structurally impossible.

pnpm add @braedonsaunders/appkit-errors @braedonsaunders/appkit-ui

Import @braedonsaunders/appkit-errors/styles.css beside the AppKit UI stylesheet so Tailwind scans the React entry.

What it makes true

  1. One vocabulary. ErrorKind — validation (400) · denied (401/403) · not-found (404/410) · conflict (409) · refused (422) · transport (no response) · unexpected (everything else). Classified from the status by kindForStatus; the stable server code and 400 issues[] ride along on the ActionError. Never parse a message string to learn what happened.
  2. A read path that cannot throw. readActionResult(response) and fetchAction(url, init) return a discriminated ActionResult — never throw. Non-JSON bodies (proxy error pages, empty responses) become messageless refusals classified by status instead of escaping past the notification and wedging the button.
  3. Presentation that persists. useAction pins the refusal until the next action; ActionAlert renders it as a house Alert (role="alert", destructive). Notifications still fire through host wiring — the alert is what outlives them.
  4. Busy state that always releases. executeAction runs onSettled in a finally. Call sites never write their own reset again.
  5. Localizable by construction. The package authors no user-facing copy: every fallback and title is a host-supplied string, and server reasons surface verbatim. toDisplayMessage replaces SQL, stack frames, driver text and internal ids with the fallback.
  6. House style. ActionAlert is @braedonsaunders/appkit-ui Alert primitives — no parallel design language.

Use

'use client'
import { fetchAction } from '@braedonsaunders/appkit-errors'
import { ActionAlert, useAction } from '@braedonsaunders/appkit-errors/react'

const { busy, refusal, execute } = useAction({
  notifyError: (message) => toast.error(message),
  notifySuccess: (message) => toast.success(message),
})

async function save() {
  await execute(
    () =>
      fetchAction('/api/records/actions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'save', recordId: id }),
      }),
    {
      fallbackMessage: t('actions.saveFailed'),
      successMessage: t('actions.saved'),
      onOk: () => router.refresh(),
      // onRefused is optional: the refusal is already pinned + notified.
      // Branch on error.kind / error.code here, never on message text.
    },
  )
}

return (
  <>
    <ActionAlert error={refusal} fallbackMessage={t('actions.saveFailed')} />
    <Button disabled={busy} onClick={save}>…</Button>
  </>
)

Branching guide: conflict → offer reload; denied → explain, never retry; not-found → refresh; transport with aborted → timeout copy; validation → render error.issues (the alert already lists them).

Host-owned boundaries

  • The endpoint contract: action routes return { error } with an optional stable code and issues[]. Map domain failures (rule rejections, revision conflicts) to statuses; the package classifies from there.
  • All user-facing copy: fallbacks, titles, notifications.
  • The notification library: useAction takes notifyError/notifySuccess callbacks and never imports one, so the core also works in workers.
  • Error reporting: useAction reports unexpected and transport failures to console.error by default; override reportError with real reporting (which may sample routine transport failures).