UNPKG

@kephas/core

Version:

Provides a common infrastructure for all the other Kephas Framework components: ambient services, dynamic reflection, composition, application management, and others.

1,407 lines (1,378 loc) 46.2 kB
import 'reflect-metadata'; import { __decorate, __metadata } from 'tslib'; import { sha256 } from 'js-sha256'; import { fromByteArray } from 'base64-js'; /** * Signals that an argument is not valid. * * @export * @class ArgumentError * @extends {Error} */ class ArgumentError extends Error { /** * Creates an instance of ArgumentError. * @param {string} message The error message. * @param {string} argName The argument name. * @memberof ArgumentError */ constructor(message, argName) { super(message); this.argName = argName; } } /** * Provides contract checks. * * @export * @class Requires */ class Requires { /** * Requires that the argument has a value (not null or undefined or empty). * * @static * @param {*} value * @param {string} name * @memberof Requires */ static HasValue(value, name) { if (!value) { throw new ArgumentError(`The argument '${name}' is not set.`, name); } } } /** * Signals that an operation does not have a proper implementation. * * @export * @class NotImplementedError * @extends {Error} */ class NotImplementedError extends Error { /** * Creates an instance of NotImplementedError. * @param {string} message The error message. * @memberof ArgumentError */ constructor(message) { super(message); } } /** * Signals that an operation is not supported. * * @export * @class NotSupportedError * @extends {Error} */ class NotSupportedError extends Error { /** * Creates an instance of NotSupportedError. * @param {string} message The error message. * @memberof ArgumentError */ constructor(message) { super(message); } } /** * Class decorator for preventing it being specialized. * * @export * @param {Function} ctor The decorated class. */ function Sealed(ctor) { Object.freeze(ctor); Object.freeze(ctor.prototype); } /** * Provides the functionality of a dynamic object. * * @export * @class Expando */ class Expando { } /** * Arguments used in command line execution. * * @export * @class Args * @implements {Expando} */ class Args { /** * Creates an instance of Args. * @param {(string | string[] | {})} [args] The arguments. * @memberof Args */ constructor(args) { if (typeof args === 'string') { this._fillArgsFromString(args, this); } else if (Array.isArray(args)) { this._fillArgsFromStringArray(args, this); } else if (typeof args === 'object') { this._fillArgsFromObject(args, this); } } _fillArgsFromObject(source, target) { for (let key in source) { target[key] = source[key]; } return target; } _fillArgsFromString(source, target) { var args = source.split(' ').filter(i => i !== ''); return this._fillArgsFromStringArray(args, target); } _fillArgsFromStringArray(source, target) { let key = ''; let value = null; let expectedValue = false; for (let currentArg of source) { var keyStartIndex = 0; if (currentArg.startsWith("--")) { keyStartIndex = 2; } else if (currentArg.startsWith("-")) { keyStartIndex = 1; } else if (currentArg.startsWith("/")) { // "/SomeSwitch" is equivalent to "--SomeSwitch" keyStartIndex = 1; } // if we received a new argument, but we expected a value, add the previous key with the value "true" if (expectedValue) { expectedValue = false; if (keyStartIndex > 0) { // set the previous key to true and continue with processing the current arg target[key] = true; } else { target[key] = Args._unescape(currentArg); continue; } } // currentArg starts a new argument var separator = currentArg.indexOf('='); if (separator >= 0) { // currentArg specifies a key with value key = Args._unescape(currentArg.substr(keyStartIndex, separator - keyStartIndex)); value = Args._unescape(currentArg.substr(separator + 1)); } else { // currentArg specifies only a key // If there is no prefix in current argument, consider it as a key with value "true" if (keyStartIndex == 0) { key = Args._unescape(currentArg); value = true; } else { key = Args._unescape(currentArg.substr(keyStartIndex)); expectedValue = true; } } // Override value when key is duplicated. So we always have the last argument win. if (!expectedValue) { target[key] = value; } } if (expectedValue) { target[key] = true; } return target; } static _unescape(value) { if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substr(1, value.length - 2); return value.replace("\\\"", "\""); } return value; } } /** * Provides contextual information. * * @export * @class Context */ class Context extends Expando { } /** * Signals an error with respect to a service or its registration. * * @export * @class ServiceError * @extends {Error} */ class ServiceError extends Error { /** * Creates an instance of ServiceError. * @param {string} message The error message. * @memberof ServiceError */ constructor(message) { super(message); } } /** * Enumerates the priority values. * They are practically a convenient way to provide integer values for defining priorities. * A lower value indicates a higher priority. * * @export * @enum {number} */ var Priority; (function (Priority) { /** * The lowest priority. Typically used by the null services. */ Priority[Priority["Lowest"] = 2147483647] = "Lowest"; /** * The low priority. Typically used by the default services. */ Priority[Priority["Low"] = 1000000] = "Low"; /** * The below normal priority. Typically used by services with a higher specialization than the default ones. */ Priority[Priority["BelowNormal"] = 1000] = "BelowNormal"; /** * The normal priority (the default). */ Priority[Priority["Normal"] = 0] = "Normal"; /** * The above normal priority. */ Priority[Priority["AboveNormal"] = -1000] = "AboveNormal"; /** * The high priority. */ Priority[Priority["High"] = -1000000] = "High"; /** * The highest priority. */ Priority[Priority["Highest"] = -2147483648] = "Highest"; })(Priority || (Priority = {})); /** * Metadata for application services. * * @export * @class AppServiceMetadata */ class AppServiceMetadata { /** * Creates an instance of AppServiceMetadata. * * @param {number|Priority} [overridePriority=Priority.Normal] Optional. The override priority. * @param {number|Priority} [processingPriority=Priority.Normal] Optional. The processing priority. * @param {string} [serviceName] Optional. The service name. * @param {Type<T>} [serviceType] Optional. The service implementation type. * @param {() => T} [serviceFactory] Optional. The service factory. * @param {T} [serviceInstance] Optional. The service instance. * @param {AppServiceInfo} [serviceContract] Optional. The service contract. * @memberof AppServiceMetadata */ constructor({ overridePriority = Priority.Normal, processingPriority = Priority.Normal, serviceName, serviceType, serviceFactory, serviceInstance, ...args } = {}) { /** * Gets the override priority. * * @type {number} * @memberof AppServiceInfo */ this.overridePriority = Priority.Normal; /** * Gets the processing priority. * * @type {number} * @memberof AppServiceInfo */ this.processingPriority = Priority.Normal; this.overridePriority = overridePriority; this.processingPriority = processingPriority; this.serviceName = serviceName; this.serviceFactory = serviceFactory; this._serviceInstance = serviceInstance; this._serviceType = serviceType; Object.assign(this, args); } /** * Gets the service implementation type. * * @type {Function} * @memberof AppServiceMetadata */ get serviceType() { return this._serviceType; } /** * Gets the application service contract information. * * @type {AppServiceInfo} * @memberof AppServiceMetadata */ get serviceContract() { return this._serviceContract; } /** * Gets the service instance. * * @type {T} * @memberof AppServiceMetadata */ get serviceInstance() { return this._serviceInstance; } } /** * Enumerates the lifetime values for application services. * * @export * @enum {number} */ var AppServiceLifetime; (function (AppServiceLifetime) { /** * The application service is shared (default). */ AppServiceLifetime[AppServiceLifetime["Singleton"] = 0] = "Singleton"; /** * The application service in instantiated with every request. */ AppServiceLifetime[AppServiceLifetime["Transient"] = 1] = "Transient"; /** * The application service is shared within a scope. */ AppServiceLifetime[AppServiceLifetime["Scoped"] = 2] = "Scoped"; })(AppServiceLifetime || (AppServiceLifetime = {})); /** * Provides information about the application service. * * @export * @class AppServiceInfo */ class AppServiceInfo { /** * Creates an instance of AppServiceInfo. * @param {Type<T>} contractType The contract type. * @param {*} contractToken The contract token. * @param {boolean} [allowMultiple=false] Indicates whether multiple instances of the provided * @param {AppServiceLifetime} [lifetime=AppServiceLifetime.Singleton] The application service lifetime. * @memberof AppServiceInfo */ constructor({ contractType, contractToken, allowMultiple = false, lifetime = AppServiceLifetime.Singleton, ...args }) { /** * Gets a value indicating whether multiple services for this contract are allowed. * * @type {boolean} * @memberof AppServiceInfo */ this.allowMultiple = false; /** * Gets the application service lifetime. * * @type {AppServiceLifetime} * @memberof AppServiceInfo */ this.lifetime = AppServiceLifetime.Singleton; this._services = []; this.contractType = contractType; this.contractToken = contractToken; this.allowMultiple = allowMultiple; this.lifetime = lifetime; Object.assign(this, args); } /** * Gets an iteration of registered services. * * @readonly * @type {IterableIterator<AppServiceMetadata>} * @memberof AppServiceInfo */ get services() { return this._services.values(); } /** * Registers a service implementation for this contract. * * @template T The service implementation type. * @param {AppServiceMetadata<T>} service * @returns {(boolean | ServiceError | AppServiceMetadata<any>)} * True, if the service was registered successfully. * False, if the service was not registered due to a higher override priority service already registered. * ServiceError, if a service is already registered with the same override priority. * AppServiceMetadata<any>, if the service to register overrid an existing one. The overridden service is returned. * @memberof AppServiceInfo */ registerService(service) { if (this.allowMultiple) { this._services.push(service); return true; } if (this._services.length > 0) { if (this._services[0].overridePriority > service.overridePriority) { const overridden = this._services[0]; this._services[0] = service; return overridden; } if (this._services[0].overridePriority === service.overridePriority) { const firstServiceType = this._services[0].serviceType; return new ServiceError(`Multiple services registered with the same override priority '${service.overridePriority}' as singleton: '${firstServiceType && firstServiceType.name}' and '${service.serviceType && service.serviceType.name}'.`); } return false; } this._services.push(service); return true; } } /** * Registry for the application service information. * * @export * @class AppServiceInfoRegistry */ class AppServiceInfoRegistry { /** * Creates an instance of AppServiceInfoRegistry. * @memberof AppServiceInfoRegistry */ constructor() { this._serviceContracts = []; this._services = []; this.registerServiceContract(AppServiceInfoRegistry, new AppServiceInfo({ contractType: AppServiceInfoRegistry, lifetime: AppServiceLifetime.Singleton })); this.registerService(AppServiceInfoRegistry, new AppServiceMetadata({ overridePriority: Priority.Low, serviceType: AppServiceInfoRegistry, serviceInstance: this, })); } /** * Gets the static instance of the registry. * * @static * @memberof AppServiceInfoRegistry */ static get Instance() { return AppServiceInfoRegistry._instance ? AppServiceInfoRegistry._instance : (AppServiceInfoRegistry._instance = new AppServiceInfoRegistry()); } ; /** * Gets an iterator over service contracts. * * @returns {IterableIterator<AppServiceInfo>} The iterator over service contracts. * @memberof AppServiceInfoRegistry */ get serviceContracts() { return this._serviceContracts.values(); } /** * Gets an iterator of services. * * @readonly * @type {IterableIterator<AppServiceMetadata>} The iterator over services. * @memberof AppServiceInfoRegistry */ get services() { return this._services.values(); } /** * Registers the provided type as a service contract. * * @static * @param {AbstractType} type The type to be registered. * @param {AppServiceInfo} appServiceInfo The service information. * @memberof AppServiceInfoRegistry */ registerServiceContract(type, appServiceInfo) { Requires.HasValue(type, 'type'); Reflect.defineMetadata(AppServiceInfoRegistry._serviceContractKey, appServiceInfo, type); this._serviceContracts.push(appServiceInfo); return this; } /** * Registers the provided type as a service type. * * @static * @param {Type<T>} type The type to be registered. * @param {AppServiceMetadata} [metadata] Optional. The service metadata. * @memberof AppServiceInfoRegistry */ registerService(type, metadata) { Requires.HasValue(type, 'type'); const appServiceInfo = (metadata && metadata.serviceContract) || this._getContractOfService(type); if (!appServiceInfo) { throw new ServiceError(`The service contract for '${type.name}' could not be identified. Check that the service or one of its bases is decorated as AppServiceContract or SingletonAppServiceContract.`); } metadata = metadata || new AppServiceMetadata(); metadata['_serviceType'] = type; metadata['_serviceContract'] = appServiceInfo; let result = appServiceInfo.registerService(metadata); if (result instanceof ServiceError) { throw result; } if (result instanceof AppServiceMetadata) { const overriddenServiceType = result.serviceType; const overriddenIndex = this._services.findIndex(m => m.serviceType === overriddenServiceType); if (overriddenIndex >= 0) { this._services[overriddenIndex] = metadata; } result = true; } else if (result) { this._services.push(metadata); } if (result) { Reflect.defineMetadata(AppServiceInfoRegistry._serviceContractKey, appServiceInfo, type); Reflect.defineMetadata(AppServiceInfoRegistry._serviceMetadataKey, metadata, type); } return this; } /** * Gets the service contract from the provided type, if possible. * * @param {AbstractType} type The type assumed to be a service contract or a service type. * @returns {(AppServiceInfo | null)} The AppServiceInfo instance or null, if the type is not a service contract. * @memberof AppServiceInfoRegistry */ getServiceContract(type) { Requires.HasValue(type, 'type'); return Reflect.getOwnMetadata(AppServiceInfoRegistry._serviceContractKey, type) || null; } /** * Gets a value indicating whether a type is a service contract. * * @param {AbstractType} type The type assumed to be a service contract. * @returns {boolean} * @memberof AppServiceInfoRegistry */ isServiceContract(type) { Requires.HasValue(type, 'type'); return Reflect.hasOwnMetadata(AppServiceInfoRegistry._serviceContractKey, type); } /** * Gets the service metadata from the provided type, if possible. * * @param {AbstractType} type The type assumed to be a service type. * @returns {(AppServiceMetadata | null)} * @memberof AppServiceInfoRegistry */ getServiceMetadata(type) { Requires.HasValue(type, 'type'); return Reflect.getOwnMetadata(AppServiceInfoRegistry._serviceMetadataKey, type) || null; } /** * Gets a value indicating whether a type is a service. * * @param {AbstractType} type * @returns {boolean} * @memberof AppServiceInfoRegistry */ isService(type) { Requires.HasValue(type, 'type'); return Reflect.hasOwnMetadata(AppServiceInfoRegistry._serviceMetadataKey, type); } _getContractOfService(type) { while (type) { const contract = this.getServiceContract(type); if (contract) { return contract; } const typePrototype = Object.getPrototypeOf(type.prototype); type = typePrototype && typePrototype.constructor; } return null; } } // metadata keys should be defined before the instance is created, // otherwise they will be null when registering the contracts. AppServiceInfoRegistry._serviceContractKey = 'kephas:serviceContract'; AppServiceInfoRegistry._serviceMetadataKey = 'kephas:serviceMetadata'; /** * Marks a class as being an application service. Its closest base registered as service contract is * considered to be its contract. * * @export * @param {number|Priority} [overridePriority=Priority.Normal] Optional. The override priority. * @param {number|Priority} [processingPriority=Priority.Normal] Optional. The processing priority. * @param {string} [serviceName] Optional. The service name. * @returns A function. */ function AppService({ overridePriority = Priority.Normal, processingPriority = Priority.Normal, serviceName, provider, registry } = {}) { return (type) => { (registry || AppServiceInfoRegistry.Instance).registerService(type, new AppServiceMetadata({ overridePriority, processingPriority, serviceName, serviceType: type, serviceInstance: typeof provider === 'object' ? provider : undefined, serviceFactory: typeof provider === 'function' ? provider : undefined, })); }; } /** * Marks a class as being contract for transient application services. * * @export * @param {boolean} [allowMultiple=false] Indicates whether multiple services may be registered with the same contract or not. * @param {AbstractType} [contractType] Indicates the contract type, if different from the decorated type. * @param {*} [contractToken] Indicates the contract token, if different from the contract type. */ function AppServiceContract({ allowMultiple = false, contractType, contractToken, registry, ...args } = {}) { return (type) => { contractType = contractType ? contractType : type; contractToken = contractToken ? contractToken : contractType; const appServiceInfo = new AppServiceInfo({ contractType, contractToken, allowMultiple, lifetime: AppServiceLifetime.Transient, ...args }); (registry || AppServiceInfoRegistry.Instance).registerServiceContract(type, appServiceInfo); }; } /** * Marks a class as being contract for singleton application services. * * @export * @param {boolean} [allowMultiple=false] Indicates whether multiple services may be registered with the same contract or not. * @param {AbstractType} [contractType] Indicates the contract type, if different from the decorated type. * @param {*} [contractToken] Indicates the contract token, if different from the contract type. */ function SingletonAppServiceContract({ allowMultiple = false, contractType, contractToken, registry, ...args } = {}) { return (type) => { contractType = contractType ? contractType : type; contractToken = contractToken ? contractToken : contractType; const appServiceInfo = new AppServiceInfo({ contractType, contractToken, allowMultiple, lifetime: AppServiceLifetime.Singleton, ...args }); (registry || AppServiceInfoRegistry.Instance).registerServiceContract(type, appServiceInfo); }; } /** * A deferrable value. * * @export * @class Deferrable * @template T */ let Deferrable = class Deferrable { /** * Creates an instance of Deferrable. * @memberof Deferrable */ constructor() { /** * Resolves the promise to the indicated value. * * @param {(T | PromiseLike<T>)} [value] The resolved value. * @memberof Deferrable */ this.resolve = v => { }; /** * Rejects the promise with the indicated reason. * * @param {*} [reason] The reason for rejection. * @memberof Deferrable */ this.reject = r => { }; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } }; Deferrable = __decorate([ Sealed, __metadata("design:paramtypes", []) ], Deferrable); /** * Helper class for working with services. * * @export * @class ServiceHelper */ class ServiceHelper { /** * Initializes the service asynchronously, provided it implements either the * Initializable or AsyncInitializable interfaces. * * @static * @param {{ [key: string]: any }} service * @param {Context} [context] The context to pass to the initialization method. * @returns {Promise<void>} A promise returning the asynchronous result. * @memberof ServiceHelper */ static initializeAsync(service, context) { if (service.initializeAsync) { return service.initializeAsync(context); } const deferrable = new Deferrable(); if (service.initialize) { deferrable.resolve(true); service.initialize(context); } else { deferrable.resolve(false); } return deferrable.promise; } } /** * Enumerates the logging levels. * * @enum {number} */ var LogLevel; (function (LogLevel) { /** * Fatal errors. */ LogLevel[LogLevel["Fatal"] = 0] = "Fatal"; /** * Common errors. */ LogLevel[LogLevel["Error"] = 1] = "Error"; /** * Warning information. */ LogLevel[LogLevel["Warning"] = 2] = "Warning"; /** * Common information. */ LogLevel[LogLevel["Info"] = 3] = "Info"; /** * Debugging information. */ LogLevel[LogLevel["Debug"] = 4] = "Debug"; /** * Tracing information. */ LogLevel[LogLevel["Trace"] = 5] = "Trace"; })(LogLevel || (LogLevel = {})); /** * Base service for logging. * * @class Logger */ let Logger = class Logger { constructor() { this._logLevel = LogLevel.Info; } /** * Logs the information at the provided level. * * @param {LogLevel | string} level The logging level. * @param {Error} exception The error that occured (may not be specified). * @param {string} messageFormat The message format. * @param {...any[]} args The arguments for the message format. * @memberof Logger */ log(level, exception, messageFormat, ...args) { if (typeof level === 'string') { level = LogLevel[level]; } if (this.isEnabled(level)) { this.write(level, exception, messageFormat, args); } } /** * Indicates whether logging at the indicated level is enabled. * @param {LogLevel | string} level The logging level. * @return true if enabled, false if not. */ isEnabled(level) { if (typeof level === 'string') { level = LogLevel[level]; } return level <= this._logLevel; } /** * Sets the logging level to the indicated one. * * @param {(LogLevel | string)} level The new log level. * @memberof Logger */ setLevel(level) { if (typeof level === 'string') { level = LogLevel[level]; } this._logLevel = level; } /** * Logs the event at the fatal level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ fatal(event, ...args) { this._log(LogLevel.Fatal, event, args); } /** * Logs the event at the error level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ error(event, ...args) { this._log(LogLevel.Error, event, args); } /** * Logs the event at the warning level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ warn(event, ...args) { this._log(LogLevel.Warning, event, args); } /** * Logs the event at the information level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ info(event, ...args) { this._log(LogLevel.Info, event, args); } /** * Logs the event at the debug level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ debug(event, ...args) { this._log(LogLevel.Debug, event, args); } /** * Logs the event at the trace level. * * @param {Error | string} event The event to be logged. * @param {...any[]} args The arguments for the event. * @memberof Logger */ trace(event, ...args) { this._log(LogLevel.Trace, event, args); } /** * Overridable method for writing to the log. * * @param {LogLevel} level The logging level. * @param {Error} exception The error that occured (may not be specified). * @param {string} messageFormat The message format. * @param {any[]} args The arguments for the message format. * @memberof Logger */ write(level, exception, messageFormat, args) { const message = exception ? exception.message : messageFormat; switch (level) { case LogLevel.Fatal: console.error('FATAL ' + message, ...args); break; case LogLevel.Error: console.error(message, ...args); break; case LogLevel.Warning: console.warn(message, ...args); break; case LogLevel.Info: console.info(message, ...args); break; case LogLevel.Debug: console.debug(message, ...args); break; case LogLevel.Trace: console.trace(message, ...args); break; default: break; } } _log(level, event, args) { if (!this.isEnabled(level)) { return; } if (typeof event === 'string') { this.write(level, null, event, args); } else { const messageFormat = args && args.length && args[0]; args = (args && args.length && args.splice(0, 1)) || []; this.write(level, event, messageFormat, args); } } }; Logger = __decorate([ AppService({ overridePriority: Priority.Low }), SingletonAppServiceContract() ], Logger); /** * Signals an injection error. * * @export * @class InjectionError * @extends {Error} */ class InjectionError extends Error { /** * Creates an instance of InjectionError. * @param {string} message The error message. * @memberof InjectionError */ constructor(message) { super(message); } } var Injector_1; /** * Contract for composition context. * * @export * @abstract * @class Injector */ let Injector = Injector_1 = class Injector { /** * Gets or sets the static instance of the Injector. * * @readonly * @static * @type {Injector} * @memberof Injector */ static get instance() { return Injector_1._instance; } static set instance(value) { if (value === Injector_1._instance) { return; } if (value && Injector_1._instance) { throw new InjectionError(`Both the instance (${Injector_1._instance}) and the new value (${value}) are set. If you really intend to change the injector, set it first to null and then set the new value.`); } Injector_1._instance = value; } }; Injector = Injector_1 = __decorate([ SingletonAppServiceContract() ], Injector); var LiteInjector_1; /** * Provides a container for the dependency injection. * * @export * @class LiteInjector */ let LiteInjector = LiteInjector_1 = class LiteInjector extends Injector { /** * Creates an instance of LiteInjector. * @param {AppServiceInfoRegistry} [registry] * @memberof LiteInjector */ constructor(registry) { super(); this._singletons = new WeakMap(); this._registry = registry || (registry = AppServiceInfoRegistry.Instance); if (registry !== AppServiceInfoRegistry.Instance) { const appServiceInfo = new AppServiceInfo({ contractType: Injector, allowMultiple: false, lifetime: AppServiceLifetime.Singleton, }); registry.registerServiceContract(Injector, appServiceInfo); registry.registerService(LiteInjector_1, new AppServiceMetadata({ overridePriority: Priority.Low, _serviceContract: appServiceInfo, serviceType: LiteInjector_1, serviceInstance: this, })); } } /** * Gets the service instance of the required service contract type. * * @template T * @param {Type<T>} type The service contract type. * @param notFoundResolver A resolver for the case when a type cannot be resolved. * @returns {T} The requested service. * @memberof LiteInjector */ resolve(type, notFoundResolver) { const serviceInfo = notFoundResolver ? this._tryGetServiceContract(type) : this._getServiceContract(type); if (!serviceInfo) { return notFoundResolver(type); } if (serviceInfo.lifetime === AppServiceLifetime.Singleton) { let service = this._singletons.get(type); if (!service) { const serviceMetadata = this._getSingleServiceMetadata(serviceInfo); service = this._createInstance(serviceMetadata, notFoundResolver); this._singletons.set(type, service); } return service; } else { return this._createInstance(this._getSingleServiceMetadata(serviceInfo), notFoundResolver); } } /** * Gets an array of service instances. * * @template T * @param {Type<T>} type The service contract type. * @param notFoundResolver A resolver for the case when a type cannot be resolved. * @returns {T[]} The array of the requested service. * @memberof LiteInjector */ resolveMany(type, notFoundResolver) { const serviceInfo = notFoundResolver ? this._tryGetServiceContract(type) : this._getServiceContract(type); if (!serviceInfo) { // the resolver should know that it should return an array of items. return notFoundResolver(type); } if (serviceInfo.lifetime === AppServiceLifetime.Singleton) { let services = this._singletons.get(type); if (services === undefined || services === null) { services = [...serviceInfo.services].map(s => this._createInstance(s, notFoundResolver)); this._singletons.set(type, services); } return services; } else { return [...serviceInfo.services].map(s => this._createInstance(s, notFoundResolver)); } } _tryGetServiceContract(type) { return this._registry.getServiceContract(type); } _getServiceContract(type) { const serviceInfo = this._registry.getServiceContract(type); if (!serviceInfo) { throw new InjectionError(`The type '${type.name}' is not registered as a service contract.`); } return serviceInfo; } _getSingleServiceMetadata(serviceInfo) { const services = [...serviceInfo.services]; if (services.length === 0) { throw new InjectionError(`The service contract '${serviceInfo.contractType.name}' does not have any services registered.`); } if (services.length > 1) { throw new InjectionError(`The service contract '${serviceInfo.contractType.name}' has multiple services registered: '${services.join("', '")}'.`); } return services[0]; } _createInstance(serviceMetadata, notFoundResolver) { if (serviceMetadata.serviceInstance) { return serviceMetadata.serviceInstance; } if (serviceMetadata.serviceFactory) { return serviceMetadata.serviceFactory(this); } const serviceType = serviceMetadata.serviceType; const paramTypes = Reflect.getOwnMetadata('design:paramtypes', serviceType); if (paramTypes) { const ctorArgs = paramTypes.map(t => this.resolve(t, notFoundResolver)); return new serviceType(...ctorArgs); } return new serviceType(); } }; LiteInjector = LiteInjector_1 = __decorate([ AppService({ overridePriority: Priority.Low, provider: _ => Injector.instance }), __metadata("design:paramtypes", [AppServiceInfoRegistry]) ], LiteInjector); // make sure the injector is set. if (!Injector.instance) { Injector.instance = new LiteInjector(); } /** * Base class for serializable objects. * * @export * @abstract * @class Serializable */ class Serializable { /** * Gets or sets the name of the key holding the type's full name. * * @static * @type {string} * @memberof Serializable */ static get TypeFullNameKey() { return Serializable._typeFullNameKey; } static set TypeFullNameKey(value) { Requires.HasValue(value, 'value'); Serializable._typeFullNameKey = value; } /** * Sets the type name for serialization/deserialization purposes. * * @static * @template T * @param {AbstractType} type The type where the full name should be set. * @param {string} typeFullName The type's full name. * * @memberOf Serializable */ static setTypeFullName(type, typeFullName) { Requires.HasValue(typeFullName, 'typeFullName'); Reflect.defineMetadata(Serializable._typeFullNameKey, typeFullName, type); } /** * Sets the type namespace for serialization/deserialization purposes. * * @static * @template T * @param {AbstractType} type The type where the type name should be set. * @param {string} namespace The type namespace. * * @memberOf Serializable */ static setTypeNamespace(type, namespace) { Reflect.defineMetadata(Serializable._typeNamespaceKey, namespace, type); } /** * Gets the type's full name for serialization/deserialization purposes. * * @static * @param {{} | AbstractType} typeOrInstance The type from where the type name should be retrieved. * @returns {(string | undefined)} The type's full name. * * @memberOf Serializable */ static getTypeFullName(typeOrInstance) { if (typeOrInstance instanceof Function) { return Reflect.getOwnMetadata(Serializable._typeFullNameKey, typeOrInstance); } return Reflect.getOwnMetadata(Serializable._typeFullNameKey, typeOrInstance.constructor); } /** * Gets the type namespace for serialization/deserialization purposes. * * @static * @param {AbstractType} typeOrInstance The type from where the type name should be retrieved. * @returns {(string | undefined)} The type name. * * @memberOf Serializable */ static getTypeNamespace(typeOrInstance) { return Reflect.getOwnMetadata(Serializable._typeNamespaceKey, typeOrInstance); } /** * Converts the provided object to a JSON representation. * * @static * @param {object} obj The object to be converted. * @returns {*} The object containing the JSON representation. * @memberof Serializable */ static getJSON(obj) { const json = {}; const type = obj.constructor; let typeName = Serializable.getTypeFullName(type) || Serializable.getTypeFullName(obj); if (!typeName) { typeName = type.name; const namespace = Serializable.getTypeNamespace(type); if (namespace) { typeName = `${namespace}.${typeName}`; } } if (typeName) { json[Serializable._typeFullNameKey] = typeName; } Object.keys(obj).forEach(propName => { if (!propName.startsWith('_') && !propName.startsWith('#')) { json[propName] = obj[propName]; } }); return json; } /** * Converts the provided object to a string. * * @static * @param {object} obj The object to be converted. * @returns {string} The object's string representation. * @memberof Serializable */ static getString(obj) { return JSON.stringify(Serializable.getJSON(obj)); } /** * Converts this object to a JSON representation. * * @returns {{}} * * @memberOf Serializable */ toJSON() { return Serializable.getJSON(this); } /** * Converts this object to a string. * * @returns {string} * * @memberOf Serializable */ toString() { return JSON.stringify(this.toJSON()); } } Serializable._typeFullNameKey = '$type'; Serializable._typeNamespaceKey = 'kephas:namespace'; /** * Class decorator for annotating the namespace of a class. * * @export * @param {Function} ctor The decorated class. */ function Namespace(namespace) { return (type) => { Serializable.setTypeNamespace(type, namespace); }; } /** * Class decorator for annotating the full name of a class. * * @export * @param {Function} ctor The decorated class. */ function FullName(fullName) { return (type) => { Serializable.setTypeFullName(type, fullName); }; } ; /** * Provides the Hash method for hashing values. * * @export * @class HashingService */ let HashingService = class HashingService { /** * Hashes the value producing a Base64 encoded string. * * @param {string} value The value to hash. * @returns {string} The hash value as a Base64 encoded string. * @memberof HashingService */ hash(value, context) { const hashedValue = sha256.array(value); return fromByteArray(hashedValue); } }; HashingService = __decorate([ AppService({ overridePriority: Priority.Low }), SingletonAppServiceContract() ], HashingService); // tslint:disable: max-classes-per-file class EventSubscription { constructor(match, callback, _onDispose) { this.match = match; this.callback = callback; this._onDispose = _onDispose; } dispose() { this._onDispose(this); } } /** * Singleton application service handling in-process event publishing/subscribing. * * @export * @class EventHub */ let EventHub = class EventHub { /** * Creates an instance of EventHub. * * @param {Logger} logger The logger. * @memberof EventHub */ constructor(logger) { this._subscriptions = []; this._logger = logger || new Logger(); } /** * Gets the logger. * * @readonly * @protected * @type {Logger} * @memberof EventHub */ get logger() { return this._logger; } /** * Publishes the event asynchronously to its subscribers. * * @param {*} event The event. * @param {Context} [context] Optional. The context. * @returns {Promise<any>} The promise. * @memberof EventHub */ async publishAsync(event, context) { const subscriptions = this._subscriptions.filter(s => s.match(event)); for (const subscription of subscriptions) { try { const promise = subscription.callback(event, context); if (promise) { await promise; } } catch (err) { this.logger.error(err); } } } /** * Subscribes to the event(s) matching the criteria. * * @template T The event type. * @param {(AbstractType | Type<T>)} match Specifies the match type. * @param {((event: T, context?: Context) => Promise<any> | void)} callback The callback. * @returns {Disposable} A disposable event subscription. * @memberof EventHub */ subscribe(match, callback) { const subscription = new EventSubscription(this._getMatch(match), callback, s => { const i = this._subscriptions.indexOf(s); this._subscriptions.splice(i, 1); }); this._subscriptions.push(subscription); return subscription; } _getMatch(match) { return event => event instanceof match; } }; EventHub = __decorate([ AppService({ overridePriority: Priority.Low }), SingletonAppServiceContract(), __metadata("design:paramtypes", [Logger]) ], EventHub); /* * Public API Surface of core * Check */ /** * Generated bundle index. Do not edit. */ export { AppService, AppServiceContract, AppServiceInfo, AppServiceInfoRegistry, AppServiceLifetime, AppServiceMetadata, Args, ArgumentError, Context, Deferrable, EventHub, Expando, FullName, HashingService, InjectionError, Injector, LiteInjector, LogLevel, Logger, Namespace, NotImplementedError, NotSupportedError, Priority, Requires, Sealed, Serializable, ServiceError, ServiceHelper, SingletonAppServiceContract }; //# sourceMappingURL=kephas-core.mjs.map