Skip to content

Sessions ​

The session() middleware adds ctx.session to every update with a resolvable chat/user key. It is designed for small per-user state such as preferences, wizard progress, or temporary form data.

Quick Start ​

ts
import { Bot, session } from 'vibegram';

const bot = new Bot(process.env.BOT_TOKEN!);

bot.use(session());

bot.command('count', async ctx => {
    ctx.session.count = (ctx.session.count ?? 0) + 1;
    await ctx.reply(`Count: ${ctx.session.count}`);
});

Session state is loaded before downstream middleware runs and saved after next() completes.

Typed Sessions ​

ts
type MySession = {
    count: number;
    locale?: string;
};

bot.use(
    session<MySession>({
        initial: () => ({ count: 0 }),
    })
);

bot.command('lang', ctx => {
    ctx.session.locale = 'id';
});

Use initial() to avoid defensive checks in every handler.

Session Options ​

OptionTypeDefaultDescription
storeSessionStorenew MemorySessionStore()Storage adapter for session data.
getSessionKey(ctx) => string | undefined${chatId}:${fromId}Generates the storage key.
initial() => S{}Creates a new session when no record exists.

If getSessionKey() returns undefined, the update continues without ctx.session persistence.

Concurrency Safety ​

The middleware serializes load-handler-save cycles per session key. Two updates for the same user do not save over each other from the same starting state. Different session keys can still run concurrently.

MemorySessionStore ​

ts
import { MemorySessionStore, session } from 'vibegram';

bot.use(
    session({
        store: new MemorySessionStore(
            24 * 60 * 60 * 1000, // ttlMs
            10_000, // maxEntries
            60_000 // cleanupIntervalMs
        ),
    })
);

The built-in store is volatile. It is useful for development, single-process bots, and short-lived state. It expires records by TTL and evicts the least-recently-used entry when maxEntries is reached.

Redis Adapter ​

Use an external store for production bots that run on multiple workers or need state to survive restarts.

ts
const redisStore = {
    async get(key: string) {
        const value = await redis.get(key);
        return value ? JSON.parse(value) : undefined;
    },
    async set(key: string, value: unknown) {
        await redis.set(key, JSON.stringify(value), { EX: 60 * 60 * 24 });
    },
    async delete(key: string) {
        await redis.del(key);
    },
};

bot.use(session({ store: redisStore }));

Clearing Sessions ​

Assign null or undefined to ctx.session to delete the stored session after the handler finishes.

ts
bot.command('reset', async ctx => {
    ctx.session = null;
    await ctx.reply('Session cleared.');
});

Custom Session Keys ​

ts
bot.use(
    session({
        getSessionKey: ctx => {
            if (!ctx.chat) return undefined;
            return `chat:${ctx.chat.id}`;
        },
    })
);

Use chat-level keys for shared group state and chat+user keys for private user state.

Custom Storage Adapters ​

ts
import type { SessionStore } from 'vibegram';

class DatabaseSessionStore implements SessionStore {
    async get(key: string) {
        return db.session.findUnique({ where: { key } });
    }

    async set(key: string, value: unknown) {
        await db.session.upsert({
            where: { key },
            create: { key, value },
            update: { value },
        });
    }

    async delete(key: string) {
        await db.session.delete({ where: { key } });
    }
}

Adapters should use parameterized queries, TTL or cleanup policies, and stable JSON serialization for values.

Memory Management ​

For long-running processes, prefer one of these:

  • Keep the default MemorySessionStore limits for small bots.
  • Tune ttlMs and maxEntries for expected traffic.
  • Call store.close() during graceful shutdown if you created a MemorySessionStore manually.
  • Use Redis or another external store for horizontally scaled production bots.

Released under the ISC License.