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

@inkly/protocol

v0.0.0

Published

Shared foundation for inkly: typed contracts, wire protocol, codecs, and adapter interfaces.

Downloads

32

Readme

@inkly/protocol

The shared foundation for inkly — the typed contract system, the wire protocol, codecs, error types, and the adapter/peer interfaces that the server core, the client, and every runtime adapter build on.

This is a low-level building block. Most apps depend on @inkly/core (server) and @inkly/client (client), which re-export the pieces you need. You only depend on @inkly/protocol directly when writing an adapter or a plugin.

Why it exists

Every layer of inkly needs to agree on three things:

  1. What can be sent — the contract() type system (actions, events, streams).
  2. How it is framed — the inkly.v1 wire protocol (a small discriminated union of frames).
  3. How a connection looks — the Peer / adapter interfaces that normalize Node, Bun, Deno, and Cloudflare sockets into one shape.

Keeping these in a single dependency-free package means the client and server can share exact types with no codegen and no schema duplication, and adapters can be written against a stable surface.

Zero runtime dependencies

The only dependency is @standard-schema/spec, which is types-only (it ships no runtime code). Validators (Zod, Valibot, ArkType, ...) are user-supplied and reached through the standard ~standard interface, so inkly never bundles a validator.

Contract

import { contract } from "@inkly/protocol";
import { z } from "zod";

export const chat = contract({
  actions: {
    sendMessage: {
      input: z.object({ room: z.string(), text: z.string() }),
      output: z.object({ id: z.string(), at: z.number() }),
    },
  },
  events: {
    message: z.object({ id: z.string(), text: z.string() }),
  },
  streams: {
    assistant: { input: z.object({ prompt: z.string() }), yields: z.object({ token: z.string() }) },
  },
});

A contract is a plain object brand-tagged with ~inkly. Both the server and the client import the same chat value and infer their types from typeof chat.

Inference helpers

import type { ActionInput, ActionOutput, EventPayload, StreamYield } from "@inkly/protocol";

type SendInput = ActionInput<typeof chat, "sendMessage">; // { room: string; text: string }
type SendOutput = ActionOutput<typeof chat, "sendMessage">; // { id: string; at: number }
type Msg = EventPayload<typeof chat, "message">; // { id: string; text: string }
type Token = StreamYield<typeof chat, "assistant">; // { token: string }

An action with no output infers void.

Validation

import { validate, ValidationError } from "@inkly/protocol";

const parsed = await validate(chat.actions.sendMessage.input, raw); // throws ValidationError on failure

validate() talks to any Standard Schema validator through ~standard, so the same call works with Zod, Valibot, or ArkType with no adapters.

Wire protocol

SUBPROTOCOL = "inkly.v1". Frames are a discriminated union on t:

| Direction | t values | | --- | --- | | client -> server | hello, rpc, sub, unsub, ping | | server -> client | ready, ack, err, event, chunk, end, pong |

event and chunk frames carry a monotonic seq, and hello may carry a resume request — this is what powers reconnect-and-replay. See docs/overview.md and docs/wire-protocol.md.

Codec

import { jsonCodec } from "@inkly/protocol";

const bytes = jsonCodec.encode({ t: "ping" });
const frame = jsonCodec.decode(bytes);

jsonCodec is the zero-dependency default. The Codec interface lets binary codecs (e.g. MessagePack) plug in without touching the rest of the stack.

Errors

InklyError is the base class; Unauthorized (401), Forbidden (403), NotFound (404), TimeoutError, ResumeExpired, and ValidationError (400) extend it. toWireError(err, expose?) normalizes any thrown value into a serializable WireError, masking non-inkly errors by default so internal details never leak to clients.

License

Dazza Public License 1.0 (LicenseRef-Dazza-1.0).