@deepkit/app
Version:
Deepkit App, CLI framework and service container
338 lines • 17.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.App = exports.onAppShutdown = exports.onAppError = exports.onAppExecuted = exports.onAppExecute = exports.__ΩAppErrorEvent = exports.__ΩAppExecutedEvent = exports.__ΩAppEvent = exports.RootAppModule = void 0;
exports.setPartialConfig = setPartialConfig;
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*/
const { __ΩExtractClassType } = require('@deepkit/core');
/*@ts-ignore*/
const { __ΩConfigLoader } = require('./service-container.js');
/*@ts-ignore*/
const { __ΩEventListenerCallback } = require('@deepkit/event');
/*@ts-ignore*/
const { __ΩEventDispatcherDispatchType } = require('@deepkit/event');
/*@ts-ignore*/
const { __ΩReceiveType } = require('@deepkit/type');
/*@ts-ignore*/
const { __ΩClassType } = require('@deepkit/core');
/*@ts-ignore*/
const { __ΩScope } = require('@deepkit/injector');
/*@ts-ignore*/
const { __ΩResolveToken } = require('@deepkit/injector');
/*@ts-ignore*/
const { __ΩConfigureProviderOptions } = require('@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.
*/
const core_1 = require("@deepkit/core");
const service_container_js_1 = require("./service-container.js");
const injector_1 = require("@deepkit/injector");
const module_js_1 = require("./module.js");
const configuration_js_1 = require("./configuration.js");
const event_1 = require("@deepkit/event");
const type_1 = require("@deepkit/type");
const logger_1 = require("@deepkit/logger");
const command_js_1 = require("./command.js");
function setPartialConfig(target, partial, incomingPath = '') {
for (const i in partial) {
const path = (incomingPath ? incomingPath + '.' : '') + i;
if ((0, core_1.isObject)(partial[i])) {
setPartialConfig(target, partial[i], path);
}
else {
(0, core_1.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 = (0, core_1.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 === type_1.ReflectionKind.class || property.type.kind === type_1.ReflectionKind.objectLiteral) {
parseEnv(config, prefix, type_1.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;
(0, core_1.setPathValue)(config, dotPath, envContainer[envName]);
}
}
}
parseEnv.__type = ['config', 'prefix', () => type_1.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 configuration_js_1.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', () => module_js_1.AppModule, 'module', 'config', () => type_1.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.'];
class RootAppModule extends module_js_1.AppModule {
}
exports.RootAppModule = RootAppModule;
RootAppModule.__type = ['T', () => module_js_1.AppModule, 'RootAppModule', 'b!Pe"!7"5e!!6"w#'];
const __ΩAppEvent = ['command', 'parameters', () => injector_1.InjectorContext, 'injector', 'AppEvent', 'P&4!P&"LM4"P7#4$Mw%y'];
exports.__ΩAppEvent = __ΩAppEvent;
const __ΩAppExecutedEvent = [() => __ΩAppEvent, 'exitCode', 'AppExecutedEvent', 'Pn!\'4"Mw#y'];
exports.__ΩAppExecutedEvent = __ΩAppExecutedEvent;
const __ΩAppErrorEvent = [() => __ΩAppEvent, () => __ΩError, 'error', 'AppErrorEvent', 'Pn!n"4#Mw$y'];
exports.__ΩAppErrorEvent = __Ω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).
*/
exports.onAppExecute = (event_1.DataEventToken.Ω = [[() => __ΩAppEvent, 'n!']], new event_1.DataEventToken('app.execute'));
/**
* When a CLI command is successfully executed, this event is emitted.
*/
exports.onAppExecuted = (event_1.DataEventToken.Ω = [[() => __ΩAppExecutedEvent, 'n!']], new event_1.DataEventToken('app.executed'));
/**
* When a CLI command failed to execute, this event is emitted.
*/
exports.onAppError = (event_1.DataEventToken.Ω = [[() => __ΩAppErrorEvent, 'n!']], new event_1.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.
*/
exports.onAppShutdown = (event_1.DataEventToken.Ω = [[() => __ΩAppEvent, 'n!']], new event_1.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.
*/
class App {
constructor(appModuleOptions, serviceContainer, appModule) {
this.appModule = appModule || new RootAppModule({}, appModuleOptions);
this.serviceContainer = serviceContainer || new service_container_js_1.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 = (0, injector_1.injectedFunction)(fn, injector);
return resolvedFunction();
}
command(name, callback) {
callback = (0, core_1.isFunction)(name) ? name : callback;
name = (0, core_1.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(event_1.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}`);
}
}, [() => module_js_1.AppModule, 'module', 'config', () => type_1.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(event_1.EventDispatcher);
const logger = this.get(logger_1.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 || (0, command_js_1.getBinFromEnvironment)();
let binary = (0, core_1.pathBasename)(bin[0]);
let file = (0, core_1.pathBasename)(bin[1]);
bin = `${binary} ${file}`;
}
try {
return await (0, command_js_1.executeCommand)(bin, argv || (0, command_js_1.getArgsFromEnvironment)(), eventDispatcher, logger, scopedInjectorContext, this.serviceContainer.cliControllerRegistry.controllers);
}
finally {
if ('undefined' !== typeof process) {
process.removeListener('unhandledRejection', unhandledRejectionHandler);
}
}
}
}
exports.App = App;
App.__type = ['T', () => EnvConfigLoader, 'envConfigLoader', () => service_container_js_1.ServiceContainer, 'serviceContainer', () => __ΩExtractClassType, "config", () => module_js_1.AppModule, 'appModule', 'appModuleOptions', () => service_container_js_1.ServiceContainer, () => module_js_1.AppModule, 'constructor', () => module_js_1.AppModule, 'module', () => App, 'fromModule', () => __ΩParameters, "appModule", "setup", 'args', 'setup', '', 'use', 'fn', () => module_js_1.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', () => module_js_1.AppModule, () => __ΩClassType, () => module_js_1.AppModule, 'moduleOrClass', () => __ΩScope, 'scope', () => __ΩResolveToken, 'get', () => module_js_1.AppModule, () => __ΩClassType, () => module_js_1.AppModule, 'getInjector', () => injector_1.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
;