Storage Gets Type-Safe Keys, Scenes Learn onEnter & SQLite Joins the Family
January 1 – February 8, 2026
The headline of this cycle: @gramio/storage ships a major v2.0.0 with typed template literal keys, a brand-new SQLite adapter lands for Bun, and scenes get a long-awaited onEnter() hook. Plus: callback queries now expose chatId directly, and UniqueGiftInfo adapts to Telegram's new TON payment fields. Here's everything that shipped.
@gramio/storage v2.0.0 — Typed Keys with Template Literals
BREAKING: Storage interface now uses keyof Data for all keys
This is the biggest change of the cycle. The Storage<Data> interface has been completely reworked — all four methods (get, set, has, delete) now constrain keys to keyof Data and infer value types from the key automatically.
Before (v1.x):
interface Storage<Data = any> {
get<T = Data>(key: string): MaybePromise<Data | undefined>;
set(key: string, value: Data): MaybePromise<void>;
// ...
}After (v2.0.0):
interface Storage<Data = any> {
get<K extends keyof Data>(key: K): MaybePromise<Data[K] | undefined>;
set<K extends keyof Data>(key: K, value: Data[K]): MaybePromise<void>;
has<K extends keyof Data>(key: K): MaybePromise<boolean>;
delete<K extends keyof Data>(key: K): MaybePromise<boolean>;
}This means TypeScript now infers the correct value type based on the key you're accessing — including template literal patterns:
type Data = Record<`user:${number}`, { name: string; age: number }>
& Record<`session:${string}`, { token: string; expires: number }>;
const storage = inMemoryStorage<Data>();
await storage.set("user:1", { name: "Alice", age: 30 });
const user = await storage.get("user:1");
// ^? { name: string; age: number } | undefined
await storage.set("session:abc", { token: "xyz", expires: 123 });
const session = await storage.get("session:abc");
// ^? { token: string; expires: number } | undefinedMigration: If you were using storage.get<SomeType>("key") to override the return type, this pattern no longer works. Instead, define your key-value mapping as a type parameter to the storage constructor.
Bug fix: inMemoryStorage generic types now flow correctly
The inMemoryStorage<Data>() function now properly passes its generic through to the Storage<Data> interface, fixing implicit any issues in previous versions.
@gramio/storage-sqlite — New SQLite Adapter (Bun)
A brand-new storage adapter backed by SQLite has landed! It uses bun:sqlite under the hood with WAL journal mode for maximum performance.
import { sqliteStorage } from "@gramio/storage-sqlite";
// Pass a filename — creates a new database automatically
const storage = sqliteStorage({ filename: "bot-data.db" });
// Or pass an existing Bun SQLite Database instance
import { Database } from "bun:sqlite";
const db = new Database("bot-data.db");
const storage = sqliteStorage({ db });Features:
- TTL support — Set
$ttlin seconds for automatic key expiry - Custom table name — Defaults to
"gramio_storage", configurable viatableName - JSONB storage — Values are JSON-serialized and stored as JSONB
- Lazy expiry — Expired entries are cleaned on startup and checked on each
get()
WARNING
This adapter currently supports Bun only. Node.js support is planned — PRs are welcome!
@gramio/storage-redis — ioredis Moved to Peer Dependencies
ioredis is no longer bundled as a direct dependency of @gramio/storage-redis. You now need to install it separately:
npm install @gramio/storage-redis ioredisyarn add @gramio/storage-redis ioredispnpm add @gramio/storage-redis ioredisbun add @gramio/storage-redis ioredisThis gives you full control over the ioredis version and avoids duplication when you're already using it elsewhere in your project.
@gramio/scenes — onEnter() Hook
Run logic when a user enters a scene
Scenes now support an onEnter() method that lets you register a handler to run once when a scene is entered — perfect for sending a welcome message, initializing state, or logging.
import { Scene } from "@gramio/scenes";
const registrationScene = new Scene("registration")
.onEnter((context) => {
return context.send("Welcome to the registration wizard! Let's get started.");
})
.step("message", (context) => {
if (context.scene.step.firstTime) return context.send("What's your name?");
// ...
});The handler is fully async-compatible and is awaited before the scene proceeds. Community contribution by @unitoshka.
@gramio/contexts v0.3.1 — Callback Query chatId & UniqueGift TON Support
chatId on callback query context
You no longer need to dig through context.message?.chat?.id to get the chat ID in a callback query handler. The new chatId getter gives you direct access:
bot.on("callback_query", (context) => {
console.log(context.chatId); // number | undefined
});Community contribution by @n08i40k.
UniqueGiftInfo adapts to Telegram's new resale fields
Telegram updated the Bot API to support TON (toncoin) payments for unique gift resales. UniqueGiftInfo has been updated with two new properties:
lastResaleCurrency—"XTR"(Telegram Stars) or"TON"(toncoins)lastResaleAmount— The raw payment amount in the respective currency
The existing lastResaleStarCount now returns the amount only when lastResaleCurrency === "XTR", and undefined otherwise. The @gramio/types dependency was bumped to ^9.3.0 to match.
Documentation & Community
Type-safe storage docs with generics
A whole new section was added to the Storages page covering type-safe storage with generics — basic usage, template literal types, and complex type unions. Both EN and RU versions.
Community contributions
- Sury validation examples added to scenes
.ask()docs alongside existing Zod examples (@unitoshka) - Fixed
context.scene.next()→context.scene.step.next()in scenes docs - Fixed undefined language errors in code blocks (
curl→bash,tree→bash) - Fixed broken autoload plugin link pointing to wrong URL (@ttempaa)
- Renamed
index.md→overview.mdfor plugins, storages, and TMA to fix VitePress routing (@ttempaa) - Added
AGENTS.mdwith bilingual documentation sync rules
Examples: Forum bot migrates to Cloudflare Workers
The gramio-forum-bot example was fully migrated to Cloudflare Workers with proper HMAC webhook verification via Web Crypto API, token sanitization, message truncation (4096 char limit), and noise filtering for chore(deps) commits. New webhook handlers were added for issue_comment, pull_request_review, and pull_request_review_comment events.