@kephas/core
Version:
Provides a common infrastructure for all the other Kephas Framework components: ambient services, dynamic reflection, composition, application management, and others.
1,412 lines (1,383 loc) • 46.8 kB
JavaScript
import { __rest, __decorate, __metadata, __awaiter } from 'tslib';
import 'reflect-metadata';
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(_a = {}) {
var { overridePriority = Priority.Normal, processingPriority = Priority.Normal, serviceName, serviceType, serviceFactory, serviceInstance } = _a, args = __rest(_a, ["overridePriority", "processingPriority", "serviceName", "serviceType", "serviceFactory", "serviceInstance"]);
/**
* 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(_a) {
var { contractType, contractToken, allowMultiple = false, lifetime = AppServiceLifetime.Singleton } = _a, args = __rest(_a, ["contractType", "contractToken", "allowMultiple", "lifetime"]);
/**
* 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(_a = {}) {
var { allowMultiple = false, contractType, contractToken, registry } = _a, args = __rest(_a, ["allowMultiple", "contractType", "contractToken", "registry"]);
return (type) => {
contractType = contractType ? contractType : type;
contractToken = contractToken ? contractToken : contractType;
const appServiceInfo = new AppServiceInfo(Object.assign({ 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(_a = {}) {
var { allowMultiple = false, contractType, contractToken, registry } = _a, args = __rest(_a, ["allowMultiple", "contractType", "contractToken", "registry"]);
return (type) => {
contractType = contractType ? contractType : type;
contractToken = contractToken ? contractToken : contractType;
const appServiceInfo = new AppServiceInfo(Object.assign({ 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);
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
*/
publishAsync(event, context) {
return __awaiter(this, void 0, void 0, function* () {
const subscriptions = this._subscriptions.filter(s => s.match(event));
for (const subscription of subscriptions) {
try {
const promise = subscription.callback(event, context);
if (promise) {
yield 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