Package Exports
- @mdf.js/core
- @mdf.js/core/dist/Health
- @mdf.js/core/dist/Health/index.js
- @mdf.js/core/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@mdf.js/core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@mdf.js/core
Mytra Development Framework - @mdf.js/core
Core module with shared components for resource management and instrumentation API
Table of contents
- @mdf.js/core
Introduction
The @mdf.js/core module is a set of types, interfaces, and classes that standardize the way in which the state of the resources managed by the applications developed with @mdf.js is reported. This module is part of the @mdf.js ecosystem, which aims to provide a set of tools and libraries that facilitate the development of applications in Node.js, especially those that require a high level of observability and monitoring.
The @mdf.js/core module is composed of the following elements:
- The
Healthinterface, which is a set of types and interfaces that standardize the way in which the state of the resources is reported. - The
Layernamespace, which contains the following elements:- The
Appinterface, which is a set of types and interfaces that standardize the components of an application from the observability point of view. - The
ProviderAPI, which allows for the instrumentation of resource providers (databases, publish/subscribe services, etc.) so they can be managed in a standardized way within the @mdf.js API, especially in terms of observability, configuration management, and resource provider state management.
- The
- The
JobsAPI, which allows for the creation and management of jobs in a standardized way within the @mdf.js API.
Installation
To install the @mdf.js/core module, you can use the following commands:
- npm
npm install @mdf.js/core- yarn
yarn add @mdf.js/coreHealth Interface
The Health interface is a set of types and interfaces that standardize the way in which the state of the resources managed by the Provider is reported. The Health interface is composed of the following types and interfaces:
Status: a type that represents the status of a resource, which can be one of the following values:pass: indicates that the resource is in a normal operating state.fail: indicates that the resource is in an error state.warn: indicates that the resource is in a warning state.
Check<T>: an interface that defines the structure of a check object, with the following properties:componentId: a unique identifier for an instance of a specific sub-component/dependency of a service in UUID v4 format. Multiple objects with the samecomponentIdmay appear in the details if they are from different nodes.componentType: an optional string that SHOULD be present ifcomponentNameis present. It indicates the type of the component, which could be a pre-defined value from the spec (such ascomponent,datastore, orsystem), a common and standard term from a well-known source (like schema.org, IANA, or microformats), or a URI indicating extra semantics and processing rules.observedValue: an optional property that could be any valid JSON value (such as a string, number, object, array, or literal). The type is referenced byTin the interface definition.observedUnit: an optional string that SHOULD be present ifmetricValueis present. It could be a common and standard term from a well-known source or a URI indicating extra semantics and processing rules.status: a value of typeStatusindicating whether the service status is acceptable or not.affectedEndpoints: an optional array of strings containing URI Templates as defined by [RFC6570], indicating which particular endpoints are affected by the check.time: an optional string indicating the date-time, in ISO8601 format, at which the reading of themetricValuewas recorded.output: an optional property containing raw error output in case of “fail” or “warn” states. This field SHOULD be omitted for “pass” state.links: an optional object containing link relations and URIs [RFC3986] for external links that may contain more information about the health of the endpoint. This includes potentially a “self” link, which may be used by clients to check health via HTTP response code.- ... and any other property that the
Providerdeveloper considers necessary to provide more information about the check.
Checks: The "checks" object within the health check model allows for the representation of the health status of various logical sub-components of a service. This flexible structure is designed to accommodate the complexities of modern distributed systems, where each component may consist of multiple nodes, each potentially exhibiting a different health status. Here's a breakdown of how the "checks" object is structured and the semantics of its keys and values:- Each key in the "checks" object represents a logical sub-component of the service. The uniqueness of each key ensures that the health status of each sub-component can be individually assessed and reported.
- The value associated with each key is an array of
Checkobjects. This array accommodates scenarios where a single logical sub-component is supported by multiple nodes. For single-node sub-components, or when the distinction between nodes is not relevant, a single-element array is used for consistency. - The key for each sub-component is a unique string within the "details" section of the health check model. It may consist of two parts, separated by a colon (
:):{componentName}:{metricName}. The structure of these keys is as follows:componentName: This part of the key provides a human-readable identifier for the component. It must not contain a colon, as the colon serves as the delimiter between the component name and the metric name.metricName: This part specifies the particular metric for which the health status is reported. Like the component name, it must not contain a colon. The metric name can be a pre-defined value specified by the health check model (such as "utilization," "responseTime," "connections," or "uptime"), a common term from a recognized standard or organization (like schema.org, IANA, or microformats), or a URI that conveys additional semantics and processing rules associated with the metric.
The
Checkstype is defined to capture this structure, where each entry in the object maps to an array ofCheckobjects, allowing for a detailed and nuanced representation of the health status across different parts of a service and its underlying infrastructure.export type Checks<T = any> = { [entry in CheckEntry]: Check<T>[]; };
And finally, the Health export an auxiliary method overallStatus that determine the Status of the component based on the Checks object.
function overallStatus(checks: Checks): Status Layer.App Interface
The App interface is a set of types and interfaces that standardize the way in which the state of the application is reported. The App API define 3 different types of components from the observability point of view:
Component: a component is any part of the system that has a own identity and can be monitored for error handling. The only requirement is to emit an error event when something goes wrong, to have a name and unique component identifier./** Component */ export interface Component extends EventEmitter { /** Emitted when the component throw an error*/ on(event: 'error', listener: (error: Crash | Error) => void): this; /** Component name */ name: string; /** Component identifier */ componentId: string; }
This interface define:
- Properties:
name: the name of the component, this name is used by the observability layers to identify the component.componentId: a unique identifier for the instance in UUID v4 format.
- Events:
on('error', listener: (error: Crash | Error) => void): this: event emitted every time theComponentemits an error.
- Properties:
Resource: a resource is extended component that represent the access to an external/internal resource, besides the error handling and identity, it has a start, stop and close methods to manage the resource lifecycle. It also has a checks property to define the checks that will be performed over the resource to achieve the resulted status. The most typical example of a resource are theProviderthat allow to access to external databases, message brokers, etc./** Resource */ export interface Resource extends Component { /** Emitted when the component throw an error*/ on(event: 'error', listener: (error: Crash | Error) => void): this; /** Emitted on every status change */ on(event: 'status', listener: (status: Status) => void): this; /** Checks performed over this component to achieve the resulted status */ checks: Checks; /** Resource status */ status: Status; /** Resource start function */ start: () => Promise<void>; /** Resource stop function */ stop: () => Promise<void>; /** Resource close function */ close: () => Promise<void>; }
Besides the
Componentproperties and events, this interface define:- Properties:
checks: list of checks performed by the component to determine its state. It is a list of objects of typeHealth.Checks.status: the current status of theResource. It is a variable of typeHealth.Statuswhose value can be:pass: indicates that theResourceis in a normal operating state. If all the checks are inpassstate, theResourcewill be inpassstate.fail: indicates that theResourceis in an error state. If any of the checks are infailstate, theResourcewill be infailstate.warn: indicates that theResourceis in a warning state. If any of the checks are inwarnstate, theResourcewill be inwarnstate.
- Methods:
start(): Promise<void>: initialize theResource, internal jobs, external dependencies connections ....stop(): Promise<void>: stops theResource, close connections, stop internal jobs, etc.close(): Promise<void>: closes theResource, release resources, destroy connections, etc.
- Events:
on('status', listener: (status: Health.Status) => void): this: event emitted every time theResourcechanges its state.
- Properties:
Service: a service is a special kind of resource that besidesResourceproperties, it could offer:- Its own REST API endpoints, using an express router, to expose details about service.
- A links property to define the endpoints that the service expose.
- A metrics property to expose the metrics registry where the service will register its own metrics. This registry should be a prom-client registry.
/** Service */ export interface Service extends Resource { /** Express router */ router?: Router; /** Service base path */ links?: Links; /** Metrics registry */ metrics?: Registry; }
Besides the
Resourceproperties, methods and events, this interface define:- Properties:
router: an express router that will be used to expose the service endpoints.links: an object containing link relations and URIs [RFC3986] for external links that may contain more information about the health of the endpoint. This includes potentially a “self” link, which may be used by clients to check health via HTTP response code.metrics: a metrics registry that will be used to register the service metrics. This registry should be a prom-client registry.
Layer.Provider API
The Provider API of @mdf.js allows for the instrumentation of resource providers (databases, publish/subscribe services, etc.) so they can be managed in a standardized way within the @mdf.js API, especially in terms of:
- Observability, as all Providers implement the
Layer.App.Resourceinterface. - Configuration management, providing an interface for managing default, specific, or environment variable-based configurations.
- Resource provider state management, through the standardization of the states and operation modes of the Providers.
Some examples of providers instrumented with this API are:
I Want to Use an Already Instrumented Provider
A provider that has been correctly instrumented with @mdf.js/core API always offers a Factory class with a single static method create that allows creating new instances of the provider with the desired configuration for each case.
This create method may receive a configuration object with the following optional properties:
name: the name of the provider that will be used for observability, if not specified, the default provider name will be used.logger: aLoggerInstanceobject, belonging to the @mdf.js/logger module or any other object that implements theLoggerInstanceinterface. If specified, it will be used by both theProviderand thePortit wraps. If not specified, a DEBUG type logger will be used with the provider's name indicated in thenameproperty, or if not specified, with the default provider name.config: specific configuration object for the module wrapped by theProviderin question. If not specified, the default configuration set by theProviderdeveloper will be used.useEnvironment: this property can be a boolean or a string, with its default value beingfalse. It can take the following values:boolean:true: indicates that the environment variables defined by theProviderdeveloper should be used, combined with theProvider's default values and the configuration passed as an argument. The configuration is established in this order of priority: first, the arguments provided directly are taken into account, then the configurations defined in the system's environment variables, and lastly, if none of the above is available, the default values are applied.false: indicates that the environment variables defined by theProviderdeveloper should NOT be used, only the default values will be combined with the configuration passed as an argument. In this case, the configuration is established in this order of priority: first, the arguments provided directly are taken into account, and then the default values.
string: if a string is passed, it will be used as a prefix for the environment configuration variables, represented inSCREAMING_SNAKE_CASE, which will be transformed tocamelCaseand combined with the rest of the configuration, except with the environment variables defined by theProviderdeveloper. In this case, the configuration is established in this order of priority: first, the arguments provided directly are taken into account, then the configurations defined in the system's environment variables, and lastly, if none of the above is available, the default values are applied.
Note: The aim of this configuration handling is to allow the user to work in two different modes:
- User rules: the user sets their own configuration, disregarding the environment variables indicated by the
Providerdeveloper, with the alternative of being able to use a fast track of environment variables usage through a prefix. That is:useEnvironment: falseoruseEnvironment: 'MY_PREFIX_'.- Provider rules: the user prefers to use the environment variables defined by the
Providerdeveloper, in which case the management of the environment variables should be delegated to theProvider, allowing the user to set specific configuration values through the input argument, an attempt to create a mixed configuration where both theProviderand the service/application try to use environment variables, can lead to undesirable situations. That is:useEnvironment: true.
import { Mongo } from '@mdf.js/mongo-provider';
// Using only `Provider` default values:
// - [x] `Provider` default values
// - [] `Provider` environment variables
// - [] User custom values
// - [] Parsing of environment variables
const myProvider = Mongo.Factory.create();
// Using `Provider` default values and custom values:
// - [x] `Provider` default values
// - [] `Provider` environment variables
// - [x] User custom values
// - [] Parsing of environment variables
const myProvider = Mongo.Factory.create({
config: {
url: 'mongodb://localhost:27017',
appName: 'myName',
},
});
const myProvider = Mongo.Factory.create({
config: {
url: 'mongodb://localhost:27017',
appName: 'myName',
},
useEnvironment: false
});
// Using `Provider` default values, custom values and `Provider` environment variables:
// - [x] `Provider` default values
// - [x] `Provider` environment variables
// - [x] User custom values
// - [] Parsing of environment variables
const myProvider = Mongo.Factory.create({
config: {
url: 'mongodb://localhost:27017',
appName: 'myName',
},
useEnvironment: true
});
// Using `Provider` default values, custom values and `Provider` environment variables with a prefix:
// - [x] `Provider` default values
// - [] `Provider` environment variables
// - [x] User custom values
// - [x] Parsing of environment variables
const myProvider = Mongo.Factory.create({
config: {
url: 'mongodb://localhost:27017',
appName: 'myName',
},
useEnvironment: 'MY_PREFIX_'
});Now that we have our provider instance, let's see what it offers:
- Properties:
componentId: a unique identifier for the instance in UUID v4 format.name: the name of the provider, by default, set by the Provider developer or the name provided in the configuration.config: the resulting configuration that was used to create the instance.state: the current state of theProvider. It is a variable of typeProviderStatewhose value can be:running: indicates that theProvideris in a normal operating state.stopped: indicates that theProviderhas been stopped or has not been initialized.error: indicates that theProviderhas encountered an error in its operation.
error: in case theProvideris in an error state, this property will contain an object with the error information. This property is of typeProviderError, which is a type alias forCrash | Multi | undefined, you can find more information aboutCrashandMultitypes in the documentation of @mdf.js/crash.date: the date in ISO 8601 format of the last update of theProvider's state.checks: list of checks performed by theProviderto determine its state. It is a list of objects of typeHealth.Checks, which will contain at least one entry with the information of the state check of theProviderunder the property[${name}:status]where${name}is the name of theProvider, indicated in the configuration or by default. This field will contain an array with a single object of typeHealth.Checkthat will contain the information of the state check of theProvider. Example value of checks:{ "myName:status": [ { "status": "pass", // "pass" | "fail" | "warn" "componentId": "00000000-0000-0000-0000-000000000000", // UUID v4 "componentType": "connection", // or any other type indicated by the `Provider` developer "observedValue": "running", // "running" | "stopped" | "error" "time": "2024-10-10T10:10:10.000Z", "output": undefined, // or the information of the error property } ] }
The
Providerdeveloper can add more checks to the list ofchecksto provide more information about the state of theProvideror the resources it manages.client: instance of the client/resource wrapped by theProviderthat has been created with the provided configuration.
Note: the instance returned by the
createmethod is an instance ofProviderwith generic types for theconfig(PortConfig) andclient(PortClient) properties, which should be extended by the Provider developer to provide a better usage experience, so that the user can know both the configuration and the client that is being used, as well as the client that has been wrapped.
- Methods:
async start(): Promise<void>: starts theProviderand the resource it wraps.async stop(): Promise<void>: stops theProviderand the resource it wraps.async fail(error: Crash | Error): Promise<void>: sets theProviderin an error state and saves the error information.
- Events:
on('error', listener: (error: Crash | Error) => void): this: event emitted every time theProvideremits an error.on('status', listener: (status: Health.Status) => void): this: event emitted every time theProviderchanges its state.
I want to instrument a Provider with this API
To instrument a provider with the @mdf.js/core Provider API, the following actions must be taken:
- Use the abstract class
Port, provided by the API, which must be extended by theProviderdeveloper. - Define the properties of the
PortConfigValidationStructobject, which indicate the default values of theProvider, the values coming from environment variables, and the validation object of type 'Schema' from the Joi module. - Use the
ProviderFactoryCreatorfunction to create an instance of theProvider, the class of type Mixin that standardizes the creation ofProviderinstances with the desired configuration.
Let's see point by point how the instrumentation of a Provider is carried out.
1. Extendiendo la clase Port
The Port should be extended to implement a new specific Port. This class implements some util logic to facilitate the creation of new Ports, for this reason is exposed as abstract class, instead of an interface. The developer should keep the constructor signature, in order to maintain the compatibility with the ProviderFactoryCreator function.

The basic operations that already implemented in the class are:
- Properties:
uuid: create by thePortclass, it is a unique identifier for the port instance, this uuid is used in error traceability.name: the name of the port, by default, set by the Provider developer or the name provided in the configuration.config: the resulting configuration that was used to create thePortinstance.logger: aLoggerInstanceobject, belonging to the @mdf.js/logger module or any other object that implements theLoggerInstanceinterface. This property is used to log information about the port and the resources it manages. ThePortclass set the context of the logger to the portnameand theuuid, so it's not necessary to include the context and the uuid of the port in the log messages.checks: list of checks performed by thePortby the use ofaddCheckmethod, these checks are collected by theProvider, together with the own check ofstatus, and offered to the observability layers.
Note: As the signature of the
Portconstructor should maintained:constructor(config: PortConfig, logger: LoggerInstance, name: string)Your
Portclass extension will receive theconfig,loggerandnameproperties, and you should call thesuperconstructor with these properties.
What the developers of the Provider should develop in their own Port class extension is:
async start(): Promise<void>method, which is responsible initialize or stablish the connection to the resources.async stop(): Promise<void>method, which is responsible stop services or disconnect from the resources.async close(): Promise<void>method, which is responsible to destroy the services, resources or perform a simple disconnection.stateproperty, a boolean value that indicates if the port is connected (true) or healthy (true) or not (false).clientproperty, that return the PortClient instance that is used to interact with the resources.
In the next example you can see the expected behavior of a Port class extension when the start, stop and close methods are called depending on the state of the port:¡.

In the other hand, this class extends the EventEmitter class, so it's possible to emit events to notify the status of the port:
on('error', listener: (error: Crash) => void): this: should be emitted to notify errors in the resource management or access, this will not change the provider state, but the error will be registered in the observability layers.on('closed', listener: (error?: Crash) => void): this: should be emitted if the access to the resources is not longer possible. This event should not be emitted whenstoporclosemethods are used. If the event includes an error, the provider will indicate this error as the cause of the port closure and will be registered in the observability layers.on('unhealthy', listener: (error: Crash) => void): this: should be emitted when the port has limited access or no access to the resources, but the provider is still running and trying to recover the access. If the event includes an error, the provider will indicate this error as the cause of the port unhealthiness and will be registered in the observability layers.on('healthy', listener: () => void): this: should be emitted when the port has recovered the access to the resources.

Check some examples of implementation in:
2. Defining the PortConfigValidationStruct object
The PortConfigValidationStruct object is a type that defines the default values of the Provider, the values coming from environment variables, and the validation object of type 'Schema' from the Joi module.
The PortConfigValidationStruct object should have the following properties:
defaultConfig: an object with the default values of theProvider.envBaseConfig: an object with the environment variables that theProviderwill use, if any.schema: a Joi schema object that will be used to validate the configuration object passed to theProvider.
import Joi from 'joi';
export const PortConfigValidationStruct = {
defaultConfig: {
url: 'mongodb://localhost:27017',
appName: 'myName',
},
envBaseConfig: {
url: process.env['MONGO_URL'],
appName: process.env['MONGO_APP_NAME'],
},
schema: Joi.object({
url: Joi.string().uri().required(),
appName: Joi.string().required(),
}),
};3. Using the ProviderFactoryCreator function
The ProviderFactoryCreator function is a utility function that allows creating instances of the Provider with the desired configuration. This function receives the following arguments:
port: the class that extends thePortclass and implements the specificProvider.validation: thePortConfigValidationStructobject that defines the default values, environment variables, and the validation schema of theProvider.defaultName: the default name of theProvider, so that it will be used if the name is not provided in the configuration.type: the type of theProvider, which will be used to identify the kind ofProviderin the observability layers.
const Factory = ProviderFactoryCreator(MongoPort, myConfig, 'Mongo', 'database');The Factory object returned by the ProviderFactoryCreator function has a single static method create that allows creating new instances of the Provider with the desired configuration for each case.
Small and simple example
port.ts
import { Layer } from '@mdf.js/core';
import { LoggerInstance } from '@mdf.js/logger';
import { CONFIG_PROVIDER_BASE_NAME } from '../config';
import { Client, Config } from './types';
export type Client = Console;
export type Config = {}
export class Port extends Layer.Provider.Port<Client, Config> {
/** Client handler */
private readonly instance: Client;
/** */
private interval: NodeJS.Timeout;
/**
* Implementation of functionalities of an HTTP client port instance.
* @param config - Port configuration options
* @param logger - Port logger, to be used internally
* @param name - Port name, to be used in the logger
*/
constructor(config: Config, logger: LoggerInstance, name: string) {
super(config, logger, name);
this.instance = console;
this.interval = setInterval(this.myCheckFunction, 1000);
}
/** Stupid check function */
private readonly myCheckFunction = (): void => {
// Check the client status
this.addCheck('myCheck', {
status: 'pass',
componentId: this.uuid,
componentType: 'console',
observedValue: 'im stupid',
time: new Date().toISOString(),
});
// Emit the status event
this.emit('healthy');
}
/** Return the underlying port instance */
public get client(): Client {
return this.instance;
}
/** Return the port state as a boolean value, true if the port is available, false in otherwise */
public get state(): boolean {
return true;
}
/** Initialize the port instance */
public async start(): Promise<void> {
// Nothing to do is a stupid port
}
/** Stop the port instance */
public async stop(): Promise<void> {
// Nothing to do is a stupid port
}
/** Close the port instance */
public async close(): Promise<void> {
// Nothing to do is a stupid port
}
}config.ts
import { Layer } from '@mdf.js/core';
import { Config } from './port';
import Joi from 'joi';
export const config: Layer.Provider.PortConfigValidationStruct<Config> = {
defaultConfig: {},
envBaseConfig: {},
schema: Joi.object({}),
};factory.ts
import { Layer } from '@mdf.js/core';
import { configEntry } from '../config';
import { Port, Client, Config } from './port';
export const Factory = Layer.Provider.ProviderFactoryCreator<Client, Config, Port>(
Port,
configEntry,
`myConsole`,
'console'
);index.ts
import { Factory } from './factory';
const myProvider = Factory.create();
myProvider.instance.log('Hello world!');
console.log(myProvider.state); // true
console.log(myProvider.checks); // { "myConsole:status": [{ status: 'pass', ... }], { "myConsole:myCheck": [{ status: 'pass', ... }] }
myProvider.on('healthy', () => {
console.log('Im healthy');
});Jobs API
The Jobs API from @mdf.js allows for the management of job requests and executions within an @mdf.js application in a simplified and standardized way. The two main elements of this API are:
- The
JobHandlerclass, which is responsible for "transporting" the information of the jobs to be executed, as well as notifying the execution thereof through events to interested observers. - The
JobRequestinterface defines the structure of job requests.
class JobHandler<
Type extends string = string,
Data = unknown,
CustomHeaders extends Record<string, any> = NoMoreHeaders,
CustomOptions extends Record<string, any> = NoMoreOptions,
>;
interface JobRequest<
Type extends string = string,
Data = unknown,
CustomHeaders extends Record<string, any> = NoMoreHeaders,
CustomOptions extends Record<string, any> = NoMoreOptions,
>;Customization of Generic Types
Both the class, JobHandler, and the interface, JobRequest use generic types to define the structure of the data transported in the jobs, as well as to define the custom headers and options that can be added to the jobs. In this way, the Jobs API is flexible and can be used in different contexts and with different types of data. The generic parameters for the JobHandler class and the JobRequest interface are as follows:
Type: a string type representing the type or types of job to be executed. This string type can be used to filter the jobs to be executed, so that only jobs of a specific type are executed, or to apply different execution logic depending on the job type. For example, it can be used to execute notification jobs:email,sms,push, etc, so the generic typeTypewould be declared astype Type = 'email' | 'sms' | 'push'.Data: a generic type representing the structure of the data transported in the jobs. This type can be any type of data, from a primitive type like a number or a string, to a complex object with multiple properties. For example, if you want to send an email, the data could be an object with the propertiesto,subject, andbody.CustomHeaders: a generic type representing the custom headers that can be added to the jobs. This type must be a key-value map, where the key is a string and the value can be any type of data. These custom headers can be used to add additional information to the jobs, such as metadata, authentication information, etc. Custom headers are optional and it is not necessary to add them to the jobs if not needed. By default, the generic typeCustomHeadersisNoMoreHeaders, which is a type that does not allow adding custom headers to the jobs. An example of a custom header could be an authentication header containing an access token to an external API:{ Authorization: 'Bearer <access token>' }.CustomOptions: a generic type representing the custom options that can be added to the jobs. This type must be a key-value map, where the key is a string and the value can be any type of data. These custom options can be used to add additional information to the jobs, such as specific configurations, execution parameters, etc. Custom options are optional and it is not necessary to add them to the jobs if not needed. By default, the generic typeCustomOptionsisNoMoreOptions, which is a type that does not allow adding custom options to the jobs. In addition to the custom options, there is the propertynumberOfHandlers, read the section on theJobHandlerclass for more information.
An example of customizing the generic types of the JobHandler class and the JobRequest interface would be as follows:
import { Jobs } from '@mdf.js/core';
type Type = 'email' | 'sms' | 'push';
type Data = { to: string; subject: string; body: string };
type CustomHeaders = { Authorization: string };
type CustomOptions = { retry: number };
export type MyOwnJobRequest = Jobs.JobRequest<Type, Data, CustomHeaders, CustomOptions>;
export class MyOwnJobHandler extends Jobs.JobHandler<Type, Data, CustomHeaders, CustomOptions> {}
const myHandler = new MyOwnJobHandler('multi', { to: '', body: '', subject: '' }, 'email', {
headers: { Authorization: '' },
retry: 0,
});
const myHandler2 = new MyOwnJobHandler({
data: { to: '', body: '', subject: '' },
type: 'email',
jobUserId: '123',
options: { headers: { Authorization: '' }, retry: 0 },
});JobHandler Class
Let's look in more detail at the structure of the JobHandler class:
constructor: there are two ways to instantiate aJobHandler:constructor(jobRequest: JobRequest<Type, Data, CustomHeaders>): by using aJobRequestobject that contains the information of the job to be executed.constructor(jobUserId: string, data: Data, type?: Type, options?: Options<CustomHeaders>): by using the necessary parameters to create aJobRequestobject. Ultimately, both cases are equivalent, as theJobRequestobject contains the same data as the constructor parameters. Thus, we can analyze the parameters for creation through theJobRequestobject:type: a string type representing the type of job to be executed. The type of this variable is of the generic typeType, read the section on customization of generic types for more information.data: a generic type representing the structure of the data transported in the jobs. The type of this variable is of the generic typeData, read the section on customization of generic types for more information.options: this parameter is optional and is used to add custom headers or options to the jobs. The type of this variable is an object containing two properties:headers: an object containing the custom headers that will be added to the job. By default, this property is of the typeCustomHeaders, read the section on customization of generic types for more information.numberOfHandlers: an integer number indicating the number of handlers that will be used to execute the job. By default, this property is1, which means that the job has to be confirmed, through the use of thedonemethod, only once. If a value greater than1is set, the job has to be confirmedntimes, wherenis the value ofnumberOfHandlers.
jobUserId: an identifier for the job. It should be used to identify the job in the user's logic. When identifying a job, keep in mind the following:- Property
uuid: each new instance ofJobHandlerhas a unique identifier that can be accessed only in read mode through the propertyuuid. - Property
type: indicates the type of job to be executed. - Property
jobUserId: identifier of the job that should be used to identify the job in the user's logic. That is, we can have several jobs whosejobUserIdisalarmNotification, being able to have each one of them a differenttypeor not and being all instances uniquely identifiable by theiruuid.
- Property
Properties:
uuid: a unique identifier for the instance of theJobHandler. This identifier is read-only and is generated automatically when creating a new instance of theJobHandler.type: a string type representing the type of job to be executed. This type is of the generic typeType, read the section on customization of generic types for more information.jobUserId: an identifier for the job. It should be used to identify the job in the user's logic.jobUserUUID: is a UUID v5 hash that is generated from thejobUserId. This hash is read-only and is generated automatically when creating a new instance of theJobHandler.status: an enumerated type ofStatusthat indicates the status of the job. The possible values are:Status.PENDING(pending): indicates that the job is pending execution. It is the initial state of a job.Status.PROCESSING(processing): indicates that the job is being processed.Status.COMPLETED(completed): indicates that the job has been completed successfully.Status.FAILED(failed): indicates that the job has failed.
data: a generic type representing the structure of the data transported in the jobs. This type is of the generic typeData. When accessing the property for the first time, i.e., when the status isStatus.PENDING, the job changes its status toStatus.PROCESSING.options: contains the options indicated in the constructor of the class.createdAt: the creation date of the job as aDateobject.hasErrors: a boolean value that indicates if the job contains errors. This value is read-only and is set automatically when an error occurs in the job. These errors are included through thedoneandaddErrormethods.errors: if there are errors in the job, this property contains aMultiobject belonging to the@mdf.js/crashmodule that contains the information of the errors. This property is read-only and is set automatically when an error occurs in the job. These errors are included through thedoneandaddErrormethods.processTime: if the job has been completed successfully, this property contains the time it took to process the job in milliseconds, otherwise, the value is-1.
Methods:
public addError(error: Crash | Multi): void: adds an error to the job. This method is used to add errors to the job that have occurred during its execution. The errors added through this method are included in theerrorsproperty and thehasErrorsproperty is set totrue. The error created is of the typeValidationError.public done(error?: Crash): void: finishes the job. This method is used to finish the job and change its status toStatus.COMPLETEDif no error has occurred, or toStatus.FAILEDif an error has occurred. If an error is provided, it is added to theerrorsproperty and thehasErrorsproperty is set totrue. This method will have to be called as many times asnumberOfHandlershas been set in the constructor, once the number of calls is reached, the job will emit thedoneevent.public result(): Result<Type>: returns aResultobject containing the information of the job./** Job result interface */ export interface Result<Type extends string = string> { /** Unique job processing identification */ uuid: string; /** Job type */ type: Type; /** Timestamp, in ISO format, of the job creation date */ createdAt: string; /** Timestamp, in ISO format, of the job resolve date */ resolvedAt: string; /** Number of entities processed with success in this job */ quantity: number; /** Flag that indicate that the publication process has some errors */ hasErrors: boolean; /** Array of errors */ errors?: MultiObject; /** User job request identifier, defined by the user */ jobUserId: string; /** Unique user job request identification, based on jobUserId */ jobUserUUID: string; /** Job status */ status: Status; }
public toObject(): JobObject<Type, Data, CustomHeaders, CustomOptions>: returns aJobObjecttype object containing the information of the job./** Job object interface */ export interface JobObject< Type extends string = string, Data = any, CustomHeaders extends Record<string, any> = NoMoreHeaders, CustomOptions extends Record<string, any> = NoMoreOptions, > extends JobRequest<Type, Data, CustomHeaders, CustomOptions> { /** Job type identification, used to identify specific job handlers to be applied */ type: Type; /** Unique job processing identification */ uuid: string; /** Unique user job request identification, generated by UUID V5 standard and based on jobUserId */ jobUserUUID: string; ** Job status */ status: Status; }
Events:
on(event: 'done', listener: (uuid: string, result: Result<Type>, error?: Multi) => void): this: event emitted when the job has been completed successfully or has failed. The event returns the unique identifier of the job, the information of the job in aResultobject type, and an error in case a failure has occurred.
License
Copyright 2024 Mytra Control S.L. All rights reserved.
Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.