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

capnweb-validate

v0.2.2

Published

Build-time TypeScript validation transform for capnweb RPC.

Readme

capnweb-validate

Build-time runtime validation for Cap'n Web and Workers RPC services.

capnweb-validate keeps TypeScript method signatures as the source of truth. Add @validateRpc() to the service class; a bundler plugin or CLI rewrites the decorator and injects validators generated from the resolved TypeScript types.

If a validation decorator is left untransformed, it throws with a configuration error instead of silently running without validation.

Install

npm install capnweb capnweb-validate

Workers RPC users can install capnweb-validate without installing capnweb. The root package has no runtime dependency on capnweb; Cap'n Web-specific helpers live under capnweb-validate/capnweb and internal transform outputs.

Server Usage

import { newWorkersRpcResponse, RpcTarget } from "capnweb";
import { validateRpc } from "capnweb-validate";

type User = { id: string; name: string };

@validateRpc()
export class Api extends RpcTarget {
  async authenticate(sessionToken: string): Promise<User> {
    // ...
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return newWorkersRpcResponse(request, new Api());
  },
};

@validateRpc() validates calls on class instances, so it works with Cap'n Web, Workers WorkerEntrypoint, and Workers DurableObject services.

With no explicit type argument, the RPC surface is the class's public string-named methods and RPC-readable getters/properties, matching Cap'n Web dispatch. implements SomeInterface can sharpen matching signatures, but it does not hide extra public class methods. Keep local-only helpers private or symbol-named.

An explicit @validateRpc<SomeInterface>() makes SomeInterface the RPC surface. Public class methods outside that interface are rejected over RPC.

Generic service classes

A decorator emits one validator at the class declaration. If the class itself is generic, the transform cannot specialize that validator for each later new expression.

Use an explicit RPC surface when type arguments are known at the decorator site:

@validateRpc<Gatekeeper<GmailSession, number, undefined>>()
class GmailGatekeeper
  extends RpcTarget
  implements Gatekeeper<GmailSession, number, undefined> {
  // ...
}

A generic implementation class needs no annotation. An unconstrained type parameter defaults to any with a warning; a constrained parameter validates against its constraint:

@validateRpc()
class ArrayCursor<T> extends RpcTarget implements Cursor<T> {
  // `T` defaults to `any` (warned). `<T extends Session>` would validate
  // those positions against `Session`.
}

Pass @validateRpc<Cursor<string>>() to validate the Cursor<string> surface, or @validateRpc<Cursor<any>>() to silence the warning while keeping Cursor positions permissive.

Client Usage

Client-side stub validation is explicit. Wrap a Cap'n Web client stub with validateStub<T>() when the caller wants return values and pipelined calls checked against a concrete surface:

import { newHttpBatchRpcSession } from "capnweb";
import { validateStub } from "capnweb-validate";

import type { Api } from "./worker";

export const api = validateStub<Api>(newHttpBatchRpcSession<Api>("/rpc"));

The same helper can wrap Workers RPC service stubs:

import { validateStub } from "capnweb-validate";

import type { Api } from "./worker";

export default {
  async fetch(_request: Request, env: { SERVICE: unknown }) {
    const api = validateStub<Api>(env.SERVICE as object);
    return Response.json(await api.status());
  },
};

validateStub<T>() validates resolved return values on the caller side. It does not validate outgoing arguments; the receiver validates those on arrival. Normal Cap'n Web client constructors are not rewritten automatically.

Bundler Plugins

Use the adapter that matches your bundler:

import capnwebValidate from "capnweb-validate/vite";     // or
import capnwebValidate from "capnweb-validate/rollup";    // or
import capnwebValidate from "capnweb-validate/webpack";   // or
import capnwebValidate from "capnweb-validate/rspack";    // or
import capnwebValidate from "capnweb-validate/esbuild";   // or
import capnwebValidate from "capnweb-validate/farm";

export default {
  plugins: [capnwebValidate()],
};

The plugin transforms matching modules in memory; user source files are not modified on disk.

CLI

Wrangler does not expose a bundler plugin hook. For Wrangler, CI, or any flow that needs transformed files on disk, run:

capnweb-validate build --out .capnweb-validate

Options:

  • --out <dir> writes the transformed source tree. Required.
  • --tsconfig <path> defaults to ./tsconfig.json.
  • --cwd <dir> defaults to process.cwd().

Point the downstream build tool at the generated entry under --out.

Opting out per method

Use @skipRpcValidation() when one RPC method should not get generated argument or return validators:

import { RpcTarget } from "capnweb";
import { skipRpcValidation, validateRpc } from "capnweb-validate";

@validateRpc()
class Api extends RpcTarget {
  @skipRpcValidation()
  unsafe(payload: unknown): unknown {
    return payload;
  }
}

The method still goes through capnweb normally. This only disables capnweb-validate validation for that method.

Validation Errors

Validation failures throw TypeError, so validation errors keep their standard error type when they cross RPC boundaries. The message includes the failing path, expected type, and actual type:

try {
  await api.authenticate(123 as never);
} catch (err) {
  if (err instanceof TypeError) {
    console.log(err.message);
  }
}

Where errors surface depends on which boundary failed:

| Boundary | Failure | How it surfaces | | -------- | ------- | --------------- | | Client stub | Bad resolved return | The returned promise rejects. | | Server target | Bad incoming argument | The server throws and the caller observes an RPC rejection. |

Current Type Coverage

The supported set matches capnweb's published wire format. Every type capnweb guarantees can travel over RPC also has a precise build-time validator:

Pass-by-value:

  • Primitives: string, number, boolean, null, undefined, bigint, any, unknown, plus string / number / boolean literal types. any and unknown use a permissive validator.
  • Containers: arrays, tuples (validated by exact length and per-position element type), Map<K, V> / ReadonlyMap<K, V>, Set<T> / ReadonlySet<T>, plain object shapes, unions, Record<K, T> and index signatures (string and numeric keys both validate their value type, since object keys cross the wire as strings), and Promise<T> return values (unwrapped one level). Optional properties (foo?: T) widen to T | undefined, matching how the wire deserializes a missing key.
  • Built-ins from the RPC-compatible runtime set: Date, ArrayBuffer, DataView, RegExp, Uint8Array, other typed arrays, Error and its standard subclasses (EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError), Blob, ReadableStream, WritableStream, Headers, Request, Response.

Some host objects are checked by exact prototype to match the transport behavior; Error, ArrayBuffer, RegExp, DataView, and typed arrays use instanceof. File is rejected at build time because it is a common Blob subclass that does not match the supported Blob validator.

Pass-by-reference:

  • Plain functions (validated as typeof === "function").
  • RpcStub<T> and RpcPromise<T> by symbol name, and any user-defined class that extends RpcTarget (the resolver walks getBaseTypes).
  • Workers RPC Fetcher<T> values and branded RpcTarget / WorkerEntrypoint capabilities. These are validated as pass-through stubs; lifecycle methods such as fetch, queue, and tail remain pass-through.

Rejected types: these cannot be validated as supported RPC values, and the transform refuses to compile a service that uses them so the user finds out at build time, not at the first RPC call:

| Type | Build error hint | | ------------------ | ---------------------------------------------------------- | | WeakMap | WeakMap is not a supported RPC validation type. | | WeakSet | WeakSet is not a supported RPC validation type. | | SharedArrayBuffer| SharedArrayBuffer is not a supported RPC validation type.| | File | Use a Blob or Uint8Array; File is not supported. |

If a method signature contains a leaf the resolver cannot lower, such as a generic type parameter with no inference source, an unsupported recursive corner, or a rejected built-in nested inside an object, the transform fails at the call site with a class-qualified list of every offending field. You fix them in one pass rather than rebuilding once per field.

Recursive object and union shapes are emitted with lazy back-references. The resolver also has a guardrail for pathological non-recursive nesting. If the lowered type graph exceeds the internal resolution depth limit, it fails with type exceeds maximum resolution depth (64).

An overloaded method exposes several call signatures; validating against one would reject valid calls to the others. Overloaded methods are passed through unvalidated with a warning. Collapse the overloads into a single signature with union parameters to validate the method, or @skipRpcValidation() to silence the warning.

License

MIT.