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

telemeister

v0.1.21

Published

TypeScript Telegram Bot Boilerplate with XState

Readme

Telemeister

A TypeScript Telegram Bot Framework with Grammy, XState-powered Finite State Machines (FSM), Prisma ORM for persistence, and a type-safe builder pattern for defining conversation flows.

Goal: Build bot infrastructure with explicit structure that allows an LLM to build and verify bots from text descriptions, and detect inconsistencies in those descriptions.

Features

  • NPM Package: Install as a dependency to any bot project
  • Project Scaffolding: npx telemeister create-bot creates new projects
  • Grammy Bot Framework: Modern, TypeScript-first Telegram Bot API library
  • XState FSM: Compact, maintainable state machines using XState's "states as data" pattern
  • Type-Safe State Transitions: Full TypeScript support with strict transition types
  • State Machine Configuration: JSON-based state machine definition (bot.json)
  • Auto-Generated Types: TypeScript types generated from state machine config
  • State Diagram Visualization: Mermaid diagrams (MD + PNG) auto-generated
  • Prisma ORM 7.x: Modern database toolkit with driver adapters for SQLite and MySQL
  • Single Schema: One Prisma schema works for both SQLite (dev) and MySQL (production)
  • Builder Pattern: Fluent API for defining state handlers
  • Dual Mode: Supports both Polling and Webhook modes
  • CLI Tools: Built-in commands for managing states, transitions, and webhooks

Quick Start

Create a New Bot

npx telemeister create-bot my-bot
cd my-bot
npm install

Environment Setup

cp .env.example .env
# Edit .env with your credentials

Required environment variables:

BOT_TOKEN=your_bot_token    # From @BotFather (https://t.me/BotFather)

# Database Configuration
# For SQLite (development):
DATABASE_URL="file:./dev.db"

# For MySQL (production):
# DATABASE_URL="mysql://user:password@localhost:3306/dbname"

Database Setup

Generate Prisma Client:

npm run db:generate

Run Migrations:

# Development (SQLite)
npm run db:migrate

# Production (MySQL) - after updating DATABASE_URL
npm run db:deploy

Run the Bot

Polling mode (development):

npm run dev

Webhook mode (production):

# Set webhook URL first
npm run webhook:set -- https://your-domain.com/webhook

# Start in webhook mode
BOT_MODE=webhook npm run dev

Project Structure

my-bot/
├── bot.json                 # State machine configuration (source of truth, gitignored)
├── src/
│   ├── bot-state-types.ts   # Auto-generated types (DO NOT EDIT)
│   ├── bot-diagram.md       # Auto-generated Mermaid diagram
│   ├── bot-diagram.png      # Auto-generated diagram image
│   ├── handlers/            # Your state handlers
│   │   ├── index.ts        # Handler imports
│   │   ├── idle/           # Idle state handler
│   │   ├── welcome/        # Welcome state handler
│   │   └── menu/           # Menu state handler
│   └── index.ts            # Bot entry point
├── prisma/
│   └── schema.prisma       # Database schema
├── .env                    # Environment variables (gitignored)
└── package.json

State Management

State Machine Configuration

The bot.json file is the source of truth for your state machine:

{
  "idle": ["welcome"],
  "welcome": ["menu"],
  "menu": ["welcome", "idle"]
}

Each key is a state, and the array contains valid transition targets.

CLI Commands

| Command | Description | |---------|-------------| | telemeister state:add <name> | Add a new state + create handler | | telemeister state:delete <name> | Delete a state (with safety checks) | | telemeister state:sync | Sync types + create missing handlers | | telemeister state:transition:add <from> <to> | Add a transition | | telemeister state:transition:delete <from> <to> | Delete a transition |

Or use npm scripts:

npm run state:add -- settings
npm run state:sync

Adding a New State

telemeister state:add collectEmail

This command:

  • Adds "collectEmail": [] to bot.json
  • Creates src/handlers/collectEmail/index.ts with a template
  • Updates src/handlers/index.ts with the import
  • Regenerates src/bot-state-types.ts
  • Regenerates src/bot-diagram.md and src/bot-diagram.png

Adding Transitions

telemeister state:transition:add collectEmail completed

This updates bot.json, regenerates types and diagrams.

Deleting States

Safety checks prevent accidental deletion:

  • Cannot delete if handler folder is non-empty
  • Cannot delete if state has outgoing transitions
  • Cannot delete if state has incoming transitions
# Remove transitions first
telemeister state:transition:delete collectEmail completed

# Then empty the handler folder or move files
rm -rf src/handlers/collectEmail

# Now delete the state
telemeister state:delete collectEmail

Syncing

telemeister state:sync

This regenerates:

  • src/bot-state-types.ts - TypeScript types from bot.json
  • src/bot-diagram.md - Mermaid diagram
  • src/bot-diagram.png - PNG image (requires mermaid-cli)
  • Creates missing handler folders (never overwrites existing)

Auto-Generated Types

The src/bot-state-types.ts file is auto-generated:

// Auto-generated by state:sync - DO NOT EDIT

export type AppStates = 'idle' | 'menu' | 'welcome';

export type StateTransitions = {
  idle: 'welcome' | void;
  menu: 'idle' | 'welcome' | void;
  welcome: 'menu' | void;
};

export type IdleTransitions = Promise<StateTransitions['idle']>;
export type MenuTransitions = Promise<StateTransitions['menu']>;
export type WelcomeTransitions = Promise<StateTransitions['welcome']>;

Handler API

Strict Transition Types

Handlers use generated types for strict return type checking:

import { appBuilder, type AppContext } from 'telemeister/core';
import type { MenuTransitions } from './bot-state-types.js';

appBuilder
  .forState('menu')
  .onEnter(async (context: AppContext): MenuTransitions => {
    await context.send('Welcome to menu!');
    // Can only return 'idle', 'welcome', or void
  })
  .onResponse(async (context: AppContext, response): MenuTransitions => {
    if (response === 'back') return 'welcome'; // ✅ Valid
    if (response === 'exit') return 'idle';    // ✅ Valid
    return 'invalid';  // ❌ Type error - not in transitions
  });

Context Methods

interface BotHandlerContext<TState> {
  // User info
  userId: number;
  telegramId: number;
  chatId: number;
  currentState: TState;

  // Messaging
  send: (text: string) => Promise<unknown>;

  // Data persistence (per-user)
  setData: <T>(key: string, value: T) => void;
  getData: <T>(key: string) => T | undefined;

  // State transition
  transition: (toState: TState) => Promise<void>;
}

Handler Types

// Called when entering a state
.onEnter(async (context) => {
  await context.send("Welcome!");
  // Optionally return a state for immediate transition
  return "anotherState";
})

// Called when user sends a message
.onResponse(async (context, response) => {
  // Return state name to transition, or void/undefined to stay
  if (response === "yes") return "confirmed";
  return "cancelled";
})

State Diagram

Auto-generated visualizations are updated on every state/transition change:

src/bot-diagram.md:

# Bot State Diagram

```mermaid
stateDiagram-v2
    idle --> welcome
    welcome --> menu
    menu --> welcome
    menu --> idle

**`src/bot-diagram.png`:** PNG image rendered by mermaid-cli.

## Database Configuration

### Switching Between SQLite and MySQL

**1. Update `prisma/schema.prisma`:**
```prisma
datasource db {
  provider = "sqlite"  // Change to "mysql" for production
}

2. Update .env:

# SQLite (development)
DATABASE_URL="file:./dev.db"

# MySQL (production)
DATABASE_URL="mysql://user:password@localhost:3306/dbname"

3. Regenerate and migrate:

npm run db:generate
npm run db:migrate

Database Commands

npm run db:generate    # Generate Prisma Client after schema changes
npm run db:migrate     # Create and apply migrations (development)
npm run db:deploy      # Apply migrations in production
npm run db:push        # Push schema changes without migration files
npm run db:studio      # Open Prisma Studio (database GUI)

Webhook Commands

# Set webhook URL
npm run webhook:set -- https://your-domain.com/webhook

# Check webhook info
npm run webhook:info

# Delete webhook (switch back to polling)
npm run webhook:delete

Database Schema

Users are persisted with:

  • telegramId - Telegram user ID
  • chatId - Telegram chat ID
  • currentState - Current FSM state
  • stateData - JSON data storage for user context (in separate userInfo relation)

Prisma Schema

model User {
  id           Int       @id @default(autoincrement())
  telegramId   Int       @unique
  chatId       Int
  currentState String    @default("idle")
  updatedAt    DateTime  @updatedAt
  info         UserInfo?

  @@index([currentState])
}

model UserInfo {
  id        Int    @id @default(autoincrement())
  userId    Int    @unique
  user      User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  stateData String @default("{}")
}

Architecture

State Persistence Flow

User sends message
       ↓
Load user from DB (by telegramId)
       ↓
Execute onResponse for current state
       ↓
Handler returns nextState (or void)
       ↓
Update DB with new state
       ↓
Execute onEnter for new state
       ↓
Send prompt to user

Compact FSM Pattern

Instead of defining every state in XState:

// Traditional - verbose
states: {
  idle: { on: { START: 'welcome' } },
  welcome: { on: { NEXT: 'menu' } },
  // ... every state
}

// Telemeister - compact
states: {
  active: {
    on: {
      TRANSITION: {
        actions: assign({ currentState: ({ event }) => event.toState }),
        target: 'active',
        reenter: true, // Triggers onEnter
      }
    }
  }
}

The actual state value is stored in context.currentState. The bot.json file is the source of truth for valid states and transitions.

Development

Developing the Telemeister Framework

This repository contains the Telemeister framework source code.

# Clone and install
git clone <repo>
cd telemeister
npm install

# Build
npm run build

# Run CLI locally
npm run telemeister:state:add -- settings

# Or use tsx directly
npx tsx src/cli/cli.ts state:add settings

Publishing

npm run build
npm version patch
npm publish

Technology Stack

  • Grammy: Modern Telegram Bot API framework with excellent TypeScript support
  • XState: State machines for complex conversation flows
  • Prisma ORM 7.x: Database toolkit with driver adapters
    • Driver Adapters: Required adapters for database connections (@prisma/adapter-better-sqlite3, @prisma/adapter-mariadb)
    • ESM-Only: Native ES module support
    • Generated Client in Source: Better IDE support and file watching
  • EJS: Template engine for handler generation
  • Mermaid CLI: Diagram generation

License

MIT