@nymphjs/nymph 1.0.0-beta.57 → 1.0.0-beta.59

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +0 -8
  3. package/dist/Entity.d.ts +65 -6
  4. package/dist/Entity.js +53 -1
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +1 -0
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +15 -70
  9. package/dist/Nymph.d.ts +14 -6
  10. package/dist/Nymph.js +37 -11
  11. package/dist/Nymph.js.map +1 -1
  12. package/dist/Nymph.types.d.ts +2 -2
  13. package/dist/driver/NymphDriver.d.ts +47 -11
  14. package/dist/driver/NymphDriver.js +28 -5
  15. package/dist/driver/NymphDriver.js.map +1 -1
  16. package/dist/driver/testArtifacts.d.ts +1 -1
  17. package/dist/driver/testArtifacts.js +77 -3
  18. package/dist/driver/testArtifacts.js.map +1 -1
  19. package/dist/errors/EntityUniqueConstraintError.d.ts +3 -0
  20. package/dist/errors/EntityUniqueConstraintError.js +11 -0
  21. package/dist/errors/EntityUniqueConstraintError.js.map +1 -0
  22. package/dist/errors/index.d.ts +1 -0
  23. package/dist/errors/index.js +1 -0
  24. package/dist/errors/index.js.map +1 -1
  25. package/dist/testArtifacts.d.ts +2 -6
  26. package/dist/testArtifacts.js +4 -18
  27. package/dist/testArtifacts.js.map +1 -1
  28. package/dist/testMocks.d.ts +9 -7
  29. package/dist/testMocks.js +19 -10
  30. package/dist/testMocks.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/Entity.test.ts +1 -0
  33. package/src/Entity.ts +113 -15
  34. package/src/Entity.types.ts +18 -70
  35. package/src/Nymph.ts +55 -15
  36. package/src/Nymph.types.ts +2 -2
  37. package/src/driver/NymphDriver.ts +74 -67
  38. package/src/driver/testArtifacts.ts +102 -1
  39. package/src/errors/EntityUniqueConstraintError.ts +6 -0
  40. package/src/errors/index.ts +2 -0
  41. package/src/testArtifacts.ts +8 -27
  42. package/src/testMocks.ts +32 -17
@@ -5,7 +5,9 @@ import strtotime from 'locutus/php/datetime/strtotime';
5
5
  import { guid } from '@nymphjs/guid';
6
6
 
7
7
  import type Nymph from '../Nymph';
8
- import {
8
+ import type { Selector, Options, FormattedSelector } from '../Nymph.types';
9
+ import Entity, { type EntityInstanceType } from '../Entity';
10
+ import type {
9
11
  EntityConstructor,
10
12
  EntityData,
11
13
  EntityInterface,
@@ -13,7 +15,6 @@ import {
13
15
  SerializedEntityData,
14
16
  } from '../Entity.types';
15
17
  import { InvalidParametersError, UnableToConnectError } from '../errors';
16
- import { Selector, Options, FormattedSelector } from '../Nymph.types';
17
18
  import { xor } from '../utils';
18
19
 
19
20
  // from: https://stackoverflow.com/a/6969486/664915
@@ -97,11 +98,11 @@ export default abstract class NymphDriver {
97
98
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
98
99
  options?: Options<T>,
99
100
  ...selectors: Selector[]
100
- ): Promise<ReturnType<T['factorySync']>[]>;
101
+ ): Promise<EntityInstanceType<T>[]>;
101
102
  abstract getEntities<T extends EntityConstructor = EntityConstructor>(
102
103
  options?: Options<T>,
103
104
  ...selectors: Selector[]
104
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number>;
105
+ ): Promise<EntityInstanceType<T>[] | string[] | number>;
105
106
  abstract getUID(name: string): Promise<number | null>;
106
107
  abstract import(filename: string, transaction?: boolean): Promise<boolean>;
107
108
  abstract newUID(name: string): Promise<number | null>;
@@ -662,12 +663,12 @@ export default abstract class NymphDriver {
662
663
 
663
664
  protected iterateSelectorsForQuery(
664
665
  selectors: FormattedSelector[],
665
- callback: (
666
- key: string,
667
- value: any,
668
- typeIsOr: boolean,
669
- typeIsNot: boolean,
670
- ) => string,
666
+ callback: (data: {
667
+ key: string;
668
+ value: any;
669
+ typeIsOr: boolean;
670
+ typeIsNot: boolean;
671
+ }) => string,
671
672
  ) {
672
673
  const queryParts = [];
673
674
  for (const curSelector of selectors) {
@@ -680,7 +681,7 @@ export default abstract class NymphDriver {
680
681
  if (key === 'type') {
681
682
  continue;
682
683
  }
683
- let curQuery = callback(key, value, typeIsOr, typeIsNot);
684
+ let curQuery = callback({ key, value, typeIsOr, typeIsNot });
684
685
  if (curQuery) {
685
686
  if (curSelectorQuery) {
686
687
  curSelectorQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -699,11 +700,11 @@ export default abstract class NymphDriver {
699
700
  protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
700
701
  options: Options<T> & { return: 'count' },
701
702
  selectors: Selector[],
702
- performQueryCallback: (
703
- options: Options<T>,
704
- formattedSelectors: FormattedSelector[],
705
- etype: string,
706
- ) => {
703
+ performQueryCallback: (data: {
704
+ options: Options<T>;
705
+ selectors: FormattedSelector[];
706
+ etype: string;
707
+ }) => {
707
708
  result: any;
708
709
  },
709
710
  rowFetchCallback: () => any,
@@ -723,11 +724,11 @@ export default abstract class NymphDriver {
723
724
  protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
724
725
  options: Options<T> & { return: 'guid' },
725
726
  selectors: Selector[],
726
- performQueryCallback: (
727
- options: Options<T>,
728
- formattedSelectors: FormattedSelector[],
729
- etype: string,
730
- ) => {
727
+ performQueryCallback: (data: {
728
+ options: Options<T>;
729
+ selectors: FormattedSelector[];
730
+ etype: string;
731
+ }) => {
731
732
  result: any;
732
733
  },
733
734
  rowFetchCallback: () => any,
@@ -747,11 +748,11 @@ export default abstract class NymphDriver {
747
748
  protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
748
749
  options: Options<T>,
749
750
  selectors: Selector[],
750
- performQueryCallback: (
751
- options: Options<T>,
752
- formattedSelectors: FormattedSelector[],
753
- etype: string,
754
- ) => {
751
+ performQueryCallback: (data: {
752
+ options: Options<T>;
753
+ selectors: FormattedSelector[];
754
+ etype: string;
755
+ }) => {
755
756
  result: any;
756
757
  },
757
758
  rowFetchCallback: () => any,
@@ -767,15 +768,18 @@ export default abstract class NymphDriver {
767
768
  name: string;
768
769
  svalue: string;
769
770
  },
770
- ): { result: any; process: () => ReturnType<T['factorySync']>[] | Error };
771
+ ): {
772
+ result: any;
773
+ process: () => EntityInstanceType<T>[] | Error;
774
+ };
771
775
  protected getEntitiesRowLike<T extends EntityConstructor = EntityConstructor>(
772
776
  options: Options<T>,
773
777
  selectors: Selector[],
774
- performQueryCallback: (
775
- options: Options<T>,
776
- formattedSelectors: FormattedSelector[],
777
- etype: string,
778
- ) => {
778
+ performQueryCallback: (data: {
779
+ options: Options<T>;
780
+ selectors: FormattedSelector[];
781
+ etype: string;
782
+ }) => {
779
783
  result: any;
780
784
  },
781
785
  rowFetchCallback: () => any,
@@ -793,7 +797,7 @@ export default abstract class NymphDriver {
793
797
  },
794
798
  ): {
795
799
  result: any;
796
- process: () => ReturnType<T['factorySync']>[] | string[] | number | Error;
800
+ process: () => EntityInstanceType<T>[] | string[] | number | Error;
797
801
  } {
798
802
  if (!this.isConnected()) {
799
803
  throw new UnableToConnectError('not connected to DB');
@@ -811,7 +815,7 @@ export default abstract class NymphDriver {
811
815
  }
812
816
  }
813
817
 
814
- let entities: ReturnType<T['factorySync']>[] | string[] = [];
818
+ let entities: EntityInstanceType<T>[] | string[] = [];
815
819
  let count = 0;
816
820
  const EntityClass =
817
821
  options.class ?? (this.nymph.getEntityClass('Entity') as T);
@@ -833,7 +837,7 @@ export default abstract class NymphDriver {
833
837
  (Object.keys(selectors[0]).length === 2 ||
834
838
  (Object.keys(selectors[0]).length === 3 && 'tag' in selectors[0]))
835
839
  ) {
836
- const entity = this.pullCache<ReturnType<T['factorySync']>>(
840
+ const entity = this.pullCache<EntityInstanceType<T>>(
837
841
  selectors[0]['guid'],
838
842
  EntityClass.class,
839
843
  !!options.skipAc,
@@ -871,11 +875,11 @@ export default abstract class NymphDriver {
871
875
  const [options, ...selectors] = qref;
872
876
  this.nymph.runQueryCallbacks(options, selectors);
873
877
  }
874
- const { result } = performQueryCallback(
878
+ const { result } = performQueryCallback({
875
879
  options,
876
- formattedSelectors.selectors,
880
+ selectors: formattedSelectors.selectors,
877
881
  etype,
878
- );
882
+ });
879
883
 
880
884
  return {
881
885
  result,
@@ -915,9 +919,7 @@ export default abstract class NymphDriver {
915
919
  // Make sure that $row is incremented :)
916
920
  row = rowFetchCallback();
917
921
  }
918
- const entity = EntityClass.factorySync() as ReturnType<
919
- T['factorySync']
920
- >;
922
+ const entity = EntityClass.factorySync() as EntityInstanceType<T>;
921
923
  entity.$nymph = this.nymph;
922
924
  if (options.skipAc != null) {
923
925
  entity.$useSkipAc(!!options.skipAc);
@@ -957,24 +959,26 @@ export default abstract class NymphDriver {
957
959
 
958
960
  protected async saveEntityRowLike(
959
961
  entity: EntityInterface,
960
- saveNewEntityCallback: (
961
- entity: EntityInterface,
962
- guid: string,
963
- tags: string[],
964
- data: EntityData,
965
- sdata: SerializedEntityData,
966
- cdate: number,
967
- etype: string,
968
- ) => Promise<boolean>,
969
- saveExistingEntityCallback: (
970
- entity: EntityInterface,
971
- guid: string,
972
- tags: string[],
973
- data: EntityData,
974
- sdata: SerializedEntityData,
975
- mdate: number,
976
- etype: string,
977
- ) => Promise<boolean>,
962
+ saveNewEntityCallback: (data: {
963
+ entity: EntityInterface;
964
+ guid: string;
965
+ tags: string[];
966
+ data: EntityData;
967
+ sdata: SerializedEntityData;
968
+ uniques: string[];
969
+ cdate: number;
970
+ etype: string;
971
+ }) => Promise<boolean>,
972
+ saveExistingEntityCallback: (data: {
973
+ entity: EntityInterface;
974
+ guid: string;
975
+ tags: string[];
976
+ data: EntityData;
977
+ sdata: SerializedEntityData;
978
+ uniques: string[];
979
+ mdate: number;
980
+ etype: string;
981
+ }) => Promise<boolean>,
978
982
  startTransactionCallback: (() => Promise<void>) | null = null,
979
983
  commitTransactionCallback:
980
984
  | ((success: boolean) => Promise<boolean>)
@@ -985,6 +989,7 @@ export default abstract class NymphDriver {
985
989
  const tags = difference(entity.tags, ['']);
986
990
  const data = entity.$getData();
987
991
  const sdata = entity.$getSData();
992
+ const uniques = await entity.$getUniques();
988
993
  const EntityClass = entity.constructor as EntityConstructor;
989
994
  const etype = EntityClass.ETYPE;
990
995
  if (startTransactionCallback) {
@@ -994,15 +999,16 @@ export default abstract class NymphDriver {
994
999
  if (entity.guid == null) {
995
1000
  const cdate = mdate;
996
1001
  const newId = guid();
997
- success = await saveNewEntityCallback(
1002
+ success = await saveNewEntityCallback({
998
1003
  entity,
999
- newId,
1004
+ guid: newId,
1000
1005
  tags,
1001
1006
  data,
1002
1007
  sdata,
1008
+ uniques,
1003
1009
  cdate,
1004
1010
  etype,
1005
- );
1011
+ });
1006
1012
  if (success) {
1007
1013
  entity.guid = newId;
1008
1014
  entity.cdate = cdate;
@@ -1013,15 +1019,16 @@ export default abstract class NymphDriver {
1013
1019
  if (this.nymph.config.cache) {
1014
1020
  this.cleanCache(entity.guid);
1015
1021
  }
1016
- success = await saveExistingEntityCallback(
1022
+ success = await saveExistingEntityCallback({
1017
1023
  entity,
1018
- entity.guid,
1024
+ guid: entity.guid,
1019
1025
  tags,
1020
1026
  data,
1021
1027
  sdata,
1028
+ uniques,
1022
1029
  mdate,
1023
1030
  etype,
1024
- );
1031
+ });
1025
1032
  if (success) {
1026
1033
  entity.mdate = mdate;
1027
1034
  }
@@ -1051,7 +1058,7 @@ export default abstract class NymphDriver {
1051
1058
  * @param useSkipAc Whether to tell the entity to use skip_ac.
1052
1059
  * @returns The entity or null if it's not cached.
1053
1060
  */
1054
- protected pullCache<T extends EntityInterface = EntityInterface>(
1061
+ protected pullCache<T extends Entity>(
1055
1062
  guid: string,
1056
1063
  className: string,
1057
1064
  useSkipAc = false,
@@ -1125,7 +1132,7 @@ export default abstract class NymphDriver {
1125
1132
  // Find which entity has been accessed the least.
1126
1133
  const least =
1127
1134
  Object.entries(this.entityCount)
1128
- .sort(([guidA, countA], [guidB, countB]) => countA - countB)
1135
+ .sort(([_guidA, countA], [_guidB, countB]) => countA - countB)
1129
1136
  .pop()?.[0] ?? null;
1130
1137
  if (least == null) {
1131
1138
  // This should never happen.
@@ -3,6 +3,7 @@ import strtotime from 'locutus/php/datetime/strtotime';
3
3
  import { guid } from '@nymphjs/guid';
4
4
 
5
5
  import type Nymph from '../Nymph';
6
+ import { EntityUniqueConstraintError } from '../errors';
6
7
  import {
7
8
  TestBModel as TestBModelClass,
8
9
  TestModel as TestModelClass,
@@ -10,7 +11,7 @@ import {
10
11
  TestEmptyModel as TestEmptyModelClass,
11
12
  } from '../testArtifacts';
12
13
 
13
- export function QueriesTest(
14
+ export function EntitiesTest(
14
15
  nymph: Nymph,
15
16
  it: (name: string, fn: () => void) => void,
16
17
  ) {
@@ -1804,6 +1805,106 @@ These hats are absolutely fantastic.`;
1804
1805
 
1805
1806
  expect(entity).toBeNull();
1806
1807
  });
1808
+
1809
+ it('allows non-duplicate unique strings', async () => {
1810
+ // Creating entity...
1811
+ const uniqueEntityA = await TestModel.factory();
1812
+ uniqueEntityA.name = 'Entity Test ' + new Date().toLocaleString();
1813
+ uniqueEntityA.uniques = ['test a'];
1814
+
1815
+ // Saving entity...
1816
+ expect(await uniqueEntityA.$save()).toEqual(true);
1817
+ expect(uniqueEntityA.guid).not.toBeNull();
1818
+
1819
+ // Creating entity...
1820
+ const uniqueEntityB = await TestModel.factory();
1821
+ uniqueEntityB.name = 'Entity Test ' + new Date().toLocaleString();
1822
+ uniqueEntityB.uniques = ['test b'];
1823
+
1824
+ // Saving entity...
1825
+ expect(await uniqueEntityB.$save()).toEqual(true);
1826
+ expect(uniqueEntityB.guid).not.toBeNull();
1827
+
1828
+ expect(uniqueEntityA.guid).not.toEqual(uniqueEntityB.guid);
1829
+
1830
+ expect(await uniqueEntityA.$delete()).toEqual(true);
1831
+ expect(await uniqueEntityB.$delete()).toEqual(true);
1832
+ });
1833
+
1834
+ it('throws on duplicate unique strings', async () => {
1835
+ // Creating entity...
1836
+ const uniqueEntityA = await TestModel.factory();
1837
+ uniqueEntityA.name = 'Entity Test ' + new Date().toLocaleString();
1838
+ uniqueEntityA.uniques = ['test a'];
1839
+
1840
+ // Saving entity...
1841
+ expect(await uniqueEntityA.$save()).toEqual(true);
1842
+ expect(uniqueEntityA.guid).not.toBeNull();
1843
+
1844
+ // Creating entity...
1845
+ const uniqueEntityB = await TestModel.factory();
1846
+ uniqueEntityB.name = 'Entity Test ' + new Date().toLocaleString();
1847
+ uniqueEntityB.uniques = ['test a'];
1848
+
1849
+ // Saving entity...
1850
+ try {
1851
+ await uniqueEntityB.$save();
1852
+
1853
+ throw Error("Shouldn't get past the uniqueness check.");
1854
+ } catch (e: any) {
1855
+ expect(e).toBeInstanceOf(EntityUniqueConstraintError);
1856
+ }
1857
+ expect(uniqueEntityB.guid).toBeNull();
1858
+
1859
+ expect(await uniqueEntityA.$delete()).toEqual(true);
1860
+ });
1861
+
1862
+ it('allows duplicate unique string if first transaction is rolled back', async () => {
1863
+ // Creating transaction entity...
1864
+ const tnymphA = await nymph.startTransaction('test-unique-a');
1865
+ const TestModelA = tnymphA.getEntityClass(TestModel);
1866
+ const uniqueEntityA = await TestModelA.factory();
1867
+ uniqueEntityA.name = 'Entity Test ' + new Date().toLocaleString();
1868
+ uniqueEntityA.uniques = ['test a'];
1869
+
1870
+ // Saving entity...
1871
+ expect(await uniqueEntityA.$save()).toEqual(true);
1872
+ expect(uniqueEntityA.guid).not.toBeNull();
1873
+
1874
+ // Rolling back transaction...
1875
+ expect(await tnymphA.rollback('test-unique-a')).toEqual(true);
1876
+
1877
+ // Creating transaction entity...
1878
+ const tnymphB = await nymph.startTransaction('test-unique-b');
1879
+ const TestModelB = tnymphB.getEntityClass(TestModel);
1880
+ const uniqueEntityB = await TestModelB.factory();
1881
+ uniqueEntityB.name = 'Entity Test ' + new Date().toLocaleString();
1882
+ uniqueEntityB.uniques = ['test a'];
1883
+
1884
+ // Saving entity...
1885
+ expect(await uniqueEntityB.$save()).toEqual(true);
1886
+ expect(uniqueEntityB.guid).not.toBeNull();
1887
+
1888
+ expect(uniqueEntityA.guid).not.toEqual(uniqueEntityB.guid);
1889
+
1890
+ expect(await tnymphB.commit('test-unique-b')).toEqual(true);
1891
+
1892
+ const uniqueEntityACheck = await nymph.getEntity(
1893
+ { class: TestModel },
1894
+ { type: '&', guid: uniqueEntityA.guid ?? '' },
1895
+ );
1896
+ expect(uniqueEntityACheck).toBeNull();
1897
+
1898
+ const uniqueEntityBCheck = await nymph.getEntity(
1899
+ { class: TestModel },
1900
+ { type: '&', guid: uniqueEntityB.guid ?? '' },
1901
+ );
1902
+ expect(uniqueEntityBCheck).not.toBeNull();
1903
+
1904
+ if (uniqueEntityBCheck != null) {
1905
+ expect(await uniqueEntityBCheck.$delete()).toEqual(true);
1906
+ }
1907
+ });
1807
1908
  }
1808
1909
 
1809
1910
  export function UIDTest(
@@ -0,0 +1,6 @@
1
+ export class EntityUniqueConstraintError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'EntityUniqueConstraintError';
5
+ }
6
+ }
@@ -6,6 +6,8 @@ export * from './EntityInvalidDataError';
6
6
 
7
7
  export * from './EntityIsSleepingReferenceError';
8
8
 
9
+ export * from './EntityUniqueConstraintError';
10
+
9
11
  export * from './InvalidParametersError';
10
12
 
11
13
  export * from './InvalidStateError';
@@ -17,8 +17,10 @@ export type TestModelData = {
17
17
  [k: string]: TestModel & TestModelData;
18
18
  };
19
19
  parent?: TestModel & TestModelData;
20
- // For Import/Export Tests.
20
+ // For import/export tests.
21
21
  index?: string;
22
+ // For uniqueness tests.
23
+ uniques?: string[];
22
24
  };
23
25
 
24
26
  /**
@@ -34,19 +36,16 @@ export class TestModel extends Entity<TestModelData> {
34
36
  protected $protectedTags = ['test', 'notag'];
35
37
  protected $allowlistTags? = ['newtag'];
36
38
 
37
- static async factory(guid?: string): Promise<TestModel & TestModelData> {
38
- return (await super.factory(guid)) as TestModel & TestModelData;
39
- }
40
-
41
- static factorySync(): TestModel & TestModelData {
42
- return super.factorySync() as TestModel & TestModelData;
43
- }
44
-
45
39
  constructor() {
46
40
  super();
47
41
 
48
42
  this.$addTag('test');
49
43
  this.$data.boolean = true;
44
+ this.$data.uniques = [];
45
+ }
46
+
47
+ public async $getUniques() {
48
+ return this.$data.uniques ?? [];
50
49
  }
51
50
 
52
51
  public $useProtectedData() {
@@ -75,14 +74,6 @@ export class TestModel extends Entity<TestModelData> {
75
74
  export class TestBModel extends TestModel {
76
75
  static ETYPE = 'test_b_model';
77
76
  static class = 'TestBModel';
78
-
79
- static async factory(guid?: string): Promise<TestBModel & TestModelData> {
80
- return (await super.factory(guid)) as TestBModel & TestModelData;
81
- }
82
-
83
- static factorySync(): TestBModel & TestModelData {
84
- return super.factorySync() as TestBModel & TestModelData;
85
- }
86
77
  }
87
78
 
88
79
  export type TestEmptyModelData = {};
@@ -93,14 +84,4 @@ export type TestEmptyModelData = {};
93
84
  export class TestEmptyModel extends Entity<TestEmptyModelData> {
94
85
  static ETYPE = 'test_empty_model';
95
86
  static class = 'TestEmptyModel';
96
-
97
- static async factory(
98
- guid?: string,
99
- ): Promise<TestEmptyModel & TestEmptyModelData> {
100
- return (await super.factory(guid)) as TestEmptyModel & TestEmptyModelData;
101
- }
102
-
103
- static factorySync(): TestEmptyModel & TestEmptyModelData {
104
- return super.factorySync() as TestEmptyModel & TestEmptyModelData;
105
- }
106
87
  }
package/src/testMocks.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { guid } from '@nymphjs/guid';
2
2
 
3
3
  import type Nymph from './Nymph';
4
- import { EntityConstructor, EntityInterface, EntityJson } from './Entity.types';
4
+ import type { EntityInstanceType } from './Entity';
5
+ import type {
6
+ EntityConstructor,
7
+ EntityInterface,
8
+ EntityJson,
9
+ } from './Entity.types';
5
10
  import { Options, Selector } from './Nymph.types';
6
11
  import { ClassNotAvailableError } from './errors';
7
12
  import Entity from './Entity';
@@ -22,7 +27,7 @@ export class MockNymphDriver {
22
27
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
23
28
  options: Options<T>,
24
29
  ...selectors: Selector[]
25
- ): Promise<ReturnType<T['factorySync']> | null>;
30
+ ): Promise<EntityInstanceType<T> | null>;
26
31
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
27
32
  options: Options<T> & { return: 'guid' },
28
33
  guid: string,
@@ -30,19 +35,19 @@ export class MockNymphDriver {
30
35
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
31
36
  options: Options<T>,
32
37
  guid: string,
33
- ): Promise<ReturnType<T['factorySync']> | null>;
38
+ ): Promise<EntityInstanceType<T> | null>;
34
39
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
35
40
  options: Options<T> = {},
36
41
  ...selectors: Selector[] | string[]
37
- ): Promise<ReturnType<T['factorySync']> | string | null> {
42
+ ): Promise<EntityInstanceType<T> | string | null> {
38
43
  const guid =
39
44
  typeof selectors[0] === 'string' ? selectors[0] : selectors[0].guid;
40
45
  if (!options || !guid || typeof guid !== 'string' || !(guid in entities)) {
41
46
  return null;
42
47
  }
43
48
 
44
- const entity: EntityInterface = (
45
- (options.class as T) ?? this.nymph.getEntityClass('Entity')
49
+ const entity = (
50
+ (options.class ?? this.nymph.getEntityClass('Entity')) as T
46
51
  ).factorySync();
47
52
  const entityJson = entities[guid];
48
53
  entity.guid = guid;
@@ -51,7 +56,7 @@ export class MockNymphDriver {
51
56
  entity.tags = entityJson.tags;
52
57
  entity.$putData(entityJson.data, {});
53
58
 
54
- return entity as ReturnType<T['factorySync']>;
59
+ return entity as EntityInstanceType<T>;
55
60
  }
56
61
  }
57
62
 
@@ -68,21 +73,20 @@ export class MockNymph {
68
73
  this.Entity = this.addEntityClass(Entity);
69
74
  }
70
75
 
71
- public addEntityClass<T extends EntityConstructor>(entityClass: T): T {
76
+ public addEntityClass<T extends EntityConstructor>(EntityClass: T): T {
72
77
  const nymph = this;
73
- class NymphEntity extends entityClass {
78
+ this.entityClasses[EntityClass.class] = class extends EntityClass {
74
79
  static nymph: Nymph = nymph as unknown as Nymph;
75
80
 
76
81
  constructor(...args: any[]) {
77
82
  super(...args);
78
83
  }
79
- }
80
- this.entityClasses[entityClass.class] = NymphEntity;
81
- return NymphEntity;
84
+ };
85
+ return this.entityClasses[EntityClass.class] as T;
82
86
  }
83
87
 
84
- public getEntityClass<T extends EntityConstructor>(className: T): T;
85
88
  public getEntityClass(className: string): EntityConstructor;
89
+ public getEntityClass<T extends EntityConstructor>(className: T): T;
86
90
  public getEntityClass<T extends EntityConstructor = EntityConstructor>(
87
91
  className: T | string,
88
92
  ): T | EntityConstructor {
@@ -95,7 +99,18 @@ export class MockNymph {
95
99
  if (key in this.entityClasses) {
96
100
  return this.entityClasses[key];
97
101
  }
98
- throw new ClassNotAvailableError('Tried to use class: ' + key);
102
+ throw new ClassNotAvailableError(`Tried to use missing class "${key}".`);
103
+ }
104
+
105
+ public getEntityClassByEtype(etype: string): EntityConstructor {
106
+ for (const EntityClass of Object.values(this.entityClasses)) {
107
+ if (EntityClass.ETYPE === etype) {
108
+ return EntityClass;
109
+ }
110
+ }
111
+ throw new ClassNotAvailableError(
112
+ `Tried to use missing class by etype "${etype}".`,
113
+ );
99
114
  }
100
115
 
101
116
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
@@ -105,7 +120,7 @@ export class MockNymph {
105
120
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
106
121
  options: Options<T>,
107
122
  ...selectors: Selector[]
108
- ): Promise<ReturnType<T['factorySync']> | null>;
123
+ ): Promise<EntityInstanceType<T> | null>;
109
124
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
110
125
  options: Options<T> & { return: 'guid' },
111
126
  guid: string,
@@ -113,11 +128,11 @@ export class MockNymph {
113
128
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
114
129
  options: Options<T>,
115
130
  guid: string,
116
- ): Promise<ReturnType<T['factorySync']> | null>;
131
+ ): Promise<EntityInstanceType<T> | null>;
117
132
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
118
133
  options: Options<T> = {},
119
134
  ...selectors: Selector[] | string[]
120
- ): Promise<ReturnType<T['factorySync']> | string | null> {
135
+ ): Promise<EntityInstanceType<T> | string | null> {
121
136
  // @ts-ignore: The selector type is correct here.
122
137
  return await this.driver.getEntity(options, ...selectors);
123
138
  }