@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/benchmarks/feature-chaining.bench.ts +465 -0
- package/benchmarks/feature-optimize.bench.ts +280 -0
- package/dist/browser/index.d.mts +64 -13
- package/dist/browser/index.mjs +2 -2
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/index.cjs +329 -129
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.mts +64 -13
- package/dist/node/index.d.ts +64 -13
- package/dist/node/index.mjs +329 -129
- package/dist/node/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/lib/A-Context/A-Context.class.ts +214 -41
- package/src/lib/A-Entity/A-Entity.class.ts +3 -3
- package/src/lib/A-Feature/A-Feature.class.ts +64 -56
- package/src/lib/A-Scope/A-Scope.class.ts +143 -72
- package/tests/A-Feature.test.ts +101 -0
- package/tests/A-Meta.test.ts +46 -2
- package/tests/A-Scope.test.ts +188 -0
- package/tsconfig.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaas/a-concept",
|
|
3
|
-
"version": "0.3.
|
|
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
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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 =>
|
|
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.
|
|
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
|
-
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
379
|
-
|
|
379
|
+
for (const stage of this) {
|
|
380
|
+
if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
|
|
380
381
|
|
|
381
|
-
|
|
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
|
-
*
|
|
419
|
+
* Async continuation — processes remaining stages after the first async pivot.
|
|
420
|
+
* Resumes from the current iterator position (this._index).
|
|
395
421
|
*/
|
|
396
|
-
private
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
414
|
-
|
|
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
|
*
|