@lumjs/core 1.0.0-beta.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/CHANGELOG.md +17 -0
- package/README.md +27 -0
- package/TODO.md +23 -0
- package/index.js +64 -0
- package/package.json +15 -0
- package/src/context.js +47 -0
- package/src/descriptors.js +243 -0
- package/src/enum.js +123 -0
- package/src/flags.js +45 -0
- package/src/lazy.js +102 -0
- package/src/meta.js +61 -0
- package/src/obj/clone.js +201 -0
- package/src/obj/copyall.js +18 -0
- package/src/obj/copyprops.js +104 -0
- package/src/obj/index.js +18 -0
- package/src/obj/lock.js +64 -0
- package/src/obj/merge.js +74 -0
- package/src/obj/ns.js +194 -0
- package/src/objectid.js +85 -0
- package/src/observable.js +292 -0
- package/src/opt.js +60 -0
- package/src/prop.js +170 -0
- package/src/strings.js +76 -0
- package/src/types.js +545 -0
- package/test/types.js +185 -0
package/src/meta.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get a stacktrace. Differs from browser to browser.
|
|
5
|
+
*/
|
|
6
|
+
function stacktrace(msg)
|
|
7
|
+
{
|
|
8
|
+
return (new Error(msg)).stack.split("\n");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.stacktrace = stacktrace;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Abstract classes for Javascript.
|
|
15
|
+
*/
|
|
16
|
+
class AbstractClass
|
|
17
|
+
{
|
|
18
|
+
/**
|
|
19
|
+
* You must override the constructor.
|
|
20
|
+
*/
|
|
21
|
+
constructor()
|
|
22
|
+
{
|
|
23
|
+
const name = this.constructor.name;
|
|
24
|
+
throw new Error(`Cannot create instance of abstract class ${name}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If you want to mark a method as abstract use this.
|
|
29
|
+
*/
|
|
30
|
+
$abstract(name)
|
|
31
|
+
{
|
|
32
|
+
throw new Error(`Abstract method ${name}() was not implemented`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.AbstractClass = AbstractClass;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Function prototypes for async, generator, and async generator functions.
|
|
41
|
+
*/
|
|
42
|
+
const Functions =
|
|
43
|
+
{
|
|
44
|
+
/**
|
|
45
|
+
* Constructor for dynamic generator functions.
|
|
46
|
+
*/
|
|
47
|
+
Generator: Object.getPrototypeOf(function*(){}).constructor,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Constructor for dynamic async functions.
|
|
51
|
+
*/
|
|
52
|
+
Async: Object.getPrototypeOf(async function(){}).constructor,
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Constructor for dynamic async generator functions.
|
|
56
|
+
*/
|
|
57
|
+
AsyncGenerator: Object.getPrototypeOf(async function*(){}).constructor,
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.Functions = Functions;
|
package/src/obj/clone.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Import *most* required bits here.
|
|
2
|
+
const {B,N,F, isObj, isComplex, def} = require('../types');
|
|
3
|
+
const Enum = require('../enum');
|
|
4
|
+
const {getDescriptor, DESC} = require('../descriptors');
|
|
5
|
+
const copyProps = require('./copyprops');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An enum of supported modes for the `clone` method.
|
|
9
|
+
*
|
|
10
|
+
* `CLONE.DEFAULT` → Shallow clone of enumerable properties for most objects.
|
|
11
|
+
* `CLONE.JSON` → Deep clone using JSON serialization (Arrays included.)
|
|
12
|
+
* `CLONE.FULL` → Shallow clone of all object properties.
|
|
13
|
+
* `CLONE.ALL` → Shallow clone of all properties (Arrays included.)
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
const CLONE = Enum(['DEF','JSON','FULL','ALL']);
|
|
17
|
+
|
|
18
|
+
exports.CLONE = CLONE;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clone an object or function.
|
|
22
|
+
*
|
|
23
|
+
* @param {object|function} obj - The object we want to clone.
|
|
24
|
+
* @param {object} [opts={}] - Options for the cloning process.
|
|
25
|
+
*
|
|
26
|
+
* @param {number} [opts.mode=MODE_DEFAULT] - One of the `CLONE.*` enum values.
|
|
27
|
+
*
|
|
28
|
+
* For any mode that doesn't saay "Arrays included", Array objects will
|
|
29
|
+
* use a shortcut technique of `obj.slice()` to create the clone.
|
|
30
|
+
*
|
|
31
|
+
* Note: The `CLONE` enum is also aliased as `clone.MODE` as an alternative.
|
|
32
|
+
*
|
|
33
|
+
* @param {boolean} [opts.addClone=false] - Call {@link Lum._.addClone} on the cloned object.
|
|
34
|
+
*
|
|
35
|
+
* The options sent to this function will be used as the defaults in
|
|
36
|
+
* the clone() method added to the object.
|
|
37
|
+
*
|
|
38
|
+
* @param {boolean} [opts.addLock=false] - Call {@link Lum._.addLock} on the cloned object.
|
|
39
|
+
*
|
|
40
|
+
* No further options for this, just add a lock() method to the clone.
|
|
41
|
+
*
|
|
42
|
+
* @param {?object} [opts.copy] Call {@link Lum._.copy} on the cloned object.
|
|
43
|
+
*
|
|
44
|
+
* Will pass the original `obj` as the source to copy from.
|
|
45
|
+
* Will pass `opts.copy` as the options.
|
|
46
|
+
*
|
|
47
|
+
* @return {object} - The clone of the object.
|
|
48
|
+
*/
|
|
49
|
+
function clone(obj, opts={})
|
|
50
|
+
{
|
|
51
|
+
//console.debug("Lum~clone()", obj, opts);
|
|
52
|
+
|
|
53
|
+
if (!isComplex(obj))
|
|
54
|
+
{ // Doesn't need cloning.
|
|
55
|
+
//console.debug("no cloning required");
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!isObj(opts))
|
|
60
|
+
{ // Opts has to be a valid object.
|
|
61
|
+
opts = {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mode = typeof opts.mode === N ? opts.mode : CLONE.DEF;
|
|
65
|
+
const reclone = typeof opts.addClone === B ? opts.addClone : false;
|
|
66
|
+
const relock = typeof opts.addLock === B ? opts.addLock : false;
|
|
67
|
+
|
|
68
|
+
let copy;
|
|
69
|
+
|
|
70
|
+
//console.debug("::clone", {mode, reclone, relock});
|
|
71
|
+
|
|
72
|
+
if (mode === CLONE.JSON)
|
|
73
|
+
{ // Deep clone enumerable properties using JSON trickery.
|
|
74
|
+
//console.debug("::clone using JSON cloning");
|
|
75
|
+
copy = JSON.parse(JSON.stringify(obj));
|
|
76
|
+
}
|
|
77
|
+
else if (mode !== CLONE.ALL && Array.isArray(obj))
|
|
78
|
+
{ // Make a shallow copy using slice.
|
|
79
|
+
//console.debug("::clone using Array.slice()");
|
|
80
|
+
copy = obj.slice();
|
|
81
|
+
}
|
|
82
|
+
else
|
|
83
|
+
{ // Build a clone using a simple loop.
|
|
84
|
+
//console.debug("::clone using simple loop");
|
|
85
|
+
copy = {};
|
|
86
|
+
|
|
87
|
+
let props;
|
|
88
|
+
if (mode === CLONE.ALL || mode === CLONE.FULL)
|
|
89
|
+
{ // All object properties.
|
|
90
|
+
//console.debug("::clone getting all properties");
|
|
91
|
+
props = Object.getOwnPropertyNames(obj);
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
{ // Enumerable properties.
|
|
95
|
+
//console.debug("::clone getting enumerable properties");
|
|
96
|
+
props = Object.keys(obj);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//console.debug("::clone[props]", props);
|
|
100
|
+
|
|
101
|
+
for (let p = 0; p < props.length; p++)
|
|
102
|
+
{
|
|
103
|
+
let prop = props[p];
|
|
104
|
+
copy[prop] = obj[prop];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (reclone)
|
|
109
|
+
{ // Add the clone() method to the clone, with the passed opts as defaults.
|
|
110
|
+
addClone(copy, opts);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (opts.copy)
|
|
114
|
+
{ // Pass the clone through the copyProps() function as well.
|
|
115
|
+
copyProps(obj, copy, opts.copy);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (relock)
|
|
119
|
+
{ // Add the lock() method to the clone.
|
|
120
|
+
addLock(copy, opts);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return copy;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Alias the CLONE enum as clone.MODE
|
|
127
|
+
def(clone, 'MODE', CLONE);
|
|
128
|
+
|
|
129
|
+
// Export the clone here.
|
|
130
|
+
exports.clone = clone;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add a clone() method to an object.
|
|
134
|
+
*
|
|
135
|
+
* @param {object|function} obj - The object to add clone() to.
|
|
136
|
+
* @param {object} [defOpts=null] Default options for the clone() method.
|
|
137
|
+
*
|
|
138
|
+
* If `null` or anything other than an object, the defaults will be:
|
|
139
|
+
*
|
|
140
|
+
* ```{mode: CLONE.DEF, addClone: true, addLock: false}```
|
|
141
|
+
*
|
|
142
|
+
* @method Lum._.addClone
|
|
143
|
+
*/
|
|
144
|
+
function addClone(obj, defOpts=null)
|
|
145
|
+
{
|
|
146
|
+
if (!isObj(defOpts))
|
|
147
|
+
{ // Assign a default set of defaults.
|
|
148
|
+
defOpts = {mode: CLONE.DEF, addClone: true, addLock: false};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const defDesc = getDescriptor(defOpts.cloneDesc ?? DESC.CONF);
|
|
152
|
+
|
|
153
|
+
defDesc.setValue(function (opts)
|
|
154
|
+
{
|
|
155
|
+
if (!isObj(opts))
|
|
156
|
+
opts = defOpts;
|
|
157
|
+
return clone(obj, opts);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return def(obj, 'clone', defDesc);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
exports.addClone = addClone;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clone an object if it's not extensible (locked, sealed, frozen, etc.)
|
|
167
|
+
*
|
|
168
|
+
* If the object is extensible, it's returned as is.
|
|
169
|
+
*
|
|
170
|
+
* If not, if the object has a `clone()` method it will be used.
|
|
171
|
+
* Otherwise use the {@link Lum._.clone} method.
|
|
172
|
+
*
|
|
173
|
+
* @param {object} obj - The object to clone if needed.
|
|
174
|
+
* @param {object} [opts] - Options to pass to `clone()` method.
|
|
175
|
+
*
|
|
176
|
+
* @return {object} - Either the original object, or an extensible clone.
|
|
177
|
+
*
|
|
178
|
+
* @method Lum._.cloneIfLocked
|
|
179
|
+
*/
|
|
180
|
+
function cloneIfLocked(obj, opts)
|
|
181
|
+
{
|
|
182
|
+
if (!Object.isExtensible(obj))
|
|
183
|
+
{
|
|
184
|
+
if (typeof obj.clone === F)
|
|
185
|
+
{ // Use the object's clone() method.
|
|
186
|
+
return obj.clone(opts);
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
{ // Use our own clone method.
|
|
190
|
+
return clone(obj, opts);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Return the object itself, it's fine.
|
|
195
|
+
return obj;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
exports.cloneIfLocked = cloneIfLocked;
|
|
199
|
+
|
|
200
|
+
// Import `addLock()` here *after* assigning the clone methods.
|
|
201
|
+
const {addLock} = require('./lock');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a 'dumb' copy method.
|
|
3
|
+
*
|
|
4
|
+
* It does no type checking, and has no qualms about overwriting properties.
|
|
5
|
+
* You probably want something like `copyProps` instead.
|
|
6
|
+
*/
|
|
7
|
+
function copyAll(target, ...sources)
|
|
8
|
+
{
|
|
9
|
+
for (const source of sources)
|
|
10
|
+
{
|
|
11
|
+
for (const name in source)
|
|
12
|
+
{
|
|
13
|
+
target[name] = source[name];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = copyAll;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
|
|
2
|
+
// Get some constants
|
|
3
|
+
const {B,isObj,isComplex,isArray,def: defProp} = require('../types');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Copy properties from one object to another.
|
|
7
|
+
*
|
|
8
|
+
* @param {(object|function)} source - The object to copy properties from.
|
|
9
|
+
* @param {(object|function)} target - The target to copy properties to.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} [propOpts] Options for how to copy properties.
|
|
12
|
+
* @param {boolean} [propOpts.default=true] Copy only enumerable properties.
|
|
13
|
+
* @param {boolean} [propOpts.all=false] Copy ALL object properties.
|
|
14
|
+
* @param {Array} [propOpts.props] A list of specific properties to copy.
|
|
15
|
+
* @param {object} [propOpts.overrides] Descriptor overrides for properties.
|
|
16
|
+
* @param {Array} [propOpts.exclude] A list of properties NOT to copy.
|
|
17
|
+
* @param {*} [propOpts.overwrite=false] Overwrite existing properties.
|
|
18
|
+
* If this is a `boolean` value, it will allow or disallow overwriting
|
|
19
|
+
* of any and all properties in the target object.
|
|
20
|
+
*
|
|
21
|
+
* If this is an object, it can be an Array of property names to allow
|
|
22
|
+
* to be overwritten, or a map of property name to a boolean indicating
|
|
23
|
+
* if that property can be overwritten or not.
|
|
24
|
+
*
|
|
25
|
+
* @returns {object} The `target` object.
|
|
26
|
+
*/
|
|
27
|
+
function copyProps(source, target, propOpts)
|
|
28
|
+
{
|
|
29
|
+
if (!isComplex(source) || !isComplex(target))
|
|
30
|
+
{
|
|
31
|
+
throw new TypeError("source and target both need to be objects");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isObj(propOpts))
|
|
35
|
+
propOpts = {default: true};
|
|
36
|
+
|
|
37
|
+
const defOverrides = propOpts.overrides ?? {};
|
|
38
|
+
const defOverwrite = propOpts.overwrite ?? false;
|
|
39
|
+
|
|
40
|
+
const exclude = isArray(propOpts.exclude) ? propOpts.exclude : null;
|
|
41
|
+
|
|
42
|
+
let propDefs;
|
|
43
|
+
|
|
44
|
+
if (propOpts.props && isArray(propOpts.props))
|
|
45
|
+
{
|
|
46
|
+
propDefs = propOpts.props;
|
|
47
|
+
}
|
|
48
|
+
else if (propOpts.all)
|
|
49
|
+
{
|
|
50
|
+
propDefs = Object.getOwnPropertyNames(source);
|
|
51
|
+
}
|
|
52
|
+
else if (propOpts.default)
|
|
53
|
+
{
|
|
54
|
+
propDefs = Object.keys(source);
|
|
55
|
+
}
|
|
56
|
+
else if (propOpts.overrides)
|
|
57
|
+
{
|
|
58
|
+
propDefs = Object.keys(propOpts.overrides);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!propDefs)
|
|
62
|
+
{
|
|
63
|
+
console.error("Could not determine properties to copy", propOpts);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For each propDef found, add it to the target.
|
|
68
|
+
for (let p = 0; p < propDefs.length; p++)
|
|
69
|
+
{
|
|
70
|
+
const prop = propDefs[p];
|
|
71
|
+
if (exclude && exclude.indexOf(prop) !== -1)
|
|
72
|
+
continue; // Excluded property.
|
|
73
|
+
|
|
74
|
+
let overwrite = false;
|
|
75
|
+
if (typeof defOverwrite === B)
|
|
76
|
+
{
|
|
77
|
+
overwrite = defOverwrite;
|
|
78
|
+
}
|
|
79
|
+
else if (isObj(defOverwrite) && typeof defOverwrite[prop] === B)
|
|
80
|
+
{
|
|
81
|
+
overwrite = defOverwrite[prop];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const def = Object.getOwnPropertyDescriptor(source, prop)
|
|
85
|
+
if (def === undefined) continue; // Invalid property.
|
|
86
|
+
|
|
87
|
+
if (isObj(defOverrides[prop]))
|
|
88
|
+
{
|
|
89
|
+
for (const key in defOverrides[prop])
|
|
90
|
+
{
|
|
91
|
+
const val = defOverrides[prop][key];
|
|
92
|
+
def[key] = val;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (overwrite || target[prop] === undefined)
|
|
96
|
+
{ // Property doesn't already exist, let's add it.
|
|
97
|
+
defProp(target, prop, def);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return target;
|
|
102
|
+
} // copyProps()
|
|
103
|
+
|
|
104
|
+
module.exports = copyProps;
|
package/src/obj/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Object helper utilities.
|
|
2
|
+
// We *could* use `copyAll` to simply merge everything in here automatically.
|
|
3
|
+
// But I'm going to be more explicit and import everything I want separately.
|
|
4
|
+
// I may change my mind on that in the future, who knows.
|
|
5
|
+
|
|
6
|
+
const copyAll = require('./copyall');
|
|
7
|
+
const copyProps = require('./copyprops');
|
|
8
|
+
const {CLONE,clone,addClone,cloneIfLocked} = require('./clone');
|
|
9
|
+
const {lock,addLock} = require('./lock');
|
|
10
|
+
const {mergeNested,syncNested} = require('./merge');
|
|
11
|
+
const {SOA,nsString,nsArray,getObjectPath,setObjectPath} = require('./ns');
|
|
12
|
+
|
|
13
|
+
module.exports =
|
|
14
|
+
{
|
|
15
|
+
CLONE, clone, addClone, cloneIfLocked, lock, addLock,
|
|
16
|
+
mergeNested, syncNested, SOA, nsString, nsArray,
|
|
17
|
+
getObjectPath, setObjectPath, copyProps, copyAll,
|
|
18
|
+
}
|
package/src/obj/lock.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Import *most* required bits here.
|
|
2
|
+
const {B, isObj, def} = require('../types');
|
|
3
|
+
const {getDescriptor, DESC} = require('../descriptors');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lock an object using Object.freeze()
|
|
7
|
+
*
|
|
8
|
+
* @param {object} obj - The object we want to lock.
|
|
9
|
+
* @param {boolean} [clonable=true] Pass to {@link Lum._.addClone} first?
|
|
10
|
+
* @param {object} [cloneOpts=null] Options for addClone.
|
|
11
|
+
* @param {boolean} [useSeal=false] Use Object.seal() instead of freeze.
|
|
12
|
+
*
|
|
13
|
+
* If cloneOpts is `null` then we will use the following:
|
|
14
|
+
*
|
|
15
|
+
* ```{mode: CLONE.DEF, addClone: true, addLock: true}```
|
|
16
|
+
*
|
|
17
|
+
* @return {object} The locked object.
|
|
18
|
+
*
|
|
19
|
+
* @method Lum._.lock
|
|
20
|
+
*/
|
|
21
|
+
function lock(obj, clonable=true, cloneOpts=null, useSeal=false)
|
|
22
|
+
{
|
|
23
|
+
if (clonable)
|
|
24
|
+
{ // Add the clone method before freezing.
|
|
25
|
+
if (!isObj(cloneOpts))
|
|
26
|
+
{
|
|
27
|
+
cloneOpts = {mode: CLONE.DEF, addClone: true, addLock: true};
|
|
28
|
+
}
|
|
29
|
+
addClone(obj, cloneOpts);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Now freeze (or seal) the object.
|
|
33
|
+
return (useSeal ? Object.seal(obj) : Object.freeze(obj));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.lock = lock;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Add a lock() method to an object.
|
|
40
|
+
*
|
|
41
|
+
* Adds a wrapper version of {@link Lum._.lock} to the object as a method.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} obj - The object we're adding lock() to.
|
|
44
|
+
* @param {object} opts - Options (TODO: document this).
|
|
45
|
+
*
|
|
46
|
+
* @method Lum._.addLock
|
|
47
|
+
*/
|
|
48
|
+
function addLock(obj, opts)
|
|
49
|
+
{
|
|
50
|
+
const defDesc = getDescriptor(opts.lockDesc ?? DESC.CONF);
|
|
51
|
+
defDesc.setValue(function(obj, cloneable, cloneOpts, useSeal)
|
|
52
|
+
{
|
|
53
|
+
if (typeof cloneable !== B) clonable = opts.addClone ?? true;
|
|
54
|
+
if (!isObj(cloneOpts)) cloneOpts = opts; // Yup, just a raw copy.
|
|
55
|
+
if (typeof useSeal !== B) useSeal = opts.useSeal ?? false;
|
|
56
|
+
return lock(obj, cloneable, cloneOpts, useSeal);
|
|
57
|
+
});
|
|
58
|
+
return def(obj, 'lock', defDesc);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.addLock = addLock;
|
|
62
|
+
|
|
63
|
+
// Importing addLock at the end, just because.
|
|
64
|
+
const {addClone} = require('./clone');
|
package/src/obj/merge.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Import required bits here.
|
|
2
|
+
const {B, isObj} = require('../types');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge two objects recursively.
|
|
6
|
+
*
|
|
7
|
+
* This is currently suseptible to circular reference infinite loops,
|
|
8
|
+
* but given what it's designed for, I'm not too worried about that.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} source - The source object we're copying from.
|
|
11
|
+
* @param {object} target - The target object we're copying into.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} [opts] Options that change the behaviour.
|
|
14
|
+
* @param {boolean} [opts.overwrite=true] Allow overwriting.
|
|
15
|
+
* Unlike `copy` which does not allow overwriting by default,
|
|
16
|
+
* this method does. It's not designed for the same kind of objects
|
|
17
|
+
* as `copy`, but more for JSON structured configuration files.
|
|
18
|
+
*
|
|
19
|
+
* As this is currently the only option, passing the boolean
|
|
20
|
+
* as the `opts` argument directly will set this option.
|
|
21
|
+
*
|
|
22
|
+
* @returns {object} The `target` object.
|
|
23
|
+
*/
|
|
24
|
+
function mergeNested(source, target, opts={})
|
|
25
|
+
{
|
|
26
|
+
if (typeof opts === B)
|
|
27
|
+
opts = {overwrite: opts};
|
|
28
|
+
|
|
29
|
+
const overwrite = opts.overwrite ?? true;
|
|
30
|
+
|
|
31
|
+
for (const prop in source)
|
|
32
|
+
{
|
|
33
|
+
if (isObj(source[prop]) && isObj(to[prop]))
|
|
34
|
+
{ // Nested objects, recurse deeper.
|
|
35
|
+
mergeNested(source[prop], target[prop], opts);
|
|
36
|
+
}
|
|
37
|
+
else if (overwrite || target[prop] === undefined)
|
|
38
|
+
{ // Not tested objects, do a simple assignment.
|
|
39
|
+
target[prop] = source[prop];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.mergeNested = mergeNested;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Synchronize two objects.
|
|
50
|
+
*
|
|
51
|
+
* Literally just calls `mergeNested` twice, with the two objects swapped.
|
|
52
|
+
* Probably has all kinds of screwy behaviours because of how it works.
|
|
53
|
+
*
|
|
54
|
+
* @param {object} obj1 - The first object.
|
|
55
|
+
* Because overwrite mode is on by default, any properties in `obj1` will
|
|
56
|
+
* take precedence over the same properties in `obj2`.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} obj2 - The second object.
|
|
59
|
+
* Any properties in `obj2` that were not already in `obj1` will be added
|
|
60
|
+
* to `obj1` thanks to the second merge operation.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} [opts1] Options for the first merge operation.
|
|
63
|
+
* See `mergeNested` for details on the supported options.
|
|
64
|
+
* @param {object} [opts2=opts1] Options for the second merge operation.
|
|
65
|
+
* If this is not specified, `opts2` will be the same as `opts1`.
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
function syncNested(obj1, obj2, opts1={}, opts2=opts1)
|
|
69
|
+
{
|
|
70
|
+
mergeNested(obj1, obj2, opts1)
|
|
71
|
+
mergeNested(obj2, obj1, opts2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exports.syncNested = syncNested;
|