- @mdf.js/core
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
Health
interface, which is a set of types and interfaces that standardize the way in which the state of the resources is reported. - The
Layer
namespace, which contains the following elements:- The
App
interface, which is a set of types and interfaces that standardize the components of an application from the observability point of view. - The
Provider
API, 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
Jobs
API, which allows for the creation and management of jobs in a standardized way within the @mdf.js API.
To install the @mdf.js/core module, you can use the following commands:
- npm
npm install @mdf.js/core
- yarn
yarn add @mdf.js/core
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 samecomponentId
may appear in the details if they are from different nodes. -
componentType
: an optional string that SHOULD be present ifcomponentName
is 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 byT
in the interface definition. -
observedUnit
: an optional string that SHOULD be present ifmetricValue
is 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 typeStatus
indicating 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 themetricValue
was 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
Provider
developer 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
Check
objects. 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
Checks
type is defined to capture this structure, where each entry in the object maps to an array ofCheck
objects, 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
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 theComponent
emits 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 theProvider
that 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
Component
properties 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.Status
whose value can be:-
pass
: indicates that theResource
is in a normal operating state. If all the checks are inpass
state, theResource
will be inpass
state. -
fail
: indicates that theResource
is in an error state. If any of the checks are infail
state, theResource
will be infail
state. -
warn
: indicates that theResource
is in a warning state. If any of the checks are inwarn
state, theResource
will be inwarn
state.
-
-
-
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 theResource
changes its state.
-
-
Properties:
-
Service
: a service is a special kind of resource that besidesResource
properties, 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
Resource
properties, 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.
-
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.Resource
interface. - 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:
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
: aLoggerInstance
object, belonging to the @mdf.js/logger module or any other object that implements theLoggerInstance
interface. If specified, it will be used by both theProvider
and thePort
it wraps. If not specified, a DEBUG type logger will be used with the provider's name indicated in thename
property, or if not specified, with the default provider name. -
config
: specific configuration object for the module wrapped by theProvider
in question. If not specified, the default configuration set by theProvider
developer 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 theProvider
developer 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 theProvider
developer 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 tocamelCase
and combined with the rest of the configuration, except with the environment variables defined by theProvider
developer. 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
Provider
developer, with the alternative of being able to use a fast track of environment variables usage through a prefix. That is:useEnvironment: false
oruseEnvironment: 'MY_PREFIX_'
.- Provider rules: the user prefers to use the environment variables defined by the
Provider
developer, 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 theProvider
and 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 typeProviderState
whose value can be:-
running
: indicates that theProvider
is in a normal operating state. -
stopped
: indicates that theProvider
has been stopped or has not been initialized. -
error
: indicates that theProvider
has encountered an error in its operation.
-
-
error
: in case theProvider
is 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 aboutCrash
andMulti
types 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 theProvider
to 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 theProvider
under 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.Check
that 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
Provider
developer can add more checks to the list ofchecks
to provide more information about the state of theProvider
or the resources it manages. -
client
: instance of the client/resource wrapped by theProvider
that has been created with the provided configuration.
-
Note: the instance returned by the
create
method is an instance ofProvider
with 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 theProvider
and the resource it wraps. -
async stop(): Promise<void>
: stops theProvider
and the resource it wraps. -
async fail(error: Crash | Error): Promise<void>
: sets theProvider
in an error state and saves the error information.
-
-
Events:
-
on('error', listener: (error: Crash | Error) => void): this
: event emitted every time theProvider
emits an error. -
on('status', listener: (status: Health.Status) => void): this
: event emitted every time theProvider
changes its state.
-
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 theProvider
developer. - Define the properties of the
PortConfigValidationStruct
object, 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
ProviderFactoryCreator
function to create an instance of theProvider
, the class of type Mixin that standardizes the creation ofProvider
instances with the desired configuration.
Let's see point by point how the instrumentation of a Provider
is carried out.
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 thePort
class, 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 thePort
instance. -
logger
: aLoggerInstance
object, belonging to the @mdf.js/logger module or any other object that implements theLoggerInstance
interface. This property is used to log information about the port and the resources it manages. ThePort
class set the context of the logger to the portname
and 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 thePort
by the use ofaddCheck
method, these checks are collected by theProvider
, together with the own check ofstatus
, and offered to the observability layers.
-
Note: As the signature of the
Port
constructor should maintained:constructor(config: PortConfig, logger: LoggerInstance, name: string)Your
Port
class extension will receive theconfig
,logger
andname
properties, and you should call thesuper
constructor 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. -
state
property, a boolean value that indicates if the port is connected (true) or healthy (true) or not (false). -
client
property, 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 whenstop
orclose
methods 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:
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 theProvider
will 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(),
}),
};
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 thePort
class and implements the specificProvider
. -
validation
: thePortConfigValidationStruct
object 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 ofProvider
in 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.
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
}
}
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({}),
};
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'
);
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');
});
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
JobHandler
class, 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
JobRequest
interface 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,
>;
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 typeType
would 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 typeCustomHeaders
isNoMoreHeaders
, 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 typeCustomOptions
isNoMoreOptions
, 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 theJobHandler
class 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 },
});
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 aJobRequest
object 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 aJobRequest
object. Ultimately, both cases are equivalent, as theJobRequest
object contains the same data as the constructor parameters. Thus, we can analyze the parameters for creation through theJobRequest
object:-
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 thedone
method, only once. If a value greater than1
is set, the job has to be confirmedn
times, wheren
is 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 ofJobHandler
has 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 whosejobUserId
isalarmNotification
, being able to have each one of them a differenttype
or 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 ofStatus
that 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 aDate
object. -
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 thedone
andaddError
methods. -
errors
: if there are errors in the job, this property contains aMulti
object belonging to the@mdf.js/crash
module 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 thedone
andaddError
methods. -
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 theerrors
property and thehasErrors
property 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.COMPLETED
if no error has occurred, or toStatus.FAILED
if an error has occurred. If an error is provided, it is added to theerrors
property and thehasErrors
property is set totrue
. This method will have to be called as many times asnumberOfHandlers
has been set in the constructor, once the number of calls is reached, the job will emit thedone
event. -
public result(): Result<Type>
: returns aResult
object 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 aJobObject
type 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 aResult
object type, and an error in case a failure has occurred.
-
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.