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

@gramforge/transport-fetch

v0.1.0

Published

A zero-dependency raw Telegram Bot API adapter over the **global `fetch`** (no `node:*` on the hot path — JSR-publishable). It consumes `@gramforge/core` message values and `@gramforge/render`'s `toEntities` output, sends them over `fetch`, and returns a

Downloads

33

Readme

@gramforge/transport-fetch

A zero-dependency raw Telegram Bot API adapter over the global fetch (no node:* on the hot path — JSR-publishable). It consumes @gramforge/core message values and @gramforge/render's toEntities output, sends them over fetch, and returns a typed DeliveryResult<T> for every outcome instead of throwing.

  • Methods: sendMessage, editMessageText, sendMediaGroup, answerCallbackQuery, answerWebAppQuery.
  • Every wire response — success and error — is zod safeParsed before it reaches you; res.json() is never trusted blindly.
  • parse_mode is always NONE: formatting travels as entities, produced by render's toEntities, never as HTML/MarkdownV2.
  • 429s are typed and inert: rateLimited carries a branded retryAfterSeconds; nothing here ever sleeps or auto-retries.
  • LiveMessage: a create-once / edit-many live-progress wrapper with debounce + last-write-wins coalescing and typed swallowing of Telegram's "message is not modified" reply.

Install

pnpm add @gramforge/transport-fetch

DeliveryResult<T>

Every method returns Promise<DeliveryResult<T>>. Match it exhaustively:

import { match } from "ts-pattern";
import { createBotClient, type DeliveryResult } from "@gramforge/transport-fetch";

function report<T>(r: DeliveryResult<T>): string {
  return match(r)
    .with({ kind: "ok" }, () => "sent")
    .with({ kind: "badRequest" }, (e) => `bad request: ${e.description}`)
    .with({ kind: "unauthorized" }, (e) => `unauthorized: ${e.description}`)
    .with({ kind: "rateLimited" }, (e) => `retry after ${e.retryAfterSeconds}s`)
    .with({ kind: "serverError" }, (e) => `server ${e.status}: ${e.description}`)
    .with({ kind: "networkError" }, (e) => `network: ${e.message}`)
    .exhaustive();
}

| kind | HTTP | payload | | -------------- | -------- | ------------------------------ | | ok | 200 | value: T | | badRequest | 400 (+4xx) | description | | unauthorized | 401 | description | | rateLimited | 429 | retryAfterSeconds: RetryAfter| | serverError | 5xx / bad body | status, description | | networkError | fetch rejected / non-JSON | message |

Sending a message

import { createMessageText, text, bold, toChatId } from "@gramforge/core";
import { createBotClient } from "@gramforge/transport-fetch";

const client = createBotClient({ token: process.env.BOT_TOKEN! });
// `fetch` and `baseUrl` are injectable: { token, fetch?, baseUrl? }.

const chat = toChatId(123456);
const body = createMessageText([text("Build "), bold([text("passed")])]);
if (chat.kind === "ok" && body.kind === "ok") {
  const result = await client.sendMessage({ chatId: chat.value, message: body.value });
  // result: DeliveryResult<TelegramMessage>
}

editMessageText takes an EditTarget DU — { kind: "chatMessage", chatId, messageId } or { kind: "inlineMessage", inlineMessageId }. An inline edit returns the wire literal true, surfaced as { kind: "inlineEdited" }.

Live progress (LiveMessage)

update() is synchronous and cheap; bursts collapse to at most one edit per minEditIntervalMs (last-write-wins). A 429 is propagated to the caller and used as a floor for the next flush — never retried silently. Call finish() from a finally to write a final summary immediately.

const live = client.liveMessage({ chatId: chat.value, minEditIntervalMs: 1500 });
try {
  for (const step of steps) {
    live.update(`… ${step.label}`); // coalesced, never blocks
    await step.run();
  }
} finally {
  await live.finish("Done ✅");
}

The coalescing state machine is exported as createLiveMessage(port, options) over a LiveMessagePort seam, so @gramforge/transport-grammy reuses the identical behavior with a grammY-backed port.

Notes

  • Runtime deps: zod and ts-pattern only; uses fetch / AbortController — no node:http.
  • sendMediaGroup accepts a compile-time 2-to-10-item album tuple; albumSizeError is the runtime backstop for cast/JSON-decoded values.
  • answerWebAppQuery currently ships the article inline-result subset (a rendered MessageText); other InlineQueryResult variants are added non-breakingly when the mini-app lands.