UNPKG

@keyv/mysql

Version:

MySQL/MariaDB storage adapter for Keyv

203 lines (200 loc) 6.12 kB
// src/index.ts import EventEmitter from "events"; import mysql2 from "mysql2"; // src/pool.ts import mysql from "mysql2"; var mysqlPool; var globalUri; var parseConnectionString = (connectionString) => { connectionString = connectionString.replace(/#/g, "%23"); const url = new URL(connectionString); const poolOptions = { user: decodeURIComponent(url.username), password: decodeURIComponent(url.password) || void 0, host: url.hostname, port: url.port ? Number.parseInt(url.port, 10) : void 0, database: decodeURIComponent(url.pathname.slice(1)) // Remove the leading '/' }; for (const key of Object.keys(poolOptions)) { if (poolOptions[key] === void 0) { delete poolOptions[key]; } } return poolOptions; }; var pool = (uri, options = {}) => { if (globalUri !== uri) { mysqlPool = void 0; globalUri = uri; } const connectObject = parseConnectionString(uri); const poolOptions = { ...connectObject, ...options }; mysqlPool ??= mysql.createPool(poolOptions); return mysqlPool.promise(); }; var endPool = () => { if (mysqlPool) { mysqlPool.end(); } globalUri = void 0; }; // src/index.ts var keyvMysqlKeys = /* @__PURE__ */ new Set([ "adapter", "compression", "connect", "dialect", "keySize", "table", "ttl", "uri" ]); var KeyvMysql = class extends EventEmitter { ttlSupport = false; opts; namespace; query; constructor(keyvOptions) { super(); let options = { dialect: "mysql", uri: "mysql://localhost" }; if (typeof keyvOptions === "string") { options.uri = keyvOptions; } else { options = { ...options, ...keyvOptions }; } if (options.intervalExpiration !== void 0 && options.intervalExpiration > 0) { this.ttlSupport = true; } const mysqlOptions = Object.fromEntries( Object.entries(options).filter(([k]) => !keyvMysqlKeys.has(k)) ); delete mysqlOptions.namespace; delete mysqlOptions.serialize; delete mysqlOptions.deserialize; const connection = async () => { const conn = pool(options.uri, mysqlOptions); return async (sql) => { const data = await conn.query(sql); return data[0]; }; }; this.opts = { table: "keyv", keySize: 255, ...options }; const createTable = `CREATE TABLE IF NOT EXISTS ${this.opts.table}(id VARCHAR(${Number(this.opts.keySize)}) PRIMARY KEY, value TEXT)`; const connected = connection().then(async (query) => { await query(createTable); if (this.opts.intervalExpiration !== void 0 && this.opts.intervalExpiration > 0) { await query("SET GLOBAL event_scheduler = ON;"); await query("DROP EVENT IF EXISTS keyv_delete_expired_keys;"); await query(`CREATE EVENT IF NOT EXISTS keyv_delete_expired_keys ON SCHEDULE EVERY ${this.opts.intervalExpiration} SECOND DO DELETE FROM ${this.opts.table} WHERE CAST(value->'$.expires' AS UNSIGNED) BETWEEN 1 AND UNIX_TIMESTAMP(NOW(3)) * 1000;`); } return query; }).catch((error) => this.emit("error", error)); this.query = async (sqlString) => { const query = await connected; return query(sqlString); }; } async get(key) { const sql = `SELECT * FROM ${this.opts.table} WHERE id = ?`; const select = mysql2.format(sql, [key]); const rows = await this.query(select); const row = rows[0]; return row?.value; } async getMany(keys) { const sql = `SELECT * FROM ${this.opts.table} WHERE id IN (?)`; const select = mysql2.format(sql, [keys]); const rows = await this.query(select); const results = []; for (const key of keys) { const rowIndex = rows.findIndex((row) => row.id === key); results.push( rowIndex === -1 ? void 0 : rows[rowIndex].value ); } return results; } // biome-ignore lint/suspicious/noExplicitAny: type format async set(key, value) { const sql = `INSERT INTO ${this.opts.table} (id, value) VALUES(?, ?) ON DUPLICATE KEY UPDATE value=?;`; const insert = [key, value, value]; const upsert = mysql2.format(sql, insert); return this.query(upsert); } async delete(key) { const sql = `SELECT * FROM ${this.opts.table} WHERE id = ?`; const select = mysql2.format(sql, [key]); const delSql = `DELETE FROM ${this.opts.table} WHERE id = ?`; const del = mysql2.format(delSql, [key]); const rows = await this.query(select); const row = rows[0]; if (row === void 0) { return false; } await this.query(del); return true; } async deleteMany(key) { const sql = `DELETE FROM ${this.opts.table} WHERE id IN (?)`; const del = mysql2.format(sql, [key]); const result = await this.query(del); return result.affectedRows !== 0; } async clear() { const sql = `DELETE FROM ${this.opts.table} WHERE id LIKE ?`; const del = mysql2.format(sql, [ this.namespace ? `${this.namespace}:%` : "%" ]); await this.query(del); } async *iterator(namespace) { const limit = Number.parseInt(this.opts.iterationLimit, 10) || 10; async function* iterate(offset, options, query) { const sql = `SELECT * FROM ${options.table} WHERE id LIKE ? LIMIT ? OFFSET ?`; const select = mysql2.format(sql, [ // biome-ignore lint/style/useTemplate: need to fix `${namespace ? namespace + ":" : ""}%`, limit, offset ]); const entries = await query(select); if (entries.length === 0) { return; } for (const entry of entries) { offset += 1; yield [entry.id, entry.value]; } yield* iterate(offset, options, query); } yield* iterate(0, this.opts, this.query); } async has(key) { const exists = `SELECT EXISTS ( SELECT * FROM ${this.opts.table} WHERE id = '${key}' )`; const rows = await this.query(exists); return Object.values(rows[0])[0] === 1; } async disconnect() { endPool(); } }; var index_default = KeyvMysql; export { KeyvMysql, index_default as default };