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

@conduito/core

v0.1.1

Published

The Conduito protocol — envelope, message validation, id correlation, and a bidirectional RPC + pub/sub Endpoint over an abstract transport. Zero-dependency, transport-agnostic.

Readme

@conduito/core

O protocolo do Conduito: o envelope, a validação de mensagem, a correlação por id, e um Endpoint — um par RPC bidirecional + pub/sub — sobre um Transport abstrato. Zero-dependency e transport-agnóstico: não conhece iframe, window, nem DOM.

Filosofia, em uma frase: separe o contrato do carrier. O que uma mensagem significa (o protocolo) não deve depender de por onde ela trafega (postMessage, worker, rede). O core é o contrato; um Transport (ex.: @conduito/frame) é o carrier.

Por que assim

  • Simétrico. Diferente dos wrappers de postMessage (parent chama child, child só emite), aqui os dois lados criam um Endpoint e ambos podem request/notify/emit e handle/on. Host e app são pares.
  • Provado + corrigido. Os algoritmos vêm do postmate (testado em produção): o envelope versionado, o guard sanitize, a correlação por id monotônico. Os gaps dele foram fechados: timeout, erro tipado, cancelamento, capabilities namespaçadas (ns), e RPC nos dois sentidos.
  • Sem DOM por construção. O core compila com types: ["node"] e sem lib DOM — então nem consegue tocar window/document. A fronteira core↔transporte é garantida pelo compilador.

Instalação

pnpm add @conduito/core

Sozinho ele não fala com ninguém — pareie com um Transport (@conduito/frame pra iframe, @conduito/port pra worker) ou escreva o seu.

API

O envelope

import { PROTO, OP, type Message } from "@conduito/core";
// PROTO = "conduito/1"  — discrimina + versiona o wire
// OP    = { HANDSHAKE, HELLO, REQUEST, REPLY, ERROR, NOTIFY, EVENT, DISCOVER, CANCEL }

Toda mensagem é { proto: "conduito/1", op, … }. request/reply/error/cancel carregam id; request/notify/event carregam ns (a capability) + method/event.

Validação

import { isMessage, sanitize } from "@conduito/core";

isMessage(x); // é um envelope conduito bem-formado?
sanitize(event, allowedOrigin); // origin-pin opcional + isMessage; refina event.data → Message

Endpoint

O coração. Dado um Transport, devolve o par RPC:

import { createEndpoint } from "@conduito/core";

const peer = createEndpoint(transport, { defaultTimeout: 30_000 });

// chamar o outro lado e aguardar (rejeita em erro do peer, timeout ou abort)
const sum = await peer.request<number>("math", "add", { a: 2, b: 3 });
const big = await peer.request("report", "build", q, {
  signal: controller.signal,
});

// fire-and-forget (sem resposta)
peer.notify("log", "write", "hello");

// responder chamadas do outro lado — o handler recebe (payload, { signal })
const off = peer.handle(
  "math",
  "add",
  ({ a, b }: { a: number; b: number }) => a + b,
);
peer.handle("report", "build", async (q: Query, { signal }) =>
  render(q, { signal }),
);

// pub/sub (nos dois sentidos)
const unsub = peer.on("tokens", "changed", (payload) => render(payload));
peer.emit("tokens", "changed", { id: 1 });

peer.reset(); // o par se foi: rejeita pendências (EDISCONNECTED), mantém handlers/listeners/capabilities
peer.dispose(); // encerra: para de ouvir + rejeita pendências (EDISPOSED)

| método | direção | semântica | | -------------------------------------- | -------- | --------------------------------------------------------- | | request(ns, method, payload?, opts?) | → peer → | RPC com resposta (Promise; rejeita em erro/timeout/abort) | | notify(ns, method, payload?) | → peer | fire-and-forget | | handle(ns, method, handler) | ← peer | registra o handler (retorna unregister) | | on(ns, event, listener) | ← peer | assina um evento (retorna unsubscribe) | | emit(ns, event, payload?) | → peer | publica um evento | | reset() | — | o par se foi: rejeita pendências, mantém o registro | | dispose() | — | encerra + rejeita pendências |

opts de request/discover: { timeout?, signal? }. O timeout (default defaultTimeout, 0 desliga) rejeita com ETIMEOUT; o signal (AbortSignal) rejeita com EABORTED. Nos dois casos o endpoint manda um cancel pro peer, e o signal que o handler recebeu dispara — trabalho longo deve observá-lo. O resultado de um handler cancelado não é enviado.

Erros são sempre um ConduitoError com .code estável (nada de string pra dar regex): ENOHANDLER, EHANDLER, ETIMEOUT, EABORTED, EDISCONNECTED, EDISPOSED, ESEND (o send do request falhou — original em .cause), EREMOTE, ERESERVED. Um handler pode lançar um ConduitoError com code próprio — ele cruza o wire. Um Transport também: se o send lança um ConduitoError, o request rejeita com esse code (é como o EQUEUEFULL dos transportes chega). isConduitoError(e) faz o narrow.

Nada é engolido em silêncio. Falhas sem canal de volta — um handler de notify que lança, um transport.send que falha (peer sumiu), um cancel que não pôde ser enviado — vão pro hook opcional onError:

createEndpoint(transport, {
  onError: (error, { op, ns, method }) =>
    log.warn("conduito", op, ns, method, error),
});

Capabilities + discover

Uma capability é um domínio que o app expõe (tokens, routes, actions…): métodos chamáveis + eventos. O app declara com expose; o peer descobre com discover — o "db pull".

import { defineCapability } from "@conduito/core";

// app: declara (registra no manifest E fia os handlers de uma vez)
export const tokensCap = defineCapability({
  name: "tokens",
  version: "1.0.0",
  methods: {
    get: (path: string) => store.get(path),
    set: (t: Token) => store.set(t),
  },
  events: ["changed"],
  meta: { title: "Design tokens" },
});
app.expose(tokensCap);

// host: descobre a superfície, depois chama
const { capabilities } = await host.discover();
// → [{ name: "tokens", version: "1.0.0", methods: ["get","set"], events: ["changed"], meta: {…} }]
const primary = await host.request("tokens", "get", "primary");

expose é simétrico (host e app podem expor) e retorna um unexpose (remove do manifest + desfia os handlers). O discover é auto-respondido pelo endpoint a partir do registro — você não escreve o handler dele. O manifest é a base pra um host renderizar UI genérica de gestão, e pra uma IA saber o que pode chamar.

O manifest é vivo. Cada expose/unexpose emite o evento conduito:capabilities com o manifest novo — um host conectado acompanha sem repetir o discover:

host.on(META_NS, "capabilities", ({ capabilities }) =>
  renderSidebar(capabilities),
);

O namespace conduito (META_NS) é reservado — expose com esse nome lança ERESERVED.

Cliente tipado

defineCapability guarda a forma literal da declaração (assinaturas dos métodos, nomes dos eventos). O host pega só o tipo e ganha um cliente tipado — sem schema em runtime, sem string digitada à mão:

import { createClient } from "@conduito/core";
import type { tokensCap } from "./app-capabilities"; // type-only: os handlers nunca saem do app

const tokens = createClient<typeof tokensCap>(host, "tokens"); // o nome é checado contra a declaração

const primary = await tokens.request.get("primary"); // Promise<Token> — payload e retorno vêm do handler
tokens.notify.set({ name: "primary", value: "#000" }); // fire-and-forget, mesma assinatura
const off = tokens.on.changed((payload) => refresh(payload)); // só os eventos declarados existem

request.x(payload?, opts?) aceita as mesmas RequestOptions (timeout, signal). Um handler declarado sem parâmetro vira um método sem argumento; um (p) => … sem tipo vira unknown, como antes.

Transport (escrever o seu)

import type { Transport } from "@conduito/core";

const transport: Transport = {
  send(message) {
    /* entregar o envelope ao peer; pode lançar um ConduitoError (o code chega ao caller) */
  },
  subscribe(handler) {
    /* entregar envelopes VALIDADOS (via sanitize) ao endpoint; retorna unsubscribe */
    return () => {};
  },
};

O transporte é responsável por entrega + validação de origem — ele passa Messages já limpos ao Endpoint e nunca vaza o carrier. Ver @conduito/frame pra o transporte iframe.

Internals: docs/ARCHITECTURE.md (no repositório).