@lumjs/core 1.25.0 → 1.25.2
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/traits.js +151 -28
- package/package.json +1 -1
package/lib/traits.js
CHANGED
|
@@ -9,15 +9,32 @@ const getProp = require('./obj/getproperty');
|
|
|
9
9
|
|
|
10
10
|
const
|
|
11
11
|
{
|
|
12
|
-
def,F,B,
|
|
12
|
+
def,F,B,S,
|
|
13
13
|
isObj,isArray,isConstructor,isProperty,
|
|
14
|
-
needObj,
|
|
14
|
+
needObj,needType,
|
|
15
15
|
} = require('./types');
|
|
16
16
|
|
|
17
17
|
// Symbol for private storage of composed traits.
|
|
18
18
|
const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* For when we need a prototype object exclusively.
|
|
22
|
+
*
|
|
23
|
+
* @param {(function|object)} from - Target
|
|
24
|
+
*
|
|
25
|
+
* May either be a class constructor `function`, or a prototype `object`.
|
|
26
|
+
*
|
|
27
|
+
* @returns {object}
|
|
28
|
+
*
|
|
29
|
+
* If `from` is an `object` it will be returned as-is.
|
|
30
|
+
* If `from` is a `function` then `from.prototype` will be returned.
|
|
31
|
+
*
|
|
32
|
+
* @throws {TypeError} If `from` was not a valid value;
|
|
33
|
+
* including if it is a `function` but does not have a valid
|
|
34
|
+
* prototype object to return.
|
|
35
|
+
*
|
|
36
|
+
* @alias module:@lumjs/core/traits.ensureProto
|
|
37
|
+
*/
|
|
21
38
|
function ensureProto(from)
|
|
22
39
|
{
|
|
23
40
|
if (isObj(from))
|
|
@@ -54,7 +71,8 @@ const IGNORE_STATIC =
|
|
|
54
71
|
// Possible properties from `function` values.
|
|
55
72
|
'length', 'name', 'arguments', 'caller', 'prototype',
|
|
56
73
|
// Defined static methods from the Trait abstract class.
|
|
57
|
-
'composeInto', 'setupTrait', 'getComposed', 'decomposeFrom',
|
|
74
|
+
'composeInto', 'setupTrait', 'getComposed', 'decomposeFrom',
|
|
75
|
+
'removeTrait', 'removedTrait',
|
|
58
76
|
// Optional static getter properties for the Trait abstract class.
|
|
59
77
|
'composeOptions', 'staticOptions',
|
|
60
78
|
];
|
|
@@ -65,9 +83,21 @@ const IGNORE_STATIC =
|
|
|
65
83
|
* @param {(function|object)} target - Target to look in.
|
|
66
84
|
* @param {(function|object)} [source] Source trait to get defs for.
|
|
67
85
|
*
|
|
86
|
+
* @param {boolean} [tryClass=true] Try the class constructor as well?
|
|
87
|
+
*
|
|
88
|
+
* If this is `true` (the default value), then we will also look in
|
|
89
|
+
* the `target.constructor` IF the `target` is an `object` and EITHER
|
|
90
|
+
* one of these is true:
|
|
91
|
+
*
|
|
92
|
+
* - The specified `source` was NOT composed into the `target` directly.
|
|
93
|
+
* - No `source` was specified, and the `target` has NO traits composed
|
|
94
|
+
* directly into itself.
|
|
95
|
+
*
|
|
96
|
+
* Set this to `false` to only ever consider the `target` itself.
|
|
97
|
+
*
|
|
68
98
|
* @returns {mixed} Return value depends on a few factors.
|
|
69
99
|
*
|
|
70
|
-
* If NO traits have been composed into the `target`, returns `null`.
|
|
100
|
+
* If NO traits have been composed into the `target`, always returns `null`.
|
|
71
101
|
*
|
|
72
102
|
* If NO `source` argument was passed, then this will return a `Map` where
|
|
73
103
|
* the keys will be a full set of `source` traits that have been composed,
|
|
@@ -78,14 +108,13 @@ const IGNORE_STATIC =
|
|
|
78
108
|
* the return value will be the {@link module:@lumjs/core/traits~Composed}
|
|
79
109
|
* object for the trait.
|
|
80
110
|
*
|
|
81
|
-
* If the specified `source` trait has *never* been composed,
|
|
82
|
-
* one other trait has been, this will return `undefined`.
|
|
111
|
+
* If the specified `source` trait has *never* been composed, returns `null`.
|
|
83
112
|
*
|
|
84
113
|
* @throws {TypeError} If `target` is not valid.
|
|
85
114
|
*
|
|
86
115
|
* @alias module:@lumjs/core/traits.getComposed
|
|
87
116
|
*/
|
|
88
|
-
function getComposed(target, source)
|
|
117
|
+
function getComposed(target, source, tryClass=true)
|
|
89
118
|
{
|
|
90
119
|
needObj(target, true, 'invalid target');
|
|
91
120
|
|
|
@@ -95,15 +124,20 @@ function getComposed(target, source)
|
|
|
95
124
|
{
|
|
96
125
|
composed = target[COMPOSED_TRAITS];
|
|
97
126
|
}
|
|
98
|
-
else if (isObj(target))
|
|
99
|
-
{
|
|
100
|
-
return getComposed(target.constructor, source);
|
|
101
|
-
}
|
|
102
127
|
|
|
103
|
-
if (composed && source)
|
|
128
|
+
if (composed && source && composed.has(source))
|
|
104
129
|
{ // Let's get definitions for a specific trait.
|
|
105
130
|
return composed.get(source);
|
|
106
131
|
}
|
|
132
|
+
else if (composed && !source)
|
|
133
|
+
{ // No trait specified, but compose rules found.
|
|
134
|
+
return composed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (tryClass && isObj(target))
|
|
138
|
+
{ // Try the class constructor.
|
|
139
|
+
return getComposed(target.constructor, source);
|
|
140
|
+
}
|
|
107
141
|
|
|
108
142
|
return composed;
|
|
109
143
|
}
|
|
@@ -116,7 +150,8 @@ function mapComposed(target, source)
|
|
|
116
150
|
def(target, COMPOSED_TRAITS, {value: new Map()});
|
|
117
151
|
}
|
|
118
152
|
|
|
119
|
-
const
|
|
153
|
+
const forClass = isConstructor(target);
|
|
154
|
+
const composed = {proto: null, static: null, forClass};
|
|
120
155
|
target[COMPOSED_TRAITS].set(source, composed);
|
|
121
156
|
return composed;
|
|
122
157
|
}
|
|
@@ -580,41 +615,69 @@ class CoreTrait
|
|
|
580
615
|
*
|
|
581
616
|
* @param {(function|object)} target
|
|
582
617
|
* @param {object} [opts]
|
|
583
|
-
* @returns {object} Return value from the `
|
|
618
|
+
* @returns {object} Return value from the `removedTrait()` static method.
|
|
584
619
|
*/
|
|
585
620
|
static decomposeFrom(target, opts)
|
|
586
621
|
{
|
|
587
|
-
const info = {target};
|
|
622
|
+
const info = {target, ok:true};
|
|
588
623
|
info.composed = this.getComposed(target);
|
|
589
|
-
|
|
590
|
-
|
|
624
|
+
this.removeTrait(info);
|
|
625
|
+
|
|
626
|
+
if (info.ok)
|
|
627
|
+
{
|
|
628
|
+
info.count = decompose(target, this, opts);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return this.removedTrait(info);
|
|
591
632
|
}
|
|
592
633
|
|
|
593
634
|
/**
|
|
594
635
|
* A static method called by `decomposeFrom()`
|
|
595
|
-
*
|
|
636
|
+
* _before_ decomposing the trait properties from the target.
|
|
596
637
|
*
|
|
597
638
|
* @param {object} info - Metadata from `decomposeFrom()`
|
|
598
639
|
* @param {(function|object)} info.target - The `target` argument
|
|
599
640
|
* @param {module:@lumjs/core/traits~Composed} info.composed
|
|
600
641
|
* The property map that was previously composed.
|
|
601
|
-
* @param {
|
|
642
|
+
* @param {boolean} info.ok - Will always be `true` initially.
|
|
643
|
+
*
|
|
644
|
+
* If an overridden `removeTrait()` method sets this to `false`,
|
|
645
|
+
* then the `decomposeFrom()` operation will be cancelled before
|
|
646
|
+
* decomposing the trait.
|
|
647
|
+
*
|
|
648
|
+
*/
|
|
649
|
+
static removeTrait(info)
|
|
650
|
+
{
|
|
651
|
+
if (this.debug)
|
|
652
|
+
{
|
|
653
|
+
console.debug(this.name, "removeTrait()", info, this);
|
|
654
|
+
}
|
|
655
|
+
return info;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* A static method called by `decomposeFrom()`
|
|
660
|
+
* _after_ decomposing the trait properties from the target.
|
|
661
|
+
*
|
|
662
|
+
* @param {object} info - The same as `removeTrait()`, plus:
|
|
663
|
+
* @param {number} info.count - The number of properties decomposed;
|
|
602
664
|
*
|
|
603
665
|
* @returns {object} The `info` object, with any changes made
|
|
604
|
-
* by
|
|
666
|
+
* by `removeTrait()` and `removedTrait()` methods in the
|
|
667
|
+
* sub-class.
|
|
605
668
|
*
|
|
606
669
|
* The default implementation is a placeholder that returns the
|
|
607
670
|
* `info` object without making any changes.
|
|
608
671
|
*
|
|
609
672
|
*/
|
|
610
|
-
static
|
|
673
|
+
static removedTrait(info)
|
|
611
674
|
{
|
|
612
675
|
if (this.debug)
|
|
613
676
|
{
|
|
614
|
-
console.debug(this.name, "
|
|
677
|
+
console.debug(this.name, "removedTrait()", info, this);
|
|
615
678
|
}
|
|
616
679
|
return info;
|
|
617
|
-
}
|
|
680
|
+
}
|
|
618
681
|
|
|
619
682
|
/**
|
|
620
683
|
* A method wrapping {@link module:@lumjs/core/traits.getComposed}
|
|
@@ -630,14 +693,67 @@ class CoreTrait
|
|
|
630
693
|
|
|
631
694
|
} // CoreTrait class
|
|
632
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Build a Trait registry.
|
|
698
|
+
*
|
|
699
|
+
* @param {object} registry - Object for the registry.
|
|
700
|
+
*
|
|
701
|
+
* Generally the `exports` from a Node.js module would be good here.
|
|
702
|
+
*
|
|
703
|
+
* It will have a `Trait` property added, which is an alias to
|
|
704
|
+
* the `Trait` class constructor.
|
|
705
|
+
*
|
|
706
|
+
* It will also have a `registerTrait(name, value)` function added.
|
|
707
|
+
* This function will add new traits to the registry, using
|
|
708
|
+
* the `name` as its property key. The `value` is either the
|
|
709
|
+
* Trait sub-class constructor `function` itself, or a _lazy-loading_
|
|
710
|
+
* closure `function` that must load and return the actual sub-class
|
|
711
|
+
* constructor when executed. The registry DOES NOT support `object`
|
|
712
|
+
* type traits, or any class that doesn't extend the `Trait` class.
|
|
713
|
+
*
|
|
714
|
+
* @returns {function} The `registerTrait()` function created above.
|
|
715
|
+
* @alias module:@lumjs/core/traits.makeRegistry
|
|
716
|
+
*/
|
|
717
|
+
function makeTraitRegistry(registry)
|
|
718
|
+
{
|
|
719
|
+
needObj(registry, false, 'invalid trait registry object');
|
|
720
|
+
|
|
721
|
+
def(registry, 'Trait', CoreTrait);
|
|
722
|
+
|
|
723
|
+
function registerTrait(name, value)
|
|
724
|
+
{
|
|
725
|
+
needType(S, name, 'invalid trait name');
|
|
726
|
+
needType(F, value, 'invalid trait loader value');
|
|
727
|
+
|
|
728
|
+
if (registry[name] !== undefined)
|
|
729
|
+
{
|
|
730
|
+
console.error("trait already registered", {name,value,registry});
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (CoreTrait.isPrototypeOf(value))
|
|
735
|
+
{ // Make it available directly.
|
|
736
|
+
def(registry, name, value, def.e);
|
|
737
|
+
}
|
|
738
|
+
else
|
|
739
|
+
{ // Lazy-loading engaged.
|
|
740
|
+
def.lazy(registry, name, value, def.e);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
def(registry, 'registerTrait', registerTrait);
|
|
745
|
+
|
|
746
|
+
return registerTrait;
|
|
747
|
+
}
|
|
748
|
+
|
|
633
749
|
module.exports =
|
|
634
750
|
{
|
|
635
|
-
// The main public exports.
|
|
636
751
|
compose, composeFully, getComposed, decompose,
|
|
637
|
-
Trait: CoreTrait, IGNORE_STATIC,
|
|
752
|
+
Trait: CoreTrait, IGNORE_STATIC, ensureProto,
|
|
753
|
+
makeRegistry: makeTraitRegistry,
|
|
638
754
|
|
|
639
|
-
// Undocumented
|
|
640
|
-
|
|
755
|
+
// Undocumented:
|
|
756
|
+
hasOwn,
|
|
641
757
|
}
|
|
642
758
|
|
|
643
759
|
/**
|
|
@@ -657,6 +773,13 @@ module.exports =
|
|
|
657
773
|
*
|
|
658
774
|
* Exactly the same description as `proto`, but for static properties.
|
|
659
775
|
*
|
|
776
|
+
* @prop {boolean} forClass - Are these property maps from a class?
|
|
777
|
+
*
|
|
778
|
+
* Will be `true` if the original target was a `function`, or `false`
|
|
779
|
+
* otherwise. Only really useful when trait functions need to be called
|
|
780
|
+
* differently depending on if the trait was applied to an individual
|
|
781
|
+
* instance or a class constructor.
|
|
782
|
+
*
|
|
660
783
|
*/
|
|
661
784
|
|
|
662
785
|
/**
|