"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CubejsServerCore = void 0;
/* eslint-disable global-require,no-return-assign */
const crypto_1 = __importDefault(require("crypto"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const lru_cache_1 = require("lru-cache");
const is_docker_1 = __importDefault(require("is-docker"));
const p_limit_1 = __importDefault(require("p-limit"));
const api_gateway_1 = require("@cubejs-backend/api-gateway");
const shared_1 = require("@cubejs-backend/shared");
const RefreshScheduler_1 = require("./RefreshScheduler");
const OrchestratorApi_1 = require("./OrchestratorApi");
const CompilerApi_1 = require("./CompilerApi");
const DevServer_1 = require("./DevServer");
const agentCollect_1 = require("./agentCollect");
const OrchestratorStorage_1 = require("./OrchestratorStorage");
const logger_1 = require("./logger");
const OptsHandler_1 = require("./OptsHandler");
const DriverResolvers_1 = require("./DriverResolvers");
const { version } = require('../../../package.json');
function wrapToFnIfNeeded(possibleFn) {
    if (typeof possibleFn === 'function') {
        return possibleFn;
    }
    return () => possibleFn;
}
class AcceptAllAcceptor {
    async shouldAccept() {
        return { accepted: true };
    }
    async shouldAcceptHttp() {
        return { accepted: true };
    }
    async shouldAcceptWs() {
        return { accepted: true };
    }
}
class CubejsServerCore {
    systemOptions;
    /**
     * Returns core version based on package.json.
     */
    static version() {
        return version;
    }
    /**
     * Resolve driver module name by db type.
     */
    static driverDependencies = DriverResolvers_1.driverDependencies;
    /**
     * Resolve driver module object by db type.
     */
    static lookupDriverClass = DriverResolvers_1.lookupDriverClass;
    /**
     * Create new driver instance by specified database type.
     */
    static createDriver = DriverResolvers_1.createDriver;
    /**
     * Calculate and returns driver's max pool number.
     */
    static getDriverMaxPool = DriverResolvers_1.getDriverMaxPool;
    repository;
    devServer;
    orchestratorStorage = new OrchestratorStorage_1.OrchestratorStorage();
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    repositoryFactory;
    contextToDbType;
    contextToExternalDbType;
    compilerCache;
    contextToOrchestratorId;
    contextToCubeStoreRouterId;
    preAggregationsSchema;
    scheduledRefreshTimeZones;
    orchestratorOptions;
    logger;
    optsHandler;
    preAgentLogger;
    options;
    contextToAppId = () => process.env.CUBEJS_APP || 'STANDALONE';
    standalone = true;
    maxCompilerCacheKeep = null;
    scheduledRefreshTimerInterval = null;
    driver = null;
    apiGatewayInstance = null;
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    event;
    projectFingerprint = null;
    coreServerVersion = null;
    contextAcceptor;
    constructor(opts = {}, systemOptions) {
        this.systemOptions = systemOptions;
        this.coreServerVersion = version;
        this.logger = opts.logger || (0, logger_1.createLogger)(process.env.NODE_ENV === 'production', (0, shared_1.getEnv)('logLevel'));
        this.optsHandler = new OptsHandler_1.OptsHandler(this, opts, systemOptions);
        this.options = this.optsHandler.getCoreInitializedOptions();
        this.repository = new shared_1.FileRepository(this.options.schemaPath);
        this.repositoryFactory = this.options.repositoryFactory || (() => this.repository);
        this.contextToDbType = this.options.dbType;
        this.contextToExternalDbType = wrapToFnIfNeeded(this.options.externalDbType);
        this.preAggregationsSchema = wrapToFnIfNeeded(this.options.preAggregationsSchema);
        this.orchestratorOptions = wrapToFnIfNeeded(this.options.orchestratorOptions);
        this.scheduledRefreshTimeZones = wrapToFnIfNeeded(this.options.scheduledRefreshTimeZones || []);
        this.compilerCache = new lru_cache_1.LRUCache({
            max: this.options.compilerCacheSize || 250,
            ttl: this.options.maxCompilerCacheKeepAlive,
            updateAgeOnGet: this.options.updateCompilerCacheKeepAlive,
            // needed to clear the setInterval timer for proactive cache internal cleanups
            dispose: (v) => v.dispose(),
        });
        if (this.options.contextToAppId) {
            this.contextToAppId = this.options.contextToAppId;
            this.standalone = false;
        }
        this.contextAcceptor = this.createContextAcceptor();
        if (this.options.contextToDataSourceId) {
            throw new Error('contextToDataSourceId has been deprecated and removed. Use contextToOrchestratorId instead.');
        }
        this.contextToOrchestratorId = this.options.contextToOrchestratorId || (() => 'STANDALONE');
        this.contextToCubeStoreRouterId = this.options.contextToCubeStoreRouterId;
        // proactively free up old cache values occasionally
        if (this.options.maxCompilerCacheKeepAlive) {
            this.maxCompilerCacheKeep = setInterval(() => this.compilerCache.purgeStale(), this.options.maxCompilerCacheKeepAlive);
        }
        this.startScheduledRefreshTimer();
        this.event = async (event, props) => {
            if (!this.options.telemetry) {
                return;
            }
            if (!this.projectFingerprint) {
                try {
                    this.projectFingerprint = crypto_1.default.createHash('md5')
                        .update(JSON.stringify(fs_extra_1.default.readJsonSync('package.json')))
                        .digest('hex');
                }
                catch (e) {
                    (0, shared_1.internalExceptions)(e);
                }
            }
            const internalExceptionsEnv = (0, shared_1.getEnv)('internalExceptions');
            try {
                await (0, shared_1.track)({
                    timestamp: new Date().toJSON(),
                    event,
                    projectFingerprint: this.projectFingerprint,
                    coreServerVersion: this.coreServerVersion,
                    dockerVersion: (0, shared_1.getEnv)('dockerImageVersion'),
                    isDocker: (0, is_docker_1.default)(),
                    internalExceptions: internalExceptionsEnv !== 'false' ? internalExceptionsEnv : undefined,
                    ...props
                });
            }
            catch (e) {
                (0, shared_1.internalExceptions)(e);
            }
        };
        this.initAgent();
        if (this.options.devServer && !this.isReadyForQueryProcessing()) {
            this.event('first_server_start');
        }
        if (this.options.devServer) {
            this.devServer = new DevServer_1.DevServer(this, {
                dockerVersion: (0, shared_1.getEnv)('dockerImageVersion'),
                externalDbTypeFn: this.contextToExternalDbType,
                isReadyForQueryProcessing: this.isReadyForQueryProcessing.bind(this)
            });
            const oldLogger = this.logger;
            this.logger = ((msg, params) => {
                if (msg === 'Load Request' ||
                    msg === 'Load Request Success' ||
                    msg === 'Orchestrator error' ||
                    msg === 'Internal Server Error' ||
                    msg === 'User Error' ||
                    msg === 'Compiling schema' ||
                    msg === 'Recompiling schema' ||
                    msg === 'Slow Query Warning' ||
                    msg === 'Cube SQL Error') {
                    const props = {
                        error: params.error,
                        ...(params.apiType ? { apiType: params.apiType } : {}),
                        ...(params.protocol ? { protocol: params.protocol } : {}),
                        ...(params.appName ? { appName: params.appName } : {}),
                        ...(params.sanitizedQuery ? { query: params.sanitizedQuery } : {}),
                    };
                    this.event(msg, props);
                }
                oldLogger(msg, params);
            });
            if (!process.env.CI) {
                process.on('uncaughtException', this.onUncaughtException);
            }
        }
        else {
            const oldLogger = this.logger;
            let loadRequestCount = 0;
            let loadSqlRequestCount = 0;
            this.logger = ((msg, params) => {
                if (msg === 'Load Request Success') {
                    if (params.apiType === 'sql') {
                        loadSqlRequestCount++;
                    }
                    else {
                        loadRequestCount++;
                    }
                }
                else if (msg === 'Cube SQL Error') {
                    const props = {
                        error: params.error,
                        apiType: params.apiType,
                        protocol: params.protocol,
                        ...(params.appName ? { appName: params.appName } : {}),
                        ...(params.sanitizedQuery ? { query: params.sanitizedQuery } : {}),
                    };
                    this.event(msg, props);
                }
                oldLogger(msg, params);
            });
            if (this.options.telemetry) {
                setInterval(() => {
                    if (loadRequestCount > 0 || loadSqlRequestCount > 0) {
                        this.event('Load Request Success Aggregated', { loadRequestSuccessCount: loadRequestCount, loadSqlRequestSuccessCount: loadSqlRequestCount });
                    }
                    loadRequestCount = 0;
                    loadSqlRequestCount = 0;
                }, 60000);
            }
            this.event('Server Start');
        }
    }
    createContextAcceptor() {
        return new AcceptAllAcceptor();
    }
    /**
     * Determines whether current instance is ready to process queries.
     */
    isReadyForQueryProcessing() {
        return this.optsHandler.configuredForQueryProcessing();
    }
    startScheduledRefreshTimer() {
        if (!this.isReadyForQueryProcessing()) {
            return [false, 'Instance is not ready for query processing, refresh scheduler is disabled'];
        }
        if (this.scheduledRefreshTimerInterval) {
            return [true, null];
        }
        if (this.optsHandler.configuredForScheduledRefresh()) {
            const scheduledRefreshTimer = this.optsHandler.getScheduledRefreshInterval();
            this.scheduledRefreshTimerInterval = (0, shared_1.createCancelableInterval)(() => this.handleScheduledRefreshInterval({}), {
                interval: scheduledRefreshTimer,
                onDuplicatedExecution: (intervalId) => this.logger('Refresh Scheduler Interval', {
                    warning: `Previous interval #${intervalId} was not finished with ${scheduledRefreshTimer} interval`
                }),
                onDuplicatedStateResolved: (intervalId, elapsed) => this.logger('Refresh Scheduler Long Execution', {
                    warning: `Interval #${intervalId} finished after ${(0, shared_1.formatDuration)(elapsed)}. Please consider reducing total number of partitions by using rollup_lambda pre-aggregations.`
                })
            });
            return [true, null];
        }
        return [false, 'Instance configured without scheduler refresh timer, refresh scheduler is disabled'];
    }
    /**
     * Reload global variables and updates drivers according to new values.
     *
     * Note: currently there is no way to change CubejsServerCore.options,
     * as so, we are not refreshing CubejsServerCore.options.dbType and
     * CubejsServerCore.options.driverFactory here. If this will be changed,
     * we will need to do this in order to update driver.
     */
    reloadEnvVariables() {
        this.driver = null;
        this.options.externalDbType = this.options.externalDbType ||
            process.env.CUBEJS_EXT_DB_TYPE;
        this.options.schemaPath = process.env.CUBEJS_SCHEMA_PATH || this.options.schemaPath;
        this.contextToExternalDbType = wrapToFnIfNeeded(this.options.externalDbType);
    }
    initAgent() {
        const agentEndpointUrl = (0, shared_1.getEnv)('agentEndpointUrl');
        if (agentEndpointUrl) {
            const oldLogger = this.logger;
            this.preAgentLogger = oldLogger;
            this.logger = (msg, params) => {
                // Filling timestamp as much as earlier as we can, otherwise it can be incorrect. Because next code is async
                // with await points which can be delayed with Node.js micro-tasking.
                params.timestamp = params.timestamp || new Date().toJSON();
                oldLogger(msg, params);
                (0, agentCollect_1.agentCollect)({
                    msg,
                    ...params
                }, agentEndpointUrl, oldLogger);
            };
        }
    }
    async flushAgent() {
        const agentEndpointUrl = (0, shared_1.getEnv)('agentEndpointUrl');
        if (agentEndpointUrl) {
            await (0, agentCollect_1.agentCollect)({ msg: 'Flush Agent' }, agentEndpointUrl, this.preAgentLogger);
        }
    }
    async initApp(app) {
        const apiGateway = this.apiGateway();
        apiGateway.initApp(app);
        if (this.options.devServer) {
            this.devServer.initDevEnv(app, this.options);
        }
        else {
            app.get('/', (req, res) => {
                res.status(200)
                    .send('<html><body>Cube.js server is running in production mode. <a href="https://cube.dev/docs/deployment/production-checklist">Learn more about production mode</a>.</body></html>');
            });
        }
    }
    initSubscriptionServer(sendMessage) {
        const apiGateway = this.apiGateway();
        return apiGateway.initSubscriptionServer(sendMessage);
    }
    initSQLServer() {
        const apiGateway = this.apiGateway();
        return apiGateway.getSQLServer();
    }
    apiGateway() {
        if (this.apiGatewayInstance) {
            return this.apiGatewayInstance;
        }
        return (this.apiGatewayInstance = this.createApiGatewayInstance(this.options.apiSecret, this.getCompilerApi.bind(this), this.getOrchestratorApi.bind(this), this.logger, {
            standalone: this.standalone,
            dataSourceStorage: this.orchestratorStorage,
            basePath: this.options.basePath,
            contextRejectionMiddleware: this.contextRejectionMiddleware.bind(this),
            wsContextAcceptor: this.contextAcceptor.shouldAcceptWs.bind(this.contextAcceptor),
            checkAuth: this.options.checkAuth,
            queryRewrite: this.options.queryRewrite || this.options.queryTransformer,
            extendContext: this.options.extendContext,
            playgroundAuthSecret: (0, shared_1.getEnv)('playgroundAuthSecret'),
            jwt: this.options.jwt,
            refreshScheduler: this.getRefreshScheduler.bind(this),
            scheduledRefreshContexts: this.options.scheduledRefreshContexts,
            scheduledRefreshTimeZones: this.scheduledRefreshTimeZones,
            serverCoreVersion: this.coreServerVersion,
            contextToApiScopes: this.options.contextToApiScopes,
            gatewayPort: this.options.gatewayPort,
            event: this.event,
        }));
    }
    createApiGatewayInstance(apiSecret, getCompilerApi, getOrchestratorApi, logger, options) {
        return new api_gateway_1.ApiGateway(apiSecret, getCompilerApi, getOrchestratorApi, logger, options);
    }
    async contextRejectionMiddleware(req, res, next) {
        if (!this.standalone) {
            const result = await this.contextAcceptor.shouldAcceptHttp(req.context);
            if (!result.accepted) {
                res.writeHead(result.rejectStatusCode, result.rejectHeaders);
                res.send();
                return;
            }
        }
        if (next) {
            next();
        }
    }
    async getCompilerApi(context) {
        const appId = await this.contextToAppId(context);
        let compilerApi = this.compilerCache.get(appId);
        const currentSchemaVersion = this.options.schemaVersion && (() => this.options.schemaVersion(context));
        if (!compilerApi) {
            compilerApi = this.createCompilerApi(this.repositoryFactory(context), {
                dbType: async (dataSourceContext) => {
                    const dbType = await this.contextToDbType({ ...context, ...dataSourceContext });
                    return dbType;
                },
                externalDbType: this.contextToExternalDbType(context),
                dialectClass: (dialectContext) => (this.options.dialectFactory &&
                    this.options.dialectFactory({ ...context, ...dialectContext })),
                externalDialectClass: this.options.externalDialectFactory && this.options.externalDialectFactory(context),
                schemaVersion: currentSchemaVersion,
                contextToRoles: this.options.contextToRoles,
                contextToGroups: this.options.contextToGroups,
                preAggregationsSchema: await this.preAggregationsSchema(context),
                context,
                allowJsDuplicatePropsInSchema: this.options.allowJsDuplicatePropsInSchema,
                allowNodeRequire: this.options.allowNodeRequire,
                fastReload: this.options.fastReload,
            });
            this.compilerCache.set(appId, compilerApi);
        }
        compilerApi.schemaVersion = currentSchemaVersion;
        return compilerApi;
    }
    async resetInstanceState() {
        await this.orchestratorStorage.releaseConnections();
        this.orchestratorStorage.clear();
        this.compilerCache.clear();
        this.reloadEnvVariables();
        this.repository = new shared_1.FileRepository(this.options.schemaPath);
        this.repositoryFactory = this.options.repositoryFactory || (() => this.repository);
        this.startScheduledRefreshTimer();
    }
    async getOrchestratorApi(context) {
        const orchestratorId = await this.contextToOrchestratorId(context);
        if (this.orchestratorStorage.has(orchestratorId)) {
            return this.orchestratorStorage.get(orchestratorId);
        }
        /**
         * Hash table to store promises which will be resolved with the
         * datasource drivers. DriverFactoryByDataSource function is closure
         * this constant.
         */
        const driverPromise = {};
        let externalPreAggregationsDriverPromise = null;
        const contextToDbType = this.contextToDbType.bind(this);
        const externalDbType = this.contextToExternalDbType(context);
        // orchestrator options can be empty, if user didn't define it.
        // so we are adding default and configuring queues concurrency.
        const orchestratorOptions = this.optsHandler.getOrchestratorInitializedOptions(context, (await this.orchestratorOptions(context)) || {});
        const orchestratorApi = this.createOrchestratorApi(
        /**
         * Driver factory function `DriverFactoryByDataSource`.
         */
        async (dataSource = 'default') => {
            if (driverPromise[dataSource]) {
                return driverPromise[dataSource];
            }
            // eslint-disable-next-line no-return-assign
            return driverPromise[dataSource] = (async () => {
                let driver = null;
                try {
                    driver = await this.resolveDriver({
                        ...context,
                        dataSource,
                    }, orchestratorOptions);
                    if (typeof driver === 'object' && driver != null) {
                        if (driver.setLogger) {
                            driver.setLogger(this.logger);
                        }
                        await driver.testConnection();
                        return driver;
                    }
                    throw new Error(`Unexpected return type, driverFactory must return driver (dataSource: "${dataSource}"), actual: ${(0, shared_1.getRealType)(driver)}`);
                }
                catch (e) {
                    driverPromise[dataSource] = null;
                    if (driver) {
                        await driver.release();
                    }
                    throw e;
                }
            })();
        }, {
            externalDriverFactory: this.options.externalDriverFactory && (async () => {
                if (externalPreAggregationsDriverPromise) {
                    return externalPreAggregationsDriverPromise;
                }
                // eslint-disable-next-line no-return-assign
                return externalPreAggregationsDriverPromise = (async () => {
                    let driver = null;
                    try {
                        driver = await this.options.externalDriverFactory(context);
                        if (typeof driver === 'object' && driver != null) {
                            if (driver.setLogger) {
                                driver.setLogger(this.logger);
                            }
                            await driver.testConnection();
                            return driver;
                        }
                        throw new Error(`Unexpected return type, externalDriverFactory must return driver, actual: ${(0, shared_1.getRealType)(driver)}`);
                    }
                    catch (e) {
                        externalPreAggregationsDriverPromise = null;
                        if (driver) {
                            await driver.release();
                        }
                        throw e;
                    }
                })();
            }),
            contextToDbType: async (dataSource) => contextToDbType({
                ...context,
                dataSource
            }),
            // speedup with cache
            contextToExternalDbType: () => externalDbType,
            redisPrefix: orchestratorId,
            skipExternalCacheAndQueue: externalDbType === 'cubestore',
            cacheAndQueueDriver: this.options.cacheAndQueueDriver,
            ...orchestratorOptions,
        });
        this.orchestratorStorage.set(orchestratorId, orchestratorApi);
        return orchestratorApi;
    }
    createCompilerApi(repository, options = {}) {
        return new CompilerApi_1.CompilerApi(repository, options.dbType || this.options.dbType, this.createCompilerApiOptions(options));
    }
    createCompilerApiOptions(options = {}) {
        return {
            schemaVersion: options.schemaVersion || this.options.schemaVersion,
            contextToRoles: this.options.contextToRoles,
            contextToGroups: this.options.contextToGroups,
            devServer: this.options.devServer,
            logger: this.logger,
            externalDbType: options.externalDbType,
            preAggregationsSchema: options.preAggregationsSchema,
            allowUngroupedWithoutPrimaryKey: this.options.allowUngroupedWithoutPrimaryKey ||
                (0, shared_1.getEnv)('allowUngroupedWithoutPrimaryKey'),
            convertTzForRawTimeDimension: (0, shared_1.getEnv)('convertTzForRawTimeDimension'),
            compileContext: options.context,
            dialectClass: options.dialectClass,
            externalDialectClass: options.externalDialectClass,
            allowJsDuplicatePropsInSchema: options.allowJsDuplicatePropsInSchema,
            sqlCache: this.options.sqlCache,
            standalone: this.standalone,
            allowNodeRequire: options.allowNodeRequire,
            fastReload: options.fastReload || (0, shared_1.getEnv)('fastReload'),
            compilerCacheSize: this.options.compilerCacheSize || 250,
            maxCompilerCacheKeepAlive: this.options.maxCompilerCacheKeepAlive,
            updateCompilerCacheKeepAlive: this.options.updateCompilerCacheKeepAlive,
        };
    }
    createOrchestratorApi(getDriver, options) {
        return new OrchestratorApi_1.OrchestratorApi(getDriver, this.logger, options);
    }
    /**
     * @internal Please don't use this method directly, use refreshTimer
     */
    handleScheduledRefreshInterval = async (options) => {
        const allContexts = await this.options.scheduledRefreshContexts();
        if (allContexts.length < 1) {
            this.logger('Refresh Scheduler Error', {
                error: 'At least one context should be returned by scheduledRefreshContexts'
            });
        }
        const contexts = [];
        for (const allContext of allContexts) {
            const resContext = this.migrateBackgroundContext(allContext);
            const res = await this.contextAcceptor.shouldAccept(resContext);
            if (res.accepted) {
                contexts.push(resContext || {});
            }
        }
        const batchLimit = (0, p_limit_1.default)(this.options.scheduledRefreshBatchSize);
        return Promise.all(contexts
            .map((context) => async () => {
            const queryingOptions = {
                ...options,
                concurrency: this.options.scheduledRefreshConcurrency,
            };
            const timezonesFromOptionsOrSecurityContext = await this.scheduledRefreshTimeZones(context);
            if (timezonesFromOptionsOrSecurityContext.length > 0) {
                queryingOptions.timezones = timezonesFromOptionsOrSecurityContext;
            }
            return this.runScheduledRefresh(context, queryingOptions);
        })
            // Limit the number of refresh contexts we process per iteration
            .map(batchLimit));
    };
    getRefreshScheduler() {
        return new RefreshScheduler_1.RefreshScheduler(this);
    }
    /**
     * @internal Please don't use this method directly, use refreshTimer
     */
    async runScheduledRefresh(context, queryingOptions) {
        return this.getRefreshScheduler().runScheduledRefresh(this.migrateBackgroundContext(context), queryingOptions);
    }
    warningBackgroundContextShow = false;
    migrateBackgroundContext(ctx) {
        let result = null;
        // We renamed authInfo to securityContext, but users can continue to use both ways
        if (ctx) {
            if (ctx.securityContext && !ctx.authInfo) {
                result = {
                    ...ctx,
                    authInfo: ctx.securityContext,
                };
            }
            else if (ctx.authInfo) {
                result = {
                    ...ctx,
                    securityContext: ctx.authInfo,
                };
                if (this.warningBackgroundContextShow) {
                    this.logger('auth_info_deprecation', {
                        warning: ('authInfo was renamed to securityContext, please migrate: ' +
                            'https://github.com/cube-js/cube.js/blob/master/DEPRECATION.md#checkauthmiddleware')
                    });
                    this.warningBackgroundContextShow = false;
                }
            }
        }
        return result;
    }
    /**
     * Returns driver instance by a given context
     */
    async getDriver(context, options) {
        // TODO (buntarb): this works fine without multiple data sources.
        if (!this.driver) {
            const driver = await this.resolveDriver(context, options);
            await driver.testConnection(); // TODO mutex
            this.driver = driver;
        }
        return this.driver;
    }
    /**
     * Resolve driver by the data source.
     */
    async resolveDriver(context, options) {
        const val = await this.options.driverFactory(context);
        if ((0, DriverResolvers_1.isDriver)(val)) {
            return val;
        }
        else {
            const { type, ...rest } = val;
            const opts = Object.keys(rest).length
                ? rest
                : {
                    maxPoolSize: await CubejsServerCore.getDriverMaxPool(context, options),
                    testConnectionTimeout: options?.testConnectionTimeout,
                };
            opts.dataSource = (0, shared_1.assertDataSource)(context.dataSource);
            return CubejsServerCore.createDriver(type, opts);
        }
    }
    async testConnections() {
        return this.orchestratorStorage.testConnections();
    }
    async releaseConnections() {
        await this.orchestratorStorage.releaseConnections();
        if (this.maxCompilerCacheKeep) {
            clearInterval(this.maxCompilerCacheKeep);
        }
        this.compilerCache.clear();
        if (this.scheduledRefreshTimerInterval) {
            await this.scheduledRefreshTimerInterval.cancel();
        }
    }
    async beforeShutdown() {
        if (this.maxCompilerCacheKeep) {
            clearInterval(this.maxCompilerCacheKeep);
        }
        if (this.scheduledRefreshTimerInterval) {
            await this.scheduledRefreshTimerInterval.cancel(true);
        }
    }
    causeErrorPromise = null;
    onUncaughtException = async (e) => {
        console.error(e.stack || e);
        if (e.message && e.message.indexOf('Redis connection to') !== -1) {
            console.log('🛑 Cube.js Server requires locally running Redis instance to connect to');
            if (process.platform.indexOf('win') === 0) {
                console.log('💾 To install Redis on Windows please use https://github.com/MicrosoftArchive/redis/releases');
            }
            else if (process.platform.indexOf('darwin') === 0) {
                console.log('💾 To install Redis on Mac please use https://redis.io/topics/quickstart or `$ brew install redis`');
            }
            else {
                console.log('💾 To install Redis please use https://redis.io/topics/quickstart');
            }
        }
        if (!this.causeErrorPromise) {
            this.causeErrorPromise = this.event('Dev Server Fatal Error', {
                error: (e.stack || e.message || e).toString()
            });
        }
        await this.causeErrorPromise;
        process.exit(1);
    };
    async shutdown() {
        this.compilerCache.clear();
        if (this.devServer) {
            if (!process.env.CI) {
                process.removeListener('uncaughtException', this.onUncaughtException);
            }
        }
        if (this.apiGatewayInstance) {
            this.apiGatewayInstance.release();
        }
        return this.orchestratorStorage.releaseConnections();
    }
}
exports.CubejsServerCore = CubejsServerCore;
//# sourceMappingURL=server.js.map