@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 +13 -12
- package/readme.md +6 -6
- package/src/MikroOrmModule.spec.ts +110 -0
- package/src/MikroOrmModule.ts +104 -0
- package/src/constants.ts +2 -0
- package/src/decorators/entityManager.spec.ts +98 -0
- package/src/decorators/entityManager.ts +25 -0
- package/src/decorators/orm.spec.ts +97 -0
- package/src/decorators/orm.ts +19 -0
- package/src/decorators/subscriber.spec.ts +27 -0
- package/src/decorators/subscriber.ts +12 -0
- package/src/decorators/transactional.spec.ts +34 -0
- package/src/decorators/transactional.ts +10 -0
- package/src/filters/OptimisticLockErrorFilter.spec.ts +33 -0
- package/src/filters/OptimisticLockErrorFilter.ts +19 -0
- package/src/index.ts +17 -0
- package/src/interceptors/TransactionalInterceptor.spec.ts +248 -0
- package/src/interceptors/TransactionalInterceptor.ts +130 -0
- package/src/interfaces/AfterTransactionCommit.ts +5 -0
- package/src/interfaces/BeforeTransactionCommit.ts +5 -0
- package/src/interfaces/RetryStrategy.ts +5 -0
- package/src/services/MikroOrmContext.spec.ts +95 -0
- package/src/services/MikroOrmContext.ts +29 -0
- package/src/services/MikroOrmFactory.ts +16 -0
- package/src/services/MikroOrmRegistry.spec.ts +121 -0
- package/src/services/MikroOrmRegistry.ts +62 -0
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.
|
|
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.
|
|
31
|
-
"@tsed/core": "8.0.
|
|
32
|
-
"@tsed/di": "8.0.
|
|
33
|
-
"@tsed/json-mapper": "8.0.
|
|
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.
|
|
36
|
-
"@tsed/schema": "8.0.
|
|
37
|
-
"@tsed/testcontainers-mongo": "8.0.
|
|
38
|
-
"@tsed/typescript": "8.0.
|
|
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.
|
|
48
|
-
"@tsed/di": "8.0.
|
|
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.
|
|
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.
|
|
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.
|
|
19
|
+
<a href="https://tsed.dev/">Website</a>
|
|
20
20
|
<span> • </span>
|
|
21
|
-
<a href="https://tsed.
|
|
21
|
+
<a href="https://tsed.dev/getting-started/">Getting started</a>
|
|
22
22
|
<span> • </span>
|
|
23
23
|
<a href="https://api.tsed.io/rest/slack/tsedio/tsed">Slack</a>
|
|
24
24
|
<span> • </span>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<hr />
|
|
29
29
|
|
|
30
|
-
A package of Ts.ED framework. See website: https://tsed.
|
|
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.
|
|
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.
|
|
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});
|
package/src/constants.ts
ADDED
|
@@ -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
|
+
});
|