my-zxventures-utils
v1.0.10
Published
Common npm utils
Downloads
36
Maintainers
Readme
@zxventures/utils (icp-pkg-utils
)
Table of Contents
- @zxventures/utils (
icp-pkg-utils
)
Why?
Common utilities to use on node.js
projects.
The main idea here is to condense all the code commonly used in the different projects/microservices within @zxventures.
With this approach, we can reduce the duplicated snippets between projects along with the error occurrences, and their unit tests saving time on each new project/microservice.
Quickstart
Install
This package is scoped and is private by default.
Therefore, to use this package, it is necessary to include a file named
.npmrc
at the root of the project where this package will be used with the following content://registry.npmjs.org/:_authToken=PUT-THE-AUTHENTICATION-TOKEN-HERE
NOTE: The authentication token should be requested from the infrastructure team.
Using NPM:
npm install @zxventures/utils
Usage
This package has several common utilities. These utilities are grouped into 4 main groups: constants, errors, helpers, validations.
There are 2 ways to use the utilities in this package.
1st way
The first way is to use the root namespace of the package and require each of these groups:
// package root namespace: '@zxventures/utils'
const { constants, errors, helpers, validations } = require('@zxventures/utils');
// to work with 'constants'
const { countryCodes } = constants;
// to work with 'errors'
const { CustomBaseError, GraphqlError, JoiValidationError, ProcessEnvValidationError } = errors;
// to work with 'helpers' (hash, json, text)
const { getHashFromObject } = helpers.hash;
const { parse, stringify, clone, prettyPrint, mergeDeep, getPropertyValueCaseInsensitive } = helpers.json;
const { capitalize } = helpers.text;
// to work with 'validations'
const { commons, joiSchemaValidation, regexPatterns, envVariablesValidation } = validations;
2nd way
The second way is to use the specific namespace:
// to work with 'constants'
const { countryCodes } = require('@zxventures/utils/constants');
// to work with a specific 'constant' namespace
const countryCodes = require('@zxventures/utils/constants/country-codes');
// to work with 'errors'
const { CustomBaseError, GraphqlError, JoiValidationError, ProcessEnvValidationError } = require('@zxventures/utils/errors');
// to work with a specific 'error' namespace
const CustomBaseError = require('@zxventures/utils/errors/custom-base-error');
const GraphqlError = require('@zxventures/utils/errors/graphql-error');
const JoiValidationError = require('@zxventures/utils/errors/joi-validation-error');
const ProcessEnvValidationError = require('@zxventures/utils/errors/process-env-validation-error');
// to work with 'helpers'
const { hash, json, text } = require('@zxventures/utils/helpers');
// to work with a specific 'helper' namespace
const { getHashFromObject } = require('@zxventures/utils/helpers/hash');
const { parse, stringify, clone, prettyPrint, mergeDeep, getPropertyValueCaseInsensitive } = require('@zxventures/utils/helpers/json');
const { capitalize } = require('@zxventures/utils/helpers/text');
// to work with 'validations'
const { commons, joiSchemaValidation, regexPatterns, envVariablesValidation } = require('@zxventures/utils/validations');
// to work with a specific 'validation' namespace
const { isAsyncFunction, isPlainObject /*, and others */ } = require('@zxventures/utils/validations/commons');
const { joiSchemaValidationSync, joiSchemaValidationAsync } = require('@zxventures/utils/validations/joi-schema-validation');
const { validateProcessEnv } = require('@zxventures/utils/validations/env-variables-validation');
const { base64Pattern, isoDatePattern } = require('@zxventures/utils/validations/regex-patterns');
✅ constants
In this group, at the moment the only constant is the information of the countries (countryCodes
) and has 250 records.
This information about countries has the following properties for each country:
[
// ...
{
name: "Mexico", // full country name
alpha2: "MX", // country code using 2 chars and the value is in UPPERCASE
alpha3: "MEX", // country code using 3 chars and the value is in UPPERCASE
dialCode: "+52" // country dialing code
},
// ...
]
✅ errors
In this group, there are 4 error classes: CustomBaseError, GraphqlError JoiValidationError and ProcessEnvValidationError.
CustomBaseError
The first one is CustomBaseError
which inherits from Error
.
The idea here is that all of our custom errors inherit from this CustomBaseError
base class.
This base class (or abstract class
in other programming languages) should only be used as a base class and not as a direct instance.
This base class makes sure to set the value of the property name
of the error from the name of the constructor of the class that inherits from it.
This class has a constructor with a single parameter which is the message
(error message).
const CustomBaseError = require('@zxventures/utils/errors/custom-base-error');
class MyCustomError extends CustomBaseError {
constructor(message, errorCode, originalError) {
super(message);
this.errorCode = errorCode;
this.originalError = originalError;
}
}
const originalError = new Error('This is the original error');
const myCustomError = new MyCustomError('This is the custom error message', 101, originalError);
console.log('is instance of Error:', myCustomError instanceof Error);
console.log('is instance of CustomBaseError:', myCustomError instanceof CustomBaseError);
console.log('error name:', myCustomError.name);
/*
* console output:
* > is instance of Error: true
* > is instance of CustomBaseError: true
* > error name: MyCustomError
*
*/
GraphqlError
This error is commonly used within microservices/projects that work with GraphQL to throw a custom error.
This class inherits from CustomBaseError
and has a constructor with two parameters, the first one is the message
(error message) and the second one is the code
(error code).
const GraphqlError = require('@zxventures/utils/errors/graphql-error');
// ...
throw new GraphqlError(
'Error trying to get the minimum order value',
'IOP-CHK-1001',
);
// ...
JoiValidationError
This error is used to classify the Joi errors and has the properties message
and originalError
.
In this package, there are two useful methods to validate a Joi scheme (
joiSchemaValidationSync
andjoiSchemaValidationAsync
in the section'/validations/joi-schema-validation'
) and throw aJoiValidationError
if the schema is not valid.
const Joi = require('joi');
const { JoiValidationError, CustomBaseError } = require('@zxventures/utils/errors');
const { joiSchemaValidationSync } = require('@zxventures/utils/validations/joi-schema-validation');
// joi schema
const joiSchemaSample = Joi.object({
addressId: Joi.string().trim().required(),
city: Joi.string().trim().required(),
geoCoordinates: Joi.array().items(Joi.number()).length(2),
});
// any object to validate
const objectToValidate = {
id: 1001,
};
try {
// try to validate an invalid Joi schema
const validatedObject = joiSchemaValidationSync({
schema: joiSchemaSample,
inputToValidate: objectToValidate
});
console.log('validation result: ', validatedObject);
} catch (error) {
console.error('is instance of Error:', error instanceof Error);
console.error('is instance of CustomBaseError:', error instanceof CustomBaseError);
console.error('is instance of JoiValidationError:', error instanceof JoiValidationError);
console.log('error name:', error.name);
}
/*
* console output:
* > is instance of Error: true
* > is instance of CustomBaseError: true
* > is instance of JoiValidationError: true
* > error name: JoiValidationError
*
*/
ProcessEnvValidationError
This error is used to classify errors related to the validation of environment variables (process.env
) and has the message
and variableName
properties.
In this package, there is a useful method to validate the environment variables that are required. This method can be found in section
'/validations/env-variables-validation'
under the namevalidateProcessEnv
.
const { ProcessEnvValidationError, CustomBaseError } = require('@zxventures/utils/errors');
const { validateProcessEnv } = require('@zxventures/utils/validations/env-variables-validation');
try {
// try to validate any environment variable that does not exist.
const envVariables = validateProcessEnv('ANY_VAR_NOT_PRESENT');
console.log('validation result: ', envVariables);
} catch (error) {
console.error('is instance of Error:', error instanceof Error);
console.error('is instance of CustomBaseError:', error instanceof CustomBaseError);
console.error('is instance of ProcessEnvValidationError:', error instanceof ProcessEnvValidationError);
console.log('error name:', error.name);
}
/*
* console output:
* > is instance of Error: true
* > is instance of CustomBaseError: true
* > is instance of ProcessEnvValidationError: true
* > error name: ProcessEnvValidationError
*
*/
✅ helpers
In this group there are 3 main helpers: hash, json and text.
hash
This helper has a function that allows us to obtain a hash from an object.
const { getHashFromObject } = require('@zxventures/utils/helpers/hash');
const someObject = {
id: 1,
name: 'some name'
};
const hash = getHashFromObject(someObject);
console.log('hash:', hash);
/*
* console output:
* > hash: 7f92b7545acc8f1fe3a48a8a794759cc2e92a05464de6c96722b4096e9ac7408
*
*/
json
This helper has several helper functions that allow us to do common operations with JSON such as:
stringify
and parse
: It's similar to JSON.stringify
and JSON.parse
. It serializes and deserializes otherwise valid JSON objects containing circular references into and from a specialized JSON format. Basically, it allows us to avoid a possible cyclic object value exception. It's necessary that if you serialize with this stringify
function you also deserialize with this parse
function. It's based on @ungap/structured-clone npm package.
The parse
helper function uses internally the native JSON.parse()
function. Therefore, it receives the same two parameters as the native function. The 1st parameter is the text
which is a string to parse as JSON and the 2nd parameter is an optional parameter called reviver
. For more information see the JSON.parse() documentation.
Similarly, the stringify
function uses internally the native JSON.stringify()
function. Therefore, it receives the same three parameters as the native function. The 1st parameter is the value
which is an object to be converted into a JSON string. The 2nd parameter is optional and is called replacer
, and the 3rd parameter is also optional and is called space
. For more information see the JSON.stringify() documentation.
const { parse, stringify } = require('@zxventures/utils/helpers/json');
// a simple object
const obj = {
id: 1,
name: 'some name'
};
// add a circular reference/structure
obj.myself = obj;
// serialize object with circular reference
const stringifyResult = stringify(obj);
console.log('stringify result:', stringifyResult);
// deserialize object with circular reference
const parseResult = parse(stringifyResult);
console.log('parse result:', parseResult);
/*
* console output:
* > stringify result: [[2,[[1,2],[3,4],[5,0]]],[0,"id"],[0,1],[0,"name"],[0,"some name"],[0,"myself"]]
* > parse result: <ref *1> { id: 1, name: 'some name', myself: [Circular *1] }
*
*/
clone
: It is a simple way to create a copy of an object.
const { clone } = require('@zxventures/utils/helpers/json');
// a simple object
const obj = {
id: 1,
name: 'some name'
};
// could have a circular reference/structure
obj.myself = obj;
// create a clone
const cloned = clone(obj);
// change some value and it shouldn't affect the original object
cloned.id = 2;
// trace: check values
console.log(`obj { id: ${obj.id}, myselfId: ${obj.myself.id} }`);
console.log(`cloned { id: ${cloned.id}, myselfId: ${cloned.myself.id} }`);
/*
* console output:
* > obj { id: 1, myselfId: 1 }
* > cloned { id: 2, myselfId: 2 }
*
*/
prettyPrint
: This is a simple way to print a JSON in a pretty way. It simply encapsulates the native JSON.stringify(object, null, 2)
(extra parameters) to get a pretty JSON string. This function receives the same three parameters as the native function: object
, replacer
and space
. For more information see the JSON.stringify() documentation.
IMPORTANT: The object passed as a parameter MUST NOT HAVE A CIRCULAR REFERENCE/STRUCTURE because it uses the native function directly.
const { prettyPrint } = require('@zxventures/utils/helpers/json');
// a simple object.
// IMPORTANT: must not have a circular reference/structure
const obj = {
id: 1,
name: 'some name',
data: {
city: {
id: 1001,
iata: 'MEX',
},
},
};
// print the object in a simple way (not pretty)
console.log('simple way:', obj);
// get a pretty JSON string and print it
const pretty = prettyPrint(obj);
console.log('pretty-print:', pretty);
/*
* console output:
* > simple way: { id: 1, name: 'some name', data: { city: { id: 1001, iata: 'MEX' } } }
* > pretty-print: {
* "id": 1,
* "name": "some name",
* "data": {
* "city": {
* "id": 1001,
* "iata": "MEX"
* }
* }
* }
*
*/
mergeDeep
: It's a useful way to recursively merge values into a javascript object.
const { mergeDeep, prettyPrint } = require('@zxventures/utils/helpers/json');
// a simple object base
const obj = {
id: 1,
name: 'some name',
data: {
city: {
id: 1001,
iata: 'MEX',
},
},
};
// some modification objects
const obj2 = {
id: 2,
name: 'other name',
data: {
city: {
id: 1002,
},
},
selectedIds: [],
};
const obj3 = {
id: 3,
data: {
city: {
iata: 'COL',
},
},
selectedIds: [1, 3, 5],
};
// merge objects using native way.
// NOTE: Take a look at how the object result lost the 'data.city.id' property.
const objectAssignResult = Object.assign({}, obj, obj2, obj3);
// merge objects using 'merge deep' helper
// NOTE: Take a look at how the arrays are overwritten as the native way also does.
const mergeDeepResult = mergeDeep({}, obj, obj2, obj3);
// print the result objects
console.log('object-assign result:', prettyPrint(objectAssignResult));
console.log('merge-deep result:', prettyPrint(mergeDeepResult));
/*
* console output:
* > object-assign result: {
* "id": 3,
* "name": "other name",
* "data": {
* "city": {
* "iata": "COL"
* }
* },
* "selectedIds": [
* 1,
* 3,
* 5
* ]
* }
* > merge-deep result: {
* "id": 3,
* "name": "other name",
* "data": {
* "city": {
* "id": 1002,
* "iata": "COL"
* }
* },
* "selectedIds": [
* 1,
* 3,
* 5
* ]
* }
*
*/
getPropertyValueCaseInsensitive
: It is a useful way to get the value of some property in a case insensitive way.
It also allows us to get the value in a deep way.
The first parameter is the object and the second parameter is a string with the property name or the path to the deep property name.
const { getPropertyValueCaseInsensitive } = require('@zxventures/utils/helpers/json');
// a object without `CamelCase` properties
const obj = {
UniqueID: 1001,
DatA: {
User: {
naME: 'user name',
Email: '[email protected]',
LOCATION: {
NAme: 'Bogota, Colombia',
laTITUde: 4.606880,
lonGItuDE: -74.071838,
},
},
},
};
// e.g. get the value of a root property
const value1 = getPropertyValueCaseInsensitive(obj, "uniqueId");
console.log('value1:', value1);
// e.g. get the value of a deep property
const value2 = getPropertyValueCaseInsensitive(obj, "DATA.USER.NAME");
console.log('value2:', value2);
const value3 = getPropertyValueCaseInsensitive(obj, "data.user.location.name");
console.log('value3:', value3);
/*
* console output:
* value1: 1001
* value2: user name
* value3: Bogota, Colombia
*
*/
text
This helper has a function called capitalize
that allows us to obtain an capitalize text.
It receives as the first parameter a string and the second parameter is an optional parameter to indicate if only the first word is capitalized (it is the default value) or if all the words must be capitalized.
const { capitalize } = require('@zxventures/utils/helpers/text');
const value1 = capitalize('this is an example');
console.log('value1:', value1);
const value2 = capitalize('this is an example', false);
console.log('value2:', value2);
const value3 = capitalize('THIS IS AN EXAMPLE', true);
console.log('value3:', value3);
/*
* console output:
* value1: This is an example
* value2: This Is An Example
* value3: This is an example
*
*/
✅ validations
In this group there are 4 validation utilities: commons, regex-patterns, joi-schema-validation and env-variables-validation.
commons
In this section, there are a large number of validations that are commonly used in our projects. All of them return a boolean value and their names are clear and self-descriptive indicating their purpose.
const {
isUndefined,
isNull,
isNullOrUndefined,
isString,
isNumber,
isArray,
isFunction,
isAsyncFunction,
isPromise,
isObject,
isPlainObject,
isDate,
isIsoDate,
isBase64,
isURLSearchParams,
isRegExp,
stringHasValues,
arrayHasValues,
} = require('@zxventures/utils/validations/commons');
regex-patterns
In this section, there are 2 regular expressions commonly used in our projects: base64Pattern
and isoDatePattern
.
const { base64Pattern, isoDatePattern } = require('@zxventures/utils/validations/regex-patterns');
console.log('base64Pattern:', base64Pattern);
console.log('isoDatePattern:', isoDatePattern);
/*
* console output:
* > base64Pattern: /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
* > isoDatePattern: /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/
*
*/
joi-schema-validation
In this section, there are 2 functions to validate Joi schemas, one of them for synchronous validations and the other for asynchronous validations: joiSchemaValidationSync
and joiSchemaValidationAsync
.
const Joi = require('joi');
const { prettyPrint } = require('@zxventures/utils/helpers/json');
const { joiSchemaValidationSync, joiSchemaValidationAsync } = require('@zxventures/utils/validations/joi-schema-validation');
// joi schema
const joiSchemaSample = Joi.object({
addressId: Joi.string().trim().required(),
city: Joi.string().trim().required(),
geoCoordinates: Joi.array().items(Joi.number()).length(2),
});
// any object to validate
const objectToValidate = {
id: 1001,
};
// synchronously validation of an invalid Joi scheme
const testSync = () => {
try {
const validatedObject = joiSchemaValidationSync({
schema: joiSchemaSample,
inputToValidate: objectToValidate
});
console.log('validation result: ', validatedObject);
} catch (error) {
const { message, originalError } = error;
console.error('error.message: ', message);
console.error('error.originalError: ', prettyPrint(originalError));
}
};
// asynchronous validation of an invalid Joi scheme
const testAsync = async () => {
try {
const validatedObject = await joiSchemaValidationAsync({
schema: joiSchemaSample,
inputToValidate: objectToValidate
});
console.log('validation result: ', validatedObject);
} catch (error) {
const { message, originalError } = error;
console.error('error.message: ', message);
console.error('error.originalError: ', prettyPrint(originalError));
}
};
/*
* --- In both cases (`sync` and `async`) the output of the console is the same ---
*
* console output:
* > error.message: "addressId" is required
* > error.originalError: {
* "_original": {
* "id": 1001
* },
* "details": [
* {
* "message": "\"addressId\" is required",
* "path": [
* "addressId"
* ],
* "type": "any.required",
* "context": {
* "label": "addressId",
* "key": "addressId"
* }
* }
* ]
* }
*
*/
env-variables-validation
In this section, there is a function to validate the environment variables that are required using the function validateProcessEnv
.
This function has a single parameter which is a list of required environment variable name strings where at least one element must be present, and returns an object with each environment variable name as properties.
const { ProcessEnvValidationError, CustomBaseError } = require('@zxventures/utils/errors');
const { validateProcessEnv } = require('@zxventures/utils/validations/env-variables-validation');
/*
* Existing environment variables:
* process.env.VAR_A = 'A';
* process.env.VAR_B = 'B';
* process.env.VAR_C = 'C';
*
*/
// validate and obtain some existing variables
const envVariables = validateProcessEnv('VAR_A', 'VAR_B', 'VAR_C');
console.log('envVariables:', envVariables);
// try to validate any environment variable that does not exist.
try {
const envVariables = validateProcessEnv('ANY_VAR_NOT_PRESENT');
console.log('validation result: ', envVariables);
} catch (error) {
const { message, variableName, name } = error;
console.error('error', { message, variableName });
console.error('is instance of Error:', error instanceof Error);
console.error('is instance of CustomBaseError:', error instanceof CustomBaseError);
console.error('is instance of ProcessEnvValidationError:', error instanceof ProcessEnvValidationError);
console.log('error name:', name);
}
/*
* console output:
* > envVariables: { VAR_A: 'A', VAR_B: 'B', VAR_C: 'C' }
* > error {
* message: 'environment variable not found',
* variableName: 'ANY_VAR_NOT_PRESENT'
* }
* > is instance of Error: true
* > is instance of CustomBaseError: true
* > is instance of ProcessEnvValidationError: true
* > error name: ProcessEnvValidationError
*
*/
Run unit test and code coverage
ADVICE: It is recommended to review and run the unit tests to better understand each of the utilities provided by this package.
Run
npm test
To run code coverage use
npm run coverage