@nestjs/cqrs 11.0.3 → 12.0.0

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 (148) hide show
  1. package/.circleci/config.yml +3 -3
  2. package/.nvmrc +1 -0
  3. package/dist/aggregate-root.d.ts +3 -67
  4. package/dist/aggregate-root.js +6 -83
  5. package/dist/classes/command.d.ts +2 -2
  6. package/dist/classes/command.js +2 -6
  7. package/dist/classes/constants.js +1 -4
  8. package/dist/classes/index.d.ts +2 -2
  9. package/dist/classes/index.js +2 -18
  10. package/dist/classes/query.d.ts +2 -2
  11. package/dist/classes/query.js +2 -6
  12. package/dist/command-bus.d.ts +5 -5
  13. package/dist/command-bus.js +29 -32
  14. package/dist/constants.js +1 -4
  15. package/dist/cqrs.module.d.ts +5 -5
  16. package/dist/cqrs.module.js +34 -37
  17. package/dist/decorators/command-handler.decorator.d.ts +1 -1
  18. package/dist/decorators/command-handler.decorator.js +9 -13
  19. package/dist/decorators/constants.js +7 -10
  20. package/dist/decorators/events-handler.decorator.d.ts +1 -1
  21. package/dist/decorators/events-handler.decorator.js +9 -13
  22. package/dist/decorators/index.d.ts +5 -5
  23. package/dist/decorators/index.js +5 -21
  24. package/dist/decorators/publishable.decorator.js +3 -6
  25. package/dist/decorators/query-handler.decorator.d.ts +1 -1
  26. package/dist/decorators/query-handler.decorator.js +9 -13
  27. package/dist/decorators/saga.decorator.js +5 -9
  28. package/dist/event-bus.d.ts +10 -8
  29. package/dist/event-bus.js +51 -51
  30. package/dist/event-publisher.d.ts +6 -7
  31. package/dist/event-publisher.js +7 -10
  32. package/dist/exceptions/command-not-found.exception.js +1 -5
  33. package/dist/exceptions/index.d.ts +7 -7
  34. package/dist/exceptions/index.js +7 -23
  35. package/dist/exceptions/invalid-command-handler.exception.js +1 -5
  36. package/dist/exceptions/invalid-events-handler.exception.js +1 -5
  37. package/dist/exceptions/invalid-query-handler.exception.js +1 -5
  38. package/dist/exceptions/invalid-saga.exception.js +1 -5
  39. package/dist/exceptions/query-not-found.exception.js +1 -5
  40. package/dist/exceptions/unsupported-saga-scope.exception.js +1 -5
  41. package/dist/helpers/default-command-pubsub.d.ts +1 -1
  42. package/dist/helpers/default-command-pubsub.js +1 -5
  43. package/dist/helpers/default-event-id-provider.d.ts +1 -1
  44. package/dist/helpers/default-event-id-provider.js +3 -6
  45. package/dist/helpers/default-pubsub.d.ts +1 -1
  46. package/dist/helpers/default-pubsub.js +1 -5
  47. package/dist/helpers/default-query-pubsub.d.ts +1 -1
  48. package/dist/helpers/default-query-pubsub.js +1 -5
  49. package/dist/helpers/default-unhandled-exception-pubsub.d.ts +1 -1
  50. package/dist/helpers/default-unhandled-exception-pubsub.js +1 -5
  51. package/dist/index.d.ts +15 -14
  52. package/dist/index.js +15 -30
  53. package/dist/interfaces/aggregate-root.interface.d.ts +53 -0
  54. package/dist/interfaces/aggregate-root.interface.js +1 -0
  55. package/dist/interfaces/commands/command-bus.interface.d.ts +3 -3
  56. package/dist/interfaces/commands/command-bus.interface.js +1 -2
  57. package/dist/interfaces/commands/command-handler.interface.d.ts +2 -2
  58. package/dist/interfaces/commands/command-handler.interface.js +1 -2
  59. package/dist/interfaces/commands/command-metadata.interface.js +1 -2
  60. package/dist/interfaces/commands/command-publisher.interface.d.ts +1 -1
  61. package/dist/interfaces/commands/command-publisher.interface.js +1 -2
  62. package/dist/interfaces/commands/command.interface.js +1 -2
  63. package/dist/interfaces/cqrs-module-options.interface.d.ts +5 -5
  64. package/dist/interfaces/cqrs-module-options.interface.js +1 -2
  65. package/dist/interfaces/events/event-bus.interface.d.ts +2 -2
  66. package/dist/interfaces/events/event-bus.interface.js +1 -2
  67. package/dist/interfaces/events/event-handler.interface.d.ts +1 -1
  68. package/dist/interfaces/events/event-handler.interface.js +1 -2
  69. package/dist/interfaces/events/event-id-provider.interface.d.ts +1 -1
  70. package/dist/interfaces/events/event-id-provider.interface.js +1 -2
  71. package/dist/interfaces/events/event-publisher.interface.d.ts +2 -2
  72. package/dist/interfaces/events/event-publisher.interface.js +1 -2
  73. package/dist/interfaces/events/event.interface.js +1 -2
  74. package/dist/interfaces/events/message-source.interface.d.ts +1 -1
  75. package/dist/interfaces/events/message-source.interface.js +1 -2
  76. package/dist/interfaces/exceptions/unhandled-exception-info.interface.d.ts +2 -2
  77. package/dist/interfaces/exceptions/unhandled-exception-info.interface.js +1 -2
  78. package/dist/interfaces/exceptions/unhandled-exception-publisher.interface.d.ts +3 -3
  79. package/dist/interfaces/exceptions/unhandled-exception-publisher.interface.js +1 -2
  80. package/dist/interfaces/index.d.ts +20 -19
  81. package/dist/interfaces/index.js +20 -35
  82. package/dist/interfaces/providers-introspection-result.interface.d.ts +4 -4
  83. package/dist/interfaces/providers-introspection-result.interface.js +1 -2
  84. package/dist/interfaces/queries/query-bus.interface.d.ts +3 -3
  85. package/dist/interfaces/queries/query-bus.interface.js +1 -2
  86. package/dist/interfaces/queries/query-handler.interface.d.ts +2 -2
  87. package/dist/interfaces/queries/query-handler.interface.js +1 -2
  88. package/dist/interfaces/queries/query-metadata.interface.js +1 -2
  89. package/dist/interfaces/queries/query-publisher.interface.d.ts +1 -1
  90. package/dist/interfaces/queries/query-publisher.interface.js +1 -2
  91. package/dist/interfaces/queries/query-result.interface.js +1 -2
  92. package/dist/interfaces/queries/query.interface.js +1 -2
  93. package/dist/interfaces/saga.type.d.ts +2 -2
  94. package/dist/interfaces/saga.type.js +1 -2
  95. package/dist/mixins/index.d.ts +1 -0
  96. package/dist/mixins/index.js +1 -0
  97. package/dist/mixins/with-aggregate-root.mixin.d.ts +11 -0
  98. package/dist/mixins/with-aggregate-root.mixin.js +67 -0
  99. package/dist/operators/index.d.ts +1 -1
  100. package/dist/operators/index.js +1 -17
  101. package/dist/operators/of-type.d.ts +1 -1
  102. package/dist/operators/of-type.js +3 -6
  103. package/dist/query-bus.d.ts +5 -5
  104. package/dist/query-bus.js +28 -31
  105. package/dist/scopes/async.context.js +9 -13
  106. package/dist/scopes/index.d.ts +1 -1
  107. package/dist/scopes/index.js +1 -17
  108. package/dist/services/explorer.service.d.ts +5 -5
  109. package/dist/services/explorer.service.js +14 -20
  110. package/dist/storages/aggregate-root.storage.d.ts +3 -3
  111. package/dist/storages/aggregate-root.storage.js +1 -5
  112. package/dist/unhandled-exception-bus.d.ts +2 -2
  113. package/dist/unhandled-exception-bus.js +14 -17
  114. package/dist/utils/index.d.ts +1 -1
  115. package/dist/utils/index.js +1 -17
  116. package/dist/utils/observable-bus.js +3 -7
  117. package/package.json +34 -26
  118. package/test/e2e/basic-flows.spec.ts +46 -26
  119. package/test/e2e/generics.spec.ts +3 -3
  120. package/test/e2e/request-scope.spec.ts +23 -22
  121. package/test/e2e/sync-event-handlers-app.spec.ts +123 -0
  122. package/test/e2e/unhandled-exceptions.spec.ts +36 -20
  123. package/tsconfig.build.json +3 -2
  124. package/vitest.config.e2e.ts +13 -0
  125. package/vitest.config.ts +8 -0
  126. package/dist/exceptions/invalid-module-ref.exception.d.ts +0 -3
  127. package/dist/exceptions/invalid-module-ref.exception.js +0 -8
  128. package/dist/helpers/default-get-event-id.d.ts +0 -9
  129. package/dist/helpers/default-get-event-id.js +0 -18
  130. package/dist/helpers/default-get-event-name.d.ts +0 -2
  131. package/dist/helpers/default-get-event-name.js +0 -8
  132. package/dist/interfaces/cqrs-options.interface.d.ts +0 -10
  133. package/dist/interfaces/cqrs-options.interface.js +0 -2
  134. package/dist/interfaces/event-observable.interface.d.ts +0 -4
  135. package/dist/interfaces/event-observable.interface.js +0 -2
  136. package/dist/utils/command-handler.decorator.d.ts +0 -3
  137. package/dist/utils/command-handler.decorator.js +0 -9
  138. package/dist/utils/constants.d.ts +0 -2
  139. package/dist/utils/constants.js +0 -4
  140. package/dist/utils/default-pubsub.d.ts +0 -9
  141. package/dist/utils/default-pubsub.js +0 -14
  142. package/dist/utils/events-handler.decorator.d.ts +0 -3
  143. package/dist/utils/events-handler.decorator.js +0 -9
  144. package/eslint.config.mjs +0 -42
  145. package/index.d.ts +0 -1
  146. package/index.js +0 -6
  147. package/jest.config.js +0 -9
  148. package/test/jest-e2e.json +0 -9
package/package.json CHANGED
@@ -1,14 +1,29 @@
1
1
  {
2
2
  "name": "@nestjs/cqrs",
3
- "version": "11.0.3",
3
+ "version": "12.0.0",
4
4
  "description": "A lightweight CQRS module for Nest framework (node.js)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "engines": {
18
+ "node": "^20.19.0 || >=22.12.0"
19
+ },
5
20
  "license": "MIT",
6
21
  "url": "https://github.com/nestjs/cqrs#readme",
7
22
  "scripts": {
8
- "test": "jest",
23
+ "test": "vitest run",
9
24
  "build": "tsc -p tsconfig.build.json",
10
25
  "format": "prettier --write \"**/*.ts\"",
11
- "lint": "eslint \"src/**/*.ts\" --fix",
26
+ "lint": "oxlint src/",
12
27
  "prepublish:next": "npm run build",
13
28
  "publish:next": "npm publish --access public --tag next",
14
29
  "prepublish:npm": "npm run build",
@@ -16,36 +31,29 @@
16
31
  "prerelease": "npm run build",
17
32
  "release": "release-it",
18
33
  "prepare": "husky",
19
- "test:e2e": "jest --config ./test/jest-e2e.json --runInBand"
34
+ "test:e2e": "vitest run --config vitest.config.e2e.ts"
20
35
  },
21
36
  "devDependencies": {
22
- "@commitlint/cli": "19.8.0",
23
- "@commitlint/config-angular": "19.8.0",
24
- "@eslint/eslintrc": "3.3.1",
25
- "@eslint/js": "9.23.0",
26
- "@nestjs/common": "11.0.12",
27
- "@nestjs/core": "11.0.12",
28
- "@nestjs/testing": "11.0.12",
29
- "@types/jest": "29.5.14",
30
- "@types/node": "22.13.11",
31
- "eslint": "9.23.0",
32
- "eslint-config-prettier": "10.1.1",
33
- "eslint-plugin-prettier": "5.2.4",
34
- "globals": "16.0.0",
37
+ "@commitlint/cli": "21.2.2",
38
+ "@commitlint/config-angular": "21.2.2",
39
+ "@nestjs/common": "12.0.1",
40
+ "@nestjs/core": "12.0.1",
41
+ "@nestjs/testing": "12.0.1",
42
+ "@types/node": "24.13.3",
35
43
  "husky": "9.1.7",
36
- "jest": "29.7.0",
37
- "lint-staged": "15.5.0",
38
- "prettier": "3.5.3",
44
+ "lint-staged": "17.3.0",
45
+ "oxlint": "1.80.0",
46
+ "prettier": "3.9.6",
39
47
  "reflect-metadata": "0.2.2",
40
- "release-it": "18.1.2",
48
+ "release-it": "21.0.2",
41
49
  "rxjs": "7.8.2",
42
- "ts-jest": "29.2.6",
43
- "typescript": "5.8.2",
44
- "typescript-eslint": "8.27.0"
50
+ "typescript": "7.0.2",
51
+ "vite": "8.2.2",
52
+ "vitest": "4.1.11"
45
53
  },
46
54
  "peerDependencies": {
47
- "@nestjs/common": "^10.0.0 || ^11.0.0",
48
- "@nestjs/core": "^10.0.0 || ^11.0.0",
55
+ "@nestjs/common": "^12.0.0",
56
+ "@nestjs/core": "^12.0.0",
49
57
  "reflect-metadata": "^0.1.13 || ^0.2.0",
50
58
  "rxjs": "^7.2.0"
51
59
  },
@@ -1,20 +1,24 @@
1
1
  import { Test, TestingModule } from '@nestjs/testing';
2
2
  import { setTimeout } from 'timers/promises';
3
- import { CommandBus, QueryBus } from '../../src';
4
- import { AppModule } from '../src/app.module';
5
- import { DropAncientItemHandler } from '../src/heroes/commands/handlers/drop-ancient-item.handler';
6
- import { KillDragonHandler } from '../src/heroes/commands/handlers/kill-dragon.handler';
7
- import { DropAncientItemCommand } from '../src/heroes/commands/impl/drop-ancient-item.command';
8
- import { KillDragonCommand } from '../src/heroes/commands/impl/kill-dragon.command';
9
- import { HeroFoundItemHandler } from '../src/heroes/events/handlers/hero-found-item.handler';
10
- import { HeroKilledDragonHandler } from '../src/heroes/events/handlers/hero-killed-dragon.handler';
11
- import { HeroFoundItemEvent } from '../src/heroes/events/impl/hero-found-item.event';
12
- import { HeroKilledDragonEvent } from '../src/heroes/events/impl/hero-killed-dragon.event';
13
- import { GetHeroesQuery } from '../src/heroes/queries/impl';
14
- import { HERO_ID } from '../src/heroes/repository/fixtures/user';
15
- import { ANCIENT_ITEM_ID } from '../src/heroes/sagas/heroes.sagas';
16
- import { NoopHandler } from '../src/noop/events/handlers/noop.handler';
17
- import { waitImmediate } from '../utils/wait-immediate';
3
+ import type { MockInstance } from 'vitest';
4
+ import { CommandBus, QueryBus } from '../../src/index.js';
5
+ import { AppModule } from '../src/app.module.js';
6
+ import { DropAncientItemHandler } from '../src/heroes/commands/handlers/drop-ancient-item.handler.js';
7
+ import { KillDragonHandler } from '../src/heroes/commands/handlers/kill-dragon.handler.js';
8
+ import { DropAncientItemCommand } from '../src/heroes/commands/impl/drop-ancient-item.command.js';
9
+ import { KillDragonCommand } from '../src/heroes/commands/impl/kill-dragon.command.js';
10
+ import { DragonDiedHandler } from '../src/heroes/events/handlers/dragon-died.handler.js';
11
+ import { HeroFoundItemHandler } from '../src/heroes/events/handlers/hero-found-item.handler.js';
12
+ import { HeroKilledDragonHandler } from '../src/heroes/events/handlers/hero-killed-dragon.handler.js';
13
+ import { DragonDiedEvent } from '../src/heroes/events/impl/dragon-died.event.js';
14
+ import { HeroFoundItemEvent } from '../src/heroes/events/impl/hero-found-item.event.js';
15
+ import { HeroKilledDragonEvent } from '../src/heroes/events/impl/hero-killed-dragon.event.js';
16
+ import { DRAGON_ID } from '../src/heroes/repository/fixtures/dragon.js';
17
+ import { GetHeroesQuery, GetHeroQuery } from '../src/heroes/queries/impl/index.js';
18
+ import { HERO_ID } from '../src/heroes/repository/fixtures/user.js';
19
+ import { ANCIENT_ITEM_ID } from '../src/heroes/sagas/heroes.sagas.js';
20
+ import { NoopHandler } from '../src/noop/events/handlers/noop.handler.js';
21
+ import { waitImmediate } from '../utils/wait-immediate.js';
18
22
 
19
23
  describe('Basic flows', () => {
20
24
  let moduleRef: TestingModule;
@@ -28,23 +32,26 @@ describe('Basic flows', () => {
28
32
  });
29
33
 
30
34
  describe('when "KillDragonCommand" command is dispatched', () => {
31
- let killDragonExecuteSpy: jest.SpyInstance;
32
- let heroKilledDragonHandleSpy: jest.SpyInstance;
33
- let noopEventHandleSpy: jest.SpyInstance;
35
+ let killDragonExecuteSpy: MockInstance;
36
+ let heroKilledDragonHandleSpy: MockInstance;
37
+ let dragonDiedHandleSpy: MockInstance;
38
+ let noopEventHandleSpy: MockInstance;
34
39
  let command: KillDragonCommand;
35
40
 
36
41
  beforeAll(async () => {
37
42
  const killDragonHandler = moduleRef.get(KillDragonHandler);
38
43
  const heroKilledDragonHandler = moduleRef.get(HeroKilledDragonHandler);
44
+ const dragonDiedHandler = moduleRef.get(DragonDiedHandler);
39
45
  const noopHandler = moduleRef.get(NoopHandler);
40
46
 
41
- killDragonExecuteSpy = jest.spyOn(killDragonHandler, 'execute');
42
- heroKilledDragonHandleSpy = jest.spyOn(heroKilledDragonHandler, 'handle');
43
- noopEventHandleSpy = jest.spyOn(noopHandler, 'handle');
47
+ killDragonExecuteSpy = vi.spyOn(killDragonHandler, 'execute');
48
+ heroKilledDragonHandleSpy = vi.spyOn(heroKilledDragonHandler, 'handle');
49
+ dragonDiedHandleSpy = vi.spyOn(dragonDiedHandler, 'handle');
50
+ noopEventHandleSpy = vi.spyOn(noopHandler, 'handle');
44
51
 
45
52
  const commandBus = moduleRef.get(CommandBus);
46
53
  const heroId = HERO_ID;
47
- const dragonId = 'dragonId';
54
+ const dragonId = DRAGON_ID;
48
55
 
49
56
  command = new KillDragonCommand(heroId, dragonId);
50
57
  await commandBus.execute(command);
@@ -60,19 +67,24 @@ describe('Basic flows', () => {
60
67
  expect(heroKilledDragonHandleSpy).toHaveBeenCalledWith(event);
61
68
  });
62
69
 
70
+ it('should handle "DragonDiedEvent" event from aggregate root', () => {
71
+ const event = new DragonDiedEvent(DRAGON_ID);
72
+ expect(dragonDiedHandleSpy).toHaveBeenCalledWith(event);
73
+ });
74
+
63
75
  it('should not trigger "NoopHandler" event', () => {
64
76
  expect(noopEventHandleSpy).not.toHaveBeenCalled();
65
77
  });
66
78
 
67
79
  describe('when saga triggered', () => {
68
- let dropAncientExecuteSpy: jest.SpyInstance;
69
- let heroFoundItemSpy: jest.SpyInstance;
80
+ let dropAncientExecuteSpy: MockInstance;
81
+ let heroFoundItemSpy: MockInstance;
70
82
 
71
83
  beforeAll(async () => {
72
84
  const dropAncientItemHandler = moduleRef.get(DropAncientItemHandler);
73
85
  const heroFoundItemHandler = moduleRef.get(HeroFoundItemHandler);
74
- dropAncientExecuteSpy = jest.spyOn(dropAncientItemHandler, 'execute');
75
- heroFoundItemSpy = jest.spyOn(heroFoundItemHandler, 'handle');
86
+ dropAncientExecuteSpy = vi.spyOn(dropAncientItemHandler, 'execute');
87
+ heroFoundItemSpy = vi.spyOn(heroFoundItemHandler, 'handle');
76
88
 
77
89
  // Wait 1 second for saga to be triggered
78
90
  await setTimeout(1000);
@@ -100,6 +112,14 @@ describe('Basic flows', () => {
100
112
  });
101
113
  });
102
114
 
115
+ describe('when "GetHeroQuery" query is executed', () => {
116
+ it('should return a single hero', async () => {
117
+ const queryBus = moduleRef.get(QueryBus);
118
+ const hero = await queryBus.execute(new GetHeroQuery(1234));
119
+ expect(hero).toEqual(expect.objectContaining({ id: HERO_ID }));
120
+ });
121
+ });
122
+
103
123
  afterAll(async () => {
104
124
  await moduleRef.close();
105
125
  });
@@ -1,4 +1,4 @@
1
- import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
1
+ import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper.js';
2
2
  import { Test, TestingModule } from '@nestjs/testing';
3
3
  import {
4
4
  Command,
@@ -6,8 +6,8 @@ import {
6
6
  ICommandHandler,
7
7
  Query,
8
8
  QueryBus,
9
- } from '../../src';
10
- import { AppModule } from '../src/app.module';
9
+ } from '../../src/index.js';
10
+ import { AppModule } from '../src/app.module.js';
11
11
 
12
12
  describe('Generics', () => {
13
13
  let moduleRef: TestingModule;
@@ -1,19 +1,20 @@
1
1
  import { Test, TestingModule } from '@nestjs/testing';
2
2
  import { setTimeout } from 'timers/promises';
3
- import { AsyncContext, CommandBus, QueryBus } from '../../src';
4
- import { AppModule } from '../src/app.module';
5
- import { HERO_ID } from '../src/heroes/repository/fixtures/user';
6
- import { ANCIENT_ITEM_ID } from '../src/heroes/sagas/heroes.sagas';
7
- import { ScopedDropAncientItemHandler } from '../src/scoped/commands/handlers/scoped-drop-ancient-item.handler';
8
- import { ScopedKillDragonHandler } from '../src/scoped/commands/handlers/scoped-kill-dragon.handler';
9
- import { ScopedDropAncientItemCommand } from '../src/scoped/commands/impl/scoped-drop-ancient-item.command';
10
- import { ScopedKillDragonCommand } from '../src/scoped/commands/impl/scoped-kill-dragon.command';
11
- import { ScopedHeroFoundItemHandler } from '../src/scoped/events/handlers/scoped-hero-found-item.handler';
12
- import { ScopedHeroKilledDragonHandler } from '../src/scoped/events/handlers/scoped-hero-killed-dragon.handler';
13
- import { ScopedHeroFoundItemEvent } from '../src/scoped/events/impl/hero-found-item.event';
14
- import { ScopedHeroKilledDragonEvent } from '../src/scoped/events/impl/hero-killed-dragon.event';
15
- import { ScopedGetHeroesQuery } from '../src/scoped/queries/impl';
16
- import { waitImmediate } from '../utils/wait-immediate';
3
+ import type { MockInstance } from 'vitest';
4
+ import { AsyncContext, CommandBus, QueryBus } from '../../src/index.js';
5
+ import { AppModule } from '../src/app.module.js';
6
+ import { HERO_ID } from '../src/heroes/repository/fixtures/user.js';
7
+ import { ANCIENT_ITEM_ID } from '../src/heroes/sagas/heroes.sagas.js';
8
+ import { ScopedDropAncientItemHandler } from '../src/scoped/commands/handlers/scoped-drop-ancient-item.handler.js';
9
+ import { ScopedKillDragonHandler } from '../src/scoped/commands/handlers/scoped-kill-dragon.handler.js';
10
+ import { ScopedDropAncientItemCommand } from '../src/scoped/commands/impl/scoped-drop-ancient-item.command.js';
11
+ import { ScopedKillDragonCommand } from '../src/scoped/commands/impl/scoped-kill-dragon.command.js';
12
+ import { ScopedHeroFoundItemHandler } from '../src/scoped/events/handlers/scoped-hero-found-item.handler.js';
13
+ import { ScopedHeroKilledDragonHandler } from '../src/scoped/events/handlers/scoped-hero-killed-dragon.handler.js';
14
+ import { ScopedHeroFoundItemEvent } from '../src/scoped/events/impl/hero-found-item.event.js';
15
+ import { ScopedHeroKilledDragonEvent } from '../src/scoped/events/impl/hero-killed-dragon.event.js';
16
+ import { ScopedGetHeroesQuery } from '../src/scoped/queries/impl/index.js';
17
+ import { waitImmediate } from '../utils/wait-immediate.js';
17
18
 
18
19
  describe('Request scope', () => {
19
20
  let moduleRef: TestingModule;
@@ -29,8 +30,8 @@ describe('Request scope', () => {
29
30
  describe('when "ScopedKillDragonCommand" command is dispatched', () => {
30
31
  const asyncContext = new AsyncContext();
31
32
 
32
- let killDragonExecuteSpy: jest.SpyInstance;
33
- let heroKilledDragonHandleSpy: jest.SpyInstance;
33
+ let killDragonExecuteSpy: MockInstance;
34
+ let heroKilledDragonHandleSpy: MockInstance;
34
35
  let command: ScopedKillDragonCommand;
35
36
 
36
37
  beforeAll(async () => {
@@ -45,8 +46,8 @@ describe('Request scope', () => {
45
46
  asyncContext.id,
46
47
  );
47
48
 
48
- killDragonExecuteSpy = jest.spyOn(killDragonHandler, 'execute');
49
- heroKilledDragonHandleSpy = jest.spyOn(heroKilledDragonHandler, 'handle');
49
+ killDragonExecuteSpy = vi.spyOn(killDragonHandler, 'execute');
50
+ heroKilledDragonHandleSpy = vi.spyOn(heroKilledDragonHandler, 'handle');
50
51
 
51
52
  const commandBus = moduleRef.get(CommandBus);
52
53
  const heroId = HERO_ID;
@@ -70,8 +71,8 @@ describe('Request scope', () => {
70
71
  });
71
72
 
72
73
  describe('when saga triggered', () => {
73
- let dropAncientExecuteSpy: jest.SpyInstance;
74
- let heroFoundItemSpy: jest.SpyInstance;
74
+ let dropAncientExecuteSpy: MockInstance;
75
+ let heroFoundItemSpy: MockInstance;
75
76
 
76
77
  beforeAll(async () => {
77
78
  const dropAncientItemHandler = await moduleRef.resolve(
@@ -82,8 +83,8 @@ describe('Request scope', () => {
82
83
  ScopedHeroFoundItemHandler,
83
84
  asyncContext.id,
84
85
  );
85
- dropAncientExecuteSpy = jest.spyOn(dropAncientItemHandler, 'execute');
86
- heroFoundItemSpy = jest.spyOn(heroFoundItemHandler, 'handle');
86
+ dropAncientExecuteSpy = vi.spyOn(dropAncientItemHandler, 'execute');
87
+ heroFoundItemSpy = vi.spyOn(heroFoundItemHandler, 'handle');
87
88
 
88
89
  // Wait 1 second for saga to be triggered
89
90
  await setTimeout(1000);
@@ -0,0 +1,123 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { MockInstance, vi } from 'vitest';
3
+ import { AsyncHandlersAppModule } from '../src/sync-event-handlers/async-handlers-app.module.js';
4
+ import { EventBus } from '../../src/event-bus.js';
5
+ import { HeroKilledDragon2SlowHandler } from '../src/sync-event-handlers/events/handlers/hero-killed-dragon2-slow.handler.js';
6
+ import { HeroKilledDragonSlowHandler } from '../src/sync-event-handlers/events/handlers/hero-killed-dragon-slow.handler.js';
7
+ import { HeroFoundItemSlowHandler } from '../src/sync-event-handlers/events/handlers/hero-found-item-slow.handler.js';
8
+ import { SyncHandlersAppModule } from '../src/sync-event-handlers/sync-handlers-app.module.js';
9
+ import { HeroFoundItemEvent } from '../src/heroes/events/impl/hero-found-item.event.js';
10
+ import { HeroKilledDragonEvent } from '../src/heroes/events/impl/hero-killed-dragon.event.js';
11
+
12
+ describe('Sync event handlers', () => {
13
+ let moduleRef: TestingModule;
14
+ let eventBus: EventBus;
15
+ let heroFoundItemHandler: HeroFoundItemSlowHandler;
16
+ let heroFoundItemEnded: MockInstance;
17
+ let heroKilledDragonHandler: HeroKilledDragonSlowHandler;
18
+ let heroKilledDragonEnded: MockInstance;
19
+ let heroKilledDragon2Handler: HeroKilledDragon2SlowHandler;
20
+ let heroKilledDragon2Ended: MockInstance;
21
+
22
+ beforeEach(() => {
23
+ heroFoundItemEnded.mockClear();
24
+ heroKilledDragonEnded.mockClear();
25
+ heroKilledDragon2Ended.mockClear();
26
+ });
27
+
28
+ describe('when event handlers are fully asynchronous', () => {
29
+ beforeAll(async () => {
30
+ moduleRef = await Test.createTestingModule({
31
+ imports: [AsyncHandlersAppModule],
32
+ }).compile();
33
+
34
+ await moduleRef.init();
35
+ eventBus = moduleRef.get(EventBus);
36
+ heroFoundItemHandler = moduleRef.get(HeroFoundItemSlowHandler);
37
+ heroKilledDragonHandler = moduleRef.get(HeroKilledDragonSlowHandler);
38
+ heroKilledDragon2Handler = moduleRef.get(HeroKilledDragon2SlowHandler);
39
+ heroFoundItemEnded = vi.spyOn(heroFoundItemHandler, 'end');
40
+ heroKilledDragonEnded = vi.spyOn(heroKilledDragonHandler, 'end');
41
+ heroKilledDragon2Ended = vi.spyOn(heroKilledDragon2Handler, 'end');
42
+ });
43
+
44
+ describe('when "HeroFoundItemEvent" event is published', () => {
45
+ it('should wait for the event handlers to complete', async () => {
46
+ const event = new HeroFoundItemEvent('hero1', 'item1');
47
+
48
+ await eventBus.publish(event);
49
+
50
+ expect(heroFoundItemEnded).not.toHaveBeenCalled();
51
+ expect(heroKilledDragonEnded).not.toHaveBeenCalled();
52
+ expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
53
+ });
54
+ });
55
+
56
+ describe('when "HeroKilledDragonEvent" event is published', () => {
57
+ it('should wait for the event handlers to complete', async () => {
58
+ const event = new HeroKilledDragonEvent('hero1', 'dragon1');
59
+
60
+ await eventBus.publish(event);
61
+
62
+ expect(heroFoundItemEnded).not.toHaveBeenCalled();
63
+ expect(heroKilledDragonEnded).not.toHaveBeenCalled();
64
+ expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
65
+ });
66
+ });
67
+ });
68
+
69
+ describe('when event handlers are synchronous', () => {
70
+ beforeAll(async () => {
71
+ moduleRef = await Test.createTestingModule({
72
+ imports: [SyncHandlersAppModule],
73
+ }).compile();
74
+
75
+ await moduleRef.init();
76
+ eventBus = moduleRef.get(EventBus);
77
+ heroFoundItemHandler = moduleRef.get(HeroFoundItemSlowHandler);
78
+ heroKilledDragonHandler = moduleRef.get(HeroKilledDragonSlowHandler);
79
+ heroKilledDragon2Handler = moduleRef.get(HeroKilledDragon2SlowHandler);
80
+ heroFoundItemEnded = vi.spyOn(heroFoundItemHandler, 'end');
81
+ heroKilledDragonEnded = vi.spyOn(heroKilledDragonHandler, 'end');
82
+ heroKilledDragon2Ended = vi.spyOn(heroKilledDragon2Handler, 'end');
83
+ });
84
+
85
+ describe('when "HeroFoundItemEvent" event is published', () => {
86
+ it('should wait for the event handlers to complete', async () => {
87
+ const event = new HeroFoundItemEvent('hero1', 'item1');
88
+
89
+ await eventBus.publish(event);
90
+
91
+ expect(heroFoundItemEnded).toHaveBeenCalledTimes(1);
92
+ expect(heroKilledDragonEnded).not.toHaveBeenCalled();
93
+ expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
94
+ });
95
+ });
96
+
97
+ describe('when "HeroKilledDragonEvent" event is published', () => {
98
+ it('should wait for the event handlers to complete', async () => {
99
+ const event = new HeroKilledDragonEvent('hero1', 'dragon1');
100
+
101
+ await eventBus.publish(event);
102
+
103
+ expect(heroFoundItemEnded).not.toHaveBeenCalled();
104
+ expect(heroKilledDragonEnded).toHaveBeenCalledTimes(1);
105
+ expect(heroKilledDragon2Ended).toHaveBeenCalledTimes(1);
106
+ });
107
+ });
108
+
109
+ describe('when an unknown event is published', () => {
110
+ it('should return directly', async () => {
111
+ await eventBus.publish({ id: 'unknown event' });
112
+
113
+ expect(heroFoundItemEnded).not.toHaveBeenCalled();
114
+ expect(heroKilledDragonEnded).not.toHaveBeenCalled();
115
+ expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
116
+ });
117
+ });
118
+ });
119
+
120
+ afterAll(async () => {
121
+ await moduleRef.close();
122
+ });
123
+ });
@@ -1,9 +1,9 @@
1
1
  import { Test, TestingModule } from '@nestjs/testing';
2
2
  import { take } from 'rxjs';
3
- import { CommandBus, UnhandledExceptionBus } from '../../src';
4
- import { AppModule } from '../src/app.module';
5
- import { UnhandledExceptionCommand } from '../src/errors/commands/unhandled-exception.command';
6
- import { UnhandledExceptionEvent } from '../src/errors/events/unhandled-exception.event';
3
+ import { CommandBus, UnhandledExceptionBus } from '../../src/index.js';
4
+ import { AppModule } from '../src/app.module.js';
5
+ import { UnhandledExceptionCommand } from '../src/errors/commands/unhandled-exception.command.js';
6
+ import { UnhandledExceptionEvent } from '../src/errors/events/unhandled-exception.event.js';
7
7
 
8
8
  describe('Unhandled exceptions', () => {
9
9
  let moduleRef: TestingModule;
@@ -36,34 +36,50 @@ describe('Unhandled exceptions', () => {
36
36
  });
37
37
 
38
38
  describe('when exception is thrown from event handler', () => {
39
- it('should forward exception to UnhandledExceptionBus', (done) => {
39
+ it('should forward exception to UnhandledExceptionBus', async () => {
40
40
  const command = new UnhandledExceptionCommand('event');
41
41
  const commandBus = moduleRef.get(CommandBus);
42
42
 
43
- unhandledExceptionBus.pipe(take(1)).subscribe((exceptionInfo) => {
44
- expect(exceptionInfo.exception).toEqual(
45
- new Error(`Unhandled exception in ${command.failAt}`),
46
- );
47
- expect(exceptionInfo.cause).toBeInstanceOf(UnhandledExceptionEvent);
48
- done();
43
+ const exceptionPromise = new Promise<void>((resolve, reject) => {
44
+ unhandledExceptionBus.pipe(take(1)).subscribe((exceptionInfo) => {
45
+ try {
46
+ expect(exceptionInfo.exception).toEqual(
47
+ new Error(`Unhandled exception in ${command.failAt}`),
48
+ );
49
+ expect(exceptionInfo.cause).toBeInstanceOf(UnhandledExceptionEvent);
50
+ resolve();
51
+ } catch (err) {
52
+ reject(err);
53
+ }
54
+ });
55
+ commandBus.execute(command).catch((err) => reject(err));
49
56
  });
50
- commandBus.execute(command).catch((err) => done(err));
57
+
58
+ await exceptionPromise;
51
59
  });
52
60
  });
53
61
 
54
62
  describe('when exception is thrown from saga', () => {
55
- it('should forward exception to UnhandledExceptionBus', (done) => {
63
+ it('should forward exception to UnhandledExceptionBus', async () => {
56
64
  const command = new UnhandledExceptionCommand('saga');
57
65
  const commandBus = moduleRef.get(CommandBus);
58
66
 
59
- unhandledExceptionBus.pipe(take(1)).subscribe((exceptionInfo) => {
60
- expect(exceptionInfo.exception).toEqual(
61
- new Error(`Unhandled exception in ${command.failAt}`),
62
- );
63
- expect(exceptionInfo.cause).toEqual('onError');
64
- done();
67
+ const exceptionPromise = new Promise<void>((resolve, reject) => {
68
+ unhandledExceptionBus.pipe(take(1)).subscribe((exceptionInfo) => {
69
+ try {
70
+ expect(exceptionInfo.exception).toEqual(
71
+ new Error(`Unhandled exception in ${command.failAt}`),
72
+ );
73
+ expect(exceptionInfo.cause).toEqual('onError');
74
+ resolve();
75
+ } catch (err) {
76
+ reject(err);
77
+ }
78
+ });
79
+ commandBus.execute(command).catch((err) => reject(err));
65
80
  });
66
- commandBus.execute(command).catch((err) => done(err));
81
+
82
+ await exceptionPromise;
67
83
  });
68
84
  });
69
85
 
@@ -2,10 +2,11 @@
2
2
  "extends": "./tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "./dist",
5
- "rootDir": "./src"
5
+ "rootDir": "./src",
6
+ "types": ["node"]
6
7
  },
7
8
  "include": [
8
- "src/**/*",
9
+ "src/**/*"
9
10
  ],
10
11
  "exclude": [
11
12
  "node_modules",
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ oxc: {
5
+ transform: { decorators: { legacy: true, emitDecoratorMetadata: true } },
6
+ },
7
+ test: {
8
+ globals: true,
9
+ environment: 'node',
10
+ include: ['test/e2e/**/*.spec.ts'],
11
+ fileParallelism: false,
12
+ },
13
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ oxc: {
5
+ transform: { decorators: { legacy: true, emitDecoratorMetadata: true } },
6
+ },
7
+ test: { globals: true, environment: 'node', include: ['src/**/*.spec.ts'] },
8
+ });
@@ -1,3 +0,0 @@
1
- export declare class InvalidModuleRefException extends Error {
2
- constructor();
3
- }
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- class InvalidModuleRefException extends Error {
4
- constructor() {
5
- super(`Invalid ModuleRef exception. Remember to set module reference "setModuleRef()".`);
6
- }
7
- }
8
- exports.InvalidModuleRefException = InvalidModuleRefException;
@@ -1,9 +0,0 @@
1
- import { IEvent } from '../interfaces';
2
- import { Type } from '@nestjs/common';
3
- /**
4
- * Null if the published class is not connected to any handler
5
- * @param event
6
- * @returns
7
- */
8
- export declare const defaultGetEventId: <EventBase extends IEvent = IEvent>(event: EventBase) => string;
9
- export declare const defaultReflectEventId: <EventBase extends Type<IEvent> = Type<IEvent>>(event: EventBase) => string;
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultReflectEventId = exports.defaultGetEventId = void 0;
4
- const constants_1 = require("../decorators/constants");
5
- /**
6
- * Null if the published class is not connected to any handler
7
- * @param event
8
- * @returns
9
- */
10
- const defaultGetEventId = (event) => {
11
- const { constructor } = Object.getPrototypeOf(event);
12
- return Reflect.getMetadata(constants_1.EVENT_METADATA, constructor)?.id ?? null;
13
- };
14
- exports.defaultGetEventId = defaultGetEventId;
15
- const defaultReflectEventId = (event) => {
16
- return Reflect.getMetadata(constants_1.EVENT_METADATA, event).id;
17
- };
18
- exports.defaultReflectEventId = defaultReflectEventId;
@@ -1,2 +0,0 @@
1
- import { IEvent } from '../interfaces';
2
- export declare const defaultGetEventName: <EventBase extends IEvent = IEvent>(event: EventBase) => string;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultGetEventName = void 0;
4
- const defaultGetEventName = (event) => {
5
- const { constructor } = Object.getPrototypeOf(event);
6
- return constructor.name;
7
- };
8
- exports.defaultGetEventName = defaultGetEventName;
@@ -1,10 +0,0 @@
1
- import { Type } from '@nestjs/common';
2
- import { ICommandHandler } from './commands/command-handler.interface';
3
- import { IEventHandler } from './events/event-handler.interface';
4
- import { IQueryHandler } from './queries/query-handler.interface';
5
- export interface CqrsOptions {
6
- events?: Type<IEventHandler>[];
7
- queries?: Type<IQueryHandler>[];
8
- commands?: Type<ICommandHandler>[];
9
- sagas?: Type<any>[];
10
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- export interface EventObservable<T> {
3
- ofType(...events: any[]): Observable<T>;
4
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +0,0 @@
1
- import 'reflect-metadata';
2
- import { ICommand } from '../index';
3
- export declare const CommandHandler: (command: ICommand) => ClassDecorator;