@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.
Files changed (2) hide show
  1. package/lib/traits.js +151 -28
  2. 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
- // Protected function to get a class prototype.
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', 'removeTrait',
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, but at least
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 composed = {proto: null, static: null};
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 `removeTrait()` static method.
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
- info.count = decompose(target, this, opts);
590
- return this.removeTrait(info);
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
- * _after_ decomposing the trait properties from the target.
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 {number} info.count - The number of properties decomposed.
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 an overridden `removeTrait()` method in the sub-class.
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 removeTrait(info)
673
+ static removedTrait(info)
611
674
  {
612
675
  if (this.debug)
613
676
  {
614
- console.debug(this.name, "removeTrait()", info, this);
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 protected functions.
640
- ensureProto, hasOwn,
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.25.0",
3
+ "version": "1.25.2",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {