@poppinss/utils 6.9.4 → 7.0.0-next.1

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 (46) hide show
  1. package/README.md +205 -829
  2. package/build/chunk-SKNTF5Q5.js +14 -0
  3. package/build/chunk-XFX47BKO.js +23 -0
  4. package/build/{chunk-XHQBV7AF.js → chunk-YFSCSJNE.js} +3 -9
  5. package/build/index.d.ts +4 -24
  6. package/build/index.js +57 -243
  7. package/build/{src → modules}/assert.js +1 -3
  8. package/build/{src → modules}/base64.d.ts +2 -2
  9. package/build/modules/base64.js +43 -0
  10. package/build/modules/exception.d.ts +1 -0
  11. package/build/modules/exception.js +12 -0
  12. package/build/{src → modules/fs}/fs_import_all.d.ts +6 -1
  13. package/build/{src → modules/fs}/fs_read_all.d.ts +6 -1
  14. package/build/modules/fs/main.d.ts +2 -0
  15. package/build/modules/fs/main.js +82 -0
  16. package/build/modules/json/main.d.ts +2 -0
  17. package/build/modules/json/main.js +8 -0
  18. package/build/{src → modules}/json/safe_parse.d.ts +1 -1
  19. package/build/{src → modules}/json/safe_stringify.d.ts +1 -1
  20. package/build/{src → modules}/string/main.js +1 -3
  21. package/build/modules/string/string_builder.d.ts +2 -0
  22. package/build/modules/string/string_builder.js +6 -0
  23. package/build/modules/types.d.ts +1 -0
  24. package/build/modules/types.js +2 -0
  25. package/build/src/compose.d.ts +12 -10
  26. package/build/src/define_static_property.d.ts +2 -4
  27. package/package.json +26 -27
  28. package/build/chunk-2KG3PWR4.js +0 -17
  29. package/build/chunk-4V5ON6P7.js +0 -7
  30. package/build/chunk-EJKUJ44Y.js +0 -12
  31. package/build/chunk-H54AK3PI.js +0 -7
  32. package/build/src/exception.d.ts +0 -1
  33. package/build/src/exception.js +0 -9
  34. package/build/src/exceptions/main.d.ts +0 -1
  35. package/build/src/exceptions/main.js +0 -9
  36. package/build/src/json/main.d.ts +0 -7
  37. package/build/src/json/main.js +0 -7
  38. package/build/src/object_builder.d.ts +0 -1
  39. package/build/src/slash.d.ts +0 -24
  40. package/build/src/slash.js +0 -7
  41. package/build/src/string/string_builder.d.ts +0 -3
  42. package/build/src/string/string_builder.js +0 -9
  43. package/build/src/types.d.ts +0 -35
  44. package/build/src/types.js +0 -0
  45. /package/build/{src → modules}/assert.d.ts +0 -0
  46. /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,18 +30,17 @@ 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
 
36
- <a href="https://pkg-size.dev/@poppinss/utils@next">
37
- <img src="https://pkg-size.dev/badge/install/319382" title="Install size for @poppinss/utils">
38
- </a>
43
+ <a href="https://pkg-size.dev/@poppinss/utils@next"><img src="https://pkg-size.dev/badge/install/370120" title="Install size for @poppinss/utils"></a>
39
44
 
40
45
  ## Installation
41
46
 
@@ -48,607 +53,167 @@ npm i @poppinss/utils
48
53
  yarn add @poppinss/utils
49
54
  ```
50
55
 
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
56
+ ## JSON helpers
169
57
 
170
- Convert a word to its plural form. The method is exported directly from the [pluralize](https://www.npmjs.com/package/pluralize) package.
58
+ 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
59
 
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
60
+ ### safeParse
187
61
 
188
- This method combines the `singular` and `plural` methods and uses one or the other based on the count. For example:
62
+ 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
63
 
190
64
  ```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.
65
+ import { safeParse } from '@poppinss/utils/json'
201
66
 
202
- ```ts
203
- string.pluralize.addUncountableRule('paper')
204
- string.pluralize.addSingularRule(/singles$/i, 'singular')
67
+ safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
68
+ // { a: 5, b: 6 }
205
69
  ```
206
70
 
207
- #### isPlural
71
+ ### safeStringify
208
72
 
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.
73
+ 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.
74
+ The `safeStringify` method accepts the same set of parameters as the `JSON.stringify` method.
210
75
 
211
76
  ```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
77
+ import { safeStringify } from '@poppinss/utils/json'
477
78
 
478
- Remove multiple whitespaces from a string to a single whitespace.
79
+ const value = {
80
+ b: 2,
81
+ c: BigInt(10),
82
+ }
479
83
 
480
- ```ts
481
- string.condenseWhitespace('hello world')
482
- // hello world
84
+ // Circular reference
85
+ value.a = value
483
86
 
484
- string.condenseWhitespace(' hello world ')
485
- // hello world
87
+ safeStringify(value)
88
+ // '{"b":2,"c":"10"}'
486
89
  ```
487
90
 
488
- #### ordinal
91
+ ## Lodash helpers
489
92
 
490
- Get the ordinal letter for a given number.
93
+ 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
94
 
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
- ```
95
+ > **Why not use something else**: All other helpers I have used are not as accurate or well implemented as lodash.
501
96
 
502
- #### seconds.(parse/format)
97
+ - pick
98
+ - omit
99
+ - has
100
+ - get
101
+ - set
102
+ - unset
103
+ - mergeWith
104
+ - merge
105
+ - size
106
+ - clone
107
+ - cloneDeep
108
+ - toPath
503
109
 
504
- Parse a string-based time expression to seconds.
110
+ You can use the methods as follows.
505
111
 
506
112
  ```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.
113
+ import lodash from '@poppinss/utils/lodash'
512
114
 
513
- ```ts
514
- string.seconds.parse(180) // 180
115
+ lodash.pick(collection, keys)
515
116
  ```
516
117
 
517
- You can format seconds to a pretty string using the `format` method.
118
+ ## FS helpers
518
119
 
519
- ```ts
520
- string.seconds.format(36000) // 10h
521
- string.seconds.format(36000, true) // 10 hours
522
- ```
120
+ ### fsReadAll
523
121
 
524
- #### milliseconds.(parse/format)
122
+ 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
123
 
526
- Parse a string-based time expression to milliseconds.
124
+ - Dot files and directories are ignored.
125
+ - Only files are returned (not directories).
126
+ - You can define how the output paths should be returned. The supported types are `relative`, `absolute`, `unixRelative`, `unixAbsolute`, and `url`.
527
127
 
528
128
  ```ts
529
- string.milliseconds.parse('1 h') // 3.6e6
530
- string.milliseconds.parse('1 day') // 8.64e7
531
- ```
129
+ import { fsReadAll } from '@poppinss/utils/fs'
532
130
 
533
- Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in milliseconds.
131
+ const basePath = new URL('./config', import.meta.url)
132
+ const files = await fsReadAll(basePath, { pathType: 'url' })
534
133
 
535
- ```ts
536
- string.milliseconds.parse(180) // 180
134
+ console.log(files)
537
135
  ```
538
136
 
539
- Using the `format` method, you can format milliseconds to a pretty string.
137
+ #### OPTIONS
540
138
 
541
- ```ts
542
- string.seconds.format(3.6e6) // 1h
543
- string.seconds.format(3.6e6, true) // 1 hour
544
- ```
139
+ <dl>
140
+ <dt>ignoreMissingRoot</dt>
141
+ <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
142
 
546
- #### bytes.(parse/format)
143
+ <dt>filter</dt>
144
+ <dd>Define a filter to ignore certain paths. The method is called on the final list of files.</dd>
547
145
 
548
- Parse a string-based unit expression to bytes.
146
+ <dt>sort</dt>
147
+ <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd>
549
148
 
550
- ```ts
551
- string.bytes.parse('1KB') // 1024
552
- string.bytes.parse('1MB') // 1048576
553
- ```
149
+ <dt>pathType</dt>
150
+ <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>
151
+ </dl>
554
152
 
555
- Passing a numeric value to the `parse` method is returned as it is, assuming the value is already in bytes.
153
+ ### fsImportAll
556
154
 
557
- ```ts
558
- string.bytes.parse(1024) // 1024
559
- ```
155
+ 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
156
 
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.
157
+ - If there are nested directories, then the output will also contain nested objects.
158
+ - 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
159
 
563
160
  ```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
161
+ import { fsImportAll } from '@poppinss/utils/fs'
570
162
 
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.
163
+ const configDir = new URL('./config', import.meta.url)
164
+ const collection = await fsImportAll(configDir)
572
165
 
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')
166
+ console.log(collection)
579
167
  ```
580
168
 
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),
169
+ ```sh
170
+ // title: Directory structure
171
+ ├── js
172
+ │   └── config.cjs
173
+ ├── json
174
+ │   └── main.json
175
+ └── ts
176
+ ├── app.ts
177
+ └── server.ts
178
+ ```
179
+
180
+ ```ts
181
+ // title: Output
182
+ {
183
+ ts: {
184
+ app: {},
185
+ server: {},
186
+ },
187
+ js: {
188
+ config: {},
189
+ },
190
+ json: {
191
+ main: {},
192
+ },
603
193
  }
604
-
605
- // Circular reference
606
- value.a = value
607
-
608
- safeStringify(value)
609
- // '{"b":2,"c":10}'
610
194
  ```
611
195
 
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.
196
+ #### OPTIONS
616
197
 
617
- - You can pass a replacer function as the second parameter.
618
- - And number of spaces as the third parameter.
198
+ <dl>
199
+ <dt>ignoreMissingRoot</dt>
200
+ <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
201
 
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.
202
+ <dt>filter</dt>
203
+ <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
204
 
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'
205
+ <dt>sort</dt>
206
+ <dd>Define a custom method to sort file paths. By default, the files are sorted using natural sort.</dd>
643
207
 
644
- lodash.pick(collection, keys)
645
- ```
208
+ <dt>transformKeys</dt>
209
+ <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>
210
+ </dl>
646
211
 
647
- ### Assertion helpers
212
+ ## Assertion helpers
648
213
 
649
- The following assertion methods offers type-safe approach for writing conditionals and throwing error when the variable has unexpected values.
214
+ The following assertion methods offer a type-safe approach for writing conditionals and throwing errors when the variable has unexpected values.
650
215
 
651
- #### assertExists(message?: string)
216
+ ### assertExists
652
217
 
653
218
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `false`, `null`, or `undefined`.
654
219
 
@@ -658,10 +223,10 @@ import { assertExists } from '@poppinss/utils/assert'
658
223
  const value = false as string | false
659
224
  assertExists(value)
660
225
 
661
- // value is string
226
+ // value is a string
662
227
  ```
663
228
 
664
- #### assertNotNull(value: unknown, message?: string)
229
+ ### assertNotNull
665
230
 
666
231
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `null`.
667
232
 
@@ -671,10 +236,10 @@ import { assertNotNull } from '@poppinss/utils/assert'
671
236
  const value = null as string | null
672
237
  assertNotNull(value)
673
238
 
674
- // value is string
239
+ // value is a string
675
240
  ```
676
241
 
677
- #### assertIsDefined(value: unknown, message?: string)
242
+ ### assertIsDefined
678
243
 
679
244
  Throws [AssertionError](https://nodejs.org/api/assert.html#new-assertassertionerroroptions) when the value is `undefined`.
680
245
 
@@ -684,10 +249,10 @@ import { assertIsDefined } from '@poppinss/utils/assert'
684
249
  const value = undefined as string | undefined
685
250
  assertIsDefined(value)
686
251
 
687
- // value is string
252
+ // value is a string
688
253
  ```
689
254
 
690
- #### assertUnreachable(value: unknown)
255
+ ### assertUnreachable
691
256
 
692
257
  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
258
 
@@ -696,67 +261,63 @@ import { assertUnreachable } from '@poppinss/utils/assert'
696
261
  assertUnreachable()
697
262
  ```
698
263
 
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
- ```
264
+ ## Base64 encoding
706
265
 
707
- #### base64
266
+ ### encode
708
267
 
709
- Utility methods to base64 encode and decode values.
268
+ Base64 encodes a string or a Buffer value.
710
269
 
711
270
  ```ts
712
- import { base64 } from '@poppinss/utils'
271
+ import base64 from '@poppinss/utils/base64'
713
272
 
714
273
  base64.encode('hello world')
715
274
  // aGVsbG8gd29ybGQ=
716
275
  ```
717
276
 
718
- Similar to the `encode` method, you can use the `urlEncode` to generate a base64 string safe to pass in a URL.
277
+ ### urlEncode
719
278
 
720
- The `urlEncode` method performs the following replacements.
279
+ The `urlEncode` method returns a base64 string safe for use inside a URL. The following characters are replaced.
721
280
 
722
- - Replace `+` with `-`.
723
- - Replace `/` with `_`.
724
- - And remove the `=` sign from the end of the string.
281
+ - The `+` character is replaced with `-`.
282
+ - The `/` character is replaced with `_`.
283
+ - Trailing `=` sign is removed.
725
284
 
726
285
  ```ts
727
286
  base64.urlEncode('hello world')
728
287
  // aGVsbG8gd29ybGQ
729
288
  ```
730
289
 
731
- You can use the `decode` and the `urlDecode` methods to decode a previously encoded base64 string.
290
+ ### decode
291
+
292
+ 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
293
 
733
294
  ```ts
734
295
  base64.decode(base64.encode('hello world'))
735
- // hello world
736
296
 
737
- base64.urlDecode(base64.urlEncode('hello world'))
738
- // hello world
297
+ base64.decode('foo') // null
298
+ base64.decode('foo', true) // throws error
739
299
  ```
740
300
 
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.
301
+ ### urlDecode
302
+
303
+ 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
304
 
743
305
  ```ts
744
- base64.decode('hello world') // null
745
- base64.decode('hello world', 'utf-8', true) // raises exception
306
+ base64.urlDecode(base64.urlEncode('hello world'))
307
+
308
+ base64.urlDecode('foo') // null
309
+ base64.urlDecode('foo', true) // throws error
746
310
  ```
747
311
 
748
- #### compose
312
+ ## compose
749
313
 
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.
314
+ 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
315
 
752
316
  ```ts
753
317
  class User extends UserWithAttributes(UserWithAge(UserWithPassword(UserWithEmail(BaseModel)))) {}
754
318
  ```
755
319
 
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.
320
+ 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
321
 
761
322
  ```ts
762
323
  import { compose } from '@poppinss/utils'
@@ -770,13 +331,14 @@ class User extends compose(
770
331
  ) {}
771
332
  ```
772
333
 
773
- #### defineStaticProperty
334
+ ## defineStaticProperty
774
335
 
775
- The `defineStaticProperty` method allows you to define static properties on a class with different reference strategies.
336
+ ### PROBLEM STATEMENT
776
337
 
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.
338
+ If you use class inheritance alongside static properties, you will either share properties by reference or define them directly on the parent class.
778
339
 
779
- In the following example, we are not inherting `columns` from the `AppModel`. Instead, we define a new set of columns on the `UserModel`.
340
+ **Redefining a property**\
341
+ In the following example, we re-define the `static columns` member on the `UserModel` class.
780
342
 
781
343
  ```ts
782
344
  class AppModel {
@@ -788,7 +350,8 @@ class UserModel extends AppModel {
788
350
  }
789
351
  ```
790
352
 
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.
353
+ **Sharing by reference**\
354
+ 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
355
 
793
356
  ```ts
794
357
  class AppModel {
@@ -799,7 +362,9 @@ class UserModel extends AppModel {}
799
362
  UserModel.columns.push('username')
800
363
  ```
801
364
 
802
- The ideal behavior is to deep clone the `columns` array and then push new values to it.
365
+ ### SOLUTION
366
+
367
+ 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
368
 
804
369
  ```ts
805
370
  import lodash from '@poppinss/utils/lodash'
@@ -808,64 +373,45 @@ class AppModel {
808
373
  static columns = ['id']
809
374
  }
810
375
 
811
- const inheritedColumns = lodash.cloneDeep(AppModel.columns)
812
376
  class UserModel extends AppModel {
813
- static columns = inheritedColumns.push('username')
377
+ static columns = lodash.cloneDeep(AppModel.columns)
814
378
  }
379
+
380
+ UserModel.columns.push('username')
815
381
  ```
816
382
 
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.
383
+ 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
384
 
819
385
  ```ts
820
- class UserModel extends AppModel {}
386
+ import { defineStaticProperty } from '@poppinss/utils'
821
387
 
388
+ class AppModel {
389
+ static columns = ['id']
390
+ }
391
+
392
+ class UserModel extends AppModel {}
822
393
  defineStaticProperty(UserModel, 'columns', {
823
394
  strategy: 'inherit',
824
395
  initialValue: [],
825
396
  })
826
397
  ```
827
398
 
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.
399
+ #### AVAILABLE STRATEGIES
852
400
 
853
- The interpolation of error message is performed using the `util.format` message.
401
+ <dl>
402
+ <dt>inherit</dt>
403
+ <dd>The <code>inherit</code> strategy clones the value from the parent class.</dd>
854
404
 
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
- )
405
+ <dt>define</dt>
406
+ <dd>The <code>define</code> strategy always re-defines the property, discarding any values on the parent class.</dd>
861
407
 
862
- const id = 1
863
- throw new E_RESOURCE_NOT_FOUND([id])
864
- ```
408
+ <dt>callback</dt>
409
+ <dd>The <code>strategy</code> value can be a function to perform custom clone operations.</dd>
410
+ </dl>
865
411
 
866
- #### flatten
412
+ ## flatten
867
413
 
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.
414
+ 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
415
 
870
416
  ```ts
871
417
  import { flatten } from '@poppinss/utils'
@@ -907,65 +453,9 @@ flatten({
907
453
  // }
908
454
  ```
909
455
 
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
456
+ ## isScriptFile
943
457
 
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.
458
+ 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
459
 
970
460
  ```ts
971
461
  import { isScriptFile } from '@poppinss/utils'
@@ -979,41 +469,18 @@ isScriptFile('foo/bar.ts') // true
979
469
  isScriptFile('foo/bar.d.ts') // false
980
470
  ```
981
471
 
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'
472
+ ## importDefault
986
473
 
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.
474
+ Returns the default exported value from a dynamic import function. An exception is thrown when the module does not have a `default` export.
1008
475
 
1009
476
  ```ts
1010
477
  import { importDefault } from '@poppinss/utils'
1011
478
  const defaultVal = await importDefault(() => import('./some_module.js'))
1012
479
  ```
1013
480
 
1014
- #### naturalSort
481
+ ## naturalSort
1015
482
 
1016
- A sorting function to use natural sort for ordering an array.
483
+ Sort values of an Array using natural sort.
1017
484
 
1018
485
  ```ts
1019
486
  import { naturalSort } from '@poppinss/utils'
@@ -1025,7 +492,7 @@ const values = ['1_foo_bar', '12_foo_bar'].sort(naturalSort)
1025
492
  // Default sorting: ['1_foo_bar', '12_foo_bar']
1026
493
  ```
1027
494
 
1028
- #### safeEqual
495
+ ## safeEqual
1029
496
 
1030
497
  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
498
 
@@ -1035,7 +502,7 @@ Under the hood, this method uses Node.js [crypto.timeSafeEqual](https://nodejs.o
1035
502
  import { safeEqual } from '@poppinss/utils'
1036
503
 
1037
504
  /**
1038
- * The trusted value, it might be saved inside the db
505
+ * The trusted value. Might be saved inside the db
1039
506
  */
1040
507
  const trustedValue = 'hello world'
1041
508
 
@@ -1045,24 +512,15 @@ const trustedValue = 'hello world'
1045
512
  const userInput = 'hello'
1046
513
 
1047
514
  if (safeEqual(trustedValue, userInput)) {
1048
- // both are same
515
+ //Both are the same
1049
516
  } else {
1050
- // value mis-match
517
+ // value mismatch
1051
518
  }
1052
519
  ```
1053
520
 
1054
- #### slash
521
+ ## MessageBuilder
1055
522
 
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:
523
+ The `MessageBuilder` is used to stringify values with an encoded expiry date and purpose.
1066
524
 
1067
525
  ```ts
1068
526
  import { MessageBuilder } from '@poppinss/utils'
@@ -1087,12 +545,12 @@ const encoded = builder.build(
1087
545
  */
1088
546
  ```
1089
547
 
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.
548
+ 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
549
 
1092
- During the email verification, you can decrypt the key and then ask the `MessageBuilder` to verify the payload.
550
+ Later, when the encrypted value is presented to perform an action, you must decrypt or unsign it and verify it using the `MessageBuilder`.
1093
551
 
1094
552
  ```ts
1095
- const decoded = builder.verify(value, 'email_verification')
553
+ const decoded = builder.verify(decryptedValue, 'email_verification')
1096
554
  if (!decoded) {
1097
555
  return 'Invalid token'
1098
556
  }
@@ -1100,62 +558,11 @@ if (!decoded) {
1100
558
  console.log(decoded.token)
1101
559
  ```
1102
560
 
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
561
+ ## Secret
1153
562
 
1154
- Creates a secret value that prevents itself from getting logged inside `console.log` statements, during JSON serialization, and string concatenation.
563
+ Wrap a value inside a Secret object to prevent it from leaking inside log statements or serialized payloads.
1155
564
 
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.
565
+ 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
566
 
1160
567
  ```ts
1161
568
  class Token {
@@ -1171,7 +578,7 @@ const token = new Token().generate()
1171
578
  return response.send(token)
1172
579
  ```
1173
580
 
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.
581
+ At the same time, you want to drop a log statement inside your application that you can later use to debug its flow.
1175
582
 
1176
583
  ```ts
1177
584
  const token = new Token().generate()
@@ -1182,11 +589,9 @@ logger.log('token generated %O', token)
1182
589
  return response.send(token)
1183
590
  ```
1184
591
 
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.
592
+ 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
593
 
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.
594
+ 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
595
 
1191
596
  ```ts
1192
597
  import { Secret } from '@poppinss/utils'
@@ -1210,8 +615,8 @@ logger.log('token generated %O', token)
1210
615
  return response.send(token)
1211
616
  ```
1212
617
 
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.
618
+ **Need the original value back?**\
619
+ 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
620
 
1216
621
  ```ts
1217
622
  const secret = new Secret('opaque_raw_token')
@@ -1220,35 +625,6 @@ const rawValue = secret.release()
1220
625
  rawValue === opaque_raw_token // true
1221
626
  ```
1222
627
 
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
628
  [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/utils/checks.yml?style=for-the-badge
1253
629
  [gh-workflow-url]: https://github.com/poppinss/utils/actions/workflows/checks.yml 'Github action'
1254
630
  [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript