@adaas/a-concept 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaas/a-concept",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "A-Concept is a framework of the new generation that is tailored to use AI, enabling developers to create AI-powered applications with ease. It provides a structured approach to building, managing, and deploying AI-driven solutions.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.cjs",
@@ -375,11 +375,11 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
375
375
 
376
376
  // Convert iterator to array to get all stages
377
377
  const stages = Array.from(this);
378
-
378
+
379
379
  return this.processStagesSequentially(stages, scope, 0);
380
380
 
381
381
  } catch (error) {
382
- this.failed(new A_FeatureError({
382
+ throw this.failed(new A_FeatureError({
383
383
  title: A_FeatureError.FeatureProcessingError,
384
384
  description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${this.stage?.name || 'N/A'}.`,
385
385
  stage: this.stage,
@@ -392,11 +392,16 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
392
392
  * Process stages one by one, ensuring each stage completes before starting the next
393
393
  */
394
394
  private processStagesSequentially(
395
- stages: A_Stage[],
396
- scope: A_Scope | undefined,
395
+ stages: A_Stage[],
396
+ scope: A_Scope | undefined,
397
397
  index: number
398
398
  ): Promise<void> | void {
399
399
  try {
400
+ // Check if feature has been interrupted before processing next stage
401
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) {
402
+ return;
403
+ }
404
+
400
405
  // If we've processed all stages, complete the feature
401
406
  if (index >= stages.length) {
402
407
  this.completed();
@@ -408,22 +413,28 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
408
413
 
409
414
  if (A_TypeGuards.isPromiseInstance(result)) {
410
415
  // Async stage - return promise that processes remaining stages
411
- return result.then(() => {
412
- return this.processStagesSequentially(stages, scope, index + 1);
413
- }).catch(error => {
414
- this.failed(new A_FeatureError({
415
- title: A_FeatureError.FeatureProcessingError,
416
- description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
417
- stage: stage,
418
- originalError: error
419
- }));
420
- });
416
+ return result
417
+ .then(() => {
418
+ // Check for interruption after async stage completes
419
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) {
420
+ return;
421
+ }
422
+ return this.processStagesSequentially(stages, scope, index + 1);
423
+ })
424
+ .catch(error => {
425
+ throw this.failed(new A_FeatureError({
426
+ title: A_FeatureError.FeatureProcessingError,
427
+ description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${stage.name}.`,
428
+ stage: stage,
429
+ originalError: error
430
+ }));
431
+ });
421
432
  } else {
422
433
  // Sync stage - continue to next stage immediately
423
434
  return this.processStagesSequentially(stages, scope, index + 1);
424
435
  }
425
436
  } catch (error) {
426
- this.failed(new A_FeatureError({
437
+ throw this.failed(new A_FeatureError({
427
438
  title: A_FeatureError.FeatureProcessingError,
428
439
  description: `An error occurred while processing the A-Feature: ${this.name}. Failed at stage: ${this.stage?.name || 'N/A'}.`,
429
440
  stage: this.stage,
@@ -455,19 +466,24 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
455
466
  completed(): void {
456
467
  if (this.isProcessed) return;
457
468
 
469
+ // Don't complete if interrupted
470
+ if (this.state === A_TYPES__FeatureState.INTERRUPTED) {
471
+ return;
472
+ }
458
473
 
459
474
  this._state = A_TYPES__FeatureState.COMPLETED;
460
475
 
461
476
  this.scope.destroy();
462
477
  }
463
478
  /**
464
- * This method marks the feature as failed and throws an error
479
+ * This method marks the feature as failed and returns the error
465
480
  * Uses to mark the feature as failed
466
481
  *
467
482
  * @param error
483
+ * @returns The error that caused the failure
468
484
  */
469
- failed(error: A_FeatureError) {
470
- if (this.isProcessed) return;
485
+ failed(error: A_FeatureError): A_FeatureError {
486
+ if (this.isProcessed) return this._error!;
471
487
 
472
488
  this._state = A_TYPES__FeatureState.FAILED;
473
489
 
@@ -475,45 +491,47 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
475
491
 
476
492
  this.scope.destroy();
477
493
 
478
- throw this._error;
494
+ return this._error;
479
495
  }
480
496
  /**
481
- * This method marks the feature as failed and throws an error
497
+ * This method marks the feature as interrupted and throws an error
482
498
  * Uses to interrupt or end the feature processing
483
499
  *
484
500
  * @param error
485
501
  */
486
- async interrupt(
502
+ interrupt(
487
503
  /**
488
504
  * The reason of feature interruption
489
505
  */
490
506
  reason?: string | A_StageError | Error
491
- ) {
492
- if (this.isProcessed) return;
507
+ ): A_FeatureError {
508
+ if (this.isProcessed) return this._error!;
493
509
 
494
510
  this._state = A_TYPES__FeatureState.INTERRUPTED;
495
511
 
496
512
  switch (true) {
497
513
  case A_TypeGuards.isString(reason):
498
- this._error = new A_FeatureError(A_FeatureError.Interruption, reason);
514
+ this._error = new A_FeatureError(A_FeatureError.Interruption, reason as string);
499
515
  break;
500
516
 
501
517
  case A_TypeGuards.isErrorInstance(reason):
502
518
  this._error = new A_FeatureError({
503
519
  code: A_FeatureError.Interruption,
504
- title: reason.title,
505
- description: reason.description,
520
+ title: (reason as any).title || 'Feature Interrupted',
521
+ description: (reason as any).description || (reason as Error).message,
506
522
  stage: this.stage,
507
523
  originalError: reason
508
524
  });
509
525
  break;
510
526
 
511
527
  default:
528
+ this._error = new A_FeatureError(A_FeatureError.Interruption, 'Feature was interrupted');
512
529
  break;
513
530
  }
514
531
 
515
-
516
532
  this.scope.destroy();
533
+
534
+ return this._error;
517
535
  }
518
536
 
519
537
 
@@ -573,7 +591,17 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
573
591
  // create new caller for the chained feature
574
592
  feature._caller = this._caller;
575
593
 
576
- return feature.process(featureScope);
594
+ const result = feature.process(featureScope);
595
+
596
+ // If the chained feature processing returns a promise, ensure errors are propagated
597
+ if (A_TypeGuards.isPromiseInstance(result)) {
598
+ return result.catch(error => {
599
+ // Re-throw to ensure chained feature errors propagate to caller
600
+ throw error;
601
+ });
602
+ }
603
+
604
+ return result;
577
605
  }
578
606
 
579
607
 
@@ -1814,6 +1814,13 @@ export class A_Scope<
1814
1814
  this._components.delete(param1.constructor as _ComponentType[number]);
1815
1815
  A_Context.deregister(param1);
1816
1816
 
1817
+ const ctor = param1.constructor as _ComponentType[number];
1818
+
1819
+ const hasComponent = this._components.has(ctor);
1820
+ if (!hasComponent) {
1821
+ this.allowedComponents.delete(ctor);
1822
+ }
1823
+
1817
1824
  break;
1818
1825
  }
1819
1826
  // 3) In case when it's a A-Entity instance
@@ -1821,14 +1828,28 @@ export class A_Scope<
1821
1828
 
1822
1829
  this._entities.delete(param1.aseid.toString());
1823
1830
  A_Context.deregister(param1);
1831
+
1832
+ const ctor = param1.constructor as _EntityType[number];
1833
+
1834
+ const hasEntity = Array.from(this._entities.values()).some(entity => entity instanceof ctor);
1835
+ if (!hasEntity) {
1836
+ this.allowedEntities.delete(ctor);
1837
+ }
1838
+
1824
1839
  break;
1825
1840
  }
1826
1841
  // 4) In case when it's a A-Fragment instance
1827
1842
  case A_TypeGuards.isFragmentInstance(param1): {
1828
-
1829
1843
  this._fragments.delete(param1.constructor as A_TYPES__Fragment_Constructor<_FragmentType[number]>);
1830
1844
  A_Context.deregister(param1);
1831
1845
 
1846
+ const ctor = param1.constructor as A_TYPES__Fragment_Constructor<_FragmentType[number]>;
1847
+
1848
+ const hasFragment = Array.from(this._fragments.values()).some(fragment => fragment instanceof ctor);
1849
+ if (!hasFragment) {
1850
+ this.allowedFragments.delete(ctor);
1851
+ }
1852
+
1832
1853
  break;
1833
1854
  }
1834
1855
  // 5) In case when it's a A-Error instance
@@ -1836,6 +1857,14 @@ export class A_Scope<
1836
1857
 
1837
1858
  this._errors.delete(param1.code);
1838
1859
  A_Context.deregister(param1);
1860
+
1861
+ const ctor = param1.constructor as _ErrorType[number];
1862
+
1863
+ const hasError = Array.from(this._errors.values()).some(error => error instanceof ctor);
1864
+ if (!hasError) {
1865
+ this.allowedErrors.delete(ctor);
1866
+ }
1867
+
1839
1868
  break;
1840
1869
  }
1841
1870
 
@@ -1850,16 +1879,40 @@ export class A_Scope<
1850
1879
  // 8) In case when it's a A-Fragment constructor
1851
1880
  case A_TypeGuards.isFragmentConstructor(param1): {
1852
1881
  this.allowedFragments.delete(param1 as A_TYPES__Fragment_Constructor<_FragmentType[number]>);
1882
+ // and then deregister all instances of this fragment
1883
+ Array.from(this._fragments.entries()).forEach(([ctor, instance]) => {
1884
+ if (A_CommonHelper.isInheritedFrom(ctor, param1)) {
1885
+ this._fragments.delete(ctor);
1886
+ A_Context.deregister(instance);
1887
+ }
1888
+ });
1889
+
1853
1890
  break;
1854
1891
  }
1855
1892
  // 9) In case when it's a A-Entity constructor
1856
1893
  case A_TypeGuards.isEntityConstructor(param1): {
1857
1894
  this.allowedEntities.delete(param1 as _EntityType[number]);
1895
+ // and then deregister all instances of this entity
1896
+ Array.from(this._entities.entries()).forEach(([aseid, instance]) => {
1897
+ if (A_CommonHelper.isInheritedFrom(instance.constructor, param1)) {
1898
+ this._entities.delete(aseid);
1899
+ A_Context.deregister(instance);
1900
+ }
1901
+ });
1902
+
1858
1903
  break;
1859
1904
  }
1860
1905
  // 10) In case when it's a A-Error constructor
1861
1906
  case A_TypeGuards.isErrorConstructor(param1): {
1862
1907
  this.allowedErrors.delete(param1 as _ErrorType[number]);
1908
+ // and then deregister all instances of this error
1909
+ Array.from(this._errors.entries()).forEach(([code, instance]) => {
1910
+ if (A_CommonHelper.isInheritedFrom(instance.constructor, param1)) {
1911
+ this._errors.delete(code);
1912
+ A_Context.deregister(instance);
1913
+ }
1914
+ });
1915
+
1863
1916
  break;
1864
1917
  }
1865
1918
 
@@ -1875,6 +1928,7 @@ export class A_Scope<
1875
1928
  `Cannot deregister ${componentName} from the scope ${this.name}`
1876
1929
  );
1877
1930
  }
1931
+
1878
1932
  }
1879
1933
 
1880
1934
  /**
@@ -591,22 +591,21 @@ describe('A-Feature tests', () => {
591
591
  ]
592
592
  });
593
593
 
594
- feature.process();
595
-
596
- await new Promise<void>(async (resolve) => {
597
- setTimeout(() => {
598
- feature.interrupt();
599
- }, 1000);
600
-
601
-
602
- setTimeout(() => {
603
- expect(feature.state).toBe(A_TYPES__FeatureState.INTERRUPTED);
604
- expect(executionOrder).toEqual(['feature1', 'feature2']);
605
-
606
- resolve();
607
-
608
- }, 3000);
609
- });
594
+ await Promise.all([
595
+ feature.process(),
596
+ new Promise<void>(async (resolve) => {
597
+ setTimeout(() => {
598
+ feature.interrupt();
599
+ resolve();
600
+ }, 800);
601
+ }),
602
+ new Promise<void>(async (resolve) => {
603
+ setTimeout(() => {
604
+ expect(feature.state).toBe(A_TYPES__FeatureState.INTERRUPTED);
605
+ resolve();
606
+ }, 1000);
607
+ })
608
+ ]);
610
609
 
611
610
  }, 5000);
612
611
  it('Should allow to use extension if only parent class provided', async () => {
@@ -868,4 +867,171 @@ describe('A-Feature tests', () => {
868
867
  ]);
869
868
 
870
869
  })
870
+ it('Should throw a Sync error when executed sync', async () => {
871
+
872
+ const resultChain: string[] = [];
873
+
874
+
875
+ class ChildComponent_A extends A_Component {
876
+ @A_Feature.Extend({
877
+ name: 'testFeature',
878
+ })
879
+ test1() {
880
+ resultChain.push('ChildComponent_A.test');
881
+ throw new A_Error('Deliberate Sync Error in test1');
882
+ }
883
+ }
884
+
885
+ class ChildComponent_B extends A_Component {
886
+ @A_Feature.Extend({
887
+ name: 'testFeature',
888
+ })
889
+ test2() {
890
+ resultChain.push('ChildComponent_B.test');
891
+ }
892
+ }
893
+
894
+
895
+ const testScope = new A_Scope({ name: 'TestScope', components: [ChildComponent_A, ChildComponent_B] });
896
+
897
+ try {
898
+ testScope.resolve(ChildComponent_A)!.call('testFeature');
899
+ } catch (error) {
900
+ expect(error).toBeInstanceOf(A_Error);
901
+ expect((error as A_Error).originalError).toBeInstanceOf(A_Error)
902
+ expect((error as A_Error).originalError.message).toBe('Deliberate Sync Error in test1');
903
+ }
904
+
905
+ expect(resultChain).toEqual([
906
+ 'ChildComponent_A.test'
907
+ ]);
908
+ const feature = new A_Feature({
909
+ name: 'testFeature',
910
+ component: testScope.resolve(ChildComponent_A)!,
911
+ })
912
+
913
+ try {
914
+ feature.process();
915
+ } catch (error) {
916
+ expect(error).toBeInstanceOf(A_Error);
917
+ expect((error as A_Error).originalError).toBeInstanceOf(A_Error)
918
+ expect((error as A_Error).originalError.message).toBe('Deliberate Sync Error in test1');
919
+ }
920
+
921
+ expect(feature.state).toBe(A_TYPES__FeatureState.FAILED);
922
+
923
+ expect(resultChain).toEqual([
924
+ 'ChildComponent_A.test',
925
+ 'ChildComponent_A.test'
926
+ ]);
927
+ })
928
+ it('Should throw an Async error when executed async', async () => {
929
+
930
+ const resultChain: string[] = [];
931
+
932
+ class ChildComponent_A extends A_Component {
933
+ @A_Feature.Extend({
934
+ name: 'testFeature',
935
+ })
936
+ async test1() {
937
+ resultChain.push('ChildComponent_A.test');
938
+
939
+ await new Promise<void>(async (resolve, reject) => {
940
+ setTimeout(() => {
941
+ reject(new A_Error('Deliberate Async Error in test1'));
942
+ }, 2000);
943
+ });
944
+ }
945
+ }
946
+
947
+ class ChildComponent_B extends A_Component {
948
+ @A_Feature.Extend({
949
+ name: 'testFeature',
950
+ })
951
+ async test2() {
952
+ resultChain.push('ChildComponent_B.test');
953
+ }
954
+ }
955
+
956
+ const testScope = new A_Scope({ name: 'TestScope', components: [ChildComponent_A, ChildComponent_B] });
957
+ try {
958
+ await Promise.all([
959
+ new Promise<void>(async (resolve) => {
960
+ setTimeout(() => {
961
+ resultChain.push('feature3');
962
+
963
+ resolve();
964
+ }, 1000);
965
+ }),
966
+ testScope.resolve(ChildComponent_A)!.call('testFeature')
967
+ ]);
968
+
969
+ } catch (error) {
970
+ expect(error).toBeInstanceOf(A_Error);
971
+ expect((error as A_Error).originalError).toBeInstanceOf(A_Error)
972
+ expect((error as A_Error).originalError.message).toBe('Deliberate Async Error in test1');
973
+ }
974
+ expect(resultChain).toEqual([
975
+ 'ChildComponent_A.test',
976
+ 'feature3'
977
+ ]);
978
+
979
+ const feature = new A_Feature({
980
+ name: 'testFeature',
981
+ component: testScope.resolve(ChildComponent_A)!,
982
+ })
983
+
984
+ try {
985
+ await feature.process();
986
+ } catch (error) {
987
+ expect(error).toBeInstanceOf(A_Error);
988
+ expect((error as A_Error).originalError).toBeInstanceOf(A_Error)
989
+ expect((error as A_Error).originalError.message).toBe('Deliberate Async Error in test1');
990
+ }
991
+
992
+ expect(feature.state).toBe(A_TYPES__FeatureState.FAILED);
993
+
994
+ expect(resultChain).toEqual([
995
+ 'ChildComponent_A.test',
996
+ 'feature3',
997
+ 'ChildComponent_A.test'
998
+ ]);
999
+ })
1000
+
1001
+ it('Should throw an Async error when executed async and error in method', async () => {
1002
+
1003
+ const resultChain: string[] = [];
1004
+
1005
+ class ChildComponent_A extends A_Component {
1006
+ @A_Feature.Extend({
1007
+ name: 'testFeature',
1008
+ })
1009
+ async test1() {
1010
+ resultChain.push('ChildComponent_A.test');
1011
+
1012
+ throw new A_Error('Deliberate Async Error in test1');
1013
+ }
1014
+ }
1015
+
1016
+ const testScope = new A_Scope({ name: 'TestScope', components: [ChildComponent_A] });
1017
+
1018
+ const feature = new A_Feature({
1019
+ name: 'testFeature',
1020
+ component: testScope.resolve(ChildComponent_A)!,
1021
+ })
1022
+
1023
+ try {
1024
+ await feature.process();
1025
+ } catch (error) {
1026
+ expect(error).toBeInstanceOf(A_Error);
1027
+ expect((error as A_Error).originalError).toBeInstanceOf(A_Error)
1028
+ expect((error as A_Error).originalError.message).toBe('Deliberate Async Error in test1');
1029
+ }
1030
+
1031
+ expect(feature.state).toBe(A_TYPES__FeatureState.FAILED);
1032
+
1033
+ expect(resultChain).toEqual([
1034
+ 'ChildComponent_A.test'
1035
+ ]);
1036
+ })
871
1037
  });
@@ -705,4 +705,109 @@ describe('A-Scope tests', () => {
705
705
  expect(resolvedA).toBeInstanceOf(MyEntity_B);
706
706
  expect(resolvedA?.name).toBe('Entity1');
707
707
  });
708
+
709
+ it('Should deregister entities properly', async () => {
710
+
711
+ class MyEntity extends A_Entity { }
712
+
713
+ class MyFragment extends A_Fragment { }
714
+
715
+ class MyComponent extends A_Component { }
716
+
717
+ const scope = new A_Scope({ name: 'TestScope' });
718
+
719
+
720
+
721
+ const entity = new MyEntity();
722
+ const fragment = new MyFragment();
723
+
724
+ scope.register(entity);
725
+ scope.register(fragment);
726
+ scope.register(MyComponent);
727
+
728
+ expect(scope.has(MyEntity)).toBe(true);
729
+ expect(scope.has(MyFragment)).toBe(true);
730
+ expect(scope.has(MyComponent)).toBe(true);
731
+
732
+ scope.deregister(entity);
733
+ scope.deregister(fragment);
734
+ scope.deregister(MyComponent);
735
+
736
+ expect(scope.has(MyEntity)).toBe(false);
737
+ expect(scope.has(MyFragment)).toBe(false);
738
+ expect(scope.has(MyComponent)).toBe(false);
739
+ });
740
+
741
+ it('Should deregister all entities by class', async () => {
742
+
743
+ class BaseEntity extends A_Entity { }
744
+
745
+ class MyEntityA extends A_Entity { }
746
+ class MyEntityB extends BaseEntity { }
747
+
748
+
749
+ const scope = new A_Scope({ name: 'TestScope' });
750
+
751
+
752
+
753
+ const entityA1 = new MyEntityA();
754
+ const entityA2 = new MyEntityA();
755
+ const entityB1 = new MyEntityB();
756
+
757
+ scope.register(entityA1);
758
+ scope.register(entityA2);
759
+ scope.register(entityB1);
760
+
761
+ expect(scope.resolveAll(MyEntityA).length).toBe(2);
762
+ expect(scope.resolveAll(BaseEntity).length).toBe(1);
763
+
764
+ scope.deregister(MyEntityA);
765
+
766
+ expect(scope.resolveAll(MyEntityA).length).toBe(0);
767
+ expect(scope.resolveAll(BaseEntity).length).toBe(1);
768
+
769
+ const entityB2 = new MyEntityB();
770
+ scope.register(entityB2);
771
+
772
+ expect(scope.resolveAll(BaseEntity).length).toBe(2);
773
+
774
+ scope.deregister(BaseEntity);
775
+
776
+ expect(scope.resolveAll(BaseEntity).length).toBe(0);
777
+ });
778
+ it('Should register/deregister fragments with the same names', async () => {
779
+
780
+ class MyFragment extends A_Fragment {
781
+
782
+ array: string[] = [];
783
+
784
+ constructor(name: string) {
785
+ super({ name });
786
+ }
787
+ }
788
+
789
+ const scope = new A_Scope({ name: 'TestScope' });
790
+
791
+ const fragmentA1 = new MyFragment('fragmentA');
792
+
793
+ scope.register(fragmentA1);
794
+
795
+ expect(scope.has(MyFragment)).toBe(true);
796
+ expect(scope.resolve(MyFragment)).toBe(fragmentA1);
797
+
798
+ scope.deregister(fragmentA1);
799
+
800
+ fragmentA1.array.push('newData');
801
+
802
+
803
+ expect(scope.has(MyFragment)).toBe(false);
804
+
805
+ const fragmentA2 = new MyFragment('fragmentA');
806
+
807
+ scope.register(fragmentA2);
808
+
809
+ expect(scope.has(MyFragment)).toBe(true);
810
+ expect(scope.resolve(MyFragment)).toBe(fragmentA2);
811
+ expect(fragmentA2.array.length).toBe(0);
812
+ });
708
813
  });