Files
queue/packages/queue/src/redis-connection.ts

96 lines
2.4 KiB
TypeScript

import Redis, { type RedisOptions } from 'ioredis';
const ERR_MISSING_REDIS_URL =
'Missing required Valkey/Redis connection URL. Set VALKEY_URL or REDIS_URL.';
export interface RedisHealthCheck {
readonly checkedAt: number;
readonly latencyMs: number;
readonly ok: boolean;
readonly response?: string;
readonly error?: string;
}
export interface RedisPingClient {
ping(): Promise<string>;
}
export type RedisClientConstructor<TClient> = new (
url: string,
options?: RedisOptions,
) => TClient;
export interface CreateRedisClientOptions<TClient> {
readonly env?: NodeJS.ProcessEnv;
readonly redisConstructor?: RedisClientConstructor<TClient>;
readonly redisOptions?: RedisOptions;
}
export function resolveRedisUrl(env: NodeJS.ProcessEnv = process.env): string {
const resolvedUrl = env.VALKEY_URL ?? env.REDIS_URL;
if (typeof resolvedUrl !== 'string' || resolvedUrl.trim().length === 0) {
throw new Error(ERR_MISSING_REDIS_URL);
}
return resolvedUrl;
}
export function createRedisClient<TClient = Redis>(
options: CreateRedisClientOptions<TClient> = {},
): TClient {
const redisUrl = resolveRedisUrl(options.env);
const RedisCtor =
options.redisConstructor ??
(Redis as unknown as RedisClientConstructor<TClient>);
return new RedisCtor(redisUrl, {
maxRetriesPerRequest: null,
...options.redisOptions,
});
}
export async function runRedisHealthCheck(
client: RedisPingClient,
): Promise<RedisHealthCheck> {
const startedAt = process.hrtime.bigint();
try {
const response = await client.ping();
const elapsedMs = Number((process.hrtime.bigint() - startedAt) / 1_000_000n);
return {
checkedAt: Date.now(),
latencyMs: elapsedMs,
ok: true,
response,
};
} catch (error) {
const elapsedMs = Number((process.hrtime.bigint() - startedAt) / 1_000_000n);
const message =
error instanceof Error ? error.message : 'Unknown redis health check error';
return {
checkedAt: Date.now(),
latencyMs: elapsedMs,
ok: false,
error: message,
};
}
}
export async function assertRedisHealthy(
client: RedisPingClient,
): Promise<RedisHealthCheck> {
const health = await runRedisHealthCheck(client);
if (!health.ok) {
throw new Error(
`Redis health check failed after ${health.latencyMs}ms: ${health.error ?? 'unknown error'}`,
);
}
return health;
}