From dd5b999c5e8da7cad7ffe7c03009202a5574d6c4 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Wed, 18 Sep 2024 13:48:19 +0100 Subject: [PATCH 01/15] fix: rename `mikro-orm-middleware` to `multiple-mikro-orm` and allow `forRoot` registering to resolve issue with ordering of middleware being applied --- README.md | 4 ++-- src/index.ts | 1 + src/middleware.helper.ts | 4 ++-- src/mikro-orm.common.ts | 1 + src/mikro-orm.module.ts | 9 +++++---- ...module.ts => multiple-mikro-orm.module.ts} | 19 ++++++++----------- src/typings.ts | 4 ++-- tests/mikro-orm.middleware.test.ts | 17 +++++------------ 8 files changed, 26 insertions(+), 33 deletions(-) rename src/{mikro-orm-middleware.module.ts => multiple-mikro-orm.module.ts} (51%) diff --git a/README.md b/README.md index bc6b2d3..6499028 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ More information about [enableShutdownHooks](https://docs.nestjs.com/fundamental ## Multiple Database Connections -You can define multiple database connections by registering multiple `MikroOrmModule` and setting their `contextName`. If you want to use middleware request context you must disable automatic middleware and register `MikroOrmModule` with `forMiddleware()` or use NestJS `Injection Scope` +You can define multiple database connections by registering several MikroOrmModules, each with a unique contextName and setting registerRequestContext to false. If you want to use the middleware request context you must register `MultipleMikroOrmModule` with `forRoot()` or use NestJS `Injection Scope` ```typescript @Module({ @@ -357,7 +357,7 @@ You can define multiple database connections by registering multiple `MikroOrmMo registerRequestContext: false, // disable automatatic middleware ... }), - MikroOrmModule.forMiddleware() + MultipleMikroOrmModule.forRoot() ], controllers: [AppController], providers: [AppService], diff --git a/src/index.ts b/src/index.ts index 85e84db..ff14837 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from './mikro-orm.module'; export * from './mikro-orm.common'; export * from './mikro-orm.middleware'; +export * from './multiple-mikro-orm.module'; export * from './multiple-mikro-orm.middleware'; export * from './typings'; diff --git a/src/middleware.helper.ts b/src/middleware.helper.ts index a8119e0..dd143f9 100644 --- a/src/middleware.helper.ts +++ b/src/middleware.helper.ts @@ -1,7 +1,7 @@ -import type { MikroOrmMiddlewareModuleOptions, NestMiddlewareConsumer } from './typings'; +import type { MultipleMikroOrmModuleOptions, NestMiddlewareConsumer } from './typings'; import type { MiddlewareConsumer } from '@nestjs/common'; -export function forRoutesPath(options: MikroOrmMiddlewareModuleOptions, consumer: MiddlewareConsumer) { +export function forRoutesPath(options: MultipleMikroOrmModuleOptions, consumer: MiddlewareConsumer) { const isNestMiddleware = (consumer: MiddlewareConsumer): consumer is NestMiddlewareConsumer => { return typeof (consumer as any).httpAdapter === 'object'; }; diff --git a/src/mikro-orm.common.ts b/src/mikro-orm.common.ts index 1dd8914..096c3a9 100644 --- a/src/mikro-orm.common.ts +++ b/src/mikro-orm.common.ts @@ -3,6 +3,7 @@ import { Inject, Logger } from '@nestjs/common'; import type { EntityName } from './typings'; export const MIKRO_ORM_MODULE_OPTIONS = Symbol('mikro-orm-module-options'); +export const MULTPLE_MIKRO_ORM_MODULE_OPTIONS = Symbol('multiple-mikro-orm-module-options'); export const CONTEXT_NAMES: string[] = []; export const logger = new Logger(MikroORM.name); diff --git a/src/mikro-orm.module.ts b/src/mikro-orm.module.ts index b5949b3..3ac4218 100644 --- a/src/mikro-orm.module.ts +++ b/src/mikro-orm.module.ts @@ -1,12 +1,12 @@ import { Utils, type AnyEntity } from '@mikro-orm/core'; import { Module, type DynamicModule } from '@nestjs/common'; import { MikroOrmCoreModule } from './mikro-orm-core.module'; -import { MikroOrmMiddlewareModule } from './mikro-orm-middleware.module'; +import { MultipleMikroOrmModule } from './multiple-mikro-orm.module'; import { MikroOrmEntitiesStorage } from './mikro-orm.entities.storage'; import { createMikroOrmRepositoryProviders } from './mikro-orm.providers'; import type { EntityName, - MikroOrmMiddlewareModuleOptions, + MultipleMikroOrmModuleOptions, MikroOrmModuleAsyncOptions, MikroOrmModuleFeatureOptions, MikroOrmModuleSyncOptions, @@ -55,10 +55,11 @@ export class MikroOrmModule { }; } - static forMiddleware(options?: MikroOrmMiddlewareModuleOptions): DynamicModule { + // Remove this in the next major version, kept for backwards compatibility + static forMiddleware(options?: MultipleMikroOrmModuleOptions): DynamicModule { return { module: MikroOrmModule, - imports: [MikroOrmMiddlewareModule.forMiddleware(options)], + imports: [MultipleMikroOrmModule.forRoot(options)], }; } diff --git a/src/mikro-orm-middleware.module.ts b/src/multiple-mikro-orm.module.ts similarity index 51% rename from src/mikro-orm-middleware.module.ts rename to src/multiple-mikro-orm.module.ts index 52eac73..6e5a287 100644 --- a/src/mikro-orm-middleware.module.ts +++ b/src/multiple-mikro-orm.module.ts @@ -2,26 +2,23 @@ import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer } from ' import type { MikroORM } from '@mikro-orm/core'; import { forRoutesPath } from './middleware.helper'; -import { CONTEXT_NAMES, getMikroORMToken, MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; +import { CONTEXT_NAMES, getMikroORMToken, MULTPLE_MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; import { MultipleMikroOrmMiddleware } from './multiple-mikro-orm.middleware'; -import { MikroOrmMiddlewareModuleOptions } from './typings'; +import { MultipleMikroOrmModuleOptions } from './typings'; @Global() @Module({}) -export class MikroOrmMiddlewareModule { +export class MultipleMikroOrmModule { - constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS) - private readonly options: MikroOrmMiddlewareModuleOptions) { } + constructor(@Inject(MULTPLE_MIKRO_ORM_MODULE_OPTIONS) + private readonly options: MultipleMikroOrmModuleOptions) { } - static forMiddleware(options?: MikroOrmMiddlewareModuleOptions) { - // Work around due to nestjs not supporting the ability to register multiple types - // https://github.com/nestjs/nest/issues/770 - // https://github.com/nestjs/nest/issues/4786#issuecomment-755032258 - workaround suggestion + static forRoot(options?: MultipleMikroOrmModuleOptions) { const inject = CONTEXT_NAMES.map(name => getMikroORMToken(name)); return { - module: MikroOrmMiddlewareModule, + module: MultipleMikroOrmModule, providers: [ - { provide: MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, + { provide: MULTPLE_MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, { provide: 'MikroORMs', useFactory: (...args: MikroORM[]) => args, diff --git a/src/typings.ts b/src/typings.ts index 8efffca..ac78487 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -10,7 +10,7 @@ type MikroOrmNestScopeOptions = { scope?: Scope; }; -export type MikroOrmMiddlewareModuleOptions = { +export type MultipleMikroOrmModuleOptions = { /** * Routes to apply the middleware. * @@ -24,7 +24,7 @@ export type MikroOrmMiddlewareModuleOptions = { export type MikroOrmModuleOptions = { registerRequestContext?: boolean; autoLoadEntities?: boolean; -} & Options & MikroOrmMiddlewareModuleOptions; +} & Options & MultipleMikroOrmModuleOptions; export interface MikroOrmModuleFeatureOptions { entities?: EntityName[]; diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index 62264b2..df82fe2 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -1,16 +1,9 @@ -import type { Options } from '@mikro-orm/core'; -import { MikroORM } from '@mikro-orm/core'; +import { MikroORM, type Options } from '@mikro-orm/core'; import { SqliteDriver } from '@mikro-orm/sqlite'; -import type { INestApplication } from '@nestjs/common'; -import { - Controller, - Get, - Module, -} from '@nestjs/common'; -import type { TestingModule } from '@nestjs/testing'; -import { Test } from '@nestjs/testing'; +import { Controller, Get, Module, type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { InjectMikroORM, MikroOrmModule } from '../src'; +import { InjectMikroORM, MikroOrmModule, MultipleMikroOrmModule } from '../src'; import { Bar } from './entities/bar.entity'; import { Foo } from './entities/foo.entity'; @@ -55,7 +48,7 @@ class TestController { registerRequestContext: false, ...testOptions, }), - MikroOrmModule.forMiddleware(), + MultipleMikroOrmModule.forRoot(), MikroOrmModule.forFeature([Foo], 'database1'), MikroOrmModule.forFeature([Bar], 'database2'), ], From 046af6d9286b30999871617b5dec48302bd08a46 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sat, 9 Nov 2024 18:17:46 +0000 Subject: [PATCH 02/15] revert multiple-mikro-orm-module-option symbol --- src/mikro-orm.common.ts | 1 - src/multiple-mikro-orm.module.ts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mikro-orm.common.ts b/src/mikro-orm.common.ts index 096c3a9..1dd8914 100644 --- a/src/mikro-orm.common.ts +++ b/src/mikro-orm.common.ts @@ -3,7 +3,6 @@ import { Inject, Logger } from '@nestjs/common'; import type { EntityName } from './typings'; export const MIKRO_ORM_MODULE_OPTIONS = Symbol('mikro-orm-module-options'); -export const MULTPLE_MIKRO_ORM_MODULE_OPTIONS = Symbol('multiple-mikro-orm-module-options'); export const CONTEXT_NAMES: string[] = []; export const logger = new Logger(MikroORM.name); diff --git a/src/multiple-mikro-orm.module.ts b/src/multiple-mikro-orm.module.ts index 6e5a287..38461b7 100644 --- a/src/multiple-mikro-orm.module.ts +++ b/src/multiple-mikro-orm.module.ts @@ -2,7 +2,7 @@ import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer } from ' import type { MikroORM } from '@mikro-orm/core'; import { forRoutesPath } from './middleware.helper'; -import { CONTEXT_NAMES, getMikroORMToken, MULTPLE_MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; +import { CONTEXT_NAMES, getMikroORMToken, MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; import { MultipleMikroOrmMiddleware } from './multiple-mikro-orm.middleware'; import { MultipleMikroOrmModuleOptions } from './typings'; @@ -10,7 +10,7 @@ import { MultipleMikroOrmModuleOptions } from './typings'; @Module({}) export class MultipleMikroOrmModule { - constructor(@Inject(MULTPLE_MIKRO_ORM_MODULE_OPTIONS) + constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS) private readonly options: MultipleMikroOrmModuleOptions) { } static forRoot(options?: MultipleMikroOrmModuleOptions) { @@ -18,7 +18,7 @@ export class MultipleMikroOrmModule { return { module: MultipleMikroOrmModule, providers: [ - { provide: MULTPLE_MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, + { provide: MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, { provide: 'MikroORMs', useFactory: (...args: MikroORM[]) => args, From ff2b056192e3ce2407faabd829e3a936fad2a439 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sat, 9 Nov 2024 20:13:33 +0000 Subject: [PATCH 03/15] add additional test for middleware changes --- tests/entities/foo.entity.ts | 3 +- tests/mikro-orm.middleware.test.ts | 87 +++++++++---- tests/mikro-orm.multiple-middleware.test.ts | 128 ++++++++++++++++++++ 3 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 tests/mikro-orm.multiple-middleware.test.ts diff --git a/tests/entities/foo.entity.ts b/tests/entities/foo.entity.ts index b5aa3da..0e7b8fa 100644 --- a/tests/entities/foo.entity.ts +++ b/tests/entities/foo.entity.ts @@ -1,6 +1,7 @@ -import { PrimaryKey, Entity } from '@mikro-orm/core'; +import { PrimaryKey, Entity, Filter } from '@mikro-orm/core'; @Entity() +@Filter({ name: 'id', cond: args => ({ id: args.id }) }) export class Foo { @PrimaryKey() diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index df82fe2..c9d74e7 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -1,9 +1,17 @@ -import { MikroORM, type Options } from '@mikro-orm/core'; +import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; import { SqliteDriver } from '@mikro-orm/sqlite'; -import { Controller, Get, Module, type INestApplication } from '@nestjs/common'; +import { + Controller, + Get, + type INestApplication, + Injectable, + MiddlewareConsumer, + Module, type NestMiddleware, + NestModule, +} from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { InjectMikroORM, MikroOrmModule, MultipleMikroOrmModule } from '../src'; +import { InjectEntityManager, InjectMikroORM, MikroOrmModule } from '../src'; import { Bar } from './entities/bar.entity'; import { Foo } from './entities/foo.entity'; @@ -14,45 +22,82 @@ const testOptions: Options = { entities: ['entities'], }; -@Controller() -class TestController { +@Controller('/foo') +export class FooController { - constructor( - @InjectMikroORM('database1') private database1: MikroORM, - @InjectMikroORM('database2') private database2: MikroORM, - ) {} + constructor(@InjectMikroORM('database-foo') private database1: MikroORM) {} - @Get('foo') + @Get() foo() { return this.database1.em !== this.database1.em.getContext(); } - @Get('bar') +} + +@Controller('/bar') +export class BarController { + + constructor(@InjectMikroORM('database-bar') private database2: MikroORM) {} + + @Get() bar() { return this.database2.em !== this.database2.em.getContext(); } } +@Injectable() +export class TestMiddleware implements NestMiddleware { + + constructor(@InjectEntityManager('database-foo') private readonly em: EntityManager) {} + + use(req: unknown, res: unknown, next: (...args: any[]) => void) { + // Throws error "Using global EntityManager instance methods for context specific actions is disallowed" + this.em.setFilterParams('id', { id: '1' }); + + return next(); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Foo], 'database-foo')], + controllers: [FooController], +}) +class FooModule implements NestModule { + + configure(consumer: MiddlewareConsumer): void { + consumer + .apply(TestMiddleware) + .forRoutes('/'); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Bar], 'database-bar')], + controllers: [BarController], +}) +class BarModule {} + @Module({ imports: [ MikroOrmModule.forRootAsync({ - contextName: 'database1', + contextName: 'database-foo', useFactory: () => ({ registerRequestContext: false, ...testOptions, }), }), MikroOrmModule.forRoot({ - contextName: 'database2', + contextName: 'database-bar', registerRequestContext: false, ...testOptions, }), - MultipleMikroOrmModule.forRoot(), - MikroOrmModule.forFeature([Foo], 'database1'), - MikroOrmModule.forFeature([Bar], 'database2'), + MikroOrmModule.forMiddleware(), + FooModule, + BarModule, ], - controllers: [TestController], }) class TestModule {} @@ -69,12 +114,8 @@ describe('Middleware executes request context for all MikroORM registered', () = await app.init(); }); - it(`forRoutes(/foo) should return 'true'`, () => { - return request(app.getHttpServer()).get('/foo').expect(200, 'true'); - }); - - it(`forRoutes(/bar) should return 'true'`, () => { - return request(app.getHttpServer()).get('/foo').expect(200, 'true'); + it(`forRoutes(/foo) should return error`, () => { + return request(app.getHttpServer()).get('/foo').expect(500); }); afterAll(async () => { diff --git a/tests/mikro-orm.multiple-middleware.test.ts b/tests/mikro-orm.multiple-middleware.test.ts new file mode 100644 index 0000000..331607a --- /dev/null +++ b/tests/mikro-orm.multiple-middleware.test.ts @@ -0,0 +1,128 @@ +import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; +import { SqliteDriver } from '@mikro-orm/sqlite'; +import { + Controller, + Get, + Module, + type INestApplication, + Injectable, + type NestMiddleware, + MiddlewareConsumer, + NestModule, +} from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { InjectEntityManager, InjectMikroORM, MikroOrmModule, MultipleMikroOrmModule } from '../src'; +import { Bar } from './entities/bar.entity'; +import { Foo } from './entities/foo.entity'; + +const testOptions: Options = { + dbName: ':memory:', + driver: SqliteDriver, + baseDir: __dirname, + entities: ['entities'], +}; + +@Controller('/foo') +class FooController { + + constructor(@InjectMikroORM('database-multi-foo') private database1: MikroORM) {} + + @Get() + foo() { + return this.database1.em !== this.database1.em.getContext(); + } + +} + +@Controller('/bar') +class BarController { + + constructor(@InjectMikroORM('database-multi-bar') private database2: MikroORM) {} + + @Get() + bar() { + return this.database2.em !== this.database2.em.getContext(); + } + +} + +@Injectable() +export class TestMiddleware implements NestMiddleware { + + constructor(@InjectEntityManager('database-multi-foo') private readonly em: EntityManager) {} + + use(req: unknown, res: unknown, next: (...args: any[]) => void) { + this.em.setFilterParams('id', { id: '1' }); + + return next(); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Foo], 'database-multi-foo')], + controllers: [FooController], +}) +class FooModule implements NestModule { + + configure(consumer: MiddlewareConsumer): void { + consumer + .apply(TestMiddleware) + .forRoutes('/'); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Bar], 'database-multi-bar')], + controllers: [BarController], +}) +class BarModule {} + +@Module({ + imports: [ + MikroOrmModule.forRootAsync({ + contextName: 'database-multi-foo', + useFactory: () => ({ + registerRequestContext: false, + ...testOptions, + }), + }), + MikroOrmModule.forRoot({ + contextName: 'database-multi-bar', + registerRequestContext: false, + ...testOptions, + }), + MultipleMikroOrmModule.forRoot(), + FooModule, + BarModule, + ], +}) +class TestModule {} + +describe('Multiple Middleware executes request context for all MikroORM registered', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + + await app.init(); + }); + + it(`forRoutes(/foo) should return 'true'`, () => { + return request(app.getHttpServer()).get('/foo').expect(200, 'true'); + }); + + it(`forRoutes(/bar) should return 'true'`, () => { + return request(app.getHttpServer()).get('/bar').expect(200, 'true'); + }); + + afterAll(async () => { + await app.close(); + }); +}); From 5661304b1b522c3da7a9f400b3d7449040594c79 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sat, 9 Nov 2024 20:17:03 +0000 Subject: [PATCH 04/15] undo export changes --- tests/mikro-orm.middleware.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index c9d74e7..77bc737 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -23,7 +23,7 @@ const testOptions: Options = { }; @Controller('/foo') -export class FooController { +class FooController { constructor(@InjectMikroORM('database-foo') private database1: MikroORM) {} @@ -35,7 +35,7 @@ export class FooController { } @Controller('/bar') -export class BarController { +class BarController { constructor(@InjectMikroORM('database-bar') private database2: MikroORM) {} @@ -47,7 +47,7 @@ export class BarController { } @Injectable() -export class TestMiddleware implements NestMiddleware { +class TestMiddleware implements NestMiddleware { constructor(@InjectEntityManager('database-foo') private readonly em: EntityManager) {} From 58dfb65124dd53f14ee9c2bdeb98a4a9fcff79e6 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sun, 10 Nov 2024 20:20:29 +0000 Subject: [PATCH 05/15] PR fixes --- src/mikro-orm.module.ts | 10 ++- src/typings.ts | 5 +- tests/mikro-orm.middleware.test.ts | 85 ++++++--------------- tests/mikro-orm.multiple-middleware.test.ts | 4 +- 4 files changed, 36 insertions(+), 68 deletions(-) diff --git a/src/mikro-orm.module.ts b/src/mikro-orm.module.ts index 3ac4218..58647e0 100644 --- a/src/mikro-orm.module.ts +++ b/src/mikro-orm.module.ts @@ -4,12 +4,12 @@ import { MikroOrmCoreModule } from './mikro-orm-core.module'; import { MultipleMikroOrmModule } from './multiple-mikro-orm.module'; import { MikroOrmEntitiesStorage } from './mikro-orm.entities.storage'; import { createMikroOrmRepositoryProviders } from './mikro-orm.providers'; -import type { +import { EntityName, - MultipleMikroOrmModuleOptions, MikroOrmModuleAsyncOptions, MikroOrmModuleFeatureOptions, MikroOrmModuleSyncOptions, + MikroOrmMiddlewareModuleOptions, } from './typings'; @Module({}) @@ -55,8 +55,10 @@ export class MikroOrmModule { }; } - // Remove this in the next major version, kept for backwards compatibility - static forMiddleware(options?: MultipleMikroOrmModuleOptions): DynamicModule { + /** + * @deprecated Use `MultipleMikroOrmModule.forRoot()`. This signature will be removed in v7. + */ + static forMiddleware(options?: MikroOrmMiddlewareModuleOptions): DynamicModule { return { module: MikroOrmModule, imports: [MultipleMikroOrmModule.forRoot(options)], diff --git a/src/typings.ts b/src/typings.ts index ac78487..6e0176b 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -9,7 +9,10 @@ export interface NestMiddlewareConsumer extends MiddlewareConsumer { type MikroOrmNestScopeOptions = { scope?: Scope; }; - +/** + * @deprecated Use `MultipleMikroOrmModuleOptions`. This type will be removed in v7. + */ +export type MikroOrmMiddlewareModuleOptions = MultipleMikroOrmModuleOptions; export type MultipleMikroOrmModuleOptions = { /** * Routes to apply the middleware. diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index 77bc737..21e056c 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -1,17 +1,13 @@ -import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; +import type { Options , MikroORM } from '@mikro-orm/core'; import { SqliteDriver } from '@mikro-orm/sqlite'; -import { +import { INestApplication , Controller, Get, - type INestApplication, - Injectable, - MiddlewareConsumer, - Module, type NestMiddleware, - NestModule, + Module, } from '@nestjs/common'; -import { Test, type TestingModule } from '@nestjs/testing'; +import { TestingModule , Test } from '@nestjs/testing'; import request from 'supertest'; -import { InjectEntityManager, InjectMikroORM, MikroOrmModule } from '../src'; +import { InjectMikroORM, MikroOrmModule } from '../src'; import { Bar } from './entities/bar.entity'; import { Foo } from './entities/foo.entity'; @@ -22,82 +18,45 @@ const testOptions: Options = { entities: ['entities'], }; -@Controller('/foo') -class FooController { +@Controller() +class TestController { - constructor(@InjectMikroORM('database-foo') private database1: MikroORM) {} + constructor( + @InjectMikroORM('database1') private database1: MikroORM, + @InjectMikroORM('database2') private database2: MikroORM, + ) {} - @Get() + @Get('foo') foo() { return this.database1.em !== this.database1.em.getContext(); } -} - -@Controller('/bar') -class BarController { - - constructor(@InjectMikroORM('database-bar') private database2: MikroORM) {} - - @Get() + @Get('bar') bar() { return this.database2.em !== this.database2.em.getContext(); } } -@Injectable() -class TestMiddleware implements NestMiddleware { - - constructor(@InjectEntityManager('database-foo') private readonly em: EntityManager) {} - - use(req: unknown, res: unknown, next: (...args: any[]) => void) { - // Throws error "Using global EntityManager instance methods for context specific actions is disallowed" - this.em.setFilterParams('id', { id: '1' }); - - return next(); - } - -} - -@Module({ - imports: [MikroOrmModule.forFeature([Foo], 'database-foo')], - controllers: [FooController], -}) -class FooModule implements NestModule { - - configure(consumer: MiddlewareConsumer): void { - consumer - .apply(TestMiddleware) - .forRoutes('/'); - } - -} - -@Module({ - imports: [MikroOrmModule.forFeature([Bar], 'database-bar')], - controllers: [BarController], -}) -class BarModule {} - @Module({ imports: [ MikroOrmModule.forRootAsync({ - contextName: 'database-foo', + contextName: 'database1', useFactory: () => ({ registerRequestContext: false, ...testOptions, }), }), MikroOrmModule.forRoot({ - contextName: 'database-bar', + contextName: 'database2', registerRequestContext: false, ...testOptions, }), MikroOrmModule.forMiddleware(), - FooModule, - BarModule, + MikroOrmModule.forFeature([Foo], 'database1'), + MikroOrmModule.forFeature([Bar], 'database2'), ], + controllers: [TestController], }) class TestModule {} @@ -114,8 +73,12 @@ describe('Middleware executes request context for all MikroORM registered', () = await app.init(); }); - it(`forRoutes(/foo) should return error`, () => { - return request(app.getHttpServer()).get('/foo').expect(500); + it(`forRoutes(/foo) should return 'true'`, () => { + return request(app.getHttpServer()).get('/foo').expect(200, 'true'); + }); + + it(`forRoutes(/bar) should return 'true'`, () => { + return request(app.getHttpServer()).get('/foo').expect(200, 'true'); }); afterAll(async () => { diff --git a/tests/mikro-orm.multiple-middleware.test.ts b/tests/mikro-orm.multiple-middleware.test.ts index 331607a..5ed91bf 100644 --- a/tests/mikro-orm.multiple-middleware.test.ts +++ b/tests/mikro-orm.multiple-middleware.test.ts @@ -99,14 +99,14 @@ class BarModule {} BarModule, ], }) -class TestModule {} +class TestMultipleModule {} describe('Multiple Middleware executes request context for all MikroORM registered', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [TestModule], + imports: [TestMultipleModule], }).compile(); app = moduleFixture.createNestApplication(); From 6078d0df0e817ec40b033a55fc3eb9d37d7771dc Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sun, 10 Nov 2024 20:32:51 +0000 Subject: [PATCH 06/15] rename from `multiple` to `multi` --- src/index.ts | 2 +- src/middleware.helper.ts | 4 ++-- src/mikro-orm.module.ts | 4 ++-- ...e-mikro-orm.module.ts => multi-mikro-orm.module.ts} | 10 +++++----- src/typings.ts | 6 +++--- ...ware.test.ts => mikro-orm.multi-middleware.test.ts} | 8 ++++---- 6 files changed, 17 insertions(+), 17 deletions(-) rename src/{multiple-mikro-orm.module.ts => multi-mikro-orm.module.ts} (78%) rename tests/{mikro-orm.multiple-middleware.test.ts => mikro-orm.multi-middleware.test.ts} (95%) diff --git a/src/index.ts b/src/index.ts index ff14837..d263aa3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export * from './mikro-orm.module'; export * from './mikro-orm.common'; export * from './mikro-orm.middleware'; -export * from './multiple-mikro-orm.module'; +export * from './multi-mikro-orm.module'; export * from './multiple-mikro-orm.middleware'; export * from './typings'; diff --git a/src/middleware.helper.ts b/src/middleware.helper.ts index dd143f9..d4541c1 100644 --- a/src/middleware.helper.ts +++ b/src/middleware.helper.ts @@ -1,7 +1,7 @@ -import type { MultipleMikroOrmModuleOptions, NestMiddlewareConsumer } from './typings'; +import type { MultiMikroOrmModuleOptions, NestMiddlewareConsumer } from './typings'; import type { MiddlewareConsumer } from '@nestjs/common'; -export function forRoutesPath(options: MultipleMikroOrmModuleOptions, consumer: MiddlewareConsumer) { +export function forRoutesPath(options: MultiMikroOrmModuleOptions, consumer: MiddlewareConsumer) { const isNestMiddleware = (consumer: MiddlewareConsumer): consumer is NestMiddlewareConsumer => { return typeof (consumer as any).httpAdapter === 'object'; }; diff --git a/src/mikro-orm.module.ts b/src/mikro-orm.module.ts index 58647e0..075b34d 100644 --- a/src/mikro-orm.module.ts +++ b/src/mikro-orm.module.ts @@ -1,7 +1,7 @@ import { Utils, type AnyEntity } from '@mikro-orm/core'; import { Module, type DynamicModule } from '@nestjs/common'; import { MikroOrmCoreModule } from './mikro-orm-core.module'; -import { MultipleMikroOrmModule } from './multiple-mikro-orm.module'; +import { MultiMikroOrmModule } from './multi-mikro-orm.module'; import { MikroOrmEntitiesStorage } from './mikro-orm.entities.storage'; import { createMikroOrmRepositoryProviders } from './mikro-orm.providers'; import { @@ -61,7 +61,7 @@ export class MikroOrmModule { static forMiddleware(options?: MikroOrmMiddlewareModuleOptions): DynamicModule { return { module: MikroOrmModule, - imports: [MultipleMikroOrmModule.forRoot(options)], + imports: [MultiMikroOrmModule.forRoot(options)], }; } diff --git a/src/multiple-mikro-orm.module.ts b/src/multi-mikro-orm.module.ts similarity index 78% rename from src/multiple-mikro-orm.module.ts rename to src/multi-mikro-orm.module.ts index 38461b7..ccc9573 100644 --- a/src/multiple-mikro-orm.module.ts +++ b/src/multi-mikro-orm.module.ts @@ -4,19 +4,19 @@ import type { MikroORM } from '@mikro-orm/core'; import { forRoutesPath } from './middleware.helper'; import { CONTEXT_NAMES, getMikroORMToken, MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; import { MultipleMikroOrmMiddleware } from './multiple-mikro-orm.middleware'; -import { MultipleMikroOrmModuleOptions } from './typings'; +import { MultiMikroOrmModuleOptions } from './typings'; @Global() @Module({}) -export class MultipleMikroOrmModule { +export class MultiMikroOrmModule { constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS) - private readonly options: MultipleMikroOrmModuleOptions) { } + private readonly options: MultiMikroOrmModuleOptions) { } - static forRoot(options?: MultipleMikroOrmModuleOptions) { + static forRoot(options?: MultiMikroOrmModuleOptions) { const inject = CONTEXT_NAMES.map(name => getMikroORMToken(name)); return { - module: MultipleMikroOrmModule, + module: MultiMikroOrmModule, providers: [ { provide: MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, { diff --git a/src/typings.ts b/src/typings.ts index 6e0176b..a76a994 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -12,8 +12,8 @@ type MikroOrmNestScopeOptions = { /** * @deprecated Use `MultipleMikroOrmModuleOptions`. This type will be removed in v7. */ -export type MikroOrmMiddlewareModuleOptions = MultipleMikroOrmModuleOptions; -export type MultipleMikroOrmModuleOptions = { +export type MikroOrmMiddlewareModuleOptions = MultiMikroOrmModuleOptions; +export type MultiMikroOrmModuleOptions = { /** * Routes to apply the middleware. * @@ -27,7 +27,7 @@ export type MultipleMikroOrmModuleOptions = { export type MikroOrmModuleOptions = { registerRequestContext?: boolean; autoLoadEntities?: boolean; -} & Options & MultipleMikroOrmModuleOptions; +} & Options & MultiMikroOrmModuleOptions; export interface MikroOrmModuleFeatureOptions { entities?: EntityName[]; diff --git a/tests/mikro-orm.multiple-middleware.test.ts b/tests/mikro-orm.multi-middleware.test.ts similarity index 95% rename from tests/mikro-orm.multiple-middleware.test.ts rename to tests/mikro-orm.multi-middleware.test.ts index 5ed91bf..3b69f9b 100644 --- a/tests/mikro-orm.multiple-middleware.test.ts +++ b/tests/mikro-orm.multi-middleware.test.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { InjectEntityManager, InjectMikroORM, MikroOrmModule, MultipleMikroOrmModule } from '../src'; +import { InjectEntityManager, InjectMikroORM, MikroOrmModule, MultiMikroOrmModule } from '../src'; import { Bar } from './entities/bar.entity'; import { Foo } from './entities/foo.entity'; @@ -94,19 +94,19 @@ class BarModule {} registerRequestContext: false, ...testOptions, }), - MultipleMikroOrmModule.forRoot(), + MultiMikroOrmModule.forRoot(), FooModule, BarModule, ], }) -class TestMultipleModule {} +class TestMultiModule {} describe('Multiple Middleware executes request context for all MikroORM registered', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [TestMultipleModule], + imports: [TestMultiModule], }).compile(); app = moduleFixture.createNestApplication(); From 7b70c3d18913d2d567420f81bf7638ac1a2ac1a5 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sun, 10 Nov 2024 20:34:06 +0000 Subject: [PATCH 07/15] comment name correction --- src/typings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typings.ts b/src/typings.ts index a76a994..4cd7730 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -10,7 +10,7 @@ type MikroOrmNestScopeOptions = { scope?: Scope; }; /** - * @deprecated Use `MultipleMikroOrmModuleOptions`. This type will be removed in v7. + * @deprecated Use `MultiMikroOrmModuleOptions`. This type will be removed in v7. */ export type MikroOrmMiddlewareModuleOptions = MultiMikroOrmModuleOptions; export type MultiMikroOrmModuleOptions = { From 6fee872e70935e9cd60d23e897c429f974272e89 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Sun, 10 Nov 2024 20:35:12 +0000 Subject: [PATCH 08/15] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin Adámek --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6499028..fe7f621 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ More information about [enableShutdownHooks](https://docs.nestjs.com/fundamental ## Multiple Database Connections -You can define multiple database connections by registering several MikroOrmModules, each with a unique contextName and setting registerRequestContext to false. If you want to use the middleware request context you must register `MultipleMikroOrmModule` with `forRoot()` or use NestJS `Injection Scope` +You can define multiple database connections by registering multiple `MikroOrmModule`s, each with a unique `contextName`. You will need to disable the automatic request context middleware by setting `registerRequestContext` to `false`, as it wouldn't work with this approach - note that this needs to be part of all your `MikroOrmModule`s with non-default `contextName`. To have the same automatic request context behaviour, you must register `MultiMikroOrmModule` with `forRoot()` instead: ```typescript @Module({ From 6994fd163dc68bb802f1f6f0ce7bc618220bdd3e Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Thu, 14 Nov 2024 20:39:08 +0000 Subject: [PATCH 09/15] fix: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe7f621..9ab9cd3 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ You can define multiple database connections by registering multiple `MikroOrmMo registerRequestContext: false, // disable automatatic middleware ... }), - MultipleMikroOrmModule.forRoot() + MultiMikroOrmModule.forRoot() ], controllers: [AppController], providers: [AppService], From 8512e2b5fdbb41898c14b5e2b7ea45316cb01947 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 10:29:30 +0000 Subject: [PATCH 10/15] fix: PR fixes and implement fix for middleware issue on `forRoot` and `forRootAsync` --- src/index.ts | 1 - src/mikro-orm-core.module.ts | 14 +- ...dule.ts => mikro-orm-middleware.module.ts} | 12 +- src/mikro-orm.module.ts | 24 +--- src/typings.ts | 8 +- tests/mikro-orm.middleware.test.ts | 83 +++++++++--- tests/mikro-orm.multi-middleware.test.ts | 128 ------------------ 7 files changed, 88 insertions(+), 182 deletions(-) rename src/{multi-mikro-orm.module.ts => mikro-orm-middleware.module.ts} (73%) delete mode 100644 tests/mikro-orm.multi-middleware.test.ts diff --git a/src/index.ts b/src/index.ts index d263aa3..85e84db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ export * from './mikro-orm.module'; export * from './mikro-orm.common'; export * from './mikro-orm.middleware'; -export * from './multi-mikro-orm.module'; export * from './multiple-mikro-orm.middleware'; export * from './typings'; diff --git a/src/mikro-orm-core.module.ts b/src/mikro-orm-core.module.ts index a59568e..7034133 100644 --- a/src/mikro-orm-core.module.ts +++ b/src/mikro-orm-core.module.ts @@ -1,5 +1,15 @@ import { Configuration, ConfigurationLoader, EntityManager, MikroORM, type Dictionary } from '@mikro-orm/core'; -import { Global, Inject, Module, RequestMethod, type DynamicModule, type MiddlewareConsumer, type OnApplicationShutdown, type Type } from '@nestjs/common'; +import { + Global, + Inject, + Module, + RequestMethod, + type DynamicModule, + type MiddlewareConsumer, + type OnApplicationShutdown, + type Type, + NestModule, +} from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { forRoutesPath } from './middleware.helper'; @@ -31,7 +41,7 @@ const PACKAGES = { @Global() @Module({}) -export class MikroOrmCoreModule implements OnApplicationShutdown { +export class MikroOrmCoreModule implements NestModule, OnApplicationShutdown { constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS) private readonly options: MikroOrmModuleOptions, diff --git a/src/multi-mikro-orm.module.ts b/src/mikro-orm-middleware.module.ts similarity index 73% rename from src/multi-mikro-orm.module.ts rename to src/mikro-orm-middleware.module.ts index ccc9573..747a900 100644 --- a/src/multi-mikro-orm.module.ts +++ b/src/mikro-orm-middleware.module.ts @@ -1,22 +1,22 @@ -import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer } from '@nestjs/common'; +import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer, NestModule } from '@nestjs/common'; import type { MikroORM } from '@mikro-orm/core'; import { forRoutesPath } from './middleware.helper'; import { CONTEXT_NAMES, getMikroORMToken, MIKRO_ORM_MODULE_OPTIONS } from './mikro-orm.common'; import { MultipleMikroOrmMiddleware } from './multiple-mikro-orm.middleware'; -import { MultiMikroOrmModuleOptions } from './typings'; +import { MikroOrmMiddlewareModuleOptions } from './typings'; @Global() @Module({}) -export class MultiMikroOrmModule { +export class MikroOrmMiddlewareModule implements NestModule { constructor(@Inject(MIKRO_ORM_MODULE_OPTIONS) - private readonly options: MultiMikroOrmModuleOptions) { } + private readonly options: MikroOrmMiddlewareModuleOptions) { } - static forRoot(options?: MultiMikroOrmModuleOptions) { + static forRoot(options?: MikroOrmMiddlewareModuleOptions) { const inject = CONTEXT_NAMES.map(name => getMikroORMToken(name)); return { - module: MultiMikroOrmModule, + module: MikroOrmMiddlewareModule, providers: [ { provide: MIKRO_ORM_MODULE_OPTIONS, useValue: options || {} }, { diff --git a/src/mikro-orm.module.ts b/src/mikro-orm.module.ts index 075b34d..ef8607c 100644 --- a/src/mikro-orm.module.ts +++ b/src/mikro-orm.module.ts @@ -1,7 +1,7 @@ import { Utils, type AnyEntity } from '@mikro-orm/core'; import { Module, type DynamicModule } from '@nestjs/common'; import { MikroOrmCoreModule } from './mikro-orm-core.module'; -import { MultiMikroOrmModule } from './multi-mikro-orm.module'; +import { MikroOrmMiddlewareModule } from './mikro-orm-middleware.module'; import { MikroOrmEntitiesStorage } from './mikro-orm.entities.storage'; import { createMikroOrmRepositoryProviders } from './mikro-orm.providers'; import { @@ -23,18 +23,12 @@ export class MikroOrmModule { MikroOrmEntitiesStorage.clear(contextName); } - static forRoot(options?: MikroOrmModuleSyncOptions): DynamicModule { - return { - module: MikroOrmModule, - imports: [MikroOrmCoreModule.forRoot(options)], - }; + static forRoot(options?: MikroOrmModuleSyncOptions): DynamicModule | Promise { + return MikroOrmCoreModule.forRoot(options); } - static forRootAsync(options: MikroOrmModuleAsyncOptions): DynamicModule { - return { - module: MikroOrmModule, - imports: [MikroOrmCoreModule.forRootAsync(options)], - }; + static forRootAsync(options: MikroOrmModuleAsyncOptions): DynamicModule | Promise { + return MikroOrmCoreModule.forRootAsync(options); } static forFeature(options: EntityName[] | MikroOrmModuleFeatureOptions, contextName?: string): DynamicModule { @@ -55,14 +49,8 @@ export class MikroOrmModule { }; } - /** - * @deprecated Use `MultipleMikroOrmModule.forRoot()`. This signature will be removed in v7. - */ static forMiddleware(options?: MikroOrmMiddlewareModuleOptions): DynamicModule { - return { - module: MikroOrmModule, - imports: [MultiMikroOrmModule.forRoot(options)], - }; + return MikroOrmMiddlewareModule.forRoot(options); } } diff --git a/src/typings.ts b/src/typings.ts index 4cd7730..a207bc9 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -9,11 +9,7 @@ export interface NestMiddlewareConsumer extends MiddlewareConsumer { type MikroOrmNestScopeOptions = { scope?: Scope; }; -/** - * @deprecated Use `MultiMikroOrmModuleOptions`. This type will be removed in v7. - */ -export type MikroOrmMiddlewareModuleOptions = MultiMikroOrmModuleOptions; -export type MultiMikroOrmModuleOptions = { +export type MikroOrmMiddlewareModuleOptions = { /** * Routes to apply the middleware. * @@ -27,7 +23,7 @@ export type MultiMikroOrmModuleOptions = { export type MikroOrmModuleOptions = { registerRequestContext?: boolean; autoLoadEntities?: boolean; -} & Options & MultiMikroOrmModuleOptions; +} & Options & MikroOrmMiddlewareModuleOptions; export interface MikroOrmModuleFeatureOptions { entities?: EntityName[]; diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index 21e056c..0ac3981 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -1,13 +1,18 @@ -import type { Options , MikroORM } from '@mikro-orm/core'; +import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; import { SqliteDriver } from '@mikro-orm/sqlite'; -import { INestApplication , +import { Controller, Get, Module, + type INestApplication, + Injectable, + type NestMiddleware, + MiddlewareConsumer, + NestModule, } from '@nestjs/common'; -import { TestingModule , Test } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { InjectMikroORM, MikroOrmModule } from '../src'; +import { InjectEntityManager, InjectMikroORM, MikroOrmModule } from '../src'; import { Bar } from './entities/bar.entity'; import { Foo } from './entities/foo.entity'; @@ -18,54 +23,90 @@ const testOptions: Options = { entities: ['entities'], }; -@Controller() -class TestController { +@Controller('/foo') +class FooController { - constructor( - @InjectMikroORM('database1') private database1: MikroORM, - @InjectMikroORM('database2') private database2: MikroORM, - ) {} + constructor(@InjectMikroORM('database-multi-foo') private database1: MikroORM) {} - @Get('foo') + @Get() foo() { return this.database1.em !== this.database1.em.getContext(); } - @Get('bar') +} + +@Controller('/bar') +class BarController { + + constructor(@InjectMikroORM('database-multi-bar') private database2: MikroORM) {} + + @Get() bar() { return this.database2.em !== this.database2.em.getContext(); } } +@Injectable() +export class TestMiddleware implements NestMiddleware { + + constructor(@InjectEntityManager('database-multi-foo') private readonly em: EntityManager) {} + + use(req: unknown, res: unknown, next: (...args: any[]) => void) { + this.em.setFilterParams('id', { id: '1' }); + + return next(); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Foo], 'database-multi-foo')], + controllers: [FooController], +}) +class FooModule implements NestModule { + + configure(consumer: MiddlewareConsumer): void { + consumer + .apply(TestMiddleware) + .forRoutes('/'); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Bar], 'database-multi-bar')], + controllers: [BarController], +}) +class BarModule {} + @Module({ imports: [ MikroOrmModule.forRootAsync({ - contextName: 'database1', + contextName: 'database-multi-foo', useFactory: () => ({ registerRequestContext: false, ...testOptions, }), }), MikroOrmModule.forRoot({ - contextName: 'database2', + contextName: 'database-multi-bar', registerRequestContext: false, ...testOptions, }), MikroOrmModule.forMiddleware(), - MikroOrmModule.forFeature([Foo], 'database1'), - MikroOrmModule.forFeature([Bar], 'database2'), + FooModule, + BarModule, ], - controllers: [TestController], }) -class TestModule {} +class TestMultiModule {} -describe('Middleware executes request context for all MikroORM registered', () => { +describe('Multiple Middleware executes request context for all MikroORM registered', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [TestModule], + imports: [TestMultiModule], }).compile(); app = moduleFixture.createNestApplication(); @@ -78,7 +119,7 @@ describe('Middleware executes request context for all MikroORM registered', () = }); it(`forRoutes(/bar) should return 'true'`, () => { - return request(app.getHttpServer()).get('/foo').expect(200, 'true'); + return request(app.getHttpServer()).get('/bar').expect(200, 'true'); }); afterAll(async () => { diff --git a/tests/mikro-orm.multi-middleware.test.ts b/tests/mikro-orm.multi-middleware.test.ts deleted file mode 100644 index 3b69f9b..0000000 --- a/tests/mikro-orm.multi-middleware.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; -import { SqliteDriver } from '@mikro-orm/sqlite'; -import { - Controller, - Get, - Module, - type INestApplication, - Injectable, - type NestMiddleware, - MiddlewareConsumer, - NestModule, -} from '@nestjs/common'; -import { Test, type TestingModule } from '@nestjs/testing'; -import request from 'supertest'; -import { InjectEntityManager, InjectMikroORM, MikroOrmModule, MultiMikroOrmModule } from '../src'; -import { Bar } from './entities/bar.entity'; -import { Foo } from './entities/foo.entity'; - -const testOptions: Options = { - dbName: ':memory:', - driver: SqliteDriver, - baseDir: __dirname, - entities: ['entities'], -}; - -@Controller('/foo') -class FooController { - - constructor(@InjectMikroORM('database-multi-foo') private database1: MikroORM) {} - - @Get() - foo() { - return this.database1.em !== this.database1.em.getContext(); - } - -} - -@Controller('/bar') -class BarController { - - constructor(@InjectMikroORM('database-multi-bar') private database2: MikroORM) {} - - @Get() - bar() { - return this.database2.em !== this.database2.em.getContext(); - } - -} - -@Injectable() -export class TestMiddleware implements NestMiddleware { - - constructor(@InjectEntityManager('database-multi-foo') private readonly em: EntityManager) {} - - use(req: unknown, res: unknown, next: (...args: any[]) => void) { - this.em.setFilterParams('id', { id: '1' }); - - return next(); - } - -} - -@Module({ - imports: [MikroOrmModule.forFeature([Foo], 'database-multi-foo')], - controllers: [FooController], -}) -class FooModule implements NestModule { - - configure(consumer: MiddlewareConsumer): void { - consumer - .apply(TestMiddleware) - .forRoutes('/'); - } - -} - -@Module({ - imports: [MikroOrmModule.forFeature([Bar], 'database-multi-bar')], - controllers: [BarController], -}) -class BarModule {} - -@Module({ - imports: [ - MikroOrmModule.forRootAsync({ - contextName: 'database-multi-foo', - useFactory: () => ({ - registerRequestContext: false, - ...testOptions, - }), - }), - MikroOrmModule.forRoot({ - contextName: 'database-multi-bar', - registerRequestContext: false, - ...testOptions, - }), - MultiMikroOrmModule.forRoot(), - FooModule, - BarModule, - ], -}) -class TestMultiModule {} - -describe('Multiple Middleware executes request context for all MikroORM registered', () => { - let app: INestApplication; - - beforeAll(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [TestMultiModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - - await app.init(); - }); - - it(`forRoutes(/foo) should return 'true'`, () => { - return request(app.getHttpServer()).get('/foo').expect(200, 'true'); - }); - - it(`forRoutes(/bar) should return 'true'`, () => { - return request(app.getHttpServer()).get('/bar').expect(200, 'true'); - }); - - afterAll(async () => { - await app.close(); - }); -}); From 0206c70e3b7d93641dfb91784bdd4b4244976a6a Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 11:07:22 +0000 Subject: [PATCH 11/15] add test --- tests/mikro-orm.module-middleware.test.ts | 94 +++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/mikro-orm.module-middleware.test.ts diff --git a/tests/mikro-orm.module-middleware.test.ts b/tests/mikro-orm.module-middleware.test.ts new file mode 100644 index 0000000..4ccb766 --- /dev/null +++ b/tests/mikro-orm.module-middleware.test.ts @@ -0,0 +1,94 @@ +import { EntityManager, MikroORM, type Options } from '@mikro-orm/core'; +import { SqliteDriver } from '@mikro-orm/sqlite'; +import { + Controller, + Get, + Module, + type INestApplication, + Injectable, + type NestMiddleware, + MiddlewareConsumer, + NestModule, +} from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { MikroOrmModule } from '../src'; +import { Foo } from './entities/foo.entity'; + +const testOptions: Options = { + dbName: ':memory:', + driver: SqliteDriver, + baseDir: __dirname, + entities: ['entities'], +}; + +@Controller('/foo') +class FooController { + + constructor(private database1: MikroORM) {} + + @Get() + foo() { + return this.database1.em !== this.database1.em.getContext(); + } + +} + +@Injectable() +export class TestMiddleware implements NestMiddleware { + + constructor(private readonly em: EntityManager) {} + + use(req: unknown, res: unknown, next: (...args: any[]) => void) { + this.em.setFilterParams('id', { id: '1' }); + + return next(); + } + +} + +@Module({ + imports: [MikroOrmModule.forFeature([Foo])], + controllers: [FooController], +}) +class FooModule implements NestModule { + + configure(consumer: MiddlewareConsumer): void { + consumer + .apply(TestMiddleware) + .forRoutes('/'); + } + +} + +@Module({ + imports: [ + MikroOrmModule.forRootAsync({ + useFactory: () => testOptions, + }), + FooModule, + ], +}) +class TestModule {} + +describe('Middleware executes request context', () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + + await app.init(); + }); + + it(`forRoutes(/foo) should return 'true'`, () => { + return request(app.getHttpServer()).get('/foo').expect(200, 'true'); + }); + + afterAll(async () => { + await app.close(); + }); +}); From 06c4b8b58f51831189c3aeac91d066031e52226c Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 11:09:57 +0000 Subject: [PATCH 12/15] fixes --- README.md | 2 +- src/middleware.helper.ts | 4 ++-- src/typings.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9ab9cd3..952ea7d 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ You can define multiple database connections by registering multiple `MikroOrmMo registerRequestContext: false, // disable automatatic middleware ... }), - MultiMikroOrmModule.forRoot() + MikroOrmModule.forMiddleware() ], controllers: [AppController], providers: [AppService], diff --git a/src/middleware.helper.ts b/src/middleware.helper.ts index d4541c1..a8119e0 100644 --- a/src/middleware.helper.ts +++ b/src/middleware.helper.ts @@ -1,7 +1,7 @@ -import type { MultiMikroOrmModuleOptions, NestMiddlewareConsumer } from './typings'; +import type { MikroOrmMiddlewareModuleOptions, NestMiddlewareConsumer } from './typings'; import type { MiddlewareConsumer } from '@nestjs/common'; -export function forRoutesPath(options: MultiMikroOrmModuleOptions, consumer: MiddlewareConsumer) { +export function forRoutesPath(options: MikroOrmMiddlewareModuleOptions, consumer: MiddlewareConsumer) { const isNestMiddleware = (consumer: MiddlewareConsumer): consumer is NestMiddlewareConsumer => { return typeof (consumer as any).httpAdapter === 'object'; }; diff --git a/src/typings.ts b/src/typings.ts index a207bc9..8efffca 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -9,6 +9,7 @@ export interface NestMiddlewareConsumer extends MiddlewareConsumer { type MikroOrmNestScopeOptions = { scope?: Scope; }; + export type MikroOrmMiddlewareModuleOptions = { /** * Routes to apply the middleware. From 13eec19ae1403e2dd500e63243634d1bcdb572f2 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 11:12:04 +0000 Subject: [PATCH 13/15] set NestModule to type --- src/mikro-orm-core.module.ts | 2 +- src/mikro-orm-middleware.module.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mikro-orm-core.module.ts b/src/mikro-orm-core.module.ts index 7034133..71e7f2a 100644 --- a/src/mikro-orm-core.module.ts +++ b/src/mikro-orm-core.module.ts @@ -6,9 +6,9 @@ import { RequestMethod, type DynamicModule, type MiddlewareConsumer, + type NestModule, type OnApplicationShutdown, type Type, - NestModule, } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; diff --git a/src/mikro-orm-middleware.module.ts b/src/mikro-orm-middleware.module.ts index 747a900..5bedd5f 100644 --- a/src/mikro-orm-middleware.module.ts +++ b/src/mikro-orm-middleware.module.ts @@ -1,4 +1,4 @@ -import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer, NestModule } from '@nestjs/common'; +import { Global, Inject, Module, RequestMethod, type MiddlewareConsumer, type NestModule } from '@nestjs/common'; import type { MikroORM } from '@mikro-orm/core'; import { forRoutesPath } from './middleware.helper'; From 9d7bec634770158597277dba3192f1d633ae7f77 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 11:14:05 +0000 Subject: [PATCH 14/15] missing `type` in imports --- tests/mikro-orm.middleware.test.ts | 4 ++-- tests/mikro-orm.module-middleware.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/mikro-orm.middleware.test.ts b/tests/mikro-orm.middleware.test.ts index 0ac3981..07cb746 100644 --- a/tests/mikro-orm.middleware.test.ts +++ b/tests/mikro-orm.middleware.test.ts @@ -6,9 +6,9 @@ import { Module, type INestApplication, Injectable, + type MiddlewareConsumer, type NestMiddleware, - MiddlewareConsumer, - NestModule, + type NestModule, } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; diff --git a/tests/mikro-orm.module-middleware.test.ts b/tests/mikro-orm.module-middleware.test.ts index 4ccb766..a53b8b2 100644 --- a/tests/mikro-orm.module-middleware.test.ts +++ b/tests/mikro-orm.module-middleware.test.ts @@ -6,9 +6,9 @@ import { Module, type INestApplication, Injectable, + type MiddlewareConsumer, type NestMiddleware, - MiddlewareConsumer, - NestModule, + type NestModule, } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import request from 'supertest'; From 26cf085383a56e8cf5bd403b661502ae47f7e990 Mon Sep 17 00:00:00 2001 From: Steven Tsang Date: Mon, 18 Nov 2024 11:21:20 +0000 Subject: [PATCH 15/15] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 952ea7d..da4786c 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ More information about [enableShutdownHooks](https://docs.nestjs.com/fundamental ## Multiple Database Connections -You can define multiple database connections by registering multiple `MikroOrmModule`s, each with a unique `contextName`. You will need to disable the automatic request context middleware by setting `registerRequestContext` to `false`, as it wouldn't work with this approach - note that this needs to be part of all your `MikroOrmModule`s with non-default `contextName`. To have the same automatic request context behaviour, you must register `MultiMikroOrmModule` with `forRoot()` instead: +You can define multiple database connections by registering multiple `MikroOrmModule`'s, each with a unique `contextName`. You will need to disable the automatic request context middleware by setting `registerRequestContext` to `false`, as it wouldn't work with this approach - note that this needs to be part of all your `MikroOrmModule`s with non-default `contextName`. To have the same automatic request context behaviour, you must register `MikroOrmModule` with `forMiddleware()` instead: ```typescript @Module({