@adaas/a-concept 0.3.8 → 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.
@@ -20,6 +20,51 @@ import { A_Caller } from "@adaas/a-concept/a-caller";
20
20
  import { A_Entity } from "@adaas/a-concept/a-entity";
21
21
  import { A_Context } from "@adaas/a-concept/a-context";
22
22
  import { createSuite, BenchResult, printSummary } from './helpers';
23
+ import Table from 'cli-table3';
24
+
25
+ // ──────────────────────────────────────────────────────────────
26
+ // Heap measurement helpers
27
+ // ──────────────────────────────────────────────────────────────
28
+
29
+ interface HeapSnapshot {
30
+ benchName: string;
31
+ heapBefore: number; // bytes
32
+ heapAfter: number; // bytes
33
+ heapDelta: number; // bytes
34
+ }
35
+
36
+ function takeHeapSnapshot(): number {
37
+ try { global.gc!(); } catch (_) { /* --expose-gc not set */ }
38
+ return process.memoryUsage().heapUsed;
39
+ }
40
+
41
+ function formatBytes(bytes: number): string {
42
+ const mb = bytes / (1024 * 1024);
43
+ return `${mb >= 0 ? '+' : ''}${mb.toFixed(2)} MB`;
44
+ }
45
+
46
+ function printHeapTable(title: string, snapshots: HeapSnapshot[]) {
47
+ console.log(`\n${'─'.repeat(70)}`);
48
+ console.log(` 🧠 ${title} — Heap Usage`);
49
+ console.log(`${'─'.repeat(70)}`);
50
+
51
+ const table = new Table({
52
+ head: ['Benchmark', 'Heap Before', 'Heap After', 'Δ Heap'],
53
+ colWidths: [40, 14, 14, 14],
54
+ style: { head: ['yellow'] },
55
+ });
56
+
57
+ for (const s of snapshots) {
58
+ table.push([
59
+ s.benchName,
60
+ (s.heapBefore / 1024 / 1024).toFixed(2) + ' MB',
61
+ (s.heapAfter / 1024 / 1024).toFixed(2) + ' MB',
62
+ formatBytes(s.heapDelta),
63
+ ]);
64
+ }
65
+
66
+ console.log(table.toString());
67
+ }
23
68
 
24
69
 
25
70
 
@@ -93,6 +138,7 @@ export class ChainingComponent extends A_Component {
93
138
 
94
139
  export async function runFeatureChainingBenchmarks(): Promise<any> {
95
140
  const allResults: BenchResult[] = [];
141
+ const heapSnapshots: HeapSnapshot[] = [];
96
142
 
97
143
 
98
144
 
@@ -111,81 +157,110 @@ export async function runFeatureChainingBenchmarks(): Promise<any> {
111
157
 
112
158
  entity.scope.inherit(childScope);
113
159
 
114
-
115
-
160
+ // Heap tracking state — shared across benchmarks in this suite
161
+ let currentHeapBefore = 0;
116
162
 
117
163
  suite
118
- .add('new Feature instantiation (A_Context.featureTemplate)', () => {
119
- const definition = A_Context.featureTemplate('entityFeature', entity);
120
-
121
- })
122
- .add('new Feature instantiation (new A_Feature)', () => {
123
- const feature = new A_Feature({
124
- name: 'entityFeature',
125
- component: entity,
126
- scope: entity.scope
164
+ .on('cycle', (event: any) => {
165
+ const heapAfter = takeHeapSnapshot();
166
+ heapSnapshots.push({
167
+ benchName: event.target.name,
168
+ heapBefore: currentHeapBefore,
169
+ heapAfter,
170
+ heapDelta: heapAfter - currentHeapBefore,
127
171
  });
128
172
  })
129
- .add('new Feature execution (direct)', () => {
130
- const feature = new A_Feature({
131
- name: 'entityFeature',
132
- component: entity,
133
- scope: entity.scope
134
- });
135
173
 
136
- for (const stage of feature) {
137
- stage.process(entity.scope)
174
+ suite
175
+ .add('new Feature instantiation (A_Context.featureTemplate)', {
176
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
177
+ fn: () => {
178
+ const definition = A_Context.featureTemplate('entityFeature', entity);
138
179
  }
139
180
  })
140
- .add('new Feature execution (direct entity.call)', () => {
141
-
142
- const res = entity.call('entityFeature', entity.scope);
143
-
144
- if (res instanceof Promise)
145
- throw new Error('Expected synchronous execution for baseline feature call');
181
+ .add('new Feature instantiation (new A_Feature)', {
182
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
183
+ fn: () => {
184
+ const feature = new A_Feature({
185
+ name: 'entityFeature',
186
+ component: entity,
187
+ scope: entity.scope
188
+ });
189
+ }
146
190
  })
147
- .add('new Feature execution (wrapped entity.call)', () => {
148
-
149
- const res = entity.entityFeature();
150
-
151
- if (res instanceof Promise)
152
- throw new Error('Expected synchronous execution for baseline feature call');
153
-
191
+ .add('new Feature execution (direct)', {
192
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
193
+ fn: () => {
194
+ const feature = new A_Feature({
195
+ name: 'entityFeature',
196
+ component: entity,
197
+ scope: entity.scope
198
+ });
199
+
200
+ for (const stage of feature) {
201
+ stage.process(entity.scope)
202
+ }
203
+ }
154
204
  })
155
- .add('[duplicated 1] new Feature execution (wrapped entity.call)', () => {
156
-
157
- const res = entity.entityFeature();
158
-
159
- if (res instanceof Promise)
160
- throw new Error('Expected synchronous execution for baseline feature call');
205
+ .add('new Feature execution (direct entity.call)', {
206
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
207
+ fn: () => {
208
+ const res = entity.call('entityFeature', entity.scope);
161
209
 
210
+ if (res instanceof Promise)
211
+ throw new Error('Expected synchronous execution for baseline feature call');
212
+ }
162
213
  })
163
- .add('[duplicated 2] new Feature execution (wrapped entity.call)', () => {
164
-
165
- const res = entity.entityFeature();
166
-
167
- if (res instanceof Promise)
168
- throw new Error('Expected synchronous execution for baseline feature call');
214
+ .add('new Feature execution (wrapped entity.call)', {
215
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
216
+ fn: () => {
217
+ const res = entity.entityFeature();
169
218
 
219
+ if (res instanceof Promise)
220
+ throw new Error('Expected synchronous execution for baseline feature call');
221
+ }
170
222
  })
171
- .add('[duplicated 3] new Feature execution (wrapped entity.call)', () => {
172
-
173
- const res = entity.entityFeature();
174
- if (res instanceof Promise)
175
- throw new Error('Expected synchronous execution for baseline feature call');
223
+ .add('[duplicated 1] new Feature execution (wrapped entity.call)', {
224
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
225
+ fn: () => {
226
+ const res = entity.entityFeature();
176
227
 
228
+ if (res instanceof Promise)
229
+ throw new Error('Expected synchronous execution for baseline feature call');
230
+ }
177
231
  })
178
- .add('[duplicated 4] new Feature execution (wrapped entity.call)', () => {
179
-
180
- const res = entity.entityFeature();
232
+ .add('[duplicated 2] new Feature execution (wrapped entity.call)', {
233
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
234
+ fn: () => {
235
+ const res = entity.entityFeature();
181
236
 
182
- if (res instanceof Promise)
183
- throw new Error('Expected synchronous execution for baseline feature call');
237
+ if (res instanceof Promise)
238
+ throw new Error('Expected synchronous execution for baseline feature call');
239
+ }
240
+ })
241
+ .add('[duplicated 3] new Feature execution (wrapped entity.call)', {
242
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
243
+ fn: () => {
244
+ const res = entity.entityFeature();
245
+ if (res instanceof Promise)
246
+ throw new Error('Expected synchronous execution for baseline feature call');
247
+ }
248
+ })
249
+ .add('[duplicated 4] new Feature execution (wrapped entity.call)', {
250
+ onStart: () => { currentHeapBefore = takeHeapSnapshot(); },
251
+ fn: () => {
252
+ const res = entity.entityFeature();
184
253
 
254
+ if (res instanceof Promise)
255
+ throw new Error('Expected synchronous execution for baseline feature call');
256
+ }
185
257
  })
186
258
  });
187
259
  allResults.push(...baselineResults);
188
260
 
261
+ // Print heap summary
262
+ printHeapTable('Feature Call — Baseline (no chain)', heapSnapshots);
263
+
189
264
  return allResults;
190
265
  }
191
266
 
@@ -2010,9 +2010,10 @@ declare class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES_
2010
2010
  */
2011
2011
  scope?: A_Scope): Promise<void> | void;
2012
2012
  /**
2013
- * Process stages one by one, ensuring each stage completes before starting the next
2013
+ * Async continuation processes remaining stages after the first async pivot.
2014
+ * Resumes from the current iterator position (this._index).
2014
2015
  */
2015
- private processStagesSequentially;
2016
+ private processRemainingStagesAsync;
2016
2017
  private createStageError;
2017
2018
  /**
2018
2019
  * This method moves the feature to the next stage
@@ -2988,15 +2989,6 @@ type A_TYPES_ScopeDependentComponents = A_Component | A_Entity | A_Fragment | A_
2988
2989
  type A_TYPES_ScopeIndependentComponents = A_Error | A_Scope | A_Caller;
2989
2990
 
2990
2991
  declare class A_Scope<_MetaItems extends Record<string, any> = any, _ComponentType extends A_TYPES__Component_Constructor[] = A_TYPES__Component_Constructor[], _ErrorType extends A_TYPES__Error_Constructor[] = A_TYPES__Error_Constructor[], _EntityType extends A_TYPES__Entity_Constructor[] = A_TYPES__Entity_Constructor[], _FragmentType extends A_Fragment[] = A_Fragment[]> {
2991
- /**
2992
- * Auto-incrementing counter for generating unique scope IDs.
2993
- */
2994
- private static _nextUid;
2995
- /**
2996
- * Unique numeric ID for this scope instance. Used as a cache key discriminator
2997
- * to prevent collisions between scopes with the same name or version.
2998
- */
2999
- readonly uid: number;
3000
2992
  /**
3001
2993
  * Scope Name uses for identification and logging purposes
3002
2994
  */
@@ -3025,6 +3017,11 @@ declare class A_Scope<_MetaItems extends Record<string, any> = any, _ComponentTy
3025
3017
  * Invalidated by incrementing _version (cache is cleared on bump).
3026
3018
  */
3027
3019
  protected _resolveConstructorCache: Map<string | Function, Function | null>;
3020
+ /**
3021
+ * Cached fingerprint string. Invalidated on every bumpVersion() call.
3022
+ */
3023
+ private _cachedFingerprint;
3024
+ private _cachedFingerprintVersion;
3028
3025
  /**
3029
3026
  * A set of allowed components, A set of constructors that are allowed in the scope
3030
3027
  *
@@ -3091,6 +3088,12 @@ declare class A_Scope<_MetaItems extends Record<string, any> = any, _ComponentTy
3091
3088
  * allowing external caches to detect staleness via numeric comparison.
3092
3089
  */
3093
3090
  get version(): number;
3091
+ /**
3092
+ * Returns a content-addressable fingerprint of the scope.
3093
+ * Two scopes with identical content (components, entities, fragments, errors, imports, parent)
3094
+ * will produce the same fingerprint. Dynamically recomputed when scope content changes.
3095
+ */
3096
+ get fingerprint(): string;
3094
3097
  /**
3095
3098
  * Returns an Array of entities registered in the scope
3096
3099
  *
@@ -3132,6 +3135,16 @@ declare class A_Scope<_MetaItems extends Record<string, any> = any, _ComponentTy
3132
3135
  * Must be called on every scope mutation (register, deregister, import, deimport, inherit, destroy).
3133
3136
  */
3134
3137
  protected bumpVersion(): void;
3138
+ /**
3139
+ * Computes the aggregate version of this scope and all reachable scopes (parent + imports).
3140
+ * Used to detect when any transitive dependency has changed, so the fingerprint cache can be invalidated.
3141
+ */
3142
+ private aggregateVersion;
3143
+ /**
3144
+ * Computes a deterministic content-addressable fingerprint string.
3145
+ * Includes components, entities, fragments, errors, parent, and imports.
3146
+ */
3147
+ private computeFingerprint;
3135
3148
  /**
3136
3149
  * 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.
3137
3150
  * It refers to the visibility and accessibility of :
@@ -4210,12 +4223,22 @@ declare class A_Context {
4210
4223
  * Key format: `${featureName}::${componentConstructorName}::${scopeVersion}::${metaVersion}`
4211
4224
  * Automatically invalidated when scope version or meta version changes.
4212
4225
  */
4213
- protected _featureExtensionsCache: Map<string, Array<A_TYPES__A_StageStep>>;
4226
+ protected _featureCache: Map<string, Array<A_TYPES__A_StageStep>>;
4214
4227
  /**
4215
4228
  * Maximum number of entries in the featureExtensions cache.
4216
4229
  * When exceeded, the entire cache is cleared to prevent unbounded growth.
4217
4230
  */
4218
4231
  protected static readonly FEATURE_EXTENSIONS_CACHE_MAX_SIZE = 1024;
4232
+ /**
4233
+ * For each indexed constructor, stores the set of all its ancestor constructors
4234
+ * (walking up the prototype chain). Enables O(1) isInheritedFrom checks.
4235
+ */
4236
+ protected _ancestors: Map<Function, Set<Function>>;
4237
+ /**
4238
+ * For each constructor, stores the set of all known descendant constructors.
4239
+ * Enables O(1) "find a descendant in a Set" lookups.
4240
+ */
4241
+ protected _descendants: Map<Function, Set<Function>>;
4219
4242
  protected _globals: Map<string, any>;
4220
4243
  /**
4221
4244
  * Private constructor to enforce singleton pattern.
@@ -4531,6 +4554,33 @@ declare class A_Context {
4531
4554
  * Resets the Context to its initial state.
4532
4555
  */
4533
4556
  static reset(): void;
4557
+ /**
4558
+ * Index a constructor's full prototype chain into the inheritance graph.
4559
+ * Safe to call multiple times for the same constructor — it's a no-op if already indexed.
4560
+ *
4561
+ * After indexing, `A_Context.isIndexedInheritedFrom(child, parent)` becomes O(1).
4562
+ */
4563
+ static indexConstructor(ctor: Function): void;
4564
+ /**
4565
+ * O(1) check whether `child` inherits from `parent` using the pre-built index.
4566
+ * Falls back to prototype chain walking if either is not yet indexed.
4567
+ *
4568
+ * [!] Handles the same-class case: returns true if child === parent.
4569
+ */
4570
+ static isIndexedInheritedFrom(child: Function, parent: Function): boolean;
4571
+ /**
4572
+ * Find the first constructor in `candidates` that is a descendant of (or equal to) `parent`.
4573
+ * Returns undefined if none found.
4574
+ *
4575
+ * Uses the optimal strategy based on set sizes:
4576
+ * - If candidates is small, iterates candidates and checks ancestry (O(c))
4577
+ * - If descendants is small, iterates descendants and checks membership (O(d))
4578
+ */
4579
+ static findDescendantIn<T extends Function>(parent: Function, candidates: Set<T> | Array<T>): T | undefined;
4580
+ /**
4581
+ * Returns the set of indexed ancestors for a constructor, or undefined if not indexed.
4582
+ */
4583
+ static getAncestors(ctor: Function): Set<Function> | undefined;
4534
4584
  /**
4535
4585
  * Type guard to check if the param is allowed for scope allocation.
4536
4586
  *