@adaas/a-concept 0.3.4 → 0.3.6

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.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "A-Concept is a framework of the new generation that is tailored to use AI, enabling developers to create AI-powered applications with ease. It provides a structured approach to building, managing, and deploying AI-driven solutions.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -50,7 +50,7 @@ export class A_ComponentMeta<T extends A_TYPES__ComponentMeta = A_TYPES__Compone
50
50
  before: extension.before || '',
51
51
  after: extension.after || '',
52
52
  throwOnError: extension.throwOnError || true,
53
- override: ''
53
+ override: extension.override || '',
54
54
  });
55
55
 
56
56
  });
@@ -834,6 +834,17 @@ export class A_Context {
834
834
  steps.delete(`${A_CommonHelper.getComponentName(inherited)}.${declaration.handler}`);
835
835
  }
836
836
 
837
+ // Check if the declaration has an override regexp and remove matching steps
838
+ // Match against both the full step key (Component.handler) and the handler alone
839
+ if (declaration.override) {
840
+ const overrideRegexp = new RegExp(declaration.override);
841
+ for (const [stepKey, step] of steps) {
842
+ if (overrideRegexp.test(stepKey) || overrideRegexp.test(step.handler)) {
843
+ steps.delete(stepKey);
844
+ }
845
+ }
846
+ }
847
+
837
848
  steps.set(`${A_CommonHelper.getComponentName(cmp)}.${declaration.handler}`, {
838
849
  dependency: new A_Dependency(cmp),
839
850
  ...declaration
@@ -177,18 +177,34 @@ export function A_Feature_Extend(
177
177
  ];
178
178
 
179
179
  // ensure that other regexps are preserved
180
+ // Only remove the handler from another regexp if that regexp targets
181
+ // the SAME feature name (i.e., same feature, different scope — inheritance case).
182
+ // If the other regexp targets a DIFFERENT feature, leave it alone so that
183
+ // the same method can serve multiple features.
184
+ const currentFeatureName = (param1 && typeof param1 === 'object' && !A_TypeGuards.isRegExp(param1) && (param1 as Partial<A_TYPES__FeatureExtendDecoratorConfig>).name)
185
+ || propertyKey;
186
+
180
187
  for (const [key, handlers] of existedMeta.entries()) {
181
188
 
182
189
  const indexInAnother = handlers.findIndex(item => item.handler === propertyKey);
183
190
 
184
- // if the same handler exists in another regexp, remove it
191
+ // if the same handler exists in another regexp, check if it's the same feature
185
192
  if (key !== targetRegexp.source && indexInAnother !== -1) {
186
- handlers.splice(indexInAnother, 1);
187
- // if no handlers left for this regexp, remove the regexp entry
188
- if (handlers.length === 0) {
189
- existedMeta.delete(key);
190
- } else {
191
- existedMeta.set(key, handlers);
193
+ // Extract the feature name from the other regexp key
194
+ // Regexp keys are built as: ^...\.featureName$ or .*\.featureName$
195
+ const keyStr = String(key);
196
+ const featureNameMatch = keyStr.match(/\\\.\s*([^\\.$]+)\$$/);
197
+ const otherFeatureName = featureNameMatch ? featureNameMatch[1] : null;
198
+
199
+ // Only clean up if the other regexp targets the same feature name
200
+ if (otherFeatureName === currentFeatureName) {
201
+ handlers.splice(indexInAnother, 1);
202
+ // if no handlers left for this regexp, remove the regexp entry
203
+ if (handlers.length === 0) {
204
+ existedMeta.delete(key);
205
+ } else {
206
+ existedMeta.set(key, handlers);
207
+ }
192
208
  }
193
209
  }
194
210
  }
@@ -16,6 +16,11 @@ export class A_StepsManager {
16
16
  public tempMark: Set<string>;
17
17
  public sortedEntities: string[];
18
18
 
19
+ /**
20
+ * Maps each step instance to a unique ID.
21
+ * Duplicate steps (same dependency.name + handler) get indexed suffixes (e.g., #1, #2).
22
+ */
23
+ private _uniqueIdMap: Map<A_TYPES__A_StageStep, string> = new Map();
19
24
 
20
25
  private _isBuilt: boolean = false;
21
26
 
@@ -27,6 +32,7 @@ export class A_StepsManager {
27
32
  this.tempMark = new Set();
28
33
  this.sortedEntities = [];
29
34
 
35
+ this.assignUniqueIds();
30
36
  }
31
37
 
32
38
  private prepareSteps(
@@ -44,20 +50,68 @@ export class A_StepsManager {
44
50
  }));
45
51
  }
46
52
 
47
- private ID(step: A_TYPES__A_StageStep) {
53
+ /**
54
+ * Returns the base (non-unique) ID for a step: `dependency.name.handler`
55
+ */
56
+ private baseID(step: A_TYPES__A_StageStep) {
48
57
  return `${step.dependency.name}.${step.handler}`;
49
58
  }
50
59
 
60
+ /**
61
+ * Returns the unique ID assigned to a specific step instance.
62
+ * Falls back to baseID if not yet assigned.
63
+ */
64
+ private ID(step: A_TYPES__A_StageStep) {
65
+ return this._uniqueIdMap.get(step) || this.baseID(step);
66
+ }
67
+
68
+ /**
69
+ * Assigns unique IDs to all steps.
70
+ * Duplicate base IDs get an index suffix (#0, #1, ...).
71
+ * Steps with unique base IDs keep their original ID (no suffix).
72
+ */
73
+ private assignUniqueIds() {
74
+ // Count occurrences of each base ID
75
+ const counts = new Map<string, number>();
76
+ for (const step of this.entities) {
77
+ const base = this.baseID(step);
78
+ counts.set(base, (counts.get(base) || 0) + 1);
79
+ }
80
+
81
+ // Assign unique IDs with index suffix only for duplicates
82
+ const indexTracker = new Map<string, number>();
83
+ for (const step of this.entities) {
84
+ const base = this.baseID(step);
85
+ if (counts.get(base)! > 1) {
86
+ const idx = indexTracker.get(base) || 0;
87
+ this._uniqueIdMap.set(step, `${base}#${idx}`);
88
+ indexTracker.set(base, idx + 1);
89
+ } else {
90
+ this._uniqueIdMap.set(step, base);
91
+ }
92
+ }
93
+ }
94
+
51
95
  private buildGraph() {
52
96
  if (this._isBuilt) return;
53
97
  this._isBuilt = true;
54
98
 
55
- // Filter override
99
+ // Filter override — match against both the full base ID and handler name
100
+ // so both formats work (e.g., `^.*\.step(4|5)$` and `^test1$`)
101
+ // A step's own override pattern should not cause it to filter itself out
56
102
  this.entities = this.entities
57
103
  .filter((step, i, self) =>
58
- !self.some(s => s.override ? new RegExp(s.override).test(this.ID(step)) : false)
104
+ !self.some((s, j) => {
105
+ if (i === j || !s.override) return false;
106
+ const re = new RegExp(s.override);
107
+ return re.test(this.baseID(step)) || re.test(step.handler);
108
+ })
59
109
  );
60
110
 
111
+ // Re-assign unique IDs after filtering
112
+ this._uniqueIdMap.clear();
113
+ this.assignUniqueIds();
114
+
61
115
  // Initialize graph nodes
62
116
  this.entities.forEach(entity => this.graph.set(this.ID(entity), new Set()));
63
117
 
@@ -90,12 +144,12 @@ export class A_StepsManager {
90
144
  });
91
145
  }
92
146
 
93
- // Match entities by name or regex
147
+ // Match entities by name or regex — matches against the base ID for pattern compatibility
94
148
  private matchEntities(entityId: string, pattern: string): string[] {
95
149
  const regex = new RegExp(pattern);
96
150
 
97
151
  return this.entities
98
- .filter(entity => regex.test(this.ID(entity)) && this.ID(entity) !== entityId)
152
+ .filter(entity => regex.test(this.baseID(entity)) && this.ID(entity) !== entityId)
99
153
  .map(entity => this.ID(entity));
100
154
  }
101
155
 
@@ -1038,4 +1038,71 @@ describe('A-Feature tests', () => {
1038
1038
  'ChildComponent_A.test'
1039
1039
  ]);
1040
1040
  })
1041
+ it('Should keep override definition', async () => {
1042
+
1043
+ const resultChain: string[] = [];
1044
+
1045
+ class ChildComponent_A extends A_Component {
1046
+ @A_Feature.Extend({
1047
+ name: 'testFeature',
1048
+ })
1049
+ async test1() {
1050
+ resultChain.push('ChildComponent_A.test1');
1051
+ }
1052
+
1053
+ @A_Feature.Extend({
1054
+ name: 'testFeature',
1055
+ override: ['test1']
1056
+ })
1057
+ async test2() {
1058
+ resultChain.push('ChildComponent_A.test2');
1059
+ }
1060
+ }
1061
+
1062
+ const testScope = new A_Scope({ name: 'TestScope', components: [ChildComponent_A] });
1063
+
1064
+ const featureDefinition = A_Context.featureTemplate('testFeature', testScope.resolve(ChildComponent_A)!, testScope);
1065
+
1066
+
1067
+ expect(featureDefinition).toBeDefined();
1068
+ expect(featureDefinition.length).toBe(1);
1069
+ expect(featureDefinition[0].handler).toBe('test2');
1070
+
1071
+ await testScope.resolve(ChildComponent_A)!.call('testFeature');
1072
+
1073
+ expect(resultChain).toEqual([
1074
+ 'ChildComponent_A.test2'
1075
+ ]);
1076
+ })
1077
+
1078
+ it('Should allow to define 2 extension under the same method', async () => {
1079
+
1080
+ const resultChain: string[] = [];
1081
+
1082
+ class ComponentA extends A_Component {
1083
+ @A_Feature.Extend({
1084
+ name: 'testFeature1',
1085
+ })
1086
+ @A_Feature.Extend({
1087
+ name: 'testFeature2',
1088
+ })
1089
+ async feature1() {
1090
+ resultChain.push('ComponentA.feature1');
1091
+ }
1092
+ }
1093
+
1094
+ const testScope = new A_Scope({ name: 'TestScope', components: [ComponentA] });
1095
+
1096
+ const component = testScope.resolve(ComponentA)!;
1097
+
1098
+ component.call('testFeature1');
1099
+ component.call('testFeature2');
1100
+
1101
+ expect(resultChain).toEqual([
1102
+ 'ComponentA.feature1',
1103
+ 'ComponentA.feature1'
1104
+ ]);
1105
+ })
1106
+
1107
+
1041
1108
  });