This commit is contained in:
10
lib/services/cache/cache-service.ts
vendored
Normal file
10
lib/services/cache/cache-service.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export type CacheSetOptions = {
|
||||
ttlSeconds?: number;
|
||||
};
|
||||
|
||||
export interface CacheService {
|
||||
get<T>(key: string): Promise<T | undefined>;
|
||||
set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>;
|
||||
del(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
30
lib/services/cache/memory-cache-service.ts
vendored
Normal file
30
lib/services/cache/memory-cache-service.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { CacheService, CacheSetOptions } from './cache-service';
|
||||
|
||||
type Entry = {
|
||||
value: unknown;
|
||||
expiresAt?: number;
|
||||
};
|
||||
|
||||
export class MemoryCacheService implements CacheService {
|
||||
private readonly store = new Map<string, Entry>();
|
||||
|
||||
async get<T>(key: string) {
|
||||
const entry = this.store.get(key);
|
||||
if (!entry) return undefined;
|
||||
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
||||
this.store.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
return entry.value as T;
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, options?: CacheSetOptions) {
|
||||
const ttl = options?.ttlSeconds;
|
||||
const expiresAt = ttl ? Date.now() + ttl * 1000 : undefined;
|
||||
this.store.set(key, { value, expiresAt });
|
||||
}
|
||||
|
||||
async del(key: string) {
|
||||
this.store.delete(key);
|
||||
}
|
||||
}
|
||||
49
lib/services/cache/redis-cache-service.ts
vendored
Normal file
49
lib/services/cache/redis-cache-service.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import { createClient, type RedisClientType } from 'redis';
|
||||
import type { CacheService, CacheSetOptions } from './cache-service';
|
||||
|
||||
export type RedisCacheServiceOptions = {
|
||||
url: string;
|
||||
keyPrefix?: string;
|
||||
};
|
||||
|
||||
// Thin wrapper around shared Redis (platform provides host `redis`).
|
||||
// Values are JSON-serialized.
|
||||
export class RedisCacheService implements CacheService {
|
||||
private readonly client: RedisClientType;
|
||||
private readonly keyPrefix: string;
|
||||
|
||||
constructor(options: RedisCacheServiceOptions) {
|
||||
this.client = createClient({ url: options.url });
|
||||
this.keyPrefix = options.keyPrefix ?? '';
|
||||
|
||||
// Fire-and-forget connect.
|
||||
this.client.connect().catch(() => undefined);
|
||||
}
|
||||
|
||||
private k(key: string) {
|
||||
return `${this.keyPrefix}${key}`;
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | undefined> {
|
||||
const raw = await this.client.get(this.k(key));
|
||||
if (raw == null) return undefined;
|
||||
return JSON.parse(raw) as T;
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void> {
|
||||
const ttl = options?.ttlSeconds;
|
||||
const raw = JSON.stringify(value);
|
||||
|
||||
if (ttl && ttl > 0) {
|
||||
await this.client.set(this.k(key), raw, { EX: ttl });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.client.set(this.k(key), raw);
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
await this.client.del(this.k(key));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user