Utils
A toolkit of utilities used across all the AdonisJS, Edge, and Japa packages
Install / Use
/learn @poppinss/UtilsREADME
@poppinss/utils
A toolkit of utilities used across all the AdonisJS, Edge, and Japa packages
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
Why does this package exist?
My open-source projects (including AdonisJS) use many single-purpose utility packages from npm. Over the years, I have faced the following challenges when using these packages.
- Finding the perfect package for the use case takes a lot of time. The package should be well maintained, have good test coverage, and not accumulate debt by supporting some old versions of Node.js.
- Some packages are great, but they end up pulling a lot of unnecessary dependencies like (requiring TypeScript as a prod dependency)
- Sometimes, I use different packages for the same utility (because I cannot remember what I used last time in that other package). So I want to spend time once choosing the one I need and then bundle it inside
@poppinss/utils. - Some authors introduce breaking changes too often (not a criticism). Therefore, I prefer wrapping their packages with my external API only to absorb breaking changes in one place.
- The rest are some handwritten utilities that fit my needs.
Re-exported packages
The following packages are re-exported as it is and you must consult their documentation for usage instructions.
| Package | Subpath export |
| ------------------------------------------------------------------------ | --------------------------- |
| @poppinss/exception | @poppinss/utils/exception |
| @poppinss/string | @poppinss/utils/string |
| @poppinss/types | @poppinss/utils/types |
Other packages to use
A note to self and others to consider the following packages.
| Package | Description | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ | | he | For escaping HTML entities and encoding Unicode symbols. Has zero dependencies | | @sindresorhus/is | For advanced type checking. Has zero dependencies | | moize | For memoizing functions with complex parameters |
Package size
Even though I do not care much about package size (most of my work is consumed on the server side), I am mindful of the utilities and ensure that I do not end up using really big packages for smaller use cases.
Here's the last checked install size of this package.
<a href="https://pkg-size.dev/@poppinss/utils@next"><img src="https://pkg-size.dev/badge/install/370120" title="Install size for @poppinss/utils"></a>
Installation
Install the package from the npm registry as follows:
npm i @poppinss/utils
# Yarn lovers
yarn add @poppinss/utils
JSON helpers
Safely parse and stringify JSON values. These helpers are thin wrappers over secure-json-parse and safe-stable-stringify packages.
safeParse
The native implementation of JSON.parse opens up the possibility for prototype poisoning. The safeParse method protects you from that by removing the __proto__ and the constructor.prototype properties from the JSON string at the time of parsing it.
import { safeParse } from '@poppinss/utils/json'
safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
// { a: 5, b: 6 }
safeStringify
The native implementation of JSON.stringify cannot handle circular references or language-specific data types like BigInt. The safeStringify method removes circular references and converts BigInts to strings.
The safeStringify method accepts the same set of parameters as the JSON.stringify method.
import { safeStringify } from '@poppinss/utils/json'
const value = {
b: 2,
c: BigInt(10),
}
// Circular reference
value.a = value
safeStringify(value)
// '{"b":2,"c":"10"}'
Lodash helpers
Lodash is quite a big library, and we do not use all its helper methods. Therefore, we create a custom build using the lodash CLI and bundle only the needed ones.
Why not use something else: All other helpers I have used are not as accurate or well implemented as lodash.
- pick
- pickBy
- omit
- omitBy
- has
- get
- set
- unset
- mergeWith
- merge
- size
- clone
- cloneWith
- cloneDeep
- cloneDeepWith
- toPath
You can use the methods as follows.
import lodash from '@poppinss/utils/lodash'
lodash.pick(collection, keys)
FS helpers
fsReadAll
Get a recursive list of all files from a given directory. This method is similar to the Node.js readdir method, with the following differences.
- Dot files and directories are ignored.
- Only files are returned (not directories).
- You can define how the output paths should be returned. The supported types are
relative,absolute,unixRelative,unixAbsolute, andurl.
import { fsReadAll } from '@poppinss/utils/fs'
const basePath = new URL('./config', import.meta.url)
const files = await fsReadAll(basePath, { pathType: 'url' })
console.log(files)
OPTIONS
<dl> <dt>ignoreMissingRoot</dt> <dd>By default, an exception is raised when the root directory is missing. Setting <code>ignoreMissingRoot</code> to true will not result in an error, and an empty array will be returned.</dd> <dt>filter</dt> <dd>Define a filter to ignore certain paths. The method is called on the final list of files.</dd> <dt>sort</dt> <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd> <dt>pathType</dt> <dd>Define how to return the collected paths. By default, OS-specific relative paths are returned. If you want to import the collected files, you must set the <code>pathType = 'url'</code></dd> </dl>fsImportAll
The fsImportAll method recursively imports all the JavaScript, TypeScript, and JSON files from a given directory and returns their exported values as an object of key-value pairs.
- If there are nested directories, then the output will also contain nested objects.
- Value is the exported values from the module. Only the default value is used if a module exports both the
defaultandnamedvalues.
import { fsImportAll } from '@poppinss/utils/fs'
const configDir = new URL('./config', import.meta.url)
const collection = await fsImportAll(configDir)
console.log(collection)
// title: Directory structure
├── js
│ └── config.cjs
├── json
│ └── main.json
└── ts
├── app.ts
└── server.ts
// title: Output
{
ts: {
app: {},
server: {},
},
js: {
config: {},
},
json: {
main: {},
},
}
OPTIONS
<dl> <dt>ignoreMissingRoot</dt> <dd>By default, an exception is raised when the root directory is missing. Setting <code>ignoreMissingRoot</code> to true will not result in an error, and an empty object will be returned.</dd> <dt>filter</dt> <dd>Define a filter to ignore certain paths. By default only files ending with <code>.js</code>, <code>.ts</code>, <code>.json</code>, <code>.cjs</code>, and <code>.mjs</code> are imported.</dd> <dt>sort</dt> <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd> <dt>transformKeys</dt> <dd>Define a callback method to transform the keys for the final object. The method receives an array of nested keys and must return an array back.</dd> </dl>Assertion helpers
The following assertion methods offer a type-safe approach for writing conditionals and throwing errors when the variable has unexpected values.
assertExists
Throws AssertionError when the value is false, null, or undefined.
import { assertExists } from '@poppinss/utils/assert'
const value = false as string | false
assertExists(value)
// value is a string
assertNotNull
Throws AssertionError when the value is null.
import { assertNotNull } from '@poppinss/utils/assert'
const value = null as string | null
assertNotNull(value)
// value is a string
assertIsDefined
Throws AssertionError when the value is undefined.
import { assertIsDefined } from '@poppinss/utils/assert'
const value = undefined as string | undefined
assertIsDefined(value)
// value is a string
assertUnreachable
Throws AssertionError when the method is invoked. In other words, this method always throws an exception.
import { assertUnreachable } from '@poppinss/utils/assert'
assertUnreachable()
Base64 encoding
encode
Base64 encodes a string or a Buffer value.
import base64 from '@poppinss/utils/base64'
base64.encode('hello world')
// aGVsbG8gd29ybGQ=
urlEncode
The urlEncode method returns a base64 string safe for use inside a URL. The following characters are replaced.
- The
+character is replaced with-. - The
/character is replaced with_. - Trailing
=
