UNPKG

redis-smq

Version:

A high-performance, reliable, and scalable message queue for Node.js.

258 lines 9.83 kB
"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