togethere.cloud/private_html/disciplines/ping-pong/1v1/node-server/src/redisClient.js

141 lines
3.3 KiB
JavaScript

import { createClient } from 'redis';
import { config } from './config.js';
class InMemoryRedisClient {
constructor() {
this.mode = 'memory';
this._strings = new Map();
this._sortedSets = new Map();
}
_now() {
return Date.now();
}
_purgeExpiredStrings() {
const now = this._now();
for (const [key, entry] of this._strings.entries()) {
if (entry.expiresAt !== null && entry.expiresAt <= now) {
this._strings.delete(key);
}
}
}
_getStringEntry(key) {
this._purgeExpiredStrings();
return this._strings.get(key) ?? null;
}
_getSortedSet(key) {
let set = this._sortedSets.get(key);
if (!set) {
set = new Map();
this._sortedSets.set(key, set);
}
return set;
}
async connect() {
return this;
}
on() {
return this;
}
async zAdd(key, entries) {
const set = this._getSortedSet(key);
for (const entry of entries) {
set.set(String(entry.value), Number(entry.score));
}
}
async zCard(key) {
return this._getSortedSet(key).size;
}
async zRem(key, ...values) {
const set = this._getSortedSet(key);
let removed = 0;
for (const value of values) {
if (set.delete(String(value))) {
removed += 1;
}
}
return removed;
}
async zRange(key, start, stop) {
const items = Array.from(this._getSortedSet(key).entries())
.sort((left, right) => left[1] - right[1] || left[0].localeCompare(right[0]))
.map(([value]) => value);
const normalizedStop = stop < 0 ? items.length + stop : stop;
return items.slice(start, normalizedStop + 1);
}
async set(key, value, options = {}) {
const existing = this._getStringEntry(key);
if (options.NX && existing) {
return null;
}
let expiresAt = null;
if (typeof options.PX === 'number') {
expiresAt = this._now() + options.PX;
} else if (typeof options.EX === 'number') {
expiresAt = this._now() + (options.EX * 1000);
}
this._strings.set(key, { value: String(value), expiresAt });
return 'OK';
}
async get(key) {
const entry = this._getStringEntry(key);
return entry ? entry.value : null;
}
async del(key) {
this._purgeExpiredStrings();
const existed = this._strings.delete(key);
return existed ? 1 : 0;
}
}
export async function createRedis() {
const client = createClient({
url: config.redisUrl,
socket: {
connectTimeout: 3000,
reconnectStrategy: false,
},
});
client.on('error', (err) => {
console.error('[redis] error', err);
});
try {
await client.connect();
client.mode = 'redis';
console.log('[redis] connected');
return client;
} catch (error) {
try {
if (typeof client.disconnect === 'function') {
await client.disconnect();
} else if (typeof client.quit === 'function') {
await client.quit();
}
} catch {
// Ignore cleanup errors and continue with in-memory fallback.
}
console.warn('[redis] unavailable, using in-memory fallback:', error instanceof Error ? error.message : String(error));
return new InMemoryRedisClient();
}
}
export function key(...parts) {
return config.redisKeyPrefix + parts.join('');
}