redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
258 lines • 9.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Configuration = void 0;
const redis_smq_common_1 = require("redis-smq-common");
const configuration_not_found_error_js_1 = require("../errors/configuration-not-found.error.js");
const redis_keys_js_1 = require("../common/redis/redis-keys/redis-keys.js");
const parse_config_js_1 = require("./parse-config.js");
const default_config_js_1 = require("./default-config.js");
const with_shared_pool_connection_js_1 = require("../common/redis/redis-connection-pool/with-shared-pool-connection.js");
const index_js_1 = require("../errors/index.js");
const scripts_js_1 = require("../common/redis/scripts.js");
var EConfigurationField;
(function (EConfigurationField) {
EConfigurationField["VERSION"] = "version";
EConfigurationField["DATA"] = "data";
})(EConfigurationField || (EConfigurationField = {}));
class Configuration extends redis_smq_common_1.EventEmitter {
constructor() {
super();
this.operationLock = false;
this.operationQueue = [];
this.completionWaiters = [];
this.config = {
data: (0, parse_config_js_1.parseConfig)(default_config_js_1.defaultConfig),
version: 0,
};
}
static initialize(cb) {
if (!this.validateInitState(cb))
return;
this.state.goingUp();
const instance = new Configuration();
instance.reload((err) => {
if (err instanceof configuration_not_found_error_js_1.ConfigurationNotFoundError) {
return instance.save(instance.config.data, (saveErr) => {
if (saveErr) {
this.state.rollback();
this.instance = null;
this.handleInitComplete(saveErr, cb);
}
else {
this.instance = instance;
this.state.commit();
this.handleInitComplete(null, cb);
}
});
}
if (err) {
this.state.rollback();
this.instance = null;
return this.handleInitComplete(err, cb);
}
this.instance = instance;
this.state.commit();
this.handleInitComplete(null, cb);
});
}
static getInstance() {
const state = this.state;
if (state.isDown()) {
throw new redis_smq_common_1.OperationNotAllowedError({
message: 'Configuration not initialized. Call initialize() first.',
});
}
if (state.isGoingUp()) {
throw new redis_smq_common_1.OperationNotAllowedError({
message: 'Configuration is initializing. Please wait.',
});
}
if (state.isGoingDown()) {
throw new redis_smq_common_1.OperationNotAllowedError({
message: 'Configuration is shutting down.',
});
}
if (!this.instance) {
throw new redis_smq_common_1.PanicError({
message: 'Configuration instance is null despite being initialized',
});
}
return this.instance;
}
static getConfig() {
return this.getInstance().getConfig().data;
}
static shutdown(cb) {
const state = this.state;
if (state.isDown())
return cb();
if (state.isGoingDown()) {
return cb(new redis_smq_common_1.OperationNotAllowedError({ message: 'Already shutting down' }));
}
if (state.isGoingUp()) {
return cb(new redis_smq_common_1.OperationNotAllowedError({
message: 'Cannot shutdown while initializing',
}));
}
state.goingDown();
if (this.instance) {
this.instance.waitForCompletion(() => {
this.instance = null;
state.commit();
cb();
});
}
else {
state.commit();
cb();
}
}
getConfig() {
return Object.freeze(Object.assign({}, this.config));
}
reload(cb) {
(0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, done) => {
const key = redis_keys_js_1.redisKeys.getMainKeys().keyConfiguration;
client.hgetall(key, (err, result) => {
if (err)
return done(err);
const version = result === null || result === void 0 ? void 0 : result[EConfigurationField.VERSION];
const data = result === null || result === void 0 ? void 0 : result[EConfigurationField.DATA];
if (!version || !data) {
return done(new configuration_not_found_error_js_1.ConfigurationNotFoundError());
}
Object.assign(this.config.data, JSON.parse(data));
this.config.version = Number(version);
done(null, this.config);
});
}, cb);
}
save(config, cb) {
this.runExclusive((done) => {
(0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => {
const key = redis_keys_js_1.redisKeys.getMainKeys().keyConfiguration;
const configData = JSON.stringify(config);
client.runScript(scripts_js_1.ERedisScriptName.SAVE_CONFIG, [key], [
EConfigurationField.VERSION,
EConfigurationField.DATA,
this.config.version,
configData,
], (err, reply) => {
if (err)
return cb(err);
if (typeof reply === 'number') {
Object.assign(this.config.data, config);
this.config.version = Number(reply);
this.emit('configuration.updated', this.config.data, this.config.version);
return cb();
}
if (reply === 'VERSION_MISMATCH') {
return this.reload((loadErr) => {
if (loadErr)
return cb(loadErr);
cb(new index_js_1.ConfigurationUpdateError({
message: 'Version mismatch during save',
}));
});
}
cb(new index_js_1.UnexpectedScriptReplyError({
message: 'Unexpected result from config save',
metadata: { reply },
}));
});
}, done);
}, cb);
}
updateConfigFromEvent(config, version, cb) {
this.runExclusive((done) => {
if (version <= this.config.version) {
return done(new index_js_1.ConfigurationUpdateError({
message: `Version mismatch: current=${this.config.version}, event=${version}`,
}));
}
Object.assign(this.config.data, config);
this.config.version = version;
this.emit('configuration.updated', this.config.data, this.config.version);
done();
}, cb);
}
static validateInitState(cb) {
const state = this.state;
if (state.isUp()) {
cb(new redis_smq_common_1.OperationNotAllowedError({
message: 'Configuration already initialized',
}));
return false;
}
if (state.isGoingUp()) {
this.initQueue.push(cb);
return false;
}
if (state.isGoingDown()) {
cb(new redis_smq_common_1.OperationNotAllowedError({
message: 'Cannot initialize while shutting down',
}));
return false;
}
return true;
}
static handleInitComplete(err, cb) {
const queued = this.initQueue.splice(0);
cb(err);
queued.forEach((qcb) => qcb(err));
}
runExclusive(fn, done) {
const execute = () => {
this.operationLock = true;
const release = (err) => {
try {
done(err);
}
finally {
this.operationLock = false;
this.notifyWaiters();
this.processNext();
}
};
try {
fn(release);
}
catch (err) {
release(err instanceof Error ? err : new index_js_1.ConfigurationUpdateError());
}
};
if (this.operationLock) {
this.operationQueue.push(execute);
}
else {
execute();
}
}
processNext() {
if (this.operationLock || this.operationQueue.length === 0)
return;
setImmediate(() => {
var _a;
if (!this.operationLock) {
(_a = this.operationQueue.shift()) === null || _a === void 0 ? void 0 : _a();
}
});
}
notifyWaiters() {
if (!this.operationLock && this.operationQueue.length === 0) {
const waiters = this.completionWaiters.splice(0);
waiters.forEach((waiter) => waiter());
}
}
waitForCompletion(cb) {
if (!this.operationLock && this.operationQueue.length === 0) {
return cb();
}
this.completionWaiters.push(cb);
}
}
exports.Configuration = Configuration;
Configuration.instance = null;
Configuration.state = new redis_smq_common_1.PowerSwitch();
Configuration.initQueue = [];
//# sourceMappingURL=configuration.js.map