/** * Database query caching with Redis * Implements query result caching with automatic invalidation * * Note: This module uses optional dynamic import for @the-order/cache * to avoid requiring it as a direct dependency. If cache is not available, * queries will execute directly without caching. */ import { query } from './client'; import type { QueryResult, QueryResultRow } from './client'; export interface CacheOptions { ttl?: number; // Time to live in seconds keyPrefix?: string; enabled?: boolean; } // Cache client interface (matches @the-order/cache API) // This interface allows us to use the cache without a compile-time dependency interface CacheClient { get(key: string): Promise; set(key: string, value: unknown, ttl?: number): Promise; delete(key: string): Promise; invalidate(pattern: string): Promise; } // Cache client instance (lazy-loaded via dynamic import) let cacheClientPromise: Promise | null = null; /** * Get cache client (lazy-loaded via dynamic import) * Returns null if cache module is not available */ async function getCacheClient(): Promise { if (cacheClientPromise === null) { cacheClientPromise = (async () => { try { // Use dynamic import with a string literal that TypeScript can't resolve at compile time // This is done by constructing the import path dynamically const cacheModulePath = '@the-order/cache'; // eslint-disable-next-line @typescript-eslint/no-implied-eval const importFunc = new Function('specifier', 'return import(specifier)'); const cacheModule = await importFunc(cacheModulePath); return cacheModule.getCacheClient() as CacheClient; } catch { // Cache module not available - caching will be disabled return null; } })(); } return cacheClientPromise; } /** * Execute a query with caching */ export async function cachedQuery( sql: string, params?: unknown[], options: CacheOptions = {} ): Promise> { const { ttl = 3600, keyPrefix = 'db:query:', enabled = true } = options; if (!enabled) { return query(sql, params); } const cache = await getCacheClient(); if (!cache) { // Cache not available - execute query directly return query(sql, params); } const cacheKey = `${keyPrefix}${sql}:${JSON.stringify(params || [])}`; // Try to get from cache const cached = await cache.get>(cacheKey); if (cached) { return cached; } // Execute query const result = await query(sql, params); // Cache result await cache.set(cacheKey, result, ttl); return result; } /** * Invalidate cache for a pattern */ export async function invalidateCache(pattern: string): Promise { const cache = await getCacheClient(); if (!cache) { return 0; } return cache.invalidate(`db:query:${pattern}*`); } /** * Invalidate cache for a specific query */ export async function invalidateQueryCache(sql: string, params?: unknown[]): Promise { const cache = await getCacheClient(); if (!cache) { return; } const cacheKey = `db:query:${sql}:${JSON.stringify(params || [])}`; await cache.delete(cacheKey); } /** * Cache decorator for database functions * Note: This is a simplified implementation. In production, you'd need to * extract SQL and params from the function or pass them as metadata. */ export function cached Promise>>( fn: T ): T { return (async (...args: Parameters) => { const result = await fn(...args); return result; }) as T; }