@keyv/mysql
Version:
MySQL/MariaDB storage adapter for Keyv
303 lines (299 loc) • 10.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
KeyvMysql: () => KeyvMysql,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
var import_node_events = __toESM(require("events"), 1);
var import_mysql22 = __toESM(require("mysql2"), 1);
// src/pool.ts
var import_mysql2 = __toESM(require("mysql2"), 1);
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 ??= import_mysql2.default.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 import_node_events.default {
/**
* Indicates whether TTL (Time To Live) support is enabled.
* Set to true when intervalExpiration is configured.
*/
ttlSupport = false;
/**
* Configuration options for the MySQL adapter.
*/
opts;
/**
* Optional namespace for key prefixing.
*/
namespace;
/**
* Query function for executing SQL statements against the MySQL database.
*/
query;
/**
* Creates a new KeyvMysql instance.
* @param keyvOptions - Configuration options or connection URI string
*/
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);
};
}
/**
* Retrieves a value from the store by key.
* @param key - The key to retrieve
* @returns The stored value or undefined if not found
*/
async get(key) {
const sql = `SELECT * FROM ${this.opts.table} WHERE id = ?`;
const select = import_mysql22.default.format(sql, [key]);
const rows = await this.query(select);
const row = rows[0];
return row?.value;
}
/**
* Retrieves multiple values from the store by their keys.
* @param keys - Array of keys to retrieve
* @returns Array of stored values in the same order as the input keys, with undefined for missing keys
*/
async getMany(keys) {
const sql = `SELECT * FROM ${this.opts.table} WHERE id IN (?)`;
const select = import_mysql22.default.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;
}
/**
* Sets a value in the store for the given key.
* If the key already exists, it will be updated.
* @param key - The key to set
* @param value - The value to store
* @returns Promise that resolves when the operation completes
*/
// 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 = import_mysql22.default.format(sql, insert);
return this.query(upsert);
}
/**
* Deletes a key-value pair from the store.
* @param key - The key to delete
* @returns True if the key existed and was deleted, false if the key did not exist
*/
async delete(key) {
const sql = `SELECT * FROM ${this.opts.table} WHERE id = ?`;
const select = import_mysql22.default.format(sql, [key]);
const delSql = `DELETE FROM ${this.opts.table} WHERE id = ?`;
const del = import_mysql22.default.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;
}
/**
* Deletes multiple key-value pairs from the store.
* @param key - Array of keys to delete
* @returns True if at least one key was deleted, false if no keys were found
*/
async deleteMany(key) {
const sql = `DELETE FROM ${this.opts.table} WHERE id IN (?)`;
const del = import_mysql22.default.format(sql, [key]);
const result = await this.query(del);
return result.affectedRows !== 0;
}
/**
* Clears all entries from the store.
* If a namespace is set, only entries within that namespace are cleared.
* @returns Promise that resolves when the operation completes
*/
async clear() {
const sql = `DELETE FROM ${this.opts.table} WHERE id LIKE ?`;
const del = import_mysql22.default.format(sql, [
this.namespace ? `${this.namespace}:%` : "%"
]);
await this.query(del);
}
/**
* Returns an async iterator for iterating over all key-value pairs in the store.
* @param namespace - Optional namespace to filter results
* @yields Arrays containing [key, value] pairs
*/
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 = import_mysql22.default.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);
}
/**
* Checks if a key exists in the store.
* @param key - The key to check
* @returns True if the key exists, false otherwise
*/
async has(key) {
const sql = `SELECT EXISTS ( SELECT * FROM ${this.opts.table} WHERE id = ? )`;
const exists = import_mysql22.default.format(sql, [key]);
const rows = await this.query(exists);
return Object.values(rows[0])[0] === 1;
}
/**
* Disconnects from the MySQL database and closes the connection pool.
* @returns Promise that resolves when disconnected
*/
async disconnect() {
endPool();
}
};
var index_default = KeyvMysql;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
KeyvMysql
});
/* v8 ignore next -- @preserve */