@splendidlabz/utils 1.3.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/.eslintrc.cjs +3 -0
- package/.turbo/turbo-lint.log +10 -0
- package/.turbo/turbo-test.log +10 -0
- package/CHANGELOG.md +37 -0
- package/actions/index.js +5 -0
- package/actions/masonry.js +38 -0
- package/actions/mutation-observer.js +43 -0
- package/actions/prefer-horizontal-scroll.js +125 -0
- package/actions/resize-observer.js +38 -0
- package/actions/sticky.js +88 -0
- package/dom/bounding-box.js +54 -0
- package/dom/clipboard.js +14 -0
- package/dom/cookie.js +31 -0
- package/dom/css-vars.js +11 -0
- package/dom/events.js +76 -0
- package/dom/events.test.js +99 -0
- package/dom/focusable.js +56 -0
- package/dom/font-size.js +24 -0
- package/dom/get-element.js +69 -0
- package/dom/hash.js +9 -0
- package/dom/index.js +18 -0
- package/dom/keyboard.js +39 -0
- package/dom/local-store.js +29 -0
- package/dom/media.js +27 -0
- package/dom/pkce.js +34 -0
- package/dom/query-params.js +14 -0
- package/dom/random-string.js +18 -0
- package/dom/session-store.js +29 -0
- package/dom/trap-focus.js +55 -0
- package/dom/ui/aria-current.js +20 -0
- package/dom/ui/inconsistent-button-fix.js +8 -0
- package/dom/ui/index.js +4 -0
- package/dom/ui/scroll-container.js +25 -0
- package/dom/ui/traverse-and-scramble.js +40 -0
- package/lib/arrays/index.js +62 -0
- package/lib/auth/index.js +1 -0
- package/lib/auth/route-manager.js +44 -0
- package/lib/checks.js +11 -0
- package/lib/date/days.js +9 -0
- package/lib/date/index.js +2 -0
- package/lib/date/months.js +74 -0
- package/lib/form/form-data.js +73 -0
- package/lib/form/index.js +2 -0
- package/lib/form/sanitize.js +47 -0
- package/lib/functions/debounce.js +18 -0
- package/lib/functions/env.js +5 -0
- package/lib/functions/functional.js +31 -0
- package/lib/functions/index.js +5 -0
- package/lib/functions/throttle.js +11 -0
- package/lib/functions/timeout.js +15 -0
- package/lib/index.js +12 -0
- package/lib/index.test.js +9 -0
- package/lib/numbers/index.js +10 -0
- package/lib/objects/camelcase-keys.js +17 -0
- package/lib/objects/camelcase-keys.test.js +72 -0
- package/lib/objects/empty.js +10 -0
- package/lib/objects/extend.js +16 -0
- package/lib/objects/extend.test.js +35 -0
- package/lib/objects/flatten.js +19 -0
- package/lib/objects/index.js +12 -0
- package/lib/objects/json.js +7 -0
- package/lib/objects/loop.js +11 -0
- package/lib/objects/loop.test.js +31 -0
- package/lib/objects/mix/mix.js +100 -0
- package/lib/objects/mix/mix.md +78 -0
- package/lib/objects/mix/mix.test.js +324 -0
- package/lib/objects/nested-property.js +36 -0
- package/lib/objects/normalize-object.js +10 -0
- package/lib/objects/omit-empty.js +23 -0
- package/lib/objects/omit-empty.test.js +85 -0
- package/lib/objects/size.js +41 -0
- package/lib/objects/split.js +19 -0
- package/lib/promises/index.js +1 -0
- package/lib/promises/reject.js +17 -0
- package/lib/strings/convert-case/convert-case.js +86 -0
- package/lib/strings/convert-case/convert-case.md +44 -0
- package/lib/strings/convert-case/convert-case.test.js +50 -0
- package/lib/strings/index.js +4 -0
- package/lib/strings/markdown.js +39 -0
- package/lib/strings/pluralize.js +2 -0
- package/lib/strings/query-string.js +18 -0
- package/lib/style/index.js +11 -0
- package/lib/symbols/index.js +1 -0
- package/lib/symbols/symbols.js +9 -0
- package/node/common.js +5 -0
- package/node/dirname.js +17 -0
- package/node/file-cache.js +135 -0
- package/node/file.js +13 -0
- package/node/hash.js +5 -0
- package/node/index.js +6 -0
- package/node/pkce.js +33 -0
- package/node/random-string.js +10 -0
- package/package.json +35 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Mix
|
|
2
|
+
|
|
3
|
+
- Merges objects. Like `Object.assign`, but performs a deep merge so won't be any mutation.
|
|
4
|
+
- [Why I created `mix`](https://zellwk.com/blog/copy-properties-of-one-object-to-another-object)
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Import the module and use it.
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import mix from '@zellwk/javascript/mix'
|
|
12
|
+
mix(target, source)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
Pass objects into mix to combine them. Mix creates a new object for you automatically. Nothing will get mutated.
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
let count = 0
|
|
21
|
+
const one = { one: 'one' }
|
|
22
|
+
const two = { two: 'two' }
|
|
23
|
+
const mixed = mix(one, two)
|
|
24
|
+
|
|
25
|
+
// Output
|
|
26
|
+
// {
|
|
27
|
+
// one: 'one',
|
|
28
|
+
// two: 'two',
|
|
29
|
+
// }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
1. It copies accessors.
|
|
35
|
+
2. It performs a deep merge so no mutation can happen
|
|
36
|
+
|
|
37
|
+
### Copying accessors
|
|
38
|
+
|
|
39
|
+
Mix copies accessors along with other properties. Most other deep merging libraries don't do this. `Object.assign` doesn't do this either.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
let count = 0
|
|
43
|
+
const one = { one: 'one' }
|
|
44
|
+
const two = { two: 'two' }
|
|
45
|
+
const three = {
|
|
46
|
+
get count () {
|
|
47
|
+
return count
|
|
48
|
+
},
|
|
49
|
+
set count (value) {
|
|
50
|
+
count = value
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const mixed = mix({}, one, two, three)
|
|
54
|
+
|
|
55
|
+
// Output
|
|
56
|
+
// {
|
|
57
|
+
// one: 'one',
|
|
58
|
+
// two: 'two',
|
|
59
|
+
// get count () { return count } ,
|
|
60
|
+
// set count (value) { count = value }
|
|
61
|
+
// }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Deep Merging
|
|
65
|
+
|
|
66
|
+
`mix` copies nested objects and arrays so you don't have to worry about mutation.
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
const one = {}
|
|
70
|
+
const two = { nested: { value: 'two' } }
|
|
71
|
+
const three = mix(one, two)
|
|
72
|
+
|
|
73
|
+
// Nested values do not get mutated
|
|
74
|
+
three.nested.value = 'three'
|
|
75
|
+
console.log(two.nested.value) // 'two'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That's it!
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { mix } from './mix.js'
|
|
4
|
+
|
|
5
|
+
it('Merge objects', () => {
|
|
6
|
+
const one = { one: 'one', overwrite: 'nope' }
|
|
7
|
+
const two = { two: 'two', overwrite: 'yes' }
|
|
8
|
+
const three = mix(one, two)
|
|
9
|
+
|
|
10
|
+
expect(three).toEqual({
|
|
11
|
+
one: 'one',
|
|
12
|
+
two: 'two',
|
|
13
|
+
overwrite: 'yes',
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('Empty + Nested', () => {
|
|
18
|
+
const one = {}
|
|
19
|
+
const two = {
|
|
20
|
+
nested: {
|
|
21
|
+
value: 'nested-two',
|
|
22
|
+
deep: {
|
|
23
|
+
value: 'deep-two',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
const three = mix(one, two)
|
|
28
|
+
|
|
29
|
+
expect(three).toEqual(two)
|
|
30
|
+
expect(three.nested.value).toBe('nested-two')
|
|
31
|
+
expect(three.nested.deep.value).toBe('deep-two')
|
|
32
|
+
|
|
33
|
+
// Prevents mutation of nested object
|
|
34
|
+
three.nested.value = 'three'
|
|
35
|
+
expect(two.nested.value).toBe('nested-two')
|
|
36
|
+
|
|
37
|
+
// Prevents mutation of deeply nested object
|
|
38
|
+
three.nested.deep.value = 'deep-three'
|
|
39
|
+
expect(two.nested.deep.value).toBe('deep-two')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('Double Nested Objects', () => {
|
|
43
|
+
const one = {
|
|
44
|
+
one: 'one',
|
|
45
|
+
nested: {
|
|
46
|
+
one: 'nested-one',
|
|
47
|
+
deep: {
|
|
48
|
+
one: 'deep-one',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const two = {
|
|
54
|
+
two: 'two',
|
|
55
|
+
nested: {
|
|
56
|
+
two: 'nested-two',
|
|
57
|
+
deep: {
|
|
58
|
+
two: 'deep-two',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const three = mix(one, two)
|
|
64
|
+
|
|
65
|
+
expect(three.one).toBe('one')
|
|
66
|
+
expect(three.two).toBe('two')
|
|
67
|
+
expect(three.nested.one).toBe('nested-one')
|
|
68
|
+
expect(three.nested.two).toBe('nested-two')
|
|
69
|
+
expect(three.nested.deep.one).toBe('deep-one')
|
|
70
|
+
expect(three.nested.deep.two).toBe('deep-two')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('Accessors', () => {
|
|
74
|
+
let count = 0
|
|
75
|
+
const one = {}
|
|
76
|
+
const two = {
|
|
77
|
+
get count() {
|
|
78
|
+
return count
|
|
79
|
+
},
|
|
80
|
+
set count(value) {
|
|
81
|
+
count = value
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const three = mix(one, two)
|
|
86
|
+
expect(three).toEqual(two)
|
|
87
|
+
|
|
88
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(three, 'count')
|
|
89
|
+
three.count = 3
|
|
90
|
+
expect(three.count).toBe(3)
|
|
91
|
+
expect(typeof propertyDescriptor.get).toBe('function')
|
|
92
|
+
expect(typeof propertyDescriptor.set).toBe('function')
|
|
93
|
+
expect(two.count !== three.count)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('Empty + Nested accessors', () => {
|
|
97
|
+
let count = 0
|
|
98
|
+
const one = {}
|
|
99
|
+
const two = {
|
|
100
|
+
nested: {
|
|
101
|
+
get count() {
|
|
102
|
+
return count
|
|
103
|
+
},
|
|
104
|
+
set count(value) {
|
|
105
|
+
count = value
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const three = mix(one, two)
|
|
111
|
+
expect(three).toEqual(two)
|
|
112
|
+
expect(two.nested).not.toBe(three.nested)
|
|
113
|
+
|
|
114
|
+
const descriptor = Object.getOwnPropertyDescriptor(three.nested, 'count')
|
|
115
|
+
expect(typeof descriptor.get).toBe('function')
|
|
116
|
+
expect(typeof descriptor.set).toBe('function')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('Double nested with Accessors', () => {
|
|
120
|
+
let count = 0
|
|
121
|
+
const one = {
|
|
122
|
+
nested: {
|
|
123
|
+
get accessOne() {
|
|
124
|
+
return count
|
|
125
|
+
},
|
|
126
|
+
set accessOne(value) {
|
|
127
|
+
count = value
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
const two = {
|
|
132
|
+
nested: {
|
|
133
|
+
get accessTwo() {
|
|
134
|
+
return count
|
|
135
|
+
},
|
|
136
|
+
set accessTwo(value) {
|
|
137
|
+
count = value
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const three = mix(one, two)
|
|
143
|
+
expect(one.nested).not.toBe(three.nested)
|
|
144
|
+
expect(two.nested).not.toBe(three.nested)
|
|
145
|
+
|
|
146
|
+
const accessOne = Object.getOwnPropertyDescriptor(three.nested, 'accessOne')
|
|
147
|
+
const accessTwo = Object.getOwnPropertyDescriptor(three.nested, 'accessTwo')
|
|
148
|
+
|
|
149
|
+
expect(typeof accessOne.get).toBe('function')
|
|
150
|
+
expect(typeof accessOne.set).toBe('function')
|
|
151
|
+
expect(typeof accessTwo.get).toBe('function')
|
|
152
|
+
expect(typeof accessTwo.set).toBe('function')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('Nested arrays', () => {
|
|
156
|
+
const one = {}
|
|
157
|
+
const two = { array: [1, 2, 3] }
|
|
158
|
+
const three = mix(one, two)
|
|
159
|
+
|
|
160
|
+
expect(three).toEqual(two)
|
|
161
|
+
expect(typeof three.array.push).toBe('function')
|
|
162
|
+
|
|
163
|
+
// Prevent mutation
|
|
164
|
+
three.array.push(4)
|
|
165
|
+
expect(two.array).toHaveLength(3)
|
|
166
|
+
expect(three.array).toHaveLength(4)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('Double objects with nested same array', () => {
|
|
170
|
+
const one = {
|
|
171
|
+
array: [1, 2, 3],
|
|
172
|
+
}
|
|
173
|
+
const two = {
|
|
174
|
+
array: [4, 5, 6],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const three = mix(one, two)
|
|
178
|
+
expect(typeof three.array.push).toBe('function')
|
|
179
|
+
expect(three.array).toHaveLength(6)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('Objects in Arrays', () => {
|
|
183
|
+
const one = {}
|
|
184
|
+
const two = {
|
|
185
|
+
array: [
|
|
186
|
+
1,
|
|
187
|
+
{ value: 2 },
|
|
188
|
+
3,
|
|
189
|
+
{
|
|
190
|
+
get count() {
|
|
191
|
+
return 1
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
const three = mix(one, two)
|
|
197
|
+
|
|
198
|
+
expect(three).toEqual(two)
|
|
199
|
+
expect(typeof three.array.push).toBe('function')
|
|
200
|
+
expect(two.array[1]).not.toBe(three.array[1])
|
|
201
|
+
|
|
202
|
+
// Ensures accessors are copied over
|
|
203
|
+
const descriptor = Object.getOwnPropertyDescriptor(three.array[3], 'count')
|
|
204
|
+
expect(typeof descriptor.get).toBe('function')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('Deep nested arrays', () => {
|
|
208
|
+
const one = {}
|
|
209
|
+
const two = {
|
|
210
|
+
array: [1, [2], 3],
|
|
211
|
+
}
|
|
212
|
+
const three = mix(one, two)
|
|
213
|
+
|
|
214
|
+
expect(three).toEqual(two)
|
|
215
|
+
expect(three.array).toHaveLength(3)
|
|
216
|
+
expect(three.array[1]).toHaveLength(1)
|
|
217
|
+
expect(two.array[1]).not.toBe(three.array[1])
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('Functions (cannot be cloned)', () => {
|
|
221
|
+
const one = {}
|
|
222
|
+
const two = {
|
|
223
|
+
nested: {
|
|
224
|
+
identity(i) {
|
|
225
|
+
return i
|
|
226
|
+
},
|
|
227
|
+
deep: {
|
|
228
|
+
identity(i) {
|
|
229
|
+
return i
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const three = mix(one, two)
|
|
236
|
+
expect(three).toEqual(two)
|
|
237
|
+
expect(three.nested.identity).toBe(two.nested.identity)
|
|
238
|
+
expect(three.nested.deep.identity).toBe(two.nested.deep.identity)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('Nested Dates', () => {
|
|
242
|
+
const one = {}
|
|
243
|
+
const two = {
|
|
244
|
+
date: new Date(),
|
|
245
|
+
nested: {
|
|
246
|
+
date: new Date(),
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
const three = mix(one, two)
|
|
250
|
+
|
|
251
|
+
expect(three.date).not.toBe(two.date)
|
|
252
|
+
expect(three.date.getTime()).toBe(two.date.getTime())
|
|
253
|
+
expect(three.nested.date).not.toBe(two.nested.date)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('Nested Maps', () => {
|
|
257
|
+
const one = {}
|
|
258
|
+
const two = {
|
|
259
|
+
map: new Map(),
|
|
260
|
+
nested: {
|
|
261
|
+
map: new Map(),
|
|
262
|
+
},
|
|
263
|
+
}
|
|
264
|
+
const a = { one: 1 }
|
|
265
|
+
const b = { two: 2 }
|
|
266
|
+
two.map.set('item', { one: 1 })
|
|
267
|
+
two.map.set(a, b)
|
|
268
|
+
two.nested.map.set('item', { one: 1 })
|
|
269
|
+
|
|
270
|
+
const three = mix(one, two)
|
|
271
|
+
|
|
272
|
+
expect(three.map).not.toBe(two.map)
|
|
273
|
+
expect(three.map).toEqual(two.map)
|
|
274
|
+
expect(three.map.get('item')).toEqual({ one: 1 })
|
|
275
|
+
expect(three.map.get('item')).not.toBe(two.map.get('item'))
|
|
276
|
+
|
|
277
|
+
expect(three.nested.map).toEqual(two.nested.map)
|
|
278
|
+
expect(three.nested.map).not.toBe(two.nested.map)
|
|
279
|
+
|
|
280
|
+
expect(three.map.get(a)).toEqual(b)
|
|
281
|
+
expect(three.map.get(a)).not.toBe(two.map.get(a))
|
|
282
|
+
|
|
283
|
+
// Note: WeakMap keys are not enumerable... so they cannot be cloned.
|
|
284
|
+
// That's why we're not testing WeakMap
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('Nested Sets', () => {
|
|
288
|
+
const one = {}
|
|
289
|
+
const two = {
|
|
290
|
+
set: new Set(),
|
|
291
|
+
nested: {
|
|
292
|
+
set: new Set(),
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
const a = { object: 'test' }
|
|
296
|
+
two.set.add('one')
|
|
297
|
+
two.set.add(a)
|
|
298
|
+
two.nested.set.add('nested')
|
|
299
|
+
|
|
300
|
+
const three = mix(one, two)
|
|
301
|
+
|
|
302
|
+
expect(three.set).not.toBe(two.set)
|
|
303
|
+
expect(three.set).toEqual(two.set)
|
|
304
|
+
expect(three.set.has('one')).toBe(true)
|
|
305
|
+
expect(three.set.has(a)).toBe(false)
|
|
306
|
+
|
|
307
|
+
expect(three.nested.set).not.toBe(two.nested.set)
|
|
308
|
+
expect(three.nested.set).toEqual(two.nested.set)
|
|
309
|
+
expect(three.nested.set.has('nested')).toBe(true)
|
|
310
|
+
|
|
311
|
+
// Note: WeakSet contains objects only... so there's no point creating a deep clone. You won't be able to access them.
|
|
312
|
+
// That's why we're not testing WeakSet
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// Prevents Prototype Pollution
|
|
316
|
+
// https://codeburst.io/what-is-prototype-pollution-49482fc4b638
|
|
317
|
+
it('Safe Merge', () => {
|
|
318
|
+
const polluted = JSON.parse('{"__proto__": {"admin": true}}')
|
|
319
|
+
const mixed = mix(polluted)
|
|
320
|
+
|
|
321
|
+
/* eslint-disable */
|
|
322
|
+
expect(mixed.__proto__).toEqual({})
|
|
323
|
+
/* eslint-enable */
|
|
324
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the value at the given path in the object.
|
|
3
|
+
* If the path does not exist, it returns undefined.
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} object - The object to get the nested property from.
|
|
6
|
+
* @param {Array} pathArr - The array representing the path to the property.
|
|
7
|
+
* @return {*} The value at the given path in the object or undefined if the path does not exist.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* let object = {
|
|
11
|
+
* one: {
|
|
12
|
+
* two: {
|
|
13
|
+
* three: "value"
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* };
|
|
17
|
+
* let path = ['one', 'two', 'three'];
|
|
18
|
+
* console.log(getNestedProperty(object, path)); // Outputs: "value"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Array syntax
|
|
22
|
+
export function getNestedProperty(object, pathArr) {
|
|
23
|
+
return pathArr.reduce(
|
|
24
|
+
(nestedObject, key) =>
|
|
25
|
+
nestedObject && nestedObject[key] !== 'undefined'
|
|
26
|
+
? nestedObject[key]
|
|
27
|
+
: undefined,
|
|
28
|
+
object,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Dot syntax
|
|
33
|
+
export function getNestedProperty2(object, path) {
|
|
34
|
+
const pathArray = path.split('.')
|
|
35
|
+
return getNestedProperty(object, pathArray)
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { pipe } from '../functions/functional.js'
|
|
2
|
+
import { camelCaseKeys } from './camelcase-keys.js'
|
|
3
|
+
import { omitEmpty } from './omit-empty.js'
|
|
4
|
+
|
|
5
|
+
// Deprecated. Should remove.
|
|
6
|
+
// Not using anymore.
|
|
7
|
+
// Because snake_case is quite common.
|
|
8
|
+
export function normalizeObject(object) {
|
|
9
|
+
return pipe(omitEmpty, camelCaseKeys)(object)
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Written by Claude AI and tested.
|
|
2
|
+
export function omitEmpty(obj, shallow = false) {
|
|
3
|
+
if (typeof obj !== 'object' || obj === null) return obj
|
|
4
|
+
if (obj instanceof Date) return obj
|
|
5
|
+
|
|
6
|
+
const result = Array.isArray(obj) ? [] : {}
|
|
7
|
+
|
|
8
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
9
|
+
if (
|
|
10
|
+
value === null ||
|
|
11
|
+
value === undefined ||
|
|
12
|
+
value === '' ||
|
|
13
|
+
(typeof value === 'object' &&
|
|
14
|
+
!(value instanceof Date) &&
|
|
15
|
+
Object.keys(value).length === 0)
|
|
16
|
+
) {
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
result[key] = shallow ? value : omitEmpty(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result
|
|
23
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { omitEmpty } from './omit-empty'
|
|
4
|
+
|
|
5
|
+
it('Omit Empty Basic', ctx => {
|
|
6
|
+
const date = new Date()
|
|
7
|
+
const object = {
|
|
8
|
+
// Should not be omitted
|
|
9
|
+
a: { one: 1 },
|
|
10
|
+
b: [1],
|
|
11
|
+
c: 1,
|
|
12
|
+
d: 0,
|
|
13
|
+
e: 'string',
|
|
14
|
+
k: date,
|
|
15
|
+
l: false,
|
|
16
|
+
|
|
17
|
+
// Should be omitted
|
|
18
|
+
f: {},
|
|
19
|
+
g: [],
|
|
20
|
+
h: '',
|
|
21
|
+
i: null,
|
|
22
|
+
j: undefined,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const expected = {
|
|
26
|
+
a: { one: 1 },
|
|
27
|
+
b: [1],
|
|
28
|
+
c: 1,
|
|
29
|
+
d: 0,
|
|
30
|
+
e: 'string',
|
|
31
|
+
k: date,
|
|
32
|
+
l: false,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = omitEmpty(object)
|
|
36
|
+
expect(result).toEqual(expected)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Omit Empty Nested', ctx => {
|
|
40
|
+
const object = {
|
|
41
|
+
a: {
|
|
42
|
+
one: {},
|
|
43
|
+
two: [],
|
|
44
|
+
three: 1,
|
|
45
|
+
four: 'string',
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const expected = {
|
|
50
|
+
a: {
|
|
51
|
+
three: 1,
|
|
52
|
+
four: 'string',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = omitEmpty(object)
|
|
57
|
+
expect(result).toEqual(expected)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('Omit Empty Recursive', ctx => {
|
|
61
|
+
const object = {
|
|
62
|
+
a: {
|
|
63
|
+
one: {},
|
|
64
|
+
two: [],
|
|
65
|
+
three: {
|
|
66
|
+
alpha: {},
|
|
67
|
+
beta: [],
|
|
68
|
+
tetha: 1,
|
|
69
|
+
gamma: 'string',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const expected = {
|
|
75
|
+
a: {
|
|
76
|
+
three: {
|
|
77
|
+
tetha: 1,
|
|
78
|
+
gamma: 'string',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = omitEmpty(object)
|
|
84
|
+
expect(result).toEqual(expected)
|
|
85
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// https://gist.github.com/rajinwonderland/36887887b8a8f12063f1d672e318e12e
|
|
2
|
+
export function sizeOf(obj) {
|
|
3
|
+
let bytes = 0
|
|
4
|
+
|
|
5
|
+
function size(obj) {
|
|
6
|
+
if (obj !== null && obj !== undefined) {
|
|
7
|
+
switch (typeof obj) {
|
|
8
|
+
case 'number':
|
|
9
|
+
bytes += 8
|
|
10
|
+
break
|
|
11
|
+
case 'string':
|
|
12
|
+
bytes += obj.length * 2
|
|
13
|
+
break
|
|
14
|
+
case 'boolean':
|
|
15
|
+
bytes += 4
|
|
16
|
+
break
|
|
17
|
+
case 'object':
|
|
18
|
+
// eslint-disable-next-line no-case-declarations
|
|
19
|
+
const objClass = Object.prototype.toString.call(obj).slice(8, -1)
|
|
20
|
+
if (objClass === 'Object' || objClass === 'Array') {
|
|
21
|
+
for (const key in obj) {
|
|
22
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
23
|
+
if (!obj.hasOwnProperty(key)) continue
|
|
24
|
+
size(obj[key])
|
|
25
|
+
}
|
|
26
|
+
} else bytes += obj.toString().length * 2
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return bytes
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatByteSize(bytes) {
|
|
34
|
+
if (bytes < 1024) return bytes + ' bytes'
|
|
35
|
+
else if (bytes < 1048576) return (bytes / 1024).toFixed(3) + ' KiB'
|
|
36
|
+
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(3) + ' MiB'
|
|
37
|
+
else return (bytes / 1073741824).toFixed(3) + ' GiB'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return formatByteSize(size(obj))
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split on object up based on the keys provided.
|
|
3
|
+
* @param {Object} obj
|
|
4
|
+
* @param {Array} keys
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function splitObject(obj, keys) {
|
|
8
|
+
const picked = {}
|
|
9
|
+
const omitted = { ...obj }
|
|
10
|
+
|
|
11
|
+
keys.forEach(key => {
|
|
12
|
+
if (key in obj) {
|
|
13
|
+
picked[key] = obj[key]
|
|
14
|
+
delete omitted[key]
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return { picked, omitted }
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './reject.js'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import statuses from 'statuses'
|
|
2
|
+
import { omitEmpty } from '../objects/omit-empty.js'
|
|
3
|
+
|
|
4
|
+
// Rejects a promise with HTTP Status Code
|
|
5
|
+
export function reject(props, { includeStack = true } = {}) {
|
|
6
|
+
const { status, statusText, body, message } = props
|
|
7
|
+
const e = new Error()
|
|
8
|
+
const err = {
|
|
9
|
+
status,
|
|
10
|
+
statusText: statusText || (status && statuses(status)),
|
|
11
|
+
body,
|
|
12
|
+
message,
|
|
13
|
+
stack: includeStack && e.stack,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return Promise.reject(omitEmpty(err))
|
|
17
|
+
}
|