Skip to content

Типобезопасные ключи в 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):

ts
interface Storage<Data = any> {
    get<T = Data>(key: string): MaybePromise<Data | undefined>;
    set(key: string, value: Data): MaybePromise<void>;
    // ...
}

Стало (v2.0.0):

ts
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 паттернов:

ts
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-журналом для максимальной скорости.

ts
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. Теперь его нужно устанавливать отдельно:

sh
npm install @gramio/storage-redis ioredis
sh
yarn add @gramio/storage-redis ioredis
sh
pnpm add @gramio/storage-redis ioredis
sh
bun add @gramio/storage-redis ioredis

Так вы сами контролируете версию ioredis и не получаете дубликаты, если он уже есть в проекте.

@gramio/scenes — Хук onEnter()

Логика при входе в сцену

В сценах появился метод onEnter() — регистрирует обработчик, который срабатывает один раз при входе в сцену. Удобно для приветственного сообщения, подготовки данных или логирования.

ts
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 даёт доступ напрямую:

ts
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() в доке по сценам
  • Исправлены ошибки подсветки синтаксиса в блоках кода (curlbash, treebash)
  • Починена битая ссылка в плагине autoload (@ttempaa)
  • Переименованы index.mdoverview.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.