@mdf.js/utils
Version:
MMS - API Core - Common utils tools
362 lines (272 loc) • 16.1 kB
Markdown
# **@mdf.js/utils**
[](https://nodejs.org/en/)
[](https://www.typescriptlang.org/)
[](https://snyk.io/package/npm/snyk)
[](https://mytracontrol.github.io/mdf.js/)
<!-- markdownlint-disable MD033 MD041 -->
<p align="center">
<div style="text-align:center;background-image:radial-gradient(circle farthest-corner at 50% 50%, #104c60, #0c0c13);">
<img src="https://assets.website-files.com/626a3ef32d23835d9b2e4532/6290ab1e2d3e0d922913a6e3_digitalizacion_ENG.svg"alt="netin"width="500">
</div>
</p>
<h1 style="text-align:center;margin-bottom:0">Mytra Development Framework - @mdf.js/utils</h1>
<h5 style="text-align:center;margin-top:0">Collection of tools useful for several different tasks within the @mdf.js ecosystem</h5>
<!-- markdownlint-enable MD033 -->
***
## **Table of contents**
- [**@mdf.js/utils**](#mdfjsutils)
- [**Table of contents**](#table-of-contents)
- [**Introduction**](#introduction)
- [**Installation**](#installation)
- [**Use**](#use)
- [**`retry` and `retryBind`**](#retry-and-retrybind)
- [**`prettyMS`**](#prettyms)
- [**`loadFile`**](#loadfile)
- [**`findNodeModule`**](#findnodemodule)
- [**`escapeRegExp`**](#escaperegexp)
- [**`coerce`**](#coerce)
- [**`camelCase`**](#camelcase)
- [**`deCycle` and `retroCycle`**](#decycle-and-retrocycle)
- [**`formatEnv`**](#formatenv)
- [**License**](#license)
## **Introduction**
The **@mdf.js/utils** module is a collection of tools useful for several different tasks within the @mdf.js ecosystem. It is a collection of utilities that are used in different parts of the framework, such as the API, the CLI, the documentation...
The list of utilities are:
- **`retry`** and **`retryBind`**: A function that allows you to retry a promise a certain number of times before giving up.
- **`prettyMS`**: A function that converts milliseconds to a human-readable format.
- **`loadFile`**: A function that loads a file from the file system, logging the process.
- **`findNodeModule`**: A function that finds a node module in the file system.
- **`escapeRegExp`**: A function that escapes a string to be used in a regular expression.
- **`coerce`**: function for data type coercion, specially useful for environment variables and configuration files.
- **`camelCase`**: function for converting strings to camelCase.
- **`cycle`**: function for managing circular references in objects.
- **`formatEnv`**: functions for formatting environment variables.
- **`mock`**: functions for mocking objects, specially useful for testing in Jest.
## **Installation**
To install the **@mdf.js/utils** module, you can use the following commands:
- **NPM**
```shell
npm install @mdf.js/utils
```
- **Yarn**
```shell
yarn add @mdf.js/utils
```
## **Use**
### **`retry` and `retryBind`**
The `retry` and `retryBind` functions are used to retry a promise a certain number of times before giving up. The difference between both functions is that the `retryBind` can bind the context of the promise to a concrete object.
Both functions have similar signatures:
- **`retry`**: `retry<T>(task: TaskAsPromise<T>, funcArgs: TaskArguments, options: RetryOptions): Promise<T>`.
- **`retryBind`**: `retryBind<T, U>(task: TaskAsPromise<T>, bindTo: U, funcArgs: TaskArguments, options: RetryOptions): Promise<T>`.
The common parameters are between both functions are:
- **`task`** (`TaskAsPromise<T>`): The task to be executed. `TaskAsPromise<T>` is alias type for `(...args: TaskArguments) => Promise<T>`.
- **`funcArgs`** (`TaskArguments`): The arguments to be passed to the task. `TaskArguments` is an alias type for `any[]`.
- **`options`** (`RetryOptions`): The options for the retry. `RetryOptions` is an interface with the following properties:
- **`logger`** (`LoggerFunction`): The logger function used for logging retry attempts. `LoggerFunction` is an alias type for `(error: Crash | Multi | Boom) => void`. `Crash`, `Multi`, and `Boom` are errors defined in the [`@mdf.js/crash`](https://www.npmjs.com/package/@mdf.js/crash).
- **`waitTime`** (`number`): The time to wait between retry attempts, in milliseconds. Default is `1000`.
- **`maxWaitTime`** (`number`): The maximum time to wait between retry attempts, in milliseconds. Default is `15000`.
- **`abortSignal`** (`AbortSignal`): The signal to be used to interrupt the retry process. Default is `null`.
- **`attempts`** (`number`): The maximum number of retry attempts. Default is `Number.MAX_SAFE_INTEGER`.
- **`timeout`** (`number`): Timeout for each try. Default is `undefined`.
- **`interrupt`** (`() => boolean`): A function that determines whether to interrupt the retry process. Should return `true` to interrupt, `false` otherwise. Default is `undefined`. **Deprecated**. Use `abortSignal` instead.
The extra parameter for the `retryBind` function is:
- **`bindTo`** (`U`): The object to bind the context of the promise to.
***Simple task***:
```typescript
import { retry, retryBind } from '@mdf.js/utils';
import { Logger } from '@mdf.js/logger';
const logger: Logger = new Logger();
const task = async (a: number, b: number) => {
if (Math.random() < 0.5) {
throw new Error('Random error');
}
return a + b;
};
const funcArgs = [1, 2];
const options = {
logger: logger.crash,
waitTime: 1000,
maxWaitTime: 15000,
attempts: 5,
timeout: 5000,
};
retry(task, [1, 2], options).then(console.log).catch(console.error);
```
Aborting the retry process can be done using an `AbortSignal`:
```typescript
import { retry } from '@mdf.js/utils';
import { Logger } from '@mdf.js/logger';
const logger: Logger = new Logger();
const controller = new AbortController();
class MyContext {
public c = 10;
public task = async (a: number, b: number) => {
if (Math.random() < 0.5) {
throw new Error('Random error');
}
return a + b;
};
}
const context = new MyContext();
const options = {
logger: logger.crash,
waitTime: 1000,
maxWaitTime: 15000,
attempts: 5,
timeout: 5000,
abortSignal: controller.signal,
};
setTimeout(() => controller.abort(), 3000);
retryBind(context.task, context, [1, 2], options).then(console.log).catch(console.error);
```
### **`prettyMS`**
The `prettyMS` function is used to convert milliseconds to a human-readable format. It has the following signature:
- **`prettyMS`**: `(ms: number): string`.
```typescript
import { prettyMS } from '@mdf.js/utils';
console.log(prettyMS(1000)); // 1s
console.log(prettyMS(1000 * 60)); // 1m
console.log(prettyMS(1000 * 60 * 60)); // 1h
console.log(prettyMS(1000 * 60 * 60 * 24)); // 1d
```
### **`loadFile`**
The `loadFile` function is used to load a file from the file system, logging the process. It has the following signature:
- **`loadFile`**: `(path: string, logger?: LoggerInstance): Buffer | undefined`. The `logger` parameter is optional and is used to log the process, it should be an instance of the `LoggerInstance` class from the `@mdf.js/logger` package or a simple object with a `debug` method that accepts a string.
```typescript
import { loadFile } from '@mdf.js/utils';
const logger = {
debug: (message: string) => console.log(message),
};
const file = loadFile('path/to/file', logger);
```
### **`findNodeModule`**
The `findNodeModule` function is used to find a node module in the file system. It has the following signature:
- **`findNodeModule`**: `(module: string, dir?: string): string | undefined`. The `dir` parameter is optional and is used to specify the current working directory, default is `__dirname`, this means that the search will start from the own module.
```typescript
import { findNodeModule } from '@mdf.js/utils';
const modulePath = findNodeModule('module-name');
```
### **`escapeRegExp`**
The `escapeRegExp` function is used to get the source of a regular expression pattern and escape it. It has the following signature:
- **`escapeRegExp`**: `(regex: RexExp): string`. The `regex` parameter is the regular expression to escape.
```typescript
import { escapeRegExp } from '@mdf.js/utils';
const escaped = escapeRegExp(/([.*+?^=!:${}()|\[\]\/\\])/g);
console.log(escaped); // \(\[\.\*\+\?\^\=\!\:\$\{\}\(\)\|\[\]\/\\]\)
```
### **`coerce`**
The `coerce` function is used for data type coercion, specially useful for environment variables and configuration files. It has the following signature:
- **`coerce`**: `<T extends Coerceable>(env: string | undefined, alternative?: T): T | undefined`. The `env` parameter is the value to coerce, and the `alternative` parameter is the default value to return if the coercion fails. The `Coerceable` type is an alias type for `number | boolean | Record<string, any> | any[] | null`.
```typescript
import { coerce } from '@mdf.js/utils';
// process.env['MY_ENV_VAR'] = '1';
const asNumber = coerce<number>(process.env['MY_ENV_VAR'], 10); // Coerce to number, default to 10
// process.env['MY_ENV_VAR'] = 'true' or 'false';
const asBoolean = coerce<boolean>(process.env['MY_ENV_VAR'], true); // Coerce to boolean, default to true
// process.env['MY_ENV_VAR'] = '{"a": 1}';
const asObject = coerce<Record<string, any>>(process.env['MY_ENV_VAR'], { a: 1 }); // Coerce to object, default to { a: 1 }
// process.env['MY_ENV_VAR'] = '[1,2,3]';
const asArray = coerce<any[]>(process.env['MY_ENV_VAR'], [1, 2, 3]); // Coerce to array, default to [1, 2, 3]
// process.env['MY_ENV_VAR'] = 'null' or 'NULL';
const asNull = coerce(process.env['MY_ENV_VAR']); // Coerce to null
```
### **`camelCase`**
The `camelCase` function is used to convert strings to camelCase. It has the following signature:
- **`camelCase`**: `(input: string | string[], options?: Options): string`. The `input` parameter is the string or array of strings to convert, and the `options` parameter is an object with the following properties:
- **`pascalCase`** (`boolean`): Convert to PascalCase. `foo-bar` -> `FooBar`. Default is `false`.
- **`preserveConsecutiveUppercase`** (`boolean`): Preserve consecutive uppercase characters. `foo-BAR` -> `fooBAR`. Default is `false`.
- **`locale`** (`string | string[]`): The locale parameter indicates the locale to be used to convert to upper/lower case according to any locale-specific case mappings. If multiple locales are given in an array, the best available locale is used. Setting `locale: false` ignores the platform locale and uses the [Unicode Default Case Conversion](https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#simple-single-character-case-mapping) algorithm. Default: The host environment’s current locale.
```ts
import { camelCase } from 'camelCase';
camelCase('foo-bar');
//=> 'fooBar'
camelCase('foo-bar', { pascalCase: true });
//=> 'FooBar'
camelCase('foo-BAR', { preserveConsecutiveUppercase: true });
//=> 'fooBAR'
camelCase('lorem-ipsum', { locale: 'en-US' });
//=> 'loremIpsum'
camelCase('lorem-ipsum', { locale: 'tr-TR' });
//=> 'loremİpsum'
camelCase('lorem-ipsum', { locale: ['en-US', 'en-GB'] });
//=> 'loremIpsum'
camelCase('lorem-ipsum', { locale: ['tr', 'TR', 'tr-TR'] });
//=> 'loremİpsum'
```
### **`deCycle` and `retroCycle`**
The `deCycle` and `retroCycle` functions are used to manage circular references in objects. The `deCycle` function is used to remove circular references from an object, and the `retroCycle` function is used to restore circular references to an object. They have the following signatures:
- **`deCycle`**: `(object: any, replacer?: (value: any) => any): any`. The `object` parameter is the object to remove circular references from, and the `replacer` parameter is a function that replaces circular references with a placeholder. Default is `undefined`.
- **`retroCycle`**: `(obj: any): any`. The `obj` parameter is the object to restore circular references to.
```typescript
import { deCycle, retroCycle } from '@mdf.js/utils';
const obj = { a: 1 };
obj.b = obj;
const deCycled = deCycle(obj);
console.log(deCycled); // { a: 1, b: '$' }
const retroCycled = retroCycle(deCycled);
console.log(retroCycled); // { a: 1, b: [Circular] }
```
### **`formatEnv`**
The `formatEnv` function is used to read environment variables (`process.env`), filter them based on the indicated prefix, and return an object with the values sanitized and the keys formatted based on the specified options. It has the following signatures:
- **`formatEnv`**: `<T extends Record<string, any> = Record<string, any>>(): T`. Read environment variables (`process.env`) and return an object with the values sanitized and the keys formatted.
- **`formatEnv`**: `<T extends Record<string, any> = Record<string, any>>(prefix: string): T`. Read environment variables (`process.env`), filter them based on the indicated prefix, and return an object with the values sanitized and the keys formatted.
- **`formatEnv`**: `<T extends Record<string, any> = Record<string, any>>(prefix: string, options: Partial<ReadEnvOptions>): T`. Read environment variables (`process.env`), filter them based on the indicated prefix, and return an object with the values sanitized and the keys formatted based on the specified options. The `ReadEnvOptions` type is an interface with the following properties:
- **`separator`** (`string`): The separator to use for nested keys. Default is `__`.
- **`format`** (`'camelcase' | 'pascalcase' | 'lowercase' | 'uppercase'`): The format to use for the keys. Default is `'camelcase'`.
- **`includePrefix`** (`boolean`): Whether to include the prefix in the keys. Default is `false`.
- **`formatEnv`**: `<T extends Record<string, any> = Record<string, any>>(prefix: string, options: Partial<ReadEnvOptions>, source: Record<string, string | undefined>): T`. Process a source, encoded as an environment variables file, filter them based on the indicated prefix, and return an object with the values sanitized and the keys formatted based on the specified options.
```typescript
import { formatEnv } from '@mdf.js/utils';
process.env['MY_OWN_TEST'] = 'test';
const env = {
EXAMPLE_OBJECT: '{"prop": "value"}',
EXAMPLE_ARRAY: '[1,2,3, "string", {"prop": "value"}, 5.2]',
EXAMPLE_INVALID_OBJECT: '{"prop": }"value"}',
EXAMPLE_INVALID_ARRAY: '[1,2,3, "string", ]{"prop": "value"}, 5.2]',
EXAMPLE_TRUE: 'true',
EXAMPLE_FALSE: 'false',
EXAMPLE_INT: '5',
EXAMPLE_NEGATIVE_INT: '-11',
EXAMPLE_FLOAT: '5.2456',
EXAMPLE_NEGATIVE_FLOAT: '-2.4567',
EXAMPLE_INT_ZERO: '0',
EXAMPLE_FLOAT_ZERO: '0.00',
EXAMPLE_NEGATIVE_INT_ZERO: '-0',
EXAMPLE_NEGATIVE_FLOAT_ZERO: '-0.00',
EXAMPLE_STRING: 'example',
EXAMPLE_DEEP__OBJECT__PROPERTY: 'value',
EXAMPLE_NOT_SHOULD_BE_SANITIZED: 5,
};
console.log(formatEnv()); // { myOwnTest: 'test' }
console.log(
formatEnv('EXAMPLE', { separator: '__', format: 'camelcase', includePrefix: false }, env)
);
// {
// object: { prop: 'value' },
// array: [1, 2, 3, 'string', { prop: 'value' }, 5.2],
// invalidObject: '{"prop": }"value"}',
// invalidArray: '[1,2,3, "string", ]{"prop": "value"}, 5.2]',
// true: true,
// false: false,
// int: 5,
// negativeInt: -11,
// float: 5.2456,
// negativeFloat: -2.4567,
// intZero: 0,
// floatZero: 0,
// negativeIntZero: -0,
// negativeFloatZero: -0,
// string: 'example',
// deep: {
// object: {
// property: 'value',
// },
// },
// notShouldBeSanitized: 5,
// }
```
## **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>.
```
```