@lumjs/core 1.24.1 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,3 @@
1
-
2
- const {F,S} = require('../types');
3
-
4
1
  /**
5
2
  * A wrapper class to abstract functionality of various kinds of lists.
6
3
  *
@@ -313,3 +310,5 @@ function removeItems(list, ...items)
313
310
  List.removeItems = removeItems;
314
311
 
315
312
  module.exports = exports = List;
313
+
314
+ const {F,S} = require('../types');
package/lib/index.js CHANGED
@@ -86,6 +86,13 @@ lazy(exports, 'obj', () => require('./obj'));
86
86
  */
87
87
  lazy(exports, 'console', () => require('./console'));
88
88
 
89
+ /**
90
+ * A simplistic implementation of class/object traits «Lazy»
91
+ * @name module:@lumjs/core.traits
92
+ * @type {module:@lumjs/core/traits}
93
+ */
94
+ lazy(exports, 'traits', () => require('./traits'));
95
+
89
96
  /**
90
97
  * Functions for getting values and properties with fallback defaults «Lazy»
91
98
  * @name module:@lumjs/core.opt
@@ -1,5 +1,6 @@
1
- const types = require('../types');
2
- const {isComplex,isObj} = types;
1
+ "use strict";
2
+
3
+ const {isComplex,isObj} = require('../types');
3
4
 
4
5
  /**
5
6
  * Get a property descriptor.
package/lib/traits.js ADDED
@@ -0,0 +1,681 @@
1
+ /**
2
+ * A very simplistic Trait system.
3
+ * @module @lumjs/core/traits
4
+ */
5
+
6
+ "use strict";
7
+
8
+ const getProp = require('./obj/getproperty');
9
+
10
+ const
11
+ {
12
+ def,F,B,
13
+ isObj,isArray,isConstructor,isProperty,
14
+ needObj,
15
+ } = require('./types');
16
+
17
+ // Symbol for private storage of composed traits.
18
+ const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
19
+
20
+ // Protected function to get a class prototype.
21
+ function ensureProto(from)
22
+ {
23
+ if (isObj(from))
24
+ { // Assume an object is already a prototype.
25
+ return from;
26
+ }
27
+ else if (isConstructor(from))
28
+ { // It's a constructor, return its prototype.
29
+ return from.prototype;
30
+ }
31
+ else
32
+ {
33
+ throw new TypeError("Invalid class constructor or object instance");
34
+ }
35
+ }
36
+
37
+ const hasOwn
38
+ = (typeof Object.hasOwn === F)
39
+ ? Object.hasOwn
40
+ : (obj,prop) => obj.hasOwnProperty(prop);
41
+
42
+ /**
43
+ * Default list of properties to ignore if `opts.static` is `true`.
44
+ *
45
+ * If you specify your own `opts.filter` values, you should probably
46
+ * use this list as well.
47
+ *
48
+ * @see module:@lumjs/core/traits.compose
49
+ * @type {Array}
50
+ * @alias module:@lumjs/core/traits.IGNORE_STATIC
51
+ */
52
+ const IGNORE_STATIC =
53
+ [
54
+ // Possible properties from `function` values.
55
+ 'length', 'name', 'arguments', 'caller', 'prototype',
56
+ // Defined static methods from the Trait abstract class.
57
+ 'composeInto', 'setupTrait', 'getComposed', 'decomposeFrom', 'removeTrait',
58
+ // Optional static getter properties for the Trait abstract class.
59
+ 'composeOptions', 'staticOptions',
60
+ ];
61
+
62
+ /**
63
+ * Get a composed definition list.
64
+ *
65
+ * @param {(function|object)} target - Target to look in.
66
+ * @param {(function|object)} [source] Source trait to get defs for.
67
+ *
68
+ * @returns {mixed} Return value depends on a few factors.
69
+ *
70
+ * If NO traits have been composed into the `target`, returns `null`.
71
+ *
72
+ * If NO `source` argument was passed, then this will return a `Map` where
73
+ * the keys will be a full set of `source` traits that have been composed,
74
+ * and each value will be a {@link module:@lumjs/core/traits~Composed} object.
75
+ *
76
+ * If a `source` argument was passed, and that trait has been composed by
77
+ * the `target` at least once (even if it has since been decomposed),
78
+ * the return value will be the {@link module:@lumjs/core/traits~Composed}
79
+ * object for the trait.
80
+ *
81
+ * If the specified `source` trait has *never* been composed, but at least
82
+ * one other trait has been, this will return `undefined`.
83
+ *
84
+ * @throws {TypeError} If `target` is not valid.
85
+ *
86
+ * @alias module:@lumjs/core/traits.getComposed
87
+ */
88
+ function getComposed(target, source)
89
+ {
90
+ needObj(target, true, 'invalid target');
91
+
92
+ let composed = null;
93
+
94
+ if (target[COMPOSED_TRAITS] instanceof Map)
95
+ {
96
+ composed = target[COMPOSED_TRAITS];
97
+ }
98
+ else if (isObj(target))
99
+ {
100
+ return getComposed(target.constructor, source);
101
+ }
102
+
103
+ if (composed && source)
104
+ { // Let's get definitions for a specific trait.
105
+ return composed.get(source);
106
+ }
107
+
108
+ return composed;
109
+ }
110
+
111
+ // Private function to initialize composed maps.
112
+ function mapComposed(target, source)
113
+ {
114
+ if (!(target[COMPOSED_TRAITS] instanceof Map))
115
+ {
116
+ def(target, COMPOSED_TRAITS, {value: new Map()});
117
+ }
118
+
119
+ const composed = {proto: null, static: null};
120
+ target[COMPOSED_TRAITS].set(source, composed);
121
+ return composed;
122
+ }
123
+
124
+ /**
125
+ * Compose a trait into a target.
126
+ *
127
+ * @param {(function|object)} target - Target to compose trait into.
128
+ *
129
+ * Pass a class constructor function if you want to add properties
130
+ * that will be available to every instance of the class.
131
+ *
132
+ * Pass an object instance of you only want the properties added to
133
+ * an individual object and not the class itself.
134
+ *
135
+ * @param {(function|object)} source - Source trait to compose from.
136
+ *
137
+ * Generally _should_ be the class constructor function of the Trait.
138
+ *
139
+ * This _may_ be an object instance or prototype object, but that usage
140
+ * is NOT generally recommended, and may lead to unforeseen side-effects.
141
+ *
142
+ * @param {object} [opts] Options for what to compose, and how.
143
+ *
144
+ * @param {(Array|function)} [opts.filter] Filter property names/symbols.
145
+ *
146
+ * Only used if `.props` is NOT defined, this can be used to block
147
+ * specific properties from being detected automatically.
148
+ *
149
+ * Has absolutely no effect if the `.props` option is defined.
150
+ *
151
+ * If it is a `function` it will be used by `Array#filter()` as a test
152
+ * to determine the validity of property names/symbols.
153
+ *
154
+ * If it is an `Array` the property names/symbols in the array
155
+ * will **ignored/skipped** (i.e. a negative filter).
156
+ *
157
+ * If not specified, the defaults depend on the `.static` option:
158
+ * - If `.static` is `false` there is no default value.
159
+ * - If `.static` is `true`, the default is to ignore all of the
160
+ * built-in properties `Object.getOwnPropertyNames()` returns on
161
+ * `function` values (which class constructors are), plus any
162
+ * of the static methods and getters from the `Trait` abstract class.
163
+ *
164
+ * @param {object} [opts.props] A list of trait properties to compose.
165
+ *
166
+ * May be a flat `Array` or a `Set` in which case the same keys are used
167
+ * for the both the source and target properties.
168
+ *
169
+ * May be a `Map` or just a plain `object` whose enumerable properties
170
+ * will be used as keys/value, where the key is the property name in
171
+ * the source, and the value is the property name to use in the target.
172
+ *
173
+ * If the target property is not a `string` or `Symbol`, then the source
174
+ * property key will be used. This makes it easy to rename some properties
175
+ * but not others.
176
+ *
177
+ * If omitted or set to any non-object value, we will get a list of
178
+ * all properties defined on the `source`, by default using the
179
+ * `Object.getOwnPropertyNames()` method.
180
+ *
181
+ * @param {boolean} [opts.overwrite=false] Overwrite existing properties?
182
+ *
183
+ * @param {boolean} [opts.static=false] Compose static properties?
184
+ *
185
+ * - If `true` ONLY the *static* properties/methods will be composed.
186
+ * - If `false` ONLY the *prototype* properties/methods will be composed.
187
+ *
188
+ * In order to compose _both_ you'd need to call the function twice,
189
+ * or use the {@link module:@lumjs/core/traits.composeFully} function.
190
+ *
191
+ * Only useful if `source` is a class constructor (always recommended).
192
+ *
193
+ * Default is `false`.
194
+ *
195
+ * @param {boolean} [opts.symbols=false] Auto-compose `Symbol` properties?
196
+ *
197
+ * Calls `Object.getOwnPropertySymbols()` when generating a list of
198
+ * trait properties to compose.
199
+ *
200
+ * Only used if `.props` is NOT defined; has no effect if `.props` is set.
201
+ *
202
+ * Default is `false`.
203
+ *
204
+ * @prop {boolean} [opts.strings=true] Auto-compose `string` properties?
205
+ *
206
+ * Calls `Object.getOwnPropertyNames()` when generating a list of
207
+ * trait properties to compose.
208
+ *
209
+ * Only used if `.props` is NOT defined; has no effect if `.props` is set.
210
+ *
211
+ * Default is `true`.
212
+ *
213
+ * @returns {@link module:@lumjs/core/traits~Composed}
214
+ *
215
+ * @throws {TypeError} If any of the arguments are not valid types.
216
+ *
217
+ * @alias module:@lumjs/core/traits.compose
218
+ */
219
+ function compose(specTarget, specSource, opts={})
220
+ {
221
+ needObj(specSource, true, 'invalid source trait');
222
+ needObj(opts, false, 'invalid options object');
223
+
224
+ const composedMaps
225
+ = getComposed(specTarget, specSource)
226
+ ?? mapComposed(specTarget, specSource);
227
+
228
+ const isStatic = opts.static ?? false;
229
+
230
+ const mapId = isStatic ? 'static' : 'proto';
231
+
232
+ if (composedMaps[mapId])
233
+ {
234
+ console.warn(specSource, mapId, "already composed", specTarget, opts);
235
+ return composedMaps;
236
+ }
237
+
238
+ const target = isStatic ? specTarget : ensureProto(specTarget);
239
+ const source = isStatic ? specSource : ensureProto(specSource);
240
+
241
+ let props = opts.props;
242
+
243
+ if (!isObj(props))
244
+ { // No valid props specified, use all 'own' properties.
245
+ const doStr = opts.strings ?? true,
246
+ doSym = opts.symbols ?? false;
247
+
248
+ props = doStr ? Object.getOwnPropertyNames(source) : [];
249
+
250
+ if (doSym)
251
+ { // Compose symbols as well.
252
+ const symbols = Object.getOwnPropertySymbols(source);
253
+ props.push(...symbols);
254
+ }
255
+
256
+ let filter = opts.filter ?? (isStatic ? IGNORE_STATIC : null);
257
+
258
+ if (isArray(filter))
259
+ { // Convert ignore into a filter function.
260
+ const ignoreList = filter;
261
+ filter = (p) => !ignoreList.includes(p);
262
+ }
263
+
264
+ if (typeof filter === F)
265
+ {
266
+ props = props.filter(filter);
267
+ }
268
+ }
269
+
270
+ let sprops, tprops;
271
+
272
+ if (isArray(props))
273
+ { // A flat list of properties, use the same names.
274
+ sprops = tprops = props;
275
+ }
276
+ else if (props instanceof Set)
277
+ { // Almost identical to using an Array.
278
+ sprops = tprops = Array.from(props.values());
279
+ }
280
+ else if (props instanceof Map)
281
+ { // A structured Map of source name to dest name.
282
+ sprops = Array.from(props.keys());
283
+ tprops = Array.from(props.values());
284
+ }
285
+ else
286
+ { // A plain object mapping.
287
+ sprops = Object.keys(props);
288
+ tprops = Object.values(props);
289
+ }
290
+
291
+ // A map to store the composed property info in.
292
+ const composed = composedMaps[mapId] = new Map();
293
+
294
+ for (const i in sprops)
295
+ {
296
+ let sprop = sprops[i];
297
+ let tprop = tprops[i];
298
+
299
+ const cdef =
300
+ {
301
+ added: true,
302
+ found: true,
303
+ };
304
+
305
+ if (!isProperty(sprop))
306
+ { // Should never happen, but...
307
+ sprop = sprop.toString();
308
+ }
309
+
310
+ if (tprop === '' || !isProperty(tprop))
311
+ { // Use the source name as the target name.
312
+ tprop = sprop;
313
+ }
314
+
315
+ cdef.id = tprop; // Ref to the target property here.
316
+
317
+ if (sprop !== tprop)
318
+ { // Source key differs, add a reference to it.
319
+ cdef.srcKey = sprop;
320
+ }
321
+
322
+ const propDesc = getProp(source, sprop, null);
323
+ cdef.newDescriptor = propDesc;
324
+
325
+ if (!propDesc)
326
+ { // No source property, that's weird.
327
+ cdef.added = false;
328
+ cdef.found = false;
329
+ }
330
+ else if (hasOwn(target, tprop))
331
+ { // The target property already exists.
332
+ if (opts.overwrite)
333
+ { // Save the overwritten property descriptor.
334
+ cdef.oldDescriptor = getProp(target, tprop);
335
+ }
336
+ else
337
+ { // Not going to add it.
338
+ cdef.added = false;
339
+ }
340
+ }
341
+
342
+ if (cdef.added)
343
+ { // (re-)define the target property.
344
+ def(target, tprop, propDesc);
345
+ }
346
+
347
+ composed.set(tprop, cdef);
348
+ }
349
+
350
+ composedMaps[mapId] = composed;
351
+ return composedMaps;
352
+ }
353
+
354
+ function getOpts(inOpts, isStatic)
355
+ {
356
+ return Object.assign({}, inOpts, {static: isStatic});
357
+ }
358
+
359
+ /**
360
+ * A wrapper around `compose()` that calls it twice.
361
+ *
362
+ * - Once to compose *prototype* properties.
363
+ * - Then to compose *static* properties.
364
+ *
365
+ * While the `target` and `source` arguments _can_ be specified
366
+ * as `object` values, for the purposes of this function it makes
367
+ * the most sense to pass class constructor `function` values.
368
+ * The behaviour if `object` values are passed is *undefined* and
369
+ * *untested*, and the rest of the function documentation assumes
370
+ * only `function` values will be passed.
371
+ *
372
+ * @param {function} target - Target class
373
+ * @param {function} source - Source trait
374
+ *
375
+ * @param {object} [protoOpts] Options for the *prototype* call.
376
+ *
377
+ * Compiled options will have `static` option forced to `false`.
378
+ *
379
+ * @param {object} [staticOpts] Options for the *static* call.
380
+ *
381
+ * Compiled options will have `static` option forced to `true`.
382
+ *
383
+ * @param {boolean} [reverse=false] Reverse order of composing?
384
+ *
385
+ * If `true` then *static* properties will be composed first,
386
+ * followed by *prototype* properties.
387
+ *
388
+ * @returns {@link module:@lumjs/core/traits~Composed}
389
+ *
390
+ * Will have both `proto` and `static` properties set.
391
+ *
392
+ * @see module:@lumjs/core/traits.compose
393
+ * @alias module:@lumjs/core/traits.composeFully
394
+ */
395
+ function composeFully(target, source, protoOpts, staticOpts, reverse=false)
396
+ {
397
+ const OPR = getOpts(protoOpts, false);
398
+ const OST = getOpts(staticOpts, true);
399
+ const ALL = reverse ? [OST,OPR] : [OPR,OST];
400
+
401
+ let composed;
402
+
403
+ for (const opts of ALL)
404
+ {
405
+ composed = compose(target, source, opts);
406
+ }
407
+
408
+ return composed;
409
+ }
410
+
411
+ /**
412
+ * Decompose (remove) a trait from a target.
413
+ *
414
+ * Applicable properties that were added to the `target` will be removed.
415
+ *
416
+ * If an existing property was overwritten when the trait was composed,
417
+ * the original property will be restored.
418
+ *
419
+ * @param {(function|object)} target - Target to decompose trait from.
420
+ * @param {(function|object)} source - Source trait we are decomposing.
421
+ *
422
+ * @param {object} [opts] Options
423
+ * @param {boolean} [opts.static] Decompose only a subset of properties?
424
+ *
425
+ * - If `true` ONLY *static* properties will be decomposed.
426
+ * - If `false` ONLY *prototype* properties will be decomposed.
427
+ * - If omitted **ALL** properties will be decomposed!
428
+ *
429
+ * @returns {number} Number of properties decomposed.
430
+ */
431
+ function decompose(specTarget, specSource, opts={})
432
+ {
433
+ const isStatic = opts.static
434
+
435
+ let c = 0;
436
+
437
+ if (typeof isStatic !== B)
438
+ { // The default is to remove all composed properties.
439
+ c += decompose(specTarget, specSource, getOpts(opts, true));
440
+ c += decompose(specTarget, specSource, getOpts(opts, false));
441
+ return c;
442
+ }
443
+
444
+ const composedMaps = getComposed(specTarget, specSource);
445
+ const mapId = isStatic ? 'static' : 'proto';
446
+ if (composedMaps && composedMaps[mapId] instanceof Map)
447
+ { // Found a valid map.
448
+ const composed = composedMaps[mapId];
449
+ const target = isStatic ? specTarget : ensureProto(specTarget);
450
+
451
+ for (const [prop,spec] of composed)
452
+ {
453
+ if (spec.added)
454
+ {
455
+ if (spec.oldDescriptor)
456
+ { // An old descriptor to restore.
457
+ def(target, prop, spec.oldDescriptor);
458
+ }
459
+ else
460
+ { // Just delete the added property descriptor.
461
+ delete target[prop];
462
+ }
463
+ c++; // Increment the counter.
464
+ }
465
+ } // for composed
466
+
467
+ // Now remove the property map itself.
468
+ composedMaps[mapId] = null;
469
+ }
470
+
471
+ return c;
472
+ }
473
+
474
+ /**
475
+ * An abstract class for Traits.
476
+ *
477
+ * Simply offers a couple static methods and APIs that
478
+ * wrap the `compose()` and `composeFully()` functions,
479
+ * and makes it fairly simple to create Trait classes.
480
+ *
481
+ * @alias module:@lumjs/core/traits.Trait
482
+ */
483
+ class CoreTrait
484
+ {
485
+ /**
486
+ * Extend another class or object instance with the methods and
487
+ * getter/setter properties from a Trait.
488
+ *
489
+ * The sub-class of Trait this static method is called on will always
490
+ * be the `source` argument.
491
+ *
492
+ * @param {(function|object)} target - Target class or instance.
493
+ *
494
+ * @param {object} [protoOpts] Options for `compose()` function.
495
+ *
496
+ * If this is not specified, or is any value other than an `object`,
497
+ * we will look for defaults in a `composeOptions` static property:
498
+ *
499
+ * ```js
500
+ * static get composeOptions() { return {your: default, options: here}; }
501
+ * ```
502
+ *
503
+ * @param {(object|true)} [staticOpts] Static options.
504
+ *
505
+ * If this is set we'll use `composeFully()` instead of using `compose()`.
506
+ *
507
+ * If this value is an `object` it will be used as the `staticOpts`.
508
+ *
509
+ * If this is the special value `true`, then we will look for the options
510
+ * in a `staticOptions` static property:
511
+ *
512
+ * ```js
513
+ * static get staticOptions() { return {your: static, options: here}; }
514
+ * ```
515
+ *
516
+ * If this any value other than an `object` or `true`, it will be ignored
517
+ * entirely, and the regular `compose()` call will be used.
518
+ *
519
+ * @returns {object} Return value from the `setupTrait()` static method.
520
+ *
521
+ */
522
+ static composeInto(target, protoOpts, staticOpts)
523
+ {
524
+ if (!isObj(protoOpts))
525
+ {
526
+ protoOpts = this.composeOptions ?? {};
527
+ }
528
+
529
+ if (staticOpts === true)
530
+ {
531
+ staticOpts = this.staticOptions ?? {};
532
+ }
533
+
534
+ let composed;
535
+
536
+ if (isObj(staticOpts))
537
+ {
538
+ composed = composeFully(target, this, protoOpts, staticOpts);
539
+ }
540
+ else
541
+ {
542
+ composed = compose(target, this, protoOpts);
543
+ }
544
+
545
+ return this.setupTrait({target, protoOpts, staticOpts, composed});
546
+ }
547
+
548
+ /**
549
+ * A static method called by `composeInto()`
550
+ * _after_ composing the trait properties into the target.
551
+ *
552
+ * @param {object} info - Metadata from `composeInto()`
553
+ * @param {(function|object)} info.target - The `target` argument
554
+ * @param {object} info.protoOpts - The `protoOpts` used
555
+ * @param {object} [info.staticOpts] The `staticOpts` if used
556
+ * @param {module:@lumjs/core/traits~Composed} info.composed
557
+ * The return value from `compose()` or `composeFully()`.
558
+ *
559
+ * @returns {object} The `info` object, with any changes made
560
+ * by an overridden `setupTrait()` method in the sub-class.
561
+ *
562
+ * The default implementation is a placeholder that returns the
563
+ * `info` object without making any changes.
564
+ *
565
+ */
566
+ static setupTrait(info)
567
+ {
568
+ if (this.debug)
569
+ {
570
+ console.debug(this.name, "setupTrait()", info, this);
571
+ }
572
+ return info;
573
+ }
574
+
575
+ /**
576
+ * A method wrapping {@link module:@lumjs/core/traits.decompose}
577
+ * where the `source` is always the Trait sub-class constructor.
578
+ *
579
+ * See the `decompose()` docs for descriptions of the other arguments.
580
+ *
581
+ * @param {(function|object)} target
582
+ * @param {object} [opts]
583
+ * @returns {object} Return value from the `removeTrait()` static method.
584
+ */
585
+ static decomposeFrom(target, opts)
586
+ {
587
+ const info = {target};
588
+ info.composed = this.getComposed(target);
589
+ info.count = decompose(target, this, opts);
590
+ return this.removeTrait(info);
591
+ }
592
+
593
+ /**
594
+ * A static method called by `decomposeFrom()`
595
+ * _after_ decomposing the trait properties from the target.
596
+ *
597
+ * @param {object} info - Metadata from `decomposeFrom()`
598
+ * @param {(function|object)} info.target - The `target` argument
599
+ * @param {module:@lumjs/core/traits~Composed} info.composed
600
+ * The property map that was previously composed.
601
+ * @param {number} info.count - The number of properties decomposed.
602
+ *
603
+ * @returns {object} The `info` object, with any changes made
604
+ * by an overridden `removeTrait()` method in the sub-class.
605
+ *
606
+ * The default implementation is a placeholder that returns the
607
+ * `info` object without making any changes.
608
+ *
609
+ */
610
+ static removeTrait(info)
611
+ {
612
+ if (this.debug)
613
+ {
614
+ console.debug(this.name, "removeTrait()", info, this);
615
+ }
616
+ return info;
617
+ }
618
+
619
+ /**
620
+ * A method wrapping {@link module:@lumjs/core/traits.getComposed}
621
+ * where the `source` is always the Trait sub-class constructor.
622
+ *
623
+ * @param {(function|object)} target
624
+ * @returns {mixed} Return value from `getComposed()`
625
+ */
626
+ static getComposed(target)
627
+ {
628
+ return getComposed(target, this);
629
+ }
630
+
631
+ } // CoreTrait class
632
+
633
+ module.exports =
634
+ {
635
+ // The main public exports.
636
+ compose, composeFully, getComposed, decompose,
637
+ Trait: CoreTrait, IGNORE_STATIC,
638
+
639
+ // Undocumented protected functions.
640
+ ensureProto, hasOwn,
641
+ }
642
+
643
+ /**
644
+ * Composed property maps.
645
+ *
646
+ * @typedef {object} module:@lumjs/core/traits~Composed
647
+ *
648
+ * @prop {?Map} proto - Map of composed *prototype* properties.
649
+ *
650
+ * Keys are the `string` or `symbol` for each composed property.
651
+ * Values are {@link module:@lumjs/core/traits~ComposedProperty} objects.
652
+ *
653
+ * If no applicable trait properties are currently composed,
654
+ * this will be `null`.
655
+ *
656
+ * @prop {?Map} static - Map of composed *static* properties.
657
+ *
658
+ * Exactly the same description as `proto`, but for static properties.
659
+ *
660
+ */
661
+
662
+ /**
663
+ * Composed property definitions.
664
+ *
665
+ * @typedef {object} module:@lumjs/core/traits~ComposedProperty
666
+ *
667
+ * @prop {(string|Symbol)} id - The property key on the `target`
668
+ * @prop {(string|Symbol)} [srcKey] The property key from the `source`;
669
+ * only included if different from `propKey`.
670
+ *
671
+ * @prop {?object} newDescriptor - The property descriptor to be added;
672
+ * will be `null` if the property did not exist.
673
+ *
674
+ * @prop {object} [oldDescriptor] The replaced property descriptor;
675
+ * only included if there was an existing property in the `target`
676
+ * and the `opts.overwrite` option was `true`.
677
+ *
678
+ * @prop {boolean} found - Was the property found in the `source` ?
679
+ * @prop {boolean} added - Was the property added to the `target` ?
680
+ *
681
+ */
@@ -124,6 +124,18 @@ function isIterable(v)
124
124
  return (isObj(v) && typeof v[Symbol.iterator] === F);
125
125
  }
126
126
 
127
+ /**
128
+ * Does a value appear to be a class constructor?
129
+ *
130
+ * @param {*} v - Value to test
131
+ * @returns {boolean}
132
+ * @alias module:@lumjs/core/types.isConstructor
133
+ */
134
+ function isConstructor(v)
135
+ {
136
+ return (typeof v === F && isObj(v.prototype));
137
+ }
138
+
127
139
  /**
128
140
  * See if an object can be used as a valid *descriptor*.
129
141
  *
@@ -216,6 +228,6 @@ function doesDescriptorTemplate(obj, accessor=false, strict=true)
216
228
  module.exports =
217
229
  {
218
230
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
219
- isIterable, nonEmptyArray, isArguments, isProperty,
231
+ isIterable, nonEmptyArray, isArguments, isProperty, isConstructor,
220
232
  doesDescriptor, doesDescriptorTemplate,
221
233
  }
@@ -24,7 +24,7 @@ const
24
24
  {
25
25
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
26
26
  nonEmptyArray, isArguments, isProperty, doesDescriptor, isIterable,
27
- doesDescriptorTemplate,
27
+ isConstructor, doesDescriptorTemplate,
28
28
  } = require('./basics');
29
29
 
30
30
  // Root namespace helpers.
@@ -59,10 +59,13 @@ module.exports =
59
59
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
60
60
  nonEmptyArray, isArguments, isProperty, doesDescriptor,
61
61
  isInstance, isType, isa, needObj, needType, needs, stringify,
62
- doesDescriptorTemplate, ownCount, isIterable,
62
+ doesDescriptorTemplate, ownCount, isIterable, isConstructor,
63
63
  isArrayOf, isListOf, isMapOf, isObjOf, OfTest,
64
64
  console,
65
65
  }
66
66
 
67
- // Last but not least, this will be the module for TYPES.add()
67
+ // This will be the module for TYPES.add()
68
68
  def(TYPES, '$module', module);
69
+
70
+ // Extend `unbound` with add() and remove() methods.
71
+ require('./unbound/extend')(unbound);
package/lib/types/root.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const {U} = require('./js');
2
2
  const {isNil,isArray} = require('./basics');
3
- const removeFromArray = require('../arrays/list').removeItems;
4
3
 
5
4
  // «private»
6
5
  function no_root()
@@ -21,7 +20,7 @@ const root = typeof globalThis !== U ? globalThis
21
20
  exports.root = root;
22
21
 
23
22
  // A list of objects to be considered unbound globally.
24
- const unboundObjects = [];
23
+ const unboundObjects = require('./unbound/objects');
25
24
 
26
25
  /**
27
26
  * Pass `this` here to see if it is bound to an object.
@@ -52,42 +51,3 @@ function unbound(whatIsThis, rootIsUnbound=true, areUnbound=false)
52
51
  }
53
52
 
54
53
  exports.unbound = unbound;
55
-
56
- // Now that 'unbound' is exported, we can do some wibbly wobbly magic.
57
- const def = require('./def');
58
-
59
- /**
60
- * Add an item to the unbound global objects list.
61
- *
62
- * @function
63
- * @param {(object|function)} obj - The object to be considered unbound.
64
- * @returns {boolean} Will be `false` if `obj` is already unbound.
65
- * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
66
- * @alias module:@lumjs/core/types.unbound.add
67
- */
68
- def(unbound, 'add', function (obj)
69
- {
70
- needObj(obj, true);
71
- if (unbound(obj, true, true))
72
- { // Item is already unbound.
73
- return false;
74
- }
75
- // Add to list and we're done.
76
- unboundObjects.push(obj);
77
- return true;
78
- });
79
-
80
- /**
81
- * Remove an item from the unbound global objects list.
82
- *
83
- * @function
84
- * @param {(object|function)} obj - The object to be removed.
85
- * @returns {boolean} Will be `false` if the item was not in the list.
86
- * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
87
- * @alias module:@lumjs/core/types.unbound.remove
88
- */
89
- def(unbound, 'remove', function(obj)
90
- {
91
- needObj(obj, true);
92
- return (removeFromArray(unboundObjects, obj) > 0);
93
- });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ const def = require('../def');
4
+ const {needObj} = require('../needs');
5
+ const removeFromArray = require('../../arrays/list').removeItems;
6
+ const unboundObjects = require('./objects');
7
+
8
+ // Adds a couple magic methods to the `unbound` function.
9
+ module.exports = function(unbound)
10
+ {
11
+ /**
12
+ * Add an item to the unbound global objects list.
13
+ *
14
+ * @function
15
+ * @param {(object|function)} obj - The object to be considered unbound.
16
+ * @returns {boolean} Will be `false` if `obj` is already unbound.
17
+ * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
18
+ * @name module:@lumjs/core/types.unbound.add
19
+ */
20
+ def(unbound, 'add', function (obj)
21
+ {
22
+ needObj(obj, true);
23
+ if (unbound(obj, true, true))
24
+ { // Item is already unbound.
25
+ return false;
26
+ }
27
+ // Add to list and we're done.
28
+ unboundObjects.push(obj);
29
+ return true;
30
+ });
31
+
32
+ /**
33
+ * Remove an item from the unbound global objects list.
34
+ *
35
+ * @function
36
+ * @param {(object|function)} obj - The object to be removed.
37
+ * @returns {boolean} Will be `false` if the item was not in the list.
38
+ * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
39
+ * @name module:@lumjs/core/types.unbound.remove
40
+ */
41
+ def(unbound, 'remove', function(obj)
42
+ {
43
+ needObj(obj, true);
44
+ return (removeFromArray(unboundObjects, obj) > 0);
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ // Private storage, not exported outside the package.
2
+ module.exports = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.24.1",
3
+ "version": "1.25.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {
@@ -18,6 +18,7 @@
18
18
  "./observable": "./lib/observable.js",
19
19
  "./opt": "./lib/opt.js",
20
20
  "./strings": "./lib/strings.js",
21
+ "./traits": "./lib/traits.js",
21
22
  "./types": "./lib/types/index.js",
22
23
 
23
24
  "./package.json": "./package.json"