@ditojs/utils 1.23.0 → 1.25.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/package.json +2 -2
- package/src/__snapshots__/index.test.js.snap +3 -1
- package/src/dataPath/getEntriesAtDataPath.js +36 -12
- package/src/dataPath/getEntriesAtDataPath.test.js +61 -1
- package/src/dataPath/getValueAtDataPath.js +16 -7
- package/src/dataPath/getValueAtDataPath.test.js +48 -1
- package/src/dataPath/parseDataPath.test.js +1 -1
- package/src/index.js +1 -0
- package/src/promise/index.js +2 -0
- package/src/promise/mapConcurrently.js +32 -0
- package/src/promise/mapSequentially.js +9 -0
- package/src/string/format.test.js +2 -2
- package/src/string/formatDate.test.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Utility Functions – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/master/packages/utils",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"typescript": "^4.9.5"
|
|
33
33
|
},
|
|
34
34
|
"types": "types",
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "29e7f8d8c613c48a856d90d1ee876e85c8d84c08",
|
|
36
36
|
"scripts": {
|
|
37
37
|
"types": "tsc --noEmit types/index.d.ts"
|
|
38
38
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Vitest Snapshot v1
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`index > exports all symbols 1`] = `
|
|
4
4
|
{
|
|
@@ -62,7 +62,9 @@ exports[`index > exports all symbols 1`] = `
|
|
|
62
62
|
"isString": [Function],
|
|
63
63
|
"isUrl": [Function],
|
|
64
64
|
"labelize": [Function],
|
|
65
|
+
"mapConcurrently": [Function],
|
|
65
66
|
"mapKeys": [Function],
|
|
67
|
+
"mapSequentially": [Function],
|
|
66
68
|
"mapValues": [Function],
|
|
67
69
|
"merge": [Function],
|
|
68
70
|
"mixin": [Function],
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import { parseDataPath } from './parseDataPath.js'
|
|
2
2
|
import { normalizeDataPath } from './normalizeDataPath.js'
|
|
3
3
|
|
|
4
|
+
const NOT_FOUND = Symbol('NOT_FOUND')
|
|
5
|
+
|
|
4
6
|
export function getEntriesAtDataPath(
|
|
5
7
|
obj,
|
|
6
8
|
path,
|
|
7
9
|
handleError = () => { throw new Error(`Invalid path: ${path}`) }
|
|
8
10
|
) {
|
|
9
11
|
const parsedPath = parseDataPath(path)
|
|
12
|
+
|
|
13
|
+
const prefixEntries = (obj, index, getEntries) => {
|
|
14
|
+
const prefix = normalizeDataPath(parsedPath.slice(0, index))
|
|
15
|
+
return Object.entries(obj).reduce(
|
|
16
|
+
(map, [key, value]) => {
|
|
17
|
+
const pathKey = prefix ? `${prefix}/${key}` : key
|
|
18
|
+
for (const [path, val] of Object.entries(getEntries(value))) {
|
|
19
|
+
if (val !== NOT_FOUND) {
|
|
20
|
+
map[`${pathKey}/${path}`] = val
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return map
|
|
24
|
+
},
|
|
25
|
+
{}
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
let index = 0
|
|
11
30
|
for (const part of parsedPath) {
|
|
12
31
|
if (obj && typeof obj === 'object') {
|
|
@@ -16,18 +35,23 @@ export function getEntriesAtDataPath(
|
|
|
16
35
|
continue
|
|
17
36
|
} else if (part === '*') {
|
|
18
37
|
// Support wildcards on arrays and objects
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
const pathNested = parsedPath.slice(index + 1)
|
|
39
|
+
return prefixEntries(
|
|
40
|
+
obj,
|
|
41
|
+
index,
|
|
42
|
+
value => getEntriesAtDataPath(value, pathNested, handleError)
|
|
43
|
+
)
|
|
44
|
+
} else if (part === '**') {
|
|
45
|
+
// Support wildcards on arrays and objects
|
|
46
|
+
const pathNested = parsedPath.slice(index + 1)
|
|
47
|
+
const pathSelf = parsedPath.slice(index)
|
|
48
|
+
return prefixEntries(
|
|
49
|
+
obj,
|
|
50
|
+
index,
|
|
51
|
+
value => ({
|
|
52
|
+
...getEntriesAtDataPath(value, pathNested, () => NOT_FOUND),
|
|
53
|
+
...getEntriesAtDataPath(value, pathSelf, () => NOT_FOUND)
|
|
54
|
+
})
|
|
31
55
|
)
|
|
32
56
|
}
|
|
33
57
|
}
|
|
@@ -52,7 +52,7 @@ describe('getEntriesAtDataPath()', () => {
|
|
|
52
52
|
.toStrictEqual({})
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
it('should return wildcard matches', () => {
|
|
55
|
+
it('should return shallow wildcard matches', () => {
|
|
56
56
|
const data = {
|
|
57
57
|
object1: {
|
|
58
58
|
array: [
|
|
@@ -141,4 +141,64 @@ describe('getEntriesAtDataPath()', () => {
|
|
|
141
141
|
'object3/array/1/two/name': 'two.two'
|
|
142
142
|
})
|
|
143
143
|
})
|
|
144
|
+
|
|
145
|
+
it('should return deep wildcard matches', () => {
|
|
146
|
+
const data = {
|
|
147
|
+
object: {
|
|
148
|
+
array: [
|
|
149
|
+
{ name: 'one' },
|
|
150
|
+
{ name: 'two' },
|
|
151
|
+
{
|
|
152
|
+
object: {
|
|
153
|
+
one: { name: 'three' },
|
|
154
|
+
two: { name: 'four' }
|
|
155
|
+
},
|
|
156
|
+
array: [
|
|
157
|
+
{ name: 'five' },
|
|
158
|
+
{ name: 'six' },
|
|
159
|
+
{
|
|
160
|
+
object: {
|
|
161
|
+
one: { name: 'seven' },
|
|
162
|
+
two: { name: 'eight' }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const expected = {
|
|
172
|
+
'object/array/0/name': 'one',
|
|
173
|
+
'object/array/1/name': 'two',
|
|
174
|
+
'object/array/2/object/one/name': 'three',
|
|
175
|
+
'object/array/2/object/two/name': 'four',
|
|
176
|
+
'object/array/2/array/0/name': 'five',
|
|
177
|
+
'object/array/2/array/1/name': 'six',
|
|
178
|
+
'object/array/2/array/2/object/one/name': 'seven',
|
|
179
|
+
'object/array/2/array/2/object/two/name': 'eight'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let result = getEntriesAtDataPath(data, '**/name')
|
|
183
|
+
expect(result).toEqual(expected)
|
|
184
|
+
expect(Object.values(result)).toEqual(Object.values(expected))
|
|
185
|
+
|
|
186
|
+
result = getEntriesAtDataPath(data, 'object/**/name')
|
|
187
|
+
expect(result).toEqual(expected)
|
|
188
|
+
expect(Object.values(result)).toEqual(Object.values(expected))
|
|
189
|
+
|
|
190
|
+
expect(getEntriesAtDataPath(data, 'object/**/array/**/name')).toEqual({
|
|
191
|
+
'object/array/2/array/0/name': 'five',
|
|
192
|
+
'object/array/2/array/1/name': 'six',
|
|
193
|
+
'object/array/2/array/2/object/one/name': 'seven',
|
|
194
|
+
'object/array/2/array/2/object/two/name': 'eight'
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
expect(getEntriesAtDataPath(data, 'object/**/object/**/name')).toEqual({
|
|
198
|
+
'object/array/2/object/one/name': 'three',
|
|
199
|
+
'object/array/2/object/two/name': 'four',
|
|
200
|
+
'object/array/2/array/2/object/one/name': 'seven',
|
|
201
|
+
'object/array/2/array/2/object/two/name': 'eight'
|
|
202
|
+
})
|
|
203
|
+
})
|
|
144
204
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { isArray } from '../base/index.js'
|
|
1
|
+
import { isArray, asArray } from '../base/index.js'
|
|
2
2
|
import { parseDataPath } from './parseDataPath.js'
|
|
3
3
|
|
|
4
|
+
const NOT_FOUND = Symbol('NOT_FOUND')
|
|
5
|
+
|
|
4
6
|
export function getValueAtDataPath(
|
|
5
7
|
obj,
|
|
6
8
|
path,
|
|
@@ -16,13 +18,20 @@ export function getValueAtDataPath(
|
|
|
16
18
|
continue
|
|
17
19
|
} else if (part === '*') {
|
|
18
20
|
// Support wildcards on arrays and objects
|
|
19
|
-
const
|
|
21
|
+
const pathNested = parsedPath.slice(index + 1)
|
|
22
|
+
const values = isArray(obj) ? obj : Object.values(obj)
|
|
23
|
+
return values.map(
|
|
24
|
+
value => getValueAtDataPath(value, pathNested, handleError)
|
|
25
|
+
).flat(1)
|
|
26
|
+
} else if (part === '**') {
|
|
27
|
+
// Support deep wildcards on arrays and objects
|
|
28
|
+
const pathNested = parsedPath.slice(index + 1)
|
|
29
|
+
const pathSelf = parsedPath.slice(index)
|
|
20
30
|
const values = isArray(obj) ? obj : Object.values(obj)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return flat > 0 ? results.flat(flat) : results
|
|
31
|
+
return values.map(value => [
|
|
32
|
+
...asArray(getValueAtDataPath(value, pathNested, () => NOT_FOUND)),
|
|
33
|
+
...asArray(getValueAtDataPath(value, pathSelf, () => NOT_FOUND))
|
|
34
|
+
]).flat(1).filter(value => value !== NOT_FOUND)
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
return handleError?.(obj, part, index)
|
|
@@ -40,7 +40,7 @@ describe('getValueAtDataPath()', () => {
|
|
|
40
40
|
.toBe('Error: unknown, 1')
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
it('should return wildcard matches', () => {
|
|
43
|
+
it('should return shallow wildcard matches', () => {
|
|
44
44
|
const data = {
|
|
45
45
|
object1: {
|
|
46
46
|
array: [
|
|
@@ -73,5 +73,52 @@ describe('getValueAtDataPath()', () => {
|
|
|
73
73
|
])
|
|
74
74
|
expect(getValueAtDataPath(data, '*/*/*/name'))
|
|
75
75
|
.toEqual(['one', 'two', 'one', 'two'])
|
|
76
|
+
|
|
77
|
+
expect(getValueAtDataPath(data, '**/name'))
|
|
78
|
+
.toEqual(['one', 'two', 'one', 'two'])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should return deep wildcard matches', () => {
|
|
82
|
+
const data = {
|
|
83
|
+
object: {
|
|
84
|
+
object: {
|
|
85
|
+
array: [
|
|
86
|
+
{ name: 'one' },
|
|
87
|
+
{ name: 'two' },
|
|
88
|
+
{
|
|
89
|
+
object: {
|
|
90
|
+
one: { name: 'three' },
|
|
91
|
+
two: { name: 'four' }
|
|
92
|
+
},
|
|
93
|
+
array: [
|
|
94
|
+
{ name: 'five' },
|
|
95
|
+
{ name: 'six' },
|
|
96
|
+
{
|
|
97
|
+
object: {
|
|
98
|
+
one: { name: 'seven' },
|
|
99
|
+
two: { name: 'eight' }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(getValueAtDataPath(data, 'object/**/name'))
|
|
110
|
+
.toEqual(['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'])
|
|
111
|
+
|
|
112
|
+
expect(getValueAtDataPath(data, 'object/**/object/one/name'))
|
|
113
|
+
.toEqual(['three', 'seven'])
|
|
114
|
+
|
|
115
|
+
expect(getValueAtDataPath(data, 'object/**/object/two/name'))
|
|
116
|
+
.toEqual(['four', 'eight'])
|
|
117
|
+
|
|
118
|
+
expect(getValueAtDataPath(data, 'object/**/array/0/name'))
|
|
119
|
+
.toEqual(['one', 'five'])
|
|
120
|
+
|
|
121
|
+
expect(getValueAtDataPath(data, 'object/**/array/1/name'))
|
|
122
|
+
.toEqual(['two', 'six'])
|
|
76
123
|
})
|
|
77
124
|
})
|
|
@@ -31,7 +31,7 @@ describe('parseDataPath()', () => {
|
|
|
31
31
|
.toStrictEqual(['..', 'object', 'value'])
|
|
32
32
|
expect(parseDataPath('./object/value'))
|
|
33
33
|
.toStrictEqual(['.', 'object', 'value'])
|
|
34
|
-
// This happens when
|
|
34
|
+
// This happens when concatenating a data path with another absolute one, an
|
|
35
35
|
// empty space will be interpreted as "start from scratch" when normalizing.
|
|
36
36
|
expect(parseDataPath('//object/value'))
|
|
37
37
|
.toStrictEqual(['', 'object', 'value'])
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './array/index.js'
|
|
|
4
4
|
export * from './object/index.js'
|
|
5
5
|
export * from './string/index.js'
|
|
6
6
|
export * from './timer/index.js'
|
|
7
|
+
export * from './promise/index.js'
|
|
7
8
|
export * from './function/index.js'
|
|
8
9
|
export * from './dataPath/index.js'
|
|
9
10
|
export * from './html/index.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export async function mapConcurrently(
|
|
2
|
+
input,
|
|
3
|
+
callback,
|
|
4
|
+
{ concurrency = null } = {}
|
|
5
|
+
) {
|
|
6
|
+
const array = await input
|
|
7
|
+
if (concurrency === null) {
|
|
8
|
+
return Promise.all(array.map(callback))
|
|
9
|
+
} else {
|
|
10
|
+
const promises = []
|
|
11
|
+
const results = []
|
|
12
|
+
let index = 0
|
|
13
|
+
|
|
14
|
+
function next() {
|
|
15
|
+
if (index < array.length) {
|
|
16
|
+
const i = index++
|
|
17
|
+
return Promise.resolve(callback(array[i], i, array)).then(result => {
|
|
18
|
+
results[i] = result
|
|
19
|
+
return next()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
while (concurrency-- > 0) {
|
|
25
|
+
const promise = next()
|
|
26
|
+
if (!promise) break
|
|
27
|
+
promises.push(promise)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Promise.all(promises).then(() => results)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -19,7 +19,7 @@ describe('format()', () => {
|
|
|
19
19
|
it('should use the en-US locale by default', () => {
|
|
20
20
|
expect(format(integer)).toBe('123,456,789')
|
|
21
21
|
expect(format(float)).toBe('123,456.789')
|
|
22
|
-
expect(format(date)).toBe('June 9, 2012 at 10:45:30
|
|
22
|
+
expect(format(date)).toBe('June 9, 2012 at 10:45:30 PM')
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
it('should format numbers with different locale and default options', () => {
|
|
@@ -188,7 +188,7 @@ describe('format()', () => {
|
|
|
188
188
|
).toBe('May 24, 2016')
|
|
189
189
|
expect(
|
|
190
190
|
format('2016-05-24T15:54:14.876Z', { date: true, time: true })
|
|
191
|
-
).toBe('May 24, 2016 at 05:54:14
|
|
191
|
+
).toBe('May 24, 2016 at 05:54:14 PM')
|
|
192
192
|
expect(format('123456789', { number: true })).toBe('123,456,789')
|
|
193
193
|
expect(format('123456.789', { number: true })).toBe('123,456.789')
|
|
194
194
|
expect(format('Hello World')).toBe('Hello World')
|
|
@@ -8,8 +8,8 @@ describe('formatDate()', () => {
|
|
|
8
8
|
})
|
|
9
9
|
|
|
10
10
|
it('should use the en-US locale by default', () => {
|
|
11
|
-
expect(formatDate(date)).toBe('June 9, 2012 at 10:45:30
|
|
12
|
-
expect(formatDate(date, {})).toBe('June 9, 2012 at 10:45:30
|
|
11
|
+
expect(formatDate(date)).toBe('June 9, 2012 at 10:45:30 PM')
|
|
12
|
+
expect(formatDate(date, {})).toBe('June 9, 2012 at 10:45:30 PM')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
it('should format dates with different locale and default options', () => {
|