UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

141 lines (116 loc) 3.78 kB
import { $atom, $inject, $state, Alepha, type Static, z } from "alepha"; import { CacheProvider } from "alepha/cache"; import { $logger } from "alepha/logger"; import { RedisProvider } from "alepha/redis"; // --------------------------------------------------------------------------------------------------------------------- /** * Redis cache configuration atom. */ export const redisCacheOptions = $atom({ name: "alepha.cache.redis.options", schema: z.object({ prefix: z .text({ description: "Prefix for all cache keys in Redis. Useful for testing or multi-tenant applications.", }) .optional(), }), default: {}, }); export type RedisCacheOptions = Static<typeof redisCacheOptions.schema>; declare module "alepha" { interface State { [redisCacheOptions.key]: RedisCacheOptions; } } // --------------------------------------------------------------------------------------------------------------------- export class RedisCacheProvider extends CacheProvider { protected readonly log = $logger(); protected readonly redisProvider = $inject(RedisProvider); protected readonly options = $state(redisCacheOptions); protected readonly alepha = $inject(Alepha); public async get(name: string, key: string): Promise<Uint8Array | undefined> { if (!this.alepha.isStarted()) { return; } const keyWithPrefix = this.prefix(name, key); const buffer = await this.redisProvider.get(keyWithPrefix); if (!buffer) { return; } this.log.debug(`Cache hit`, { size: buffer.byteLength, key: keyWithPrefix, }); return new Uint8Array(buffer); } public async set( name: string, key: string, value: Uint8Array | string, ttl?: number, ): Promise<Uint8Array> { if (!this.alepha.isReady()) { return new Uint8Array(Buffer.from(value)); } const buffer = Buffer.from(value); const prefix = this.prefix(name, key); if (ttl) { return new Uint8Array( await this.redisProvider.set(prefix, buffer, { expiration: { type: "PX", value: ttl }, }), ); } return new Uint8Array(await this.redisProvider.set(prefix, buffer)); } public async del(name: string, ...keys: string[]): Promise<void> { const nameKey = this.prefix(name); if (keys.length === 0) { const matched = await this.redisProvider.keys(`${nameKey}:*`); if (matched.length > 0) { await this.redisProvider.del(matched); } return; } const prefixed = keys.map((key) => key.startsWith(nameKey) ? key : this.prefix(name, key), ); if (prefixed.length > 0) { await this.redisProvider.del(prefixed); } } public async has(name: string, key: string): Promise<boolean> { return this.redisProvider.has(this.prefix(name, key)); } public async keys(name: string, filter?: string): Promise<string[]> { if (filter) { return await this.redisProvider.keys(`${this.prefix(name)}:${filter}*`); } return this.redisProvider.keys(`${this.prefix(name)}:*`); } public async clear(): Promise<void> { this.log.debug("Clearing all cache"); const pattern = `${this.prefix()}:*`; const keys = await this.redisProvider.keys(pattern); if (keys.length > 0) { await this.redisProvider.del(keys); } } public async incr( name: string, key: string, amount: number, ): Promise<number> { const keyWithPrefix = this.prefix(name, key); return this.redisProvider.incr(keyWithPrefix, amount); } protected prefix(...path: string[]): string { const parts = ["cache", ...path]; if (this.options.prefix) { parts.unshift(this.options.prefix); } return parts.join(":"); } }