@lumjs/core 1.6.1 → 1.7.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/lib/index.js CHANGED
@@ -51,8 +51,36 @@ has('lazy', {value: types.lazy});
51
51
 
52
52
  // The module builder itself.
53
53
  has('ModuleBuilder', {value: Builder});
54
- // And a shortcut function.
55
- has('buildModule', {value: function(m,o) { return Builder.build(m,o); }});
54
+
55
+ /**
56
+ * Return a set of functions for building a module.
57
+ *
58
+ * @name module:@lumjs/core.buildModule
59
+ * @function
60
+ * @see module:@lumjs/core/modulebuilder.build
61
+ */
62
+ has('buildModule',
63
+ {
64
+ value: function(m,o)
65
+ {
66
+ return Builder.build(m,o);
67
+ },
68
+ });
69
+
70
+ /**
71
+ * Create a new `ModuleBuilder` instance.
72
+ *
73
+ * @name module:@lumjs/core.newBuilder
74
+ * @function
75
+ * @see module:@lumjs/core/modulebuilder.new
76
+ */
77
+ has('newBuilder',
78
+ {
79
+ value: function(m, o)
80
+ {
81
+ return Builder.new(m,o);
82
+ }
83
+ });
56
84
 
57
85
  /**
58
86
  * Array utility functions «Lazy»
@@ -1,18 +1,15 @@
1
1
 
2
- const {S,F,B,isObj,needObj,needType,def,lazy} = require('./types');
3
-
4
- function clone(obj)
2
+ const
5
3
  {
6
- const copy = {};
7
- for (const name in obj)
8
- {
9
- copy[name] = obj[name];
10
- }
11
- return copy;
12
- }
4
+ S,F,B,
5
+ isObj,needObj,needType,
6
+ def,lazy
7
+ } = require('./types');
8
+
9
+ const clone = require('./obj/copyall').duplicateOne;
13
10
 
14
11
  // Methods we want to export in the functional API.
15
- const BUILD_METHODS = ['has', 'can', 'from'];
12
+ const BUILD_METHODS = ['has', 'can', 'from', 'set'];
16
13
 
17
14
  /**
18
15
  * A class to make building modules easier.
@@ -76,6 +73,11 @@ class ModuleBuilder
76
73
  // Get a descriptor for a module export.
77
74
  requireDescriptor(name, conf={})
78
75
  {
76
+ if (typeof conf.getter === F || typeof conf.setter === F)
77
+ { // It's an accessor-style descriptor already.
78
+ return conf;
79
+ }
80
+
79
81
  let value = conf.value;
80
82
 
81
83
  if (value === undefined)
@@ -268,6 +270,33 @@ class ModuleBuilder
268
270
  return this;
269
271
  }
270
272
 
273
+ /**
274
+ * Define an exported property in our module.
275
+ *
276
+ * This is literally just a wrapper around the
277
+ * [def()]{@link module:@lumjs/core/types.def} method,
278
+ * that passes `this.module.exports` as the `obj` parameter.
279
+ *
280
+ * @param {*} name - See `def()` for details.
281
+ * @param {*} value - See `def()` for details.
282
+ * @param {*} opts - See `def()` for details.
283
+ * @returns {(object|function)} What is returned may vary:
284
+ *
285
+ * If the `def()` returns a *bound* copy of itself, we'll return that
286
+ * bound function directly.
287
+ *
288
+ * In any other case, we return `this` ModuleBuilder instance.
289
+ */
290
+ set(name, value, opts)
291
+ {
292
+ const retval = def(this.module.exports, name, value, opts);
293
+ if (typeof retval === F && isObj(retval.$this) && retval.$this.bound === retval)
294
+ { // A bound copy was returned, we're going to return it directly.
295
+ return retval;
296
+ }
297
+ return this;
298
+ }
299
+
271
300
  /**
272
301
  * Create a functional API for the ModuleBuilder class.
273
302
  *
@@ -278,12 +307,14 @@ class ModuleBuilder
278
307
  * They each have a special `builder` property which is a reference
279
308
  * to the underlying `ModuleBuilder` instance.
280
309
  *
281
- * There's also a `builder` property in the exported function list.
310
+ * There's also a `builder` property in the exported function list,
311
+ * as well as a copy of the `def()` method for quick exporting.
282
312
  *
283
313
  * Example usage:
284
314
  *
285
315
  * ```js
286
- * const {has,can,from} = require('@lumjs/core').ModuleBuilder.build(module);
316
+ * const {has,can,from,set}
317
+ * = require('@lumjs/core').ModuleBuilder.build(module);
287
318
  * // or the shortcut: require('@lumjs/core).buildModule(module);
288
319
  *
289
320
  * // exports.foo = require('./foo');
@@ -301,7 +332,7 @@ class ModuleBuilder
301
332
  static build(targetModule, opts)
302
333
  {
303
334
  const builder = new this(targetModule, opts);
304
- const funcs = {builder};
335
+ const funcs = {builder, def};
305
336
  for (const name of BUILD_METHODS)
306
337
  {
307
338
  const func = function()
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * This is a 'dumb' copy method.
3
3
  *
4
- * It does no type checking, and has no qualms about overwriting properties.
5
- * You probably want something like `copyProps` instead.
4
+ * It only copies enumerable properties, does no type checking,
5
+ * and has no qualms about overwriting properties.
6
+ *
7
+ * Use `copyProps`, or `mergeNested` for more robust versions.
6
8
  *
7
9
  * @alias module:@lumjs/core/obj.copyAll
8
10
  */
@@ -17,5 +19,30 @@ function copyAll(target, ...sources)
17
19
  }
18
20
  return target;
19
21
  }
22
+ exports.copyAll = copyAll;
23
+
24
+ /**
25
+ * Make a copy of a single object using `copyAll`.
26
+ *
27
+ * Use `clone` for a more robust version.
28
+ *
29
+ * Alias: `copyAll.clone`
30
+ *
31
+ * @alias module:@lumjs/core/obj.duplicateOne
32
+ * @param {object} obj - The object to duplicate.
33
+ * @returns {object} A clone of the object.
34
+ */
35
+ exports.duplicateOne = copyAll.clone = obj => copyAll({}, obj);
20
36
 
21
- module.exports = copyAll;
37
+ /**
38
+ * Make a new object containing properties from other objects using `copyAll`.
39
+ *
40
+ * Use `copyProps.into({}).from(...sources)` for a more robust version.
41
+ *
42
+ * Alias: `copyAll.clone`
43
+ *
44
+ * @alias module:@lumjs/core/obj.duplicateOne
45
+ * @param {object} obj - The object to duplicate.
46
+ * @returns {object} A clone of the object.
47
+ */
48
+ exports.duplicateAll = copyAll.duplicate = () => copyAll({}, ...arguments);
@@ -1,6 +1,11 @@
1
1
 
2
2
  // Get some constants
3
- const {B,isObj,isComplex,isArray,def: defProp} = require('../types');
3
+ const {B,N,F,isObj,isArray,needObj,def} = require('../types');
4
+ const {duplicateOne: clone} = require('./copyall');
5
+
6
+ const RECURSE_NONE = 0;
7
+ const RECURSE_ALL = -1;
8
+ const RECURSE_LIST = -2;
4
9
 
5
10
  /**
6
11
  * Copy properties from one object to another.
@@ -8,61 +13,143 @@ const {B,isObj,isComplex,isArray,def: defProp} = require('../types');
8
13
  * @param {(object|function)} source - The object to copy properties from.
9
14
  * @param {(object|function)} target - The target to copy properties to.
10
15
  *
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.
16
+ * @param {object} [opts] Options for how to copy properties.
17
+ * @param {boolean} [opts.default=true] Copy only enumerable properties.
18
+ * @param {boolean} [opts.all=false] Copy ALL object properties.
19
+ * @param {Array} [opts.props] A list of specific properties to copy.
20
+ * @param {Array} [opts.exclude] A list of properties NOT to copy.
21
+ * @param {object} [opts.overrides] Descriptor overrides for properties.
22
+ *
23
+ * The object is considered a map, where each *key* is the name of the
24
+ * property, and the value should be an `object` containing any valid
25
+ * descriptor properties.
26
+ *
27
+ * If `opts.default` is explicitly set to `false` and `opts.overrides`
28
+ * is set, then not only will it be used as a list of overrides,
29
+ * but only the properties specified in it will be copied.
30
+ *
31
+ * @param {*} [opts.overwrite=false] Overwrite existing properties.
32
+ *
33
+ * If this is a `boolean` value, it will allow or disallow overwriting
34
+ * of any and all properties in the target object.
20
35
  *
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.
36
+ * If this is an `object`, it can be an `Array` of property names to allow
37
+ * to be overwritten, or a *map* of property name to a `boolean` indicating
38
+ * if that property can be overwritten or not.
39
+ *
40
+ * Finally, if this is a `function` it'll be passed the property name and
41
+ * must return a boolean indicating if overwriting is allowed.
42
+ *
43
+ * @param {number} [opts.recursive=0] Enable recursive copying of objects.
44
+ *
45
+ * If it is `0` (also `copyProps.RECURSE_NONE`) then no recursion is done.
46
+ * In this case, the regular assignment rules (including `opts.overwrite`)
47
+ * will be used regardless of the property type.
48
+ * This is the default value.
49
+ *
50
+ * If this is *above zero*, it's the recursion depth for `object` properties.
51
+ *
52
+ * If this is *below zero*, then it should be one of the constant values:
53
+ *
54
+ * | Contant | Value | Description |
55
+ * | ------------------------ | ----- | ------------------------------------ |
56
+ * | `copyProps.RECURSE_ALL` | `-1` | Recurse to an *unlimited* depth. |
57
+ * | `copyProps.RECURSE_LIST` | `-2` | Recurse `opts.recurseOpts` props. |
58
+ *
59
+ * @param {object} [opts.recurseOpts] Options for recursive properties.
60
+ *
61
+ * If `opts.recursive` is not `0` then `opts.recurseOpts` can be a map of
62
+ * property names to further objects, which will be used as the `opts` for
63
+ * that property when calling `copyProps()` recursively.
64
+ *
65
+ * So you could have nested `opts.recurseOpts` values if required.
66
+ *
67
+ * The `recursive` property will automatically be added to the individual
68
+ * `recurseOpts`, automatically applying the correct value.
69
+ *
70
+ * The `opts.recurseOpts` option has an extra-special meaning if `opts.recurse`
71
+ * is set to `RECURSE_LIST`, as then *only* the properties with options defined
72
+ * in `opts.recurseOpts` will be recursed. The rest will simply be copied.
24
73
  *
25
74
  * @returns {object} The `target` object.
26
75
  * @alias module:@lumjs/core/obj.copyProps
27
76
  */
28
- function copyProps(source, target, propOpts)
77
+ function copyProps(source, target, opts={})
29
78
  {
30
79
  //console.debug("copyProps", source, target, propOpts);
31
- if (!isComplex(source) || !isComplex(target))
80
+ needObj(source, true, 'source must be an object or function');
81
+ needObj(target, true, 'target must be an object or function');
82
+ needObj(opts, false, 'opts must be an object');
83
+
84
+ const useDefaults = opts.default ?? true;
85
+ const overrides = opts.overrides ?? {};
86
+
87
+ let recursive;
88
+ if (typeof opts.recursive === N)
89
+ {
90
+ recursive = opts.recursive;
91
+ }
92
+ else if (typeof opts.recursive === B)
32
93
  {
33
- throw new TypeError("source and target both need to be objects");
94
+ recursive = opts.recursive ? RECURSE_ALL : RECURSE_NONE;
95
+ }
96
+ else
97
+ {
98
+ recursive = RECURSE_NONE;
34
99
  }
35
100
 
36
- if (!isObj(propOpts))
37
- propOpts = {default: true};
101
+ let recurseCache, recurseOpts;
102
+ if (recursive !== RECURSE_NONE)
103
+ {
104
+ recurseCache = opts.recurseCache ?? [];
105
+ recurseOpts = opts.recurseOpts ?? {};
106
+ }
38
107
 
39
- const defOverrides = propOpts.overrides ?? {};
40
- const defOverwrite = propOpts.overwrite ?? false;
108
+ let overwrites;
109
+ if (typeof opts.overwrite === F)
110
+ { // A custom function.
111
+ overwrites = opts.overwrite;
112
+ }
113
+ else if (isObj(opts.overwrite))
114
+ { // An object may be an array or a map.
115
+ if (isArray(opts.overwrite))
116
+ { // A flat array of properties to overwrite.
117
+ overwrites = prop => opts.overwrite.includes(prop);
118
+ }
119
+ else
120
+ { // A map of properties to overwrite.
121
+ overwrites = prop => opts.overwrite[prop] ?? false;
122
+ }
123
+ }
124
+ else
125
+ { // The only other values should be boolean.
126
+ overwrites = () => opts.overwrite ?? false;
127
+ }
41
128
 
42
- const exclude = isArray(propOpts.exclude) ? propOpts.exclude : null;
129
+ const exclude = isArray(opts.exclude) ? opts.exclude : null;
43
130
 
44
131
  let propDefs;
45
132
 
46
- if (propOpts.props && isArray(propOpts.props))
133
+ if (isArray(opts.props))
47
134
  {
48
- propDefs = propOpts.props;
135
+ propDefs = opts.props;
49
136
  }
50
- else if (propOpts.all)
137
+ else if (opts.all)
51
138
  {
52
139
  propDefs = Object.getOwnPropertyNames(source);
53
140
  }
54
- else if (propOpts.default)
141
+ else if (useDefaults)
55
142
  {
56
143
  propDefs = Object.keys(source);
57
144
  }
58
- else if (propOpts.overrides)
145
+ else
59
146
  {
60
- propDefs = Object.keys(propOpts.overrides);
147
+ propDefs = Object.keys(overrides);
61
148
  }
62
149
 
63
150
  if (!propDefs)
64
151
  {
65
- console.error("Could not determine properties to copy", propOpts);
152
+ console.error("Could not determine properties to copy", opts);
66
153
  return;
67
154
  }
68
155
 
@@ -71,34 +158,57 @@ function copyProps(source, target, propOpts)
71
158
  {
72
159
  //console.debug(" @prop:", prop);
73
160
  if (exclude && exclude.indexOf(prop) !== -1)
74
- continue; // Excluded property.
75
-
76
- let overwrite = false;
77
- if (typeof defOverwrite === B)
78
- {
79
- overwrite = defOverwrite;
80
- }
81
- else if (isObj(defOverwrite) && typeof defOverwrite[prop] === B)
82
- {
83
- overwrite = defOverwrite[prop];
161
+ { // Excluded property.
162
+ continue;
84
163
  }
85
164
 
86
- const def = Object.getOwnPropertyDescriptor(source, prop);
165
+ let desc = Object.getOwnPropertyDescriptor(source, prop);
87
166
  //console.debug(" @desc:", def);
88
- if (def === undefined) continue; // Invalid property.
167
+ if (desc === undefined)
168
+ { // A non-existent property.
169
+ continue; // Invalid property.
170
+ }
89
171
 
90
- if (isObj(defOverrides[prop]))
91
- {
92
- for (const key in defOverrides[prop])
172
+ if (isObj(overrides[prop]))
173
+ { // Overriding descriptor properties.
174
+ desc = clone(desc);
175
+ for (const key in overrides[prop])
93
176
  {
94
- const val = defOverrides[prop][key];
95
- def[key] = val;
177
+ const val = overrides[prop][key];
178
+ desc[key] = val;
96
179
  }
97
180
  }
98
181
 
99
- if (overwrite || target[prop] === undefined)
182
+ let overwrite = overwrites(prop);
183
+
184
+ if (recursive !== 0
185
+ && (recursive !== RECURSE_LIST || isObj(recurseOpts[prop]))
186
+ && isObj(desc.value)
187
+ && isObj(target[prop])
188
+ && !recurseCache.includes(desc.value)
189
+ && !recurseCache.includes(target[prop]))
190
+ { // Recursive mode is enabled, so we're going to go deeper.
191
+ recurseCache.push(decs.value);
192
+ if (desc.value !== target[prop])
193
+ { // They're not the same literal object already.
194
+ recurseCache.push(target[prop]);
195
+ const ropts
196
+ = isObj(recurseOpts[prop])
197
+ ? clone(recurseOpts[prop])
198
+ : {overwrite};
199
+ // Always set the cache.
200
+ ropts.recurseCache = recurseCache;
201
+ if (typeof ropts.recursive !== N)
202
+ { // Set the recursive option.
203
+ ropts.recursive = (recursive > 0) ? recursive - 1 : recursive;
204
+ }
205
+ // Okay, we're ready, let's recurse now!
206
+ copyProps(desc.value, target[prop], ropts);
207
+ }
208
+ }
209
+ else if (overwrite || target[prop] === undefined)
100
210
  { // Property doesn't already exist, let's add it.
101
- defProp(target, prop, def);
211
+ def(target, prop, desc);
102
212
  }
103
213
  }
104
214
 
@@ -107,4 +217,159 @@ function copyProps(source, target, propOpts)
107
217
  return target;
108
218
  } // copyProps()
109
219
 
220
+ def(copyProps, 'RECURSE_NONE', RECURSE_NONE);
221
+ def(copyProps, 'RECURSE_ALL', RECURSE_ALL);
222
+ def(copyProps, 'RECURSE_LIST', RECURSE_LIST);
223
+
224
+ /**
225
+ * A class providing a declarative `copyProps()` API,
226
+ * which makes it easy to copy one or more sources,
227
+ * into one or more targets.
228
+ *
229
+ * This class is not directly accessible, and instead is available
230
+ * via some special sub-methods of `copyProps`; examples:
231
+ *
232
+ * ```js
233
+ * // Get a copy of the `copyProps` function.
234
+ * const cp = require('@lumjs/core').obj.copyProps;
235
+ *
236
+ * // Starting with the target:
237
+ * cp.into(targetObj).given({all: true}).from(source1, source2);
238
+ *
239
+ * // Starting with the sources:
240
+ * cp.from(sourceObj).given({exclude: ['dontCopy']}).into(target1, target2);
241
+ *
242
+ * // Starting with the options:
243
+ * cp.given({recursive: cp.RECURSE_ALL}).from(source1, source2).into(target1, target2);
244
+ *
245
+ *
246
+ * ```
247
+ *
248
+ * @alias module:@lumjs/core/obj~CopyProps
249
+ */
250
+ class $CopyProps
251
+ {
252
+ constructor(opts={})
253
+ {
254
+ this.opts = opts;
255
+ this.sources = [];
256
+ this.targets = [];
257
+ }
258
+
259
+ /**
260
+ * Set a single option.
261
+ * @param {string} name - The option name
262
+ * @param {*} value - The option value
263
+ * @returns {object} `this`
264
+ */
265
+ set(name, value)
266
+ {
267
+ this.opts[name] = value;
268
+ return this;
269
+ }
270
+
271
+ /**
272
+ * Set all of the options.
273
+ *
274
+ * This replaces the currently set options entirely.
275
+ *
276
+ * @param {object} opts - The options to set.
277
+ * @returns {object} `this`
278
+ */
279
+ given(opts)
280
+ {
281
+ needObj(opts);
282
+ this.opts = opts;
283
+ return this;
284
+ }
285
+
286
+ /**
287
+ * Specify the `targets` to copy properties into.
288
+ *
289
+ * @param {...object} [targets] The target objects
290
+ *
291
+ * If `this.sources` has objects in it already,
292
+ * then we'll run `copyProps()` for each of the
293
+ * sources into each of the `targets`.
294
+ *
295
+ * If `this.sources` is empty, then this will set
296
+ * `this.target` to the specified value.
297
+ *
298
+ * You can specify no sources at all to clear the
299
+ * currently set `this.targets` value.
300
+ *
301
+ * @returns {object} `this`
302
+ */
303
+ into(...targets)
304
+ {
305
+ if (this.sources.length > 0 && targets.length > 0)
306
+ {
307
+ this.$run(this.sources, targets);
308
+ }
309
+ else
310
+ {
311
+ this.targets = targets;
312
+ }
313
+ return this;
314
+ }
315
+
316
+ /**
317
+ * Specify the `sources` to copy properties from.
318
+ *
319
+ * @param {...object} [sources] The source objects.
320
+ *
321
+ * If `this.targets` has objects in it already,
322
+ * then we'll run `copyProps()` for each of the
323
+ * `sources` into each of the targets.
324
+ *
325
+ * If `this.targets` is empty, then this will set
326
+ * `this.sources` to the specified value.
327
+ *
328
+ * You can specify no sources at all to clear the
329
+ * currently set `this.sources` value.
330
+ *
331
+ * @returns {object} `this`
332
+ */
333
+ from(...sources)
334
+ {
335
+ if (this.targets.length > 0 && sources.length > 0)
336
+ {
337
+ this.$run(sources, this.targets);
338
+ }
339
+ else
340
+ {
341
+ this.sources = sources;
342
+ }
343
+ return this;
344
+ }
345
+
346
+ // Protected method doesn't need to be documented.
347
+ $run(sources, targets)
348
+ {
349
+ for (const source of sources)
350
+ {
351
+ for (const target of targets)
352
+ {
353
+ copyProps(source, target, this.opts);
354
+ }
355
+ }
356
+ }
357
+
358
+ }
359
+
360
+ copyProps.given = function(opts)
361
+ {
362
+ return new $CopyProps(opts);
363
+ }
364
+
365
+ copyProps.into = function(...targets)
366
+ {
367
+ return ((new $CopyProps()).into(...targets));
368
+ }
369
+
370
+ copyProps.from = function(...sources)
371
+ {
372
+ return ((new $CopyProps()).from(...sources));
373
+ }
374
+
110
375
  module.exports = copyProps;
package/lib/obj/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module @lumjs/core/obj
4
4
  */
5
5
 
6
- const copyAll = require('./copyall');
6
+ const {copyAll,duplicateOne,duplicateAll} = require('./copyall');
7
7
  const copyProps = require('./copyprops');
8
8
  const {CLONE,clone,addClone,cloneIfLocked} = require('./clone');
9
9
  const {lock,addLock} = require('./lock');
@@ -21,5 +21,5 @@ module.exports =
21
21
  CLONE, clone, addClone, cloneIfLocked, lock, addLock,
22
22
  mergeNested, syncNested, copyProps, copyAll, ns,
23
23
  getObjectPath, setObjectPath, getNamespace, setNamespace,
24
- getProperty,
24
+ getProperty, duplicateAll, duplicateOne,
25
25
  }
package/lib/obj/merge.js CHANGED
@@ -1,48 +1,56 @@
1
1
  // Import required bits here.
2
- const {B, isObj} = require('../types');
2
+ const {B, N, isObj} = require('../types');
3
+ const copyProps = require('./copyprops');
4
+
5
+ // A shortcut for the recursive option.
6
+ const recursive = copyProps.RECURSE_ALL;
3
7
 
4
8
  /**
5
9
  * Merge two objects recursively.
6
10
  *
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.
11
+ * This used to be a standalone function, but was poorly designed.
12
+ * It's now a wrapper around the
13
+ * [copyProps()]{@link module:@lumjs/core/obj.copyProps} method.
9
14
  *
10
15
  * @param {object} source - The source object we're copying from.
11
16
  * @param {object} target - The target object we're copying into.
12
17
  *
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.
18
+ * @param {(object|boolean)} [opts] Options for `copyProps()`
19
+ *
20
+ * If `opts.recursive` is not a `number`, it'll be set to
21
+ * `copyProps.RECURSE_ALL` to enable recursion with no
22
+ * depth limits.
23
+ *
24
+ * Also, if `opts.overwrite` is not explicitly set, it will
25
+ * be set as `true`, a different default value than `copyProps()`.
26
+ *
27
+ * For backwards compatibility, if this specified as a `boolean`
28
+ * value instead of an object, it'll be assumed to be value
29
+ * for the `opts.overwrite` option.
21
30
  *
22
31
  * @returns {object} The `target` object.
23
32
  * @alias module:@lumjs/core/obj.mergeNested
24
33
  */
25
- function mergeNested(source, target, opts={})
26
- {
27
- if (typeof opts === B)
28
- opts = {overwrite: opts};
29
-
30
- const overwrite = opts.overwrite ?? true;
31
-
32
- for (const prop in source)
33
- {
34
- if (isObj(source[prop]) && isObj(to[prop]))
35
- { // Nested objects, recurse deeper.
36
- mergeNested(source[prop], target[prop], opts);
37
- }
38
- else if (overwrite || target[prop] === undefined)
39
- { // Not tested objects, do a simple assignment.
40
- target[prop] = source[prop];
41
- }
42
- }
43
-
44
- return target;
45
- }
34
+ function mergeNested(source, target, opts={})
35
+ {
36
+ if (typeof opts === B)
37
+ { // Boolean overwrite option was specified.
38
+ opts = {overwrite: opts, recursive};
39
+ }
40
+ else if (!isObj(opts))
41
+ { // Wasn't an object, use default values.
42
+ opts = {overwrite: true, recursive};
43
+ }
44
+ else
45
+ { // If recursive or overwrite aren't set, set them.
46
+ if (opts.recursive === undefined)
47
+ opts.recursive = recursive;
48
+ if (opts.overwrite === undefined)
49
+ opts.overwrite = true;
50
+ }
51
+
52
+ return copyProps(source, target, opts);
53
+ }
46
54
 
47
55
  exports.mergeNested = mergeNested;
48
56
 
package/lib/types/def.js CHANGED
@@ -2,10 +2,7 @@
2
2
  const unbound = require('./root').unbound;
3
3
  const {F, B} = require('./js');
4
4
  const {isObj, isNil, isProperty, doesDescriptor} = require('./basics');
5
- const copy = require('../obj/copyall');
6
-
7
- // A really really cheap version of clone().
8
- const clone = obj => copy({}, obj);
5
+ const {copy, duplicateOne: clone} = require('../obj/copyall');
9
6
 
10
7
  /**
11
8
  * A wrapper around `Object.defineProperty()`.
package/lib/types/isa.js CHANGED
@@ -10,177 +10,174 @@ const TYPES = require('./typelist');
10
10
  * @returns {boolean}
11
11
  * @alias module:@lumjs/core/types.isInstance
12
12
  */
13
- function isInstance(v, f, needProto=false)
14
- {
15
- if (!isObj(v)) return false; // Not an object.
16
- if (needProto && (typeof v.prototype !== O || v.prototype === null))
17
- { // Has no prototype.
18
- return false;
19
- }
20
-
21
- if (typeof f !== F || !(v instanceof f)) return false;
22
-
23
- // Everything passed.
24
- return true;
25
- }
26
-
27
- exports.isInstance = isInstance;
28
-
29
- /**
30
- * A smarter `typeof` function.
31
- *
32
- * @param {string} type - The type we're checking for.
33
- *
34
- * This supports all the same type names as `typeof` plus any of
35
- * the properties defined in the `TYPES` object.
36
- *
37
- * One other thing, while `typeof` reports `null` as being an `object`,
38
- * this function does not count `null` as a valid `object`.
39
- *
40
- * @param {*} v - The value we're testing.
41
- * @returns {boolean} If the value was of the desired type.
42
- * @alias module:@lumjs/core/types.isType
43
- */
44
- function isType(type, v)
45
- {
46
- if (typeof type !== S || !TYPES.list.includes(type))
47
- {
48
- throw new TypeError(`Invalid type ${JSON.stringify(type)} specified`);
49
- }
50
-
51
- if (typeof TYPES.tests[type] === F)
52
- { // A type-specific test.
53
- return TYPES.tests[type](v);
54
- }
55
- else
56
- { // No type-specific tests.
57
- return (typeof v === type);
58
- }
59
- }
60
-
61
- exports.isType = isType;
62
-
63
- // Default options parser.
64
- const DEFAULT_ISA_PARSER = function(type, v)
65
- { // `this` is the options object itself.
66
- if (typeof type.is === F)
67
- { // We assume `is()` methods are the type check.
68
- if (type.is(v)) return true;
69
- }
70
-
71
- if (typeof type.isa === O)
72
- { // Process our known options.
73
- const opts = type.isa;
74
-
75
- if (typeof opts.process === F)
76
- { // We'll pass the current `opts` as well as the `v` to this method.
77
- // It doesn't return anything on its own, but can assign further tests,
78
- // which by default only apply to `object`
79
- opts.process(this, v);
80
- }
81
-
82
- if (typeof opts.parsers === F)
83
- { // Assign another parser.
84
- this.parsers.push(opts.parsers);
85
- }
86
-
87
- if (typeof opts.test === F)
88
- { // A custom test, will be passed `v` and the current `opts`.
89
- if (opts.test(v, this))
90
- { // Mark this as having passed.
91
- return true;
92
- }
93
- }
94
-
95
- // Okay, now anything else gets set.
96
- const RESERVED = ['parsers','process','test'];
97
- for (const opt in opts)
98
- {
99
- if (RESERVED.includes(opt)) continue; // Skip it.
100
- this[opt] = opts[opt];
101
- }
102
-
103
- }
104
-
105
- // We should almost always return false.
106
- return false;
107
- }
108
-
109
- /**
110
- * Is value a certain type, or an instance of a certain class.
111
- *
112
- * @param {*} v - The value we're testing.
113
- * @param {...any} types - The types the value should be one of.
114
- *
115
- * For each of the `types`, if it is a `string` we test with `isType()`,
116
- * if it is a `function` we test with `isInstance()`.
117
- *
118
- * If it is an `object` and has an `is()` method, use that as the test.
119
- *
120
- * If it is an `object` it will be passed to the current options parsers.
121
- * The default options parser looks for an `object` property called `isa`,
122
- * which supports the following child properties:
123
- *
124
- * - `needProto: boolean`, Change the `needProto` option for `isInstance()`
125
- * - `parsers: function`, Add another options parser function.
126
- * - `process: function`, A one-time set-up function.
127
- * - `test: function`, Pass the `v` to this test and return `true` if it passes.
128
- * - Anything else will be set as an option.
129
- *
130
- * Any other type value will only match if `v === type`
131
- *
132
- * @returns {boolean} Was the value one of the desired types?
133
- * @alias module:@lumjs/core/types.isa
134
- */
135
- function isa(v, ...types)
136
- {
137
- // A special options object.
138
- const opts =
139
- {
140
- needProto: false,
141
- parsers:
142
- [
143
- DEFAULT_ISA_PARSER,
144
- ],
145
- process(type, v)
146
- {
147
- for (const parser of this.parsers)
148
- {
149
- if (typeof parser === F)
150
- {
151
- if (parser.call(this, type, v) === true)
152
- { // Returning true means a custom test passed.
153
- return true;
154
- }
155
- }
156
- }
157
- return false;
158
- },
159
- }
160
-
161
- for (const type of types)
162
- {
163
- // First a quick test for absolute equality.
164
- if (v === type) return true;
165
-
166
- // With that out of the way, let's go!
167
- if (typeof type === S)
168
- { // A string is passed to isType()
169
- if (isType(type, v)) return true;
170
- }
171
- else if (typeof type === F)
172
- { // A function is passed to isInstance()
173
- if (isInstance(v, type, opts.needProto)) return true;
174
- }
175
- else if (isObj(type))
176
- { // Objects can be additional tests, or options.
177
- if (opts.process(type, v)) return true;
178
- }
179
-
180
- }
13
+ function isInstance(v, f, needProto=false)
14
+ {
15
+ if (!isObj(v)) return false; // Not an object.
16
+ if (needProto && (typeof v.prototype !== O || v.prototype === null))
17
+ { // Has no prototype.
18
+ return false;
19
+ }
20
+
21
+ if (typeof f !== F || !(v instanceof f)) return false;
22
+
23
+ // Everything passed.
24
+ return true;
25
+ }
181
26
 
182
- // None of the tests passed. We have failed.
183
- return false;
184
- }
27
+ exports.isInstance = isInstance;
28
+
29
+ /**
30
+ * A smarter `typeof` function.
31
+ *
32
+ * @param {string} type - The type we're checking for.
33
+ *
34
+ * This supports all the same type names as `typeof` plus any of
35
+ * the properties defined in the `TYPES` object.
36
+ *
37
+ * One other thing, while `typeof` reports `null` as being an `object`,
38
+ * this function does not count `null` as a valid `object`.
39
+ *
40
+ * @param {*} v - The value we're testing.
41
+ * @returns {boolean} If the value was of the desired type.
42
+ * @alias module:@lumjs/core/types.isType
43
+ */
44
+ function isType(type, v)
45
+ {
46
+ if (typeof type !== S || !TYPES.list.includes(type))
47
+ {
48
+ throw new TypeError(`Invalid type ${JSON.stringify(type)} specified`);
49
+ }
50
+
51
+ if (typeof TYPES.tests[type] === F)
52
+ { // A type-specific test.
53
+ return TYPES.tests[type](v);
54
+ }
55
+ else
56
+ { // No type-specific tests.
57
+ return (typeof v === type);
58
+ }
59
+ }
60
+
61
+ exports.isType = isType;
62
+
63
+ // Default options parser.
64
+ const DEFAULT_ISA_PARSER = function(type, v)
65
+ { // `this` is the options object itself.
66
+ if (typeof type.is === F)
67
+ { // We assume `is()` methods are the type check.
68
+ if (type.is(v)) return true;
69
+ }
70
+
71
+ if (typeof type.isa === O)
72
+ { // Process our known options.
73
+ const opts = type.isa;
74
+
75
+ if (typeof opts.process === F)
76
+ { // Run a method to extend the options further.
77
+ opts.process(this, v, type);
78
+ }
79
+
80
+ if (typeof opts.parsers === F)
81
+ { // Assign another parser.
82
+ this.parsers.push(opts.parsers);
83
+ }
84
+
85
+ if (typeof opts.test === F)
86
+ { // A custom test, will be passed `v` and the current `opts`.
87
+ if (opts.test(v, this))
88
+ { // Mark this as having passed.
89
+ return true;
90
+ }
91
+ }
92
+
93
+ // Okay, now anything else gets set.
94
+ const RESERVED = ['parsers','process','test'];
95
+ for (const opt in opts)
96
+ {
97
+ if (RESERVED.includes(opt)) continue; // Skip it.
98
+ this[opt] = opts[opt];
99
+ }
100
+
101
+ }
102
+
103
+ // We should almost always return false.
104
+ return false;
105
+ }
106
+
107
+ // Process further options
108
+ function processOptions(type, v)
109
+ {
110
+ for (const parser of this.parsers)
111
+ {
112
+ if (typeof parser === F)
113
+ {
114
+ if (parser.call(this, type, v) === true)
115
+ { // Returning true means a custom test passed.
116
+ return true;
117
+ }
118
+ }
119
+ }
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Is value a certain type, or an instance of a certain class.
125
+ *
126
+ * @param {*} v - The value we're testing.
127
+ * @param {...any} types - The types the value should be one of.
128
+ *
129
+ * For each of the `types`, if it is a `string` we test with `isType()`,
130
+ * if it is a `function` we test with `isInstance()`.
131
+ *
132
+ * If it is an `object` and has an `is()` method, use that as the test.
133
+ *
134
+ * If it is an `object` it will be passed to the current options parsers.
135
+ * The default options parser looks for an `object` property called `isa`,
136
+ * which supports the following child properties:
137
+ *
138
+ * - `needProto: boolean`, Change the `needProto` option for `isInstance()`
139
+ * - `parsers: function`, Add another options parser function.
140
+ * - `process: function`, A one-time set-up function.
141
+ * - `test: function`, Pass the `v` to this test and return `true` if it passes.
142
+ * - Anything else will be set as an option.
143
+ *
144
+ * Any other type value will only match if `v === type`
145
+ *
146
+ * @returns {boolean} Was the value one of the desired types?
147
+ * @alias module:@lumjs/core/types.isa
148
+ */
149
+ function isa(v, ...types)
150
+ {
151
+ // A special options object.
152
+ const opts =
153
+ {
154
+ needProto: false,
155
+ parsers: [DEFAULT_ISA_PARSER],
156
+ process: processOptions,
157
+ }
158
+
159
+ for (const type of types)
160
+ {
161
+ // First a quick test for absolute equality.
162
+ if (v === type) return true;
163
+
164
+ // With that out of the way, let's go!
165
+ if (typeof type === S)
166
+ { // A string is passed to isType()
167
+ if (isType(type, v)) return true;
168
+ }
169
+ else if (typeof type === F)
170
+ { // A function is passed to isInstance()
171
+ if (isInstance(v, type, opts.needProto)) return true;
172
+ }
173
+ else if (isObj(type))
174
+ { // Objects can be additional tests, or options.
175
+ if (opts.process(type, v)) return true;
176
+ }
177
+ }
178
+
179
+ // None of the tests passed. We have failed.
180
+ return false;
181
+ }
185
182
 
186
- exports.isa = isa;
183
+ exports.isa = isa;
@@ -6,23 +6,42 @@ const {isObj, isComplex} = require('./basics');
6
6
  * If a value is not an object, throw an error.
7
7
  *
8
8
  * @param {*} v - The value we're testing.
9
- * @param {boolean} [allowFunc=false] - Also accept functions?
9
+ * @param {(boolean|string)} [allowFunc=false] - Also accept `function`?
10
+ *
11
+ * By default this function uses `isObj()` to perform the
12
+ * test. If `allowFunc` is `true` then it'll use `isComplex()` instead.
13
+ *
14
+ * If this is a `string` then it'll be assigned to the `msg`
15
+ * parameter, and `allowFunc` will be `true` if the word
16
+ * `function` is found in the message, or `false` otherwise.
17
+ *
10
18
  * @param {string} [msg] A custom error message.
19
+ *
20
+ * If not specified, a generic one will be used.
21
+ *
11
22
  * @throws {TypeError} If the type check failed.
12
23
  * @alias module:@lumjs/core/types.needObj
13
24
  */
14
25
  function needObj (v, allowFunc=false, msg=null)
15
26
  {
16
- if (allowFunc && isComplex(v)) return;
17
- if (isObj(v)) return;
27
+ if (typeof allowFunc === S)
28
+ { // A message was passed.
29
+ msg = allowFunc;
30
+ allowFunc = msg.toLowerCase().includes(F);
31
+ }
18
32
 
19
- if (typeof msg !== S)
20
- { // Use a default message.
21
- msg = "Invalid object";
22
- if (allowFunc)
23
- msg += " or function";
33
+ const ok = allowFunc ? isComplex(v) : isObj(v);
34
+
35
+ if (!ok)
36
+ { // Did not pass the test.
37
+ if (typeof msg !== S)
38
+ { // Use a default message.
39
+ msg = "Invalid object";
40
+ if (allowFunc)
41
+ msg += " or function";
42
+ }
43
+ throw new TypeError(msg);
24
44
  }
25
- throw new TypeError(msg);
26
45
  }
27
46
 
28
47
  exports.needObj = needObj;
@@ -101,6 +120,7 @@ function needs(v, ...types)
101
120
  {
102
121
  error = new TypeError("value did not pass needs check");
103
122
  }
123
+ console.error("needs()", v, types);
104
124
  throw error;
105
125
  }
106
126
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {