@nymphjs/nymph 1.0.0-beta.121 → 1.0.0-beta.122

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 (53) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +12 -4
  3. package/dist/Entity.d.ts +4 -3
  4. package/dist/Entity.js +33 -6
  5. package/dist/Entity.js.map +1 -1
  6. package/dist/Entity.test.js +19 -12
  7. package/dist/Entity.test.js.map +1 -1
  8. package/dist/Entity.types.d.ts +19 -15
  9. package/dist/decorators/index.d.ts +1 -0
  10. package/dist/decorators/index.js +2 -0
  11. package/dist/decorators/index.js.map +1 -0
  12. package/dist/decorators/transactional.d.ts +12 -0
  13. package/dist/decorators/transactional.js +45 -0
  14. package/dist/decorators/transactional.js.map +1 -0
  15. package/dist/driver/NymphDriver.js +14 -6
  16. package/dist/driver/NymphDriver.js.map +1 -1
  17. package/dist/driver/testArtifacts.js +115 -36
  18. package/dist/driver/testArtifacts.js.map +1 -1
  19. package/dist/errors/MethodFailedError.d.ts +3 -0
  20. package/dist/errors/MethodFailedError.js +7 -0
  21. package/dist/errors/MethodFailedError.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/helpers/index.d.ts +1 -0
  26. package/dist/helpers/index.js +2 -0
  27. package/dist/helpers/index.js.map +1 -0
  28. package/dist/helpers/transaction.d.ts +15 -0
  29. package/dist/helpers/transaction.js +47 -0
  30. package/dist/helpers/transaction.js.map +1 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/testArtifacts.d.ts +2 -0
  35. package/dist/testArtifacts.js +113 -34
  36. package/dist/testArtifacts.js.map +1 -1
  37. package/dist/testMocks.js +2 -2
  38. package/dist/testMocks.js.map +1 -1
  39. package/package.json +14 -5
  40. package/src/Entity.test.ts +19 -12
  41. package/src/Entity.ts +34 -9
  42. package/src/Entity.types.ts +19 -15
  43. package/src/decorators/index.ts +1 -0
  44. package/src/decorators/transactional.ts +63 -0
  45. package/src/driver/NymphDriver.ts +18 -6
  46. package/src/driver/testArtifacts.ts +146 -36
  47. package/src/errors/MethodFailedError.ts +6 -0
  48. package/src/errors/index.ts +2 -0
  49. package/src/helpers/index.ts +1 -0
  50. package/src/helpers/transaction.ts +65 -0
  51. package/src/index.ts +2 -0
  52. package/src/testArtifacts.ts +49 -0
  53. package/src/testMocks.ts +4 -2
@@ -5,7 +5,10 @@ import { strtotime } from 'locutus/php/datetime/index';
5
5
  import { guid } from '@nymphjs/guid';
6
6
 
7
7
  import type Nymph from '../Nymph.js';
8
- import { EntityUniqueConstraintError } from '../errors/index.js';
8
+ import {
9
+ EntityUniqueConstraintError,
10
+ EntityInvalidDataError,
11
+ } from '../errors/index.js';
9
12
  import {
10
13
  TestBModel as TestBModelClass,
11
14
  TestModel as TestModelClass,
@@ -56,22 +59,22 @@ export function EntitiesTest(
56
59
  testEntity.number = 30;
57
60
  testEntity.numberString = '30';
58
61
  testEntity.timestamp = Date.now();
59
- expect(await testEntity.$save()).toEqual(true);
62
+ await testEntity.$save();
60
63
  expect(testEntity.guid).not.toBeNull();
61
64
  testGuid = testEntity.guid as string;
62
65
 
63
66
  const entityReferenceTest: TestModelClass & TestModelData =
64
67
  new TestModel();
65
- entityReferenceTest.string = 'wrong';
68
+ entityReferenceTest.string = 'ref string';
66
69
  entityReferenceTest.timestamp = (strtotime('-2 days') || 0) * 1000;
67
- expect(await entityReferenceTest.$save()).toEqual(true);
70
+ await entityReferenceTest.$save();
68
71
  refGuid = entityReferenceTest.guid as string;
69
72
  testEntity.reference = entityReferenceTest;
70
73
  testEntity.refArray = [entityReferenceTest];
71
- expect(await testEntity.$save()).toEqual(true);
74
+ await testEntity.$save();
72
75
 
73
76
  entityReferenceTest.test = 'good';
74
- expect(await entityReferenceTest.$save()).toEqual(true);
77
+ await entityReferenceTest.$save();
75
78
 
76
79
  // Test asynchronous getEntity.
77
80
  testEntity = (await nymph.getEntity(
@@ -94,7 +97,7 @@ export function EntitiesTest(
94
97
  testEntity.number = 100 - i;
95
98
  testEntity.$removeTag('test');
96
99
  testEntity.$addTag('multiTest');
97
- expect(await testEntity.$save()).toEqual(true);
100
+ await testEntity.$save();
98
101
  // Pause for a few milliseconds so the cdate and mdate can be sorted.
99
102
  await new Promise((res) => setTimeout(() => res(1), 5));
100
103
  }
@@ -105,7 +108,7 @@ export function EntitiesTest(
105
108
  let amodels = await nymph.getEntities({ class: TestModel });
106
109
  expect(Array.isArray(amodels)).toEqual(true);
107
110
  for (const cur of amodels) {
108
- expect(await cur.$delete()).toEqual(true);
111
+ await cur.$delete();
109
112
  }
110
113
 
111
114
  amodels = await nymph.getEntities({ class: TestModel });
@@ -114,7 +117,7 @@ export function EntitiesTest(
114
117
  let bmodels = await nymph.getEntities({ class: TestBModel });
115
118
  expect(Array.isArray(bmodels)).toEqual(true);
116
119
  for (const cur of bmodels) {
117
- expect(await cur.$delete()).toEqual(true);
120
+ await cur.$delete();
118
121
  }
119
122
 
120
123
  bmodels = await nymph.getEntities({ class: TestBModel });
@@ -128,7 +131,13 @@ export function EntitiesTest(
128
131
  it("doesn't create empty entity", async () => {
129
132
  const testEntity = await TestEmptyModel.factory();
130
133
 
131
- expect(await testEntity.$save()).toBeFalsy();
134
+ try {
135
+ await testEntity.$save();
136
+
137
+ throw new Error('Should have thrown.');
138
+ } catch (e: any) {
139
+ expect(e).toBeInstanceOf(EntityInvalidDataError);
140
+ }
132
141
  expect(testEntity.guid).toBeNull();
133
142
  });
134
143
 
@@ -180,7 +189,7 @@ export function EntitiesTest(
180
189
  // Get the guaranteed GUID.
181
190
  const testGuid = testEntity.$getGuaranteedGUID();
182
191
 
183
- expect(await testEntity.$save()).toEqual(true);
192
+ await testEntity.$save();
184
193
  expect(testEntity.guid).not.toBeNull();
185
194
  expect(testEntity.guid).toEqual(testGuid);
186
195
 
@@ -189,7 +198,7 @@ export function EntitiesTest(
189
198
  expect(testEntity.$is(resultEntity)).toEqual(true);
190
199
 
191
200
  // Delete the entity.
192
- expect(await testEntity.$delete()).toEqual(true);
201
+ await testEntity.$delete();
193
202
  });
194
203
 
195
204
  it('create, list, and delete indexes', async () => {
@@ -324,7 +333,7 @@ export function EntitiesTest(
324
333
 
325
334
  // Change a value in the test entity.
326
335
  testEntity.string = 'bad value';
327
- expect(await testEntity.$save()).toEqual(true);
336
+ await testEntity.$save();
328
337
 
329
338
  // Rollback the transaction.
330
339
  await transaction.rollback('test');
@@ -344,7 +353,7 @@ export function EntitiesTest(
344
353
  expect(await transaction.inTransaction()).toEqual(true);
345
354
 
346
355
  // Delete the entity.
347
- expect(await testEntity.$delete()).toEqual(true);
356
+ await testEntity.$delete();
348
357
  const resultEntity = await transaction.getEntity(
349
358
  { class: TestModel },
350
359
  testGuid,
@@ -374,7 +383,7 @@ export function EntitiesTest(
374
383
 
375
384
  // Make a change.
376
385
  testEntity.string = 'fish';
377
- expect(await testEntity.$save()).toEqual(true);
386
+ await testEntity.$save();
378
387
 
379
388
  // Commit the transaction.
380
389
  await transaction.commit('test.1');
@@ -389,11 +398,103 @@ export function EntitiesTest(
389
398
 
390
399
  // Finally, change it back.
391
400
  testEntity.string = 'test';
392
- expect(await testEntity.$save()).toEqual(true);
401
+ await testEntity.$save();
393
402
 
394
403
  // TODO: nested transactions
395
404
  });
396
405
 
406
+ it('transactional decorator', async () => {
407
+ await createTestEntities();
408
+
409
+ if (testEntity.reference == null) {
410
+ throw new Error('Reference is null.');
411
+ }
412
+ await testEntity.reference.$wake();
413
+
414
+ expect(testEntity.string).toEqual('test');
415
+ expect(testEntity.reference.string).toEqual('ref string');
416
+
417
+ try {
418
+ await testEntity.$testTransactionalDecorator(true);
419
+
420
+ throw Error('Should have thrown an error.');
421
+ } catch (e: any) {
422
+ expect(e.message).toEqual('An error.');
423
+ }
424
+
425
+ if (!(await testEntity.$refresh())) {
426
+ throw new Error("Entity couldn't be refreshed.");
427
+ }
428
+ await testEntity.reference.$wake();
429
+
430
+ expect(testEntity.string).toEqual('test');
431
+ expect(testEntity.reference.string).toEqual('ref string');
432
+
433
+ expect(await testEntity.$testTransactionalDecorator(false)).toEqual(
434
+ 'Success.',
435
+ );
436
+
437
+ if (!(await testEntity.$refresh())) {
438
+ throw new Error("Entity couldn't be refreshed.");
439
+ }
440
+ await testEntity.reference.$wake();
441
+
442
+ expect(testEntity.string).toEqual('this has been changed');
443
+ expect(testEntity.reference.string).toEqual('this has been changed too');
444
+
445
+ // Reset back to original values.
446
+ testEntity.reference.string = 'ref string';
447
+ await testEntity.reference.$save();
448
+ testEntity.string = 'test';
449
+ await testEntity.$save();
450
+ });
451
+
452
+ it('transaction helper', async () => {
453
+ await createTestEntities();
454
+
455
+ if (testEntity.reference == null) {
456
+ throw new Error('Reference is null.');
457
+ }
458
+ await testEntity.reference.$wake();
459
+
460
+ expect(testEntity.string).toEqual('test');
461
+ expect(testEntity.reference.string).toEqual('ref string');
462
+
463
+ try {
464
+ await testEntity.$testTransactionHelper(true);
465
+
466
+ throw Error('Should have thrown an error.');
467
+ } catch (e: any) {
468
+ expect(e.message).toEqual('An error.');
469
+ }
470
+
471
+ if (!(await testEntity.$refresh())) {
472
+ throw new Error("Entity couldn't be refreshed.");
473
+ }
474
+ await testEntity.reference.$wake();
475
+
476
+ expect(testEntity.string).toEqual('test');
477
+ expect(testEntity.reference.string).toEqual('ref string');
478
+
479
+ expect(await testEntity.$testTransactionHelper(false)).toEqual(
480
+ 'Success.',
481
+ );
482
+
483
+ if (!(await testEntity.$refresh())) {
484
+ throw new Error("Entity couldn't be refreshed.");
485
+ }
486
+ await testEntity.reference.$wake();
487
+
488
+ expect(testEntity.string).toEqual('this has been changed');
489
+ expect(testEntity.reference.string).toEqual('this has been changed too');
490
+
491
+ // Reset back to original values.
492
+ testEntity.reference.string = 'ref string';
493
+ await testEntity.reference.$save();
494
+ testEntity.string = 'test';
495
+ await testEntity.$save();
496
+ });
497
+
397
498
  it('options', async () => {
398
499
  await createTestEntities();
399
500
 
@@ -674,7 +775,7 @@ export function EntitiesTest(
674
775
  // Retrieving entity by !equal...
675
776
  const resultEntity = await nymph.getEntities(
676
777
  { class: TestModel },
677
- { type: '&', tag: 'test', '!equal': ['string', 'wrong'] },
778
+ { type: '&', tag: 'test', '!equal': ['string', 'ref string'] },
678
779
  );
679
780
  expect(testEntity.$inArray(resultEntity)).toEqual(true);
680
781
  expect(referenceEntity.$inArray(resultEntity)).toEqual(false);
@@ -700,7 +801,7 @@ export function EntitiesTest(
700
801
  // Retrieving entity by !like...
701
802
  const resultEntity = await nymph.getEntities(
702
803
  { class: TestModel },
703
- { type: '&', tag: 'test', '!like': ['string', 'wr_n%'] },
804
+ { type: '&', tag: 'test', '!like': ['string', 'r_f%'] },
704
805
  );
705
806
  expect(testEntity.$inArray(resultEntity)).toEqual(true);
706
807
  expect(referenceEntity.$inArray(resultEntity)).toEqual(false);
@@ -726,7 +827,7 @@ export function EntitiesTest(
726
827
  // Retrieving entity by !ilike...
727
828
  const resultEntity = await nymph.getEntities(
728
829
  { class: TestModel },
729
- { type: '&', tag: 'test', '!ilike': ['string', 'wr_n%'] },
830
+ { type: '&', tag: 'test', '!ilike': ['string', 'r_f%'] },
730
831
  );
731
832
  expect(testEntity.$inArray(resultEntity)).toEqual(true);
732
833
  expect(referenceEntity.$inArray(resultEntity)).toEqual(false);
@@ -2130,14 +2231,19 @@ export function EntitiesTest(
2130
2231
  await createMultipleTestEntities();
2131
2232
 
2132
2233
  for (const sort of ['cdate', 'mdate'] as ('cdate' | 'mdate')[]) {
2234
+ // These ones might have the same dates (saved at exactly the same time)
2235
+ // so we use >= and <= instead of > and <.
2133
2236
  // Retrieving entities sorted...
2134
2237
  let resultEntities = await nymph.getEntities({
2135
2238
  class: TestModel,
2136
2239
  sort,
2137
2240
  });
2138
2241
  expect(resultEntities.length).toBeGreaterThan(100);
2242
+ expect(resultEntities[resultEntities.length - 1][sort]).toBeGreaterThan(
2243
+ resultEntities[0][sort] ?? 0,
2244
+ );
2139
2245
  for (let i = 0; i < resultEntities.length - 1; i++) {
2140
- expect(resultEntities[i + 1][sort]).toBeGreaterThan(
2246
+ expect(resultEntities[i + 1][sort]).toBeGreaterThanOrEqual(
2141
2247
  resultEntities[i][sort] ?? 0,
2142
2248
  );
2143
2249
  }
@@ -2160,13 +2266,17 @@ export function EntitiesTest(
2160
2266
  reverse: true,
2161
2267
  });
2162
2268
  expect(resultEntities.length).toBeGreaterThan(100);
2269
+ expect(resultEntities[resultEntities.length - 1][sort]).toBeLessThan(
2270
+ resultEntities[0][sort] ?? 0,
2271
+ );
2163
2272
  for (let i = 0; i < resultEntities.length - 1; i++) {
2164
- expect(resultEntities[i + 1][sort]).toBeLessThan(
2273
+ expect(resultEntities[i + 1][sort]).toBeLessThanOrEqual(
2165
2274
  resultEntities[i][sort] ?? 0,
2166
2275
  );
2167
2276
  }
2168
2277
 
2169
2278
  // And again with other selectors.
2279
+ // These ones should have different dates every time.
2170
2280
  // Retrieving entities sorted...
2171
2281
  resultEntities = await nymph.getEntities(
2172
2282
  { class: TestModel, sort },
@@ -2269,7 +2379,7 @@ export function EntitiesTest(
2269
2379
 
2270
2380
  // Deleting referenced entities...
2271
2381
  await testEntity.reference?.$wake();
2272
- expect(await testEntity.reference?.$delete()).toEqual(true);
2382
+ await testEntity.reference?.$delete();
2273
2383
  await Promise.all(testEntity.refArray?.map((e) => e.$wake()) || []);
2274
2384
  expect(testEntity.reference?.guid).toBeNull();
2275
2385
  });
@@ -2278,7 +2388,7 @@ export function EntitiesTest(
2278
2388
  await createTestEntities();
2279
2389
 
2280
2390
  // Deleting entity...
2281
- expect(await testEntity.$delete()).toEqual(true);
2391
+ await testEntity.$delete();
2282
2392
  expect(testEntity.guid).toBeNull();
2283
2393
 
2284
2394
  const entity = await nymph.getEntity(
@@ -2296,7 +2406,7 @@ export function EntitiesTest(
2296
2406
  uniqueEntityA.uniques = ['test a'];
2297
2407
 
2298
2408
  // Saving entity...
2299
- expect(await uniqueEntityA.$save()).toEqual(true);
2409
+ await uniqueEntityA.$save();
2300
2410
  expect(uniqueEntityA.guid).not.toBeNull();
2301
2411
 
2302
2412
  // Creating entity...
@@ -2305,13 +2415,13 @@ export function EntitiesTest(
2305
2415
  uniqueEntityB.uniques = ['test b'];
2306
2416
 
2307
2417
  // Saving entity...
2308
- expect(await uniqueEntityB.$save()).toEqual(true);
2418
+ await uniqueEntityB.$save();
2309
2419
  expect(uniqueEntityB.guid).not.toBeNull();
2310
2420
 
2311
2421
  expect(uniqueEntityA.guid).not.toEqual(uniqueEntityB.guid);
2312
2422
 
2313
- expect(await uniqueEntityA.$delete()).toEqual(true);
2314
- expect(await uniqueEntityB.$delete()).toEqual(true);
2423
+ await uniqueEntityA.$delete();
2424
+ await uniqueEntityB.$delete();
2315
2425
  });
2316
2426
 
2317
2427
  it('throws on duplicate unique strings', async () => {
@@ -2321,7 +2431,7 @@ export function EntitiesTest(
2321
2431
  uniqueEntityA.uniques = ['test a'];
2322
2432
 
2323
2433
  // Saving entity...
2324
- expect(await uniqueEntityA.$save()).toEqual(true);
2434
+ await uniqueEntityA.$save();
2325
2435
  expect(uniqueEntityA.guid).not.toBeNull();
2326
2436
 
2327
2437
  // Creating entity...
@@ -2339,7 +2449,7 @@ export function EntitiesTest(
2339
2449
  }
2340
2450
  expect(uniqueEntityB.guid).toBeNull();
2341
2451
 
2342
- expect(await uniqueEntityA.$delete()).toEqual(true);
2452
+ await uniqueEntityA.$delete();
2343
2453
  });
2344
2454
 
2345
2455
  it('allows duplicate unique string if first transaction is rolled back', async () => {
@@ -2351,7 +2461,7 @@ export function EntitiesTest(
2351
2461
  uniqueEntityA.uniques = ['test a'];
2352
2462
 
2353
2463
  // Saving entity...
2354
- expect(await uniqueEntityA.$save()).toEqual(true);
2464
+ await uniqueEntityA.$save();
2355
2465
  expect(uniqueEntityA.guid).not.toBeNull();
2356
2466
 
2357
2467
  // Rolling back transaction...
@@ -2365,7 +2475,7 @@ export function EntitiesTest(
2365
2475
  uniqueEntityB.uniques = ['test a'];
2366
2476
 
2367
2477
  // Saving entity...
2368
- expect(await uniqueEntityB.$save()).toEqual(true);
2478
+ await uniqueEntityB.$save();
2369
2479
  expect(uniqueEntityB.guid).not.toBeNull();
2370
2480
 
2371
2481
  expect(uniqueEntityA.guid).not.toEqual(uniqueEntityB.guid);
@@ -2385,7 +2495,7 @@ export function EntitiesTest(
2385
2495
  expect(uniqueEntityBCheck).not.toBeNull();
2386
2496
 
2387
2497
  if (uniqueEntityBCheck != null) {
2388
- expect(await uniqueEntityBCheck.$delete()).toEqual(true);
2498
+ await uniqueEntityBCheck.$delete();
2389
2499
  }
2390
2500
  });
2391
2501
  });
@@ -2448,7 +2558,7 @@ export function ExportImportTest(
2448
2558
  let all = await nymph.getEntities({ class: TestModel });
2449
2559
  expect(Array.isArray(all)).toEqual(true);
2450
2560
  for (const cur of all) {
2451
- expect(await cur.$delete()).toEqual(true);
2561
+ await cur.$delete();
2452
2562
  }
2453
2563
 
2454
2564
  all = await nymph.getEntities({ class: TestModel });
@@ -2457,7 +2567,7 @@ export function ExportImportTest(
2457
2567
  all = await nymph.getEntities({ class: TestBModel });
2458
2568
  expect(Array.isArray(all)).toEqual(true);
2459
2569
  for (const cur of all) {
2460
- expect(await cur.$delete()).toEqual(true);
2570
+ await cur.$delete();
2461
2571
  }
2462
2572
 
2463
2573
  all = await nymph.getEntities({ class: TestBModel });
@@ -2547,11 +2657,11 @@ export function ExportImportTest(
2547
2657
  entityReferenceTest.string = 'another';
2548
2658
  entityReferenceTest.index = i + 'b';
2549
2659
 
2550
- expect(await entityReferenceTest.$save()).toEqual(true);
2660
+ await entityReferenceTest.$save();
2551
2661
  testEntity.reference = entityReferenceTest;
2552
2662
  testEntity.refArray = [entityReferenceTest];
2553
2663
 
2554
- expect(await testEntity.$save()).toEqual(true);
2664
+ await testEntity.$save();
2555
2665
  }
2556
2666
 
2557
2667
  await checkEntityDataAndCount();
@@ -0,0 +1,6 @@
1
+ export class MethodFailedError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'MethodFailedError';
5
+ }
6
+ }
@@ -14,6 +14,8 @@ export * from './InvalidParametersError.js';
14
14
 
15
15
  export * from './InvalidStateError.js';
16
16
 
17
+ export * from './MethodFailedError.js';
18
+
17
19
  export * from './NotConfiguredError.js';
18
20
 
19
21
  export * from './QueryFailedError.js';
@@ -0,0 +1 @@
1
+ export * from './transaction.js';
@@ -0,0 +1,65 @@
1
+ import Nymph from '../Nymph.js';
2
+
3
+ /**
4
+ * Run a function inside of a transaction.
5
+ *
6
+ * @param nymph The instance of Nymph to start the transaction on.
7
+ * @param name The name of the transaction.
8
+ * @param fn The function that runs inside the transaction. It is passed the
9
+ * transactional instance of Nymph. If it resolves, the transaction is
10
+ * committed, and if it throws, the transaction is rolled back.
11
+ * @param finallyFn An optional function to run after the transaction is
12
+ * committed or rolled back. It is passed the original instance
13
+ * of Nymph. It SHOULD NOT throw.
14
+ * @returns The return value of the function.
15
+ */
16
+ export async function transaction<T>(
17
+ nymph: Nymph,
18
+ name: string,
19
+ fn: (nymph: Nymph) => Promise<T>,
20
+ finallyFn?: (nymph: Nymph) => Promise<void>,
21
+ ): Promise<T> {
22
+ nymph.config.debugInfo('nymph:transaction', `Starting transaction ${name}`);
23
+ const tnymph = await nymph.startTransaction(name);
24
+ try {
25
+ const result = await fn(tnymph);
26
+
27
+ nymph.config.debugInfo(
28
+ 'nymph:transaction',
29
+ `Committing transaction ${name}`,
30
+ );
31
+ if (!(await tnymph.commit(name))) {
32
+ throw new Error('Transaction could not be committed.');
33
+ }
34
+
35
+ if (finallyFn) {
36
+ await finallyFn(nymph);
37
+ }
38
+
39
+ return result;
40
+ } catch (e: any) {
41
+ try {
42
+ nymph.config.debugInfo(
43
+ 'nymph:transaction',
44
+ `Rolling back transaction ${name}, reason: ${e.message}`,
45
+ );
46
+ await tnymph.rollback(name);
47
+ } catch (e: any) {
48
+ nymph.config.debugError(
49
+ 'nymph:transaction',
50
+ `Rollback of transaction ${name} failed, reason: ${e.message}`,
51
+ );
52
+ }
53
+ if (finallyFn) {
54
+ try {
55
+ await finallyFn(nymph);
56
+ } catch (e: any) {
57
+ nymph.config.debugError(
58
+ 'nymph:transaction',
59
+ `Finally function of transaction ${name} threw error: ${e.message}, this is a mistake and should be fixed, finally functions should never throw`,
60
+ );
61
+ }
62
+ }
63
+ throw e;
64
+ }
65
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './conf/index.js';
2
2
  export * from './driver/index.js';
3
3
  export * from './errors/index.js';
4
+ export * from './decorators/index.js';
5
+ export * from './helpers/index.js';
4
6
 
5
7
  export * from './Entity.types.js';
6
8
 
@@ -1,4 +1,6 @@
1
1
  import Entity from './Entity.js';
2
+ import { transactional } from './decorators/index.js';
3
+ import { transaction } from './helpers/index.js';
2
4
 
3
5
  export type TestModelData = {
4
6
  name?: string;
@@ -67,6 +69,53 @@ export class TestModel extends Entity<TestModelData> {
67
69
  this.$protectedTags = $protectedTags;
68
70
  };
69
71
  }
72
+
73
+ @transactional
74
+ public async $testTransactionalDecorator(throwError: boolean) {
75
+ this.$data.string = 'this has been changed';
76
+
77
+ if (this.$data.reference) {
78
+ await this.$data.reference.$wake();
79
+ this.$data.reference.string = 'this has been changed too';
80
+ await this.$data.reference.$save();
81
+ }
82
+
83
+ await this.$save();
84
+
85
+ if (throwError) {
86
+ throw Error('An error.');
87
+ }
88
+
89
+ return 'Success.';
90
+ }
91
+
92
+ public async $testTransactionHelper(throwError: boolean) {
93
+ return await transaction(
94
+ this.$nymph,
95
+ 'test-transaction',
96
+ async (nymph) => {
97
+ this.$setNymph(nymph);
98
+ this.$data.string = 'this has been changed';
99
+
100
+ if (this.$data.reference) {
101
+ await this.$data.reference.$wake();
102
+ this.$data.reference.string = 'this has been changed too';
103
+ await this.$data.reference.$save();
104
+ }
105
+
106
+ await this.$save();
107
+
108
+ if (throwError) {
109
+ throw Error('An error.');
110
+ }
111
+
112
+ return 'Success.';
113
+ },
114
+ async (nymph) => {
115
+ this.$setNymph(nymph);
116
+ },
117
+ );
118
+ }
70
119
  }
71
120
 
72
121
  /**
package/src/testMocks.ts CHANGED
@@ -8,7 +8,7 @@ import type {
8
8
  EntityJson,
9
9
  } from './Entity.types.js';
10
10
  import { Options, Selector } from './Nymph.types.js';
11
- import { ClassNotAvailableError } from './errors/index.js';
11
+ import { ClassNotAvailableError, EntityConflictError } from './errors/index.js';
12
12
  import Entity from './Entity.js';
13
13
 
14
14
  const entities: { [k: string]: EntityJson } = {};
@@ -163,7 +163,9 @@ export class MockNymph {
163
163
  entity.guid in entities &&
164
164
  (entity.mdate ?? 0) < (entities[entity.guid].mdate ?? 0)
165
165
  ) {
166
- return false;
166
+ throw new EntityConflictError(
167
+ "Entity either doesn't exist or is newer in the DB.",
168
+ );
167
169
  }
168
170
 
169
171
  entity.mdate = Date.now();