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

@laburen/delta-sdk

v1.3.0

Published

Instrumentación para los workers de Laburen en Cloudflare, sin dependencias de runtime. Puerta /telemetry: errores y eventos a la ingesta

Readme

@laburen/delta-sdk

Sirve para cualquier worker, MCP o workflow de Cloudflare.

El paquete de Laburen para instrumentar workers de Cloudflare, sin dependencias de runtime. Está organizado en puertas: cada una es un import distinto para un propósito distinto, todas conviviendo en el mismo paquete.

Puertas

| Puerta | Para qué | |---|---| | @laburen/delta-sdk | la raíz: solo los tipos compartidos entre puertas (Envelope, Notify, Severity, Surface, TelemetryEvent) — sin runtime | | @laburen/delta-sdk/telemetry | le manda los errores y eventos de tu worker a la ingesta de telemetría (telemetry.laburen.com), que los guarda en D1 y avisa por Slack al canal del cliente |

Instalar el paquete, la API key y el ctx son iguales para cualquier puerta: ver docs/empezar.md.

telemetry

Le manda los errores de tu worker a la ingesta de telemetría (telemetry.laburen.com), que los guarda en D1 y avisa por Slack al canal del cliente.

Lo que tenés que saber antes de empezar

  • captureException nunca lanza y no se puede await. Un cliente de telemetría no puede romper la app que observa.
  • No hay reintentos. Si la ingesta está caída, el evento se descarta y se cuenta.
  • Los wrappers solo ven lo que ESCAPA de la función que envuelven. Si tu catch loguea y devuelve un valor, el wrapper no se entera. Ver docs/telemetry/guia.md.

Caso 1 — MCP en un Durable Object (el 80% del uso)

import { createTelemetry } from "@laburen/delta-sdk/telemetry";

export class MiAgenteMCP extends McpAgent<Env> {
  server = new McpServer({ name: "mi-mcp", version: "1.0.0" });

  async init() {
    const telemetry = createTelemetry({
      service: { name: "mi-mcp", version: "1.0.0", environment: this.env.ENVIRONMENT },
      surface: "durable_object",
      organizationId: this.env.MI_ORGANIZATION_ID,
      agentId: this.env.MI_AGENT_ID,
      apiKey: this.env.LABUREN_TELEMETRY_KEY,
      notify: { slack: { enabled: true, channel: this.env.MI_SLACK_CHANNEL_ID } },
      ctx: this.ctx,
    });

    // La tool que NO atrapa su error: la envolvés y listo.
    this.server.registerTool("MiTool", schema,
      telemetry.withTool("MiTool", async (args) => {
        const token = await renovarToken();   // si lanza: se captura Y se re-lanza
        return await hacerAlgo(args, token);
      }),
    );

    // La tool que SÍ atrapa su error: sumás una línea adentro del catch que ya tenés.
    this.server.registerTool("OtraTool", schema, async (args) => {
      try {
        return await hacerOtraCosa(args);
      } catch (error) {
        telemetry.captureException(error, { workUnit: "OtraTool" });   // ← lo único nuevo
        return { content: [{ type: "text", text: `Ocurrió un error: ${error.message}` }], isError: true };
      }
    });
  }
}

createTelemetry va DENTRO de init(), nunca a nivel de módulo. Un valor aleatorio generado en el scope global hace fallar el deploy con "Disallowed operation called within global scope", y wrangler dev local no lo detecta.

Y no envuelvas el export default con withTelemetry en un MCP. Aunque el worker tenga un fetch que rutea hacia el Durable Object, el framework de MCP atrapa el error de la tool y lo convierte en un resultado con isError: true antes de que llegue ahí: envolver el export default no reportaría nada. Acá va createTelemetry + withTool, y nada más. Ver docs/telemetry/guia.md.

Caso 2 — Worker plano, cola y cron

import { withTelemetry } from "@laburen/delta-sdk/telemetry";

export default withTelemetry(
  (env: Env) => ({
    service: { name: "mi-worker", version: "1.0.0" },
    organizationId: env.MI_ORGANIZATION_ID,
    agentId: env.MI_AGENT_ID,
    apiKey: env.LABUREN_TELEMETRY_KEY,
    notify: { slack: { enabled: true, channel: env.MI_SLACK_CHANNEL_ID } },
    queueMaxRetries: 3,                    // el MISMO número del wrangler.jsonc, sin sumarle 1
  }),
  {
    async fetch(req, env, ctx, telemetry) { /* ... */ },   // → surface "worker"
    async queue(batch, env, ctx)          { /* ... */ },   // → surface "queue"
    async scheduled(ctrl, env, ctx)       { /* ... */ },   // → surface "cron"
  },
);

La config no lleva surface: se deduce del nombre del handler, y pasarla no compila.

El cuarto argumento (telemetry) es el mismo cliente que usa el wrapper. Un handler que declara solo tres lo ignora y sigue andando igual; uno que lo declara puede usarlo en su propio catch sin llamar a createTelemetry de nuevo. Ver docs/telemetry/guia.md.

Los nombres de las env var no están estandarizados entre proyectos. Abrí tu env.d.ts antes de copiar los nombres: no asumas que son los mismos que en otro worker.

Caso 3 — Un error que nunca fue una excepción

Es el caso más común en los repos que ya existen: if (!res.ok) { log; return null }. No hay nada que pasarle a captureException, así que va captureEvent.

if (!res.ok) {
  telemetry.captureEvent("mi-integracion: crearTicket devolvió no-2xx", {
    workUnit: "crear-ticket",
    attributes: { status: res.status, ticket_id: ticketId },   // ← los ids van ACÁ
  });
  return null;
}

El message va sin interpolar ids. Si el mensaje lleva ticket:157249, cada ocurrencia genera un grupo nuevo y el dedup de la ingesta no sirve.

Documentación

| Archivo | Para qué | |---|---| | docs/empezar.md | instalar, la API key, el ctx, y qué puerta usar — transversal a cualquier puerta | | docs/superficies.md | qué cambia entre worker, durable object, workflow, queue, alarm y cron — transversal a cualquier puerta | | docs/troubleshooting.md | "no llega nada", error 1042, 401, "el worker no deploya" — síntomas compartidos por cualquier puerta | | docs/telemetry/guia.md | la guía larga de /telemetry: config campo por campo, CaptureOptions, scrubbing, beforeSend | | docs/telemetry/que-se-manda.md | el contrato de /telemetry en prosa, con un payload de ejemplo | | docs/telemetry/troubleshooting.md | síntomas propios de /telemetry: 413, los límites del scrubbing |

El TypeScript real está en node_modules/@laburen/delta-sdk/src/: si algo no cierra, se lee.

Versionado

El paquete es additive-only: se agregan funciones y campos opcionales, nunca se renombra ni se saca nada. Un worker que se queda en 1.2.0 tres años sigue funcionando. En la práctica el paquete vive en 1.x y los bumps son minor y patch. El CHANGELOG dice, en cada entrada, qué cambió y si hay que hacer algo.