├── .gitignore ├── types.ts ├── defaults.ts ├── tsconfig.json ├── memory.ts ├── .github └── workflows │ └── release.yml ├── LICENSE ├── sqlite.ts ├── redis.ts ├── mysql.ts ├── pg.ts ├── package.json ├── kysely.ts ├── mongodb.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | *.js 3 | node_modules -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | export type { SessionStore } from "telegraf"; 2 | -------------------------------------------------------------------------------- /defaults.ts: -------------------------------------------------------------------------------- 1 | export const defaults = { table: "telegraf-sessions" }; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "strict": true, 6 | "skipLibCheck": true, 7 | "skipDefaultLibCheck": true, 8 | "declaration": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "rootDir": "." 11 | }, 12 | "include": ["./*.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /memory.ts: -------------------------------------------------------------------------------- 1 | import { SessionStore } from "./types"; 2 | 3 | /** 4 | * You should probably use builtin session instead of this. 5 | * This stub implementation exists for completion's sake, and for extensibility in the future. 6 | * 7 | * This is also the default import of `@telegraf/session`, but you should not import the bare path. 8 | * Prefer `@telegraf/session/redis` and friends. 9 | * */ 10 | export const Memory = (): SessionStore => new Map(); 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v2.* 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run : npm ci --ignore-scripts 15 | - run : npm run prepare 16 | - name: Publish to npm 17 | run : | 18 | npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} 19 | npm publish --ignore-scripts 20 | env : 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Telegraf contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sqlite.ts: -------------------------------------------------------------------------------- 1 | import { SqliteDialect } from "kysely"; 2 | import { Database as Db, Options } from "better-sqlite3"; 3 | import { KyselyStore } from "./kysely"; 4 | import Database = require("better-sqlite3"); 5 | import { SessionStore } from "./types"; 6 | 7 | interface NewDatabaseOpts { 8 | /** Filename to use for SQLite sessions. */ 9 | filename: string; 10 | /** 11 | * Better-SQLite3 new Database options. 12 | * 13 | * Remember to install the db driver `'better-sqlite3'`. 14 | * 15 | * @see {@link https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#new-databasepath-options Better-SQLite3 | new Database} 16 | * */ 17 | config?: Options; 18 | /** Called on fatal connection or setup errors */ 19 | onInitError?: (err: unknown) => void; 20 | } 21 | 22 | interface ExistingDatabaseOpts { 23 | /** If passed, we'll reuse this instance of Better-SQLite3 Database instead of creating our own. */ 24 | database: Db; 25 | /** Called on fatal connection or setup errors */ 26 | onInitError?: (err: unknown) => void; 27 | } 28 | 29 | /** @unstable */ 30 | export function SQLite(opts: NewDatabaseOpts): SessionStore; 31 | export function SQLite(opts: ExistingDatabaseOpts): SessionStore; 32 | export function SQLite(opts: NewDatabaseOpts | ExistingDatabaseOpts) { 33 | return KyselyStore({ 34 | config: 35 | "database" in opts 36 | ? { dialect: new SqliteDialect({ database: opts.database }) } 37 | : { dialect: new SqliteDialect({ database: new Database(opts.filename, opts.config) }) }, 38 | onInitError: opts.onInitError, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /redis.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "redis"; 2 | import type { RedisClientOptions } from "redis"; 3 | import { SessionStore } from "./types"; 4 | 5 | type Client = ReturnType; 6 | 7 | interface NewClientOpts { 8 | /** 9 | * `redis[s]://[[username][:password]@][host][:port][/db-number]` 10 | * 11 | * See [`redis`](https://www.iana.org/assignments/uri-schemes/prov/redis) and [`rediss`](https://www.iana.org/assignments/uri-schemes/prov/rediss) IANA registration for more details 12 | */ 13 | url?: string; 14 | config?: Omit; 15 | /** Prefix to use for session keys. Defaults to "telegraf:". */ 16 | prefix?: string; 17 | /** Called on fatal connection or setup errors */ 18 | onInitError?: (err: unknown) => void; 19 | } 20 | 21 | interface ExistingClientOpts { 22 | /** If passed, we'll reuse this client instead of creating our own. */ 23 | client: Client; 24 | /** Prefix to use for session keys. Defaults to "telegraf:". */ 25 | prefix?: string; 26 | } 27 | 28 | /** @unstable */ 29 | export function Redis(opts: NewClientOpts): SessionStore; 30 | export function Redis(opts: ExistingClientOpts): SessionStore; 31 | export function Redis(opts: NewClientOpts | ExistingClientOpts): SessionStore { 32 | let client: Client; 33 | if ("client" in opts) client = opts.client; 34 | else client = createClient({ ...opts.config, url: opts.url }); 35 | 36 | const connection = client.connect(); 37 | 38 | const prefix = opts.prefix || "telegraf:"; 39 | 40 | return { 41 | async get(key) { 42 | await connection; 43 | const value = await client.get(prefix + key); 44 | return value ? JSON.parse(value) : undefined; 45 | }, 46 | async set(key: string, session: Session) { 47 | await connection; 48 | return await client.set(prefix + key, JSON.stringify(session)); 49 | }, 50 | async delete(key: string) { 51 | await connection; 52 | return await client.del(prefix + key); 53 | }, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /mysql.ts: -------------------------------------------------------------------------------- 1 | import { MysqlDialect } from "kysely"; 2 | import { createPool, Pool, PoolOptions } from "mysql2"; 3 | import { KyselyStore } from "./kysely"; 4 | import { SessionStore } from "./types"; 5 | 6 | interface NewPoolOpts { 7 | host?: string | undefined; 8 | port?: number | undefined; 9 | database?: string | undefined; 10 | user?: string | undefined; 11 | password?: string; 12 | /** 13 | * MySQL2 Pool options. 14 | * 15 | * Remember to install the db driver `'mysql2'`. 16 | * 17 | * @see {@link https://github.com/sidorares/node-mysql2#using-connection-pools Node MySQL 2 | Using connection pools} 18 | * */ 19 | config?: Omit; 20 | /** Table name to use for sessions. Defaults to "telegraf-sessions". */ 21 | table?: string; 22 | /** Called on fatal connection or setup errors */ 23 | onInitError?: (err: unknown) => void; 24 | } 25 | 26 | interface ExistingPoolOpts { 27 | /** If passed, we'll reuse this instance of MySQL2 Pool instead of creating our own. */ 28 | pool: Pool; 29 | /** Table name to use for sessions. Defaults to "telegraf-sessions". */ 30 | table?: string; 31 | /** Called on fatal connection or setup errors */ 32 | onInitError?: (err: unknown) => void; 33 | } 34 | 35 | /** @unstable */ 36 | export function MySQL(opts: NewPoolOpts): SessionStore; 37 | export function MySQL(opts: ExistingPoolOpts): SessionStore; 38 | export function MySQL(opts: NewPoolOpts | ExistingPoolOpts) { 39 | return KyselyStore({ 40 | config: 41 | "pool" in opts 42 | ? { dialect: new MysqlDialect({ pool: opts.pool }) } 43 | : { 44 | dialect: new MysqlDialect({ 45 | pool: createPool({ 46 | host: opts.host, 47 | port: opts.port, 48 | database: opts.database, 49 | user: opts.user, 50 | password: opts.password, 51 | ...opts.config, 52 | }), 53 | }), 54 | }, 55 | table: opts.table, 56 | onInitError: opts.onInitError, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /pg.ts: -------------------------------------------------------------------------------- 1 | import { PostgresDialect } from "kysely"; 2 | import { Pool, PoolConfig } from "pg"; 3 | import { KyselyStore } from "./kysely"; 4 | import { SessionStore } from "./types"; 5 | 6 | interface NewPoolOpts { 7 | host?: string | undefined; 8 | port?: number | undefined; 9 | database?: string | undefined; 10 | user?: string | undefined; 11 | password?: string | (() => string | Promise) | undefined; 12 | /** 13 | * Postgres Pool config. 14 | * 15 | * Remember to install the db driver `'pg'`. 16 | * 17 | * @see {@link https://node-postgres.com/apis/pool node-postgres | Pool Options} 18 | * */ 19 | config?: Omit; 20 | /** Table name to use for sessions. Defaults to "telegraf-sessions". */ 21 | table?: string; 22 | /** Called on fatal connection or setup errors */ 23 | onInitError?: (err: unknown) => void; 24 | } 25 | 26 | interface ExistingPoolOpts { 27 | /** If passed, we'll reuse this instance of pg Pool instead of creating our own. */ 28 | pool: Pool; 29 | /** Table name to use for sessions. Defaults to "telegraf-sessions". */ 30 | table?: string; 31 | /** Called on fatal connection or setup errors */ 32 | onInitError?: (err: unknown) => void; 33 | } 34 | 35 | /** @unstable */ 36 | export function Postgres(opts: NewPoolOpts): SessionStore; 37 | export function Postgres(opts: ExistingPoolOpts): SessionStore; 38 | export function Postgres(opts: NewPoolOpts | ExistingPoolOpts) { 39 | return KyselyStore({ 40 | config: 41 | "pool" in opts 42 | ? { dialect: new PostgresDialect({ pool: opts.pool }) } 43 | : { 44 | dialect: new PostgresDialect({ 45 | pool: new Pool({ 46 | host: opts.host, 47 | port: opts.port, 48 | database: opts.database, 49 | user: opts.user, 50 | password: opts.password, 51 | ...opts.config, 52 | }), 53 | }), 54 | }, 55 | table: opts.table, 56 | onInitError: opts.onInitError, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@telegraf/session", 3 | "version": "2.0.0-beta.7", 4 | "description": "Session store adapters for Telegraf", 5 | "main": "./memory.js", 6 | "homepage": "https://github.com/telegraf/session", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+ssh://git@github.com/telegraf/session.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/telegraf/session/issues" 13 | }, 14 | "scripts": { 15 | "clean": "rimraf ./*.js ./*.d.ts", 16 | "prepare": "rimraf ./*.js ./*.d.ts && tsc" 17 | }, 18 | "keywords": [ 19 | "telegraf", 20 | "telegram", 21 | "bot", 22 | "session", 23 | "mongodb" 24 | ], 25 | "author": "Muthu Kumar <@MKRhere> (https://mkr.pw)", 26 | "license": "MIT", 27 | "files": [ 28 | "./*.js", 29 | "./*.d.ts" 30 | ], 31 | "exports": { 32 | "./mongodb": { 33 | "types": "./mongodb.d.ts", 34 | "default": "./mongodb.js" 35 | }, 36 | "./redis": { 37 | "types": "./redis.d.ts", 38 | "default": "./redis.js" 39 | }, 40 | "./mysql": { 41 | "types": "./mysql.d.ts", 42 | "default": "./mysql.js" 43 | }, 44 | "./pg": { 45 | "types": "./pg.d.ts", 46 | "default": "./pg.js" 47 | }, 48 | "./sqlite": { 49 | "types": "./sqlite.d.ts", 50 | "default": "./sqlite.js" 51 | } 52 | }, 53 | "peerDependencies": { 54 | "@types/better-sqlite3": "^7.6.9", 55 | "@types/pg": "^8.11.0", 56 | "better-sqlite3": "^9.3.0", 57 | "kysely": "0.27.2 <1", 58 | "mongodb": "^6.3.0", 59 | "mysql2": "^3.9.0", 60 | "pg": "^8.11.3", 61 | "redis": "^4.6.12", 62 | "telegraf": ">=4.12.0" 63 | }, 64 | "peerDependenciesMeta": { 65 | "@types/better-sqlite3": { 66 | "optional": true 67 | }, 68 | "@types/pg": { 69 | "optional": true 70 | }, 71 | "better-sqlite3": { 72 | "optional": true 73 | }, 74 | "kysely": { 75 | "optional": true 76 | }, 77 | "mongodb": { 78 | "optional": true 79 | }, 80 | "mysql2": { 81 | "optional": true 82 | }, 83 | "pg": { 84 | "optional": true 85 | }, 86 | "redis": { 87 | "optional": true 88 | } 89 | }, 90 | "devDependencies": { 91 | "@types/better-sqlite3": "^7.6.9", 92 | "@types/pg": "^8.11.0", 93 | "better-sqlite3": "^9.3.0", 94 | "kysely": "^0.27.2", 95 | "mongodb": "^6.3.0", 96 | "mysql2": "^3.9.0", 97 | "pg": "^8.11.3", 98 | "redis": "^4.6.12", 99 | "rimraf": "^5.0.5", 100 | "telegraf": "^4.15.3", 101 | "typescript": "^5.3.3" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /kysely.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, MysqlDialect } from "kysely"; 2 | import type { KyselyConfig } from "kysely"; 3 | import type { SessionStore } from "./types"; 4 | import { defaults } from "./defaults"; 5 | 6 | interface SessionTable { 7 | key: string; 8 | session: string; 9 | } 10 | 11 | interface Database { 12 | "telegraf-sessions": SessionTable; 13 | } 14 | 15 | interface NewClientOpts { 16 | /** 17 | * Knex database config. 18 | * 19 | * Remember to install the corresponding db driver. 20 | * 21 | * @see {@link https://knexjs.org/guide/#configuration-options Knex | Configuration Options} 22 | * */ 23 | config: KyselyConfig; 24 | /** Table name to use for sessions. Defaults to "telegraf-sessions". */ 25 | table?: string; 26 | /** Called on fatal connection or setup errors */ 27 | onInitError?: (err: unknown) => void; 28 | } 29 | 30 | /** @unstable */ 31 | export const KyselyStore = (opts: NewClientOpts): SessionStore => { 32 | // this assertion is a hack to make the Database type work 33 | const table = (opts.table ?? defaults.table) as "telegraf-sessions"; 34 | 35 | const client: Kysely = new Kysely(opts.config); 36 | 37 | const create = client.schema 38 | .createTable(table) 39 | .ifNotExists() 40 | .addColumn("key", "varchar(32)", col => col.primaryKey().notNull()) 41 | .addColumn("session", "text") 42 | .execute(); 43 | 44 | if ("onInitError" in opts) create.catch(opts.onInitError); 45 | 46 | return { 47 | async get(key) { 48 | await create; 49 | 50 | const value = ( 51 | await client 52 | // 53 | .selectFrom(table) 54 | .select("session") 55 | .where("key", "=", key) 56 | .limit(1) 57 | .executeTakeFirst() 58 | )?.session; 59 | 60 | return value ? JSON.parse(value) : undefined; 61 | }, 62 | async set(key: string, value: Session) { 63 | await create; 64 | 65 | const session = JSON.stringify(value); 66 | 67 | const res = await (opts.config.dialect instanceof MysqlDialect 68 | ? client 69 | .insertInto(table) 70 | .values({ key, session }) 71 | // MySQL has ON DUPLICATE KEY UPDATE 72 | .onDuplicateKeyUpdate({ session }) 73 | : client 74 | .insertInto(table) 75 | .values({ key, session }) 76 | // Postgres and SQLITE have ON CONFLICT DO UPDATE SET 77 | .onConflict(b => b.column("key").doUpdateSet({ session })) 78 | ).executeTakeFirst(); 79 | }, 80 | async delete(key: string) { 81 | await create; 82 | 83 | await client // 84 | .deleteFrom(table) 85 | .where("key", "=", key) 86 | .executeTakeFirst(); 87 | }, 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /mongodb.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, MongoClientOptions } from "mongodb"; 2 | import { defaults } from "./defaults"; 3 | import { SessionStore } from "./types"; 4 | 5 | interface NewClientOpts { 6 | /** MongoDB connection URL; required. */ 7 | url: string; 8 | /** 9 | * MongoDB MongoClientOptions. 10 | * 11 | * Remember to install the db driver `'mongodb'`. 12 | * 13 | * @see {@link https://www.mongodb.com/docs/drivers/node/current/fundamentals/connection/connection-options MongoDB | Connection Options} 14 | * */ 15 | config?: MongoClientOptions; 16 | /** The name of the database we want to use. If not provided, database name will be taken from connection string. */ 17 | database?: string; 18 | /** MongoDB collection name to use for sessions. Defaults to "telegraf-sessions". */ 19 | collection?: string; 20 | /** Called on fatal connection or setup errors */ 21 | onInitError?: (err: unknown) => void; 22 | } 23 | 24 | interface ExistingClientOpts { 25 | /** If passed, we'll reuse this client instead of creating our own. */ 26 | client: MongoClient; 27 | /** The name of the database we want to use. If not provided, database name will be taken from connection string. */ 28 | database?: string; 29 | /** MongoDB collection name to use for sessions. Defaults to "telegraf-sessions". */ 30 | collection?: string; 31 | /** Called on fatal connection or setup errors */ 32 | onInitError?: (err: unknown) => void; 33 | } 34 | 35 | /** @unstable */ 36 | export function Mongo(opts: NewClientOpts): SessionStore; 37 | export function Mongo(opts: ExistingClientOpts): SessionStore; 38 | export function Mongo(opts: NewClientOpts | ExistingClientOpts): SessionStore { 39 | interface SessionDoc { 40 | key: string; 41 | session: Session; 42 | } 43 | 44 | let client: MongoClient; 45 | let connection: Promise | undefined; 46 | 47 | if ("client" in opts) client = opts.client; 48 | else { 49 | client = new MongoClient(opts.url, opts.config); 50 | connection = client.connect(); 51 | connection.catch(opts.onInitError); 52 | } 53 | 54 | const collection = client.db(opts.database).collection(opts.collection ?? defaults.table); 55 | 56 | return { 57 | async get(key) { 58 | // since we synchronously return store instance 59 | await connection; 60 | return (await collection.findOne({ key }))?.session; 61 | }, 62 | async set(key: string, session: Session) { 63 | await connection; 64 | await collection.updateOne({ key }, { $set: { key, session } }, { upsert: true }); 65 | }, 66 | async delete(key: string) { 67 | await connection; 68 | await collection.deleteOne({ key }); 69 | }, 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@telegraf/session` 2 | 3 | This package provides official storage adapters for Telegraf v4.12+ sessions [[see motivation]](#background). 4 | 5 | > [!WARNING] 6 | > **You're not meant to import the default path!** Read one of the following sections before using this module. 7 | 8 | An in-memory session module is bundled with Telegraf. The following modules are available here: 9 | 10 | - [Redis](#redis) 11 | - [MongoDB](#mongodb) 12 | - [SQLite](#sqlite) 13 | - [PostgreSQL](#postgresql) 14 | - [MySQL / MariaDB](#mysql--mariadb) 15 | 16 | ## Redis 17 | 18 | Install the official Redis driver alongside this module. 19 | 20 | ```shell 21 | npm i @telegraf/session redis 22 | ``` 23 | 24 | Usage is pretty straightforward: 25 | 26 | ```TS 27 | import { Redis } from "@telegraf/session/redis"; 28 | 29 | const store = Redis({ 30 | url: "redis://127.0.0.1:6379", 31 | }); 32 | 33 | const bot = new Telegraf(token, opts); 34 | bot.use(session({ store })); 35 | 36 | // the rest of your bot 37 | ``` 38 | 39 | To reuse an existing Redis client, use `Redis({ client })` instead. 40 | 41 | ## MongoDB 42 | 43 | Install the official MongoDB driver alongside this module. 44 | 45 | ```shell 46 | npm i @telegraf/session mongodb 47 | ``` 48 | 49 | Usage is pretty straightforward: 50 | 51 | ```TS 52 | import { Mongo } from "@telegraf/session/mongodb"; 53 | 54 | const store = Mongo({ 55 | url: "mongodb://127.0.0.1:27017", 56 | database: "telegraf-bot", 57 | }); 58 | 59 | const bot = new Telegraf(token, opts); 60 | bot.use(session({ store })); 61 | 62 | // the rest of your bot 63 | ``` 64 | 65 | To reuse an existing MongoDB client, use `Mongo({ client })` instead. 66 | 67 | ## SQLite 68 | 69 | Install the Better-SQLite3 driver and types alongside this module. 70 | 71 | ```shell 72 | npm i @telegraf/session kysely better-sqlite3 73 | npm i --save-dev @types/better-sqlite3 74 | ``` 75 | 76 | Usage is pretty straightforward: 77 | 78 | ```TS 79 | import { SQLite } from "@telegraf/session/sqlite"; 80 | 81 | const store = SQLite({ 82 | filename: "./telegraf-sessions.sqlite", 83 | }); 84 | 85 | const bot = new Telegraf(token, opts); 86 | bot.use(session({ store })); 87 | 88 | // the rest of your bot 89 | ``` 90 | 91 | To reuse an existing Better-SQLite3 database instance, use `SQLite({ database })` instead. 92 | 93 | ## PostgreSQL 94 | 95 | Install the 'pg' PostgreSQL driver and types alongside this module. 96 | 97 | ```shell 98 | npm i @telegraf/session kysely pg 99 | npm i --save-dev @types/pg 100 | ``` 101 | 102 | Usage is pretty straightforward: 103 | 104 | ```TS 105 | import { Postgres } from "@telegraf/session/pg"; 106 | 107 | const store = Postgres({ 108 | host: "127.0.0.1", 109 | database: "telegraf-test", 110 | user: "database-user", 111 | password: "hunter2", 112 | }); 113 | 114 | const bot = new Telegraf(token, opts); 115 | bot.use(session({ store })); 116 | 117 | // the rest of your bot 118 | ``` 119 | 120 | To reuse an existing pg pool, use `Postgres({ pool })` instead. 121 | 122 | ## MySQL / MariaDB 123 | 124 | Install the 'mysql2' MySQL driver alongside this module. 125 | 126 | ```shell 127 | npm i @telegraf/session kysely mysql2 128 | ``` 129 | 130 | Usage is pretty straightforward: 131 | 132 | ```TS 133 | import { MySQL } from "@telegraf/session/mysql"; 134 | 135 | const store = MySQL({ 136 | host: "127.0.0.1", 137 | database: "telegraf-test", 138 | user: "database-user", 139 | password: "hunter2", 140 | }); 141 | 142 | const bot = new Telegraf(token, opts); 143 | bot.use(session({ store })); 144 | 145 | // the rest of your bot 146 | ``` 147 | 148 | To reuse an existing MySQL2 pool, use `MySQL({ pool })` instead. 149 | 150 | ## Background 151 | 152 | Since [telegraf#1372](https://github.com/telegraf/telegraf/issues/1372), it has been known that all asynchronous session middleware have been prone to race-conditions. This was addressed in [telegraf#1713](https://github.com/telegraf/telegraf/pull/1713), but third-party session middleware continue to be affected. Since Telegraf 1.12.0, it's recommended that third-party plugins only provide the store parameter for session, instead of implementing session themselves. This way, they can take advantage of the safety provided by Telegraf's builtin session. Of course, if your plugin has an exceptional usecase, it may need to implement its own middleware. 153 | 154 | To begin to solve this problem, we officially maintain the 5 most common storage backends. This package is considered beta, and may have minor breaking changes and bugfixes before a semver stable release. Feedback is welcome via issues and in the group: [TelegrafJSChat](https://t.me/TelegrafJSChat) 155 | --------------------------------------------------------------------------------