@citrineos/util
Version:
The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.
140 lines • 5.7 kB
JavaScript
// 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