Skip to main content

Helpers

Understand how to use all the Athenna Helpers from @athenna/common and other packages.

Introduction

Athenna includes a variety of "helpers" classes inside the @athenna/common package that comes by default installed in your application. Many of these classes are used by the framework itself; however, you are free to use them in your own applications if you find them convenient.

Available helpers

  • Clean - Remove falsy values from different data structures.
  • Color - The UI Kit of Athenna command line applications.
  • Exception - Create errors with more details to handle them easily.
  • Exec - Simple helpers that executes some operation, like executing a command in a child process.
  • FakeApi - Create a fake REST API using json files to map the routes and their returns (similiar to WireMock).
  • File - Create, copy, move, delete and get information about files.
  • Folder - Create, copy, move, delete and get information about folders.
  • Globals - Execute global helpers exposed by Athenna.
  • HttpClient - Make HTTP requests for other servers with a human-friendly and powerful API.
  • HttpClientBuilder - Build requests using builder pattern instead of creating options as objects.
  • Is - Validate if your data is equals to some type or pattern.
  • Json - Simple helpers to manipulate JSON.
  • Module - Simple helpers to manipulate Node.js modules.
  • Number - Simple helpers to manipulate numbers.
  • ObjectBuilder - Build objects removing undefined and null values and without memory reference.
  • Options - Simple helpers to develop API's (functions and methods) with good options and configurations for developers.
  • Parser - Parse data from X to Y and Y to X.
  • Path - Get the full path to some file or folder starting from your application PWD.
  • Route - Simple helpers to manipulate route params, query params and more.
  • String - Simple helpers to manipulate strings.
  • Uuid - Create customized UUID v4 and validate if they are valid.

Clean

Clean::cleanArray()

Clean falsy and empty values from an array:

import { Clean } from '@athenna/common'

const array = [
'',
1,
null,
undefined,
{ joao: 'joao', lenon: null },
{}
]

const cleanedArray = Clean.cleanArray(array)

console.log(cleanedArray)

/**
* [
* 1,
* { joao: 'joao', lenon: null },
* {}
* ]
*/

The cleanArray() method supports two options, removeEmpty to remove empty values like {} and [] and recursive to recursivelly remove data from objects and arrays:

const cleanedArray = Clean.cleanArray(array, {
removeEmpty: true,
recursive: true
})

console.log(cleanedArray)

/**
* [
* 1, { joao: 'joao' }
* ]
*/

Clean::cleanObject()

const object = {
key: 'value',
emptyArray: [],
emptyObject: {},
object: { joao: 'joao' },
nullValue: null,
undefinedValue: undefined,
arrayWithSubs: [null, 1, { joao: 'joao', lenon: null }, {}],
}

const cleanedObject = Clean.cleanObject(object)

console.log(cleanedObject)
/**
* {
* key: 'value',
* emptyArray: [],
* emptyObject: {},
* object: { joao: 'joao' },
* arrayWithSubs: [null, 1, { joao: 'joao', lenon: null }, {}],
* }
*/

The cleanObject() method supports two options, removeEmpty to remove empty values like {} and [] and recursive to recursivelly remove data from objects and arrays:

const cleanedObject = Clean.cleanObject(object, {
removeEmpty: true,
recursive: true,
})

console.log(cleanedObject)
/**
* {
* key: 'value',
* object: { joao: 'joao' },
* arrayWithSubs: [1, { joao: 'joao' }],
* }
*/

Color

This helper uses chalk library under the hood to create unique colors for Athenna CLI applications. In this documentation we are going to focus only in helpers that Color provides different from chalk.

Color::apply()

Apply the color engine of Athenna to some string. The color engine execute chalk methods under the hood. In the example bellow we are executing yellow() and bold() method of chalk in the word World:

import { Color } from '@athenna/common'

const colorizedString = Color.apply('Hello ({yellow, bold} World)!')
info

The color of World word will be yellow with bold format.

Color::remove()

Remove all the formats and colors of your string:

import { Color } from '@athenna/common'

const noColorString = Color.remove(Color.yellow('Davi Mezencio'))

Color::httpMethod()

Get an instance of chalk with a determined color for each type of http method:

import { Color } from '@athenna/common'

Color.httpMethod('GET').bold('Request Received')
Color.httpMethod('HEAD').bold('Request Received')
Color.httpMethod('POST').bold('Request Received')
Color.httpMethod('PUT').bold('Request Received')
Color.httpMethod('PATCH').bold('Request Received')
Color.httpMethod('DELETE').bold('Request Received')
Color.httpMethod('OPTIONS').bold('Request Received')

Exception

In this documentation section we are going to cover only the helpers of the Exception class. You can learn more about exceptions when handling errors in your application. Those are the available documentation for error handling by application:

Exception.toJSON()

Get the informations of the exception as JSON:

import { Exception } from '@athenna/common'

const exception = new Exception({
status: 500,
details: [],
otherInfos: {},
name: 'ErrorName',
code: 'ERROR_CODE',
message: 'Some exception has ocurred.',
help: 'Try restarting your computer, should work.'
})

const json = exception.toJSON() // { status: 500, name: 'ErrorName', ... }

Exception.prettify()

Transform the exception to a human redable format:

import { Exception } from '@athenna/common'

const exception = new Exception({
status: 500,
details: [],
otherInfos: {},
name: 'ErrorName',
code: 'ERROR_CODE',
message: 'Some exception has ocurred.',
help: 'Try restarting your computer, should work.'
})

Logger.error(await exception.prettify())

Exec

Exec::sleep()

Put the code to sleep for a determined amount of MS:

import { Exec } from '@athenna/common'

await Exec.sleep(3000) // 3 seconds

Exec::concurrently()

Execute some callback concurrently for each value of the array:

import { Exec } from '@athenna/common'

const array = [1, 2, 3]

const newArray = await Exec.concurrently(array, async (number) => {
// Some sync operation just to simulate
await Exec.sleep(1000)

return number++
})

Link some library using any kind of package manager (tested only with npm):

import { Exec } from '@athenna/common'

await Exec.link('@athenna/mail')
await Exec.link(['@athenna/mail', '@athenna/database'], { registry: 'npm' })
await Exec.link(['@athenna/mail', '@athenna/database'], {
silent: true,
args: ['-D'],
reject: false,
registry: 'npm',
cwd: Path.pwd('other-pkg'),
})

Exec::install()

Install some library using any kind of package manager (tested only with npm):

import { Exec } from '@athenna/common'

await Exec.install('@athenna/mail')
await Exec.install(['@athenna/mail', '@athenna/database'], { registry: 'npm' })
await Exec.install(['@athenna/mail', '@athenna/database'], {
silent: true,
dev: false,
args: ['-D'],
cached: false,
reject: false,
registry: 'npm',
cwd: Path.pwd('other-pkg'),
})

Exec::command()

Execute some command of your OS in a child process:

import { Exec } from '@athenna/common'

const { stdout, stderr } = await Exec.command('ls -la')

You can add as second parameter an object with some options:

import { Exec, type CommandInput } from '@athenna/common'

const options: CommandInput = {}

const { stdout, stderr } = await Exec.command('ls -la', options)

Exec::shell()

Same as Exec::command() method, but works only for shell consoles:

import { Exec } from '@athenna/common'

const { stdout, stderr } = await Exec.shell('ls -la')

Exec::node()

Execute some Node.js script in a child process:

#!/usr/bin/env node

import { Exec } from '@athenna/common'

Exec.artisan('./index.ts', {
nodeOptions: ['--import=@athenna/tsconfig']
})

Exec::artisan()

This method was created specially to execute artisan scripts in child processes. Under the hood it calls Exec::node() method but with predefined options:

#!/usr/bin/env node

import { Exec } from '@athenna/common'

Exec.artisan('./bootstrap/artisan.js', {
nodeOptions: ['--enable-source-maps', '--import=@athenna/tsconfig']
})

Exec::download()

Download some file from some URL and get the File instance:

import { Exec } from '@athenna/common'

// File helper class instance
const file = await Exec.download(Path.storage('file.txt'), 'https://athenna.io/file.txt')

Exec::pagination()

Create a pagination object for your API:

import { Exec } from '@athenna/common'

const data = [{...}, {...}]

const paginateObject = await Exec.pagination(data, data.length, {
page: 0,
limit: 10,
resourceUrl: Config.get('app.url')
})

console.log(paginateObject)
/**
* {
* data: [{...}, {...}],
* meta: {
* itemCount: 2,
* totalItems: 2,
* totalPages: 1,
* currentPage: 0,
* itemsPerPage: 10,
* },
* links: {
* first: 'http://localhost:3000?limit=10',
* previous: 'http://localhost:3000?page=0&limit=10',
* next: 'http://localhost:3000?page=1&limit=10',
* last: 'http://localhost:3000?page=0&limit=10',
* }
* }
*/

FakeApi

FakeApi::start()

Start the fake server on port 8989 and loading the path Path.resources('fake-api'):

import { FakeApi } from '@athenna/common'

await FakeApi.start()
note

You can change the server port and the path to read the json files:

import { FakeApi } from '@athenna/common'

await FakeApi.start(8989, Path.resources('path'))

FakeApi::stop()

Stop the fake api server and call FakeApi::recreate() method to create a new server instance:

import { FakeApi } from '@athenna/common'

await FakeApi.stop()

FakeApi::isRunning()

Verify if the fake api server is running:

import { FakeApi } from '@athenna/common'

if (FakeApi.isRunning()) {
// do something...
}

FakeApi::listRoutes()

List all the routes registered in the fake api server:

import { FakeApi } from '@athenna/common'

const routes = FakeApi.listRoutes()

console.log(routes)

FakeApi::registerFile()

Register a route using a json file in the fake api:

import { FakeApi } from '@athenna/common'

FakeApi.registerFile(Path.resources('fake-api/users.json'))

Content of Path.resources('fake-api/users.json') file:

{
"path": "/users",
"method": "GET",
"statusCode": 200, 👈 // The response status code
"body": [ 👈 // The response body
{
"id": 1,
"name": "João Lenon",
},
{
"id": 2,
"name": "Thais Gabriela",
}
],
"headers": { 👈 // The response header
"Content-Type": "application/json"
}
}

FakeApi::registerFolder()

Recursively register all the json files of some folder:

import { FakeApi } from '@athenna/common'

await FakeApi.registerFolder(Path.resources('fake-api'))

FakeApi::build()

Use this method to programatically build the routes u sing the builder pattern:

import { FakeApi } from '@athenna/common'
import type { RouteOptions } from 'fastify'

const options: RouteOptions = {}
const fakeApiBuilder = FakeApi.build()

const users = [
{
id: 1,
name: 'João Lenon',
},
{
id: 2,
name: 'Thais Gabriela',
}
]

fakeApiBuilder
.path('/users')
.method('GET')
.statusCode(200)
.body(users)
.header({ 'Content-Type': 'application/json' })
.register(options)

File

File.load() & File.loadSync()

Creates the file is does not exist and also load the file information:

import { File } from '@athenna/common'

const existent = new File(Path.storage('existent.txt'))
const nonExistent = new File('./nonExistent.txt', 'File content')

// Load the file info and content.
await existent.load({ withContent: true }) 👈

// Create and load the file info without the
// content (be careful when loading big files).
nonExistent.loadSync() 👈

After loading process, the file will contain new informations:

  • createdAt - The date when the file was created.
  • accessedAt - The date when the file was last accessed.
  • modifiedAt - The date when the file was last modified.
  • fileSize - The size of the file in MB.
  • content - The content of the file as Buffer if withContent was true.

File.copy() & File.copySync()

Create a copy of the file in other location or with other name:

import { File } from '@athenna/common'

const copiedFile = file.copySync('./copy-of-file.txt')
const copiedFile = await file.copy(Path.storage('copy-of-file.txt'))

To copy the file and load the content of the copy set the withContent as true:

import { File } from '@athenna/common'

const copiedFile = await file.copy(Path.storage('copy-of-file.txt'), {
withContent: true 👈
})

When copying the file you can set the mockedValues to true to create a file with fake name:

import { File } from '@athenna/common'

const copiedFile = await file.copy(Path.storage('copy-of-file.txt'), {
mockedValues: true 👈
})

File.move() & File.moveSync()

Move the file to other location:

import { File } from '@athenna/common'

const movedFile = file.moveSync('./move-of-file.txt') 👈
const movedFile = await file.move(Path.storage('move-of-file.txt')) 👈

To move the file and load the content of the move set the withContent as true:

import { File } from '@athenna/common'

const movedFile = await file.move(Path.storage('move-of-file.txt'), {
withContent: true 👈
})

When moving the file you can set the mockedValues to true to create a file with fake name:

import { File } from '@athenna/common'

const movedFile = await file.move(Path.storage('file-path.txt'), {
mockedValues: true 👈
})

File.remove() & File.removeSync()

Delete a file from the file system:

import { File } from '@athenna/common'

file.removeSync() 👈
await file.remove() 👈

File.setContent() & File.setContentSync()

Set the content of a file overwriting the existing content:

import { File } from '@athenna/common'

const file = new File('./file.txt', 'Hello')

file.setContentSync('Hello World!') 👈
await file.setContent('Hello World!') 👈

File.getContent() & File.getContentSync()

Get the content of a file as Buffer:

import { File } from '@athenna/common'

const contentBuffer = file.getContentSync() 👈
const contentBuffer = await file.getContent() 👈

To save the content of the file in the instance set the saveContent as true:

import { File } from '@athenna/common'

const content = await file.getContent({ saveContent: true }) 👈

File.getContentAsString() & File.getContentAsStringSync()

Same behavior of getContent()/getContentSync(), but return the content as string:

import { File } from '@athenna/common'

const contentString = file.getContentAsStringSync() 👈
const contentString = await file.getContentAsString() 👈

File.getContentAsJson() & File.getContentAsJsonSync()

Same behavior of getContent()/getContentSync(), but return the content as object if the content is a valid JSON string:

import { File } from '@athenna/common'

const contentJSON = file.getContentAsJsonSync() 👈
const contentJSON = await file.getContentAsJson() 👈

File.getContentAsYaml() & File.getContentAsYamlSync()

Same behavior of getContentAsJson()/getContentAsJsonSync(), but automatically parse the the yaml to an object if the content is a valid YAML string:

import { File } from '@athenna/common'

const contentYAML = file.getContentAsYamlSync() 👈
const contentYAML = await file.getContentAsYaml() 👈

File.getContentAsBuilder() & File.getContentAsBuilderSync()

Same behavior of getContent()/getContentSync(), but return the content as an ObjectBuilder instance if the content is a valid JSON string:

import { File } from '@athenna/common'

const contentObjectBuilder = file.getContentAsBuilderSync() 👈
const contentObjectBuilder = await file.getContentAsBuilder() 👈

File.append() & File.appendSync()

Add content to the end of the file:

import { File } from '@athenna/common'

const file = new File('./file.txt', 'Hello')

file.appendSync(' World') 👈
await file.append('!\n') 👈

File.prepend() & File.prependSync()

Add content to the top of the file:

import { File } from '@athenna/common'

const file = new File('./file.txt', 'World')

file.prependSync('ello ') 👈
await file.prepend('H') 👈

File.createReadStream()

Create a readable stream instance of the file:

const stream = file.createReadStream()

File.createWriteStream()

Create a writable stream instance of the file:

const stream = file.createWriteStream()

File.toJSON()

Get the informations of the file as JSON:

const infos = file.toJSON() 👈

File.import()

Import the file path if is a valid module:

import { File } from '@athenna/common'

const file = new File('./file.js', "console.log('hello')")

const module = await file.import() 👈

File.safeImport()

Same as import() method, but if the file is not a valid module the exception will be ignored:

import { File } from '@athenna/common'

const file = new File('./file.txt', "console.log('hello')")

const module = await file.safeImport() 👈

Importing files that got any errors like syntax errors will also not throw:

import { File } from '@athenna/common'

const file = new File('./file.js', "console.log('hello")

const module = await file.safeImport() 👈 // Nothing happens

File::safeRemove()

Call for a delete operation without worrying about exceptions because the file does not exist:

import { File } from '@athenna/common'

await File.safeRemove(Path.storage('file.txt')) 👈
await File.safeRemove(Path.storage('not-found.txt')) 👈 // Will not throw

File::exists() & File::existsSync()

Verify if a file exists or not:

if (File.existsSync('package.json')) {
// do something
}

if (await File.exists('package.json')) {
// do something
}

File::isFile() & File::isFileSync()

Verify if a file is a valid file or not:

if (File.isFileSync('package.json')) {
// do something
}

if (await File.isFile('package.json')) {
// do something
}

File::createFileOfSize()

Create a fake file with determined size for testing purposes:

const file = await File.createFileOfSize('fake.js', 1024 * 1024 * 100) 👈

Folder

Folder.load() & Folder.loadSync()

Creates the folder if it does not exist and also load the folder information:

import { Folder } from '@athenna/common'

const existent = new Folder(Path.storage('existent'))
const nonExistent = new Folder('./nonExistent')

// Load the folder info with sub folders and with file contents.
await existent.load({ withSub: true, withContent: true }) 👈

// Create and load the folder info without the
// content (be careful when loading big files).
nonExistent.loadSync() 👈

After loading process, the folder will contain new informations:

  • createdAt - The date when the folder was created.
  • accessedAt - The date when the folder was last accessed.
  • modifiedAt - The date when the folder was last modified.
  • folderSize - The size of the folder in MB.

Folder.copy() & Folder.copySync()

Create a copy of the folder in other location or with other name:

import { Folder } from '@athenna/common'

const copiedFolder = folder.copySync('./copy-of-folder')
const copiedFolder = await folder.copy(Path.storage('copy-of-folder'))

To copy the folder and load the sub folders and the content of the copy set the withSub and withContent as true:

import { Folder } from '@athenna/common'

const copiedFolder = await folder.copy(Path.storage('copy-of-folder'), {
withSub: true, 👈
withContent: true 👈
})

When copying the folder you can set the mockedValues to true to copy the files with fake names:

import { Folder } from '@athenna/common'

const copiedFolder = await folder.copy(Path.storage('copy-of-file'), {
mockedValues: true 👈
})

Folder.move() & Folder.moveSync()

Move the folder to other location:

import { Folder } from '@athenna/common'

const movedFolder = folder.moveSync('./move-of-folder') 👈
const movedFolder = await folder.move(Path.storage('move-of-folder')) 👈

To move the folder and load the sub folders and the content of the move set the withSub and withContent as true:

import { Folder } from '@athenna/common'

const movedFolder = await folder.move(Path.storage('move-of-folder'), {
withSub: true, 👈
withContent: true 👈
})

When moving the folder you can set the mockedValues to true to move the files with fake names:

import { Folder } from '@athenna/common'

const movedFolder = await folder.move(Path.storage('file-path'), {
mockedValues: true 👈
})

Folder.remove() & Folder.removeSync()

Delete a folder from the folder system:

import { Folder } from '@athenna/common'

folder.removeSync() 👈
await folder.remove() 👈

Folder.toJSON()

Get the informations of the folder as JSON:

const infos = folder.toJSON() 👈

Folder.getFilesByPattern()

Get all the files of a folder using a glob pattern:

const files = folder.getFilesByPattern('**/*.js') 👈

Folder.getFoldersByPattern()

Get all the folders of a folder using a glob pattern:

const folders = folder.getFoldersByPattern('**/*') 👈

Folder::safeRemove()

Call for a delete operation without worrying about exceptions because the folder does not exist:

import { Folder } from '@athenna/common'

await Folder.safeRemove(Path.storage('folder')) 👈
await Folder.safeRemove(Path.storage('not-found')) 👈 // Will not throw

Folder::exists() & Folder::existsSync()

Verify if a folder exists or not:

if (Folder.existsSync('app')) {
// do something
}

if (await Folder.exists('app')) {
// do something
}

Folder::isFolder() & Folder::isFolderSync()

Verify if a folder is a valid folder or not:

if (Folder.isFolderSync('app')) {
// do something
}

if (await Folder.isFolder('app')) {
// do something
}

Folder::size() & Folder::sizeSync()

Get the size of the folder in MB:

if (Folder.sizeSync('app') === 100) {
// do something
}

if (await Folder.size('app') === 100) {
// do something
}

Globals

Array.athenna.toJSON()

For each value inside an array, execute the toJSON() method if exists:

class User {
public constructor(private name: string) {}

public toJSON(criterias: any = {}) {
return { name: this.name }
}
}

const criterias = {}
const users = [new User('lenon'), new User('txsoura'), {}, 1, 0]

const jsons = users.athenna.toJSON(criterias)
// [{ name: 'lenon' }, { name:'txsoura' }]

Array.athenna.toResource()

For each value inside an array, execute the toResource() method if exists:

class User {
public constructor(private name: string) {}

public toResource(criterias: any = {}) {
return { name: this.name }
}
}

const criterias = {}
const users = [new User('lenon'), new User('txsoura'), {}, 1, 0]

const resources = users.athenna.toResource(criterias)
// [{ name: 'lenon' }, { name:'txsoura' }]

Array.athenna.toCollection()

Parse an array to an Athenna collection:

class User {
public constructor(private name: string) {}
}

const users = [new User('lenon'), new User('txsoura')]

const collection = users.athenna.toCollection().all()
// [{ name: 'lenon' }, { name:'txsoura' }]

Error.toAthennaException()

Parse a vanilla error to an Athenna exception:

new Error().toAthennaException({ status: 404 })

HttpClient

This helper uses the got library under the hood to build the requests.

HttpClient::get()

Make a GET request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const options: Request = {
timeout: 2000
}

const response: Response<any> = await HttpClient.get('https://athenna.io', options)

HttpClient::post()

Make a POST request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const url = 'https://athenna.io'

const options: Request = {
body: {
name: 'João Lenon',
age: 22,
},
timeout: 2000
}

const response: Response<any> = await HttpClient.post(url, options)

HttpClient::put()

Make a PUT request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const url = 'https://athenna.io'

const options: Request = {
body: {
name: 'João Lenon',
age: 22,
},
timeout: 2000
}

const response: Response<any> = await HttpClient.put(url, options)

HttpClient::patch()

Make a PATCH request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const url = 'https://athenna.io'

const options: Request = {
body: {
name: 'João Lenon',
age: 22,
},
timeout: 2000
}

const response: Response<any> = await HttpClient.patch(url, options)

HttpClient::delete()

Make a DELETE request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const url = 'https://athenna.io'

const options: Request = {
timeout: 2000,
}

const response: Response<any> = await HttpClient.delete(url, options)

HttpClient::head()

Make a HEAD request to some URL:

import { 
HttpClient,
type Request,
type Response
} from '@athenna/common'

const url = 'https://athenna.io'

const options: Request = {
timeout: 2000,
}

const response: Response<any> = await HttpClient.head(url, options)

HttpClient::builder() & HttpClient::setBuilder()

Set the HttpClientBuilder instance that HttpClient class will use to build your requests in the example above:

import { 
HttpClient,
type RequestError,
type RetryObject
} from '@athenna/common'

const newBuilder = true
const builder = HttpClient.builder(newBuilder)

builder
.timeout(2000)
.prefixUrl('https://athenna.io')
.retryStrategy((error: RequestError, execCount: number, retryObject: RetryObject) => {
if (execCount === 3) {
return 0
}

return 2000
})

HttpClient.setBuilder(builder)

// Now all the requests done with HttpClient
// will use the same predefined builder options:
const response = await HttpClient.get('https://athenna.io')

HttpClientBuilder

This helper uses the got library under the hood to build the requests.

HttpClientBuilder.setInitHook()

Called with the plain request options, right before their normalization. The second argument represents the current Options instance.

note
  • This hook must be synchronous.
  • This is called every time options are merged.
  • The options object may not have the url property. To modify it, use a beforeRequest hook instead.
  • This hook is called when a new instance of Options is created. Do not confuse this with the creation of Request or got(…).

For example, this can be used to fix typos to migrate from older versions faster.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.setInitHook(init => {
if ('followRedirects' in init) {
init.followRedirect = init.followRedirects
delete init.followRedirects
}
})

HttpClientBuilder.setBeforeRequestHook()

Called right before making the request with options.createNativeRequestOptions(). This hook is especially useful in conjunction with HttpClient.setBuilder(customBuilder) when you want to sign your request.

note
  • HttpClient will make no further changes to the request before it is sent.
  • Changing options.json or options.form has no effect on the request. You should change options.body instead. If needed, update the options.headers accordingly.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.setBeforeRequestHook(options => {
options.body = JSON.stringify({ payload: 'new' })
options.headers['content-length'] = options.body.length.toString()
})

HttpClientBuilder.setBeforeRedirectHook()

The equivalent of setBeforeRequestHook but when redirecting.

note
  • This is especially useful when you want to avoid dead sites.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.setBeforeRedirectHook((options, response) => {
if (options.hostname === 'deadSite') {
options.hostname = 'fallbackSite'
}
})

HttpClientBuilder.setBeforeErrorHook()

Called with a RequestError instance. The error is passed to the hook right before it's thrown. This is especially useful when you want to have more detailed errors.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.responseType('json')
.setBeforeErrorHook(error => {
const { response } = error

if (response && response.body) {
error.name = 'GitHubError'
error.message = `${response.body.message} (${response.statusCode})`
}

return error
})

HttpClientBuilder.setBeforeRetryHook()

The equivalent of setBeforeErrorHook but when retrying. Additionally, there is a second argument retryCount, the current retry number.

note
  • When using the Stream API, this hook is ignored.
  • When retrying, the beforeRequest hook is called afterwards.
  • If no retry occurs, the beforeError hook is called instead.

This hook is especially useful when you want to retrieve the cause of a retry.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.setBeforeRetryHook((error, retryCount) => {
console.log(`Retrying [${retryCount}]: ${error.code}`)
// Retrying [1]: ERR_NON_2XX_3XX_RESPONSE
})

HttpClientBuilder.setAfterResponseHook()

Each function should return the response. This is especially useful when you want to refresh an access token.

note
  • When using the Stream API, this hook is ignored.
  • Calling the retryWithMergedOptions function will trigger beforeRetry hooks. If the retry is successful, all remaining afterResponse hooks will be called. In case of an error, beforeRetry hooks will be called instead.

Meanwhile, the init, beforeRequest , beforeRedirect as well as already executed afterResponse hooks will be skipped.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.mutableDefaults(true)
.setBeforeRetry(error => {
// This will be called on `retryWithMergedOptions(...)`
})
.setAfterResponseHook((response, retryWithMergedOptions) => {
// Unauthorized
if (response.statusCode === 401) {
// Refresh the access token
const updatedOptions = {
headers: {
token: getNewToken()
}
};

// Update the defaults
instance.defaults.options.merge(updatedOptions)

// Make a new retry
return retryWithMergedOptions(updatedOptions)
}

// No changes otherwise
return response
})

HttpClientBuilder.agent()

An object representing http, https and http2 keys for http.Agent, https.Agent and http2wrapper.Agent instance. This is necessary because a request to one protocol might redirect to another. In such a scenario, HttpClient will switch over to the right protocol agent for you. If a key is not present, it will default to a global agent.

import { HttpClientBuilder } from '@athenna/common'
import HttpAgent, { HttpsAgent } from 'agentkeepalive'

const builder = new HttpClientBuilder()

builder.agent({ http: new HttpAgent(), https: new HttpsAgent() })

HttpClientBuilder.http2()

If set to true, HttpClient will additionally accept HTTP2 requests.

It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol:

note
  • This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy.
  • Overriding options.request will disable HTTP2 support.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.http2(true)

HttpClientBuilder.h2session()

Set the http2 session instance to be used by the request:

import { connect } from 'node:http2'
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()
const session = connect('https://localhost:1234')

builder.h2session(session)

HttpClientBuilder.decompress()

Decompress the response automatically. This will set the accept-encoding header to gzip, deflate, br unless you set it yourself. If this is disabled, a compressed response is returned as a Buffer. This may be useful if you want to handle decompression yourself or stream the raw compressed data.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.decompress(true)

HttpClientBuilder.timeout()

Milliseconds to wait for the server to end the response before aborting the request with a timeout error (a.k.a. request property). By default, there's no timeout.

This also accepts an object with the following fields to constrain the duration of each phase of the request lifecycle:

  • lookup starts when a socket is assigned and ends when the hostname has been resolved. Does not apply when using a Unix domain socket.
  • connect starts when lookup completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected.
  • secureConnect starts when connect completes and ends when the handshaking process completes (HTTPS only).
  • socket starts when the socket is connected. See request.setTimeout.
  • response starts when the request has been written to the socket and ends when the response headers are received.
  • send starts when the socket is connected and ends with the request has been written to the socket.
  • request starts when the request is initiated and ends when the response's end event fires.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.timeout(2000)
// or
builder.timeout({
lookup: 2000,
})

HttpClientBuilder.body()

Set the request body:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.body('hello', 'world!')
// or overwriting the entire body set before
builder.body({ hello: 'world!' })

HttpClientBuilder.input()

Alias for HttpClientBuilder.body() method:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.input('hello', 'world!')

HttpClientBuilder.form()

Set the request body as form:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.form({
hello: 'world!'
})

HttpClientBuilder.when()

Execute a closure only when first value is truthy:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.when(true, (builder, value) => {
builder.input('isAdmin', value)
})

HttpClientBuilder.header()

Set a header of the request:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.header('Content-Type', 'application/json')
// or overwriting the entire header set before
builder.header({ 'Content-Type': 'application/json' })

HttpClientBuilder.safeHeader()

Same as header(), but if the header already exists it will not be overwritten:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.header('Content-Type', 'application/json')
.safeHeader('Content-Type', 'will-not-change')

HttpClientBuilder.removeHeader()

Remove the header of the request:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.header('Content-Type', 'application/json')
.removeHeader('Content-Type')

HttpClientBuilder.prefixUrl()

When specified, prefixUrl will be prepended to url. The prefix can be any valid URL, either relative or absolute. A trailing slash / is optional - one will be added automatically:

note
  • prefixUrl will be ignored if the url argument is a URL instance.
  • Leading slashes in input are disallowed when using this option to enforce consistency and avoid confusion. For example, when the prefix URL is https://example.com/foo and the input is /bar, there's ambiguity whether the resulting URL would become https://example.com/foo/bar or https://example.com/bar. The latter is used by browsers.
  • You can change prefixUrl using hooks as long as the URL still includes the prefixUrl. If the URL doesn't include it anymore, it will throw.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.prefixUrl('https://athenna.io')

HttpClientBuilder.method()

Set the request method:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.method('POST')

HttpClientBuilder.url()

Set the request url:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.url('https://athenna.io/api/v1')

HttpClientBuilder.cookieJar()

Set the cookie jar of the request:

note
  • If you provide this option, options.headers.cookie will be overridden.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.cookieJar(new CookieJar())

HttpClientBuilder.signal()

Set the signal of the request:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()
const abortController = new AbortController()

build.signal(abortController.signal)

setTimeout(() => {
abortController.abort()
}, 100)

HttpClientBuilder.ignoreInvalidCookies()

Ignore invalid cookies instead of throwing an error. Only useful when the cookieJar option has been set. Not recommended:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.ignoreInvalidCookies(true)

HttpClientBuilder.searchParams()

Query string that will be added to the request URL. This will override the query string in url. If you need to pass in an array, you can do it using a URLSearchParams instance:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.searchParams('hello', 'world!')
// overwrite queries set before
.searchParams({ hello: 'world!' })
// overwrite queries set before
.searchParams(new URLSearchParams([['key', 'a'], ['key', 'b']]))

HttpClientBuilder.query()

Alias for searchParams() method.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.query('hello', 'world!')
// overwrite queries set before
.query({ hello: 'world!' })
// overwrite queries set before
.query(new URLSearchParams([['key', 'a'], ['key', 'b']]))

HttpClientBuilder.safeQuery()

Same as query(), but if the query already exists it will not be overwritten:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.safeQuery('hello', 'world!')

HttpClientBuilder.dnsLookup()

Set the DNS lookup options of the request:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.dnsLookup({
family: 6
})

HttpClientBuilder.dnsCache()

An instance of CacheableLookup used for making DNS lookups. Useful when making lots of requests to different public hostnames.

CacheableLookup uses dns.resolver4(..) and dns.resolver6(...) under the hood and fall backs to dns.lookup(...) when the first two fail, which may lead to additional delay:

note
  • This should stay disabled when making requests to internal hostnames such as localhost, database.local etc.
import CacheableLookup from 'cacheable-lookup'
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.dnsCache(new CacheableLookup())

HttpClientBuilder.context()

User data. context is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder
.setBeforeRequestHook(options => {
if (!options.context || !options.context.token) {
throw new Error('Token required')
}

options.headers.token = options.context.token
})
.context({ token: 'secret' })

HttpClientBuilder.hooks()

Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially:

import { HttpClientBuilder, type Hooks } from '@athenna/common'

const builder = new HttpClientBuilder()

const hooks: Hooks = {}

builder.hooks(hooks)

HttpClientBuilder.followRedirect()

Defines if redirect responses should be followed automatically. Note that if a 303 is sent by the server in response to any request type (POST, DELETE, etc.), HttpClient will automatically request the resource pointed to in the location header via GET. This is in accordance with the spec. You can optionally turn on this behavior also for other redirect codes - see methodRewriting:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.followRedirect(true)

HttpClientBuilder.followRedirects()

Alias for followRedirect().

HttpClientBuilder.maxRedirects()

If exceeded, the request will be aborted and a MaxRedirectsError will be thrown:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.maxRedirects(5)

HttpClientBuilder.cacheOptions()

Set the cache options of the request from http-cache-semantics:

import { HttpClientBuilder, type CacheOptions } from '@athenna/common'

const builder = new HttpClientBuilder()

const cacheOptions: CacheOptions = {
shared: true,
immutableMinTimeToLive: 1000,
}

builder.cacheOptions(cacheOptions)

HttpClientBuilder.cache()

A cache adapter instance for storing cached response data:

import { HttpClientBuilder } from '@athenna/common'

const cache = new Map()
const builder = new HttpClientBuilder()

builder.cache(cache)

HttpClientBuilder.throwHttpErrors()

Determines if an error is thrown for unsuccessful responses.

If this is disabled, requests that encounter an error status code will be resolved with the response instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.throwHttpErrors(false)

HttpClientBuilder.username()

Username for Basic Authentication:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.username('jlenon7')

HttpClientBuilder.password()

Password for Basic Authentication:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.username('12345')

HttpClientBuilder.allowGetBody()

Set this to true to allow sending body for the GET method. However, the HTTP/2 specification says that An HTTP GET request includes request header fields and no payload body, therefore when using the HTTP/2 protocol this option will have no effect. This option is only meant to interact with non-compliant servers when you have no other choice.

note
  • The RFC 7231 doesn't specify any particular behavior for the GET method having a payload, therefore it's considered an anti-pattern.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.allowGetBody(true)

HttpClientBuilder.methodRewriting()

Specifies if the HTTP request method should be rewritten as GET on redirects.

As the specification prefers to rewrite the HTTP method only on 303 responses, this is HttpClient's default behavior. Setting methodRewriting to true will also rewrite 301 and 302 responses, as allowed by the spec. This is the behavior followed by curl and browsers.

note
  • HttpClient never performs method rewriting on 307 and 308 responses, as this is [explicitly prohibited by the specification.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.methodRewriting(true)

HttpClientBuilder.dnsLookupIpVersion()

Indicates which DNS record family to use.

Values:

  • undefined: IPv4 (if present) or IPv6
  • 4: Only IPv4
  • 6: Only IPv6
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.dnsLookupIpVersion(6)

HttpClientBuilder.parseJson()

Define a function to parse JSON responses:

import Bourne from '@hapi/bourne'
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.parseJson(text => Bourne.parse(text))

HttpClientBuilder.stringifyJson()

Define a function to stringify JSON requests:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.stringifyJson(object => JSON.stringify(object))

HttpClientBuilder.retry()

An object representing limit, calculateDelay, methods, statusCodes, maxRetryAfter and errorCodes fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum Retry-After time and allowed error codes.

Delays between retries counts with function 1000 * Math.pow(2, retry) + Math.random() * 100, where retry is attempt number (starts from 1).

The calculateDelay property is a function that receives an object with attemptCount, retryOptions, error and computedValue properties for current retry count, the retry options, error and default computed value. The function must return a delay in milliseconds (or a Promise resolving with it) (0 return value cancels retry).

By default, it retries only on the specified methods, status codes, and on these network errors:

  • ETIMEDOUT: One of the timeout limits were reached.
  • ECONNRESET: Connection was forcibly closed by a peer.
  • EADDRINUSE: Could not bind to any free port.
  • ECONNREFUSED: Connection was refused by the server.
  • EPIPE: The remote side of the stream being written has been closed.
  • ENOTFOUND: Couldn't resolve the hostname to an IP address.
  • ENETUNREACH: No internet connection.
  • EAI_AGAIN: DNS lookup timed out.
note
  • If maxRetryAfter is set to undefined, it will use options.timeout.
  • If Retry-After header is greater than maxRetryAfter, it will cancel the request.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.retry({
limit: 5,
calculateDelay: ({ attemptCount }) => {
return attemptCount * 1000
},
methods: ['GET'],
statusCodes: [[100, 199], 429, 404],
maxRetryAfter: 5000,
errorCodes: ['ETIMEDOUT'],
})

HttpClientBuilder.retryStrategy()

This method is just an alias to configure your own retry strategy.

The strategy function needs to return the delay between the execution count of each request, if the strategy function returns 0, the retry is canceled:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.retryStrategy((error, execCount, retryObject) => {
if (execCount === 3) {
return 0
}

return 2000
})

HttpClientBuilder.localAddress()

From http.RequestOptions. The IP address used to send the request from.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.localAddress('192.168.0.1')

HttpClientBuilder.createConnection()

Define a function used to retrieve a net.Socket instance when the agent option is not used.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.createConnection(options => {
return new net.Socket(options)
})

HttpClientBuilder.https()

Define options to make https requests:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.https({
rejectUnauthorized: false
})

HttpClientBuilder.encoding()

Encoding to be used on setEncoding of the response data.

To get a Buffer, you need to set responseType to buffer instead. Don't set this option to null.

note
  • This doesn't affect streams! Instead, you need to do HttpClientBuilder.stream(...).setEncoding(encoding).
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.encoding('utf8')

HttpClientBuilder.resolveBodyOnly()

When set to true the promise will return the response body instead of the response object:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.resolveBodyOnly(true)

HttpClientBuilder.responseType()

The parsing method. The promise also has .text(), .json() and .buffer() methods which return another HttClient promise for the parsed body.

It's like setting the options to {responseType: 'json', resolveBodyOnly: true} but without affecting the main HttpClient promise.

note
  • When using streams, this option is ignored.
import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.responseType('json')

HttpClientBuilder.pagination()

Set pagination options to your request:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.pagination({ countLimit: 100 })

HttpClientBuilder.setHost()

Set the host option:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.setHost(true)

HttpClientBuilder.maxHeaderSize()

Set the maxHeaderSize option.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.maxHeaderSize(1000)

HttpClientBuilder.enableUnixSockets()

Set the enableUnixSockets option.

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

builder.enableUnixSockets(true)

HttpClientBuilder.stream()

Execute the request and return as stream:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const stream = await builder.stream()

HttpClientBuilder.paginate()

Execute the request and return as paginated response:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const paginatedResponse = await builder.paginate()

HttpClientBuilder.request()

Execute the request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder.request()

HttpClientBuilder.get()

Execute a GET request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder.get()

HttpClientBuilder.post()

Execute a POST request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder
.url('https://localhost:3000/users')
.body({ name: 'lenon' })
.post()
// or
const response = await builder.post('https://localhost:3000/users', {
body: { name: 'lenon' }
})

HttpClientBuilder.put()

Execute a PUT request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder
.url('https://localhost:3000/users')
.body({ name: 'lenon' })
.put()
// or
const response = await builder.put('https://localhost:3000/users', {
body: { name: 'lenon' }
})

HttpClientBuilder.patch()

Execute a PATCH request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder
.url('https://localhost:3000/users')
.body({ name: 'lenon' })
.patch()
// or
const response = await builder.patch('https://localhost:3000/users', {
body: { name: 'lenon' }
})

HttpClientBuilder.delete()

Execute a DELETE request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder.delete()

HttpClientBuilder.head()

Execute a HEAD request using all the options defined:

import { HttpClientBuilder } from '@athenna/common'

const builder = new HttpClientBuilder()

const response = await builder.head()

Is

Is::kindOf()

Return the kind of the argument:

import { Is } from '@athenna/common'

Is.kindOf('hello') // string

Is::Mac()

Validate if the current OS is a MacOS:

import { Is } from '@athenna/common'

Is.Mac() // true

Is::Linux()

Validate if the current OS is a distribution of Linux:

import { Is } from '@athenna/common'

Is.Linux() // true

Is::Windows()

Validate if the current OS is a Windows:

import { Is } from '@athenna/common'

Is.Windows() // true

Is::Module()

Validate if a path or File instance is a module:

import { Is, File } from '@athenna/common'

Is.Module('./hello.js') // true
Is.Module('./hello.ts') // true
Is.Module('./hello.json') // false

Is.Module(new File('./hello.js')) // true
Is.Module(new File('./hello.ts')) // true
Is.Module(new File('./hello.json')) // false

Is::Uuid()

Validate if the value is a valid UUID v4:

import { Is } from '@athenna/common'

Is.Uuid('adm::a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11') // true
Is.Uuid('adm::a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', { prefix: 'adm' }) // true

Is::Defined()

Validate if the value is defined:

import { Is } from '@athenna/common'

Is.Defined('Hello World') // true

Is::Json()

Validate if the value is a valid JSON string:

import { Is } from '@athenna/common'

Is.Json('{ "name": "João Lenon" }') // true

Is::Ip()

Validate if the value is a valid IP address:

import { Is } from '@athenna/common'

Is.Ip('192.168.0.1') // true

Is::Empty()

Validate if the value is empty:

import { Is } from '@athenna/common'

Is.Empty(undefined) // true
Is.Empty('') // true
Is.Empty([]) // true
Is.Empty({}) // true

Is::Cep()

Validate if the value is a valid Brazil CEP:

import { Is } from '@athenna/common'

Is.Cep('85869-676') // true

Is::Cpf()

Validate if the value is a valid Brazil Cpf:

import { Is } from '@athenna/common'

Is.Cpf('208.464.590-51') // true

Is::Cnpj()

Validate if the value is a valid Brazil Cnpj:

import { Is } from '@athenna/common'

Is.Cnpj('71.151.950/0001-65') // true

Is::Async()

Verify if a function is async:

import { Is } from '@athenna/common'

Is.Async(async () => {}) // true
Is.Async(async function () {}) // true

Is::Undefined()

Validate if the value is undefined:

import { Is } from '@athenna/common'

Is.Undefined(undefined) // true

Is::Null()

Validate if the value is null:

import { Is } from '@athenna/common'

Is.Null(null) // true

Is::Boolean()

Validate if the value is boolean:

import { Is } from '@athenna/common'

Is.Boolean(true) // true

Is::Buffer()

Validate if the value is Buffer:

import { Is } from '@athenna/common'

Is.Buffer(new Buffer()) // true

Is::Number()

Validate if the value is number:

import { Is } from '@athenna/common'

Is.Number(10) // true

Is::String()

Validate if the value is string:

import { Is } from '@athenna/common'

Is.String('Athenna is the best!') // true

Is::Object()

Validate if the value is Object:

import { Is } from '@athenna/common'

Is.Object({ hello: 'world!' }) // true

Is::Date()

Validate if the value is Date:

import { Is } from '@athenna/common'

Is.Date(new Date()) // true

Is::Array()

Validate if the value is array:

import { Is } from '@athenna/common'

Is.Array([1, 2, 3, 4, 5]) // true

Is::Regexp()

Validate if the value is Regexp:

import { Is } from '@athenna/common'

Is.Regexp(new RegExp()) // true

Is::Error()

Validate if the value is error:

import { Is } from '@athenna/common'

Is.Error(new Error()) // true

Is::Exception()

Validate if the value is Exception or any variation that extends from it:

import { Is, Exception } from '@athenna/common'

Is.Exception(new Error()) // false
Is.Exception(new Exception()) // true

Is::Class()

Validate if the value is class:

import { Is } from '@athenna/common'

Is.Class(Is) // true

Is::Float()

Validate if the value is float:

import { Is } from '@athenna/common'

Is.Float(10.5) // true

Is::ArrayOfObjects()

Validate if the value is an array of objects:

import { Is } from '@athenna/common'

Is.ArrayOfObjects([{}, {}, {}]) // true

Json

Json::builder()

Create a new instance of ObjectBuilder:

import { Json } from '@athenna/common'

const builder = Json.builder({ referencedValues: false })

Json::copy()

Deep copy any object without memory reference:

import { Json } from '@athenna/common'

const object = { name: 'João Lenon' }
const copiedObject = Json.copy(object)

object.name = 'Victor Tesoura'

console.log(object.name === copiedObject.name) // false

Json::getJson()

Find all JSON values inside a string and return it:

import { Json } from '@athenna/common'

const string = 'Hello { "name": "João Lenon" }'
const arrayOfFoundJson = Json.getJson(string)
// ['{"name":"João Lenon"}']

Json::parse()

Safe parse a JSON string. If the JSON is not valid, returns null:

import { Json } from '@athenna/common'

const string = 'Hello { "name": "Lenon" }'
const json = Json.parse(string) // null

console.log('{ "name": "Lenon" }') // { name: 'Lenon' }

Json::observeChanges()

Observe any change done inside an object and execute some closure:

import { Json } from '@athenna/common'

const object = { name: 'Lenon' }

Json.observeChanges(object, (value) => {
console.log('Object changed!', value)
})

object.name = 'João Lenon'

Json::fillable()

Remove all keys from data that is not inside the array:

import { Json } from '@athenna/common'

const data = {
name: 'João Lenon',
age: 22,
role: 'admin',
deletedAt: new Date()
}

const parsedData = Json.fillable(data, ['name', 'age'])
// { name: 'João Lenon', age: 22 }

Json::get()

Get the object properties using dot notation:

import { Json } from '@athenna/common'

const object = {
name: 'João Lenon',
age: 22,
role: 'admin',
deletedAt: new Date(),
configs: {
theme: 'dark',
language: 'en'
}
}

Json.get(object, 'name') // João Lenon
Json.get(object, 'configs.theme') // dark

Json::omit()

Get all keys from an object omitting selected ones:

const omitted = Json.omit({ name: 'Lenon', age: 23 }, ['name'])

console.log(omitted) // { age: 23 }

Json::pick()

Pick only selected keys from an object:

const picked = Json.pick({ name: 'Lenon', age: 23 }, ['name'])

console.log(picked) // { name: 'Lenon' }

Module

Module::get()

Get the default module or the first exported module of a file:

default.ts
export default {
name: 'João Lenon'
}
object.ts
export const person = {
name: 'João Lenon'
}
import { Module } from '@athenna/common'

await Module.get(import('./default.js')) // { name: 'João Lenon' }
await Module.get(import('./object.js')) // { name: 'João Lenon' }

Module::getAll()

Get all exported modules of a path and return as array:

import { Module } from '@athenna/common'

const modules = [import('./object.js'), import('./default.js')]

await Module.getAll(modules)
// [{ name: 'João Lenon' }, { name: 'João Lenon' }]

Module::getFrom()

Same as Module::get() method, but import from a path:

import { Module } from '@athenna/common'

await Module.getFrom('./default.js') // { name: 'João Lenon' }
await Module.getFrom('./object.js') // { name: 'João Lenon' }

Module::getAllFrom()

Same as Module::getAll() method, but import from a path:

import { Module } from '@athenna/common'

const paths = ['./object.js', './default.js']

await Module.getAllFrom(paths)
// [{ name: 'João Lenon' }, { name: 'João Lenon' }]

Module::getWithAlias()

Get the default module or the first exported module of a file with alias:

MyService.ts
export default class MyService {}
import { Module } from '@athenna/common'

const modules = [import('./MyService.js')]
const subAlias = 'App/Services'

await Module.getWithAlias(modules, subAlias)
// { alias: 'App/Services/MyService', module: [class MyService] }

Module::getAllWithAlias()

Get all exported modules of a path and return as array with alias:

import { Module } from '@athenna/common'

const modules = [import('./MyService.js')]
const subAlias = 'App/Services'

await Module.getAllWithAlias(modules, subAlias)
// [{ alias: 'App/Services/MyService', module: { name: 'João Lenon' } }]

Module::getFromWithAlias()

Same as Module::getWithAlias() method, but import from a path:

import { Module } from '@athenna/common'

const paths = ['./MyService.js']
const subAlias = 'App/Services'

await Module.getAllWithAlias(paths, subAlias)
// [{ alias: 'App/Services/MyService', module: { name: 'João Lenon' } }]

Module::getAllFromWithAlias()

Same as Module::getAllWithAlias() method, but import from a path:

import { Module } from '@athenna/common'

const paths = ['./MyService.js']
const subAlias = 'App/Services'

await Module.getAllWithAlias(paths, subAlias)
// [{ alias: 'App/Services/MyService', module: { name: 'João Lenon' } }]

Module::getAllJSFilesFrom()

Get all the Path.ext() files from a path:

import { File, Path, Module } from '@athenna/common'

const files: File[] = await Module.getAllJSFilesFrom(Path.app())

Module::import()

Import a full path using the path href to ensure compatibility between OS's:

import { Module } from '@athenna/common'

const module = await Module.import('./MyService.js')

Module::safeImport()

Same as Module::import() method but return null if the module does not exist:

import { Module } from '@athenna/common'

const module = await Module.safeImport('./notFound.js')

Module::resolve()

Resolve the module path by meta url and import it:

import { Module } from '@athenna/common'

const module = await Module.resolve('./MyService.js', import.meta.url)

You can add the following options to it as third argument:

const module = './MyService.js'
const parentURL = import.meta.url

const module = await Module.resolve(module, parentURL, {
// Automatically import the module instead of returning
// the module path resolved.
import: true,

// Automatically get the imported module using `Module.get()`
// method.
getModule: true
})

Module::createDirname()

Crete the old __dirname property:

import { Module } from '@athenna/common'

const __dirname = Module.createDirname(import.meta.url)

Module::createFilename()

Crete the old __filename property:

import { Module } from '@athenna/common'

const __filename = Module.createFilename(import.meta.url)

Module::createRequire()

Crete the old require() function:

import { Module } from '@athenna/common'

const require = Module.createRequire(import.meta.url)

Number

Number::getHigher()

Get the higher number of an array of numbers:

import { Number } from '@athenna/common'

const higher = Number.getHigher([1, 2, 3, 4, 5]) // 5

Number::getKmRadius()

Get km radius between two coordinates:

import { Number } from '@athenna/common'

const centerCord = {
latitude: -25503207,
longitude: -545390592
}

const pointCord = {
latitude: -254957901,
longitude: -545671577
}

const radius = Number.getKmRadius(centerCord, pointCord)
// 5338.683217695541

Number::getLower()

Get the lower number of an array of numbers:

import { Number } from '@athenna/common'

const lower = Number.getLower([1, 2, 3, 4, 5]) // 1

Number::extractNumber()

Extract all numbers inside a string and return as a unique number:

import { Number } from '@athenna/common'

const string = 'Hello 123 World 456'
const number = Number.extractNumber(string) // 123456

Number::extractNumbers()

Extract all numbers inside a string:

import { Number } from '@athenna/common'

const string = 'Hello 123 World 456'
const numbers = Number.extractNumbers(string) // ['123', '456']

Number::argsAverage()

Get the average of all numbers in function arguments:

import { Number } from '@athenna/common'

const average = Number.argsAverage(1, 2, 3, 4, 5) // 3

Number::arrayAverage()

Get the average of all numbers in an array:

import { Number } from '@athenna/common'

const average = Number.argsAverage([1, 2, 3, 4, 5]) // 3

Number::randomIntFromInterval()

Generate a random integer number between two numbers:

import { Number } from '@athenna/common'

const random = Number.randomIntFromInterval(1, 10) // 5

ObjectBuilder

new ObjectBuilder()

Create a new instance of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder({
// Dont create memory referenced values.
referencedValues: false,
// Dont ignore null values when calling `set()` method.
ignoreNull: false,
// Ignore undefined values when calling `set()` method.
ignoreUndefined: true,
// Default value to set if value is undefined on calling `set()` method.
defaultValue: 'Hello World'
})

const key = 'name'
const value = 'João Lenon'
const defaultValue = 'User Name'

builder.set(key, value, defaultValue)
console.log(builder.get(key)) // João Lenon

builder.set(key, undefined)
console.log(builder.get(key)) // Hello World

ObjectBuilder.set()

Set a value to the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'João Lenon', age: 22, options: { role: 'admin' } })

console.log(builder.get())
// { name: 'João Lenon', age: 22, options: { role: 'admin' } }

Set a value to the object builder using dot notation:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()
const defaultValue = null

builder.set('name', 'João Lenon', defaultValue)
builder.set('age', 22, defaultValue)
builder.set('deletedAt', undefined, defaultValue)
builder.set('options.role', 'admin', defaultValue)

console.log(builder.get())
// { name: 'João Lenon', age: 22, deletedAt: null, options: { role: 'admin' } }

ObjectBuilder.delete()

Delete a value from the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })
builder.delete('name')

ObjectBuilder.get()

Get the value of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon', options: { role: 'admin' } })

console.log(builder.get('name')) // Lenon
console.log(builder.get('options.role')) // admin

ObjectBuilder.keys()

Get all the keys of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

console.log(builder.keys()) // ['name']

ObjectBuilder.values()

Get all the values of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

console.log(builder.values()) // ['Lenon']

ObjectBuilder.entries()

Get all the entries of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

console.log(builder.entries()) // [['name', 'Lenon']]

ObjectBuilder.forEachKey()

Iterate over all the keys of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

builder.forEachKey((key) => {
console.log(key) // name
})

ObjectBuilder.forEachValue()

Iterate over all the values of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

builder.forEachValue((value) => {
console.log(value) // Lenon
})

ObjectBuilder.forEachEntry()

Iterate over all the entries of the object builder:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

builder.forEachEntry(([key, value]) => {
console.log(key, value) // name Lenon
})

ObjectBuilder.is() & ObjectBuilder.isNot()

Verify if the object key path is or not the same value:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

builder.is('name', 'Lenon') // true
builder.isNot('name', 'Lenon') // false

ObjectBuilder.exists() & ObjectBuilder.notExists()

Verify if the object key path exists or not:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon' })

builder.exists('name') // true
builder.notExists('name') // false

ObjectBuilder.existsAll() & ObjectBuilder.notExistsAll()

Verify if all the object key paths exists or not:

import { ObjectBuilder } from '@athenna/common'

const builder = new ObjectBuilder()

builder.set({ name: 'Lenon', age: 22 })

builder.existsAll(['name', 'age']) // true
builder.notExistsAll('name', 'age') // false

Options

Options::create()

Creates an option object with default values:

import { Options } from '@athenna/common'

type Options = {
opts1?: boolean
opts2?: string
}

function doSomething(options: Options = {}) {
options = Options.create(options, {
opts1: true