UNPKG

mastercache

Version:

Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers

116 lines (97 loc) 3.49 kB
import { SqliteAdapter, type Kysely, MysqlAdapter } from 'kysely'; import { DatabaseDriver } from '../database'; import type { CreateDriverResult, DatabaseAdapter, KyselyConfig } from '../../../types/main'; /** * Kysely adapter for the DatabaseDriver */ export class KyselyAdapter implements DatabaseAdapter { #dialect: 'mysql' | 'pg' | 'sqlite'; #tableName!: string; #connection: Kysely<any>; constructor(config: KyselyConfig) { this.#connection = config.connection; const adapter = this.#connection.getExecutor().adapter; if (adapter instanceof SqliteAdapter) { this.#dialect = 'sqlite'; } else if (adapter instanceof MysqlAdapter) { this.#dialect = 'mysql'; } else { this.#dialect = 'pg'; } } setTableName(tableName: string): void { this.#tableName = tableName; } async get(key: string): Promise<{ value: any; expiresAt: number | null } | undefined> { const result = await this.#connection .selectFrom(this.#tableName) .select(['value', 'expires_at']) .where('key', '=', key) .executeTakeFirst(); if (!result) return; return { value: result.value, expiresAt: result.expires_at }; } async delete(key: string): Promise<boolean> { const result = await this.#connection .deleteFrom(this.#tableName) .where('key', '=', key) .executeTakeFirst(); return result.numDeletedRows > 0; } async deleteMany(keys: string[]): Promise<number> { const result = await this.#connection .deleteFrom(this.#tableName) .where('key', 'in', keys) .executeTakeFirst(); return +result.numDeletedRows.toString(); } async disconnect(): Promise<void> { await this.#connection.destroy(); } async createTableIfNotExists(): Promise<void> { await this.#connection.schema .createTable(this.#tableName) .addColumn('key', 'varchar(255)', (col) => col.primaryKey().notNull()) .addColumn('value', 'text') .addColumn('expires_at', 'bigint') .ifNotExists() .execute(); } async pruneExpiredEntries(): Promise<void> { await this.#connection .deleteFrom(this.#tableName) .where('expires_at', '<', Date.now()) .execute(); } async clear(prefix: string): Promise<void> { await this.#connection.deleteFrom(this.#tableName).where('key', 'like', `${prefix}%`).execute(); } async set(row: { value: any; key: string; expiresAt: Date | null }): Promise<void> { const expiresAt = this.#dialect === 'sqlite' ? row.expiresAt?.getTime() : row.expiresAt; await this.#connection .insertInto(this.#tableName) .values({ key: row.key, value: row.value, expires_at: expiresAt ?? null }) .$if(this.#dialect === 'mysql', (query) => query.onDuplicateKeyUpdate({ value: row.value, expires_at: expiresAt }), ) .$if(this.#dialect !== 'mysql', (query) => { return query.onConflict((conflict) => { return conflict.columns(['key']).doUpdateSet({ value: row.value, expires_at: expiresAt }); }); }) .execute(); } } /** * Create a kysely driver * You will need to install the underlying database package (mysql2, pg, sqlite3, etc) */ export function kyselyDriver(options: KyselyConfig): CreateDriverResult<DatabaseDriver> { return { options, factory: (config: KyselyConfig) => { const adapter = new KyselyAdapter(config); return new DatabaseDriver(adapter, config); }, }; }