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

ts-procedures

v11.0.0

Published

A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.

Downloads

2,957

Readme

ts-procedures

A TypeScript RPC framework that creates type-safe, schema-validated procedure calls from a single function definition. Define procedures once on the server and get full type inference, runtime validation, HTTP serving, and generated clients (TypeScript, Kotlin, Swift) — with typed errors end to end.

import { Type } from 'typebox'
import { Procedures } from 'ts-procedures'

type Ctx = { db: Db }
const { Create } = Procedures<Ctx>()

export const { GetUser } = Create(
  'GetUser',
  {
    schema: {
      params: Type.Object({ id: Type.String() }),
      returnType: Type.Object({ id: Type.String(), name: Type.String() }),
    },
  },
  async (ctx, params) => {
    const user = await ctx.db.users.find(params.id)
    if (!user) throw ctx.error('User not found', { id: params.id })
    return user
  },
)

// Directly callable (great for tests), fully typed:
await GetUser({ db }, { id: 'u1' })

The four procedure kinds

| Creator | Kind | Shape | |---|---|---| | Create | rpc | (ctx, params) => Promise<T> — POST, body in/JSON out | | CreateStream | rpc-stream | async generator — SSE (or text) stream | | CreateHttp | http | REST route with per-channel input (pathParams, query, body, headers) | | CreateHttpStream | http-stream | REST route streaming SSE, optional initial headers |

Every creator returns { [name]: handler, procedure: handler, info }: the named handler for direct calls, and info for introspection (computed JSON Schema, validators, your extended config).

const { CreateHttp } = Procedures<Ctx>({ http: { pathPrefix: '/v1', scope: 'users' } })

export const { UpdateUser } = CreateHttp(
  'UpdateUser',
  {
    path: '/users/:id',
    method: 'put',
    errors: ['NotFound'], // taxonomy keys → typed client errors
    schema: {
      req: {
        pathParams: Type.Object({ id: Type.String() }),
        body: Type.Object({ name: Type.String() }),
      },
      res: { body: Type.Object({ id: Type.String(), name: Type.String() }) },
    },
  },
  async (ctx, req) => ctx.db.users.update(req.pathParams.id, req.body),
)

Serving over HTTP (Hono)

import { Hono } from 'hono'
import { HonoAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono'

const errors = defineErrorTaxonomy({
  NotFound: { class: NotFoundError, statusCode: 404 },
})

const builder = new HonoAppBuilder({ pathPrefix: '/api', errors })
  .register(factory, (c) => ({ db: makeDb(c) })) // context per request (sync or async)

const app = builder.build() // a Hono app — mount anywhere Hono runs

One builder serves all four kinds. Config is stratified: kind-specific blocks (rpc.onSuccess, api.queryParser, stream.defaultStreamMode / onStreamStart / onStreamEnd / onMidStreamError) plus cross-cutting error handling (errors taxonomy, unknownError, imperative onError, onRequestError observer) and lifecycle (onRequestStart/End).

An Astro adapter ships too: createAstroHandler from ts-procedures/astro serves built Hono apps from an Astro catch-all route.

Error taxonomy

Declarative, typo-proof error handling: map error classes (or predicates) to status codes and wire bodies once, and the same source of truth drives runtime responses, envelope docs, and generated typed client errors.

const errors = defineErrorTaxonomy({
  NotFound:  { class: NotFoundError, statusCode: 404 },
  UseCase:   { class: UseCaseError, statusCode: 422, toResponse: (e) => ({ message: e.publicMessage }) },
  PgUnique:  { match: (e): e is DatabaseError => isPgError(e, '23505'), statusCode: 409 },
})

Subclasses are checked before base classes automatically (topological sort); { name } is injected into bodies so client dispatch always works; framework defaults (ProcedureValidationError → 400, etc.) layer underneath yours.

Generated clients

Codegen consumes a DocEnvelope — from a live URL or a file written with writeDocEnvelope(builder, 'envelope.json'):

npx ts-procedures-codegen --url http://localhost:3000/api/docs --out src/generated
# or offline:
npx ts-procedures-codegen --file envelope.json --out src/generated
import { createApiClient } from './generated'

const api = createApiClient({ basePath: 'https://api.example.com' })

const user = await api.users.GetUser({ id: 'u1' })          // throws typed errors
const result = await api.users.GetUser.safe({ id: 'u1' })   // Result<T, E> instead

for await (const event of api.users.WatchUsers({})) { ... } // TypedStream
  • Typed errors: routes declaring errors: [...] get real error classes — catch (e) { if (e instanceof ApiErrors.NotFound) ... } — plus a named Errors union per route.
  • Self-contained by default: the generated directory bundles its own runtime (_client.ts / _types.ts); no runtime dependency on this package.
  • Shared models: schemas carrying $id are hoisted once into _models.ts (or re-exported from your own package via --shared-models-module).
  • Kotlin / Swift: --target kotlin --kotlin-package com.example.api or --target swift emit types + route constants/path builders for mobile teams (they own the HTTP layer).
  • Watch mode, config file (ts-procedures-codegen.config.json), strict flags with did-you-mean, orphaned-file pruning — see npx ts-procedures-codegen --help.

Validation & schemas

  • TypeBox is built in (import { Type } from 'typebox'); schema.params / schema.req.* are validated at runtime with AJV (allErrors, coerceTypes, removeAdditional); schema.returnType / schema.res document and drive codegen.
  • Customize AJV per factory: Procedures({ validation: { ajv: {...} } }).
  • Skip per-call validation for trusted internal factories: Procedures({ validation: false }) (schemas still computed; bad schemas still fail fast at registration).
  • Plug in another schema library with a 3-line SchemaAdapter (Procedures({ schema: { adapters: [zodAdapter] } })).
  • Factory middleware (ctx + input aware, runs for all procedures): Procedures({ middleware: [({ ctx, input, next }) => ...] }).

Streaming

Stream handlers always receive ctx.signal — it aborts on client disconnect, and with reason 'stream-completed' on normal completion. Yields become SSE events (customize with sse(data, { event, id, retry })); the generator's return value arrives as the event: 'return' payload, surfaced by generated clients as await stream.result. Opt into per-yield validation with validateYields: true.

Build your own server adapter

Everything the Hono adapter uses lives in ts-procedures/server, framework-free: route-doc builders, taxonomy dispatch (data in, data out), request channel extraction (RequestSource), SSE metadata, DocRegistry. A Fastify or Express adapter is a few thin handlers — src/adapters/hono/ is the reference implementation.

Subpaths

| Import | Contents | |---|---| | ts-procedures | Procedures, error classes, schema utilities, writeDocEnvelope | | ts-procedures/hono | HonoAppBuilder, defineErrorTaxonomy, sse, DocRegistry | | ts-procedures/astro | createAstroHandler, getAstroContext | | ts-procedures/server | Transport-agnostic adapter toolkit | | ts-procedures/http | HTTP doc/config types (type-only) | | ts-procedures/http-docs | DocRegistry | | ts-procedures/http-errors | Error taxonomy helpers | | ts-procedures/client | Runtime client (createClient, adapters, hooks, errors) | | ts-procedures/codegen | generateClient programmatic API |

AI assistant setup

The package ships a single spec-compliant Agent Skill so your AI tooling knows the framework's patterns and footguns. One skill is installed into the standard agent directories (.agents/skills/ — read by Codex, Cursor, Copilot, Gemini CLI and 30+ others — plus .claude/skills/ for Claude Code):

npx ts-procedures-setup            # install the skill
npx ts-procedures-setup --dry-run  # preview
npx ts-procedures-setup --check    # CI gate: fail if outdated

The installed skill auto-updates on subsequent npm install via the package's postinstall hook. See docs/ai-agent-setup.md.

Migrating from v8

Generated client output is byte-identical to v8.6.0 — consumers have nothing to do. Server-side breaking changes are small and mechanical; see docs/migration-v8-to-v9.md.

License

MIT