Типобезопасные ключи в Storage, onEnter в сценах и новый адаптер SQLite
1 января – 8 февраля 2026
Главное в этом выпуске: @gramio/storage получил мажорное обновление v2.0.0 — ключи теперь привязаны к типам через template literal. Появился новый адаптер SQLite для Bun, а сцены наконец-то обзавелись хуком onEnter(). Помимо этого: в callback query добавили удобный chatId, а UniqueGiftInfo научился работать с TON. Разбираем по порядку.
@gramio/storage v2.0.0 — Типизированные ключи через template literal
BREAKING: Интерфейс Storage теперь завязан на keyof Data
Главное ломающее изменение выпуска. Интерфейс Storage<Data> переработан с нуля — все четыре метода (get, set, has, delete) теперь принимают только ключи из keyof Data, а тип значения выводится автоматически.
Было (v1.x):
interface Storage<Data = any> {
get<T = Data>(key: string): MaybePromise<Data | undefined>;
set(key: string, value: Data): MaybePromise<void>;
// ...
}Стало (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>;
}TypeScript теперь сам подставляет правильный тип значения по ключу — в том числе для template literal паттернов:
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 } | undefinedМиграция: если раньше вы писали storage.get<SomeType>("key") для ручного указания типа — это больше не работает. Вместо этого задайте маппинг ключей и значений через параметр типа при создании хранилища.
Исправлен проброс дженериков в inMemoryStorage
inMemoryStorage<Data>() теперь корректно прокидывает дженерик в интерфейс Storage<Data> — раньше в некоторых случаях получался неявный any.
@gramio/storage-sqlite — Новый адаптер на базе SQLite (Bun)
Свежий адаптер хранилища поверх SQLite! Работает через bun:sqlite с WAL-журналом для максимальной скорости.
import { sqliteStorage } from "@gramio/storage-sqlite";
// Указываем файл — база создастся автоматически
const storage = sqliteStorage({ filename: "bot-data.db" });
// Или передаём готовый инстанс Bun SQLite Database
import { Database } from "bun:sqlite";
const db = new Database("bot-data.db");
const storage = sqliteStorage({ db });Что умеет:
- TTL — параметр
$ttlв секундах, ключи удаляются автоматически - Своё имя таблицы — по умолчанию
"gramio_storage", меняется черезtableName - JSONB — значения хранятся в сериализованном виде как JSONB
- Ленивая очистка — просроченные записи удаляются при старте и проверяются при каждом
get()
WARNING
Пока работает только в Bun. Поддержка Node.js в планах — PR приветствуются!
@gramio/storage-redis — ioredis вынесен в peer-зависимости
ioredis больше не ставится вместе с @gramio/storage-redis. Теперь его нужно устанавливать отдельно:
npm install @gramio/storage-redis ioredisyarn add @gramio/storage-redis ioredispnpm add @gramio/storage-redis ioredisbun add @gramio/storage-redis ioredisТак вы сами контролируете версию ioredis и не получаете дубликаты, если он уже есть в проекте.
@gramio/scenes — Хук onEnter()
Логика при входе в сцену
В сценах появился метод onEnter() — регистрирует обработчик, который срабатывает один раз при входе в сцену. Удобно для приветственного сообщения, подготовки данных или логирования.
import { Scene } from "@gramio/scenes";
const registrationScene = new Scene("registration")
.onEnter((context) => {
return context.send("Добро пожаловать! Давайте начнём регистрацию.");
})
.step("message", (context) => {
if (context.scene.step.firstTime) return context.send("Как вас зовут?");
// ...
});Обработчик поддерживает async и дожидается завершения перед переходом к первому шагу. Контрибьют от @unitoshka.
@gramio/contexts v0.3.1 — chatId в callback query и TON в UniqueGift
chatId прямо из контекста callback query
Больше не нужно лезть в context.message?.chat?.id. Новый геттер chatId даёт доступ напрямую:
bot.on("callback_query", (context) => {
console.log(context.chatId); // number | undefined
});Контрибьют от @n08i40k.
UniqueGiftInfo: поддержка оплаты через TON
Telegram добавил возможность оплаты перепродажи уникальных подарков в TON (тонкоинах). В UniqueGiftInfo появились два новых свойства:
lastResaleCurrency—"XTR"(Telegram Stars) или"TON"(тонкоины)lastResaleAmount— сумма в соответствующей валюте
lastResaleStarCount теперь возвращает значение только при lastResaleCurrency === "XTR", иначе — undefined. Зависимость @gramio/types обновлена до ^9.3.0.
Документация и комьюнити
Раздел по типобезопасным хранилищам
На странице Хранилища появился полноценный раздел про типобезопасную работу с хранилищами через дженерики: базовое использование, template literal типы и составные типы из нескольких Record. На обоих языках.
Вклад сообщества
- Добавлены примеры валидации через sury в документацию
.ask()сцен — рядом с Zod (@unitoshka) - Поправлен вызов
context.scene.next()→context.scene.step.next()в доке по сценам - Исправлены ошибки подсветки синтаксиса в блоках кода (
curl→bash,tree→bash) - Починена битая ссылка в плагине autoload (@ttempaa)
- Переименованы
index.md→overview.mdдля плагинов, хранилищ и TMA — убрали проблемы с роутингом VitePress (@ttempaa) - Добавлен
AGENTS.mdс правилами синхронизации EN/RU документации
Примеры: форум-бот переехал на Cloudflare Workers
gramio-forum-bot полностью мигрировал на Cloudflare Workers: HMAC-верификация вебхуков через Web Crypto API, очистка токена от невидимых символов, обрезка сообщений до лимита 4096 символов и фильтрация chore(deps) коммитов. Добавлена обработка событий issue_comment, pull_request_review и pull_request_review_comment.