UNPKG

@citrineos/util

Version:

The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.

140 lines 5.7 kB
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project // // SPDX-License-Identifier: Apache-2.0 import { plainToInstance } from 'class-transformer'; import { createClient } from 'redis'; import { Logger } from 'tslog'; /** * Implementation of cache interface with redis storage */ export class RedisCache { _client; _logger; constructor(clientOptions, logger) { this._logger = logger ? logger.getSubLogger({ name: this.constructor.name }) : new Logger({ name: this.constructor.name }); this._client = clientOptions ? createClient(clientOptions) : createClient(); this._client.on('connect', () => this._logger.info('Redis client connected')); this._client.on('ready', () => this._logger.info('Redis client ready to use')); this._client.on('error', (err) => this._logger.error('Redis error', err)); this._client.on('end', () => this._logger.info('Redis client disconnected')); this._client.connect().catch((error) => { this._logger.error('Error connecting to Redis', error); }); } exists(key, namespace) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return this._client.exists(key).then((result) => result === 1); } async existsAnyInNamespace(namespace) { const keys = await this._client.keys(`${namespace}:*`); return keys.length > 0; } remove(key, namespace) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return this._client.del(key).then((result) => result === 1); } onChange(key, waitSeconds, namespace, classConstructor) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return new Promise((resolve) => { // Create a Redis subscriber to listen for operations affecting the key const subscriber = createClient(); // Channel: Key-space, message: the name of the event, which is the command executed on the key subscriber .subscribe(`__keyspace@0__:${key}`, (channel, message) => { switch (message) { case 'set': resolve(this.get(key, namespace, classConstructor)); subscriber .quit() .then() .catch((error) => { // Ignore error if client is already closed if (!error.message?.includes('The client is closed')) { this._logger.error('Error quitting subscriber', error); } }); break; case 'del': case 'expire': resolve(null); subscriber .quit() .then() .catch((error) => { // Ignore error if client is already closed if (!error.message?.includes('The client is closed')) { this._logger.error('Error quitting subscriber', error); } }); break; default: // Do nothing break; } }) .then() .catch((error) => { this._logger.error('Error creating Redis subscriber', error); }); setTimeout(() => { resolve(this.get(key, namespace, classConstructor)); subscriber .quit() .then() .catch((error) => { // Ignore error if client is already closed if (!error.message?.includes('The client is closed')) { this._logger.error('Error closing Redis subscriber', error); } }); }, waitSeconds * 1000); }); } get(key, namespace, classConstructor) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return this._client.get(key).then((result) => { if (result) { if (classConstructor) { return plainToInstance(classConstructor(), JSON.parse(result)); } return result; } return null; }); } set(key, value, namespace, expireSeconds) { namespace = namespace || 'default'; key = `${namespace}:${key}`; const setOptions = expireSeconds ? { EX: expireSeconds } : undefined; return this._client.set(key, value, setOptions).then((result) => { if (result) { return result === 'OK'; } return false; }); } setIfNotExist(key, value, namespace, expireSeconds) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return this._client .set(key, value, expireSeconds ? { EX: expireSeconds, NX: true } : { NX: true }) .then((result) => { if (result) { return result === 'OK'; } return false; }); } updateExpiration(key, expireSeconds, namespace) { namespace = namespace || 'default'; key = `${namespace}:${key}`; return this._client.expire(key, expireSeconds); } } //# sourceMappingURL=redis.js.map