134 lines
3.0 KiB
TypeScript
134 lines
3.0 KiB
TypeScript
import { Redis } from 'ioredis';
|
|
import { env } from '$env/dynamic/private';
|
|
|
|
export type RedisValue = string | number | boolean | object | null;
|
|
|
|
interface RedisConfig {
|
|
host: string;
|
|
port: number;
|
|
password?: string;
|
|
db?: number;
|
|
}
|
|
|
|
class Cache {
|
|
private redis: Redis;
|
|
private _isReady: boolean;
|
|
private readonly _readyPromise: Promise<boolean>;
|
|
|
|
constructor(config?: Partial<RedisConfig>) {
|
|
const redisConfig: RedisConfig = {
|
|
host: env.REDIS_HOST || '127.0.0.1',
|
|
port: env.REDIS_PORT ? parseInt(env.REDIS_PORT) : 6379,
|
|
password: env.REDIS_PASSWORD,
|
|
db: env.REDIS_DB ? parseInt(env.REDIS_DB) : 0,
|
|
...config
|
|
};
|
|
|
|
this.redis = new Redis(redisConfig);
|
|
this._isReady = false;
|
|
this._readyPromise = new Promise<boolean>((resolve, reject) => {
|
|
this.redis.on('ready', () => {
|
|
this._isReady = true;
|
|
resolve(true);
|
|
});
|
|
this.redis.on('error', (err: Error) => {
|
|
if (!this._isReady) reject(err);
|
|
console.error('Redis error:', err);
|
|
});
|
|
});
|
|
}
|
|
|
|
async ready(): Promise<boolean> {
|
|
if (this._isReady) return true;
|
|
return this._readyPromise;
|
|
}
|
|
|
|
async put(key: string, value: RedisValue, ttl?: number): Promise<boolean> {
|
|
await this.ready();
|
|
try {
|
|
const serialized = JSON.stringify(value);
|
|
if (ttl) {
|
|
await this.redis.set(key, serialized, 'EX', ttl);
|
|
} else {
|
|
await this.redis.set(key, serialized);
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Cache put error:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async get<T extends RedisValue = RedisValue>(
|
|
key: string,
|
|
defaultValue: T | null = null
|
|
): Promise<T | null> {
|
|
await this.ready();
|
|
try {
|
|
const value = await this.redis.get(key);
|
|
return value ? (JSON.parse(value) as T) : defaultValue;
|
|
} catch (error) {
|
|
console.error('Cache get error:', error);
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
async has(key: string): Promise<boolean> {
|
|
await this.ready();
|
|
try {
|
|
return (await this.redis.exists(key)) === 1;
|
|
} catch (error) {
|
|
console.error('Cache has error:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async forget(key: string): Promise<boolean> {
|
|
await this.ready();
|
|
try {
|
|
return (await this.redis.del(key)) > 0;
|
|
} catch (error) {
|
|
console.error('Cache forget error:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async flush(): Promise<boolean> {
|
|
await this.ready();
|
|
try {
|
|
await this.redis.flushdb();
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Cache flush error:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async forever(key: string, value: RedisValue): Promise<boolean> {
|
|
return this.put(key, value);
|
|
}
|
|
|
|
async remember<T extends RedisValue>(
|
|
key: string,
|
|
ttl: number,
|
|
callback: () => Promise<T> | T
|
|
): Promise<T> {
|
|
const cached = await this.get<T>(key);
|
|
if (cached !== null) return cached;
|
|
|
|
const value = await callback();
|
|
await this.put(key, value, ttl);
|
|
return value;
|
|
}
|
|
|
|
async rememberForever<T extends RedisValue>(
|
|
key: string,
|
|
callback: () => Promise<T> | T
|
|
): Promise<T> {
|
|
return this.remember(key, 0, callback);
|
|
}
|
|
}
|
|
|
|
const cache = new Cache();
|
|
export default cache;
|