@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.
- package/README.md +205 -829
- package/build/chunk-SKNTF5Q5.js +14 -0
- package/build/chunk-XFX47BKO.js +23 -0
- package/build/{chunk-XHQBV7AF.js → chunk-YFSCSJNE.js} +3 -9
- package/build/index.d.ts +4 -24
- package/build/index.js +57 -243
- package/build/{src → modules}/assert.js +1 -3
- package/build/{src → modules}/base64.d.ts +2 -2
- package/build/modules/base64.js +43 -0
- package/build/modules/exception.d.ts +1 -0
- package/build/modules/exception.js +12 -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/fs/main.js +82 -0
- package/build/modules/json/main.d.ts +2 -0
- package/build/modules/json/main.js +8 -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/{src → modules}/string/main.js +1 -3
- package/build/modules/string/string_builder.d.ts +2 -0
- package/build/modules/string/string_builder.js +6 -0
- package/build/modules/types.d.ts +1 -0
- package/build/modules/types.js +2 -0
- package/build/src/compose.d.ts +12 -10
- package/build/src/define_static_property.d.ts +2 -4
- package/package.json +26 -27
- 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/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/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,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
|
|
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
|
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
71
|
+
### safeStringify
|
|
208
72
|
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
const value = {
|
|
80
|
+
b: 2,
|
|
81
|
+
c: BigInt(10),
|
|
82
|
+
}
|
|
479
83
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// hello world
|
|
84
|
+
// Circular reference
|
|
85
|
+
value.a = value
|
|
483
86
|
|
|
484
|
-
|
|
485
|
-
//
|
|
87
|
+
safeStringify(value)
|
|
88
|
+
// '{"b":2,"c":"10"}'
|
|
486
89
|
```
|
|
487
90
|
|
|
488
|
-
|
|
91
|
+
## Lodash helpers
|
|
489
92
|
|
|
490
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
You can use the methods as follows.
|
|
505
111
|
|
|
506
112
|
```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.
|
|
113
|
+
import lodash from '@poppinss/utils/lodash'
|
|
512
114
|
|
|
513
|
-
|
|
514
|
-
string.seconds.parse(180) // 180
|
|
115
|
+
lodash.pick(collection, keys)
|
|
515
116
|
```
|
|
516
117
|
|
|
517
|
-
|
|
118
|
+
## FS helpers
|
|
518
119
|
|
|
519
|
-
|
|
520
|
-
string.seconds.format(36000) // 10h
|
|
521
|
-
string.seconds.format(36000, true) // 10 hours
|
|
522
|
-
```
|
|
120
|
+
### fsReadAll
|
|
523
121
|
|
|
524
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
-
string.milliseconds.parse('1 day') // 8.64e7
|
|
531
|
-
```
|
|
129
|
+
import { fsReadAll } from '@poppinss/utils/fs'
|
|
532
130
|
|
|
533
|
-
|
|
131
|
+
const basePath = new URL('./config', import.meta.url)
|
|
132
|
+
const files = await fsReadAll(basePath, { pathType: 'url' })
|
|
534
133
|
|
|
535
|
-
|
|
536
|
-
string.milliseconds.parse(180) // 180
|
|
134
|
+
console.log(files)
|
|
537
135
|
```
|
|
538
136
|
|
|
539
|
-
|
|
137
|
+
#### OPTIONS
|
|
540
138
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
153
|
+
### fsImportAll
|
|
556
154
|
|
|
557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
+
const configDir = new URL('./config', import.meta.url)
|
|
164
|
+
const collection = await fsImportAll(configDir)
|
|
572
165
|
|
|
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')
|
|
166
|
+
console.log(collection)
|
|
579
167
|
```
|
|
580
168
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
212
|
+
## Assertion helpers
|
|
648
213
|
|
|
649
|
-
The following assertion methods
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
+
### encode
|
|
708
267
|
|
|
709
|
-
|
|
268
|
+
Base64 encodes a string or a Buffer value.
|
|
710
269
|
|
|
711
270
|
```ts
|
|
712
|
-
import
|
|
271
|
+
import base64 from '@poppinss/utils/base64'
|
|
713
272
|
|
|
714
273
|
base64.encode('hello world')
|
|
715
274
|
// aGVsbG8gd29ybGQ=
|
|
716
275
|
```
|
|
717
276
|
|
|
718
|
-
|
|
277
|
+
### urlEncode
|
|
719
278
|
|
|
720
|
-
The `urlEncode` method
|
|
279
|
+
The `urlEncode` method returns a base64 string safe for use inside a URL. The following characters are replaced.
|
|
721
280
|
|
|
722
|
-
-
|
|
723
|
-
-
|
|
724
|
-
-
|
|
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
|
-
|
|
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.
|
|
738
|
-
//
|
|
297
|
+
base64.decode('foo') // null
|
|
298
|
+
base64.decode('foo', true) // throws error
|
|
739
299
|
```
|
|
740
300
|
|
|
741
|
-
|
|
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.
|
|
745
|
-
|
|
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
|
-
|
|
312
|
+
## compose
|
|
749
313
|
|
|
750
|
-
The `compose` helper allows you to use TypeScript class mixins with a cleaner API. Following is an example of
|
|
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
|
-
|
|
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
|
-
|
|
334
|
+
## defineStaticProperty
|
|
774
335
|
|
|
775
|
-
|
|
336
|
+
### PROBLEM STATEMENT
|
|
776
337
|
|
|
777
|
-
If you use class inheritance alongside static properties,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
+
<dl>
|
|
402
|
+
<dt>inherit</dt>
|
|
403
|
+
<dd>The <code>inherit</code> strategy clones the value from the parent class.</dd>
|
|
854
404
|
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
863
|
-
|
|
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
|
-
|
|
412
|
+
## flatten
|
|
867
413
|
|
|
868
|
-
Create a flat object from a nested object/array. The nested keys are combined with a dot
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
```ts
|
|
985
|
-
import { fsReadAll, isScriptFile } from '@poppinss/utils'
|
|
472
|
+
## importDefault
|
|
986
473
|
|
|
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.
|
|
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
|
-
|
|
481
|
+
## naturalSort
|
|
1015
482
|
|
|
1016
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
515
|
+
//Both are the same
|
|
1049
516
|
} else {
|
|
1050
|
-
// value
|
|
517
|
+
// value mismatch
|
|
1051
518
|
}
|
|
1052
519
|
```
|
|
1053
520
|
|
|
1054
|
-
|
|
521
|
+
## MessageBuilder
|
|
1055
522
|
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
563
|
+
Wrap a value inside a Secret object to prevent it from leaking inside log statements or serialized payloads.
|
|
1155
564
|
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|