@poppinss/utils 6.9.3 → 7.0.0-next.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 (38) hide show
  1. package/README.md +204 -826
  2. package/build/index.d.ts +4 -24
  3. package/build/index.js +104 -233
  4. package/build/{src → modules}/base64.d.ts +2 -2
  5. package/build/modules/exception.d.ts +1 -0
  6. package/build/{src → modules/fs}/fs_import_all.d.ts +6 -1
  7. package/build/{src → modules/fs}/fs_read_all.d.ts +6 -1
  8. package/build/modules/fs/main.d.ts +2 -0
  9. package/build/modules/json/main.d.ts +2 -0
  10. package/build/{src → modules}/json/safe_parse.d.ts +1 -1
  11. package/build/{src → modules}/json/safe_stringify.d.ts +1 -1
  12. package/build/modules/string/string_builder.d.ts +2 -0
  13. package/build/modules/types.d.ts +1 -0
  14. package/build/src/compose.d.ts +12 -10
  15. package/build/src/define_static_property.d.ts +2 -4
  16. package/package.json +18 -18
  17. package/build/chunk-2KG3PWR4.js +0 -17
  18. package/build/chunk-4V5ON6P7.js +0 -7
  19. package/build/chunk-EJKUJ44Y.js +0 -12
  20. package/build/chunk-H54AK3PI.js +0 -7
  21. package/build/chunk-XHQBV7AF.js +0 -39
  22. package/build/src/assert.js +0 -33
  23. package/build/src/exception.d.ts +0 -1
  24. package/build/src/exception.js +0 -9
  25. package/build/src/exceptions/main.d.ts +0 -1
  26. package/build/src/exceptions/main.js +0 -9
  27. package/build/src/json/main.d.ts +0 -7
  28. package/build/src/json/main.js +0 -7
  29. package/build/src/object_builder.d.ts +0 -1
  30. package/build/src/slash.d.ts +0 -24
  31. package/build/src/slash.js +0 -7
  32. package/build/src/string/main.js +0 -8
  33. package/build/src/string/string_builder.d.ts +0 -3
  34. package/build/src/string/string_builder.js +0 -9
  35. package/build/src/types.d.ts +0 -35
  36. package/build/src/types.js +0 -0
  37. /package/build/{src → modules}/assert.d.ts +0 -0
  38. /package/build/{src → modules}/string/main.d.ts +0 -0
package/README.md CHANGED
@@ -4,19 +4,25 @@
4
4
 
5
5
  [![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
6
6
 
7
- ## Why this package exists?
7
+ ## Why does this package exist?
8
8
 
9
- 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.
9
+ 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.
10
10
 
11
- - 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.
11
+ - Finding the perfect package for the use case takes a lot of time. The package should be well maintained, have good test coverage, and not accumulate debt by supporting some old versions of Node.js.
12
12
  - 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)
13
- - 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`.
13
+ - Sometimes, I use different packages for the same utility (because I cannot remember what I used last time in that other package). So I want to spend time once choosing the one I need and then bundle it inside `@poppinss/utils`.
14
14
  - 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.
15
- - Rest are some handwritten utilities to fit my needs
15
+ - The rest are some handwritten utilities that fit my needs.
16
16
 
17
- > **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.
17
+ ## Re-exported packages
18
18
 
19
- > **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.
19
+ The following packages are re-exported as it is and you must consult their documentation for usage instructions.
20
+
21
+ | Package | Subpath export |
22
+ | ------------------------------------------------------------------------ | --------------------------- |
23
+ | [@poppinss/exception](https://www.npmjs.com/package/@poppinss/exception) | `@poppinss/utils/exception` |
24
+ | [@poppinss/string](https://www.npmjs.com/package/@poppinss/string) | `@poppinss/utils/string` |
25
+ | [@poppinss/types](https://www.npmjs.com/package/@poppinss/types) | `@poppinss/utils/types` |
20
26
 
21
27
  ## Other packages to use
22
28
 
@@ -24,12 +30,13 @@ A note to self and others to consider the following packages.
24
30
 
25
31
  | Package | Description |
26
32
  | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
27
- | [he](https://www.npmjs.com/package/he) | For escaping HTML entities and encoding unicode symbols. Has zero dependencies |
33
+ | [he](https://www.npmjs.com/package/he) | For escaping HTML entities and encoding Unicode symbols. Has zero dependencies |
28
34
  | [@sindresorhus/is](https://www.npmjs.com/package/@sindresorhus/is) | For advanced type checking. Has zero dependencies |
35
+ | [moize](https://www.npmjs.com/package/moize) | For memoizing functions with complex parameters |
29
36
 
30
37
  ## Package size
31
38
 
32
- 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.
39
+ Even though I do not care much about package size (most of my work is consumed on the server side), I am mindful of the utilities and ensure that I do not end up using really big packages for smaller use cases.
33
40
 
34
41
  Here's the last checked install size of this package.
35
42
 
@@ -48,607 +55,167 @@ npm i @poppinss/utils
48
55
  yarn add @poppinss/utils
49
56
  ```
50
57
 
51
- ## Exported modules
52
-
53
- 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.
54
-
55
- ```ts
56
- // string sub-module
57
- import string from '@poppinss/utils/string'
58
-
59
- // string builder
60
- import string from '@poppinss/utils/string_builder'
61
-
62
- // json sub-module
63
- import json from '@poppinss/utils/json'
64
-
65
- // lodash sub-module
66
- import lodash from '@poppinss/utils/lodash'
67
-
68
- // assert sub-module
69
- import assert from '@poppinss/utils/assert'
70
-
71
- // main module
72
- import { base64, fsReadAll } from '@poppinss/utils'
73
-
74
- // types sub-module
75
- import { ReadAllFilesOptions } from '@poppinss/utils/types'
76
- ```
77
-
78
- ### String helpers
79
-
80
- A collection of helpers to perform operations on/related to a string value.
81
-
82
- ```ts
83
- import string from '@poppinss/utils/string'
84
- ```
85
-
86
- #### excerpt
87
-
88
- Generate an excerpt from a string value. If the input value contains HTML tags, we will remove them from the excerpt.
89
-
90
- ```ts
91
- 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>`
92
-
93
- console.log(string.excerpt(html, 70))
94
- // AdonisJS is a Node.js framework, and hence it requires Node.js to be i...
95
- ```
96
-
97
- | Argument | Type | Description |
98
- | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
99
- | `sentence` | string | The value for which to generate excerpt |
100
- | `charactersLimit` | string | The number of characters to keep |
101
- | `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit |
102
- | `options.suffix` | string | The value to append after the truncated string. Defaults to three dots `...` |
103
-
104
- #### truncate
105
-
106
- 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.
107
-
108
- ```ts
109
- 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.`
110
-
111
- console.log(string.truncate(text, 70))
112
- // AdonisJS is a Node.js framework, and hence it requires Node.js to be i...
113
- ```
114
-
115
- | Argument | Type | Description |
116
- | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
117
- | `sentence` | string | The value to truncate |
118
- | `charactersLimit` | string | The number of characters to keep |
119
- | `options.completeWords` | boolean | When set to `true`, the truncation will happen only after complete words. This option might go over the defined characters limit |
120
- | `options.suffix` | string | The value to append after the truncated string. Defaults to three dots `...` |
121
-
122
- #### slug
123
-
124
- Generate slug for a string value. The method is exported directly from the [slugify](https://www.npmjs.com/package/slugify) package.
125
-
126
- Please check the package documentation for [available options](https://www.npmjs.com/package/slugify#options).
127
-
128
- ```ts
129
- console.log(string.slug('hello ♥ world'))
130
- // hello-love-world
131
- ```
132
-
133
- You can add custom replacements for Unicode values as follows.
134
-
135
- ```ts
136
- string.slug.extend({ '☢': 'radioactive' })
137
-
138
- console.log(string.slug('unicode ♥ is ☢'))
139
- // unicode-love-is-radioactive
140
- ```
141
-
142
- #### interpolate
143
-
144
- Interpolate variables inside a string. The variables must be inside double curly braces.
145
-
146
- ```ts
147
- string.interpolate('hello {{ user.username }}', { user: { username: 'virk' } })
148
-
149
- // hello virk
150
- ```
151
-
152
- You can also replace array values by mentioning the array index.
153
-
154
- ```ts
155
- string.interpolate('hello {{ users.0 }}', { users: ['virk'] })
156
-
157
- // hello virk
158
- ```
159
-
160
- You can escape the curly braces by prefixing them with `\\`.
161
-
162
- ```ts
163
- string.interpolate('hello \\{{ users.0 }}', {})
164
-
165
- // hello {{ users.0 }}
166
- ```
167
-
168
- #### plural
58
+ ## JSON helpers
169
59
 
170
- Convert a word to its plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
60
+ Safely parse and stringify JSON values. These helpers are thin wrappers over [secure-json-parse](https://github.com/fastify/secure-json-parse) and [safe-stable-stringify](https://github.com/BridgeAR/safe-stable-stringify) packages.
171
61
 
172
- ```ts
173
- string.plural('test')
174
- // tests
175
- ```
176
-
177
- #### singular
178
-
179
- Convert a word to its singular form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
180
-
181
- ```ts
182
- string.singular('tests')
183
- // test
184
- ```
185
-
186
- #### pluralize
62
+ ### safeParse
187
63
 
188
- This method combines the `singular` and `plural` methods and uses one or the other based on the count. For example:
64
+ 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 protects you from that by removing the `__proto__` and the `constructor.prototype` properties from the JSON string at the time of parsing it.
189
65
 
190
66
  ```ts
191
- string.pluralize('box', 1) // box
192
- string.pluralize('box', 2) // boxes
193
- string.pluralize('box', 0) // boxes
194
-
195
- string.pluralize('boxes', 1) // box
196
- string.pluralize('boxes', 2) // boxes
197
- string.pluralize('boxes', 0) // boxes
198
- ```
199
-
200
- The `addPluralRule`, `addSingularRule`, `addIrregularRule`, and `addUncountableRule` methods exposed by the pluralize package can be called as follows.
67
+ import { safeParse } from '@poppinss/utils/json'
201
68
 
202
- ```ts
203
- string.pluralize.addUncountableRule('paper')
204
- string.pluralize.addSingularRule(/singles$/i, 'singular')
69
+ safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
70
+ // { a: 5, b: 6 }
205
71
  ```
206
72
 
207
- #### isPlural
73
+ ### safeStringify
208
74
 
209
- Find if a word is already in plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
75
+ The native implementation of `JSON.stringify` cannot handle circular references or language-specific data types like `BigInt`. The `safeStringify` method removes circular references and converts BigInts to strings.
76
+ The `safeStringify` method accepts the same set of parameters as the `JSON.stringify` method.
210
77
 
211
78
  ```ts
212
- string.isPlural('tests') // true
213
- ```
214
-
215
- #### isSingular
216
-
217
- 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.
218
-
219
- ```ts
220
- string.isSingular('test') // true
221
- ```
222
-
223
- #### camelCase
224
-
225
- Convert a string value to camelcase.
226
-
227
- ```ts
228
- string.camelCase('user_name') // userName
229
- ```
230
-
231
- Following are some of the conversion examples.
232
-
233
- | Input | Output |
234
- | ---------------- | ------------- |
235
- | 'test' | 'test' |
236
- | 'test string' | 'testString' |
237
- | 'Test String' | 'testString' |
238
- | 'TestV2' | 'testV2' |
239
- | '_foo_bar_' | 'fooBar' |
240
- | 'version 1.2.10' | 'version1210' |
241
- | 'version 1.21.0' | 'version1210' |
242
-
243
- #### capitalCase
244
-
245
- Convert a string value to a capital case.
246
-
247
- ```ts
248
- string.capitalCase('helloWorld') // Hello World
249
- ```
250
-
251
- Following are some of the conversion examples.
252
-
253
- | Input | Output |
254
- | ---------------- | ---------------- |
255
- | 'test' | 'Test' |
256
- | 'test string' | 'Test String' |
257
- | 'Test String' | 'Test String' |
258
- | 'TestV2' | 'Test V 2' |
259
- | 'version 1.2.10' | 'Version 1.2.10' |
260
- | 'version 1.21.0' | 'Version 1.21.0' |
261
-
262
- #### dashCase
263
-
264
- Convert a string value to a dash case.
265
-
266
- ```ts
267
- string.dashCase('helloWorld') // hello-world
268
- ```
269
-
270
- Optionally, you can capitalize the first letter of each word.
271
-
272
- ```ts
273
- string.dashCase('helloWorld', { capitalize: true }) // Hello-World
274
- ```
275
-
276
- Following are some of the conversion examples.
277
-
278
- | Input | Output |
279
- | ---------------- | -------------- |
280
- | 'test' | 'test' |
281
- | 'test string' | 'test-string' |
282
- | 'Test String' | 'test-string' |
283
- | 'Test V2' | 'test-v2' |
284
- | 'TestV2' | 'test-v-2' |
285
- | 'version 1.2.10' | 'version-1210' |
286
- | 'version 1.21.0' | 'version-1210' |
287
-
288
- #### dotCase
289
-
290
- Convert a string value to a dot case.
291
-
292
- ```ts
293
- string.dotCase('helloWorld') // hello.World
294
- ```
295
-
296
- Optionally, you can also convert the first letter of all the words to lowercase.
297
-
298
- ```ts
299
- string.dotCase('helloWorld', { lowerCase: true }) // hello.world
300
- ```
301
-
302
- Following are some of the conversion examples.
303
-
304
- | Input | Output |
305
- | ---------------- | -------------- |
306
- | 'test' | 'test' |
307
- | 'test string' | 'test.string' |
308
- | 'Test String' | 'Test.String' |
309
- | 'dot.case' | 'dot.case' |
310
- | 'path/case' | 'path.case' |
311
- | 'TestV2' | 'Test.V.2' |
312
- | 'version 1.2.10' | 'version.1210' |
313
- | 'version 1.21.0' | 'version.1210' |
314
-
315
- #### noCase
316
-
317
- Remove all sorts of casing from a string value.
318
-
319
- ```ts
320
- string.noCase('helloWorld') // hello world
321
- ```
322
-
323
- Following are some of the conversion examples.
324
-
325
- | Input | Output |
326
- | ---------------------- | ---------------------- |
327
- | 'test' | 'test' |
328
- | 'TEST' | 'test' |
329
- | 'testString' | 'test string' |
330
- | 'testString123' | 'test string123' |
331
- | 'testString_1_2_3' | 'test string 1 2 3' |
332
- | 'ID123String' | 'id123 string' |
333
- | 'foo bar123' | 'foo bar123' |
334
- | 'a1bStar' | 'a1b star' |
335
- | 'CONSTANT_CASE ' | 'constant case' |
336
- | 'CONST123_FOO' | 'const123 foo' |
337
- | 'FOO_bar' | 'foo bar' |
338
- | 'XMLHttpRequest' | 'xml http request' |
339
- | 'IQueryAArgs' | 'i query a args' |
340
- | 'dot.case' | 'dot case' |
341
- | 'path/case' | 'path case' |
342
- | 'snake_case' | 'snake case' |
343
- | 'snake_case123' | 'snake case123' |
344
- | 'snake_case_123' | 'snake case 123' |
345
- | '"quotes"' | 'quotes' |
346
- | 'version 0.45.0' | 'version 0 45 0' |
347
- | 'version 0..78..9' | 'version 0 78 9' |
348
- | 'version 4_99/4' | 'version 4 99 4' |
349
- | ' test ' | 'test' |
350
- | 'something_2014_other' | 'something 2014 other' |
351
- | 'amazon s3 data' | 'amazon s3 data' |
352
- | 'foo_13_bar' | 'foo 13 bar' |
353
-
354
- #### pascalCase
355
-
356
- Convert a string value to pascal case. Great for generating JavaScript class names.
357
-
358
- ```ts
359
- string.pascalCase('user team') // UserTeam
360
- ```
361
-
362
- Following are some of the conversion examples.
363
-
364
- | Input | Output |
365
- | ---------------- | ------------- |
366
- | 'test' | 'Test' |
367
- | 'test string' | 'TestString' |
368
- | 'Test String' | 'TestString' |
369
- | 'TestV2' | 'TestV2' |
370
- | 'version 1.2.10' | 'Version1210' |
371
- | 'version 1.21.0' | 'Version1210' |
372
-
373
- #### sentenceCase
374
-
375
- Convert a value to a sentence.
376
-
377
- ```ts
378
- string.sentenceCase('getting-started-with-adonisjs')
379
- // Getting started with adonisjs
380
- ```
381
-
382
- Following are some of the conversion examples.
383
-
384
- | Input | Output |
385
- | ---------------- | ---------------- |
386
- | 'test' | 'Test' |
387
- | 'test string' | 'Test string' |
388
- | 'Test String' | 'Test string' |
389
- | 'TestV2' | 'Test v2' |
390
- | 'version 1.2.10' | 'Version 1 2 10' |
391
- | 'version 1.21.0' | 'Version 1 21 0' |
392
-
393
- #### snakeCase
394
-
395
- Convert value to snake case.
396
-
397
- ```ts
398
- string.snakeCase('user team') // user_team
399
- ```
400
-
401
- Following are some of the conversion examples.
402
-
403
- | Input | Output |
404
- | ---------------- | -------------- |
405
- | '\_id' | 'id' |
406
- | 'test' | 'test' |
407
- | 'test string' | 'test_string' |
408
- | 'Test String' | 'test_string' |
409
- | 'Test V2' | 'test_v2' |
410
- | 'TestV2' | 'test_v_2' |
411
- | 'version 1.2.10' | 'version_1210' |
412
- | 'version 1.21.0' | 'version_1210' |
413
-
414
- #### titleCase
415
-
416
- Convert a string value to title case.
417
-
418
- ```ts
419
- string.titleCase('small word ends on')
420
- // Small Word Ends On
421
- ```
422
-
423
- Following are some of the conversion examples.
424
-
425
- | Input | Output |
426
- | ---------------------------------- | ---------------------------------- |
427
- | 'one. two.' | 'One. Two.' |
428
- | 'a small word starts' | 'A Small Word Starts' |
429
- | 'small word ends on' | 'Small Word Ends On' |
430
- | 'we keep NASA capitalized' | 'We Keep NASA Capitalized' |
431
- | 'pass camelCase through' | 'Pass camelCase Through' |
432
- | 'follow step-by-step instructions' | 'Follow Step-by-Step Instructions' |
433
- | 'this vs. that' | 'This vs. That' |
434
- | 'this vs that' | 'This vs That' |
435
- | 'newcastle upon tyne' | 'Newcastle upon Tyne' |
436
- | 'newcastle \*upon\* tyne' | 'Newcastle \*upon\* Tyne' |
437
-
438
- #### random
439
-
440
- Generate a cryptographically secure random string of a given length. The output value is URL safe base64 encoded string.
441
-
442
- ```ts
443
- string.random(32)
444
- // 8mejfWWbXbry8Rh7u8MW3o-6dxd80Thk
445
- ```
446
-
447
- #### toSentence
448
-
449
- Convert an array of words to a comma-separated sentence.
450
-
451
- ```ts
452
- string.toSentence(['routes', 'controllers', 'middleware'])
453
- // routes, controllers, and middleware
454
- ```
455
-
456
- You can replace the `and` with an `or` by specifying the `options.lastSeparator` property.
457
-
458
- ```ts
459
- string.toSentence(['routes', 'controllers', 'middleware'], {
460
- lastSeparator: ', or ',
461
- })
462
- ```
463
-
464
- 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.
465
-
466
- ```ts
467
- string.toSentence(['routes', 'controllers'])
468
- // routes and controllers
469
-
470
- string.toSentence(['routes', 'controllers'], {
471
- pairSeparator: ', and ',
472
- })
473
- // routes, and controllers
474
- ```
475
-
476
- #### condenseWhitespace
79
+ import { safeStringify } from '@poppinss/utils/json'
477
80
 
478
- Remove multiple whitespaces from a string to a single whitespace.
81
+ const value = {
82
+ b: 2,
83
+ c: BigInt(10),
84
+ }
479
85
 
480
- ```ts
481
- string.condenseWhitespace('hello world')
482
- // hello world
86
+ // Circular reference
87
+ value.a = value
483
88
 
484
- string.condenseWhitespace(' hello world ')
485
- // hello world
89
+ safeStringify(value)
90
+ // '{"b":2,"c":"10"}'
486
91
  ```
487
92
 
488
- #### ordinal
93
+ ## Lodash helpers
489
94
 
490
- Get the ordinal letter for a given number.
95
+ Lodash is quite a big library, and we do not use all its helper methods. Therefore, we create a custom build using the lodash CLI and bundle only the needed ones.
491
96
 
492
- ```ts
493
- string.ordinal(1) // 1st
494
- string.ordinal(2) // '2nd'
495
- string.ordinal(3) // '3rd'
496
- string.ordinal(4) // '4th'
497
-
498
- string.ordinal(23) // '23rd'
499
- string.ordinal(24) // '24th'
500
- ```
97
+ > **Why not use something else**: All other helpers I have used are not as accurate or well implemented as lodash.
501
98
 
502
- #### seconds.(parse/format)
99
+ - pick
100
+ - omit
101
+ - has
102
+ - get
103
+ - set
104
+ - unset
105
+ - mergeWith
106
+ - merge
107
+ - size
108
+ - clone
109
+ - cloneDeep
110
+ - toPath
503
111
 
504
- Parse a string-based time expression to seconds.
112
+ You can use the methods as follows.
505
113
 
506
114
  ```ts
507
- string.seconds.parse('10h') // 36000
508
- string.seconds.parse('1 day') // 86400
509
- ```
510
-
511
- Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in seconds.
115
+ import lodash from '@poppinss/utils/lodash'
512
116
 
513
- ```ts
514
- string.seconds.parse(180) // 180
117
+ lodash.pick(collection, keys)
515
118
  ```
516
119
 
517
- You can format seconds to a pretty string using the `format` method.
120
+ ## FS helpers
518
121
 
519
- ```ts
520
- string.seconds.format(36000) // 10h
521
- string.seconds.format(36000, true) // 10 hours
522
- ```
122
+ ### fsReadAll
523
123
 
524
- #### milliseconds.(parse/format)
124
+ Get a recursive list of all files from a given directory. This method is similar to the Node.js `readdir` method, with the following differences.
525
125
 
526
- Parse a string-based time expression to milliseconds.
126
+ - Dot files and directories are ignored.
127
+ - Only files are returned (not directories).
128
+ - You can define how the output paths should be returned. The supported types are `relative`, `absolute`, `unixRelative`, `unixAbsolute`, and `url`.
527
129
 
528
130
  ```ts
529
- string.milliseconds.parse('1 h') // 3.6e6
530
- string.milliseconds.parse('1 day') // 8.64e7
531
- ```
131
+ import { fsReadAll } from '@poppinss/utils/fs'
532
132
 
533
- Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in milliseconds.
133
+ const basePath = new URL('./config', import.meta.url)
134
+ const files = await fsReadAll(basePath, { pathType: 'url' })
534
135
 
535
- ```ts
536
- string.milliseconds.parse(180) // 180
136
+ console.log(files)
537
137
  ```
538
138
 
539
- Using the `format` method, you can format milliseconds to a pretty string.
139
+ #### OPTIONS
540
140
 
541
- ```ts
542
- string.seconds.format(3.6e6) // 1h
543
- string.seconds.format(3.6e6, true) // 1 hour
544
- ```
141
+ <dl>
142
+ <dt>ignoreMissingRoot</dt>
143
+ <dd>By default, an exception is raised when the root directory is missing. Setting <code>ignoreMissingRoot</code> to true will not result in an error, and an empty array will be returned.</dd>
545
144
 
546
- #### bytes.(parse/format)
145
+ <dt>filter</dt>
146
+ <dd>Define a filter to ignore certain paths. The method is called on the final list of files.</dd>
547
147
 
548
- Parse a string-based unit expression to bytes.
148
+ <dt>sort</dt>
149
+ <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd>
549
150
 
550
- ```ts
551
- string.bytes.parse('1KB') // 1024
552
- string.bytes.parse('1MB') // 1048576
553
- ```
151
+ <dt>pathType</dt>
152
+ <dd>Define how to return the collected paths. By default, OS-specific relative paths are returned. If you want to import the collected files, you must set the <code>pathType = 'url'</code></dd>
153
+ </dl>
554
154
 
555
- Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in bytes.
155
+ ### fsImportAll
556
156
 
557
- ```ts
558
- string.bytes.parse(1024) // 1024
559
- ```
157
+ The `fsImportAll` method recursively imports all the JavaScript, TypeScript, and JSON files from a given directory and returns their exported values as an object of key-value pairs.
560
158
 
561
- 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.
159
+ - If there are nested directories, then the output will also contain nested objects.
160
+ - Value is the exported values from the module. Only the default value is used if a module exports both the `default` and `named` values.
562
161
 
563
162
  ```ts
564
- string.bytes.format(1048576) // 1MB
565
- string.bytes.format(1024 * 1024 * 1000) // 1000MB
566
- string.bytes.format(1024 * 1024 * 1000, { thousandsSeparator: ',' }) // 1,000MB
567
- ```
568
-
569
- ### String builder
163
+ import { fsImportAll } from '@poppinss/utils/fs'
570
164
 
571
- The 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.
165
+ const configDir = new URL('./config', import.meta.url)
166
+ const collection = await fsImportAll(configDir)
572
167
 
573
- ```ts
574
- import StringBuilder from '@poppinss/utils/string_builder'
575
- const builder = new StringBuilder('hello world')
576
-
577
- const value = builder.snakeCase().suffix('_controller').toString()
578
- assert(value === 'hello_world_controller')
168
+ console.log(collection)
579
169
  ```
580
170
 
581
- ### JSON helpers
582
-
583
- Following are the helpers we use to `stringify` and `parse` JSON.
584
-
585
- #### safeParse
586
-
587
- 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.
588
-
589
- The method is a wrapper over [secure-json-parse](https://github.com/fastify/secure-json-parse) package.
590
-
591
- #### safeStringify
592
-
593
- The native implementation of `JSON.stringify` cannot handle circular references or language-specific data types like `BigInt`.
594
-
595
- 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.
596
-
597
- ```ts
598
- import { safeStringify } from '@poppinss/utils/json'
599
-
600
- const value = {
601
- b: 2,
602
- c: BigInt(10),
171
+ ```sh
172
+ // title: Directory structure
173
+ ├── js
174
+ │   └── config.cjs
175
+ ├── json
176
+ │   └── main.json
177
+ └── ts
178
+ ├── app.ts
179
+ └── server.ts
180
+ ```
181
+
182
+ ```ts
183
+ // title: Output
184
+ {
185
+ ts: {
186
+ app: {},
187
+ server: {},
188
+ },
189
+ js: {
190
+ config: {},
191
+ },
192
+ json: {
193
+ main: {},
194
+ },
603
195
  }
604
-
605
- // Circular reference
606
- value.a = value
607
-
608
- safeStringify(value)
609
- // '{"b":2,"c":10}'
610
196
  ```
611
197
 
612
- - The circular references are removed from the final JSON string.
613
- - The BigInt values are converted to a string.
614
-
615
- The `safeStringify` API is the same as the `JSON.stringify` method.
198
+ #### OPTIONS
616
199
 
617
- - You can pass a replacer function as the second parameter.
618
- - And number of spaces as the third parameter.
200
+ <dl>
201
+ <dt>ignoreMissingRoot</dt>
202
+ <dd>By default, an exception is raised when the root directory is missing. Setting <code>ignoreMissingRoot</code> to true will not result in an error, and an empty object will be returned.</dd>
619
203
 
620
- ### Lodash helpers
621
-
622
- 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.
623
-
624
- > **Why not use something else**: All other helpers I have used are not as accurate or well implemented as lodash.
204
+ <dt>filter</dt>
205
+ <dd>Define a filter to ignore certain paths. By default only files ending with <code>.js</code>, <code>.ts</code>, <code>.json</code>, <code>.cjs</code>, and <code>.mjs</code> are imported.</dd>
625
206
 
626
- - pick
627
- - omit
628
- - has
629
- - get
630
- - set
631
- - unset
632
- - mergeWith
633
- - merge
634
- - size
635
- - clone
636
- - cloneDeep
637
- - toPath
638
-
639
- You can use the methods as follows.
640
-
641
- ```ts
642
- import lodash from '@poppinss/utils/lodash'
207
+ <dt>sort</dt>
208
+ <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd>
643
209
 
644
- lodash.pick(collection, keys)
645
- ```
210
+ <dt>transformKeys</dt>
211
+ <dd>Define a callback method to transform the keys for the final object. The method receives an array of nested keys and must return an array back.</dd>
212
+ </dl>
646
213
 
647
- ### Assertion helpers
214
+ ## Assertion helpers
648
215
 
649
- The following assertion methods offers type-safe approach for writing conditionals and throwing error when the variable has unexpected values.
216
+ The following assertion methods offer a type-safe approach for writing conditionals and throwing errors when the variable has unexpected values.
650
217
 
651
- #### assertExists(message?: string)
218
+ ### assertExists
652
219
 
653
220
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `false`, `null`, or `undefined`.
654
221
 
@@ -658,10 +225,10 @@ import { assertExists } from '@poppinss/utils/assert'
658
225
  const value = false as string | false
659
226
  assertExists(value)
660
227
 
661
- // value is string
228
+ // value is a string
662
229
  ```
663
230
 
664
- #### assertNotNull(value: unknown, message?: string)
231
+ ### assertNotNull
665
232
 
666
233
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `null`.
667
234
 
@@ -671,10 +238,10 @@ import { assertNotNull } from '@poppinss/utils/assert'
671
238
  const value = null as string | null
672
239
  assertNotNull(value)
673
240
 
674
- // value is string
241
+ // value is a string
675
242
  ```
676
243
 
677
- #### assertIsDefined(value: unknown, message?: string)
244
+ ### assertIsDefined
678
245
 
679
246
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `undefined`.
680
247
 
@@ -684,10 +251,10 @@ import { assertIsDefined } from '@poppinss/utils/assert'
684
251
  const value = undefined as string | undefined
685
252
  assertIsDefined(value)
686
253
 
687
- // value is string
254
+ // value is a string
688
255
  ```
689
256
 
690
- #### assertUnreachable(value: unknown)
257
+ ### assertUnreachable
691
258
 
692
259
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the method is invoked. In other words, this method always throws an exception.
693
260
 
@@ -696,67 +263,63 @@ import { assertUnreachable } from '@poppinss/utils/assert'
696
263
  assertUnreachable()
697
264
  ```
698
265
 
699
- ### All other helpers
700
-
701
- The following helpers are exported from the package main module.
702
-
703
- ```ts
704
- import { base64, compose } from '@poppinss/utils'
705
- ```
266
+ ## Base64 encoding
706
267
 
707
- #### base64
268
+ ### encode
708
269
 
709
- Utility methods to base64 encode and decode values.
270
+ Base64 encodes a string or a Buffer value.
710
271
 
711
272
  ```ts
712
- import { base64 } from '@poppinss/utils'
273
+ import base64 from '@poppinss/utils/base64'
713
274
 
714
275
  base64.encode('hello world')
715
276
  // aGVsbG8gd29ybGQ=
716
277
  ```
717
278
 
718
- Similar to the `encode` method, you can use the `urlEncode` to generate a base64 string safe to pass in a URL.
279
+ ### urlEncode
719
280
 
720
- The `urlEncode` method performs the following replacements.
281
+ The `urlEncode` method returns a base64 string safe for use inside a URL. The following characters are replaced.
721
282
 
722
- - Replace `+` with `-`.
723
- - Replace `/` with `_`.
724
- - And remove the `=` sign from the end of the string.
283
+ - The `+` character is replaced with `-`.
284
+ - The `/` character is replaced with `_`.
285
+ - Trailing `=` sign is removed.
725
286
 
726
287
  ```ts
727
288
  base64.urlEncode('hello world')
728
289
  // aGVsbG8gd29ybGQ
729
290
  ```
730
291
 
731
- You can use the `decode` and the `urlDecode` methods to decode a previously encoded base64 string.
292
+ ### decode
293
+
294
+ Decode a previously encoded base64 string. By default, a `null` value is returned when the string cannot be decoded. However, you can turn on the strict mode to throw an exception instead.
732
295
 
733
296
  ```ts
734
297
  base64.decode(base64.encode('hello world'))
735
- // hello world
736
298
 
737
- base64.urlDecode(base64.urlEncode('hello world'))
738
- // hello world
299
+ base64.decode('foo') // null
300
+ base64.decode('foo', true) // throws error
739
301
  ```
740
302
 
741
- 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.
303
+ ### urlDecode
304
+
305
+ Decode a previously URL-encoded base64 string. By default, a `null` value is returned when the string cannot be decoded. However, you can turn on the strict mode to throw an exception instead.
742
306
 
743
307
  ```ts
744
- base64.decode('hello world') // null
745
- base64.decode('hello world', 'utf-8', true) // raises exception
308
+ base64.urlDecode(base64.urlEncode('hello world'))
309
+
310
+ base64.urlDecode('foo') // null
311
+ base64.urlDecode('foo', true) // throws error
746
312
  ```
747
313
 
748
- #### compose
314
+ ## compose
749
315
 
750
- 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.
316
+ The `compose` helper allows you to use TypeScript class mixins with a cleaner API. Following is an example of mixin usage without the compose helper.
751
317
 
752
318
  ```ts
753
319
  class User extends UserWithAttributes(UserWithAge(UserWithPassword(UserWithEmail(BaseModel)))) {}
754
320
  ```
755
321
 
756
- Following is an example with the `compose` helper.
757
-
758
- - There is no nesting.
759
- - The order of mixins is from left to right. Whereas earlier, it was inside out.
322
+ The following is an example of the `compose` helper. As you can notice, the `compose` removes nesting and applies mixins from left to right.
760
323
 
761
324
  ```ts
762
325
  import { compose } from '@poppinss/utils'
@@ -770,13 +333,14 @@ class User extends compose(
770
333
  ) {}
771
334
  ```
772
335
 
773
- #### defineStaticProperty
336
+ ## defineStaticProperty
774
337
 
775
- The `defineStaticProperty` method allows you to define static properties on a class with different reference strategies.
338
+ ### PROBLEM STATEMENT
776
339
 
777
- 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.
340
+ If you use class inheritance alongside static properties, you will either share properties by reference or define them directly on the parent class.
778
341
 
779
- In the following example, we are not inherting `columns` from the `AppModel`. Instead, we define a new set of columns on the `UserModel`.
342
+ **Redefining a property**\
343
+ In the following example, we re-define the `static columns` member on the `UserModel` class.
780
344
 
781
345
  ```ts
782
346
  class AppModel {
@@ -788,7 +352,8 @@ class UserModel extends AppModel {
788
352
  }
789
353
  ```
790
354
 
791
- 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.
355
+ **Sharing by reference**\
356
+ In the following example, we are share the `static columns` between the `AppModel` and the `UserModel` classes. However, mutating the property via the `UserModel` will also impact the `AppModel` (not something we want).
792
357
 
793
358
  ```ts
794
359
  class AppModel {
@@ -799,7 +364,9 @@ class UserModel extends AppModel {}
799
364
  UserModel.columns.push('username')
800
365
  ```
801
366
 
802
- The ideal behavior is to deep clone the `columns` array and then push new values to it.
367
+ ### SOLUTION
368
+
369
+ To solve the mutation side-effect, you must deep-clone the `columns` array from the parent class and re-define them on `UserModel` class.
803
370
 
804
371
  ```ts
805
372
  import lodash from '@poppinss/utils/lodash'
@@ -808,64 +375,45 @@ class AppModel {
808
375
  static columns = ['id']
809
376
  }
810
377
 
811
- const inheritedColumns = lodash.cloneDeep(AppModel.columns)
812
378
  class UserModel extends AppModel {
813
- static columns = inheritedColumns.push('username')
379
+ static columns = lodash.cloneDeep(AppModel.columns)
814
380
  }
381
+
382
+ UserModel.columns.push('username')
815
383
  ```
816
384
 
817
- 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.
385
+ The `defineStaticProperty` method abstracts the logic of cloning the values. Member values are only cloned when the same member is not defined as an `ownProperty`.
818
386
 
819
387
  ```ts
820
- class UserModel extends AppModel {}
388
+ import { defineStaticProperty } from '@poppinss/utils'
821
389
 
390
+ class AppModel {
391
+ static columns = ['id']
392
+ }
393
+
394
+ class UserModel extends AppModel {}
822
395
  defineStaticProperty(UserModel, 'columns', {
823
396
  strategy: 'inherit',
824
397
  initialValue: [],
825
398
  })
826
399
  ```
827
400
 
828
- - The `inherit` strategy clones the value from the parent class.
829
- - The `define` strategy always re-defines the property, discarding any values on the parent class.
830
- - The `strategy` value can be function to perform a custom clone operations.
831
-
832
- #### Exception
833
-
834
- 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.
835
-
836
- ```ts
837
- import { Exception } from '@poppinss/utils/exception'
838
-
839
- class ResourceNotFound extends Exception {
840
- static code = 'E_RESOURCE_NOT_FOUND'
841
- static status = 404
842
- static message = 'Unable to find resource'
843
- }
844
-
845
- throw new ResourceNotFound()
846
- ```
847
-
848
- #### Anonymous error classes
849
-
850
- You can also create an anonymous exception class using the `createError` method. The return value is a class
851
- constructor that accepts an array of values to use for interpolation.
401
+ #### AVAILABLE STRATEGIES
852
402
 
853
- The interpolation of error message is performed using the `util.format` message.
403
+ <dl>
404
+ <dt>inherit</dt>
405
+ <dd>The <code>inherit</code> strategy clones the value from the parent class.</dd>
854
406
 
855
- ```ts
856
- import { createError } from '@poppinss/utils/exception'
857
- const E_RESOURCE_NOT_FOUND = createError(
858
- 'Unable to find resource with id %d',
859
- 'E_RESOURCE_NOT_FOUND'
860
- )
407
+ <dt>define</dt>
408
+ <dd>The <code>define</code> strategy always re-defines the property, discarding any values on the parent class.</dd>
861
409
 
862
- const id = 1
863
- throw new E_RESOURCE_NOT_FOUND([id])
864
- ```
410
+ <dt>callback</dt>
411
+ <dd>The <code>strategy</code> value can be a function to perform custom clone operations.</dd>
412
+ </dl>
865
413
 
866
- #### flatten
414
+ ## flatten
867
415
 
868
- 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.
416
+ 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.
869
417
 
870
418
  ```ts
871
419
  import { flatten } from '@poppinss/utils'
@@ -907,65 +455,9 @@ flatten({
907
455
  // }
908
456
  ```
909
457
 
910
- #### fsReadAll
911
-
912
- 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.
913
-
914
- ```ts
915
- import { fsReadAll } from '@poppinss/utils'
916
-
917
- const files = await fsReadAll(new URL('./config', import.meta.url), { pathType: 'url' })
918
- await Promise.all(files.map((file) => import(file)))
919
- ```
920
-
921
- You can also pass the options along with the directory path as the second argument.
922
-
923
- ```ts
924
- type Options = {
925
- ignoreMissingRoot?: boolean
926
- filter?: (filePath: string, index: number) => boolean
927
- sort?: (current: string, next: string) => number
928
- pathType?: 'relative' | 'unixRelative' | 'absolute' | 'unixAbsolute' | 'url'
929
- }
930
-
931
- const options: Partial<Options> = {}
932
- await fsReadAll(location, options)
933
- ```
934
-
935
- | Argument | Type | Description |
936
- | ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
937
- | `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. |
938
- | `filter` | method | Define a filter to ignore certain paths. The method is called on the final list of files. |
939
- | `sort` | method | Define a custom method to sort file paths. By default, the files are sorted using natural sort. |
940
- | `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'` |
941
-
942
- #### fsImportAll
458
+ ## isScriptFile
943
459
 
944
- The `fsImportAll` method imports all the files recursively from a given directory and set the exported value from each module on an object.
945
-
946
- ```ts
947
- import { fsImportAll } from '@poppinss/utils'
948
-
949
- const collection = await fsImportAll(new URL('./config', import.meta.url))
950
- console.log(collection)
951
- ```
952
-
953
- - Collection is an object with a tree of key-value pair.
954
- - The key is the nested object created from the file path.
955
- - 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.
956
-
957
- The second param is the options to customize the import behavior.
958
-
959
- | Argument | Type | Description |
960
- | ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
961
- | `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. |
962
- | `filter` | method | Define a filter to ignore certain paths. By default only files ending with `.js`, `.ts`, `.json`, `.cjs`, and `.mjs` are imported. |
963
- | `sort` | method | Define a custom method to sort file paths. By default, the files are sorted using natural sort. |
964
- | `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. |
965
-
966
- #### isScriptFile
967
-
968
- 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.
460
+ 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.
969
461
 
970
462
  ```ts
971
463
  import { isScriptFile } from '@poppinss/utils'
@@ -979,41 +471,18 @@ isScriptFile('foo/bar.ts') // true
979
471
  isScriptFile('foo/bar.d.ts') // false
980
472
  ```
981
473
 
982
- The goal of this method is to use it as a filter with the `fsReadAll` method.
983
-
984
- ```ts
985
- import { fsReadAll, isScriptFile } from '@poppinss/utils'
474
+ ## importDefault
986
475
 
987
- const dir = new URL('./config', import.meta.url)
988
- const options = { pathType: 'url', filter: isScriptFile }
989
-
990
- const files = await fsReadAll(dir, options)
991
-
992
- await Promise.all(
993
- files.map((file) => {
994
- if (file.endsWith('.json')) {
995
- return import(file, { with: { type: 'json' } })
996
- }
997
-
998
- return import(file)
999
- })
1000
- )
1001
- ```
1002
-
1003
- #### importDefault
1004
-
1005
- A helper function that assert a lazy import function output to have a `default export`, otherwise raises an exception.
1006
-
1007
- We use dynamic default exports a lot in AdonisJS apps, so extracting the check to a helper function.
476
+ Returns the default exported value from a dynamic import function. An exception is thrown when the module does not have a `default` export.
1008
477
 
1009
478
  ```ts
1010
479
  import { importDefault } from '@poppinss/utils'
1011
480
  const defaultVal = await importDefault(() => import('./some_module.js'))
1012
481
  ```
1013
482
 
1014
- #### naturalSort
483
+ ## naturalSort
1015
484
 
1016
- A sorting function to use natural sort for ordering an array.
485
+ Sort values of an Array using natural sort.
1017
486
 
1018
487
  ```ts
1019
488
  import { naturalSort } from '@poppinss/utils'
@@ -1025,7 +494,7 @@ const values = ['1_foo_bar', '12_foo_bar'].sort(naturalSort)
1025
494
  // Default sorting: ['1_foo_bar', '12_foo_bar']
1026
495
  ```
1027
496
 
1028
- #### safeEqual
497
+ ## safeEqual
1029
498
 
1030
499
  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).
1031
500
 
@@ -1035,7 +504,7 @@ Under the hood, this method uses Node.js [crypto.timeSafeEqual](https://nodejs.o
1035
504
  import { safeEqual } from '@poppinss/utils'
1036
505
 
1037
506
  /**
1038
- * The trusted value, it might be saved inside the db
507
+ * The trusted value. Might be saved inside the db
1039
508
  */
1040
509
  const trustedValue = 'hello world'
1041
510
 
@@ -1045,24 +514,15 @@ const trustedValue = 'hello world'
1045
514
  const userInput = 'hello'
1046
515
 
1047
516
  if (safeEqual(trustedValue, userInput)) {
1048
- // both are same
517
+ //Both are the same
1049
518
  } else {
1050
- // value mis-match
519
+ // value mismatch
1051
520
  }
1052
521
  ```
1053
522
 
1054
- #### slash
523
+ ## MessageBuilder
1055
524
 
1056
- Convert OS-specific file paths to Unix file paths. Credits [https://github.com/sindresorhus/slash](https://github.com/sindresorhus/slash)
1057
-
1058
- ```ts
1059
- import { slash } from '@poppinss/utils/slash'
1060
- slash('foo\\bar') // foo/bar
1061
- ```
1062
-
1063
- #### MessageBuilder
1064
-
1065
- Message builder is a convenience layer to stringify JavaScript values with an expiry date and a purpose. For example:
525
+ The `MessageBuilder` is used to stringify values with an encoded expiry date and purpose.
1066
526
 
1067
527
  ```ts
1068
528
  import { MessageBuilder } from '@poppinss/utils'
@@ -1087,12 +547,12 @@ const encoded = builder.build(
1087
547
  */
1088
548
  ```
1089
549
 
1090
- 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.
550
+ Once you have the JSON string with the expiration and purpose, you can encrypt it or sign it (to prevent tampering) and share it with the client.
1091
551
 
1092
- During the email verification, you can decrypt the key and then ask the `MessageBuilder` to verify the payload.
552
+ Later, when the encrypted value is presented to perform an action, you must decrypt or unsign it and verify it using the `MessageBuilder`.
1093
553
 
1094
554
  ```ts
1095
- const decoded = builder.verify(value, 'email_verification')
555
+ const decoded = builder.verify(decryptedValue, 'email_verification')
1096
556
  if (!decoded) {
1097
557
  return 'Invalid token'
1098
558
  }
@@ -1100,62 +560,11 @@ if (!decoded) {
1100
560
  console.log(decoded.token)
1101
561
  ```
1102
562
 
1103
- 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.
1104
-
1105
- ```ts
1106
- const decoded = builder.verify(value, 'reset_password')
1107
- ```
1108
-
1109
- #### ObjectBuilder
1110
-
1111
- 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`.
1112
-
1113
- ```ts
1114
- const startingObject = {
1115
- a: 1
1116
- // Add "b", if it exists
1117
- ...(b ? { b } : {})
1118
- }
1119
-
1120
- // OR
1121
- if (b) {
1122
- startingObject.b = b
1123
- }
1124
- ```
1125
-
1126
- Instead of writing conditionals, you can consider using the Object builder fluent API.
1127
-
1128
- ```ts
1129
- const builder = new ObjectBuilder({ a: 1 })
1130
-
1131
- const plainObject = builder.add('b', b).toObject()
1132
- ```
1133
-
1134
- By default, only the `undefined` values are ignored. However, you can also ignore `null` values.
1135
-
1136
- ```ts
1137
- const ignoreNullValues = true
1138
- const builder = new ObjectBuilder({ a: 1 }, ignoreNullValues)
1139
- ```
1140
-
1141
- Following are the available methods on the `ObjectBuilder` class.
1142
-
1143
- ```ts
1144
- builder.remove(key)
1145
- builder.has(key)
1146
- builder.get(key)
1147
- builder.add(key)
1148
-
1149
- builder.toObject() // get plain object
1150
- ```
1151
-
1152
- #### Secret
563
+ ## Secret
1153
564
 
1154
- Creates a secret value that prevents itself from getting logged inside `console.log` statements, during JSON serialization, and string concatenation.
565
+ Wrap a value inside a Secret object to prevent it from leaking inside log statements or serialized payloads.
1155
566
 
1156
- To understand why you need a special `Secret` object, you need to understand the root of the problem. Let's start with an example.
1157
-
1158
- Given 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.
567
+ For example, you issue an opaque token to a user and persist its hash inside the database. The plain token (aka raw value) is shared with the user and should only be visible once (for security reasons).
1159
568
 
1160
569
  ```ts
1161
570
  class Token {
@@ -1171,7 +580,7 @@ const token = new Token().generate()
1171
580
  return response.send(token)
1172
581
  ```
1173
582
 
1174
- At 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.
583
+ At the same time, you want to drop a log statement inside your application that you can later use to debug its flow.
1175
584
 
1176
585
  ```ts
1177
586
  const token = new Token().generate()
@@ -1182,11 +591,9 @@ logger.log('token generated %O', token)
1182
591
  return response.send(token)
1183
592
  ```
1184
593
 
1185
- BOOM! 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.
1186
-
1187
- Now, 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.
594
+ As you can notice above, logging the token also logs its raw value. 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.
1188
595
 
1189
- This is what exactly the `Secret` utility class does for you. Create values that prevent themselves from leaking inside logs or during JSON serialization.
596
+ To prevent this from happening, you can wrap the secret values like an opaque token inside the `Secret` utility class. Logging an instance of the `Secret` class will redact the underlying value.
1190
597
 
1191
598
  ```ts
1192
599
  import { Secret } from '@poppinss/utils'
@@ -1210,8 +617,8 @@ logger.log('token generated %O', token)
1210
617
  return response.send(token)
1211
618
  ```
1212
619
 
1213
- **Need the original value back?**
1214
- You 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.
620
+ **Need the original value back?**\
621
+ You can call the `release` method to regain the original value. 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.
1215
622
 
1216
623
  ```ts
1217
624
  const secret = new Secret('opaque_raw_token')
@@ -1220,35 +627,6 @@ const rawValue = secret.release()
1220
627
  rawValue === opaque_raw_token // true
1221
628
  ```
1222
629
 
1223
- > 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.
1224
-
1225
- #### dirname/filename
1226
-
1227
- ES modules does not have magic variables `__filename` and `__dirname`. You can use these helpers to get the current directory and filenames as follows.
1228
-
1229
- ```ts
1230
- import { getDirname, getFilename } from '@poppinss/utils'
1231
-
1232
- const dirname = getDirname(import.meta.url)
1233
- const filename = getFilename(import.meta.url)
1234
- ```
1235
-
1236
- #### joinToURL
1237
-
1238
- Similar to the Node.js `path.join`, but instead expects the first parameter to be a URL instance or a string with the `file:///` protocol.
1239
-
1240
- The return value is an absolute file system path without the `file:///` protocol.
1241
-
1242
- ```ts
1243
- import { joinToURL } from '@poppinss/utils'
1244
-
1245
- // With URL as a string
1246
- const APP_PATH = joinToURL(import.meta.url, 'app')
1247
-
1248
- // With URL instance
1249
- const APP_PATH = joinToURL(new URL('./', import.meta.url), 'app')
1250
- ```
1251
-
1252
630
  [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/utils/checks.yml?style=for-the-badge
1253
631
  [gh-workflow-url]: https://github.com/poppinss/utils/actions/workflows/checks.yml 'Github action'
1254
632
  [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript