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

@eddy-works/never-rest

v0.6.0

Published

Result-based contracts for HTTP and in-process dispatch — handlers return Result instead of throwing

Readme

never-rest

npm version CI types included license node

An opinionated architectural choice — never-rest puts Result-based railway-oriented programming at the API boundary. Whether either side uses railway style internally is up to that team and does not matter to the contract. The assumption is that at least one side wants it; otherwise there is no reason to reach for this.

On top of that choice: a ContractDef where handlers return Result instead of throwing. serve projects that contract onto HTTP. ./local runs the same contract in-process — module to module, or behind a host that already carries the operation as a string (NDJSON, MCP stdio, agent tool calls). Errors carry their cause chain across boundaries. Disclosure is graded by caller trust — not blanket obfuscation.

Package: @eddy-works/never-rest · Licence: Apache-2.0 · Peer: neverthrow · Runtime deps: none (validation via Standard Schema)

Install

npm i @eddy-works/never-rest neverthrow
pnpm add @eddy-works/never-rest neverthrow

neverthrow is a peer dependency — it's the Result / ResultAsync implementation never-rest builds on.

Exports

| Module | Key exports | | --- | --- | | @eddy-works/never-rest | RailError, railError, chain, flatten, formatChain, statusFor, toDeclaredResponse, HOST_STATUSES, disclose, respond | | @eddy-works/never-rest/contract | RouteDef, ContractDef, ClientArgsOf, HandlerArgsOf, OutputOf, ErrorOf, ClientErrorOf, ServerErrorOf, parseRouteSources, parseOutput, compileContract, isContractPath, compilePath, matchPath, normalizePath, assertHandlersComplete, ContractConfigurationError | | @eddy-works/never-rest/server | serve, Handler, Handlers, ServeHandler, compileRoutes, matchRoute, assertProtocolResponse | | @eddy-works/never-rest/client | createClient, Client, ClientOptions, buildRequest | | @eddy-works/never-rest/node | toNodeHandler, FetchHandler, NodeHttpHandler | | @eddy-works/never-rest/local | createLocalClient, createDispatcher | | @eddy-works/never-rest/testing | createTestClient, assertProtocolResponse, checkTransportStability, checkContractOutputs | | @eddy-works/never-rest/openapi | toOpenAPI, OpenApiExportError | | @eddy-works/never-rest/query | createQueryOptions, createMutationOptions, isRetryable |

The problem

Most REST libraries assume handlers throw. Middleware intercepts exceptions, typed errors get lost at the boundary, and clients branch on tuples or catch blocks instead of composing with andThen. Contract DSLs (initContract(), chained builders) inflate TypeScript instantiation cost — @ts-rest/core measures ~5,984 instantiations per route on a 20-route fixture. oRPC types errors on the wire but its server model is throw-based; its non-throwing safe() client does not compose with map / andThen / match. never-rest is contract-first with plain object literals, Result/ResultAsync end to end, and a published per-route type budget enforced in CI.

No middleware

When handlers return Result, auth, side effects, and after-effects are just functions in the chain — not a separate interceptor stack:

getInvoice: ({ params, request }) =>
  requireAuth(request) // gate
    .andThen((session) => requireRole(session, 'billing'))
    .andTee((session) => metrics.increment('invoice.auth_ok')) // side effect
    .andThen((session) => loadInvoiceFor(session, params.id))
    .andTee((invoice) => audit.read('invoice', invoice.id)), // after-effect (best-effort)

If auth fails, the domain call never runs. Tee effects observe without inventing new failure modes; use andThen when a follow-up must succeed. Full catalogue (router, recover, fan-out, lift, …) with neverthrow and ROP links: docs/railway-patterns.md. Thesis: docs/concepts.md — No middleware.

Quickstart

Handlers return a neverthrow Result — never throw. Compose with map / andThen on the server; the client is the same ResultAsync shape.

import { ok, err, type Result } from 'neverthrow';
import { z } from 'zod';
import { railError, type RailError } from '@eddy-works/never-rest';
import type { ContractDef } from '@eddy-works/never-rest/contract';
import { serve, type Handlers } from '@eddy-works/never-rest/server';
import { createClient } from '@eddy-works/never-rest/client';

const userSchema = z.object({ id: z.string(), name: z.string() });
type User = z.infer<typeof userSchema>;

// Plumbing — declare routes, schemas, and status map.
const contract = {
  getUser: {
    method: 'GET',
    path: '/users/:id',
    params: z.object({ id: z.string() }),
    output: userSchema,
    errors: { not_found: 404 },
  },
  createUser: {
    method: 'POST',
    path: '/users',
    body: z.object({ name: z.string().min(1) }),
    output: userSchema,
    success: 201,
    errors: { conflict: 409 },
  },
} as const satisfies ContractDef;

// Business logic — compose with `map` / `andThen` the same way as the client.
const users = new Map<string, User>([['ada', { id: 'ada', name: 'Ada' }]]);

function findUser(id: string): Result<User, RailError<'not_found'>> {
  const user = users.get(id);
  if (user === undefined) {
    return err(railError('not_found', `User ${id} not found`));
  }
  return ok(user);
}

function reserveId(name: string): Result<string, RailError<'conflict'>> {
  const id = name.toLowerCase();
  if (users.has(id)) {
    return err(railError('conflict', `User ${id} already exists`));
  }
  return ok(id);
}

const handlers: Handlers<typeof contract, undefined> = {
  getUser: ({ params }): Result<User, RailError<'not_found'>> =>
    findUser(params.id).map((user) => ({ ...user, name: user.name.trim() })),
  createUser: ({ body }): Result<User, RailError<'conflict'>> =>
    reserveId(body.name).map((id) => {
      const user = { id, name: body.name };
      users.set(id, user);
      return user;
    }),
};

// Plumbing — mount the contract; disclosure grades what callers see.
export default serve(contract, handlers, {
  origin: 'users-api',
  disclosure: (req) =>
    req.headers.get('x-internal') === '1' ? 'full' : 'public',
});

const client = createClient(contract, { baseUrl: 'https://api.example.com' });

await client
  .getUser({ params: { id: 'ada' } })
  .andThen((user) => client.createUser({ body: { name: `${user.name} Jr` } }))
  .match(
    (user) => console.log(user.id),
    (error) => console.error(error.code), // not_found | conflict | validation_error | internal | unavailable
  );

Bring any Standard Schema validator (Zod 4, Valibot, ArkType). Use as const satisfies ContractDef on every contract — without as const, errors widens and domain codes stop being literal. serve returns a callable fetch handler (always answers, including route_not_found) plus cooperative handle() on Workers, Deno, Bun, Node 18+, SvelteKit, Next. For classic Node/http or Express, use toNodeHandler from @eddy-works/never-rest/node.

The same contract also runs without HTTP. Handlers that omit request are LocalHandlers — assignable to serve as well:

import { createLocalClient, createDispatcher } from '@eddy-works/never-rest/local';

const localHandlers = {
  getUser: ({ params }) => findUser(params.id),
  createUser: ({ body }) =>
    reserveId(body.name).map((id) => {
      const user = { id, name: body.name };
      users.set(id, user);
      return user;
    }),
};

const localUsers = createLocalClient(contract, localHandlers, {
  origin: 'users-api',
});
await localUsers.getUser({ params: { id: 'ada' } });

await createDispatcher(contract, localHandlers, { origin: 'users-api' }).dispatch(
  'getUser',
  { params: { id: 'ada' } },
);

createLocalClient is one typed method per operation. createDispatcher is the same machinery addressed by operation name, for a socket, MCP stdio, or tool-call host that already has the string. Both validate declared input and output; neither constructs a Request or Response. Disclosure defaults to full. Route errors status maps are ignored. See docs/api.md — local.

Examples

Mini projects share one contract and mount it on different runtimes — see examples/README.md:

| Example | Runtime | | --- | --- | | express | Express via ./node | | hono | Hono | | next-app-router | Next.js App Router | | sveltekit | SvelteKit | | cloudflare-workers | Cloudflare Workers | | gateway | Cause chains + disclosure | | validators | Zod / Valibot / ArkType | | files-and-streams | Sibling multipart + SSE |

pnpm build
pnpm --filter @never-rest-examples/express start

Type budget

never-rest optimises for measured TypeScript instantiations per route, enforced in CI via @ark/attest. On synthetic 1–40 route fixtures against real src types (TypeScript 5.9.3), combined contract + client marginal slope is ~584 instantiations per route. Published budget: 1,800 per route. Research anchor for @ts-rest/core's c.router() DSL: ~5,984 per route — roughly 10× never-rest.

Methodology, reproduction, and slope breakdown: docs/performance.md.

When not to use this

| Alternative | Prefer it when | | --- | --- | | ts-rest | You want the initContract() builder, existing ecosystem adapters, or OpenAPI generation today. ts-rest is mature for contract-first REST with Zod; its DSL costs substantially more per route in instantiation benchmarks. Last stable release noted in project research: 2025-03-04. | | oRPC | You want RPC-style procedures, streaming, or framework integrations oRPC already ships. Server handlers use throw errors.NOT_FOUND(); typed errors do not compose as Result. The safe() client returns a tuple, not a composable ResultAsync. | | Throwing handlers + middleware | Your team already standardises on exception middleware, you do not need cross-service cause chains, and graded disclosure is unnecessary. |

Documentation

Browsable site: project-eddy.github.io/never-rest.

| Doc | Topic | | --- | --- | | docs/concepts.md | Railway at the boundary, HTTP and local transports, no middleware, errors as data, trust circles | | docs/railway-patterns.md | Full railway/neverthrow pattern catalogue + white-label tenant kitchen sink | | docs/advanced-usage.md | Policy without middleware — capabilities, composers, host wraps, agents | | docs/api.md | Every public export, signature, example — including ./local | | docs/examples.md | Express, Next, SvelteKit, Hono, Workers, gateway, files-and-streams | | docs/files-and-streams.md | JSON on the railway; multipart and SSE on the host | | docs/errors-as-intelligence.md | nextStep, origin, retryable, gateway chains | | docs/comparison.md | vs ts-rest, oRPC, tRPC, and Hono RPC | | docs/migrating.md | From ts-rest, oRPC, throwing handlers | | docs/performance.md | Type instantiation budget (~584/route, CI gate) |

Agent lookup index: skills/never-rest/SKILL.md.

Specs

Gherkin scenarios in specs/ — extract with pnpm specs:extract. Tests map one-to-one to scenario titles:

| Spec | Tests | | --- | --- | | specs/status-mapping.spec.md | src/status.test.ts, src/respond.test.ts, src/server/serve.test.ts | | specs/graded-disclosure.spec.md | src/disclose.test.ts, src/respond.test.ts, src/server/serve.test.ts | | specs/cause-chaining.spec.md | src/error.test.ts, src/server/serve.test.ts | | specs/client-results.spec.md | src/client/create.test.ts | | specs/server-output-validation.spec.md | src/server/serve.test.ts | | specs/contract-compilation.spec.md | src/contract/compile.test.ts, src/contract/path.test.ts, src/server/serve.test.ts | | specs/wire-serialization.spec.md | src/client/create.test.ts, src/client/request.ts paths | | specs/input-sources.spec.md | src/contract/compile.test.ts, src/contract/parse.test.ts | | specs/openapi-export.spec.md | src/openapi/to-openapi.test.ts | | specs/local-dispatch.spec.md | src/local/dispatch.test.ts | | specs/railway-boundary.spec.md | src/railway/ |

See specs/README.md for extraction and layout.