@adaas/a-concept 0.3.6 → 0.3.7

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.6",
3
+ "version": "0.3.7",
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,11 @@
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:scope-resolve": "ts-node -r tsconfig-paths/register benchmarks/scope-resolve.bench.ts",
62
+ "bench:feature-lifecycle": "ts-node -r tsconfig-paths/register benchmarks/feature-lifecycle.bench.ts",
58
63
  "start": "nodemon ./tests/example-usage.ts",
59
64
  "example:simple": "nodemon ./examples/simple/concept.ts",
60
65
  "example:http": "nodemon ./examples/simple-http-server/concept.ts",
@@ -67,11 +72,14 @@
67
72
  "test:simple": "nodemon ./examples/simple/concept.ts"
68
73
  },
69
74
  "devDependencies": {
75
+ "@types/benchmark": "^2.1.5",
70
76
  "@types/chai": "^4.3.14",
71
77
  "@types/jest": "^29.5.14",
72
78
  "@types/mocha": "^10.0.6",
73
79
  "@types/node": "^24.9.1",
80
+ "benchmark": "^2.1.4",
74
81
  "chai": "^5.1.0",
82
+ "cli-table3": "^0.6.5",
75
83
  "dotenv": "^16.4.5",
76
84
  "jest": "^29.7.0",
77
85
  "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
- // name for error messages
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 ${componentName} is not allowed for feature definition.`);
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 ${componentName} is not allowed for feature definition.`);
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
- for (const callName of callNames) {
818
- // We need to get all components that has extensions for the feature in component
819
- for (const [cmp, meta] of instance._metaStorage) {
820
- // Just try to make sure that component not only Indexed but also presented in scope
821
- if (scope.has(cmp) && (
822
- A_TypeGuards.isComponentMetaInstance(meta)
823
- ||
824
- A_TypeGuards.isContainerMetaInstance(meta)
825
- )) {
826
- allowedComponents.add(cmp);
827
- // Get all extensions for the feature
828
- meta
829
- .extensions(callName)
830
- .forEach((declaration) => {
831
- const inherited = Array.from(allowedComponents).reverse().find(c => A_CommonHelper.isInheritedFrom(cmp, c) && c !== cmp);
832
-
833
- if (inherited) {
834
- steps.delete(`${A_CommonHelper.getComponentName(inherited)}.${declaration.handler}`);
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
- // 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
- }
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
- steps.set(`${A_CommonHelper.getComponentName(cmp)}.${declaration.handler}`, {
849
- dependency: new A_Dependency(cmp),
850
- ...declaration
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
- return instance.filterToMostDerived(scope, Array.from(steps.values()));
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
- // Check if this class is parent of any other in the list
876
- const isParentOfAnother = items.some(other => {
877
- if (other === item) return false;
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
- const otherClass = scope.resolveConstructor(other.dependency.name);
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
- if (!currentClass || !otherClass) return false;
965
+ for (const [depName, ctor] of resolvedClasses) {
966
+ if (!ctor) continue;
882
967
 
883
- return currentClass.prototype.isPrototypeOf(otherClass.prototype);
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
- // Keep only classes that are not parent of any other
887
- return !isParentOfAnother;
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
 
@@ -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
- return key instanceof RegExp
135
- ? key
136
- : new RegExp(key);
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.
@@ -46,6 +46,17 @@ export class A_Scope<
46
46
  _FragmentType extends A_Fragment[] = A_Fragment[],
47
47
  > {
48
48
 
49
+ /**
50
+ * Auto-incrementing counter for generating unique scope IDs.
51
+ */
52
+ private static _nextUid: number = 0;
53
+
54
+ /**
55
+ * Unique numeric ID for this scope instance. Used as a cache key discriminator
56
+ * to prevent collisions between scopes with the same name or version.
57
+ */
58
+ readonly uid: number = A_Scope._nextUid++;
59
+
49
60
  /**
50
61
  * Scope Name uses for identification and logging purposes
51
62
  */
@@ -61,6 +72,25 @@ export class A_Scope<
61
72
  */
62
73
  protected _meta: A_Meta<_MetaItems> = new A_Meta<_MetaItems>();
63
74
 
75
+ // ===========================================================================
76
+ // --------------------Cache & Versioning--------------------------------------
77
+ // ===========================================================================
78
+ /**
79
+ * Monotonically increasing version counter. Incremented on every mutation
80
+ * (register, deregister, import, deimport, inherit, destroy) so that
81
+ * external caches (e.g. A_Context feature-extension cache) can detect
82
+ * staleness cheaply via numeric comparison.
83
+ */
84
+ protected _version: number = 0;
85
+
86
+ /**
87
+ * Cache for resolveConstructor results (both positive and negative).
88
+ * Key = constructor name (string) or constructor reference toString.
89
+ * Value = resolved constructor or `null` for negative results.
90
+ * Invalidated by incrementing _version (cache is cleared on bump).
91
+ */
92
+ protected _resolveConstructorCache: Map<string | Function, Function | null> = new Map();
93
+
64
94
  // ===========================================================================
65
95
  // --------------------ALLowed Constructors--------------------------------
66
96
  // ===========================================================================
@@ -137,6 +167,11 @@ export class A_Scope<
137
167
  * Returns a list of Constructors for A-Errors that are available in the scope
138
168
  */
139
169
  get allowedErrors() { return this._allowedErrors }
170
+ /**
171
+ * Returns the current version of the scope. Each mutation increments the version,
172
+ * allowing external caches to detect staleness via numeric comparison.
173
+ */
174
+ get version(): number { return this._version }
140
175
  // ===========================================================================
141
176
  // --------------------Readonly Registered Properties--------------------------
142
177
  // ===========================================================================
@@ -180,6 +215,15 @@ export class A_Scope<
180
215
  return this._parent;
181
216
  }
182
217
 
218
+ /**
219
+ * Increments the scope version and clears internal caches.
220
+ * Must be called on every scope mutation (register, deregister, import, deimport, inherit, destroy).
221
+ */
222
+ protected bumpVersion(): void {
223
+ this._version++;
224
+ this._resolveConstructorCache.clear();
225
+ }
226
+
183
227
  /**
184
228
  * A_Scope is a unique A-Concept Structure that allows to operate with A-Concept Primitives and Models in a specific context and with specific rules.
185
229
  * It refers to the visibility and accessibility of :
@@ -379,6 +423,8 @@ export class A_Scope<
379
423
  if (this.issuer()) {
380
424
  A_Context.deallocate(this);
381
425
  }
426
+
427
+ this.bumpVersion();
382
428
  }
383
429
 
384
430
 
@@ -469,6 +515,7 @@ export class A_Scope<
469
515
 
470
516
 
471
517
  this._parent = parent;
518
+ this.bumpVersion();
472
519
  return this;
473
520
  }
474
521
 
@@ -495,6 +542,7 @@ export class A_Scope<
495
542
  return;
496
543
 
497
544
  this._imports.add(scope);
545
+ this.bumpVersion();
498
546
  });
499
547
 
500
548
  return this;
@@ -510,8 +558,10 @@ export class A_Scope<
510
558
  deimport(...scopes: A_Scope[]): A_Scope {
511
559
 
512
560
  scopes.forEach(scope => {
513
- if (this._imports.has(scope))
561
+ if (this._imports.has(scope)) {
514
562
  this._imports.delete(scope);
563
+ this.bumpVersion();
564
+ }
515
565
  });
516
566
  return this;
517
567
  }
@@ -894,6 +944,29 @@ export class A_Scope<
894
944
  `Invalid constructor name provided: ${name}`
895
945
  );
896
946
 
947
+ // ---- Optimization: check resolveConstructor cache for string lookups ----
948
+ const cacheKey = name;
949
+ if (this._resolveConstructorCache.has(cacheKey)) {
950
+ const cached = this._resolveConstructorCache.get(cacheKey);
951
+ return (cached === null ? undefined : cached) as any;
952
+ }
953
+
954
+ const resolved = this._resolveConstructorUncached<T>(name);
955
+
956
+ // Store in cache (null for negative/miss results)
957
+ this._resolveConstructorCache.set(cacheKey, resolved ?? null);
958
+
959
+ return resolved;
960
+ }
961
+
962
+ /**
963
+ * Internal uncached implementation of resolveConstructor for string names.
964
+ * Separated to allow the public method to wrap with caching.
965
+ */
966
+ private _resolveConstructorUncached<T extends A_TYPES__A_DependencyInjectable>(
967
+ name: string
968
+ ): A_TYPES__Entity_Constructor<T> | A_TYPES__Component_Constructor<T> | A_TYPES__Fragment_Constructor<T> | undefined {
969
+
897
970
  // 1) Check components
898
971
  const component = Array.from(this.allowedComponents).find(
899
972
  c => c.name === name
@@ -1715,6 +1788,7 @@ export class A_Scope<
1715
1788
  );
1716
1789
 
1717
1790
  A_Context.register(this, param1);
1791
+ this.bumpVersion();
1718
1792
 
1719
1793
  break;
1720
1794
  }
@@ -1726,6 +1800,7 @@ export class A_Scope<
1726
1800
 
1727
1801
  this._entities.set(param1.aseid.toString(), param1 as InstanceType<_EntityType[number]>);
1728
1802
  A_Context.register(this, param1);
1803
+ this.bumpVersion();
1729
1804
  break;
1730
1805
  }
1731
1806
  // 4) In case when it's a A-Fragment instance
@@ -1740,6 +1815,7 @@ export class A_Scope<
1740
1815
  );
1741
1816
 
1742
1817
  A_Context.register(this, param1);
1818
+ this.bumpVersion();
1743
1819
 
1744
1820
  break;
1745
1821
  }
@@ -1754,6 +1830,7 @@ export class A_Scope<
1754
1830
  );
1755
1831
 
1756
1832
  A_Context.register(this, (param1 as any));
1833
+ this.bumpVersion();
1757
1834
  break;
1758
1835
  }
1759
1836
 
@@ -1762,26 +1839,34 @@ export class A_Scope<
1762
1839
  // ------------------------------------------
1763
1840
  // 6) In case when it's a A-Component constructor
1764
1841
  case A_TypeGuards.isComponentConstructor(param1): {
1765
- if (!this.allowedComponents.has(param1))
1842
+ if (!this.allowedComponents.has(param1)) {
1766
1843
  this.allowedComponents.add(param1 as _ComponentType[number]);
1844
+ this.bumpVersion();
1845
+ }
1767
1846
  break;
1768
1847
  }
1769
1848
  // 8) In case when it's a A-Fragment constructor
1770
1849
  case A_TypeGuards.isFragmentConstructor(param1): {
1771
- if (!this.allowedFragments.has(param1))
1850
+ if (!this.allowedFragments.has(param1)) {
1772
1851
  this.allowedFragments.add(param1 as A_TYPES__Fragment_Constructor<_FragmentType[number]>);
1852
+ this.bumpVersion();
1853
+ }
1773
1854
  break;
1774
1855
  }
1775
1856
  // 9) In case when it's a A-Entity constructor
1776
1857
  case A_TypeGuards.isEntityConstructor(param1): {
1777
- if (!this.allowedEntities.has(param1))
1858
+ if (!this.allowedEntities.has(param1)) {
1778
1859
  this.allowedEntities.add(param1 as _EntityType[number]);
1860
+ this.bumpVersion();
1861
+ }
1779
1862
  break;
1780
1863
  }
1781
1864
  // 10) In case when it's a A-Error constructor
1782
1865
  case A_TypeGuards.isErrorConstructor(param1): {
1783
- if (!this.allowedErrors.has(param1))
1866
+ if (!this.allowedErrors.has(param1)) {
1784
1867
  this.allowedErrors.add(param1 as _ErrorType[number]);
1868
+ this.bumpVersion();
1869
+ }
1785
1870
  break;
1786
1871
  }
1787
1872
 
@@ -1891,6 +1976,7 @@ export class A_Scope<
1891
1976
  this.allowedComponents.delete(ctor);
1892
1977
  }
1893
1978
 
1979
+ this.bumpVersion();
1894
1980
  break;
1895
1981
  }
1896
1982
  // 3) In case when it's a A-Entity instance
@@ -1906,6 +1992,7 @@ export class A_Scope<
1906
1992
  this.allowedEntities.delete(ctor);
1907
1993
  }
1908
1994
 
1995
+ this.bumpVersion();
1909
1996
  break;
1910
1997
  }
1911
1998
  // 4) In case when it's a A-Fragment instance
@@ -1920,6 +2007,7 @@ export class A_Scope<
1920
2007
  this.allowedFragments.delete(ctor);
1921
2008
  }
1922
2009
 
2010
+ this.bumpVersion();
1923
2011
  break;
1924
2012
  }
1925
2013
  // 5) In case when it's a A-Error instance
@@ -1935,6 +2023,7 @@ export class A_Scope<
1935
2023
  this.allowedErrors.delete(ctor);
1936
2024
  }
1937
2025
 
2026
+ this.bumpVersion();
1938
2027
  break;
1939
2028
  }
1940
2029
 
@@ -1944,6 +2033,7 @@ export class A_Scope<
1944
2033
  // 6) In case when it's a A-Component constructor
1945
2034
  case A_TypeGuards.isComponentConstructor(param1): {
1946
2035
  this.allowedComponents.delete(param1 as _ComponentType[number]);
2036
+ this.bumpVersion();
1947
2037
  break;
1948
2038
  }
1949
2039
  // 8) In case when it's a A-Fragment constructor
@@ -1957,6 +2047,7 @@ export class A_Scope<
1957
2047
  }
1958
2048
  });
1959
2049
 
2050
+ this.bumpVersion();
1960
2051
  break;
1961
2052
  }
1962
2053
  // 9) In case when it's a A-Entity constructor
@@ -1970,6 +2061,7 @@ export class A_Scope<
1970
2061
  }
1971
2062
  });
1972
2063
 
2064
+ this.bumpVersion();
1973
2065
  break;
1974
2066
  }
1975
2067
  // 10) In case when it's a A-Error constructor
@@ -1983,6 +2075,7 @@ export class A_Scope<
1983
2075
  }
1984
2076
  });
1985
2077
 
2078
+ this.bumpVersion();
1986
2079
  break;
1987
2080
  }
1988
2081