Utils
Collection of reusable scripts used by AdonisJS core team
This module exports a collection of re-usable utilties to avoid re-writing the same code in every other package. We also include a handful of Lodash utilities, which are used across the AdonisJS packages eco-system.
Table of contents
Installation
Install the package from npm registry as follows:
npm i @poppinss/utils
yarn add @poppinss/utils
and then use it as follows:
import { requireAll } from '@poppinss/utils'
requireAll(__dirname)
Exception
A custom exception class that extends the Error
class to add support for defining status
and error codes
.
import { Exception } from '@poppinss/utils'
throw new Error('Something went wrong', 500, 'E_RUNTIME_EXCEPTION')
throw new Error('Route not found', 404, 'E_ROUTE_NOT_FOUND')
fsReadAll
A utility to recursively read all script files for a given directory. This method is equivalent to
readdir + recursive + filter (.js, .json, .ts)
.
import { fsReadAll } from '@poppinss/utils'
const files = fsReadAll(__dirname)
You can also define your custom filter function. The filter function must return true
for files to be included.
const files = fsReadAll(__dirname, (file) => {
return file.endsWith('.foo.js')
})
requireAll
Same as fsReadAll
, but instead require the files. Helpful when you want to load all the config files inside a directory on app boot.
import { requireAll } from '@poppinss/utils'
const config = requireAll(join(__dirname, 'config'))
{
file1: {},
file2: {}
}
esmRequire
Utility to require script files wihtout worrying about CommonJs
and ESM
exports. This is how it works.
- Returns the exported value for
module.exports
. - Returns the default value is an ESM module has
export default
. - Returns all exports if is an ESM module and doesn't have
export default
.
foo.js
module.exports = {
greeting: 'Hello world'
}
foo.default.js
export default {
greeting: 'Hello world'
}
foo.esm.js
export const greeting = {
greeting: 'hello world'
}
import { esmRequire } from '@poppinss/utils'
esmRequire('./foo.js')
esmRequire('./foo.default.js')
esmRequire('./foo.esm.js')
esmResolver
The esmResolver
method works similar to esmRequire
. However, instead of requiring the file, it accepts the object and returns the exported as per the same logic defined above.
import { esmRequire } from '@poppinss/utils'
esmResolver({ greeting: 'hello world' })
esmResolver({
default: { greeting: 'hello world' },
__esModule: true,
})
esmResolver({
greeting: { greeting: 'hello world' },
__esModule: true,
})
resolveFrom
Works similar to require.resolve
, however it handles the absolute paths properly.
import { resolveFrom } from '@poppinss/utils'
resolveFrom(__dirname, 'npm-package')
resolveFrom(__dirname, './foo.js')
resolveFrom(__dirname, join(__dirname, './foo.js'))
interpolate
A small utility function to interpolate values inside a string.
import { interpolate } from '@poppinss/utils'
interpolate('hello {username}', { username: 'virk' })
interpolate('hello {users.0.username}', { users: [{ username: 'virk' }] })
If value is missing, it will be replaced with an undefined
string.
Lodash utilities
Lodash itself is a bulky library and most of the times, we don't need all the functions from it. For this purpose, the lodash team decided to publish individual methods to npm as packages. However, most of those individual packages are outdated.
At this point, whether we should use the complete lodash build or use outdated individual packages. Both are not acceptable.
Instead, we make use of lodash-cli
to create a custom build of all the utilities we ever need inside the AdonisJS eco-system and export it as part of this package. Why part of this package?
Well, creating custom builds in multiple packages will cause friction, so it's better to keep it at a single place.
Do note: There are no Typescript types for the lodash methods, since their CLI doesn't generate one and the one published on @types/lodash
package are again maintained by community and not the lodash core team, so at times, they can also be outdated.
import { lodash } from '@poppinss/utils'
lodash.snakeCase('HelloWorld')
Exported methods
Following is the list of exported helpers.
Base 64 Encode/Decode
Following helpers for base64 encoding/decoding also exists.
encode
import { base64 } from '@poppinss/utils'
base64.encode('hello world')
base64.encode(Buffer.from('hello world', 'binary'))
decode
import { base64 } from '@poppinss/utils'
base64.decode(base64.encode('hello world'))
base64.decode(base64.encode(Buffer.from('hello world', 'binary')), 'binary')
urlEncode
Same as encode
, but safe for URLS and Filenames
urlDecode
Same as decode
, but decodes the urlEncode
output values
Random String
A helper to generate random strings of a given length. Uses crypto
under the hood.
import { randomString } from '@poppinss/utils'
randomString(32)
randomString(128)
Safe equal
Compares two values by avoid timing attack. Accepts any input that can be passed to Buffer.from
import { safeValue } from '@poppinss/utils'
if (safeValue('foo', 'foo')) {
}
Safe stringify
Similar to JSON.stringify
, but also handles Circular references by removing them.
import { safeStringify } from '@poppinss/utils'
const o = { b: 1, a: 0 }
o.o = o
console.log(safeStringify(o))
console.log(JSON.stringify(o))
Safe parse
Similar to JSON.parse
, but protects against Prototype Poisoning
import { safeParse } from '@poppinss/utils'
const input = '{ "user": { "__proto__": { "isAdmin": true } } }'
JSON.parse(input)
safeParse(input)
Message Builder
Message builder provides a sane API for stringifying objects similar to JSON.stringify
but has a few advantages.
- It is safe from JSON poisoning vulnerability.
- You can define expiry and purpose for the encoding. The
verify
method will respect these values.
The message builder alone may seem useless, since anyone can decode the object and change its expiry or purpose. However, you can generate an hash of the stringified object and verify for tampering by validating the hash. This is what AdonisJS does for cookies.
import { MessageBuilder } from '@poppinss/utils'
const builder = new MessageBuilder()
const encoded = builder.build(
{ username: 'virk' },
'1 hour',
'login',
)
Now verify it
builder.verify(encoded)
builder.verify(encoded, 'register')
builder.verify(encoded, 'login')