{"id":39873816,"url":"https://github.com/poppinss/utils","last_synced_at":"2026-01-18T14:21:05.542Z","repository":{"id":34896200,"uuid":"186765108","full_name":"poppinss/utils","owner":"poppinss","description":"A toolkit of utilities used across all the AdonisJS, Edge, and Japa packages","archived":false,"fork":false,"pushed_at":"2026-01-03T23:08:15.000Z","size":1558,"stargazers_count":76,"open_issues_count":1,"forks_count":15,"subscribers_count":4,"default_branch":"6.x","last_synced_at":"2026-01-04T17:54:06.031Z","etag":null,"topics":["esm","exception","readdir","require-all","utils"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/poppinss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"thetutlage"}},"created_at":"2019-05-15T06:42:12.000Z","updated_at":"2025-08-19T02:42:48.000Z","dependencies_parsed_at":"2024-01-15T20:04:21.906Z","dependency_job_id":"67f322b2-1740-41a0-9f71-f5d21b28b113","html_url":"https://github.com/poppinss/utils","commit_stats":{"total_commits":197,"total_committers":6,"mean_commits":"32.833333333333336","dds":"0.060913705583756306","last_synced_commit":"9a1280ac62b2f0091e529275031dd092367b78d9"},"previous_names":[],"tags_count":101,"template":false,"template_full_name":null,"purl":"pkg:github/poppinss/utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poppinss%2Futils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poppinss%2Futils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poppinss%2Futils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poppinss%2Futils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/poppinss","download_url":"https://codeload.github.com/poppinss/utils/tar.gz/refs/heads/6.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poppinss%2Futils/sbom","scorecard":{"id":741227,"data":{"date":"2025-08-11","repo":{"name":"github.com/poppinss/utils","commit":"2cc797b2a066e5e131804b13ba106fde0bc6dff5"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/checks.yml:1","Warn: topLevel 'contents' permission set to 'write': .github/workflows/release.yml:4","Warn: no topLevel permission defined: .github/workflows/stale.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/checks.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/checks.yml/6.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/checks.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/checks.yml/6.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/checks.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/checks.yml/6.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/labels.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/labels.yml/6.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/labels.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/labels.yml/6.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/release.yml/6.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/release.yml/6.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/stale.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/poppinss/utils/stale.yml/6.x?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/release.yml:28","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 third-party GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/poppinss/.github/docs/SECURITY.md:1","Info: Found linked content: github.com/poppinss/.github/docs/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/poppinss/.github/docs/SECURITY.md:1","Info: Found text in security policy: github.com/poppinss/.github/docs/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch '6.x'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-22T17:31:34.742Z","repository_id":34896200,"created_at":"2025-08-22T17:31:34.742Z","updated_at":"2025-08-22T17:31:34.742Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28537525,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T13:04:05.990Z","status":"ssl_error","status_checked_at":"2026-01-18T13:01:44.092Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esm","exception","readdir","require-all","utils"],"created_at":"2026-01-18T14:21:04.459Z","updated_at":"2026-01-18T14:21:05.530Z","avatar_url":"https://github.com/poppinss.png","language":"TypeScript","funding_links":["https://github.com/sponsors/thetutlage"],"categories":[],"sub_categories":[],"readme":"# @poppinss/utils\n\n\u003e A toolkit of utilities used across all the AdonisJS, Edge, and Japa packages\n\n[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]\n\n## Why this package exists?\n\nMany of 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.\n\n- It takes a lot of time to find a perfect package for the use case. The package should be well maintained, have good test coverage, and not accumulate debt by supporting some old versions of Node.js.\n- Some packages are great, but they end up pulling a lot of unnecessary dependencies like [(requiring TypeScript as a prod dependency)](https://github.com/blakeembrey/change-case/issues/281)\n- Sometimes I end up using 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`.\n- 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.\n- Rest are some handwritten utilities to fit my needs\n\n\u003e **Note**: If you are creating an AdonisJS package, I highly recommend using this package since it is already part of the user's project dependencies.\n\n\u003e **Warning**: This package is not for general use (outside the AdonisJS ecosystem). I will not add new helpers or remove any to cater to a broader audience.\n\n## Other packages to use\n\nA note to self and others to consider the following packages.\n\n| Package                                                            | Description                                                                    |\n| ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |\n| [he](https://www.npmjs.com/package/he)                             | For escaping HTML entities and encoding unicode symbols. Has zero dependencies |\n| [@sindresorhus/is](https://www.npmjs.com/package/@sindresorhus/is) | For advanced type checking. Has zero dependencies                              |\n\n## Package size\n\nEven though I do not care much about the package size (most of work is consumed on server side), I am mindful around the utilities and ensure not end up using really big packages for smaller use-cases.\n\nHere's the last checked install size of this package.\n\n\u003ca href=\"https://pkg-size.dev/@poppinss/utils@next\"\u003e\n  \u003cimg src=\"https://pkg-size.dev/badge/install/319382\" title=\"Install size for @poppinss/utils\"\u003e\n\u003c/a\u003e\n\n## Installation\n\nInstall the package from the npm registry as follows:\n\n```sh\nnpm i @poppinss/utils\n\n# Yarn lovers\nyarn add @poppinss/utils\n```\n\n## Exported modules\n\nFollowing are the exported modules. Only the generic helpers are shipped from the main path. The rest of the helpers are grouped inside sub-modules.\n\n```ts\n// string sub-module\nimport string from '@poppinss/utils/string'\n\n// string builder\nimport string from '@poppinss/utils/string_builder'\n\n// json sub-module\nimport json from '@poppinss/utils/json'\n\n// lodash sub-module\nimport lodash from '@poppinss/utils/lodash'\n\n// assert sub-module\nimport assert from '@poppinss/utils/assert'\n\n// main module\nimport { base64, fsReadAll } from '@poppinss/utils'\n\n// types sub-module\nimport { ReadAllFilesOptions } from '@poppinss/utils/types'\n```\n\n### String helpers\n\nA collection of helpers to perform operations on/related to a string value.\n\n```ts\nimport string from '@poppinss/utils/string'\n```\n\n#### excerpt\n\nGenerate an excerpt from a string value. If the input value contains HTML tags, we will remove them from the excerpt.\n\n```ts\nconst html = `\u003cp\u003eAdonisJS is a Node.js framework, and hence it requires Node.js to be installed on your computer. To be precise, we need at least the latest release of \u003ccode\u003eNode.js v14\u003c/code\u003e.\u003c/p\u003e`\n\nconsole.log(string.excerpt(html, 70))\n// AdonisJS is a Node.js framework, and hence it requires Node.js to be i...\n```\n\n| Argument                | Type    | Description                                                                                                                      |\n| ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |\n| `sentence`              | string  | The value for which to generate excerpt                                                                                          |\n| `charactersLimit`       | string  | The number of characters to keep                                                                                                 |\n| `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit |\n| `options.suffix`        | string  | The value to append after the truncated string. Defaults to three dots `...`                                                     |\n\n#### truncate\n\nTruncate a string value to a certain length. The method is the same as the `excerpt` method but does not remove any HTML tags. It is a great fit when you are truncating a non-HTML string.\n\n```ts\nconst text = `AdonisJS is a Node.js framework, and hence it requires Node.js to be installed on your computer. To be precise, we need at least the latest release of Node.js 14.`\n\nconsole.log(string.truncate(text, 70))\n// AdonisJS is a Node.js framework, and hence it requires Node.js to be i...\n```\n\n| Argument                | Type    | Description                                                                                                                      |\n| ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |\n| `sentence`              | string  | The value to truncate                                                                                                            |\n| `charactersLimit`       | string  | The number of characters to keep                                                                                                 |\n| `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit |\n| `options.suffix`        | string  | The value to append after the truncated string. Defaults to three dots `...`                                                     |\n\n#### slug\n\nGenerate slug for a string value. The method is exported directly from the [slugify](https://www.npmjs.com/package/slugify) package.\n\nPlease check the package documentation for [available options](https://www.npmjs.com/package/slugify#options).\n\n```ts\nconsole.log(string.slug('hello ♥ world'))\n// hello-love-world\n```\n\nYou can add custom replacements for Unicode values as follows.\n\n```ts\nstring.slug.extend({ '☢': 'radioactive' })\n\nconsole.log(string.slug('unicode ♥ is ☢'))\n// unicode-love-is-radioactive\n```\n\n#### interpolate\n\nInterpolate variables inside a string. The variables must be inside double curly braces.\n\n```ts\nstring.interpolate('hello {{ user.username }}', { user: { username: 'virk' } })\n\n// hello virk\n```\n\nYou can also replace array values by mentioning the array index.\n\n```ts\nstring.interpolate('hello {{ users.0 }}', { users: ['virk'] })\n\n// hello virk\n```\n\nYou can escape the curly braces by prefixing them with `\\\\`.\n\n```ts\nstring.interpolate('hello \\\\{{ users.0 }}', {})\n\n// hello {{ users.0 }}\n```\n\n#### plural\n\nConvert a word to its plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.\n\n```ts\nstring.plural('test')\n// tests\n```\n\n#### singular\n\nConvert a word to its singular form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.\n\n```ts\nstring.singular('tests')\n// test\n```\n\n#### pluralize\n\nThis method combines the `singular` and `plural` methods and uses one or the other based on the count. For example:\n\n```ts\nstring.pluralize('box', 1) // box\nstring.pluralize('box', 2) // boxes\nstring.pluralize('box', 0) // boxes\n\nstring.pluralize('boxes', 1) // box\nstring.pluralize('boxes', 2) // boxes\nstring.pluralize('boxes', 0) // boxes\n```\n\nThe `addPluralRule`, `addSingularRule`, `addIrregularRule`, and `addUncountableRule` methods exposed by the pluralize package can be called as follows.\n\n```ts\nstring.pluralize.addUncountableRule('paper')\nstring.pluralize.addSingularRule(/singles$/i, 'singular')\n```\n\n#### isPlural\n\nFind if a word is already in plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.\n\n```ts\nstring.isPlural('tests') // true\n```\n\n#### isSingular\n\nFind if a word is already in a singular form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.\n\n```ts\nstring.isSingular('test') // true\n```\n\n#### camelCase\n\nConvert a string value to camelcase.\n\n```ts\nstring.camelCase('user_name') // userName\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output        |\n| ---------------- | ------------- |\n| 'test'           | 'test'        |\n| 'test string'    | 'testString'  |\n| 'Test String'    | 'testString'  |\n| 'TestV2'         | 'testV2'      |\n| '_foo_bar_'      | 'fooBar'      |\n| 'version 1.2.10' | 'version1210' |\n| 'version 1.21.0' | 'version1210' |\n\n#### capitalCase\n\nConvert a string value to a capital case.\n\n```ts\nstring.capitalCase('helloWorld') // Hello World\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output           |\n| ---------------- | ---------------- |\n| 'test'           | 'Test'           |\n| 'test string'    | 'Test String'    |\n| 'Test String'    | 'Test String'    |\n| 'TestV2'         | 'Test V 2'       |\n| 'version 1.2.10' | 'Version 1.2.10' |\n| 'version 1.21.0' | 'Version 1.21.0' |\n\n#### dashCase\n\nConvert a string value to a dash case.\n\n```ts\nstring.dashCase('helloWorld') // hello-world\n```\n\nOptionally, you can capitalize the first letter of each word.\n\n```ts\nstring.dashCase('helloWorld', { capitalize: true }) // Hello-World\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output         |\n| ---------------- | -------------- |\n| 'test'           | 'test'         |\n| 'test string'    | 'test-string'  |\n| 'Test String'    | 'test-string'  |\n| 'Test V2'        | 'test-v2'      |\n| 'TestV2'         | 'test-v-2'     |\n| 'version 1.2.10' | 'version-1210' |\n| 'version 1.21.0' | 'version-1210' |\n\n#### dotCase\n\nConvert a string value to a dot case.\n\n```ts\nstring.dotCase('helloWorld') // hello.World\n```\n\nOptionally, you can also convert the first letter of all the words to lowercase.\n\n```ts\nstring.dotCase('helloWorld', { lowerCase: true }) // hello.world\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output         |\n| ---------------- | -------------- |\n| 'test'           | 'test'         |\n| 'test string'    | 'test.string'  |\n| 'Test String'    | 'Test.String'  |\n| 'dot.case'       | 'dot.case'     |\n| 'path/case'      | 'path.case'    |\n| 'TestV2'         | 'Test.V.2'     |\n| 'version 1.2.10' | 'version.1210' |\n| 'version 1.21.0' | 'version.1210' |\n\n#### noCase\n\nRemove all sorts of casing from a string value.\n\n```ts\nstring.noCase('helloWorld') // hello world\n```\n\nFollowing are some of the conversion examples.\n\n| Input                  | Output                 |\n| ---------------------- | ---------------------- |\n| 'test'                 | 'test'                 |\n| 'TEST'                 | 'test'                 |\n| 'testString'           | 'test string'          |\n| 'testString123'        | 'test string123'       |\n| 'testString_1_2_3'     | 'test string 1 2 3'    |\n| 'ID123String'          | 'id123 string'         |\n| 'foo bar123'           | 'foo bar123'           |\n| 'a1bStar'              | 'a1b star'             |\n| 'CONSTANT_CASE '       | 'constant case'        |\n| 'CONST123_FOO'         | 'const123 foo'         |\n| 'FOO_bar'              | 'foo bar'              |\n| 'XMLHttpRequest'       | 'xml http request'     |\n| 'IQueryAArgs'          | 'i query a args'       |\n| 'dot.case'             | 'dot case'             |\n| 'path/case'            | 'path case'            |\n| 'snake_case'           | 'snake case'           |\n| 'snake_case123'        | 'snake case123'        |\n| 'snake_case_123'       | 'snake case 123'       |\n| '\"quotes\"'             | 'quotes'               |\n| 'version 0.45.0'       | 'version 0 45 0'       |\n| 'version 0..78..9'     | 'version 0 78 9'       |\n| 'version 4_99/4'       | 'version 4 99 4'       |\n| ' test '               | 'test'                 |\n| 'something_2014_other' | 'something 2014 other' |\n| 'amazon s3 data'       | 'amazon s3 data'       |\n| 'foo_13_bar'           | 'foo 13 bar'           |\n\n#### pascalCase\n\nConvert a string value to pascal case. Great for generating JavaScript class names.\n\n```ts\nstring.pascalCase('user team') // UserTeam\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output        |\n| ---------------- | ------------- |\n| 'test'           | 'Test'        |\n| 'test string'    | 'TestString'  |\n| 'Test String'    | 'TestString'  |\n| 'TestV2'         | 'TestV2'      |\n| 'version 1.2.10' | 'Version1210' |\n| 'version 1.21.0' | 'Version1210' |\n\n#### sentenceCase\n\nConvert a value to a sentence.\n\n```ts\nstring.sentenceCase('getting-started-with-adonisjs')\n// Getting started with adonisjs\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output           |\n| ---------------- | ---------------- |\n| 'test'           | 'Test'           |\n| 'test string'    | 'Test string'    |\n| 'Test String'    | 'Test string'    |\n| 'TestV2'         | 'Test v2'        |\n| 'version 1.2.10' | 'Version 1 2 10' |\n| 'version 1.21.0' | 'Version 1 21 0' |\n\n#### snakeCase\n\nConvert value to snake case.\n\n```ts\nstring.snakeCase('user team') // user_team\n```\n\nFollowing are some of the conversion examples.\n\n| Input            | Output         |\n| ---------------- | -------------- |\n| '\\_id'           | 'id'           |\n| 'test'           | 'test'         |\n| 'test string'    | 'test_string'  |\n| 'Test String'    | 'test_string'  |\n| 'Test V2'        | 'test_v2'      |\n| 'TestV2'         | 'test_v_2'     |\n| 'version 1.2.10' | 'version_1210' |\n| 'version 1.21.0' | 'version_1210' |\n\n#### titleCase\n\nConvert a string value to title case.\n\n```ts\nstring.titleCase('small word ends on')\n// Small Word Ends On\n```\n\nFollowing are some of the conversion examples.\n\n| Input                              | Output                             |\n| ---------------------------------- | ---------------------------------- |\n| 'one. two.'                        | 'One. Two.'                        |\n| 'a small word starts'              | 'A Small Word Starts'              |\n| 'small word ends on'               | 'Small Word Ends On'               |\n| 'we keep NASA capitalized'         | 'We Keep NASA Capitalized'         |\n| 'pass camelCase through'           | 'Pass camelCase Through'           |\n| 'follow step-by-step instructions' | 'Follow Step-by-Step Instructions' |\n| 'this vs. that'                    | 'This vs. That'                    |\n| 'this vs that'                     | 'This vs That'                     |\n| 'newcastle upon tyne'              | 'Newcastle upon Tyne'              |\n| 'newcastle \\*upon\\* tyne'          | 'Newcastle \\*upon\\* Tyne'          |\n\n#### random\n\nGenerate a cryptographically secure random string of a given length. The output value is URL safe base64 encoded string.\n\n```ts\nstring.random(32)\n// 8mejfWWbXbry8Rh7u8MW3o-6dxd80Thk\n```\n\n#### toSentence\n\nConvert an array of words to a comma-separated sentence.\n\n```ts\nstring.toSentence(['routes', 'controllers', 'middleware'])\n// routes, controllers, and middleware\n```\n\nYou can replace the `and` with an `or` by specifying the `options.lastSeparator` property.\n\n```ts\nstring.toSentence(['routes', 'controllers', 'middleware'], {\n  lastSeparator: ', or ',\n})\n```\n\nIn the following example, the two words are combined using the `and` separator, not the comma (usually advocated in English). However, you can use a custom separator for a pair of words.\n\n```ts\nstring.toSentence(['routes', 'controllers'])\n// routes and controllers\n\nstring.toSentence(['routes', 'controllers'], {\n  pairSeparator: ', and ',\n})\n// routes, and controllers\n```\n\n#### condenseWhitespace\n\nRemove multiple whitespaces from a string to a single whitespace.\n\n```ts\nstring.condenseWhitespace('hello  world')\n// hello world\n\nstring.condenseWhitespace('  hello  world  ')\n// hello world\n```\n\n#### ordinal\n\nGet the ordinal letter for a given number.\n\n```ts\nstring.ordinal(1) // 1st\nstring.ordinal(2) // '2nd'\nstring.ordinal(3) // '3rd'\nstring.ordinal(4) // '4th'\n\nstring.ordinal(23) // '23rd'\nstring.ordinal(24) // '24th'\n```\n\n#### seconds.(parse/format)\n\nParse a string-based time expression to seconds.\n\n```ts\nstring.seconds.parse('10h') // 36000\nstring.seconds.parse('1 day') // 86400\n```\n\nPassing a numeric value to the `parse` method is returned as it is, assuming the value is already in seconds.\n\n```ts\nstring.seconds.parse(180) // 180\n```\n\nYou can format seconds to a pretty string using the `format` method.\n\n```ts\nstring.seconds.format(36000) // 10h\nstring.seconds.format(36000, true) // 10 hours\n```\n\n#### milliseconds.(parse/format)\n\nParse a string-based time expression to milliseconds.\n\n```ts\nstring.milliseconds.parse('1 h') // 3.6e6\nstring.milliseconds.parse('1 day') // 8.64e7\n```\n\nPassing a numeric value to the `parse` method is returned as it is, assuming the value is already in milliseconds.\n\n```ts\nstring.milliseconds.parse(180) // 180\n```\n\nUsing the `format` method, you can format milliseconds to a pretty string.\n\n```ts\nstring.seconds.format(3.6e6) // 1h\nstring.seconds.format(3.6e6, true) // 1 hour\n```\n\n#### bytes.(parse/format)\n\nParse a string-based unit expression to bytes.\n\n```ts\nstring.bytes.parse('1KB') // 1024\nstring.bytes.parse('1MB') // 1048576\n```\n\nPassing a numeric value to the `parse` method is returned as it is, assuming the value is already in bytes.\n\n```ts\nstring.bytes.parse(1024) // 1024\n```\n\nUsing the `format` method, you can format bytes to a pretty string. The method is exported directly from the [bytes](https://www.npmjs.com/package/bytes) package. Please reference the package README for available options.\n\n```ts\nstring.bytes.format(1048576) // 1MB\nstring.bytes.format(1024 * 1024 * 1000) // 1000MB\nstring.bytes.format(1024 * 1024 * 1000, { thousandsSeparator: ',' }) // 1,000MB\n```\n\n### String builder\n\nThe string builder offers a fluent API for applying a set of transforms on a string value. You can create an instance of the string builder as follows.\n\n```ts\nimport StringBuilder from '@poppinss/utils/string_builder'\nconst builder = new StringBuilder('hello world')\n\nconst value = builder.snakeCase().suffix('_controller').toString()\nassert(value === 'hello_world_controller')\n```\n\n### JSON helpers\n\nFollowing are the helpers we use to `stringify` and `parse` JSON.\n\n#### safeParse\n\nThe native implementation of `JSON.parse` opens up the possibility for [prototype poisoning](https://medium.com/intrinsic-blog/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96). The `safeParse` method removes the `__proto__` and the `constructor.prototype` properties from the JSON string at the time of parsing it.\n\nThe method is a wrapper over [secure-json-parse](https://github.com/fastify/secure-json-parse) package.\n\n#### safeStringify\n\nThe native implementation of `JSON.stringify` cannot handle circular references or language-specific data types like `BigInt`.\n\nTherefore, we use the [safe-stable-stringify](https://github.com/BridgeAR/safe-stable-stringify) package under the hood to overcome the limitations of native implementation.\n\n```ts\nimport { safeStringify } from '@poppinss/utils/json'\n\nconst value = {\n  b: 2,\n  c: BigInt(10),\n}\n\n// Circular reference\nvalue.a = value\n\nsafeStringify(value)\n// '{\"b\":2,\"c\":10}'\n```\n\n- The circular references are removed from the final JSON string.\n- The BigInt values are converted to a string.\n\nThe `safeStringify` API is the same as the `JSON.stringify` method.\n\n- You can pass a replacer function as the second parameter.\n- And number of spaces as the third parameter.\n\n### Lodash helpers\n\nLodash 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 once we need.\n\n\u003e **Why not use something else**: All other helpers I have used are not as accurate or well implemented as lodash.\n\n- pick\n- omit\n- has\n- get\n- set\n- unset\n- mergeWith\n- merge\n- size\n- clone\n- cloneWith\n- cloneDeep\n- cloneDeepWith\n- toPath\n\nYou can use the methods as follows.\n\n```ts\nimport lodash from '@poppinss/utils/lodash'\n\nlodash.pick(collection, keys)\n```\n\n### Assertion helpers\n\nThe following assertion methods offers type-safe approach for writing conditionals and throwing error when the variable has unexpected values.\n\n#### assertExists(message?: string)\n\nThrows [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `false`, `null`, or `undefined`.\n\n```ts\nimport { assertExists } from '@poppinss/utils/assert'\n\nconst value = false as string | false\nassertExists(value)\n\n// value is string\n```\n\n#### assertNotNull(value: unknown, message?: string)\n\nThrows [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `null`.\n\n```ts\nimport { assertNotNull } from '@poppinss/utils/assert'\n\nconst value = null as string | null\nassertNotNull(value)\n\n// value is string\n```\n\n#### assertIsDefined(value: unknown, message?: string)\n\nThrows [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `undefined`.\n\n```ts\nimport { assertIsDefined } from '@poppinss/utils/assert'\n\nconst value = undefined as string | undefined\nassertIsDefined(value)\n\n// value is string\n```\n\n#### assertUnreachable(value: unknown)\n\nThrows [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the method is invoked. In other words, this method always throws an exception.\n\n```ts\nimport { assertUnreachable } from '@poppinss/utils/assert'\nassertUnreachable()\n```\n\n### All other helpers\n\nThe following helpers are exported from the package main module.\n\n```ts\nimport { base64, compose } from '@poppinss/utils'\n```\n\n#### base64\n\nUtility methods to base64 encode and decode values.\n\n```ts\nimport { base64 } from '@poppinss/utils'\n\nbase64.encode('hello world')\n// aGVsbG8gd29ybGQ=\n```\n\nSimilar to the `encode` method, you can use the `urlEncode` to generate a base64 string safe to pass in a URL.\n\nThe `urlEncode` method performs the following replacements.\n\n- Replace `+` with `-`.\n- Replace `/` with `_`.\n- And remove the `=` sign from the end of the string.\n\n```ts\nbase64.urlEncode('hello world')\n// aGVsbG8gd29ybGQ\n```\n\nYou can use the `decode` and the `urlDecode` methods to decode a previously encoded base64 string.\n\n```ts\nbase64.decode(base64.encode('hello world'))\n// hello world\n\nbase64.urlDecode(base64.urlEncode('hello world'))\n// hello world\n```\n\nThe `decode` and the `urlDecode` methods return `null` when the input value is an invalid base64 string. You can turn on the `strict` mode to raise an exception instead.\n\n```ts\nbase64.decode('hello world') // null\nbase64.decode('hello world', 'utf-8', true) // raises exception\n```\n\n#### compose\n\nThe `compose` helper allows you to use TypeScript class mixins with a cleaner API. Following is an example of mixins usage without the compose helper.\n\n```ts\nclass User extends UserWithAttributes(UserWithAge(UserWithPassword(UserWithEmail(BaseModel)))) {}\n```\n\nFollowing is an example with the `compose` helper.\n\n- There is no nesting.\n- The order of mixins is from left to right. Whereas earlier, it was inside out.\n\n```ts\nimport { compose } from '@poppinss/utils'\n\nclass User extends compose(\n  BaseModel,\n  UserWithEmail,\n  UserWithPassword,\n  UserWithAge,\n  UserWithAttributes\n) {}\n```\n\n#### defineStaticProperty\n\nThe `defineStaticProperty` method allows you to define static properties on a class with different reference strategies.\n\nIf you use class inheritance alongside static properties, then either, you will share properties by reference, or you will define them directly on the parent class.\n\nIn the following example, we are not inherting `columns` from the `AppModel`. Instead, we define a new set of columns on the `UserModel`.\n\n```ts\nclass AppModel {\n  static columns = ['id']\n}\n\nclass UserModel extends AppModel {\n  static columns = ['username']\n}\n```\n\nIn the following example, we are inherting `columns` from the `AppModel`. However, the mutations (array.push) from the `UserModel` will reflect on the `AppModel` as well.\n\n```ts\nclass AppModel {\n  static columns = ['id']\n}\n\nclass UserModel extends AppModel {}\nUserModel.columns.push('username')\n```\n\nThe ideal behavior is to deep clone the `columns` array and then push new values to it.\n\n```ts\nimport lodash from '@poppinss/utils/lodash'\n\nclass AppModel {\n  static columns = ['id']\n}\n\nconst inheritedColumns = lodash.cloneDeep(AppModel.columns)\nclass UserModel extends AppModel {\n  static columns = inheritedColumns.push('username')\n}\n```\n\nThe `defineStaticProperty` method abstracts the logic to clone and also performs some interal checks to see if the value is already defined as an `ownProperty` or not.\n\n```ts\nclass UserModel extends AppModel {}\n\ndefineStaticProperty(UserModel, 'columns', {\n  strategy: 'inherit',\n  initialValue: [],\n})\n```\n\n- The `inherit` strategy clones the value from the parent class.\n- The `define` strategy always re-defines the property, discarding any values on the parent class.\n- The `strategy` value can be function to perform a custom clone operations.\n\n#### Exception\n\nA custom exception class with support for defining the error status, error code, and help description. This class aims to standardize exceptions within your projects.\n\n```ts\nimport { Exception } from '@poppinss/utils/exception'\n\nclass ResourceNotFound extends Exception {\n  static code = 'E_RESOURCE_NOT_FOUND'\n  static status = 404\n  static message = 'Unable to find resource'\n}\n\nthrow new ResourceNotFound()\n```\n\n#### Anonymous error classes\n\nYou can also create an anonymous exception class using the `createError` method. The return value is a class\nconstructor that accepts an array of values to use for interpolation.\n\nThe interpolation of error message is performed using the `util.format` message.\n\n```ts\nimport { createError } from '@poppinss/utils/exception'\nconst E_RESOURCE_NOT_FOUND = createError(\n  'Unable to find resource with id %d',\n  'E_RESOURCE_NOT_FOUND'\n)\n\nconst id = 1\nthrow new E_RESOURCE_NOT_FOUND([id])\n```\n\n#### flatten\n\nCreate a flat object from a nested object/array. The nested keys are combined with a dot-notation (`.`). The method is exported from the [flattie](https://www.npmjs.com/package/flattie) package.\n\n```ts\nimport { flatten } from '@poppinss/utils'\n\nflatten({\n  a: 'hi',\n  b: {\n    a: null,\n    b: ['foo', '', null, 'bar'],\n    d: 'hello',\n    e: {\n      a: 'yo',\n      b: undefined,\n      c: 'sup',\n      d: 0,\n      f: [\n        { foo: 123, bar: 123 },\n        { foo: 465, bar: 456 },\n      ],\n    },\n  },\n  c: 'world',\n})\n\n// {\n//   'a': 'hi',\n//   'b.b.0': 'foo',\n//   'b.b.1': '',\n//   'b.b.3': 'bar',\n//   'b.d': 'hello',\n//   'b.e.a': 'yo',\n//   'b.e.c': 'sup',\n//   'b.e.d': 0,\n//   'b.e.f.0.foo': 123,\n//   'b.e.f.0.bar': 123,\n//   'b.e.f.1.foo': 465,\n//   'b.e.f.1.bar': 456,\n//   'c': 'world'\n// }\n```\n\n#### fsReadAll\n\nGet a list of all the files from a directory. The method recursively fetches files from the main and the sub-folders. The dotfiles are ignored implicitly.\n\n```ts\nimport { fsReadAll } from '@poppinss/utils'\n\nconst files = await fsReadAll(new URL('./config', import.meta.url), { pathType: 'url' })\nawait Promise.all(files.map((file) =\u003e import(file)))\n```\n\nYou can also pass the options along with the directory path as the second argument.\n\n```ts\ntype Options = {\n  ignoreMissingRoot?: boolean\n  filter?: (filePath: string, index: number) =\u003e boolean\n  sort?: (current: string, next: string) =\u003e number\n  pathType?: 'relative' | 'unixRelative' | 'absolute' | 'unixAbsolute' | 'url'\n}\n\nconst options: Partial\u003cOptions\u003e = {}\nawait fsReadAll(location, options)\n```\n\n| Argument            | Type    | Description                                                                                                                                                                   |\n| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ignoreMissingRoot` | boolean | By default, an exception is raised when the root directory is missing. Setting `ignoreMissingRoot` to true will not result in an error and an empty array is returned back.   |\n| `filter`            | method  | Define a filter to ignore certain paths. The method is called on the final list of files.                                                                                     |\n| `sort`              | method  | Define a custom method to sort file paths. By default, the files are sorted using natural sort.                                                                               |\n| `pathType`          | enum    | 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 `pathType = 'url'` |\n\n#### fsImportAll\n\nThe `fsImportAll` method imports all the files recursively from a given directory and set the exported value from each module on an object.\n\n```ts\nimport { fsImportAll } from '@poppinss/utils'\n\nconst collection = await fsImportAll(new URL('./config', import.meta.url))\nconsole.log(collection)\n```\n\n- Collection is an object with a tree of key-value pair.\n- The key is the nested object created from the file path.\n- Value is the exported values from the module. If a module exports both the `default` and `named` values, then only the default values are used.\n\nThe second param is the options to customize the import behavior.\n\n| Argument            | Type    | Description                                                                                                                                                                  |\n| ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ignoreMissingRoot` | boolean | By default, an exception is raised when the root directory is missing. Setting `ignoreMissingRoot` to true will not result in an error and an empty object is returned back. |\n| `filter`            | method  | Define a filter to ignore certain paths. By default only files ending with `.js`, `.ts`, `.json`, `.cjs`, and `.mjs` are imported.                                           |\n| `sort`              | method  | Define a custom method to sort file paths. By default, the files are sorted using natural sort.                                                                              |\n| `transformKeys`     | method  | 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.                              |\n\n#### isScriptFile\n\nA filter to know if the file path ends with `.js`, `.json`, `.cjs`, `.mjs` or `.ts`. In the case of `.ts` files, the `.d.ts` returns false.\n\n```ts\nimport { isScriptFile } from '@poppinss/utils'\n\nisScriptFile('foo.js') // true\nisScriptFile('foo/bar.cjs') // true\nisScriptFile('foo/bar.mjs') // true\nisScriptFile('foo.json') // true\n\nisScriptFile('foo/bar.ts') // true\nisScriptFile('foo/bar.d.ts') // false\n```\n\nThe goal of this method is to use it as a filter with the `fsReadAll` method.\n\n```ts\nimport { fsReadAll, isScriptFile } from '@poppinss/utils'\n\nconst dir = new URL('./config', import.meta.url)\nconst options = { pathType: 'url', filter: isScriptFile }\n\nconst files = await fsReadAll(dir, options)\n\nawait Promise.all(\n  files.map((file) =\u003e {\n    if (file.endsWith('.json')) {\n      return import(file, { with: { type: 'json' } })\n    }\n\n    return import(file)\n  })\n)\n```\n\n#### importDefault\n\nA helper function that assert a lazy import function output to have a `default export`, otherwise raises an exception.\n\nWe use dynamic default exports a lot in AdonisJS apps, so extracting the check to a helper function.\n\n```ts\nimport { importDefault } from '@poppinss/utils'\nconst defaultVal = await importDefault(() =\u003e import('./some_module.js'))\n```\n\n#### naturalSort\n\nA sorting function to use natural sort for ordering an array.\n\n```ts\nimport { naturalSort } from '@poppinss/utils'\n\nconst values = ['1_foo_bar', '12_foo_bar'].sort()\n// Default sorting: ['12_foo_bar', '1_foo_bar']\n\nconst values = ['1_foo_bar', '12_foo_bar'].sort(naturalSort)\n// Default sorting: ['1_foo_bar', '12_foo_bar']\n```\n\n#### safeEqual\n\nCheck if two buffer or string values are the same. This method does not leak any timing information and prevents [timing attack](https://javascript.plainenglish.io/what-are-timing-attacks-and-how-to-prevent-them-using-nodejs-158cc7e2d70c).\n\nUnder the hood, this method uses Node.js [crypto.timeSafeEqual](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b) method, with support for comparing string values. _(crypto.timeSafeEqual does not support string comparison)_\n\n```ts\nimport { safeEqual } from '@poppinss/utils'\n\n/**\n * The trusted value, it might be saved inside the db\n */\nconst trustedValue = 'hello world'\n\n/**\n * Untrusted user input\n */\nconst userInput = 'hello'\n\nif (safeEqual(trustedValue, userInput)) {\n  // both are same\n} else {\n  // value mis-match\n}\n```\n\n#### slash\n\nConvert OS-specific file paths to Unix file paths. Credits [https://github.com/sindresorhus/slash](https://github.com/sindresorhus/slash)\n\n```ts\nimport { slash } from '@poppinss/utils/slash'\nslash('foo\\\\bar') // foo/bar\n```\n\n#### MessageBuilder\n\nMessage builder is a convenience layer to stringify JavaScript values with an expiry date and a purpose. For example:\n\n```ts\nimport { MessageBuilder } from '@poppinss/utils'\n\nconst builder = new MessageBuilder()\nconst encoded = builder.build(\n  {\n    token: string.random(32),\n  },\n  '1 hour',\n  'email_verification'\n)\n\n/**\n * {\n *   \"message\": {\n *    \"token\":\"GZhbeG5TvgA-7JCg5y4wOBB1qHIRtX6q\"\n *   },\n *   \"purpose\":\"email_verification\",\n *   \"expiryDate\":\"2022-10-03T04:07:13.860Z\"\n * }\n */\n```\n\nOnce you have the JSON string with the expiry and the purpose, you can encrypt it (to prevent tampering) and share it with the client.\n\nDuring the email verification, you can decrypt the key and then ask the `MessageBuilder` to verify the payload.\n\n```ts\nconst decoded = builder.verify(value, 'email_verification')\nif (!decoded) {\n  return 'Invalid token'\n}\n\nconsole.log(decoded.token)\n```\n\nNow let's imagine someone presents the same token to reset their account password. In the following example, the validation will fail since the purpose of the original token is not the same as the purpose set during the `verify` method call.\n\n```ts\nconst decoded = builder.verify(value, 'reset_password')\n```\n\n#### ObjectBuilder\n\nThe `ObjectBuilder` is a convenience class to create an object with dynamic properties. Consider the following example, where we wrap our code inside conditionals before adding the property `b` to the `startingObject`.\n\n```ts\nconst startingObject = {\n  a: 1\n  // Add \"b\", if it exists\n  ...(b ? { b } : {})\n}\n\n// OR\nif (b) {\n  startingObject.b = b\n}\n```\n\nInstead of writing conditionals, you can consider using the Object builder fluent API.\n\n```ts\nconst builder = new ObjectBuilder({ a: 1 })\n\nconst plainObject = builder.add('b', b).toObject()\n```\n\nBy default, only the `undefined` values are ignored. However, you can also ignore `null` values.\n\n```ts\nconst ignoreNullValues = true\nconst builder = new ObjectBuilder({ a: 1 }, ignoreNullValues)\n```\n\nFollowing are the available methods on the `ObjectBuilder` class.\n\n```ts\nbuilder.remove(key)\nbuilder.has(key)\nbuilder.get(key)\nbuilder.add(key)\n\nbuilder.toObject() // get plain object\n```\n\n#### Secret\n\nCreates a secret value that prevents itself from getting logged inside `console.log` statements, during JSON serialization, and string concatenation.\n\nTo understand why you need a special `Secret` object, you need to understand the root of the problem. Let's start with an example.\n\nGiven that you have a `Token` class that generates an opaque token for a user and persists its hash inside the database. The plain token (aka raw value) is shared with the user and it should only be visible once (for security reasons). Here is a dummy implementation of the same.\n\n```ts\nclass Token {\n  generate() {\n    return {\n      value: 'opaque_raw_token',\n      hash: 'hash_of_raw_token_inside_db',\n    }\n  }\n}\n\nconst token = new Token().generate()\nreturn response.send(token)\n```\n\nAt the same time, you want to drop a log statement inside your application that you can later use to debug the flow of the application, and this is how you log the token.\n\n```ts\nconst token = new Token().generate()\n\nlogger.log('token generated %O', token)\n// token generated {\"value\":\"opaque_raw_token\",\"hash\":\"hash_of_raw_token_inside_db\"}\n\nreturn response.send(token)\n```\n\nBOOM! You have weakened the security of your app. Now, anyone monitoring the logs can grab raw token values from the log and use them to perform the actions on behalf of the user.\n\nNow, to prevent this from happening, you **should work with a branded data type**. Our [old friend PHP has it](https://www.php.net/manual/en/class.sensitiveparametervalue.php), so we need it as well.\n\nThis is what exactly the `Secret` utility class does for you. Create values that prevent themselves from leaking inside logs or during JSON serialization.\n\n```ts\nimport { Secret } from '@poppinss/utils'\n\nclass Token {\n  generate() {\n    return {\n      // THIS LINE 👇\n      value: new Secret('opaque_raw_token'),\n      hash: 'hash_of_raw_token_inside_db',\n    }\n  }\n}\n\nconst token = new Token().generate()\n\nlogger.log('token generated %O', token)\n// AND THIS LOG 👇\n// token generated {\"value\":\"[redacted]\",\"hash\":\"hash_of_raw_token_inside_db\"}\n\nreturn response.send(token)\n```\n\n**Need the original value back?**\nYou can call the `release` method to get the secret value back. Again, the idea is not to prevent your code from accessing the raw value. It's to stop the logging and serialization layer from reading it.\n\n```ts\nconst secret = new Secret('opaque_raw_token')\nconst rawValue = secret.release()\n\nrawValue === opaque_raw_token // true\n```\n\n\u003e Shoutout to [https://transcend.io/blog/keep-sensitive-values-out-of-your-logs-with-types](transcend.io's article) to helping me design the API. In fact, I have ripped their implementation for my personal use.\n\n#### dirname/filename\n\nES modules does not have magic variables `__filename` and `__dirname`. You can use these helpers to get the current directory and filenames as follows.\n\n```ts\nimport { getDirname, getFilename } from '@poppinss/utils'\n\nconst dirname = getDirname(import.meta.url)\nconst filename = getFilename(import.meta.url)\n```\n\n#### joinToURL\n\nSimilar to the Node.js `path.join`, but instead expects the first parameter to be a URL instance or a string with the `file:///` protocol.\n\nThe return value is an absolute file system path without the `file:///` protocol.\n\n```ts\nimport { joinToURL } from '@poppinss/utils'\n\n// With URL as a string\nconst APP_PATH = joinToURL(import.meta.url, 'app')\n\n// With URL instance\nconst APP_PATH = joinToURL(new URL('./', import.meta.url), 'app')\n```\n\n[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/utils/checks.yml?style=for-the-badge\n[gh-workflow-url]: https://github.com/poppinss/utils/actions/workflows/checks.yml 'Github action'\n[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge\u0026logo=typescript\n[typescript-url]: \"typescript\"\n[npm-image]: https://img.shields.io/npm/v/@poppinss/utils.svg?style=for-the-badge\u0026logo=npm\n[npm-url]: https://npmjs.org/package/@poppinss/utils 'npm'\n[license-image]: https://img.shields.io/npm/l/@poppinss/utils?color=blueviolet\u0026style=for-the-badge\n[license-url]: LICENSE.md 'license'\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoppinss%2Futils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpoppinss%2Futils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoppinss%2Futils/lists"}