@nymphjs/nymph 1.0.0-beta.2 → 1.0.0-beta.21

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.
@@ -9,18 +9,31 @@ export type NymphEventType =
9
9
  | 'beforeGetEntities'
10
10
  | 'beforeSaveEntity'
11
11
  | 'afterSaveEntity'
12
+ | 'failedSaveEntity'
12
13
  | 'beforeDeleteEntity'
13
14
  | 'afterDeleteEntity'
15
+ | 'failedDeleteEntity'
14
16
  | 'beforeDeleteEntityByID'
15
17
  | 'afterDeleteEntityByID'
18
+ | 'failedDeleteEntityByID'
16
19
  | 'beforeNewUID'
17
20
  | 'afterNewUID'
21
+ | 'failedNewUID'
18
22
  | 'beforeSetUID'
19
23
  | 'afterSetUID'
24
+ | 'failedSetUID'
20
25
  | 'beforeRenameUID'
21
26
  | 'afterRenameUID'
27
+ | 'failedRenameUID'
22
28
  | 'beforeDeleteUID'
23
- | 'afterDeleteUID';
29
+ | 'afterDeleteUID'
30
+ | 'failedDeleteUID'
31
+ | 'beforeStartTransaction'
32
+ | 'afterStartTransaction'
33
+ | 'beforeCommitTransaction'
34
+ | 'afterCommitTransaction'
35
+ | 'beforeRollbackTransaction'
36
+ | 'afterRollbackTransaction';
24
37
  export type NymphConnectCallback = (
25
38
  nymph: Nymph,
26
39
  result: Promise<boolean>
@@ -29,12 +42,22 @@ export type NymphDisconnectCallback = (
29
42
  nymph: Nymph,
30
43
  result: Promise<boolean>
31
44
  ) => Promise<void>;
32
- /** The NymphQueryCallback will be called on both top level and qref queries. */
45
+ /**
46
+ * The NymphQueryCallback will be called on both top level and qref queries.
47
+ *
48
+ * This is the only callback that is not asynchronous.
49
+ *
50
+ * This also isn't necessarily run on every "query". It is run before a database
51
+ * query for an entity, but it is not run during PubSub entity change
52
+ * propagation. Therefore, it shouldn't do anything _entity_ specific, instead
53
+ * it should be _query_ specific. For example, throw an error if the user is not
54
+ * permitted to run the query at all.
55
+ */
33
56
  export type NymphQueryCallback = (
34
57
  nymph: Nymph,
35
58
  options: Options,
36
59
  selectors: FormattedSelector[]
37
- ) => Promise<void>;
60
+ ) => void;
38
61
  export type NymphBeforeGetEntityCallback = (
39
62
  nymph: Nymph,
40
63
  options: Options,
@@ -53,6 +76,10 @@ export type NymphAfterSaveEntityCallback = (
53
76
  nymph: Nymph,
54
77
  result: Promise<boolean>
55
78
  ) => Promise<void>;
79
+ export type NymphFailedSaveEntityCallback = (
80
+ nymph: Nymph,
81
+ error: any
82
+ ) => Promise<void>;
56
83
  export type NymphBeforeDeleteEntityCallback = (
57
84
  nymph: Nymph,
58
85
  entity: EntityInterface
@@ -61,6 +88,10 @@ export type NymphAfterDeleteEntityCallback = (
61
88
  nymph: Nymph,
62
89
  result: Promise<boolean>
63
90
  ) => Promise<void>;
91
+ export type NymphFailedDeleteEntityCallback = (
92
+ nymph: Nymph,
93
+ error: any
94
+ ) => Promise<void>;
64
95
  export type NymphBeforeDeleteEntityByIDCallback = (
65
96
  nymph: Nymph,
66
97
  guid: string,
@@ -70,6 +101,10 @@ export type NymphAfterDeleteEntityByIDCallback = (
70
101
  nymph: Nymph,
71
102
  result: Promise<boolean>
72
103
  ) => Promise<void>;
104
+ export type NymphFailedDeleteEntityByIDCallback = (
105
+ nymph: Nymph,
106
+ error: any
107
+ ) => Promise<void>;
73
108
  export type NymphBeforeNewUIDCallback = (
74
109
  nymph: Nymph,
75
110
  name: string
@@ -78,6 +113,10 @@ export type NymphAfterNewUIDCallback = (
78
113
  nymph: Nymph,
79
114
  result: Promise<number | null>
80
115
  ) => Promise<void>;
116
+ export type NymphFailedNewUIDCallback = (
117
+ nymph: Nymph,
118
+ error: any
119
+ ) => Promise<void>;
81
120
  export type NymphBeforeSetUIDCallback = (
82
121
  nymph: Nymph,
83
122
  name: string,
@@ -87,6 +126,10 @@ export type NymphAfterSetUIDCallback = (
87
126
  nymph: Nymph,
88
127
  result: Promise<boolean>
89
128
  ) => Promise<void>;
129
+ export type NymphFailedSetUIDCallback = (
130
+ nymph: Nymph,
131
+ error: any
132
+ ) => Promise<void>;
90
133
  export type NymphBeforeRenameUIDCallback = (
91
134
  nymph: Nymph,
92
135
  oldName: string,
@@ -96,6 +139,10 @@ export type NymphAfterRenameUIDCallback = (
96
139
  nymph: Nymph,
97
140
  result: Promise<boolean>
98
141
  ) => Promise<void>;
142
+ export type NymphFailedRenameUIDCallback = (
143
+ nymph: Nymph,
144
+ error: any
145
+ ) => Promise<void>;
99
146
  export type NymphBeforeDeleteUIDCallback = (
100
147
  nymph: Nymph,
101
148
  name: string
@@ -104,13 +151,44 @@ export type NymphAfterDeleteUIDCallback = (
104
151
  nymph: Nymph,
105
152
  result: Promise<boolean>
106
153
  ) => Promise<void>;
154
+ export type NymphFailedDeleteUIDCallback = (
155
+ nymph: Nymph,
156
+ error: any
157
+ ) => Promise<void>;
158
+ export type NymphBeforeStartTransactionCallback = (
159
+ nymph: Nymph,
160
+ name: string
161
+ ) => Promise<void>;
162
+ export type NymphAfterStartTransactionCallback = (
163
+ nymph: Nymph,
164
+ name: string,
165
+ result: Nymph
166
+ ) => Promise<void>;
167
+ export type NymphBeforeCommitTransactionCallback = (
168
+ nymph: Nymph,
169
+ name: string
170
+ ) => Promise<void>;
171
+ export type NymphAfterCommitTransactionCallback = (
172
+ nymph: Nymph,
173
+ name: string,
174
+ result: boolean
175
+ ) => Promise<void>;
176
+ export type NymphBeforeRollbackTransactionCallback = (
177
+ nymph: Nymph,
178
+ name: string
179
+ ) => Promise<void>;
180
+ export type NymphAfterRollbackTransactionCallback = (
181
+ nymph: Nymph,
182
+ name: string,
183
+ result: boolean
184
+ ) => Promise<void>;
107
185
 
108
186
  export type Options<T extends EntityConstructor = EntityConstructor> = {
109
187
  class?: T;
110
188
  limit?: number;
111
189
  offset?: number;
112
190
  reverse?: boolean;
113
- sort?: 'cdate' | 'mdate';
191
+ sort?: 'cdate' | 'mdate' | string;
114
192
  return?: 'entity' | 'guid' | 'count';
115
193
  source?: string;
116
194
  skipCache?: boolean;
@@ -54,7 +54,18 @@ export default abstract class NymphDriver {
54
54
  */
55
55
  private putDataCounter = 0;
56
56
 
57
+ /**
58
+ * This is used internally by Nymph. Don't call it yourself.
59
+ *
60
+ * @returns A clone of this instance.
61
+ */
62
+ abstract clone(): NymphDriver;
63
+
64
+ /**
65
+ * Connect to the data store.
66
+ */
57
67
  abstract connect(): Promise<boolean>;
68
+
58
69
  abstract isConnected(): boolean;
59
70
  abstract startTransaction(name: string): Promise<Nymph>;
60
71
  abstract commit(name: string): Promise<boolean>;
@@ -66,7 +77,12 @@ export default abstract class NymphDriver {
66
77
  ): Promise<boolean>;
67
78
  abstract deleteEntityByID(guid: string, className?: string): Promise<boolean>;
68
79
  abstract deleteUID(name: string): Promise<boolean>;
80
+
81
+ /**
82
+ * Disconnect from the data store.
83
+ */
69
84
  abstract disconnect(): Promise<boolean>;
85
+
70
86
  protected abstract exportEntities(
71
87
  writeLine: (line: string) => void
72
88
  ): Promise<void>;
@@ -283,7 +299,7 @@ export default abstract class NymphDriver {
283
299
  guid: string | null = null,
284
300
  tags: string[] | null = null
285
301
  ) {
286
- const formattedSelectors = this.formatSelectors(selectors);
302
+ const formattedSelectors = this.formatSelectors(selectors).selectors;
287
303
 
288
304
  for (const curSelector of formattedSelectors) {
289
305
  const type = curSelector.type;
@@ -575,8 +591,12 @@ export default abstract class NymphDriver {
575
591
  public formatSelectors(
576
592
  selectors: Selector[],
577
593
  options: Options = {}
578
- ): FormattedSelector[] {
594
+ ): {
595
+ selectors: FormattedSelector[];
596
+ qrefs: [Options, ...FormattedSelector[]][];
597
+ } {
579
598
  const newSelectors: FormattedSelector[] = [];
599
+ const qrefs: [Options, ...FormattedSelector[]][] = [];
580
600
 
581
601
  for (const curSelector of selectors) {
582
602
  const newSelector: FormattedSelector = {
@@ -615,13 +635,18 @@ export default abstract class NymphDriver {
615
635
  source: options.source,
616
636
  };
617
637
  const newSelectors = this.formatSelectors(qrefSelectors, options);
618
- this.nymph.runQueryCallbacks(newOptions, newSelectors);
619
- formatArr[i] = [name, [newOptions, ...newSelectors]];
638
+ qrefs.push(
639
+ [newOptions, ...newSelectors.selectors],
640
+ ...newSelectors.qrefs
641
+ );
642
+ formatArr[i] = [name, [newOptions, ...newSelectors.selectors]];
620
643
  }
621
644
  newSelector[key] = formatArr;
622
645
  } else if (key === 'selector' || key === '!selector') {
623
646
  const tmpArr = (Array.isArray(value) ? value : [value]) as Selector[];
624
- newSelector[key] = this.formatSelectors(tmpArr, options);
647
+ const newSelectors = this.formatSelectors(tmpArr, options);
648
+ newSelector[key] = newSelectors.selectors;
649
+ qrefs.push(...newSelectors.qrefs);
625
650
  } else if (!Array.isArray(value)) {
626
651
  // @ts-ignore: ts doesn't know what value is here.
627
652
  newSelector[key] = [[value]];
@@ -655,7 +680,7 @@ export default abstract class NymphDriver {
655
680
  newSelectors.push(newSelector);
656
681
  }
657
682
 
658
- return newSelectors;
683
+ return { selectors: newSelectors, qrefs };
659
684
  }
660
685
 
661
686
  protected iterateSelectorsForQuery(
@@ -774,7 +799,7 @@ export default abstract class NymphDriver {
774
799
  name: string;
775
800
  svalue: string;
776
801
  }
777
- ): { result: any; process: () => number };
802
+ ): { result: any; process: () => number | Error };
778
803
  protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
779
804
  options: Options<T> & { return: 'guid' },
780
805
  selectors: Selector[],
@@ -798,7 +823,7 @@ export default abstract class NymphDriver {
798
823
  name: string;
799
824
  svalue: string;
800
825
  }
801
- ): { result: any; process: () => string[] };
826
+ ): { result: any; process: () => string[] | Error };
802
827
  protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
803
828
  options: Options<T>,
804
829
  selectors: Selector[],
@@ -822,7 +847,7 @@ export default abstract class NymphDriver {
822
847
  name: string;
823
848
  svalue: string;
824
849
  }
825
- ): { result: any; process: () => ReturnType<T['factorySync']>[] };
850
+ ): { result: any; process: () => ReturnType<T['factorySync']>[] | Error };
826
851
  protected getEntitesRowLike<T extends EntityConstructor = EntityConstructor>(
827
852
  options: Options<T>,
828
853
  selectors: Selector[],
@@ -848,7 +873,7 @@ export default abstract class NymphDriver {
848
873
  }
849
874
  ): {
850
875
  result: any;
851
- process: () => ReturnType<T['factorySync']>[] | string[] | number;
876
+ process: () => ReturnType<T['factorySync']>[] | string[] | number | Error;
852
877
  } {
853
878
  if (!this.isConnected()) {
854
879
  throw new UnableToConnectError('not connected to DB');
@@ -918,78 +943,95 @@ export default abstract class NymphDriver {
918
943
  }
919
944
  }
920
945
 
921
- const formattedSelectors = this.formatSelectors(selectors, options);
922
- this.nymph.runQueryCallbacks(options, formattedSelectors);
923
- const { result } = performQueryCallback(options, formattedSelectors, etype);
946
+ try {
947
+ const formattedSelectors = this.formatSelectors(selectors, options);
948
+ this.nymph.runQueryCallbacks(options, formattedSelectors.selectors);
949
+ for (let qref of formattedSelectors.qrefs) {
950
+ const [options, ...selectors] = qref;
951
+ this.nymph.runQueryCallbacks(options, selectors);
952
+ }
953
+ const { result } = performQueryCallback(
954
+ options,
955
+ formattedSelectors.selectors,
956
+ etype
957
+ );
924
958
 
925
- return {
926
- result,
927
- process: () => {
928
- let row = rowFetchCallback();
929
- if (options.return === 'count') {
930
- while (row != null) {
931
- count += getCountCallback(row);
932
- row = rowFetchCallback();
933
- }
934
- } else if (options.return === 'guid') {
935
- while (row != null) {
936
- (entities as string[]).push(getGUIDCallback(row));
937
- row = rowFetchCallback();
938
- }
939
- } else {
940
- while (row != null) {
941
- const guid = getGUIDCallback(row);
942
- const tagsAndDates = getTagsAndDatesCallback(row);
943
- const tags = tagsAndDates.tags;
944
- const cdate = tagsAndDates.cdate;
945
- const mdate = tagsAndDates.mdate;
946
- let dataNameAndSValue = getDataNameAndSValueCallback(row);
947
- // Data.
948
- const data: EntityData = {};
949
- // Serialized data.
950
- const sdata: SerializedEntityData = {};
951
- if (dataNameAndSValue.name !== '') {
952
- // This do will keep going and adding the data until the
953
- // next entity is reached. $row will end on the next entity.
954
- do {
955
- dataNameAndSValue = getDataNameAndSValueCallback(row);
956
- sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
957
- row = rowFetchCallback();
958
- } while (row != null && getGUIDCallback(row) === guid);
959
- } else {
960
- // Make sure that $row is incremented :)
959
+ return {
960
+ result,
961
+ process: () => {
962
+ let row = rowFetchCallback();
963
+ if (options.return === 'count') {
964
+ while (row != null) {
965
+ count += getCountCallback(row);
961
966
  row = rowFetchCallback();
962
967
  }
963
- const entity = EntityClass.factorySync() as ReturnType<
964
- T['factorySync']
965
- >;
966
- entity.$nymph = this.nymph;
967
- if (options.skipAc != null) {
968
- entity.$useSkipAc(!!options.skipAc);
969
- }
970
- entity.guid = guid;
971
- entity.cdate = cdate;
972
- entity.mdate = mdate;
973
- entity.tags = tags;
974
- this.putDataCounter++;
975
- if (this.putDataCounter == 100) {
976
- throw new Error('Infinite loop detected in Entity loading.');
968
+ } else if (options.return === 'guid') {
969
+ while (row != null) {
970
+ (entities as string[]).push(getGUIDCallback(row));
971
+ row = rowFetchCallback();
977
972
  }
978
- entity.$putData(data, sdata);
979
- this.putDataCounter--;
980
- if (this.nymph.config.cache) {
981
- this.pushCache(guid, cdate, mdate, tags, data, sdata);
973
+ } else {
974
+ while (row != null) {
975
+ const guid = getGUIDCallback(row);
976
+ const tagsAndDates = getTagsAndDatesCallback(row);
977
+ const tags = tagsAndDates.tags;
978
+ const cdate = tagsAndDates.cdate;
979
+ const mdate = tagsAndDates.mdate;
980
+ let dataNameAndSValue = getDataNameAndSValueCallback(row);
981
+ // Data.
982
+ const data: EntityData = {};
983
+ // Serialized data.
984
+ const sdata: SerializedEntityData = {};
985
+ if (dataNameAndSValue.name !== '') {
986
+ // This do will keep going and adding the data until the
987
+ // next entity is reached. $row will end on the next entity.
988
+ do {
989
+ dataNameAndSValue = getDataNameAndSValueCallback(row);
990
+ sdata[dataNameAndSValue.name] = dataNameAndSValue.svalue;
991
+ row = rowFetchCallback();
992
+ } while (row != null && getGUIDCallback(row) === guid);
993
+ } else {
994
+ // Make sure that $row is incremented :)
995
+ row = rowFetchCallback();
996
+ }
997
+ const entity = EntityClass.factorySync() as ReturnType<
998
+ T['factorySync']
999
+ >;
1000
+ entity.$nymph = this.nymph;
1001
+ if (options.skipAc != null) {
1002
+ entity.$useSkipAc(!!options.skipAc);
1003
+ }
1004
+ entity.guid = guid;
1005
+ entity.cdate = cdate;
1006
+ entity.mdate = mdate;
1007
+ entity.tags = tags;
1008
+ this.putDataCounter++;
1009
+ if (this.putDataCounter == 100) {
1010
+ throw new Error('Infinite loop detected in Entity loading.');
1011
+ }
1012
+ entity.$putData(data, sdata);
1013
+ this.putDataCounter--;
1014
+ if (this.nymph.config.cache) {
1015
+ this.pushCache(guid, cdate, mdate, tags, data, sdata);
1016
+ }
1017
+ // @ts-ignore: ts doesn't know the return type here.
1018
+ entities.push(entity);
982
1019
  }
983
- // @ts-ignore: ts doesn't know the return type here.
984
- entities.push(entity);
985
1020
  }
986
- }
987
1021
 
988
- freeResultCallback();
1022
+ freeResultCallback();
989
1023
 
990
- return options.return === 'count' ? count : entities;
991
- },
992
- };
1024
+ return options.return === 'count' ? count : entities;
1025
+ },
1026
+ };
1027
+ } catch (e: any) {
1028
+ return {
1029
+ result: Promise.resolve(e),
1030
+ process: () => {
1031
+ return e;
1032
+ },
1033
+ };
1034
+ }
993
1035
  }
994
1036
 
995
1037
  protected async saveEntityRowLike(
@@ -3,16 +3,20 @@ 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 { TestBModel, TestModel, TestModelData } from '../testArtifacts';
6
+ import {
7
+ TestBModel as TestBModelClass,
8
+ TestModel as TestModelClass,
9
+ TestModelData,
10
+ } from '../testArtifacts';
7
11
 
8
12
  export function QueriesTest(
9
13
  nymph: Nymph,
10
14
  it: (name: string, fn: () => void) => void
11
15
  ) {
12
- nymph.addEntityClass(TestModel);
13
- nymph.addEntityClass(TestBModel);
16
+ const TestModel = nymph.addEntityClass(TestModelClass);
17
+ const TestBModel = nymph.addEntityClass(TestBModelClass);
14
18
 
15
- let testEntity: TestModel & TestModelData;
19
+ let testEntity: TestModelClass & TestModelData;
16
20
  let testGuid: string;
17
21
  let refGuid: string;
18
22
  let createdMultiple = false;
@@ -43,7 +47,7 @@ This one's zip code is 92064.`;
43
47
  expect(testEntity.guid).not.toBeNull();
44
48
  testGuid = testEntity.guid as string;
45
49
 
46
- const entityReferenceTest: TestModel & TestModelData = new TestModel();
50
+ const entityReferenceTest: TestModelClass & TestModelData = new TestModel();
47
51
  entityReferenceTest.string = 'wrong';
48
52
  entityReferenceTest.timestamp = strtotime('-2 days') * 1000;
49
53
  expect(await entityReferenceTest.$save()).toEqual(true);
@@ -59,7 +63,7 @@ This one's zip code is 92064.`;
59
63
  testEntity = nymph.driver.getEntitySync(
60
64
  { class: TestModel },
61
65
  testGuid
62
- ) as TestModel & TestModelData;
66
+ ) as TestModelClass & TestModelData;
63
67
  expect(testEntity).toBeInstanceOf(TestModel);
64
68
  expect(testEntity.guid).toEqual(testGuid);
65
69
 
@@ -67,7 +71,7 @@ This one's zip code is 92064.`;
67
71
  testEntity = (await nymph.getEntity(
68
72
  { class: TestModel },
69
73
  testGuid
70
- )) as TestModel & TestModelData;
74
+ )) as TestModelClass & TestModelData;
71
75
  expect(testEntity).toBeInstanceOf(TestModel);
72
76
  expect(testEntity.guid).toEqual(testGuid);
73
77
  }
@@ -80,6 +84,8 @@ This one's zip code is 92064.`;
80
84
  for (let i = 0; i < 100; i++) {
81
85
  const testEntity = await TestModel.factory();
82
86
  testEntity.name = `Multi Test ${i}`;
87
+ // Reverse the number for sort testing.
88
+ testEntity.number = 100 - i;
83
89
  testEntity.$removeTag('test');
84
90
  testEntity.$addTag('multiTest');
85
91
  expect(await testEntity.$save()).toEqual(true);
@@ -191,7 +197,7 @@ This one's zip code is 92064.`;
191
197
  testEntity = (await transaction.getEntity(
192
198
  { class: TestModel },
193
199
  testGuid
194
- )) as TestModel & TestModelData;
200
+ )) as TestModelClass & TestModelData;
195
201
 
196
202
  expect(testEntity.guid).toEqual(testGuid);
197
203
 
@@ -1633,26 +1639,23 @@ This one's zip code is 92064.`;
1633
1639
  expect(testEntity.$inArray(resultEntity)).toEqual(false);
1634
1640
  });
1635
1641
 
1636
- it('sort option', async () => {
1642
+ it('cdate and mdate sort option', async () => {
1637
1643
  await createMultipleTestEntities();
1638
1644
 
1639
- for (const sort of ['cdate', 'mdate']) {
1645
+ for (const sort of ['cdate', 'mdate'] as ('cdate' | 'mdate')[]) {
1640
1646
  // Retrieving entities sorted...
1641
- let resultEntities = await nymph.getEntities({
1642
- class: TestModel,
1643
- sort: sort as 'cdate' | 'mdate',
1644
- });
1647
+ let resultEntities = await nymph.getEntities({ class: TestModel, sort });
1645
1648
  expect(resultEntities.length).toBeGreaterThan(100);
1646
1649
  for (let i = 0; i < resultEntities.length - 1; i++) {
1647
- expect(
1648
- resultEntities[i + 1][sort as 'cdate' | 'mdate']
1649
- ).toBeGreaterThan(resultEntities[i][sort as 'cdate' | 'mdate'] ?? 0);
1650
+ expect(resultEntities[i + 1][sort]).toBeGreaterThan(
1651
+ resultEntities[i][sort] ?? 0
1652
+ );
1650
1653
  }
1651
1654
 
1652
1655
  // And test the same with guid return...
1653
1656
  let resultGuids = await nymph.getEntities({
1654
1657
  class: TestModel,
1655
- sort: sort as 'cdate' | 'mdate',
1658
+ sort,
1656
1659
  return: 'guid',
1657
1660
  });
1658
1661
  expect(resultGuids.length).toBeGreaterThan(100);
@@ -1663,43 +1666,114 @@ This one's zip code is 92064.`;
1663
1666
  // Retrieving entities reverse sorted...
1664
1667
  resultEntities = await nymph.getEntities({
1665
1668
  class: TestModel,
1666
- sort: sort as 'cdate' | 'mdate',
1669
+ sort,
1667
1670
  reverse: true,
1668
1671
  });
1669
1672
  expect(resultEntities.length).toBeGreaterThan(100);
1670
1673
  for (let i = 0; i < resultEntities.length - 1; i++) {
1671
- expect(resultEntities[i + 1][sort as 'cdate' | 'mdate']).toBeLessThan(
1672
- resultEntities[i][sort as 'cdate' | 'mdate'] ?? 0
1674
+ expect(resultEntities[i + 1][sort]).toBeLessThan(
1675
+ resultEntities[i][sort] ?? 0
1673
1676
  );
1674
1677
  }
1675
1678
 
1676
1679
  // And again with other selectors.
1677
1680
  // Retrieving entities sorted...
1678
1681
  resultEntities = await nymph.getEntities(
1679
- { class: TestModel, sort: sort as 'cdate' | 'mdate' },
1682
+ { class: TestModel, sort },
1680
1683
  { type: '&', match: ['name', '^Multi Test '] }
1681
1684
  );
1682
1685
  expect(resultEntities.length).toEqual(100);
1683
1686
  for (let i = 0; i < resultEntities.length - 1; i++) {
1684
- expect(
1685
- resultEntities[i + 1][sort as 'cdate' | 'mdate']
1686
- ).toBeGreaterThan(resultEntities[i][sort as 'cdate' | 'mdate'] ?? 0);
1687
+ expect(resultEntities[i + 1][sort]).toBeGreaterThan(
1688
+ resultEntities[i][sort] ?? 0
1689
+ );
1687
1690
  }
1688
1691
 
1689
1692
  // Retrieving entities reverse sorted...
1690
1693
  resultEntities = await nymph.getEntities(
1691
- { class: TestModel, sort: sort as 'cdate' | 'mdate', reverse: true },
1694
+ { class: TestModel, sort, reverse: true },
1692
1695
  { type: '&', match: ['name', '^Multi Test '] }
1693
1696
  );
1694
1697
  expect(resultEntities.length).toEqual(100);
1695
1698
  for (let i = 0; i < resultEntities.length - 1; i++) {
1696
- expect(resultEntities[i + 1][sort as 'cdate' | 'mdate']).toBeLessThan(
1697
- resultEntities[i][sort as 'cdate' | 'mdate'] ?? 0
1699
+ expect(resultEntities[i + 1][sort]).toBeLessThan(
1700
+ resultEntities[i][sort] ?? 0
1698
1701
  );
1699
1702
  }
1700
1703
  }
1701
1704
  });
1702
1705
 
1706
+ it('property sort option', async () => {
1707
+ await createMultipleTestEntities();
1708
+
1709
+ // Retrieving entities sorted...
1710
+ let resultEntities = await nymph.getEntities({
1711
+ class: TestModel,
1712
+ sort: 'number',
1713
+ });
1714
+ expect(resultEntities.length).toBeGreaterThan(100);
1715
+ for (let i = 0; i < resultEntities.length - 1; i++) {
1716
+ expect(resultEntities[i + 1].number ?? Infinity).toBeGreaterThanOrEqual(
1717
+ resultEntities[i].number ?? 0
1718
+ );
1719
+ }
1720
+
1721
+ // And test the same with guid return...
1722
+ let resultGuids = await nymph.getEntities({
1723
+ class: TestModel,
1724
+ sort: 'number',
1725
+ return: 'guid',
1726
+ });
1727
+ expect(resultGuids.length).toBeGreaterThan(100);
1728
+ for (let i = 0; i < resultGuids.length - 1; i++) {
1729
+ expect(resultGuids[i]).toEqual(resultEntities[i].guid);
1730
+ }
1731
+
1732
+ // Retrieving entities reverse sorted...
1733
+ resultEntities = await nymph.getEntities({
1734
+ class: TestModel,
1735
+ sort: 'number',
1736
+ reverse: true,
1737
+ });
1738
+ expect(resultEntities.length).toBeGreaterThan(100);
1739
+ for (let i = 0; i < resultEntities.length - 1; i++) {
1740
+ expect(resultEntities[i + 1].number ?? 0).toBeLessThanOrEqual(
1741
+ resultEntities[i].number ?? Infinity
1742
+ );
1743
+ }
1744
+
1745
+ // And again with other selectors.
1746
+ // Retrieving entities sorted...
1747
+ resultEntities = await nymph.getEntities(
1748
+ { class: TestModel, sort: 'number' },
1749
+ { type: '&', match: ['name', '^Multi Test '] }
1750
+ );
1751
+ expect(resultEntities.length).toEqual(100);
1752
+ for (let i = 0; i < resultEntities.length - 1; i++) {
1753
+ expect(resultEntities[i + 1].number ?? Infinity).toBeGreaterThan(
1754
+ resultEntities[i].number ?? 0
1755
+ );
1756
+ expect(resultEntities[i].name).toEqual(
1757
+ `Multi Test ${100 - (resultEntities[i].number ?? 0)}`
1758
+ );
1759
+ }
1760
+
1761
+ // Retrieving entities reverse sorted...
1762
+ resultEntities = await nymph.getEntities(
1763
+ { class: TestModel, sort: 'number', reverse: true },
1764
+ { type: '&', match: ['name', '^Multi Test '] }
1765
+ );
1766
+ expect(resultEntities.length).toEqual(100);
1767
+ for (let i = 0; i < resultEntities.length - 1; i++) {
1768
+ expect(resultEntities[i + 1].number ?? 0).toBeLessThan(
1769
+ resultEntities[i].number ?? Infinity
1770
+ );
1771
+ expect(resultEntities[i].name).toEqual(
1772
+ `Multi Test ${100 - (resultEntities[i].number ?? 0)}`
1773
+ );
1774
+ }
1775
+ });
1776
+
1703
1777
  it('delete reference', async () => {
1704
1778
  await createTestEntities();
1705
1779
 
@@ -1728,9 +1802,6 @@ export function UIDTest(
1728
1802
  nymph: Nymph,
1729
1803
  it: (name: string, fn: () => void) => void
1730
1804
  ) {
1731
- nymph.addEntityClass(TestModel);
1732
- nymph.addEntityClass(TestBModel);
1733
-
1734
1805
  it('delete old test data', async () => {
1735
1806
  expect(await nymph.deleteUID('TestUID')).toEqual(true);
1736
1807
  expect(await nymph.deleteUID('NewUID')).toEqual(true);
@@ -1772,8 +1843,8 @@ export function ExportImportTest(
1772
1843
  nymph: Nymph,
1773
1844
  it: (name: string, fn: () => void) => void
1774
1845
  ) {
1775
- nymph.addEntityClass(TestModel);
1776
- nymph.addEntityClass(TestBModel);
1846
+ const TestModel = nymph.addEntityClass(TestModelClass);
1847
+ const TestBModel = nymph.addEntityClass(TestBModelClass);
1777
1848
 
1778
1849
  async function deleteTestData() {
1779
1850
  let all = await nymph.getEntities({ class: TestModel });