Skip to content

Commit

Permalink
feat(core): allow class references in subscribers array
Browse files Browse the repository at this point in the history
Closes #4231

BREAKING CHANGE:
`@Subscriber()` decorator is now removed in favour of the `subscribers` ORM config option.
  • Loading branch information
B4nan committed Nov 5, 2023
1 parent 57b7094 commit 7c8f776
Show file tree
Hide file tree
Showing 17 changed files with 28 additions and 77 deletions.
30 changes: 0 additions & 30 deletions docs/docs/events.md
Expand Up @@ -56,36 +56,6 @@ MikroORM.init({
});
```

Or use `@Subscriber()` decorator - keep in mind that we need to make sure the file gets loaded in order to make this decorator registration work (e.g. we import that file explicitly somewhere).

```ts
import { EntityName, EventArgs, EventSubscriber, Subscriber } from '@mikro-orm/core';

@Subscriber()
export class AuthorSubscriber implements EventSubscriber<Author> {

getSubscribedEntities(): EntityName<Author>[] {
return [Author];
}

async afterCreate(args: EventArgs<Author>): Promise<void> {
// ...
}

async afterUpdate(args: EventArgs<Author>): Promise<void> {
// ...
}

}
```
:::caution Warning
Do not mix and match the `@Subscriber()` decorator and the `subscribers` array in the configuration. If you use the decorator, you **should not use** the `subscribers` array, and vice versa.

This is due to an issue that will cause each subscriber in the configuration array annotated with `@Subscriber()` to be registered twice, **which will result in duplicate events being fired.**

Additionally, future versions of MikroORM will be dropping support for the`@Subscriber()` decorator in favor of the `subscribers` array in the configuration. Therefore, it is not recommended to use the `@Subscriber()` decorator and to instead use the `subscribers` array in the configuration.
:::

Another example, where we register to all the events and all entities:

```ts
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/upgrading-v5-to-v6.md
Expand Up @@ -92,6 +92,10 @@ MikroORM.init({

You might as well want to use the `EntityRepositoryType` symbol, possibly in a custom base entity.

## Removed `@Subscriber()` decorator

Use `subscribers` array in the ORM config, it now also accepts class reference, not just instances.

## Removal of static require calls

There were some places where we did a static `require()` call, e.g. when loading the driver implementation based on the `type` option. Those places were problematic for bundlers like webpack, as well as new school build systems like vite.
Expand Down
11 changes: 0 additions & 11 deletions packages/core/src/decorators/Subscriber.ts
@@ -1,11 +0,0 @@
import type { Constructor } from '../typings';
import { MetadataStorage } from '../metadata';
import type { EventSubscriber } from '../events';

/** @deprecated This decorator will be removed in v6, prefer the `subscribers` option in the ORM config. */
export function Subscriber() {
return function (target: Constructor<EventSubscriber>) {
const subscribers = MetadataStorage.getSubscriberMetadata();
subscribers[target.name] = new target();
};
}
1 change: 0 additions & 1 deletion packages/core/src/decorators/index.ts
Expand Up @@ -13,7 +13,6 @@ export * from './Indexed';
export * from './Embeddable';
export * from './Embedded';
export * from './Filter';
export * from './Subscriber';
export * from './CreateRequestContext';
export * from './EnsureRequestContext';
export * from './hooks';
7 changes: 0 additions & 7 deletions packages/core/src/metadata/MetadataStorage.ts
Expand Up @@ -3,12 +3,10 @@ import { Utils } from '../utils/Utils';
import { MetadataError } from '../errors';
import type { EntityManager } from '../EntityManager';
import { EntityHelper } from '../entity/EntityHelper';
import type { EventSubscriber } from '../events';

export class MetadataStorage {

private static readonly metadata: Dictionary<EntityMetadata> = Utils.getGlobalStorage('metadata');
private static readonly subscribers: Dictionary<EventSubscriber> = Utils.getGlobalStorage('subscribers');
private readonly metadata: Dictionary<EntityMetadata>;

constructor(metadata: Dictionary<EntityMetadata> = {}) {
Expand Down Expand Up @@ -43,17 +41,12 @@ export class MetadataStorage {
return meta;
}

static getSubscriberMetadata(): Dictionary<EventSubscriber> {
return MetadataStorage.subscribers;
}

static init(): MetadataStorage {
return new MetadataStorage(MetadataStorage.metadata);
}

static clear(): void {
Object.keys(this.metadata).forEach(k => delete this.metadata[k]);
Object.keys(this.subscribers).forEach(k => delete this.subscribers[k]);
}

getAll(): Dictionary<EntityMetadata> {
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/utils/Configuration.ts
Expand Up @@ -25,7 +25,7 @@ import type { EntityManager } from '../EntityManager';
import type { Platform } from '../platforms';
import type { EntitySchema } from '../metadata/EntitySchema';
import type { MetadataProvider } from '../metadata/MetadataProvider';
import { MetadataStorage } from '../metadata/MetadataStorage';
import type { MetadataStorage } from '../metadata/MetadataStorage';
import { ReflectMetadataProvider } from '../metadata/ReflectMetadataProvider';
import type { EventSubscriber } from '../events';
import type { IDatabaseDriver } from '../drivers/IDatabaseDriver';
Expand Down Expand Up @@ -345,8 +345,9 @@ export class Configuration<D extends IDatabaseDriver = IDatabaseDriver> {
this.options.filters[key].default ??= true;
});

const subscribers = Object.values(MetadataStorage.getSubscriberMetadata());
this.options.subscribers = [...new Set([...this.options.subscribers, ...subscribers])];
this.options.subscribers = Utils.unique(this.options.subscribers).map(subscriber => {
return subscriber.constructor.name === 'Function' ? new (subscriber as Constructor)() : subscriber;
}) as EventSubscriber[];

if (!colors.enabled()) {
this.options.highlighter = new NullHighlighter();
Expand Down Expand Up @@ -489,7 +490,7 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver> ex
entities: (string | EntityClass<AnyEntity> | EntityClassGroup<AnyEntity> | EntitySchema)[]; // `any` required here for some TS weirdness
entitiesTs: (string | EntityClass<AnyEntity> | EntityClassGroup<AnyEntity> | EntitySchema)[]; // `any` required here for some TS weirdness
extensions: { register: (orm: MikroORM) => void }[];
subscribers: EventSubscriber[];
subscribers: (EventSubscriber | Constructor<EventSubscriber>)[];
filters: Dictionary<{ name?: string } & Omit<FilterDef, 'name'>>;
discovery: {
warnWhenNoEntities?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions tests/bootstrap.ts
Expand Up @@ -110,6 +110,7 @@ export async function initORMMySql<D extends MySqlDriver | MariaDbDriver = MySql
replicas: [{ name: 'read-1' }, { name: 'read-2' }], // create two read replicas with same configuration, just for testing purposes
migrations: { path: BASE_DIR + '/../temp/migrations', snapshot: false },
extensions: [Migrator, SeedManager, EntityGenerator],
subscribers: [new Test2Subscriber()],
}, additionalOptions));

await orm.schema.ensureDatabase();
Expand Down Expand Up @@ -149,6 +150,7 @@ export async function initORMPostgreSql(loadStrategy = LoadStrategy.SELECT_IN, e
migrations: { path: BASE_DIR + '/../temp/migrations', snapshot: false },
forceEntityConstructor: [FooBar2],
loadStrategy,
subscribers: [Test2Subscriber],
extensions: [Migrator, SeedManager, EntityGenerator],
});

Expand Down
4 changes: 0 additions & 4 deletions tests/decorators.test.ts
Expand Up @@ -8,7 +8,6 @@ import {
MetadataStorage,
ReferenceKind,
Utils,
Subscriber,
CreateRequestContext,
EnsureRequestContext,
RequestContext,
Expand Down Expand Up @@ -125,11 +124,8 @@ describe('decorators', () => {
expect(storage[key].properties.test0).toMatchObject({ kind: ReferenceKind.MANY_TO_MANY, name: 'test0' });
expect(storage[key].properties.test0.entity()).toBe(Test);
expect(Object.keys(MetadataStorage.getMetadata())).toHaveLength(7);
Subscriber()(Test6);
expect(Object.keys(MetadataStorage.getSubscriberMetadata())).toHaveLength(1);
MetadataStorage.clear();
expect(Object.keys(MetadataStorage.getMetadata())).toHaveLength(0);
expect(Object.keys(MetadataStorage.getSubscriberMetadata())).toHaveLength(0);
});

test('ManyToOne', () => {
Expand Down
9 changes: 8 additions & 1 deletion tests/features/events/events.mysql.test.ts
Expand Up @@ -13,7 +13,14 @@ describe('events (mysql)', () => {

let orm: MikroORM<MySqlDriver>;

beforeAll(async () => orm = await initORMMySql(undefined, undefined, true));
beforeAll(async () => orm = await initORMMySql(undefined, {
subscribers: [
Author2Subscriber,
EverythingSubscriber,
FlushSubscriber,
Test2Subscriber,
],
}, true));
beforeEach(async () => orm.schema.clearDatabase());
afterEach(() => {
Author2Subscriber.log.length = 0;
Expand Down
4 changes: 2 additions & 2 deletions tests/features/fulltext/GH3457.test.ts
Expand Up @@ -6,7 +6,7 @@ import {
MikroORM,
PrimaryKey,
Property,
Subscriber, wrap,
wrap,
} from '@mikro-orm/core';
import { FullTextType, PostgreSqlDriver } from '@mikro-orm/postgresql';
import { randomUUID } from 'crypto';
Expand Down Expand Up @@ -60,7 +60,6 @@ export class TestHistory {

}

@Subscriber()
export class CaseHistorySubscriber implements EventSubscriber<Test> {

async onFlush(args: FlushEventArgs): Promise<void> {
Expand All @@ -86,6 +85,7 @@ beforeAll(async () => {
entities: [Test, TestHistory],
dbName: `mikro_orm_test_3457`,
driver: PostgreSqlDriver,
subscribers: [CaseHistorySubscriber],
});
await orm.schema.refreshDatabase();
});
Expand Down
6 changes: 2 additions & 4 deletions tests/features/unit-of-work/GH4245.test.ts
@@ -1,4 +1,4 @@
import { Entity, Ref, ManyToOne, PrimaryKey, Property, EventSubscriber, ChangeSet, FlushEventArgs, Subscriber } from '@mikro-orm/core';
import { Entity, Ref, ManyToOne, PrimaryKey, Property, EventSubscriber, ChangeSet, FlushEventArgs } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
Expand All @@ -15,7 +15,6 @@ class Node {

}

@Subscriber()
class AfterFlushSubscriber implements EventSubscriber {

static readonly changeSets: ChangeSet<any>[] = [];
Expand All @@ -32,10 +31,9 @@ beforeAll(async () => {
orm = await MikroORM.init({
entities: [Node],
dbName: ':memory:',
subscribers: [AfterFlushSubscriber],
});
await orm.schema.refreshDatabase();

orm.em.getEventManager().registerSubscriber(new AfterFlushSubscriber());
});

afterAll(async () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/issues/GH3005.test.ts
@@ -1,5 +1,5 @@
import type { EventSubscriber, FlushEventArgs } from '@mikro-orm/better-sqlite';
import { Collection, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property, Subscriber, wrap } from '@mikro-orm/better-sqlite';
import { Collection, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property, wrap } from '@mikro-orm/better-sqlite';

@Entity({ tableName: 'customers' })
class Customer {
Expand Down Expand Up @@ -29,7 +29,6 @@ class Order {

}

@Subscriber()
class OrdersSubscriber implements EventSubscriber<Order> {

getSubscribedEntities() {
Expand All @@ -54,6 +53,7 @@ beforeAll(async () => {
orm = await MikroORM.init({
entities: [Order, Customer],
dbName: ':memory:',
subscribers: [OrdersSubscriber],
});
await orm.schema.refreshDatabase();
});
Expand Down
4 changes: 2 additions & 2 deletions tests/issues/GH3345.test.ts
@@ -1,5 +1,5 @@
import type { EventSubscriber, FlushEventArgs } from '@mikro-orm/sqlite';
import { Collection, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property, Subscriber } from '@mikro-orm/sqlite';
import { Collection, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property } from '@mikro-orm/sqlite';

@Entity({ tableName: 'customers' })
class Customer {
Expand Down Expand Up @@ -29,7 +29,6 @@ class Order {

}

@Subscriber()
class OrdersSubscriber implements EventSubscriber<Order> {

static emptyChangelogs: boolean[] = [];
Expand All @@ -54,6 +53,7 @@ beforeAll(async () => {
orm = await MikroORM.init({
entities: [Customer],
dbName: ':memory:',
subscribers: [OrdersSubscriber],
});
await orm.schema.createSchema();
});
Expand Down
2 changes: 0 additions & 2 deletions tests/subscribers/Author2Subscriber.ts
@@ -1,8 +1,6 @@
import type { EntityName, EventArgs, EventSubscriber } from '@mikro-orm/core';
import { Subscriber } from '@mikro-orm/core';
import { Author2 } from '../entities-sql';

@Subscriber()
export class Author2Subscriber implements EventSubscriber<Author2> {

static readonly log: [string, EventArgs<Author2>][] = [];
Expand Down
2 changes: 0 additions & 2 deletions tests/subscribers/EverythingSubscriber.ts
@@ -1,7 +1,5 @@
import type { EventArgs, EventSubscriber } from '@mikro-orm/core';
import { Subscriber } from '@mikro-orm/core';

@Subscriber()
export class EverythingSubscriber implements EventSubscriber {

static readonly log: [string, EventArgs<any>][] = [];
Expand Down
2 changes: 0 additions & 2 deletions tests/subscribers/FlushSubscriber.ts
@@ -1,7 +1,5 @@
import type { EventSubscriber, FlushEventArgs } from '@mikro-orm/core';
import { Subscriber } from '@mikro-orm/core';

@Subscriber()
export class FlushSubscriber implements EventSubscriber {

static readonly log: [string, FlushEventArgs][] = [];
Expand Down
4 changes: 1 addition & 3 deletions tests/subscribers/Test2Subscriber.ts
@@ -1,9 +1,7 @@
import type { EntityName, EventSubscriber, FlushEventArgs } from '@mikro-orm/core';
import { Subscriber } from '@mikro-orm/core';
import { Test2 } from '../entities-sql';
import type { SqlEntityManager } from '@mikro-orm/knex';
import { Test2 } from '../entities-sql';

@Subscriber()
export class Test2Subscriber implements EventSubscriber<Test2> {

static readonly log: [string, FlushEventArgs][] = [];
Expand Down

0 comments on commit 7c8f776

Please sign in to comment.