@lumjs/core 1.38.3 → 1.38.5
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/context.js +14 -12
- package/lib/env.js +937 -0
- package/lib/index.js +19 -25
- package/lib/meta.js +8 -14
- package/lib/node/index.js +14 -0
- package/lib/{modules.js → node/modules.js} +35 -6
- package/lib/node/package.js +107 -0
- package/lib/obj/apply.js +237 -29
- package/lib/obj/copyall.js +14 -20
- package/lib/obj/copyprops.js +6 -386
- package/lib/obj/cp.js +4 -1370
- package/lib/obj/cycle.js +182 -0
- package/lib/obj/cycle2.js +122 -0
- package/lib/obj/index.js +4 -4
- package/lib/obj/ns/formats.js +300 -0
- package/lib/obj/{ns.js → ns/index.js} +160 -37
- package/lib/objectid.js +236 -71
- package/lib/state.js +30 -45
- package/lib/types/index.js +1 -1
- package/lib/types/stringify.js +45 -5
- package/package.json +8 -3
- /package/lib/{types → obj}/owncount.js +0 -0
package/lib/obj/cp.js
CHANGED
|
@@ -1,1374 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Object property copying utilities
|
|
3
|
-
* @module @lumjs/core/obj/cp
|
|
4
|
-
*/
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
const {S,F,def,isObj,isComplex,isArray,isNil} = require('../types');
|
|
8
|
-
|
|
9
|
-
/// @see docs/src/obj-cp.js for type-defs and callbacks
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* A full set of type handlers for `cp`.
|
|
13
|
-
*
|
|
14
|
-
* @extends {Set}
|
|
15
|
-
* @alias module:@lumjs/core/obj/cp.HandlerSet
|
|
16
|
-
*/
|
|
17
|
-
class HandlerSet extends Set
|
|
18
|
-
{
|
|
19
|
-
/**
|
|
20
|
-
* Add a type handler to this set.
|
|
21
|
-
* @param {module:@lumjs/core/obj/cp~Typ} type
|
|
22
|
-
*
|
|
23
|
-
* May alternatively be another `HandlerSet` instance,
|
|
24
|
-
* in which case all the type handlers in it will be added.
|
|
25
|
-
*
|
|
26
|
-
* @returns {module:@lumjs/core/obj/cp.HandlerSet} `this`
|
|
27
|
-
* @throws {TypeError} If `typedef` is not valid.
|
|
28
|
-
*/
|
|
29
|
-
add(value)
|
|
30
|
-
{
|
|
31
|
-
if (value instanceof HandlerSet)
|
|
32
|
-
{
|
|
33
|
-
for (const th of value)
|
|
34
|
-
{
|
|
35
|
-
super.add(th);
|
|
36
|
-
}
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
else if ($TY.is(value))
|
|
40
|
-
{
|
|
41
|
-
return super.add(value);
|
|
42
|
-
}
|
|
43
|
-
else
|
|
44
|
-
{
|
|
45
|
-
console.debug({value, set: this});
|
|
46
|
-
throw new TypeError("Invalid TypeDef object");
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Find the handler for a given subject.
|
|
52
|
-
*
|
|
53
|
-
* If no explicit handler in this set supports the subject,
|
|
54
|
-
* the special `object` handler will be returned as a default.
|
|
55
|
-
*
|
|
56
|
-
* This method caches the result for each `subject` so that
|
|
57
|
-
* future requests don't have to run any tests.
|
|
58
|
-
*
|
|
59
|
-
* @param {object} subject
|
|
60
|
-
* @returns {module:@lumjs/core/obj/cp~Typ}
|
|
61
|
-
*/
|
|
62
|
-
for(subject)
|
|
63
|
-
{
|
|
64
|
-
if (this.subCache === undefined)
|
|
65
|
-
{
|
|
66
|
-
def(this, 'subCache', {value: new Map()});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (this.subCache.has(subject))
|
|
70
|
-
{
|
|
71
|
-
return this.subCache.get(subject);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for (const def of this)
|
|
75
|
-
{
|
|
76
|
-
if (def.test(subject))
|
|
77
|
-
{ // Found one.
|
|
78
|
-
this.subCache.set(subject, def);
|
|
79
|
-
return def;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// No specific handler found, use the default.
|
|
84
|
-
this.subCache.set(subject, TY.object);
|
|
85
|
-
return TY.object;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Clears the subject cache used by `for()`
|
|
90
|
-
*/
|
|
91
|
-
clearCache()
|
|
92
|
-
{
|
|
93
|
-
if (this.subCache instanceof Map)
|
|
94
|
-
{
|
|
95
|
-
this.subCache.clear();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
} // HandlerSet class
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Metadata and API methods for `cp.TY`
|
|
103
|
-
* @namespace
|
|
104
|
-
* @alias module:@lumjs/core/obj/cp.TY.self
|
|
105
|
-
*/
|
|
106
|
-
const $TY =
|
|
107
|
-
{
|
|
108
|
-
/**
|
|
109
|
-
* Property names that will be skipped by `TY.for()` method
|
|
110
|
-
*/
|
|
111
|
-
SKIP_FOR: ['for','object','self'],
|
|
112
|
-
/**
|
|
113
|
-
* Property names reserved for HandlerSet getter properties in `TY`
|
|
114
|
-
*/
|
|
115
|
-
DEF_SETS: ['all','default'],
|
|
116
|
-
/**
|
|
117
|
-
* Mandatory methods (function properties) in a valid type handler.
|
|
118
|
-
*/
|
|
119
|
-
NEED_FNS: ['test','new','clone','cpOver','cpSafe','getProps'],
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Default type handler functions used by `make()` function
|
|
123
|
-
*/
|
|
124
|
-
DEFAULTS:
|
|
125
|
-
{
|
|
126
|
-
clone(o,c)
|
|
127
|
-
{
|
|
128
|
-
return Object.assign(this.new(c), o);
|
|
129
|
-
},
|
|
130
|
-
cpOver: (o,s) => Object.assign(o, ...s),
|
|
131
|
-
cpSafe(o,ss,c)
|
|
132
|
-
{
|
|
133
|
-
for (const s of ss)
|
|
134
|
-
{
|
|
135
|
-
const ps = this.getProps(s,c);
|
|
136
|
-
for (const p in ps)
|
|
137
|
-
{
|
|
138
|
-
if (o[p] === undefined)
|
|
139
|
-
{
|
|
140
|
-
const d = ps[p];
|
|
141
|
-
def(o, p, d);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return o;
|
|
146
|
-
},
|
|
147
|
-
getProps(o,c)
|
|
148
|
-
{
|
|
149
|
-
const ap = Object.getOwnPropertyDescriptors(o)
|
|
150
|
-
if (c?.opts?.all) return ap;
|
|
151
|
-
|
|
152
|
-
const ep = {};
|
|
153
|
-
for (const p in ap)
|
|
154
|
-
{
|
|
155
|
-
if (ap[p].enumerable)
|
|
156
|
-
{
|
|
157
|
-
ep[p] = ap[p];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return ep;
|
|
161
|
-
},
|
|
162
|
-
extend()
|
|
163
|
-
{
|
|
164
|
-
return Object.assign({}, this, ...arguments);
|
|
165
|
-
},
|
|
166
|
-
}, // $TY.DEFAULTS
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* An array of reserved property names in `TY`;
|
|
170
|
-
* consists of `SKIP_FOR` and `DEF_SETS` combined.
|
|
171
|
-
*/
|
|
172
|
-
get reserved()
|
|
173
|
-
{
|
|
174
|
-
return $TY.DEF_SETS.concat($TY.SKIP_FOR);
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* An array of the absolute minimum _required_ methods that **MUST**
|
|
179
|
-
* be included when calling `make()` or `add()`.
|
|
180
|
-
*
|
|
181
|
-
* It's `NEED_FNS` excluding any functions provided in `DEFAULTS`.
|
|
182
|
-
*/
|
|
183
|
-
get requirements()
|
|
184
|
-
{
|
|
185
|
-
const defs = Object.keys($TY.DEFAULTS);
|
|
186
|
-
return $TY.NEED_FNS.filter(v => !defs.includes(v));
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Make a new type handler definition
|
|
191
|
-
*
|
|
192
|
-
* @param {object} indef - Properties/functions for the handler
|
|
193
|
-
*
|
|
194
|
-
* Must contain at least functions for `test` and `new`.
|
|
195
|
-
* Any other mandatory functions will fallback on default versions.
|
|
196
|
-
*
|
|
197
|
-
* @return {module:@lumjs/core/obj/cp~Typ}
|
|
198
|
-
* @throws {TypeError} If any required properties/functions are missing.
|
|
199
|
-
*/
|
|
200
|
-
make(indef)
|
|
201
|
-
{
|
|
202
|
-
const outdef = Object.assign({}, $TY.DEFAULTS, indef);
|
|
203
|
-
if (!$TY.is(outdef))
|
|
204
|
-
{
|
|
205
|
-
console.debug({indef, outdef, required: $TY.requirements});
|
|
206
|
-
throw new TypeError("Type handler is missing requirements");
|
|
207
|
-
}
|
|
208
|
-
return outdef;
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Add a new _named_ type handler (or HandlerSet) to `cp.TY`
|
|
213
|
-
*
|
|
214
|
-
* @param {string} name - Name for the new type or set
|
|
215
|
-
*
|
|
216
|
-
* For types, the classname is generally a safe choice.
|
|
217
|
-
* Examples: `Set`, `Map`, `Element`, `NodeList`, etc.
|
|
218
|
-
* Or lowercase variants: `map`, `nodelist`, etc.
|
|
219
|
-
*
|
|
220
|
-
* For extensions of existing types, try to describe the changes.
|
|
221
|
-
* Examples: `arrayPush`, `lockedObject`, etc.
|
|
222
|
-
*
|
|
223
|
-
* For sets, something descriptive of what the set contains or does.
|
|
224
|
-
* Examples: `dom`, `addToLists`, etc.
|
|
225
|
-
*
|
|
226
|
-
* The name may not be any value in the `reserved` list!
|
|
227
|
-
*
|
|
228
|
-
* @param {object} obj - What we are adding
|
|
229
|
-
*
|
|
230
|
-
* If this is a `HandlerSet` object, or implements the complete
|
|
231
|
-
* [type handler interface]{@link module:@lumjs/core/obj/cp~Typ},
|
|
232
|
-
* it will be added directly.
|
|
233
|
-
*
|
|
234
|
-
* Anything else will be passed to
|
|
235
|
-
* {@link module:@lumjs/core/obj/cp.TY.self.make make()}
|
|
236
|
-
* to build a type handler.
|
|
237
|
-
*
|
|
238
|
-
* @returns {module:@lumjs/core/obj/cp.TY.self}
|
|
239
|
-
* So you can chain multiple .add() calls together.
|
|
240
|
-
*
|
|
241
|
-
* @throws {RangeError} If `name` was a reserved value
|
|
242
|
-
* @throws {TypeError} See `make()` for details
|
|
243
|
-
* @see module:@lumjs/core/obj/cp.TY.self.make
|
|
244
|
-
*/
|
|
245
|
-
add(name, obj)
|
|
246
|
-
{
|
|
247
|
-
if ($TY.reserved.includes(name))
|
|
248
|
-
{
|
|
249
|
-
throw new RangeError(name+' is a reserved property name');
|
|
250
|
-
}
|
|
251
|
-
if (!(obj instanceof HandlerSet) && !$TY.is(obj))
|
|
252
|
-
{
|
|
253
|
-
obj = $TY.make(obj);
|
|
254
|
-
}
|
|
255
|
-
TY[name] = obj;
|
|
256
|
-
return this;
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Is a value a valid type handler definition for `cp`?
|
|
261
|
-
* @param {*} v - Value to test
|
|
262
|
-
* @returns {boolean}
|
|
263
|
-
* @see module:@lumjs/core/obj/cp~Typ
|
|
264
|
-
*/
|
|
265
|
-
is(v)
|
|
266
|
-
{
|
|
267
|
-
if (!isObj(v)) return false;
|
|
268
|
-
|
|
269
|
-
for (const need of $TY.NEED_FNS)
|
|
270
|
-
{
|
|
271
|
-
if (typeof v[need] !== F)
|
|
272
|
-
{
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return true;
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
} // {$TY}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Type handler definitions for specific types of objects
|
|
284
|
-
* that require special handling when cloning or composing.
|
|
285
|
-
*
|
|
286
|
-
* @namespace
|
|
287
|
-
* @alias module:@lumjs/core/obj/cp.TY
|
|
288
|
-
*/
|
|
289
|
-
const TY =
|
|
290
|
-
{
|
|
291
|
-
/**
|
|
292
|
-
* Handler for `object`
|
|
293
|
-
*
|
|
294
|
-
* This is a special implicit handler and does NOT need to *ever*
|
|
295
|
-
* be explicitly added to a `HandlerSet`, as it's always used as
|
|
296
|
-
* the *default fallback* if no other type handler matches.
|
|
297
|
-
*/
|
|
298
|
-
object: $TY.make(
|
|
299
|
-
{
|
|
300
|
-
_id: 'object',
|
|
301
|
-
test: () => true,
|
|
302
|
-
new: () => ({}),
|
|
303
|
-
}),
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Handler for `Array`
|
|
307
|
-
*
|
|
308
|
-
* It's `new()` method returns `[]`, and it's `clone()` method
|
|
309
|
-
* uses `subject.slice()` as a simple shallow array clone.
|
|
310
|
-
*
|
|
311
|
-
* No other array-specific behaviour is implemented in this very
|
|
312
|
-
* basic type handler, however it would be easy enough to extend
|
|
313
|
-
* it to add custom functionality (such as appending all sources
|
|
314
|
-
* into the subject for example).
|
|
315
|
-
*
|
|
316
|
-
* @type {module:@lumjs/core/obj/cp~Typ}
|
|
317
|
-
*/
|
|
318
|
-
array: $TY.make(
|
|
319
|
-
{
|
|
320
|
-
_id: 'array',
|
|
321
|
-
test: isArray,
|
|
322
|
-
new: () => ([]),
|
|
323
|
-
clone: v => v.slice(),
|
|
324
|
-
}),
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Assemble a complete Type Handler set
|
|
328
|
-
*
|
|
329
|
-
* @param {...(string|object)} types - Type handlers to include
|
|
330
|
-
*
|
|
331
|
-
* If this is a `string` it must be the name of a type handler
|
|
332
|
-
* property registered with the `TY` object itself.
|
|
333
|
-
*
|
|
334
|
-
* If this is an `object`, it must implement the
|
|
335
|
-
* {@link module:@lumjs/core/obj/cp~Typ} interface.
|
|
336
|
-
*
|
|
337
|
-
* @returns {module:@lumjs/core/obj/cp.HandlerSet}
|
|
338
|
-
* @throws {TypeError} If any of the `types` are not valid.
|
|
339
|
-
*/
|
|
340
|
-
for(...types)
|
|
341
|
-
{
|
|
342
|
-
const defset = new HandlerSet();
|
|
343
|
-
|
|
344
|
-
for (const type of types)
|
|
345
|
-
{ // First we'll add specified handlers.
|
|
346
|
-
if (typeof type === S
|
|
347
|
-
&& !$TY.SKIP_FOR.includes(type)
|
|
348
|
-
&& (type in this))
|
|
349
|
-
{ // The name of one of our registered types.
|
|
350
|
-
defset.add(this[type]);
|
|
351
|
-
}
|
|
352
|
-
else if (type !== TY.object)
|
|
353
|
-
{ // If it isn't a valid TypeDef, a TypeError will be thrown.
|
|
354
|
-
defset.add(type);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return defset;
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* A getter for a base HandlerSet with only `array` and `object` support.
|
|
363
|
-
*
|
|
364
|
-
* This getter creates a new instance every time it is accessed.
|
|
365
|
-
*
|
|
366
|
-
* @type {module:@lumjs/core/obj/cp.HandlerSet}
|
|
367
|
-
*/
|
|
368
|
-
get base()
|
|
369
|
-
{
|
|
370
|
-
return new HandlerSet([this.array]);
|
|
371
|
-
},
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* A getter for a handler set with *all* of our registered
|
|
375
|
-
* type handlers included in it.
|
|
376
|
-
*
|
|
377
|
-
* Unless another library like `@lumjs/web-core` has added
|
|
378
|
-
* extra type handlers, this will end up being the same as
|
|
379
|
-
* `default` at this point in time.
|
|
380
|
-
*
|
|
381
|
-
* Like the `default` accessor property, this getter creates
|
|
382
|
-
* a new instance every time it is accessed.
|
|
383
|
-
*
|
|
384
|
-
* @type {module:@lumjs/core/obj/cp.HandlerSet}
|
|
385
|
-
*/
|
|
386
|
-
get all()
|
|
387
|
-
{
|
|
388
|
-
const types = Object.keys(this)
|
|
389
|
-
.filter((name) => !$TY.reserved.includes(name))
|
|
390
|
-
.map((name) => this[name]);
|
|
391
|
-
return new HandlerSet(types);
|
|
392
|
-
},
|
|
393
|
-
|
|
394
|
-
} // {TY}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* A globally shared default HandlerSet that will be used
|
|
398
|
-
* if no other is specified manually.
|
|
399
|
-
*
|
|
400
|
-
* Uses an instance of `cp.TY.base` as the default value.
|
|
401
|
-
* May be overridden with any valid HandlerSet.
|
|
402
|
-
*
|
|
403
|
-
* @type {module:@lumjs/core/obj/cp.HandlerSet}
|
|
404
|
-
* @alias module:@lumjs/core/obj/cp.TY.default
|
|
405
|
-
*/
|
|
406
|
-
TY.default = TY.base;
|
|
407
|
-
|
|
408
|
-
function cpArgs(subject, ...sources)
|
|
409
|
-
{
|
|
410
|
-
let handlers;
|
|
411
|
-
if (subject instanceof HandlerSet)
|
|
412
|
-
{
|
|
413
|
-
handlers = subject;
|
|
414
|
-
subject = sources.shift();
|
|
415
|
-
}
|
|
416
|
-
else
|
|
417
|
-
{
|
|
418
|
-
handlers = TY.default;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const args = {subject, sources, handlers};
|
|
422
|
-
|
|
423
|
-
if (!isComplex(subject))
|
|
424
|
-
{
|
|
425
|
-
console.debug("Invalid subject", args);
|
|
426
|
-
args.invalid = true;
|
|
427
|
-
args.done = true;
|
|
428
|
-
return args;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
args.th = handlers.for(subject);
|
|
432
|
-
|
|
433
|
-
if (sources.length === 0)
|
|
434
|
-
{ // No sources? Just clone the subject.
|
|
435
|
-
args.subject = args.th.clone(subject);
|
|
436
|
-
args.done = true;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return args;
|
|
440
|
-
} // cpArgs()
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Copy enumerable properties into a subject.
|
|
444
|
-
*
|
|
445
|
-
* This version overwrites any existing properties, with latter sources
|
|
446
|
-
* taking precedence over earlier ones.
|
|
447
|
-
*
|
|
448
|
-
* See {@link module:@lumjs/core/obj/cp.safe cp.safe()} for a version that
|
|
449
|
-
* only copies non-existent properties.
|
|
450
|
-
*
|
|
451
|
-
* **Note**: this is the actual `obj.cp` default export value!
|
|
452
|
-
*
|
|
453
|
-
* To keep JSDoc happy it is listed as a _named export_ inside
|
|
454
|
-
* the `cp` module, but in reality, it *is* the module.
|
|
455
|
-
*
|
|
456
|
-
* So when you do: `const cp = require('@lumjs/core/obj/cp');`
|
|
457
|
-
* or `const core = require('@lumjs/core), {cp} = core.obj;`
|
|
458
|
-
* the `cp` variable will be this function. All the other named
|
|
459
|
-
* exports are properties attached to the function object itself.
|
|
460
|
-
*
|
|
461
|
-
* The named export *does* exist as an alias to itself (`cp.cp = cp`).
|
|
462
|
-
*
|
|
463
|
-
* @param {object} subject - Subject of the copy operation
|
|
464
|
-
*
|
|
465
|
-
* If this is a {@link module:@lumjs/core/obj/cp.HandlerSet HandlerSet},
|
|
466
|
-
* then it will be used instead of the default set, and the first
|
|
467
|
-
* item in `sources` will be re-assigned to the `subject` argument.
|
|
468
|
-
*
|
|
469
|
-
* If the `sources` have `0` items (after the HandlerSet logic obviously),
|
|
470
|
-
* then it indicates we want a shallow _clone_ of the subject. Which uses
|
|
471
|
-
* the {@link module:@lumjs/core/obj/cp~TypClone} method from the handler.
|
|
472
|
-
* Examples: `cp(myObj)` or `cp(myHandlers, myObj)`
|
|
473
|
-
*
|
|
474
|
-
* If you need to include *all* properties (not just *enumerable* ones),
|
|
475
|
-
* or need to do any kind of recursion or other advanced features, you'll
|
|
476
|
-
* need to use the declarative API. e.g. `cp.from(subject).all.clone()`
|
|
477
|
-
*
|
|
478
|
-
* @param {...object} [sources] Sources to copy into the subject
|
|
479
|
-
*
|
|
480
|
-
* This uses the {@link module:@lumjs/core/obj/cp~TypCpOver cpOver} method.
|
|
481
|
-
* The default version of which uses `Object.assign()` to copy properties
|
|
482
|
-
* from each source into the subject. Which may have odd results if used
|
|
483
|
-
* with certain kinds of objects.
|
|
484
|
-
*
|
|
485
|
-
* Like with cloning, if you need any advanced features, you'll need to
|
|
486
|
-
* use the declarative API. e.g. `cp.into(target).ow.deep.from(source)`
|
|
487
|
-
*
|
|
488
|
-
* @returns {object} Either `subject` or a clone of `subject`.
|
|
489
|
-
*
|
|
490
|
-
* @alias module:@lumjs/core/obj/cp.cp
|
|
491
|
-
*/
|
|
492
|
-
function cp()
|
|
493
|
-
{
|
|
494
|
-
const args = cpArgs(...arguments);
|
|
495
|
-
if (args.done) return args.subject;
|
|
496
|
-
return args.th.cpOver(args.subject, args.sources);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Copy non-existent enumerable properties into a subject.
|
|
501
|
-
*
|
|
502
|
-
* This is a version of {@link module:@lumjs/core/obj/cp.cp cp()} that
|
|
503
|
-
* uses the {@link module:@lumjs/core/obj/cp~TypCpSafe cpSafe} method
|
|
504
|
-
* when copying properties so it does not overwrite existing properties.
|
|
505
|
-
*
|
|
506
|
-
* Other than that difference, the rest of the arguments and their
|
|
507
|
-
* associated behaviours are identical to `cp()`, so see that for details.
|
|
508
|
-
*
|
|
509
|
-
* @param {object} subject
|
|
510
|
-
* @param {...object} sources
|
|
511
|
-
* @returns {object}
|
|
512
|
-
* @alias module:@lumjs/core/obj/cp.safe
|
|
513
|
-
*/
|
|
514
|
-
cp.safe = function()
|
|
515
|
-
{
|
|
516
|
-
const args = cpArgs(...arguments);
|
|
517
|
-
if (args.done) return args.subject;
|
|
518
|
-
return args.th.cpSafe(args.subject, args.sources);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Make a clone of an object using JSON
|
|
2
|
+
* Object property copying utilities.
|
|
523
3
|
*
|
|
524
|
-
*
|
|
525
|
-
* It's a ridiculously simple method for simple _deep_ cloning,
|
|
526
|
-
* but comes with many limitations inherent to JSON serialization.
|
|
4
|
+
* NOTE: This now just re-exports the `@lumjs/cp` package.
|
|
527
5
|
*
|
|
528
|
-
* @
|
|
529
|
-
* @param {object} [opts] Advanced options
|
|
530
|
-
* @param {function} [opts.replace] A JSON _replacer_ function
|
|
531
|
-
* @param {function} [opts.revive] A JSON _reviver_ function
|
|
532
|
-
* @returns {object} A clone of the `obj`
|
|
533
|
-
* @alias module:@lumjs/core/obj/cp.json
|
|
534
|
-
*/
|
|
535
|
-
cp.json = function(obj, opts={})
|
|
536
|
-
{
|
|
537
|
-
if (!isObj(obj))
|
|
538
|
-
{
|
|
539
|
-
console.warn("cp.json() does not support non-object");
|
|
540
|
-
return obj;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return JSON.parse(JSON.stringify(obj, opts.replace), opts.revive);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const DEFAULT_CPAPI_OPTS =
|
|
547
|
-
{
|
|
548
|
-
all: false,
|
|
549
|
-
overwrite: false,
|
|
550
|
-
recursive: 0,
|
|
551
|
-
toggleDepth: -1,
|
|
552
|
-
proto: false,
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const VALIDATION =
|
|
556
|
-
{
|
|
557
|
-
handlers: v => v instanceof HandlerSet,
|
|
558
|
-
onUpdate: v => typeof v === F,
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
for (const opt in DEFAULT_CPAPI_OPTS)
|
|
562
|
-
{ // Generate some default validation rules based on type values.
|
|
563
|
-
if (VALIDATION[opt] === undefined)
|
|
564
|
-
{
|
|
565
|
-
const vt = typeof DEFAULT_CPAPI_OPTS[opt];
|
|
566
|
-
VALIDATION[opt] = v => typeof(v) === vt;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const CPAPI_TOGGLE_OPTS = ['deep','ow'];
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Context object for use in the declarative API.
|
|
574
|
-
*
|
|
575
|
-
* May have additional properties added by `opts.onUpdate()` if used.
|
|
576
|
-
* This documentation lists only standard properties that will be
|
|
577
|
-
* used without any custom options.
|
|
578
|
-
*
|
|
579
|
-
* Properties marked with **¿** will only be included when applicable.
|
|
580
|
-
*
|
|
581
|
-
* Properties marked with **¡** may be set automatically when applicable.
|
|
582
|
-
* Currently that only applies to type handlers, which will be looked
|
|
583
|
-
* up using `opts.handlers.for()` as a convenience shortcut.
|
|
584
|
-
*
|
|
585
|
-
* @prop {module:@lumjs/core/obj/cp.API} cp - API instance
|
|
586
|
-
* @prop {object} opts - Current options; defaults to `cp.opts`
|
|
587
|
-
* @prop {number} depth - Current recusion depth (`0` is top-level)
|
|
588
|
-
* @prop {?object} prev - Previous context (if applicable)
|
|
589
|
-
*
|
|
590
|
-
* Will be `null` unless if `opts.recusive` is not `0` and the
|
|
591
|
-
* context is for a nested operation (top-level will always be `null`).
|
|
592
|
-
*
|
|
593
|
-
* @prop {object} [target] Current target **¿**
|
|
594
|
-
* @prop {object} [source] Current source **¿**
|
|
595
|
-
* @prop {number} [ti] Current target index **¿**
|
|
596
|
-
* @prop {number} [si] Current source index **¿**
|
|
597
|
-
* @prop {module:@lumjs/core/obj/cp~Typ} [th] Handler for `target` **¡**
|
|
598
|
-
* @prop {module:@lumjs/core/obj/cp~Typ} [sh] Handler for `source` **¡**
|
|
599
|
-
*
|
|
600
|
-
* @prop {(string|symbol)} [prop] Property being operated on **¿**
|
|
601
|
-
* @prop {object} [td] Target descriptor for `prop` **¿**
|
|
602
|
-
* @prop {object} [sd] Source descriptor for `prop` **¿**
|
|
603
|
-
*
|
|
604
|
-
* @alias module:@lumjs/core/obj/cp~Context
|
|
605
|
-
*/
|
|
606
|
-
class CPContext
|
|
607
|
-
{
|
|
608
|
-
/**
|
|
609
|
-
* Build a new Context object.
|
|
610
|
-
*
|
|
611
|
-
* Generally this is only called by the `cp.getContext()` API method.
|
|
612
|
-
*
|
|
613
|
-
* This will call `this.update(prev, data)` after setting
|
|
614
|
-
* `this.cp` and `this.opts` to their default values.
|
|
615
|
-
*
|
|
616
|
-
* Lastly it sets `this.prev` overwriting any previous value.
|
|
617
|
-
*
|
|
618
|
-
* @param {module:@lumjs/core/obj/cp.API} api - API instance
|
|
619
|
-
* @param {object} data - Initial data
|
|
620
|
-
* @param {?module:@lumjs/core/obj/cp~Context} prev - Previous context
|
|
621
|
-
*/
|
|
622
|
-
constructor(api, data, prev)
|
|
623
|
-
{
|
|
624
|
-
this.cp = api;
|
|
625
|
-
this.opts = api.opts;
|
|
626
|
-
this.depth = 0;
|
|
627
|
-
this.update(prev, data);
|
|
628
|
-
this.prev = prev;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Update the context with new data
|
|
633
|
-
*
|
|
634
|
-
* Will automatically populate certain properties depending on
|
|
635
|
-
* the data added.
|
|
636
|
-
*
|
|
637
|
-
* @param {...object} data - Properties to add to the context
|
|
638
|
-
*
|
|
639
|
-
* Nothing is
|
|
640
|
-
*
|
|
641
|
-
* @returns {object} `this`
|
|
642
|
-
*/
|
|
643
|
-
update()
|
|
644
|
-
{
|
|
645
|
-
Object.assign(this, ...arguments);
|
|
646
|
-
|
|
647
|
-
if (typeof this.opts.onUpdate === F)
|
|
648
|
-
{
|
|
649
|
-
this.opts.onUpdate(this, ...arguments);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (this.target && !this.th)
|
|
653
|
-
{
|
|
654
|
-
this.th = this.opts.handlers.for(this.target);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (this.source && !this.sh)
|
|
658
|
-
{
|
|
659
|
-
this.sh = this.opts.handlers.for(this.source);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
return this;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Set context options
|
|
667
|
-
*
|
|
668
|
-
* It makes a _new object_ composing the original `this.opts` and `changes`,
|
|
669
|
-
* and sets that to `this.opts`. It does not change the original opts object.
|
|
670
|
-
*
|
|
671
|
-
* This is meant to be called from custom `onUpdate` handler functions,
|
|
672
|
-
* and isn't actually used by anything in the library itself.
|
|
673
|
-
*
|
|
674
|
-
* @param {object} changes - Any option values you want to change.
|
|
675
|
-
*
|
|
676
|
-
* @returns {object} `this`
|
|
677
|
-
*/
|
|
678
|
-
setOpts(changes)
|
|
679
|
-
{
|
|
680
|
-
this.opts = Object.assign({}, this.opts, changes);
|
|
681
|
-
return this;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
} // CPContext class
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* A class providing a declarative API for advanced `obj.cp` API calls.
|
|
688
|
-
*
|
|
689
|
-
* This is based off of the earlier `copyProps` declarative API,
|
|
690
|
-
* and is meant to replace it entirely in future releases of this library.
|
|
691
|
-
*
|
|
692
|
-
* @alias module:@lumjs/core/obj/cp.API
|
|
693
|
-
*/
|
|
694
|
-
class CPAPI
|
|
695
|
-
{
|
|
696
|
-
/**
|
|
697
|
-
* Create a declarative `obj.cp` API instance.
|
|
698
|
-
*
|
|
699
|
-
* You generally would never call this directly, but use one of the
|
|
700
|
-
* functions in `cp` instead, a few examples:
|
|
701
|
-
*
|
|
702
|
-
* ```js
|
|
703
|
-
* // Copy properties recursively, allowing overwrite
|
|
704
|
-
* cp.into(target).ow.deep.from(source);
|
|
705
|
-
*
|
|
706
|
-
* // Clone an object, including all (not just enumerable) properties
|
|
707
|
-
* let cloned = cp.from(obj).all.clone();
|
|
708
|
-
*
|
|
709
|
-
* ```
|
|
710
|
-
*
|
|
711
|
-
* @param {(object|function)} [opts] Options
|
|
712
|
-
*
|
|
713
|
-
* If this is a `function` it will be used as the `opts.onUpdate` value.
|
|
714
|
-
*
|
|
715
|
-
* If this is a {@link module:@lumjs/core/obj/cp.HandlerSet} object,
|
|
716
|
-
* it will be used as the `opts.handlers` value.
|
|
717
|
-
*
|
|
718
|
-
* @param {module:@lumjs/core/obj/cp.HandlerSet} [opts.handlers]
|
|
719
|
-
* Specific type handlers to use with this API instance.
|
|
720
|
-
*
|
|
721
|
-
* If not specified, the default set will be used.
|
|
722
|
-
*
|
|
723
|
-
* @param {boolean} [opts.all=false] Include all properties?
|
|
724
|
-
*
|
|
725
|
-
* If `false` (default), we'll only use *enumerable* properties.
|
|
726
|
-
* If `true` we'll include *all* property descriptors in the subject.
|
|
727
|
-
*
|
|
728
|
-
* @param {boolean} [opts.overwrite=false] Overwrite existing properties?
|
|
729
|
-
*
|
|
730
|
-
* @param {boolean} [opts.proto=false] Copy prototype?
|
|
731
|
-
*
|
|
732
|
-
* If `true`, the prototype on each target will be set to the
|
|
733
|
-
* prototype of the first source. This is `false` by default.
|
|
734
|
-
*
|
|
735
|
-
* @param {number} [opts.recursive=0] Recurse into nested objects?
|
|
736
|
-
*
|
|
737
|
-
* - `0` → disables recursion entirely (default)
|
|
738
|
-
* - `> 0` → specify depth recursion should be allowed
|
|
739
|
-
* - `< 0` → unlimited recursion depth
|
|
740
|
-
*
|
|
741
|
-
* When recursion is enabled, we will cache nested objects that we've
|
|
742
|
-
* already processed, so we don't get stuck in an infinite loop.
|
|
743
|
-
*
|
|
744
|
-
* @param {number} [opts.toggleDepth=-1] This is the recursion depth
|
|
745
|
-
* that will be used when the `deep` toggle value is `true`.
|
|
746
|
-
*
|
|
747
|
-
* @param {function} [opts.onUpdate] A custom function
|
|
748
|
-
*/
|
|
749
|
-
constructor(opts={})
|
|
750
|
-
{
|
|
751
|
-
if (typeof opts === F)
|
|
752
|
-
{
|
|
753
|
-
opts = {onUpdate: opts, handlers: TY.default};
|
|
754
|
-
}
|
|
755
|
-
else if (opts instanceof HandlerSet)
|
|
756
|
-
{
|
|
757
|
-
opts = {handlers: opts};
|
|
758
|
-
}
|
|
759
|
-
else if (!(opts.handlers instanceof HandlerSet))
|
|
760
|
-
{
|
|
761
|
-
opts.handlers = TY.default;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
this.opts = Object.assign({}, DEFAULT_CPAPI_OPTS);
|
|
765
|
-
this.set(opts);
|
|
766
|
-
|
|
767
|
-
this.sources = [];
|
|
768
|
-
this.targets = [];
|
|
769
|
-
|
|
770
|
-
this.nextToggle = true;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Get a context object to pass to type handler methods
|
|
775
|
-
* @param {object} data - Initial data for the context object
|
|
776
|
-
* @param {object} [prev] A previous context object (if applicable)
|
|
777
|
-
* @returns {module:@lumjs/core/obj/cp~Context}
|
|
778
|
-
*/
|
|
779
|
-
getContext(data, prev=null)
|
|
780
|
-
{
|
|
781
|
-
return new CPContext(this, data, prev);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Use specific type handlers or a custom context onUpdate function.
|
|
786
|
-
*
|
|
787
|
-
* @param {(module:@lumjs/core/obj/cp.HandlerSet|function)} arg
|
|
788
|
-
*
|
|
789
|
-
* If this is a `function`, we will set `opts.onUpdate` using it.
|
|
790
|
-
* Otherwise we will set `opts.handlers`.
|
|
791
|
-
*
|
|
792
|
-
* @returns {object} `this`
|
|
793
|
-
* @throws {TypeError} If `arg` is invalid
|
|
794
|
-
*/
|
|
795
|
-
use(arg)
|
|
796
|
-
{
|
|
797
|
-
const opt = typeof arg === F ? 'onUpdate' : 'handlers';
|
|
798
|
-
return this.set({[opt]: arg}, true);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* The next *toggle accessor property* will be set to `false`.
|
|
803
|
-
*
|
|
804
|
-
* This only affects the very next toggle, after which the
|
|
805
|
-
* toggle value will return to its default value of `true`.
|
|
806
|
-
*
|
|
807
|
-
* You need to explicitly use `not` before each toggle property
|
|
808
|
-
* that you want to turn off instead of on.
|
|
809
|
-
*/
|
|
810
|
-
get not()
|
|
811
|
-
{
|
|
812
|
-
this.nextToggle = false;
|
|
813
|
-
return this;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Private method that handles all *toggle properties*.
|
|
818
|
-
* @private
|
|
819
|
-
* @param {string} opt - Name of option to toggle
|
|
820
|
-
* @param {*} [trueVal=true] Value to use if toggle is `true`
|
|
821
|
-
* @param {*} [falseVal=false] Value to use if toggle is `false`
|
|
822
|
-
* @returns {object} `this`
|
|
823
|
-
*/
|
|
824
|
-
$toggle(opt, trueVal=true, falseVal=false)
|
|
825
|
-
{
|
|
826
|
-
this.opts[opt] = this.nextToggle ? trueVal : falseVal;
|
|
827
|
-
this.nextToggle = true;
|
|
828
|
-
return this;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* Toggle `opts.all` with boolean value when accessed
|
|
833
|
-
*/
|
|
834
|
-
get all()
|
|
835
|
-
{
|
|
836
|
-
return this.$toggle('all');
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Toggle `opts.overwrite` with boolean value when accessed
|
|
841
|
-
*/
|
|
842
|
-
get ow()
|
|
843
|
-
{
|
|
844
|
-
return this.$toggle('overwrite');
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Toggle `opts.recursive` when accessed
|
|
849
|
-
*
|
|
850
|
-
* Value to set depends on the toggle value:
|
|
851
|
-
*
|
|
852
|
-
* `true` → `opts.recursive = opts.toggleDepth`
|
|
853
|
-
* `false` → `opts.recursive = 0`
|
|
854
|
-
*
|
|
855
|
-
*/
|
|
856
|
-
get deep()
|
|
857
|
-
{
|
|
858
|
-
const max = this.opts.toggleDepth;
|
|
859
|
-
if (max === 0) console.error("toggleDepth is 0", {cp: this});
|
|
860
|
-
return this.$toggle('recursive', max, 0);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Set specified options
|
|
865
|
-
* @param {object} opts - Options to set
|
|
866
|
-
* @param {boolean} [fatal=false] Throw on invalid type?
|
|
867
|
-
*
|
|
868
|
-
* By default we report invalid option values to the console,
|
|
869
|
-
* and simply skip setting the invalid option.
|
|
870
|
-
*
|
|
871
|
-
* If this is `true` any invalid option values will result in
|
|
872
|
-
* an error being thrown, ending the entire `set()` process.
|
|
873
|
-
*
|
|
874
|
-
* @returns {object} `this`
|
|
875
|
-
* @throws {TypeError} See `fatal` argument for details
|
|
876
|
-
*/
|
|
877
|
-
set(opts, fatal=false)
|
|
878
|
-
{
|
|
879
|
-
if (isObj(opts))
|
|
880
|
-
{
|
|
881
|
-
for (const opt in opts)
|
|
882
|
-
{
|
|
883
|
-
if (opt in VALIDATION)
|
|
884
|
-
{
|
|
885
|
-
if (!VALIDATION[opt](opts[opt]))
|
|
886
|
-
{
|
|
887
|
-
const msg = 'invalid option value';
|
|
888
|
-
const info = {opt, opts, cp: this};
|
|
889
|
-
if (fatal)
|
|
890
|
-
{
|
|
891
|
-
console.error(info);
|
|
892
|
-
throw new TypeError(msg);
|
|
893
|
-
}
|
|
894
|
-
else
|
|
895
|
-
{
|
|
896
|
-
console.error(msg, info);
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
else if (opt in CPAPI_TOGGLE_OPTS)
|
|
902
|
-
{
|
|
903
|
-
if (!opts[opt])
|
|
904
|
-
{ // Negate the toggle.
|
|
905
|
-
this.not;
|
|
906
|
-
}
|
|
907
|
-
// Now toggle it.
|
|
908
|
-
this[opt];
|
|
909
|
-
continue;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
this.opts[opt] = opts[opt];
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
else
|
|
916
|
-
{
|
|
917
|
-
console.error("invalid opts", {opts, cp: this});
|
|
918
|
-
}
|
|
919
|
-
return this;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* Specify the `targets` to copy properties into.
|
|
924
|
-
*
|
|
925
|
-
* @param {...object} [targets] The target objects
|
|
926
|
-
*
|
|
927
|
-
* If you don't specify any targets, then `this.targets`
|
|
928
|
-
* will be cleared of any existing objects.
|
|
929
|
-
*
|
|
930
|
-
* If `this.sources` has objects in it, then we'll copy
|
|
931
|
-
* those sources into each of the specified targets.
|
|
932
|
-
*
|
|
933
|
-
* If `this.sources` is empty, then this will set
|
|
934
|
-
* `this.targets` to the specified value.
|
|
935
|
-
*
|
|
936
|
-
* @returns {object} `this`
|
|
937
|
-
*/
|
|
938
|
-
into(...targets)
|
|
939
|
-
{
|
|
940
|
-
if (this.sources.length > 0 && targets.length > 0)
|
|
941
|
-
{
|
|
942
|
-
this.$runAll(this.sources, targets);
|
|
943
|
-
}
|
|
944
|
-
else
|
|
945
|
-
{
|
|
946
|
-
this.targets = targets;
|
|
947
|
-
}
|
|
948
|
-
return this;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Specify the `sources` to copy properties from.
|
|
953
|
-
*
|
|
954
|
-
* @param {...object} [sources] The source objects.
|
|
955
|
-
*
|
|
956
|
-
* If you don't specify any sources, then `this.sources`
|
|
957
|
-
* will be cleared of any existing objects.
|
|
958
|
-
*
|
|
959
|
-
* If `this.targets` has objects in it, then we'll copy
|
|
960
|
-
* the specified sources into each of those targets.
|
|
961
|
-
*
|
|
962
|
-
* If `this.targets` is empty, then this will set
|
|
963
|
-
* `this.sources` to the specified value.
|
|
964
|
-
*
|
|
965
|
-
* @returns {object} `this`
|
|
966
|
-
*/
|
|
967
|
-
from(...sources)
|
|
968
|
-
{
|
|
969
|
-
if (this.targets.length > 0 && sources.length > 0)
|
|
970
|
-
{
|
|
971
|
-
this.$runAll(sources, this.targets);
|
|
972
|
-
}
|
|
973
|
-
else
|
|
974
|
-
{
|
|
975
|
-
this.sources = sources;
|
|
976
|
-
}
|
|
977
|
-
return this;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* Add additional subjects
|
|
982
|
-
*
|
|
983
|
-
* - If you started with `from()` this adds to the sources.
|
|
984
|
-
* - If you started with `into()` this adds to the targets.
|
|
985
|
-
*
|
|
986
|
-
* @param {...object} subjects - More sources or targets to add
|
|
987
|
-
* @returns {object} `this`
|
|
988
|
-
*/
|
|
989
|
-
and(...subjects)
|
|
990
|
-
{
|
|
991
|
-
if (this.sources.length > 0)
|
|
992
|
-
{
|
|
993
|
-
this.sources.push(...subjects);
|
|
994
|
-
}
|
|
995
|
-
else if (this.targets.length > 0)
|
|
996
|
-
{
|
|
997
|
-
this.targets.push(...subjects);
|
|
998
|
-
}
|
|
999
|
-
else
|
|
1000
|
-
{
|
|
1001
|
-
console.error("No sources or targets set", {subjects, cp: this});
|
|
1002
|
-
}
|
|
1003
|
-
return this;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* See if we can use EZ mode.
|
|
1008
|
-
*
|
|
1009
|
-
* EZ-mode is a shortcut to use `cp()` or `cp.safe()`
|
|
1010
|
-
* to perform copy operations if applicable.
|
|
1011
|
-
*/
|
|
1012
|
-
get isEZ()
|
|
1013
|
-
{
|
|
1014
|
-
const o = this.opts;
|
|
1015
|
-
return (!o.all && !o.recursive);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Clone the properties of the sources into a new object.
|
|
1020
|
-
*
|
|
1021
|
-
* This will only with if `cp.into()` was used to create the
|
|
1022
|
-
* API instance. It will throw an error if `cp.from()` was used.
|
|
1023
|
-
*
|
|
1024
|
-
* This gets the type handler for the first source, uses it
|
|
1025
|
-
* to get a new object, then runs the copy operation with the
|
|
1026
|
-
* new object as the sole target.
|
|
1027
|
-
*
|
|
1028
|
-
* @returns {object} A new object
|
|
1029
|
-
* @throws {RangeError} If there are targets set instead of sources
|
|
1030
|
-
*/
|
|
1031
|
-
clone()
|
|
1032
|
-
{
|
|
1033
|
-
if (this.targets.length > 0)
|
|
1034
|
-
{
|
|
1035
|
-
console.debug(this);
|
|
1036
|
-
throw new RangeError("cannot clone when targets set");
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (this.sources.length === 0)
|
|
1040
|
-
{
|
|
1041
|
-
console.debug(this);
|
|
1042
|
-
throw new RangeError("cannot clone with no sources");
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Get the type handler for the first source.
|
|
1046
|
-
const sh = this.opts.handlers.for(this.sources[0]);
|
|
1047
|
-
const target = sh.new();
|
|
1048
|
-
|
|
1049
|
-
this.$runAll(this.sources, [target]);
|
|
1050
|
-
|
|
1051
|
-
return target;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Run a copy operation for the specified sources and targets.
|
|
1056
|
-
*
|
|
1057
|
-
* For _each target_, it will find the type handler, then do one of:
|
|
1058
|
-
*
|
|
1059
|
-
* - If `this.isEZ` is `true`, calls `cp()` or `cp.safe()` to perform
|
|
1060
|
-
* the operation, and no further processing will be required.
|
|
1061
|
-
*
|
|
1062
|
-
* - If the type handler has a `copyAll()` method,
|
|
1063
|
-
* that will be called, passing the `sources` array
|
|
1064
|
-
* in its entirety.
|
|
1065
|
-
*
|
|
1066
|
-
* - If the type handler instead has a `copyOne()` method,
|
|
1067
|
-
* that will be called once for each source.
|
|
1068
|
-
*
|
|
1069
|
-
* - If the handler has neither of those, then `this.$runOne()`
|
|
1070
|
-
* will be called once for each source.
|
|
1071
|
-
*
|
|
1072
|
-
* @private
|
|
1073
|
-
* @param {Array} sources
|
|
1074
|
-
* @param {Array} targets
|
|
1075
|
-
* @returns {void}
|
|
1076
|
-
*/
|
|
1077
|
-
$runAll(sources, targets)
|
|
1078
|
-
{
|
|
1079
|
-
const ez = this.isEZ;
|
|
1080
|
-
for (const ti in targets)
|
|
1081
|
-
{
|
|
1082
|
-
const target = targets[ti];
|
|
1083
|
-
|
|
1084
|
-
if (ez)
|
|
1085
|
-
{ // EZ mode, use one of the simple functions.
|
|
1086
|
-
const hdl = this.opts.handlers;
|
|
1087
|
-
const fun = this.opts.overwrite ? cp : cp.safe;
|
|
1088
|
-
fun(hdl, target, ...sources);
|
|
1089
|
-
}
|
|
1090
|
-
else
|
|
1091
|
-
{ // Advanced mode supports many more options.
|
|
1092
|
-
const ctx = this.getContext({target,ti,cache: []});
|
|
1093
|
-
//console.debug({target, ti, ctx});
|
|
1094
|
-
const {th} = ctx;
|
|
1095
|
-
if (typeof th.copyAll === F)
|
|
1096
|
-
{ // Type handler will handle all sources at once.
|
|
1097
|
-
th.copyAll(ctx, sources);
|
|
1098
|
-
}
|
|
1099
|
-
else
|
|
1100
|
-
{ // One source at a time.
|
|
1101
|
-
const isHandled = (typeof th.copyOne === F);
|
|
1102
|
-
for (const si in sources)
|
|
1103
|
-
{
|
|
1104
|
-
const source = sources[si];
|
|
1105
|
-
ctx.update({source,si});
|
|
1106
|
-
|
|
1107
|
-
if (isHandled)
|
|
1108
|
-
{
|
|
1109
|
-
th.copyOne(ctx);
|
|
1110
|
-
}
|
|
1111
|
-
else
|
|
1112
|
-
{
|
|
1113
|
-
this.$runOne(ctx);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
} // $runAll()
|
|
1120
|
-
|
|
1121
|
-
/**
|
|
1122
|
-
* Run a copy operation from a single source into a single target.
|
|
1123
|
-
* This is called by `$runAll()`, and also calls itself recursively
|
|
1124
|
-
* if `opts.recursive` is not set to `0` (its default value).
|
|
1125
|
-
*
|
|
1126
|
-
* @private
|
|
1127
|
-
* @param {module:@lumjs/core/obj/cp~Context} ctx - Context;
|
|
1128
|
-
* must have both `target` and `source` properties defined.
|
|
1129
|
-
* @returns {void}
|
|
1130
|
-
*/
|
|
1131
|
-
$runOne(rc)
|
|
1132
|
-
{
|
|
1133
|
-
const {target,source,cache} = rc;
|
|
1134
|
-
const sprops = rc.sh.getProps(source, rc);
|
|
1135
|
-
const tprops = rc.th.getProps(target, rc);
|
|
1136
|
-
|
|
1137
|
-
for (const prop in sprops)
|
|
1138
|
-
{
|
|
1139
|
-
const sd = sprops[prop];
|
|
1140
|
-
let td = tprops[prop];
|
|
1141
|
-
|
|
1142
|
-
const pc = this.getContext({prop,sd}, rc);
|
|
1143
|
-
const po = pc.opts;
|
|
1144
|
-
|
|
1145
|
-
if ((po.recursive < 0 || (po.recursive > pc.depth))
|
|
1146
|
-
&& isObj(sd.value)
|
|
1147
|
-
&& !cache.includes(sd.value)
|
|
1148
|
-
)
|
|
1149
|
-
{ // A nested object was found.
|
|
1150
|
-
//console.debug("recursive mode", {sd,td});
|
|
1151
|
-
cache.push(sd.value);
|
|
1152
|
-
if (sd.value !== td?.value)
|
|
1153
|
-
{ // Not the same literal object.
|
|
1154
|
-
pc.update({sh: null, source: sd.value});
|
|
1155
|
-
|
|
1156
|
-
if (isObj(td))
|
|
1157
|
-
{ // Use the existing target descriptor
|
|
1158
|
-
pc.update({td});
|
|
1159
|
-
}
|
|
1160
|
-
else
|
|
1161
|
-
{ // Create a new descriptor.
|
|
1162
|
-
const value = pc.sh.new();
|
|
1163
|
-
//console.debug({ph: pctx.sh ,value});
|
|
1164
|
-
td = Object.assign({}, sd, {value});
|
|
1165
|
-
pc.update({td});
|
|
1166
|
-
def(target, prop, pc.td);
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
//cache.push(td.value);
|
|
1170
|
-
pc.update({th: null, target: td.value, depth: pc.depth+1});
|
|
1171
|
-
|
|
1172
|
-
this.$runOne(pc);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
else if (po.overwrite || td === undefined)
|
|
1176
|
-
{
|
|
1177
|
-
def(target, prop, pc.sd);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
if (rc.si === 0 && rc.opts.proto)
|
|
1182
|
-
{ // Prototype assignment only done with first source.
|
|
1183
|
-
const sp = Object.getPrototypeOf(source);
|
|
1184
|
-
const tp = Object.getPrototypeOf(target);
|
|
1185
|
-
if (sp && sp !== tp)
|
|
1186
|
-
{ // Apply the source prototype to the target.
|
|
1187
|
-
Object.setPrototypeOf(target, sp);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
} // $runOne()
|
|
1192
|
-
|
|
1193
|
-
} // CPAPI class
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Create a new `cp` declarative API instance with the specified options.
|
|
1197
|
-
* @param {(function|object)} opts - Passed to the constructor
|
|
1198
|
-
* @returns {module:@lumjs/core/obj/cp.API} API instance
|
|
1199
|
-
* @alias module:@lumjs/core/obj/cp.with
|
|
1200
|
-
*/
|
|
1201
|
-
cp.with = function(opts)
|
|
1202
|
-
{
|
|
1203
|
-
return new CPAPI(opts);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
/**
|
|
1207
|
-
* Create a new `cp` declarative API instance with default options,
|
|
1208
|
-
* and the specified target objects.
|
|
1209
|
-
* @param {...objects} targets - Target objects
|
|
1210
|
-
* @returns {module:@lumjs/core/obj/cp.API} API instance
|
|
1211
|
-
* @alias module:@lumjs/core/obj/cp.into
|
|
1212
|
-
*/
|
|
1213
|
-
cp.into = function()
|
|
1214
|
-
{
|
|
1215
|
-
return (new CPAPI()).into(...arguments);
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Create a new `cp` declarative API instance with default options,
|
|
1220
|
-
* and the specified source objects.
|
|
1221
|
-
* @param {...objects} source - Source objects
|
|
1222
|
-
* @returns {module:@lumjs/core/obj/cp.API} API instance
|
|
1223
|
-
* @alias module:@lumjs/core/obj/cp.from
|
|
1224
|
-
*/
|
|
1225
|
-
cp.from = function()
|
|
1226
|
-
{
|
|
1227
|
-
return (new CPAPI()).from(...arguments);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* A wrapper around `cp` specifically for cloning.
|
|
1232
|
-
*
|
|
1233
|
-
* @param {object} obj - Object to clone
|
|
1234
|
-
*
|
|
1235
|
-
* @param {?object} [opts] Options
|
|
1236
|
-
*
|
|
1237
|
-
* If this is an `object`, the clone call will be:
|
|
1238
|
-
* `cp.with(opts).from(obj).ow.clone();`
|
|
1239
|
-
*
|
|
1240
|
-
* If this is `null` or `undefined`, the call will be:
|
|
1241
|
-
* `cp(obj);`
|
|
1242
|
-
*
|
|
1243
|
-
* @returns {object} `obj`
|
|
1244
|
-
* @alias module:@lumjs/core/obj/cp.clone
|
|
1245
|
-
*/
|
|
1246
|
-
cp.clone = function(obj, opts)
|
|
1247
|
-
{
|
|
1248
|
-
if (isNil(opts))
|
|
1249
|
-
{
|
|
1250
|
-
return cp(obj);
|
|
1251
|
-
}
|
|
1252
|
-
else
|
|
1253
|
-
{
|
|
1254
|
-
return cp.with(opts).from(obj).ow.clone();
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Add a clone() method to an object.
|
|
1260
|
-
*
|
|
1261
|
-
* The method added is pretty simple with a very basic signature:
|
|
1262
|
-
* `obj.clone(opts)`, and it calls `cp.clone(obj,opts);`
|
|
1263
|
-
*
|
|
1264
|
-
* @param {object} obj - Object to add method to
|
|
1265
|
-
* @param {object} [spec] Optional spec
|
|
1266
|
-
* @param {string} [spec.name='clone'] Method name to add;
|
|
1267
|
-
* Default: `clone`
|
|
1268
|
-
* @param {object} [spec.desc] Descriptor rules
|
|
1269
|
-
* @param {?object} [spec.opts] Default options for method;
|
|
1270
|
-
* Default is `null` so that simple cloning is the default.
|
|
1271
|
-
*
|
|
1272
|
-
* @returns {object} `obj`
|
|
1273
|
-
* @alias module:@lumjs/core/obj/cp.addClone
|
|
1274
|
-
*/
|
|
1275
|
-
function addClone(obj, spec={})
|
|
1276
|
-
{
|
|
1277
|
-
const name = spec.name ?? 'clone';
|
|
1278
|
-
const desc = spec.desc ?? {};
|
|
1279
|
-
const defs = spec.opts ?? null;
|
|
1280
|
-
|
|
1281
|
-
desc.value = function(opts)
|
|
1282
|
-
{
|
|
1283
|
-
if (isObj(defs) && opts !== null)
|
|
1284
|
-
{
|
|
1285
|
-
opts = Object.assign({}, defs, opts);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
return cp.clone(obj, opts);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
return def(obj, name, desc);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
/**
|
|
1295
|
-
* A singleton object offering cached `cp.API` instances.
|
|
1296
|
-
* @alias module:@lumjs/core/obj/cp.cache
|
|
6
|
+
* @module @lumjs/core/obj/cp
|
|
1297
7
|
*/
|
|
1298
|
-
|
|
1299
|
-
{
|
|
1300
|
-
/**
|
|
1301
|
-
* Return a `cp` instance with a single `target` object.
|
|
1302
|
-
*
|
|
1303
|
-
* Will cache the instance the first time, so future calls
|
|
1304
|
-
* with the same `target` will return the same instance.
|
|
1305
|
-
*
|
|
1306
|
-
* @param {object} target
|
|
1307
|
-
* @returns {module:@lumjs/core/obj/cp.API}
|
|
1308
|
-
*/
|
|
1309
|
-
into(target)
|
|
1310
|
-
{
|
|
1311
|
-
if (this.intoCache === undefined)
|
|
1312
|
-
this.intoCache = new Map();
|
|
1313
|
-
const cache = this.intoCache;
|
|
1314
|
-
if (cache.has(target))
|
|
1315
|
-
{
|
|
1316
|
-
return cache.get(target);
|
|
1317
|
-
}
|
|
1318
|
-
else
|
|
1319
|
-
{
|
|
1320
|
-
const api = cp.into(target);
|
|
1321
|
-
cache.set(target, api);
|
|
1322
|
-
return api;
|
|
1323
|
-
}
|
|
1324
|
-
},
|
|
1325
|
-
/**
|
|
1326
|
-
* Return a `cp` instance with a single `source` object.
|
|
1327
|
-
*
|
|
1328
|
-
* Will cache the instance the first time, so future calls
|
|
1329
|
-
* with the same `target` will return the same instance.
|
|
1330
|
-
*
|
|
1331
|
-
* @param {object} source
|
|
1332
|
-
* @returns {module:@lumjs/core/obj/cp.API}
|
|
1333
|
-
*/
|
|
1334
|
-
from(source)
|
|
1335
|
-
{
|
|
1336
|
-
if (this.fromCache === undefined)
|
|
1337
|
-
this.fromCache = new Map();
|
|
1338
|
-
const cache = this.fromCache;
|
|
1339
|
-
if (cache.has(source))
|
|
1340
|
-
{
|
|
1341
|
-
return cache.get(source);
|
|
1342
|
-
}
|
|
1343
|
-
else
|
|
1344
|
-
{
|
|
1345
|
-
const api = cp.from(source);
|
|
1346
|
-
cache.set(source, api);
|
|
1347
|
-
return api;
|
|
1348
|
-
}
|
|
1349
|
-
},
|
|
1350
|
-
/**
|
|
1351
|
-
* Clear the caches for `into` and `from`.
|
|
1352
|
-
* @returns {object} `cp.cache`
|
|
1353
|
-
*/
|
|
1354
|
-
clear()
|
|
1355
|
-
{
|
|
1356
|
-
if (this.intoCache)
|
|
1357
|
-
this.intoCache.clear();
|
|
1358
|
-
if (this.fromCache)
|
|
1359
|
-
this.fromCache.clear();
|
|
1360
|
-
return this;
|
|
1361
|
-
},
|
|
1362
|
-
} // cp.cache
|
|
1363
|
-
|
|
1364
|
-
// Assign the rest of the named exports
|
|
1365
|
-
Object.assign(cp,
|
|
1366
|
-
{
|
|
1367
|
-
addClone, TY, HandlerSet,
|
|
1368
|
-
API: CPAPI,
|
|
1369
|
-
Context: CPContext,
|
|
1370
|
-
__cpArgs: cpArgs,
|
|
1371
|
-
});
|
|
1372
|
-
|
|
1373
|
-
// Export the module itself, including self reference.
|
|
1374
|
-
module.exports = cp.cp = cp;
|
|
8
|
+
module.exports = require('@lumjs/cp/obj');
|