@tsed/mikro-orm 8.0.1 → 8.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,13 +2,14 @@
2
2
  "name": "@tsed/mikro-orm",
3
3
  "description": "MikroORM package for Ts.ED framework",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.3",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
9
9
  "typings": "./lib/types/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
+ "@tsed/source": "./src/index.ts",
12
13
  "types": "./lib/types/index.d.ts",
13
14
  "import": "./lib/esm/index.js",
14
15
  "default": "./lib/esm/index.js"
@@ -27,15 +28,15 @@
27
28
  "devDependencies": {
28
29
  "@mikro-orm/core": "6.3.12",
29
30
  "@mikro-orm/mongodb": "6.3.12",
30
- "@tsed/barrels": "8.0.1",
31
- "@tsed/core": "8.0.1",
32
- "@tsed/di": "8.0.1",
33
- "@tsed/json-mapper": "8.0.1",
31
+ "@tsed/barrels": "8.0.3",
32
+ "@tsed/core": "8.0.3",
33
+ "@tsed/di": "8.0.3",
34
+ "@tsed/json-mapper": "8.0.3",
34
35
  "@tsed/logger": "^6.7.8",
35
- "@tsed/platform-http": "8.0.1",
36
- "@tsed/schema": "8.0.1",
37
- "@tsed/testcontainers-mongo": "8.0.1",
38
- "@tsed/typescript": "8.0.1",
36
+ "@tsed/platform-http": "8.0.3",
37
+ "@tsed/schema": "8.0.3",
38
+ "@tsed/testcontainers-mongo": "8.0.3",
39
+ "@tsed/typescript": "8.0.3",
39
40
  "cross-env": "7.0.3",
40
41
  "eslint": "9.12.0",
41
42
  "ts-mockito": "^2.6.1",
@@ -44,10 +45,10 @@
44
45
  },
45
46
  "peerDependencies": {
46
47
  "@mikro-orm/core": ">=4.5.9",
47
- "@tsed/core": "8.0.1",
48
- "@tsed/di": "8.0.1",
48
+ "@tsed/core": "8.0.3",
49
+ "@tsed/di": "8.0.3",
49
50
  "@tsed/logger": ">=6.7.5",
50
- "@tsed/platform-http": "8.0.1"
51
+ "@tsed/platform-http": "8.0.3"
51
52
  },
52
53
  "repository": "https://github.com/tsedio/tsed",
53
54
  "bugs": {
package/readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p style="text-align: center" align="center">
2
- <a href="https://tsed.io" target="_blank"><img src="https://tsed.io/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
2
+ <a href="https://tsed.dev" target="_blank"><img src="https://tsed.dev/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
3
3
  </p>
4
4
 
5
5
  <div align="center">
@@ -16,9 +16,9 @@
16
16
  </div>
17
17
 
18
18
  <div align="center">
19
- <a href="https://tsed.io/">Website</a>
19
+ <a href="https://tsed.dev/">Website</a>
20
20
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
21
- <a href="https://tsed.io/getting-started/">Getting started</a>
21
+ <a href="https://tsed.dev/getting-started/">Getting started</a>
22
22
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
23
23
  <a href="https://api.tsed.io/rest/slack/tsedio/tsed">Slack</a>
24
24
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
@@ -27,7 +27,7 @@
27
27
 
28
28
  <hr />
29
29
 
30
- A package of Ts.ED framework. See website: https://tsed.io/tutorials/mikro-orm
30
+ A package of Ts.ED framework. See website: https://tsed.dev/tutorials/mikro-orm
31
31
 
32
32
  ## Feature
33
33
 
@@ -219,7 +219,7 @@ export class User {
219
219
  }
220
220
  ```
221
221
 
222
- Now, the model is correctly defined and can be used with a [Controller](https://tsed.io/docs/controllers.html)
222
+ Now, the model is correctly defined and can be used with a [Controller](https://tsed.dev/docs/controllers.html)
223
223
  , [AJV validation](tutorials/ajv.md),
224
224
  [Swagger](tutorials/swagger.md) and [MikroORM](https://mikro-orm.io/docs/defining-entities).
225
225
 
@@ -462,7 +462,7 @@ When using the `AfterTransactionCommit` hook, you need to handle eventual consis
462
462
 
463
463
  ## Contributors
464
464
 
465
- Please read [contributing guidelines here](https://tsed.io/contributing.html)
465
+ Please read [contributing guidelines here](https://tsed.dev/contributing.html)
466
466
 
467
467
  <a href="https://github.com/tsedio/tsed/graphs/contributors"><img src="https://opencollective.com/tsed/contributors.svg?width=890" /></a>
468
468
 
@@ -0,0 +1,110 @@
1
+ import {EntityManager, EventSubscriber, MikroORM, Options} from "@mikro-orm/core";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+ import {anyOfClass, anything, deepEqual, instance, mock, reset, verify, when} from "ts-mockito";
4
+
5
+ import {Subscriber} from "./decorators/subscriber.js";
6
+ import {MikroOrmModule} from "./MikroOrmModule.js";
7
+ import {MikroOrmContext} from "./services/MikroOrmContext.js";
8
+ import {MikroOrmRegistry} from "./services/MikroOrmRegistry.js";
9
+
10
+ class Subscriber1 implements EventSubscriber {}
11
+
12
+ @Subscriber()
13
+ class Subscriber2 implements EventSubscriber {}
14
+
15
+ describe("MikroOrmModule", () => {
16
+ const config: Options = {
17
+ type: "mongo",
18
+ entities: [],
19
+ clientUrl: "mongo://localhost",
20
+ subscribers: [new Subscriber1()]
21
+ } as never;
22
+ const mockedMikroOrmRegistry = mock<MikroOrmRegistry>();
23
+ const mockedMikroOrmContext = mock<MikroOrmContext>();
24
+ const mockedMikroORM = mock<MikroORM>();
25
+ const mockedEntityManager = mock<EntityManager>();
26
+
27
+ let mikroOrmModule!: MikroOrmModule;
28
+
29
+ beforeEach(async () => {
30
+ await PlatformTest.create({
31
+ mikroOrm: [config],
32
+ imports: [
33
+ {
34
+ token: MikroOrmContext,
35
+ use: instance(mockedMikroOrmContext)
36
+ },
37
+ {
38
+ token: MikroOrmRegistry,
39
+ use: instance(mockedMikroOrmRegistry)
40
+ }
41
+ ]
42
+ });
43
+
44
+ mikroOrmModule = PlatformTest.get<MikroOrmModule>(MikroOrmModule);
45
+ });
46
+
47
+ afterEach(() => {
48
+ vi.resetAllMocks();
49
+
50
+ reset<MikroOrmRegistry | EntityManager | MikroORM | MikroOrmContext>(
51
+ mockedMikroOrmRegistry,
52
+ mockedMikroOrmContext,
53
+ mockedMikroORM,
54
+ mockedEntityManager
55
+ );
56
+
57
+ return PlatformTest.reset();
58
+ });
59
+
60
+ describe("$onInit", () => {
61
+ it("should register the subscribers", async () => {
62
+ // act
63
+ await mikroOrmModule.$onInit();
64
+
65
+ // assert
66
+ verify(
67
+ mockedMikroOrmRegistry.register(deepEqual({...config, subscribers: [anyOfClass(Subscriber1), anyOfClass(Subscriber2)]}))
68
+ ).called();
69
+ });
70
+ });
71
+
72
+ describe("$onDestroy", () => {
73
+ it("should destroy the corresponding instances", async () => {
74
+ // act
75
+ await mikroOrmModule.$onDestroy();
76
+
77
+ // assert
78
+ verify(mockedMikroOrmRegistry.clear()).called();
79
+ });
80
+ });
81
+
82
+ describe("$alterRunInContext", () => {
83
+ it("should return a function", () => {
84
+ // arrange
85
+ const next = vi.fn();
86
+
87
+ // act
88
+ const result = mikroOrmModule.$alterRunInContext(next);
89
+
90
+ // assert
91
+ expect(result).toBeInstanceOf(Function);
92
+ });
93
+
94
+ it("should make a function create a context", () => {
95
+ // arrange
96
+ const next = vi.fn();
97
+ const manager = instance(mockedEntityManager);
98
+
99
+ when(mockedMikroOrmRegistry.values()).thenReturn([instance(mockedMikroORM)] as unknown as IterableIterator<MikroORM>);
100
+ when(mockedMikroORM.em).thenReturn(manager);
101
+ when(mockedMikroOrmContext.run(anything(), anything())).thenReturn();
102
+
103
+ // act
104
+ mikroOrmModule.$alterRunInContext(next)();
105
+
106
+ // assert
107
+ verify(mockedMikroOrmContext.run(deepEqual([manager]), next)).once();
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,104 @@
1
+ import "./services/MikroOrmFactory.js";
2
+
3
+ import {EventSubscriber, Options} from "@mikro-orm/core";
4
+ import {classOf, isFunction, Store} from "@tsed/core";
5
+ import {
6
+ AlterRunInContext,
7
+ Constant,
8
+ Inject,
9
+ InjectorService,
10
+ LocalsContainer,
11
+ Module,
12
+ OnDestroy,
13
+ OnInit,
14
+ ProviderScope,
15
+ registerProvider
16
+ } from "@tsed/di";
17
+
18
+ import {DEFAULT_CONTEXT_NAME, SUBSCRIBER_INJECTION_TYPE} from "./constants.js";
19
+ import {OptimisticLockErrorFilter} from "./filters/OptimisticLockErrorFilter.js";
20
+ import {RetryStrategy} from "./interfaces/RetryStrategy.js";
21
+ import {MikroOrmContext} from "./services/MikroOrmContext.js";
22
+ import {MikroOrmRegistry} from "./services/MikroOrmRegistry.js";
23
+
24
+ declare global {
25
+ namespace TsED {
26
+ interface Configuration {
27
+ /**
28
+ * The ORM configuration, entity metadata.
29
+ * If you omit the `options` parameter, your CLI config will be used.
30
+ */
31
+ mikroOrm?: Options[];
32
+ }
33
+ }
34
+ }
35
+
36
+ @Module({
37
+ responseFilters: [OptimisticLockErrorFilter]
38
+ })
39
+ export class MikroOrmModule implements OnDestroy, OnInit, AlterRunInContext {
40
+ @Constant("mikroOrm", [])
41
+ private readonly settings!: Options[];
42
+
43
+ @Inject()
44
+ private readonly registry!: MikroOrmRegistry;
45
+
46
+ @Inject()
47
+ private readonly context!: MikroOrmContext;
48
+
49
+ @Inject()
50
+ private readonly injector!: InjectorService;
51
+
52
+ constructor(@Inject(SUBSCRIBER_INJECTION_TYPE) private subscribers: EventSubscriber[]) {}
53
+
54
+ public async $onInit(): Promise<void> {
55
+ const container = new LocalsContainer();
56
+
57
+ await Promise.all(
58
+ this.settings.map((opts) =>
59
+ this.registry.register({
60
+ ...opts,
61
+ subscribers: this.getSubscribers(opts, container)
62
+ })
63
+ )
64
+ );
65
+ }
66
+
67
+ public $onDestroy(): Promise<void> {
68
+ return this.registry.clear();
69
+ }
70
+
71
+ public $alterRunInContext(next: (...args: unknown[]) => unknown): () => unknown | Promise<() => unknown> {
72
+ return () => this.createContext(next);
73
+ }
74
+
75
+ private getSubscribers(opts: Pick<Options, "subscribers" | "contextName">, container: LocalsContainer) {
76
+ return [...this.getUnmanagedSubscribers(opts, container), ...this.getManagedSubscribers(opts.contextName)];
77
+ }
78
+
79
+ private getUnmanagedSubscribers(opts: Pick<Options, "subscribers">, container: LocalsContainer) {
80
+ return (opts.subscribers ?? []).map((subscriber) => {
81
+ // Starting from https://github.com/mikro-orm/mikro-orm/issues/4231 mikro-orm
82
+ // accept also accepts class reference, not just instances.
83
+ if (isFunction(subscriber)) {
84
+ return this.injector.invoke(subscriber, {locals: container});
85
+ }
86
+
87
+ return subscriber;
88
+ });
89
+ }
90
+
91
+ private getManagedSubscribers(contextName: string = DEFAULT_CONTEXT_NAME): EventSubscriber[] {
92
+ return this.subscribers.filter((instance) => Store.from(classOf(instance)).get(SUBSCRIBER_INJECTION_TYPE)?.contextName === contextName);
93
+ }
94
+
95
+ private createContext(next: (...args: unknown[]) => unknown): unknown {
96
+ const instances = [...this.registry.values()];
97
+ const managers = instances.map((orm) => orm.em);
98
+
99
+ return this.context.run(managers, next);
100
+ }
101
+ }
102
+
103
+ // TODO: the IoC container should provide null or undefined by default until tsedio/tsed#1694 is closed
104
+ registerProvider({token: RetryStrategy, useValue: null});
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_CONTEXT_NAME = "default";
2
+ export const SUBSCRIBER_INJECTION_TYPE = Symbol("Subscriber");
@@ -0,0 +1,98 @@
1
+ import {MikroORM} from "@mikro-orm/core";
2
+ import {MongoEntityManager} from "@mikro-orm/mongodb";
3
+ import {DITest, Injectable} from "@tsed/di";
4
+ import {afterEach, beforeEach} from "vitest";
5
+
6
+ import {MikroOrmRegistry} from "../services/MikroOrmRegistry.js";
7
+ import {Em, EntityManager, entityManager} from "./entityManager.js";
8
+
9
+ describe("@EntityManager()", () => {
10
+ beforeEach(() => DITest.create());
11
+ afterEach(() => DITest.reset());
12
+
13
+ describe("decorator", () => {
14
+ it("should decorate property (without context)", async () => {
15
+ @Injectable()
16
+ class UsersService {
17
+ @Em()
18
+ public readonly em!: MongoEntityManager;
19
+ }
20
+
21
+ const ormRegistry = {
22
+ get: vi.fn().mockReturnValue({em: {id: "id"}} as never)
23
+ };
24
+
25
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
26
+ {
27
+ token: MikroOrmRegistry,
28
+ use: ormRegistry
29
+ }
30
+ ]);
31
+ expect(ormRegistry.get).toHaveBeenCalledWith(undefined);
32
+ expect(usersService.em).toEqual({id: "id"});
33
+ });
34
+ it("should decorate property (with context)", async () => {
35
+ @Injectable()
36
+ class UsersService {
37
+ @EntityManager("context")
38
+ public readonly orm!: MikroORM;
39
+ }
40
+
41
+ const ormRegistry = {
42
+ get: vi.fn().mockReturnValue({em: {id: "id"}} as never)
43
+ };
44
+
45
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
46
+ {
47
+ token: MikroOrmRegistry,
48
+ use: ormRegistry
49
+ }
50
+ ]);
51
+
52
+ expect(ormRegistry.get).toHaveBeenCalledWith("context");
53
+ expect(usersService.orm).toEqual({id: "id"});
54
+ });
55
+ });
56
+
57
+ describe("prop function", () => {
58
+ it("should inject property (without context)", async () => {
59
+ @Injectable()
60
+ class UsersService {
61
+ public readonly em = entityManager();
62
+ }
63
+
64
+ const ormRegistry = {
65
+ get: vi.fn().mockReturnValue({em: {id: "id"}} as never)
66
+ };
67
+
68
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
69
+ {
70
+ token: MikroOrmRegistry,
71
+ use: ormRegistry
72
+ }
73
+ ]);
74
+ expect(ormRegistry.get).toHaveBeenCalledWith(undefined);
75
+ expect(usersService.em).toEqual({id: "id"});
76
+ });
77
+ it("should inject property (with context)", async () => {
78
+ @Injectable()
79
+ class UsersService {
80
+ public readonly em = entityManager("context");
81
+ }
82
+
83
+ const ormRegistry = {
84
+ get: vi.fn().mockReturnValue({em: {id: "id"}} as never)
85
+ };
86
+
87
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
88
+ {
89
+ token: MikroOrmRegistry,
90
+ use: ormRegistry
91
+ }
92
+ ]);
93
+
94
+ expect(ormRegistry.get).toHaveBeenCalledWith("context");
95
+ expect(usersService.em).toEqual({id: "id"});
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,25 @@
1
+ import {Inject} from "@tsed/di";
2
+
3
+ import {MikroOrmRegistry} from "../services/MikroOrmRegistry.js";
4
+ import {orm} from "./orm.js";
5
+
6
+ export function entityManager(contextName?: string) {
7
+ return orm(contextName)?.em;
8
+ }
9
+
10
+ /**
11
+ * Get the entity manager for the given context name.
12
+ * @param {String} contextName
13
+ * @decorator
14
+ * @mikroOrm
15
+ */
16
+ export const EntityManager = (contextName?: string): PropertyDecorator =>
17
+ Inject(MikroOrmRegistry, {transform: (registry: MikroOrmRegistry) => registry.get(contextName)?.em}) as PropertyDecorator;
18
+
19
+ /**
20
+ * Get the entity manager for the given context name.
21
+ * @param {String} connectionName
22
+ * @decorator
23
+ * @mikroOrm
24
+ */
25
+ export const Em = EntityManager;
@@ -0,0 +1,97 @@
1
+ import {MikroORM} from "@mikro-orm/core";
2
+ import {DITest, Injectable} from "@tsed/di";
3
+ import {afterEach, beforeEach} from "vitest";
4
+
5
+ import {MikroOrmRegistry} from "../services/MikroOrmRegistry.js";
6
+ import {Orm, orm} from "./orm.js";
7
+
8
+ describe("@Orm()", () => {
9
+ beforeEach(() => DITest.create());
10
+ afterEach(() => DITest.reset());
11
+
12
+ describe("decorator", () => {
13
+ it("should decorate property (without context)", async () => {
14
+ @Injectable()
15
+ class UsersService {
16
+ @Orm()
17
+ public readonly orm!: MikroORM;
18
+ }
19
+
20
+ const ormRegistry = {
21
+ get: vi.fn().mockReturnValue({id: "id"} as never)
22
+ };
23
+
24
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
25
+ {
26
+ token: MikroOrmRegistry,
27
+ use: ormRegistry
28
+ }
29
+ ]);
30
+ expect(ormRegistry.get).toHaveBeenCalledWith(undefined);
31
+ expect(usersService.orm).toEqual({id: "id"});
32
+ });
33
+ it("should decorate property (with context)", async () => {
34
+ @Injectable()
35
+ class UsersService {
36
+ @Orm("context")
37
+ public readonly orm!: MikroORM;
38
+ }
39
+
40
+ const ormRegistry = {
41
+ get: vi.fn().mockReturnValue({id: "id"} as never)
42
+ };
43
+
44
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
45
+ {
46
+ token: MikroOrmRegistry,
47
+ use: ormRegistry
48
+ }
49
+ ]);
50
+
51
+ expect(ormRegistry.get).toHaveBeenCalledWith("context");
52
+ expect(usersService.orm).toEqual({id: "id"});
53
+ });
54
+ });
55
+
56
+ describe("prop function", () => {
57
+ it("should inject property (without context)", async () => {
58
+ @Injectable()
59
+ class UsersService {
60
+ public readonly orm = orm();
61
+ }
62
+
63
+ const ormRegistry = {
64
+ get: vi.fn().mockReturnValue({id: "id"} as never)
65
+ };
66
+
67
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
68
+ {
69
+ token: MikroOrmRegistry,
70
+ use: ormRegistry
71
+ }
72
+ ]);
73
+ expect(ormRegistry.get).toHaveBeenCalledWith(undefined);
74
+ expect(usersService.orm).toEqual({id: "id"});
75
+ });
76
+ it("should inject property (with context)", async () => {
77
+ @Injectable()
78
+ class UsersService {
79
+ public readonly orm = orm("context");
80
+ }
81
+
82
+ const ormRegistry = {
83
+ get: vi.fn().mockReturnValue({id: "id"} as never)
84
+ };
85
+
86
+ const usersService = await DITest.invoke<UsersService>(UsersService, [
87
+ {
88
+ token: MikroOrmRegistry,
89
+ use: ormRegistry
90
+ }
91
+ ]);
92
+
93
+ expect(ormRegistry.get).toHaveBeenCalledWith("context");
94
+ expect(usersService.orm).toEqual({id: "id"});
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,19 @@
1
+ import {MikroORM} from "@mikro-orm/core";
2
+ import {Inject, inject} from "@tsed/di";
3
+
4
+ import {MikroOrmRegistry} from "../services/MikroOrmRegistry.js";
5
+
6
+ /**
7
+ * Get the ORM for the given context name using new inject() function.
8
+ * @param contextName
9
+ */
10
+ export function orm(contextName?: string): MikroORM | undefined {
11
+ return inject(MikroOrmRegistry).get(contextName);
12
+ }
13
+
14
+ /**
15
+ * Get the ORM for the given context name.
16
+ * @param {String} contextName
17
+ */
18
+ export const Orm = (contextName?: string): PropertyDecorator =>
19
+ Inject(MikroOrmRegistry, {transform: (registry: MikroOrmRegistry) => registry.get(contextName)}) as PropertyDecorator;
@@ -0,0 +1,27 @@
1
+ import {EventSubscriber} from "@mikro-orm/core";
2
+ import {Store} from "@tsed/core";
3
+
4
+ import {DEFAULT_CONTEXT_NAME, SUBSCRIBER_INJECTION_TYPE} from "../constants.js";
5
+ import {Subscriber} from "./subscriber.js";
6
+
7
+ @Subscriber()
8
+ export class Subscriber1 implements EventSubscriber {}
9
+
10
+ @Subscriber({contextName: "non-default"})
11
+ export class Subscriber2 implements EventSubscriber {}
12
+
13
+ describe("@Subscriber", () => {
14
+ it("should decorate the class", () => {
15
+ // act
16
+ const result = Store.from(Subscriber1).get(SUBSCRIBER_INJECTION_TYPE);
17
+ // assert
18
+ expect(result).toEqual({contextName: DEFAULT_CONTEXT_NAME});
19
+ });
20
+
21
+ it("should decorate the class tying a context name to it", () => {
22
+ // act
23
+ const result = Store.from(Subscriber2).get(SUBSCRIBER_INJECTION_TYPE);
24
+ // assert
25
+ expect(result).toEqual({contextName: "non-default"});
26
+ });
27
+ });
@@ -0,0 +1,12 @@
1
+ import {StoreSet, useDecorators} from "@tsed/core";
2
+ import {Injectable} from "@tsed/di";
3
+
4
+ import {DEFAULT_CONTEXT_NAME, SUBSCRIBER_INJECTION_TYPE} from "../constants.js";
5
+
6
+ /**
7
+ * Register a new subscriber for the given context name.
8
+ * @decorator
9
+ * @mikroOrm
10
+ */
11
+ export const Subscriber = (options: {contextName: string} = {contextName: DEFAULT_CONTEXT_NAME}) =>
12
+ useDecorators(Injectable({type: SUBSCRIBER_INJECTION_TYPE}), StoreSet(SUBSCRIBER_INJECTION_TYPE, options));
@@ -0,0 +1,34 @@
1
+ import {Controller, DITest} from "@tsed/di";
2
+ import {Post} from "@tsed/schema";
3
+ import {afterEach, beforeEach} from "vitest";
4
+
5
+ import {TransactionalInterceptor} from "../interceptors/TransactionalInterceptor.js";
6
+ import {Transactional} from "./transactional.js";
7
+
8
+ @Controller("/users")
9
+ export class UsersCtrl {
10
+ @Post("/")
11
+ @Transactional()
12
+ create(): any {}
13
+ }
14
+
15
+ describe("@Transactional", () => {
16
+ beforeEach(() => DITest.create());
17
+ afterEach(() => DITest.reset());
18
+ it("should decorate method", async () => {
19
+ const interceptor = {
20
+ intercept: vi.fn().mockResolvedValue({})
21
+ };
22
+
23
+ const usersService = await DITest.invoke<UsersCtrl>(UsersCtrl, [
24
+ {
25
+ token: TransactionalInterceptor,
26
+ use: interceptor
27
+ }
28
+ ]);
29
+
30
+ const result = await usersService.create();
31
+
32
+ expect(result).toEqual({});
33
+ });
34
+ });
@@ -0,0 +1,10 @@
1
+ import {Intercept} from "@tsed/di";
2
+
3
+ import {TransactionalInterceptor, TransactionOptions} from "../interceptors/TransactionalInterceptor.js";
4
+
5
+ /**
6
+ * Register a new request context for your method and execute it inside the context.
7
+ * @param {String | TransactionOptions} contextNameOrOptions
8
+ */
9
+ export const Transactional = (contextNameOrOptions?: string | TransactionOptions): MethodDecorator =>
10
+ Intercept(TransactionalInterceptor, contextNameOrOptions);
@@ -0,0 +1,33 @@
1
+ import {OptimisticLockError} from "@mikro-orm/core";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+
4
+ import {OptimisticLockErrorFilter} from "./OptimisticLockErrorFilter.js";
5
+
6
+ describe("OptimisticLockErrorFilter", () => {
7
+ beforeEach(() => PlatformTest.create());
8
+ afterEach(() => PlatformTest.reset());
9
+
10
+ describe("catch", () => {
11
+ it("should set HTTP status to 409", async () => {
12
+ const optimisticLockErrorFilter = await PlatformTest.invoke(OptimisticLockErrorFilter);
13
+
14
+ // arrange
15
+ const exception = OptimisticLockError.lockFailed("entity");
16
+ const context = PlatformTest.createRequestContext();
17
+
18
+ vi.spyOn(context.logger, "error").mockReturnThis();
19
+
20
+ // act
21
+ optimisticLockErrorFilter.catch(exception, context);
22
+
23
+ expect(context.logger.error).toHaveBeenCalledWith({
24
+ event: "MIKRO_ORM_OPTIMISTIC_LOCK_ERROR",
25
+ error_name: exception.name,
26
+ error_message: exception.message,
27
+ stack: exception.stack
28
+ });
29
+ expect(context.response.getBody()).toEqual("Update refused. The resource has changed on the server. Please try again later");
30
+ expect(context.response.statusCode).toEqual(409);
31
+ });
32
+ });
33
+ });