@poppinss/utils 4.0.4 → 6.0.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +661 -873
  2. package/build/index.d.ts +16 -10
  3. package/build/index.js +21 -53
  4. package/build/lodash/main.cjs +37 -0
  5. package/build/src/{Helpers/base64.d.ts → base64.d.ts} +7 -23
  6. package/build/src/{Helpers/base64.js → base64.js} +9 -22
  7. package/build/src/compose.d.ts +17 -0
  8. package/build/src/compose.js +3 -0
  9. package/build/src/{defineStaticProperty.d.ts → define_static_property.d.ts} +2 -6
  10. package/build/src/define_static_property.js +21 -0
  11. package/build/src/exception.d.ts +16 -0
  12. package/build/src/exception.js +29 -0
  13. package/build/src/flatten.d.ts +1 -1
  14. package/build/src/flatten.js +4 -13
  15. package/build/src/fs_import_all.d.ts +3 -0
  16. package/build/src/fs_import_all.js +28 -0
  17. package/build/src/fs_read_all.d.ts +3 -0
  18. package/build/src/fs_read_all.js +55 -0
  19. package/build/src/is_script_file.d.ts +1 -0
  20. package/build/src/is_script_file.js +12 -0
  21. package/build/src/json/main.d.ts +7 -0
  22. package/build/src/json/main.js +6 -0
  23. package/build/src/json/safe_parse.d.ts +2 -0
  24. package/build/src/json/safe_parse.js +7 -0
  25. package/build/src/json/safe_stringify.d.ts +2 -0
  26. package/build/src/json/safe_stringify.js +18 -0
  27. package/build/src/message_builder.d.ts +5 -0
  28. package/build/src/message_builder.js +43 -0
  29. package/build/src/natural_sort.d.ts +1 -0
  30. package/build/src/natural_sort.js +3 -0
  31. package/build/src/object_builder.d.ts +10 -0
  32. package/build/src/object_builder.js +31 -0
  33. package/build/src/{Helpers/safeEqual.d.ts → safe_equal.d.ts} +1 -4
  34. package/build/src/safe_equal.js +14 -0
  35. package/build/src/slash.d.ts +1 -5
  36. package/build/src/slash.js +1 -24
  37. package/build/src/string/bytes.d.ts +6 -0
  38. package/build/src/string/bytes.js +12 -0
  39. package/build/src/string/change_case.d.ts +13 -0
  40. package/build/src/string/change_case.js +75 -0
  41. package/build/src/string/excerpt.d.ts +4 -0
  42. package/build/src/string/excerpt.js +10 -0
  43. package/build/src/string/interpolate.d.ts +1 -0
  44. package/build/src/{Helpers → string}/interpolate.js +1 -22
  45. package/build/src/string/main.d.ts +46 -0
  46. package/build/src/string/main.js +42 -0
  47. package/build/src/string/milliseconds.d.ts +5 -0
  48. package/build/src/string/milliseconds.js +16 -0
  49. package/build/src/string/ordinal.d.ts +1 -0
  50. package/build/src/string/ordinal.js +21 -0
  51. package/build/src/string/pluralize.d.ts +12 -0
  52. package/build/src/string/pluralize.js +12 -0
  53. package/build/src/string/random.d.ts +1 -0
  54. package/build/src/string/random.js +7 -0
  55. package/build/src/string/seconds.d.ts +5 -0
  56. package/build/src/string/seconds.js +16 -0
  57. package/build/src/string/slugify.d.ts +2 -0
  58. package/build/src/string/slugify.js +2 -0
  59. package/build/src/string/to_sentence.d.ts +5 -0
  60. package/build/src/string/to_sentence.js +13 -0
  61. package/build/src/string/truncate.d.ts +4 -0
  62. package/build/src/string/truncate.js +10 -0
  63. package/build/src/types.d.ts +14 -0
  64. package/build/src/types.js +1 -0
  65. package/lodash/lodash.types.d.ts +44 -0
  66. package/package.json +70 -82
  67. package/build/helpers.d.ts +0 -1
  68. package/build/helpers.js +0 -25
  69. package/build/src/Exception.d.ts +0 -17
  70. package/build/src/Exception.js +0 -68
  71. package/build/src/Helpers/MessageBuilder.d.ts +0 -28
  72. package/build/src/Helpers/MessageBuilder.js +0 -97
  73. package/build/src/Helpers/ObjectBuilder.d.ts +0 -46
  74. package/build/src/Helpers/ObjectBuilder.js +0 -75
  75. package/build/src/Helpers/compose.d.ts +0 -21
  76. package/build/src/Helpers/compose.js +0 -20
  77. package/build/src/Helpers/fsReadAll.d.ts +0 -4
  78. package/build/src/Helpers/fsReadAll.js +0 -23
  79. package/build/src/Helpers/index.d.ts +0 -14
  80. package/build/src/Helpers/index.js +0 -62
  81. package/build/src/Helpers/interpolate.d.ts +0 -7
  82. package/build/src/Helpers/requireAll.d.ts +0 -8
  83. package/build/src/Helpers/requireAll.js +0 -65
  84. package/build/src/Helpers/resolveDir.d.ts +0 -5
  85. package/build/src/Helpers/resolveDir.js +0 -46
  86. package/build/src/Helpers/resolveFrom.d.ts +0 -5
  87. package/build/src/Helpers/resolveFrom.js +0 -27
  88. package/build/src/Helpers/safeEqual.js +0 -42
  89. package/build/src/Helpers/string.d.ts +0 -124
  90. package/build/src/Helpers/string.js +0 -328
  91. package/build/src/Helpers/types.d.ts +0 -86
  92. package/build/src/Helpers/types.js +0 -161
  93. package/build/src/ManagerConfigValidator.d.ts +0 -20
  94. package/build/src/ManagerConfigValidator.js +0 -55
  95. package/build/src/defineStaticProperty.js +0 -57
  96. package/build/src/esmRequire.d.ts +0 -5
  97. package/build/src/esmRequire.js +0 -20
  98. package/build/src/esmResolver.d.ts +0 -1
  99. package/build/src/esmResolver.js +0 -15
  100. package/build/src/fast-safe-stringify.d.ts +0 -2
  101. package/build/src/fast-safe-stringify.js +0 -101
  102. package/build/src/isScriptFile.d.ts +0 -5
  103. package/build/src/isScriptFile.js +0 -28
  104. package/build/src/lodash.d.ts +0 -13
  105. package/build/src/lodash.js +0 -39
  106. package/build/src/safeParse.d.ts +0 -5
  107. package/build/src/safeParse.js +0 -42
  108. package/build/src/safeStringify.d.ts +0 -5
  109. package/build/src/safeStringify.js +0 -39
  110. package/types/lodash.types.d.ts +0 -65
package/README.md CHANGED
@@ -1,1264 +1,1052 @@
1
- <div align="center"><img src="https://res.cloudinary.com/adonisjs/image/upload/q_100/v1557762307/poppinss_iftxlt.jpg" width="600px"></div>
1
+ # @poppinss/utils
2
2
 
3
- # Utils
3
+ > A toolkit of utilities used across all the AdonisJS, Edge, and Japa packages
4
4
 
5
- > Collection of reusable scripts used by AdonisJS core team
6
5
 
7
6
  [![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] [![synk-image]][synk-url]
8
7
 
9
- 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.
10
-
11
- <details>
12
- <summary>
13
- <strong>Version 3.0 breaking changes</strong>
14
- </summary>
15
-
16
- The version 3.0 re-format the exports to expose an "helpers" subpath to be used within the AdonisJS apps as well.
17
-
18
- The idea is to separate helpers that we need to share with AdonisJS core inside its own module, accessible as `‌@poppinss/utils/build/helpers`.
19
-
20
-
21
- ### Inside helpers subpath
22
-
23
- Following modules are now moved to a subpath.
24
-
25
- - `MessageBuilder`
26
- - `base64`
27
- - `compose`
28
- - `fsReadAll`
29
- - `interpolate`
30
- - `requireAll`
31
- - `resolveDir`
32
- - `resolveFrom`
33
-
34
- ```ts
35
- // Earlier
36
- import {
37
- MessageBuilder,
38
- base64,
39
- compose,
40
- fsReadAll,
41
- interpolate,
42
- requireAll,
43
- resolveDir,
44
- resolveFrom,
45
- safeEqual
46
- } from '@poppinss/utils'
47
-
48
- // After version 3.0
49
- import {
50
- MessageBuilder,
51
- base64,
52
- compose,
53
- fsReadAll,
54
- interpolate,
55
- requireAll,
56
- resolveDir,
57
- resolveFrom,
58
- safeEqual
59
- } from '@poppinss/utils/build/helpers'
60
-
61
- ````
62
-
63
- ### randomString
64
-
65
- The `randomString` is now part of the `string` helpers.
66
-
67
- ```ts
68
- // Earlier
69
- import { randomString } from '@poppinss/utils'
70
- randomString(32)
71
-
72
- // After version 3.0
73
- import { string } from '@poppinss/utils/build/helpers'
74
- string.generateRandom(32)
75
- ````
76
-
77
- ### lodash
78
-
79
- The following lodash functions have been removed with new alternatives.
80
-
81
- - `snakeCase`
82
- - `camelCase`
83
- - `startCase`
84
-
85
- ```ts
86
- // Earlier
87
- import { lodash } from '@poppinss/utils'
88
-
89
- lodash.snakeCase()
90
- lodash.camelCase()
91
- lodash.startCase()
92
-
93
- // After version 3.0
94
- import { string } from '@poppinss/utils/build/helpers'
95
-
96
- string.snakeCase()
97
- string.camelCase()
98
- string.titleCase()
99
- ```
100
-
101
- </details>
102
-
103
- <!-- START doctoc generated TOC please keep comment here to allow auto update -->
104
- <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
105
- ## Table of contents
106
-
107
- - [Installation](#installation)
108
- - [Exception](#exception)
109
- - [esmRequire](#esmrequire)
110
- - [esmResolver](#esmresolver)
111
- - [Lodash utilities](#lodash-utilities)
112
- - [Exported methods](#exported-methods)
113
- - [Safe stringify](#safe-stringify)
114
- - [Safe parse](#safe-parse)
115
- - [defineStaticProperty](#definestaticproperty)
116
- - [flatten](#flatten)
117
- - [Helpers](#helpers)
118
- - [fsReadAll](#fsreadall)
119
- - [requireAll](#requireall)
120
- - [resolveFrom](#resolvefrom)
121
- - [resolveDir](#resolvedir)
122
- - [interpolate](#interpolate)
123
- - [Base 64 Encode/Decode](#base-64-encodedecode)
124
- - [encode](#encode)
125
- - [decode](#decode)
126
- - [urlEncode](#urlencode)
127
- - [urlDecode](#urldecode)
128
- - [Safe equal](#safe-equal)
129
- - [Message Builder](#message-builder)
130
- - [compose](#compose)
131
- - [Mixins gotchas](#mixins-gotchas)
132
- - [string](#string)
133
- - [camelCase](#camelcase)
134
- - [snakeCase](#snakecase)
135
- - [dashCase](#dashcase)
136
- - [pascalCase](#pascalcase)
137
- - [capitalCase](#capitalcase)
138
- - [sentenceCase](#sentencecase)
139
- - [dotCase](#dotcase)
140
- - [noCase](#nocase)
141
- - [titleCase](#titlecase)
142
- - [pluralize](#pluralize)
143
- - [truncate](#truncate)
144
- - [excerpt](#excerpt)
145
- - [condenseWhitespace](#condensewhitespace)
146
- - [escapeHTML](#escapehtml)
147
- - [encodeSymbols](#encodesymbols)
148
- - [toSentence](#tosentence)
149
- - [prettyBytes](#prettybytes)
150
- - [toBytes](#tobytes)
151
- - [prettyMs](#prettyms)
152
- - [toMs](#toms)
153
- - [ordinalize](#ordinalize)
154
- - [generateRandom](#generaterandom)
155
- - [isEmpty](#isempty)
156
- - [Types](#types)
157
- - [lookup](#lookup)
158
- - [isNull](#isnull)
159
- - [isBoolean](#isboolean)
160
- - [isBuffer](#isbuffer)
161
- - [isNumber](#isnumber)
162
- - [isString](#isstring)
163
- - [isArguments](#isarguments)
164
- - [isObject](#isobject)
165
- - [isDate](#isdate)
166
- - [isArray](#isarray)
167
- - [isRegexp](#isregexp)
168
- - [isError](#iserror)
169
- - [isFunction](#isfunction)
170
- - [isClass](#isclass)
171
- - [isInteger](#isinteger)
172
- - [isFloat](#isfloat)
173
- - [isDecimal](#isdecimal)
174
- - [ObjectBuilder](#objectbuilder)
175
-
176
- <!-- END doctoc generated TOC please keep comment here to allow auto update -->
8
+ ## Why this package exists?
177
9
 
178
- ## Installation
179
-
180
- Install the package from npm registry as follows:
181
-
182
- ```sh
183
- npm i @poppinss/utils
184
-
185
- # yarn
186
- yarn add @poppinss/utils
187
- ```
188
-
189
- and then use it as follows:
190
-
191
- ```ts
192
- import { requireAll } from '@poppinss/utils'
193
- requireAll(__dirname)
194
- ```
195
-
196
- ## Exception
197
-
198
- A custom exception class that extends the `Error` class to add support for defining `status` and `error codes`.
10
+ Many 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.
199
11
 
200
- ```ts
201
- import { Exception } from '@poppinss/utils'
12
+ - 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.
13
+ - 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)
14
+ - 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`.
15
+ - 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.
16
+ - Rest are some handwritten utilities to fit my needs
202
17
 
203
- throw new Exception('Something went wrong', 500, 'E_RUNTIME_EXCEPTION')
204
- throw new Exception('Route not found', 404, 'E_ROUTE_NOT_FOUND')
205
- ```
18
+ > **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.
206
19
 
207
- ## esmRequire
20
+ > **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.
208
21
 
209
- Utility to require script files wihtout worrying about `CommonJs` and `ESM` exports. This is how it works.
22
+ ## Other packages to use
23
+ A note to self and others to consider the following packages.
210
24
 
211
- - Returns the exported value for `module.exports`.
212
- - Returns the default value is an ESM module has `export default`.
213
- - Returns all exports if is an ESM module and doesn't have `export default`.
214
-
215
- **foo.js**
216
-
217
- ```ts
218
- module.exports = {
219
- greeting: 'Hello world',
220
- }
221
- ```
25
+ | Package | Description |
26
+ |---------|-------------|
27
+ | [he](https://www.npmjs.com/package/he) | For escaping HTML entities and encoding unicode symbols. Has zero dependencies |
28
+ | [@sindresorhus/is](https://www.npmjs.com/package/@sindresorhus/is) | For advanced type checking. Has zero dependencies |
222
29
 
223
- **foo.default.js**
30
+ ## Package size
31
+ Even 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.
224
32
 
225
- ```ts
226
- export default {
227
- greeting: 'Hello world',
228
- }
229
- ```
33
+ | | |
34
+ |------------------|-----------------|
35
+ | Source code size | `272K` (approx) |
36
+ | Dependencies size | `432K` (approx) |
230
37
 
231
- **foo.esm.js**
38
+ ## Installation
232
39
 
233
- ```ts
234
- export const greeting = {
235
- greeting: 'hello world',
236
- }
237
- ```
40
+ Install the package from the npm registry as follows:
238
41
 
239
- ```ts
240
- import { esmRequire } from '@poppinss/utils'
42
+ ```sh
43
+ npm i @poppinss/utils
241
44
 
242
- esmRequire('./foo.js') // { greeting: 'hello world' }
243
- esmRequire('./foo.default.js') // { greeting: 'hello world' }
244
- esmRequire('./foo.esm.js') // { greeting: { greeting: 'hello world' } }
45
+ # Yarn lovers
46
+ yarn add @poppinss/utils
245
47
  ```
246
48
 
247
- ## esmResolver
49
+ ## Exported modules
248
50
 
249
- 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.
51
+ Following are the exported modules. Only the generic helpers are shipped from the main path. The rest of the helpers are grouped inside sub-modules.
250
52
 
251
53
  ```ts
252
- import { esmRequire } from '@poppinss/utils'
54
+ // string sub-module
55
+ import string from '@poppinss/utils/string'
253
56
 
254
- esmResolver({ greeting: 'hello world' }) // { greeting: 'hello world' }
57
+ // json sub-module
58
+ import json from '@poppinss/utils/json'
255
59
 
256
- esmResolver({
257
- default: { greeting: 'hello world' },
258
- __esModule: true,
259
- }) // { greeting: 'hello world' }
60
+ // lodash sub-module
61
+ import lodash from '@poppinss/utils/lodash'
260
62
 
261
- esmResolver({
262
- greeting: { greeting: 'hello world' },
263
- __esModule: true,
264
- }) // { greeting: { greeting: 'hello world' } }
63
+ // main module
64
+ import { base64, Exception, fsReadAll } from '@poppinss/utils'
265
65
  ```
266
66
 
267
- ## Lodash utilities
67
+ ### String helpers
268
68
 
269
- Lodash itself is a bulky library and most of the times, we don't need all the functions from it.
270
-
271
- Also, all of the lodash functions are published as individual modules on npm. However, most of those individual packages are outdated and using them is not an option.
272
-
273
- Instead, we decided to pick some individual utilities that we need inside AdonisJS ecosystem and export them from the lodash package, as each function is exposed in its own separate file.
69
+ A collection of helpers to perform operations on/related to a string value.
274
70
 
275
71
  ```ts
276
- import { lodash } from '@poppinss/utils'
277
- lodash.get({ name: 'virk' }, 'name') // virk
72
+ import string from '@poppinss/utils/string'
278
73
  ```
279
74
 
280
- ### Exported methods
281
-
282
- Following is the list of exported helpers.
283
-
284
- - [pick](https://lodash.com/docs/latest#pick)
285
- - [omit](https://lodash.com/docs/latest#omit)
286
- - [has](https://lodash.com/docs/latest#has)
287
- - [get](https://lodash.com/docs/latest#get)
288
- - [set](https://lodash.com/docs/latest#set)
289
- - [unset](https://lodash.com/docs/latest#unset)
290
- - [mergeWith](https://lodash.com/docs/latest#mergeWith)
291
- - [merge](https://lodash.com/docs/latest#merge)
292
- - [size](https://lodash.com/docs/latest#size)
293
- - [clone](https://lodash.com/docs/latest#clone)
294
- - [cloneDeep](https://lodash.com/docs/latest#cloneDeep)
295
-
296
- ## Safe stringify
75
+ #### excerpt
297
76
 
298
- Similar to `JSON.stringify`, but also handles Circular references by removing them.
77
+ Generate an excerpt from a string value. If the input value contains HTML tags, we will remove them from the excerpt.
299
78
 
300
79
  ```ts
301
- import { safeStringify } from '@poppinss/utils'
80
+ const html = `<p>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 <code>Node.js v14</code>.</p>`
302
81
 
303
- const o = { b: 1, a: 0 }
304
- o.o = o
305
-
306
- console.log(safeStringify(o))
307
- // { "b":1,"a":0 }
308
-
309
- console.log(JSON.stringify(o))
310
- // TypeError: Converting circular structure to JSON
82
+ console.log(string.excerpt(html, 70))
83
+ // AdonisJS is a Node.js framework, and hence it requires Node.js to be i...
311
84
  ```
312
85
 
313
- ## Safe parse
86
+ | Argument | Type | Description |
87
+ |---------|------|---------------|
88
+ | `sentence` | string | The value for which to generate excerpt |
89
+ | `charactersLimit` | string | The number of characters to keep |
90
+ | `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit
91
+ | `options.suffix` | string | The value to append after the truncated string. Defaults to three dots `...` |
314
92
 
315
- Similar to `JSON.parse`, but protects against [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
93
+ #### truncate
94
+ Truncate 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.
316
95
 
317
96
  ```ts
318
- import { safeParse } from '@poppinss/utils'
319
-
320
- const input = '{ "user": { "__proto__": { "isAdmin": true } } }'
321
-
322
- JSON.parse(input)
323
- // { user: { __proto__: { isAdmin: true } } }
97
+ const 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.`
324
98
 
325
- safeParse(input)
326
- // { user: {} }
99
+ console.log(string.truncate(text, 70))
100
+ // AdonisJS is a Node.js framework, and hence it requires Node.js to be i...
327
101
  ```
328
102
 
329
- ## defineStaticProperty
103
+ | Argument | Type | Description |
104
+ |---------|------|---------------|
105
+ | `sentence` | string | The value to truncate |
106
+ | `charactersLimit` | string | The number of characters to keep |
107
+ | `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit
108
+ | `options.suffix` | string | The value to append after the truncated string. Defaults to three dots `...` |
330
109
 
331
- Explicitly define static properties on a class by checking for `hasOwnProperty`. In case of inheritance, the properties from the parent class are cloned vs following the prototypal inheritance.
110
+ #### slug
111
+ Generate slug for a string value. The method is exported directly from the [slugify](https://www.npmjs.com/package/slugify) package.
332
112
 
333
- We use/need this copy from parent class behavior a lot in AdonisJS. Here's an example of Lucid models
334
-
335
- You create an application wide base model
113
+ Please check the package documentation for [available options](https://www.npmjs.com/package/slugify#options).
336
114
 
337
115
  ```ts
338
- class AppModel extends BaseModel {
339
- @column.datetime()
340
- public createdAt: DateTime
341
- }
116
+ console.log(string.slug('hello ♥ world'))
117
+ // hello-love-world
342
118
  ```
343
119
 
344
- AdonisJS will create the `$columnDefinitions` property on the `AppModel` class, that holds all the columns
120
+ You can add custom replacements for Unicode values as follows.
345
121
 
346
122
  ```ts
347
- AppModel.$columnDefinitions // { createdAt: { columName: created_at } }
123
+ string.slug.extend({ '☢': 'radioactive' })
124
+
125
+ console.log(string.slug('unicode ♥ is ☢'))
126
+ // unicode-love-is-radioactive
348
127
  ```
349
128
 
350
- Now, lets create another model inheriting the `AppModel`
129
+ #### interpolate
130
+ Interpolate variables inside a string. The variables must be inside double curly braces.
351
131
 
352
132
  ```ts
353
- class User extends AppModel {
354
- @column()
355
- public id: number
356
- }
357
- ```
133
+ string.interpolate(
134
+ 'hello {{ user.username }}',
135
+ { user: { username: 'virk' } }
136
+ )
358
137
 
359
- As per the Javascript prototypal inheritance. The `User` model will not contain the columns from the `AppModel`, because we just re-defined the `$columnDefinitions` property. However, we don't want this behavior and instead want to copy the columns from the `AppModel` and then add new columns to it.
138
+ // hello virk
139
+ ```
360
140
 
361
- Voila! Use the `defineStaticProperty` helper from this class.
141
+ You can also replace array values by mentioning the array index.
362
142
 
363
143
  ```ts
364
- class LucidBaseModel {
365
- static boot() {
366
- defineStaticProperty(this, LucidBaseModel, {
367
- propertyName: '$columnDefinitions',
368
- defaultValue: {},
369
- strategy: 'inherit',
370
- })
371
- }
372
- }
373
- ```
144
+ string.interpolate(
145
+ 'hello {{ users.0 }}',
146
+ { users: ['virk'] }
147
+ )
374
148
 
375
- The `defineStaticProperty` takes a total of three arguments.
376
-
377
- - The first argument is always `this`.
378
- - The second argument is the root level base class. This will usually be the class exported by your package or module.
379
- - The third argument takes the `propertyName`, `defaultValue (in case, there is nothing to copy)`, and the `strategy`.
380
- - The `inherit` strategy will copy the properties from the base class.
381
- - The `define` strategy will always use the `defaultValue` to define the property on the class. In other words, there is no copy behavior, but prototypal inheritance chain is also breaked by explicitly re-defining the property.
149
+ // hello virk
150
+ ```
382
151
 
383
- ## flatten
384
- Flatten an object/array. The method wraps the [flattie](https://github.com/lukeed/flattie) package.
152
+ You can escape the curly braces by prefixing them with `\\`.
385
153
 
386
154
  ```ts
387
- import { flatten } from '@poppinss/utils'
155
+ string.interpolate('hello \\{{ users.0 }}', {})
388
156
 
389
- flatten({
390
- a: 'hi',
391
- b: {
392
- a: null,
393
- b: ['foo', '', null, 'bar'],
394
- d: 'hello',
395
- e: {
396
- a: 'yo',
397
- b: undefined,
398
- c: 'sup',
399
- d: 0,
400
- f: [
401
- { foo: 123, bar: 123 },
402
- { foo: 465, bar: 456 },
403
- ]
404
- }
405
- },
406
- c: 'world'
407
- });
408
- // {
409
- // 'a': 'hi',
410
- // 'b.b.0': 'foo',
411
- // 'b.b.1': '',
412
- // 'b.b.3': 'bar',
413
- // 'b.d': 'hello',
414
- // 'b.e.a': 'yo',
415
- // 'b.e.c': 'sup',
416
- // 'b.e.d': 0,
417
- // 'b.e.f.0.foo': 123,
418
- // 'b.e.f.0.bar': 123,
419
- // 'b.e.f.1.foo': 465,
420
- // 'b.e.f.1.bar': 456,
421
- // 'c': 'world'
422
- // }
157
+ // hello {{ users.0 }}
423
158
  ```
424
159
 
425
- ## Helpers
426
-
427
- The helpers module is also available in AdonisJS applications as follows:
160
+ #### plural
161
+ Convert a word to its plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
428
162
 
429
163
  ```ts
430
- import { fsReadAll, string, types } from '@ioc:Adonis/Core/Helpers'
164
+ string.plural('test')
165
+ // tests
431
166
  ```
432
167
 
433
- The `@poppinss/utils` exposes this module as follows
168
+ #### singular
169
+ Convert a word to its singular form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
434
170
 
435
171
  ```ts
436
- import { fsReadAll, string, types } from '@poppinss/utils/build/helpers'
172
+ string.singular('tests')
173
+ // test
437
174
  ```
438
175
 
439
- ### fsReadAll
440
-
441
- A utility to recursively read all script files for a given directory. This method is equivalent to
442
- `readdir + recursive + filter (.js, .json, .ts)`.
176
+ #### pluralize
177
+ This method combines the `singular` and `plural` methods and uses one or the other based on the count. For example:
443
178
 
444
179
  ```ts
445
- import { fsReadAll } from '@poppinss/utils/build/helpers'
180
+ string.pluralize('box', 1) // box
181
+ string.pluralize('box', 2) // boxes
182
+ string.pluralize('box', 0) // boxes
446
183
 
447
- const files = fsReadAll(__dirname) // array of strings
184
+ string.pluralize('boxes', 1) // box
185
+ string.pluralize('boxes', 2) // boxes
186
+ string.pluralize('boxes', 0) // boxes
448
187
  ```
449
188
 
450
- You can also define your custom filter function. The filter function must return `true` for files to be included.
189
+ The `addPluralRule`, `addSingularRule`, `addIrregularRule`, and `addUncountableRule` methods exposed by the pluralize package can be called as follows.
451
190
 
452
191
  ```ts
453
- const files = fsReadAll(__dirname, (file) => {
454
- return file.endsWith('.foo.js')
455
- })
192
+ string.pluralize.addUncountableRule('paper')
193
+ string.pluralize.addSingularRule(/singles$/i, 'singular')
456
194
  ```
457
195
 
458
- ### requireAll
459
-
460
- Same as `fsReadAll`, but instead require the files. Helpful when you want to load all the config files inside a directory on app boot.
196
+ #### isPlural
197
+ Find if a word is already in plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
461
198
 
462
199
  ```ts
463
- import { requireAll } from '@poppinss/utils/build/helpers'
464
- const config = requireAll(join(__dirname, 'config'))
465
-
466
- {
467
- file1: {}, // exported object
468
- file2: {} // exported object
469
- }
200
+ string.isPlural('tests') // true
470
201
  ```
471
202
 
472
- The method also accepts the following options
203
+ #### isSingular
204
+ Find if a word is already in a singular form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
473
205
 
474
206
  ```ts
475
- requireAll(join(__dirname, 'config'), recursive, optional, filter)
207
+ string.isSingular('test') // true
476
208
  ```
477
209
 
478
- - `recursive` Load all files recursively. Defaults to true.
479
- - `optional` Do not raise exception when the root directory is missing. Defaults to false.
480
- - `filter` Cherry pick files to require. By default, all JavaScript, TypeScript and JSON files are required.
481
-
482
- ### resolveFrom
483
-
484
- Works similar to `require.resolve`, however it handles the absolute paths properly.
210
+ #### camelCase
211
+ Convert a string value to camelcase.
485
212
 
486
213
  ```ts
487
- import { resolveFrom } from '@poppinss/utils/build/helpers'
488
-
489
- resolveFrom(__dirname, 'npm-package') // returns path to package "main" file
490
- resolveFrom(__dirname, './foo.js') // returns path to `foo.js` (if exists)
491
- resolveFrom(__dirname, join(__dirname, './foo.js')) // returns path to `foo.js` (if exists)
214
+ string.camelCase('user_name') // userName
492
215
  ```
493
216
 
494
- ### resolveDir
495
-
496
- The `require.resolve` or `resolveFrom` method can only resolve paths to a given file and not the directory. For example: If you pass path to a directory, then it will search for `index.js` inside it and in case of a package, it will be search for `main` entry point.
217
+ Following are some of the conversion examples.
497
218
 
498
- On the other hand, the `resolveDir` method can also resolve path to directories using following resolution.
219
+ | Input | Output |
220
+ |------|---------|
221
+ | 'test' | 'test' |
222
+ | 'test string' | 'testString' |
223
+ | 'Test String' | 'testString' |
224
+ | 'TestV2' | 'testV2' |
225
+ | '_foo_bar_' | 'fooBar' |
226
+ | 'version 1.2.10' | 'version1210' |
227
+ | 'version 1.21.0' | 'version1210' |
499
228
 
500
- - Absolute paths are returned as it is.
501
- - Relative paths starting with `./` or `.\` are resolved using `path.join`.
502
- - Path to packages inside `node_modules` are resolved as follows: - Uses `require.resolve` to resolve the `package.json` file. - Then replace the `package-name` with the absolute resolved package path.
229
+ #### capitalCase
230
+ Convert a string value to a capital case.
503
231
 
504
232
  ```ts
505
- import { resolveDir } from '@poppinss/utils/build/helpers'
506
-
507
- resolveDir(__dirname, './database/migrations')
508
- // __dirname + /database/migrations
509
-
510
- resolveDir(__dirname, 'some-package/database/migrations')
511
- // {path-to-package}/database/migrations
512
-
513
- resolveDir(__dirname, '@some/package/database/migrations')
514
- // {path-to-package}/database/migrations
233
+ string.capitalCase('helloWorld') // Hello World
515
234
  ```
516
235
 
517
- ### interpolate
236
+ Following are some of the conversion examples.
518
237
 
519
- A small utility function to interpolate values inside a string.
238
+ | Input | Output |
239
+ |------|---------|
240
+ | 'test' | 'Test' |
241
+ | 'test string' | 'Test String' |
242
+ | 'Test String' | 'Test String' |
243
+ | 'TestV2' | 'Test V 2' |
244
+ | 'version 1.2.10' | 'Version 1.2.10' |
245
+ | 'version 1.21.0' | 'Version 1.21.0' |
520
246
 
521
- ```ts
522
- import { interpolate } from '@poppinss/utils/build/helpers'
523
-
524
- interpolate('hello {{ username }}', {
525
- username: 'virk',
526
- })
247
+ #### dashCase
248
+ Convert a string value to a dash case.
527
249
 
528
- interpolate('hello {{ users.0.username }}', {
529
- users: [{ username: 'virk' }],
530
- })
250
+ ```ts
251
+ string.dashCase('helloWorld') // hello-world
531
252
  ```
532
253
 
533
- If value is missing, it will be replaced with an `'undefined'` string.
534
-
535
- Use the `\` to escape a mustache block from getting evaluated.
254
+ Optionally, you can capitalize the first letter of each word.
536
255
 
537
256
  ```ts
538
- import { interpolate } from '@poppinss/utils/build/helpers'
539
-
540
- interpolate('\\{{ username }} expression evaluates to {{ username }}', {
541
- username: 'virk',
542
- })
543
- // Output: {{ username }} expression evaluates to virk
257
+ string.dashCase('helloWorld', { capitalize: true }) // Hello-World
544
258
  ```
545
259
 
546
- ### Base 64 Encode/Decode
260
+ Following are some of the conversion examples.
547
261
 
548
- Following helpers for base64 encoding/decoding also exists.
262
+ | Input | Output |
263
+ |--------|--------|
264
+ | 'test' | 'test' |
265
+ | 'test string' | 'test-string' |
266
+ | 'Test String' | 'test-string' |
267
+ | 'Test V2' | 'test-v2' |
268
+ | 'TestV2' | 'test-v-2' |
269
+ | 'version 1.2.10' | 'version-1210' |
270
+ | 'version 1.21.0' | 'version-1210' |
549
271
 
550
- #### encode
272
+ #### dotCase
273
+ Convert a string value to a dot case.
551
274
 
552
275
  ```ts
553
- import { base64 } from '@poppinss/utils/build/helpers'
554
-
555
- base64.encode('hello world')
556
- base64.encode(Buffer.from('hello world', 'binary'))
276
+ string.dotCase('helloWorld') // hello.World
557
277
  ```
558
278
 
559
- #### decode
279
+ Optionally, you can also convert the first letter of all the words to lowercase.
560
280
 
561
281
  ```ts
562
- import { base64 } from '@poppinss/utils/build/helpers'
563
-
564
- base64.decode(base64.encode('hello world'))
565
- base64.decode(base64.encode(Buffer.from('hello world', 'binary')), 'binary')
282
+ string.dotCase('helloWorld', { lowerCase: true }) // hello.world
566
283
  ```
567
284
 
568
- #### urlEncode
285
+ Following are some of the conversion examples.
569
286
 
570
- Same as `encode`, but safe for URLS and Filenames
287
+ | Input | Output |
288
+ |--------|--------|
289
+ | 'test' | 'test' |
290
+ | 'test string' | 'test.string' |
291
+ | 'Test String' | 'Test.String' |
292
+ | 'dot.case' | 'dot.case' |
293
+ | 'path/case' | 'path.case' |
294
+ | 'TestV2' | 'Test.V.2' |
295
+ | 'version 1.2.10' | 'version.1210' |
296
+ | 'version 1.21.0' | 'version.1210' |
571
297
 
572
- #### urlDecode
298
+ #### noCase
299
+ Remove all sorts of casing from a string value.
573
300
 
574
- Same as `decode`, but decodes the `urlEncode` output values
301
+ ```ts
302
+ string.noCase('helloWorld') // hello world
303
+ ```
575
304
 
576
- ### Safe equal
305
+ Following are some of the conversion examples.
306
+
307
+ | Input | Output |
308
+ |--------|--------|
309
+ | 'test' | 'test' |
310
+ | 'TEST' | 'test' |
311
+ | 'testString' | 'test string' |
312
+ | 'testString123' | 'test string123' |
313
+ | 'testString_1_2_3' | 'test string 1 2 3' |
314
+ | 'ID123String' | 'id123 string' |
315
+ | 'foo bar123' | 'foo bar123' |
316
+ | 'a1bStar' | 'a1b star' |
317
+ | 'CONSTANT_CASE ' | 'constant case' |
318
+ | 'CONST123_FOO' | 'const123 foo' |
319
+ | 'FOO_bar' | 'foo bar' |
320
+ | 'XMLHttpRequest' | 'xml http request' |
321
+ | 'IQueryAArgs' | 'i query a args' |
322
+ | 'dot.case' | 'dot case' |
323
+ | 'path/case' | 'path case' |
324
+ | 'snake_case' | 'snake case' |
325
+ | 'snake_case123' | 'snake case123' |
326
+ | 'snake_case_123' | 'snake case 123' |
327
+ | '"quotes"' | 'quotes' |
328
+ | 'version 0.45.0' | 'version 0 45 0' |
329
+ | 'version 0..78..9' | 'version 0 78 9' |
330
+ | 'version 4_99/4' | 'version 4 99 4' |
331
+ | ' test ' | 'test' |
332
+ | 'something_2014_other' | 'something 2014 other' |
333
+ | 'amazon s3 data' | 'amazon s3 data' |
334
+ | 'foo_13_bar' | 'foo 13 bar' |
577
335
 
578
- Compares two values by avoid [timing attack](https://en.wikipedia.org/wiki/Timing_attack). Accepts any input that can be passed to `Buffer.from`
336
+ #### pascalCase
337
+ Convert a string value to pascal case. Great for generating JavaScript class names.
579
338
 
580
339
  ```ts
581
- import { safeValue } from '@poppinss/utils/build/helpers'
582
-
583
- if (safeValue('foo', 'foo')) {
584
- }
340
+ string.pascalCase('user team') // UserTeam
585
341
  ```
586
342
 
587
- ### Message Builder
343
+ Following are some of the conversion examples.
588
344
 
589
- Message builder provides a sane API for stringifying objects similar to `JSON.stringify` but has a few advantages.
345
+ | Input | Output |
346
+ |--------|--------|
347
+ | 'test' | 'Test' |
348
+ | 'test string' | 'TestString' |
349
+ | 'Test String' | 'TestString' |
350
+ | 'TestV2' | 'TestV2' |
351
+ | 'version 1.2.10' | 'Version1210' |
352
+ | 'version 1.21.0' | 'Version1210' |
590
353
 
591
- - It is safe from JSON poisoning vulnerability.
592
- - You can define expiry and purpose for the encoding. The `verify` method will respect these values.
593
-
594
- 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 the tampering by validating the hash. This is what AdonisJS does for cookies.
354
+ #### sentenceCase
355
+ Convert a value to a sentence.
595
356
 
596
357
  ```ts
597
- import { MessageBuilder } from '@poppinss/utils/build/helpers'
598
-
599
- const builder = new MessageBuilder()
600
- const encoded = builder.build({ username: 'virk' }, '1 hour', 'login')
358
+ string.sentenceCase('getting-started-with-adonisjs')
359
+ // Getting started with adonisjs
601
360
  ```
602
361
 
603
- Now verify it
362
+ Following are some of the conversion examples.
363
+
364
+ | Input | Output |
365
+ |--------|--------|
366
+ | 'test' | 'Test' |
367
+ | 'test string' | 'Test string' |
368
+ | 'Test String' | 'Test string' |
369
+ | 'TestV2' | 'Test v2' |
370
+ | 'version 1.2.10' | 'Version 1 2 10' |
371
+ | 'version 1.21.0' | 'Version 1 21 0' |
372
+
373
+ #### snakeCase
374
+ Convert value to snake case.
604
375
 
605
376
  ```ts
606
- builder.verify(encoded) // returns null, no purpose defined
607
- builder.verify(encoded, 'register') // returns null, purpose mismatch.
608
- builder.verify(encoded, 'login') // return { username: 'virk' }
377
+ string.snakeCase('user team') // user_team
609
378
  ```
610
379
 
611
- ### compose
380
+ Following are some of the conversion examples.
612
381
 
613
- Javascript doesn't have a concept of inherting multiple classes together and neither does Typescript. However, the [official documentation](https://www.typescriptlang.org/docs/handbook/mixins.html) of Typescript does talks about the concept of mixins.
382
+ | Input | Output |
383
+ |--------|--------|
384
+ | '_id' | 'id' |
385
+ | 'test' | 'test' |
386
+ | 'test string' | 'test_string' |
387
+ | 'Test String' | 'test_string' |
388
+ | 'Test V2' | 'test_v2' |
389
+ | 'TestV2' | 'test_v_2' |
390
+ | 'version 1.2.10' | 'version_1210' |
391
+ | 'version 1.21.0' | 'version_1210' |
614
392
 
615
- As per the Typescript docs, you can create and apply mixins as follows.
393
+ #### titleCase
394
+ Convert a string value to title case.
616
395
 
617
396
  ```ts
618
- type Constructor = new (...args: any[]) => any
619
-
620
- const UserWithEmail = <T extends Constructor>(superclass: T) => {
621
- return class extends superclass {
622
- public email: string
623
- }
624
- }
625
-
626
- const UserWithPassword = <T extends Constructor>(superclass: T) => {
627
- return class extends superclass {
628
- public password: string
629
- }
630
- }
631
-
632
- class BaseModel {}
633
- class User extends UserWithPassword(UserWithEmail(BaseModel)) {}
397
+ string.titleCase('small word ends on')
398
+ // Small Word Ends On
634
399
  ```
635
400
 
636
- Mixins are close to a perfect way of inherting multiple classes. I recommend reading [this article](https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) for same.
401
+ Following are some of the conversion examples.
402
+
403
+ | Input | Output |
404
+ |--------|--------|
405
+ | 'one. two.' | 'One. Two.' |
406
+ | 'a small word starts' | 'A Small Word Starts' |
407
+ | 'small word ends on' | 'Small Word Ends On' |
408
+ | 'we keep NASA capitalized' | 'We Keep NASA Capitalized' |
409
+ | 'pass camelCase through' | 'Pass camelCase Through' |
410
+ | 'follow step-by-step instructions' | 'Follow Step-by-Step Instructions' |
411
+ | 'this vs. that' | 'This vs. That' |
412
+ | 'this vs that' | 'This vs That' |
413
+ | 'newcastle upon tyne' | 'Newcastle upon Tyne' |
414
+ | 'newcastle \*upon\* tyne' | 'Newcastle \*upon\* Tyne' |
637
415
 
638
- However, the syntax of applying multiple mixins is kind of ugly, as you have to apply **mixins over mixins**, creating a nested hierarchy as shown below.
416
+ #### random
417
+ Generate a cryptographically secure random string of a given length. The output value is URL safe base64 encoded string.
639
418
 
640
419
  ```ts
641
- UserWithAttributes(UserWithAge(UserWithPassword(UserWithEmail(BaseModel))))
420
+ string.random(32)
421
+ // 8mejfWWbXbry8Rh7u8MW3o-6dxd80Thk
642
422
  ```
643
423
 
644
- The `compose` method is a small utility to improve the syntax a bit.
424
+ #### toSentence
425
+ Convert an array of words to a comma-separated sentence.
645
426
 
646
427
  ```ts
647
- import { compose } from '@poppinss/utils/build/helpers'
648
-
649
- class User extends compose(
650
- BaseModel,
651
- UserWithPassword,
652
- UserWithEmail,
653
- UserWithAge,
654
- UserWithAttributes
655
- ) {}
428
+ string.toSentence(['routes', 'controllers', 'middleware'])
429
+ // routes, controllers, and middleware
656
430
  ```
657
431
 
658
- #### Mixins gotchas
659
-
660
- Typescript has an [open issue](https://github.com/microsoft/TypeScript/issues/37142) related to the constructor arguments of the mixin class or the base class.
661
-
662
- Typescript expects all classes used in the mixin chain to have a constructor with only one argument of `...args: any[]`. For example: The following code will work fine at runtime, but the **typescript compiler complains about it**.
432
+ You can replace the `and` with an `or` by specifying the `options.lastSeparator` property.
663
433
 
664
434
  ```ts
665
- class BaseModel {
666
- constructor(name: string) {}
667
- }
668
-
669
- const UserWithEmail = <T extends typeof BaseModel>(superclass: T) => {
670
- return class extends superclass {
671
- // ERROR: A mixin class must have a constructor with a single rest parameter of type 'any[]'.ts(2545)
672
- public email: string
673
- }
674
- }
675
-
676
- class User extends compose(BaseModel, UserWithEmail) {}
435
+ string.toSentence(['routes', 'controllers', 'middleware'], {
436
+ lastSeparator: ', or '
437
+ })
677
438
  ```
678
439
 
679
- You can work around this by overriding the constructor of the base class.
440
+ In 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.
680
441
 
681
442
  ```ts
682
- import { NormalizeConstructor, compose } from '@poppinss/utils/build/helpers'
443
+ string.toSentence(['routes', 'controllers'])
444
+ // routes and controllers
683
445
 
684
- const UserWithEmail = <T extends NormalizeConstructor<typeof BaseModel>>(superclass: T) => {
685
- return class extends superclass {
686
- public email: string
687
- }
688
- }
446
+ string.toSentence(['routes', 'controllers'], {
447
+ pairSeparator: ', and '
448
+ })
449
+ // routes, and controllers
689
450
  ```
690
451
 
691
- ### string
692
-
693
- The `string` module includes a bunch of helper methods to work with strings.
694
-
695
- #### camelCase
696
-
697
- Convert a string to its `camelCase` version.
452
+ #### condenseWhitespace
453
+ Remove multiple whitespaces from a string to a single whitespace.
698
454
 
699
455
  ```ts
700
- import { string } from '@poppinss/utils/build/helpers'
456
+ string.condenseWhitespace('hello world')
457
+ // hello world
701
458
 
702
- string.camelCase('hello-world') // helloWorld
459
+ string.condenseWhitespace(' hello world ')
460
+ // hello world
703
461
  ```
704
462
 
705
- #### snakeCase
706
-
707
- Convert a string to its `snake_case` version.
463
+ #### ordinal
464
+ Get the ordinal letter for a given number.
708
465
 
709
466
  ```ts
710
- import { string } from '@poppinss/utils/build/helpers'
467
+ string.ordinal(1) // 1st
468
+ string.ordinal(2) // '2nd'
469
+ string.ordinal(3) // '3rd'
470
+ string.ordinal(4) // '4th'
711
471
 
712
- string.snakeCase('helloWorld') // hello_world
472
+ string.ordinal(23) // '23rd'
473
+ string.ordinal(24) // '24th'
713
474
  ```
714
475
 
715
- #### dashCase
716
-
717
- Convert a string to its `dash-case` version. Optionally, you can also capitalize the first letter of each segment.
476
+ #### seconds.(parse/format)
477
+ Parse a string-based time expression to seconds.
718
478
 
719
479
  ```ts
720
- import { string } from '@poppinss/utils/build/helpers'
721
-
722
- string.dashCase('helloWorld') // hello-world
723
- string.dashCase('helloWorld', { capitalize: true }) // Hello-World
480
+ string.seconds.parse('10h') // 36000
481
+ string.seconds.parse('1 day') // 86400
724
482
  ```
725
483
 
726
- #### pascalCase
727
-
728
- Convert a string to its `PascalCase` version.
484
+ Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in seconds.
729
485
 
730
486
  ```ts
731
- import { string } from '@poppinss/utils/build/helpers'
732
-
733
- string.pascalCase('helloWorld') // HelloWorld
487
+ string.seconds.parse(180) // 180
734
488
  ```
735
489
 
736
- #### capitalCase
737
-
738
- Capitalize a string
490
+ You can format seconds to a pretty string using the `format` method.
739
491
 
740
492
  ```ts
741
- import { string } from '@poppinss/utils/build/helpers'
742
-
743
- string.capitalCase('helloWorld') // Hello World
493
+ string.seconds.format(36000) // 10h
494
+ string.seconds.format(36000, true) // 10 hours
744
495
  ```
745
496
 
746
- #### sentenceCase
747
-
748
- Convert string to a sentence
497
+ #### milliseconds.(parse/format)
498
+ Parse a string-based time expression to milliseconds.
749
499
 
750
500
  ```ts
751
- import { string } from '@poppinss/utils/build/helpers'
752
-
753
- string.sentenceCase('hello-world') // Hello world
501
+ string.milliseconds.parse('1 h') // 3.6e6
502
+ string.milliseconds.parse('1 day') // 8.64e7
754
503
  ```
755
504
 
756
- #### dotCase
757
-
758
- Convert string to its `dot.case` version.
505
+ Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in milliseconds.
759
506
 
760
507
  ```ts
761
- import { string } from '@poppinss/utils/build/helpers'
762
-
763
- string.dotCase('hello-world') // hello.world
508
+ string.milliseconds.parse(180) // 180
764
509
  ```
765
510
 
766
- #### noCase
767
-
768
- Remove all sorts of casing
511
+ Using the `format` method, you can format milliseconds to a pretty string.
769
512
 
770
513
  ```ts
771
- import { string } from '@poppinss/utils/build/helpers'
772
-
773
- string.noCase('hello-world') // hello world
774
- string.noCase('hello_world') // hello world
775
- string.noCase('helloWorld') // hello world
514
+ string.seconds.format(3.6e6) // 1h
515
+ string.seconds.format(3.6e6, true) // 1 hour
776
516
  ```
777
517
 
778
- #### titleCase
779
-
780
- Convert a sentence to title case
518
+ #### bytes.(parse/format)
519
+ Parse a string-based unit expression to bytes.
781
520
 
782
521
  ```ts
783
- import { string } from '@poppinss/utils/build/helpers'
784
-
785
- string.titleCase('Here is a fox') // Here Is a fox
522
+ string.bytes.parse('1KB') // 1024
523
+ string.bytes.parse('1MB') // 1048576
786
524
  ```
787
525
 
788
- #### pluralize
789
-
790
- Pluralize a word.
526
+ Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in bytes.
791
527
 
792
528
  ```ts
793
- import { string } from '@poppinss/utils/build/helpers'
794
-
795
- string.pluralize('box') // boxes
796
- string.pluralize('i') // we
529
+ string.bytes.parse(1024) // 1024
797
530
  ```
798
531
 
799
- You can also define your own irregular rules using the `string.defineIrregularRule` method.
800
-
801
- - The first argument is the singular variation
802
- - The second argument is the plural variation
532
+ Using 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.
803
533
 
804
534
  ```ts
805
- import { string } from '@poppinss/utils/build/helpers'
806
-
807
- string.defineIrregularRule('auth', 'auth')
808
- string.plural('auth') // auth
535
+ string.bytes.format(1048576) // 1MB
536
+ string.bytes.format(1024 * 1024 * 1000) // 1000MB
537
+ string.bytes.format(1024 * 1024 * 1000, { thousandsSeparator: ',' }) // 1,000MB
809
538
  ```
810
539
 
811
- You can also define your own uncountable rules using the `string.defineUncountableRule` method.
540
+ ### JSON helpers
541
+ Following are the helpers we use to `stringify` and `parse` JSON.
812
542
 
813
- ```ts
814
- import { string } from '@poppinss/utils/build/helpers'
543
+ #### safeParse
544
+ The 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.
815
545
 
816
- string.defineUncountableRule('login')
817
- string.plural('login') // home
818
- ```
546
+ The method is a wrapper over [secure-json-parse](https://github.com/fastify/secure-json-parse) package.
819
547
 
820
- #### truncate
548
+ #### safeStringify
549
+ The native implementation of `JSON.stringify` cannot handle circular references or language-specific data types like `BigInt`.
821
550
 
822
- Truncate a string after a given number of characters
551
+ Therefore, we use the [safe-stable-stringify](https://github.com/BridgeAR/safe-stable-stringify) package under the hood to overcome the limitations of native implementation.
823
552
 
824
553
  ```ts
825
- import { string } from '@poppinss/utils/build/helpers'
554
+ import { safeStringify } from '@poppinss/utils/json'
826
555
 
827
- string.truncate('This is a very long, maybe not that long title', 12) // This is a ve...
828
- ```
556
+ const value = {
557
+ b: 2,
558
+ c: BigInt(10),
559
+ }
829
560
 
830
- By default, the string is truncated exactly after the given characters. However, you can instruct the method to wait for the words to complete.
561
+ // Circular reference
562
+ value.a = value
831
563
 
832
- ```ts
833
- string.truncate('This is a very long, maybe not that long title', 12, {
834
- completeWords: true,
835
- }) // This is a very...
564
+ safeStringify(value)
565
+ // '{"b":2,"c":10}'
836
566
  ```
837
567
 
838
- Also, it is possible to customize the suffix.
568
+ - The circular references are removed from the final JSON string.
569
+ - The BigInt values are converted to a string.
839
570
 
840
- ```ts
841
- string.truncate('This is a very long, maybe not that long title', 12, {
842
- completeWords: true,
843
- suffix: ' <a href="/1"> Read more </a>',
844
- }) // This is a very <a href="/1"> Read more </a>
845
- ```
571
+ The `safeStringify` API is the same as the `JSON.stringify` method.
846
572
 
847
- #### excerpt
573
+ - You can pass a replacer function as the second parameter.
574
+ - And number of spaces as the third parameter.
848
575
 
849
- The `excerpt` method is same as the `truncate` method. However, it strips the HTML from the string.
576
+ ### Lodash helpers
577
+ 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 once we need.
850
578
 
851
- ```ts
852
- import { string } from '@poppinss/utils/build/helpers'
579
+ > **Why not use something else**: All other helpers I have used are not as accurate or well implemented as lodash.
853
580
 
854
- string.excerpt('<p>This is a <strong>very long</strong>, maybe not that long title</p>', 12) // This is a very...
855
- ```
581
+ - pick
582
+ - omit
583
+ - has
584
+ - get
585
+ - set
586
+ - unset
587
+ - mergeWith
588
+ - merge
589
+ - size
590
+ - clone
591
+ - cloneDeep
592
+ - toPath
856
593
 
857
- #### condenseWhitespace
858
-
859
- Condense whitespaces from a given string. The method removes the whitespace from the `left`, `right` and multiple whitespace in between the words.
594
+ You can use the methods as follows.
860
595
 
861
596
  ```ts
862
- import { string } from '@poppinss/utils/build/helpers'
597
+ import lodash from '@poppinss/utils/lodash'
863
598
 
864
- string.condenseWhitespace(' hello world ')
865
- // hello world
599
+ lodash.pick(collection, keys)
866
600
  ```
867
601
 
868
- #### escapeHTML
869
-
870
- Escape HTML from the string
602
+ ### All other helpers
603
+ The following helpers are exported from the package main module.
871
604
 
872
605
  ```ts
873
- import { string } from '@poppinss/utils/build/helpers'
874
-
875
- string.escapeHTML('<p> foo © bar </p>')
876
- // &lt;p&gt; foo © bar &lt;/p&gt;
606
+ import { base64, compose } from '@poppinss/utils'
877
607
  ```
878
608
 
879
- Additonally, you can also encode non-ascii symbols
609
+ #### base64
610
+ Utility methods to base64 encode and decode values.
880
611
 
881
612
  ```ts
882
- import { string } from '@poppinss/utils/build/helpers'
613
+ import { base64 } from '@poppinss/utils'
883
614
 
884
- string.escapeHTML('<p> foo © bar </p>', {
885
- encodeSymbols: true,
886
- })
887
- // &lt;p&gt; foo &#xA9; bar &lt;/p&gt;
615
+ base64.encode('hello world')
616
+ // aGVsbG8gd29ybGQ=
888
617
  ```
889
618
 
890
- #### encodeSymbols
619
+ Similar to the `encode` method, you can use the `urlEncode` to generate a base64 string safe to pass in a URL.
891
620
 
892
- Encode symbols. Checkout [he](https://npm.im/he) for available options
621
+ The `urlEncode` method performs the following replacements.
893
622
 
894
- ```ts
895
- import { string } from '@poppinss/utils/build/helpers'
623
+ - Replace `+` with `-`.
624
+ - Replace `/` with `_`.
625
+ - And remove the `=` sign from the end of the string.
896
626
 
897
- string.encodeSymbols('foo © bar')
898
- // foo &#xA9; bar
627
+ ```ts
628
+ base64.urlEncode('hello world')
629
+ // aGVsbG8gd29ybGQ
899
630
  ```
900
631
 
901
- #### toSentence
902
-
903
- Join an array of words with a separator.
632
+ You can use the `decode` and the `urlDecode` methods to decode a previously encoded base64 string.
904
633
 
905
634
  ```ts
906
- import { string } from '@poppinss/utils/build/helpers'
907
-
908
- string.toSentence(['route', 'middleware', 'controller']) // route, middleware, and controller
635
+ base64.decode(base64.encode('hello world'))
636
+ // hello world
909
637
 
910
- string.toSentence(['route', 'middleware']) // route and middleware
638
+ base64.urlDecode(base64.urlEncode('hello world'))
639
+ // hello world
911
640
  ```
912
641
 
913
- You can also customize
914
-
915
- - `separator`: The value between two words except the last one
916
- - `pairSeparator`: The value between the first and the last word. Used, only when there are two words
917
- - `lastSeparator`: The value between the second last and the last word. Used, only when there are more than two words
642
+ The `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.
918
643
 
919
644
  ```ts
920
- string.toSentence(['route', 'middleware', 'controller'], {
921
- separator: '/ ',
922
- lastSeparator: '/or ',
923
- }) // route/ middleware/or controller
645
+ base64.decode('hello world') // null
646
+ base64.decode('hello world', 'utf-8', true) // raises exception
924
647
  ```
925
648
 
926
- #### prettyBytes
927
-
928
- Convert bytes value to a human readable string. For options, recommend the [bytes](https://www.npmjs.com/package/bytes) package.
649
+ #### compose
650
+ The `compose` helper allows you to use TypeScript class mixins with a cleaner API. Following is an example of mixins usage without the compose helper.
929
651
 
930
652
  ```ts
931
- import { string } from '@poppinss/utils/build/helpers'
932
-
933
- string.prettyBytes(1024) // 1KB
934
- string.prettyBytes(1024, { unitSeparator: ' ' }) // 1 KB
653
+ class User extends UserWithAttributes(
654
+ UserWithAge(
655
+ UserWithPassword(
656
+ UserWithEmail(BaseModel)
657
+ )
658
+ )
659
+ ) {}
935
660
  ```
936
661
 
937
- #### toBytes
662
+ Following is an example with the `compose` helper.
938
663
 
939
- Convert human readable string to bytes. This method is the opposite of the `prettyBytes` method.
664
+ - There is no nesting.
665
+ - The order of mixins is from left to right. Whereas earlier, it was inside out.
940
666
 
941
667
  ```ts
942
- import { string } from '@poppinss/utils/build/helpers'
668
+ import { compose } from '@poppinss/utils'
943
669
 
944
- string.toBytes('1KB') // 1024
670
+ class User extends compose(
671
+ BaseModel,
672
+ UserWithEmail,
673
+ UserWithPassword,
674
+ UserWithAge,
675
+ UserWithAttributes
676
+ ) {}
945
677
  ```
946
678
 
947
- #### prettyMs
948
-
949
- Convert time in milliseconds to a human readable string
950
-
951
- ```ts
952
- import { string } from '@poppinss/utils/build/helpers'
953
-
954
- string.prettyMs(60000) // 1min
955
- string.prettyMs(60000, { long: true }) // 1 minute
956
- ```
679
+ #### defineStaticProperty
680
+ The `defineStaticProperty` method allows you to define static properties on a class with different reference strategies.
957
681
 
958
- #### toMs
682
+ If 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.
959
683
 
960
- Convert human readable string to milliseconds. This method is the opposite of the `prettyMs` method.
684
+ In the following example, we are not inherting `columns` from the `AppModel`. Instead, we define a new set of columns on the `UserModel`.
961
685
 
962
686
  ```ts
963
- import { string } from '@poppinss/utils/build/helpers'
687
+ class AppModel {
688
+ static columns = ['id']
689
+ }
964
690
 
965
- string.toMs('1min') // 60000
691
+ class UserModel extends AppModel {
692
+ static columns = ['username']
693
+ }
966
694
  ```
967
695
 
968
- #### ordinalize
969
-
970
- Ordinalize a string or a number value
696
+ In 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.
971
697
 
972
698
  ```ts
973
- import { string } from '@poppinss/utils/build/helpers'
699
+ class AppModel {
700
+ static columns = ['id']
701
+ }
974
702
 
975
- string.ordinalize(1) // 1st
976
- string.ordinalize(99) // 99th
703
+ class UserModel extends AppModel {}
704
+ UserModel.columns.push('username')
977
705
  ```
978
706
 
979
- #### generateRandom
980
-
981
- Generate a cryptographically strong random string
707
+ The ideal behavior is to deep clone the `columns` array and then push new values to it.
982
708
 
983
709
  ```ts
984
- import { string } from '@poppinss/utils/build/helpers'
710
+ import lodash from '@poppinss/utils/lodash'
985
711
 
986
- string.generateRandom(32)
987
- ```
712
+ class AppModel {
713
+ static columns = ['id']
714
+ }
988
715
 
989
- #### isEmpty
716
+ const inheritedColumns = lodash.cloneDeep(AppModel.columns)
717
+ class UserModel extends AppModel {
718
+ static columns = inheritedColumns.push('username')
719
+ }
720
+ ```
990
721
 
991
- Find if a value is empty. Also checks for empty strings with all whitespace
722
+ The `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.
992
723
 
993
724
  ```ts
994
- import { string } from '@poppinss/utils/build/helpers'
725
+ class UserModel extends AppModel {
726
+ }
995
727
 
996
- string.isEmpty('') // true
997
- string.isEmpty(' ') // true
728
+ defineStaticProperty(UserModel, 'columns', {
729
+ strategy: 'inherit',
730
+ initialValue: [],
731
+ })
998
732
  ```
999
733
 
1000
- ### Types
734
+ - The `inherit` strategy clones the value from the parent class.
735
+ - The `define` strategy always re-defines the property, discarding any values on the parent class.
736
+ - The `strategy` value can be function to perform a custom clone operations.
737
+
1001
738
 
1002
- The types module allows distinguishing between different Javascript datatypes. The `typeof` returns the same type for many different values. For example:
739
+ #### Exception
740
+ A custom exception class with support for defining the error status, error code, and help description. This class aims to standardize exceptions within your projects.
1003
741
 
1004
742
  ```ts
1005
- typeof {} // object
1006
- typeof [] // object
1007
- typeof null // object
1008
- ```
743
+ import { Exception } from '@poppinss/utils'
1009
744
 
1010
- WHAT??? Yes, coz everything is an object in Javascript. To have better control, you can make use of the `types.lookup` method.
745
+ class ResourceNotFound extends Exception {
746
+ static code = 'E_RESOURCE_NOT_FOUND'
747
+ static status = 404
748
+ static message = 'Unable to find resource'
749
+ }
1011
750
 
1012
- #### lookup
751
+ throw new ResourceNotFound()
752
+ ```
1013
753
 
1014
- Returns a more accurate type for a given value.
754
+ Following is an example of creating a dynamic message with interpolation.
1015
755
 
1016
756
  ```ts
1017
- import { types } from '@poppinss/utils/build/helpers'
757
+ import { Exception } from '@poppinss/utils'
758
+ import string from '@poppinss/utils/string'
1018
759
 
1019
- types.lookup({}) // object
1020
- types.lookup([]) // array
1021
- types.lookup(Object.create(null)) // object
1022
- types.lookup(null) // null
1023
- types.lookup(function () {}) // function
1024
- types.lookup(class Foo {}) // class
1025
- types.lookup(new Map()) // map
1026
- ```
760
+ class ResourceNotFound extends Exception {
761
+ static code = 'E_RESOURCE_NOT_FOUND'
762
+ static status = 404
763
+ static message = 'Unable to find resource with id {{ id }}'
764
+ }
1027
765
 
1028
- #### isNull
766
+ throw new ResourceNotFound(
767
+ interpolate(ResourceNotFound.message, { id: 1 })
768
+ )
769
+ ```
1029
770
 
1030
- Find if the given value is null
771
+ #### flatten
772
+ Create 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.
1031
773
 
1032
774
  ```ts
1033
- import { types } from '@poppinss/utils/build/helpers'
775
+ import { flatten } from '@poppinss/utils'
1034
776
 
1035
- types.isNull(null)) // true
1036
- ```
777
+ flatten({
778
+ a: 'hi',
779
+ b: {
780
+ a: null,
781
+ b: ['foo', '', null, 'bar'],
782
+ d: 'hello',
783
+ e: {
784
+ a: 'yo',
785
+ b: undefined,
786
+ c: 'sup',
787
+ d: 0,
788
+ f: [
789
+ { foo: 123, bar: 123 },
790
+ { foo: 465, bar: 456 },
791
+ ]
792
+ }
793
+ },
794
+ c: 'world'
795
+ })
1037
796
 
1038
- #### isBoolean
797
+ // {
798
+ // 'a': 'hi',
799
+ // 'b.b.0': 'foo',
800
+ // 'b.b.1': '',
801
+ // 'b.b.3': 'bar',
802
+ // 'b.d': 'hello',
803
+ // 'b.e.a': 'yo',
804
+ // 'b.e.c': 'sup',
805
+ // 'b.e.d': 0,
806
+ // 'b.e.f.0.foo': 123,
807
+ // 'b.e.f.0.bar': 123,
808
+ // 'b.e.f.1.foo': 465,
809
+ // 'b.e.f.1.bar': 456,
810
+ // 'c': 'world'
811
+ // }
812
+ ```
1039
813
 
1040
- Find if the given value is a boolean
814
+ #### fsReadAll
815
+ Get 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.
1041
816
 
1042
817
  ```ts
1043
- import { types } from '@poppinss/utils/build/helpers'
818
+ import { fsReadAll } from '@poppinss/utils'
1044
819
 
1045
- types.isBoolean(true)) // true
820
+ const files = await fsReadAll(
821
+ new URL('./config', import.meta.url),
822
+ { pathType: 'url' }
823
+ )
824
+ await Promise.all(files.map((file) => import(file)))
1046
825
  ```
1047
826
 
1048
- #### isBuffer
1049
-
1050
- Find if the given value is a buffer
827
+ You can also pass the options along with the directory path as the second argument.
1051
828
 
1052
829
  ```ts
1053
- import { types } from '@poppinss/utils/build/helpers'
830
+ type Options = {
831
+ ignoreMissingRoot?: boolean
832
+ filter?: (filePath: string, index: number) => boolean
833
+ sort?: (current: string, next: string) => number
834
+ pathType?: 'relative' | 'unixRelative' | 'absolute' | 'unixAbsolute' | 'url'
835
+ }
1054
836
 
1055
- types.isBuffer(new Buffer())) // true
837
+ const options: Partial<Options> = {}
838
+ await fsReadAll(location, options)
1056
839
  ```
1057
840
 
1058
- #### isNumber
841
+ | Argument | Type | Description |
842
+ |---------|------|---------------|
843
+ | `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. |
844
+ | `filter` | method | Define a filter to ignore certain paths. The method is called on the final list of files. |
845
+ | `sort` | method | Define a custom method to sort file paths. By default, the files are sorted using natural sort. |
846
+ | `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'` |
1059
847
 
1060
- Find if the given value is a number
848
+ #### fsImportAll
849
+ The `fsImportAll` method imports all the files recursively from a given directory and set the exported value from each module on an object.
1061
850
 
1062
851
  ```ts
1063
- import { types } from '@poppinss/utils/build/helpers'
852
+ import { fsImportAll } from '@poppinss/utils'
1064
853
 
1065
- types.isNumber(100)) // true
854
+ const collection = await fsImportAll(new URL('./config', import.meta.url))
855
+ console.log(collection)
1066
856
  ```
1067
857
 
1068
- #### isString
1069
-
1070
- Find if the given value is a string
1071
-
1072
- ```ts
1073
- import { types } from '@poppinss/utils/build/helpers'
858
+ - Collection is an object with a tree of key-value pair.
859
+ - The key is the nested object created from the file path.
860
+ - 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.
1074
861
 
1075
- types.isString('hello')) // true
1076
- ```
862
+ The second param is the options to customize the import behavior.
1077
863
 
1078
- #### isArguments
864
+ | Argument | Type | Description |
865
+ |---------|------|---------------|
866
+ | `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. |
867
+ | `filter` | method | Define a filter to ignore certain paths. By default only files ending with `.js`, `.ts`, `.json`, `.cjs`, and `.mjs` are imported. |
868
+ | `sort` | method | Define a custom method to sort file paths. By default, the files are sorted using natural sort. |
869
+ | `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. |
1079
870
 
1080
- Find if the given value is an arguments object
871
+ #### isScriptFile
872
+ A 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.
1081
873
 
1082
874
  ```ts
1083
- import { types } from '@poppinss/utils/build/helpers'
875
+ import { isScriptFile } from '@poppinss/utils'
1084
876
 
1085
- function foo() {
1086
- types.isArguments(arguments)) // true
1087
- }
1088
- ```
877
+ isScriptFile('foo.js') // true
878
+ isScriptFile('foo/bar.cjs') // true
879
+ isScriptFile('foo/bar.mjs') // true
880
+ isScriptFile('foo.json') // true
1089
881
 
1090
- #### isObject
882
+ isScriptFile('foo/bar.ts') // true
883
+ isScriptFile('foo/bar.d.ts') // false
884
+ ```
1091
885
 
1092
- Find if the given value is a plain object
886
+ The goal of this method is to use it as a filter with the `fsReadAll` method.
1093
887
 
1094
888
  ```ts
1095
- import { types } from '@poppinss/utils/build/helpers'
889
+ import { fsReadAll, isScriptFile } from '@poppinss/utils'
1096
890
 
1097
- types.isObject({})) // true
1098
- ```
891
+ const dir = new URL('./config', import.meta.url)
892
+ const options = { pathType: 'url', filter: isScriptFile }
1099
893
 
1100
- #### isDate
894
+ const files = await fsReadAll(dir, options)
1101
895
 
1102
- Find if the given value is a date object
1103
-
1104
- ```ts
1105
- import { types } from '@poppinss/utils/build/helpers'
896
+ await Promise.all(files.map((file) => {
897
+ if (file.endsWith('.json')) {
898
+ return import(file, { assert: { type: 'json' } })
899
+ }
1106
900
 
1107
- types.isDate(new Date())) // true
901
+ return import(file)
902
+ }))
1108
903
  ```
1109
904
 
1110
- #### isArray
1111
-
1112
- Find if the given value is an array
905
+ #### naturalSort
906
+ A sorting function to use natural sort for ordering an array.
1113
907
 
1114
908
  ```ts
1115
- import { types } from '@poppinss/utils/build/helpers'
909
+ import { naturalSort } from '@poppinss/utils'
910
+
911
+ const values = ['1_foo_bar', '12_foo_bar'].sort()
912
+ // Default sorting: ['12_foo_bar', '1_foo_bar']
1116
913
 
1117
- types.isArray([1, 2, 3])) // true
914
+ const values = ['1_foo_bar', '12_foo_bar'].sort(naturalSort)
915
+ // Default sorting: ['1_foo_bar', '12_foo_bar']
1118
916
  ```
1119
917
 
1120
- #### isRegexp
918
+ #### safeEqual
919
+ Check 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).
1121
920
 
1122
- Find if the given value is an regular expression
921
+ Under 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)*
1123
922
 
1124
923
  ```ts
1125
- import { types } from '@poppinss/utils/build/helpers'
924
+ import { safeEqual } from '@poppinss/utils'
1126
925
 
1127
- types.isRegexp(/[a-z]+/)) // true
1128
- ```
1129
-
1130
- #### isError
926
+ /**
927
+ * The trusted value, it might be saved inside the db
928
+ */
929
+ const trustedValue = 'hello world'
1131
930
 
1132
- Find if the given value is an instance of the error object
931
+ /**
932
+ * Untrusted user input
933
+ */
934
+ const userInput = 'hello'
1133
935
 
1134
- ```ts
1135
- import { types } from '@poppinss/utils/build/helpers'
1136
- import { Exception } from '@poppinss/utils'
1137
-
1138
- types.isError(new Error('foo'))) // true
1139
- types.isError(new Exception('foo'))) // true
936
+ if (safeEqual(trustedValue, userInput)) {
937
+ // both are same
938
+ } else {
939
+ // value mis-match
940
+ }
1140
941
  ```
1141
942
 
1142
- #### isFunction
1143
-
1144
- Find if the given value is a function
943
+ #### slash
944
+ Convert OS-specific file paths to Unix file paths. The method is exported directly from the [slash](https://npm.im/slash) package.
1145
945
 
1146
946
  ```ts
1147
- import { types } from '@poppinss/utils/build/helpers'
1148
-
1149
- types.isFunction(function foo() {})) // true
947
+ import { slash } from '@poppinss/utils'
948
+ slash('foo\\bar') // foo/bar
1150
949
  ```
1151
950
 
1152
- #### isClass
1153
-
1154
- Find if the given value is a class constructor. Uses regex to distinguish between a function and a class.
951
+ #### MessageBuilder
952
+ Message builder is a convenience layer to stringify JavaScript values with an expiry date and a purpose. For example:
1155
953
 
1156
954
  ```ts
1157
- import { types } from '@poppinss/utils/build/helpers'
955
+ import { MessageBuilder } from '@poppinss/utils'
1158
956
 
1159
- class User {}
957
+ const builder = new MessageBuilder()
958
+ const encoded = builder.build(
959
+ {
960
+ token: string.random(32),
961
+ },
962
+ '1 hour',
963
+ 'email_verification'
964
+ )
1160
965
 
1161
- types.isClass(User) // true
1162
- types.isFunction(User) // true
966
+ /**
967
+ * {
968
+ * "message": {
969
+ * "token":"GZhbeG5TvgA-7JCg5y4wOBB1qHIRtX6q"
970
+ * },
971
+ * "purpose":"email_verification",
972
+ * "expiryDate":"2022-10-03T04:07:13.860Z"
973
+ * }
974
+ */
1163
975
  ```
1164
976
 
1165
- #### isInteger
977
+ Once you have the JSON string with the expiry and the purpose, you can encrypt it (to prevent tampering) and share it with the client.
1166
978
 
1167
- Find if the given value is an integer.
979
+ During the email verification, you can decrypt the key and then ask the `MessageBuilder` to verify the payload.
1168
980
 
1169
981
  ```ts
1170
- import { types } from '@poppinss/utils/build/helpers'
1171
-
1172
- types.isInteger(22.0) // true
1173
- types.isInteger(22) // true
1174
- types.isInteger(-1) // true
1175
- types.isInteger(-1.0) // true
982
+ const decoded = builder.verify(value, 'email_verification')
983
+ if (!decoded) {
984
+ return 'Invalid token'
985
+ }
1176
986
 
1177
- types.isInteger(22.1) // false
1178
- types.isInteger(0.3) // false
1179
- types.isInteger(-0.3) // false
987
+ console.log(decoded.token)
1180
988
  ```
1181
989
 
1182
- #### isFloat
1183
-
1184
- Find if the given value is an float number.
990
+ Now 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.
1185
991
 
1186
992
  ```ts
1187
- import { types } from '@poppinss/utils/build/helpers'
1188
-
1189
- types.isFloat(22.1) // true
1190
- types.isFloat(-22.1) // true
1191
- types.isFloat(0.3) // true
1192
- types.isFloat(-0.3) // true
1193
-
1194
- types.isFloat(22.0) // false
1195
- types.isFloat(-22.0) // false
1196
- types.isFloat(-22) // false
993
+ const decoded = builder.verify(value, 'reset_password')
1197
994
  ```
1198
995
 
1199
- #### isDecimal
1200
-
1201
- Find if the given value has a decimal. The value can be a string or a number. The number values are casted to a string by calling the `toString()` method on the value itself.
1202
-
1203
- The string conversion is peformed to test the value against a regex. Since, there is no way to natively find a decimal value in Javascript.
996
+ #### ObjectBuilder
997
+ The `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`.
1204
998
 
1205
999
  ```ts
1206
- import { types } from '@poppinss/utils/build/helpers'
1207
-
1208
- types.isDecimal('22.10') // true
1209
- types.isDecimal(22.1) // true
1210
-
1211
- types.isDecimal('-22.10') // true
1212
- types.isDecimal(-22.1) // true
1213
-
1214
- types.isDecimal('.3') // true
1215
- types.isDecimal(0.3) // true
1000
+ const startingObject = {
1001
+ a: 1
1002
+ // Add "b", if it exists
1003
+ ...(b ? { b } : {})
1004
+ }
1216
1005
 
1217
- types.isDecimal('-.3') // true
1218
- types.isDecimal(-0.3) // true
1006
+ // OR
1007
+ if (b) {
1008
+ startingObject.b = b
1009
+ }
1010
+ ```
1219
1011
 
1220
- types.isDecimal('22.00') // true
1221
- types.isDecimal(22.0) // false (gets converted to 22)
1012
+ Instead of writing conditionals, your can consider using the Object builder fluent API.
1222
1013
 
1223
- types.isDecimal('-22.00') // true
1224
- types.isDecimal(-22.0) // false (gets converted to -22)
1014
+ ```ts
1015
+ const builder = new ObjectBuilder({ a: 1 })
1225
1016
 
1226
- types.isDecimal('22') // false
1227
- types.isDecimal(22) // false
1017
+ // Add property if the value is not undefined.
1018
+ builder.add('b', b)
1228
1019
 
1229
- types.isDecimal('0.0000000000001') // true
1230
- types.isDecimal(0.0000000000001) // false (gets converted to 1e-13)
1020
+ // Get the plain object back
1021
+ const plainObject = builder.toObject()
1231
1022
  ```
1232
1023
 
1233
- ### ObjectBuilder
1234
-
1235
- A very simple class to conditionally builder an object. Quite often, I create a new object from an existing one and wants to avoid writing undefined values to it. For example
1024
+ By default, only the `undefined` values are ignored. However, you can also ignore `null` values.
1236
1025
 
1237
1026
  ```ts
1238
- const obj = {
1239
- ...(user.username ? { username: user.username } : {}),
1240
- ...(user.id ? { id: user.id } : {}),
1241
- ...(user.createdAt ? { createdAt: user.createdAt.toString() } : {}),
1242
- }
1027
+ const ignoreNullValues = true
1028
+ const builder = new ObjectBuilder({ a: 1 }, ignoreNullValues)
1243
1029
  ```
1244
1030
 
1245
- Not only the above code is harder to write. It is performance issues as well, since we are destructuring too many objects.
1246
-
1247
- To address this use case, you can make use of the `ObjectBuilder` class as follows
1031
+ Following are the available methods on the `ObjectBuilder` class.
1248
1032
 
1249
1033
  ```ts
1250
- import { ObjectBuilder } from '@poppinss/utils/build/helpers'
1034
+ builder.remove(key)
1035
+ builder.has(key)
1036
+ builder.get(key)
1037
+ builder.add(key)
1251
1038
 
1252
- const obj = new ObjectBuilder()
1253
- .add('username', user.username)
1254
- .add('id', user.id)
1255
- .add('createdAt', user.createdAt && user.createdAt.toString()).value // returns the underlying object
1039
+ builder.toObject() // get plain object
1256
1040
  ```
1257
1041
 
1258
- The `add` method ignores the value if its `undefined`. So it never gets added to the object at all. You can also ignore `null` properties by passing a boolean flag to the constructor.
1042
+ #### dirname/filename
1043
+ ES modules does not have magic variables `__filename` and `__dirname`. You can use these helpers to get the current directory and filenames as follows.
1259
1044
 
1260
1045
  ```ts
1261
- new ObjectBuilder(true) // ignore null as well
1046
+ import { getDirname, getFilename } from '@poppinss/utils'
1047
+
1048
+ const dirname = getDirname(import.meta.url)
1049
+ const filename = getFilename(import.meta.url)
1262
1050
  ```
1263
1051
 
1264
1052
  [gh-workflow-image]: https://img.shields.io/github/workflow/status/poppinss/utils/test?style=for-the-badge