@citrineos/util
Version:
The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.
165 lines • 7.51 kB
JavaScript
;
// Copyright (c) 2023 S44, LLC
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryCache = void 0;
const class_transformer_1 = require("class-transformer");
/**
* Implementation of cache interface with memory storage
*/
class MemoryCache {
constructor() {
const keySubscriptionMap = new Map();
const subscriptionHandler = {
// Returns value on keySubscriptions when Map.set(key, value) is called
set(target, property, value) {
const setOutcome = Reflect.set(target, property, value);
if (typeof property === 'string' && keySubscriptionMap.has(property) && setOutcome) {
(keySubscriptionMap === null || keySubscriptionMap === void 0 ? void 0 : keySubscriptionMap.get(property))(value);
}
return setOutcome;
},
// Returns null on keySubscriptions when Map.delete(key) is called
deleteProperty(target, property) {
const deleteOutcome = Reflect.deleteProperty(target, property);
if (typeof property === 'string' && keySubscriptionMap.has(property) && deleteOutcome) {
(keySubscriptionMap === null || keySubscriptionMap === void 0 ? void 0 : keySubscriptionMap.get(property))(null);
}
return deleteOutcome;
},
// Here to support Map.get and Map.has, does not alter behavior
get(target, property) {
const value = Reflect.get(target, property);
if (typeof value === 'function') {
// Return a bound version of the method to the original target
return value.bind(target);
}
return value;
},
};
this._cache = new Proxy(new Map(), subscriptionHandler);
this._keySubscriptionMap = keySubscriptionMap;
this._keySubscriptionPromiseMap = new Map();
this._timeoutMap = new Map();
}
exists(key, namespace) {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
return Promise.resolve(this._cache.has(namespaceKey));
}
remove(key, namespace) {
return __awaiter(this, void 0, void 0, function* () {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
return this._cache.delete(namespaceKey);
});
}
onChange(key, waitSeconds, namespace, classConstructor) {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
// Either get existing promise awaiting change on this key or create a new one and store it.
// This way, any number of threads can wait for the same key at the same time.
// Type must include 'undefined' due to Map.get(key)'s return type, however in no case can it actually be undefined.
const onChangeValuePromise = this._keySubscriptionPromiseMap.has(namespaceKey)
? this._keySubscriptionPromiseMap.get(namespaceKey)
: this._keySubscriptionPromiseMap
.set(namespaceKey, new Promise((resolve) => {
this._keySubscriptionMap.set(namespaceKey, (value) => {
resolve(value);
this._keySubscriptionMap.delete(namespaceKey);
this._keySubscriptionPromiseMap.delete(namespaceKey);
});
}))
.get(namespaceKey);
return Promise.race([
onChangeValuePromise === null || onChangeValuePromise === void 0 ? void 0 : onChangeValuePromise.then((value) => {
if (typeof value === 'string') {
if (classConstructor) {
return (0, class_transformer_1.plainToInstance)(classConstructor(), JSON.parse(value));
}
else {
return value;
}
}
else {
return value;
}
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(this.get(key, namespace, classConstructor));
}, waitSeconds * 1000);
}),
]);
}
get(key, namespace, classConstructor) {
return __awaiter(this, void 0, void 0, function* () {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
const result = this._cache.get(namespaceKey);
if (result) {
if (classConstructor) {
return (0, class_transformer_1.plainToInstance)(classConstructor(), JSON.parse(result));
}
return result;
}
return null;
});
}
set(key, value, namespace, expireSeconds) {
return __awaiter(this, void 0, void 0, function* () {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
this._cache.set(namespaceKey, value);
if (this._timeoutMap.has(namespaceKey)) {
clearTimeout(this._timeoutMap.get(namespaceKey));
}
if (expireSeconds) {
this._timeoutMap.set(namespaceKey, setTimeout(() => {
this._cache.delete(namespaceKey);
}, expireSeconds * 1000));
}
this.resolveOnChange(namespaceKey, value);
return true;
});
}
setIfNotExist(key, value, namespace, expireSeconds) {
return __awaiter(this, void 0, void 0, function* () {
namespace = namespace || 'default';
const namespaceKey = `${namespace}:${key}`;
if (this._cache.has(namespaceKey)) {
return false;
}
this._cache.set(namespaceKey, value);
if (this._timeoutMap.has(namespaceKey)) {
clearTimeout(this._timeoutMap.get(namespaceKey));
}
if (expireSeconds) {
this._timeoutMap.set(namespaceKey, setTimeout(() => {
this._cache.delete(namespaceKey);
}, expireSeconds * 1000));
}
this.resolveOnChange(namespaceKey, value);
return true;
});
}
resolveOnChange(namespaceKey, value) {
const resolveOnChangeCallback = this._keySubscriptionMap.get(namespaceKey);
if (resolveOnChangeCallback) {
resolveOnChangeCallback(value);
}
}
}
exports.MemoryCache = MemoryCache;
//# sourceMappingURL=memory.js.map