"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoStart = exports.__ΩCreateAppOptions = void 0;
exports.createApp = createApp;
const tslib_1 = require("tslib");
const __ΩPartial = ['T', 'Partial', 'l+e#!e"!fRb!Pde"!gN#"w"y'];
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
/*@ts-ignore*/
var { __ΩRootModuleDefinition } = require('@deepkit/app');
const app_1 = require("@deepkit/app");
/*@ts-ignore*/
var { __ΩClassType } = require('@deepkit/core');
const event_1 = require("@deepkit/event");
const framework_1 = require("@deepkit/framework");
const http_1 = require("@deepkit/http");
const injector_1 = require("@deepkit/injector");
const logger_1 = require("@deepkit/logger");
const orm_1 = require("@deepkit/orm");
const database_1 = require("../database");
const migration_1 = require("../database/migration");
const helpers_1 = require("../database/migration/helpers");
const health_module_1 = require("../health/health.module");
const healthcheck_service_1 = require("../health/healthcheck.service");
const decorators_1 = require("../helpers/framework/decorators");
const http_2 = require("../http");
const cors_1 = require("../http/cors");
const services_1 = require("../services");
const invoke_1 = require("../services/cli/invoke");
const repl_1 = require("../services/cli/repl");
const bootstrap_1 = require("../services/worker/bootstrap");
const sentry_1 = require("../telemetry/sentry");
const config_1 = require("./config");
const config_loader_1 = require("./config.loader");
const const_1 = require("./const");
const dev_1 = require("./dev");
const shutdown_1 = require("./shutdown");
const state_1 = require("./state");
const resolver_1 = require("./resolver");
const enableDevConsole = (const_1.isDevelopment && process.env.DEVCONSOLE_ENABLED !== 'false') || process.env.DEVCONSOLE_ENABLED === 'true';
const __ΩCreateAppOptions = ['C', () => __ΩRootModuleDefinition, () => __ΩClassType, 'config', () => __ΩPartial, 'defaultConfig', () => __ΩClassType, () => database_1.BaseDatabase, 'db', () => __ΩPartial, () => framework_1.FrameworkConfig, 'frameworkConfig', () => cors_1.HttpCorsOptions, () => cors_1.HttpCorsOptions, '', 'cors', 'enableWorker', 'enableDkRpc', 'CreateAppOptions', 'b!Pn"e"!o#"4$e"!o%"4&8P7(o\'"4)8P7+o*"4,8Pe#!2$PP7-P7.FJ//408)418)428Mw3y'];
exports.__ΩCreateAppOptions = __ΩCreateAppOptions;
// todo: figure out why the type won't pass through without conflict
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createApp(options) {
    const { config, defaultConfig, db, frameworkConfig, cors: corsOptionsFactory, ...appOptions } = options;
    const frameworkModule = new framework_1.FrameworkModule({
        port: 3000,
        migrationDir: (0, helpers_1.getMigrationsDir)(),
        httpLog: false, // don't use the default - we'll roll out own
        gracefulShutdownTimeout: 30,
        ...frameworkConfig,
        broker: {
            ...new framework_1.BrokerConfig(),
            startOnBootstrap: false,
            ...frameworkConfig?.broker
        },
        http: {
            ...frameworkConfig?.http,
            parser: {
                multipartJsonKey: '_payload',
                ...frameworkConfig?.http?.parser
            }
        }
    });
    frameworkModule.getImportedModuleByClass(http_1.HttpModule).addProvider({ provide: http_1.HttpKernel, useClass: http_2.CustomHttpKernel });
    const app = new app_1.App({
        ...appOptions,
        config: config,
        controllers: [repl_1.ReplCommand, invoke_1.ProviderInvokeCommand, ...(appOptions.controllers ?? [])],
        imports: [...(appOptions.imports ?? []), frameworkModule, new health_module_1.HealthModule()],
        providers: [
            http_1.HttpListener,
            http_2.HttpWorkflowListenerOptions,
            services_1.WorkerService,
            services_1.MailService,
            ...(appOptions.providers ?? []),
            ...(db ? [db] : []),
            ...(appOptions.enableDkRpc ? [] : [{ provide: framework_1.RpcServer, useValue: { start: () => { } } }])
        ],
        listeners: [http_2.HttpWorkflowListener, shutdown_1.ShutdownListener, ...(appOptions.listeners ?? [])]
    });
    state_1.globalState.currentApp = app;
    if (enableDevConsole) {
        try {
            // eslint-disable-next-line @typescript-eslint/no-require-imports
            require('../devconsole/patches').initDevConsole(app);
        }
        catch {
            // DevConsole not available
        }
    }
    (0, migration_1.replaceMigrationCommands)(app, frameworkModule, db);
    if (options.config) {
        app.appModule.addProvider({
            provide: options.config,
            useFactory: __assignType(function useFactory() {
                return app.appModule.config;
            }, ['useFactory', 'P"/!'])
        });
        app.appModule.addProvider({
            provide: config_1.BaseAppConfig,
            useExisting: options.config
        });
        app.addConfigLoader(new config_loader_1.CustomConfigLoader(defaultConfig));
        // reconfigure the framework module to use the port from the config
        app.setup(__assignType((module, config) => {
            if (config.PORT && !const_1.isTest) {
                module.getImportedModuleByClass(framework_1.FrameworkModule).configure({
                    port: config.PORT
                });
            }
        }, ['module', 'config', '', 'P"2!"2""/#']));
    }
    if (options.db) {
        app.appModule.addProvider({
            provide: state_1.DBProvider,
            useFactory: __assignType(function useFactory() {
                return new state_1.DBProvider(app.get(options.db));
            }, ['useFactory', 'P"/!'])
        });
        class DBHealthSetupListener {
            constructor(hcSvc) {
                this.hcSvc = hcSvc;
            }
            onServerBootstrapDone() {
                const db = app.get(options.db);
                this.hcSvc.register('Database', async () => {
                    await db.rawFind('SELECT 1');
                });
            }
        }
        DBHealthSetupListener.__type = [() => healthcheck_service_1.HealthcheckService, 'hcSvc', 'constructor', 'onServerBootstrapDone', 'DBHealthSetupListener', 'PP7!2";"0#P"0$5w%'];
        tslib_1.__decorate([
            event_1.eventDispatcher.listen(framework_1.onServerBootstrapDone),
            tslib_1.__metadata("design:type", Function),
            tslib_1.__metadata("design:paramtypes", []),
            tslib_1.__metadata("design:returntype", void 0)
        ], DBHealthSetupListener.prototype, "onServerBootstrapDone", null);
        app.appModule.addListener(DBHealthSetupListener);
    }
    if (corsOptionsFactory) {
        app.appModule.addProvider({
            provide: cors_1.HttpCorsOptionsMulti,
            useFactory: __assignType(function useFactory() {
                const options = corsOptionsFactory(app.appModule.config);
                return new cors_1.HttpCorsOptionsMulti(Array.isArray(options) ? options : [options]);
            }, [() => cors_1.HttpCorsOptionsMulti, 'useFactory', 'PP7!/"'])
        });
        app.appModule.addListener(cors_1.HttpCorsListener);
    }
    // hijack the Logger injection to replace the factory with ExtendedLogger
    // this is definitely not a good way to do this
    const originalProcessProviderFn = app.appModule.processProvider;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    app.appModule.processProvider = __assignType(function (module, token, provider) {
        if (token === logger_1.Logger) {
            if (typeof provider === 'object' && 'useFactory' in provider) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                delete provider.useFactory;
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                provider.useClass = services_1.ExtendedLogger;
            }
        }
        return originalProcessProviderFn.call(this, module, token, provider);
    }, [() => app_1.AppModule, 'module', 'Token', 'token', 'ProviderWithScope', 'provider', '', 'PP"7!2""w#2$"w%2&"/\'']);
    // Allow direct injection of ExtendedLogger by aliasing it to Logger
    app.appModule.addProvider({
        provide: services_1.ExtendedLogger,
        useExisting: logger_1.Logger
    });
    if (options.enableWorker) {
        (0, bootstrap_1.installWorkerComponents)(app);
    }
    let started = false;
    app.listen(framework_1.onServerMainBootstrapDone, () => {
        started = true;
        doAppPostStartup(app, !!options.db);
    });
    app.setup(__assignType((_module, config) => {
        if (config.SENTRY_DSN) {
            (0, sentry_1.installSentry)({
                dsn: config.SENTRY_DSN
            });
        }
    }, ['_module', 'config', '', 'P"2!"2""/#']));
    const makeErrorHandler = __assignType((type) => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return __assignType((err, promise) => {
            const logger = started ? (0, resolver_1.r)(logger_1.Logger) : new logger_1.Logger([new logger_1.ConsoleTransport()]);
            logger.error(type, err, { promise });
            (0, sentry_1.flushSentry)()
                .catch(__assignType(err => logger.error('Error flushing to Sentry', err), ['err', '', 'P"2!"/"']))
                .finally(() => process.exit(1));
        }, ['err', 'promise', '', 'P"2!"2"8"/#']);
    }, ['type', '', 'P&2!"/"']);
    process.on('uncaughtException', makeErrorHandler('uncaughtException'));
    process.on('unhandledRejection', makeErrorHandler('unhandledRejection'));
    return app;
}
createApp.__type = ['options', 'createApp', 'P"2!"/"'];
const AutoStartSymbol = Symbol('AutoStart');
exports.AutoStart = (0, decorators_1.createSymbolAttachmentClassDecorator)(AutoStartSymbol);
async function doAppPostStartup(app, enableDb) {
    if (enableDb) {
        // DK lazily starts the database registry, but we don't have any calls that
        // would cause that. without the registry, the application shutdown hook to
        // disconect from the database is never executed. thus, we'll invoke this manually.
        app.get(orm_1.DatabaseRegistry).init();
    }
    // let's do some things when we're running normally (not as a CLI service)
    if (!state_1.globalState.isCliService) {
        // there are cases where we want a provider to start automatically at application startup,
        // rather than waiting for injection via DI -- such as services that connect to 3rd parties
        // via sockets to process events.
        createAutoStartInstances(app.appModule);
        // development only
        if (const_1.isDevelopment) {
            (0, dev_1.doDevPostAppStartup)(app);
        }
        if (enableDevConsole) {
            try {
                // eslint-disable-next-line @typescript-eslint/no-require-imports
                require('../devconsole/patches').startDevConsoleSrpcServer();
            }
            catch (err) {
                app.get(logger_1.Logger).warn('Failed to start DevConsole SRPC server', err);
            }
        }
    }
}
doAppPostStartup.__type = [() => app_1.App, 'app', 'enableDb', 'doAppPostStartup', 'PP"7!2")2#8"/$'];
async function createAutoStartInstances(aModule) {
    const injector = aModule.injector;
    if (aModule.imports?.length) {
        for (const anImport of aModule.imports) {
            await createAutoStartInstances(anImport);
        }
    }
    for (const provider of aModule.providers) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if (provider[AutoStartSymbol]) {
            injector?.get(provider);
        }
    }
}
createAutoStartInstances.__type = [() => injector_1.InjectorModule, 'aModule', 'createAutoStartInstances', 'PP7!2""/#'];
//# sourceMappingURL=base.js.map