@ditojs/utils 1.21.0 → 1.23.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 +9 -8
- package/src/object/clone.js +46 -32
- package/src/object/clone.test.js +16 -0
- package/src/string/deindent.js +22 -11
- package/types/index.d.ts +25 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.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",
|
|
@@ -14,9 +14,6 @@
|
|
|
14
14
|
"src/",
|
|
15
15
|
"types/"
|
|
16
16
|
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"types": "tsc --noEmit types/index.d.ts"
|
|
19
|
-
},
|
|
20
17
|
"engines": {
|
|
21
18
|
"node": ">= 18.0.0",
|
|
22
19
|
"yarn": ">= 1.0.0"
|
|
@@ -29,11 +26,15 @@
|
|
|
29
26
|
"ie_mob >= 11"
|
|
30
27
|
],
|
|
31
28
|
"dependencies": {
|
|
32
|
-
"punycode": "^2.
|
|
29
|
+
"punycode": "^2.3.0"
|
|
33
30
|
},
|
|
34
31
|
"devDependencies": {
|
|
35
|
-
"typescript": "^4.9.
|
|
32
|
+
"typescript": "^4.9.5"
|
|
36
33
|
},
|
|
37
34
|
"types": "types",
|
|
38
|
-
"gitHead": "
|
|
39
|
-
|
|
35
|
+
"gitHead": "3258fd030226062efa6837bfc5e5a761e80cead5",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"types": "tsc --noEmit types/index.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"readme": "# Dito.js Utility Functions\n\nDito.js is a declarative and modern web framework with a focus on API driven\ndevelopment, based on Koa.js, Objection.js and Vue.js\n\nReleased in 2018 under the MIT license, with support by https://lineto.com/\n"
|
|
40
|
+
}
|
package/src/object/clone.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isArray, isObject, isDate, isRegExp, isFunction, isPromise
|
|
3
3
|
} from '../base/index.js'
|
|
4
|
-
import { pick } from '
|
|
4
|
+
import { pick } from './pick.js'
|
|
5
5
|
|
|
6
|
-
export function clone(
|
|
6
|
+
export function clone(value, options) {
|
|
7
7
|
const {
|
|
8
8
|
shallow = false,
|
|
9
9
|
enumerable = true,
|
|
@@ -14,44 +14,58 @@ export function clone(arg, options) {
|
|
|
14
14
|
? { processValue: options } // TODO: `callback` deprecated in December 2022.
|
|
15
15
|
: options || {}
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
? value
|
|
19
|
-
: clone(value, options)
|
|
17
|
+
const clones = new Map()
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
copy
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
const storeClone = (value, copy) => {
|
|
20
|
+
clones.set(value, copy)
|
|
21
|
+
return copy
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handleValue = value => {
|
|
25
|
+
return shallow || transferables?.includes(value) ? value
|
|
26
|
+
: clones.has(value) ? clones.get(value)
|
|
27
|
+
: cloneValue(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const cloneValue = value => {
|
|
31
|
+
let copy = value
|
|
32
|
+
if (isDate(value)) {
|
|
33
|
+
copy = storeClone(value, new value.constructor(+value))
|
|
34
|
+
} else if (isRegExp(value)) {
|
|
35
|
+
copy = storeClone(value, new value.constructor(value))
|
|
36
|
+
} else if (isArray(value)) {
|
|
37
|
+
copy = storeClone(value, new value.constructor(value.length))
|
|
38
|
+
for (let i = 0, l = value.length; i < l; i++) {
|
|
39
|
+
copy[i] = handleValue(value[i])
|
|
40
|
+
}
|
|
41
|
+
} else if (isObject(value)) {
|
|
32
42
|
// Rely on arg.clone() if it exists and assume it creates an actual clone.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
if (isFunction(value.clone)) {
|
|
44
|
+
copy = storeClone(value, value.clone(options))
|
|
45
|
+
} else if (isPromise(value)) {
|
|
36
46
|
// https://stackoverflow.com/questions/37063293/can-i-clone-a-promise
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
copy = storeClone(value, value.then())
|
|
48
|
+
} else {
|
|
39
49
|
// Prevent calling the actual constructor since it is not guaranteed to
|
|
40
50
|
// work as intended here, and only clone the non-inherited own properties.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
desc.value
|
|
51
|
+
copy = storeClone(value, Object.create(Object.getPrototypeOf(value)))
|
|
52
|
+
clones.set(value, copy)
|
|
53
|
+
const keys = enumerable ? Object.keys(value) : Reflect.ownKeys(value)
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (descriptors) {
|
|
56
|
+
const desc = Reflect.getOwnPropertyDescriptor(value, key)
|
|
57
|
+
if (desc.value != null) {
|
|
58
|
+
desc.value = handleValue(desc.value)
|
|
59
|
+
}
|
|
60
|
+
Reflect.defineProperty(copy, key, desc)
|
|
61
|
+
} else {
|
|
62
|
+
copy[key] = handleValue(value[key])
|
|
48
63
|
}
|
|
49
|
-
Reflect.defineProperty(copy, key, desc)
|
|
50
|
-
} else {
|
|
51
|
-
copy[key] = handleValue(arg[key])
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
}
|
|
67
|
+
return pick(processValue?.(copy), copy)
|
|
55
68
|
}
|
|
56
|
-
|
|
69
|
+
|
|
70
|
+
return cloneValue(value)
|
|
57
71
|
}
|
package/src/object/clone.test.js
CHANGED
|
@@ -60,6 +60,22 @@ describe('clone()', () => {
|
|
|
60
60
|
expect(copy).toStrictEqual({ b: 2 })
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
+
it('should preserve identity', async () => {
|
|
64
|
+
const object = { a: { b: 1 } }
|
|
65
|
+
object.c = object.a
|
|
66
|
+
const copy = clone(object)
|
|
67
|
+
expect(copy).toEqual(object)
|
|
68
|
+
expect(copy.c).toBe(copy.a)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle circular references', async () => {
|
|
72
|
+
const object = { a: { b: 1 } }
|
|
73
|
+
object.c = object
|
|
74
|
+
const copy = clone(object)
|
|
75
|
+
expect(copy).toEqual(object)
|
|
76
|
+
expect(copy.c).toBe(copy)
|
|
77
|
+
})
|
|
78
|
+
|
|
63
79
|
it('should handle non-enumerable properties', async () => {
|
|
64
80
|
const object = { a: 1 }
|
|
65
81
|
Object.defineProperty(object, 'b', { value: 2, enumerable: false })
|
package/src/string/deindent.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import os from 'os'
|
|
2
1
|
import { asArray } from '../base/index.js'
|
|
3
2
|
|
|
4
3
|
// ES6 string tag that strips indentation from multi-line strings
|
|
5
4
|
// Based on, and further improved from https://github.com/dmnd/dedent
|
|
6
5
|
export function deindent(strings, ...values) {
|
|
6
|
+
// Small helper to determine the actual line-break used in the strings, based
|
|
7
|
+
// on the line-breaks encountered.
|
|
8
|
+
const getLineBreak = line => line.match(/^(\n|\r\n|\r)/)?.[1] || '\n'
|
|
9
|
+
|
|
7
10
|
strings = asArray(strings)
|
|
8
11
|
// First, perform interpolation
|
|
9
12
|
let parts = []
|
|
@@ -21,16 +24,18 @@ export function deindent(strings, ...values) {
|
|
|
21
24
|
if (lines.length > 1) {
|
|
22
25
|
// Determine the indent by finding the immediately preceding white-space
|
|
23
26
|
// up to the previous line-break or the beginning of the string.
|
|
24
|
-
// (
|
|
25
|
-
//
|
|
26
|
-
// (\
|
|
27
|
-
// (
|
|
28
|
-
//
|
|
27
|
+
// (?:^[\n\r]*|[\n\r]) # Start at either the beginning or the previous
|
|
28
|
+
// # line break.
|
|
29
|
+
// (?:[\n\r]*) # The line break
|
|
30
|
+
// (\s+) # Collect the indenting white-space...
|
|
31
|
+
// (?:[^\n\r]*)$ # ...up to the end or the next word, but in last
|
|
32
|
+
// # line, by making sure no line breaks follow
|
|
33
|
+
// # until the end.
|
|
29
34
|
const str = parts.join('')
|
|
30
|
-
const match = str.match(/(
|
|
35
|
+
const match = str.match(/((?:^[\n\r]*|[\n\r])(?:[\n\r]*))(\s+)(?:[^\n\r]*)$/)
|
|
36
|
+
const [, lineBreaks = '', indent = ''] = match || []
|
|
31
37
|
parts = [str]
|
|
32
|
-
|
|
33
|
-
parts.push(lines.join(`${os.EOL}${indent}`))
|
|
38
|
+
parts.push(lines.join(`${getLineBreak(lineBreaks)}${indent}`))
|
|
34
39
|
} else {
|
|
35
40
|
parts.push(value)
|
|
36
41
|
}
|
|
@@ -42,7 +47,11 @@ export function deindent(strings, ...values) {
|
|
|
42
47
|
// const value = `
|
|
43
48
|
// content...
|
|
44
49
|
// ` // line break at the end remains
|
|
45
|
-
|
|
50
|
+
let offset = 0
|
|
51
|
+
if (!lines[0]) {
|
|
52
|
+
lines.shift()
|
|
53
|
+
offset++
|
|
54
|
+
}
|
|
46
55
|
let indent = Infinity
|
|
47
56
|
for (const line of lines) {
|
|
48
57
|
const match = line.match(/^(\s*)\S+/)
|
|
@@ -50,5 +59,7 @@ export function deindent(strings, ...values) {
|
|
|
50
59
|
indent = Math.min(indent, match[1].length)
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
|
-
|
|
62
|
+
const index = lines[0].length + offset
|
|
63
|
+
const lineBreak = getLineBreak(str.slice(index, index + 2))
|
|
64
|
+
return lines.map(line => line.slice(indent)).join(lineBreak)
|
|
54
65
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -108,9 +108,32 @@ export function asFunction<T>(
|
|
|
108
108
|
/* --------------------------------- object --------------------------------- */
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
111
|
+
* Clones the supplied value.
|
|
112
112
|
*/
|
|
113
|
-
export function clone<T>(arg: T,
|
|
113
|
+
export function clone<T>(arg: T, options?: {
|
|
114
|
+
/**
|
|
115
|
+
* Wether to clone the value shallowly.
|
|
116
|
+
*
|
|
117
|
+
* @default false
|
|
118
|
+
*/
|
|
119
|
+
shallow?: boolean,
|
|
120
|
+
/**
|
|
121
|
+
* Wether to clone all object properties, or only those that are defined
|
|
122
|
+
* directly on that object, and are not inherited from the object's prototype.
|
|
123
|
+
*
|
|
124
|
+
* @default true
|
|
125
|
+
*/
|
|
126
|
+
enumerable?: boolean,
|
|
127
|
+
/**
|
|
128
|
+
* @default `true` if `options.enumerable` is true, `false` otherwise
|
|
129
|
+
*/
|
|
130
|
+
descriptors?: boolean,
|
|
131
|
+
transferables?: any[],
|
|
132
|
+
/**
|
|
133
|
+
* Optional callback to process the cloned value.
|
|
134
|
+
*/
|
|
135
|
+
processValue?: <S>(arg: T) => S,
|
|
136
|
+
}): T
|
|
114
137
|
|
|
115
138
|
/**
|
|
116
139
|
* Determines whether the supplied values can be considered equal through
|