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

velho

v0.2.5

Published

TypeScript-first Twitch connectivity toolkit

Readme

velho

TypeScript-first toolkit for building Twitch integrations. velho bundles authentication helpers, a typed Helix REST client, Twitch chat (IRC over WebSocket), and EventSub utilities—all guarded by Zod validation and shipped as dual ESM/CJS builds.

Table of Contents

Features

  • OAuth helpers — Complete OAuth 2.0 flows including authorization code exchange, app-access (client credentials), and user token refresh with caching and configurable refresh leeway.
  • Interactive CLI setup — One-command OAuth setup tool (velho-setup) that handles the entire authorization flow and saves credentials automatically.
  • Typed Helix client — Minimal REST wrapper with typed GET/POST helpers, automatic retry on 401, and rate-limit header parsing.
  • Chat (IRC over WebSocket) — Connect, join, listen, speak, respond to PING, and auto-reconnect to Twitch chat.
  • EventSub tooling — Webhook signature verification + transport-agnostic handler, and a WebSocket client that maintains keepalive sessions.
  • Developer experience — Zod validation at the edges, generated .d.ts, exports map, sourcemaps, and tsup bundling for Node 18+.

Examples

Check out the example files to see velho in action:

For a complete getting started guide, see GETTING_STARTED.md.

Requirements

  • Node.js 18.0.0 or newer (for native fetch, Web Streams, and global WebSocket compatibility).
  • A Twitch application with a client ID/secret. For user scopes you will also need a refresh token issued via the OAuth authorization code flow.

Collecting Twitch credentials

Follow these Twitch developer console steps to gather the values velho expects:

  1. Visit the Twitch Developer Console and click Register Your Application (or pick an existing app).
  2. Give the application a name, choose the environment (Website Integration works for most use cases), and add an OAuth redirect URL you control. Save.
  3. Open the app from the list and copy the Client ID. Generate a New Secret to reveal the Client Secret—store it securely; Twitch only shows it once.
  4. Determine your Broadcaster ID (the Helix APIs label it broadcaster_id). Either:
    • Use the Get Users endpoint: curl -H "Client-ID: <client-id>" -H "Authorization: Bearer <token>" "https://api.twitch.tv/helix/users?login=<channel-name>" and copy the id field, or
    • Query it through the Twitch CLI: twitch api get users -q login=<channel-name> or
    • Use some 3rd party tool to query it, I'm not recomending any but simple Google search will give you answers

Keep these values in your .env file:

TWITCH_CLIENT_ID=...
TWITCH_CLIENT_SECRET=...
TWITCH_BROADCASTER_ID=...

TWITCH_BROADCASTER_ID is optional unless you call Helix endpoints that require it (e.g. channel management) or set up EventSub subscriptions tied to a broadcaster.

Installation

npm install velho
# or
pnpm add velho
# or
yarn add velho

Quick Setup with CLI Tool

Get started instantly with the interactive OAuth setup:

# Create .env file with your Twitch credentials
echo "TWITCH_CLIENT_ID=your_client_id" > .env
echo "TWITCH_CLIENT_SECRET=your_client_secret" >> .env

# Run the interactive setup
npx velho-setup

This will:

  1. Start a local server for OAuth callback
  2. Open your browser to authorize the application
  3. Automatically save TWITCH_BOT_REFRESH_TOKEN and TWITCH_BOT_USERNAME to your .env file
  4. Provide you with ready-to-use credentials

Quick Start

import { HelixClient, TwitchAuth } from "velho";

const auth = new TwitchAuth({
  clientId: process.env.TWITCH_CLIENT_ID!,
  clientSecret: process.env.TWITCH_CLIENT_SECRET!
});

const helix = new HelixClient({
  clientId: process.env.TWITCH_CLIENT_ID!,
  auth
});

const { data } = await helix.get("/streams", {
  query: { first: 1 },
});

console.log(data);

Environment Setup

Create a .env file in your project root with your Twitch credentials:

# .env
TWITCH_CLIENT_ID=your_client_id_here
TWITCH_CLIENT_SECRET=your_client_secret_here
TWITCH_BROADCASTER_ID=your_broadcaster_id_here # optional for channel-scoped APIs

Get these credentials from the Twitch Developer Console.

The authentication helper caches tokens per scope set. The Helix client retries once on 401 by invalidating and refreshing the token automatically.

Modules

Authentication

import { TwitchAuth } from "velho";

const auth = new TwitchAuth({
  clientId: "...",
  clientSecret: "...",
  refreshLeewaySeconds: 120,
});

// App access token (client credentials flow)
const appToken = await auth.getAppAccessToken(["channel:read:subscriptions"]);

// User access token from refresh token
const userToken = await auth.getUserAccessToken({ refreshToken: "..." });

// Exchange authorization code for tokens (OAuth 2.0 authorization code flow)
const newUserToken = await auth.exchangeAuthorizationCode({
  code: "authorization_code_from_callback",
  redirectUri: "http://localhost:3000/auth/callback",
});

Key points:

  • Complete OAuth 2.0 support: Authorization code exchange, client credentials, and refresh token flows
  • Tokens are cached per scope (app) or refresh token (user) until they are within the configurable refresh leeway
  • forceRefresh bypasses the cache when you know the token is invalid
  • Provide a custom fetchFn if your runtime does not expose global fetch (e.g., older Node versions or testing environments)

Helix Client

import { HelixClient, HelixRequestError } from "velho";
import { z } from "zod";

const helix = new HelixClient({
  clientId: "...",
  auth,
});

const channelsSchema = z.object({
  data: z.array(
    z.object({
      broadcaster_id: z.string(),
      broadcaster_name: z.string(),
      broadcaster_language: z.string(),
    })
  ),
});

try {
  const response = await helix.get("/channels", {
    query: { broadcaster_id: ["123456"] },
    schema: channelsSchema,
  });

  console.log(response.data.data[0]);
  console.log("Remaining quota:", response.rateLimit.remaining);
} catch (error) {
  if (error instanceof HelixRequestError) {
    console.error(error.status, error.details);
  }
}

Highlights:

  • Methods: get, post, patch, put, delete.
  • schema (optional) enforces response shape with Zod.
  • token overrides the default token strategy per request (app vs user).
  • Rate-limit headers (ratelimit-*) are parsed into HelixRateLimit on every response.

Chat (IRC over WS)

import { TwitchChatClient } from "velho";

const chat = new TwitchChatClient({
  username: "my_bot",
  token: process.env.TWITCH_CHAT_TOKEN!,
  reconnect: true,
  autoJoin: ["#velho"],
});

await chat.connect();

chat.on("ready", () => console.log("Chat ready"));
chat.on("message", (message) => {
  console.log(`[${message.channel}] <${message.username}> ${message.text}`);
});

chat.say("#velho", "Hello Twitch!");

Capabilities:

  • Emits strongly typed events: ready, message, notice, join, part, reconnect, close, raw, error.
  • Handles PING/PONG automatically.
  • listen(listener) convenience subscribes to messages and returns an unsubscribe callback.
  • Minimal reconnect strategy with configurable delay; bring your own exponential backoff if required.

EventSub

Webhook Utilities

import { handleEventSubWebhook } from "velho";
import { z } from "zod";

const cheerSchema = z.object({
  broadcaster_user_id: z.string(),
  is_anonymous: z.boolean(),
  bits: z.number(),
});

export async function twitchWebhookHandler(req, res) {
  const body = await getRawBody(req);
  const result = await handleEventSubWebhook({
    headers: req.headers,
    body,
  }, {
    secret: process.env.TWITCH_EVENTSUB_SECRET!,
    eventSchema: cheerSchema,
    onNotification: async ({ subscription, event }) => {
      console.log(subscription.type, event.bits);
    },
    onRevocation: ({ subscription }) => {
      console.warn("Revoked:", subscription);
    },
  });

  res.writeHead(result.status, result.headers);
  res.end(result.body);
}
  • Agnostic of specific HTTP frameworks—just provide raw headers and body string.
  • Signature verification uses HMAC-SHA256 and constant-time comparison.
  • webhook_callback_verification support returns the challenge automatically.

WebSocket Client

import { EventSubWebSocketClient } from "velho";

const wsClient = new EventSubWebSocketClient({ autoReconnect: true });
await wsClient.connect();

wsClient.on("connected", (session) => {
  console.log("Session ID", session.id);
});

wsClient.on("notification", ({ subscription, event }) => {
  console.log(subscription.type, event);
});
  • Maintains the active session, reconnecting as Twitch instructs.
  • Resets a keepalive timer to detect silent disconnects.
  • Leaves subscription creation to your Helix client; use the maintained session ID in Helix EventSub subscription requests.

Design & DX

  • Type Safety Everywhere — Responses validated with Zod when schemas are provided; library types guard event payloads and tokens.
  • Dual Build Output — Distributed as ESM (dist/index.mjs) and CommonJS (dist/index.cjs) with exports map and .d.ts files.
  • First-Class Node 18+ — Targets modern runtime features; polyfills only required when you target older environments.
  • Extensible Fetch — Inject your own fetch for testing or alternative runtimes (e.g., undici with instrumentation).

Development

# Install dependencies
git clone <repo>
cd velho
npm install

# Type-check
npm run check

# Build (ESM+CJS+d.ts)
npm run build
  • Source resides in src/.
  • Builds output to dist/ via tsup and cleans the folder before each run.
  • No test suite yet; consider combining this package with your integration tests or add mocks in tests/.

Contributing

We welcome contributions! See CONTRIBUTING.md for the full workflow, coding standards, and release process.

License

MIT © 2024 Joni Juntto