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.5.1

Published

Type-safe route spec definitions shared between server and client

Readme

@routepact/core

Define route pacts shared by your server and client.

Installation

npm install @routepact/core

Add a Standard Schema library. Examples use Zod.

npm install zod

Defining a pact

Pass named routes to definePact. Each route has a path, method, and schemas. HTTP routes require a response map; SSE routes require events instead. request and query are optional.

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 |

TypeScript rejects missing or conflicting response and events fields. Schemas must describe objects, such as z.object(...).

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
}

Server and client validate the body against the returned status. For a null status, return only { status: 204 }; the response has no body.

Narrow on status to get its body type:

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

Server-Sent Events (SSE)

SSE routes use sse: true and an events schema. The handler sends validated events with sendEvent. Use a discriminated union for multiple event shapes:

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() }),
    ]),
  },
});

SSE handlers return void. See the adapter READMEs for handler and connection examples.

SSE routes support get and post. Use post with a request schema when the subscription needs a body. A get stream can also be consumed with browser EventSource.

Path parameters

Path segments such as :id become typed string parameters. No schema is 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);

For /posts/:id, server ctx.params has type { id: string }.

Query parameters

Add a query schema to type query parameters. Required schema fields make the client's query option required:

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" } });

Undeclared request data

Handlers and middleware receive only declared request data:

  • A route with no query schema gets ctx.query === {}, even if the client sent query parameters.
  • A route with a query schema gets only the keys that schema declares — extras are stripped by validation.
  • A route with no request schema gets ctx.body === undefined, even if the client sent a payload.

Path parameters come from the route path and are percent-decoded strings. For raw input, use ctx.req (Express) or ctx.c (Hono).

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.