UNPKG

@deepkit/app

Version:

Deepkit App, CLI framework and service container

332 lines 16.3 kB
const __ΩParameters = ['T', 'args', '', 'Parameters', 'l>e"!R!RPde#!Ph"!@2""/#qk#\'QRb!Pde"!p)w$y']; const __ΩPartial = ['T', 'Partial', 'l+e#!e"!fRb!Pde"!gN#"w"y']; const __ΩError = ['name', 'message', 'stack', 'Error', 'P&4!&4"&4#8Mw$y']; /*@ts-ignore*/ import { __ΩExtractClassType } from '@deepkit/core'; /*@ts-ignore*/ import { __ΩConfigLoader } from './service-container.js'; /*@ts-ignore*/ import { __ΩEventListenerCallback } from '@deepkit/event'; /*@ts-ignore*/ import { __ΩEventDispatcherDispatchType } from '@deepkit/event'; /*@ts-ignore*/ import { __ΩReceiveType } from '@deepkit/type'; /*@ts-ignore*/ import { __ΩClassType } from '@deepkit/core'; /*@ts-ignore*/ import { __ΩScope } from '@deepkit/injector'; /*@ts-ignore*/ import { __ΩResolveToken } from '@deepkit/injector'; /*@ts-ignore*/ import { __ΩConfigureProviderOptions } from '@deepkit/injector'; function __assignType(fn, args) { fn.__type = args; return fn; } /* * Deepkit Framework * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt * * This program is free software: you can redistribute it and/or modify * it under the terms of the MIT License. * * You should have received a copy of the MIT License along with this program. */ import { isFunction, isObject, pathBasename, setPathValue } from '@deepkit/core'; import { ServiceContainer } from './service-container.js'; import { injectedFunction, InjectorContext } from '@deepkit/injector'; import { AppModule } from './module.js'; import { EnvConfiguration } from './configuration.js'; import { DataEventToken, EventDispatcher, } from '@deepkit/event'; import { ReflectionClass, ReflectionKind } from '@deepkit/type'; import { Logger } from '@deepkit/logger'; import { executeCommand, getArgsFromEnvironment, getBinFromEnvironment } from './command.js'; export function setPartialConfig(target, partial, incomingPath = '') { for (const i in partial) { const path = (incomingPath ? incomingPath + '.' : '') + i; if (isObject(partial[i])) { setPartialConfig(target, partial[i], path); } else { setPathValue(target, path, partial[i]); } } } setPartialConfig.__type = ['target', 'partial', 'incomingPath', () => "", 'setPartialConfig', 'PP&"LM2!P&"LM2"&2#>$"/%']; const __ΩEnvNamingStrategy = ["same", "upper", "lower", 'name', "same", "upper", "lower", '', 'EnvNamingStrategy', 'P.!.".#P&2$P&.%.&.\'-J/(Jw)y']; function camelToUpperCase(str) { return str.replace(/[A-Z]+/g, __assignType((letter) => `_${letter.toUpperCase()}`, ['letter', '', 'P&2!"/"'])).toUpperCase(); } camelToUpperCase.__type = ['str', 'camelToUpperCase', 'P&2!"/"']; function camelToLowerCase(str) { return str.replace(/[A-Z]+/g, __assignType((letter) => `_${letter.toLowerCase()}`, ['letter', '', 'P&2!"/"'])).toLowerCase(); } camelToLowerCase.__type = ['str', 'camelToLowerCase', 'P&2!"/"']; function convertNameStrategy(namingStrategy, name) { const strategy = isFunction(namingStrategy) ? namingStrategy(name) || 'same' : namingStrategy; if (strategy === 'upper') { return camelToUpperCase(name); } else if (strategy === 'lower') { return camelToLowerCase(name); } else if (strategy === 'same') { return name; } else { return strategy; } } convertNameStrategy.__type = [() => __ΩEnvNamingStrategy, 'namingStrategy', 'name', 'convertNameStrategy', 'Pn!2"&2#&/$']; function parseEnv(config, prefix, schema, incomingDotPath, incomingEnvPath, namingStrategy, envContainer) { for (const property of schema.getProperties()) { const name = convertNameStrategy(namingStrategy, property.name); if (property.type.kind === ReflectionKind.class || property.type.kind === ReflectionKind.objectLiteral) { parseEnv(config, prefix, ReflectionClass.from(property.type), (incomingDotPath ? incomingDotPath + '.' : '') + property.name, (incomingEnvPath ? incomingEnvPath + '_' : '') + name, namingStrategy, envContainer); } else { const dotPath = (incomingDotPath ? incomingDotPath + '.' : '') + property.name; const envName = prefix + (incomingEnvPath ? incomingEnvPath + '_' : '') + name; if (envContainer[envName] === undefined) continue; setPathValue(config, dotPath, envContainer[envName]); } } } parseEnv.__type = ['config', 'prefix', () => ReflectionClass, 'schema', 'incomingDotPath', 'incomingEnvPath', () => __ΩEnvNamingStrategy, 'namingStrategy', 'envContainer', 'parseEnv', 'PP&"LM2!&2"P"7#2$&2%&2&n\'2(P&"LM2)"/*']; const __ΩEnvConfigOptions = ['envFilePath', () => __ΩEnvNamingStrategy, 'namingStrategy', 'prefix', 'EnvConfigOptions', 'PP&&FJ4!8n"4#8&4$8Mw%y']; const defaultEnvConfigOptions = { prefix: 'APP_', envFilePath: ['.env'], namingStrategy: 'upper', }; class EnvConfigLoader { constructor(options) { const normalizedOptions = { ...defaultEnvConfigOptions, ...options, }; const { prefix, envFilePath, namingStrategy } = normalizedOptions; this.prefix = prefix; this.envFilePaths = Array.isArray(envFilePath) ? envFilePath : [envFilePath]; this.namingStrategy = namingStrategy; } load(module, config, schema) { const envConfiguration = new EnvConfiguration(); for (const path of this.envFilePaths) { if (envConfiguration.loadEnvFile(path)) break; } const env = Object.assign({}, envConfiguration.getAll()); Object.assign(env, process.env); parseEnv(config, this.prefix, schema, '', convertNameStrategy(this.namingStrategy, module.name), this.namingStrategy, env); } } EnvConfigLoader.__type = ['prefix', 'envFilePaths', () => __ΩEnvNamingStrategy, 'namingStrategy', () => __ΩEnvConfigOptions, 'options', 'constructor', () => AppModule, 'module', 'config', () => ReflectionClass, 'schema', 'load', 'EnvConfigLoader', '&3!9;&F3"9;n#3$9;Pn%2&8"0\'PP"7(2)P&"LM2*P"7+2,"0-5w.']; export class RootAppModule extends AppModule { } RootAppModule.__type = ['T', () => AppModule, 'RootAppModule', 'b!Pe"!7"5e!!6"w#']; const __ΩAppEvent = ['command', 'parameters', () => InjectorContext, 'injector', 'AppEvent', 'P&4!P&"LM4"P7#4$Mw%y']; export { __ΩAppEvent as __ΩAppEvent }; const __ΩAppExecutedEvent = [() => __ΩAppEvent, 'exitCode', 'AppExecutedEvent', 'Pn!\'4"Mw#y']; export { __ΩAppExecutedEvent as __ΩAppExecutedEvent }; const __ΩAppErrorEvent = [() => __ΩAppEvent, () => __ΩError, 'error', 'AppErrorEvent', 'Pn!n"4#Mw$y']; export { __ΩAppErrorEvent as __ΩAppErrorEvent }; /** * When a CLI command is about to be executed, this event is emitted. * * This is different to @deepkit/framework's onBootstrap event, which is only executed * when the server:start is execute. This event is executed for every CLI command (including server:start). */ export const onAppExecute = (DataEventToken.Ω = [[() => __ΩAppEvent, 'n!']], new DataEventToken('app.execute')); /** * When a CLI command is successfully executed, this event is emitted. */ export const onAppExecuted = (DataEventToken.Ω = [[() => __ΩAppExecutedEvent, 'n!']], new DataEventToken('app.executed')); /** * When a CLI command failed to execute, this event is emitted. */ export const onAppError = (DataEventToken.Ω = [[() => __ΩAppErrorEvent, 'n!']], new DataEventToken('app.error')); /** * When the application is about to shut down, this event is emitted. * This is always executed, even when an error occurred. So it's a good place to clean up. */ export const onAppShutdown = (DataEventToken.Ω = [[() => __ΩAppEvent, 'n!']], new DataEventToken('app.shutdown')); /** * This is the smallest available application abstraction in Deepkit. * * It is based on a module and executes registered CLI controllers in `execute`. * * @deepkit/framework extends that with a more powerful Application class, that contains also HTTP and RPC controllers. * * You can use this class for more integrated unit-tests. */ export class App { constructor(appModuleOptions, serviceContainer, appModule) { this.appModule = appModule || new RootAppModule({}, appModuleOptions); this.serviceContainer = serviceContainer || new ServiceContainer(this.appModule); } static fromModule(module) { return new App({}, undefined, module); } /** * Allows to change the module after the configuration has been loaded, right before the service container is built. * * This enables you to change the module or its imports depending on the configuration the last time before their services are built. * * At this point no services can be requested as the service container was not built. */ setup(...args) { this.appModule = this.appModule.setup(...args); return this; } /** * Allows to call services before the application bootstraps. * * This enables you to configure modules and request their services. */ use(setup) { this.appModule.use(setup); return this; } /** * Calls a function immediately and resolves all parameters using the * current service container. */ call(fn, module) { const injector = this.serviceContainer.getInjector(module || this.appModule); const resolvedFunction = injectedFunction(fn, injector); return resolvedFunction(); } command(name, callback) { callback = isFunction(name) ? name : callback; name = isFunction(name) ? '' : name; this.appModule.addCommand(name, callback); return this; } addConfigLoader(loader) { this.serviceContainer.addConfigLoader(loader); return this; } configure(config) { this.serviceContainer.appModule.configure(config); return this; } /** * Register a new event listener for given token. * * order: The lower the order, the sooner the listener is called. Default is 0. */ listen(eventToken, callback, order = 0) { const listener = { callback, order, eventToken }; this.appModule.listeners.push(listener); return this; } dispatch(eventToken, ...args) { return this.get(EventDispatcher).dispatch(eventToken, ...args); } /** * Loads environment variables and optionally reads from .env files in order to find matching configuration options * in your application and modules in order to set their values. * * Prefixing ENV variables is encouraged to avoid collisions and by default a prefix of APP_ is used * Example: * * APP_databaseUrl="mongodb://localhost/mydb" * * new App({}).loadConfigFromEnvVariables('APP_').run(); * * * `envFilePath` can be either an absolute or relative path. For relative paths the first * folder with a package.json starting from process.cwd() upwards is picked. * * So if you use 'local.env' make sure a 'local.env' file is located beside your 'package.json'. * * @param options Configuration options for retrieving configuration from env * @returns */ loadConfigFromEnv(options) { this.addConfigLoader(new EnvConfigLoader(options)); return this; } /** * Loads a JSON encoded environment variable and applies its content to the configuration. * * Example: * * APP_CONFIG={'databaseUrl": "mongodb://localhost/mydb", "moduleA": {"foo": "bar'}} * * new App().run().loadConfigFromEnvVariable('APP_CONFIG').run(); */ loadConfigFromEnvVariable(variableName = 'APP_CONFIG') { if (!process.env[variableName]) return this; this.addConfigLoader({ load: __assignType(function load(module, config, schema) { try { const jsonConfig = JSON.parse(process.env[variableName] || ''); setPartialConfig(config, module.name ? jsonConfig[module.name] : jsonConfig); } catch (error) { throw new Error(`Invalid JSON in env variable ${variableName}. Parse error: ${error}`); } }, [() => AppModule, 'module', 'config', () => ReflectionClass, 'schema', 'load', 'PP"7!2"P&"LM2#P"7$2%"/&']), }); return this; } async run(argv, bin) { const exitCode = await this.execute(argv, bin); if (exitCode > 0) process.exit(exitCode); } get(token = this.get.Ω?.[0], moduleOrClass, scope) { this.get.Ω = undefined; return this.serviceContainer.getInjector(moduleOrClass || this.appModule).get(token, scope); } getInjector(moduleOrClass) { return this.serviceContainer.getInjector(moduleOrClass || this.appModule); } getInjectorContext() { return this.serviceContainer.getInjectorContext(); } /** * @see InjectorModule.configureProvider */ configureProvider(configure, options = {}, type = this. /** * @see InjectorModule.configureProvider */ configureProvider.Ω?.[0]) { this.configureProvider.Ω = undefined; (this.appModule.configureProvider.Ω = [[type, 'n!']], this.appModule.configureProvider(configure, options, type)); return this; } async execute(argv, bin) { const eventDispatcher = this.get(EventDispatcher); const logger = this.get(Logger); function unhandledRejectionHandler(error) { logger.error('unhandledRejection', error); } unhandledRejectionHandler.__type = ['error', 'unhandledRejectionHandler', 'P"2!"/"']; if ('undefined' !== typeof process) { process.on('unhandledRejection', unhandledRejectionHandler); } const scopedInjectorContext = this.getInjectorContext().createChildScope('cli'); if ('string' !== typeof bin) { bin = bin || getBinFromEnvironment(); let binary = pathBasename(bin[0]); let file = pathBasename(bin[1]); bin = `${binary} ${file}`; } try { return await executeCommand(bin, argv || getArgsFromEnvironment(), eventDispatcher, logger, scopedInjectorContext, this.serviceContainer.cliControllerRegistry.controllers); } finally { if ('undefined' !== typeof process) { process.removeListener('unhandledRejection', unhandledRejectionHandler); } } } } App.__type = ['T', () => EnvConfigLoader, 'envConfigLoader', () => ServiceContainer, 'serviceContainer', () => __ΩExtractClassType, "config", () => AppModule, 'appModule', 'appModuleOptions', () => ServiceContainer, () => AppModule, 'constructor', () => AppModule, 'module', () => App, 'fromModule', () => __ΩParameters, "appModule", "setup", 'args', 'setup', '', 'use', 'fn', () => AppModule, 'call', 'name', 'callback', 'command', () => __ΩConfigLoader, 'loader', 'addConfigLoader', () => __ΩPartial, () => __ΩExtractClassType, "config", 'config', 'configure', 'eventToken', () => __ΩEventListenerCallback, 'order', () => 0, 'listen', 'DispatchArguments', () => __ΩEventDispatcherDispatchType, 'dispatch', () => __ΩEnvConfigOptions, 'options', 'loadConfigFromEnv', 'variableName', () => "APP_CONFIG", 'loadConfigFromEnvVariable', 'argv', 'bin', 'run', () => __ΩReceiveType, 'token', () => AppModule, () => __ΩClassType, () => AppModule, 'moduleOrClass', () => __ΩScope, 'scope', () => __ΩResolveToken, 'get', () => AppModule, () => __ΩClassType, () => AppModule, 'getInjector', () => InjectorContext, 'getInjectorContext', 'instance', () => __ΩPartial, () => __ΩConfigureProviderOptions, () => ({}), () => __ΩReceiveType, 'type', 'configureProvider', 'execute', 'App', 'b!P7"3#8<P7$3%9Pe"!.\'fo&"7(3)Pe"!2*P7+2%8P"7,2)8"0-PPe#!7.2/Pe#!7001sP!.3f.4fo2"@25!06PP"@25$/726!08PP"@25e#!/729P"7:2/8e"!0;PP&P"@25"/7J2<P"@25"/72=8!0>Pn?2@!0APe"!.DfoC"oB"2E!0FPe"!2Ge"!oH"2=\'2I>J!0KPe"!2G"wL@25e"!oM"0NPnO2P8!0QP&2R>S!0TP"F2U8&F2V8"0WPe"!oX"2Y8PP"7ZP"7\\o["J2]8n^2_8e"!o`"0aPPP"7bP"7doc"J2]8"0ePP7f0gPPe#!2h"@25"/72Fnjoi"2P>ke"!ol"2m8!0nP&F2U8P&F&J2V8\'`0o5wp']; //# sourceMappingURL=app.js.map