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

tgdb-store

v1.1.0

Published

Use a Telegram channel as a database via the Bot API (token + channel)

Downloads

2,053

Readme

TgDb — Telegram DB via Bot API

Store your app data directly in a Telegram channel using a bot.

What this gives you

  • One lightweight storage layer without a separate DB server.
  • Full row payloads live in downloadable JSON documents (tgdb-data-*.json), not duplicated as extra channel messages.
  • Automatic recovery on restart — after connect(), get() returns the same JSON you saved (see examples/restart-recovery.ts).
  • No local files needed.
  • No duplicate uploads — multiple operations in one tick are batched into a single save per table + schema pin.

How data is stored

Files in the Telegram channel

| File | Caption | Pinned | Purpose | |------|---------|--------|---------| | tgdb-schema.json | [TGDB:SCHEMA] | Yes | Master index — table list + file_id of each table snapshot | | tgdb-data-{name}.json | [TGDB:DATA:{name}] | No | Full table snapshot: key → JSON value (v2) |

Ordinary tables do not post per-row text messages. The channel stays mostly documents + optional audit lines (see auditChannelMessages).

tgdb-schema.json (pinned — entry point on restart)

{
  "version": 1,
  "updatedAt": "2026-05-10T14:00:00.000Z",
  "tables": {
    "users": {
      "rows": 2,
      "updatedAt": "2026-05-10T14:00:00.000Z",
      "dataFileId": "BQACAgIA...",
      "dataMessageId": 510
    }
  },
  "mediaDatabases": {}
}

tgdb-data-{name}.json (v2 — canonical)

{
  "version": 2,
  "table": "users",
  "updatedAt": "2026-05-10T14:00:00.000Z",
  "rows": {
    "u_1": { "id": "u_1", "name": "Alice", "balance": 1200 },
    "u_2": { "id": "u_2", "name": "Bob", "balance": 600 }
  }
}

Each table fits in one JSON document (Telegram document limit ~50 MB upload).

Update rule — batched

Every add / edit / delete / upsert:

  1. Updates in-memory state.
  2. Marks the table dirty.
  3. After the current tick, one flush per dirty table:
    • Uploads a fresh tgdb-data-{name}.json.
    • Uploads a fresh tgdb-schema.json and pins it.
    • Deletes the previous data-file message for that table (and the previous schema message).

addMany / deleteMany still produce one data-file upload and one schema upload per batch.

Restart recovery

On connect():

  1. Reads the pinned message → file_id of tgdb-schema.json.
  2. Downloads the schema → table list and dataFileId per table.
  3. Downloads each tgdb-data-{name}.json → restores rows as values.
  4. get(), exists(), add(), edit(), delete() behave normally.

Legacy v1 data files

Older snapshots used version: 1 with rows mapping keys to Telegram message_id (not readable back via Bot API). Those keys are loaded as index-only (null payload); exists() is true but get() throws until you edit/upsert the row again. Prefer a fresh channel or re-import data. After any flush with the current library, files are written as v2.

Optional audit messages

By default no [TGDB:ADD], [TGDB:CREATE], etc. text messages are sent. Set auditChannelMessages: true if you want that noise in the channel.

new TelegramDB({ token, channelId, auditChannelMessages: true });

Install

npm install tgdb-store

Usage example

import { TelegramDB } from "tgdb-store";

type User = { id: string; name: string; balance: number };
type Product = { id: string; title: string; price: number; stock: number };

async function main() {
  const db = new TelegramDB({
    token: process.env.BOT_TOKEN!,
    channelId: Number(process.env.CHANNEL_ID),
    debug: true,
  });

  await db.connect();

  const users = await db.database("users");
  const products = await db.database("products");

  await users.upsert<User>("u_1", { id: "u_1", name: "Alice", balance: 1200 });
  await users.upsert<User>("u_2", { id: "u_2", name: "Bob", balance: 600 });

  await products.upsert<Product>("p_1", { id: "p_1", title: "Keyboard", price: 120, stock: 10 });
  await products.upsert<Product>("p_2", { id: "p_2", title: "Mouse", price: 40, stock: 25 });

  const alice = await users.get<User>("u_1");
  const keyboard = await products.get<Product>("p_1");

  const qty = 2;
  const amount = keyboard.price * qty;
  if (keyboard.stock < qty) throw new Error("Not enough stock");
  if (alice.balance < amount) throw new Error("Insufficient balance");

  await users.edit<User>("u_1", { ...alice, balance: alice.balance - amount });
  await products.edit<Product>("p_1", { ...keyboard, stock: keyboard.stock - qty });

  await users.addMany<User>({
    "u_3": { id: "u_3", name: "Carol", balance: 800 },
    "u_4": { id: "u_4", name: "Dan", balance: 300 },
  });

  console.log(await users.exists("u_1")); // true — also after restart

  await users.delete("u_4");

  // await db.deleteDatabase("users");
}

main().catch(console.error);

Typical channel activity (audit off): only [TGDB:DATA:…] / [TGDB:SCHEMA] document captions as files appear in the chat.

Cross-process recovery example

See examples/restart-recovery.ts. Run after build:

npm run build
BOT_TOKEN=… CHANNEL_ID=… npm run example:restart

Tests

npm test

Integration test recovery second instance runs only when BOT_TOKEN and CHANNEL_ID are set.

Core API

const db = new TelegramDB({ token, channelId, auditChannelMessages?: false });
await db.connect();

const table = await db.database("tableName");

await table.add("key", value);          // add (throws if exists)
await table.get("key");                 // read — works after restart (v2 snapshots)
await table.edit("key", value);         // overwrite (throws if missing)
await table.upsert("key", value);       // add or overwrite
await table.delete("key");              // remove one
await table.exists("key");              // check presence — works after restart
await table.clear();                    // remove all rows
await table.addMany({ k1: v1, k2: v2 });   // batch add — one file upload
await table.deleteMany(["k1", "k2"]);      // batch delete — one file upload
await table.getRecordIds();             // returns all keys as { key: true }

await db.hasDatabase("tableName");      // check if table exists
await db.deleteDatabase("tableName");   // drop table + delete its data file
await db.mediaDatabase("files");        // binary file storage (separate from JSON tables)
await db.collection("orders");        // collection helper

Limits

  • Keys allowed: A-Z, a-z, 0-9, -, _.
  • One JSON document per logical table — practical limit ~50 MB (Telegram upload cap).
  • Values must be JSON-serializable (JSON.stringify).
  • Media files use mediaDatabase() (not the JSON row store).