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

@zeroxsolutions/server

v0.3.3

Published

Every Hono handler the ZeroXSolutions house ships: the environment gate bound to a worker's own bindings schema, the central onError, the OpenAPIHono defaultHook and the JSON:API content-negotiation middleware - kept out of the packages that decide what t

Readme

@zeroxsolutions/server

Every Hono handler the house ships: the middlewares, the onError and the defaultHook a worker wires before its routes. A concern package holds the DECISION as a plain function over values; this one holds the handler that calls it. That split is what leaves the concern packages framework-free.

| Holds | Wires the decisions in | | --- | --- | | createEnvironment | the worker's own bindingsSchema - no package owns a worker's bindings | | createErrorHandler, createValidationHook, jsonApiContentNegotiation | @zeroxsolutions/response, @zeroxsolutions/jsonapi |

What the split bought

| Package | Peers before | Peers now | | --- | --- | --- | | @zeroxsolutions/jsonapi | hono | none - plain functions over strings and documents, which is what an SPA loads | | @zeroxsolutions/response | @hono/zod-openapi, @logtape/logtape, hono, zod | zod - hono left with the plain spelling |

That is the test for anything else moving here: it moves when the move DROPS a framework peer from the package it left. auth() and permission() do not - the provider's own surface decides them, and hasPermission is a call rather than a shape. Neither does dependencies(), which registers one context's own tokens and so has nothing to share.

Install

pnpm add @zeroxsolutions/server

hono, zod and @logtape/logtape are peers - the worker already declares them. @zeroxsolutions/jsonapi and @zeroxsolutions/response come with it, as dependencies: this package CALLS them rather than reading a host's instance.

Quickstart

createEnvironment builds the gate that refuses a request whose bindings do not match the schema the transport declares its Bindings from:

// packages/<context>/src/entrypoints/http/middlewares/environment.ts
import { createEnvironment } from '@zeroxsolutions/server';
import { LOG_CATEGORY } from '../../../lib/constants/index.js';
import { bindingsSchema, type AppEnvironment } from '../env.js';

/** Refuses a request whose environment does not match `bindingsSchema`, before any other middleware reads a field. */
export const environment = createEnvironment<AppEnvironment>({ schema: bindingsSchema, category: LOG_CATEGORY });

The mount site is unchanged, because the binder returns a factory rather than the middleware:

// entrypoints/http/app.ts - first, so a missing binding is named once rather than at first use
app.use('*', environment());

A rejected parse logs app.env.invalid with the failing PATHS and the request id, then throws an error carrying no field name - which input an operator failed to set is not the caller's business, and the transport's onError renders it as a bare 500.

jsonApiContentNegotiation binds to nothing of the product's - the media type and the parameter rule are the standard's, identical on every surface answering it. It takes the surface's namespace for the same reason the other two do: a refusal it mints is an error object like any other, and a client reading not_acceptable beside api.place.not_found cannot tell which surface spoke.

// entrypoints/http/app.ts - one middleware, before the routes, never a per-handler check
import { jsonApiContentNegotiation } from '@zeroxsolutions/server';

app.use('*', jsonApiContentNegotiation({ namespace: 'api' }));

It refuses with JsonApiError, so the refusal renders through the same central onError as every other failure. @zeroxsolutions/jsonapi's README carries the two decisions the headers alone do not settle - why Content-Type is read on a write only, and why an empty Accept is refused.

One error seat

createErrorHandler is the app's onError, and every failure renders through it as one application/vnd.api+json error document carrying jsonapi: { version: '1.1' }. It asks, in order:

  1. resolve(err, c) - where a surface's domain error map answers, through @zeroxsolutions/response's createDomainErrorResolver;
  2. a request the validation hook rejected, one error object per issue;
  3. a JsonApiError, rendered as it was thrown;
  4. otherwise the status alone - a hono HTTPException at its own, anything else as a 500.

Whichever step answers, the handler writes one http.error.answered line under category before the document goes out, at the level the answered status class decides: a 5xx at error, anything else at warn. The line carries the request id, the method, the path, the answered status, the step that claimed the failure (resolve, validation, jsonapi, status) and the error's type and message. Without it a mapped 500 or a guard's 403 reaches the client with nothing in the log for an operator to join it to.

The requestId option's value becomes the id of each error object that carries none, so a client quotes the id the request's log lines carry.

createValidationHook throws its rejection rather than answering, which is what puts a 400 or 422 on that same path. It is still passed to the OpenAPIHono constructor, because without a hook @hono/zod-validator (0.9.0) answers a rejected parse with its own raw 400. And it needs createErrorHandler on the same app: hono answers an error no onError catches with a text 500.

// entrypoints/http/app.ts
const app = new OpenAPIHono<AppEnvironment>({ defaultHook: createValidationHook({ namespace: 'api' }) });
app.onError(
  createErrorHandler({
    namespace: 'api',
    category: LOG_CATEGORY,
    resolve: createDomainErrorResolver(apiErrors),
    requestId: (c) => c.var.requestId,
  }),
);

Why a binder rather than a ready middleware

The schema and the log category are the package's, not the library's: the schema is what the transport declares its Bindings from, and the category names that worker's isolate. A ready middleware would have to read both from somewhere global. Binding them once, at the module that owns them, keeps the type flowing (createEnvironment<AppEnvironment>) and leaves the mount site the shape a middleware module already has - a factory named for its file, called where it is mounted.

API reference

The version-accurate API is the shipped types - every export carries TSDoc compiled into dist/**/*.d.ts. Read it in your editor or at node_modules/@zeroxsolutions/server/dist/. This README does not restate the API: the types are the source of truth and never drift from the version.

Building & testing

pnpm nx build @zeroxsolutions/server
pnpm nx test @zeroxsolutions/server