@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.
Files changed (93) hide show
  1. package/.eslintrc.cjs +3 -0
  2. package/.turbo/turbo-lint.log +10 -0
  3. package/.turbo/turbo-test.log +10 -0
  4. package/CHANGELOG.md +37 -0
  5. package/actions/index.js +5 -0
  6. package/actions/masonry.js +38 -0
  7. package/actions/mutation-observer.js +43 -0
  8. package/actions/prefer-horizontal-scroll.js +125 -0
  9. package/actions/resize-observer.js +38 -0
  10. package/actions/sticky.js +88 -0
  11. package/dom/bounding-box.js +54 -0
  12. package/dom/clipboard.js +14 -0
  13. package/dom/cookie.js +31 -0
  14. package/dom/css-vars.js +11 -0
  15. package/dom/events.js +76 -0
  16. package/dom/events.test.js +99 -0
  17. package/dom/focusable.js +56 -0
  18. package/dom/font-size.js +24 -0
  19. package/dom/get-element.js +69 -0
  20. package/dom/hash.js +9 -0
  21. package/dom/index.js +18 -0
  22. package/dom/keyboard.js +39 -0
  23. package/dom/local-store.js +29 -0
  24. package/dom/media.js +27 -0
  25. package/dom/pkce.js +34 -0
  26. package/dom/query-params.js +14 -0
  27. package/dom/random-string.js +18 -0
  28. package/dom/session-store.js +29 -0
  29. package/dom/trap-focus.js +55 -0
  30. package/dom/ui/aria-current.js +20 -0
  31. package/dom/ui/inconsistent-button-fix.js +8 -0
  32. package/dom/ui/index.js +4 -0
  33. package/dom/ui/scroll-container.js +25 -0
  34. package/dom/ui/traverse-and-scramble.js +40 -0
  35. package/lib/arrays/index.js +62 -0
  36. package/lib/auth/index.js +1 -0
  37. package/lib/auth/route-manager.js +44 -0
  38. package/lib/checks.js +11 -0
  39. package/lib/date/days.js +9 -0
  40. package/lib/date/index.js +2 -0
  41. package/lib/date/months.js +74 -0
  42. package/lib/form/form-data.js +73 -0
  43. package/lib/form/index.js +2 -0
  44. package/lib/form/sanitize.js +47 -0
  45. package/lib/functions/debounce.js +18 -0
  46. package/lib/functions/env.js +5 -0
  47. package/lib/functions/functional.js +31 -0
  48. package/lib/functions/index.js +5 -0
  49. package/lib/functions/throttle.js +11 -0
  50. package/lib/functions/timeout.js +15 -0
  51. package/lib/index.js +12 -0
  52. package/lib/index.test.js +9 -0
  53. package/lib/numbers/index.js +10 -0
  54. package/lib/objects/camelcase-keys.js +17 -0
  55. package/lib/objects/camelcase-keys.test.js +72 -0
  56. package/lib/objects/empty.js +10 -0
  57. package/lib/objects/extend.js +16 -0
  58. package/lib/objects/extend.test.js +35 -0
  59. package/lib/objects/flatten.js +19 -0
  60. package/lib/objects/index.js +12 -0
  61. package/lib/objects/json.js +7 -0
  62. package/lib/objects/loop.js +11 -0
  63. package/lib/objects/loop.test.js +31 -0
  64. package/lib/objects/mix/mix.js +100 -0
  65. package/lib/objects/mix/mix.md +78 -0
  66. package/lib/objects/mix/mix.test.js +324 -0
  67. package/lib/objects/nested-property.js +36 -0
  68. package/lib/objects/normalize-object.js +10 -0
  69. package/lib/objects/omit-empty.js +23 -0
  70. package/lib/objects/omit-empty.test.js +85 -0
  71. package/lib/objects/size.js +41 -0
  72. package/lib/objects/split.js +19 -0
  73. package/lib/promises/index.js +1 -0
  74. package/lib/promises/reject.js +17 -0
  75. package/lib/strings/convert-case/convert-case.js +86 -0
  76. package/lib/strings/convert-case/convert-case.md +44 -0
  77. package/lib/strings/convert-case/convert-case.test.js +50 -0
  78. package/lib/strings/index.js +4 -0
  79. package/lib/strings/markdown.js +39 -0
  80. package/lib/strings/pluralize.js +2 -0
  81. package/lib/strings/query-string.js +18 -0
  82. package/lib/style/index.js +11 -0
  83. package/lib/symbols/index.js +1 -0
  84. package/lib/symbols/symbols.js +9 -0
  85. package/node/common.js +5 -0
  86. package/node/dirname.js +17 -0
  87. package/node/file-cache.js +135 -0
  88. package/node/file.js +13 -0
  89. package/node/hash.js +5 -0
  90. package/node/index.js +6 -0
  91. package/node/pkce.js +33 -0
  92. package/node/random-string.js +10 -0
  93. 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
+ }