@poppinss/utils 6.9.4 → 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.
- package/README.md +204 -826
- package/build/index.d.ts +4 -24
- package/build/index.js +103 -232
- package/build/{src → modules}/base64.d.ts +2 -2
- package/build/modules/exception.d.ts +1 -0
- package/build/{src → modules/fs}/fs_import_all.d.ts +6 -1
- package/build/{src → modules/fs}/fs_read_all.d.ts +6 -1
- package/build/modules/fs/main.d.ts +2 -0
- package/build/modules/json/main.d.ts +2 -0
- package/build/{src → modules}/json/safe_parse.d.ts +1 -1
- package/build/{src → modules}/json/safe_stringify.d.ts +1 -1
- package/build/modules/string/string_builder.d.ts +2 -0
- package/build/modules/types.d.ts +1 -0
- package/build/src/compose.d.ts +12 -10
- package/build/src/define_static_property.d.ts +2 -4
- package/package.json +18 -18
- package/build/chunk-2KG3PWR4.js +0 -17
- package/build/chunk-4V5ON6P7.js +0 -7
- package/build/chunk-EJKUJ44Y.js +0 -12
- package/build/chunk-H54AK3PI.js +0 -7
- package/build/chunk-XHQBV7AF.js +0 -39
- package/build/src/assert.js +0 -33
- package/build/src/exception.d.ts +0 -1
- package/build/src/exception.js +0 -9
- package/build/src/exceptions/main.d.ts +0 -1
- package/build/src/exceptions/main.js +0 -9
- package/build/src/json/main.d.ts +0 -7
- package/build/src/json/main.js +0 -7
- package/build/src/object_builder.d.ts +0 -1
- package/build/src/slash.d.ts +0 -24
- package/build/src/slash.js +0 -7
- package/build/src/string/main.js +0 -8
- package/build/src/string/string_builder.d.ts +0 -3
- package/build/src/string/string_builder.js +0 -9
- package/build/src/types.d.ts +0 -35
- package/build/src/types.js +0 -0
- /package/build/{src → modules}/assert.d.ts +0 -0
- /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
|
|
7
|
+
## Why does this package exist?
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
15
|
+
- The rest are some handwritten utilities that fit my needs.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
## Re-exported packages
|
|
18
18
|
|
|
19
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
73
|
+
### safeStringify
|
|
208
74
|
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
const value = {
|
|
82
|
+
b: 2,
|
|
83
|
+
c: BigInt(10),
|
|
84
|
+
}
|
|
479
85
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// hello world
|
|
86
|
+
// Circular reference
|
|
87
|
+
value.a = value
|
|
483
88
|
|
|
484
|
-
|
|
485
|
-
//
|
|
89
|
+
safeStringify(value)
|
|
90
|
+
// '{"b":2,"c":"10"}'
|
|
486
91
|
```
|
|
487
92
|
|
|
488
|
-
|
|
93
|
+
## Lodash helpers
|
|
489
94
|
|
|
490
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
You can use the methods as follows.
|
|
505
113
|
|
|
506
114
|
```ts
|
|
507
|
-
|
|
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
|
-
|
|
514
|
-
string.seconds.parse(180) // 180
|
|
117
|
+
lodash.pick(collection, keys)
|
|
515
118
|
```
|
|
516
119
|
|
|
517
|
-
|
|
120
|
+
## FS helpers
|
|
518
121
|
|
|
519
|
-
|
|
520
|
-
string.seconds.format(36000) // 10h
|
|
521
|
-
string.seconds.format(36000, true) // 10 hours
|
|
522
|
-
```
|
|
122
|
+
### fsReadAll
|
|
523
123
|
|
|
524
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
-
string.milliseconds.parse('1 day') // 8.64e7
|
|
531
|
-
```
|
|
131
|
+
import { fsReadAll } from '@poppinss/utils/fs'
|
|
532
132
|
|
|
533
|
-
|
|
133
|
+
const basePath = new URL('./config', import.meta.url)
|
|
134
|
+
const files = await fsReadAll(basePath, { pathType: 'url' })
|
|
534
135
|
|
|
535
|
-
|
|
536
|
-
string.milliseconds.parse(180) // 180
|
|
136
|
+
console.log(files)
|
|
537
137
|
```
|
|
538
138
|
|
|
539
|
-
|
|
139
|
+
#### OPTIONS
|
|
540
140
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
155
|
+
### fsImportAll
|
|
556
156
|
|
|
557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
const configDir = new URL('./config', import.meta.url)
|
|
166
|
+
const collection = await fsImportAll(configDir)
|
|
572
167
|
|
|
573
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
+
## Assertion helpers
|
|
648
215
|
|
|
649
|
-
The following assertion methods
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
268
|
+
### encode
|
|
708
269
|
|
|
709
|
-
|
|
270
|
+
Base64 encodes a string or a Buffer value.
|
|
710
271
|
|
|
711
272
|
```ts
|
|
712
|
-
import
|
|
273
|
+
import base64 from '@poppinss/utils/base64'
|
|
713
274
|
|
|
714
275
|
base64.encode('hello world')
|
|
715
276
|
// aGVsbG8gd29ybGQ=
|
|
716
277
|
```
|
|
717
278
|
|
|
718
|
-
|
|
279
|
+
### urlEncode
|
|
719
280
|
|
|
720
|
-
The `urlEncode` method
|
|
281
|
+
The `urlEncode` method returns a base64 string safe for use inside a URL. The following characters are replaced.
|
|
721
282
|
|
|
722
|
-
-
|
|
723
|
-
-
|
|
724
|
-
-
|
|
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
|
-
|
|
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.
|
|
738
|
-
//
|
|
299
|
+
base64.decode('foo') // null
|
|
300
|
+
base64.decode('foo', true) // throws error
|
|
739
301
|
```
|
|
740
302
|
|
|
741
|
-
|
|
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.
|
|
745
|
-
|
|
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
|
-
|
|
314
|
+
## compose
|
|
749
315
|
|
|
750
|
-
The `compose` helper allows you to use TypeScript class mixins with a cleaner API. Following is an example of
|
|
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
|
-
|
|
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
|
-
|
|
336
|
+
## defineStaticProperty
|
|
774
337
|
|
|
775
|
-
|
|
338
|
+
### PROBLEM STATEMENT
|
|
776
339
|
|
|
777
|
-
If you use class inheritance alongside static properties,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
+
<dl>
|
|
404
|
+
<dt>inherit</dt>
|
|
405
|
+
<dd>The <code>inherit</code> strategy clones the value from the parent class.</dd>
|
|
854
406
|
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
863
|
-
|
|
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
|
-
|
|
414
|
+
## flatten
|
|
867
415
|
|
|
868
|
-
Create a flat object from a nested object/array. The nested keys are combined with a dot
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
```ts
|
|
985
|
-
import { fsReadAll, isScriptFile } from '@poppinss/utils'
|
|
474
|
+
## importDefault
|
|
986
475
|
|
|
987
|
-
|
|
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
|
-
|
|
483
|
+
## naturalSort
|
|
1015
484
|
|
|
1016
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
517
|
+
//Both are the same
|
|
1049
518
|
} else {
|
|
1050
|
-
// value
|
|
519
|
+
// value mismatch
|
|
1051
520
|
}
|
|
1052
521
|
```
|
|
1053
522
|
|
|
1054
|
-
|
|
523
|
+
## MessageBuilder
|
|
1055
524
|
|
|
1056
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
565
|
+
Wrap a value inside a Secret object to prevent it from leaking inside log statements or serialized payloads.
|
|
1155
566
|
|
|
1156
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|