@adaas/a-concept 0.1.57 → 0.1.58

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.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "A-Concept is a framework to build new Applications within or outside the ADAAS ecosystem. This framework is designed to be modular structure regardless environment and program goal.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.cjs",
@@ -214,20 +214,31 @@ export class A_Scope<
214
214
  /**
215
215
  * This method is used to retrieve a parent scope at a specific level
216
216
  *
217
+ * [!] Note that if the level is out of bounds, undefined is returned
218
+ * [!!] Uses negative values for levels (e.g. -1 for immediate parent, -2 for grandparent, etc.)
219
+ *
217
220
  * @param level
218
221
  * @returns
219
222
  */
220
- parentAtLevel(level: number): A_Scope | undefined {
221
- let currentParent = this._parent;
222
- let currentLevel = 0;
223
- while (currentParent) {
224
- if (currentLevel === level) {
225
- return currentParent;
226
- }
227
- currentParent = currentParent._parent;
228
- currentLevel++;
223
+ parentOffset(
224
+ /**
225
+ * Level of the parent scope to retrieve
226
+ *
227
+ * Examples:
228
+ * - level 0 - immediate parent
229
+ * - level -1 - grandparent
230
+ * - level -2 - great-grandparent
231
+ */
232
+ layerOffset: number
233
+ ): A_Scope | undefined {
234
+ let parentScope = this.parent;
235
+
236
+ while (layerOffset < -1 && parentScope) {
237
+ parentScope = parentScope.parent;
238
+ layerOffset++;
229
239
  }
230
- return undefined;
240
+
241
+ return parentScope;
231
242
  }
232
243
 
233
244
 
@@ -1425,7 +1436,7 @@ export class A_Scope<
1425
1436
  const componentName = A_CommonHelper.getComponentName(arg.target)
1426
1437
 
1427
1438
  if ('instructions' in arg && !!arg.instructions) {
1428
- const { target, instructions } = arg
1439
+ const { target, parent, flat, instructions } = arg
1429
1440
  const dependency = this.resolve(target as any, instructions);
1430
1441
  if (!dependency)
1431
1442
  throw new A_ScopeError(
@@ -1435,17 +1446,47 @@ export class A_Scope<
1435
1446
 
1436
1447
  return dependency;
1437
1448
  } else {
1438
- const { target, require, create, defaultArgs } = arg;
1449
+ const { target, require, create, defaultArgs, parent, flat, } = arg;
1450
+
1451
+ let dependency;
1452
+
1453
+ // ----------------- Resolution Strategies -----------------
1454
+ switch (true) {
1455
+ // 1) Flat resolution
1456
+ case flat: {
1457
+ dependency = this.resolveFlat(target);
1458
+ break;
1459
+ }
1460
+ // 2) Parent resolution
1461
+ case parent && typeof parent.layerOffset === 'number': {
1462
+ const targetParent = this.parentOffset(parent.layerOffset);
1463
+ if(!targetParent) {
1464
+ throw new A_ScopeError(
1465
+ A_ScopeError.ResolutionError,
1466
+ `Unable to resolve parent scope at offset ${parent.layerOffset} for dependency ${componentName} for component ${component.name} in scope ${this.name}`
1467
+ );
1468
+ }
1469
+ dependency = targetParent.resolve(target);
1470
+
1471
+ break;
1472
+ }
1473
+ // 3) Normal resolution
1474
+ default: {
1475
+ dependency = this.resolve(target);
1476
+ break;
1477
+ }
1478
+ }
1439
1479
 
1440
- let dependency = this.resolve(target as any);
1480
+ // ----------------- Post-Resolution Actions -----------------
1441
1481
 
1482
+ // 1) Create default instance in case when allowed
1442
1483
  if (create && !dependency && A_TypeGuards.isAllowedForDependencyDefaultCreation(target)) {
1443
- const newDependency = new target(...defaultArgs);
1484
+ dependency = new target(...defaultArgs);
1444
1485
 
1445
- this.register(newDependency);
1446
- return newDependency;
1486
+ this.register(dependency);
1447
1487
  }
1448
1488
 
1489
+ // 2) Throw error in case when required but not resolved
1449
1490
  if (require && !dependency) {
1450
1491
  throw new A_ScopeError(
1451
1492
  A_ScopeError.ResolutionError,
@@ -1453,6 +1494,8 @@ export class A_Scope<
1453
1494
  );
1454
1495
  }
1455
1496
 
1497
+
1498
+ // Finally, return the dependency (either resolved or undefined)
1456
1499
  return dependency;
1457
1500
  }
1458
1501
  });
@@ -155,7 +155,7 @@ export class A_Stage {
155
155
  }
156
156
  // 2) Parent resolution
157
157
  case parent && typeof parent.layerOffset === 'number': {
158
- const targetParent = targetScope.parentAtLevel(parent.layerOffset);
158
+ const targetParent = targetScope.parentOffset(parent.layerOffset);
159
159
  if (!targetParent) {
160
160
  throw new A_StageError(
161
161
  A_StageError.ArgumentsResolutionError,
@@ -89,5 +89,203 @@ describe('A-Dependency tests', () => {
89
89
  expect(instance!.component).toBeInstanceOf(MyCustomEntity);
90
90
  expect(instance!.component.foo).toBe('bar');
91
91
  });
92
+ it('Should resolve only dependency on the same level with Flat directive', async () => {
93
+ class ParentComponent extends A_Component { }
94
+
95
+ class ChildComponent extends A_Component {
96
+ constructor(
97
+ @A_Dependency.Flat()
98
+ @A_Inject(ParentComponent) public parentComponent?: ParentComponent,
99
+ ) {
100
+ super();
101
+
102
+ }
103
+ }
104
+
105
+ const parentScope = new A_Scope({ components: [ParentComponent] });
106
+ const childScope = new A_Scope({ components: [ChildComponent] }, { parent: parentScope });
107
+
108
+ const instance = childScope.resolve(ChildComponent);
109
+
110
+ expect(instance).toBeDefined();
111
+ expect(instance).toBeInstanceOf(ChildComponent);
112
+ expect(instance!.parentComponent).toBeUndefined();
113
+ });
114
+
115
+ it('Should resolve create dependency with Create directive', async () => {
116
+
117
+ class MyEntity_A extends A_Entity<{ name: string }> {
118
+ name!: string;
119
+
120
+ fromNew(newEntity: { name: string; }): void {
121
+ super.fromNew(newEntity);
122
+ this.name = newEntity.name;
123
+ }
124
+ }
125
+
126
+ class ChildComponent extends A_Component {
127
+ constructor(
128
+ @A_Dependency.Default({ name: 'Entity A' })
129
+ @A_Inject(MyEntity_A) public myEntityA: MyEntity_A,
130
+ ) {
131
+ super();
132
+
133
+ }
134
+ }
135
+
136
+ const childScope = new A_Scope({ components: [ChildComponent], entities: [MyEntity_A] });
137
+
138
+ const instance = childScope.resolve(ChildComponent);
139
+
140
+ expect(instance).toBeDefined();
141
+ expect(instance).toBeInstanceOf(ChildComponent);
142
+ expect(instance!.myEntityA).toBeInstanceOf(MyEntity_A);
143
+ expect(instance!.myEntityA.name).toBe('Entity A');
144
+ });
145
+ it('Should resolve Parent entity if it exists, even if Default provided', async () => {
146
+
147
+ class MyEntity_A extends A_Entity<{ name: string }> {
148
+ name!: string;
149
+
150
+ fromNew(newEntity: { name: string; }): void {
151
+ super.fromNew(newEntity);
152
+ this.name = newEntity.name;
153
+ }
154
+ }
155
+
156
+ class ChildComponent extends A_Component {
157
+ constructor(
158
+ @A_Dependency.Default({ name: 'Child Entity' })
159
+ @A_Inject(MyEntity_A) public myEntityA: MyEntity_A,
160
+ ) {
161
+ super();
162
+
163
+ }
164
+ }
165
+
166
+ const parentScope = new A_Scope({ entities: [new MyEntity_A({ name: 'Parent Entity' })] });
167
+ const childScope = new A_Scope({ components: [ChildComponent] }, { parent: parentScope });
168
+
169
+ const instance = childScope.resolve(ChildComponent);
170
+
171
+ expect(instance).toBeDefined();
172
+ expect(instance).toBeInstanceOf(ChildComponent);
173
+ expect(instance!.myEntityA).toBeInstanceOf(MyEntity_A);
174
+ expect(instance!.myEntityA.name).toBe('Parent Entity');
175
+ });
176
+ it('Should resolve dependencies properly with combination of Directives', async () => {
177
+
178
+ class MyEntity_A extends A_Entity<{ name: string }> {
179
+ name!: string;
180
+
181
+ fromNew(newEntity: { name: string; }): void {
182
+ super.fromNew(newEntity);
183
+ this.name = newEntity.name;
184
+ }
185
+ }
186
+
187
+ class ChildComponent extends A_Component {
188
+ constructor(
189
+ @A_Dependency.Flat()
190
+ @A_Dependency.Default({ name: 'Child Entity' })
191
+ @A_Inject(MyEntity_A) public myEntityA: MyEntity_A,
192
+ ) {
193
+ super();
194
+
195
+ }
196
+ }
197
+
198
+ const parentScope = new A_Scope({ entities: [new MyEntity_A({ name: 'Parent Entity' })] });
199
+ const childScope = new A_Scope({ components: [ChildComponent] }, { parent: parentScope });
200
+
201
+ const instance = childScope.resolve(ChildComponent);
202
+
203
+ expect(instance).toBeDefined();
204
+ expect(instance).toBeInstanceOf(ChildComponent);
205
+ expect(instance!.myEntityA).toBeInstanceOf(MyEntity_A);
206
+ expect(instance!.myEntityA.name).toBe('Child Entity');
207
+ })
208
+ it('Should resolve dependency from parent scope with Parent Directive', async () => {
209
+
210
+ class MyEntity_A extends A_Entity<{ name: string }> {
211
+ name!: string;
212
+
213
+ fromNew(newEntity: { name: string; }): void {
214
+ super.fromNew(newEntity);
215
+ this.name = newEntity.name;
216
+ }
217
+ }
218
+
219
+ class ChildComponent extends A_Component {
220
+ constructor(
221
+ @A_Dependency.Parent()
222
+ @A_Inject(MyEntity_A) public myEntityA: MyEntity_A,
223
+ ) {
224
+ super();
225
+
226
+ }
227
+ }
228
+
229
+ const parentScope = new A_Scope({
230
+ name: 'Parent Scope',
231
+ entities: [new MyEntity_A({ name: 'Parent Entity' })]
232
+ });
233
+ const childScope = new A_Scope({
234
+ name: 'Child Scope',
235
+ components: [ChildComponent],
236
+ entities: [new MyEntity_A({ name: 'Child Entity' })]
237
+ }, { parent: parentScope });
238
+
239
+ const instance = childScope.resolve(ChildComponent);
240
+
241
+ expect(instance).toBeDefined();
242
+ expect(instance).toBeInstanceOf(ChildComponent);
243
+ expect(instance!.myEntityA).toBeInstanceOf(MyEntity_A);
244
+ expect(instance!.myEntityA.name).toBe('Parent Entity');
245
+ });
246
+ it('Should resolve dependency from parent scope with Parent Directive and offset', async () => {
247
+
248
+ class MyEntity_A extends A_Entity<{ name: string }> {
249
+ name!: string;
250
+
251
+ fromNew(newEntity: { name: string; }): void {
252
+ super.fromNew(newEntity);
253
+ this.name = newEntity.name;
254
+ }
255
+ }
256
+
257
+ class ChildComponent extends A_Component {
258
+ constructor(
259
+ @A_Dependency.Parent(-2)
260
+ @A_Inject(MyEntity_A) public myEntityA: MyEntity_A,
261
+ ) {
262
+ super();
263
+
264
+ }
265
+ }
266
+
267
+ const grandparentScope = new A_Scope({
268
+ name: 'Grandparent Scope',
269
+ entities: [new MyEntity_A({ name: 'Grandparent Entity' })]
270
+ });
271
+
272
+ const parentScope = new A_Scope({
273
+ name: 'Parent Scope',
274
+ entities: [new MyEntity_A({ name: 'Parent Entity' })]
275
+ }, { parent: grandparentScope });
276
+
277
+ const childScope = new A_Scope({
278
+ name: 'Child Scope',
279
+ components: [ChildComponent],
280
+ entities: [new MyEntity_A({ name: 'Child Entity' })]
281
+ }, { parent: parentScope });
282
+
283
+ const instance = childScope.resolve(ChildComponent);
284
+
285
+ expect(instance).toBeDefined();
286
+ expect(instance).toBeInstanceOf(ChildComponent);
287
+ expect(instance!.myEntityA).toBeInstanceOf(MyEntity_A);
288
+ expect(instance!.myEntityA.name).toBe('Grandparent Entity');
289
+ });
92
290
 
93
291
  });