@adaas/a-concept 0.3.7 → 0.3.9

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.7",
3
+ "version": "0.3.9",
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": {
@@ -58,6 +58,8 @@
58
58
  "bench": "ts-node -r tsconfig-paths/register benchmarks/run-all.ts",
59
59
  "bench:step-manager": "ts-node -r tsconfig-paths/register benchmarks/step-manager.bench.ts",
60
60
  "bench:feature-template": "ts-node -r tsconfig-paths/register benchmarks/feature-template.bench.ts",
61
+ "bench:feature-optimize": "node --expose-gc -r ts-node/register -r tsconfig-paths/register benchmarks/feature-optimize.bench.ts",
62
+ "bench:feature-chaining": "ts-node -r tsconfig-paths/register benchmarks/feature-chaining.bench.ts",
61
63
  "bench:scope-resolve": "ts-node -r tsconfig-paths/register benchmarks/scope-resolve.bench.ts",
62
64
  "bench:feature-lifecycle": "ts-node -r tsconfig-paths/register benchmarks/feature-lifecycle.bench.ts",
63
65
  "start": "nodemon ./tests/example-usage.ts",
@@ -131,7 +131,7 @@ export class A_Context {
131
131
  * Key format: `${featureName}::${componentConstructorName}::${scopeVersion}::${metaVersion}`
132
132
  * Automatically invalidated when scope version or meta version changes.
133
133
  */
134
- protected _featureExtensionsCache: Map<string, Array<A_TYPES__A_StageStep>> = new Map();
134
+ protected _featureCache: Map<string, Array<A_TYPES__A_StageStep>> = new Map();
135
135
 
136
136
  /**
137
137
  * Maximum number of entries in the featureExtensions cache.
@@ -139,7 +139,20 @@ export class A_Context {
139
139
  */
140
140
  protected static readonly FEATURE_EXTENSIONS_CACHE_MAX_SIZE = 1024;
141
141
 
142
+ // ====================================================================================================
143
+ // ================================ INHERITANCE INDEX ==================================================
144
+ // ====================================================================================================
145
+ /**
146
+ * For each indexed constructor, stores the set of all its ancestor constructors
147
+ * (walking up the prototype chain). Enables O(1) isInheritedFrom checks.
148
+ */
149
+ protected _ancestors: Map<Function, Set<Function>> = new Map();
142
150
 
151
+ /**
152
+ * For each constructor, stores the set of all known descendant constructors.
153
+ * Enables O(1) "find a descendant in a Set" lookups.
154
+ */
155
+ protected _descendants: Map<Function, Set<Function>> = new Map();
143
156
 
144
157
 
145
158
  protected _globals = new Map<string, any>();
@@ -225,20 +238,19 @@ export class A_Context {
225
238
  */
226
239
  component: A_TYPES_ScopeDependentComponents,
227
240
  ): void {
228
- // uses only for error messages
229
- const componentName = A_CommonHelper.getComponentName(component);
230
-
231
- const instance = this.getInstance();
232
-
233
241
  if (!component) throw new A_ContextError(
234
242
  A_ContextError.InvalidDeregisterParameterError,
235
243
  `Unable to deregister component. Component cannot be null or undefined.`);
236
244
 
237
- if (!instance._scopeStorage.has(component)) throw new A_ContextError(
238
- A_ContextError.ComponentNotRegisteredError,
239
- `Unable to deregister component. Component ${componentName} is not registered.`);
245
+ const instance = this.getInstance();
240
246
 
241
- instance._scopeStorage.delete(component);
247
+ if (!instance._scopeStorage.delete(component)) {
248
+ const componentName = A_CommonHelper.getComponentName(component)
249
+ throw new A_ContextError(
250
+ A_ContextError.ComponentNotRegisteredError,
251
+ `Unable to deregister component. Component ${componentName} is not registered.`
252
+ )
253
+ }
242
254
  }
243
255
 
244
256
  /**
@@ -351,6 +363,7 @@ export class A_Context {
351
363
  ? param1
352
364
  : this.issuer(scope);
353
365
 
366
+
354
367
  if (component)
355
368
  instance._registry.delete(component);
356
369
  if (scope)
@@ -568,6 +581,9 @@ export class A_Context {
568
581
 
569
582
  instance._metaStorage.set(property, inheritedMeta.clone());
570
583
  instance._metaVersion++;
584
+
585
+ // Index the constructor in the inheritance graph
586
+ this.indexConstructor(property);
571
587
  }
572
588
 
573
589
  // Return the meta for the property
@@ -773,6 +789,19 @@ export class A_Context {
773
789
  if (!A_TypeGuards.isAllowedForFeatureDefinition(component))
774
790
  throw new A_ContextError(A_ContextError.InvalidFeatureTemplateParameterError, `Unable to get feature template. Component of type ${A_CommonHelper.getComponentName(component)} is not allowed for feature definition.`);
775
791
 
792
+ const instance = this.getInstance();
793
+
794
+ // ---- Optimization 1: Check featureExtensions cache ----
795
+ // Use the effective scope for cache key — the scope that actually contains registered components.
796
+ // Feature scopes are ephemeral (unique uid per call) and only inherit from the component scope,
797
+ // so use the parent scope identity when available to enable cache hits across feature calls.
798
+ const cacheKey = `${String(name)}::${A_CommonHelper.getComponentName(component)}::s${scope.fingerprint}::m${instance._metaVersion}`;
799
+
800
+ const cached = instance._featureCache.get(cacheKey);
801
+ if (cached) {
802
+ return cached;
803
+ }
804
+
776
805
  const steps: A_TYPES__A_StageStep[] = [
777
806
  // 1) Get the base feature definition from the component
778
807
  ...this.featureDefinition(name, component),
@@ -780,6 +809,12 @@ export class A_Context {
780
809
  ...this.featureExtensions(name, component, scope)
781
810
  ];
782
811
 
812
+ // ---- Store result in cache ----
813
+ if (instance._featureCache.size >= A_Context.FEATURE_EXTENSIONS_CACHE_MAX_SIZE) {
814
+ instance._featureCache.clear();
815
+ }
816
+ instance._featureCache.set(cacheKey, steps);
817
+
783
818
  return steps;
784
819
  }
785
820
  // ----------------------------------------------------------------------------------------------------------
@@ -819,18 +854,7 @@ export class A_Context {
819
854
  if (!A_TypeGuards.isAllowedForFeatureDefinition(component))
820
855
  throw new A_ContextError(A_ContextError.InvalidFeatureExtensionParameterError, `Unable to get feature template. Component of type ${A_CommonHelper.getComponentName(component)} is not allowed for feature definition.`);
821
856
 
822
- // ---- Optimization 1: Check featureExtensions cache ----
823
- // Use the effective scope for cache key — the scope that actually contains registered components.
824
- // Feature scopes are ephemeral (unique uid per call) and only inherit from the component scope,
825
- // so use the parent scope identity when available to enable cache hits across feature calls.
826
- const componentCtor = typeof component === 'function' ? component : component.constructor;
827
- const effectiveScope = scope.parent || scope;
828
- const cacheKey = `${String(name)}::${componentCtor.name}::s${effectiveScope.uid}v${effectiveScope.version}::m${instance._metaVersion}`;
829
857
 
830
- const cached = instance._featureExtensionsCache.get(cacheKey);
831
- if (cached) {
832
- return cached;
833
- }
834
858
 
835
859
  const callNames = A_CommonHelper.getClassInheritanceChain(component)
836
860
  .filter(c => c !== A_Component && c !== A_Container && c !== A_Entity)
@@ -884,7 +908,7 @@ export class A_Context {
884
908
 
885
909
  for (let i = 0; i < extensions.length; i++) {
886
910
  const declaration = extensions[i];
887
- const inherited = Array.from(allowedComponents).reverse().find(c => A_CommonHelper.isInheritedFrom(cmp, c) && c !== cmp);
911
+ const inherited = Array.from(allowedComponents).reverse().find(c => A_Context.isIndexedInheritedFrom(cmp, c) && c !== cmp);
888
912
 
889
913
  if (inherited) {
890
914
  steps.delete(`${getNameCached(inherited)}.${declaration.handler}`);
@@ -911,12 +935,6 @@ export class A_Context {
911
935
 
912
936
  const result = instance.filterToMostDerived(scope, Array.from(steps.values()));
913
937
 
914
- // ---- Store result in cache ----
915
- if (instance._featureExtensionsCache.size >= A_Context.FEATURE_EXTENSIONS_CACHE_MAX_SIZE) {
916
- instance._featureExtensionsCache.clear();
917
- }
918
- instance._featureExtensionsCache.set(cacheKey, result);
919
-
920
938
  return result;
921
939
  }
922
940
 
@@ -952,11 +970,10 @@ export class A_Context {
952
970
  }
953
971
 
954
972
  // 2) Build a set of dependency names that are parents of at least one other item.
955
- // For each resolved class, walk up its prototype chain and mark any ancestor
956
- // that also appears in presentNames as "is parent of another".
973
+ // Use the inheritance index for O(1) ancestor lookups instead of walking prototypes.
957
974
  const parentNames = new Set<string>();
958
975
 
959
- // Build reverse map: constructor → dependency name (for fast lookup when walking prototypes)
976
+ // Build reverse map: constructor → dependency name (for fast lookup)
960
977
  const ctorToName = new Map<Function, string>();
961
978
  for (const [depName, ctor] of resolvedClasses) {
962
979
  if (ctor) ctorToName.set(ctor, depName);
@@ -965,15 +982,15 @@ export class A_Context {
965
982
  for (const [depName, ctor] of resolvedClasses) {
966
983
  if (!ctor) continue;
967
984
 
968
- // Walk prototype chain of this class's prototype to find ancestors
969
- let ancestor = Object.getPrototypeOf(ctor.prototype);
970
- while (ancestor && ancestor !== Object.prototype) {
971
- const ancestorCtor = ancestor.constructor;
972
- const ancestorName = ctorToName.get(ancestorCtor);
973
- if (ancestorName && ancestorName !== depName && presentNames.has(ancestorName)) {
974
- parentNames.add(ancestorName);
985
+ // Use indexed ancestors instead of walking prototype chain
986
+ const ancestors = A_Context.getAncestors(ctor);
987
+ if (ancestors) {
988
+ for (const ancestor of ancestors) {
989
+ const ancestorName = ctorToName.get(ancestor);
990
+ if (ancestorName && ancestorName !== depName && presentNames.has(ancestorName)) {
991
+ parentNames.add(ancestorName);
992
+ }
975
993
  }
976
- ancestor = Object.getPrototypeOf(ancestor);
977
994
  }
978
995
  }
979
996
 
@@ -1138,7 +1155,7 @@ export class A_Context {
1138
1155
  meta
1139
1156
  .abstractions(abstraction)
1140
1157
  .forEach((declaration) => {
1141
- const inherited = Array.from(allowedComponents).reverse().find(c => A_CommonHelper.isInheritedFrom(cmp, c) && c !== cmp);
1158
+ const inherited = Array.from(allowedComponents).reverse().find(c => A_Context.isIndexedInheritedFrom(cmp, c) && c !== cmp);
1142
1159
 
1143
1160
  if (inherited) {
1144
1161
  steps.delete(`${A_CommonHelper.getComponentName(inherited)}.${declaration.handler}`);
@@ -1163,7 +1180,9 @@ export class A_Context {
1163
1180
  const instance = A_Context.getInstance();
1164
1181
 
1165
1182
  instance._registry = new WeakMap();
1166
- instance._featureExtensionsCache.clear();
1183
+ instance._featureCache.clear();
1184
+ instance._ancestors.clear();
1185
+ instance._descendants.clear();
1167
1186
  instance._metaVersion++;
1168
1187
 
1169
1188
  const name = String(A_CONCEPT_ENV.A_CONCEPT_ROOT_SCOPE) || 'root';
@@ -1172,6 +1191,160 @@ export class A_Context {
1172
1191
  }
1173
1192
 
1174
1193
 
1194
+ // ====================================================================================================================
1195
+ // ====================================== INHERITANCE INDEX ============================================================
1196
+ // ====================================================================================================================
1197
+
1198
+ /**
1199
+ * Index a constructor's full prototype chain into the inheritance graph.
1200
+ * Safe to call multiple times for the same constructor — it's a no-op if already indexed.
1201
+ *
1202
+ * After indexing, `A_Context.isIndexedInheritedFrom(child, parent)` becomes O(1).
1203
+ */
1204
+ static indexConstructor(ctor: Function): void {
1205
+ const instance = this.getInstance();
1206
+
1207
+ // Already indexed — skip
1208
+ if (instance._ancestors.has(ctor)) return;
1209
+
1210
+ const ancestors = new Set<Function>();
1211
+ let current = Object.getPrototypeOf(ctor);
1212
+
1213
+ while (current && current !== Function.prototype && current !== Object) {
1214
+ ancestors.add(current);
1215
+
1216
+ // Also register `ctor` as a descendant of `current`
1217
+ let desc = instance._descendants.get(current);
1218
+ if (!desc) {
1219
+ desc = new Set();
1220
+ instance._descendants.set(current, desc);
1221
+ }
1222
+ desc.add(ctor);
1223
+
1224
+ // If ancestor is already indexed, merge its ancestors transitively and stop walking
1225
+ const existingAncestors = instance._ancestors.get(current);
1226
+ if (existingAncestors) {
1227
+ for (const a of existingAncestors) {
1228
+ ancestors.add(a);
1229
+ let desc2 = instance._descendants.get(a);
1230
+ if (!desc2) {
1231
+ desc2 = new Set();
1232
+ instance._descendants.set(a, desc2);
1233
+ }
1234
+ desc2.add(ctor);
1235
+ }
1236
+ break;
1237
+ }
1238
+
1239
+ current = Object.getPrototypeOf(current);
1240
+ }
1241
+
1242
+ instance._ancestors.set(ctor, ancestors);
1243
+
1244
+ // Ensure ctor has a descendants entry (possibly empty)
1245
+ if (!instance._descendants.has(ctor)) {
1246
+ instance._descendants.set(ctor, new Set());
1247
+ }
1248
+
1249
+ // Update existing descendants: if any already-indexed class lists an ancestor of ctor,
1250
+ // and ctor is more specific, those descendants are also descendants of ctor's ancestors.
1251
+ // This is already handled transitively above.
1252
+ }
1253
+
1254
+ /**
1255
+ * O(1) check whether `child` inherits from `parent` using the pre-built index.
1256
+ * Falls back to prototype chain walking if either is not yet indexed.
1257
+ *
1258
+ * [!] Handles the same-class case: returns true if child === parent.
1259
+ */
1260
+ static isIndexedInheritedFrom(child: Function, parent: Function): boolean {
1261
+ if (child === parent) return true;
1262
+
1263
+ const instance = this.getInstance();
1264
+ const ancestors = instance._ancestors.get(child);
1265
+ if (ancestors) return ancestors.has(parent);
1266
+
1267
+ // Fallback to manual walk (shouldn't happen if indexing is comprehensive)
1268
+ return A_CommonHelper.isInheritedFrom(child, parent);
1269
+ }
1270
+
1271
+ /**
1272
+ * Find the first constructor in `candidates` that is a descendant of (or equal to) `parent`.
1273
+ * Returns undefined if none found.
1274
+ *
1275
+ * Uses the optimal strategy based on set sizes:
1276
+ * - If candidates is small, iterates candidates and checks ancestry (O(c))
1277
+ * - If descendants is small, iterates descendants and checks membership (O(d))
1278
+ */
1279
+ static findDescendantIn<T extends Function>(
1280
+ parent: Function,
1281
+ candidates: Set<T> | Array<T>
1282
+ ): T | undefined {
1283
+ const candidateSize = candidates instanceof Set ? candidates.size : candidates.length;
1284
+
1285
+ // Direct hit — O(1) for Set
1286
+ if (candidates instanceof Set) {
1287
+ if (candidates.has(parent as T)) return parent as T;
1288
+ } else {
1289
+ if (candidates.includes(parent as T)) return parent as T;
1290
+ }
1291
+
1292
+ const instance = this.getInstance();
1293
+ const descendants = instance._descendants.get(parent);
1294
+ const descendantSize = descendants ? descendants.size : 0;
1295
+
1296
+ // Pick the smaller set to iterate
1297
+ if (descendantSize === 0) {
1298
+ // No indexed descendants — fall back to checking each candidate's ancestors
1299
+ if (candidates instanceof Set) {
1300
+ for (const c of candidates) {
1301
+ const ancestors = instance._ancestors.get(c);
1302
+ if (ancestors && ancestors.has(parent)) return c;
1303
+ }
1304
+ } else {
1305
+ for (const c of candidates) {
1306
+ const ancestors = instance._ancestors.get(c);
1307
+ if (ancestors && ancestors.has(parent)) return c;
1308
+ }
1309
+ }
1310
+ return undefined;
1311
+ }
1312
+
1313
+ if (candidateSize <= descendantSize) {
1314
+ // Candidates is smaller — iterate candidates and check if each is a descendant of parent
1315
+ if (candidates instanceof Set) {
1316
+ for (const c of candidates) {
1317
+ if (c === parent) return c;
1318
+ const ancestors = instance._ancestors.get(c);
1319
+ if (ancestors && ancestors.has(parent)) return c;
1320
+ }
1321
+ } else {
1322
+ for (const c of candidates) {
1323
+ if (c === parent) return c;
1324
+ const ancestors = instance._ancestors.get(c);
1325
+ if (ancestors && ancestors.has(parent)) return c;
1326
+ }
1327
+ }
1328
+ } else {
1329
+ // Descendants is smaller — iterate descendants and check membership in candidates
1330
+ for (const desc of descendants!) {
1331
+ if (candidates instanceof Set) {
1332
+ if (candidates.has(desc as T)) return desc as T;
1333
+ } else {
1334
+ if (candidates.includes(desc as T)) return desc as T;
1335
+ }
1336
+ }
1337
+ }
1338
+
1339
+ return undefined;
1340
+ }
1341
+
1342
+ /**
1343
+ * Returns the set of indexed ancestors for a constructor, or undefined if not indexed.
1344
+ */
1345
+ static getAncestors(ctor: Function): Set<Function> | undefined {
1346
+ return this.getInstance()._ancestors.get(ctor);
1347
+ }
1175
1348
 
1176
1349
 
1177
1350
  // ====================================================================================================================
@@ -269,17 +269,17 @@ export class A_Entity<
269
269
  * @param lifecycleMethod
270
270
  * @param args
271
271
  */
272
- async call(
272
+ call(
273
273
  feature: string,
274
274
  scope?: A_Scope
275
- ) {
275
+ ): Promise<any> | void {
276
276
  const newFeature = new A_Feature({
277
277
  name: feature,
278
278
  component: this,
279
279
  scope
280
280
  });
281
281
 
282
- return await newFeature.process(scope);
282
+ return newFeature.process(scope);
283
283
  }
284
284
 
285
285
 
@@ -12,7 +12,7 @@ import {
12
12
  A_StageError
13
13
  } from "@adaas/a-concept/a-stage";
14
14
  import { A_StepsManager } from "@adaas/a-concept/a-step-manager";
15
- import { A_TypeGuards} from "@adaas/a-concept/helpers/A_TypeGuards.helper";
15
+ import { A_TypeGuards } from "@adaas/a-concept/helpers/A_TypeGuards.helper";
16
16
  import { A_FeatureError } from "./A-Feature.error";
17
17
  import { A_Context } from "@adaas/a-concept/a-context";
18
18
  import { A_Caller } from "@adaas/a-concept/a-caller";
@@ -203,7 +203,7 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
203
203
  if (!params || typeof params !== 'object') {
204
204
  throw new A_FeatureError(
205
205
  A_FeatureError.FeatureInitializationError,
206
- `Invalid A-Feature initialization parameters of type: ${typeof params} with value: ${JSON.stringify(params).slice(0, 100)}...`
206
+ `Invalid A-Feature initialization parameters of type: ${typeof params} with value: ${JSON.stringify(params)?.slice(0, 100)}...`
207
207
  );
208
208
  }
209
209
  }
@@ -226,7 +226,7 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
226
226
  default:
227
227
  throw new A_FeatureError(
228
228
  A_FeatureError.FeatureInitializationError,
229
- `Invalid A-Feature initialization parameters of type: ${typeof params} with value: ${JSON.stringify(params).slice(0, 100)}...`
229
+ `Invalid A-Feature initialization parameters of type: ${typeof params} with value: ${JSON.stringify(params)?.slice(0, 100)}...`
230
230
  );
231
231
  }
232
232
  }
@@ -241,14 +241,14 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
241
241
  if (!params.template || !Array.isArray(params.template)) {
242
242
  throw new A_FeatureError(
243
243
  A_FeatureError.FeatureInitializationError,
244
- `Invalid A-Feature template provided of type: ${typeof params.template} with value: ${JSON.stringify(params.template).slice(0, 100)}...`
244
+ `Invalid A-Feature template provided of type: ${typeof params.template} with value: ${JSON.stringify(params.template)?.slice(0, 100)}...`
245
245
  );
246
246
  }
247
247
 
248
248
  if (!params.component && (!params.scope || !(params.scope instanceof A_Scope))) {
249
249
  throw new A_FeatureError(
250
250
  A_FeatureError.FeatureInitializationError,
251
- `Invalid A-Feature scope provided of type: ${typeof params.scope} with value: ${JSON.stringify(params.scope).slice(0, 100)}...`
251
+ `Invalid A-Feature scope provided of type: ${typeof params.scope} with value: ${JSON.stringify(params.scope)?.slice(0, 100)}...`
252
252
  );
253
253
  }
254
254
 
@@ -304,7 +304,7 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
304
304
  if (!params.component || !A_TypeGuards.isAllowedForFeatureDefinition(params.component)) {
305
305
  throw new A_FeatureError(
306
306
  A_FeatureError.FeatureInitializationError,
307
- `Invalid A-Feature component provided of type: ${typeof params.component} with value: ${JSON.stringify(params.component).slice(0, 100)}...`
307
+ `Invalid A-Feature component provided of type: ${typeof params.component} with value: ${JSON.stringify(params.component)?.slice(0, 100)}...`
308
308
  );
309
309
  }
310
310
 
@@ -332,6 +332,7 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
332
332
 
333
333
  // 4) allocate new scope for the feature
334
334
  const scope = A_Context.allocate(this);
335
+ // const scope = componentScope || externalScope!
335
336
 
336
337
  // 5) ensure that the scope of the caller component is inherited by the feature scope
337
338
  scope.inherit(componentScope || externalScope!);
@@ -375,10 +376,34 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
375
376
 
376
377
  this._state = A_TYPES__FeatureState.PROCESSING;
377
378
 
378
- // Convert iterator to array to get all stages
379
- const stages = Array.from(this);
379
+ for (const stage of this) {
380
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
380
381
 
381
- return this.processStagesSequentially(stages, scope, 0);
382
+ let result: Promise<void> | void
383
+
384
+ try {
385
+ result = stage.process(scope)
386
+ } catch (error) {
387
+ throw this.createStageError(error, stage)
388
+ }
389
+
390
+ // ── Async stage — pivot to async continuation ─────────────────────
391
+ if (A_TypeGuards.isPromiseInstance(result)) {
392
+ return result
393
+ .then(() => {
394
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
395
+ return this.processRemainingStagesAsync(scope)
396
+ })
397
+ .catch(error => {
398
+ throw this.createStageError(error, stage)
399
+ })
400
+ }
401
+ }
402
+
403
+ // ── All stages complete ───────────────────────────────────────────────
404
+ if (this.state !== A_TYPES__FeatureState.INTERRUPTED) {
405
+ this.completed()
406
+ }
382
407
 
383
408
  } catch (error) {
384
409
  throw this.failed(new A_FeatureError({
@@ -391,59 +416,42 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
391
416
  }
392
417
 
393
418
  /**
394
- * Process stages one by one, ensuring each stage completes before starting the next
419
+ * Async continuation processes remaining stages after the first async pivot.
420
+ * Resumes from the current iterator position (this._index).
395
421
  */
396
- private processStagesSequentially(
397
- stages: A_Stage[],
398
- scope: A_Scope | undefined,
399
- index: number
400
- ): Promise<void> | void {
401
- try {
402
- // Check if feature has been interrupted before processing next stage
403
- if (this.state === A_TYPES__FeatureState.INTERRUPTED) {
404
- return;
405
- }
422
+ private async processRemainingStagesAsync(scope: A_Scope | undefined): Promise<void> {
423
+ for (const stage of this) {
424
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
406
425
 
407
- // If we've processed all stages, complete the feature
408
- if (index >= stages.length) {
409
- this.completed();
410
- return;
426
+ try {
427
+ const result = stage.process(scope)
428
+ if (A_TypeGuards.isPromiseInstance(result)) await result
429
+ } catch (error) {
430
+ throw this.createStageError(error, stage)
411
431
  }
432
+ }
412
433
 
413
- const stage = stages[index];
414
- const result = stage.process(scope);
415
-
416
- if (A_TypeGuards.isPromiseInstance(result)) {
417
- // Async stage - return promise that processes remaining stages
418
- return result
419
- .then(() => {
420
- // Check for interruption after async stage completes
421
- if (this.state === A_TYPES__FeatureState.INTERRUPTED) {
422
- return;
423
- }
424
- return this.processStagesSequentially(stages, scope, index + 1);
425
- })
426
- .catch(error => {
427
- throw this.failed(new A_FeatureError({
428
- title: A_FeatureError.FeatureProcessingError,
429
- description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
430
- stage: stage,
431
- originalError: error
432
- }));
433
- });
434
- } else {
435
- // Sync stage - continue to next stage immediately
436
- return this.processStagesSequentially(stages, scope, index + 1);
437
- }
438
- } catch (error) {
439
- throw this.failed(new A_FeatureError({
440
- title: A_FeatureError.FeatureProcessingError,
441
- description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${this.stage?.name || 'N/A'}.`,
442
- stage: this.stage,
443
- originalError: error
444
- }));
434
+ // ── All stages complete ───────────────────────────────────────────────────
435
+ if (this.state !== A_TYPES__FeatureState.INTERRUPTED) {
436
+ this.completed()
445
437
  }
446
438
  }
439
+
440
+ private createStageError(error: unknown, stage: A_Stage): A_FeatureError {
441
+ this.failed(new A_FeatureError({
442
+ title: A_FeatureError.FeatureProcessingError,
443
+ description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
444
+ stage,
445
+ originalError: error,
446
+ }))
447
+
448
+ return new A_FeatureError({
449
+ title: A_FeatureError.FeatureProcessingError,
450
+ description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
451
+ stage,
452
+ originalError: error,
453
+ })
454
+ }
447
455
  /**
448
456
  * This method moves the feature to the next stage
449
457
  *