@adaas/a-concept 0.0.63 → 0.0.64

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.
@@ -1,112 +1,218 @@
1
- import { A_CommonHelper, ASEID } from "@adaas/a-utils";
2
- import { A_TYPES__ScopeConfig, A_TYPES__ScopeConstructor } from "./A-Scope.types";
3
- import { A_Fragment } from "../A-Fragment/A-Fragment.class";
4
- import { A_Context } from "../A-Context/A-Context.class";
5
1
  import {
6
- A_TYPES__ComponentMetaKey
7
- } from "../A-Component/A-Component.types";
8
- import { A_Component } from "../A-Component/A-Component.class";
9
- import { A_Entity } from "../A-Entity/A-Entity.class";
2
+ A_CommonHelper,
3
+ A_Error,
4
+ ASEID
5
+ } from "@adaas/a-utils";
6
+ import {
7
+ A_TYPES__AllowedCommandsConstructor,
8
+ A_TYPES__AllowedComponentsConstructor,
9
+ A_TYPES__AllowedEntitiesConstructor,
10
+ A_TYPES__AllowedFragmentsConstructor,
11
+ A_TYPES__AllowedScopesConstructor,
12
+ A_TYPES__ScopeConfig,
13
+ A_TYPES__ScopeConstructor
14
+ } from './A-Scope.types'
10
15
  import {
11
16
  A_TYPES__A_InjectDecorator_EntityInjectionInstructions,
12
17
  A_TYPES__A_InjectDecorator_EntityInjectionQuery,
13
- A_TYPES__A_InjectDecorator_Injectable
14
18
  } from "@adaas/a-concept/decorators/A-Inject/A-Inject.decorator.types";
19
+ import { A_TYPES__ComponentMetaKey } from "../A-Component/A-Component.types";
20
+ import { A_Fragment } from "../A-Fragment/A-Fragment.class";
21
+ import { A_Context } from "../A-Context/A-Context.class";
22
+ import { A_Component } from "../A-Component/A-Component.class";
23
+ import { A_Entity } from "../A-Entity/A-Entity.class";
15
24
  import { A_Command } from "../A-Command/A-Command.class";
16
25
 
17
26
 
18
- /**
19
- *
20
- *
21
- * A_Scope refers to the visibility and accessibility of :
22
- * - variables,
23
- * - Components,
24
- * - Context Fragments
25
- * - and objects in different parts of your code.
26
- * Scope determines where a particular piece of data (like a variable or function)
27
- * can be accessed, modified, or referenced, and it plays a crucial role in avoiding naming collisions and ensuring data integrity.
28
- *
29
- *
30
- */
31
- export class A_Scope {
32
27
 
28
+
29
+ export class A_Scope<
30
+ _ComponentType extends A_TYPES__AllowedComponentsConstructor[] = A_TYPES__AllowedComponentsConstructor[],
31
+ _CommandType extends A_TYPES__AllowedCommandsConstructor[] = A_TYPES__AllowedCommandsConstructor[],
32
+ _EntityType extends A_Entity[] = A_Entity[],
33
+ _FragmentType extends A_Fragment[] = A_Fragment[],
34
+ > {
35
+
36
+ /**
37
+ * Scope Name uses for identification and logging purposes
38
+ */
33
39
  readonly name: string = '';
40
+ /**
41
+ * Parent scope reference, used for inheritance of components, fragments, entities and commands
42
+ */
43
+ protected _parent?: A_Scope;
34
44
 
35
- private _components: WeakMap<typeof A_Component.constructor, any> = new WeakMap();
36
- private _fragments: WeakMap<typeof A_Fragment.constructor, any> = new WeakMap();
37
- private _commands: Map<string, A_Command> = new Map();
38
- private _entities: Map<string, A_Entity> = new Map();
45
+ // ===========================================================================
46
+ // --------------------ALLowed Constructors--------------------------------
47
+ // ===========================================================================
48
+ /**
49
+ * A set of allowed components, A set of constructors that are allowed in the scope
50
+ *
51
+ */
52
+ protected _allowedComponents = new Set<_ComponentType[number]>();
53
+ /**
54
+ * A set of allowed entities, A set of constructors that are allowed in the scope
55
+ */
56
+ protected _allowedEntities = new Set<A_TYPES__AllowedEntitiesConstructor<_EntityType[number]>>();
57
+ /**
58
+ * A set of allowed fragments, A set of constructors that are allowed in the scope
59
+ */
60
+ protected _allowedFragments = new Set<A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>>();
61
+ /**
62
+ * A set of allowed commands, A set of constructors that are allowed in the scope
63
+ */
64
+ protected _allowedCommands = new Set<_CommandType[number]>();
39
65
 
40
- private _parent?: A_Scope;
41
66
 
42
- protected params!: A_TYPES__ScopeConstructor
67
+ // ===========================================================================
68
+ // --------------------Internal Storage--------------------------------
69
+ // ===========================================================================
70
+ /**
71
+ * Internal storage for the components, fragments, entities and commands
72
+ */
73
+ protected _components: Map<_ComponentType[number], InstanceType<_ComponentType[number]>> = new Map();
74
+ /**
75
+ * Storage for the fragments, should be weak as fragments are singletons per scope
76
+ */
77
+ protected _fragments: Map<A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>, _FragmentType[number]> = new Map();
78
+ /**
79
+ * Storage for the entities, should be strong as entities are unique per aseid
80
+ */
81
+ protected _entities: Map<string, _EntityType[number]> = new Map();
82
+ /**
83
+ * Storage for the commands, should be strong as commands are unique per code
84
+ */
85
+ protected _commands: Map<string, InstanceType<_CommandType[number]>> = new Map();
43
86
 
44
87
 
45
- constructor(
46
- params: Partial<A_TYPES__ScopeConstructor>,
47
- config: Partial<A_TYPES__ScopeConfig> = {}
48
- ) {
49
- this.name = params.name || this.constructor.name;
50
88
 
51
- // TODO: move to defaults
52
- const defaultParams: A_TYPES__ScopeConstructor = {
53
- name: '',
54
- components: [],
55
- fragments: [],
56
- entities: [],
57
- commands: [],
58
- };
89
+ // ===========================================================================
90
+ // --------------------Readonly Allowed Properties----------------------------
91
+ // ===========================================================================
92
+ /**
93
+ * Returns a list of Constructors for A-Components that are available in the scope
94
+ */
95
+ get allowedComponents() { return this._allowedComponents }
96
+ /**
97
+ * Returns a list of Constructors for A-Commands that are available in the scope
98
+ */
99
+ get allowedCommands() { return this._allowedCommands }
100
+ /**
101
+ * Returns a list of Constructors for A-Fragments that are available in the scope
102
+ */
103
+ get allowedFragments() { return this._allowedFragments }
104
+ /**
105
+ * Returns a list of Constructors for A-Entities that are available in the scope
106
+ */
107
+ get allowedEntities() { return this._allowedEntities }
59
108
 
109
+ /**
110
+ * Returns an Array of entities registered in the scope
111
+ *
112
+ * [!] One entity per aseid
113
+ */
114
+ get entities(): Array<_EntityType[number]> { return Array.from(this._entities.values()) }
115
+ /**
116
+ * Returns an Array of fragments registered in the scope
117
+ *
118
+ * [!] One fragment per scope
119
+ */
120
+ get fragments(): Array<_FragmentType[number]> { return Array.from(this._fragments.values()) }
121
+ /**
122
+ * Returns an Array of components registered in the scope
123
+ *
124
+ * [!] One component instance per scope
125
+ */
126
+ get components(): Array<InstanceType<_ComponentType[number]>> { return Array.from(this._components.values()) }
127
+ /**
128
+ * Returns an Array of commands registered in the scope
129
+ *
130
+ * [!] One command per command aseid
131
+ * [!!] There may be any number of instances of the same command code, but with different aseids.
132
+ */
133
+ get commands(): Array<InstanceType<_CommandType[number]>> { return Array.from(this._commands.values()) }
60
134
 
61
- this.params = {
62
- ...defaultParams,
63
- ...params
64
- }
65
135
 
66
- this.initComponents(params.components || []);
67
- this.initFragments(params.fragments || []);
68
- this.initEntities(params.entities || []);
136
+ /**
137
+ * A_Scope refers to the visibility and accessibility of :
138
+ * - variables,
139
+ * - Components,
140
+ * - Context Fragments
141
+ * - Commands
142
+ * - Entities
143
+ * - and objects in different parts of your code.
144
+ * Scope determines where a particular piece of data (like a variable or function)
145
+ * can be accessed, modified, or referenced, and it plays a crucial role in avoiding naming collisions and ensuring data integrity.
146
+ *
147
+ * [!] The scope behavior is similar to tree structure where each scope can have a parent scope and inherit its components, fragments, entities and commands
148
+ *
149
+ * @param params
150
+ * @param config
151
+ */
152
+ constructor(
153
+ params: Partial<A_TYPES__ScopeConstructor<_ComponentType, _CommandType, _EntityType, _FragmentType>>,
154
+ config: Partial<A_TYPES__ScopeConfig> = {}
155
+ ) {
156
+ this.name = params.name || this.constructor.name
157
+
158
+ this.initComponents(params.components);
159
+ this.initCommands(params.commands);
160
+ this.initFragments(params.fragments);
161
+ this.initEntities(params.entities);
69
162
 
70
163
  if (config.parent) {
71
164
  this._parent = config.parent;
72
165
  }
73
166
  }
74
167
 
168
+ //==========================================================================
169
+ // --------------------Scope Initialization Methods---------------------------
170
+ //==========================================================================
75
171
 
76
- private initComponents(_components: Array<{ new(...args: any[]): any }>) {
77
- // _components.forEach(component => {
78
- // this._components.set(component, new component());
79
- // })
80
- }
81
-
82
- private initEntities(_entities: Array<A_Entity>) {
83
- _entities.forEach(this.register.bind(this));
84
- }
85
-
86
-
87
- private initFragments(_fragments: Array<A_Fragment>) {
88
- _fragments.forEach(this.register.bind(this));
89
- }
90
-
91
-
92
- get components() {
93
- return this.params.components || [];
94
- }
95
-
96
- get commands() {
97
- return this.params.commands || [];
98
- }
99
-
100
- get fragments() {
101
- return this.params.fragments || [];
102
- }
172
+ /**
173
+ * This method is used to initialize the components in the scope
174
+ * To save memory components are initialized only when they are requested
175
+ *
176
+ * This method only registers the component in the scope in case they are not registered yet
177
+ *
178
+ * @param _components
179
+ */
180
+ protected initComponents(_components?: _ComponentType) { _components?.forEach(this.register.bind(this)); }
181
+ /**
182
+ * This method is used to initialize the entities in the scope
183
+ *
184
+ * This method only registers the entities in the scope in case they are not registered yet
185
+ *
186
+ * @param _entities
187
+ */
188
+ protected initEntities(_entities?: _EntityType) { _entities?.forEach(this.register.bind(this)); }
189
+ /**
190
+ * This method is used to initialize the fragments in the scope
191
+ *
192
+ * This method only registers the fragments in the scope in case they are not registered yet
193
+ *
194
+ * @param _fragments
195
+ */
196
+ protected initFragments(_fragments?: _FragmentType) { _fragments?.forEach(this.register.bind(this)); }
197
+ /**
198
+ * This method is used to initialize the commands in the scope
199
+ *
200
+ * This method only registers the commands in the scope in case they are not registered yet
201
+ *
202
+ * @param _commands
203
+ */
204
+ protected initCommands(_commands?: _CommandType) { _commands?.forEach(this.register.bind(this)); }
103
205
 
104
206
 
105
- parent(setValue: A_Scope): void
106
- parent(): A_Scope
107
- parent(
108
- setValue?: A_Scope
109
- ): A_Scope | undefined {
207
+ /**
208
+ * This method is used to get or set the parent scope
209
+ *
210
+ * [!] Note that setting the parent scope will override the existing parent scope
211
+ *
212
+ * @param setValue
213
+ * @returns
214
+ */
215
+ parent(setValue?: A_Scope): A_Scope | undefined {
110
216
  if (setValue) {
111
217
  return this.inherit(setValue);
112
218
  }
@@ -114,162 +220,131 @@ export class A_Scope {
114
220
  return this._parent;
115
221
  }
116
222
 
117
- isInheritedFrom(scope: A_Scope): boolean {
118
- let current: A_Scope | undefined = this;
119
-
120
- while (current) {
121
- if (current === scope) {
122
- return true;
123
- }
124
- current = current._parent;
125
- }
126
-
127
- return false;
128
- }
129
-
130
-
131
- inherit(parent: A_Scope): A_Scope {
132
- // Prevent circular inheritance
133
- const circularCheck = this.checkCircularInheritance(parent);
134
-
135
- if (circularCheck) {
136
- throw new Error(`Circular inheritance detected: ${[...circularCheck, parent.name].join(' -> ')}`);
137
- }
138
-
139
- this._parent = parent;
140
- return this;
141
- }
142
-
143
223
 
144
224
 
145
225
  /**
146
- * Helper method to check circular inheritance
147
- * Should return a full sequence of inheritance for logging purposes
226
+ * This method is used to inherit from a parent scope
148
227
  *
149
- * @param scope
228
+ * [!] This method checks for circular inheritance and throws an error if detected
229
+ *
230
+ * @param parent
150
231
  * @returns
151
232
  */
152
- checkCircularInheritance(scope: A_Scope): Array<string> | false {
153
- const inheritanceChain: Array<string> = [];
154
- let current: A_Scope | undefined = this._parent;
155
-
156
- while (current) {
157
- inheritanceChain.push(current.name);
158
- if (current === scope) {
159
- return inheritanceChain;
160
- }
161
- current = current._parent;
162
- }
163
-
164
- return false;
165
- }
166
-
233
+ inherit(parent: A_Scope): A_Scope {
234
+ // Prevent circular inheritance
235
+ const circularCheck = this.checkCircularInheritance(parent);
167
236
 
168
- printInheritanceChain(): void {
169
- const chain: Array<string> = [];
170
- let current: A_Scope | undefined = this;
237
+ if (circularCheck)
238
+ throw new A_Error(`Circular inheritance detected: ${[...circularCheck, parent.name].join(' -> ')}`);
171
239
 
172
- while (current) {
173
- chain.push(current.name);
174
- current = current._parent;
175
- }
176
240
 
177
- console.log(chain.join(' -> '));
241
+ this._parent = parent;
242
+ return this;
178
243
  }
179
244
 
180
245
 
181
246
  /**
182
247
  * This method is used to check if the component is available in the scope
183
248
  *
249
+ * [!] Note that this method checks for the component in the current scope and all parent scopes
250
+ *
184
251
  * @param component
185
252
  * @returns
186
253
  */
187
254
  has<T extends A_Component>(
188
- component: new (...args: any[]) => T
255
+ /**
256
+ * Provide a component constructor to check if it's available in the scope
257
+ */
258
+ component: A_TYPES__AllowedComponentsConstructor<T>
189
259
  ): boolean
190
260
  has<T extends A_Entity>(
191
- entity: new (...args: any[]) => T
261
+ /**
262
+ * Provide an entity constructor to check if it's available in the scope
263
+ *
264
+ * [!] Note that entities are unique per aseid, so this method checks if there's at least one entity of the provided type in the scope
265
+ */
266
+ entity: A_TYPES__AllowedEntitiesConstructor<T>
192
267
  ): boolean
193
268
  has<T extends A_Fragment>(
194
- fragment: new (...args: any[]) => T
269
+ /**
270
+ * Provide a fragment constructor to check if it's available in the scope
271
+ */
272
+ fragment: A_TYPES__AllowedFragmentsConstructor<T>
273
+ ): boolean
274
+ has<T extends A_Fragment>(
275
+ /**
276
+ * Provide a command constructor to check if it's available in the scope
277
+ */
278
+ command: A_TYPES__AllowedCommandsConstructor<T>
195
279
  ): boolean
196
280
  has(
281
+ /**
282
+ * Provide a string to check if a component, entity or fragment with the provided name is available in the scope
283
+ */
197
284
  constructor: string
198
285
  ): boolean
199
- has<T extends A_Fragment | A_Component | A_Entity | A_Command>(
200
- entity: T | string | (new (...args: any[]) => T)
286
+ has(
287
+ ctor: unknown
201
288
  ): boolean {
202
289
 
290
+ let found = false;
203
291
 
204
292
  switch (true) {
293
+ // 1) Check by string name.
294
+ case typeof ctor === 'string': {
295
+ // 1.1 Check if it's a component name
296
+ const possibleComponent = Array.from(this.allowedComponents).find(c => c.name === ctor);
297
+ if (possibleComponent) found = true;
205
298
 
206
- case typeof entity === 'string': {
207
- const possibleComponent = this.params.components.find(c => c.name === entity);
299
+ // 1.2 Check if it's a fragment name
300
+ const possibleFragment = Array.from(this.allowedFragments).find(f => f.name === ctor);
301
+ if (possibleFragment) found = true;
208
302
 
209
- if (possibleComponent) {
210
- return true;
211
- }
303
+ // 1.3 Check if it's a command code or name
304
+ const possibleCommand = Array.from(this.allowedCommands).find(c => c.name === ctor);
305
+ if (possibleCommand) found = true;
212
306
 
213
- const possibleFragment = this.params.fragments.find(f => f.name === entity);
214
-
215
- if (possibleFragment) {
216
- return true;
217
- }
218
-
219
- if (this.params.entities.some(e => e.constructor.name === entity)) {
220
- return true;
221
- }
307
+ // 1.4 Check if it's an entity name or entity static entity property
308
+ const possibleEntity = Array.from(this.allowedEntities).find(e => e.name === ctor);
309
+ if (possibleEntity) found = true;
222
310
 
311
+ // 1.5 If not found in current scope, check parent scope
223
312
  if (!!this._parent)
224
- return this._parent.has(entity);
313
+ return this._parent.has(ctor);
225
314
 
226
315
  return false;
227
316
  }
317
+ // 2) Check if it's a Component
318
+ case this.isComponentConstructor(ctor): {
319
+ found = this.isAllowedComponent(ctor);
228
320
 
229
- case typeof entity === 'function'
230
- && A_CommonHelper.isInheritedFrom(entity, A_Component): {
231
- const found = this.components.includes(entity as { new(...args: any[]): A_Component });
232
-
233
- if (!found && !!this._parent) {
234
- return this._parent.has(entity as any);
235
- }
236
-
237
- return found;
238
- }
239
-
240
- case typeof entity === 'function'
241
- && A_CommonHelper.isInheritedFrom(entity, A_Entity): {
242
- const entities = Array.from(this._entities.values());
243
-
244
- const found = entities.find(e => e instanceof entity);
245
-
246
- return !!found;
247
- }
248
-
249
- case typeof entity === 'function'
250
- && A_CommonHelper.isInheritedFrom(entity, A_Fragment): {
251
- const found = this._fragments.has(entity);
321
+ break;
322
+ }
323
+ // 3) Check if it's an Entity
324
+ case this.isEntityConstructor(ctor): {
325
+ found = this.isAllowedEntity(ctor);
252
326
 
253
- if (!found && !!this._parent)
254
- return this._parent.has(entity as any);
327
+ break;
328
+ }
329
+ // 4) Check if it's a Fragment
330
+ case this.isFragmentConstructor(ctor): {
331
+ found = this.isAllowedFragment(ctor);
255
332
 
256
- return found;
257
- }
258
- case typeof entity === 'function'
259
- && A_CommonHelper.isInheritedFrom(entity, A_Command): {
260
- const found = this.commands.includes(entity as { new(...args: any[]): A_Command });
333
+ break;
334
+ }
335
+ // 5) Check if it's a Command
336
+ case this.isCommandConstructor(ctor): {
337
+ found = this.isAllowedCommand(ctor);
261
338
 
262
- if (!found && !!this._parent)
263
- return this._parent.has(entity as any);
339
+ break;
340
+ }
341
+ }
264
342
 
265
- return found;
266
- }
343
+ if (!found && !!this._parent)
344
+ return this._parent.has(ctor as any);
267
345
 
268
346
 
269
- default: {
270
- return false;
271
- }
272
- }
347
+ return found;
273
348
  }
274
349
 
275
350
 
@@ -286,19 +361,13 @@ export class A_Scope {
286
361
  merge(anotherScope: A_Scope): A_Scope {
287
362
  const merged = new A_Scope(
288
363
  {
289
- name: `${this.name}&${anotherScope.name}`,
290
- components: Array.from(new Set([
291
- ...this.params.components,
292
- ...anotherScope.params.components
293
- ])),
294
- fragments: Array.from(new Set([
295
- ...this.params.fragments,
296
- ...anotherScope.params.fragments
297
- ])),
298
- entities: Array.from(new Set([
299
- ...this.params.entities,
300
- ...anotherScope.params.entities
301
- ])),
364
+ name: `${this.name} + ${anotherScope.name}`,
365
+
366
+ components: [...this.allowedComponents, ...anotherScope.allowedComponents],
367
+ commands: [...this.allowedCommands, ...anotherScope.allowedCommands],
368
+
369
+ fragments: [...this.fragments, ...anotherScope.fragments],
370
+ entities: [...this.entities, ...anotherScope.entities],
302
371
  },
303
372
  {
304
373
  parent: this._parent || anotherScope._parent
@@ -320,28 +389,63 @@ export class A_Scope {
320
389
  * @param name
321
390
  * @returns
322
391
  */
323
- resolveConstructor<T extends A_Component | A_Entity>(name: string): new (...args: any[]) => T {
324
- // Check components
325
- const component = this.params.components.find(c => c.name === A_CommonHelper.toPascalCase(name));
326
- if (component) return component as any;
327
-
328
- // Check entities
329
- const entity = this.params.entities.find(e => (e.constructor as any).entity === name
330
- || (e.constructor as any).name === A_CommonHelper.toPascalCase(name)
331
- || (e.constructor as any).entity === A_CommonHelper.toKebabCase(name)
392
+ resolveConstructor<T extends A_Command>(
393
+ /**
394
+ * Provide the command name or code to retrieve its constructor
395
+ */
396
+ name: string
397
+ ): A_TYPES__AllowedCommandsConstructor<T>
398
+ resolveConstructor<T extends A_Entity>(
399
+ /**
400
+ * Provide the entity name or static entity property to retrieve its constructor
401
+ */
402
+ name: string
403
+ ): A_TYPES__AllowedEntitiesConstructor<T>
404
+ resolveConstructor<T extends A_Component>(
405
+ /**
406
+ * Provide the component name in PascalCase to retrieve its constructor
407
+ */
408
+ name: string
409
+ ): A_TYPES__AllowedComponentsConstructor<T>
410
+ resolveConstructor<T extends A_Fragment>(
411
+ /**
412
+ * Provide the fragment name in PascalCase to retrieve its constructor
413
+ */
414
+ name: string
415
+ ): A_TYPES__AllowedFragmentsConstructor<T>
416
+ resolveConstructor<T extends A_Command | A_Entity | A_Component | A_Fragment>(name: string): A_TYPES__AllowedCommandsConstructor<T> | A_TYPES__AllowedEntitiesConstructor<T> | A_TYPES__AllowedComponentsConstructor<T> | A_TYPES__AllowedFragmentsConstructor<T> {
417
+ // 1) Check components
418
+ const component = Array.from(this.allowedComponents).find(
419
+ c => c.name === name
420
+ || c.name === A_CommonHelper.toPascalCase(name)
421
+ );
422
+ if (component) return component as A_TYPES__AllowedComponentsConstructor<T>;
423
+
424
+ // 2) Check entities
425
+ const entity = Array.from(this.allowedEntities).find(
426
+ e => e.name === name
427
+ || e.name === A_CommonHelper.toPascalCase(name)
428
+ || (e as any).entity === name
429
+ || (e as any).entity === A_CommonHelper.toKebabCase(name)
332
430
  );
333
- if (entity) return entity.constructor as any;
431
+ if (entity) return entity as A_TYPES__AllowedEntitiesConstructor<T>;
334
432
 
335
- // Check commands
336
- const command = this.params.commands.find(c => (c as any).code === name
433
+ // 3) Check commands
434
+ const command = Array.from(this.allowedCommands).find(c => (c as any).code === name
337
435
  || (c as any).name === A_CommonHelper.toPascalCase(name)
338
436
  || (c as any).code === A_CommonHelper.toKebabCase(name)
339
437
  );
340
- if (command) return command as any;
438
+ if (command) return command as A_TYPES__AllowedCommandsConstructor<T>;
439
+
440
+ // 4) Check fragments
441
+ const fragment = Array.from(this.allowedFragments).find(f => f.name === name
442
+ || f.name === A_CommonHelper.toPascalCase(name)
443
+ );
444
+ if (fragment) return fragment as A_TYPES__AllowedFragmentsConstructor<T>;
341
445
 
342
446
  // If not found in current scope, check parent scope
343
447
  if (!!this._parent) {
344
- return this._parent.resolveConstructor(name);
448
+ return this._parent.resolveConstructor(name) as any;
345
449
  }
346
450
 
347
451
  throw new Error(`Component or Entity with name ${name} not found in the scope ${this.name}`);
@@ -349,135 +453,219 @@ export class A_Scope {
349
453
 
350
454
 
351
455
 
352
-
353
-
354
456
  /**
355
- * This method is used to get the component by class
457
+ * This method allows to resolve/inject a component, fragment or entity from the scope
458
+ * Depending on the provided parameters it can resolve:
459
+ * - A single component/fragment/entity by its constructor or name
460
+ * - An array of components/fragments/entities by providing an array of constructors
461
+ * - An entity or an array of entities by providing the entity constructor and query instructions
356
462
  *
357
463
  * @param component
358
464
  * @returns
359
465
  */
360
- resolve<T extends A_TYPES__A_InjectDecorator_Injectable>(
361
- string: string
362
- ): InstanceType<T>
363
- resolve<T extends A_TYPES__A_InjectDecorator_Injectable>(
364
- component: T
365
- ): InstanceType<T>
466
+ resolve<T extends A_Component>(
467
+ /**
468
+ * Provide a component constructor to resolve its instance from the scope
469
+ */
470
+ component: A_TYPES__AllowedComponentsConstructor<T>
471
+ ): T
472
+ resolve<T extends A_TYPES__AllowedComponentsConstructor[]>(
473
+ /**
474
+ * Provide an array of component constructors to resolve their instances from the scope
475
+ */
476
+ components: [...T]
477
+ ): Array<InstanceType<T[number]>>
478
+ resolve<T extends A_Fragment>(
479
+ /**
480
+ * Provide a fragment constructor to resolve its instance from the scope
481
+ */
482
+ fragment: A_TYPES__AllowedFragmentsConstructor<T>
483
+ ): T
484
+ resolve<T extends A_TYPES__AllowedFragmentsConstructor[]>(
485
+ /**
486
+ * Provide an array of fragment constructors to resolve their instances from the scope
487
+ */
488
+ fragments: [...T]
489
+ ): Array<InstanceType<T[number]>>
490
+ resolve<T extends A_Command>(
491
+ /**
492
+ * Provide a command constructor to resolve its instance from the scope
493
+ */
494
+ command: A_TYPES__AllowedCommandsConstructor<T>
495
+ ): T
496
+ resolve<T extends A_TYPES__AllowedCommandsConstructor[]>(
497
+ /**
498
+ * Provide an array of command constructors to resolve their instances from the scope
499
+ */
500
+ commands: [...T]
501
+ ): Array<InstanceType<T[number]>>
502
+ resolve<T extends A_Entity>(
503
+ /**
504
+ * Provide an entity constructor to resolve its instance or an array of instances from the scope
505
+ */
506
+ entity: A_TYPES__AllowedEntitiesConstructor<T>
507
+ ): T | undefined
508
+ resolve<T extends A_Scope>(
509
+ /**
510
+ * Uses only in case of resolving a single entity
511
+ *
512
+ * Provide an entity constructor to resolve its instance from the scope
513
+ */
514
+ scope: new (...args: any[]) => T
515
+ ): T
366
516
  resolve<T extends A_Entity>(
367
- entity: { new(...args: any[]): T },
517
+ /**
518
+ * Provide an entity constructor to resolve its instance or an array of instances from the scope
519
+ */
520
+ entity: A_TYPES__AllowedEntitiesConstructor<T>,
521
+ /**
522
+ * Provide optional instructions to find a specific entity or a set of entities
523
+ */
368
524
  instructions: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions<T>>
369
- ): T | Array<T>
370
- resolve<T extends A_TYPES__A_InjectDecorator_Injectable>(
371
- component: Array<T>
372
- ): Array<InstanceType<T>>
525
+ ): Array<T>
373
526
  // base definition
374
- resolve<T extends A_TYPES__A_InjectDecorator_Injectable>(
375
- param1: Array<T> | T | string,
376
- param2?: string | Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions>
377
- ): Array<InstanceType<T>> | InstanceType<T> {
378
-
379
-
527
+ resolve<T extends A_Component | A_Fragment | A_Entity | A_Command>(
528
+ /**
529
+ * Provide a component, fragment or entity constructor or an array of constructors to resolve its instance(s) from the scope
530
+ */
531
+ param1: unknown,
532
+ param2?: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions>
533
+ ): T | Array<T> {
380
534
  switch (true) {
381
535
  case Array.isArray(param1): {
382
- return param1.map(c => this.resolveOnce(param1 as any, param2 as any));
536
+ return param1.map(c => this.resolveOnce(c, param2)).filter(Boolean) as Array<T>;
383
537
  }
384
538
 
385
539
  case typeof param1 === 'function': {
386
- return this.resolveOnce(param1 as any, param2 as any);
540
+ return this.resolveOnce(param1, param2);
387
541
  }
388
542
 
543
+
389
544
  case typeof param1 === 'string': {
390
- return this.resolveByName(param1 as any) as InstanceType<T>;
545
+ return this.resolveByName(param1) as T;
391
546
  }
392
547
 
393
548
  default: {
394
- throw new Error('Invalid arguments provided');
549
+ throw new A_Error(`Invalid parameter provided to resolve method: ${param1} in scope ${this.name}`);
395
550
  }
396
551
  }
397
552
  }
398
553
 
399
554
 
400
- private resolveByName(name: string): A_Entity | A_Fragment | A_Component {
401
- // Check components
402
- const component = this.params.components.find(c => c.name === name);
403
- if (component) return this.resolveComponent(component);
404
555
 
405
- // Check commands
406
- const command = this.params.commands.find(c => c.name === name);
407
- if (command) return this.resolveComponent(command);
408
556
 
409
- // Check fragments
410
- const fragment = this.params.fragments.find(f => f.constructor.name === name);
411
- if (fragment) return this.resolveFragment(fragment.constructor as any);
557
+ // ==================================================================================================
558
+ // --------------------------------------------------------------------------------------------------
559
+ // -------------------------------------INTERNAL RESOLVERS-------------------------------------------
560
+ // --------------------------------------------------------------------------------------------------
561
+ // ==================================================================================================
562
+ private resolveByName(name: string): _EntityType[number] | InstanceType<_ComponentType[number]> | _FragmentType[number] | InstanceType<_CommandType[number]> {
563
+ // 1) Check components
564
+ const component = Array.from(this.allowedComponents).find(
565
+ c => c.name === name
566
+ || c.name === A_CommonHelper.toPascalCase(name)
567
+ );
568
+ if (component) return this.resolveOnce(component) as InstanceType<_ComponentType[number]>;
569
+
570
+ // 2) Check entities
571
+ const entity = Array.from(this.allowedEntities).find(
572
+ e => e.name === name
573
+ || e.name === A_CommonHelper.toPascalCase(name)
574
+ || (e as any).entity === name
575
+ || (e as any).entity === A_CommonHelper.toKebabCase(name)
576
+ );
577
+ if (entity) return this.resolveOnce(entity) as _EntityType[number];
578
+
579
+ // 3) Check commands
580
+ const command = Array.from(this.allowedCommands).find(c => (c as any).code === name
581
+ || (c as any).name === A_CommonHelper.toPascalCase(name)
582
+ || (c as any).code === A_CommonHelper.toKebabCase(name)
583
+ );
584
+ if (command) return this.resolveOnce(command) as InstanceType<_CommandType[number]>;
412
585
 
413
- // Check entities
414
- const entity = this.params.entities.find(e => e.constructor.name === name);
415
- if (entity) return this.resolveEntity(entity.constructor as any);
586
+ // 4) Check fragments
587
+ const fragment = Array.from(this.allowedFragments).find(f => f.name === name
588
+ || f.name === A_CommonHelper.toPascalCase(name)
589
+ );
590
+ if (fragment) return this.resolveOnce(fragment) as _FragmentType[number];
416
591
 
417
592
  // If not found in current scope, check parent scope
418
- if (this._parent) {
419
- return this._parent.resolveByName(name);
593
+ if (!!this._parent) {
594
+ return this._parent.resolveByName(name) as any;
420
595
  }
421
596
 
422
- throw new Error(`Component, Fragment, or Entity with name ${name} not found in the scope ${this.name}`);
597
+ throw new Error(`Component or Entity with name ${name} not found in the scope ${this.name}`);
423
598
  }
424
599
 
425
-
426
-
427
-
428
- private resolveOnce<T extends { new(...args: any[]): A_Entity }>(
429
- entity: T,
430
- instructions: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions>
431
- ): InstanceType<T> | undefined
432
- private resolveOnce<T extends A_TYPES__A_InjectDecorator_Injectable>(
433
- component: T
434
- ): InstanceType<T>
435
- private resolveOnce<T extends { new(...args: any[]): A_Entity } | A_TYPES__A_InjectDecorator_Injectable>(
436
- component: T,
600
+ /**
601
+ * This method is used internally to resolve a single component, fragment or entity from the scope
602
+ *
603
+ * @param component
604
+ * @param instructions
605
+ * @returns
606
+ */
607
+ private resolveOnce<T extends A_Component | A_Fragment | A_Entity | A_Command | A_Scope>(
608
+ component: unknown,
437
609
  instructions?: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions>
438
- ): InstanceType<T> {
610
+ ): T | Array<T> {
439
611
 
612
+ if (this.isScopeConstructor(component))
613
+ component
614
+
615
+ if (typeof component == 'function' && (component as any).name === 'A_Scope')
616
+ component
440
617
 
441
618
  switch (true) {
442
- case A_CommonHelper.isInheritedFrom(component, A_Entity): {
443
- return this.resolveEntity(component as any, instructions) as InstanceType<T>;
619
+ case this.isEntityConstructor(component): {
620
+ return this.resolveEntity(component, instructions) as T | Array<T>;
444
621
  }
445
-
446
- case A_CommonHelper.isInheritedFrom(component, A_Fragment): {
447
- return this.resolveFragment(component as typeof A_Fragment) as InstanceType<T>;
622
+ case this.isFragmentConstructor(component): {
623
+ return this.resolveFragment(component) as T;
448
624
  }
449
-
450
- case A_CommonHelper.isInheritedFrom(component, A_Scope): {
451
- return this.resolveScope(component as typeof A_Scope) as InstanceType<T>;
625
+ case this.isCommandConstructor(component): {
626
+ return this.resolveCommand(component) as T;
452
627
  }
453
-
454
- case A_CommonHelper.isInheritedFrom(component, A_Component): {
455
- return this.resolveComponent(component as typeof A_Component) as InstanceType<T>;
628
+ case this.isScopeConstructor(component): {
629
+ return this.resolveScope(component) as T;
630
+ }
631
+ case this.isComponentConstructor(component): {
632
+ return this.resolveComponent(component) as T;
456
633
  }
457
-
458
634
  default:
459
635
  throw new Error(`Injected Component ${component} not found in the scope`);
460
636
  }
461
637
  }
462
638
 
639
+ /**
640
+ * This method is used internally to resolve a single entity from the scope based on the provided instructions
641
+ *
642
+ * [!] Note that this method can return either a single entity or an array of entities depending on the instructions provided
643
+ *
644
+ * @param entity
645
+ * @param instructions
646
+ * @returns
647
+ */
648
+ private resolveEntity<T extends A_Entity>(
649
+ entity: A_TYPES__AllowedEntitiesConstructor<T>,
650
+ instructions?: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions<T>>
651
+ ): T | Array<T> | undefined {
463
652
 
464
- private resolveEntity<T extends { new(...args: any[]): A_Entity }>(
465
- entity: T,
466
- instructions?: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions<InstanceType<T>>>
467
- ): InstanceType<T> | undefined | InstanceType<T>[] {
468
-
469
- const query = instructions?.query || {} as Partial<A_TYPES__A_InjectDecorator_EntityInjectionQuery<InstanceType<T>>>;
653
+ const query = instructions?.query || {} as Partial<A_TYPES__A_InjectDecorator_EntityInjectionQuery<T>>;
470
654
  const count = instructions?.pagination?.count || 1;
471
655
 
472
656
  switch (true) {
657
+ /**
658
+ * 1) In case when no instructions provided, return the first found entity of the provided type
659
+ *
660
+ * [!] Note that it returns ONLY ONE entity
661
+ * [!!] In case when no entity found in the current scope, it tries to resolve it from the parent scope (if exists)
662
+ */
473
663
  case !instructions: {
474
- const entities = Array.from(this._entities.values());
475
-
476
- const found = entities.find(e => e instanceof entity);
664
+ const found = this.entities.find(e => e instanceof entity);
477
665
 
478
666
  switch (true) {
479
667
  case !!found:
480
- return found as InstanceType<T>;
668
+ return found as T;
481
669
 
482
670
  case !found && !!this._parent:
483
671
  return this._parent.resolveEntity(entity, instructions);
@@ -486,74 +674,84 @@ export class A_Scope {
486
674
  throw new Error(`Entity ${entity.name} not found in the scope ${this.name}`);
487
675
  }
488
676
  }
489
-
677
+ /**
678
+ * 2) In case when aseid is provided in the query, we can directly get the entity from the map
679
+ *
680
+ * [!] Note that it returns ONLY ONE entity
681
+ */
490
682
  case !!query.aseid
491
683
  && typeof query.aseid === 'string'
492
684
  && this._entities.has(query.aseid): {
493
- return this._entities.get(query.aseid) as InstanceType<T>;
685
+ return this._entities.get(query.aseid) as T;
494
686
  }
495
-
687
+ /**
688
+ * 3) In case when aseid is provided as ASEID instance, we can directly get the entity from the map
689
+ *
690
+ * [!] Note that it returns ONLY ONE entity
691
+ */
496
692
  case !!query.aseid
497
693
  && typeof query.aseid === 'object'
498
694
  && query.aseid instanceof ASEID
499
695
  && this._entities.has(query.aseid.toString()): {
500
- return this._entities.get(query.aseid.toString()) as InstanceType<T>;
696
+ return this._entities.get(query.aseid.toString()) as T;
501
697
  }
502
-
698
+ /**
699
+ * 4) In case when id is provided in the query, we have to find the entity by the id
700
+ *
701
+ * [!] Note that it returns ONLY ONE entity
702
+ */
503
703
  case !!query.id: {
504
704
 
505
- // in this case we have to find the entity by the id
506
- const entities = Array.from(this._entities.values());
507
-
508
- const found = entities.filter(
509
- e => e instanceof entity
510
- ).find(e => {
511
- return String(e.id) === String(query.id)
512
- });
705
+ const found = this.entities
706
+ .filter(e => e instanceof entity)
707
+ .find(e => String(e.id) === String(query.id));
513
708
 
514
- return found as InstanceType<T>;
709
+ return found as T;
515
710
  }
516
-
711
+ /**
712
+ * 5) In case when there's a query object, we have to filter the entities by the query
713
+ *
714
+ * [!] Note that it can return either a single entity or an array of entities depending on the count instruction
715
+ * [!!] In case when no entity found in the current scope, it tries to resolve it from the parent scope (if exists)
716
+ */
517
717
  default: {
518
- const entities = Array.from(this._entities.values());
519
-
520
- const found = entities.filter(
521
- e => e instanceof entity
522
- ).filter(e => {
523
- return Object.entries(query).every(([key, value]) => {
524
- if (key in e) {
525
- return (e as any)[key] === value;
526
- }
527
- return false;
718
+
719
+ const found = this.entities
720
+ .filter(e => e instanceof entity)
721
+ .filter(e => {
722
+ return Object
723
+ .entries(query)
724
+ .every(([key, value]) => {
725
+ if (key in e) {
726
+ return (e as any)[key] === value;
727
+ }
728
+ return false;
729
+ });
528
730
  });
529
- });
530
731
 
531
732
  if (found.length === 0 && !!this._parent)
532
733
  return this._parent.resolveEntity(entity, instructions);
533
734
 
534
735
  if (count === 1)
535
- return found[0] as InstanceType<T>;
736
+ return found[0] as T;
536
737
 
537
- return found as InstanceType<T>[];
738
+ return found as T[];
538
739
  }
539
-
540
740
  }
541
741
  }
542
742
 
543
-
544
-
545
- private resolveFragment<T extends typeof A_Fragment>(fragment: T): InstanceType<T> {
546
-
547
- const fragmentInstancePresented = this.fragments.some(fr => fr instanceof fragment);
548
-
743
+ /**
744
+ * This method is used internally to resolve a single fragment from the scope
745
+ *
746
+ * @param fragment
747
+ * @returns
748
+ */
749
+ private resolveFragment<T extends A_Fragment>(fragment: A_TYPES__AllowedFragmentsConstructor<T>): _FragmentType[number] {
750
+ const fragmentInstancePresented = this._fragments.get(fragment);
549
751
 
550
752
  switch (true) {
551
-
552
753
  case fragmentInstancePresented && this._fragments.has(fragment):
553
- return this._fragments.get(fragment);
554
-
555
- case fragmentInstancePresented && !this._fragments.has(fragment):
556
- return this.fragments.find(fr => fr instanceof fragment) as InstanceType<T>;
754
+ return fragmentInstancePresented;
557
755
 
558
756
  case !fragmentInstancePresented && !!this._parent:
559
757
  return this._parent.resolveFragment(fragment);
@@ -562,28 +760,34 @@ export class A_Scope {
562
760
  throw new Error(`Fragment ${fragment.name} not found in the scope ${this.name}`);
563
761
  }
564
762
  }
565
-
566
-
567
- private resolveScope(scope: typeof A_Scope): A_Scope {
763
+ /**
764
+ * This method is used internally to resolve a single scope from the current scope
765
+ *
766
+ * @param scope
767
+ * @returns
768
+ */
769
+ private resolveScope(scope: A_TYPES__AllowedScopesConstructor): A_Scope {
568
770
  return this;
569
771
  }
570
-
571
-
572
- private resolveComponent<T extends A_Component>(component: {
573
- new(...args: any[]): T
574
- }): T {
772
+ /**
773
+ * This method is used internally to resolve a single component from the scope
774
+ *
775
+ * @param component
776
+ * @returns
777
+ */
778
+ private resolveComponent<T extends A_Component>(component: A_TYPES__AllowedComponentsConstructor<T>): InstanceType<_ComponentType[number]> {
575
779
 
576
780
  // The idea here that in case when Scope has no exact component we have to resolve it from the _parent
577
781
  // BUT: if it's not presented in _parent we have to check for inheritance
578
782
  // That means that we should ensure that there's no components that are children of the required component
579
783
  switch (true) {
580
- // In case when the component is available and exists in the scope
581
- case this.components.includes(component) && this._components.has(component): {
582
- return this._components.get(component);
784
+ // 1) In case when the component is available and exists in the scope
785
+ case this.allowedComponents.has(component) && this._components.has(component): {
786
+ return this._components.get(component)!;
583
787
  }
584
788
 
585
- // In case the component available but does NOT exist in the scope
586
- case this.components.includes(component) && !this._components.has(component): {
789
+ // 2) In case the component available but does NOT exist in the scope
790
+ case this.allowedComponents.has(component) && !this._components.has(component): {
587
791
  const componentMeta = A_Context.meta(component)
588
792
 
589
793
  const argsMeta = componentMeta.get(A_TYPES__ComponentMetaKey.INJECTIONS);
@@ -597,53 +801,48 @@ export class A_Scope {
597
801
  instructions
598
802
  );
599
803
  }
600
- return this.resolve(arg.target)
804
+ // TODO: Fix types mismatch here
805
+ return this.resolve<T>(arg.target as any);
601
806
  });
602
807
 
603
808
  const newComponent = new component(...resolvedArgs)
604
809
 
605
810
  this.register(newComponent);
606
811
 
607
- return this._components.get(component);
812
+ return this._components.get(component)!;
608
813
  }
609
814
 
610
- // In case when there's a component that is inherited from the required component
611
- case !this.components.includes(component) && this.components.some(el => A_CommonHelper.isInheritedFrom(el, component)): {
612
-
613
- const found = this.components.find(el => A_CommonHelper.isInheritedFrom(el, component));
815
+ // 3) In case when there's a component that is inherited from the required component
816
+ case !this.allowedComponents.has(component) && Array.from(this.allowedComponents).some(el => A_CommonHelper.isInheritedFrom(el, component)): {
817
+ const found = Array.from(this.allowedComponents).find(el => A_CommonHelper.isInheritedFrom(el, component))!;
614
818
 
615
- return this.resolveComponent<T>(found as any);
819
+ return this.resolveComponent(found);
616
820
  }
617
821
 
618
- // In case when the component is not available in the scope but the _parent is available
619
- case !this.components.includes(component) && !!this._parent: {
620
- return this._parent.resolveComponent(component);
822
+ // 4) In case when the component is not available in the scope but the _parent is available
823
+ case !!this._parent: {
824
+ return this._parent.resolveComponent(component) as InstanceType<_ComponentType[number]>;
621
825
  }
622
826
 
623
827
  default:
624
828
  throw new Error(`Component ${component.name} not found in the scope ${this.name}`);
625
829
  }
626
830
  }
627
-
628
-
629
831
  /**
630
832
  * Should be similar to resolveEntity but for commands
631
833
  *
632
834
  * @param command
633
835
  */
634
- private resolveCommand<T extends A_Command>(command: {
635
- new(...args: any[]): T
636
- }): T {
637
- const commands = Array.from(this._commands.values());
836
+ private resolveCommand(command: _CommandType[number]): InstanceType<_CommandType[number]> {
638
837
 
639
- const found = commands.find(e => e instanceof command);
838
+ const found = this.commands.find(e => e instanceof command);
640
839
 
641
840
  switch (true) {
642
841
  case !!found:
643
- return found as T;
842
+ return found
644
843
 
645
844
  case !found && !!this._parent:
646
- return this._parent.resolveCommand(command);
845
+ return this._parent.resolveCommand(command) as InstanceType<_CommandType[number]>;
647
846
 
648
847
  default:
649
848
  throw new Error(`Command ${command.name} not found in the scope ${this.name}`);
@@ -657,86 +856,137 @@ export class A_Scope {
657
856
  *
658
857
  * @param fragment
659
858
  */
660
- register<T extends A_Component>(component: new (...args: any[]) => T): void
661
- register<T extends A_Entity>(entity: new (...args: any[]) => T): void
662
- register<T extends A_Command>(command: new (...args: any[]) => T): void
663
- register(entity: A_Entity): void
664
- register(component: A_Component): void
665
- register(fragment: A_Fragment): void
859
+ register<T extends A_Component>(
860
+ /**
861
+ * Provide a component constructor to register it in the scope
862
+ */
863
+ component: A_TYPES__AllowedComponentsConstructor<T>
864
+ ): void
865
+ register<T extends A_Entity>(
866
+ /**
867
+ * Provide an entity constructor to register it in the scope
868
+ */
869
+ entity: A_TYPES__AllowedEntitiesConstructor<T>
870
+ ): void
871
+ register<T extends A_Command>(
872
+ /**
873
+ * Provide a command constructor to register it in the scope
874
+ */
875
+ command: A_TYPES__AllowedCommandsConstructor<T>
876
+ ): void
877
+ register<T extends A_Fragment>(
878
+ /**
879
+ * Provide a command instance to register it in the scope
880
+ */
881
+ fragment: A_TYPES__AllowedFragmentsConstructor<T>
882
+ ): void
666
883
  register(
667
- param1: A_Fragment
668
- | A_Component
669
- | A_Entity
670
- | (new (...args: any[]) => A_Component)
671
- | (new (...args: any[]) => A_Entity)
672
- | (new (...args: any[]) => A_Command)
884
+ /**
885
+ * Provide an entity instance to register it in the scope
886
+ */
887
+ entity: A_Entity
888
+ ): void
889
+ register(
890
+ /**
891
+ * Provide a command instance to register it in the scope
892
+ */
893
+ component: A_Component
894
+ ): void
895
+ register(
896
+ /**
897
+ * Provide a command instance to register it in the scope
898
+ */
899
+ command: A_Command
900
+ ): void
901
+ register(
902
+ /**
903
+ * Provide a fragment instance to register it in the scope
904
+ */
905
+ fragment: A_Fragment
906
+ ): void
907
+ register(
908
+ param1: unknown
673
909
  ): void {
674
910
  switch (true) {
675
- case param1 instanceof A_Component && !this._components.has(param1.constructor): {
676
- this._components.set(param1.constructor, param1);
911
+ // ------------------------------------------
912
+ // ------------ Instances ----------------
913
+ // ------------------------------------------
914
+ // 1) In case when it's a A-Component instance
915
+ case param1 instanceof A_Component: {
677
916
 
678
- const allowedComponent = this.components.find(c => c === param1.constructor);
917
+ if (!this.allowedComponents.has(param1.constructor as _ComponentType[number]))
918
+ this.allowedComponents.add(param1.constructor as _ComponentType[number]);
679
919
 
680
- if (!allowedComponent) {
681
- this.components.push(param1.constructor as any);
682
- }
920
+ this._components.set(
921
+ param1.constructor as _ComponentType[number],
922
+ param1 as InstanceType<_ComponentType[number]>
923
+ );
683
924
 
684
925
  A_Context.register(this, param1);
926
+
685
927
  break;
686
928
  }
929
+ // 2) In case when it's a A-Command instance
930
+ case param1 instanceof A_Command: {
931
+
932
+ if (!this.allowedCommands.has(param1.constructor as _CommandType[number]))
933
+ this.allowedCommands.add(param1.constructor as _CommandType[number]);
934
+
935
+ this._commands.set((param1 as any).constructor.code, param1 as InstanceType<_CommandType[number]>);
687
936
 
688
- case param1 instanceof A_Entity && !this._entities.has(param1.aseid.toString()): {
689
- this._entities.set(param1.aseid.toString(), param1);
690
937
  A_Context.register(this, param1);
691
938
  break;
692
939
  }
940
+ // 3) In case when it's a A-Entity instance
941
+ case param1 instanceof A_Entity && !this._entities.has(param1.aseid.toString()): {
693
942
 
694
- case param1 instanceof A_Fragment && !this._fragments.has(param1.constructor): {
695
- const allowedFragment = this.fragments.find(fr => fr instanceof param1.constructor);
696
-
697
- if (!allowedFragment) {
698
- this.fragments.push(param1);
699
- }
943
+ if (!this.allowedEntities.has(param1.constructor as A_TYPES__AllowedEntitiesConstructor<_EntityType[number]>))
944
+ this.allowedEntities.add(param1.constructor as A_TYPES__AllowedEntitiesConstructor<_EntityType[number]>);
700
945
 
701
- this._fragments.set(param1.constructor, param1);
946
+ this._entities.set(param1.aseid.toString(), param1);
702
947
  A_Context.register(this, param1);
703
948
  break;
704
949
  }
950
+ // 4) In case when it's a A-Fragment instance
951
+ case param1 instanceof A_Fragment: {
705
952
 
706
- case param1 instanceof A_Component: {
707
- this._components.set(param1.constructor, param1);
953
+ if (!this.allowedFragments.has(param1.constructor as A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>))
954
+ this.allowedFragments.add(param1.constructor as A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>);
708
955
 
709
- const allowedComponent = this.components.find(c => c === param1.constructor);
710
-
711
- if (!allowedComponent) {
712
- this.components.push(param1.constructor as any);
713
- }
956
+ this._fragments.set(
957
+ param1.constructor as A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>,
958
+ param1 as _FragmentType[number]
959
+ );
714
960
 
715
961
  A_Context.register(this, param1);
962
+
716
963
  break;
717
964
  }
718
-
719
- case typeof param1 === 'function' && A_CommonHelper.isInheritedFrom(param1, A_Component): {
720
- const allowedComponent = this.components.find(c => c === param1);
721
-
722
- if (!allowedComponent)
723
- this.components.push(param1);
965
+ // ------------------------------------------
966
+ // ------------ Constructors ----------------
967
+ // ------------------------------------------
968
+ // 5) In case when it's a A-Component constructor
969
+ case this.isComponentConstructor(param1): {
970
+ if (!this.allowedComponents.has(param1))
971
+ this.allowedComponents.add(param1 as _ComponentType[number]);
724
972
  break;
725
973
  }
726
- case typeof param1 === 'function' && A_CommonHelper.isInheritedFrom(param1, A_Entity): {
727
- const allowedEntity = this.params.entities.find(e => e.constructor === param1);
728
-
729
- if (!allowedEntity) {
730
- this.params.entities.push(new (param1 as any)());
731
- }
974
+ // 6) In case when it's a A-Command constructor
975
+ case this.isCommandConstructor(param1): {
976
+ if (!this.allowedCommands.has(param1))
977
+ this.allowedCommands.add(param1 as _CommandType[number]);
732
978
  break;
733
979
  }
734
- case typeof param1 === 'function' && A_CommonHelper.isInheritedFrom(param1, A_Command): {
735
- const allowedCommand = this.commands.find(c => c === param1);
736
-
737
- if (!allowedCommand) {
738
- this.commands.push(param1 as any);
739
- }
980
+ // 7) In case when it's a A-Fragment constructor
981
+ case this.isFragmentConstructor(param1): {
982
+ if (!this.allowedFragments.has(param1))
983
+ this.allowedFragments.add(param1 as A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]>);
984
+ break;
985
+ }
986
+ // 8) In case when it's a A-Entity constructor
987
+ case this.isEntityConstructor(param1): {
988
+ if (!this.allowedEntities.has(param1))
989
+ this.allowedEntities.add(param1 as A_TYPES__AllowedEntitiesConstructor<_EntityType[number]>);
740
990
  break;
741
991
  }
742
992
 
@@ -748,23 +998,182 @@ export class A_Scope {
748
998
  else
749
999
  throw new Error(`Cannot register ${param1} in the scope ${this.name}`);
750
1000
  }
1001
+ }
1002
+
1003
+
1004
+
751
1005
 
1006
+ /**
1007
+ * This method is useful when you want to serialize the scope to JSON
1008
+ *
1009
+ * [!] Note this is not a deep serialization, only the fragments are serialized
1010
+ * [!] Fragments are a storage for information which is relevant to the scope
1011
+ *
1012
+ * @returns
1013
+ */
1014
+ toJSON(): Record<string, any> {
1015
+ return this.fragments
1016
+ .reduce((acc, fragment) => {
752
1017
 
1018
+ const serialized = fragment.toJSON()
753
1019
 
1020
+ return {
1021
+ ...acc,
1022
+ [serialized.name]: serialized
1023
+ }
1024
+ }, {});
1025
+ }
1026
+
1027
+
1028
+
1029
+ //==========================================================================
1030
+ // --------------------Scope Type Check Helpers---------------------------
1031
+ //==========================================================================
1032
+ /**
1033
+ * Type guard to check if the constructor is of type A_Component
1034
+ *
1035
+ * @param ctor
1036
+ * @returns
1037
+ */
1038
+ protected isComponentConstructor(ctor: unknown): ctor is A_TYPES__AllowedComponentsConstructor {
1039
+ return typeof ctor === 'function' && A_CommonHelper.isInheritedFrom(ctor, A_Component);
1040
+ }
1041
+ /**
1042
+ * Type guard to check if the constructor is of type A_Command
1043
+ *
1044
+ * @param ctor
1045
+ * @returns
1046
+ */
1047
+ protected isCommandConstructor(ctor: unknown): ctor is A_TYPES__AllowedCommandsConstructor {
1048
+ return typeof ctor === 'function' && A_CommonHelper.isInheritedFrom(ctor, A_Command);
1049
+ }
1050
+ /**
1051
+ * Type guard to check if the constructor is of type A_Fragment
1052
+ *
1053
+ * @param ctor
1054
+ * @returns
1055
+ */
1056
+ protected isFragmentConstructor(ctor: any): ctor is A_TYPES__AllowedFragmentsConstructor {
1057
+ return typeof ctor === 'function' && A_CommonHelper.isInheritedFrom(ctor, A_Fragment);
1058
+ }
1059
+ /**
1060
+ * Type guard to check if the constructor is of type A_Entity
1061
+ *
1062
+ * @param ctor
1063
+ * @returns
1064
+ */
1065
+ protected isEntityConstructor(ctor: unknown): ctor is A_TYPES__AllowedEntitiesConstructor {
1066
+ return typeof ctor === 'function' && A_CommonHelper.isInheritedFrom(ctor, A_Entity);
1067
+ }
1068
+ /**
1069
+ * Type guard to check if the constructor is of type A_Scope
1070
+ *
1071
+ * @param ctor
1072
+ * @returns
1073
+ */
1074
+ protected isScopeConstructor(ctor: unknown): ctor is A_TYPES__AllowedScopesConstructor {
1075
+ return typeof ctor === 'function' && A_CommonHelper.isInheritedFrom(ctor, A_Scope);
1076
+ }
1077
+ // -------------------------------------------------------------------------------
1078
+ // --------------------Scope Allowed Type Check Helpers---------------------------
1079
+ // -------------------------------------------------------------------------------
1080
+ /**
1081
+ * Type guard to check if the constructor is of type A_Component and is allowed in the scope
1082
+ *
1083
+ * @param ctor
1084
+ * @returns
1085
+ */
1086
+ protected isAllowedComponent(ctor: unknown): ctor is _ComponentType[number] {
1087
+ return this.isComponentConstructor(ctor) && this.allowedComponents.has(ctor);
1088
+ }
1089
+ /**
1090
+ * Type guard to check if the constructor is of type A_Command and is allowed in the scope
1091
+ *
1092
+ * @param ctor
1093
+ * @returns
1094
+ */
1095
+ protected isAllowedCommand(ctor: unknown): ctor is _CommandType[number] {
1096
+ return this.isCommandConstructor(ctor) && this.allowedCommands.has(ctor);
1097
+ }
1098
+ /**
1099
+ * Type guard to check if the constructor is of type A_Entity and is allowed in the scope
1100
+ *
1101
+ * @param ctor
1102
+ * @returns
1103
+ */
1104
+ protected isAllowedEntity(ctor: unknown): ctor is A_TYPES__AllowedEntitiesConstructor<_EntityType[number]> {
1105
+ return this.isEntityConstructor(ctor) && this.allowedEntities.has(ctor);
1106
+ }
1107
+ /**
1108
+ * Type guard to check if the constructor is of type A_Fragment and is allowed in the scope
1109
+ *
1110
+ * @param ctor
1111
+ * @returns
1112
+ */
1113
+ protected isAllowedFragment(ctor: unknown): ctor is A_TYPES__AllowedFragmentsConstructor<_FragmentType[number]> {
1114
+ return this.isFragmentConstructor(ctor) && this.allowedFragments.has(ctor);
754
1115
  }
755
1116
 
756
1117
 
757
1118
 
758
1119
 
759
- toJSON(): Record<string, any> {
760
- return this.fragments.reduce((acc, fragment) => {
1120
+ // ==========================================================================
1121
+ // --------------------DEBUG & Helpers Methods--------------------------------
1122
+ // ===========================================================================
1123
+ /**
1124
+ * This method is used to check if the scope is inherited from another scope
1125
+ *
1126
+ * @param scope
1127
+ * @returns
1128
+ */
1129
+ isInheritedFrom(scope: A_Scope): boolean {
1130
+ let current: A_Scope | undefined = this;
761
1131
 
762
- const serialized = fragment.toJSON()
1132
+ while (current) {
1133
+ if (current === scope) {
1134
+ return true;
1135
+ }
1136
+ current = current._parent;
1137
+ }
1138
+
1139
+ return false;
1140
+ }
1141
+
1142
+ /**
1143
+ * Helper method to check circular inheritance
1144
+ * Should return a full sequence of inheritance for logging purposes
1145
+ *
1146
+ * @param scope
1147
+ * @returns
1148
+ */
1149
+ checkCircularInheritance(scope: A_Scope): Array<string> | false {
1150
+ const inheritanceChain: Array<string> = [];
1151
+ let current: A_Scope | undefined = this._parent;
763
1152
 
764
- return {
765
- ...acc,
766
- [serialized.name]: serialized
1153
+ while (current) {
1154
+ inheritanceChain.push(current.name);
1155
+ if (current === scope) {
1156
+ return inheritanceChain;
767
1157
  }
768
- }, {});
1158
+ current = current._parent;
1159
+ }
1160
+
1161
+ return false;
769
1162
  }
770
- }
1163
+
1164
+ /**
1165
+ * Helper method to print the inheritance chain of the scope
1166
+ */
1167
+ printInheritanceChain(): void {
1168
+ const chain: Array<string> = [];
1169
+ let current: A_Scope | undefined = this;
1170
+
1171
+ while (current) {
1172
+ chain.push(current.name);
1173
+ current = current._parent;
1174
+ }
1175
+
1176
+ console.log(chain.join(' -> '));
1177
+ }
1178
+ }
1179
+