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

@routepact/core

v0.2.5

Published

Type-safe route spec definitions shared between server and client

Downloads

1,437

Readme

@routepact/core

Shared types and utilities for defining type-safe route pacts. This package is the contract between your server and client — import it in shared code that both sides depend on.

Installation

npm install @routepact/core

You also need a schema library that implements the Standard Schema interface (e.g. Zod, Valibot, ArkType). Examples below use Zod but any Standard Schema-compatible library works.

npm install zod    # or valibot, arktype, etc.

Defining a pact

definePact takes an object where each key is a route name and each value is a route definition with a path, method, and validation schemas. Every non-SSE route must declare a response map (keyed by HTTP status code); request and query are optional. SSE routes declare an events schema instead of response.

import { definePact } from "@routepact/core";
import { z } from "zod";

const post = z.object({ id: z.string(), title: z.string(), body: z.string() });

export const PostPacts = definePact({
  list: {
    method: "get",
    path: "/posts",
    response: {
      200: z.object({
        items: z.object({ id: z.string(), title: z.string() }).array(),
        total: z.number(),
      }),
    },
    query: z.object({
      page: z.string().optional(),
      limit: z.string().optional(),
    }),
  },
  getById: {
    method: "get",
    path: "/posts/:id",
    response: {
      200: post,
      404: z.object({ error: z.string() }),
    },
  },
  create: {
    method: "post",
    path: "/posts",
    request: z.object({ title: z.string(), body: z.string() }),
    response: { 201: post },
  },
  update: {
    method: "patch",
    path: "/posts/:id",
    request: z.object({ title: z.string().optional(), body: z.string().optional() }),
    response: { 200: post },
  },
  delete: {
    method: "delete",
    path: "/posts/:id",
    response: { 204: null }, // null = a status that carries no body
  },
});

Validation schemas

| Field | Required for | Description | | ---------- | ------------------------------- | ------------------------------------------------------------------------------------------- | | response | Every non-SSE route | Map of HTTP status code → object schema (or null for a bodyless status) | | events | Every SSE route | Object schema for the SSE event shape — replaces response on sse: true routes | | request | Only for post, patch, put | Object schema for the request body on the server | | query | Optional | Object schema for query parameters — typed on both server and client |

A route is one or the other: a standard route declares response (and never events), an SSE route declares events (and never response). TypeScript rejects a route that declares neither, both, or the wrong one for its sse flag.

All schemas must describe object types (e.g. z.object(...) in Zod). Primitive or array schemas are not supported at the pact level.

Response statuses

response is keyed by HTTP status code, so each status gets its own validated body:

response: {
  200: z.object({ id: z.string(), name: z.string() }),
  404: z.object({ error: z.string() }),
  204: null, // declared, but carries no body
}

The server adapter validates the handler's body against the schema for the status it returned; the client validates the received body against the schema for the status it got back. A null entry declares a status that sends/receives no body — the handler returns just { status: 204 } and the adapters send an empty response.

The handler return type and the client result are both a discriminated union over the declared statuses, so you narrow on status to reach the matching body:

// handler
route.handler(({ params }) =>
  found
    ? { status: 200, body: { id: params.id, name: "Alice" } }
    : { status: 404, body: { error: "not found" } },
);

Server-Sent Events (SSE)

Add sse: true to a route definition to mark it as a streaming endpoint. SSE routes declare an events schema (not response); the server adapter sets up the SSE response and injects a sendEvent function into the handler context. A discriminated union is the natural events schema, letting you send different event shapes in a single stream:

export const NotificationPacts = definePact({
  stream: {
    method: "get",
    path: "/notifications/:userId",
    sse: true,
    events: z.discriminatedUnion("type", [
      z.object({ type: z.literal("message"), text: z.string() }),
      z.object({ type: z.literal("ping"), timestamp: z.number() }),
    ]),
  },
});

The handler return type becomes void for SSE routes — use sendEvent instead of returning { status, body }. sendEvent is typed to the events schema. See the adapter READMEs for the full handler example and connection lifetime details.

Path parameters

Parameters in the path (:param) are extracted as a type-safe object. The server adapter automatically populates params and types it as { [key: string]: string } based on the path string — no schema needed.

// TypeScript requires params when the path has parameters
await client.request(PostPacts.getById, { params: { id: "abc" } });

// TypeScript forbids params when there are none
await client.request(PostPacts.list);

On the server, ctx.params is typed as { id: string } when the path is /posts/:id.

Query parameters

Add a query schema to make query parameters type-safe on both server and client. If any field in the schema is required, TypeScript will require the query option at the call site:

const list = {
  method: "get",
  path: "/posts",
  query: z.object({ page: z.string().optional(), sort: z.string() }), // sort is required
};

// TypeScript requires query.sort
await request(PostPacts.list, { query: { sort: "createdAt" } });

Validation errors

| Error class | Status | When | | ------------------------- | ------ | --------------------------------------------- | | RequestValidationError | 400 | Request body or query fails schema validation | | ResponseValidationError | 500 | Response body fails schema validation |

Both extend ValidationError and expose a cause property with the Standard Schema issues array.

Type reference

| Type | Description | | ------------------------------- | ---------------------------------------------------------------------------------------------------- | | AnyPact | Record<string, AnyPactRoute> — a map of named route definitions | | AnyPactRoute | Widened route type — HttpPactRoute<...> \| SsePactRoute<...> | | PactRoute<TPath, TMethod> | A single route — the union of HttpPactRoute and SsePactRoute | | HttpPactRoute<TPath, TMethod> | A standard route: response required (a ResponseSchemaMap), no events | | SsePactRoute<TPath, TMethod> | An SSE route (sse: true): events required, no response | | ResponseSchemaMap | { [status: number]: StandardSchemaObject \| null } — per-status response schemas (null = no body) | | BaseRouteValidation | The shared optional schemas (request, query) every route may declare | | HttpMethod | "get" \| "post" \| "patch" \| "put" \| "delete" | | BuiltRouter | The result of calling .routes() on a RouterBuilder — passed to toExpressRouter / toHonoRouter | | FinalizedRoute | The result of calling .handler() on a RouteBuilder | | MiddlewareContext | Context object passed to .use() callbacks — includes params, query, body, extensions, plus framework properties | | MiddlewareRequirementsContext | Context type used by defineMiddleware — narrows extensions and params based on declared requirements | | HandlerContext | Context object passed to .handler() callbacks — same shape as MiddlewareContext, plus sendEvent (typed to the events schema) when sse: true | | RouteHandlerResult<TPact> | Return type of a handler — a { status; body } discriminated union over the declared statuses (body optional for null statuses); void for SSE routes | | RouteRequest<TPact> | Inferred output type of the request schema — never if the pact has no request schema | | RouteParams<TPact> | Inferred path parameter record — {} if the path has no :param segments | | RouteQuery<TPact> | Inferred output type of the query schema — {} if the pact has no query schema | | RouteResponse<TPact> | Union of every status's inferred body type (undefined for null statuses) | | RouteEvent<TPact> | Inferred output type of the events schema for SSE routes — never otherwise | | RouteClientResult<TPact> | The client's return type — a { status; body } discriminated union over the declared statuses | | ExtractParams<TPath> | Extracts parameter names from a path string | | ExpectedParams<TPath> | Maps extracted param names to string values | | RouteOptions<TRoute> | Inferred call-site options (params, payload, query) for the client | | ValidationError | Base class for all validation errors — has status and cause: StandardSchemaV1.Issue[] | | RequestValidationError | Extends ValidationError — thrown on bad request body or query (400) | | ResponseValidationError | Extends ValidationError — thrown on bad response body (500) |