@adaas/a-concept 0.1.52 → 0.1.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaas/a-concept",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "A-Concept is a framework to build new Applications within or outside the ADAAS ecosystem. This framework is designed to be modular structure regardless environment and program goal.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.cjs",
@@ -709,26 +709,33 @@ export class A_Context {
709
709
  if (!A_TypeGuards.isAllowedForFeatureDefinition(component))
710
710
  throw new A_ContextError(A_ContextError.InvalidFeatureExtensionParameterError, `Unable to get feature template. Component of type ${componentName} is not allowed for feature definition.`);
711
711
 
712
- const callName = `${component.constructor.name}.${name}`;
712
+
713
+ const callNames = A_CommonHelper.getClassInheritanceChain(component)
714
+ .filter(c => c !== A_Component && c !== A_Container && c !== A_Entity)
715
+ .map(c => `${c.name}.${name}`);
716
+
717
+ // const callName = `${component.constructor.name}.${name}`;
713
718
 
714
719
  const steps: A_TYPES__A_StageStep[] = [];
715
720
 
716
- // We need to get all components that has extensions for the feature in component
717
- for (const [cmp, meta] of instance._metaStorage) {
718
- // Just try to make sure that component not only Indexed but also presented in scope
719
- if (scope.has(cmp) && (
720
- A_TypeGuards.isComponentMetaInstance(meta)
721
- || A_TypeGuards.isContainerMetaInstance(meta)
722
- )) {
723
- // Get all extensions for the feature
724
- meta
725
- .extensions(callName)
726
- .forEach((declaration) => {
727
- steps.push({
728
- component: cmp,
729
- ...declaration
721
+ for (const callName of callNames) {
722
+ // We need to get all components that has extensions for the feature in component
723
+ for (const [cmp, meta] of instance._metaStorage) {
724
+ // Just try to make sure that component not only Indexed but also presented in scope
725
+ if (scope.has(cmp) && (
726
+ A_TypeGuards.isComponentMetaInstance(meta)
727
+ || A_TypeGuards.isContainerMetaInstance(meta)
728
+ )) {
729
+ // Get all extensions for the feature
730
+ meta
731
+ .extensions(callName)
732
+ .forEach((declaration) => {
733
+ steps.push({
734
+ component: cmp,
735
+ ...declaration
736
+ });
730
737
  });
731
- });
738
+ }
732
739
  }
733
740
  }
734
741
 
@@ -474,6 +474,66 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
474
474
  }
475
475
 
476
476
 
477
+ /**
478
+ * Allows to chain the feature to another feature.
479
+ * In this case the parent feature scope (if new not provided), stages, caller will be used.
480
+ *
481
+ * [!] Note: Chained feature will use the same caller as the parent feature.
482
+ *
483
+ * @param feature
484
+ */
485
+ chain(
486
+ /**
487
+ * A Feature to be chained
488
+ */
489
+ feature: A_Feature,
490
+ /**
491
+ * Optional scope to be used for the chained feature.
492
+ */
493
+ scope?: A_Scope
494
+ )
495
+ chain<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__FeatureAvailableComponents>(
496
+ /**
497
+ * Component whose feature should be chained
498
+ */
499
+ component: A_TYPES__FeatureAvailableComponents,
500
+ /**
501
+ * A Feature Name to be chained
502
+ */
503
+ feature: string,
504
+ /**
505
+ * Optional scope to be used for the chained feature.
506
+ */
507
+ scope?: A_Scope
508
+ )
509
+ chain<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__FeatureAvailableComponents>(
510
+ param1: A_TYPES__FeatureAvailableComponents | A_Feature,
511
+ param2?: string | A_Scope,
512
+ param3?: A_Scope
513
+ ) {
514
+ let feature: A_Feature;
515
+ let scope: A_Scope | undefined;
516
+
517
+ if (param1 instanceof A_Feature) {
518
+ feature = param1;
519
+ scope = param2 instanceof A_Scope ? param2 : undefined;
520
+ } else {
521
+ feature = new A_Feature({
522
+ name: param2 as string,
523
+ component: param1 as T
524
+ });
525
+ scope = param3 instanceof A_Scope ? param3 : undefined;
526
+ }
527
+
528
+ const featureScope = scope || this.scope;
529
+
530
+ // create new caller for the chained feature
531
+ feature._caller = this._caller;
532
+
533
+ return feature.process(featureScope);
534
+ }
535
+
536
+
477
537
 
478
538
 
479
539
  toString(): string {
@@ -30,6 +30,68 @@ export class A_CommonHelper {
30
30
  return false;
31
31
  }
32
32
 
33
+
34
+ /**
35
+ * Get all parent classes of a given class
36
+ *
37
+ * @param childClass
38
+ * @returns
39
+ */
40
+ static getParentClasses(childClass: any): any[] {
41
+
42
+ // first we need to check is that a constructor or instance
43
+ let current = typeof childClass === 'function'
44
+ ? Object.getPrototypeOf(childClass)
45
+ : Object.getPrototypeOf(childClass.constructor);
46
+
47
+ const parents = [] as any[];
48
+
49
+ // Traverse the prototype chain
50
+ while (current && current !== Function.prototype) {
51
+ parents.push(current);
52
+ current = Object.getPrototypeOf(current);
53
+ }
54
+ return parents;
55
+
56
+ }
57
+
58
+ /**
59
+ * Get the class inheritance chain as an array of class names
60
+ *
61
+ * @param childClass
62
+ * @returns
63
+ */
64
+ static getClassInheritanceChain(childClass: any): any[] {
65
+
66
+ // first we need to check is that a constructor or instance
67
+ let current = typeof childClass === 'function'
68
+ ? Object.getPrototypeOf(childClass)
69
+ : Object.getPrototypeOf(childClass.constructor);
70
+
71
+ // then if input is instance we have to include its own class name
72
+ const chain = typeof childClass === 'function'
73
+ ? [childClass]
74
+ : [childClass.constructor];
75
+
76
+
77
+ // Traverse the prototype chain
78
+ while (current && current !== Function.prototype) {
79
+ chain.push(current);
80
+ current = Object.getPrototypeOf(current);
81
+ }
82
+ return chain;
83
+ }
84
+
85
+ /**
86
+ * Get the parent class of a given class
87
+ *
88
+ * @param childClass
89
+ * @returns
90
+ */
91
+ static getParentClass(childClass: any): any {
92
+ return Object.getPrototypeOf(childClass);
93
+ }
94
+
33
95
  /**
34
96
  * Omit properties from an object or array with nested objects
35
97
  *
@@ -5,7 +5,7 @@ import { A_Scope } from "@adaas/a-concept/global/A-Scope/A-Scope.class";
5
5
  import { A_Caller } from '@adaas/a-concept/global/A-Caller/A_Caller.class';
6
6
  import { A_Context } from '@adaas/a-concept/global/A-Context/A-Context.class';
7
7
  import { A_TYPES__ComponentMetaKey } from '@adaas/a-concept/global/A-Component/A-Component.constants';
8
- import { A_Error, A_TYPES__FeatureState } from "../src";
8
+ import { A_Entity, A_Error, A_TYPES__FeatureState } from "../src";
9
9
 
10
10
  jest.retryTimes(0);
11
11
 
@@ -582,4 +582,88 @@ describe('A-Feature tests', () => {
582
582
  });
583
583
 
584
584
  }, 5000);
585
+ it('Should allow to use extension if only parent class provided', async () => {
586
+ const executionResults: string[] = [];
587
+
588
+ class BaseEntity extends A_Entity {
589
+ async test() {
590
+ await this.call('myFeature');
591
+ }
592
+
593
+ }
594
+
595
+ class MyComponent extends A_Component {
596
+
597
+ @A_Feature.Extend({
598
+ name: 'myFeature',
599
+ scope: [BaseEntity]
600
+ })
601
+ testMethod() {
602
+ executionResults.push('testMethod');
603
+ }
604
+ }
605
+
606
+ class My_Entity extends BaseEntity {
607
+
608
+ }
609
+
610
+ const scope = new A_Scope({ name: 'TestScope', components: [MyComponent] });
611
+
612
+
613
+ const myEntity = new My_Entity({ name: 'MyEntityInstance' });
614
+ const baseEntity = new BaseEntity({ name: 'BaseEntityInstance' });
615
+
616
+
617
+ scope.register(myEntity);
618
+ scope.register(baseEntity);
619
+
620
+ await baseEntity.test();
621
+
622
+ expect(executionResults).toEqual(['testMethod']);
623
+
624
+ await myEntity.test();
625
+
626
+ expect(executionResults).toEqual(['testMethod', 'testMethod']);
627
+ });
628
+ it('Should allow be possible to do a Feature Chaining without Caller Change', async () => {
629
+ const executionResults: string[] = [];
630
+
631
+ class Component_B extends A_Component {
632
+
633
+ @A_Feature.Extend()
634
+ featureB(
635
+ @A_Inject(A_Caller) caller: Component_A
636
+ ) {
637
+ executionResults.push('featureB');
638
+
639
+ expect(caller).toBeInstanceOf(Component_A);
640
+ }
641
+ }
642
+
643
+ class Component_A extends A_Component {
644
+
645
+ @A_Feature.Extend()
646
+ async featureA(
647
+ @A_Inject(A_Feature) feature: A_Feature,
648
+ @A_Inject(Component_B) compb: Component_B
649
+ ) {
650
+ executionResults.push('featureA');
651
+
652
+ await feature.chain(compb, 'featureB');
653
+ }
654
+ }
655
+
656
+
657
+
658
+ const scope = new A_Scope({ name: 'TestScope', components: [Component_A, Component_B] });
659
+
660
+
661
+ const compA = scope.resolve(Component_A)!;
662
+
663
+ await compA.call('featureA');
664
+
665
+ expect(executionResults).toEqual(['featureA', 'featureB']);
666
+
667
+
668
+ });
585
669
  });