2023-07-30 18:27:45 +02:00
|
|
|
import {
|
2023-08-04 13:48:12 +02:00
|
|
|
Bulk,
|
2023-07-30 18:27:45 +02:00
|
|
|
connect,
|
|
|
|
Redis,
|
|
|
|
RedisConnectOptions,
|
|
|
|
RedisValue,
|
|
|
|
} from "https://deno.land/x/redis@v0.31.0/mod.ts";
|
2023-08-05 22:16:14 +02:00
|
|
|
import { createLogger } from "@lib/log.ts";
|
2023-08-13 02:26:22 +02:00
|
|
|
import { getTimeCacheKey } from "@lib/string.ts";
|
2023-07-30 18:27:45 +02:00
|
|
|
|
|
|
|
const REDIS_HOST = Deno.env.get("REDIS_HOST");
|
|
|
|
const REDIS_PASS = Deno.env.get("REDIS_PASS") || "";
|
|
|
|
const REDIS_PORT = Deno.env.get("REDIS_PORT");
|
|
|
|
|
2023-08-05 22:16:14 +02:00
|
|
|
const log = createLogger("cache");
|
|
|
|
|
2023-08-04 22:35:25 +02:00
|
|
|
async function createCache<T>(): Promise<Redis> {
|
2023-07-30 18:27:45 +02:00
|
|
|
if (REDIS_HOST) {
|
|
|
|
const conf: RedisConnectOptions = {
|
|
|
|
hostname: REDIS_HOST,
|
|
|
|
port: REDIS_PORT || 6379,
|
2023-08-04 13:48:12 +02:00
|
|
|
maxRetryCount: 2,
|
2023-07-30 18:27:45 +02:00
|
|
|
};
|
|
|
|
if (REDIS_PASS) {
|
|
|
|
conf.password = REDIS_PASS;
|
|
|
|
}
|
2023-08-04 13:48:12 +02:00
|
|
|
try {
|
|
|
|
const client = await connect(conf);
|
2023-08-05 22:16:14 +02:00
|
|
|
log.info("redis connected");
|
2023-08-04 13:48:12 +02:00
|
|
|
return client;
|
|
|
|
} catch (_err) {
|
2023-08-05 22:16:14 +02:00
|
|
|
log.info("cant connect to redis, falling back to mock");
|
2023-08-04 13:48:12 +02:00
|
|
|
}
|
2023-07-30 18:27:45 +02:00
|
|
|
}
|
|
|
|
|
2023-08-04 13:48:12 +02:00
|
|
|
const mockRedis = new Map<string, RedisValue>();
|
|
|
|
|
|
|
|
return {
|
2023-08-04 22:35:25 +02:00
|
|
|
async keys() {
|
|
|
|
return mockRedis.keys();
|
|
|
|
},
|
|
|
|
async delete(key: string) {
|
|
|
|
mockRedis.delete(key);
|
|
|
|
return key;
|
|
|
|
},
|
2023-08-04 13:48:12 +02:00
|
|
|
async set(key: string, value: RedisValue) {
|
|
|
|
mockRedis.set(key, value);
|
|
|
|
return value.toString();
|
|
|
|
},
|
|
|
|
async get(key: string) {
|
|
|
|
return mockRedis.get(key) as Bulk;
|
|
|
|
},
|
|
|
|
};
|
2023-07-30 18:27:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const cache = await createCache();
|
|
|
|
|
|
|
|
export async function get<T>(id: string, binary = false) {
|
|
|
|
if (binary && !(cache instanceof Map)) {
|
2023-08-01 03:15:15 +02:00
|
|
|
const cacheHit = await cache.sendCommand("GET", [id], {
|
2023-07-30 18:27:45 +02:00
|
|
|
returnUint8Arrays: true,
|
|
|
|
}) as T;
|
2023-08-01 03:15:15 +02:00
|
|
|
return cacheHit;
|
2023-07-30 18:27:45 +02:00
|
|
|
}
|
2023-08-01 03:15:15 +02:00
|
|
|
const cacheHit = await cache.get(id) as T;
|
|
|
|
return cacheHit;
|
2023-07-30 18:27:45 +02:00
|
|
|
}
|
2023-08-01 18:35:35 +02:00
|
|
|
export function clearAll() {
|
|
|
|
if ("flushall" in cache) {
|
|
|
|
return cache.flushall();
|
|
|
|
} else {
|
|
|
|
for (const k of cache.keys()) {
|
|
|
|
cache.delete(k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function expire(id: string, seconds: number) {
|
|
|
|
if ("expire" in cache) {
|
|
|
|
return cache.expire(id, seconds);
|
|
|
|
}
|
|
|
|
}
|
2023-07-30 18:27:45 +02:00
|
|
|
|
2023-08-02 01:58:03 +02:00
|
|
|
type RedisOptions = {
|
|
|
|
expires?: number;
|
2023-08-13 02:26:22 +02:00
|
|
|
noLog?: boolean;
|
2023-08-02 01:58:03 +02:00
|
|
|
};
|
|
|
|
|
2023-08-04 22:35:25 +02:00
|
|
|
export function del(key: string) {
|
|
|
|
return cache.del(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function keys(prefix: string) {
|
|
|
|
return cache.keys(prefix);
|
|
|
|
}
|
|
|
|
|
2023-08-11 16:13:20 +02:00
|
|
|
export function set<T extends RedisValue>(
|
2023-08-02 01:58:03 +02:00
|
|
|
id: string,
|
|
|
|
content: T,
|
|
|
|
options?: RedisOptions,
|
|
|
|
) {
|
2023-08-13 02:26:22 +02:00
|
|
|
if (options?.noLog !== true) log.debug("storing ", { id });
|
2023-08-11 16:13:20 +02:00
|
|
|
return cache.set(id, content, { ex: options?.expires || undefined });
|
2023-07-30 18:27:45 +02:00
|
|
|
}
|
2023-08-09 23:51:40 +02:00
|
|
|
|
|
|
|
export const cacheFunction = async <T extends (() => Promise<unknown>)>(
|
|
|
|
{
|
|
|
|
fn,
|
|
|
|
id,
|
|
|
|
options = {},
|
|
|
|
}: {
|
|
|
|
fn: T;
|
|
|
|
id: string;
|
|
|
|
options?: RedisOptions;
|
|
|
|
},
|
|
|
|
): Promise<Awaited<ReturnType<T>>> => {
|
|
|
|
const cacheResult = await get(id) as string;
|
|
|
|
|
|
|
|
if (cacheResult) {
|
|
|
|
return JSON.parse(cacheResult) as Awaited<ReturnType<typeof fn>>;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = await fn();
|
|
|
|
|
|
|
|
set(id, JSON.stringify(result), options);
|
|
|
|
|
|
|
|
return result as Awaited<ReturnType<typeof fn>>;
|
|
|
|
};
|