Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
const process = require('node:process');
const consts = require('@supertenant/superconsts');

/**
 * @typedef {import('./binding').SpanID} SpanID
 * @typedef {import('./binding').SpanLabels} SpanLabels
 * @typedef {{
 *   PollInterval: number
 * }} WaitActionDefinition
 *
 * @typedef {{
 *  HttpReject: {
 *    http_rc: number
 *  }
 * }} HttpRejectActionDefinition
 *
 * @typedef {{
 *  SqlReject: {
 *   sql_rc: number,
 *   error_message: string,
 *   sql_state: string
 *  }
 * }} SqlRejectActionDefinition
 *
 * @typedef {{} | WaitActionDefinition | HttpRejectActionDefinition | SqlRejectActionDefinition} ActionDefinition
 * @typedef {{
 *     Action: number
 *     Definition: ActionDefinition
 * }} Action
 */

/** @type {Action} */
const executeAction = {
    Action: consts.Action.Execute,
    Definition: {}
};


class SuperBrain {
    constructor() {
        this._initialized = false;
        this._lib = null;
        this._config = null;
        this._serverless = ['y', 'yes', 'true', 't', '1'].indexOf(
                               (process.env.SUPERTENANT_SUPERBRAIN_SERVERLESS ?? '').toLowerCase()) !== -1;
        this._integration_circuit_breakers =
            (process.env.SUPERTENANT_SUPERMETER_INTEGRATION_CIRCUIT_BREAKERS ?? '').split(',').map((s) => s.trim());
    }

    /**
     * @param {string?} [configPath]
     * @returns {boolean}
     */
    init(configPath) {
        if (this._initialized) {
            return this._lib != null;
        }
        this._initialized = true;

        configPath = configPath ?? '';
        if (typeof (configPath) !== 'string') {
            throw 'configPath must be a string (or null/undefined)';
        }

        this._lib = require('./binding.js');
        let version = 'unknown';
        try {
            version = process.env.npm_package_version;
            version ||= 'unknown';
        } catch (e) {
        }

        // copy environment variables from node to Go Brain so they can be used when initializing it.
        for (const key in process.env) {
            if (typeof key === 'string') {
                if (key.startsWith('SUPERTENANT') || key.startsWith('SUPERMETER')) {
                    const val = process.env[key];
                    if (typeof val === 'string') {
                        this._lib.setenv(key, val);
                    }
                }
            }
        }

        try {
            if (!this._lib.init(version, configPath, 'nodejs', process.version)) {
                this._lib = null;
                return false;
            }
        } catch (e) {
            this._lib = null;
            return false;
        }
        this._updateConfig();
        return true;
    }

    /**
     * @param {number?} [timeout]
     * @returns {boolean}
     */
    shutdown(timeout) {
        timeout = timeout ?? 10000;
        if (typeof timeout != 'number' || timeout < 0) {
            throw 'timeout must be a non-negative number (or null/undefined)';
        }
        if (!this._initialized) {
            return true;
        }
        return this._lib.shutdown(timeout);
    }

    /**
     * @returns {boolean}
     */
    isServerless() {
        if (!this._initialized || this._lib == null) {
            return this._serverless;
        }
        return this._lib.is_serverless();
    }

    /**
     * @returns {boolean}
     */
    isCircuitBreakerEnabled() {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.is_circuit_breaker_enabled();
    }

    /**
     * @param {string} [integrationModule]
     * @returns {boolean}
     */
    isIntegrationCircuitBreakerEnabled(integrationModule) {
        if (typeof integrationModule !== 'string' || integrationModule === '') {
            throw 'empty/non-string integrationModule';
        }
        if (this._integration_circuit_breakers.indexOf(integrationModule) !== -1) {
            return true;
        }
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.is_integration_circuit_breaker_enabled(integrationModule);
    }

    /**
     * @returns {boolean}
     */
    enableCircuitBreaker() {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.enable_circuit_breaker();
    }

    /**
     * @param {import('@supertenant/superconsts').BrainLogLevel} [level]
     * @param {string} [msg]
     * @returns {boolean}
     */
    log(level, msg) {
        if (!this._initialized || this._lib == null) {
            return;
        }
        if (typeof level !== 'number' || typeof msg !== 'string') {
            throw 'level must be a number and msg must be a string';
        }
        this._lib.log(level, msg);
    }

    /**
     * @param {number} [taskId]
     * @param {import('@supertenant/superconsts').SpanType} [spanType]
     * @param {import('./binding.js').SpanLabels} [labels]
     * @returns {import('./binding.js').JSOpenSpanResult?}
     */
    openSpan(taskId, spanType, labels) {
        if (!this._initialized || this._lib == null) {
            return null;
        }
        return this._lib.open_span(taskId, spanType, labels);
    }

    /**
     * @param {import('./binding.js').OpenSpanResult} [openSpanResult]
     * @returns {Action}
     */
    pollSpanAction(openSpanResult) {
        if (!this._initialized || this._lib == null) {
            return executeAction;
        }
        const actionRef = this._lib.poll_span_action(openSpanResult);
        // short circuit: execute action always have the same ID.
        if (actionRef == consts.Action.Execute) {
            return executeAction;
        }
        return this._getAction(actionRef);
    }

    /**
     * @param {SpanID} [spanId]
     * @param {SpanLabels} [spanLabels]
     * @returns {boolean}
     */
    closeSpan(spanId, spanLabels) {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.close_span(spanId, spanLabels);
    }

    /**
     * @param {SpanID} [spanId]
     * @returns {boolean}
     */
    _debugHasSpan(spanId) {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib._debug_has_span(spanId);
    }

    /**
     * @param {number} parentTaskId
     * @param {number} taskId
     * @param {import('@supertenant/superconsts').Task} [taskType]
     * @returns {boolean}
     */
    createTask(parentTaskId, taskId, taskType) {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.create_task(parentTaskId, taskId, taskType);
    }

    /**
     * @param {number} parentTaskId
     * @param {import('@supertenant/superconsts').Task} [taskType]
     * @returns {number}
     */
    createTaskAutoInc(parentTaskId, taskType) {
        if (!this._initialized || this._lib == null) {
            return 0;
        }
        return this._lib.create_task_auto_inc(parentTaskId, taskType);
    }

    /**
     * @param {number} taskId
     * @returns {boolean}
     */
    finishTask(taskId) {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        return this._lib.finish_task(taskId);
    }

    /**
     * @returns {void}
     */
    printVersion() {
        if (!this._initialized || this._lib == null) {
            console.log('@supertenant/superbrain printVersion: not initialized');
            return;
        }
        return this._lib.print_version();
    }

    /**
     * @param {number} actionId
     * @returns {Action}
     */
    _getAction(actionId) {
        if (actionId == consts.Action.Execute) {
            return executeAction;
        }
        if (this._config != null && this._config.hasOwnProperty(actionId)) {
            return this._config[actionId];
        }
        // TODO don't try to update config if updated it very recently.
        this._updateConfig();  // action ID wasn't found - need to refresh config to see if now we find it.
        if (this._config != null && this._config.hasOwnProperty(actionId)) {
            return this._config[actionId];
        }
        // TODO log this only every X seconds.
        this.log(
            consts.BrainLogLevel.Error,
            'actionId ' + actionId + ' not found in ' + this._config + ', defaulting to Execute');
        return executeAction;
    }

    /**
     * @returns {boolean}
     */
    _updateConfig() {
        if (!this._initialized || this._lib == null) {
            return false;
        }
        let configStr = this._lib.get_config();
        try {
            this._config = JSON.parse(configStr);
            return true;
        } catch (err) {
            this.log(consts.BrainLogLevel.Error, 'failed to parse config: ' + err);
            return false;
        }
    }
}

/** @type {SuperBrain} */
module.exports = new SuperBrain();