@yaebal/session
per-chat session state with a pluggable storage adapter.
install
pnpm add @yaebal/sessionusage
session() is a plugin — pass it to .install() on a bot or composer.
it loads ctx.session from storage before your handlers run and writes it back
after next() resolves. the context type is augmented automatically.
import { Bot } from "@yaebal/core";
import { session } from "@yaebal/session";
interface MySession {
count: number;
lastCommand: string | null;
}
const bot = new Bot(process.env.BOT_TOKEN!)
.install(session<MySession>({
initial: () => ({ count: 0, lastCommand: null }),
}));
// ctx.session is now fully typed as MySession
bot.on("message:text", async (ctx) => {
ctx.session.count++;
ctx.session.lastCommand = ctx.text;
await ctx.reply(`message #${ctx.session.count}`);
});
bot.start();custom storage
the default MemoryStorage is lost on restart. swap it for any object that
implements StorageAdapter<T>:
import { session, type StorageAdapter } from "@yaebal/session";
// example: a minimal Redis adapter
class RedisStorage<T> implements StorageAdapter<T> {
constructor(private redis: Redis) {}
async get(key: string): Promise<T | undefined> {
const raw = await this.redis.get(key);
return raw ? JSON.parse(raw) : undefined;
}
async set(key: string, value: T): Promise<void> {
await this.redis.set(key, JSON.stringify(value));
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
}
bot.install(session({
initial: () => ({ count: 0 }),
storage: new RedisStorage(redis),
}));per-user sessions
override getKey to change the partition. defaults to ctx.chat.id.
bot.install(session({
initial: () => ({ preferences: {} }),
// partition by user instead of chat
getKey: (ctx) => ctx.from?.id?.toString(),
}));api
| export | signature | description |
|---|---|---|
session | (options: SessionOptions<S>) => Plugin<Context, { session: S }> | creates the session plugin |
MemoryStorage | class MemoryStorage<T> implements StorageAdapter<T> | default in-memory store; lost on restart |
StorageAdapter | interface | implement this to persist sessions externally |
SessionOptions | interface | options passed to session() |
SessionOptions<S>
| field | type | required | description |
|---|---|---|---|
initial | () => S | yes | factory called when no stored session exists for the key |
storage | StorageAdapter<S> | no | defaults to new MemoryStorage<S>() |
getKey | (ctx: Context) => string | undefined | no | defaults to ctx.chat?.id?.toString() |
StorageAdapter<T>
| method | signature |
|---|---|
get | (key: string) => T | undefined | Promise<T | undefined> |
set | (key: string, value: T) => unknown | Promise<unknown> |
delete | (key: string) => unknown | Promise<unknown> |
writes happen unconditionally after
updates without a chat (e.g.
next(). if a handler throws, next() never resolves, so the write is skipped and storage is left untouched —
the session is not half-saved on error. updates without a chat (e.g.
poll updates) receive a throwaway
session from initial() that is never persisted. getKey returning undefined triggers this path.