@adaas/a-concept 0.3.6 → 0.3.8
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/README.md +116 -78
- package/benchmarks/feature-chaining.bench.ts +465 -0
- package/benchmarks/feature-lifecycle.bench.ts +245 -0
- package/benchmarks/feature-optimize.bench.ts +205 -0
- package/benchmarks/feature-profiling.bench.ts +415 -0
- package/benchmarks/feature-template.bench.ts +211 -0
- package/benchmarks/helpers.ts +97 -0
- package/benchmarks/run-all.ts +58 -0
- package/benchmarks/scope-resolve.bench.ts +245 -0
- package/benchmarks/step-manager.bench.ts +146 -0
- package/dist/browser/index.d.mts +72 -3
- package/dist/browser/index.mjs +2 -2
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/index.cjs +244 -87
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.mts +72 -3
- package/dist/node/index.d.ts +72 -3
- package/dist/node/index.mjs +244 -87
- package/dist/node/index.mjs.map +1 -1
- package/package.json +11 -1
- package/src/lib/A-Context/A-Context.class.ts +155 -60
- package/src/lib/A-Entity/A-Entity.class.ts +3 -3
- package/src/lib/A-Feature/A-Feature.class.ts +46 -41
- package/src/lib/A-Feature/A-Feature.types.ts +6 -10
- package/src/lib/A-Meta/A-Meta.class.ts +18 -3
- package/src/lib/A-Scope/A-Scope.class.ts +98 -5
- package/tests/A-Feature.test.ts +101 -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.8",
|
|
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": {
|
|
@@ -55,6 +55,13 @@
|
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"test": "jest --detectOpenHandles",
|
|
58
|
+
"bench": "ts-node -r tsconfig-paths/register benchmarks/run-all.ts",
|
|
59
|
+
"bench:step-manager": "ts-node -r tsconfig-paths/register benchmarks/step-manager.bench.ts",
|
|
60
|
+
"bench:feature-template": "ts-node -r tsconfig-paths/register benchmarks/feature-template.bench.ts",
|
|
61
|
+
"bench:feature-optimize": "ts-node -r tsconfig-paths/register benchmarks/feature-optimize.bench.ts",
|
|
62
|
+
"bench:feature-chaining": "ts-node -r tsconfig-paths/register benchmarks/feature-chaining.bench.ts",
|
|
63
|
+
"bench:scope-resolve": "ts-node -r tsconfig-paths/register benchmarks/scope-resolve.bench.ts",
|
|
64
|
+
"bench:feature-lifecycle": "ts-node -r tsconfig-paths/register benchmarks/feature-lifecycle.bench.ts",
|
|
58
65
|
"start": "nodemon ./tests/example-usage.ts",
|
|
59
66
|
"example:simple": "nodemon ./examples/simple/concept.ts",
|
|
60
67
|
"example:http": "nodemon ./examples/simple-http-server/concept.ts",
|
|
@@ -67,11 +74,14 @@
|
|
|
67
74
|
"test:simple": "nodemon ./examples/simple/concept.ts"
|
|
68
75
|
},
|
|
69
76
|
"devDependencies": {
|
|
77
|
+
"@types/benchmark": "^2.1.5",
|
|
70
78
|
"@types/chai": "^4.3.14",
|
|
71
79
|
"@types/jest": "^29.5.14",
|
|
72
80
|
"@types/mocha": "^10.0.6",
|
|
73
81
|
"@types/node": "^24.9.1",
|
|
82
|
+
"benchmark": "^2.1.4",
|
|
74
83
|
"chai": "^5.1.0",
|
|
84
|
+
"cli-table3": "^0.6.5",
|
|
75
85
|
"dotenv": "^16.4.5",
|
|
76
86
|
"jest": "^29.7.0",
|
|
77
87
|
"mocha": "^10.4.0",
|
|
@@ -119,6 +119,26 @@ export class A_Context {
|
|
|
119
119
|
*/
|
|
120
120
|
protected _metaStorage: Map<A_TYPES__MetaLinkedComponentConstructors, A_Meta> = new Map();
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Monotonically increasing version counter for _metaStorage.
|
|
124
|
+
* Incremented whenever a new entry is added to _metaStorage so that
|
|
125
|
+
* caches depending on meta content can detect staleness.
|
|
126
|
+
*/
|
|
127
|
+
protected _metaVersion: number = 0;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cache for featureExtensions results.
|
|
131
|
+
* Key format: `${featureName}::${componentConstructorName}::${scopeVersion}::${metaVersion}`
|
|
132
|
+
* Automatically invalidated when scope version or meta version changes.
|
|
133
|
+
*/
|
|
134
|
+
protected _featureExtensionsCache: Map<string, Array<A_TYPES__A_StageStep>> = new Map();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Maximum number of entries in the featureExtensions cache.
|
|
138
|
+
* When exceeded, the entire cache is cleared to prevent unbounded growth.
|
|
139
|
+
*/
|
|
140
|
+
protected static readonly FEATURE_EXTENSIONS_CACHE_MAX_SIZE = 1024;
|
|
141
|
+
|
|
122
142
|
|
|
123
143
|
|
|
124
144
|
|
|
@@ -547,6 +567,7 @@ export class A_Context {
|
|
|
547
567
|
inheritedMeta = new metaType();
|
|
548
568
|
|
|
549
569
|
instance._metaStorage.set(property, inheritedMeta.clone());
|
|
570
|
+
instance._metaVersion++;
|
|
550
571
|
}
|
|
551
572
|
|
|
552
573
|
// Return the meta for the property
|
|
@@ -590,6 +611,7 @@ export class A_Context {
|
|
|
590
611
|
: param1.constructor as A_TYPES__MetaLinkedComponentConstructors;
|
|
591
612
|
|
|
592
613
|
instance._metaStorage.set(constructor, existingMeta ? meta.from(existingMeta) : meta);
|
|
614
|
+
instance._metaVersion++;
|
|
593
615
|
}
|
|
594
616
|
|
|
595
617
|
|
|
@@ -743,16 +765,13 @@ export class A_Context {
|
|
|
743
765
|
*/
|
|
744
766
|
scope: A_Scope = this.scope(component)
|
|
745
767
|
): Array<A_TYPES__A_StageStep> {
|
|
746
|
-
//
|
|
747
|
-
const componentName = A_CommonHelper.getComponentName(component);
|
|
748
|
-
|
|
749
|
-
// Input validation
|
|
768
|
+
// Input validation (defer expensive getComponentName to error paths only)
|
|
750
769
|
if (!component) throw new A_ContextError(A_ContextError.InvalidFeatureTemplateParameterError, `Unable to get feature template. Component cannot be null or undefined.`);
|
|
751
770
|
if (!name) throw new A_ContextError(A_ContextError.InvalidFeatureTemplateParameterError, `Unable to get feature template. Feature name cannot be null or undefined.`);
|
|
752
771
|
|
|
753
772
|
// Check if the parameter is allowed for feature definition
|
|
754
773
|
if (!A_TypeGuards.isAllowedForFeatureDefinition(component))
|
|
755
|
-
throw new A_ContextError(A_ContextError.InvalidFeatureTemplateParameterError, `Unable to get feature template. Component of type ${
|
|
774
|
+
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.`);
|
|
756
775
|
|
|
757
776
|
const steps: A_TYPES__A_StageStep[] = [
|
|
758
777
|
// 1) Get the base feature definition from the component
|
|
@@ -791,76 +810,124 @@ export class A_Context {
|
|
|
791
810
|
): Array<A_TYPES__A_StageStep> {
|
|
792
811
|
|
|
793
812
|
const instance = this.getInstance();
|
|
794
|
-
// name for error messages
|
|
795
|
-
const componentName = A_CommonHelper.getComponentName(component);
|
|
796
813
|
|
|
797
|
-
// Input validation
|
|
814
|
+
// Input validation (defer expensive getComponentName to error paths only)
|
|
798
815
|
if (!component) throw new A_ContextError(A_ContextError.InvalidFeatureExtensionParameterError, `Unable to get feature template. Component cannot be null or undefined.`);
|
|
799
816
|
if (!name) throw new A_ContextError(A_ContextError.InvalidFeatureExtensionParameterError, `Unable to get feature template. Feature name cannot be null or undefined.`);
|
|
800
817
|
|
|
801
818
|
// Check if the parameter is allowed for feature definition
|
|
802
819
|
if (!A_TypeGuards.isAllowedForFeatureDefinition(component))
|
|
803
|
-
throw new A_ContextError(A_ContextError.InvalidFeatureExtensionParameterError, `Unable to get feature template. Component of type ${
|
|
804
|
-
|
|
820
|
+
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
|
+
|
|
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
|
+
|
|
830
|
+
const cached = instance._featureExtensionsCache.get(cacheKey);
|
|
831
|
+
if (cached) {
|
|
832
|
+
return cached;
|
|
833
|
+
}
|
|
805
834
|
|
|
806
835
|
const callNames = A_CommonHelper.getClassInheritanceChain(component)
|
|
807
836
|
.filter(c => c !== A_Component && c !== A_Container && c !== A_Entity)
|
|
808
837
|
.map(c => `${c.name}.${name}`);
|
|
809
838
|
|
|
810
|
-
// const callNames = [`${A_CommonHelper.getComponentName(component)}.${name}`];
|
|
811
|
-
// const callNames = [`BaseComponent.testFeature`];
|
|
812
|
-
|
|
813
839
|
const steps: Map<string, A_TYPES__A_StageStep> = new Map();
|
|
814
840
|
|
|
815
841
|
const allowedComponents: Set<A_TYPES__MetaLinkedComponentConstructors> = new Set();
|
|
816
842
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
843
|
+
// ---- Optimization 2: Pre-cache component names and dependencies ----
|
|
844
|
+
// Avoid repeated getComponentName() and new A_Dependency() for the same constructor
|
|
845
|
+
const componentNameCache = new Map<A_TYPES__MetaLinkedComponentConstructors, string>();
|
|
846
|
+
const dependencyCache = new Map<A_TYPES__MetaLinkedComponentConstructors, A_Dependency>();
|
|
847
|
+
const getNameCached = (cmp: A_TYPES__MetaLinkedComponentConstructors): string => {
|
|
848
|
+
let n = componentNameCache.get(cmp);
|
|
849
|
+
if (n === undefined) {
|
|
850
|
+
n = A_CommonHelper.getComponentName(cmp);
|
|
851
|
+
componentNameCache.set(cmp, n);
|
|
852
|
+
}
|
|
853
|
+
return n;
|
|
854
|
+
};
|
|
855
|
+
const getDependencyCached = (cmp: A_TYPES__MetaLinkedComponentConstructors): A_Dependency => {
|
|
856
|
+
let d = dependencyCache.get(cmp);
|
|
857
|
+
if (!d) {
|
|
858
|
+
d = new A_Dependency(cmp);
|
|
859
|
+
dependencyCache.set(cmp, d);
|
|
860
|
+
}
|
|
861
|
+
return d;
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// ---- Optimization 3: Build a local set of metas that are in scope ----
|
|
865
|
+
// Pre-filter _metaStorage entries to only those present in scope,
|
|
866
|
+
// avoiding repeated scope.has() calls per callName iteration.
|
|
867
|
+
const scopeFilteredMetas: Array<[A_TYPES__MetaLinkedComponentConstructors, A_ComponentMeta | A_ContainerMeta]> = [];
|
|
868
|
+
for (const [cmp, meta] of instance._metaStorage) {
|
|
869
|
+
if (scope.has(cmp) && (
|
|
870
|
+
A_TypeGuards.isComponentMetaInstance(meta)
|
|
871
|
+
||
|
|
872
|
+
A_TypeGuards.isContainerMetaInstance(meta)
|
|
873
|
+
)) {
|
|
874
|
+
scopeFilteredMetas.push([cmp, meta as A_ComponentMeta | A_ContainerMeta]);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
836
877
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
878
|
+
for (const callName of callNames) {
|
|
879
|
+
// Use pre-filtered list instead of iterating all _metaStorage
|
|
880
|
+
for (const [cmp, meta] of scopeFilteredMetas) {
|
|
881
|
+
allowedComponents.add(cmp);
|
|
882
|
+
// Get all extensions for the feature
|
|
883
|
+
const extensions = meta.extensions(callName);
|
|
884
|
+
|
|
885
|
+
for (let i = 0; i < extensions.length; i++) {
|
|
886
|
+
const declaration = extensions[i];
|
|
887
|
+
const inherited = Array.from(allowedComponents).reverse().find(c => A_CommonHelper.isInheritedFrom(cmp, c) && c !== cmp);
|
|
888
|
+
|
|
889
|
+
if (inherited) {
|
|
890
|
+
steps.delete(`${getNameCached(inherited)}.${declaration.handler}`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Check if the declaration has an override regexp and remove matching steps
|
|
894
|
+
// Match against both the full step key (Component.handler) and the handler alone
|
|
895
|
+
if (declaration.override) {
|
|
896
|
+
const overrideRegexp = new RegExp(declaration.override);
|
|
897
|
+
for (const [stepKey, step] of steps) {
|
|
898
|
+
if (overrideRegexp.test(stepKey) || overrideRegexp.test(step.handler)) {
|
|
899
|
+
steps.delete(stepKey);
|
|
846
900
|
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
847
903
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
});
|
|
904
|
+
steps.set(`${getNameCached(cmp)}.${declaration.handler}`, {
|
|
905
|
+
dependency: getDependencyCached(cmp),
|
|
906
|
+
...declaration
|
|
907
|
+
});
|
|
853
908
|
}
|
|
854
909
|
}
|
|
855
910
|
}
|
|
856
911
|
|
|
857
|
-
|
|
912
|
+
const result = instance.filterToMostDerived(scope, Array.from(steps.values()));
|
|
913
|
+
|
|
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
|
+
return result;
|
|
858
921
|
}
|
|
859
922
|
|
|
860
923
|
|
|
861
924
|
/**
|
|
862
925
|
* method helps to filter steps in a way that only the most derived classes are kept.
|
|
863
926
|
*
|
|
927
|
+
* Optimized: Uses a pre-built constructor→class map and single-pass prototype chain
|
|
928
|
+
* walk to eliminate parent classes in O(n·d) where d is inheritance depth,
|
|
929
|
+
* instead of the previous O(n²) with isPrototypeOf checks.
|
|
930
|
+
*
|
|
864
931
|
* @param scope
|
|
865
932
|
* @param items
|
|
866
933
|
* @returns
|
|
@@ -868,24 +935,50 @@ export class A_Context {
|
|
|
868
935
|
private filterToMostDerived(
|
|
869
936
|
scope: A_Scope,
|
|
870
937
|
items: A_TYPES__A_StageStep[]): Array<A_TYPES__A_StageStep> {
|
|
871
|
-
return items.filter(item => {
|
|
872
|
-
// const currentClass = scope.resolveConstructor(item.dependency.name)
|
|
873
|
-
const currentClass = scope.resolveConstructor(item.dependency.name)
|
|
874
938
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
939
|
+
if (items.length <= 1) return items;
|
|
940
|
+
|
|
941
|
+
// 1) Resolve all dependency names to actual constructor classes
|
|
942
|
+
// and build a Set of dependency names present in the items list
|
|
943
|
+
const resolvedClasses = new Map<string, Function | undefined>();
|
|
944
|
+
const presentNames = new Set<string>();
|
|
945
|
+
|
|
946
|
+
for (const item of items) {
|
|
947
|
+
const depName = item.dependency.name;
|
|
948
|
+
if (!resolvedClasses.has(depName)) {
|
|
949
|
+
resolvedClasses.set(depName, scope.resolveConstructor(depName) as Function | undefined);
|
|
950
|
+
}
|
|
951
|
+
presentNames.add(depName);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// 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".
|
|
957
|
+
const parentNames = new Set<string>();
|
|
878
958
|
|
|
879
|
-
|
|
959
|
+
// Build reverse map: constructor → dependency name (for fast lookup when walking prototypes)
|
|
960
|
+
const ctorToName = new Map<Function, string>();
|
|
961
|
+
for (const [depName, ctor] of resolvedClasses) {
|
|
962
|
+
if (ctor) ctorToName.set(ctor, depName);
|
|
963
|
+
}
|
|
880
964
|
|
|
881
|
-
|
|
965
|
+
for (const [depName, ctor] of resolvedClasses) {
|
|
966
|
+
if (!ctor) continue;
|
|
882
967
|
|
|
883
|
-
|
|
884
|
-
|
|
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);
|
|
975
|
+
}
|
|
976
|
+
ancestor = Object.getPrototypeOf(ancestor);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
885
979
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
});
|
|
980
|
+
// 3) Filter: keep only items whose dependency name is NOT in parentNames
|
|
981
|
+
return items.filter(item => !parentNames.has(item.dependency.name));
|
|
889
982
|
}
|
|
890
983
|
|
|
891
984
|
|
|
@@ -1070,6 +1163,8 @@ export class A_Context {
|
|
|
1070
1163
|
const instance = A_Context.getInstance();
|
|
1071
1164
|
|
|
1072
1165
|
instance._registry = new WeakMap();
|
|
1166
|
+
instance._featureExtensionsCache.clear();
|
|
1167
|
+
instance._metaVersion++;
|
|
1073
1168
|
|
|
1074
1169
|
const name = String(A_CONCEPT_ENV.A_CONCEPT_ROOT_SCOPE) || 'root';
|
|
1075
1170
|
|
|
@@ -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
|
|
|
@@ -398,52 +398,57 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
|
|
|
398
398
|
scope: A_Scope | undefined,
|
|
399
399
|
index: number
|
|
400
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
|
-
}
|
|
406
401
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
402
|
+
// ── Sync loop — avoids stack overflow for all-sync chains ────────────────
|
|
403
|
+
while (index < stages.length) {
|
|
404
|
+
|
|
405
|
+
if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
|
|
412
406
|
|
|
413
|
-
const stage = stages[index]
|
|
414
|
-
|
|
407
|
+
const stage = stages[index]
|
|
408
|
+
let result: Promise<void> | void
|
|
415
409
|
|
|
410
|
+
try {
|
|
411
|
+
result = stage.process(scope)
|
|
412
|
+
} catch (error) {
|
|
413
|
+
throw this.createStageError(error, stage)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── Async stage — hand off to promise chain ───────────────────────────
|
|
416
417
|
if (A_TypeGuards.isPromiseInstance(result)) {
|
|
417
|
-
// Async stage - return promise that processes remaining stages
|
|
418
418
|
return result
|
|
419
419
|
.then(() => {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
return this.processStagesSequentially(stages, scope, index + 1);
|
|
420
|
+
if (this.state === A_TYPES__FeatureState.INTERRUPTED) return
|
|
421
|
+
return this.processStagesSequentially(stages, scope, index + 1)
|
|
425
422
|
})
|
|
426
423
|
.catch(error => {
|
|
427
|
-
throw this.
|
|
428
|
-
|
|
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);
|
|
424
|
+
throw this.createStageError(error, stage)
|
|
425
|
+
})
|
|
437
426
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
427
|
+
|
|
428
|
+
index++
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── All stages complete ───────────────────────────────────────────────────
|
|
432
|
+
if (this.state !== A_TYPES__FeatureState.INTERRUPTED) {
|
|
433
|
+
this.completed()
|
|
445
434
|
}
|
|
446
435
|
}
|
|
436
|
+
|
|
437
|
+
private createStageError(error: unknown, stage: A_Stage): A_FeatureError {
|
|
438
|
+
this.failed(new A_FeatureError({
|
|
439
|
+
title: A_FeatureError.FeatureProcessingError,
|
|
440
|
+
description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
|
|
441
|
+
stage,
|
|
442
|
+
originalError: error,
|
|
443
|
+
}))
|
|
444
|
+
|
|
445
|
+
return new A_FeatureError({
|
|
446
|
+
title: A_FeatureError.FeatureProcessingError,
|
|
447
|
+
description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
|
|
448
|
+
stage,
|
|
449
|
+
originalError: error,
|
|
450
|
+
})
|
|
451
|
+
}
|
|
447
452
|
/**
|
|
448
453
|
* This method moves the feature to the next stage
|
|
449
454
|
*
|
|
@@ -159,12 +159,10 @@ export type A_TYPES__FeatureAvailableConstructors = A_TYPES__Component_Construct
|
|
|
159
159
|
// ---------------------------------------------------------------------------
|
|
160
160
|
/**
|
|
161
161
|
* Indicates a type of Feature Define decorator
|
|
162
|
+
*
|
|
163
|
+
* [!] Uses a single generic descriptor to support both sync and async methods
|
|
162
164
|
*/
|
|
163
|
-
export type A_TYPES__FeatureDefineDecoratorDescriptor =
|
|
164
|
-
TypedPropertyDescriptor<(...args: any[]) => any>
|
|
165
|
-
| TypedPropertyDescriptor<(...args: any[]) => any>
|
|
166
|
-
| TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
|
|
167
|
-
| TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
|
|
165
|
+
export type A_TYPES__FeatureDefineDecoratorDescriptor = TypedPropertyDescriptor<(...args: any[]) => any>
|
|
168
166
|
/**
|
|
169
167
|
* Describes additional configuration properties to be used in Feature Define decorator
|
|
170
168
|
*/
|
|
@@ -230,12 +228,10 @@ export type A_TYPES__FeatureDefineDecoratorMeta = {
|
|
|
230
228
|
// ---------------------------------------------------------------------------
|
|
231
229
|
/**
|
|
232
230
|
* Descriptor type for A_Extend decorator
|
|
231
|
+
*
|
|
232
|
+
* [!] Uses a single generic descriptor to support both sync and async methods
|
|
233
233
|
*/
|
|
234
|
-
export type A_TYPES__FeatureExtendDecoratorDescriptor =
|
|
235
|
-
TypedPropertyDescriptor<() => any>
|
|
236
|
-
| TypedPropertyDescriptor<(...args: any[]) => any>
|
|
237
|
-
| TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
|
|
238
|
-
| TypedPropertyDescriptor<() => Promise<any>>
|
|
234
|
+
export type A_TYPES__FeatureExtendDecoratorDescriptor = TypedPropertyDescriptor<(...args: any[]) => any>
|
|
239
235
|
/**
|
|
240
236
|
* Target type for A_Extend decorator
|
|
241
237
|
*
|
|
@@ -130,10 +130,25 @@ export class A_Meta<
|
|
|
130
130
|
* @param key
|
|
131
131
|
* @returns
|
|
132
132
|
*/
|
|
133
|
+
/**
|
|
134
|
+
* Cache for compiled RegExp instances keyed by their string source.
|
|
135
|
+
* Avoids re-compiling the same regex pattern on every find() call.
|
|
136
|
+
*/
|
|
137
|
+
private _regExpCache?: Map<string, RegExp>;
|
|
138
|
+
|
|
133
139
|
private convertToRegExp(key: string | RegExp): RegExp {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
if (key instanceof RegExp) return key;
|
|
141
|
+
|
|
142
|
+
// Use cache to avoid re-compiling the same regex pattern repeatedly
|
|
143
|
+
if (!this._regExpCache) {
|
|
144
|
+
this._regExpCache = new Map();
|
|
145
|
+
}
|
|
146
|
+
let cached = this._regExpCache.get(key);
|
|
147
|
+
if (!cached) {
|
|
148
|
+
cached = new RegExp(key);
|
|
149
|
+
this._regExpCache.set(key, cached);
|
|
150
|
+
}
|
|
151
|
+
return cached;
|
|
137
152
|
}
|
|
138
153
|
/**
|
|
139
154
|
* Method to find values in the map by name.
|