From a3bd37c83c9dcafa6599e4efbd090c31670873a8 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Fri, 1 Nov 2024 22:17:11 +0000 Subject: [PATCH 01/31] renamed events legacy --- src/app/events/listeners/ExampleListener.ts | 2 +- .../events/subscribers/ExampleSubscriber.ts | 2 +- .../2024-09-06-create-failed-worker-table.ts | 2 +- .../2024-09-06-create-worker-table.ts | 2 +- src/config/events.ts | 10 +-- .../domains/console/commands/WorkerCommand.ts | 4 +- .../domains/database/base/BaseQueryBuilder.ts | 0 .../drivers/QueueDriver.ts | 8 +- .../drivers/SynchronousDriver.ts | 4 +- .../exceptions/EventDriverException.ts | 0 .../exceptions/EventSubscriberException.ts | 0 .../factory/failedWorkerModelFactory.ts | 2 +- .../factory/workerModelFactory.ts | 2 +- .../interfaces/IDispatchable.ts | 6 +- .../interfaces/IEvent.ts | 24 +++--- .../interfaces/IEventConfig.ts | 30 +++---- .../interfaces/IEventDispatcher.ts | 7 ++ .../interfaces/IEventDriver.ts | 4 +- .../interfaces/IEventListener.ts | 10 +-- .../interfaces/IEventPayload.ts | 8 +- .../interfaces/IEventService.ts | 8 +- .../models/FailedWorkerModel.ts | 0 .../models/WorkerModel.ts | 0 .../providers/EventProvider.ts | 68 ++++++++-------- .../services/EventDispatcher.ts | 78 +++++++++---------- .../services/EventListener.ts | 20 ++--- .../services/EventService.ts | 14 ++-- .../services/EventSubscriber.ts | 8 +- .../services/QueueDriverOptions.ts | 0 .../services/Worker.ts | 14 ++-- .../events/interfaces/IEventDispatcher.ts | 7 -- src/core/interfaces/ICoreContainers.ts | 2 +- src/core/providers/CoreProviders.ts | 2 +- ...Queue.test.ts => eventQueueLegacy.test.ts} | 2 +- ...ntSync.test.ts => eventSync.testLegacy.ts} | 2 +- ...{TestListener.ts => TestListenerLegacy.ts} | 2 +- ...Listener.ts => TestQueueListenerLegacy.ts} | 2 +- ...criber.ts => TestQueueSubscriberLegacy.ts} | 2 +- ...scriber.ts => TestSyncSubscriberLegacy.ts} | 2 +- src/tests/models/models/TestWorkerModel.ts | 2 +- src/tests/providers/TestEventProvider.ts | 14 ++-- src/tests/runApp.test.ts | 2 +- 42 files changed, 189 insertions(+), 189 deletions(-) create mode 100644 src/core/domains/database/base/BaseQueryBuilder.ts rename src/core/domains/{events => events-legacy}/drivers/QueueDriver.ts (81%) rename src/core/domains/{events => events-legacy}/drivers/SynchronousDriver.ts (82%) rename src/core/domains/{events => events-legacy}/exceptions/EventDriverException.ts (100%) rename src/core/domains/{events => events-legacy}/exceptions/EventSubscriberException.ts (100%) rename src/core/domains/{events => events-legacy}/factory/failedWorkerModelFactory.ts (93%) rename src/core/domains/{events => events-legacy}/factory/workerModelFactory.ts (96%) rename src/core/domains/{events => events-legacy}/interfaces/IDispatchable.ts (97%) rename src/core/domains/{events => events-legacy}/interfaces/IEvent.ts (75%) rename src/core/domains/{events => events-legacy}/interfaces/IEventConfig.ts (58%) create mode 100644 src/core/domains/events-legacy/interfaces/IEventDispatcher.ts rename src/core/domains/{events => events-legacy}/interfaces/IEventDriver.ts (72%) rename src/core/domains/{events => events-legacy}/interfaces/IEventListener.ts (97%) rename src/core/domains/{events => events-legacy}/interfaces/IEventPayload.ts (97%) rename src/core/domains/{events => events-legacy}/interfaces/IEventService.ts (68%) rename src/core/domains/{events => events-legacy}/models/FailedWorkerModel.ts (100%) rename src/core/domains/{events => events-legacy}/models/WorkerModel.ts (100%) rename src/core/domains/{events => events-legacy}/providers/EventProvider.ts (73%) rename src/core/domains/{events => events-legacy}/services/EventDispatcher.ts (75%) rename src/core/domains/{events => events-legacy}/services/EventListener.ts (52%) rename src/core/domains/{events => events-legacy}/services/EventService.ts (73%) rename src/core/domains/{events => events-legacy}/services/EventSubscriber.ts (82%) rename src/core/domains/{events => events-legacy}/services/QueueDriverOptions.ts (100%) rename src/core/domains/{events => events-legacy}/services/Worker.ts (89%) delete mode 100644 src/core/domains/events/interfaces/IEventDispatcher.ts rename src/tests/events/{eventQueue.test.ts => eventQueueLegacy.test.ts} (99%) rename src/tests/events/{eventSync.test.ts => eventSync.testLegacy.ts} (97%) rename src/tests/events/listeners/{TestListener.ts => TestListenerLegacy.ts} (72%) rename src/tests/events/listeners/{TestQueueListener.ts => TestQueueListenerLegacy.ts} (84%) rename src/tests/events/subscribers/{TestQueueSubscriber.ts => TestQueueSubscriberLegacy.ts} (73%) rename src/tests/events/subscribers/{TestSyncSubscriber.ts => TestSyncSubscriberLegacy.ts} (71%) diff --git a/src/app/events/listeners/ExampleListener.ts b/src/app/events/listeners/ExampleListener.ts index 5f166aa43..92335df26 100644 --- a/src/app/events/listeners/ExampleListener.ts +++ b/src/app/events/listeners/ExampleListener.ts @@ -1,4 +1,4 @@ -import EventListener from "@src/core/domains/events/services/EventListener"; +import EventListener from "@src/core/domains/events-legacy/services/EventListener"; export class ExampleListener extends EventListener<{userId: string}> { diff --git a/src/app/events/subscribers/ExampleSubscriber.ts b/src/app/events/subscribers/ExampleSubscriber.ts index fcdd2ce48..48a9e5897 100644 --- a/src/app/events/subscribers/ExampleSubscriber.ts +++ b/src/app/events/subscribers/ExampleSubscriber.ts @@ -1,4 +1,4 @@ -import EventSubscriber from "@src/core/domains/events/services/EventSubscriber"; +import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; type Payload = { userId: string; diff --git a/src/app/migrations/2024-09-06-create-failed-worker-table.ts b/src/app/migrations/2024-09-06-create-failed-worker-table.ts index a7994b30a..4f329ef7d 100644 --- a/src/app/migrations/2024-09-06-create-failed-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-failed-worker-table.ts @@ -1,4 +1,4 @@ -import FailedWorkerModel, { FailedWorkerModelData } from "@src/core/domains/events/models/FailedWorkerModel"; +import FailedWorkerModel, { FailedWorkerModelData } from "@src/core/domains/events-legacy/models/FailedWorkerModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; diff --git a/src/app/migrations/2024-09-06-create-worker-table.ts b/src/app/migrations/2024-09-06-create-worker-table.ts index b8b7f826a..dcf10d676 100644 --- a/src/app/migrations/2024-09-06-create-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-worker-table.ts @@ -1,4 +1,4 @@ -import WorkerModel, { WorkerModelData } from "@src/core/domains/events/models/WorkerModel"; +import WorkerModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; diff --git a/src/config/events.ts b/src/config/events.ts index 1c8ea5e95..3e35cb0fb 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -1,9 +1,9 @@ import { ExampleListener } from "@src/app/events/listeners/ExampleListener"; -import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events/drivers/QueueDriver"; -import SynchronousDriver from "@src/core/domains/events/drivers/SynchronousDriver"; -import { IEventDrivers, ISubscribers } from "@src/core/domains/events/interfaces/IEventConfig"; -import WorkerModel from "@src/core/domains/events/models/WorkerModel"; -import DriverOptions from "@src/core/domains/events/services/QueueDriverOptions"; +import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; +import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; +import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; +import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; /** * Default Event Driver Configuration diff --git a/src/core/domains/console/commands/WorkerCommand.ts b/src/core/domains/console/commands/WorkerCommand.ts index febf1b1d7..4ac6cfe62 100644 --- a/src/core/domains/console/commands/WorkerCommand.ts +++ b/src/core/domains/console/commands/WorkerCommand.ts @@ -1,8 +1,8 @@ import BaseCommand from "@src/core/domains/console/base/BaseCommand"; -import Worker from "@src/core/domains/events/services/Worker"; +import Worker from "@src/core/domains/events-legacy/services/Worker"; import { App } from "@src/core/services/App"; -export default class WorkerCommand extends BaseCommand { +export default class WorkerLegacyCommand extends BaseCommand { /** * The signature of the command diff --git a/src/core/domains/database/base/BaseQueryBuilder.ts b/src/core/domains/database/base/BaseQueryBuilder.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/domains/events/drivers/QueueDriver.ts b/src/core/domains/events-legacy/drivers/QueueDriver.ts similarity index 81% rename from src/core/domains/events/drivers/QueueDriver.ts rename to src/core/domains/events-legacy/drivers/QueueDriver.ts index cea6d22f3..0eb899a5e 100644 --- a/src/core/domains/events/drivers/QueueDriver.ts +++ b/src/core/domains/events-legacy/drivers/QueueDriver.ts @@ -1,7 +1,7 @@ -import WorkerModelFactory from '@src/core/domains/events/factory/workerModelFactory'; -import { IEvent } from '@src/core/domains/events/interfaces/IEvent'; -import IEventDriver from '@src/core/domains/events/interfaces/IEventDriver'; -import WorkerModel from '@src/core/domains/events/models/WorkerModel'; +import WorkerModelFactory from '@src/core/domains/events-legacy/factory/workerModelFactory'; +import { IEvent } from '@src/core/domains/events-legacy/interfaces/IEvent'; +import IEventDriver from '@src/core/domains/events-legacy/interfaces/IEventDriver'; +import WorkerModel from '@src/core/domains/events-legacy/models/WorkerModel'; import { ModelConstructor } from '@src/core/interfaces/IModel'; /** diff --git a/src/core/domains/events/drivers/SynchronousDriver.ts b/src/core/domains/events-legacy/drivers/SynchronousDriver.ts similarity index 82% rename from src/core/domains/events/drivers/SynchronousDriver.ts rename to src/core/domains/events-legacy/drivers/SynchronousDriver.ts index 90485d862..5534b7dd8 100644 --- a/src/core/domains/events/drivers/SynchronousDriver.ts +++ b/src/core/domains/events-legacy/drivers/SynchronousDriver.ts @@ -1,5 +1,5 @@ -import { IEvent } from '@src/core/domains/events/interfaces/IEvent'; -import IEventDriver from '@src/core/domains/events/interfaces/IEventDriver'; +import { IEvent } from '@src/core/domains/events-legacy/interfaces/IEvent'; +import IEventDriver from '@src/core/domains/events-legacy/interfaces/IEventDriver'; import { App } from '@src/core/services/App'; /** diff --git a/src/core/domains/events/exceptions/EventDriverException.ts b/src/core/domains/events-legacy/exceptions/EventDriverException.ts similarity index 100% rename from src/core/domains/events/exceptions/EventDriverException.ts rename to src/core/domains/events-legacy/exceptions/EventDriverException.ts diff --git a/src/core/domains/events/exceptions/EventSubscriberException.ts b/src/core/domains/events-legacy/exceptions/EventSubscriberException.ts similarity index 100% rename from src/core/domains/events/exceptions/EventSubscriberException.ts rename to src/core/domains/events-legacy/exceptions/EventSubscriberException.ts diff --git a/src/core/domains/events/factory/failedWorkerModelFactory.ts b/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts similarity index 93% rename from src/core/domains/events/factory/failedWorkerModelFactory.ts rename to src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts index f09c5e0bd..f58ad7947 100644 --- a/src/core/domains/events/factory/failedWorkerModelFactory.ts +++ b/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts @@ -1,4 +1,4 @@ -import FailedWorkerModel, { initialFailedWorkerModalData } from "@src/core/domains/events/models/FailedWorkerModel"; +import FailedWorkerModel, { initialFailedWorkerModalData } from "@src/core/domains/events-legacy/models/FailedWorkerModel"; type Params = { eventName: string; diff --git a/src/core/domains/events/factory/workerModelFactory.ts b/src/core/domains/events-legacy/factory/workerModelFactory.ts similarity index 96% rename from src/core/domains/events/factory/workerModelFactory.ts rename to src/core/domains/events-legacy/factory/workerModelFactory.ts index 0c66813d4..57899ff31 100644 --- a/src/core/domains/events/factory/workerModelFactory.ts +++ b/src/core/domains/events-legacy/factory/workerModelFactory.ts @@ -1,4 +1,4 @@ -import WorkerModel, { initialWorkerModalData } from "@src/core/domains/events/models/WorkerModel"; +import WorkerModel, { initialWorkerModalData } from "@src/core/domains/events-legacy/models/WorkerModel"; import { ModelConstructor } from "@src/core/interfaces/IModel"; type Params = { diff --git a/src/core/domains/events/interfaces/IDispatchable.ts b/src/core/domains/events-legacy/interfaces/IDispatchable.ts similarity index 97% rename from src/core/domains/events/interfaces/IDispatchable.ts rename to src/core/domains/events-legacy/interfaces/IDispatchable.ts index ed49ff4ff..603995865 100644 --- a/src/core/domains/events/interfaces/IDispatchable.ts +++ b/src/core/domains/events-legacy/interfaces/IDispatchable.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars */ -export default interface IDispatchable { - dispatch: (...args: any[]) => any; +/* eslint-disable no-unused-vars */ +export default interface IDispatchable { + dispatch: (...args: any[]) => any; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEvent.ts b/src/core/domains/events-legacy/interfaces/IEvent.ts similarity index 75% rename from src/core/domains/events/interfaces/IEvent.ts rename to src/core/domains/events-legacy/interfaces/IEvent.ts index 9ea7badf5..5e17d115e 100644 --- a/src/core/domains/events/interfaces/IEvent.ts +++ b/src/core/domains/events-legacy/interfaces/IEvent.ts @@ -1,13 +1,13 @@ -import { eventSubscribers } from "@src/config/events"; -import { IEventDrivers, ISubscribers } from "@src/core/domains/events/interfaces/IEventConfig"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; - -export interface IEvent< - Payload extends IEventPayload = IEventPayload, - Watchters extends ISubscribers = typeof eventSubscribers, - Drivers extends IEventDrivers = IEventDrivers -> { - name: keyof Watchters & string; - driver: keyof Drivers; - payload: Payload; +import { eventSubscribers } from "@src/config/events"; +import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; + +export interface IEvent< + Payload extends IEventPayload = IEventPayload, + Watchters extends ISubscribers = typeof eventSubscribers, + Drivers extends IEventDrivers = IEventDrivers +> { + name: keyof Watchters & string; + driver: keyof Drivers; + payload: Payload; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventConfig.ts b/src/core/domains/events-legacy/interfaces/IEventConfig.ts similarity index 58% rename from src/core/domains/events/interfaces/IEventConfig.ts rename to src/core/domains/events-legacy/interfaces/IEventConfig.ts index 512b863d8..e0e14e571 100644 --- a/src/core/domains/events/interfaces/IEventConfig.ts +++ b/src/core/domains/events-legacy/interfaces/IEventConfig.ts @@ -1,16 +1,16 @@ -import { IDriverConstructor } from '@src/core/domains/events/interfaces/IEventDriver'; -import { EventListenerConstructor } from "@src/core/domains/events/interfaces/IEventListener"; -import DriverOptions from "@src/core/domains/events/services/QueueDriverOptions"; - -export interface IDriverConfig { - driver: IDriverConstructor - options?: DriverOptions -} - -export type IEventDrivers = { - [key: string]: IDriverConfig -} - -export type ISubscribers = { - [key: string]: Array +import { IDriverConstructor } from '@src/core/domains/events-legacy/interfaces/IEventDriver'; +import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; +import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; + +export interface IDriverConfig { + driver: IDriverConstructor + options?: DriverOptions +} + +export type IEventDrivers = { + [key: string]: IDriverConfig +} + +export type ISubscribers = { + [key: string]: Array } \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts b/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts new file mode 100644 index 000000000..a60559989 --- /dev/null +++ b/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts @@ -0,0 +1,7 @@ +/* eslint-disable no-unused-vars */ +import IDispatchable from "@src/core/domains/events-legacy/interfaces/IDispatchable"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; + +export interface IEventDispatcher extends IDispatchable { + dispatch: (event: IEvent) => Promise; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventDriver.ts b/src/core/domains/events-legacy/interfaces/IEventDriver.ts similarity index 72% rename from src/core/domains/events/interfaces/IEventDriver.ts rename to src/core/domains/events-legacy/interfaces/IEventDriver.ts index 9e1aa6be2..6ab445017 100644 --- a/src/core/domains/events/interfaces/IEventDriver.ts +++ b/src/core/domains/events-legacy/interfaces/IEventDriver.ts @@ -1,6 +1,6 @@ /* eslint-disable no-unused-vars */ -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; export type IDriverConstructor< Payload extends IEventPayload = IEventPayload, diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events-legacy/interfaces/IEventListener.ts similarity index 97% rename from src/core/domains/events/interfaces/IEventListener.ts rename to src/core/domains/events-legacy/interfaces/IEventListener.ts index 6077bcaf1..82787b09c 100644 --- a/src/core/domains/events/interfaces/IEventListener.ts +++ b/src/core/domains/events-legacy/interfaces/IEventListener.ts @@ -1,6 +1,6 @@ -/* eslint-disable no-unused-vars */ -export type EventListenerConstructor = new (...args: any[]) => EventListener; - -export interface IEventListener { - handle: (...args: any[]) => any; +/* eslint-disable no-unused-vars */ +export type EventListenerConstructor = new (...args: any[]) => EventListener; + +export interface IEventListener { + handle: (...args: any[]) => any; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventPayload.ts b/src/core/domains/events-legacy/interfaces/IEventPayload.ts similarity index 97% rename from src/core/domains/events/interfaces/IEventPayload.ts rename to src/core/domains/events-legacy/interfaces/IEventPayload.ts index 53978349b..8e936ae1c 100644 --- a/src/core/domains/events/interfaces/IEventPayload.ts +++ b/src/core/domains/events-legacy/interfaces/IEventPayload.ts @@ -1,5 +1,5 @@ -export type TSerializableTypes = number | string | boolean | undefined; - -export interface IEventPayload { - [key: string | number | symbol]: TSerializableTypes +export type TSerializableTypes = number | string | boolean | undefined; + +export interface IEventPayload { + [key: string | number | symbol]: TSerializableTypes } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events-legacy/interfaces/IEventService.ts similarity index 68% rename from src/core/domains/events/interfaces/IEventService.ts rename to src/core/domains/events-legacy/interfaces/IEventService.ts index fad5dd06d..ee4015f57 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events-legacy/interfaces/IEventService.ts @@ -1,8 +1,8 @@ /* eslint-disable no-unused-vars */ -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; -import { IDriverConfig, IEventDrivers, ISubscribers } from "@src/core/domains/events/interfaces/IEventConfig"; -import { EventListenerConstructor } from "@src/core/domains/events/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; +import { IDriverConfig, IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; export interface EventServiceConfig { defaultDriver: string; diff --git a/src/core/domains/events/models/FailedWorkerModel.ts b/src/core/domains/events-legacy/models/FailedWorkerModel.ts similarity index 100% rename from src/core/domains/events/models/FailedWorkerModel.ts rename to src/core/domains/events-legacy/models/FailedWorkerModel.ts diff --git a/src/core/domains/events/models/WorkerModel.ts b/src/core/domains/events-legacy/models/WorkerModel.ts similarity index 100% rename from src/core/domains/events/models/WorkerModel.ts rename to src/core/domains/events-legacy/models/WorkerModel.ts diff --git a/src/core/domains/events/providers/EventProvider.ts b/src/core/domains/events-legacy/providers/EventProvider.ts similarity index 73% rename from src/core/domains/events/providers/EventProvider.ts rename to src/core/domains/events-legacy/providers/EventProvider.ts index 8eacab1ba..6a80ab6bd 100644 --- a/src/core/domains/events/providers/EventProvider.ts +++ b/src/core/domains/events-legacy/providers/EventProvider.ts @@ -1,35 +1,35 @@ - -import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/events"; -import BaseProvider from "@src/core/base/Provider"; -import { EventServiceConfig } from "@src/core/domains/events/interfaces/IEventService"; -import EventService from "@src/core/domains/events/services/EventService"; -import { App } from "@src/core/services/App"; -import WorkerCommand from "@src/core/domains/console/commands/WorkerCommand"; - -export default class EventProvider extends BaseProvider { - - protected config: EventServiceConfig = { - defaultDriver: defaultEventDriver, - drivers: eventDrivers, - subscribers: eventSubscribers - }; - - public async register(): Promise { - this.log('Registering EventProvider'); - - /** - * Register event service - */ - App.setContainer('events', new EventService(this.config)); - - /** - * Register system provided commands - */ - App.container('console').register().registerAll([ - WorkerCommand - ]) - } - - public async boot(): Promise {} - + +import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/events"; +import BaseProvider from "@src/core/base/Provider"; +import WorkerLegacyCommand from "@src/core/domains/console/commands/WorkerCommand"; +import { EventServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import EventService from "@src/core/domains/events-legacy/services/EventService"; +import { App } from "@src/core/services/App"; + +export default class EventProvider extends BaseProvider { + + protected config: EventServiceConfig = { + defaultDriver: defaultEventDriver, + drivers: eventDrivers, + subscribers: eventSubscribers + }; + + public async register(): Promise { + this.log('Registering EventProvider'); + + /** + * Register event service + */ + App.setContainer('events', new EventService(this.config)); + + /** + * Register system provided commands + */ + App.container('console').register().registerAll([ + WorkerLegacyCommand + ]) + } + + public async boot(): Promise {} + } \ No newline at end of file diff --git a/src/core/domains/events/services/EventDispatcher.ts b/src/core/domains/events-legacy/services/EventDispatcher.ts similarity index 75% rename from src/core/domains/events/services/EventDispatcher.ts rename to src/core/domains/events-legacy/services/EventDispatcher.ts index 4e548c3d4..463739f82 100644 --- a/src/core/domains/events/services/EventDispatcher.ts +++ b/src/core/domains/events-legacy/services/EventDispatcher.ts @@ -1,40 +1,40 @@ -import Singleton from "@src/core/base/Singleton"; -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; -import { IDriverConfig } from "@src/core/domains/events/interfaces/IEventConfig"; -import { IEventDispatcher } from "@src/core/domains/events/interfaces/IEventDispatcher"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; -import { App } from "@src/core/services/App"; - - -export default class EventDispatcher extends Singleton implements IEventDispatcher { - - /** - * Handle the dispatched event - * @param event - */ - public async dispatch(event: IEvent) { - App.container('logger').info(`[EventDispatcher:dispatch] Event '${event.name}' with driver '${event.driver}'`) - - const driverOptions = this.getDriverOptionsFromEvent(event) - const driverCtor = driverOptions.driver - - const instance = new driverCtor(); - await instance.handle(event, driverOptions.options?.getOptions()); - } - - /** - * Get the driver constructor based on the name of the worker defiend in the Event - * @param IEvent event - * @returns - */ - protected getDriverOptionsFromEvent(event: IEvent): IDriverConfig { - const driver = App.container('events').config.drivers[event.driver] - - if(!driver) { - throw new Error('Driver not found \'' + event.driver + '\'') - } - - return driver - } - +import Singleton from "@src/core/base/Singleton"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; +import { IDriverConfig } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import { IEventDispatcher } from "@src/core/domains/events-legacy/interfaces/IEventDispatcher"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; +import { App } from "@src/core/services/App"; + + +export default class EventDispatcher extends Singleton implements IEventDispatcher { + + /** + * Handle the dispatched event + * @param event + */ + public async dispatch(event: IEvent) { + App.container('logger').info(`[EventDispatcher:dispatch] Event '${event.name}' with driver '${event.driver}'`) + + const driverOptions = this.getDriverOptionsFromEvent(event) + const driverCtor = driverOptions.driver + + const instance = new driverCtor(); + await instance.handle(event, driverOptions.options?.getOptions()); + } + + /** + * Get the driver constructor based on the name of the worker defiend in the Event + * @param IEvent event + * @returns + */ + protected getDriverOptionsFromEvent(event: IEvent): IDriverConfig { + const driver = App.container('events').config.drivers[event.driver] + + if(!driver) { + throw new Error('Driver not found \'' + event.driver + '\'') + } + + return driver + } + } \ No newline at end of file diff --git a/src/core/domains/events/services/EventListener.ts b/src/core/domains/events-legacy/services/EventListener.ts similarity index 52% rename from src/core/domains/events/services/EventListener.ts rename to src/core/domains/events-legacy/services/EventListener.ts index 08574ef14..3c0b4ff0c 100644 --- a/src/core/domains/events/services/EventListener.ts +++ b/src/core/domains/events-legacy/services/EventListener.ts @@ -1,11 +1,11 @@ -/* eslint-disable no-unused-vars */ -import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; - -export default abstract class EventListener< - Payload extends IEventPayload = IEventPayload -> implements IEventListener { - - handle!: (payload: Payload) => any; - +/* eslint-disable no-unused-vars */ +import { IEventListener } from "@src/core/domains/events-legacy/interfaces/IEventListener"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; + +export default abstract class EventListener< + Payload extends IEventPayload = IEventPayload +> implements IEventListener { + + handle!: (payload: Payload) => any; + } \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events-legacy/services/EventService.ts similarity index 73% rename from src/core/domains/events/services/EventService.ts rename to src/core/domains/events-legacy/services/EventService.ts index 68add9430..d614857b8 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events-legacy/services/EventService.ts @@ -1,11 +1,11 @@ import Singleton from "@src/core/base/Singleton"; -import EventDriverException from "@src/core/domains/events/exceptions/EventDriverException"; -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; -import { IDriverConfig } from "@src/core/domains/events/interfaces/IEventConfig"; -import { EventListenerConstructor } from "@src/core/domains/events/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; -import { EventServiceConfig, IEventService } from "@src/core/domains/events/interfaces/IEventService"; -import EventDispatcher from "@src/core/domains/events/services/EventDispatcher"; +import EventDriverException from "@src/core/domains/events-legacy/exceptions/EventDriverException"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; +import { IDriverConfig } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; +import { EventServiceConfig, IEventService } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import EventDispatcher from "@src/core/domains/events-legacy/services/EventDispatcher"; /** * Event Service diff --git a/src/core/domains/events/services/EventSubscriber.ts b/src/core/domains/events-legacy/services/EventSubscriber.ts similarity index 82% rename from src/core/domains/events/services/EventSubscriber.ts rename to src/core/domains/events-legacy/services/EventSubscriber.ts index 26ee138eb..951287d6a 100644 --- a/src/core/domains/events/services/EventSubscriber.ts +++ b/src/core/domains/events-legacy/services/EventSubscriber.ts @@ -1,7 +1,7 @@ -import EventSubscriberException from "@src/core/domains/events/exceptions/EventSubscriberException"; -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; -import { IEventDrivers, ISubscribers } from '@src/core/domains/events/interfaces/IEventConfig'; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import EventSubscriberException from "@src/core/domains/events-legacy/exceptions/EventSubscriberException"; +import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; +import { IEventDrivers, ISubscribers } from '@src/core/domains/events-legacy/interfaces/IEventConfig'; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; /** * EventSubscriber diff --git a/src/core/domains/events/services/QueueDriverOptions.ts b/src/core/domains/events-legacy/services/QueueDriverOptions.ts similarity index 100% rename from src/core/domains/events/services/QueueDriverOptions.ts rename to src/core/domains/events-legacy/services/QueueDriverOptions.ts diff --git a/src/core/domains/events/services/Worker.ts b/src/core/domains/events-legacy/services/Worker.ts similarity index 89% rename from src/core/domains/events/services/Worker.ts rename to src/core/domains/events-legacy/services/Worker.ts index 0d792ae8c..3f8adb136 100644 --- a/src/core/domains/events/services/Worker.ts +++ b/src/core/domains/events-legacy/services/Worker.ts @@ -1,12 +1,12 @@ import Repository from "@src/core/base/Repository"; import Singleton from "@src/core/base/Singleton"; -import { QueueDriverOptions } from "@src/core/domains/events/drivers/QueueDriver"; -import EventDriverException from "@src/core/domains/events/exceptions/EventDriverException"; -import FailedWorkerModelFactory from "@src/core/domains/events/factory/failedWorkerModelFactory"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; -import WorkerModel from "@src/core/domains/events/models/WorkerModel"; -import EventSubscriber from "@src/core/domains/events/services/EventSubscriber"; -import DriverOptions from "@src/core/domains/events/services/QueueDriverOptions"; +import { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; +import EventDriverException from "@src/core/domains/events-legacy/exceptions/EventDriverException"; +import FailedWorkerModelFactory from "@src/core/domains/events-legacy/factory/failedWorkerModelFactory"; +import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; +import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; +import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; +import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; import { App } from "@src/core/services/App"; /** diff --git a/src/core/domains/events/interfaces/IEventDispatcher.ts b/src/core/domains/events/interfaces/IEventDispatcher.ts deleted file mode 100644 index 75edb633b..000000000 --- a/src/core/domains/events/interfaces/IEventDispatcher.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-unused-vars */ -import IDispatchable from "@src/core/domains/events/interfaces/IDispatchable"; -import { IEvent } from "@src/core/domains/events/interfaces/IEvent"; - -export interface IEventDispatcher extends IDispatchable { - dispatch: (event: IEvent) => Promise; -} \ No newline at end of file diff --git a/src/core/interfaces/ICoreContainers.ts b/src/core/interfaces/ICoreContainers.ts index 140c65179..f3aa5e578 100644 --- a/src/core/interfaces/ICoreContainers.ts +++ b/src/core/interfaces/ICoreContainers.ts @@ -1,7 +1,7 @@ import { IAuthService } from '@src/core/domains/auth/interfaces/IAuthService'; import ICommandService from '@src/core/domains/console/interfaces/ICommandService'; import { IDatabaseService } from '@src/core/domains/database/interfaces/IDatabaseService'; -import { IEventService } from '@src/core/domains/events/interfaces/IEventService'; +import { IEventService } from '@src/core/domains/events-legacy/interfaces/IEventService'; import { IRequestContext } from '@src/core/domains/express/interfaces/ICurrentRequest'; import IExpressService from '@src/core/domains/express/interfaces/IExpressService'; import { ILoggerService } from '@src/core/domains/logger/interfaces/ILoggerService'; diff --git a/src/core/providers/CoreProviders.ts b/src/core/providers/CoreProviders.ts index 269636de3..56439cfbb 100644 --- a/src/core/providers/CoreProviders.ts +++ b/src/core/providers/CoreProviders.ts @@ -1,7 +1,7 @@ import AuthProvider from "@src/core/domains/auth/providers/AuthProvider"; import ConsoleProvider from "@src/core/domains/console/providers/ConsoleProvider"; import DatabaseProvider from "@src/core/domains/database/providers/DatabaseProvider"; -import EventProvider from "@src/core/domains/events/providers/EventProvider"; +import EventProvider from "@src/core/domains/events-legacy/providers/EventProvider"; import ExpressProvider from "@src/core/domains/express/providers/ExpressProvider"; import LoggerProvider from "@src/core/domains/logger/providers/LoggerProvider"; import MakeProvider from "@src/core/domains/make/providers/MakeProvider"; diff --git a/src/tests/events/eventQueue.test.ts b/src/tests/events/eventQueueLegacy.test.ts similarity index 99% rename from src/tests/events/eventQueue.test.ts rename to src/tests/events/eventQueueLegacy.test.ts index 65b792a34..104f3cb0c 100644 --- a/src/tests/events/eventQueue.test.ts +++ b/src/tests/events/eventQueueLegacy.test.ts @@ -4,7 +4,7 @@ import Repository from '@src/core/base/Repository'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; -import TestQueueSubscriber from '@src/tests/events/subscribers/TestQueueSubscriber'; +import TestQueueSubscriber from '@src/tests/events/subscribers/TestQueueSubscriberLegacy'; import { TestMovieModel } from '@src/tests/models/models/TestMovie'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; diff --git a/src/tests/events/eventSync.test.ts b/src/tests/events/eventSync.testLegacy.ts similarity index 97% rename from src/tests/events/eventSync.test.ts rename to src/tests/events/eventSync.testLegacy.ts index 83836c2d6..9acca1ad2 100644 --- a/src/tests/events/eventSync.test.ts +++ b/src/tests/events/eventSync.testLegacy.ts @@ -3,7 +3,7 @@ import { describe } from '@jest/globals'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; -import TestSubscriber from '@src/tests/events/subscribers/TestSyncSubscriber'; +import TestSubscriber from '@src/tests/events/subscribers/TestSyncSubscriberLegacy'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; diff --git a/src/tests/events/listeners/TestListener.ts b/src/tests/events/listeners/TestListenerLegacy.ts similarity index 72% rename from src/tests/events/listeners/TestListener.ts rename to src/tests/events/listeners/TestListenerLegacy.ts index 44694bda9..5d9a91fb7 100644 --- a/src/tests/events/listeners/TestListener.ts +++ b/src/tests/events/listeners/TestListenerLegacy.ts @@ -1,4 +1,4 @@ -import EventListener from "@src/core/domains/events/services/EventListener"; +import EventListener from "@src/core/domains/events-legacy/services/EventListener"; import { App } from "@src/core/services/App"; export class TestListener extends EventListener { diff --git a/src/tests/events/listeners/TestQueueListener.ts b/src/tests/events/listeners/TestQueueListenerLegacy.ts similarity index 84% rename from src/tests/events/listeners/TestQueueListener.ts rename to src/tests/events/listeners/TestQueueListenerLegacy.ts index 719d278c1..e8fc40409 100644 --- a/src/tests/events/listeners/TestQueueListener.ts +++ b/src/tests/events/listeners/TestQueueListenerLegacy.ts @@ -1,4 +1,4 @@ -import EventListener from "@src/core/domains/events/services/EventListener"; +import EventListener from "@src/core/domains/events-legacy/services/EventListener"; import { App } from "@src/core/services/App"; import { TestMovieModel } from "@src/tests/models/models/TestMovie"; diff --git a/src/tests/events/subscribers/TestQueueSubscriber.ts b/src/tests/events/subscribers/TestQueueSubscriberLegacy.ts similarity index 73% rename from src/tests/events/subscribers/TestQueueSubscriber.ts rename to src/tests/events/subscribers/TestQueueSubscriberLegacy.ts index 2945e92dd..dad050760 100644 --- a/src/tests/events/subscribers/TestQueueSubscriber.ts +++ b/src/tests/events/subscribers/TestQueueSubscriberLegacy.ts @@ -1,4 +1,4 @@ -import EventSubscriber from "@src/core/domains/events/services/EventSubscriber"; +import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; export default class TestQueueSubscriber extends EventSubscriber { diff --git a/src/tests/events/subscribers/TestSyncSubscriber.ts b/src/tests/events/subscribers/TestSyncSubscriberLegacy.ts similarity index 71% rename from src/tests/events/subscribers/TestSyncSubscriber.ts rename to src/tests/events/subscribers/TestSyncSubscriberLegacy.ts index 5696e5fd8..099048158 100644 --- a/src/tests/events/subscribers/TestSyncSubscriber.ts +++ b/src/tests/events/subscribers/TestSyncSubscriberLegacy.ts @@ -1,4 +1,4 @@ -import EventSubscriber from "@src/core/domains/events/services/EventSubscriber"; +import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; export default class TestSubscriber extends EventSubscriber { diff --git a/src/tests/models/models/TestWorkerModel.ts b/src/tests/models/models/TestWorkerModel.ts index 96415b8a9..3c9af895c 100644 --- a/src/tests/models/models/TestWorkerModel.ts +++ b/src/tests/models/models/TestWorkerModel.ts @@ -1,4 +1,4 @@ -import WorkerModel, { WorkerModelData } from "@src/core/domains/events/models/WorkerModel"; +import WorkerModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerModel"; export default class TestWorkerModel extends WorkerModel { diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 43707b957..76ad716fb 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -1,10 +1,10 @@ -import QueueDriver, { QueueDriverOptions } from '@src/core/domains/events/drivers/QueueDriver'; -import SynchronousDriver from "@src/core/domains/events/drivers/SynchronousDriver"; -import { EventServiceConfig } from "@src/core/domains/events/interfaces/IEventService"; -import EventProvider from "@src/core/domains/events/providers/EventProvider"; -import { default as DriverOptions } from '@src/core/domains/events/services/QueueDriverOptions'; -import { TestListener } from "@src/tests/events/listeners/TestListener"; -import { TestQueueListener } from "@src/tests/events/listeners/TestQueueListener"; +import QueueDriver, { QueueDriverOptions } from '@src/core/domains/events-legacy/drivers/QueueDriver'; +import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; +import { EventServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import EventProvider from "@src/core/domains/events-legacy/providers/EventProvider"; +import { default as DriverOptions } from '@src/core/domains/events-legacy/services/QueueDriverOptions'; +import { TestListener } from "@src/tests/events/listeners/TestListenerLegacy"; +import { TestQueueListener } from "@src/tests/events/listeners/TestQueueListenerLegacy"; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; class TestEventProvider extends EventProvider { diff --git a/src/tests/runApp.test.ts b/src/tests/runApp.test.ts index 445e50554..e9705437b 100644 --- a/src/tests/runApp.test.ts +++ b/src/tests/runApp.test.ts @@ -3,7 +3,7 @@ import appConfig from '@src/config/app'; import AuthService from '@src/core/domains/auth/services/AuthService'; import ConsoleService from '@src/core/domains/console/service/ConsoleService'; import DatabaseService from '@src/core/domains/database/services/DatabaseService'; -import EventService from '@src/core/domains/events/services/EventService'; +import EventService from '@src/core/domains/events-legacy/services/EventService'; import ExpressService from '@src/core/domains/express/services/ExpressService'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; From fea79ecfbd2e56e92e447e5debca7d048c44f90d Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Sat, 2 Nov 2024 18:23:31 +0000 Subject: [PATCH 02/31] refactor(events): entire events domain refactor progress --- src/config/events.ts | 110 ++++++++---------- src/config/eventsLegacy.ts | 71 +++++++++++ src/core/concerns/HasRegisterableConcern.ts | 48 ++++++++ .../broadcast/abstract/BroadcastEvent.ts | 2 +- .../domains/broadcast/abstract/Broadcaster.ts | 2 +- .../broadcast/interfaces/IBroadcastEvent.ts | 2 +- .../drivers/SynchronousDriver.ts | 2 +- .../events-legacy/interfaces/IEvent.ts | 2 +- .../events-legacy/interfaces/IEventConfig.ts | 2 +- .../events-legacy/interfaces/IEventService.ts | 6 +- ...ventProvider.ts => EventLegacyProvider.ts} | 10 +- .../events-legacy/services/EventDispatcher.ts | 4 +- .../events-legacy/services/EventService.ts | 8 +- .../domains/events-legacy/services/Worker.ts | 4 +- src/core/domains/events/base/BaseDriver.ts | 20 ++++ src/core/domains/events/base/BaseEvent.ts | 51 ++++++++ .../domains/events/base/BaseEventListener.ts | 20 ++++ .../events/base/BaseEventSubscriber.ts | 9 ++ src/core/domains/events/base/BaseService.ts | 10 ++ .../events/DispatchBroadcastEvent.ts | 18 +++ .../events/concerns/HasListenerConcern.ts | 16 +++ .../domains/events/drivers/QueableDriver.ts | 16 +++ src/core/domains/events/drivers/SyncDriver.ts | 17 +++ .../domains/events/interfaces/IBaseEvent.ts | 9 ++ .../events/interfaces/IDispatchable.ts | 5 + .../domains/events/interfaces/IEventDriver.ts | 5 + .../events/interfaces/IEventListener.ts | 5 + .../events/interfaces/IEventPayload.ts | 10 ++ .../events/interfaces/IEventService.ts | 17 +++ .../events/interfaces/IEventSubscriber.ts | 1 + .../domains/events/interfaces/IExecutable.ts | 5 + .../interfaces/IHasDispatcherConcern.ts | 7 ++ .../events/interfaces/IHasListenerConcern.ts | 3 + .../domains/events/interfaces/INameable.ts | 3 + .../interfaces/IQueableDriverOptions.ts | 10 ++ .../events/interfaces/config/IEventConfig.ts | 10 ++ .../interfaces/config/IEventDriversConfig.ts | 12 ++ .../config/IEventListenersConfig.ts | 10 ++ .../domains/events/providers/EventProvider.ts | 51 ++++++++ .../domains/events/services/EventService.ts | 109 +++++++++++++++++ .../OnAttributeChangeBroadcastEvent.ts | 2 +- .../SetAttributeBroadcastEvent.ts | 2 +- src/core/interfaces/ICoreContainers.ts | 5 +- .../concerns/IHasRegisterableConcern.ts | 21 ++++ src/core/providers/CoreProviders.ts | 2 +- ...ueueLegacy.test.ts => eventQueueLegacy.ts} | 2 +- ...ntSync.testLegacy.ts => eventSync.test.ts} | 8 +- .../events/events/TestEventQueueEvent.ts | 20 ++++ src/tests/events/events/TestEventSyncEvent.ts | 21 ++++ src/tests/events/listeners/TestListener.ts | 7 ++ .../events/listeners/TestListenerLegacy.ts | 10 -- .../listeners/TestQueueListenerLegacy.ts | 16 --- .../subscribers/TestQueueSubscriberLegacy.ts | 12 -- .../events/subscribers/TestSubscriber.ts | 9 ++ .../subscribers/TestSyncSubscriberLegacy.ts | 12 -- .../providers/TestEventLegacyProvider.ts | 42 +++++++ src/tests/providers/TestEventProvider.ts | 61 +++++----- 57 files changed, 801 insertions(+), 173 deletions(-) create mode 100644 src/config/eventsLegacy.ts create mode 100644 src/core/concerns/HasRegisterableConcern.ts rename src/core/domains/events-legacy/providers/{EventProvider.ts => EventLegacyProvider.ts} (72%) create mode 100644 src/core/domains/events/base/BaseDriver.ts create mode 100644 src/core/domains/events/base/BaseEvent.ts create mode 100644 src/core/domains/events/base/BaseEventListener.ts create mode 100644 src/core/domains/events/base/BaseEventSubscriber.ts create mode 100644 src/core/domains/events/base/BaseService.ts create mode 100644 src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts create mode 100644 src/core/domains/events/concerns/HasListenerConcern.ts create mode 100644 src/core/domains/events/drivers/QueableDriver.ts create mode 100644 src/core/domains/events/drivers/SyncDriver.ts create mode 100644 src/core/domains/events/interfaces/IBaseEvent.ts create mode 100644 src/core/domains/events/interfaces/IDispatchable.ts create mode 100644 src/core/domains/events/interfaces/IEventDriver.ts create mode 100644 src/core/domains/events/interfaces/IEventListener.ts create mode 100644 src/core/domains/events/interfaces/IEventPayload.ts create mode 100644 src/core/domains/events/interfaces/IEventService.ts create mode 100644 src/core/domains/events/interfaces/IEventSubscriber.ts create mode 100644 src/core/domains/events/interfaces/IExecutable.ts create mode 100644 src/core/domains/events/interfaces/IHasDispatcherConcern.ts create mode 100644 src/core/domains/events/interfaces/IHasListenerConcern.ts create mode 100644 src/core/domains/events/interfaces/INameable.ts create mode 100644 src/core/domains/events/interfaces/IQueableDriverOptions.ts create mode 100644 src/core/domains/events/interfaces/config/IEventConfig.ts create mode 100644 src/core/domains/events/interfaces/config/IEventDriversConfig.ts create mode 100644 src/core/domains/events/interfaces/config/IEventListenersConfig.ts create mode 100644 src/core/domains/events/providers/EventProvider.ts create mode 100644 src/core/domains/events/services/EventService.ts create mode 100644 src/core/interfaces/concerns/IHasRegisterableConcern.ts rename src/tests/events/{eventQueueLegacy.test.ts => eventQueueLegacy.ts} (97%) rename src/tests/events/{eventSync.testLegacy.ts => eventSync.test.ts} (78%) create mode 100644 src/tests/events/events/TestEventQueueEvent.ts create mode 100644 src/tests/events/events/TestEventSyncEvent.ts create mode 100644 src/tests/events/listeners/TestListener.ts delete mode 100644 src/tests/events/listeners/TestListenerLegacy.ts delete mode 100644 src/tests/events/listeners/TestQueueListenerLegacy.ts delete mode 100644 src/tests/events/subscribers/TestQueueSubscriberLegacy.ts create mode 100644 src/tests/events/subscribers/TestSubscriber.ts delete mode 100644 src/tests/events/subscribers/TestSyncSubscriberLegacy.ts create mode 100644 src/tests/providers/TestEventLegacyProvider.ts diff --git a/src/config/events.ts b/src/config/events.ts index 3e35cb0fb..8e6d28ebb 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -1,71 +1,57 @@ -import { ExampleListener } from "@src/app/events/listeners/ExampleListener"; -import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; -import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; -import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; -import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; +import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; +import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; +import EventService from "@src/core/domains/events/services/EventService"; +import TestListener from "@src/tests/events/listeners/TestListener"; +import TestSubscriber from "@src/tests/events/subscribers/TestSubscriber"; /** - * Default Event Driver Configuration - * - * This setting determines which event driver will be used by default when no specific - * driver is defined for an event. The value is read from the APP_EVENT_DRIVER - * environment variable, falling back to 'sync' if not set. - * - * Options: - * - 'sync': Events are processed immediately. - * - 'queue': Events are queued for background processing. + * Event Drivers Constants */ -export const defaultEventDriver: string = process.env.APP_EVENT_DRIVER ?? 'sync'; +export const EVENT_DRIVERS = { + SYNC: 'sync', + QUEABLE: 'queueable' +} -/** - * Event Drivers Configuration - * - * This object defines the available event drivers and their configurations. - * Each driver can have its own set of options to customize its behavior. - * - * Structure: - * { - * [driverName: string]: { - * driver: Class extending IEventDriver, - * options?: DriverOptions object - * } - * } - */ -export const eventDrivers: IEventDrivers = { - // Synchronous Driver: Processes events immediately - sync: { - driver: SynchronousDriver - }, - // Queue Driver: Saves events for background processing - queue: { - driver: QueueDriver, - options: new DriverOptions({ - queueName: 'default', // Name of the queue - retries: 3, // Number of retry attempts for failed events +export const eventConfig: IEventConfig = { + + /** + * Default Event Driver + */ + defaultDriver: SyncDriver, + + /** + * Event Drivers Configuration + * + * This object defines the available event drivers and their configurations. + * Each driver can have its own set of options to customize its behavior. + */ + drivers: { + + // Synchronous Driver: Processes events immediately + [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), + + // Queue Driver: Saves events for background processing + [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + queueName: 'default', // Name of the queue + retries: 3, // Number of retry attempts for failed events failedCollection: 'failedWorkers', // Collection to store failed events - runAfterSeconds: 10, // Delay before processing queued events - workerModelCtor: WorkerModel // Constructor for the Worker model + runAfterSeconds: 10, // Delay before processing queued events + workerModelCtor: WorkerModel // Constructor for the Worker model }) - } -} as const; + + }, -/** - * Event Subscribers Configuration - * - * This object maps event names to arrays of listener classes. When an event - * is dispatched, all listeners registered for that event will be executed. - * - * Structure: - * { - * [eventName: string]: Array - * } - * - * Example usage: - * When an 'OnExample' event is dispatched, the ExampleListener will be triggered. - */ -export const eventSubscribers: ISubscribers = { - 'OnExample': [ - ExampleListener - ] + /** + * Event Listeners Configuration + */ + listeners: EventService.createListeners([ + { + listener: TestListener, + subscribers: [ + TestSubscriber + ] + } + ]) } \ No newline at end of file diff --git a/src/config/eventsLegacy.ts b/src/config/eventsLegacy.ts new file mode 100644 index 000000000..8a82fdf6c --- /dev/null +++ b/src/config/eventsLegacy.ts @@ -0,0 +1,71 @@ +import { ExampleListener } from "@src/app/events/listeners/ExampleListener"; +import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; +import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; +import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; +import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; +import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; + +/** + * Default Event Driver Configuration + * + * This setting determines which event driver will be used by default when no specific + * driver is defined for an event. The value is read from the APP_EVENT_DRIVER + * environment variable, falling back to 'sync' if not set. + * + * Options: + * - 'sync': Events are processed immediately. + * - 'queue': Events are queued for background processing. + */ +export const defaultEventDriver: string = process.env.APP_EVENT_DRIVER ?? 'sync'; + +/** + * Event Drivers Configuration + * + * This object defines the available event drivers and their configurations. + * Each driver can have its own set of options to customize its behavior. + * + * Structure: + * { + * [driverName: string]: { + * driver: Class extending IEventDriver, + * options?: DriverOptions object + * } + * } + */ +export const eventDrivers: IEventDrivers = { + // Synchronous Driver: Processes events immediately + sync: { + driverCtor: SynchronousDriver + }, + // Queue Driver: Saves events for background processing + queue: { + driverCtor: QueueDriver, + options: new DriverOptions({ + queueName: 'default', // Name of the queue + retries: 3, // Number of retry attempts for failed events + failedCollection: 'failedWorkers', // Collection to store failed events + runAfterSeconds: 10, // Delay before processing queued events + workerModelCtor: WorkerModel // Constructor for the Worker model + }) + } +} as const; + +/** + * Event Subscribers Configuration + * + * This object maps event names to arrays of listener classes. When an event + * is dispatched, all listeners registered for that event will be executed. + * + * Structure: + * { + * [eventName: string]: Array + * } + * + * Example usage: + * When an 'OnExample' event is dispatched, the ExampleListener will be triggered. + */ +export const eventSubscribers: ISubscribers = { + 'OnExample': [ + ExampleListener + ] +} \ No newline at end of file diff --git a/src/core/concerns/HasRegisterableConcern.ts b/src/core/concerns/HasRegisterableConcern.ts new file mode 100644 index 000000000..6d33be590 --- /dev/null +++ b/src/core/concerns/HasRegisterableConcern.ts @@ -0,0 +1,48 @@ +import { IHasRegisterableConcern, IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +const HasRegisterableConcern = (Broadcaster: ICtor) => { + return class HasRegisterable extends Broadcaster implements IHasRegisterableConcern { + + protected registerObject: IRegsiterList = {} + + private static defaultList = 'default'; + + constructor() { + super(); + this.registerList = {}; + } + + register(key: string, ...args: unknown[]): void { + if(!this.registerList[HasRegisterable.defaultList]) { + this.registerList[HasRegisterable.defaultList] = new Map(); + } + + this.registerList[HasRegisterable.defaultList].set(key, args); + } + + registerByList(listName: string, key: string, ...args: unknown[]): void { + this.registerObject[listName] = this.registerObject[listName] ?? new Map(); + this.registerObject[listName].set(key, args); + } + + setRegisteredByList(listName: string, registered: Map): void { + this.registerList[listName] = registered + } + + getRegisteredObject(): IRegsiterList { + return this.registerObject; + } + + getRegisteredList(): TRegisterMap { + return this.getRegisteredByList(HasRegisterable.defaultList); + } + + getRegisteredByList(listName: string): TRegisterMap { + return this.registerObject[listName] ?? new Map(); + } + + } +} + +export default HasRegisterableConcern \ No newline at end of file diff --git a/src/core/domains/broadcast/abstract/BroadcastEvent.ts b/src/core/domains/broadcast/abstract/BroadcastEvent.ts index cc89ffdbb..93cf66ef7 100644 --- a/src/core/domains/broadcast/abstract/BroadcastEvent.ts +++ b/src/core/domains/broadcast/abstract/BroadcastEvent.ts @@ -18,7 +18,7 @@ abstract class BroadcastEvent implements IBroadcastEvent { * * @returns The name of the event. */ - abstract getEventName(): string; + abstract getName(): string; /** * Returns the payload of the event. diff --git a/src/core/domains/broadcast/abstract/Broadcaster.ts b/src/core/domains/broadcast/abstract/Broadcaster.ts index e432f6043..1a04035c0 100644 --- a/src/core/domains/broadcast/abstract/Broadcaster.ts +++ b/src/core/domains/broadcast/abstract/Broadcaster.ts @@ -19,7 +19,7 @@ abstract class Broadcaster implements IBroadcaster { * @param args The arguments to pass to the listeners. */ async broadcast(event: IBroadcastEvent): Promise { - const eventName = event.getEventName() + const eventName = event.getName() if(!this.broadcastListeners.has(eventName)) { this.createBroadcastListener(eventName) diff --git a/src/core/domains/broadcast/interfaces/IBroadcastEvent.ts b/src/core/domains/broadcast/interfaces/IBroadcastEvent.ts index 2a165ed13..5f94b3864 100644 --- a/src/core/domains/broadcast/interfaces/IBroadcastEvent.ts +++ b/src/core/domains/broadcast/interfaces/IBroadcastEvent.ts @@ -1,6 +1,6 @@ export interface IBroadcastEvent { - getEventName(): string; + getName(): string; getPayload(): T; } \ No newline at end of file diff --git a/src/core/domains/events-legacy/drivers/SynchronousDriver.ts b/src/core/domains/events-legacy/drivers/SynchronousDriver.ts index 5534b7dd8..45c538c7c 100644 --- a/src/core/domains/events-legacy/drivers/SynchronousDriver.ts +++ b/src/core/domains/events-legacy/drivers/SynchronousDriver.ts @@ -18,7 +18,7 @@ export default class SynchronousDriver implements IEventDriver { const eventName = event.name // Get all the listeners with this eventName - const listenerConstructors = App.container('events').getListenersByEventName(eventName) + const listenerConstructors = App.container('eventsLegacy').getListenersByEventName(eventName) // Process each listener synchronously for (const listenerCtor of listenerConstructors) { diff --git a/src/core/domains/events-legacy/interfaces/IEvent.ts b/src/core/domains/events-legacy/interfaces/IEvent.ts index 5e17d115e..77e837a44 100644 --- a/src/core/domains/events-legacy/interfaces/IEvent.ts +++ b/src/core/domains/events-legacy/interfaces/IEvent.ts @@ -1,4 +1,4 @@ -import { eventSubscribers } from "@src/config/events"; +import { eventSubscribers } from "@src/config/eventsLegacy"; import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; diff --git a/src/core/domains/events-legacy/interfaces/IEventConfig.ts b/src/core/domains/events-legacy/interfaces/IEventConfig.ts index e0e14e571..8e3b88973 100644 --- a/src/core/domains/events-legacy/interfaces/IEventConfig.ts +++ b/src/core/domains/events-legacy/interfaces/IEventConfig.ts @@ -3,7 +3,7 @@ import { EventListenerConstructor } from "@src/core/domains/events-legacy/interf import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; export interface IDriverConfig { - driver: IDriverConstructor + driverCtor: IDriverConstructor options?: DriverOptions } diff --git a/src/core/domains/events-legacy/interfaces/IEventService.ts b/src/core/domains/events-legacy/interfaces/IEventService.ts index ee4015f57..145a34da0 100644 --- a/src/core/domains/events-legacy/interfaces/IEventService.ts +++ b/src/core/domains/events-legacy/interfaces/IEventService.ts @@ -4,14 +4,14 @@ import { IDriverConfig, IEventDrivers, ISubscribers } from "@src/core/domains/ev import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -export interface EventServiceConfig { +export interface EventLegacyServiceConfig { defaultDriver: string; drivers: IEventDrivers; subscribers: ISubscribers; } -export interface IEventService { - config: EventServiceConfig; +export interface IEventLegacyService { + config: EventLegacyServiceConfig; dispatch(event: IEvent): Promise; getListenersByEventName(eventName: string): EventListenerConstructor[]; getDriver(driverName: string): IDriverConfig; diff --git a/src/core/domains/events-legacy/providers/EventProvider.ts b/src/core/domains/events-legacy/providers/EventLegacyProvider.ts similarity index 72% rename from src/core/domains/events-legacy/providers/EventProvider.ts rename to src/core/domains/events-legacy/providers/EventLegacyProvider.ts index 6a80ab6bd..7b54300c5 100644 --- a/src/core/domains/events-legacy/providers/EventProvider.ts +++ b/src/core/domains/events-legacy/providers/EventLegacyProvider.ts @@ -1,14 +1,14 @@ -import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/events"; +import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/eventsLegacy"; import BaseProvider from "@src/core/base/Provider"; import WorkerLegacyCommand from "@src/core/domains/console/commands/WorkerCommand"; -import { EventServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import { EventLegacyServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; import EventService from "@src/core/domains/events-legacy/services/EventService"; import { App } from "@src/core/services/App"; -export default class EventProvider extends BaseProvider { +export default class EventLegacyProvider extends BaseProvider { - protected config: EventServiceConfig = { + protected config: EventLegacyServiceConfig = { defaultDriver: defaultEventDriver, drivers: eventDrivers, subscribers: eventSubscribers @@ -20,7 +20,7 @@ export default class EventProvider extends BaseProvider { /** * Register event service */ - App.setContainer('events', new EventService(this.config)); + App.setContainer('eventsLegacy', new EventService(this.config)); /** * Register system provided commands diff --git a/src/core/domains/events-legacy/services/EventDispatcher.ts b/src/core/domains/events-legacy/services/EventDispatcher.ts index 463739f82..486a9aa96 100644 --- a/src/core/domains/events-legacy/services/EventDispatcher.ts +++ b/src/core/domains/events-legacy/services/EventDispatcher.ts @@ -16,7 +16,7 @@ export default class EventDispatcher extends Singleton implements IEventDispatch App.container('logger').info(`[EventDispatcher:dispatch] Event '${event.name}' with driver '${event.driver}'`) const driverOptions = this.getDriverOptionsFromEvent(event) - const driverCtor = driverOptions.driver + const driverCtor = driverOptions.driverCtor const instance = new driverCtor(); await instance.handle(event, driverOptions.options?.getOptions()); @@ -28,7 +28,7 @@ export default class EventDispatcher extends Singleton implements IEventDispatch * @returns */ protected getDriverOptionsFromEvent(event: IEvent): IDriverConfig { - const driver = App.container('events').config.drivers[event.driver] + const driver = App.container('eventsLegacy').config.drivers[event.driver] if(!driver) { throw new Error('Driver not found \'' + event.driver + '\'') diff --git a/src/core/domains/events-legacy/services/EventService.ts b/src/core/domains/events-legacy/services/EventService.ts index d614857b8..b48eb496f 100644 --- a/src/core/domains/events-legacy/services/EventService.ts +++ b/src/core/domains/events-legacy/services/EventService.ts @@ -4,7 +4,7 @@ import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; import { IDriverConfig } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -import { EventServiceConfig, IEventService } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import { EventLegacyServiceConfig, IEventLegacyService } from "@src/core/domains/events-legacy/interfaces/IEventService"; import EventDispatcher from "@src/core/domains/events-legacy/services/EventDispatcher"; /** @@ -12,18 +12,18 @@ import EventDispatcher from "@src/core/domains/events-legacy/services/EventDispa * * Provides methods for dispatching events and retrieving event listeners. */ -export default class EventService extends Singleton implements IEventService { +export default class EventService extends Singleton implements IEventLegacyService { /** * Config. */ - public config!: EventServiceConfig; + public config!: EventLegacyServiceConfig; /** * Constructor. * @param config Event service config. */ - constructor(config: EventServiceConfig) { + constructor(config: EventLegacyServiceConfig) { super(config); } diff --git a/src/core/domains/events-legacy/services/Worker.ts b/src/core/domains/events-legacy/services/Worker.ts index 3f8adb136..73961f617 100644 --- a/src/core/domains/events-legacy/services/Worker.ts +++ b/src/core/domains/events-legacy/services/Worker.ts @@ -85,7 +85,7 @@ export default class Worker extends Singleton { * @returns */ getOptions(driver: string): QueueDriverOptions { - const eventDriver = App.container('events').getDriver(driver) + const eventDriver = App.container('eventsLegacy').getDriver(driver) if(!eventDriver) { throw new EventDriverException(`Driver '${driver}' not found`) @@ -119,7 +119,7 @@ export default class Worker extends Singleton { const event = new EventSubscriber(eventName as string, this.syncDriver, payload) // Dispatch the event - await App.container('events').dispatch(event) + await App.container('eventsLegacy').dispatch(event) // Delete record as it was a success await model.delete(); diff --git a/src/core/domains/events/base/BaseDriver.ts b/src/core/domains/events/base/BaseDriver.ts new file mode 100644 index 000000000..1ac419e10 --- /dev/null +++ b/src/core/domains/events/base/BaseDriver.ts @@ -0,0 +1,20 @@ +/* eslint-disable no-unused-vars */ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; + +abstract class BaseDriver implements IEventDriver { + + protected eventService!: IEventService; + + constructor(eventService: IEventService) { + this.eventService = eventService + } + + abstract dispatch(event: IBaseEvent): Promise; + + abstract getName(): string; + +} + +export default BaseDriver \ No newline at end of file diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts new file mode 100644 index 000000000..39eaf1ae2 --- /dev/null +++ b/src/core/domains/events/base/BaseEvent.ts @@ -0,0 +1,51 @@ +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +abstract class BaseEvent implements IBaseEvent { + + protected payload: IEventPayload | null = null; + + protected driver!: ICtor; + + /** + * Constructor + * @param payload The payload of the event + * @param driver The class of the event driver + */ + constructor(driver: ICtor = SyncDriver, payload: IEventPayload | null = null) { + this.payload = payload; + this.driver = driver; + } + + // eslint-disable-next-line no-unused-vars + async execute(...args: any[]): Promise { + + /* Nothing to execute*/ + } + + getPayload(): T { + return this.payload as T + } + + /** + * Returns the name of the event. + * + * @returns The name of the event as a string. + */ + getName(): string { + return this.constructor.name + } + + /** + * @returns The event driver constructor. + */ + getDriverCtor(): ICtor { + return this.driver; + } + +} + +export default BaseEvent \ No newline at end of file diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts new file mode 100644 index 000000000..66a100579 --- /dev/null +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -0,0 +1,20 @@ +import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; + +class BaseEventListener extends BaseEvent implements IEventListener { + + constructor() { + super(); + this.notifySubscribers(); + } + + // eslint-disable-next-line no-unused-vars + async dispatch(...arg: any[]): Promise { /* Nothing to dispatch */ } + + protected notifySubscribers() { + + } + +} + +export default BaseEventListener \ No newline at end of file diff --git a/src/core/domains/events/base/BaseEventSubscriber.ts b/src/core/domains/events/base/BaseEventSubscriber.ts new file mode 100644 index 000000000..eb16ecec9 --- /dev/null +++ b/src/core/domains/events/base/BaseEventSubscriber.ts @@ -0,0 +1,9 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import { IEventSubscriber } from "@src/core/domains/events/interfaces/IEventSubscriber"; + +class BaseEventSubscriber extends BaseEvent implements IEventSubscriber { + +} + +export default BaseEventSubscriber \ No newline at end of file diff --git a/src/core/domains/events/base/BaseService.ts b/src/core/domains/events/base/BaseService.ts new file mode 100644 index 000000000..7fa99e8d9 --- /dev/null +++ b/src/core/domains/events/base/BaseService.ts @@ -0,0 +1,10 @@ +import HasRegisterableConcern from "@src/core/concerns/HasRegisterableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import compose from "@src/core/util/compose"; + +const BaseService: ICtor = compose( + class {}, + HasRegisterableConcern, +); + +export default BaseService \ No newline at end of file diff --git a/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts b/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts new file mode 100644 index 000000000..317472d6b --- /dev/null +++ b/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts @@ -0,0 +1,18 @@ +import BroadcastEvent from "@src/core/domains/broadcast/abstract/BroadcastEvent"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; + +class DispatchBroadcastEvent extends BroadcastEvent { + + static readonly eventName: string = "dispatch"; + + constructor(payload: IBaseEvent) { + super(payload); + } + + getName(): string { + return DispatchBroadcastEvent.eventName; + } + +} + +export default DispatchBroadcastEvent \ No newline at end of file diff --git a/src/core/domains/events/concerns/HasListenerConcern.ts b/src/core/domains/events/concerns/HasListenerConcern.ts new file mode 100644 index 000000000..3711303fb --- /dev/null +++ b/src/core/domains/events/concerns/HasListenerConcern.ts @@ -0,0 +1,16 @@ +import { IBroadcaster } from "@src/core/domains/broadcast/interfaces/IBroadcaster" +import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern" +import { ICtor } from "@src/core/interfaces/ICtor" + +const HasListenerConcern = (Base: ICtor) => { + return class HasListener extends Base implements IHasListenerConcern { + + constructor() { + super(); + } + + + } +} + +export default HasListenerConcern \ No newline at end of file diff --git a/src/core/domains/events/drivers/QueableDriver.ts b/src/core/domains/events/drivers/QueableDriver.ts new file mode 100644 index 000000000..b9f46ecb1 --- /dev/null +++ b/src/core/domains/events/drivers/QueableDriver.ts @@ -0,0 +1,16 @@ +import { EVENT_DRIVERS } from "@src/config/events"; +import BaseDriver from "@src/core/domains/events/base/BaseDriver"; + +class QueueableDriver extends BaseDriver { + + async dispatch(): Promise { + // todo save event to queue + } + + getName(): string { + return EVENT_DRIVERS.QUEABLE; + } + +} + +export default QueueableDriver \ No newline at end of file diff --git a/src/core/domains/events/drivers/SyncDriver.ts b/src/core/domains/events/drivers/SyncDriver.ts new file mode 100644 index 000000000..dfd8c6485 --- /dev/null +++ b/src/core/domains/events/drivers/SyncDriver.ts @@ -0,0 +1,17 @@ +import { EVENT_DRIVERS } from "@src/config/events"; +import BaseDriver from "@src/core/domains/events/base/BaseDriver"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; + +class SyncDriver extends BaseDriver { + + async dispatch(event: IBaseEvent): Promise { + await event.execute(); + } + + getName(): string { + return EVENT_DRIVERS.SYNC; + } + +} + +export default SyncDriver \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts new file mode 100644 index 000000000..d1c29f1f8 --- /dev/null +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -0,0 +1,9 @@ +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IExecutable } from "@src/core/domains/events/interfaces/IExecutable"; +import { INameable } from "@src/core/domains/events/interfaces/INameable"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export interface IBaseEvent extends INameable, IExecutable +{ + getDriverCtor(): ICtor; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IDispatchable.ts b/src/core/domains/events/interfaces/IDispatchable.ts new file mode 100644 index 000000000..6643f0b86 --- /dev/null +++ b/src/core/domains/events/interfaces/IDispatchable.ts @@ -0,0 +1,5 @@ +/* eslint-disable no-unused-vars */ +export interface IDispatchable +{ + dispatch(...arg: any[]): Promise; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventDriver.ts b/src/core/domains/events/interfaces/IEventDriver.ts new file mode 100644 index 000000000..ece284a5b --- /dev/null +++ b/src/core/domains/events/interfaces/IEventDriver.ts @@ -0,0 +1,5 @@ + +import IDispatchable from "@src/core/domains/events-legacy/interfaces/IDispatchable"; +import { INameable } from "@src/core/domains/events/interfaces/INameable"; + +export default interface IEventDriver extends INameable, IDispatchable {} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events/interfaces/IEventListener.ts new file mode 100644 index 000000000..f5d136e3d --- /dev/null +++ b/src/core/domains/events/interfaces/IEventListener.ts @@ -0,0 +1,5 @@ +import { INameable } from "@src/core/domains/events/interfaces/INameable"; + +export interface IEventListener extends INameable { + +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventPayload.ts b/src/core/domains/events/interfaces/IEventPayload.ts new file mode 100644 index 000000000..ae34919d0 --- /dev/null +++ b/src/core/domains/events/interfaces/IEventPayload.ts @@ -0,0 +1,10 @@ +export type TSerializableTypes = number | string | boolean | undefined; + +export interface IEventPayload { + [key: string | number | symbol]: TSerializableTypes +} + +export interface IEventPayloadWithDriver { + driver: string; + payload: IEventPayload +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts new file mode 100644 index 000000000..30e34ce58 --- /dev/null +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -0,0 +1,17 @@ +/* eslint-disable no-unused-vars */ +import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; +import { TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IHasDispatcherConcern } from "@src/core/domains/events/interfaces/IHasDispatcherConcern"; +import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern"; +import { IHasRegisterableConcern } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern +{ + registerDriver(driverIdentifierConstant: string, driverConfig: IEventDriversConfigOption): void; + + registerListener(listenerConfig: TListenersConfigOption): void; + + getDefaultDriverCtor(): ICtor; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventSubscriber.ts b/src/core/domains/events/interfaces/IEventSubscriber.ts new file mode 100644 index 000000000..ba8f96778 --- /dev/null +++ b/src/core/domains/events/interfaces/IEventSubscriber.ts @@ -0,0 +1 @@ +export interface IEventSubscriber {} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IExecutable.ts b/src/core/domains/events/interfaces/IExecutable.ts new file mode 100644 index 000000000..74c5fb8a5 --- /dev/null +++ b/src/core/domains/events/interfaces/IExecutable.ts @@ -0,0 +1,5 @@ +/* eslint-disable no-unused-vars */ +export interface IExecutable +{ + execute(...args: any[]): Promise; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IHasDispatcherConcern.ts b/src/core/domains/events/interfaces/IHasDispatcherConcern.ts new file mode 100644 index 000000000..2fb4e9430 --- /dev/null +++ b/src/core/domains/events/interfaces/IHasDispatcherConcern.ts @@ -0,0 +1,7 @@ +/* eslint-disable no-unused-vars */ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; + +export interface IHasDispatcherConcern { + + dispatch: (event: IBaseEvent) => Promise; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IHasListenerConcern.ts b/src/core/domains/events/interfaces/IHasListenerConcern.ts new file mode 100644 index 000000000..942526bbe --- /dev/null +++ b/src/core/domains/events/interfaces/IHasListenerConcern.ts @@ -0,0 +1,3 @@ +export interface IHasListenerConcern { + +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/INameable.ts b/src/core/domains/events/interfaces/INameable.ts new file mode 100644 index 000000000..82fe7f8e5 --- /dev/null +++ b/src/core/domains/events/interfaces/INameable.ts @@ -0,0 +1,3 @@ +export interface INameable { + getName(): string; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IQueableDriverOptions.ts b/src/core/domains/events/interfaces/IQueableDriverOptions.ts new file mode 100644 index 000000000..0b27c13ba --- /dev/null +++ b/src/core/domains/events/interfaces/IQueableDriverOptions.ts @@ -0,0 +1,10 @@ +import { ICtor } from "@src/core/interfaces/ICtor"; +import { IModel } from "@src/core/interfaces/IModel"; + +export interface IQueableDriverOptions { + queueName: string; + retries: number; + failedCollection: string; + runAfterSeconds: number; + workerModelCtor: ICtor; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventConfig.ts b/src/core/domains/events/interfaces/config/IEventConfig.ts new file mode 100644 index 000000000..ca3e76afc --- /dev/null +++ b/src/core/domains/events/interfaces/config/IEventConfig.ts @@ -0,0 +1,10 @@ +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventDriversConfig } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; +import { IEventListenersConfig } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export interface IEventConfig { + defaultDriver: ICtor; + drivers: IEventDriversConfig, + listeners: IEventListenersConfig +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventDriversConfig.ts b/src/core/domains/events/interfaces/config/IEventDriversConfig.ts new file mode 100644 index 000000000..96ace18c8 --- /dev/null +++ b/src/core/domains/events/interfaces/config/IEventDriversConfig.ts @@ -0,0 +1,12 @@ +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export interface IEventDriversConfigOption { + driverCtor: ICtor, + options?: Record; +} + +export interface IEventDriversConfig +{ + [key: string]: IEventDriversConfigOption +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventListenersConfig.ts b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts new file mode 100644 index 000000000..5e1ab248e --- /dev/null +++ b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts @@ -0,0 +1,10 @@ +import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; +import { IEventSubscriber } from "@src/core/domains/events/interfaces/IEventSubscriber"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export type TListenersConfigOption = { + listener: ICtor; + subscribers: ICtor[] +} + +export type IEventListenersConfig = TListenersConfigOption[] \ No newline at end of file diff --git a/src/core/domains/events/providers/EventProvider.ts b/src/core/domains/events/providers/EventProvider.ts new file mode 100644 index 000000000..2e902f2c6 --- /dev/null +++ b/src/core/domains/events/providers/EventProvider.ts @@ -0,0 +1,51 @@ +import { eventConfig } from "@src/config/events"; +import BaseProvider from "@src/core/base/Provider"; +import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import EventService from "@src/core/domains/events/services/EventService"; +import { App } from "@src/core/services/App"; + +class EventProvider extends BaseProvider { + + protected config: IEventConfig = eventConfig; + + async register(): Promise { + + const eventService = new EventService(this.config); + this.registerDrivers(eventService); + this.registerListeners(eventService); + + App.setContainer('events', eventService); + } + + async boot(): Promise {} + + /** + * Registers all event drivers defined in the configuration with the provided event service. + * Iterates over the event driver configuration and registers each driver + * using its identifier constant and configuration. + * + * @param eventService The event service to register drivers with. + */ + private registerDrivers(eventService: IEventService) { + for(const driverIdentifierConstant of Object.keys(this.config.drivers)) { + eventService.registerDriver(driverIdentifierConstant, this.config.drivers[driverIdentifierConstant]); + } + } + + /** + * Registers all event listeners defined in the configuration with the provided event service. + * Iterates over the event listeners configuration and registers each listener + * using its identifier constant and configuration. + * + * @param eventService The event service to register listeners with. + */ + private registerListeners(eventService: IEventService) { + for(const listenerConfig of this.config.listeners) { + eventService.registerListener(listenerConfig) + } + } + +} + +export default EventProvider \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts new file mode 100644 index 000000000..247dd7605 --- /dev/null +++ b/src/core/domains/events/services/EventService.ts @@ -0,0 +1,109 @@ +/* eslint-disable no-unused-vars */ +import BaseService from "@src/core/domains/events/base/BaseService"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; +import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; +import { IEventListenersConfig, TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; + +class EventService extends BaseService implements IEventService { + + static readonly REGISTERED_DRIVERS = "registeredDrivers"; + + static readonly REGISTERED_LISTENERS = "registeredListeners"; + + protected config!: IEventConfig; + + constructor(config: IEventConfig) { + super(config) + } + + /** + * Create an event driver config. + * @param driverCtor The event driver class. + * @param options The event driver options. + * @returns The event driver config. + */ + public static createConfig(driverCtor: ICtor, options?: T): IEventDriversConfigOption { + return { + driverCtor, + options + } + } + + /** + * Declare HasRegisterableConcern methods. + */ + declare register: (key: string, value: unknown) => void; + + declare registerByList: (listName: string, key: string, value: unknown) => void; + + declare setRegisteredByList: (listName: string, registered: Map) => void; + + declare getRegisteredByList: (listName: string) => Map; + + declare getRegisteredList: () => TRegisterMap; + + declare getRegisteredObject: () => IRegsiterList; + + /** + * Create an event listeners config. + * @param config The event listeners config. + * @returns The event listeners config. + */ + public static createListeners(config: IEventListenersConfig): IEventListenersConfig { + return config + } + + /** + * Dispatch an event using its registered driver. + * @param event The event to be dispatched. + */ + async dispatch(event: IBaseEvent): Promise { + const eventDriverCtor = event.getDriverCtor() + const eventDriver = new eventDriverCtor(this) + await eventDriver.dispatch(event) + } + + /** + * Register a driver with the event service + * @param driverIdentifierConstant a constant string to identify the driver + * @param driverConfig the driver configuration + */ + registerDriver(driverIdentifierConstant: string, driverConfig: IEventDriversConfigOption): void { + this.registerByList( + EventService.REGISTERED_DRIVERS, + driverIdentifierConstant, + driverConfig + ) + } + + /** + * Register a listener with the event service + * @param listenerIdentifierConstant a constant string to identify the listener + * @param listenerConfig the listener configuration + */ + registerListener(listenerConfig: TListenersConfigOption): void { + const listenerIdentifier = new listenerConfig.listener().getName() + + this.registerByList( + EventService.REGISTERED_LISTENERS, + listenerIdentifier, + listenerConfig + ) + } + + /** + * Get the default event driver constructor. + * @returns The default event driver constructor. + */ + getDefaultDriverCtor(): ICtor { + return this.config.defaultDriver + } + +} + +export default EventService \ No newline at end of file diff --git a/src/core/events/concerns/HasAttribute/OnAttributeChangeBroadcastEvent.ts b/src/core/events/concerns/HasAttribute/OnAttributeChangeBroadcastEvent.ts index 2c6b8f899..c998c319f 100644 --- a/src/core/events/concerns/HasAttribute/OnAttributeChangeBroadcastEvent.ts +++ b/src/core/events/concerns/HasAttribute/OnAttributeChangeBroadcastEvent.ts @@ -15,7 +15,7 @@ class OnAttributeChangeBroadcastEvent extends BroadcastEvent { super(data); } - getEventName(): string { + getName(): string { return OnAttributeChangeBroadcastEvent.eventName; } diff --git a/src/core/events/concerns/HasAttribute/SetAttributeBroadcastEvent.ts b/src/core/events/concerns/HasAttribute/SetAttributeBroadcastEvent.ts index d3630884e..e440dfcb6 100644 --- a/src/core/events/concerns/HasAttribute/SetAttributeBroadcastEvent.ts +++ b/src/core/events/concerns/HasAttribute/SetAttributeBroadcastEvent.ts @@ -13,7 +13,7 @@ class SetAttributeBroadcastEvent extends BroadcastEvent { super(data); } - getEventName(): string { + getName(): string { return SetAttributeBroadcastEvent.eventName; } diff --git a/src/core/interfaces/ICoreContainers.ts b/src/core/interfaces/ICoreContainers.ts index f3aa5e578..25f0ea864 100644 --- a/src/core/interfaces/ICoreContainers.ts +++ b/src/core/interfaces/ICoreContainers.ts @@ -1,7 +1,8 @@ import { IAuthService } from '@src/core/domains/auth/interfaces/IAuthService'; import ICommandService from '@src/core/domains/console/interfaces/ICommandService'; import { IDatabaseService } from '@src/core/domains/database/interfaces/IDatabaseService'; -import { IEventService } from '@src/core/domains/events-legacy/interfaces/IEventService'; +import { IEventLegacyService } from '@src/core/domains/events-legacy/interfaces/IEventService'; +import { IEventService } from '@src/core/domains/events/interfaces/IEventService'; import { IRequestContext } from '@src/core/domains/express/interfaces/ICurrentRequest'; import IExpressService from '@src/core/domains/express/interfaces/IExpressService'; import { ILoggerService } from '@src/core/domains/logger/interfaces/ILoggerService'; @@ -15,6 +16,8 @@ export interface ICoreContainers { * Event Dispatcher Service * Provided by '@src/core/domains/events/providers/EventProvider' */ + eventsLegacy: IEventLegacyService; + events: IEventService; /** diff --git a/src/core/interfaces/concerns/IHasRegisterableConcern.ts b/src/core/interfaces/concerns/IHasRegisterableConcern.ts new file mode 100644 index 000000000..723bbe63c --- /dev/null +++ b/src/core/interfaces/concerns/IHasRegisterableConcern.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-unused-vars */ +export type TRegisterMap = Map; + +export interface IRegsiterList { + [key: string]: TRegisterMap +}; + +export interface IHasRegisterableConcern +{ + register(key: string, value: unknown): void; + + registerByList(listName: string, key: string, value: unknown): void; + + setRegisteredByList(listName: string, registered: Map): void; + + getRegisteredByList(listName: string): Map; + + getRegisteredList(): TRegisterMap; + + getRegisteredObject(): IRegsiterList; +} \ No newline at end of file diff --git a/src/core/providers/CoreProviders.ts b/src/core/providers/CoreProviders.ts index 56439cfbb..269636de3 100644 --- a/src/core/providers/CoreProviders.ts +++ b/src/core/providers/CoreProviders.ts @@ -1,7 +1,7 @@ import AuthProvider from "@src/core/domains/auth/providers/AuthProvider"; import ConsoleProvider from "@src/core/domains/console/providers/ConsoleProvider"; import DatabaseProvider from "@src/core/domains/database/providers/DatabaseProvider"; -import EventProvider from "@src/core/domains/events-legacy/providers/EventProvider"; +import EventProvider from "@src/core/domains/events/providers/EventProvider"; import ExpressProvider from "@src/core/domains/express/providers/ExpressProvider"; import LoggerProvider from "@src/core/domains/logger/providers/LoggerProvider"; import MakeProvider from "@src/core/domains/make/providers/MakeProvider"; diff --git a/src/tests/events/eventQueueLegacy.test.ts b/src/tests/events/eventQueueLegacy.ts similarity index 97% rename from src/tests/events/eventQueueLegacy.test.ts rename to src/tests/events/eventQueueLegacy.ts index 104f3cb0c..1367da680 100644 --- a/src/tests/events/eventQueueLegacy.test.ts +++ b/src/tests/events/eventQueueLegacy.ts @@ -8,7 +8,7 @@ import TestQueueSubscriber from '@src/tests/events/subscribers/TestQueueSubscrib import { TestMovieModel } from '@src/tests/models/models/TestMovie'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; -import TestEventProvider from '@src/tests/providers/TestEventProvider'; +import TestEventProvider from '@src/tests/providers/TestEventLegacyProvider'; import 'dotenv/config'; import { DataTypes } from 'sequelize'; diff --git a/src/tests/events/eventSync.testLegacy.ts b/src/tests/events/eventSync.test.ts similarity index 78% rename from src/tests/events/eventSync.testLegacy.ts rename to src/tests/events/eventSync.test.ts index 9acca1ad2..d8d423a47 100644 --- a/src/tests/events/eventSync.testLegacy.ts +++ b/src/tests/events/eventSync.test.ts @@ -3,7 +3,7 @@ import { describe } from '@jest/globals'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; -import TestSubscriber from '@src/tests/events/subscribers/TestSyncSubscriberLegacy'; +import TestEventSyncEvent from '@src/tests/events/events/TestEventSyncEvent'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; @@ -27,7 +27,9 @@ describe('mock event service', () => { * Dispatch a synchronus event */ test('test dispatch event sync', () => { - App.container('events').dispatch(new TestSubscriber({ hello: 'world' })); - expect(true).toBeTruthy() + + App.container('events').dispatch(new TestEventSyncEvent({ hello: 'world' })); + + // todo: check something has changed. }) }); \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueEvent.ts b/src/tests/events/events/TestEventQueueEvent.ts new file mode 100644 index 000000000..66cb9bed4 --- /dev/null +++ b/src/tests/events/events/TestEventQueueEvent.ts @@ -0,0 +1,20 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; + +class TestEventQueueEvent extends BaseEvent { + + static readonly eventName = 'TestEventQueueEvent'; + + constructor(payload: IEventPayload) { + super(payload, QueueableDriver) + } + + getName(): string { + return TestEventQueueEvent.eventName; + } + +} + +export default TestEventQueueEvent \ No newline at end of file diff --git a/src/tests/events/events/TestEventSyncEvent.ts b/src/tests/events/events/TestEventSyncEvent.ts new file mode 100644 index 000000000..589b2e133 --- /dev/null +++ b/src/tests/events/events/TestEventSyncEvent.ts @@ -0,0 +1,21 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; + + +class TestEventSyncEvent extends BaseEvent { + + static readonly eventName = 'TestEventSyncEvent'; + + constructor(payload: IEventPayload) { + super(SyncDriver, payload); + } + + async execute(): Promise { + console.log('Executed TestEventSyncEvent', this.getPayload(), this.getName()) + } + +} + +export default TestEventSyncEvent \ No newline at end of file diff --git a/src/tests/events/listeners/TestListener.ts b/src/tests/events/listeners/TestListener.ts new file mode 100644 index 000000000..a45d45885 --- /dev/null +++ b/src/tests/events/listeners/TestListener.ts @@ -0,0 +1,7 @@ +import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; + +class TestListener extends BaseEventListener { + +} + +export default TestListener \ No newline at end of file diff --git a/src/tests/events/listeners/TestListenerLegacy.ts b/src/tests/events/listeners/TestListenerLegacy.ts deleted file mode 100644 index 5d9a91fb7..000000000 --- a/src/tests/events/listeners/TestListenerLegacy.ts +++ /dev/null @@ -1,10 +0,0 @@ -import EventListener from "@src/core/domains/events-legacy/services/EventListener"; -import { App } from "@src/core/services/App"; - -export class TestListener extends EventListener { - - handle = async (payload: any) => { - App.container('logger').info('[TestListener]', payload) - } - -} \ No newline at end of file diff --git a/src/tests/events/listeners/TestQueueListenerLegacy.ts b/src/tests/events/listeners/TestQueueListenerLegacy.ts deleted file mode 100644 index e8fc40409..000000000 --- a/src/tests/events/listeners/TestQueueListenerLegacy.ts +++ /dev/null @@ -1,16 +0,0 @@ -import EventListener from "@src/core/domains/events-legacy/services/EventListener"; -import { App } from "@src/core/services/App"; -import { TestMovieModel } from "@src/tests/models/models/TestMovie"; - -export class TestQueueListener extends EventListener<{name: string}> { - - handle = async (payload: {name: string}) => { - App.container('logger').info('[TestQueueListener]', { name: payload }) - - const movie = new TestMovieModel({ - name: payload.name - }); - await movie.save(); - } - -} \ No newline at end of file diff --git a/src/tests/events/subscribers/TestQueueSubscriberLegacy.ts b/src/tests/events/subscribers/TestQueueSubscriberLegacy.ts deleted file mode 100644 index dad050760..000000000 --- a/src/tests/events/subscribers/TestQueueSubscriberLegacy.ts +++ /dev/null @@ -1,12 +0,0 @@ -import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; - -export default class TestQueueSubscriber extends EventSubscriber { - - constructor(payload: any) { - const eventName = 'TestQueueEvent' - const driver = 'testing'; - - super(eventName, driver, payload) - } - -} \ No newline at end of file diff --git a/src/tests/events/subscribers/TestSubscriber.ts b/src/tests/events/subscribers/TestSubscriber.ts new file mode 100644 index 000000000..d6c060fb7 --- /dev/null +++ b/src/tests/events/subscribers/TestSubscriber.ts @@ -0,0 +1,9 @@ +import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubscriber"; + + +class TestSubscriber extends BaseEventSubscriber { + + +} + +export default TestSubscriber \ No newline at end of file diff --git a/src/tests/events/subscribers/TestSyncSubscriberLegacy.ts b/src/tests/events/subscribers/TestSyncSubscriberLegacy.ts deleted file mode 100644 index 099048158..000000000 --- a/src/tests/events/subscribers/TestSyncSubscriberLegacy.ts +++ /dev/null @@ -1,12 +0,0 @@ -import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; - -export default class TestSubscriber extends EventSubscriber { - - constructor(payload: any) { - const eventName = 'TestEvent' - const driver = 'sync'; - - super(eventName, driver, payload) - } - -} \ No newline at end of file diff --git a/src/tests/providers/TestEventLegacyProvider.ts b/src/tests/providers/TestEventLegacyProvider.ts new file mode 100644 index 000000000..eac2b1142 --- /dev/null +++ b/src/tests/providers/TestEventLegacyProvider.ts @@ -0,0 +1,42 @@ +import QueueDriver, { QueueDriverOptions } from '@src/core/domains/events-legacy/drivers/QueueDriver'; +import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; +import { EventLegacyServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; +import EventLegacyProvider from "@src/core/domains/events-legacy/providers/EventLegacyProvider"; +import { default as DriverOptions } from '@src/core/domains/events-legacy/services/QueueDriverOptions'; +import { TestListener } from "@src/tests/events/listeners/TestListenerLegacy"; +import { TestQueueListenerLegacy } from "@src/tests/events/listeners/TestQueueListenerLegacy"; +import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; + +class TestEventProvider extends EventLegacyProvider { + + protected config: EventLegacyServiceConfig = { + defaultDriver: 'sync', + drivers: { + testing: { + driverCtor: QueueDriver, + options: new DriverOptions({ + queueName: 'testQueue', + retries: 3, + failedCollection: 'testFailedWorkers', + runAfterSeconds: 0, + workerModelCtor: TestWorkerModel, + runOnce: true + }) + }, + sync: { + driverCtor: SynchronousDriver + } + }, + subscribers: { + 'TestQueueEvent': [ + TestQueueListenerLegacy + ], + 'TestEvent': [ + TestListener + ] + } + } + +} + +export default TestEventProvider \ No newline at end of file diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 76ad716fb..78069fcfd 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -1,40 +1,39 @@ -import QueueDriver, { QueueDriverOptions } from '@src/core/domains/events-legacy/drivers/QueueDriver'; -import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; -import { EventServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; -import EventProvider from "@src/core/domains/events-legacy/providers/EventProvider"; -import { default as DriverOptions } from '@src/core/domains/events-legacy/services/QueueDriverOptions'; -import { TestListener } from "@src/tests/events/listeners/TestListenerLegacy"; -import { TestQueueListener } from "@src/tests/events/listeners/TestQueueListenerLegacy"; +import { EVENT_DRIVERS } from '@src/config/events'; +import QueueableDriver from '@src/core/domains/events/drivers/QueableDriver'; +import SyncDriver from '@src/core/domains/events/drivers/SyncDriver'; +import { IEventConfig } from '@src/core/domains/events/interfaces/config/IEventConfig'; +import EventProvider from '@src/core/domains/events/providers/EventProvider'; +import EventService from '@src/core/domains/events/services/EventService'; +import TestListener from '@src/tests/events/listeners/TestListener'; +import TestSubscriber from '@src/tests/events/subscribers/TestSubscriber'; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; class TestEventProvider extends EventProvider { - protected config: EventServiceConfig = { - defaultDriver: 'sync', + protected config: IEventConfig = { + + defaultDriver: SyncDriver, + drivers: { - testing: { - driver: QueueDriver, - options: new DriverOptions({ - queueName: 'testQueue', - retries: 3, - failedCollection: 'testFailedWorkers', - runAfterSeconds: 0, - workerModelCtor: TestWorkerModel, - runOnce: true - }) - }, - sync: { - driver: SynchronousDriver - } + [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), + [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + queueName: 'testQueue', + retries: 3, + failedCollection: 'testFailedWorkers', + runAfterSeconds: 0, + workerModelCtor: TestWorkerModel, + runOnce: true + }) }, - subscribers: { - 'TestQueueEvent': [ - TestQueueListener - ], - 'TestEvent': [ - TestListener - ] - } + + listeners: EventService.createListeners([ + { + listener: TestListener, + subscribers: [ + TestSubscriber + ] + } + ]) } } From b7c19c97ba0b4edc7694ccec01be74661943fd09 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Sat, 2 Nov 2024 20:17:43 +0000 Subject: [PATCH 03/31] refactor(events): event progress, generic base event, listener notifies subscribers --- src/core/concerns/HasRegisterableConcern.ts | 8 ++-- src/core/domains/events/base/BaseEvent.ts | 41 ++++++++++++++++--- .../domains/events/base/BaseEventListener.ts | 10 ++++- .../events/base/BaseEventSubscriber.ts | 9 ---- .../domains/events/interfaces/IBaseEvent.ts | 4 ++ .../events/interfaces/IEventListener.ts | 4 +- .../events/interfaces/IEventSubscriber.ts | 1 - .../config/IEventListenersConfig.ts | 7 +++- .../domains/events/services/EventService.ts | 6 +-- .../concerns/IHasRegisterableConcern.ts | 6 +-- src/tests/events/listeners/TestListener.ts | 4 ++ .../events/subscribers/TestSubscriber.ts | 11 ++++- 12 files changed, 78 insertions(+), 33 deletions(-) delete mode 100644 src/core/domains/events/base/BaseEventSubscriber.ts delete mode 100644 src/core/domains/events/interfaces/IEventSubscriber.ts diff --git a/src/core/concerns/HasRegisterableConcern.ts b/src/core/concerns/HasRegisterableConcern.ts index 6d33be590..56d04c2cd 100644 --- a/src/core/concerns/HasRegisterableConcern.ts +++ b/src/core/concerns/HasRegisterableConcern.ts @@ -34,12 +34,12 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { return this.registerObject; } - getRegisteredList(): TRegisterMap { - return this.getRegisteredByList(HasRegisterable.defaultList); + getRegisteredList(): T { + return this.getRegisteredByList(HasRegisterable.defaultList) as T; } - getRegisteredByList(listName: string): TRegisterMap { - return this.registerObject[listName] ?? new Map(); + getRegisteredByList(listName: string): T { + return this.registerObject[listName] as T ?? new Map(); } } diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 39eaf1ae2..8d4391b07 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -3,6 +3,11 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { App } from "@src/core/services/App"; + +import { TListenersMap } from "../interfaces/config/IEventListenersConfig"; +import { IEventService } from "../interfaces/IEventService"; +import EventService from "../services/EventService"; abstract class BaseEvent implements IBaseEvent { @@ -19,20 +24,27 @@ abstract class BaseEvent implements IBaseEvent { this.payload = payload; this.driver = driver; } - - // eslint-disable-next-line no-unused-vars - async execute(...args: any[]): Promise { - /* Nothing to execute*/ + /** + * Gets the event service that handles event dispatching and listener registration. + * @returns The event service. + */ + getEventService(): IEventService { + return App.container('events'); } + + // eslint-disable-next-line no-unused-vars + async execute(...args: any[]): Promise {/* Nothing to execute */} + /** + * @template T The type of the payload to return. + * @returns The payload of the event. + */ getPayload(): T { return this.payload as T } /** - * Returns the name of the event. - * * @returns The name of the event as a string. */ getName(): string { @@ -46,6 +58,23 @@ abstract class BaseEvent implements IBaseEvent { return this.driver; } + /** + * Returns an array of event subscriber constructors that are listening to this event. + * @returns An array of event subscriber constructors. + */ + getSubscribers(): ICtor[] { + const eventService = this.getEventService(); + const registeredListeners = eventService.getRegisteredByList(EventService.REGISTERED_LISTENERS); + + const listenerConfig = registeredListeners.get(this.getName())?.[0]; + + if(!listenerConfig) { + return []; + } + + return listenerConfig.subscribers; + } + } export default BaseEvent \ No newline at end of file diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts index 66a100579..8e9e8457d 100644 --- a/src/core/domains/events/base/BaseEventListener.ts +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -1,5 +1,5 @@ -import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; class BaseEventListener extends BaseEvent implements IEventListener { @@ -8,11 +8,17 @@ class BaseEventListener extends BaseEvent implements IEventListener { this.notifySubscribers(); } + // eslint-disable-next-line no-unused-vars async dispatch(...arg: any[]): Promise { /* Nothing to dispatch */ } protected notifySubscribers() { - + const eventService = this.getEventService(); + const subscribers = this.getSubscribers(); + + for (const subscriber of subscribers) { + eventService.dispatch(new subscriber); + } } } diff --git a/src/core/domains/events/base/BaseEventSubscriber.ts b/src/core/domains/events/base/BaseEventSubscriber.ts deleted file mode 100644 index eb16ecec9..000000000 --- a/src/core/domains/events/base/BaseEventSubscriber.ts +++ /dev/null @@ -1,9 +0,0 @@ - -import BaseEvent from "@src/core/domains/events/base/BaseEvent"; -import { IEventSubscriber } from "@src/core/domains/events/interfaces/IEventSubscriber"; - -class BaseEventSubscriber extends BaseEvent implements IEventSubscriber { - -} - -export default BaseEventSubscriber \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index d1c29f1f8..6e3a155fb 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -3,7 +3,11 @@ import { IExecutable } from "@src/core/domains/events/interfaces/IExecutable"; import { INameable } from "@src/core/domains/events/interfaces/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { IEventService } from "./IEventService"; + export interface IBaseEvent extends INameable, IExecutable { + getEventService(): IEventService; getDriverCtor(): ICtor; + getPayload(): T; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events/interfaces/IEventListener.ts index f5d136e3d..19c1c4f25 100644 --- a/src/core/domains/events/interfaces/IEventListener.ts +++ b/src/core/domains/events/interfaces/IEventListener.ts @@ -1,5 +1,7 @@ import { INameable } from "@src/core/domains/events/interfaces/INameable"; -export interface IEventListener extends INameable { +import { IBaseEvent } from "./IBaseEvent"; + +export interface IEventListener extends INameable, IBaseEvent { } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventSubscriber.ts b/src/core/domains/events/interfaces/IEventSubscriber.ts deleted file mode 100644 index ba8f96778..000000000 --- a/src/core/domains/events/interfaces/IEventSubscriber.ts +++ /dev/null @@ -1 +0,0 @@ -export interface IEventSubscriber {} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventListenersConfig.ts b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts index 5e1ab248e..ce9bbd927 100644 --- a/src/core/domains/events/interfaces/config/IEventListenersConfig.ts +++ b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts @@ -1,10 +1,13 @@ import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; -import { IEventSubscriber } from "@src/core/domains/events/interfaces/IEventSubscriber"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { IBaseEvent } from "../IBaseEvent"; + export type TListenersConfigOption = { listener: ICtor; - subscribers: ICtor[] + subscribers: ICtor[] } +export type TListenersMap = Map + export type IEventListenersConfig = TListenersConfigOption[] \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 247dd7605..90e46c9f4 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -43,9 +43,9 @@ class EventService extends BaseService implements IEventService { declare setRegisteredByList: (listName: string, registered: Map) => void; - declare getRegisteredByList: (listName: string) => Map; + declare getRegisteredByList: (listName: string) => T; - declare getRegisteredList: () => TRegisterMap; + declare getRegisteredList: () => T; declare getRegisteredObject: () => IRegsiterList; @@ -87,7 +87,7 @@ class EventService extends BaseService implements IEventService { * @param listenerConfig the listener configuration */ registerListener(listenerConfig: TListenersConfigOption): void { - const listenerIdentifier = new listenerConfig.listener().getName() + const listenerIdentifier = listenerConfig.listener.name this.registerByList( EventService.REGISTERED_LISTENERS, diff --git a/src/core/interfaces/concerns/IHasRegisterableConcern.ts b/src/core/interfaces/concerns/IHasRegisterableConcern.ts index 723bbe63c..5c587c9b6 100644 --- a/src/core/interfaces/concerns/IHasRegisterableConcern.ts +++ b/src/core/interfaces/concerns/IHasRegisterableConcern.ts @@ -11,11 +11,11 @@ export interface IHasRegisterableConcern registerByList(listName: string, key: string, value: unknown): void; - setRegisteredByList(listName: string, registered: Map): void; + setRegisteredByList(listName: string, registered: TRegisterMap): void; - getRegisteredByList(listName: string): Map; + getRegisteredByList(listName: string): T; - getRegisteredList(): TRegisterMap; + getRegisteredList(): T; getRegisteredObject(): IRegsiterList; } \ No newline at end of file diff --git a/src/tests/events/listeners/TestListener.ts b/src/tests/events/listeners/TestListener.ts index a45d45885..cfbc96a10 100644 --- a/src/tests/events/listeners/TestListener.ts +++ b/src/tests/events/listeners/TestListener.ts @@ -2,6 +2,10 @@ import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; class TestListener extends BaseEventListener { + async execute(...args: any[]): Promise { + console.log('Executed TestListener', this.getPayload(), this.getName()); + } + } export default TestListener \ No newline at end of file diff --git a/src/tests/events/subscribers/TestSubscriber.ts b/src/tests/events/subscribers/TestSubscriber.ts index d6c060fb7..ed6193c26 100644 --- a/src/tests/events/subscribers/TestSubscriber.ts +++ b/src/tests/events/subscribers/TestSubscriber.ts @@ -1,8 +1,15 @@ -import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubscriber"; +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import TestEventSyncEvent from "../events/TestEventSyncEvent"; -class TestSubscriber extends BaseEventSubscriber { +class TestSubscriber extends BaseEvent { + + async execute(...args: any[]): Promise { + console.log('Executed TestSubscriber', this.getPayload(), this.getName()) + + this.getEventService().dispatch(new TestEventSyncEvent(this.getPayload())); + } } From 4c0ef45f0c2db98c9dccf96b10e1d128ab2059f6 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Sat, 2 Nov 2024 21:48:23 +0000 Subject: [PATCH 04/31] refactor(events): Working test and mock assertion --- src/core/domains/events/base/BaseEvent.ts | 32 ++------- .../domains/events/base/BaseEventListener.ts | 34 ++++++--- src/core/domains/events/base/BaseService.ts | 2 + .../events/concerns/EventMockableConcern.ts | 71 +++++++++++++++++++ .../events/concerns/HasListenerConcern.ts | 16 ----- .../events/exceptions/MockException.ts | 8 +++ .../domains/events/interfaces/IBaseEvent.ts | 4 +- .../events/interfaces/IEventListener.ts | 3 +- .../events/interfaces/IEventService.ts | 6 +- .../events/interfaces/IMockableConcern.ts | 13 ++++ .../config/IEventListenersConfig.ts | 3 +- .../domains/events/services/EventService.ts | 46 ++++++++++-- src/tests/events/eventSync.test.ts | 14 +++- src/tests/events/events/TestEventSyncEvent.ts | 3 +- src/tests/events/listeners/TestListener.ts | 6 ++ .../events/subscribers/TestSubscriber.ts | 5 +- 16 files changed, 197 insertions(+), 69 deletions(-) create mode 100644 src/core/domains/events/concerns/EventMockableConcern.ts delete mode 100644 src/core/domains/events/concerns/HasListenerConcern.ts create mode 100644 src/core/domains/events/exceptions/MockException.ts create mode 100644 src/core/domains/events/interfaces/IMockableConcern.ts diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 8d4391b07..560bfb663 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -1,28 +1,27 @@ -import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; -import { TListenersMap } from "../interfaces/config/IEventListenersConfig"; -import { IEventService } from "../interfaces/IEventService"; -import EventService from "../services/EventService"; - abstract class BaseEvent implements IBaseEvent { protected payload: IEventPayload | null = null; protected driver!: ICtor; + protected defaultDriver!: ICtor; + /** * Constructor * @param payload The payload of the event * @param driver The class of the event driver */ - constructor(driver: ICtor = SyncDriver, payload: IEventPayload | null = null) { + constructor(payload: IEventPayload | null = null, driver?: ICtor) { this.payload = payload; - this.driver = driver; + this.defaultDriver = App.container('events').getDefaultDriverCtor(); + this.driver = driver ?? this.defaultDriver; } /** @@ -55,24 +54,7 @@ abstract class BaseEvent implements IBaseEvent { * @returns The event driver constructor. */ getDriverCtor(): ICtor { - return this.driver; - } - - /** - * Returns an array of event subscriber constructors that are listening to this event. - * @returns An array of event subscriber constructors. - */ - getSubscribers(): ICtor[] { - const eventService = this.getEventService(); - const registeredListeners = eventService.getRegisteredByList(EventService.REGISTERED_LISTENERS); - - const listenerConfig = registeredListeners.get(this.getName())?.[0]; - - if(!listenerConfig) { - return []; - } - - return listenerConfig.subscribers; + return this.driver ?? this.defaultDriver; } } diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts index 8e9e8457d..acea1f60f 100644 --- a/src/core/domains/events/base/BaseEventListener.ts +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -1,23 +1,41 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; class BaseEventListener extends BaseEvent implements IEventListener { - constructor() { - super(); + /** + * Constructor + * + * Creates a new instance of the event listener and dispatches the event to + * all subscribers. + * + * @param payload The payload of the event to dispatch + */ + constructor(payload?: IEventPayload, driver?: ICtor) { + super(payload, driver); this.notifySubscribers(); } - - // eslint-disable-next-line no-unused-vars - async dispatch(...arg: any[]): Promise { /* Nothing to dispatch */ } - + /** + * Notifies all subscribers of this event that the event has been dispatched. + * + * Retrieves all subscribers of this event from the event service, creates + * a new instance of each subscriber, passing the payload of this event to + * the subscriber's constructor, and then dispatches the subscriber event + * using the event service. + */ protected notifySubscribers() { const eventService = this.getEventService(); - const subscribers = this.getSubscribers(); + const subscribers = eventService.getSubscribers(this.getName()); for (const subscriber of subscribers) { - eventService.dispatch(new subscriber); + const subscriberEvent = new subscriber(this.getPayload()); + + eventService.dispatch(subscriberEvent); } } diff --git a/src/core/domains/events/base/BaseService.ts b/src/core/domains/events/base/BaseService.ts index 7fa99e8d9..4d36c415b 100644 --- a/src/core/domains/events/base/BaseService.ts +++ b/src/core/domains/events/base/BaseService.ts @@ -1,10 +1,12 @@ import HasRegisterableConcern from "@src/core/concerns/HasRegisterableConcern"; +import EventMockableConcern from "@src/core/domains/events/concerns/EventMockableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import compose from "@src/core/util/compose"; const BaseService: ICtor = compose( class {}, HasRegisterableConcern, + EventMockableConcern, ); export default BaseService \ No newline at end of file diff --git a/src/core/domains/events/concerns/EventMockableConcern.ts b/src/core/domains/events/concerns/EventMockableConcern.ts new file mode 100644 index 000000000..6b6da491c --- /dev/null +++ b/src/core/domains/events/concerns/EventMockableConcern.ts @@ -0,0 +1,71 @@ +import MockException from "@src/core/domains/events/exceptions/MockException"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import { IMockableConcern, TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +const EventMockableConcern = (Base: ICtor) => { + return class EventMockable extends Base implements IMockableConcern { + + /** Array of events to mock */ + mockEvents: ICtor[] = []; + + /** Array of events that have been dispatched */ + mockEventsDispatched: IBaseEvent[] = []; + + /** + * Mocks an event to be dispatched. + * + * The mocked event will be added to the {@link mockEvents} array. + * When the event is dispatched, the {@link mockEventDispatched} method + * will be called and the event will be added to the {@link mockEventsDispatched} array. + * + * @param event The event to mock. + */ + mockEvent(event: ICtor): void { + this.mockEvents.push(event) + } + + /** + * This method is called when an event is dispatched. It will check if the event + * has been mocked with the {@link mockEvent} method. If it has, the event will be + * added to the {@link mockEventsDispatched} array. + * + * @param event - The event that was dispatched. + */ + mockEventDispatched(event: IBaseEvent): void { + + const shouldMock = this.mockEvents.find(eCtor => (new eCtor(null)).getName() === event.getName()) + + if(!shouldMock) { + return + } + + this.mockEventsDispatched.push(event) + } + + + /** + * Asserts that a specific event has been dispatched and that its payload satisfies the given condition. + * + * @param eventCtor - The event to check for dispatch. + * @param callback - A function that takes the event payload and returns a boolean indicating + * whether the payload satisfies the condition. + * + * @throws Will throw an error if the event was not dispatched or if the dispatched event's + * payload does not satisfy the given condition. + */ + assertDispatched(eventCtor: ICtor, callback: TMockableEventCallback): boolean { + const eventCtorName = (new eventCtor(null)).getName() + const dispatchedEvent = this.mockEventsDispatched.find(e => e.getName() === eventCtorName) + + if(!dispatchedEvent) { + throw new MockException(`Event ${eventCtorName} was not dispatched`) + } + + return callback(dispatchedEvent.getPayload()) + } + + } +} + +export default EventMockableConcern \ No newline at end of file diff --git a/src/core/domains/events/concerns/HasListenerConcern.ts b/src/core/domains/events/concerns/HasListenerConcern.ts deleted file mode 100644 index 3711303fb..000000000 --- a/src/core/domains/events/concerns/HasListenerConcern.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IBroadcaster } from "@src/core/domains/broadcast/interfaces/IBroadcaster" -import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern" -import { ICtor } from "@src/core/interfaces/ICtor" - -const HasListenerConcern = (Base: ICtor) => { - return class HasListener extends Base implements IHasListenerConcern { - - constructor() { - super(); - } - - - } -} - -export default HasListenerConcern \ No newline at end of file diff --git a/src/core/domains/events/exceptions/MockException.ts b/src/core/domains/events/exceptions/MockException.ts new file mode 100644 index 000000000..ed63dcca0 --- /dev/null +++ b/src/core/domains/events/exceptions/MockException.ts @@ -0,0 +1,8 @@ +export default class MockException extends Error { + + constructor(message: string = 'Mock Exception') { + super(message); + this.name = 'MockException'; + } + +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index 6e3a155fb..930ad2219 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -1,10 +1,10 @@ + import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; import { IExecutable } from "@src/core/domains/events/interfaces/IExecutable"; import { INameable } from "@src/core/domains/events/interfaces/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { IEventService } from "./IEventService"; - export interface IBaseEvent extends INameable, IExecutable { getEventService(): IEventService; diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events/interfaces/IEventListener.ts index 19c1c4f25..6dbcd940b 100644 --- a/src/core/domains/events/interfaces/IEventListener.ts +++ b/src/core/domains/events/interfaces/IEventListener.ts @@ -1,7 +1,6 @@ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { INameable } from "@src/core/domains/events/interfaces/INameable"; -import { IBaseEvent } from "./IBaseEvent"; - export interface IEventListener extends INameable, IBaseEvent { } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts index 30e34ce58..a967b7e00 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -1,17 +1,21 @@ /* eslint-disable no-unused-vars */ import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; import { TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IHasDispatcherConcern } from "@src/core/domains/events/interfaces/IHasDispatcherConcern"; import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern"; +import { IMockableConcern } from "@src/core/domains/events/interfaces/IMockableConcern"; import { IHasRegisterableConcern } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern +export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IMockableConcern { registerDriver(driverIdentifierConstant: string, driverConfig: IEventDriversConfigOption): void; registerListener(listenerConfig: TListenersConfigOption): void; getDefaultDriverCtor(): ICtor; + + getSubscribers(eventName: string): ICtor[]; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IMockableConcern.ts b/src/core/domains/events/interfaces/IMockableConcern.ts new file mode 100644 index 000000000..af433aa38 --- /dev/null +++ b/src/core/domains/events/interfaces/IMockableConcern.ts @@ -0,0 +1,13 @@ +/* eslint-disable no-unused-vars */ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import { ICtor } from "@src/core/interfaces/ICtor"; + +export type TMockableEventCallback = (payload: T) => boolean; + +export interface IMockableConcern { + mockEvent(event: ICtor): void; + + mockEventDispatched(event: IBaseEvent): void; + + assertDispatched(eventCtor: ICtor, callback: TMockableEventCallback): boolean +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventListenersConfig.ts b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts index ce9bbd927..497d74da2 100644 --- a/src/core/domains/events/interfaces/config/IEventListenersConfig.ts +++ b/src/core/domains/events/interfaces/config/IEventListenersConfig.ts @@ -1,8 +1,7 @@ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { IBaseEvent } from "../IBaseEvent"; - export type TListenersConfigOption = { listener: ICtor; subscribers: ICtor[] diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 90e46c9f4..7a4f3870c 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -3,9 +3,10 @@ import BaseService from "@src/core/domains/events/base/BaseService"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; -import { IEventListenersConfig, TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; +import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; @@ -13,12 +14,17 @@ class EventService extends BaseService implements IEventService { static readonly REGISTERED_DRIVERS = "registeredDrivers"; - static readonly REGISTERED_LISTENERS = "registeredListeners"; + static readonly REGISTERED_LISTENERS = "registeredListeners"; + + static readonly REGISTERED_MOCK_EVENTS = "mockEvents"; + + static readonly REGISTERED_MOCK_DISPATCHED = "mockDispatched"; protected config!: IEventConfig; constructor(config: IEventConfig) { - super(config) + super() + this.config = config; } /** @@ -41,7 +47,7 @@ class EventService extends BaseService implements IEventService { declare registerByList: (listName: string, key: string, value: unknown) => void; - declare setRegisteredByList: (listName: string, registered: Map) => void; + declare setRegisteredByList: (listName: string, registered: TRegisterMap) => void; declare getRegisteredByList: (listName: string) => T; @@ -49,6 +55,15 @@ class EventService extends BaseService implements IEventService { declare getRegisteredObject: () => IRegsiterList; + /** + * Declare EventMockableConcern methods. + */ + declare mockEvent: (event: ICtor) => void; + + declare mockEventDispatched: (event: IBaseEvent) => void; + + declare assertDispatched: (eventCtor: ICtor, callback: TMockableEventCallback) => boolean + /** * Create an event listeners config. * @param config The event listeners config. @@ -62,12 +77,17 @@ class EventService extends BaseService implements IEventService { * Dispatch an event using its registered driver. * @param event The event to be dispatched. */ - async dispatch(event: IBaseEvent): Promise { + async dispatch(event: IBaseEvent): Promise { + const eventDriverCtor = event.getDriverCtor() const eventDriver = new eventDriverCtor(this) await eventDriver.dispatch(event) + + this.mockEventDispatched(event) } + + /** * Register a driver with the event service * @param driverIdentifierConstant a constant string to identify the driver @@ -104,6 +124,22 @@ class EventService extends BaseService implements IEventService { return this.config.defaultDriver } + /** + * Returns an array of event subscriber constructors that are listening to this event. + * @returns An array of event subscriber constructors. + */ + getSubscribers(eventName: string): ICtor[] { + const registeredListeners = this.getRegisteredByList(EventService.REGISTERED_LISTENERS); + + const listenerConfig = registeredListeners.get(eventName)?.[0]; + + if(!listenerConfig) { + return []; + } + + return listenerConfig.subscribers; + } + } export default EventService \ No newline at end of file diff --git a/src/tests/events/eventSync.test.ts b/src/tests/events/eventSync.test.ts index d8d423a47..feff0d3dd 100644 --- a/src/tests/events/eventSync.test.ts +++ b/src/tests/events/eventSync.test.ts @@ -26,10 +26,18 @@ describe('mock event service', () => { /** * Dispatch a synchronus event */ - test('test dispatch event sync', () => { + test('test dispatch event sync', async () => { + + const eventService = App.container('events'); - App.container('events').dispatch(new TestEventSyncEvent({ hello: 'world' })); + eventService.mockEvent(TestEventSyncEvent) + + await eventService.dispatch(new TestEventSyncEvent({ hello: 'world' })); - // todo: check something has changed. + expect( + eventService.assertDispatched<{ hello: string }>(TestEventSyncEvent, (payload) => { + return payload.hello === 'world' + }) + ).toBeTruthy() }) }); \ No newline at end of file diff --git a/src/tests/events/events/TestEventSyncEvent.ts b/src/tests/events/events/TestEventSyncEvent.ts index 589b2e133..393a2c057 100644 --- a/src/tests/events/events/TestEventSyncEvent.ts +++ b/src/tests/events/events/TestEventSyncEvent.ts @@ -1,6 +1,5 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; -import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; @@ -9,7 +8,7 @@ class TestEventSyncEvent extends BaseEvent { static readonly eventName = 'TestEventSyncEvent'; constructor(payload: IEventPayload) { - super(SyncDriver, payload); + super(payload); } async execute(): Promise { diff --git a/src/tests/events/listeners/TestListener.ts b/src/tests/events/listeners/TestListener.ts index cfbc96a10..822708a3c 100644 --- a/src/tests/events/listeners/TestListener.ts +++ b/src/tests/events/listeners/TestListener.ts @@ -1,7 +1,13 @@ import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; class TestListener extends BaseEventListener { + + constructor(payload: { hello: string }) { + super(payload, SyncDriver); + } + // eslint-disable-next-line no-unused-vars async execute(...args: any[]): Promise { console.log('Executed TestListener', this.getPayload(), this.getName()); } diff --git a/src/tests/events/subscribers/TestSubscriber.ts b/src/tests/events/subscribers/TestSubscriber.ts index ed6193c26..2cddc9c8d 100644 --- a/src/tests/events/subscribers/TestSubscriber.ts +++ b/src/tests/events/subscribers/TestSubscriber.ts @@ -1,12 +1,11 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; - -import TestEventSyncEvent from "../events/TestEventSyncEvent"; +import TestEventSyncEvent from "@src/tests/events/events/TestEventSyncEvent"; class TestSubscriber extends BaseEvent { async execute(...args: any[]): Promise { - console.log('Executed TestSubscriber', this.getPayload(), this.getName()) + console.log('Executed TestSubscriber', this.getPayload(), this.getName(), {args}) this.getEventService().dispatch(new TestEventSyncEvent(this.getPayload())); } From 7dfb1df1189d6a847ba1c780724cc0e7c90276ca Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Sun, 3 Nov 2024 01:18:42 +0000 Subject: [PATCH 05/31] refactor(events): queuable work, worker saving, updated test --- .../2024-09-06-create-failed-worker-table.ts | 4 +- .../2024-09-06-create-worker-table.ts | 3 +- src/config/events.ts | 27 +++-- src/config/eventsLegacy.ts | 4 +- src/core/concerns/HasAttributesConcern.ts | 10 ++ .../concerns/HasDatabaseConnectionConcern.ts | 15 +++ src/core/concerns/HasObserverConcern.ts | 16 +++ .../concerns/HasPrepareDocumentConcern.ts | 13 ++ src/core/concerns/HasRegisterableConcern.ts | 37 ++++-- ...orkerCommand.ts => WorkerLegacyCommand.ts} | 4 +- .../events-legacy/drivers/QueueDriver.ts | 4 +- .../factory/failedWorkerModelFactory.ts | 6 +- .../factory/workerModelFactory.ts | 6 +- .../models/FailedWorkerLegacyModel.ts | 76 ++++++++++++ .../events-legacy/models/WorkerLegacyModel.ts | 78 ++++++++++++ .../providers/EventLegacyProvider.ts | 2 +- .../services/{Worker.ts => WorkerLegacy.ts} | 18 +-- src/core/domains/events/base/BaseDriver.ts | 21 +++- src/core/domains/events/base/BaseEvent.ts | 18 ++- .../domains/events/base/BaseEventListener.ts | 11 +- .../domains/events/commands/WorkerCommand.ts | 32 +++++ .../events/concerns/EventMockableConcern.ts | 10 +- .../events/concerns/EventWorkerConcern.ts | 11 ++ .../domains/events/drivers/QueableDriver.ts | 113 +++++++++++++++++- .../exceptions/EventDispatchException.ts | 8 ++ .../events/exceptions/EventDriverException.ts | 8 ++ ...MockException.ts => EventMockException.ts} | 2 +- .../domains/events/interfaces/IBaseEvent.ts | 8 +- .../domains/events/interfaces/IEventDriver.ts | 2 +- .../events/interfaces/IEventListener.ts | 2 +- .../events/interfaces/IEventService.ts | 6 +- .../events/interfaces/IMockableConcern.ts | 4 +- .../domains/events/interfaces/IQueueName.ts | 3 + .../events/interfaces/config/IEventConfig.ts | 7 +- .../interfaces/config/IEventDriversConfig.ts | 2 + .../models/FailedWorkerModel.ts | 0 .../models/WorkerModel.ts | 3 +- .../domains/events/providers/EventProvider.ts | 22 ++-- .../domains/events/services/EventService.ts | 96 ++++++++++++--- .../concerns}/IDispatchable.ts | 0 .../concerns}/IExecutable.ts | 0 .../concerns/IHasRegisterableConcern.ts | 2 +- .../concerns}/INameable.ts | 0 src/core/services/App.ts | 35 ++++++ src/tests/events/eventSync.test.ts | 24 +++- .../events/TestEventSyncBadPayloadEvent.ts | 22 ++++ src/tests/events/events/TestEventSyncEvent.ts | 2 + src/tests/models/models/TestWorkerModel.ts | 4 +- src/tests/providers/TestEventProvider.ts | 10 ++ 49 files changed, 715 insertions(+), 96 deletions(-) rename src/core/domains/console/commands/{WorkerCommand.ts => WorkerLegacyCommand.ts} (90%) create mode 100644 src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts create mode 100644 src/core/domains/events-legacy/models/WorkerLegacyModel.ts rename src/core/domains/events-legacy/services/{Worker.ts => WorkerLegacy.ts} (90%) create mode 100644 src/core/domains/events/commands/WorkerCommand.ts create mode 100644 src/core/domains/events/concerns/EventWorkerConcern.ts create mode 100644 src/core/domains/events/exceptions/EventDispatchException.ts create mode 100644 src/core/domains/events/exceptions/EventDriverException.ts rename src/core/domains/events/exceptions/{MockException.ts => EventMockException.ts} (68%) create mode 100644 src/core/domains/events/interfaces/IQueueName.ts rename src/core/domains/{events-legacy => events}/models/FailedWorkerModel.ts (100%) rename src/core/domains/{events-legacy => events}/models/WorkerModel.ts (94%) rename src/core/{domains/events/interfaces => interfaces/concerns}/IDispatchable.ts (100%) rename src/core/{domains/events/interfaces => interfaces/concerns}/IExecutable.ts (100%) rename src/core/{domains/events/interfaces => interfaces/concerns}/INameable.ts (100%) create mode 100644 src/tests/events/events/TestEventSyncBadPayloadEvent.ts diff --git a/src/app/migrations/2024-09-06-create-failed-worker-table.ts b/src/app/migrations/2024-09-06-create-failed-worker-table.ts index 4f329ef7d..b0c955a30 100644 --- a/src/app/migrations/2024-09-06-create-failed-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-failed-worker-table.ts @@ -1,4 +1,4 @@ -import FailedWorkerModel, { FailedWorkerModelData } from "@src/core/domains/events-legacy/models/FailedWorkerModel"; +import FailedWorkerLegacyModel, { FailedWorkerModelData } from "@src/core/domains/events-legacy/models/FailedWorkerLegacyModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; @@ -11,7 +11,7 @@ export class CreateFailedWorkerTableMigration extends BaseMigration { group?: string = 'app:setup'; - table = (new FailedWorkerModel({} as FailedWorkerModelData)).table + table = (new FailedWorkerLegacyModel({} as FailedWorkerModelData)).table async up(): Promise { await this.schema.createTable(this.table, { diff --git a/src/app/migrations/2024-09-06-create-worker-table.ts b/src/app/migrations/2024-09-06-create-worker-table.ts index dcf10d676..2824cb6d2 100644 --- a/src/app/migrations/2024-09-06-create-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-worker-table.ts @@ -1,4 +1,5 @@ -import WorkerModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerModel"; +import { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; +import WorkerModel from "@src/core/domains/events/models/WorkerModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; diff --git a/src/config/events.ts b/src/config/events.ts index 8e6d28ebb..395b96664 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -1,8 +1,11 @@ -import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; -import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; +import QueueableDriver, { TQueueDriverOptions } from "@src/core/domains/events/drivers/QueableDriver"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; +import FailedWorkerModel from "@src/core/domains/events/models/FailedWorkerModel"; +import WorkerModel from "@src/core/domains/events/models/WorkerModel"; import EventService from "@src/core/domains/events/services/EventService"; +import TestEventQueueEvent from "@src/tests/events/events/TestEventQueueEvent"; +import TestEventSyncEvent from "@src/tests/events/events/TestEventSyncEvent"; import TestListener from "@src/tests/events/listeners/TestListener"; import TestSubscriber from "@src/tests/events/subscribers/TestSubscriber"; @@ -33,16 +36,21 @@ export const eventConfig: IEventConfig = { [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), // Queue Driver: Saves events for background processing - [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { - queueName: 'default', // Name of the queue - retries: 3, // Number of retry attempts for failed events - failedCollection: 'failedWorkers', // Collection to store failed events - runAfterSeconds: 10, // Delay before processing queued events - workerModelCtor: WorkerModel // Constructor for the Worker model + [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + queueName: 'default', // Name of the queue + retries: 3, // Number of retry attempts for failed events + runAfterSeconds: 10, // Delay before processing queued events + workerModelCtor: WorkerModel, // Constructor for the Worker model + failedWorkerModelCtor: FailedWorkerModel, }) }, + events: EventService.createEvents([ + TestEventQueueEvent, + TestEventSyncEvent + ]), + /** * Event Listeners Configuration */ @@ -53,5 +61,6 @@ export const eventConfig: IEventConfig = { TestSubscriber ] } - ]) + ]), + } \ No newline at end of file diff --git a/src/config/eventsLegacy.ts b/src/config/eventsLegacy.ts index 8a82fdf6c..4a840a079 100644 --- a/src/config/eventsLegacy.ts +++ b/src/config/eventsLegacy.ts @@ -2,7 +2,7 @@ import { ExampleListener } from "@src/app/events/listeners/ExampleListener"; import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; +import WorkerLegacyModel from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; /** @@ -45,7 +45,7 @@ export const eventDrivers: IEventDrivers = { retries: 3, // Number of retry attempts for failed events failedCollection: 'failedWorkers', // Collection to store failed events runAfterSeconds: 10, // Delay before processing queued events - workerModelCtor: WorkerModel // Constructor for the Worker model + workerModelCtor: WorkerLegacyModel // Constructor for the Worker model }) } } as const; diff --git a/src/core/concerns/HasAttributesConcern.ts b/src/core/concerns/HasAttributesConcern.ts index 1a55d8bdc..1d274ca37 100644 --- a/src/core/concerns/HasAttributesConcern.ts +++ b/src/core/concerns/HasAttributesConcern.ts @@ -5,6 +5,16 @@ import { IHasAttributes, IHasAttributesSetAttributeOptions as SetAttributeOption import { ICtor } from "@src/core/interfaces/ICtor"; import IModelAttributes from "@src/core/interfaces/IModelData"; +/** + * Concern that adds the ability to set and retrieve attributes from a model, and to broadcast when attributes change. + * The concern is a mixin and can be applied to any class that implements the IBroadcaster interface. + * The concern adds the following methods to the class: setAttribute, getAttribute, getAttributes, getOriginal, isDirty, and getDirty. + * The concern also adds a constructor that subscribes to the SetAttributeBroadcastEvent and calls the onSetAttributeEvent method when the event is triggered. + * The concern is generic and can be used with any type of model attributes. + * @template Attributes The type of the model's attributes. + * @param {ICtor} Base The base class to extend with the concern. + * @returns {ICtor} A class that extends the base class with the concern. + */ const HasAttributesConcern = (Base: ICtor) => { return class HasAttributes extends Base implements IHasAttributes { diff --git a/src/core/concerns/HasDatabaseConnectionConcern.ts b/src/core/concerns/HasDatabaseConnectionConcern.ts index 8181def9d..935604de3 100644 --- a/src/core/concerns/HasDatabaseConnectionConcern.ts +++ b/src/core/concerns/HasDatabaseConnectionConcern.ts @@ -4,6 +4,21 @@ import { IHasDatabaseConnection } from "@src/core/interfaces/concerns/IHasDataba import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; +/** + * A concern that provides database connection capabilities to a class. + * + * Automatically injects the `connection` and `table` properties, as well as + * the `getDocumentManager` and `getSchema` methods. + * + * To use this concern, simply call it in the class definition, like so: + * + * class MyModel extends HasDatabaseConnection(Base) { + * + * } + * + * @param Base The class to extend. + * @returns A class that extends Base and implements IHasDatabaseConnection. + */ const HasDatabaseConnectionConcern = (Base: ICtor) => { return class HasDatabaseConnection extends Base implements IHasDatabaseConnection { diff --git a/src/core/concerns/HasObserverConcern.ts b/src/core/concerns/HasObserverConcern.ts index 95aafac3d..2defb1cfc 100644 --- a/src/core/concerns/HasObserverConcern.ts +++ b/src/core/concerns/HasObserverConcern.ts @@ -6,6 +6,22 @@ import SetAttributeBroadcastEvent from "@src/core/events/concerns/HasAttribute/S import { ICtor } from "@src/core/interfaces/ICtor"; import IModelAttributes from "@src/core/interfaces/IModelData"; +/** + * Attaches an observer to a model. + * + * The observer is an instance of a class that implements the IObserver interface. + * The observer is responsible for handling events that are broadcasted from the model. + * The observer can also be used to handle custom events that are not part of the predefined set. + * + * The HasObserverConcern adds the following methods to the model: + * - onAttributeChange: Called when a HasAttributeBroadcastEvent is triggered from a model with the HasAttributes concern. + * - observeWith: Attatch the Observer to this instance. + * - observeData: Data has changed, pass it through the appropriate method, return the data. + * - observeDataCustom: A custom observer method. + * + * @param Broadcaster The class that implements the IBroadcaster interface. This class is responsible for broadcasting events to the observer. + * @returns A class that extends the Broadcaster class and implements the IHasObserver interface. + */ const HasObserverConcern = (Broadcaster: ICtor) => { return class HasObserver extends Broadcaster implements IHasObserver { diff --git a/src/core/concerns/HasPrepareDocumentConcern.ts b/src/core/concerns/HasPrepareDocumentConcern.ts index bca686eac..ce7a66314 100644 --- a/src/core/concerns/HasPrepareDocumentConcern.ts +++ b/src/core/concerns/HasPrepareDocumentConcern.ts @@ -1,6 +1,19 @@ import { IHasPrepareDocument } from "@src/core/interfaces/concerns/IHasPrepareDocument"; import { ICtor } from "@src/core/interfaces/ICtor"; +/** + * Concern providing a method to prepare a document for saving to the database. + * + * Automatically stringifies any fields specified in the `json` property. + * + * @example + * class MyModel extends BaseModel { + * json = ['options']; + * } + * + * @template T The type of the prepared document. + * @returns {T} The prepared document. + */ const HasPrepareDocumentConcern = (Base: ICtor) => { return class HasPrepareDocument extends Base implements IHasPrepareDocument { diff --git a/src/core/concerns/HasRegisterableConcern.ts b/src/core/concerns/HasRegisterableConcern.ts index 56d04c2cd..308c79817 100644 --- a/src/core/concerns/HasRegisterableConcern.ts +++ b/src/core/concerns/HasRegisterableConcern.ts @@ -1,24 +1,41 @@ import { IHasRegisterableConcern, IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; +/** + * Concern that allows a class to register arbitrary values by key. + * This allows for decoupling of the registration of values from the class itself. + * The registered values can then be retrieved by key. + * + * If a list name is provided, the registered values are stored in a map with that name. + * If not, the default list 'default' is used. + * The registered values can then be retrieved by list name. + * + * @example + * class MyClass extends HasRegisterableConcern(MyBaseClass) { + * constructor() { + * super(); + * this.register('myKey', 'myValue'); + * } + * } + * const myInstance = new MyClass(); + * myInstance.getRegisteredObject()['default'].get('myKey'); // returns 'myValue' + * + * @param {ICtor} Broadcaster The base class to extend. + * @returns {ICtor} The class that extends the passed in class. + */ const HasRegisterableConcern = (Broadcaster: ICtor) => { return class HasRegisterable extends Broadcaster implements IHasRegisterableConcern { protected registerObject: IRegsiterList = {} private static defaultList = 'default'; - - constructor() { - super(); - this.registerList = {}; - } register(key: string, ...args: unknown[]): void { - if(!this.registerList[HasRegisterable.defaultList]) { - this.registerList[HasRegisterable.defaultList] = new Map(); + if(!this.registerObject[HasRegisterable.defaultList]) { + this.registerObject[HasRegisterable.defaultList] = new Map(); } - this.registerList[HasRegisterable.defaultList].set(key, args); + this.registerObject[HasRegisterable.defaultList].set(key, args); } registerByList(listName: string, key: string, ...args: unknown[]): void { @@ -27,7 +44,7 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { } setRegisteredByList(listName: string, registered: Map): void { - this.registerList[listName] = registered + this.registerObject[listName] = registered } getRegisteredObject(): IRegsiterList { @@ -35,7 +52,7 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { } getRegisteredList(): T { - return this.getRegisteredByList(HasRegisterable.defaultList) as T; + return this.getRegisteredByList(HasRegisterable.defaultList) } getRegisteredByList(listName: string): T { diff --git a/src/core/domains/console/commands/WorkerCommand.ts b/src/core/domains/console/commands/WorkerLegacyCommand.ts similarity index 90% rename from src/core/domains/console/commands/WorkerCommand.ts rename to src/core/domains/console/commands/WorkerLegacyCommand.ts index 4ac6cfe62..cc5c24375 100644 --- a/src/core/domains/console/commands/WorkerCommand.ts +++ b/src/core/domains/console/commands/WorkerLegacyCommand.ts @@ -1,5 +1,5 @@ import BaseCommand from "@src/core/domains/console/base/BaseCommand"; -import Worker from "@src/core/domains/events-legacy/services/Worker"; +import WorkerLegacy from "@src/core/domains/events-legacy/services/WorkerLegacy"; import { App } from "@src/core/services/App"; export default class WorkerLegacyCommand extends BaseCommand { @@ -22,7 +22,7 @@ export default class WorkerLegacyCommand extends BaseCommand { async execute() { const driver = this.getDriverName(); - const worker = Worker.getInstance() + const worker = WorkerLegacy.getInstance() worker.setDriver(driver) App.container('logger').console('Running worker...', worker.options) diff --git a/src/core/domains/events-legacy/drivers/QueueDriver.ts b/src/core/domains/events-legacy/drivers/QueueDriver.ts index 0eb899a5e..5655ab20e 100644 --- a/src/core/domains/events-legacy/drivers/QueueDriver.ts +++ b/src/core/domains/events-legacy/drivers/QueueDriver.ts @@ -1,7 +1,7 @@ import WorkerModelFactory from '@src/core/domains/events-legacy/factory/workerModelFactory'; import { IEvent } from '@src/core/domains/events-legacy/interfaces/IEvent'; import IEventDriver from '@src/core/domains/events-legacy/interfaces/IEventDriver'; -import WorkerModel from '@src/core/domains/events-legacy/models/WorkerModel'; +import WorkerLegacyModel from '@src/core/domains/events-legacy/models/WorkerLegacyModel'; import { ModelConstructor } from '@src/core/interfaces/IModel'; /** @@ -9,7 +9,7 @@ import { ModelConstructor } from '@src/core/interfaces/IModel'; * * Saves events for background processing */ -export type WorkerModelCtor = ModelConstructor +export type WorkerModelCtor = ModelConstructor export type QueueDriverOptions = { diff --git a/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts b/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts index f58ad7947..b15f49ceb 100644 --- a/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts +++ b/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts @@ -1,4 +1,4 @@ -import FailedWorkerModel, { initialFailedWorkerModalData } from "@src/core/domains/events-legacy/models/FailedWorkerModel"; +import FailedWorkerLegacyModel, { initialFailedWorkerModalData } from "@src/core/domains/events-legacy/models/FailedWorkerLegacyModel"; type Params = { eventName: string; @@ -15,8 +15,8 @@ export default class FailedWorkerModelFactory { * @param error The error that caused the event to fail * @returns A new instance of FailedWorkerModel */ - create(collection: string, { eventName, payload, error }: Params): FailedWorkerModel { - return new FailedWorkerModel({ + create(collection: string, { eventName, payload, error }: Params): FailedWorkerLegacyModel { + return new FailedWorkerLegacyModel({ ...initialFailedWorkerModalData, eventName, payload, diff --git a/src/core/domains/events-legacy/factory/workerModelFactory.ts b/src/core/domains/events-legacy/factory/workerModelFactory.ts index 57899ff31..3afe9c742 100644 --- a/src/core/domains/events-legacy/factory/workerModelFactory.ts +++ b/src/core/domains/events-legacy/factory/workerModelFactory.ts @@ -1,4 +1,4 @@ -import WorkerModel, { initialWorkerModalData } from "@src/core/domains/events-legacy/models/WorkerModel"; +import WorkerLegacyModel, { initialWorkerModalData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; import { ModelConstructor } from "@src/core/interfaces/IModel"; type Params = { @@ -6,7 +6,7 @@ type Params = { eventName: string; payload: any, retries: number, - workerModelCtor: ModelConstructor + workerModelCtor: ModelConstructor } export default class WorkerModelFactory { @@ -20,7 +20,7 @@ export default class WorkerModelFactory { * @param workerModelCtor The constructor for the WorkerModel to create * @returns A new instance of WorkerModel */ - create(collection: string, { queueName, eventName, payload, retries, workerModelCtor }: Params): WorkerModel { + create(collection: string, { queueName, eventName, payload, retries, workerModelCtor }: Params): WorkerLegacyModel { return new workerModelCtor({ ...initialWorkerModalData, queueName, diff --git a/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts b/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts new file mode 100644 index 000000000..afde2d72b --- /dev/null +++ b/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts @@ -0,0 +1,76 @@ +import Model from "@src/core/base/Model"; +import IModelAttributes from "@src/core/interfaces/IModelData"; + +/** + * Represents a failed worker model. + * + * @interface FailedWorkerModelData + * @extends IModelAttributes + */ +export interface FailedWorkerModelData extends IModelAttributes { + + /** + * The name of the event that failed. + */ + eventName: string; + + /** + * The payload of the event that failed. + */ + payload: any; + + /** + * The error that caused the event to fail. + */ + error: any; + + /** + * The date when the event failed. + */ + failedAt: Date; +} + +/** + * Initial data for a failed worker model. + */ +export const initialFailedWorkerModalData = { + eventName: '', + payload: null, + error: null, + failedAt: new Date() +}; + +/** + * FailedWorkerModel class. + * + * @class FailedWorkerModel + * @extends Model + */ +export default class FailedWorkerLegacyModel extends Model { + + /** + * Dates fields. + */ + dates = ['failedAt']; + + /** + * Fields of the model. + */ + fields = [ + 'eventName', + 'payload', + 'error', + 'failedAt' + ]; + + /** + * Constructor. + * + * @param {FailedWorkerModelData} data - The data for the model. + */ + constructor(data: FailedWorkerModelData) { + super(data); + } + +} + diff --git a/src/core/domains/events-legacy/models/WorkerLegacyModel.ts b/src/core/domains/events-legacy/models/WorkerLegacyModel.ts new file mode 100644 index 000000000..cc78bebd3 --- /dev/null +++ b/src/core/domains/events-legacy/models/WorkerLegacyModel.ts @@ -0,0 +1,78 @@ +import Model from "@src/core/base/Model"; +import IModelAttributes from "@src/core/interfaces/IModelData"; + +export interface WorkerModelData extends IModelAttributes { + queueName: string; + eventName: string; + payload: any; + attempt: number; + retries: number; + createdAt: Date; +} + +export const initialWorkerModalData = { + queueName: '', + eventName: '', + payload: null, + attempt: 0, + retries: 0, + createdAt: new Date() +} + +/** + * WorkerModel class. + * + * Represents a worker model that stores data for a background job. + * + * @class WorkerModel + * @extends Model + */ +export default class WorkerLegacyModel extends Model { + + + /** + * The list of date fields. + * + * @type {string[]} + */ + dates = ['createdAt'] + + /** + * The list of fields. + * + * @type {string[]} + */ + fields = [ + 'queueName', + 'eventName', + 'payload', + 'attempt', + 'retries', + 'createdAt' + ] + + /** + * The list of fields that are JSON. + * + * @type {string[]} + */ + json = [ + 'payload' + ] + + constructor(data: WorkerModelData) { + super(data); + } + + public getPayload(): unknown { + try { + const payload = this.getAttribute('payload'); + return JSON.parse(payload) + } + // eslint-disable-next-line no-unused-vars + catch (err) { + return null + } + } + +} \ No newline at end of file diff --git a/src/core/domains/events-legacy/providers/EventLegacyProvider.ts b/src/core/domains/events-legacy/providers/EventLegacyProvider.ts index 7b54300c5..61eee1286 100644 --- a/src/core/domains/events-legacy/providers/EventLegacyProvider.ts +++ b/src/core/domains/events-legacy/providers/EventLegacyProvider.ts @@ -1,7 +1,7 @@ import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/eventsLegacy"; import BaseProvider from "@src/core/base/Provider"; -import WorkerLegacyCommand from "@src/core/domains/console/commands/WorkerCommand"; +import WorkerLegacyCommand from "@src/core/domains/console/commands/WorkerLegacyCommand"; import { EventLegacyServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; import EventService from "@src/core/domains/events-legacy/services/EventService"; import { App } from "@src/core/services/App"; diff --git a/src/core/domains/events-legacy/services/Worker.ts b/src/core/domains/events-legacy/services/WorkerLegacy.ts similarity index 90% rename from src/core/domains/events-legacy/services/Worker.ts rename to src/core/domains/events-legacy/services/WorkerLegacy.ts index 73961f617..374e491ec 100644 --- a/src/core/domains/events-legacy/services/Worker.ts +++ b/src/core/domains/events-legacy/services/WorkerLegacy.ts @@ -4,7 +4,7 @@ import { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/Queu import EventDriverException from "@src/core/domains/events-legacy/exceptions/EventDriverException"; import FailedWorkerModelFactory from "@src/core/domains/events-legacy/factory/failedWorkerModelFactory"; import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -import WorkerModel from "@src/core/domains/events-legacy/models/WorkerModel"; +import WorkerLegacyModel from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; import { App } from "@src/core/services/App"; @@ -14,7 +14,7 @@ import { App } from "@src/core/services/App"; * * This service provides methods for working with the worker table. */ -export default class Worker extends Singleton { +export default class WorkerLegacy extends Singleton { /** * Queue driver options @@ -51,11 +51,11 @@ export default class Worker extends Singleton { } // Worker service - const worker = Worker.getInstance(); - let model: WorkerModel; + const worker = WorkerLegacy.getInstance(); + let model: WorkerLegacyModel; // Fetch the current list of queued results - const workerResults: WorkerModel[] = await worker.getWorkerResults(this.options.queueName) + const workerResults: WorkerLegacyModel[] = await worker.getWorkerResults(this.options.queueName) this.logToConsole('collection: ' + new this.options.workerModelCtor().table) this.logToConsole(`${workerResults.length} queued items with queue name '${this.options.queueName}'`) @@ -99,7 +99,7 @@ export default class Worker extends Singleton { * @returns */ async getWorkerResults(queueName: string) { - const workerRepository = new Repository(this.options.workerModelCtor) + const workerRepository = new Repository(this.options.workerModelCtor) return await workerRepository.findMany({ queueName @@ -110,7 +110,7 @@ export default class Worker extends Singleton { * Proces the worker by dispatching it through the event driver 'sync' * @param model */ - async processWorkerModel(model: WorkerModel) { + async processWorkerModel(model: WorkerLegacyModel) { model.table = new this.options.workerModelCtor().table const eventName = model.getAttribute('eventName') const payload = model.getPayload() as IEventPayload @@ -133,7 +133,7 @@ export default class Worker extends Singleton { * @param err * @returns */ - async failedWorkerModel(model: WorkerModel, err: Error) { + async failedWorkerModel(model: WorkerLegacyModel, err: Error) { model.table = new this.options.workerModelCtor().table; // Get attempts and max retreis @@ -159,7 +159,7 @@ export default class Worker extends Singleton { * @param model * @param err */ - async moveFailedWorkerModel(model: WorkerModel, err: Error) { + async moveFailedWorkerModel(model: WorkerLegacyModel, err: Error) { this.logToConsole('Moved to failed') const failedWorkerModel = (new FailedWorkerModelFactory).create( diff --git a/src/core/domains/events/base/BaseDriver.ts b/src/core/domains/events/base/BaseDriver.ts index 1ac419e10..3534b5a51 100644 --- a/src/core/domains/events/base/BaseDriver.ts +++ b/src/core/domains/events/base/BaseDriver.ts @@ -3,6 +3,9 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { IEventDriversConfigOption } from "../interfaces/config/IEventDriversConfig"; + + abstract class BaseDriver implements IEventDriver { protected eventService!: IEventService; @@ -11,9 +14,25 @@ abstract class BaseDriver implements IEventDriver { this.eventService = eventService } + /** + * @returns The name of the event driver. + */ + getName(): string { + return this.constructor.name + } + + /** + * Dispatches an event. + * @param event + */ abstract dispatch(event: IBaseEvent): Promise; - abstract getName(): string; + /** + * @returns The configuration options for this event driver, or undefined if not found. + */ + protected getOptions(): T | undefined { + return this.eventService.getDriverOptions(this)?.options as T ?? undefined + } } diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 560bfb663..e5bd7f7b1 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -13,6 +13,11 @@ abstract class BaseEvent implements IBaseEvent { protected defaultDriver!: ICtor; + /** + * Provide a namespace to avoid conflicts with other events. + */ + protected namespace: string = ''; + /** * Constructor * @param payload The payload of the event @@ -20,7 +25,8 @@ abstract class BaseEvent implements IBaseEvent { */ constructor(payload: IEventPayload | null = null, driver?: ICtor) { this.payload = payload; - this.defaultDriver = App.container('events').getDefaultDriverCtor(); + // Use safeContainer here to avoid errors during registering which runs during boot up. + this.defaultDriver = App.safeContainer('events')?.getDefaultDriverCtor() as ICtor; this.driver = driver ?? this.defaultDriver; } @@ -35,6 +41,13 @@ abstract class BaseEvent implements IBaseEvent { // eslint-disable-next-line no-unused-vars async execute(...args: any[]): Promise {/* Nothing to execute */} + /** + * @returns The name of the queue as a string. + */ + getQueueName(): string { + return 'default'; + } + /** * @template T The type of the payload to return. * @returns The payload of the event. @@ -47,7 +60,8 @@ abstract class BaseEvent implements IBaseEvent { * @returns The name of the event as a string. */ getName(): string { - return this.constructor.name + const prefix = this.namespace === '' ? '' : (this.namespace + '/') + return prefix + this.constructor.name } /** diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts index acea1f60f..372944ca6 100644 --- a/src/core/domains/events/base/BaseEventListener.ts +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -1,9 +1,9 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; -import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; -import { ICtor } from "@src/core/interfaces/ICtor"; - import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import { App } from "@src/core/services/App"; class BaseEventListener extends BaseEvent implements IEventListener { @@ -17,6 +17,11 @@ class BaseEventListener extends BaseEvent implements IEventListener { */ constructor(payload?: IEventPayload, driver?: ICtor) { super(payload, driver); + + if(!App.containerReady('events')) { + return; + } + this.notifySubscribers(); } diff --git a/src/core/domains/events/commands/WorkerCommand.ts b/src/core/domains/events/commands/WorkerCommand.ts new file mode 100644 index 000000000..cf887e1da --- /dev/null +++ b/src/core/domains/events/commands/WorkerCommand.ts @@ -0,0 +1,32 @@ +import BaseCommand from "@src/core/domains/console/base/BaseCommand"; + +export default class WorkerCommand extends BaseCommand { + + /** + * The signature of the command + */ + signature: string = 'worker'; + + description = 'Run the worker to process queued event items'; + + /** + * Whether to keep the process alive after command execution + */ + public keepProcessAlive = true; + + /** + * Execute the command + */ + + async execute() { + + + + // setInterval(async () => { + // await worker.work() + // App.container('logger').console('Running worker again in ' + worker.options.runAfterSeconds.toString() + ' seconds') + // }, worker.options.runAfterSeconds * 1000) + } + + +} \ No newline at end of file diff --git a/src/core/domains/events/concerns/EventMockableConcern.ts b/src/core/domains/events/concerns/EventMockableConcern.ts index 6b6da491c..90c5b7d8d 100644 --- a/src/core/domains/events/concerns/EventMockableConcern.ts +++ b/src/core/domains/events/concerns/EventMockableConcern.ts @@ -1,4 +1,4 @@ -import MockException from "@src/core/domains/events/exceptions/MockException"; +import EventMockException from "@src/core/domains/events/exceptions/EventMockException"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { IMockableConcern, TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; @@ -54,12 +54,16 @@ const EventMockableConcern = (Base: ICtor) => { * @throws Will throw an error if the event was not dispatched or if the dispatched event's * payload does not satisfy the given condition. */ - assertDispatched(eventCtor: ICtor, callback: TMockableEventCallback): boolean { + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean { const eventCtorName = (new eventCtor(null)).getName() const dispatchedEvent = this.mockEventsDispatched.find(e => e.getName() === eventCtorName) if(!dispatchedEvent) { - throw new MockException(`Event ${eventCtorName} was not dispatched`) + throw new EventMockException(`Event ${eventCtorName} was not dispatched`) + } + + if(typeof callback !== 'function') { + return true; } return callback(dispatchedEvent.getPayload()) diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts new file mode 100644 index 000000000..014272027 --- /dev/null +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -0,0 +1,11 @@ +import { ICtor } from "@src/core/interfaces/ICtor"; + +const EventWorkerConcern = (Base: ICtor) => { + return class EventWorkerConcern extends Base { + + // todo read and process events from db + + } +} + +export default EventWorkerConcern \ No newline at end of file diff --git a/src/core/domains/events/drivers/QueableDriver.ts b/src/core/domains/events/drivers/QueableDriver.ts index b9f46ecb1..ae33b23c2 100644 --- a/src/core/domains/events/drivers/QueableDriver.ts +++ b/src/core/domains/events/drivers/QueableDriver.ts @@ -1,14 +1,117 @@ -import { EVENT_DRIVERS } from "@src/config/events"; import BaseDriver from "@src/core/domains/events/base/BaseDriver"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import { IModel } from "@src/core/interfaces/IModel"; +import { z } from "zod"; + +import EventDriverException from "../exceptions/EventDriverException"; +import { IBaseEvent } from "../interfaces/IBaseEvent"; + + +export type TQueueDriverOptions = { + + /** + * Name of the queue + */ + queueName: string; + + /** + * Name of the event, defaults to the IEvent.name + */ + eventName?: string; + + /** + * Number of retry attempts for failed events + */ + retries: number; + + /** + * Delay before processing queued events + */ + runAfterSeconds: number; + + /** + * Constructor for the Worker model + */ + workerModelCtor: ICtor; + + /** + * Constructor for the Worker model for failed events + */ + failedWorkerModelCtor: ICtor; + + /** + * Run the worker only once, defaults to false + */ + runOnce?: boolean; +} class QueueableDriver extends BaseDriver { - async dispatch(): Promise { - // todo save event to queue + /** + * Dispatches an event by saving it to the worker model. + * + * First, it retrieves the options for the queue driver using the getOptions method. + * Then, it validates the options using the validateOptions method. + * If the options are invalid, it throws an EventDriverException. + * Finally, it creates a new instance of the worker model using the options.workerModelCtor, + * and saves it to the database. + * + * @param event The event to dispatch. + * @throws {EventDriverException} If the options are invalid. + * @returns A promise that resolves once the event has been dispatched. + */ + async dispatch(event: IBaseEvent): Promise { + + const options = this.getOptions() + + this.validateOptions(options) + + this.updateWorkerQueueTable(options as TQueueDriverOptions, event) } - getName(): string { - return EVENT_DRIVERS.QUEABLE; + /** + * Updates the worker queue table with the given event. + * + * It creates a new instance of the worker model using the options.workerModelCtor, + * and saves it to the database. This method is used by the dispatch method to + * save the event to the worker queue table. + * + * @param options The options to use when updating the worker queue table. + * @param event The event to update the worker queue table with. + * @returns A promise that resolves once the worker queue table has been updated. + * @throws {EventDriverException} If the options are invalid. + */ + private async updateWorkerQueueTable(options: TQueueDriverOptions, event: IBaseEvent) { + const workerModel = new options.workerModelCtor({ + queueName: event.getQueueName(), + eventName: event.getName(), + payload: JSON.stringify(event.getPayload()), + }) + await workerModel.save(); + } + + /** + * Validates the options for the queue driver + * @param options The options to validate + * @throws {EventDriverException} If the options are invalid + * @private + */ + private validateOptions(options: TQueueDriverOptions | undefined) { + const schema = z.object({ + queueName: z.string(), + eventName: z.string().optional(), + retries: z.number(), + runAfterSeconds: z.number(), + workerModelCtor: z.any(), + failedWorkerModelCtor: z.any(), + runOnce: z.boolean().optional() + }) + + const parsedResult = schema.safeParse(options) + + if(!parsedResult.success) { + throw new EventDriverException('Invalid queue driver options: '+ parsedResult.error.message); + } } } diff --git a/src/core/domains/events/exceptions/EventDispatchException.ts b/src/core/domains/events/exceptions/EventDispatchException.ts new file mode 100644 index 000000000..7f958c859 --- /dev/null +++ b/src/core/domains/events/exceptions/EventDispatchException.ts @@ -0,0 +1,8 @@ +export default class EventDispatchException extends Error { + + constructor(message: string = 'Event Dispatch Exception') { + super(message); + this.name = 'EventDispatchException'; + } + +} \ No newline at end of file diff --git a/src/core/domains/events/exceptions/EventDriverException.ts b/src/core/domains/events/exceptions/EventDriverException.ts new file mode 100644 index 000000000..b86d5253b --- /dev/null +++ b/src/core/domains/events/exceptions/EventDriverException.ts @@ -0,0 +1,8 @@ +export default class EventDriverException extends Error { + + constructor(message: string = 'Event Driver Exception') { + super(message); + this.name = 'EventDriverException'; + } + +} \ No newline at end of file diff --git a/src/core/domains/events/exceptions/MockException.ts b/src/core/domains/events/exceptions/EventMockException.ts similarity index 68% rename from src/core/domains/events/exceptions/MockException.ts rename to src/core/domains/events/exceptions/EventMockException.ts index ed63dcca0..5dc57092f 100644 --- a/src/core/domains/events/exceptions/MockException.ts +++ b/src/core/domains/events/exceptions/EventMockException.ts @@ -1,4 +1,4 @@ -export default class MockException extends Error { +export default class EventMockException extends Error { constructor(message: string = 'Mock Exception') { super(message); diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index 930ad2219..17091c7ac 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -1,11 +1,13 @@ import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; -import { IExecutable } from "@src/core/domains/events/interfaces/IExecutable"; -import { INameable } from "@src/core/domains/events/interfaces/INameable"; +import { IExecutable } from "@src/core/interfaces/concerns/IExecutable"; +import { INameable } from "@src/core/interfaces/concerns/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IBaseEvent extends INameable, IExecutable +import { IQueueName } from "./IQueueName"; + +export interface IBaseEvent extends INameable, IExecutable, IQueueName { getEventService(): IEventService; getDriverCtor(): ICtor; diff --git a/src/core/domains/events/interfaces/IEventDriver.ts b/src/core/domains/events/interfaces/IEventDriver.ts index ece284a5b..88970b4a1 100644 --- a/src/core/domains/events/interfaces/IEventDriver.ts +++ b/src/core/domains/events/interfaces/IEventDriver.ts @@ -1,5 +1,5 @@ import IDispatchable from "@src/core/domains/events-legacy/interfaces/IDispatchable"; -import { INameable } from "@src/core/domains/events/interfaces/INameable"; +import { INameable } from "@src/core/interfaces/concerns/INameable"; export default interface IEventDriver extends INameable, IDispatchable {} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events/interfaces/IEventListener.ts index 6dbcd940b..882973f9e 100644 --- a/src/core/domains/events/interfaces/IEventListener.ts +++ b/src/core/domains/events/interfaces/IEventListener.ts @@ -1,5 +1,5 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; -import { INameable } from "@src/core/domains/events/interfaces/INameable"; +import { INameable } from "@src/core/interfaces/concerns/INameable"; export interface IEventListener extends INameable, IBaseEvent { diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts index a967b7e00..8a1240092 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -11,11 +11,15 @@ import { ICtor } from "@src/core/interfaces/ICtor"; export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IMockableConcern { - registerDriver(driverIdentifierConstant: string, driverConfig: IEventDriversConfigOption): void; + registerEvent(event: ICtor): void; + + registerDriver(driverConfig: IEventDriversConfigOption): void; registerListener(listenerConfig: TListenersConfigOption): void; getDefaultDriverCtor(): ICtor; + getDriverOptions(driver: IEventDriver): IEventDriversConfigOption | undefined; + getSubscribers(eventName: string): ICtor[]; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IMockableConcern.ts b/src/core/domains/events/interfaces/IMockableConcern.ts index af433aa38..3ecd34b40 100644 --- a/src/core/domains/events/interfaces/IMockableConcern.ts +++ b/src/core/domains/events/interfaces/IMockableConcern.ts @@ -2,12 +2,12 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { ICtor } from "@src/core/interfaces/ICtor"; -export type TMockableEventCallback = (payload: T) => boolean; +export type TMockableEventCallback = (payload: TPayload) => boolean; export interface IMockableConcern { mockEvent(event: ICtor): void; mockEventDispatched(event: IBaseEvent): void; - assertDispatched(eventCtor: ICtor, callback: TMockableEventCallback): boolean + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IQueueName.ts b/src/core/domains/events/interfaces/IQueueName.ts new file mode 100644 index 000000000..58f3e6f6f --- /dev/null +++ b/src/core/domains/events/interfaces/IQueueName.ts @@ -0,0 +1,3 @@ +export interface IQueueName { + getQueueName(): string; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventConfig.ts b/src/core/domains/events/interfaces/config/IEventConfig.ts index ca3e76afc..0e30bcb7d 100644 --- a/src/core/domains/events/interfaces/config/IEventConfig.ts +++ b/src/core/domains/events/interfaces/config/IEventConfig.ts @@ -3,8 +3,11 @@ import { IEventDriversConfig } from "@src/core/domains/events/interfaces/config/ import { IEventListenersConfig } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { IBaseEvent } from "../IBaseEvent"; + export interface IEventConfig { defaultDriver: ICtor; - drivers: IEventDriversConfig, - listeners: IEventListenersConfig + drivers: IEventDriversConfig; + events: ICtor[]; + listeners: IEventListenersConfig; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/config/IEventDriversConfig.ts b/src/core/domains/events/interfaces/config/IEventDriversConfig.ts index 96ace18c8..3733378b1 100644 --- a/src/core/domains/events/interfaces/config/IEventDriversConfig.ts +++ b/src/core/domains/events/interfaces/config/IEventDriversConfig.ts @@ -6,6 +6,8 @@ export interface IEventDriversConfigOption { options?: Record; } +export type TEventDriversRegister = Record; + export interface IEventDriversConfig { [key: string]: IEventDriversConfigOption diff --git a/src/core/domains/events-legacy/models/FailedWorkerModel.ts b/src/core/domains/events/models/FailedWorkerModel.ts similarity index 100% rename from src/core/domains/events-legacy/models/FailedWorkerModel.ts rename to src/core/domains/events/models/FailedWorkerModel.ts diff --git a/src/core/domains/events-legacy/models/WorkerModel.ts b/src/core/domains/events/models/WorkerModel.ts similarity index 94% rename from src/core/domains/events-legacy/models/WorkerModel.ts rename to src/core/domains/events/models/WorkerModel.ts index bb966ab6e..88d47a2d4 100644 --- a/src/core/domains/events-legacy/models/WorkerModel.ts +++ b/src/core/domains/events/models/WorkerModel.ts @@ -29,6 +29,7 @@ export const initialWorkerModalData = { */ export default class WorkerModel extends Model { + table: string = 'worker_queue'; /** * The list of date fields. @@ -61,7 +62,7 @@ export default class WorkerModel extends Model { ] constructor(data: WorkerModelData) { - super(data); + super({...initialWorkerModalData, ...data}); } public getPayload(): unknown { diff --git a/src/core/domains/events/providers/EventProvider.ts b/src/core/domains/events/providers/EventProvider.ts index 2e902f2c6..64ae92b59 100644 --- a/src/core/domains/events/providers/EventProvider.ts +++ b/src/core/domains/events/providers/EventProvider.ts @@ -12,7 +12,9 @@ class EventProvider extends BaseProvider { async register(): Promise { const eventService = new EventService(this.config); + this.registerDrivers(eventService); + this.registerEvents(eventService); this.registerListeners(eventService); App.setContainer('events', eventService); @@ -22,22 +24,26 @@ class EventProvider extends BaseProvider { /** * Registers all event drivers defined in the configuration with the provided event service. - * Iterates over the event driver configuration and registers each driver - * using its identifier constant and configuration. - * * @param eventService The event service to register drivers with. */ private registerDrivers(eventService: IEventService) { - for(const driverIdentifierConstant of Object.keys(this.config.drivers)) { - eventService.registerDriver(driverIdentifierConstant, this.config.drivers[driverIdentifierConstant]); + for(const driverKey of Object.keys(this.config.drivers)) { + eventService.registerDriver(this.config.drivers[driverKey]); + } + } + + /** + * Registers all event constructors defined in the configuration with the provided event service. + * @param eventService The event service to register events with. + */ + private registerEvents(eventService: IEventService) { + for(const event of this.config.events) { + eventService.registerEvent(event); } } /** * Registers all event listeners defined in the configuration with the provided event service. - * Iterates over the event listeners configuration and registers each listener - * using its identifier constant and configuration. - * * @param eventService The event service to register listeners with. */ private registerListeners(eventService: IEventService) { diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 7a4f3870c..d957de566 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -10,8 +10,12 @@ import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@s import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; +import EventDispatchException from "../exceptions/EventDispatchException"; + class EventService extends BaseService implements IEventService { + static readonly REGISTERED_EVENTS = "registeredEvents"; + static readonly REGISTERED_DRIVERS = "registeredDrivers"; static readonly REGISTERED_LISTENERS = "registeredListeners"; @@ -28,7 +32,6 @@ class EventService extends BaseService implements IEventService { } /** - * Create an event driver config. * @param driverCtor The event driver class. * @param options The event driver options. * @returns The event driver config. @@ -41,7 +44,23 @@ class EventService extends BaseService implements IEventService { } /** - * Declare HasRegisterableConcern methods. + * @param events An array of event constructors to be registered. + * @returns The same array of event constructors. + */ + public static createEvents(events: ICtor[]): ICtor[] { + return events + } + + /** + * @param config The event listeners config. + * @returns The event listeners config. + */ + public static createListeners(config: IEventListenersConfig): IEventListenersConfig { + return config + } + + /** + * Declare HasRegisterableConcern methods. */ declare register: (key: string, value: unknown) => void; @@ -62,16 +81,7 @@ class EventService extends BaseService implements IEventService { declare mockEventDispatched: (event: IBaseEvent) => void; - declare assertDispatched: (eventCtor: ICtor, callback: TMockableEventCallback) => boolean - - /** - * Create an event listeners config. - * @param config The event listeners config. - * @returns The event listeners config. - */ - public static createListeners(config: IEventListenersConfig): IEventListenersConfig { - return config - } + declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean /** * Dispatch an event using its registered driver. @@ -79,6 +89,10 @@ class EventService extends BaseService implements IEventService { */ async dispatch(event: IBaseEvent): Promise { + if(!this.isRegisteredEvent(event)) { + throw new EventDispatchException(`Event '${event.getName()}' not registered. The event must be added to the \`events\` array in the config. See @src/config/events.ts`) + } + const eventDriverCtor = event.getDriverCtor() const eventDriver = new eventDriverCtor(this) await eventDriver.dispatch(event) @@ -86,17 +100,38 @@ class EventService extends BaseService implements IEventService { this.mockEventDispatched(event) } + /** + * @param event The event class to be checked + * @returns True if the event is registered, false otherwise + * @private + */ + private isRegisteredEvent(event: IBaseEvent): boolean { + return this.getRegisteredByList>>(EventService.REGISTERED_EVENTS).has(event.getName()); + } + /** + * Register an event with the event service + * @param event The event class to be registered + */ + registerEvent(event: ICtor): void { + this.registerByList( + EventService.REGISTERED_EVENTS, + new event().getName(), + event + ) + } /** * Register a driver with the event service * @param driverIdentifierConstant a constant string to identify the driver * @param driverConfig the driver configuration */ - registerDriver(driverIdentifierConstant: string, driverConfig: IEventDriversConfigOption): void { + registerDriver(driverConfig: IEventDriversConfigOption): void { + const driverIdentifier = driverConfig.driverCtor.name + this.registerByList( EventService.REGISTERED_DRIVERS, - driverIdentifierConstant, + driverIdentifier, driverConfig ) } @@ -109,11 +144,32 @@ class EventService extends BaseService implements IEventService { registerListener(listenerConfig: TListenersConfigOption): void { const listenerIdentifier = listenerConfig.listener.name + + // Update registered listeners this.registerByList( EventService.REGISTERED_LISTENERS, listenerIdentifier, listenerConfig ) + + // Update the registered events from the listener and subscribers + this.registerEventsFromListenerConfig(listenerConfig) + } + + /** + * Registers the events associated with the listener configuration with the event service. + * Iterates over the subscribers and registers each subscriber event with the event service. + * @param listenerConfig The listener configuration. + */ + private registerEventsFromListenerConfig(listenerConfig: TListenersConfigOption): void { + + // Update registered events with the listener + this.registerEvent(listenerConfig.listener) + + // Update the registered events from the subscribers + for(const subscriber of listenerConfig.subscribers) { + this.registerEvent(subscriber) + } } /** @@ -124,6 +180,18 @@ class EventService extends BaseService implements IEventService { return this.config.defaultDriver } + /** + * Retrieves the configuration options for a given event driver constructor. + * @param driverCtor The constructor of the event driver. + * @returns The configuration options for the specified event driver, or undefined if not found. + */ + getDriverOptions(driver: IEventDriver): IEventDriversConfigOption | undefined { + const registeredDrivers = this.getRegisteredByList>(EventService.REGISTERED_DRIVERS); + const driverConfig = registeredDrivers.get(driver.getName())?.[0]; + + return driverConfig ?? undefined + } + /** * Returns an array of event subscriber constructors that are listening to this event. * @returns An array of event subscriber constructors. diff --git a/src/core/domains/events/interfaces/IDispatchable.ts b/src/core/interfaces/concerns/IDispatchable.ts similarity index 100% rename from src/core/domains/events/interfaces/IDispatchable.ts rename to src/core/interfaces/concerns/IDispatchable.ts diff --git a/src/core/domains/events/interfaces/IExecutable.ts b/src/core/interfaces/concerns/IExecutable.ts similarity index 100% rename from src/core/domains/events/interfaces/IExecutable.ts rename to src/core/interfaces/concerns/IExecutable.ts diff --git a/src/core/interfaces/concerns/IHasRegisterableConcern.ts b/src/core/interfaces/concerns/IHasRegisterableConcern.ts index 5c587c9b6..686a13408 100644 --- a/src/core/interfaces/concerns/IHasRegisterableConcern.ts +++ b/src/core/interfaces/concerns/IHasRegisterableConcern.ts @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -export type TRegisterMap = Map; +export type TRegisterMap = Map; export interface IRegsiterList { [key: string]: TRegisterMap diff --git a/src/core/domains/events/interfaces/INameable.ts b/src/core/interfaces/concerns/INameable.ts similarity index 100% rename from src/core/domains/events/interfaces/INameable.ts rename to src/core/interfaces/concerns/INameable.ts diff --git a/src/core/services/App.ts b/src/core/services/App.ts index 65ddd755c..b1d9bc924 100644 --- a/src/core/services/App.ts +++ b/src/core/services/App.ts @@ -76,6 +76,41 @@ export class App extends Singleton { return kernel.containers.get(name); } + /** + * Safely retrieves a container by its name. + * Attempts to get the specified container from the kernel. + * If the container is not initialized, it returns undefined. + * Throws an error for other exceptions. + * + * @template K - The type of the container key. + * @param {K} name - The name of the container to retrieve. + * @returns {IContainers[K] | undefined} The container if found, otherwise undefined. + * @throws {Error} If an unexpected error occurs. + */ + public static safeContainer(name: K): IContainers[K] | undefined { + try { + return this.container(name); + } + catch (err) { + if(err instanceof UninitializedContainerError) { + return undefined; + } + + throw err + } + } + + /** + * Checks if a container is ready. + * A container is considered ready if it has been set using the setContainer method. + * @template K - The type of the container key. + * @param {K} name - The name of the container to check. + * @returns {boolean} Whether the container is ready or not. + */ + public static containerReady(name: K): boolean { + return this.safeContainer(name) !== undefined + } + /** * Gets the environment * @returns The environment if set, or undefined if not diff --git a/src/tests/events/eventSync.test.ts b/src/tests/events/eventSync.test.ts index feff0d3dd..ed1c80d1c 100644 --- a/src/tests/events/eventSync.test.ts +++ b/src/tests/events/eventSync.test.ts @@ -7,6 +7,8 @@ import TestEventSyncEvent from '@src/tests/events/events/TestEventSyncEvent'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; +import TestEventSyncBadPayloadEvent from './events/TestEventSyncBadPayloadEvent'; + describe('mock event service', () => { /** @@ -26,7 +28,7 @@ describe('mock event service', () => { /** * Dispatch a synchronus event */ - test('test dispatch event sync', async () => { + test('test dispatch event sync with valid payload', async () => { const eventService = App.container('events'); @@ -40,4 +42,24 @@ describe('mock event service', () => { }) ).toBeTruthy() }) + + /** + * Dispatch a synchronus event + */ + test('test dispatch event sync with invalid payload', async () => { + + const eventService = App.container('events'); + + eventService.mockEvent(TestEventSyncBadPayloadEvent) + + await eventService.dispatch(new TestEventSyncBadPayloadEvent({ unexpectedProperty: 123 })); + + expect(eventService.assertDispatched(TestEventSyncBadPayloadEvent)).toBeTruthy() + + expect( + eventService.assertDispatched<{ hello: string }>(TestEventSyncBadPayloadEvent, (payload) => { + return payload.hello === 'world' + }) + ).toBeFalsy() + }) }); \ No newline at end of file diff --git a/src/tests/events/events/TestEventSyncBadPayloadEvent.ts b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts new file mode 100644 index 000000000..af89ede87 --- /dev/null +++ b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts @@ -0,0 +1,22 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; + + +class TestEventSyncBadPayloadEvent extends BaseEvent { + + constructor(payload: unknown) { + super(payload as IEventPayload); + } + + async execute(): Promise { + console.log('Executed TestEventSyncBadPayloadEvent', this.getPayload(), this.getName()) + } + + getName(): string { + return 'TestEventSyncBadPayloadEvent' + } + +} + +export default TestEventSyncBadPayloadEvent \ No newline at end of file diff --git a/src/tests/events/events/TestEventSyncEvent.ts b/src/tests/events/events/TestEventSyncEvent.ts index 393a2c057..fa0aec5bb 100644 --- a/src/tests/events/events/TestEventSyncEvent.ts +++ b/src/tests/events/events/TestEventSyncEvent.ts @@ -7,6 +7,8 @@ class TestEventSyncEvent extends BaseEvent { static readonly eventName = 'TestEventSyncEvent'; + protected namespace: string = 'testing'; + constructor(payload: IEventPayload) { super(payload); } diff --git a/src/tests/models/models/TestWorkerModel.ts b/src/tests/models/models/TestWorkerModel.ts index 3c9af895c..ee61e46f1 100644 --- a/src/tests/models/models/TestWorkerModel.ts +++ b/src/tests/models/models/TestWorkerModel.ts @@ -1,6 +1,6 @@ -import WorkerModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerModel"; +import WorkerLegacyModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; -export default class TestWorkerModel extends WorkerModel { +export default class TestWorkerModel extends WorkerLegacyModel { constructor(data: WorkerModelData | null = null) { super(data ?? {} as WorkerModelData) diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 78069fcfd..ac8382c83 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -8,6 +8,10 @@ import TestListener from '@src/tests/events/listeners/TestListener'; import TestSubscriber from '@src/tests/events/subscribers/TestSubscriber'; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; +import TestEventQueueEvent from '../events/events/TestEventQueueEvent'; +import TestEventSyncBadPayloadEvent from '../events/events/TestEventSyncBadPayloadEvent'; +import TestEventSyncEvent from '../events/events/TestEventSyncEvent'; + class TestEventProvider extends EventProvider { protected config: IEventConfig = { @@ -26,6 +30,12 @@ class TestEventProvider extends EventProvider { }) }, + events: [ + TestEventSyncEvent, + TestEventSyncBadPayloadEvent, + TestEventQueueEvent + ], + listeners: EventService.createListeners([ { listener: TestListener, From b4c1f6ac9b23aa36743907ecced749607dc95e27 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Sun, 3 Nov 2024 21:46:25 +0000 Subject: [PATCH 06/31] refactor(events) events queable failed test progress (broken) --- src/config/events.ts | 10 +- .../console/commands/WorkerLegacyCommand.ts | 2 +- src/core/domains/events/base/BaseService.ts | 3 + .../events/DispatchBroadcastEvent.ts | 18 --- .../domains/events/commands/WorkerCommand.ts | 67 +++++++++- .../events/concerns/EventMockableConcern.ts | 19 +++ .../events/concerns/EventWorkerConcern.ts | 124 +++++++++++++++++- .../domains/events/drivers/QueableDriver.ts | 10 +- src/core/domains/events/drivers/SyncDriver.ts | 2 +- .../events/exceptions/EventWorkerException.ts | 8 ++ .../domains/events/interfaces/IBaseEvent.ts | 5 +- .../domains/events/interfaces/IEventDriver.ts | 2 +- .../events/interfaces/IEventService.ts | 11 +- .../events/interfaces/IEventWorkerConcern.ts | 40 ++++++ .../domains/events/interfaces/IQueueName.ts | 3 - .../events/models/FailedWorkerModel.ts | 34 +---- src/core/domains/events/models/WorkerModel.ts | 18 +-- .../domains/events/providers/EventProvider.ts | 6 + .../domains/events/services/EventService.ts | 55 +++++++- src/core/interfaces/concerns/IDispatchable.ts | 2 +- .../interfaces/concerns/IHasAttributes.ts | 4 +- src/tests/events/eventQueable.test.ts | 116 ++++++++++++++++ src/tests/events/eventQueableFailed.test.ts | 78 +++++++++++ ...estEventQueueAddAlwaysFailsEventToQueue.ts | 35 +++++ .../events/TestEventQueueAlwaysFailsEvent.ts | 26 ++++ .../TestEventQueueCalledFromWorkerEvent.ts | 22 ++++ .../events/events/TestEventQueueEvent.ts | 15 +++ .../events/TestEventSyncBadPayloadEvent.ts | 2 + .../events/helpers/createWorketTables.ts | 32 +++++ .../models/models/TestFailedWorkerModel.ts | 11 ++ src/tests/models/models/TestWorkerModel.ts | 9 +- src/tests/providers/TestEventProvider.ts | 15 ++- 32 files changed, 704 insertions(+), 100 deletions(-) delete mode 100644 src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts create mode 100644 src/core/domains/events/exceptions/EventWorkerException.ts create mode 100644 src/core/domains/events/interfaces/IEventWorkerConcern.ts delete mode 100644 src/core/domains/events/interfaces/IQueueName.ts create mode 100644 src/tests/events/eventQueable.test.ts create mode 100644 src/tests/events/eventQueableFailed.test.ts create mode 100644 src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts create mode 100644 src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts create mode 100644 src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts create mode 100644 src/tests/events/helpers/createWorketTables.ts create mode 100644 src/tests/models/models/TestFailedWorkerModel.ts diff --git a/src/config/events.ts b/src/config/events.ts index 395b96664..b302372a6 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -13,8 +13,8 @@ import TestSubscriber from "@src/tests/events/subscribers/TestSubscriber"; * Event Drivers Constants */ export const EVENT_DRIVERS = { - SYNC: 'sync', - QUEABLE: 'queueable' + SYNC: EventService.getDriverName(SyncDriver), + QUEABLE: EventService.getDriverName(QueueableDriver) } export const eventConfig: IEventConfig = { @@ -46,6 +46,9 @@ export const eventConfig: IEventConfig = { }, + /** + * Register Events + */ events: EventService.createEvents([ TestEventQueueEvent, TestEventSyncEvent @@ -53,6 +56,9 @@ export const eventConfig: IEventConfig = { /** * Event Listeners Configuration + * + * These are automatically registered with the event service + * and do not need to be added to 'events' array. */ listeners: EventService.createListeners([ { diff --git a/src/core/domains/console/commands/WorkerLegacyCommand.ts b/src/core/domains/console/commands/WorkerLegacyCommand.ts index cc5c24375..ccd245051 100644 --- a/src/core/domains/console/commands/WorkerLegacyCommand.ts +++ b/src/core/domains/console/commands/WorkerLegacyCommand.ts @@ -7,7 +7,7 @@ export default class WorkerLegacyCommand extends BaseCommand { /** * The signature of the command */ - signature: string = 'worker'; + signature: string = 'worker:legacy'; description = 'Run the worker to process queued event items'; diff --git a/src/core/domains/events/base/BaseService.ts b/src/core/domains/events/base/BaseService.ts index 4d36c415b..d6a866a3b 100644 --- a/src/core/domains/events/base/BaseService.ts +++ b/src/core/domains/events/base/BaseService.ts @@ -3,9 +3,12 @@ import EventMockableConcern from "@src/core/domains/events/concerns/EventMockabl import { ICtor } from "@src/core/interfaces/ICtor"; import compose from "@src/core/util/compose"; +import EventWorkerConcern from "../concerns/EventWorkerConcern"; + const BaseService: ICtor = compose( class {}, HasRegisterableConcern, + EventWorkerConcern, EventMockableConcern, ); diff --git a/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts b/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts deleted file mode 100644 index 317472d6b..000000000 --- a/src/core/domains/events/broadcast/events/DispatchBroadcastEvent.ts +++ /dev/null @@ -1,18 +0,0 @@ -import BroadcastEvent from "@src/core/domains/broadcast/abstract/BroadcastEvent"; -import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; - -class DispatchBroadcastEvent extends BroadcastEvent { - - static readonly eventName: string = "dispatch"; - - constructor(payload: IBaseEvent) { - super(payload); - } - - getName(): string { - return DispatchBroadcastEvent.eventName; - } - -} - -export default DispatchBroadcastEvent \ No newline at end of file diff --git a/src/core/domains/events/commands/WorkerCommand.ts b/src/core/domains/events/commands/WorkerCommand.ts index cf887e1da..0d43ea7ed 100644 --- a/src/core/domains/events/commands/WorkerCommand.ts +++ b/src/core/domains/events/commands/WorkerCommand.ts @@ -1,4 +1,11 @@ +import { EVENT_DRIVERS } from "@src/config/events"; import BaseCommand from "@src/core/domains/console/base/BaseCommand"; +import { App } from "@src/core/services/App"; +import { z } from "zod"; + +import { IEventDriversConfigOption } from "../interfaces/config/IEventDriversConfig"; +import { IEventService } from "../interfaces/IEventService"; +import { TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; export default class WorkerCommand extends BaseCommand { @@ -7,26 +14,76 @@ export default class WorkerCommand extends BaseCommand { */ signature: string = 'worker'; - description = 'Run the worker to process queued event items'; + description = 'Run the worker to process queued event items. --queue=[queue]'; /** * Whether to keep the process alive after command execution */ public keepProcessAlive = true; + protected eventService: IEventService = App.container('events'); + /** * Execute the command */ async execute() { + const options = this.getWorkerOptions(); + + await this.eventService.runWorker(options); + + const intervalId = setInterval(async () => { + await this.eventService.runWorker(options); + App.container('logger').console('Running worker again in '+ options.runAfterSeconds +' seconds') + }, options.runAfterSeconds * 1000) + + if(options.runOnce) { + clearInterval(intervalId); + App.container('logger').console('runOnce enabled. Quitting...'); + return; + } + } + + /** + * Gets the worker options from the CLI arguments or the default value. + * @returns The worker options. + */ + private getWorkerOptions(): TEventWorkerOptions { + const driverName = this.getArguementByKey('driver')?.value ?? EVENT_DRIVERS.QUEABLE; + const queueName = this.getArguementByKey('queue')?.value ?? 'default'; + const options = this.eventService.getDriverOptionsByName(driverName)?.options; + this.validateOptions(driverName, options); - // setInterval(async () => { - // await worker.work() - // App.container('logger').console('Running worker again in ' + worker.options.runAfterSeconds.toString() + ' seconds') - // }, worker.options.runAfterSeconds * 1000) + return { ...options, queueName } as TEventWorkerOptions; } + /** + * Validates the options for the worker + * @param driverName The name of the driver + * @param options The options to validate + * @throws {Error} If the options are invalid + * @private + */ + private validateOptions(driverName: string, options: IEventDriversConfigOption['options'] | undefined) { + if(!options) { + throw new Error('Could not find options for driver: '+ driverName); + } + + const schema = z.object({ + retries: z.number(), + runAfterSeconds: z.number(), + runOnce: z.boolean().optional(), + workerModelCtor: z.any(), + failedWorkerModelCtor: z.any(), + }) + + const parsedResult = schema.safeParse(options) + + if(!parsedResult.success) { + throw new Error('Invalid worker options: '+ parsedResult.error.message); + } + } } \ No newline at end of file diff --git a/src/core/domains/events/concerns/EventMockableConcern.ts b/src/core/domains/events/concerns/EventMockableConcern.ts index 90c5b7d8d..8a640a8b1 100644 --- a/src/core/domains/events/concerns/EventMockableConcern.ts +++ b/src/core/domains/events/concerns/EventMockableConcern.ts @@ -23,6 +23,16 @@ const EventMockableConcern = (Base: ICtor) => { */ mockEvent(event: ICtor): void { this.mockEvents.push(event) + this.removeMockEventDispatched(event) + } + + /** + * Removes the given event from the {@link mockEvents} array. + * + * @param event - The event to remove from the {@link mockEvents} array. + */ + removeMockEvent(event: ICtor): void { + this.mockEvents = this.mockEvents.filter(e => (new e).getName() !== (new event).getName()) } /** @@ -43,6 +53,15 @@ const EventMockableConcern = (Base: ICtor) => { this.mockEventsDispatched.push(event) } + /** + * Removes all events from the {@link mockEventsDispatched} array that match the given event constructor. + * + * @param event - The event to remove from the {@link mockEventsDispatched} array. + */ + removeMockEventDispatched(event: ICtor): void { + this.mockEventsDispatched = this.mockEventsDispatched.filter(e => e.getName() !== (new event).getName()) + } + /** * Asserts that a specific event has been dispatched and that its payload satisfies the given condition. diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts index 014272027..5e193cc2b 100644 --- a/src/core/domains/events/concerns/EventWorkerConcern.ts +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -1,9 +1,129 @@ +import Repository from "@src/core/base/Repository"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { App } from "@src/core/services/App"; + +import EventWorkerException from "../exceptions/EventWorkerException"; +import { IEventPayload } from "../interfaces/IEventPayload"; +import { IEventWorkerConcern, IWorkerModel, TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; const EventWorkerConcern = (Base: ICtor) => { - return class EventWorkerConcern extends Base { + return class EventWorkerConcern extends Base implements IEventWorkerConcern { + + /** + * Run the worker to process queued event items + * + * Fetches documents from the worker model repository and runs each + * document through the handleWorkerModel method. This method is + * responsible for processing the event contained in the document. + * + * @param options The options to use when running the worker + * @returns A promise that resolves once the worker has finished + * processing the documents. + */ + async runWorker(options: TEventWorkerOptions): Promise { + + const workerModels = await this.fetchWorkerModelDocuments(options) + + App.container('logger').console('Queued items: ', workerModels.length) + + if(workerModels.length === 0) { + App.container('logger').console("No queued items"); + return; + } + + for(const workerModel of workerModels) { + await this.handleWorkerModel(workerModel, options) + } + } + + /** + * Handles a single worker model document + * @param workerModel The worker model document to process + * @param options The options to use when processing the event + * @private + */ + private async handleWorkerModel(workerModel: IWorkerModel, options: TEventWorkerOptions): Promise { + try { + const eventName = workerModel.getAttribute('eventName'); + + if(typeof eventName !== 'string') { + throw new EventWorkerException('Event name must be a string'); + } + + const eventCtor = App.container('events').getEventCtorByName(eventName) - // todo read and process events from db + if(!eventCtor) { + throw new EventWorkerException(`Event '${eventName}' not found`); + } + + const payload = workerModel.getPayload() + + const eventInstance = new eventCtor(payload); + await eventInstance.execute(); + + await workerModel.delete(); + } + catch (err) { + App.container('logger').error(err) + await this.handleUpdateWorkerModelAttempts(workerModel, options) + } + } + + /** + * Handles updating the worker model document with the number of attempts + * it has made to process the event. + * @param workerModel The worker model document to update + * @param options The options to use when updating the worker model document + * @private + */ + private async handleUpdateWorkerModelAttempts(workerModel: IWorkerModel, options: TEventWorkerOptions) { + + const attempt = workerModel.getAttribute('attempt') ?? 0 + const retries = workerModel.getAttribute('retries') ?? 0 + + if(attempt >= retries) { + await this.handleFailedWorkerModel(workerModel, options) + return; + } + + await workerModel.attr('attempt', attempt + 1) + await workerModel.save(); + } + + /** + * Handles a worker model that has failed to process. + * + * Saves a new instance of the failed worker model to the database + * and deletes the original worker model document. + * + * @param workerModel The worker model document to handle + * @param options The options to use when handling the failed worker model + * @private + */ + private async handleFailedWorkerModel(workerModel: IWorkerModel, options: TEventWorkerOptions) { + const FailedWorkerModel = new options.failedWorkerModelCtor({ + eventName: workerModel.getAttribute('eventName'), + queueName: workerModel.getAttribute('queueName'), + payload: workerModel.getAttribute('payload') ?? '{}', + error: '', + failedAt: new Date() + }) + await FailedWorkerModel.save(); + await workerModel.delete(); + } + + /** + * Fetches worker model documents + */ + private async fetchWorkerModelDocuments(options: TEventWorkerOptions): Promise { + return await new Repository(options.workerModelCtor).findMany({ + queueName: options.queueName, + }, { + sort: { + createdAt: 'asc' + } + }) + } } } diff --git a/src/core/domains/events/drivers/QueableDriver.ts b/src/core/domains/events/drivers/QueableDriver.ts index ae33b23c2..2646c1389 100644 --- a/src/core/domains/events/drivers/QueableDriver.ts +++ b/src/core/domains/events/drivers/QueableDriver.ts @@ -5,6 +5,7 @@ import { z } from "zod"; import EventDriverException from "../exceptions/EventDriverException"; import { IBaseEvent } from "../interfaces/IBaseEvent"; +import { IWorkerModel, TFailedWorkerModelData } from "../interfaces/IEventWorkerConcern"; export type TQueueDriverOptions = { @@ -32,12 +33,12 @@ export type TQueueDriverOptions = { /** * Constructor for the Worker model */ - workerModelCtor: ICtor; + workerModelCtor: ICtor; /** * Constructor for the Worker model for failed events */ - failedWorkerModelCtor: ICtor; + failedWorkerModelCtor: ICtor>; /** * Run the worker only once, defaults to false @@ -66,7 +67,7 @@ class QueueableDriver extends BaseDriver { this.validateOptions(options) - this.updateWorkerQueueTable(options as TQueueDriverOptions, event) + await this.updateWorkerQueueTable(options as TQueueDriverOptions, event) } /** @@ -85,7 +86,8 @@ class QueueableDriver extends BaseDriver { const workerModel = new options.workerModelCtor({ queueName: event.getQueueName(), eventName: event.getName(), - payload: JSON.stringify(event.getPayload()), + retries: options.retries, + payload: JSON.stringify(event.getPayload() ?? {}), }) await workerModel.save(); } diff --git a/src/core/domains/events/drivers/SyncDriver.ts b/src/core/domains/events/drivers/SyncDriver.ts index dfd8c6485..a8a5db46f 100644 --- a/src/core/domains/events/drivers/SyncDriver.ts +++ b/src/core/domains/events/drivers/SyncDriver.ts @@ -11,7 +11,7 @@ class SyncDriver extends BaseDriver { getName(): string { return EVENT_DRIVERS.SYNC; } - + } export default SyncDriver \ No newline at end of file diff --git a/src/core/domains/events/exceptions/EventWorkerException.ts b/src/core/domains/events/exceptions/EventWorkerException.ts new file mode 100644 index 000000000..458c2cd07 --- /dev/null +++ b/src/core/domains/events/exceptions/EventWorkerException.ts @@ -0,0 +1,8 @@ +export default class EventWorkerException extends Error { + + constructor(message: string = 'Event Worker Exception') { + super(message); + this.name = 'EventWorkerException'; + } + +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index 17091c7ac..1ec41abdf 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -5,10 +5,9 @@ import { IExecutable } from "@src/core/interfaces/concerns/IExecutable"; import { INameable } from "@src/core/interfaces/concerns/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { IQueueName } from "./IQueueName"; - -export interface IBaseEvent extends INameable, IExecutable, IQueueName +export interface IBaseEvent extends INameable, IExecutable { + getQueueName(): string; getEventService(): IEventService; getDriverCtor(): ICtor; getPayload(): T; diff --git a/src/core/domains/events/interfaces/IEventDriver.ts b/src/core/domains/events/interfaces/IEventDriver.ts index 88970b4a1..1c2bc856f 100644 --- a/src/core/domains/events/interfaces/IEventDriver.ts +++ b/src/core/domains/events/interfaces/IEventDriver.ts @@ -1,5 +1,5 @@ -import IDispatchable from "@src/core/domains/events-legacy/interfaces/IDispatchable"; +import { IDispatchable } from "@src/core/interfaces/concerns/IDispatchable"; import { INameable } from "@src/core/interfaces/concerns/INameable"; export default interface IEventDriver extends INameable, IDispatchable {} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts index 8a1240092..e49f308d9 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -9,8 +9,13 @@ import { IMockableConcern } from "@src/core/domains/events/interfaces/IMockableC import { IHasRegisterableConcern } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IMockableConcern +import { IEventConfig } from "./config/IEventConfig"; +import { IEventWorkerConcern } from "./IEventWorkerConcern"; + +export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IEventWorkerConcern, IMockableConcern { + getConfig(): IEventConfig; + registerEvent(event: ICtor): void; registerDriver(driverConfig: IEventDriversConfigOption): void; @@ -21,5 +26,9 @@ export interface IEventService extends IHasRegisterableConcern, IHasDispatcherCo getDriverOptions(driver: IEventDriver): IEventDriversConfigOption | undefined; + getDriverOptionsByName(driverName: string): IEventDriversConfigOption | undefined; + + getEventCtorByName(eventName: string): ICtor | undefined; + getSubscribers(eventName: string): ICtor[]; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventWorkerConcern.ts b/src/core/domains/events/interfaces/IEventWorkerConcern.ts new file mode 100644 index 000000000..e2bdeaf04 --- /dev/null +++ b/src/core/domains/events/interfaces/IEventWorkerConcern.ts @@ -0,0 +1,40 @@ +/* eslint-disable no-unused-vars */ +import { ICtor } from "@src/core/interfaces/ICtor"; +import { IModel } from "@src/core/interfaces/IModel"; + + +export type TWorkerModelData = { + queueName: string; + eventName: string; + payload: any; + attempt: number; + retries: number; + createdAt: Date; +} + +export type TFailedWorkerModel = IModel; + +export type TFailedWorkerModelData = { + eventName: string; + queueName: string; + payload: string; + error: string; + failedAt: Date; +} + +export interface IWorkerModel extends IModel { + getPayload(): T | null; +} + +export type TEventWorkerOptions = { + queueName: string; + retries: number; + runAfterSeconds: number; + workerModelCtor: ICtor + failedWorkerModelCtor: ICtor; + runOnce?: boolean; +} + +export interface IEventWorkerConcern { + runWorker(options: TEventWorkerOptions): Promise; +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IQueueName.ts b/src/core/domains/events/interfaces/IQueueName.ts deleted file mode 100644 index 58f3e6f6f..000000000 --- a/src/core/domains/events/interfaces/IQueueName.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IQueueName { - getQueueName(): string; -} \ No newline at end of file diff --git a/src/core/domains/events/models/FailedWorkerModel.ts b/src/core/domains/events/models/FailedWorkerModel.ts index 99e3ef778..81dfc72d4 100644 --- a/src/core/domains/events/models/FailedWorkerModel.ts +++ b/src/core/domains/events/models/FailedWorkerModel.ts @@ -1,38 +1,10 @@ import Model from "@src/core/base/Model"; -import IModelAttributes from "@src/core/interfaces/IModelData"; -/** - * Represents a failed worker model. - * - * @interface FailedWorkerModelData - * @extends IModelAttributes - */ -export interface FailedWorkerModelData extends IModelAttributes { - - /** - * The name of the event that failed. - */ - eventName: string; - - /** - * The payload of the event that failed. - */ - payload: any; +import { TFailedWorkerModelData } from "../interfaces/IEventWorkerConcern"; - /** - * The error that caused the event to fail. - */ - error: any; - /** - * The date when the event failed. - */ - failedAt: Date; -} +export interface FailedWorkerModelData extends TFailedWorkerModelData {} -/** - * Initial data for a failed worker model. - */ export const initialFailedWorkerModalData = { eventName: '', payload: null, @@ -69,7 +41,7 @@ export default class FailedWorkerModel extends Model { * @param {FailedWorkerModelData} data - The data for the model. */ constructor(data: FailedWorkerModelData) { - super(data); + super({ ...initialFailedWorkerModalData, ...data }); } } diff --git a/src/core/domains/events/models/WorkerModel.ts b/src/core/domains/events/models/WorkerModel.ts index 88d47a2d4..ea6e732b6 100644 --- a/src/core/domains/events/models/WorkerModel.ts +++ b/src/core/domains/events/models/WorkerModel.ts @@ -1,14 +1,8 @@ import Model from "@src/core/base/Model"; -import IModelAttributes from "@src/core/interfaces/IModelData"; -export interface WorkerModelData extends IModelAttributes { - queueName: string; - eventName: string; - payload: any; - attempt: number; - retries: number; - createdAt: Date; -} +import { IWorkerModel, TWorkerModelData } from "../interfaces/IEventWorkerConcern"; + +export interface WorkerModelData extends TWorkerModelData {} export const initialWorkerModalData = { queueName: '', @@ -27,7 +21,7 @@ export const initialWorkerModalData = { * @class WorkerModel * @extends Model */ -export default class WorkerModel extends Model { +export default class WorkerModel extends Model implements IWorkerModel { table: string = 'worker_queue'; @@ -65,10 +59,10 @@ export default class WorkerModel extends Model { super({...initialWorkerModalData, ...data}); } - public getPayload(): unknown { + getPayload(): T | null { try { const payload = this.getAttribute('payload'); - return JSON.parse(payload) + return JSON.parse(payload) as T } // eslint-disable-next-line no-unused-vars catch (err) { diff --git a/src/core/domains/events/providers/EventProvider.ts b/src/core/domains/events/providers/EventProvider.ts index 64ae92b59..275609038 100644 --- a/src/core/domains/events/providers/EventProvider.ts +++ b/src/core/domains/events/providers/EventProvider.ts @@ -5,6 +5,8 @@ import { IEventService } from "@src/core/domains/events/interfaces/IEventService import EventService from "@src/core/domains/events/services/EventService"; import { App } from "@src/core/services/App"; +import WorkerCommand from "../commands/WorkerCommand"; + class EventProvider extends BaseProvider { protected config: IEventConfig = eventConfig; @@ -18,6 +20,10 @@ class EventProvider extends BaseProvider { this.registerListeners(eventService); App.setContainer('events', eventService); + + App.container('console').register().registerAll([ + WorkerCommand + ]) } async boot(): Promise {} diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index d957de566..e9c2fdc68 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -11,6 +11,7 @@ import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import EventDispatchException from "../exceptions/EventDispatchException"; +import { TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; class EventService extends BaseService implements IEventService { @@ -31,6 +32,15 @@ class EventService extends BaseService implements IEventService { this.config = config; } + /** + * Retrieves the name of the event driver from its constructor. + * @param driver The constructor of the event driver. + * @returns The name of the event driver as a string. + */ + public static getDriverName(driver: ICtor): string { + return driver.name + } + /** * @param driverCtor The event driver class. * @param options The event driver options. @@ -60,7 +70,14 @@ class EventService extends BaseService implements IEventService { } /** - * Declare HasRegisterableConcern methods. + * @returns The current event configuration as an instance of IEventConfig. + */ + getConfig(): IEventConfig { + return this.config + } + + /** + * Declare HasRegisterableConcern methods. */ declare register: (key: string, value: unknown) => void; @@ -81,7 +98,12 @@ class EventService extends BaseService implements IEventService { declare mockEventDispatched: (event: IBaseEvent) => void; - declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean + declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean + + /** + * Delcare EventWorkerConcern methods. + */ + declare runWorker: (options: TEventWorkerOptions) => Promise; /** * Dispatch an event using its registered driver. @@ -93,11 +115,13 @@ class EventService extends BaseService implements IEventService { throw new EventDispatchException(`Event '${event.getName()}' not registered. The event must be added to the \`events\` array in the config. See @src/config/events.ts`) } + // Mock the dispatch before dispatching the event, as any errors thrown during the dispatch will not be caught + this.mockEventDispatched(event) + const eventDriverCtor = event.getDriverCtor() const eventDriver = new eventDriverCtor(this) await eventDriver.dispatch(event) - this.mockEventDispatched(event) } /** @@ -127,7 +151,7 @@ class EventService extends BaseService implements IEventService { * @param driverConfig the driver configuration */ registerDriver(driverConfig: IEventDriversConfigOption): void { - const driverIdentifier = driverConfig.driverCtor.name + const driverIdentifier = EventService.getDriverName(driverConfig.driverCtor) this.registerByList( EventService.REGISTERED_DRIVERS, @@ -144,7 +168,6 @@ class EventService extends BaseService implements IEventService { registerListener(listenerConfig: TListenersConfigOption): void { const listenerIdentifier = listenerConfig.listener.name - // Update registered listeners this.registerByList( EventService.REGISTERED_LISTENERS, @@ -192,6 +215,28 @@ class EventService extends BaseService implements IEventService { return driverConfig ?? undefined } + /** + * Retrieves the configuration options for a given event driver by name. + * @param driverName The name of the event driver. + * @returns The configuration options for the specified event driver, or undefined if not found. + */ + getDriverOptionsByName(driverName: string): IEventDriversConfigOption | undefined { + const registeredDrivers = this.getRegisteredByList>(EventService.REGISTERED_DRIVERS); + const driverConfig = registeredDrivers. get(driverName)?.[0]; + + return driverConfig ?? undefined + } + + /** + * Retrieves the event constructor for a given event name. + * @param eventName The name of the event. + * @returns The event constructor for the specified event, or undefined if not found. + */ + getEventCtorByName(eventName: string): ICtor | undefined { + const registeredEvents = this.getRegisteredByList>>(EventService.REGISTERED_EVENTS); + return registeredEvents.get(eventName)?.[0] + } + /** * Returns an array of event subscriber constructors that are listening to this event. * @returns An array of event subscriber constructors. diff --git a/src/core/interfaces/concerns/IDispatchable.ts b/src/core/interfaces/concerns/IDispatchable.ts index 6643f0b86..cb491410d 100644 --- a/src/core/interfaces/concerns/IDispatchable.ts +++ b/src/core/interfaces/concerns/IDispatchable.ts @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ export interface IDispatchable { - dispatch(...arg: any[]): Promise; + dispatch(...args: unknown[]): Promise; } \ No newline at end of file diff --git a/src/core/interfaces/concerns/IHasAttributes.ts b/src/core/interfaces/concerns/IHasAttributes.ts index 003ab3b8c..51ab2b34f 100644 --- a/src/core/interfaces/concerns/IHasAttributes.ts +++ b/src/core/interfaces/concerns/IHasAttributes.ts @@ -12,11 +12,11 @@ export interface IHasAttributes(key: K, value?: unknown): Attributes[K] | null | undefined; setAttribute(key: keyof Attributes, value?: unknown): Promise; - getAttribute(key: keyof Attributes): Attributes[keyof Attributes] | null + getAttribute(key: K): Attributes[K] | null getAttributes(...args: any[]): Attributes | null; diff --git a/src/tests/events/eventQueable.test.ts b/src/tests/events/eventQueable.test.ts new file mode 100644 index 000000000..b987e2e28 --- /dev/null +++ b/src/tests/events/eventQueable.test.ts @@ -0,0 +1,116 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; +import EventService from '@src/core/domains/events/services/EventService'; +import { IModel } from '@src/core/interfaces/IModel'; +import Kernel from '@src/core/Kernel'; +import { App } from '@src/core/services/App'; +import testAppConfig from '@src/tests/config/testConfig'; +import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestEventProvider from '@src/tests/providers/TestEventProvider'; + +import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; +import TestWorkerModel from '../models/models/TestWorkerModel'; +import TestDatabaseProvider from '../providers/TestDatabaseProvider'; +import TestEventQueueAddAlwaysFailsEventToQueue from './events/TestEventQueueAddAlwaysFailsEventToQueue'; +import TestEventQueueAlwaysFailsEvent from './events/TestEventQueueAlwaysFailsEvent'; +import TestEventQueueEvent from './events/TestEventQueueEvent'; +import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; + + +describe('mock queable event', () => { + + /** + * Register the test event provider + */ + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + new TestConsoleProvider(), + new TestDatabaseProvider(), + new TestEventProvider() + ] + }, {}) + }) + + afterAll(async () => { + // await dropWorkerTables(); + }) + + + /** + * - Dispatch TestEventQueueEvent, this will add a queued item to the database + * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent + * - Check the events have been dispatched + * - Check the worker empty has been cleared + */ + // test('test queued worker ', async () => { + + // await dropWorkerTables(); + // await createWorkerTables(); + + // const eventService = App.container('events'); + + // eventService.mockEvent(TestEventQueueEvent) + // eventService.mockEvent(TestEventQueueCalledFromWorkerEvent); + + // await eventService.dispatch(new TestEventQueueEvent({ hello: 'world' })); + + // expect( + // eventService.assertDispatched<{ hello: string }>(TestEventQueueEvent, (payload) => { + // return payload.hello === 'world' + // }) + // ).toBeTruthy() + + // // run the worker + // await App.container('console').reader(['worker', '--queue-name=testQueue']).handle(); + + // expect( + // eventService.assertDispatched<{ hello: string }>(TestEventQueueCalledFromWorkerEvent, (payload) => { + // return payload.hello === 'world' + // }) + // ).toBeTruthy() + + + // const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) + // expect(results.length).toBe(0) + // }) + + /** + * - Dispatch TestEventQueueEvent, this will add a queued item to the database + * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent + * - Check the events have been dispatched + * - Check the worker empty has been cleared + */ + test('test dispatch event queable ', async () => { + + await dropWorkerTables(); + await createWorkerTables(); + + const eventService = App.container('events'); + const driverOptions = eventService.getDriverOptionsByName(EventService.getDriverName(QueueableDriver))?.['options'] as TQueueDriverOptions; + const attempts = driverOptions?.retries ?? 3 + + eventService.mockEvent(TestEventQueueAddAlwaysFailsEventToQueue) + + await eventService.dispatch(new TestEventQueueAddAlwaysFailsEventToQueue()); + + expect(eventService.assertDispatched(TestEventQueueEvent)).toBeTruthy() + + for(let i = 0; i < attempts; i++) { + eventService.mockEvent(TestEventQueueAlwaysFailsEvent); + + await App.container('console').reader(['worker', '--queue-name=testQueue']).handle(); + + expect(eventService.assertDispatched(TestEventQueueAlwaysFailsEvent)).toBeTruthy() + } + + const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) + expect(results.length).toBe(0) + + const failedResults = await App.container('db').documentManager().table(new TestFailedWorkerModel().table).findMany({}) + expect(failedResults.length).toBe(1) + }) +}); \ No newline at end of file diff --git a/src/tests/events/eventQueableFailed.test.ts b/src/tests/events/eventQueableFailed.test.ts new file mode 100644 index 000000000..f4414436e --- /dev/null +++ b/src/tests/events/eventQueableFailed.test.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; +import EventService from '@src/core/domains/events/services/EventService'; +import { IModel } from '@src/core/interfaces/IModel'; +import Kernel from '@src/core/Kernel'; +import { App } from '@src/core/services/App'; +import testAppConfig from '@src/tests/config/testConfig'; +import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestEventProvider from '@src/tests/providers/TestEventProvider'; + +import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; +import TestWorkerModel from '../models/models/TestWorkerModel'; +import TestDatabaseProvider from '../providers/TestDatabaseProvider'; +import TestEventQueueAddAlwaysFailsEventToQueue from './events/TestEventQueueAddAlwaysFailsEventToQueue'; +import TestEventQueueAlwaysFailsEvent from './events/TestEventQueueAlwaysFailsEvent'; +import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; + + +describe('mock queable event failed', () => { + + /** + * Register the test event provider + */ + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + new TestConsoleProvider(), + new TestDatabaseProvider(), + new TestEventProvider() + ] + }, {}) + }) + + afterAll(async () => { + // await dropWorkerTables(); + }) + + + /** + * - Dispatch TestEventQueueEvent, this will add a queued item to the database + * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent + * - Check the events have been dispatched + * - Check the worker empty has been cleared + */ + test('test dispatch event queable ', async () => { + + await dropWorkerTables(); + await createWorkerTables(); + + const eventService = App.container('events'); + const driverOptions = eventService.getDriverOptionsByName(EventService.getDriverName(QueueableDriver))?.['options'] as TQueueDriverOptions; + const attempts = driverOptions?.retries ?? 3 + + eventService.mockEvent(TestEventQueueAddAlwaysFailsEventToQueue) + + await eventService.dispatch(new TestEventQueueAddAlwaysFailsEventToQueue()); + + expect(eventService.assertDispatched(TestEventQueueAddAlwaysFailsEventToQueue)).toBeTruthy() + + for(let i = 0; i < attempts; i++) { + App.container('events').mockEvent(TestEventQueueAlwaysFailsEvent); + + // todo: missing await within this logic I think + await App.container('console').reader(['worker', '--queue=testQueue']).handle(); + + expect(App.container('events').assertDispatched(TestEventQueueAlwaysFailsEvent)).toBeTruthy() + } + + const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) + expect(results.length).toBe(0) + + const failedResults = await App.container('db').documentManager().table(new TestFailedWorkerModel().table).findMany({}) + expect(failedResults.length).toBe(1) + }) +}); \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts new file mode 100644 index 000000000..1a56b0bd5 --- /dev/null +++ b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts @@ -0,0 +1,35 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; +import { App } from "@src/core/services/App"; + +import TestEventQueueAlwaysFailsEvent from "./TestEventQueueAlwaysFailsEvent"; + + +class TestEventQueueAddAlwaysFailsEventToQueue extends BaseEvent { + + protected namespace: string = 'testing'; + + static readonly eventName = 'TestEventQueueAddAlwaysFailsEventToQueue'; + + constructor() { + super(null, QueueableDriver) + } + + getQueueName(): string { + return 'testQueue'; + } + + getName(): string { + return TestEventQueueAddAlwaysFailsEventToQueue.eventName; + } + + async execute(...args: any[]): Promise { + console.log('Executed TestEventQueueAddAlwaysFailsEventToQueue', this.getPayload(), this.getName(), {args}) + + await App.container('events').dispatch(new TestEventQueueAlwaysFailsEvent()) + } + +} + +export default TestEventQueueAddAlwaysFailsEventToQueue \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts b/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts new file mode 100644 index 000000000..9c7cda7d6 --- /dev/null +++ b/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts @@ -0,0 +1,26 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; + + +class TestEventQueueAlwaysFailsEvent extends BaseEvent { + + protected namespace: string = 'testing'; + + static readonly eventName = 'TestEventQueueAlwaysFailsEvent'; + + getQueueName(): string { + return 'testQueue'; + } + + getName(): string { + return TestEventQueueAlwaysFailsEvent.eventName; + } + + async execute(...args: any[]): Promise { + console.log('Executed TestEventQueueAlwaysFailsEvent', this.getPayload(), this.getName(), {args}) + throw new Error('Always fails'); + } + +} + +export default TestEventQueueAlwaysFailsEvent \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts new file mode 100644 index 000000000..6b1a262eb --- /dev/null +++ b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts @@ -0,0 +1,22 @@ + +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; + +class TestEventQueueCalledFromWorkerEvent extends BaseEvent { + + protected namespace: string = 'testing'; + + static readonly eventName = 'TestEventQueueCalledFromWorkerEvent'; + + constructor(payload: IEventPayload) { + super(payload, SyncDriver) + } + + getName(): string { + return TestEventQueueCalledFromWorkerEvent.eventName; + } + +} + +export default TestEventQueueCalledFromWorkerEvent \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueEvent.ts b/src/tests/events/events/TestEventQueueEvent.ts index 66cb9bed4..9c0721fb3 100644 --- a/src/tests/events/events/TestEventQueueEvent.ts +++ b/src/tests/events/events/TestEventQueueEvent.ts @@ -2,19 +2,34 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { App } from "@src/core/services/App"; + +import TestEventQueueCalledFromWorkerEvent from "./TestEventQueueCalledFromWorkerEvent"; class TestEventQueueEvent extends BaseEvent { + protected namespace: string = 'testing'; + static readonly eventName = 'TestEventQueueEvent'; constructor(payload: IEventPayload) { super(payload, QueueableDriver) } + getQueueName(): string { + return 'testQueue'; + } + getName(): string { return TestEventQueueEvent.eventName; } + async execute(...args: any[]): Promise { + console.log('Executed TestEventQueueEvent', this.getPayload(), this.getName(), {args}) + + App.container('events').dispatch(new TestEventQueueCalledFromWorkerEvent(this.getPayload())) + } + } export default TestEventQueueEvent \ No newline at end of file diff --git a/src/tests/events/events/TestEventSyncBadPayloadEvent.ts b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts index af89ede87..8b874bb45 100644 --- a/src/tests/events/events/TestEventSyncBadPayloadEvent.ts +++ b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts @@ -5,6 +5,8 @@ import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload class TestEventSyncBadPayloadEvent extends BaseEvent { + protected namespace: string = 'testing'; + constructor(payload: unknown) { super(payload as IEventPayload); } diff --git a/src/tests/events/helpers/createWorketTables.ts b/src/tests/events/helpers/createWorketTables.ts new file mode 100644 index 000000000..9d751143c --- /dev/null +++ b/src/tests/events/helpers/createWorketTables.ts @@ -0,0 +1,32 @@ +import { App } from "@src/core/services/App"; +import TestFailedWorkerModel from "@src/tests/models/models/TestFailedWorkerModel"; +import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; +import { DataTypes } from "sequelize"; + +export const dropWorkerTables = async () => { + await App.container('db').schema().dropTable((new TestWorkerModel).table); + + await App.container('db').schema().dropTable((new TestFailedWorkerModel).table); +} + +export const createWorkerTables = async () => { + + await App.container('db').schema().createTable((new TestWorkerModel).table, { + queueName: DataTypes.STRING, + eventName: DataTypes.STRING, + payload: DataTypes.JSON, + attempt: DataTypes.INTEGER, + retries: DataTypes.INTEGER, + createdAt: DataTypes.DATE + }); + + await App.container('db').schema().createTable((new TestFailedWorkerModel).table, { + queueName: DataTypes.STRING, + eventName: DataTypes.STRING, + payload: DataTypes.JSON, + error: DataTypes.STRING, + failedAt: DataTypes.DATE + }) +} + +export default createWorkerTables \ No newline at end of file diff --git a/src/tests/models/models/TestFailedWorkerModel.ts b/src/tests/models/models/TestFailedWorkerModel.ts new file mode 100644 index 000000000..dda8a7459 --- /dev/null +++ b/src/tests/models/models/TestFailedWorkerModel.ts @@ -0,0 +1,11 @@ +import { TFailedWorkerModelData } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; +import FailedWorkerModel from "@src/core/domains/events/models/FailedWorkerModel"; + +export default class TestFailedWorkerModel extends FailedWorkerModel { + + constructor(data: TFailedWorkerModelData | null = null) { + super(data ?? {} as TFailedWorkerModelData) + this.table = 'testsWorkerFailed' + } + +} \ No newline at end of file diff --git a/src/tests/models/models/TestWorkerModel.ts b/src/tests/models/models/TestWorkerModel.ts index ee61e46f1..5e98dbee7 100644 --- a/src/tests/models/models/TestWorkerModel.ts +++ b/src/tests/models/models/TestWorkerModel.ts @@ -1,9 +1,10 @@ -import WorkerLegacyModel, { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; +import { TWorkerModelData } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; +import WorkerModel from "@src/core/domains/events/models/WorkerModel"; -export default class TestWorkerModel extends WorkerLegacyModel { +export default class TestWorkerModel extends WorkerModel { - constructor(data: WorkerModelData | null = null) { - super(data ?? {} as WorkerModelData) + constructor(data: TWorkerModelData | null = null) { + super(data ?? {} as TWorkerModelData) this.table = 'testsWorker' } diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index ac8382c83..4e197e9aa 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -1,5 +1,5 @@ import { EVENT_DRIVERS } from '@src/config/events'; -import QueueableDriver from '@src/core/domains/events/drivers/QueableDriver'; +import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; import SyncDriver from '@src/core/domains/events/drivers/SyncDriver'; import { IEventConfig } from '@src/core/domains/events/interfaces/config/IEventConfig'; import EventProvider from '@src/core/domains/events/providers/EventProvider'; @@ -8,9 +8,13 @@ import TestListener from '@src/tests/events/listeners/TestListener'; import TestSubscriber from '@src/tests/events/subscribers/TestSubscriber'; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; +import TestEventQueueAddAlwaysFailsEventToQueue from '../events/events/TestEventQueueAddAlwaysFailsEventToQueue'; +import TestEventQueueAlwaysFailsEvent from '../events/events/TestEventQueueAlwaysFailsEvent'; +import TestEventQueueCalledFromWorkerEvent from '../events/events/TestEventQueueCalledFromWorkerEvent'; import TestEventQueueEvent from '../events/events/TestEventQueueEvent'; import TestEventSyncBadPayloadEvent from '../events/events/TestEventSyncBadPayloadEvent'; import TestEventSyncEvent from '../events/events/TestEventSyncEvent'; +import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; class TestEventProvider extends EventProvider { @@ -20,11 +24,11 @@ class TestEventProvider extends EventProvider { drivers: { [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), - [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { queueName: 'testQueue', retries: 3, - failedCollection: 'testFailedWorkers', runAfterSeconds: 0, + failedWorkerModelCtor: TestFailedWorkerModel, workerModelCtor: TestWorkerModel, runOnce: true }) @@ -33,7 +37,10 @@ class TestEventProvider extends EventProvider { events: [ TestEventSyncEvent, TestEventSyncBadPayloadEvent, - TestEventQueueEvent + TestEventQueueEvent, + TestEventQueueCalledFromWorkerEvent, + TestEventQueueAddAlwaysFailsEventToQueue, + TestEventQueueAlwaysFailsEvent ], listeners: EventService.createListeners([ From c89f4b92bf17e8d9c7e8d862856bcb48791b2ef5 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Mon, 4 Nov 2024 11:33:14 +0000 Subject: [PATCH 07/31] Fixed tests failing --- .../domains/events/commands/WorkerCommand.ts | 1 - .../events/concerns/EventWorkerConcern.ts | 9 +- src/tests/events/eventQueable.test.ts | 116 ------------------ src/tests/events/eventQueableSuccess.test.ts | 77 ++++++++++++ 4 files changed, 84 insertions(+), 119 deletions(-) delete mode 100644 src/tests/events/eventQueable.test.ts create mode 100644 src/tests/events/eventQueableSuccess.test.ts diff --git a/src/core/domains/events/commands/WorkerCommand.ts b/src/core/domains/events/commands/WorkerCommand.ts index 0d43ea7ed..aca05ebb4 100644 --- a/src/core/domains/events/commands/WorkerCommand.ts +++ b/src/core/domains/events/commands/WorkerCommand.ts @@ -40,7 +40,6 @@ export default class WorkerCommand extends BaseCommand { if(options.runOnce) { clearInterval(intervalId); App.container('logger').console('runOnce enabled. Quitting...'); - return; } } diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts index 5e193cc2b..fc191f509 100644 --- a/src/core/domains/events/concerns/EventWorkerConcern.ts +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -79,14 +79,18 @@ const EventWorkerConcern = (Base: ICtor) => { private async handleUpdateWorkerModelAttempts(workerModel: IWorkerModel, options: TEventWorkerOptions) { const attempt = workerModel.getAttribute('attempt') ?? 0 + const newAttempt = attempt + 1 const retries = workerModel.getAttribute('retries') ?? 0 - if(attempt >= retries) { + console.log('Handle update attempts', {attempt, newAttempt, retries}) + + await workerModel.attr('attempt', attempt + 1) + + if(newAttempt >= retries) { await this.handleFailedWorkerModel(workerModel, options) return; } - await workerModel.attr('attempt', attempt + 1) await workerModel.save(); } @@ -101,6 +105,7 @@ const EventWorkerConcern = (Base: ICtor) => { * @private */ private async handleFailedWorkerModel(workerModel: IWorkerModel, options: TEventWorkerOptions) { + console.log('handle failed worker model', {eventName: workerModel.getAttribute('eventName'), queueName: workerModel.getAttribute('queueName'), payload: workerModel.getAttribute('payload')}) const FailedWorkerModel = new options.failedWorkerModelCtor({ eventName: workerModel.getAttribute('eventName'), queueName: workerModel.getAttribute('queueName'), diff --git a/src/tests/events/eventQueable.test.ts b/src/tests/events/eventQueable.test.ts deleted file mode 100644 index b987e2e28..000000000 --- a/src/tests/events/eventQueable.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* eslint-disable no-undef */ -import { describe } from '@jest/globals'; -import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; -import EventService from '@src/core/domains/events/services/EventService'; -import { IModel } from '@src/core/interfaces/IModel'; -import Kernel from '@src/core/Kernel'; -import { App } from '@src/core/services/App'; -import testAppConfig from '@src/tests/config/testConfig'; -import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; -import TestEventProvider from '@src/tests/providers/TestEventProvider'; - -import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; -import TestWorkerModel from '../models/models/TestWorkerModel'; -import TestDatabaseProvider from '../providers/TestDatabaseProvider'; -import TestEventQueueAddAlwaysFailsEventToQueue from './events/TestEventQueueAddAlwaysFailsEventToQueue'; -import TestEventQueueAlwaysFailsEvent from './events/TestEventQueueAlwaysFailsEvent'; -import TestEventQueueEvent from './events/TestEventQueueEvent'; -import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; - - -describe('mock queable event', () => { - - /** - * Register the test event provider - */ - beforeAll(async () => { - await Kernel.boot({ - ...testAppConfig, - providers: [ - ...testAppConfig.providers, - new TestConsoleProvider(), - new TestDatabaseProvider(), - new TestEventProvider() - ] - }, {}) - }) - - afterAll(async () => { - // await dropWorkerTables(); - }) - - - /** - * - Dispatch TestEventQueueEvent, this will add a queued item to the database - * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent - * - Check the events have been dispatched - * - Check the worker empty has been cleared - */ - // test('test queued worker ', async () => { - - // await dropWorkerTables(); - // await createWorkerTables(); - - // const eventService = App.container('events'); - - // eventService.mockEvent(TestEventQueueEvent) - // eventService.mockEvent(TestEventQueueCalledFromWorkerEvent); - - // await eventService.dispatch(new TestEventQueueEvent({ hello: 'world' })); - - // expect( - // eventService.assertDispatched<{ hello: string }>(TestEventQueueEvent, (payload) => { - // return payload.hello === 'world' - // }) - // ).toBeTruthy() - - // // run the worker - // await App.container('console').reader(['worker', '--queue-name=testQueue']).handle(); - - // expect( - // eventService.assertDispatched<{ hello: string }>(TestEventQueueCalledFromWorkerEvent, (payload) => { - // return payload.hello === 'world' - // }) - // ).toBeTruthy() - - - // const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) - // expect(results.length).toBe(0) - // }) - - /** - * - Dispatch TestEventQueueEvent, this will add a queued item to the database - * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent - * - Check the events have been dispatched - * - Check the worker empty has been cleared - */ - test('test dispatch event queable ', async () => { - - await dropWorkerTables(); - await createWorkerTables(); - - const eventService = App.container('events'); - const driverOptions = eventService.getDriverOptionsByName(EventService.getDriverName(QueueableDriver))?.['options'] as TQueueDriverOptions; - const attempts = driverOptions?.retries ?? 3 - - eventService.mockEvent(TestEventQueueAddAlwaysFailsEventToQueue) - - await eventService.dispatch(new TestEventQueueAddAlwaysFailsEventToQueue()); - - expect(eventService.assertDispatched(TestEventQueueEvent)).toBeTruthy() - - for(let i = 0; i < attempts; i++) { - eventService.mockEvent(TestEventQueueAlwaysFailsEvent); - - await App.container('console').reader(['worker', '--queue-name=testQueue']).handle(); - - expect(eventService.assertDispatched(TestEventQueueAlwaysFailsEvent)).toBeTruthy() - } - - const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) - expect(results.length).toBe(0) - - const failedResults = await App.container('db').documentManager().table(new TestFailedWorkerModel().table).findMany({}) - expect(failedResults.length).toBe(1) - }) -}); \ No newline at end of file diff --git a/src/tests/events/eventQueableSuccess.test.ts b/src/tests/events/eventQueableSuccess.test.ts new file mode 100644 index 000000000..0215e1e0f --- /dev/null +++ b/src/tests/events/eventQueableSuccess.test.ts @@ -0,0 +1,77 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import { IModel } from '@src/core/interfaces/IModel'; +import Kernel from '@src/core/Kernel'; +import { App } from '@src/core/services/App'; +import testAppConfig from '@src/tests/config/testConfig'; +import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestEventProvider from '@src/tests/providers/TestEventProvider'; + +import TestWorkerModel from '../models/models/TestWorkerModel'; +import TestDatabaseProvider from '../providers/TestDatabaseProvider'; +import TestEventQueueCalledFromWorkerEvent from './events/TestEventQueueCalledFromWorkerEvent'; +import TestEventQueueEvent from './events/TestEventQueueEvent'; +import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; + + +describe('mock queable event', () => { + + /** + * Register the test event provider + */ + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + new TestConsoleProvider(), + new TestDatabaseProvider(), + new TestEventProvider() + ] + }, {}) + }) + + afterAll(async () => { + // await dropWorkerTables(); + }) + + + /** + * - Dispatch TestEventQueueEvent, this will add a queued item to the database + * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent + * - Check the events have been dispatched + * - Check the worker empty has been cleared + */ + test('test queued worker ', async () => { + + await dropWorkerTables(); + await createWorkerTables(); + + const eventService = App.container('events'); + + eventService.mockEvent(TestEventQueueEvent) + eventService.mockEvent(TestEventQueueCalledFromWorkerEvent); + + await eventService.dispatch(new TestEventQueueEvent({ hello: 'world' })); + + expect( + eventService.assertDispatched<{ hello: string }>(TestEventQueueEvent, (payload) => { + return payload.hello === 'world' + }) + ).toBeTruthy() + + // run the worker + await App.container('console').reader(['worker', '--queue=testQueue']).handle(); + + expect( + eventService.assertDispatched<{ hello: string }>(TestEventQueueCalledFromWorkerEvent, (payload) => { + return payload.hello === 'world' + }) + ).toBeTruthy() + + + const results = await App.container('db').documentManager().table(new TestWorkerModel().table).findMany({}) + expect(results.length).toBe(0) + }) + +}); \ No newline at end of file From 3dda3588dade822239dada34d14e220a6c24c3be Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Mon, 4 Nov 2024 11:33:28 +0000 Subject: [PATCH 08/31] yarn fix --- src/core/domains/events/base/BaseDriver.ts | 3 +-- src/core/domains/events/base/BaseService.ts | 3 +-- src/core/domains/events/commands/WorkerCommand.ts | 7 +++---- .../domains/events/concerns/EventWorkerConcern.ts | 10 +++------- src/core/domains/events/drivers/QueableDriver.ts | 7 +++---- .../domains/events/interfaces/IEventService.ts | 5 ++--- .../events/interfaces/config/IEventConfig.ts | 3 +-- .../domains/events/models/FailedWorkerModel.ts | 3 +-- src/core/domains/events/models/WorkerModel.ts | 3 +-- .../domains/events/providers/EventProvider.ts | 3 +-- src/core/domains/events/services/EventService.ts | 5 ++--- src/tests/events/eventQueableFailed.test.ts | 13 ++++++------- src/tests/events/eventQueableSuccess.test.ts | 11 +++++------ src/tests/events/eventSync.test.ts | 2 +- .../TestEventQueueAddAlwaysFailsEventToQueue.ts | 3 +-- src/tests/events/events/TestEventQueueEvent.ts | 3 +-- src/tests/providers/TestEventProvider.ts | 15 +++++++-------- 17 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/core/domains/events/base/BaseDriver.ts b/src/core/domains/events/base/BaseDriver.ts index 3534b5a51..fb7ca61ec 100644 --- a/src/core/domains/events/base/BaseDriver.ts +++ b/src/core/domains/events/base/BaseDriver.ts @@ -2,8 +2,7 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; - -import { IEventDriversConfigOption } from "../interfaces/config/IEventDriversConfig"; +import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; abstract class BaseDriver implements IEventDriver { diff --git a/src/core/domains/events/base/BaseService.ts b/src/core/domains/events/base/BaseService.ts index d6a866a3b..5faf387f1 100644 --- a/src/core/domains/events/base/BaseService.ts +++ b/src/core/domains/events/base/BaseService.ts @@ -1,10 +1,9 @@ import HasRegisterableConcern from "@src/core/concerns/HasRegisterableConcern"; import EventMockableConcern from "@src/core/domains/events/concerns/EventMockableConcern"; +import EventWorkerConcern from "@src/core/domains/events/concerns/EventWorkerConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import compose from "@src/core/util/compose"; -import EventWorkerConcern from "../concerns/EventWorkerConcern"; - const BaseService: ICtor = compose( class {}, HasRegisterableConcern, diff --git a/src/core/domains/events/commands/WorkerCommand.ts b/src/core/domains/events/commands/WorkerCommand.ts index aca05ebb4..c4f2caae5 100644 --- a/src/core/domains/events/commands/WorkerCommand.ts +++ b/src/core/domains/events/commands/WorkerCommand.ts @@ -1,12 +1,11 @@ import { EVENT_DRIVERS } from "@src/config/events"; import BaseCommand from "@src/core/domains/console/base/BaseCommand"; +import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; +import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { TEventWorkerOptions } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { App } from "@src/core/services/App"; import { z } from "zod"; -import { IEventDriversConfigOption } from "../interfaces/config/IEventDriversConfig"; -import { IEventService } from "../interfaces/IEventService"; -import { TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; - export default class WorkerCommand extends BaseCommand { /** diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts index fc191f509..67df19d85 100644 --- a/src/core/domains/events/concerns/EventWorkerConcern.ts +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -1,11 +1,10 @@ import Repository from "@src/core/base/Repository"; +import EventWorkerException from "@src/core/domains/events/exceptions/EventWorkerException"; +import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { IEventWorkerConcern, IWorkerModel, TEventWorkerOptions } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; -import EventWorkerException from "../exceptions/EventWorkerException"; -import { IEventPayload } from "../interfaces/IEventPayload"; -import { IEventWorkerConcern, IWorkerModel, TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; - const EventWorkerConcern = (Base: ICtor) => { return class EventWorkerConcern extends Base implements IEventWorkerConcern { @@ -82,8 +81,6 @@ const EventWorkerConcern = (Base: ICtor) => { const newAttempt = attempt + 1 const retries = workerModel.getAttribute('retries') ?? 0 - console.log('Handle update attempts', {attempt, newAttempt, retries}) - await workerModel.attr('attempt', attempt + 1) if(newAttempt >= retries) { @@ -105,7 +102,6 @@ const EventWorkerConcern = (Base: ICtor) => { * @private */ private async handleFailedWorkerModel(workerModel: IWorkerModel, options: TEventWorkerOptions) { - console.log('handle failed worker model', {eventName: workerModel.getAttribute('eventName'), queueName: workerModel.getAttribute('queueName'), payload: workerModel.getAttribute('payload')}) const FailedWorkerModel = new options.failedWorkerModelCtor({ eventName: workerModel.getAttribute('eventName'), queueName: workerModel.getAttribute('queueName'), diff --git a/src/core/domains/events/drivers/QueableDriver.ts b/src/core/domains/events/drivers/QueableDriver.ts index 2646c1389..0118aae87 100644 --- a/src/core/domains/events/drivers/QueableDriver.ts +++ b/src/core/domains/events/drivers/QueableDriver.ts @@ -1,12 +1,11 @@ import BaseDriver from "@src/core/domains/events/base/BaseDriver"; +import EventDriverException from "@src/core/domains/events/exceptions/EventDriverException"; +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; +import { IWorkerModel, TFailedWorkerModelData } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import { IModel } from "@src/core/interfaces/IModel"; import { z } from "zod"; -import EventDriverException from "../exceptions/EventDriverException"; -import { IBaseEvent } from "../interfaces/IBaseEvent"; -import { IWorkerModel, TFailedWorkerModelData } from "../interfaces/IEventWorkerConcern"; - export type TQueueDriverOptions = { diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts index e49f308d9..14abd5fc8 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -1,17 +1,16 @@ /* eslint-disable no-unused-vars */ +import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; import { TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; +import { IEventWorkerConcern } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { IHasDispatcherConcern } from "@src/core/domains/events/interfaces/IHasDispatcherConcern"; import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern"; import { IMockableConcern } from "@src/core/domains/events/interfaces/IMockableConcern"; import { IHasRegisterableConcern } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { IEventConfig } from "./config/IEventConfig"; -import { IEventWorkerConcern } from "./IEventWorkerConcern"; - export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IEventWorkerConcern, IMockableConcern { getConfig(): IEventConfig; diff --git a/src/core/domains/events/interfaces/config/IEventConfig.ts b/src/core/domains/events/interfaces/config/IEventConfig.ts index 0e30bcb7d..542b0aa74 100644 --- a/src/core/domains/events/interfaces/config/IEventConfig.ts +++ b/src/core/domains/events/interfaces/config/IEventConfig.ts @@ -1,10 +1,9 @@ +import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventDriversConfig } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; import { IEventListenersConfig } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { IBaseEvent } from "../IBaseEvent"; - export interface IEventConfig { defaultDriver: ICtor; drivers: IEventDriversConfig; diff --git a/src/core/domains/events/models/FailedWorkerModel.ts b/src/core/domains/events/models/FailedWorkerModel.ts index 81dfc72d4..68ba24ea9 100644 --- a/src/core/domains/events/models/FailedWorkerModel.ts +++ b/src/core/domains/events/models/FailedWorkerModel.ts @@ -1,6 +1,5 @@ import Model from "@src/core/base/Model"; - -import { TFailedWorkerModelData } from "../interfaces/IEventWorkerConcern"; +import { TFailedWorkerModelData } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; export interface FailedWorkerModelData extends TFailedWorkerModelData {} diff --git a/src/core/domains/events/models/WorkerModel.ts b/src/core/domains/events/models/WorkerModel.ts index ea6e732b6..e5b678855 100644 --- a/src/core/domains/events/models/WorkerModel.ts +++ b/src/core/domains/events/models/WorkerModel.ts @@ -1,6 +1,5 @@ import Model from "@src/core/base/Model"; - -import { IWorkerModel, TWorkerModelData } from "../interfaces/IEventWorkerConcern"; +import { IWorkerModel, TWorkerModelData } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; export interface WorkerModelData extends TWorkerModelData {} diff --git a/src/core/domains/events/providers/EventProvider.ts b/src/core/domains/events/providers/EventProvider.ts index 275609038..f4bfbf3bd 100644 --- a/src/core/domains/events/providers/EventProvider.ts +++ b/src/core/domains/events/providers/EventProvider.ts @@ -1,12 +1,11 @@ import { eventConfig } from "@src/config/events"; import BaseProvider from "@src/core/base/Provider"; +import WorkerCommand from "@src/core/domains/events/commands/WorkerCommand"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; import EventService from "@src/core/domains/events/services/EventService"; import { App } from "@src/core/services/App"; -import WorkerCommand from "../commands/WorkerCommand"; - class EventProvider extends BaseProvider { protected config: IEventConfig = eventConfig; diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index e9c2fdc68..332250e19 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -1,8 +1,10 @@ /* eslint-disable no-unused-vars */ import BaseService from "@src/core/domains/events/base/BaseService"; +import EventDispatchException from "@src/core/domains/events/exceptions/EventDispatchException"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { TEventWorkerOptions } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; import { IEventDriversConfigOption } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; @@ -10,9 +12,6 @@ import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@s import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; -import EventDispatchException from "../exceptions/EventDispatchException"; -import { TEventWorkerOptions } from "../interfaces/IEventWorkerConcern"; - class EventService extends BaseService implements IEventService { static readonly REGISTERED_EVENTS = "registeredEvents"; diff --git a/src/tests/events/eventQueableFailed.test.ts b/src/tests/events/eventQueableFailed.test.ts index f4414436e..cdc04c253 100644 --- a/src/tests/events/eventQueableFailed.test.ts +++ b/src/tests/events/eventQueableFailed.test.ts @@ -6,16 +6,15 @@ import { IModel } from '@src/core/interfaces/IModel'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; +import TestEventQueueAddAlwaysFailsEventToQueue from '@src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue'; +import TestEventQueueAlwaysFailsEvent from '@src/tests/events/events/TestEventQueueAlwaysFailsEvent'; +import createWorkerTables, { dropWorkerTables } from '@src/tests/events/helpers/createWorketTables'; +import TestFailedWorkerModel from '@src/tests/models/models/TestFailedWorkerModel'; +import TestWorkerModel from '@src/tests/models/models/TestWorkerModel'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; -import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; -import TestWorkerModel from '../models/models/TestWorkerModel'; -import TestDatabaseProvider from '../providers/TestDatabaseProvider'; -import TestEventQueueAddAlwaysFailsEventToQueue from './events/TestEventQueueAddAlwaysFailsEventToQueue'; -import TestEventQueueAlwaysFailsEvent from './events/TestEventQueueAlwaysFailsEvent'; -import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; - describe('mock queable event failed', () => { diff --git a/src/tests/events/eventQueableSuccess.test.ts b/src/tests/events/eventQueableSuccess.test.ts index 0215e1e0f..afc801676 100644 --- a/src/tests/events/eventQueableSuccess.test.ts +++ b/src/tests/events/eventQueableSuccess.test.ts @@ -4,15 +4,14 @@ import { IModel } from '@src/core/interfaces/IModel'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; +import TestEventQueueCalledFromWorkerEvent from '@src/tests/events/events/TestEventQueueCalledFromWorkerEvent'; +import TestEventQueueEvent from '@src/tests/events/events/TestEventQueueEvent'; +import createWorkerTables, { dropWorkerTables } from '@src/tests/events/helpers/createWorketTables'; +import TestWorkerModel from '@src/tests/models/models/TestWorkerModel'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; -import TestWorkerModel from '../models/models/TestWorkerModel'; -import TestDatabaseProvider from '../providers/TestDatabaseProvider'; -import TestEventQueueCalledFromWorkerEvent from './events/TestEventQueueCalledFromWorkerEvent'; -import TestEventQueueEvent from './events/TestEventQueueEvent'; -import createWorkerTables, { dropWorkerTables } from './helpers/createWorketTables'; - describe('mock queable event', () => { diff --git a/src/tests/events/eventSync.test.ts b/src/tests/events/eventSync.test.ts index ed1c80d1c..4ee8e5da1 100644 --- a/src/tests/events/eventSync.test.ts +++ b/src/tests/events/eventSync.test.ts @@ -7,7 +7,7 @@ import TestEventSyncEvent from '@src/tests/events/events/TestEventSyncEvent'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; -import TestEventSyncBadPayloadEvent from './events/TestEventSyncBadPayloadEvent'; +import TestEventSyncBadPayloadEvent from '@src/tests/events/events/TestEventSyncBadPayloadEvent'; describe('mock event service', () => { diff --git a/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts index 1a56b0bd5..50e88f940 100644 --- a/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts +++ b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts @@ -2,8 +2,7 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; import { App } from "@src/core/services/App"; - -import TestEventQueueAlwaysFailsEvent from "./TestEventQueueAlwaysFailsEvent"; +import TestEventQueueAlwaysFailsEvent from "@src/tests/events/events/TestEventQueueAlwaysFailsEvent"; class TestEventQueueAddAlwaysFailsEventToQueue extends BaseEvent { diff --git a/src/tests/events/events/TestEventQueueEvent.ts b/src/tests/events/events/TestEventQueueEvent.ts index 9c0721fb3..a50941da7 100644 --- a/src/tests/events/events/TestEventQueueEvent.ts +++ b/src/tests/events/events/TestEventQueueEvent.ts @@ -3,8 +3,7 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { App } from "@src/core/services/App"; - -import TestEventQueueCalledFromWorkerEvent from "./TestEventQueueCalledFromWorkerEvent"; +import TestEventQueueCalledFromWorkerEvent from "@src/tests/events/events/TestEventQueueCalledFromWorkerEvent"; class TestEventQueueEvent extends BaseEvent { diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 4e197e9aa..60a2020cb 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -4,18 +4,17 @@ import SyncDriver from '@src/core/domains/events/drivers/SyncDriver'; import { IEventConfig } from '@src/core/domains/events/interfaces/config/IEventConfig'; import EventProvider from '@src/core/domains/events/providers/EventProvider'; import EventService from '@src/core/domains/events/services/EventService'; +import TestEventQueueAddAlwaysFailsEventToQueue from '@src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue'; +import TestEventQueueAlwaysFailsEvent from '@src/tests/events/events/TestEventQueueAlwaysFailsEvent'; +import TestEventQueueCalledFromWorkerEvent from '@src/tests/events/events/TestEventQueueCalledFromWorkerEvent'; +import TestEventQueueEvent from '@src/tests/events/events/TestEventQueueEvent'; +import TestEventSyncBadPayloadEvent from '@src/tests/events/events/TestEventSyncBadPayloadEvent'; +import TestEventSyncEvent from '@src/tests/events/events/TestEventSyncEvent'; import TestListener from '@src/tests/events/listeners/TestListener'; import TestSubscriber from '@src/tests/events/subscribers/TestSubscriber'; +import TestFailedWorkerModel from '@src/tests/models/models/TestFailedWorkerModel'; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; -import TestEventQueueAddAlwaysFailsEventToQueue from '../events/events/TestEventQueueAddAlwaysFailsEventToQueue'; -import TestEventQueueAlwaysFailsEvent from '../events/events/TestEventQueueAlwaysFailsEvent'; -import TestEventQueueCalledFromWorkerEvent from '../events/events/TestEventQueueCalledFromWorkerEvent'; -import TestEventQueueEvent from '../events/events/TestEventQueueEvent'; -import TestEventSyncBadPayloadEvent from '../events/events/TestEventSyncBadPayloadEvent'; -import TestEventSyncEvent from '../events/events/TestEventSyncEvent'; -import TestFailedWorkerModel from '../models/models/TestFailedWorkerModel'; - class TestEventProvider extends EventProvider { protected config: IEventConfig = { From 7bd0acbd5bd25c79b139caff842a67540b59faf5 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Mon, 4 Nov 2024 13:15:01 +0000 Subject: [PATCH 09/31] renamed create config methods --- src/config/events.ts | 8 ++++---- src/core/domains/events/services/EventService.ts | 6 +++--- src/tests/providers/TestEventProvider.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config/events.ts b/src/config/events.ts index b302372a6..cc4dbfb3a 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -33,10 +33,10 @@ export const eventConfig: IEventConfig = { drivers: { // Synchronous Driver: Processes events immediately - [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), + [EVENT_DRIVERS.SYNC]: EventService.createConfigDriver(SyncDriver, {}), // Queue Driver: Saves events for background processing - [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + [EVENT_DRIVERS.QUEABLE]: EventService.createConfigDriver(QueueableDriver, { queueName: 'default', // Name of the queue retries: 3, // Number of retry attempts for failed events runAfterSeconds: 10, // Delay before processing queued events @@ -49,7 +49,7 @@ export const eventConfig: IEventConfig = { /** * Register Events */ - events: EventService.createEvents([ + events: EventService.createConfigEvents([ TestEventQueueEvent, TestEventSyncEvent ]), @@ -60,7 +60,7 @@ export const eventConfig: IEventConfig = { * These are automatically registered with the event service * and do not need to be added to 'events' array. */ - listeners: EventService.createListeners([ + listeners: EventService.createConfigListeners([ { listener: TestListener, subscribers: [ diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 332250e19..a114de061 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -45,7 +45,7 @@ class EventService extends BaseService implements IEventService { * @param options The event driver options. * @returns The event driver config. */ - public static createConfig(driverCtor: ICtor, options?: T): IEventDriversConfigOption { + public static createConfigDriver(driverCtor: ICtor, options?: T): IEventDriversConfigOption { return { driverCtor, options @@ -56,7 +56,7 @@ class EventService extends BaseService implements IEventService { * @param events An array of event constructors to be registered. * @returns The same array of event constructors. */ - public static createEvents(events: ICtor[]): ICtor[] { + public static createConfigEvents(events: ICtor[]): ICtor[] { return events } @@ -64,7 +64,7 @@ class EventService extends BaseService implements IEventService { * @param config The event listeners config. * @returns The event listeners config. */ - public static createListeners(config: IEventListenersConfig): IEventListenersConfig { + public static createConfigListeners(config: IEventListenersConfig): IEventListenersConfig { return config } diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 60a2020cb..122935ac7 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -22,8 +22,8 @@ class TestEventProvider extends EventProvider { defaultDriver: SyncDriver, drivers: { - [EVENT_DRIVERS.SYNC]: EventService.createConfig(SyncDriver, {}), - [EVENT_DRIVERS.QUEABLE]: EventService.createConfig(QueueableDriver, { + [EVENT_DRIVERS.SYNC]: EventService.createConfigDriver(SyncDriver, {}), + [EVENT_DRIVERS.QUEABLE]: EventService.createConfigDriver(QueueableDriver, { queueName: 'testQueue', retries: 3, runAfterSeconds: 0, @@ -42,7 +42,7 @@ class TestEventProvider extends EventProvider { TestEventQueueAlwaysFailsEvent ], - listeners: EventService.createListeners([ + listeners: EventService.createConfigListeners([ { listener: TestListener, subscribers: [ From 6b5134e106bfdaae854b595dbd88919f581e4e7a Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Mon, 4 Nov 2024 22:29:36 +0000 Subject: [PATCH 10/31] event progress --- src/app/events/listeners/ExampleListener.ts | 10 ------ .../listeners/UserRegisteredListener.ts | 11 ++++++ .../events/subscribers/ExampleSubscriber.ts | 16 --------- .../events/subscribers/UserRegisteredEvent.ts | 27 ++++++++++++++ src/app/observers/UserObserver.ts | 13 +++++++ src/config/events.ts | 10 +++--- src/config/eventsLegacy.ts | 3 +- src/core/domains/events/base/BaseEvent.ts | 35 ++++++++++++++++--- .../domains/events/base/BaseEventListener.ts | 5 ++- .../events/concerns/EventMockableConcern.ts | 4 ++- .../events/concerns/EventWorkerConcern.ts | 4 +-- src/core/domains/events/drivers/SyncDriver.ts | 5 --- .../EventInvalidPayloadException.ts | 8 +++++ .../domains/events/interfaces/IBaseEvent.ts | 6 ++-- .../events/interfaces/IEventListener.ts | 2 +- .../events/interfaces/IEventPayload.ts | 8 ++--- .../events/interfaces/IMockableConcern.ts | 6 ++-- .../domains/events/services/EventService.ts | 4 ++- src/core/services/App.ts | 2 +- .../TestEventQueueCalledFromWorkerEvent.ts | 3 +- .../events/events/TestEventQueueEvent.ts | 3 +- .../events/TestEventSyncBadPayloadEvent.ts | 5 ++- src/tests/events/events/TestEventSyncEvent.ts | 9 ++--- 23 files changed, 123 insertions(+), 76 deletions(-) delete mode 100644 src/app/events/listeners/ExampleListener.ts create mode 100644 src/app/events/listeners/UserRegisteredListener.ts delete mode 100644 src/app/events/subscribers/ExampleSubscriber.ts create mode 100644 src/app/events/subscribers/UserRegisteredEvent.ts create mode 100644 src/core/domains/events/exceptions/EventInvalidPayloadException.ts diff --git a/src/app/events/listeners/ExampleListener.ts b/src/app/events/listeners/ExampleListener.ts deleted file mode 100644 index 92335df26..000000000 --- a/src/app/events/listeners/ExampleListener.ts +++ /dev/null @@ -1,10 +0,0 @@ -import EventListener from "@src/core/domains/events-legacy/services/EventListener"; - -export class ExampleListener extends EventListener<{userId: string}> { - - // eslint-disable-next-line no-unused-vars - handle = async (payload: { userId: string}) => { - // Handle the logic - } - -} \ No newline at end of file diff --git a/src/app/events/listeners/UserRegisteredListener.ts b/src/app/events/listeners/UserRegisteredListener.ts new file mode 100644 index 000000000..2946d10ce --- /dev/null +++ b/src/app/events/listeners/UserRegisteredListener.ts @@ -0,0 +1,11 @@ +import { IUserData } from "@src/app/models/auth/User"; +import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; + +export class UserRegisteredListener extends BaseEventListener { + + // eslint-disable-next-line no-unused-vars + async execute(payload: IUserData): Promise { + // Handle some logic + } + +} \ No newline at end of file diff --git a/src/app/events/subscribers/ExampleSubscriber.ts b/src/app/events/subscribers/ExampleSubscriber.ts deleted file mode 100644 index 48a9e5897..000000000 --- a/src/app/events/subscribers/ExampleSubscriber.ts +++ /dev/null @@ -1,16 +0,0 @@ -import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; - -type Payload = { - userId: string; -} - -export default class ExampleSubscriber extends EventSubscriber { - - constructor(payload: Payload) { - const eventName = 'OnExample' - const driver = 'queue'; - - super(eventName, driver, payload) - } - -} \ No newline at end of file diff --git a/src/app/events/subscribers/UserRegisteredEvent.ts b/src/app/events/subscribers/UserRegisteredEvent.ts new file mode 100644 index 000000000..d99968719 --- /dev/null +++ b/src/app/events/subscribers/UserRegisteredEvent.ts @@ -0,0 +1,27 @@ +import { IUserData } from "@src/app/models/auth/User"; +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; + +export default class UserRegisteredEvent extends BaseEvent { + + static readonly eventName = 'UserRegisteredEvent'; + + protected namespace: string = 'auth'; + + constructor(payload) { + super(payload, SyncDriver); + } + + getName(): string { + return UserRegisteredEvent.eventName; + } + + getQueueName(): string { + return 'default'; + } + + async execute(payload: IUserData): Promise { + console.log('User was registered', payload); + } + +} \ No newline at end of file diff --git a/src/app/observers/UserObserver.ts b/src/app/observers/UserObserver.ts index daa6ce8c2..68effe650 100644 --- a/src/app/observers/UserObserver.ts +++ b/src/app/observers/UserObserver.ts @@ -3,6 +3,8 @@ import hashPassword from "@src/core/domains/auth/utils/hashPassword"; import Observer from "@src/core/domains/observer/services/Observer"; import { App } from "@src/core/services/App"; +import { UserRegisteredListener } from "../events/listeners/UserRegisteredListener"; + /** * Observer for the User model. * @@ -22,6 +24,17 @@ export default class UserObserver extends Observer { return data } + /** + * Called after the User model has been created. + * Dispatches the UserRegisteredEvent event to trigger related subscribers. + * @param data The User data that has been created. + * @returns The processed User data. + */ + async created(data: IUserData): Promise { + App.container('events').dispatch(new UserRegisteredListener(data)) + return data + } + /** * Updates the roles of the user based on the groups they belong to. * Retrieves the roles associated with each group the user belongs to from the permissions configuration. diff --git a/src/config/events.ts b/src/config/events.ts index cc4dbfb3a..2afd8f4ef 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -1,13 +1,12 @@ +import { UserRegisteredListener } from "@src/app/events/listeners/UserRegisteredListener"; +import UserRegisteredEvent from "@src/app/events/subscribers/UserRegisteredEvent"; import QueueableDriver, { TQueueDriverOptions } from "@src/core/domains/events/drivers/QueableDriver"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; import FailedWorkerModel from "@src/core/domains/events/models/FailedWorkerModel"; import WorkerModel from "@src/core/domains/events/models/WorkerModel"; import EventService from "@src/core/domains/events/services/EventService"; -import TestEventQueueEvent from "@src/tests/events/events/TestEventQueueEvent"; import TestEventSyncEvent from "@src/tests/events/events/TestEventSyncEvent"; -import TestListener from "@src/tests/events/listeners/TestListener"; -import TestSubscriber from "@src/tests/events/subscribers/TestSubscriber"; /** * Event Drivers Constants @@ -50,7 +49,6 @@ export const eventConfig: IEventConfig = { * Register Events */ events: EventService.createConfigEvents([ - TestEventQueueEvent, TestEventSyncEvent ]), @@ -62,9 +60,9 @@ export const eventConfig: IEventConfig = { */ listeners: EventService.createConfigListeners([ { - listener: TestListener, + listener: UserRegisteredListener, subscribers: [ - TestSubscriber + UserRegisteredEvent ] } ]), diff --git a/src/config/eventsLegacy.ts b/src/config/eventsLegacy.ts index 4a840a079..d509ceaa9 100644 --- a/src/config/eventsLegacy.ts +++ b/src/config/eventsLegacy.ts @@ -1,4 +1,3 @@ -import { ExampleListener } from "@src/app/events/listeners/ExampleListener"; import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; @@ -66,6 +65,6 @@ export const eventDrivers: IEventDrivers = { */ export const eventSubscribers: ISubscribers = { 'OnExample': [ - ExampleListener + ] } \ No newline at end of file diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index e5bd7f7b1..5334ece1b 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -1,13 +1,15 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; -abstract class BaseEvent implements IBaseEvent { +import EventInvalidPayloadException from "../exceptions/EventInvalidPayloadException"; +import { TISerializablePayload } from "../interfaces/IEventPayload"; - protected payload: IEventPayload | null = null; +abstract class BaseEvent implements IBaseEvent { + + protected payload: TPayload | null = null; protected driver!: ICtor; @@ -23,11 +25,34 @@ abstract class BaseEvent implements IBaseEvent { * @param payload The payload of the event * @param driver The class of the event driver */ - constructor(payload: IEventPayload | null = null, driver?: ICtor) { + constructor(payload: TPayload | null = null, driver?: ICtor) { this.payload = payload; + // Use safeContainer here to avoid errors during registering which runs during boot up. this.defaultDriver = App.safeContainer('events')?.getDefaultDriverCtor() as ICtor; this.driver = driver ?? this.defaultDriver; + + // Ensure the payload is valid + if(!this.validatePayload()) { + throw new EventInvalidPayloadException('Invalid payload. Must be JSON serializable.'); + } + } + + /** + * Validates the payload of the event. Ensures that the payload is an object with types that match: + * string, number, boolean, object, array, null. + * @throws {EventInvalidPayloadException} If the payload is invalid. + */ + validatePayload(): boolean { + try { + JSON.stringify(this.payload); + } + // eslint-disable-next-line no-unused-vars + catch (err) { + return false + } + + return true } /** @@ -52,7 +77,7 @@ abstract class BaseEvent implements IBaseEvent { * @template T The type of the payload to return. * @returns The payload of the event. */ - getPayload(): T { + getPayload(): T { return this.payload as T } diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts index 372944ca6..2f9c4bdab 100644 --- a/src/core/domains/events/base/BaseEventListener.ts +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -1,11 +1,10 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventListener } from "@src/core/domains/events/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; -class BaseEventListener extends BaseEvent implements IEventListener { +class BaseEventListener extends BaseEvent implements IEventListener { /** * Constructor @@ -15,7 +14,7 @@ class BaseEventListener extends BaseEvent implements IEventListener { * * @param payload The payload of the event to dispatch */ - constructor(payload?: IEventPayload, driver?: ICtor) { + constructor(payload?: TPayload, driver?: ICtor) { super(payload, driver); if(!App.containerReady('events')) { diff --git a/src/core/domains/events/concerns/EventMockableConcern.ts b/src/core/domains/events/concerns/EventMockableConcern.ts index 8a640a8b1..651fa527e 100644 --- a/src/core/domains/events/concerns/EventMockableConcern.ts +++ b/src/core/domains/events/concerns/EventMockableConcern.ts @@ -3,6 +3,8 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { IMockableConcern, TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; +import { TISerializablePayload } from "../interfaces/IEventPayload"; + const EventMockableConcern = (Base: ICtor) => { return class EventMockable extends Base implements IMockableConcern { @@ -73,7 +75,7 @@ const EventMockableConcern = (Base: ICtor) => { * @throws Will throw an error if the event was not dispatched or if the dispatched event's * payload does not satisfy the given condition. */ - assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean { + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean { const eventCtorName = (new eventCtor(null)).getName() const dispatchedEvent = this.mockEventsDispatched.find(e => e.getName() === eventCtorName) diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts index 67df19d85..c995fb360 100644 --- a/src/core/domains/events/concerns/EventWorkerConcern.ts +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -1,6 +1,6 @@ import Repository from "@src/core/base/Repository"; import EventWorkerException from "@src/core/domains/events/exceptions/EventWorkerException"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; +import { TISerializablePayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { IEventWorkerConcern, IWorkerModel, TEventWorkerOptions } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; @@ -55,7 +55,7 @@ const EventWorkerConcern = (Base: ICtor) => { throw new EventWorkerException(`Event '${eventName}' not found`); } - const payload = workerModel.getPayload() + const payload = workerModel.getPayload() const eventInstance = new eventCtor(payload); await eventInstance.execute(); diff --git a/src/core/domains/events/drivers/SyncDriver.ts b/src/core/domains/events/drivers/SyncDriver.ts index a8a5db46f..394fe54a7 100644 --- a/src/core/domains/events/drivers/SyncDriver.ts +++ b/src/core/domains/events/drivers/SyncDriver.ts @@ -1,4 +1,3 @@ -import { EVENT_DRIVERS } from "@src/config/events"; import BaseDriver from "@src/core/domains/events/base/BaseDriver"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; @@ -7,10 +6,6 @@ class SyncDriver extends BaseDriver { async dispatch(event: IBaseEvent): Promise { await event.execute(); } - - getName(): string { - return EVENT_DRIVERS.SYNC; - } } diff --git a/src/core/domains/events/exceptions/EventInvalidPayloadException.ts b/src/core/domains/events/exceptions/EventInvalidPayloadException.ts new file mode 100644 index 000000000..2f4577c4b --- /dev/null +++ b/src/core/domains/events/exceptions/EventInvalidPayloadException.ts @@ -0,0 +1,8 @@ +export default class EventInvalidPayloadException extends Error { + + constructor(message: string = 'Invalid payload') { + super(message); + this.name = 'EventInvalidPayloadException'; + } + +} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index 1ec41abdf..f4f3e358a 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -5,10 +5,12 @@ import { IExecutable } from "@src/core/interfaces/concerns/IExecutable"; import { INameable } from "@src/core/interfaces/concerns/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IBaseEvent extends INameable, IExecutable +import { TISerializablePayload } from "./IEventPayload"; + +export interface IBaseEvent extends INameable, IExecutable { getQueueName(): string; getEventService(): IEventService; getDriverCtor(): ICtor; - getPayload(): T; + getPayload(): T; } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventListener.ts b/src/core/domains/events/interfaces/IEventListener.ts index 882973f9e..799716a3a 100644 --- a/src/core/domains/events/interfaces/IEventListener.ts +++ b/src/core/domains/events/interfaces/IEventListener.ts @@ -1,6 +1,6 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { INameable } from "@src/core/interfaces/concerns/INameable"; -export interface IEventListener extends INameable, IBaseEvent { +export interface IEventListener extends INameable, IBaseEvent { } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IEventPayload.ts b/src/core/domains/events/interfaces/IEventPayload.ts index ae34919d0..6a3d0b611 100644 --- a/src/core/domains/events/interfaces/IEventPayload.ts +++ b/src/core/domains/events/interfaces/IEventPayload.ts @@ -1,10 +1,8 @@ -export type TSerializableTypes = number | string | boolean | undefined; +export type TSerializableValues = number | string | boolean | undefined | null; -export interface IEventPayload { - [key: string | number | symbol]: TSerializableTypes -} +export type TISerializablePayload = Record | TSerializableValues; export interface IEventPayloadWithDriver { driver: string; - payload: IEventPayload + payload: TISerializablePayload } \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IMockableConcern.ts b/src/core/domains/events/interfaces/IMockableConcern.ts index 3ecd34b40..fcaee2489 100644 --- a/src/core/domains/events/interfaces/IMockableConcern.ts +++ b/src/core/domains/events/interfaces/IMockableConcern.ts @@ -2,12 +2,14 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { ICtor } from "@src/core/interfaces/ICtor"; -export type TMockableEventCallback = (payload: TPayload) => boolean; +import { TISerializablePayload } from "./IEventPayload"; + +export type TMockableEventCallback = (payload: TPayload) => boolean; export interface IMockableConcern { mockEvent(event: ICtor): void; mockEventDispatched(event: IBaseEvent): void; - assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean; } \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index a114de061..9eb2c0b6c 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -12,6 +12,8 @@ import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@s import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; +import { TISerializablePayload } from "../interfaces/IEventPayload"; + class EventService extends BaseService implements IEventService { static readonly REGISTERED_EVENTS = "registeredEvents"; @@ -97,7 +99,7 @@ class EventService extends BaseService implements IEventService { declare mockEventDispatched: (event: IBaseEvent) => void; - declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean + declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean /** * Delcare EventWorkerConcern methods. diff --git a/src/core/services/App.ts b/src/core/services/App.ts index b1d9bc924..756f3ee4a 100644 --- a/src/core/services/App.ts +++ b/src/core/services/App.ts @@ -51,7 +51,7 @@ export class App extends Singleton { if (kernel.booted()) { throw new Error('Kernel is already booted'); } - if (!name) { + if (!name || name === '') { throw new Error('Container name cannot be empty'); } if (kernel.containers.has(name)) { diff --git a/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts index 6b1a262eb..2069af559 100644 --- a/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts +++ b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts @@ -1,7 +1,6 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; class TestEventQueueCalledFromWorkerEvent extends BaseEvent { @@ -9,7 +8,7 @@ class TestEventQueueCalledFromWorkerEvent extends BaseEvent { static readonly eventName = 'TestEventQueueCalledFromWorkerEvent'; - constructor(payload: IEventPayload) { + constructor(payload) { super(payload, SyncDriver) } diff --git a/src/tests/events/events/TestEventQueueEvent.ts b/src/tests/events/events/TestEventQueueEvent.ts index a50941da7..e8b5d5b73 100644 --- a/src/tests/events/events/TestEventQueueEvent.ts +++ b/src/tests/events/events/TestEventQueueEvent.ts @@ -1,7 +1,6 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; import { App } from "@src/core/services/App"; import TestEventQueueCalledFromWorkerEvent from "@src/tests/events/events/TestEventQueueCalledFromWorkerEvent"; @@ -11,7 +10,7 @@ class TestEventQueueEvent extends BaseEvent { static readonly eventName = 'TestEventQueueEvent'; - constructor(payload: IEventPayload) { + constructor(payload) { super(payload, QueueableDriver) } diff --git a/src/tests/events/events/TestEventSyncBadPayloadEvent.ts b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts index 8b874bb45..5f82e6049 100644 --- a/src/tests/events/events/TestEventSyncBadPayloadEvent.ts +++ b/src/tests/events/events/TestEventSyncBadPayloadEvent.ts @@ -1,14 +1,13 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; class TestEventSyncBadPayloadEvent extends BaseEvent { protected namespace: string = 'testing'; - constructor(payload: unknown) { - super(payload as IEventPayload); + constructor(payload) { + super(payload); } async execute(): Promise { diff --git a/src/tests/events/events/TestEventSyncEvent.ts b/src/tests/events/events/TestEventSyncEvent.ts index fa0aec5bb..d4866c0c4 100644 --- a/src/tests/events/events/TestEventSyncEvent.ts +++ b/src/tests/events/events/TestEventSyncEvent.ts @@ -1,18 +1,13 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; class TestEventSyncEvent extends BaseEvent { static readonly eventName = 'TestEventSyncEvent'; - protected namespace: string = 'testing'; - - constructor(payload: IEventPayload) { - super(payload); - } - + protected namespace: string = 'testing'; + async execute(): Promise { console.log('Executed TestEventSyncEvent', this.getPayload(), this.getName()) } From ce38f483b26b01d5c765e86add783a19c014ff13 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Mon, 4 Nov 2024 23:03:06 +0000 Subject: [PATCH 11/31] eventAuthRegistered test --- ...eredListener.ts => UserCreatedListener.ts} | 2 +- ...teredEvent.ts => UserCreatedSubscriber.ts} | 6 +- src/app/observers/UserObserver.ts | 4 +- src/config/events.ts | 8 +- src/core/domains/events/base/BaseEvent.ts | 3 +- .../domains/events/base/BaseEventListener.ts | 21 ------ .../events/concerns/EventMockableConcern.ts | 3 +- .../domains/events/interfaces/IBaseEvent.ts | 3 +- .../events/interfaces/IMockableConcern.ts | 5 +- .../domains/events/services/EventService.ts | 26 ++++++- .../events/eventAuthUserRegistered.test.ts | 75 +++++++++++++++++++ src/tests/providers/TestAuthProvider.ts | 9 +++ src/tests/providers/TestEventProvider.ts | 14 +++- 13 files changed, 136 insertions(+), 43 deletions(-) rename src/app/events/listeners/{UserRegisteredListener.ts => UserCreatedListener.ts} (81%) rename src/app/events/subscribers/{UserRegisteredEvent.ts => UserCreatedSubscriber.ts} (76%) create mode 100644 src/tests/events/eventAuthUserRegistered.test.ts create mode 100644 src/tests/providers/TestAuthProvider.ts diff --git a/src/app/events/listeners/UserRegisteredListener.ts b/src/app/events/listeners/UserCreatedListener.ts similarity index 81% rename from src/app/events/listeners/UserRegisteredListener.ts rename to src/app/events/listeners/UserCreatedListener.ts index 2946d10ce..62ea6341e 100644 --- a/src/app/events/listeners/UserRegisteredListener.ts +++ b/src/app/events/listeners/UserCreatedListener.ts @@ -1,7 +1,7 @@ import { IUserData } from "@src/app/models/auth/User"; import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; -export class UserRegisteredListener extends BaseEventListener { +export class UserCreatedListener extends BaseEventListener { // eslint-disable-next-line no-unused-vars async execute(payload: IUserData): Promise { diff --git a/src/app/events/subscribers/UserRegisteredEvent.ts b/src/app/events/subscribers/UserCreatedSubscriber.ts similarity index 76% rename from src/app/events/subscribers/UserRegisteredEvent.ts rename to src/app/events/subscribers/UserCreatedSubscriber.ts index d99968719..a9f1c656f 100644 --- a/src/app/events/subscribers/UserRegisteredEvent.ts +++ b/src/app/events/subscribers/UserCreatedSubscriber.ts @@ -2,7 +2,7 @@ import { IUserData } from "@src/app/models/auth/User"; import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; -export default class UserRegisteredEvent extends BaseEvent { +export default class UserCreatedSubscriber extends BaseEvent { static readonly eventName = 'UserRegisteredEvent'; @@ -13,7 +13,7 @@ export default class UserRegisteredEvent extends BaseEvent { } getName(): string { - return UserRegisteredEvent.eventName; + return UserCreatedSubscriber.eventName; } getQueueName(): string { @@ -21,7 +21,7 @@ export default class UserRegisteredEvent extends BaseEvent { } async execute(payload: IUserData): Promise { - console.log('User was registered', payload); + console.log('User was created', payload); } } \ No newline at end of file diff --git a/src/app/observers/UserObserver.ts b/src/app/observers/UserObserver.ts index 68effe650..49dd8caa3 100644 --- a/src/app/observers/UserObserver.ts +++ b/src/app/observers/UserObserver.ts @@ -3,7 +3,7 @@ import hashPassword from "@src/core/domains/auth/utils/hashPassword"; import Observer from "@src/core/domains/observer/services/Observer"; import { App } from "@src/core/services/App"; -import { UserRegisteredListener } from "../events/listeners/UserRegisteredListener"; +import { UserCreatedListener } from "../events/listeners/UserCreatedListener"; /** * Observer for the User model. @@ -31,7 +31,7 @@ export default class UserObserver extends Observer { * @returns The processed User data. */ async created(data: IUserData): Promise { - App.container('events').dispatch(new UserRegisteredListener(data)) + await App.container('events').dispatch(new UserCreatedListener(data)) return data } diff --git a/src/config/events.ts b/src/config/events.ts index 2afd8f4ef..bdb7c0fb9 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -1,5 +1,5 @@ -import { UserRegisteredListener } from "@src/app/events/listeners/UserRegisteredListener"; -import UserRegisteredEvent from "@src/app/events/subscribers/UserRegisteredEvent"; +import { UserCreatedListener } from "@src/app/events/listeners/UserCreatedListener"; +import UserCreatedSubscriber from "@src/app/events/subscribers/UserCreatedSubscriber"; import QueueableDriver, { TQueueDriverOptions } from "@src/core/domains/events/drivers/QueableDriver"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventConfig"; @@ -60,9 +60,9 @@ export const eventConfig: IEventConfig = { */ listeners: EventService.createConfigListeners([ { - listener: UserRegisteredListener, + listener: UserCreatedListener, subscribers: [ - UserRegisteredEvent + UserCreatedSubscriber ] } ]), diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 5334ece1b..4cc6cc312 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -5,9 +5,8 @@ import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; import EventInvalidPayloadException from "../exceptions/EventInvalidPayloadException"; -import { TISerializablePayload } from "../interfaces/IEventPayload"; -abstract class BaseEvent implements IBaseEvent { +abstract class BaseEvent implements IBaseEvent { protected payload: TPayload | null = null; diff --git a/src/core/domains/events/base/BaseEventListener.ts b/src/core/domains/events/base/BaseEventListener.ts index 2f9c4bdab..c63511d46 100644 --- a/src/core/domains/events/base/BaseEventListener.ts +++ b/src/core/domains/events/base/BaseEventListener.ts @@ -20,27 +20,6 @@ class BaseEventListener extends BaseEvent implemen if(!App.containerReady('events')) { return; } - - this.notifySubscribers(); - } - - /** - * Notifies all subscribers of this event that the event has been dispatched. - * - * Retrieves all subscribers of this event from the event service, creates - * a new instance of each subscriber, passing the payload of this event to - * the subscriber's constructor, and then dispatches the subscriber event - * using the event service. - */ - protected notifySubscribers() { - const eventService = this.getEventService(); - const subscribers = eventService.getSubscribers(this.getName()); - - for (const subscriber of subscribers) { - const subscriberEvent = new subscriber(this.getPayload()); - - eventService.dispatch(subscriberEvent); - } } } diff --git a/src/core/domains/events/concerns/EventMockableConcern.ts b/src/core/domains/events/concerns/EventMockableConcern.ts index 651fa527e..0d5f8f01e 100644 --- a/src/core/domains/events/concerns/EventMockableConcern.ts +++ b/src/core/domains/events/concerns/EventMockableConcern.ts @@ -3,7 +3,6 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { IMockableConcern, TMockableEventCallback } from "@src/core/domains/events/interfaces/IMockableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { TISerializablePayload } from "../interfaces/IEventPayload"; const EventMockableConcern = (Base: ICtor) => { return class EventMockable extends Base implements IMockableConcern { @@ -75,7 +74,7 @@ const EventMockableConcern = (Base: ICtor) => { * @throws Will throw an error if the event was not dispatched or if the dispatched event's * payload does not satisfy the given condition. */ - assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean { + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean { const eventCtorName = (new eventCtor(null)).getName() const dispatchedEvent = this.mockEventsDispatched.find(e => e.getName() === eventCtorName) diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index f4f3e358a..c90bb511e 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -5,9 +5,8 @@ import { IExecutable } from "@src/core/interfaces/concerns/IExecutable"; import { INameable } from "@src/core/interfaces/concerns/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { TISerializablePayload } from "./IEventPayload"; -export interface IBaseEvent extends INameable, IExecutable +export interface IBaseEvent extends INameable, IExecutable { getQueueName(): string; getEventService(): IEventService; diff --git a/src/core/domains/events/interfaces/IMockableConcern.ts b/src/core/domains/events/interfaces/IMockableConcern.ts index fcaee2489..85b27ce2b 100644 --- a/src/core/domains/events/interfaces/IMockableConcern.ts +++ b/src/core/domains/events/interfaces/IMockableConcern.ts @@ -2,14 +2,13 @@ import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import { ICtor } from "@src/core/interfaces/ICtor"; -import { TISerializablePayload } from "./IEventPayload"; -export type TMockableEventCallback = (payload: TPayload) => boolean; +export type TMockableEventCallback = (payload: TPayload) => boolean; export interface IMockableConcern { mockEvent(event: ICtor): void; mockEventDispatched(event: IBaseEvent): void; - assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean; + assertDispatched(eventCtor: ICtor, callback?: TMockableEventCallback): boolean; } \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 9eb2c0b6c..7ded826e3 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -12,7 +12,8 @@ import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@s import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; -import { TISerializablePayload } from "../interfaces/IEventPayload"; +import BaseEventListener from "../base/BaseEventListener"; + class EventService extends BaseService implements IEventService { @@ -99,7 +100,7 @@ class EventService extends BaseService implements IEventService { declare mockEventDispatched: (event: IBaseEvent) => void; - declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean + declare assertDispatched: (eventCtor: ICtor, callback?: TMockableEventCallback) => boolean /** * Delcare EventWorkerConcern methods. @@ -123,6 +124,10 @@ class EventService extends BaseService implements IEventService { const eventDriver = new eventDriverCtor(this) await eventDriver.dispatch(event) + // Notify all subscribers of the event + if(event instanceof BaseEventListener) { + await this.notifySubscribers(event); + } } /** @@ -238,6 +243,23 @@ class EventService extends BaseService implements IEventService { return registeredEvents.get(eventName)?.[0] } + /** + * Notifies all subscribers of this event that the event has been dispatched. + * + * Retrieves all subscribers of this event from the event service, creates + * a new instance of each subscriber, passing the payload of this event to + * the subscriber's constructor, and then dispatches the subscriber event + * using the event service. + */ + async notifySubscribers(eventListener: BaseEventListener) { + const subscribers = this.getSubscribers(eventListener.getName()); + + for (const subscriber of subscribers) { + const eventSubscriber = new subscriber(eventListener.getPayload()); + await this.dispatch(eventSubscriber); + } + } + /** * Returns an array of event subscriber constructors that are listening to this event. * @returns An array of event subscriber constructors. diff --git a/src/tests/events/eventAuthUserRegistered.test.ts b/src/tests/events/eventAuthUserRegistered.test.ts new file mode 100644 index 000000000..f82f86501 --- /dev/null +++ b/src/tests/events/eventAuthUserRegistered.test.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import { UserCreatedListener } from '@src/app/events/listeners/UserCreatedListener'; +import UserCreatedSubscriber from '@src/app/events/subscribers/UserCreatedSubscriber'; +import { IUserData } from '@src/app/models/auth/User'; +import UserFactory from '@src/core/domains/auth/factory/userFactory'; +import Kernel from '@src/core/Kernel'; +import { App } from '@src/core/services/App'; +import testAppConfig from '@src/tests/config/testConfig'; +import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; +import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; +import TestEventProvider from '@src/tests/providers/TestEventProvider'; + + +describe('mock queable event', () => { + + /** + * Register the test event provider + */ + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + new TestConsoleProvider(), + new TestDatabaseProvider(), + new TestEventProvider() + ] + }, {}) + }) + + afterAll(async () => { + // await dropWorkerTables(); + }) + + + /** + * - Dispatch TestEventQueueEvent, this will add a queued item to the database + * - Run the worker, which will dispatch TestEventQueueCalledFromWorkerEvent + * - Check the events have been dispatched + * - Check the worker empty has been cleared + */ + test('test queued worker ', async () => { + + const eventService = App.container('events'); + + eventService.mockEvent(UserCreatedListener) + eventService.mockEvent(UserCreatedSubscriber) + + const testUser = new UserFactory().createWithData({ + email: 'test@example.com', + hashedPassword: 'password', + roles: [], + groups: [], + firstName: 'Tony', + lastName: 'Stark' + }) + + await testUser.save(); + expect(testUser.getId()).toBeTruthy(); + + const expectedPayloadCallback = (payload: IUserData) => { + return payload.email === 'test@example.com' + } + + expect( + eventService.assertDispatched(UserCreatedListener, expectedPayloadCallback) + ).toBeTruthy() + + // expect( + // eventService.assertDispatched(UserCreatedSubscriber, expectedPayloadCallback) + // ).toBeTruthy() + }) + +}); \ No newline at end of file diff --git a/src/tests/providers/TestAuthProvider.ts b/src/tests/providers/TestAuthProvider.ts new file mode 100644 index 000000000..9ddb586ab --- /dev/null +++ b/src/tests/providers/TestAuthProvider.ts @@ -0,0 +1,9 @@ +import AuthProvider from "@src/core/domains/auth/providers/AuthProvider"; + +export default class TestAuthProvider extends AuthProvider { + + /** + * todo use test models + */ + +} diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index 122935ac7..f82ea9e6d 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -1,3 +1,5 @@ +import { UserCreatedListener } from '@src/app/events/listeners/UserCreatedListener'; +import UserCreatedSubscriber from '@src/app/events/subscribers/UserCreatedSubscriber'; import { EVENT_DRIVERS } from '@src/config/events'; import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; import SyncDriver from '@src/core/domains/events/drivers/SyncDriver'; @@ -34,12 +36,16 @@ class TestEventProvider extends EventProvider { }, events: [ + // Sync events TestEventSyncEvent, TestEventSyncBadPayloadEvent, + + // Queable (worker events) TestEventQueueEvent, TestEventQueueCalledFromWorkerEvent, TestEventQueueAddAlwaysFailsEventToQueue, - TestEventQueueAlwaysFailsEvent + TestEventQueueAlwaysFailsEvent, + ], listeners: EventService.createConfigListeners([ @@ -48,6 +54,12 @@ class TestEventProvider extends EventProvider { subscribers: [ TestSubscriber ] + }, + { + listener: UserCreatedListener, + subscribers: [ + UserCreatedSubscriber + ] } ]) } From af2be3b8ba1567c4511009fc3a0954dd111a78d9 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Tue, 5 Nov 2024 21:07:09 +0000 Subject: [PATCH 12/31] event auth tests --- .../events/listeners/UserCreatedListener.ts | 8 +++-- .../subscribers/UserCreatedSubscriber.ts | 4 ++- src/core/domains/events/base/BaseEvent.ts | 16 ++++++++-- .../domains/events/interfaces/IBaseEvent.ts | 2 ++ .../domains/events/services/EventService.ts | 4 ++- .../events/eventAuthUserRegistered.test.ts | 20 +++++++------ src/tests/events/eventQueableSuccess.test.ts | 6 ++-- ...estEventQueueAddAlwaysFailsEventToQueue.ts | 4 +-- .../events/TestEventQueueAlwaysFailsEvent.ts | 4 +-- .../events/events/TestEventQueueEvent.ts | 4 +-- .../events/auth/TestUserCreatedListener.ts | 11 +++++++ .../events/auth/TestUserCreatedSubscriber.ts | 29 +++++++++++++++++++ src/tests/events/listeners/TestListener.ts | 4 +-- .../events/subscribers/TestSubscriber.ts | 4 +-- 14 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 src/tests/events/events/auth/TestUserCreatedListener.ts create mode 100644 src/tests/events/events/auth/TestUserCreatedSubscriber.ts diff --git a/src/app/events/listeners/UserCreatedListener.ts b/src/app/events/listeners/UserCreatedListener.ts index 62ea6341e..a521a7011 100644 --- a/src/app/events/listeners/UserCreatedListener.ts +++ b/src/app/events/listeners/UserCreatedListener.ts @@ -3,8 +3,12 @@ import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; export class UserCreatedListener extends BaseEventListener { - // eslint-disable-next-line no-unused-vars - async execute(payload: IUserData): Promise { + + async execute(): Promise { + + // eslint-disable-next-line no-unused-vars + const userData = this.getPayload(); + // Handle some logic } diff --git a/src/app/events/subscribers/UserCreatedSubscriber.ts b/src/app/events/subscribers/UserCreatedSubscriber.ts index a9f1c656f..2dda036cf 100644 --- a/src/app/events/subscribers/UserCreatedSubscriber.ts +++ b/src/app/events/subscribers/UserCreatedSubscriber.ts @@ -20,7 +20,9 @@ export default class UserCreatedSubscriber extends BaseEvent { return 'default'; } - async execute(payload: IUserData): Promise { + async execute(): Promise { + const payload = this.getPayload(); + console.log('User was created', payload); } diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 4cc6cc312..585b1cacc 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -37,6 +37,11 @@ abstract class BaseEvent implements IBaseEvent { } } + /** + * Executes the event. + */ + async execute(): Promise {/* Nothing to execute */} + /** * Validates the payload of the event. Ensures that the payload is an object with types that match: * string, number, boolean, object, array, null. @@ -61,9 +66,6 @@ abstract class BaseEvent implements IBaseEvent { getEventService(): IEventService { return App.container('events'); } - - // eslint-disable-next-line no-unused-vars - async execute(...args: any[]): Promise {/* Nothing to execute */} /** * @returns The name of the queue as a string. @@ -80,6 +82,14 @@ abstract class BaseEvent implements IBaseEvent { return this.payload as T } + /** + * Sets the payload of the event. + * @param payload The payload of the event to set. + */ + setPayload(payload: TPayload): void { + this.payload = payload + } + /** * @returns The name of the event as a string. */ diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index c90bb511e..e1b4f9289 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-unused-vars */ import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; @@ -12,4 +13,5 @@ export interface IBaseEvent extends INameable, IExecutable getEventService(): IEventService; getDriverCtor(): ICtor; getPayload(): T; + setPayload(payload: TPayload): void; } \ No newline at end of file diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index 7ded826e3..e08e61517 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -255,7 +255,9 @@ class EventService extends BaseService implements IEventService { const subscribers = this.getSubscribers(eventListener.getName()); for (const subscriber of subscribers) { - const eventSubscriber = new subscriber(eventListener.getPayload()); + const eventSubscriber = new subscriber(null); + eventSubscriber.setPayload(eventListener.getPayload()); + await this.dispatch(eventSubscriber); } } diff --git a/src/tests/events/eventAuthUserRegistered.test.ts b/src/tests/events/eventAuthUserRegistered.test.ts index f82f86501..5598923b4 100644 --- a/src/tests/events/eventAuthUserRegistered.test.ts +++ b/src/tests/events/eventAuthUserRegistered.test.ts @@ -1,7 +1,5 @@ /* eslint-disable no-undef */ import { describe } from '@jest/globals'; -import { UserCreatedListener } from '@src/app/events/listeners/UserCreatedListener'; -import UserCreatedSubscriber from '@src/app/events/subscribers/UserCreatedSubscriber'; import { IUserData } from '@src/app/models/auth/User'; import UserFactory from '@src/core/domains/auth/factory/userFactory'; import Kernel from '@src/core/Kernel'; @@ -11,6 +9,9 @@ import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; +import { TestUserCreatedListener } from './events/auth/TestUserCreatedListener'; +import TestUserCreatedSubscriber from './events/auth/TestUserCreatedSubscriber'; + describe('mock queable event', () => { @@ -44,8 +45,8 @@ describe('mock queable event', () => { const eventService = App.container('events'); - eventService.mockEvent(UserCreatedListener) - eventService.mockEvent(UserCreatedSubscriber) + eventService.mockEvent(TestUserCreatedListener) + eventService.mockEvent(TestUserCreatedSubscriber) const testUser = new UserFactory().createWithData({ email: 'test@example.com', @@ -60,16 +61,17 @@ describe('mock queable event', () => { expect(testUser.getId()).toBeTruthy(); const expectedPayloadCallback = (payload: IUserData) => { - return payload.email === 'test@example.com' + return payload.id === testUser.getId() && payload.email === 'test@example.com' } expect( - eventService.assertDispatched(UserCreatedListener, expectedPayloadCallback) + eventService.assertDispatched(TestUserCreatedListener, expectedPayloadCallback) ).toBeTruthy() - // expect( - // eventService.assertDispatched(UserCreatedSubscriber, expectedPayloadCallback) - // ).toBeTruthy() + expect( + eventService.assertDispatched(TestUserCreatedSubscriber, expectedPayloadCallback) + ).toBeTruthy() }) + }); \ No newline at end of file diff --git a/src/tests/events/eventQueableSuccess.test.ts b/src/tests/events/eventQueableSuccess.test.ts index afc801676..0e34e010c 100644 --- a/src/tests/events/eventQueableSuccess.test.ts +++ b/src/tests/events/eventQueableSuccess.test.ts @@ -51,7 +51,7 @@ describe('mock queable event', () => { eventService.mockEvent(TestEventQueueEvent) eventService.mockEvent(TestEventQueueCalledFromWorkerEvent); - await eventService.dispatch(new TestEventQueueEvent({ hello: 'world' })); + await eventService.dispatch(new TestEventQueueEvent({ hello: 'world', createdAt: new Date() })); expect( eventService.assertDispatched<{ hello: string }>(TestEventQueueEvent, (payload) => { @@ -63,8 +63,8 @@ describe('mock queable event', () => { await App.container('console').reader(['worker', '--queue=testQueue']).handle(); expect( - eventService.assertDispatched<{ hello: string }>(TestEventQueueCalledFromWorkerEvent, (payload) => { - return payload.hello === 'world' + eventService.assertDispatched<{ hello: string, createdAt: Date }>(TestEventQueueCalledFromWorkerEvent, (payload) => { + return payload.hello === 'world' && payload.createdAt instanceof Date }) ).toBeTruthy() diff --git a/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts index 50e88f940..25a1f180a 100644 --- a/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts +++ b/src/tests/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts @@ -23,8 +23,8 @@ class TestEventQueueAddAlwaysFailsEventToQueue extends BaseEvent { return TestEventQueueAddAlwaysFailsEventToQueue.eventName; } - async execute(...args: any[]): Promise { - console.log('Executed TestEventQueueAddAlwaysFailsEventToQueue', this.getPayload(), this.getName(), {args}) + async execute(): Promise { + console.log('Executed TestEventQueueAddAlwaysFailsEventToQueue', this.getPayload(), this.getName()) await App.container('events').dispatch(new TestEventQueueAlwaysFailsEvent()) } diff --git a/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts b/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts index 9c7cda7d6..704e68a9b 100644 --- a/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts +++ b/src/tests/events/events/TestEventQueueAlwaysFailsEvent.ts @@ -16,8 +16,8 @@ class TestEventQueueAlwaysFailsEvent extends BaseEvent { return TestEventQueueAlwaysFailsEvent.eventName; } - async execute(...args: any[]): Promise { - console.log('Executed TestEventQueueAlwaysFailsEvent', this.getPayload(), this.getName(), {args}) + async execute(): Promise { + console.log('Executed TestEventQueueAlwaysFailsEvent', this.getPayload(), this.getName()) throw new Error('Always fails'); } diff --git a/src/tests/events/events/TestEventQueueEvent.ts b/src/tests/events/events/TestEventQueueEvent.ts index e8b5d5b73..6837107d0 100644 --- a/src/tests/events/events/TestEventQueueEvent.ts +++ b/src/tests/events/events/TestEventQueueEvent.ts @@ -22,8 +22,8 @@ class TestEventQueueEvent extends BaseEvent { return TestEventQueueEvent.eventName; } - async execute(...args: any[]): Promise { - console.log('Executed TestEventQueueEvent', this.getPayload(), this.getName(), {args}) + async execute(): Promise { + console.log('Executed TestEventQueueEvent', this.getPayload(), this.getName()) App.container('events').dispatch(new TestEventQueueCalledFromWorkerEvent(this.getPayload())) } diff --git a/src/tests/events/events/auth/TestUserCreatedListener.ts b/src/tests/events/events/auth/TestUserCreatedListener.ts new file mode 100644 index 000000000..e0cc66592 --- /dev/null +++ b/src/tests/events/events/auth/TestUserCreatedListener.ts @@ -0,0 +1,11 @@ +import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; + +export class TestUserCreatedListener extends BaseEventListener { + + protected namespace: string = 'testing'; + + async execute(): Promise { + console.log('Executed TestUserCreatedListener', this.getPayload(), this.getName()) + } + +} \ No newline at end of file diff --git a/src/tests/events/events/auth/TestUserCreatedSubscriber.ts b/src/tests/events/events/auth/TestUserCreatedSubscriber.ts new file mode 100644 index 000000000..03dac4048 --- /dev/null +++ b/src/tests/events/events/auth/TestUserCreatedSubscriber.ts @@ -0,0 +1,29 @@ +import { IUserData } from "@src/app/models/auth/User"; +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; + +export default class TestUserCreatedSubscriber extends BaseEvent { + + static readonly eventName = 'UserRegisteredEvent'; + + protected namespace: string = 'testing'; + + constructor(payload) { + super(payload, SyncDriver); + } + + getName(): string { + return TestUserCreatedSubscriber.eventName; + } + + getQueueName(): string { + return 'default'; + } + + async execute(): Promise { + const payload = this.getPayload(); + + console.log('User was created', payload); + } + +} \ No newline at end of file diff --git a/src/tests/events/listeners/TestListener.ts b/src/tests/events/listeners/TestListener.ts index 822708a3c..5bea708d0 100644 --- a/src/tests/events/listeners/TestListener.ts +++ b/src/tests/events/listeners/TestListener.ts @@ -7,8 +7,8 @@ class TestListener extends BaseEventListener { super(payload, SyncDriver); } - // eslint-disable-next-line no-unused-vars - async execute(...args: any[]): Promise { + + async execute(): Promise { console.log('Executed TestListener', this.getPayload(), this.getName()); } diff --git a/src/tests/events/subscribers/TestSubscriber.ts b/src/tests/events/subscribers/TestSubscriber.ts index 2cddc9c8d..9c993cb30 100644 --- a/src/tests/events/subscribers/TestSubscriber.ts +++ b/src/tests/events/subscribers/TestSubscriber.ts @@ -4,8 +4,8 @@ import TestEventSyncEvent from "@src/tests/events/events/TestEventSyncEvent"; class TestSubscriber extends BaseEvent { - async execute(...args: any[]): Promise { - console.log('Executed TestSubscriber', this.getPayload(), this.getName(), {args}) + async execute(): Promise { + console.log('Executed TestSubscriber', this.getPayload(), this.getName()) this.getEventService().dispatch(new TestEventSyncEvent(this.getPayload())); } From 73b646a4a3279fbbad0347da8327d91ce164e9b5 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Tue, 5 Nov 2024 21:12:45 +0000 Subject: [PATCH 13/31] add castable concern --- src/core/concerns/HasCastableConcern.ts | 280 ++++++++++++++++++ src/core/exceptions/CastException.ts | 8 + .../concerns/IHasCastableConcern.ts | 21 ++ src/tests/casts/cast.test.ts | 194 ++++++++++++ 4 files changed, 503 insertions(+) create mode 100644 src/core/concerns/HasCastableConcern.ts create mode 100644 src/core/exceptions/CastException.ts create mode 100644 src/core/interfaces/concerns/IHasCastableConcern.ts create mode 100644 src/tests/casts/cast.test.ts diff --git a/src/core/concerns/HasCastableConcern.ts b/src/core/concerns/HasCastableConcern.ts new file mode 100644 index 000000000..02ed2902b --- /dev/null +++ b/src/core/concerns/HasCastableConcern.ts @@ -0,0 +1,280 @@ +import CastException from "../exceptions/CastException"; +import { IHasCastableConcern, TCastableType } from "../interfaces/concerns/IHasCastableConcern"; +import { ICtor } from "../interfaces/ICtor"; + +const HasCastableConcernMixin = (Base: ICtor) => { + return class HasCastableConcern extends Base implements IHasCastableConcern { + + + /** + * Casts the given data to the specified type. + * @template T The type to cast to + * @param {unknown} data - The data to cast + * @param {TCastableType} type - The target type for casting + * @returns {T} The casted data + * @throws {CastException} If the cast operation fails or is invalid + */ + cast(data: unknown, type: TCastableType): T { + if (!this.isValidType(type)) { + throw new CastException(`Invalid cast type: ${type}`); + } + + if (data === null || data === undefined) { + if (type === 'null') return null as T; + if (type === 'undefined') return undefined as T; + throw new CastException(`Cannot cast null/undefined to ${type}`); + } + + try { + switch (type) { + case 'string': return this.castString(data); + case 'number': return this.castNumber(data); + case 'boolean': return this.castBoolean(data); + case 'array': return this.castArray(data); + case 'object': return this.castObject(data); + case 'date': return this.castDate(data); + case 'integer': return this.castInteger(data); + case 'float': return this.castFloat(data); + case 'bigint': return this.castBigInt(data); + case 'map': return this.castMap(data); + case 'set': return this.castSet(data); + case 'symbol': return Symbol(String(data)) as unknown as T; + default: + throw new CastException(`Unsupported cast type: ${type}`); + } + } + catch (error) { + throw new CastException(`Cast failed: ${(error as Error).message}`); + } + } + + /** + * Validates if the given type is a supported castable type + * @param {TCastableType} type - The type to validate + * @returns {boolean} True if the type is valid, false otherwise + */ + isValidType(type: TCastableType): boolean { + const validTypes: TCastableType[] = [ + 'string', 'number', 'boolean', 'array', 'object', 'date', + 'integer', 'float', 'bigint', 'null', 'undefined', 'symbol', + 'map', 'set' + ]; + return validTypes.includes(type); + } + + /** + * Validates if a string represents a valid date + * @param {string} date - The date string to validate + * @returns {boolean} True if the string is a valid date, false otherwise + * @private + */ + private isValidDate(date: string): boolean { + const timestamp = Date.parse(date); + return !isNaN(timestamp); + } + + /** + * Casts data to a string + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a string + * @throws {CastException} If the cast operation fails + * @private + */ + private castString(data: unknown): T { + if (data instanceof Date) { + return data.toISOString() as unknown as T; + } + if (typeof data === 'object') { + return JSON.stringify(data) as unknown as T; + } + return String(data) as unknown as T; + } + + /** + * Casts data to a number + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a number + * @throws {CastException} If the data cannot be converted to a number + * @private + */ + private castNumber(data: unknown): T { + if (typeof data === 'string') { + const num = Number(data); + if (isNaN(num)) throw new CastException('Invalid number string'); + return num as unknown as T; + } + if (data instanceof Date) { + return data.getTime() as unknown as T; + } + return Number(data) as unknown as T; + } + + /** + * Casts data to a boolean + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a boolean + * @throws {CastException} If the data cannot be converted to a boolean + * @private + */ + private castBoolean(data: unknown): T { + if (typeof data === 'string') { + const lowercased = data.toLowerCase(); + if (['true', '1', 'yes'].includes(lowercased)) return true as unknown as T; + if (['false', '0', 'no'].includes(lowercased)) return false as unknown as T; + throw new CastException('Invalid boolean string'); + } + return Boolean(data) as unknown as T; + } + + /** + * Casts data to an array + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as an array + * @throws {CastException} If the data cannot be converted to an array + * @private + */ + private castArray(data: unknown): T { + if (typeof data === 'string') { + try { + return JSON.parse(data) as unknown as T; + } + catch { + return [data] as unknown as T; + } + } + if (data instanceof Set || data instanceof Map) { + return Array.from(data) as unknown as T; + } + if (Array.isArray(data)) return data as T; + return [data] as unknown as T; + } + + /** + * Casts data to an object + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as an object + * @throws {CastException} If the data cannot be converted to an object + * @private + */ + private castObject(data: unknown): T { + if (typeof data === 'string') { + try { + return JSON.parse(data) as T; + } + catch (error) { + throw new CastException('Invalid JSON string for object conversion'); + } + } + if (Array.isArray(data) || data instanceof Set || data instanceof Map) { + return Object.fromEntries( + Array.from(data).map((val, idx) => [idx, val]) + ) as unknown as T; + } + return Object(data) as T; + } + + /** + * Casts data to a Date object + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a Date object + * @throws {CastException} If the data cannot be converted to a Date + * @private + */ + private castDate(data: unknown): T { + if (data instanceof Date) return data as T; + if (typeof data === 'number') { + return new Date(data) as unknown as T; + } + if (typeof data === 'string' && this.isValidDate(data)) { + return new Date(data) as unknown as T; + } + throw new CastException('Invalid date format'); + } + + /** + * Casts data to an integer + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as an integer + * @throws {CastException} If the data cannot be converted to an integer + * @private + */ + private castInteger(data: unknown): T { + const int = parseInt(String(data), 10); + if (isNaN(int)) throw new CastException('Invalid integer'); + return int as unknown as T; + } + + /** + * Casts data to a float + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a float + * @throws {CastException} If the data cannot be converted to a float + * @private + */ + private castFloat(data: unknown): T { + const float = parseFloat(String(data)); + if (isNaN(float)) throw new CastException('Invalid float'); + return float as unknown as T; + } + + /** + * Casts data to a BigInt + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a BigInt + * @throws {CastException} If the data cannot be converted to a BigInt + * @private + */ + private castBigInt(data: unknown): T { + if (typeof data === 'string' || typeof data === 'number') { + try { + return BigInt(data) as unknown as T; + } + catch { + throw new CastException('Cannot convert to BigInt'); + } + } + throw new CastException('Cannot convert to BigInt'); + } + + /** + * Casts data to a Map + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a Map + * @throws {CastException} If the data cannot be converted to a Map + * @private + */ + private castMap(data: unknown): T { + if (data instanceof Map) return data as T; + throw new CastException('Cannot convert to Map'); + } + + /** + * Casts data to a Set + * @template T The return type + * @param {unknown} data - The data to cast + * @returns {T} The data as a Set + * @throws {CastException} If the data cannot be converted to a Set + * @private + */ + private castSet(data: unknown): T { + if (data instanceof Set) return data as T; + if (Array.isArray(data)) { + return new Set(data) as unknown as T; + } + return new Set([data]) as unknown as T; + } + + } +} + +export default HasCastableConcernMixin; \ No newline at end of file diff --git a/src/core/exceptions/CastException.ts b/src/core/exceptions/CastException.ts new file mode 100644 index 000000000..e65510401 --- /dev/null +++ b/src/core/exceptions/CastException.ts @@ -0,0 +1,8 @@ +export default class CastException extends Error { + + constructor(message: string = 'Cast Exception') { + super(message); + this.name = 'CastException'; + } + +} \ No newline at end of file diff --git a/src/core/interfaces/concerns/IHasCastableConcern.ts b/src/core/interfaces/concerns/IHasCastableConcern.ts new file mode 100644 index 000000000..9ab690065 --- /dev/null +++ b/src/core/interfaces/concerns/IHasCastableConcern.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-unused-vars */ +export type TCastableType = + | 'string' + | 'number' + | 'boolean' + | 'array' + | 'object' + | 'date' + | 'integer' + | 'float' + | 'bigint' + | 'null' + | 'undefined' + | 'symbol' + | 'map' + | 'set'; + +export interface IHasCastableConcern { + cast(data: unknown, type: TCastableType): T; + isValidType(type: TCastableType): boolean; +} \ No newline at end of file diff --git a/src/tests/casts/cast.test.ts b/src/tests/casts/cast.test.ts new file mode 100644 index 000000000..8eefc7884 --- /dev/null +++ b/src/tests/casts/cast.test.ts @@ -0,0 +1,194 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import HasCastableConcern from '@src/core/concerns/HasCastableConcern'; +import CastException from '@src/core/exceptions/CastException'; +import { IHasCastableConcern } from '@src/core/interfaces/concerns/IHasCastableConcern'; +import { ICtor } from '@src/core/interfaces/ICtor'; +import Kernel from '@src/core/Kernel'; +import compose from '@src/core/util/compose'; +import testAppConfig from '@src/tests/config/testConfig'; + +describe('HasCastableConcern Tests', () => { + let castable: IHasCastableConcern; + + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + ] + }, {}); + + const CastableClass = compose(class {}, HasCastableConcern) as ICtor; + castable = new CastableClass(); + }); + + describe('String Casting Tests', () => { + it('should cast various types to string', () => { + expect(castable.cast(123, 'string')).toBe('123'); + expect(castable.cast(true, 'string')).toBe('true'); + expect(castable.cast([1, 2, 3], 'string')).toBe('[1,2,3]'); + expect(castable.cast({ a: 1 }, 'string')).toBe('{"a":1}'); + + const date = new Date('2024-01-01'); + expect(castable.cast(date, 'string')).toBe(date.toISOString()); + }); + }); + + describe('Number Casting Tests', () => { + it('should cast valid values to number', () => { + expect(castable.cast('123', 'number')).toBe(123); + expect(castable.cast('123.45', 'number')).toBe(123.45); + expect(castable.cast(true, 'number')).toBe(1); + expect(castable.cast(false, 'number')).toBe(0); + }); + + it('should throw CastException for invalid number strings', () => { + expect(() => castable.cast('abc', 'number')).toThrow(CastException); + }); + }); + + describe('Boolean Casting Tests', () => { + it('should cast strings to boolean', () => { + expect(castable.cast('true', 'boolean')).toBe(true); + expect(castable.cast('false', 'boolean')).toBe(false); + expect(castable.cast('1', 'boolean')).toBe(true); + expect(castable.cast('0', 'boolean')).toBe(false); + expect(castable.cast('yes', 'boolean')).toBe(true); + expect(castable.cast('no', 'boolean')).toBe(false); + }); + + it('should cast numbers to boolean', () => { + expect(castable.cast(1, 'boolean')).toBe(true); + expect(castable.cast(0, 'boolean')).toBe(false); + }); + + it('should throw CastException for invalid boolean strings', () => { + expect(() => castable.cast('invalid', 'boolean')).toThrow(CastException); + }); + }); + + describe('Array Casting Tests', () => { + it('should cast to array', () => { + expect(castable.cast('["a","b"]', 'array')).toEqual(['a', 'b']); + expect(castable.cast(new Set([1, 2]), 'array')).toEqual([1, 2]); + expect(castable.cast(123, 'array')).toEqual([123]); + expect(castable.cast('invalid json', 'array')).toEqual(['invalid json']); + }); + }); + + describe('Object Casting Tests', () => { + it('should cast to object', () => { + expect(castable.cast('{"a":1}', 'object')).toEqual({ a: 1 }); + expect(castable.cast([1, 2], 'object')).toEqual({ '0': 1, '1': 2 }); + }); + + it('should throw CastException for invalid JSON strings', () => { + expect(() => castable.cast('invalid json', 'object')).toThrow(CastException); + }); + }); + + describe('Date Casting Tests', () => { + it('should cast to date', () => { + const date = new Date('2024-01-01'); + expect(castable.cast('2024-01-01', 'date')).toEqual(date); + expect(castable.cast(date.getTime(), 'date')).toEqual(date); + expect(castable.cast(date, 'date')).toEqual(date); + }); + + it('should throw CastException for invalid dates', () => { + expect(() => castable.cast('invalid date', 'date')).toThrow(CastException); + }); + }); + + describe('Integer Casting Tests', () => { + it('should cast to integer', () => { + expect(castable.cast('123', 'integer')).toBe(123); + expect(castable.cast('123.45', 'integer')).toBe(123); + expect(castable.cast(123.45, 'integer')).toBe(123); + }); + + it('should throw CastException for invalid integers', () => { + expect(() => castable.cast('abc', 'integer')).toThrow(CastException); + }); + }); + + describe('Float Casting Tests', () => { + it('should cast to float', () => { + expect(castable.cast('123.45', 'float')).toBe(123.45); + expect(castable.cast(123, 'float')).toBe(123.0); + }); + + it('should throw CastException for invalid floats', () => { + expect(() => castable.cast('abc', 'float')).toThrow(CastException); + }); + }); + + describe('BigInt Casting Tests', () => { + it('should cast to BigInt', () => { + expect(castable.cast('123', 'bigint')).toBe(BigInt(123)); + expect(castable.cast(123, 'bigint')).toBe(BigInt(123)); + }); + + it('should throw CastException for invalid BigInt values', () => { + expect(() => castable.cast('abc', 'bigint')).toThrow(CastException); + expect(() => castable.cast({}, 'bigint')).toThrow(CastException); + }); + }); + + describe('Map Casting Tests', () => { + it('should handle Map casting', () => { + const map = new Map([['a', 1]]); + expect(castable.cast(map, 'map')).toBe(map); + }); + + it('should throw CastException for invalid Map conversions', () => { + expect(() => castable.cast({}, 'map')).toThrow(CastException); + }); + }); + + describe('Set Casting Tests', () => { + it('should cast to Set', () => { + expect(castable.cast([1, 2, 3], 'set')).toEqual(new Set([1, 2, 3])); + expect(castable.cast(1, 'set')).toEqual(new Set([1])); + }); + }); + + describe('Symbol Casting Tests', () => { + it('should cast to Symbol', () => { + const sym = castable.cast('test', 'symbol') + expect(typeof sym).toBe('symbol'); + expect(sym.toString()).toBe('Symbol(test)'); + }); + }); + + describe('Null and Undefined Handling Tests', () => { + it('should handle null values correctly', () => { + expect(castable.cast(null, 'null')).toBeNull(); + expect(() => castable.cast(null, 'string')).toThrow(CastException); + }); + + it('should handle undefined values correctly', () => { + expect(castable.cast(undefined, 'undefined')).toBeUndefined(); + expect(() => castable.cast(undefined, 'string')).toThrow(CastException); + }); + }); + + describe('Invalid Type Tests', () => { + it('should throw CastException for invalid types', () => { + expect(() => castable.cast('test', 'invalid' as any)).toThrow(CastException); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty values', () => { + expect(castable.cast('', 'string')).toBe(''); + expect(castable.cast([], 'array')).toEqual([]); + expect(castable.cast({}, 'object')).toEqual({}); + }); + + it('should handle special characters', () => { + expect(castable.cast('§±!@#$%^&*()', 'string')).toBe('§±!@#$%^&*()'); + }); + }); +}); \ No newline at end of file From 4cd59f0de9f8abe46d75e76267cfc5d570b92c98 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Tue, 5 Nov 2024 21:45:40 +0000 Subject: [PATCH 14/31] added tests for casting --- src/core/base/BaseCastable.ts | 9 + src/core/concerns/HasCastableConcern.ts | 19 +- src/core/domains/events/base/BaseEvent.ts | 22 +- .../domains/events/base/BaseEventCastable.ts | 7 + .../domains/events/interfaces/IBaseEvent.ts | 3 +- .../concerns/IHasCastableConcern.ts | 4 +- src/tests/casts/cast.test.ts | 142 +++++---- src/tests/casts/castObject.test.ts | 276 ++++++++++++++++++ src/tests/events/eventSync.test.ts | 3 +- 9 files changed, 415 insertions(+), 70 deletions(-) create mode 100644 src/core/base/BaseCastable.ts create mode 100644 src/core/domains/events/base/BaseEventCastable.ts create mode 100644 src/tests/casts/castObject.test.ts diff --git a/src/core/base/BaseCastable.ts b/src/core/base/BaseCastable.ts new file mode 100644 index 000000000..6cd29e010 --- /dev/null +++ b/src/core/base/BaseCastable.ts @@ -0,0 +1,9 @@ +import HasCastableConcern from "@src/core/concerns/HasCastableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import compose from "@src/core/util/compose"; + +import { IHasCastableConcern } from "../interfaces/concerns/IHasCastableConcern"; + +const BaseCastable: ICtor = compose(class {}, HasCastableConcern) + +export default BaseCastable \ No newline at end of file diff --git a/src/core/concerns/HasCastableConcern.ts b/src/core/concerns/HasCastableConcern.ts index 02ed2902b..fb6c263b0 100644 --- a/src/core/concerns/HasCastableConcern.ts +++ b/src/core/concerns/HasCastableConcern.ts @@ -5,6 +5,23 @@ import { ICtor } from "../interfaces/ICtor"; const HasCastableConcernMixin = (Base: ICtor) => { return class HasCastableConcern extends Base implements IHasCastableConcern { + casts: Record = {}; + + /** + * Casts each property of the given data object according to the types specified in the casts record. + * @template ReturnType The return type of the casted object + * @param {Record} data - The object containing data to be casted + * @returns {ReturnType} The object with its properties casted to the specified types + */ + getCastFromObject(data: Record): ReturnType { + for(const [key, type] of Object.entries(this.casts)) { + if (key in data) { + data[key] = this.getCast(data[key], type); + } + } + + return data as ReturnType; + } /** * Casts the given data to the specified type. @@ -14,7 +31,7 @@ const HasCastableConcernMixin = (Base: ICtor) => { * @returns {T} The casted data * @throws {CastException} If the cast operation fails or is invalid */ - cast(data: unknown, type: TCastableType): T { + getCast(data: unknown, type: TCastableType): T { if (!this.isValidType(type)) { throw new CastException(`Invalid cast type: ${type}`); } diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 585b1cacc..dab87492e 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -1,12 +1,14 @@ +import BaseCastable from "@src/core/base/BaseCastable"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; +import { TCastableType } from "@src/core/interfaces/concerns/IHasCastableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; import EventInvalidPayloadException from "../exceptions/EventInvalidPayloadException"; -abstract class BaseEvent implements IBaseEvent { +abstract class BaseEvent extends BaseCastable implements IBaseEvent { protected payload: TPayload | null = null; @@ -14,9 +16,8 @@ abstract class BaseEvent implements IBaseEvent { protected defaultDriver!: ICtor; - /** - * Provide a namespace to avoid conflicts with other events. - */ + abstract casts: Record; + protected namespace: string = ''; /** @@ -25,6 +26,7 @@ abstract class BaseEvent implements IBaseEvent { * @param driver The class of the event driver */ constructor(payload: TPayload | null = null, driver?: ICtor) { + super() this.payload = payload; // Use safeContainer here to avoid errors during registering which runs during boot up. @@ -37,6 +39,18 @@ abstract class BaseEvent implements IBaseEvent { } } + /** + * Declare HasCastableConcern methods. + */ + // eslint-disable-next-line no-unused-vars + declare getCastFromObject: (data: Record) => ReturnType; + + // eslint-disable-next-line no-unused-vars + declare getCast: (data: unknown, type: TCastableType) => T; + + // eslint-disable-next-line no-unused-vars + declare isValidType: (type: TCastableType) => boolean; + /** * Executes the event. */ diff --git a/src/core/domains/events/base/BaseEventCastable.ts b/src/core/domains/events/base/BaseEventCastable.ts new file mode 100644 index 000000000..d0e43223c --- /dev/null +++ b/src/core/domains/events/base/BaseEventCastable.ts @@ -0,0 +1,7 @@ +import HasCastableConcern from "@src/core/concerns/HasCastableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; +import compose from "@src/core/util/compose"; + +const BaseEventCastable: ICtor = compose(class {}, HasCastableConcern) + +export default BaseEventCastable \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IBaseEvent.ts b/src/core/domains/events/interfaces/IBaseEvent.ts index e1b4f9289..99f6a80d4 100644 --- a/src/core/domains/events/interfaces/IBaseEvent.ts +++ b/src/core/domains/events/interfaces/IBaseEvent.ts @@ -3,11 +3,12 @@ import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; import { IExecutable } from "@src/core/interfaces/concerns/IExecutable"; +import { IHasCastableConcern } from "@src/core/interfaces/concerns/IHasCastableConcern"; import { INameable } from "@src/core/interfaces/concerns/INameable"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IBaseEvent extends INameable, IExecutable +export interface IBaseEvent extends INameable, IExecutable, IHasCastableConcern { getQueueName(): string; getEventService(): IEventService; diff --git a/src/core/interfaces/concerns/IHasCastableConcern.ts b/src/core/interfaces/concerns/IHasCastableConcern.ts index 9ab690065..984f82039 100644 --- a/src/core/interfaces/concerns/IHasCastableConcern.ts +++ b/src/core/interfaces/concerns/IHasCastableConcern.ts @@ -16,6 +16,8 @@ export type TCastableType = | 'set'; export interface IHasCastableConcern { - cast(data: unknown, type: TCastableType): T; + casts: Record; + getCastFromObject(data: Record): ReturnType; + getCast(data: unknown, type: TCastableType): T; isValidType(type: TCastableType): boolean; } \ No newline at end of file diff --git a/src/tests/casts/cast.test.ts b/src/tests/casts/cast.test.ts index 8eefc7884..51efb2127 100644 --- a/src/tests/casts/cast.test.ts +++ b/src/tests/casts/cast.test.ts @@ -1,11 +1,9 @@ /* eslint-disable no-undef */ import { describe } from '@jest/globals'; -import HasCastableConcern from '@src/core/concerns/HasCastableConcern'; +import BaseCastable from '@src/core/base/BaseCastable'; import CastException from '@src/core/exceptions/CastException'; -import { IHasCastableConcern } from '@src/core/interfaces/concerns/IHasCastableConcern'; -import { ICtor } from '@src/core/interfaces/ICtor'; +import { IHasCastableConcern, TCastableType } from '@src/core/interfaces/concerns/IHasCastableConcern'; import Kernel from '@src/core/Kernel'; -import compose from '@src/core/util/compose'; import testAppConfig from '@src/tests/config/testConfig'; describe('HasCastableConcern Tests', () => { @@ -19,144 +17,166 @@ describe('HasCastableConcern Tests', () => { ] }, {}); - const CastableClass = compose(class {}, HasCastableConcern) as ICtor; - castable = new CastableClass(); + + castable = new BaseCastable(); }); + describe('getCastFromObject', () => { + class TestClass extends BaseCastable { + + casts: Record = { + test: 'string', + } + + data = { + age: "18", + name: "John", + books: [ + "Book 1", + ], + createdAt: new Date() + } + + }; + + const testClass = new TestClass(); + testClass.data = testClass.getCastFromObject(testClass.data); + }) + describe('String Casting Tests', () => { it('should cast various types to string', () => { - expect(castable.cast(123, 'string')).toBe('123'); - expect(castable.cast(true, 'string')).toBe('true'); - expect(castable.cast([1, 2, 3], 'string')).toBe('[1,2,3]'); - expect(castable.cast({ a: 1 }, 'string')).toBe('{"a":1}'); + expect(castable.getCast(123, 'string')).toBe('123'); + expect(castable.getCast(true, 'string')).toBe('true'); + expect(castable.getCast([1, 2, 3], 'string')).toBe('[1,2,3]'); + expect(castable.getCast({ a: 1 }, 'string')).toBe('{"a":1}'); const date = new Date('2024-01-01'); - expect(castable.cast(date, 'string')).toBe(date.toISOString()); + expect(castable.getCast(date, 'string')).toBe(date.toISOString()); }); }); describe('Number Casting Tests', () => { it('should cast valid values to number', () => { - expect(castable.cast('123', 'number')).toBe(123); - expect(castable.cast('123.45', 'number')).toBe(123.45); - expect(castable.cast(true, 'number')).toBe(1); - expect(castable.cast(false, 'number')).toBe(0); + expect(castable.getCast('123', 'number')).toBe(123); + expect(castable.getCast('123.45', 'number')).toBe(123.45); + expect(castable.getCast(true, 'number')).toBe(1); + expect(castable.getCast(false, 'number')).toBe(0); }); it('should throw CastException for invalid number strings', () => { - expect(() => castable.cast('abc', 'number')).toThrow(CastException); + expect(() => castable.getCast('abc', 'number')).toThrow(CastException); }); }); describe('Boolean Casting Tests', () => { it('should cast strings to boolean', () => { - expect(castable.cast('true', 'boolean')).toBe(true); - expect(castable.cast('false', 'boolean')).toBe(false); - expect(castable.cast('1', 'boolean')).toBe(true); - expect(castable.cast('0', 'boolean')).toBe(false); - expect(castable.cast('yes', 'boolean')).toBe(true); - expect(castable.cast('no', 'boolean')).toBe(false); + expect(castable.getCast('true', 'boolean')).toBe(true); + expect(castable.getCast('false', 'boolean')).toBe(false); + expect(castable.getCast('1', 'boolean')).toBe(true); + expect(castable.getCast('0', 'boolean')).toBe(false); + expect(castable.getCast('yes', 'boolean')).toBe(true); + expect(castable.getCast('no', 'boolean')).toBe(false); }); it('should cast numbers to boolean', () => { - expect(castable.cast(1, 'boolean')).toBe(true); - expect(castable.cast(0, 'boolean')).toBe(false); + expect(castable.getCast(1, 'boolean')).toBe(true); + expect(castable.getCast(0, 'boolean')).toBe(false); }); it('should throw CastException for invalid boolean strings', () => { - expect(() => castable.cast('invalid', 'boolean')).toThrow(CastException); + expect(() => castable.getCast('invalid', 'boolean')).toThrow(CastException); }); }); describe('Array Casting Tests', () => { it('should cast to array', () => { - expect(castable.cast('["a","b"]', 'array')).toEqual(['a', 'b']); - expect(castable.cast(new Set([1, 2]), 'array')).toEqual([1, 2]); - expect(castable.cast(123, 'array')).toEqual([123]); - expect(castable.cast('invalid json', 'array')).toEqual(['invalid json']); + expect(castable.getCast('["a","b"]', 'array')).toEqual(['a', 'b']); + expect(castable.getCast(new Set([1, 2]), 'array')).toEqual([1, 2]); + expect(castable.getCast(123, 'array')).toEqual([123]); + expect(castable.getCast('invalid json', 'array')).toEqual(['invalid json']); }); }); describe('Object Casting Tests', () => { it('should cast to object', () => { - expect(castable.cast('{"a":1}', 'object')).toEqual({ a: 1 }); - expect(castable.cast([1, 2], 'object')).toEqual({ '0': 1, '1': 2 }); + expect(castable.getCast('{"a":1}', 'object')).toEqual({ a: 1 }); + expect(castable.getCast([1, 2], 'object')).toEqual({ '0': 1, '1': 2 }); }); it('should throw CastException for invalid JSON strings', () => { - expect(() => castable.cast('invalid json', 'object')).toThrow(CastException); + expect(() => castable.getCast('invalid json', 'object')).toThrow(CastException); }); }); describe('Date Casting Tests', () => { it('should cast to date', () => { const date = new Date('2024-01-01'); - expect(castable.cast('2024-01-01', 'date')).toEqual(date); - expect(castable.cast(date.getTime(), 'date')).toEqual(date); - expect(castable.cast(date, 'date')).toEqual(date); + expect(castable.getCast('2024-01-01', 'date')).toEqual(date); + expect(castable.getCast(date.getTime(), 'date')).toEqual(date); + expect(castable.getCast(date, 'date')).toEqual(date); }); it('should throw CastException for invalid dates', () => { - expect(() => castable.cast('invalid date', 'date')).toThrow(CastException); + expect(() => castable.getCast('invalid date', 'date')).toThrow(CastException); }); }); describe('Integer Casting Tests', () => { it('should cast to integer', () => { - expect(castable.cast('123', 'integer')).toBe(123); - expect(castable.cast('123.45', 'integer')).toBe(123); - expect(castable.cast(123.45, 'integer')).toBe(123); + expect(castable.getCast('123', 'integer')).toBe(123); + expect(castable.getCast('123.45', 'integer')).toBe(123); + expect(castable.getCast(123.45, 'integer')).toBe(123); }); it('should throw CastException for invalid integers', () => { - expect(() => castable.cast('abc', 'integer')).toThrow(CastException); + expect(() => castable.getCast('abc', 'integer')).toThrow(CastException); }); }); describe('Float Casting Tests', () => { it('should cast to float', () => { - expect(castable.cast('123.45', 'float')).toBe(123.45); - expect(castable.cast(123, 'float')).toBe(123.0); + expect(castable.getCast('123.45', 'float')).toBe(123.45); + expect(castable.getCast(123, 'float')).toBe(123.0); }); it('should throw CastException for invalid floats', () => { - expect(() => castable.cast('abc', 'float')).toThrow(CastException); + expect(() => castable.getCast('abc', 'float')).toThrow(CastException); }); }); describe('BigInt Casting Tests', () => { it('should cast to BigInt', () => { - expect(castable.cast('123', 'bigint')).toBe(BigInt(123)); - expect(castable.cast(123, 'bigint')).toBe(BigInt(123)); + expect(castable.getCast('123', 'bigint')).toBe(BigInt(123)); + expect(castable.getCast(123, 'bigint')).toBe(BigInt(123)); }); it('should throw CastException for invalid BigInt values', () => { - expect(() => castable.cast('abc', 'bigint')).toThrow(CastException); - expect(() => castable.cast({}, 'bigint')).toThrow(CastException); + expect(() => castable.getCast('abc', 'bigint')).toThrow(CastException); + expect(() => castable.getCast({}, 'bigint')).toThrow(CastException); }); }); describe('Map Casting Tests', () => { it('should handle Map casting', () => { const map = new Map([['a', 1]]); - expect(castable.cast(map, 'map')).toBe(map); + expect(castable.getCast(map, 'map')).toBe(map); }); it('should throw CastException for invalid Map conversions', () => { - expect(() => castable.cast({}, 'map')).toThrow(CastException); + expect(() => castable.getCast({}, 'map')).toThrow(CastException); }); }); describe('Set Casting Tests', () => { it('should cast to Set', () => { - expect(castable.cast([1, 2, 3], 'set')).toEqual(new Set([1, 2, 3])); - expect(castable.cast(1, 'set')).toEqual(new Set([1])); + expect(castable.getCast([1, 2, 3], 'set')).toEqual(new Set([1, 2, 3])); + expect(castable.getCast(1, 'set')).toEqual(new Set([1])); }); }); describe('Symbol Casting Tests', () => { it('should cast to Symbol', () => { - const sym = castable.cast('test', 'symbol') + const sym = castable.getCast('test', 'symbol') expect(typeof sym).toBe('symbol'); expect(sym.toString()).toBe('Symbol(test)'); }); @@ -164,31 +184,31 @@ describe('HasCastableConcern Tests', () => { describe('Null and Undefined Handling Tests', () => { it('should handle null values correctly', () => { - expect(castable.cast(null, 'null')).toBeNull(); - expect(() => castable.cast(null, 'string')).toThrow(CastException); + expect(castable.getCast(null, 'null')).toBeNull(); + expect(() => castable.getCast(null, 'string')).toThrow(CastException); }); it('should handle undefined values correctly', () => { - expect(castable.cast(undefined, 'undefined')).toBeUndefined(); - expect(() => castable.cast(undefined, 'string')).toThrow(CastException); + expect(castable.getCast(undefined, 'undefined')).toBeUndefined(); + expect(() => castable.getCast(undefined, 'string')).toThrow(CastException); }); }); describe('Invalid Type Tests', () => { it('should throw CastException for invalid types', () => { - expect(() => castable.cast('test', 'invalid' as any)).toThrow(CastException); + expect(() => castable.getCast('test', 'invalid' as any)).toThrow(CastException); }); }); describe('Edge Cases', () => { it('should handle empty values', () => { - expect(castable.cast('', 'string')).toBe(''); - expect(castable.cast([], 'array')).toEqual([]); - expect(castable.cast({}, 'object')).toEqual({}); + expect(castable.getCast('', 'string')).toBe(''); + expect(castable.getCast([], 'array')).toEqual([]); + expect(castable.getCast({}, 'object')).toEqual({}); }); it('should handle special characters', () => { - expect(castable.cast('§±!@#$%^&*()', 'string')).toBe('§±!@#$%^&*()'); + expect(castable.getCast('§±!@#$%^&*()', 'string')).toBe('§±!@#$%^&*()'); }); }); }); \ No newline at end of file diff --git a/src/tests/casts/castObject.test.ts b/src/tests/casts/castObject.test.ts new file mode 100644 index 000000000..2d30c57a3 --- /dev/null +++ b/src/tests/casts/castObject.test.ts @@ -0,0 +1,276 @@ +/* eslint-disable no-undef */ +import { describe } from '@jest/globals'; +import BaseCastable from '@src/core/base/BaseCastable'; +import CastException from '@src/core/exceptions/CastException'; +import { TCastableType } from '@src/core/interfaces/concerns/IHasCastableConcern'; +import Kernel from '@src/core/Kernel'; +import testAppConfig from '@src/tests/config/testConfig'; + +describe('HasCastableConcern Tests', () => { + beforeAll(async () => { + await Kernel.boot({ + ...testAppConfig, + providers: [ + ...testAppConfig.providers, + ] + }, {}); + }); + + describe('getCastFromObject', () => { + describe('basic type casting', () => { + interface TestData { + age: number; + isActive: boolean; + joinDate: Date; + score: number; + rank: number; + items: string[]; + settings: { theme: string }; + userId: bigint; + userType: symbol; + name: string; + books: string[]; + createdAt: Date; + } + + class TestClass extends BaseCastable { + + casts: Record = { + age: 'number', + isActive: 'boolean', + joinDate: 'date', + score: 'float', + rank: 'integer', + items: 'array', + settings: 'object', + userId: 'bigint', + userType: 'symbol' + } + + data = { + age: "25", + isActive: "1", + joinDate: "2024-01-01", + score: "91.5", + rank: "1", + items: '["item1", "item2"]', + settings: '{"theme":"dark"}', + userId: "1234567890", + userType: "premium", + name: "John", + books: ["Book 1"], + createdAt: new Date() + } + + } + + const testClass = new TestClass(); + const result = testClass.getCastFromObject(testClass.data); + + it('should cast values according to casts property', () => { + expect(typeof result.age).toBe('number'); + expect(result.age).toBe(25); + + expect(typeof result.isActive).toBe('boolean'); + expect(result.isActive).toBe(true); + + expect(result.joinDate).toBeInstanceOf(Date); + expect(result.joinDate.toISOString()).toContain('2024-01-01'); + + expect(typeof result.score).toBe('number'); + expect(result.score).toBe(91.5); + + expect(typeof result.rank).toBe('number'); + expect(result.rank).toBe(1); + expect(Number.isInteger(result.rank)).toBe(true); + + expect(Array.isArray(result.items)).toBe(true); + expect(result.items).toEqual(['item1', 'item2']); + + expect(typeof result.settings).toBe('object'); + expect(result.settings).toEqual({ theme: 'dark' }); + + expect(typeof result.userId).toBe('bigint'); + expect(result.userId.toString()).toBe('1234567890'); + + expect(typeof result.userType).toBe('symbol'); + expect(result.userType.toString()).toBe('Symbol(premium)'); + }); + + it('should not cast properties not defined in casts', () => { + expect(typeof result.name).toBe('string'); + expect(result.name).toBe('John'); + + expect(Array.isArray(result.books)).toBe(true); + expect(result.books).toEqual(['Book 1']); + + expect(result.createdAt).toBeInstanceOf(Date); + }); + }); + + describe('error handling', () => { + interface InvalidData { + age: number; + joinDate: Date; + score: number; + } + + class InvalidCastClass extends BaseCastable { + + casts: Record = { + age: 'number', + joinDate: 'date', + score: 'float' + } + + data = { + age: "not a number", + joinDate: "invalid date", + score: "not a float" + } + + } + + it('should throw CastException for invalid cast values', () => { + const invalidClass = new InvalidCastClass(); + expect(() => { + invalidClass.getCastFromObject(invalidClass.data); + }).toThrow(CastException); + }); + }); + + describe('null and undefined handling', () => { + interface NullableData { + nullValue: null; + undefinedValue: undefined; + optionalNumber: number; + } + + class NullableClass extends BaseCastable { + + casts: Record = { + nullValue: 'null', + undefinedValue: 'undefined', + optionalNumber: 'number' + } + + data = { + nullValue: null, + undefinedValue: undefined, + optionalNumber: null + } + + } + + it('should handle null and undefined values correctly', () => { + const nullableClass = new NullableClass(); + + // Test null and undefined separately first + const validData = { + nullValue: null, + undefinedValue: undefined + }; + + const result: NullableData = nullableClass.getCastFromObject(validData); + expect(result.nullValue).toBeNull(); + expect(result.undefinedValue).toBeUndefined(); + }); + + it('should throw CastException when trying to cast null to number', () => { + const nullableClass = new NullableClass(); + + expect(() => { + nullableClass.getCastFromObject(nullableClass.data); + }).toThrow(CastException); + }); + }); + + describe('empty and invalid casts', () => { + interface EmptyData { + name: string; + age: string; + } + + class EmptyCastClass extends BaseCastable { + + casts: Record = {} + + data = { + name: "John", + age: "25" + } + + } + + it('should return original data when no casts are defined', () => { + const emptyClass = new EmptyCastClass(); + const result = emptyClass.getCastFromObject(emptyClass.data); + + expect(result).toEqual(emptyClass.data); + }); + + interface InvalidTypeData { + age: unknown; + } + + class InvalidTypeCastClass extends BaseCastable { + + casts: Record = { + age: 'invalid' as TCastableType + } + + data = { + age: "25" + } + + } + + it('should throw CastException for invalid cast types', () => { + const invalidClass = new InvalidTypeCastClass(); + expect(() => { + invalidClass.getCastFromObject(invalidClass.data); + }).toThrow(CastException); + }); + }); + + describe('performance with many properties', () => { + interface LargeData { + prop1: number; + prop2: string; + prop3: boolean; + nonCast1: string; + nonCast2: string; + nonCast3: string; + } + + class LargeDataClass extends BaseCastable { + + casts: Record = { + prop1: 'number', + prop2: 'string', + prop3: 'boolean', + } + + data = { + prop1: "1", + prop2: 2, + prop3: "true", + nonCast1: "value1", + nonCast2: "value2", + nonCast3: "value3" + } + + } + + it('should handle large objects efficiently', () => { + const largeClass = new LargeDataClass(); + const result = largeClass.getCastFromObject(largeClass.data); + + expect(typeof result.prop1).toBe('number'); + expect(typeof result.prop2).toBe('string'); + expect(typeof result.prop3).toBe('boolean'); + expect(result.nonCast1).toBe('value1'); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/tests/events/eventSync.test.ts b/src/tests/events/eventSync.test.ts index 4ee8e5da1..477cdde98 100644 --- a/src/tests/events/eventSync.test.ts +++ b/src/tests/events/eventSync.test.ts @@ -3,12 +3,11 @@ import { describe } from '@jest/globals'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; +import TestEventSyncBadPayloadEvent from '@src/tests/events/events/TestEventSyncBadPayloadEvent'; import TestEventSyncEvent from '@src/tests/events/events/TestEventSyncEvent'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; -import TestEventSyncBadPayloadEvent from '@src/tests/events/events/TestEventSyncBadPayloadEvent'; - describe('mock event service', () => { /** From 45402951913b9f4b4a24803dfe76508cb631a531 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Tue, 5 Nov 2024 21:56:47 +0000 Subject: [PATCH 15/31] cast event payload --- src/core/concerns/HasCastableConcern.ts | 8 ++++---- src/core/domains/events/base/BaseEvent.ts | 10 +++++----- src/core/interfaces/concerns/IHasCastableConcern.ts | 6 ++++-- src/core/util/castObject.ts | 8 ++++++++ .../events/TestEventQueueCalledFromWorkerEvent.ts | 5 +++++ 5 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 src/core/util/castObject.ts diff --git a/src/core/concerns/HasCastableConcern.ts b/src/core/concerns/HasCastableConcern.ts index fb6c263b0..e43093b53 100644 --- a/src/core/concerns/HasCastableConcern.ts +++ b/src/core/concerns/HasCastableConcern.ts @@ -1,11 +1,11 @@ import CastException from "../exceptions/CastException"; -import { IHasCastableConcern, TCastableType } from "../interfaces/concerns/IHasCastableConcern"; +import { IHasCastableConcern, TCastableType, TCasts } from "../interfaces/concerns/IHasCastableConcern"; import { ICtor } from "../interfaces/ICtor"; const HasCastableConcernMixin = (Base: ICtor) => { return class HasCastableConcern extends Base implements IHasCastableConcern { - casts: Record = {}; + casts: TCasts = {}; /** * Casts each property of the given data object according to the types specified in the casts record. @@ -13,8 +13,8 @@ const HasCastableConcernMixin = (Base: ICtor) => { * @param {Record} data - The object containing data to be casted * @returns {ReturnType} The object with its properties casted to the specified types */ - getCastFromObject(data: Record): ReturnType { - for(const [key, type] of Object.entries(this.casts)) { + getCastFromObject(data: Record, casts: TCasts = this.casts ): ReturnType { + for(const [key, type] of Object.entries(casts)) { if (key in data) { data[key] = this.getCast(data[key], type); } diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index dab87492e..41103d04f 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -2,7 +2,7 @@ import BaseCastable from "@src/core/base/BaseCastable"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; -import { TCastableType } from "@src/core/interfaces/concerns/IHasCastableConcern"; +import { TCastableType, TCasts } from "@src/core/interfaces/concerns/IHasCastableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; @@ -16,10 +16,10 @@ abstract class BaseEvent extends BaseCastable implements IBa protected defaultDriver!: ICtor; - abstract casts: Record; - protected namespace: string = ''; + casts: TCasts = {}; + /** * Constructor * @param payload The payload of the event @@ -43,7 +43,7 @@ abstract class BaseEvent extends BaseCastable implements IBa * Declare HasCastableConcern methods. */ // eslint-disable-next-line no-unused-vars - declare getCastFromObject: (data: Record) => ReturnType; + declare getCastFromObject: (data: Record, casts: TCasts) => ReturnType; // eslint-disable-next-line no-unused-vars declare getCast: (data: unknown, type: TCastableType) => T; @@ -93,7 +93,7 @@ abstract class BaseEvent extends BaseCastable implements IBa * @returns The payload of the event. */ getPayload(): T { - return this.payload as T + return this.getCastFromObject(this.payload as Record, this.casts) } /** diff --git a/src/core/interfaces/concerns/IHasCastableConcern.ts b/src/core/interfaces/concerns/IHasCastableConcern.ts index 984f82039..7d7ab96ad 100644 --- a/src/core/interfaces/concerns/IHasCastableConcern.ts +++ b/src/core/interfaces/concerns/IHasCastableConcern.ts @@ -17,7 +17,9 @@ export type TCastableType = export interface IHasCastableConcern { casts: Record; - getCastFromObject(data: Record): ReturnType; + getCastFromObject(data: Record, casts: TCasts): ReturnType; getCast(data: unknown, type: TCastableType): T; isValidType(type: TCastableType): boolean; -} \ No newline at end of file +} + +export type TCasts = Record; \ No newline at end of file diff --git a/src/core/util/castObject.ts b/src/core/util/castObject.ts new file mode 100644 index 000000000..70b8ca13f --- /dev/null +++ b/src/core/util/castObject.ts @@ -0,0 +1,8 @@ +import BaseCastable from "../base/BaseCastable"; +import { TCasts } from "../interfaces/concerns/IHasCastableConcern"; + +const castObject = (data: unknown, casts: TCasts): ReturnType => { + return new BaseCastable().getCastFromObject(data as Record, casts) +} + +export default castObject \ No newline at end of file diff --git a/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts index 2069af559..4b2e50ad0 100644 --- a/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts +++ b/src/tests/events/events/TestEventQueueCalledFromWorkerEvent.ts @@ -1,6 +1,7 @@ import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; +import { TCasts } from "@src/core/interfaces/concerns/IHasCastableConcern"; class TestEventQueueCalledFromWorkerEvent extends BaseEvent { @@ -8,6 +9,10 @@ class TestEventQueueCalledFromWorkerEvent extends BaseEvent { static readonly eventName = 'TestEventQueueCalledFromWorkerEvent'; + casts: TCasts = { + createdAt: "date" + } + constructor(payload) { super(payload, SyncDriver) } From a12c0b43a2f249875bca20480b034cf0b57d4877 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 20:41:16 +0000 Subject: [PATCH 16/31] Comments, removed legacy events --- src/core/concerns/HasRegisterableConcern.ts | 42 ++++ .../events-legacy/drivers/QueueDriver.ts | 71 ------- .../drivers/SynchronousDriver.ts | 30 --- .../exceptions/EventDriverException.ts | 8 - .../exceptions/EventSubscriberException.ts | 8 - .../factory/failedWorkerModelFactory.ts | 28 --- .../factory/workerModelFactory.ts | 34 ---- .../events-legacy/interfaces/IDispatchable.ts | 4 - .../events-legacy/interfaces/IEvent.ts | 13 -- .../events-legacy/interfaces/IEventConfig.ts | 16 -- .../interfaces/IEventDispatcher.ts | 7 - .../events-legacy/interfaces/IEventDriver.ts | 13 -- .../interfaces/IEventListener.ts | 6 - .../events-legacy/interfaces/IEventPayload.ts | 5 - .../events-legacy/interfaces/IEventService.ts | 18 -- .../models/FailedWorkerLegacyModel.ts | 76 ------- .../events-legacy/models/WorkerLegacyModel.ts | 78 ------- .../providers/EventLegacyProvider.ts | 35 ---- .../events-legacy/services/EventDispatcher.ts | 40 ---- .../events-legacy/services/EventListener.ts | 11 - .../events-legacy/services/EventService.ts | 61 ------ .../events-legacy/services/EventSubscriber.ts | 56 ----- .../services/QueueDriverOptions.ts | 32 --- .../events-legacy/services/WorkerLegacy.ts | 191 ------------------ .../domains/events/base/BaseEventCastable.ts | 7 - .../concerns/IHasCastableConcern.ts | 2 +- 26 files changed, 43 insertions(+), 849 deletions(-) delete mode 100644 src/core/domains/events-legacy/drivers/QueueDriver.ts delete mode 100644 src/core/domains/events-legacy/drivers/SynchronousDriver.ts delete mode 100644 src/core/domains/events-legacy/exceptions/EventDriverException.ts delete mode 100644 src/core/domains/events-legacy/exceptions/EventSubscriberException.ts delete mode 100644 src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts delete mode 100644 src/core/domains/events-legacy/factory/workerModelFactory.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IDispatchable.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEvent.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventConfig.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventDispatcher.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventDriver.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventListener.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventPayload.ts delete mode 100644 src/core/domains/events-legacy/interfaces/IEventService.ts delete mode 100644 src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts delete mode 100644 src/core/domains/events-legacy/models/WorkerLegacyModel.ts delete mode 100644 src/core/domains/events-legacy/providers/EventLegacyProvider.ts delete mode 100644 src/core/domains/events-legacy/services/EventDispatcher.ts delete mode 100644 src/core/domains/events-legacy/services/EventListener.ts delete mode 100644 src/core/domains/events-legacy/services/EventService.ts delete mode 100644 src/core/domains/events-legacy/services/EventSubscriber.ts delete mode 100644 src/core/domains/events-legacy/services/QueueDriverOptions.ts delete mode 100644 src/core/domains/events-legacy/services/WorkerLegacy.ts delete mode 100644 src/core/domains/events/base/BaseEventCastable.ts diff --git a/src/core/concerns/HasRegisterableConcern.ts b/src/core/concerns/HasRegisterableConcern.ts index 308c79817..1a23c3688 100644 --- a/src/core/concerns/HasRegisterableConcern.ts +++ b/src/core/concerns/HasRegisterableConcern.ts @@ -30,6 +30,14 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { private static defaultList = 'default'; + /** + * Registers a key-value pair in the default list. + * If the default list does not exist, it initializes it as a new Map. + * + * @param {string} key - The key to register the value under. + * @param {...unknown[]} args - The values to be associated with the key. + * @returns {void} + */ register(key: string, ...args: unknown[]): void { if(!this.registerObject[HasRegisterable.defaultList]) { this.registerObject[HasRegisterable.defaultList] = new Map(); @@ -38,23 +46,57 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { this.registerObject[HasRegisterable.defaultList].set(key, args); } + /** + * Registers a key-value pair in the list with the given name. + * If the list does not exist, it initializes it as a new Map. + * + * @param {string} listName - The name of the list to register the value in. + * @param {string} key - The key to register the value under. + * @param {...unknown[]} args - The values to be associated with the key. + * @returns {void} + */ registerByList(listName: string, key: string, ...args: unknown[]): void { this.registerObject[listName] = this.registerObject[listName] ?? new Map(); this.registerObject[listName].set(key, args); } + /** + * Sets the registered values for the given list name. + * If the list does not exist, it initializes it as a new Map. + * @param {string} listName - The name of the list to set the values for. + * @param {Map} registered - The values to be associated with the list. + * @returns {void} + */ setRegisteredByList(listName: string, registered: Map): void { this.registerObject[listName] = registered } + /** + * Retrieves the entire register object containing all registered lists and their key-value pairs. + * + * @returns {IRegsiterList} The complete register object. + */ getRegisteredObject(): IRegsiterList { return this.registerObject; } + /** + * Retrieves the registered values from the default list. + * + * @returns {TRegisterMap} A map of key-value pairs from the default list. + */ getRegisteredList(): T { return this.getRegisteredByList(HasRegisterable.defaultList) } + /** + * Retrieves the registered values for a specific list. + * If the list does not exist, returns an empty map. + * + * @template T Type of the register map. + * @param {string} listName - The name of the list to retrieve values from. + * @returns {T} A map of key-value pairs associated with the specified list. + */ getRegisteredByList(listName: string): T { return this.registerObject[listName] as T ?? new Map(); } diff --git a/src/core/domains/events-legacy/drivers/QueueDriver.ts b/src/core/domains/events-legacy/drivers/QueueDriver.ts deleted file mode 100644 index 5655ab20e..000000000 --- a/src/core/domains/events-legacy/drivers/QueueDriver.ts +++ /dev/null @@ -1,71 +0,0 @@ -import WorkerModelFactory from '@src/core/domains/events-legacy/factory/workerModelFactory'; -import { IEvent } from '@src/core/domains/events-legacy/interfaces/IEvent'; -import IEventDriver from '@src/core/domains/events-legacy/interfaces/IEventDriver'; -import WorkerLegacyModel from '@src/core/domains/events-legacy/models/WorkerLegacyModel'; -import { ModelConstructor } from '@src/core/interfaces/IModel'; - -/** - * QueueDriver - * - * Saves events for background processing - */ -export type WorkerModelCtor = ModelConstructor - -export type QueueDriverOptions = { - - /** - * Name of the queue - */ - queueName: string; - - /** - * Name of the event, defaults to the IEvent.name - */ - eventName?: string; - - /** - * Number of retry attempts for failed events - */ - retries: number; - - /** - * Collection to store failed events - */ - failedCollection: string; - - /** - * Delay before processing queued events - */ - runAfterSeconds: number; - - /** - * Constructor for the Worker model - */ - workerModelCtor: WMCtor; - - /** - * Run the worker only once, defaults to false - */ - runOnce?: boolean; -} - -export default class QueueDriver implements IEventDriver { - - /** - * Handle the dispatched event - * @param event - * @param options - */ - async handle(event: IEvent, options: QueueDriverOptions) { - const workerModel = (new WorkerModelFactory).create(new options.workerModelCtor().table, { - queueName: options.queueName, - eventName: event.name, - payload: JSON.stringify(event.payload), - retries: options.retries, - workerModelCtor: options.workerModelCtor - }); - - await workerModel.save(); - } - -} diff --git a/src/core/domains/events-legacy/drivers/SynchronousDriver.ts b/src/core/domains/events-legacy/drivers/SynchronousDriver.ts deleted file mode 100644 index 45c538c7c..000000000 --- a/src/core/domains/events-legacy/drivers/SynchronousDriver.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IEvent } from '@src/core/domains/events-legacy/interfaces/IEvent'; -import IEventDriver from '@src/core/domains/events-legacy/interfaces/IEventDriver'; -import { App } from '@src/core/services/App'; - -/** - * Synchronous event driver - * - * This driver will process events synchronously as soon as they are dispatched. - */ -export default class SynchronousDriver implements IEventDriver { - - /** - * Process an event synchronously - * - * @param event The event to process - */ - async handle(event: IEvent) { - const eventName = event.name - - // Get all the listeners with this eventName - const listenerConstructors = App.container('eventsLegacy').getListenersByEventName(eventName) - - // Process each listener synchronously - for (const listenerCtor of listenerConstructors) { - const listener = new listenerCtor(); - await listener.handle(event.payload); - } - } - -} diff --git a/src/core/domains/events-legacy/exceptions/EventDriverException.ts b/src/core/domains/events-legacy/exceptions/EventDriverException.ts deleted file mode 100644 index b86d5253b..000000000 --- a/src/core/domains/events-legacy/exceptions/EventDriverException.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default class EventDriverException extends Error { - - constructor(message: string = 'Event Driver Exception') { - super(message); - this.name = 'EventDriverException'; - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/exceptions/EventSubscriberException.ts b/src/core/domains/events-legacy/exceptions/EventSubscriberException.ts deleted file mode 100644 index 40b9fe19b..000000000 --- a/src/core/domains/events-legacy/exceptions/EventSubscriberException.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default class EventSubscriberException extends Error { - - constructor(message: string = 'Event Subscriber Exception') { - super(message); - this.name = 'EventSubscriberException'; - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts b/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts deleted file mode 100644 index b15f49ceb..000000000 --- a/src/core/domains/events-legacy/factory/failedWorkerModelFactory.ts +++ /dev/null @@ -1,28 +0,0 @@ -import FailedWorkerLegacyModel, { initialFailedWorkerModalData } from "@src/core/domains/events-legacy/models/FailedWorkerLegacyModel"; - -type Params = { - eventName: string; - payload: any, - error: any; -} -export default class FailedWorkerModelFactory { - - /** - * Creates a new instance of FailedWorkerModel - * @param collection The database collection to store the model in - * @param eventName The name of the event that failed - * @param payload The payload of the event that failed - * @param error The error that caused the event to fail - * @returns A new instance of FailedWorkerModel - */ - create(collection: string, { eventName, payload, error }: Params): FailedWorkerLegacyModel { - return new FailedWorkerLegacyModel({ - ...initialFailedWorkerModalData, - eventName, - payload, - error, - failedAt: new Date(), - }) - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/factory/workerModelFactory.ts b/src/core/domains/events-legacy/factory/workerModelFactory.ts deleted file mode 100644 index 3afe9c742..000000000 --- a/src/core/domains/events-legacy/factory/workerModelFactory.ts +++ /dev/null @@ -1,34 +0,0 @@ -import WorkerLegacyModel, { initialWorkerModalData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; -import { ModelConstructor } from "@src/core/interfaces/IModel"; - -type Params = { - queueName: string; - eventName: string; - payload: any, - retries: number, - workerModelCtor: ModelConstructor -} -export default class WorkerModelFactory { - - /** - * Creates a new instance of WorkerModel - * @param collection The database collection to store the model in - * @param queueName The name of the queue to store the model in - * @param eventName The name of the event to store the model with - * @param payload The payload of the event to store the model with - * @param retries The number of retries for the event to store the model with - * @param workerModelCtor The constructor for the WorkerModel to create - * @returns A new instance of WorkerModel - */ - create(collection: string, { queueName, eventName, payload, retries, workerModelCtor }: Params): WorkerLegacyModel { - return new workerModelCtor({ - ...initialWorkerModalData, - queueName, - eventName, - payload, - retries, - createdAt: new Date() - }, collection) - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IDispatchable.ts b/src/core/domains/events-legacy/interfaces/IDispatchable.ts deleted file mode 100644 index 603995865..000000000 --- a/src/core/domains/events-legacy/interfaces/IDispatchable.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable no-unused-vars */ -export default interface IDispatchable { - dispatch: (...args: any[]) => any; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEvent.ts b/src/core/domains/events-legacy/interfaces/IEvent.ts deleted file mode 100644 index 77e837a44..000000000 --- a/src/core/domains/events-legacy/interfaces/IEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { eventSubscribers } from "@src/config/eventsLegacy"; -import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; - -export interface IEvent< - Payload extends IEventPayload = IEventPayload, - Watchters extends ISubscribers = typeof eventSubscribers, - Drivers extends IEventDrivers = IEventDrivers -> { - name: keyof Watchters & string; - driver: keyof Drivers; - payload: Payload; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventConfig.ts b/src/core/domains/events-legacy/interfaces/IEventConfig.ts deleted file mode 100644 index 8e3b88973..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IDriverConstructor } from '@src/core/domains/events-legacy/interfaces/IEventDriver'; -import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; -import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; - -export interface IDriverConfig { - driverCtor: IDriverConstructor - options?: DriverOptions -} - -export type IEventDrivers = { - [key: string]: IDriverConfig -} - -export type ISubscribers = { - [key: string]: Array -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts b/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts deleted file mode 100644 index a60559989..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventDispatcher.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-unused-vars */ -import IDispatchable from "@src/core/domains/events-legacy/interfaces/IDispatchable"; -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; - -export interface IEventDispatcher extends IDispatchable { - dispatch: (event: IEvent) => Promise; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventDriver.ts b/src/core/domains/events-legacy/interfaces/IEventDriver.ts deleted file mode 100644 index 6ab445017..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventDriver.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; - -export type IDriverConstructor< -Payload extends IEventPayload = IEventPayload, -Options extends object = object, -Driver extends IEventDriver = IEventDriver -> = new (...args: any[]) => Driver; - -export default interface IEventDriver { - handle(event: IEvent, options?: Options): any; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventListener.ts b/src/core/domains/events-legacy/interfaces/IEventListener.ts deleted file mode 100644 index 82787b09c..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventListener.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable no-unused-vars */ -export type EventListenerConstructor = new (...args: any[]) => EventListener; - -export interface IEventListener { - handle: (...args: any[]) => any; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventPayload.ts b/src/core/domains/events-legacy/interfaces/IEventPayload.ts deleted file mode 100644 index 8e936ae1c..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventPayload.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type TSerializableTypes = number | string | boolean | undefined; - -export interface IEventPayload { - [key: string | number | symbol]: TSerializableTypes -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/interfaces/IEventService.ts b/src/core/domains/events-legacy/interfaces/IEventService.ts deleted file mode 100644 index 145a34da0..000000000 --- a/src/core/domains/events-legacy/interfaces/IEventService.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; -import { IDriverConfig, IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; - -export interface EventLegacyServiceConfig { - defaultDriver: string; - drivers: IEventDrivers; - subscribers: ISubscribers; -} - -export interface IEventLegacyService { - config: EventLegacyServiceConfig; - dispatch(event: IEvent): Promise; - getListenersByEventName(eventName: string): EventListenerConstructor[]; - getDriver(driverName: string): IDriverConfig; -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts b/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts deleted file mode 100644 index afde2d72b..000000000 --- a/src/core/domains/events-legacy/models/FailedWorkerLegacyModel.ts +++ /dev/null @@ -1,76 +0,0 @@ -import Model from "@src/core/base/Model"; -import IModelAttributes from "@src/core/interfaces/IModelData"; - -/** - * Represents a failed worker model. - * - * @interface FailedWorkerModelData - * @extends IModelAttributes - */ -export interface FailedWorkerModelData extends IModelAttributes { - - /** - * The name of the event that failed. - */ - eventName: string; - - /** - * The payload of the event that failed. - */ - payload: any; - - /** - * The error that caused the event to fail. - */ - error: any; - - /** - * The date when the event failed. - */ - failedAt: Date; -} - -/** - * Initial data for a failed worker model. - */ -export const initialFailedWorkerModalData = { - eventName: '', - payload: null, - error: null, - failedAt: new Date() -}; - -/** - * FailedWorkerModel class. - * - * @class FailedWorkerModel - * @extends Model - */ -export default class FailedWorkerLegacyModel extends Model { - - /** - * Dates fields. - */ - dates = ['failedAt']; - - /** - * Fields of the model. - */ - fields = [ - 'eventName', - 'payload', - 'error', - 'failedAt' - ]; - - /** - * Constructor. - * - * @param {FailedWorkerModelData} data - The data for the model. - */ - constructor(data: FailedWorkerModelData) { - super(data); - } - -} - diff --git a/src/core/domains/events-legacy/models/WorkerLegacyModel.ts b/src/core/domains/events-legacy/models/WorkerLegacyModel.ts deleted file mode 100644 index cc78bebd3..000000000 --- a/src/core/domains/events-legacy/models/WorkerLegacyModel.ts +++ /dev/null @@ -1,78 +0,0 @@ -import Model from "@src/core/base/Model"; -import IModelAttributes from "@src/core/interfaces/IModelData"; - -export interface WorkerModelData extends IModelAttributes { - queueName: string; - eventName: string; - payload: any; - attempt: number; - retries: number; - createdAt: Date; -} - -export const initialWorkerModalData = { - queueName: '', - eventName: '', - payload: null, - attempt: 0, - retries: 0, - createdAt: new Date() -} - -/** - * WorkerModel class. - * - * Represents a worker model that stores data for a background job. - * - * @class WorkerModel - * @extends Model - */ -export default class WorkerLegacyModel extends Model { - - - /** - * The list of date fields. - * - * @type {string[]} - */ - dates = ['createdAt'] - - /** - * The list of fields. - * - * @type {string[]} - */ - fields = [ - 'queueName', - 'eventName', - 'payload', - 'attempt', - 'retries', - 'createdAt' - ] - - /** - * The list of fields that are JSON. - * - * @type {string[]} - */ - json = [ - 'payload' - ] - - constructor(data: WorkerModelData) { - super(data); - } - - public getPayload(): unknown { - try { - const payload = this.getAttribute('payload'); - return JSON.parse(payload) - } - // eslint-disable-next-line no-unused-vars - catch (err) { - return null - } - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/providers/EventLegacyProvider.ts b/src/core/domains/events-legacy/providers/EventLegacyProvider.ts deleted file mode 100644 index 61eee1286..000000000 --- a/src/core/domains/events-legacy/providers/EventLegacyProvider.ts +++ /dev/null @@ -1,35 +0,0 @@ - -import { defaultEventDriver, eventDrivers, eventSubscribers } from "@src/config/eventsLegacy"; -import BaseProvider from "@src/core/base/Provider"; -import WorkerLegacyCommand from "@src/core/domains/console/commands/WorkerLegacyCommand"; -import { EventLegacyServiceConfig } from "@src/core/domains/events-legacy/interfaces/IEventService"; -import EventService from "@src/core/domains/events-legacy/services/EventService"; -import { App } from "@src/core/services/App"; - -export default class EventLegacyProvider extends BaseProvider { - - protected config: EventLegacyServiceConfig = { - defaultDriver: defaultEventDriver, - drivers: eventDrivers, - subscribers: eventSubscribers - }; - - public async register(): Promise { - this.log('Registering EventProvider'); - - /** - * Register event service - */ - App.setContainer('eventsLegacy', new EventService(this.config)); - - /** - * Register system provided commands - */ - App.container('console').register().registerAll([ - WorkerLegacyCommand - ]) - } - - public async boot(): Promise {} - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/services/EventDispatcher.ts b/src/core/domains/events-legacy/services/EventDispatcher.ts deleted file mode 100644 index 486a9aa96..000000000 --- a/src/core/domains/events-legacy/services/EventDispatcher.ts +++ /dev/null @@ -1,40 +0,0 @@ -import Singleton from "@src/core/base/Singleton"; -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; -import { IDriverConfig } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import { IEventDispatcher } from "@src/core/domains/events-legacy/interfaces/IEventDispatcher"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -import { App } from "@src/core/services/App"; - - -export default class EventDispatcher extends Singleton implements IEventDispatcher { - - /** - * Handle the dispatched event - * @param event - */ - public async dispatch(event: IEvent) { - App.container('logger').info(`[EventDispatcher:dispatch] Event '${event.name}' with driver '${event.driver}'`) - - const driverOptions = this.getDriverOptionsFromEvent(event) - const driverCtor = driverOptions.driverCtor - - const instance = new driverCtor(); - await instance.handle(event, driverOptions.options?.getOptions()); - } - - /** - * Get the driver constructor based on the name of the worker defiend in the Event - * @param IEvent event - * @returns - */ - protected getDriverOptionsFromEvent(event: IEvent): IDriverConfig { - const driver = App.container('eventsLegacy').config.drivers[event.driver] - - if(!driver) { - throw new Error('Driver not found \'' + event.driver + '\'') - } - - return driver - } - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/services/EventListener.ts b/src/core/domains/events-legacy/services/EventListener.ts deleted file mode 100644 index 3c0b4ff0c..000000000 --- a/src/core/domains/events-legacy/services/EventListener.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IEventListener } from "@src/core/domains/events-legacy/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; - -export default abstract class EventListener< - Payload extends IEventPayload = IEventPayload -> implements IEventListener { - - handle!: (payload: Payload) => any; - -} \ No newline at end of file diff --git a/src/core/domains/events-legacy/services/EventService.ts b/src/core/domains/events-legacy/services/EventService.ts deleted file mode 100644 index b48eb496f..000000000 --- a/src/core/domains/events-legacy/services/EventService.ts +++ /dev/null @@ -1,61 +0,0 @@ -import Singleton from "@src/core/base/Singleton"; -import EventDriverException from "@src/core/domains/events-legacy/exceptions/EventDriverException"; -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; -import { IDriverConfig } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import { EventListenerConstructor } from "@src/core/domains/events-legacy/interfaces/IEventListener"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -import { EventLegacyServiceConfig, IEventLegacyService } from "@src/core/domains/events-legacy/interfaces/IEventService"; -import EventDispatcher from "@src/core/domains/events-legacy/services/EventDispatcher"; - -/** - * Event Service - * - * Provides methods for dispatching events and retrieving event listeners. - */ -export default class EventService extends Singleton implements IEventLegacyService { - - /** - * Config. - */ - public config!: EventLegacyServiceConfig; - - /** - * Constructor. - * @param config Event service config. - */ - constructor(config: EventLegacyServiceConfig) { - super(config); - } - - /** - * Dispatch an event. - * @param event Event to dispatch. - * @returns - */ - async dispatch(event: IEvent) { - return await (new EventDispatcher).dispatch(event); - } - - /** - * Get an array of listeners by the event name. - * @param eventName Event name. - * @returns Array of listeners. - */ - getListenersByEventName(eventName: string): EventListenerConstructor[] { - return this.config.subscribers[eventName] ?? []; - } - - /** - * Get event driver. - * @param driverName Driver name. - * @returns Driver config. - */ - getDriver(driverName: string): IDriverConfig { - if(!this.config.drivers[driverName]) { - throw new EventDriverException(`Driver '${driverName}' not found`); - } - return this.config.drivers[driverName]; - } - -} - diff --git a/src/core/domains/events-legacy/services/EventSubscriber.ts b/src/core/domains/events-legacy/services/EventSubscriber.ts deleted file mode 100644 index 951287d6a..000000000 --- a/src/core/domains/events-legacy/services/EventSubscriber.ts +++ /dev/null @@ -1,56 +0,0 @@ -import EventSubscriberException from "@src/core/domains/events-legacy/exceptions/EventSubscriberException"; -import { IEvent } from "@src/core/domains/events-legacy/interfaces/IEvent"; -import { IEventDrivers, ISubscribers } from '@src/core/domains/events-legacy/interfaces/IEventConfig'; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; - -/** - * EventSubscriber - * - * Represents an event and its handler. - * - * @template Payload type of the event payload - * @template Watchters object with event names as keys and event listener classes as values - * @template Drivers object with driver names as keys and the driver classes as values - */ -export default class EventSubscriber< - Payload extends IEventPayload, - Watchters extends ISubscribers = ISubscribers, - Drivers extends IEventDrivers = IEventDrivers -> implements IEvent { - - /** - * Name of the event - */ - public name: keyof Watchters & string; - - /** - * Name of the event driver - */ - public driver: keyof Drivers; - - /** - * Payload of the event - */ - public payload: Payload; - - /** - * Constructor - * - * @param name name of the event - * @param driver name of the event driver - * @param payload payload of the event - */ - constructor(name: keyof Watchters & string, driver: keyof Drivers, payload: Payload) { - this.name = name; - this.driver = driver; - this.payload = payload; - - if(!this.name) { - throw new EventSubscriberException('EventSubscriber must have a \'name\' property') - } - if(!this.driver) { - throw new EventSubscriberException('EventSubscriber must have a \'driver\' property') - } - } - -} diff --git a/src/core/domains/events-legacy/services/QueueDriverOptions.ts b/src/core/domains/events-legacy/services/QueueDriverOptions.ts deleted file mode 100644 index 6fcafb242..000000000 --- a/src/core/domains/events-legacy/services/QueueDriverOptions.ts +++ /dev/null @@ -1,32 +0,0 @@ - -/** - * Class for storing driver options - * - * @template Options - Type of options object - */ -export default class DriverOptions = {}> { - - /** - * The options object - */ - protected options: Options; - - /** - * Constructor - * - * @param options - The options object to store - */ - constructor(options: Options = {} as Options) { - this.options = options; - } - - /** - * Get the options object - * - * @returns The options object - */ - getOptions(): Options { - return this.options; - } - -} diff --git a/src/core/domains/events-legacy/services/WorkerLegacy.ts b/src/core/domains/events-legacy/services/WorkerLegacy.ts deleted file mode 100644 index 374e491ec..000000000 --- a/src/core/domains/events-legacy/services/WorkerLegacy.ts +++ /dev/null @@ -1,191 +0,0 @@ -import Repository from "@src/core/base/Repository"; -import Singleton from "@src/core/base/Singleton"; -import { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; -import EventDriverException from "@src/core/domains/events-legacy/exceptions/EventDriverException"; -import FailedWorkerModelFactory from "@src/core/domains/events-legacy/factory/failedWorkerModelFactory"; -import { IEventPayload } from "@src/core/domains/events-legacy/interfaces/IEventPayload"; -import WorkerLegacyModel from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; -import EventSubscriber from "@src/core/domains/events-legacy/services/EventSubscriber"; -import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; -import { App } from "@src/core/services/App"; - -/** - * Worker service - * - * This service provides methods for working with the worker table. - */ -export default class WorkerLegacy extends Singleton { - - /** - * Queue driver options - */ - public options!: QueueDriverOptions; - - /** - * Sync driver for running events - */ - private syncDriver: string = 'sync'; - - /** - * Set the driver for the worker - * - * @param driver Driver to set - */ - setDriver(driver: string) { - this.options = this.getOptions(driver) - this.logToConsole(`Driver set to '${driver}'`,) - } - - /** - * Work the worker - * - * This method will fetch all queued items and process them through the - * event driver set by the setDriver method. If an error occurs, the - * worker will retry up to the number of times specified in the options. - * After the number of retries has been exceeded, the worker will move - * the item to the failed collection. - */ - async work() { - if(!this.options) { - throw new EventDriverException(`Driver not defined. Did you forget to call 'setDriver'?`) - } - - // Worker service - const worker = WorkerLegacy.getInstance(); - let model: WorkerLegacyModel; - - // Fetch the current list of queued results - const workerResults: WorkerLegacyModel[] = await worker.getWorkerResults(this.options.queueName) - - this.logToConsole('collection: ' + new this.options.workerModelCtor().table) - this.logToConsole(`${workerResults.length} queued items with queue name '${this.options.queueName}'`) - - for(const workerModel of workerResults) { - // We set the model here to pass it to the failedWorkerModel method, - // but allowing the worker to continue processing - model = workerModel - - try { - App.container('logger').console('Worker processing model', model.getId()?.toString()) - await worker.processWorkerModel(model) - } - catch (err) { - if(!(err instanceof Error)) { - App.container('logger').error(err) - return; - } - - await worker.failedWorkerModel(model, err) - } - } - } - - /** - * Get the driver options based on the driver provided - * @returns - */ - getOptions(driver: string): QueueDriverOptions { - const eventDriver = App.container('eventsLegacy').getDriver(driver) - - if(!eventDriver) { - throw new EventDriverException(`Driver '${driver}' not found`) - } - - return (eventDriver.options as DriverOptions).getOptions() - } - - /** - * Get the worker results from oldest to newest - * @returns - */ - async getWorkerResults(queueName: string) { - const workerRepository = new Repository(this.options.workerModelCtor) - - return await workerRepository.findMany({ - queueName - }) - } - - /** - * Proces the worker by dispatching it through the event driver 'sync' - * @param model - */ - async processWorkerModel(model: WorkerLegacyModel) { - model.table = new this.options.workerModelCtor().table - const eventName = model.getAttribute('eventName') - const payload = model.getPayload() as IEventPayload - - // Use the sync driver - const event = new EventSubscriber(eventName as string, this.syncDriver, payload) - - // Dispatch the event - await App.container('eventsLegacy').dispatch(event) - - // Delete record as it was a success - await model.delete(); - - this.logToConsole(`Processed: ${eventName}`) - } - - /** - * Fail the worker - * @param model - * @param err - * @returns - */ - async failedWorkerModel(model: WorkerLegacyModel, err: Error) { - model.table = new this.options.workerModelCtor().table; - - // Get attempts and max retreis - const { retries } = this.options - const currentAttempt = (model.getAttribute('attempt') ?? 0) - const nextCurrentAttempt = currentAttempt + 1 - - this.logToConsole(`Failed ${model.getAttribute('eventName')} attempts ${currentAttempt + 1} out of ${retries}, ID: ${model.getId()?.toString()}`) - - // If reached max, move to failed collection - if(nextCurrentAttempt >= retries) { - await this.moveFailedWorkerModel(model, err); - return; - } - - // Otherwise, update the attempt count - model.setAttribute('attempt', currentAttempt + 1) - await model.save() - } - - /** - * Moved worker to the failed collection - * @param model - * @param err - */ - async moveFailedWorkerModel(model: WorkerLegacyModel, err: Error) { - this.logToConsole('Moved to failed') - - const failedWorkerModel = (new FailedWorkerModelFactory).create( - this.options.failedCollection, - { - eventName: model.getAttribute('eventName') as string, - payload: JSON.stringify(model.getPayload()), - error: { - name: err.name, - message: err.message, - stack: err.stack, - } - } - ) - - await failedWorkerModel.save() - await model.delete(); - } - - /** - * Logs a message to the console - * @param message The message to log - */ - protected logToConsole(message: string) { - App.container('logger').console('[Worker]: ', message) - } - -} - diff --git a/src/core/domains/events/base/BaseEventCastable.ts b/src/core/domains/events/base/BaseEventCastable.ts deleted file mode 100644 index d0e43223c..000000000 --- a/src/core/domains/events/base/BaseEventCastable.ts +++ /dev/null @@ -1,7 +0,0 @@ -import HasCastableConcern from "@src/core/concerns/HasCastableConcern"; -import { ICtor } from "@src/core/interfaces/ICtor"; -import compose from "@src/core/util/compose"; - -const BaseEventCastable: ICtor = compose(class {}, HasCastableConcern) - -export default BaseEventCastable \ No newline at end of file diff --git a/src/core/interfaces/concerns/IHasCastableConcern.ts b/src/core/interfaces/concerns/IHasCastableConcern.ts index 7d7ab96ad..8aa6796eb 100644 --- a/src/core/interfaces/concerns/IHasCastableConcern.ts +++ b/src/core/interfaces/concerns/IHasCastableConcern.ts @@ -17,7 +17,7 @@ export type TCastableType = export interface IHasCastableConcern { casts: Record; - getCastFromObject(data: Record, casts: TCasts): ReturnType; + getCastFromObject(data: Record, casts?: TCasts): ReturnType; getCast(data: unknown, type: TCastableType): T; isValidType(type: TCastableType): boolean; } From 4a41316fb0113a5229b1ed444e5363f9a5528a92 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 20:52:53 +0000 Subject: [PATCH 17/31] Updated make for subscribers, listeners --- .../events/listeners/UserCreatedListener.ts | 10 ++--- .../make/commands/MakeListenerCommand.ts | 2 +- .../make/commands/MakeSubscriberCommand.ts | 2 +- .../make/templates/Listener.ts.template | 26 +++++------ .../make/templates/Subscriber.ts.template | 43 +++++++++---------- 5 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/app/events/listeners/UserCreatedListener.ts b/src/app/events/listeners/UserCreatedListener.ts index a521a7011..f1cf680cc 100644 --- a/src/app/events/listeners/UserCreatedListener.ts +++ b/src/app/events/listeners/UserCreatedListener.ts @@ -1,15 +1,15 @@ -import { IUserData } from "@src/app/models/auth/User"; import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; export class UserCreatedListener extends BaseEventListener { - + /** + * Optional method to execute before the subscribers are dispatched. + */ async execute(): Promise { - // eslint-disable-next-line no-unused-vars - const userData = this.getPayload(); + // const payload = this.getPayload(); - // Handle some logic + // Handle logic } } \ No newline at end of file diff --git a/src/core/domains/make/commands/MakeListenerCommand.ts b/src/core/domains/make/commands/MakeListenerCommand.ts index 869262b79..a8ae08a26 100644 --- a/src/core/domains/make/commands/MakeListenerCommand.ts +++ b/src/core/domains/make/commands/MakeListenerCommand.ts @@ -5,7 +5,7 @@ export default class MakeListenerCommand extends BaseMakeFileCommand { constructor() { super({ signature: 'make:listener', - description: 'Create a new listener', + description: 'Create a new listener event', makeType: 'Listener', args: ['name'], endsWith: 'Listener' diff --git a/src/core/domains/make/commands/MakeSubscriberCommand.ts b/src/core/domains/make/commands/MakeSubscriberCommand.ts index f2e6917ba..00afc811a 100644 --- a/src/core/domains/make/commands/MakeSubscriberCommand.ts +++ b/src/core/domains/make/commands/MakeSubscriberCommand.ts @@ -5,7 +5,7 @@ export default class MakeSubscriberCommand extends BaseMakeFileCommand { constructor() { super({ signature: 'make:subscriber', - description: 'Create a new subscriber', + description: 'Create a new subscriber event', makeType: 'Subscriber', args: ['name'], endsWith: 'Subscriber' diff --git a/src/core/domains/make/templates/Listener.ts.template b/src/core/domains/make/templates/Listener.ts.template index 3bc95d8e7..e461d27b5 100644 --- a/src/core/domains/make/templates/Listener.ts.template +++ b/src/core/domains/make/templates/Listener.ts.template @@ -1,24 +1,18 @@ -import { IEventPayload } from "@src/core/domains/events/interfaces/IEventPayload"; -import EventListener from "@src/core/domains/events/services/EventListener"; - -export interface I#name#Data extends IEventPayload { - -} - -/** - * - */ -export class #name# extends EventListener { +import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; + +class #name# extends BaseEventListener { /** - * Handle the dispatched event - * @param payload The dispatched event payload + * Optional method to execute before the subscribers are dispatched. */ - handle = async (payload: I#name#Data) => { + async execute(): Promise { - // Handle the logic + // eslint-disable-next-line no-unused-vars + const payload = this.getPayload(); + // Handle logic } - + } +export default #name# \ No newline at end of file diff --git a/src/core/domains/make/templates/Subscriber.ts.template b/src/core/domains/make/templates/Subscriber.ts.template index 3d0ee77cd..ac1c0f8ee 100644 --- a/src/core/domains/make/templates/Subscriber.ts.template +++ b/src/core/domains/make/templates/Subscriber.ts.template @@ -1,31 +1,30 @@ -import EventSubscriber from "@src/core/domains/events/services/EventSubscriber"; +import BaseEvent from "@src/core/domains/events/base/BaseEvent"; +import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; -export type I#name#Data = { +class #name# extends BaseEvent { -} - -/** - * - * - * @param payload The payload of the event. - */ -export default class #name# extends EventSubscriber { + static readonly eventName = '#name'; + + protected namespace: string = 'default'; - /** - * Constructor - * @param payload The payload of the event. - */ - constructor(payload: I#name#Data) { + constructor(payload) { + super(payload, SyncDriver); + } - // Set the name of the event. - const eventName = 'TestSubscriber' + getName(): string { + return #name#.eventName; + } - // Set to 'queue' if you want the event to be added to the worker queue - // and processed by the worker command. - // Set to 'sync' if you want the event to be processed immediately. - const driver = 'queue'; + getQueueName(): string { + return 'default'; + } - super(eventName, driver, payload) + async execute(): Promise { + const payload = this.getPayload(); + + // Handle logic } } + +export default #name# \ No newline at end of file From 45bfe402634d19974397757a1a40d583751dfc0a Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 20:53:29 +0000 Subject: [PATCH 18/31] eslint --- src/app/observers/UserObserver.ts | 3 +-- src/core/base/BaseCastable.ts | 3 +-- src/core/concerns/HasCastableConcern.ts | 6 +++--- src/core/domains/events/base/BaseEvent.ts | 3 +-- src/core/domains/events/services/EventService.ts | 3 +-- src/core/util/castObject.ts | 4 ++-- src/tests/events/eventAuthUserRegistered.test.ts | 5 ++--- 7 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/app/observers/UserObserver.ts b/src/app/observers/UserObserver.ts index 49dd8caa3..af3541f33 100644 --- a/src/app/observers/UserObserver.ts +++ b/src/app/observers/UserObserver.ts @@ -1,10 +1,9 @@ +import { UserCreatedListener } from "@src/app/events/listeners/UserCreatedListener"; import { IUserData } from "@src/app/models/auth/User"; import hashPassword from "@src/core/domains/auth/utils/hashPassword"; import Observer from "@src/core/domains/observer/services/Observer"; import { App } from "@src/core/services/App"; -import { UserCreatedListener } from "../events/listeners/UserCreatedListener"; - /** * Observer for the User model. * diff --git a/src/core/base/BaseCastable.ts b/src/core/base/BaseCastable.ts index 6cd29e010..f9073b217 100644 --- a/src/core/base/BaseCastable.ts +++ b/src/core/base/BaseCastable.ts @@ -1,9 +1,8 @@ import HasCastableConcern from "@src/core/concerns/HasCastableConcern"; +import { IHasCastableConcern } from "@src/core/interfaces/concerns/IHasCastableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; import compose from "@src/core/util/compose"; -import { IHasCastableConcern } from "../interfaces/concerns/IHasCastableConcern"; - const BaseCastable: ICtor = compose(class {}, HasCastableConcern) export default BaseCastable \ No newline at end of file diff --git a/src/core/concerns/HasCastableConcern.ts b/src/core/concerns/HasCastableConcern.ts index e43093b53..b357a318e 100644 --- a/src/core/concerns/HasCastableConcern.ts +++ b/src/core/concerns/HasCastableConcern.ts @@ -1,6 +1,6 @@ -import CastException from "../exceptions/CastException"; -import { IHasCastableConcern, TCastableType, TCasts } from "../interfaces/concerns/IHasCastableConcern"; -import { ICtor } from "../interfaces/ICtor"; +import CastException from "@src/core/exceptions/CastException"; +import { IHasCastableConcern, TCastableType, TCasts } from "@src/core/interfaces/concerns/IHasCastableConcern"; +import { ICtor } from "@src/core/interfaces/ICtor"; const HasCastableConcernMixin = (Base: ICtor) => { return class HasCastableConcern extends Base implements IHasCastableConcern { diff --git a/src/core/domains/events/base/BaseEvent.ts b/src/core/domains/events/base/BaseEvent.ts index 41103d04f..c03190ba3 100644 --- a/src/core/domains/events/base/BaseEvent.ts +++ b/src/core/domains/events/base/BaseEvent.ts @@ -1,4 +1,5 @@ import BaseCastable from "@src/core/base/BaseCastable"; +import EventInvalidPayloadException from "@src/core/domains/events/exceptions/EventInvalidPayloadException"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventService } from "@src/core/domains/events/interfaces/IEventService"; @@ -6,8 +7,6 @@ import { TCastableType, TCasts } from "@src/core/interfaces/concerns/IHasCastabl import { ICtor } from "@src/core/interfaces/ICtor"; import { App } from "@src/core/services/App"; -import EventInvalidPayloadException from "../exceptions/EventInvalidPayloadException"; - abstract class BaseEvent extends BaseCastable implements IBaseEvent { protected payload: TPayload | null = null; diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index e08e61517..eebbc84e7 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -1,4 +1,5 @@ /* eslint-disable no-unused-vars */ +import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; import BaseService from "@src/core/domains/events/base/BaseService"; import EventDispatchException from "@src/core/domains/events/exceptions/EventDispatchException"; import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; @@ -12,8 +13,6 @@ import { IEventListenersConfig, TListenersConfigOption, TListenersMap } from "@s import { ICtor } from "@src/core/interfaces/ICtor"; import { IRegsiterList, TRegisterMap } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; -import BaseEventListener from "../base/BaseEventListener"; - class EventService extends BaseService implements IEventService { diff --git a/src/core/util/castObject.ts b/src/core/util/castObject.ts index 70b8ca13f..0d9933959 100644 --- a/src/core/util/castObject.ts +++ b/src/core/util/castObject.ts @@ -1,5 +1,5 @@ -import BaseCastable from "../base/BaseCastable"; -import { TCasts } from "../interfaces/concerns/IHasCastableConcern"; +import BaseCastable from "@src/core/base/BaseCastable"; +import { TCasts } from "@src/core/interfaces/concerns/IHasCastableConcern"; const castObject = (data: unknown, casts: TCasts): ReturnType => { return new BaseCastable().getCastFromObject(data as Record, casts) diff --git a/src/tests/events/eventAuthUserRegistered.test.ts b/src/tests/events/eventAuthUserRegistered.test.ts index 5598923b4..f9bb77c20 100644 --- a/src/tests/events/eventAuthUserRegistered.test.ts +++ b/src/tests/events/eventAuthUserRegistered.test.ts @@ -5,13 +5,12 @@ import UserFactory from '@src/core/domains/auth/factory/userFactory'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; +import { TestUserCreatedListener } from '@src/tests/events/events/auth/TestUserCreatedListener'; +import TestUserCreatedSubscriber from '@src/tests/events/events/auth/TestUserCreatedSubscriber'; import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; -import { TestUserCreatedListener } from './events/auth/TestUserCreatedListener'; -import TestUserCreatedSubscriber from './events/auth/TestUserCreatedSubscriber'; - describe('mock queable event', () => { From da1b58758f6e420c93a7d50a753c118029539381 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 20:58:34 +0000 Subject: [PATCH 19/31] Updated worker migrations to use newest models --- src/app/migrations/2024-09-06-create-failed-worker-table.ts | 4 ++-- src/app/migrations/2024-09-06-create-worker-table.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/migrations/2024-09-06-create-failed-worker-table.ts b/src/app/migrations/2024-09-06-create-failed-worker-table.ts index b0c955a30..a7994b30a 100644 --- a/src/app/migrations/2024-09-06-create-failed-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-failed-worker-table.ts @@ -1,4 +1,4 @@ -import FailedWorkerLegacyModel, { FailedWorkerModelData } from "@src/core/domains/events-legacy/models/FailedWorkerLegacyModel"; +import FailedWorkerModel, { FailedWorkerModelData } from "@src/core/domains/events/models/FailedWorkerModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; @@ -11,7 +11,7 @@ export class CreateFailedWorkerTableMigration extends BaseMigration { group?: string = 'app:setup'; - table = (new FailedWorkerLegacyModel({} as FailedWorkerModelData)).table + table = (new FailedWorkerModel({} as FailedWorkerModelData)).table async up(): Promise { await this.schema.createTable(this.table, { diff --git a/src/app/migrations/2024-09-06-create-worker-table.ts b/src/app/migrations/2024-09-06-create-worker-table.ts index 2824cb6d2..b8b7f826a 100644 --- a/src/app/migrations/2024-09-06-create-worker-table.ts +++ b/src/app/migrations/2024-09-06-create-worker-table.ts @@ -1,5 +1,4 @@ -import { WorkerModelData } from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; -import WorkerModel from "@src/core/domains/events/models/WorkerModel"; +import WorkerModel, { WorkerModelData } from "@src/core/domains/events/models/WorkerModel"; import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; import { DataTypes } from "sequelize"; From 51aa57f08a90673ee4a15a7672746a7a794b9b62 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:05:40 +0000 Subject: [PATCH 20/31] Removed test event from events config, updated event names, added isRegisteredInList to HasRegisterConcern --- .../subscribers/UserCreatedSubscriber.ts | 2 +- src/app/observers/UserObserver.ts | 1 - src/config/events.ts | 3 +-- src/core/concerns/HasRegisterableConcern.ts | 19 +++++++++++++++++++ .../domains/events/services/EventService.ts | 2 ++ .../concerns/IHasRegisterableConcern.ts | 2 ++ .../events/auth/TestUserCreatedSubscriber.ts | 2 +- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/app/events/subscribers/UserCreatedSubscriber.ts b/src/app/events/subscribers/UserCreatedSubscriber.ts index 2dda036cf..8180df820 100644 --- a/src/app/events/subscribers/UserCreatedSubscriber.ts +++ b/src/app/events/subscribers/UserCreatedSubscriber.ts @@ -4,7 +4,7 @@ import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; export default class UserCreatedSubscriber extends BaseEvent { - static readonly eventName = 'UserRegisteredEvent'; + static readonly eventName = 'UserCreatedSubscriber'; protected namespace: string = 'auth'; diff --git a/src/app/observers/UserObserver.ts b/src/app/observers/UserObserver.ts index af3541f33..2797c6a04 100644 --- a/src/app/observers/UserObserver.ts +++ b/src/app/observers/UserObserver.ts @@ -25,7 +25,6 @@ export default class UserObserver extends Observer { /** * Called after the User model has been created. - * Dispatches the UserRegisteredEvent event to trigger related subscribers. * @param data The User data that has been created. * @returns The processed User data. */ diff --git a/src/config/events.ts b/src/config/events.ts index bdb7c0fb9..0410fb1fa 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -6,7 +6,6 @@ import { IEventConfig } from "@src/core/domains/events/interfaces/config/IEventC import FailedWorkerModel from "@src/core/domains/events/models/FailedWorkerModel"; import WorkerModel from "@src/core/domains/events/models/WorkerModel"; import EventService from "@src/core/domains/events/services/EventService"; -import TestEventSyncEvent from "@src/tests/events/events/TestEventSyncEvent"; /** * Event Drivers Constants @@ -49,7 +48,7 @@ export const eventConfig: IEventConfig = { * Register Events */ events: EventService.createConfigEvents([ - TestEventSyncEvent + UserCreatedListener ]), /** diff --git a/src/core/concerns/HasRegisterableConcern.ts b/src/core/concerns/HasRegisterableConcern.ts index 1a23c3688..d2ec239b6 100644 --- a/src/core/concerns/HasRegisterableConcern.ts +++ b/src/core/concerns/HasRegisterableConcern.ts @@ -39,6 +39,10 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { * @returns {void} */ register(key: string, ...args: unknown[]): void { + if(this.isRegisteredInList(HasRegisterable.defaultList, key)) { + return; + } + if(!this.registerObject[HasRegisterable.defaultList]) { this.registerObject[HasRegisterable.defaultList] = new Map(); } @@ -56,6 +60,10 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { * @returns {void} */ registerByList(listName: string, key: string, ...args: unknown[]): void { + if(this.isRegisteredInList(listName, key)) { + return; + } + this.registerObject[listName] = this.registerObject[listName] ?? new Map(); this.registerObject[listName].set(key, args); } @@ -100,6 +108,17 @@ const HasRegisterableConcern = (Broadcaster: ICtor) => { getRegisteredByList(listName: string): T { return this.registerObject[listName] as T ?? new Map(); } + + /** + * Checks if a key is registered in a specific list. + * @private + * @param {string} listName - The name of the list to check for the key. + * @param {string} key - The key to check for in the list. + * @returns {boolean} True if the key is registered in the list, false otherwise. + */ + isRegisteredInList(listName: string, key: string): boolean { + return this.getRegisteredByList(listName).has(key) + } } } diff --git a/src/core/domains/events/services/EventService.ts b/src/core/domains/events/services/EventService.ts index eebbc84e7..560b6920c 100644 --- a/src/core/domains/events/services/EventService.ts +++ b/src/core/domains/events/services/EventService.ts @@ -92,6 +92,8 @@ class EventService extends BaseService implements IEventService { declare getRegisteredObject: () => IRegsiterList; + declare isRegisteredInList: (listName: string, key: string) => boolean; + /** * Declare EventMockableConcern methods. */ diff --git a/src/core/interfaces/concerns/IHasRegisterableConcern.ts b/src/core/interfaces/concerns/IHasRegisterableConcern.ts index 686a13408..3b92f1ea5 100644 --- a/src/core/interfaces/concerns/IHasRegisterableConcern.ts +++ b/src/core/interfaces/concerns/IHasRegisterableConcern.ts @@ -18,4 +18,6 @@ export interface IHasRegisterableConcern getRegisteredList(): T; getRegisteredObject(): IRegsiterList; + + isRegisteredInList(listName: string, key: string): boolean; } \ No newline at end of file diff --git a/src/tests/events/events/auth/TestUserCreatedSubscriber.ts b/src/tests/events/events/auth/TestUserCreatedSubscriber.ts index 03dac4048..98ec7384e 100644 --- a/src/tests/events/events/auth/TestUserCreatedSubscriber.ts +++ b/src/tests/events/events/auth/TestUserCreatedSubscriber.ts @@ -4,7 +4,7 @@ import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; export default class TestUserCreatedSubscriber extends BaseEvent { - static readonly eventName = 'UserRegisteredEvent'; + static readonly eventName = 'TestUserCreatedSubscriber'; protected namespace: string = 'testing'; From 20c0f33aa7ef91956ac38f7729cb47cbc4e2c098 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:08:31 +0000 Subject: [PATCH 21/31] removed console log --- src/app/events/subscribers/UserCreatedSubscriber.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/events/subscribers/UserCreatedSubscriber.ts b/src/app/events/subscribers/UserCreatedSubscriber.ts index 8180df820..010de7ce5 100644 --- a/src/app/events/subscribers/UserCreatedSubscriber.ts +++ b/src/app/events/subscribers/UserCreatedSubscriber.ts @@ -1,4 +1,3 @@ -import { IUserData } from "@src/app/models/auth/User"; import BaseEvent from "@src/core/domains/events/base/BaseEvent"; import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; @@ -21,9 +20,9 @@ export default class UserCreatedSubscriber extends BaseEvent { } async execute(): Promise { - const payload = this.getPayload(); + // const payload = this.getPayload(); - console.log('User was created', payload); + // Handle logic } } \ No newline at end of file From c39c350fd968fd8b640e5c73f2af0dd4be911461 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:09:08 +0000 Subject: [PATCH 22/31] updated events in config --- src/config/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/events.ts b/src/config/events.ts index 0410fb1fa..951d95b15 100644 --- a/src/config/events.ts +++ b/src/config/events.ts @@ -48,7 +48,7 @@ export const eventConfig: IEventConfig = { * Register Events */ events: EventService.createConfigEvents([ - UserCreatedListener + ]), /** From 6ab7f847df2e11807ae40b9bd1d3e22cc60b7c60 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:09:52 +0000 Subject: [PATCH 23/31] deleted events legacy config --- src/config/eventsLegacy.ts | 70 -------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 src/config/eventsLegacy.ts diff --git a/src/config/eventsLegacy.ts b/src/config/eventsLegacy.ts deleted file mode 100644 index d509ceaa9..000000000 --- a/src/config/eventsLegacy.ts +++ /dev/null @@ -1,70 +0,0 @@ -import QueueDriver, { QueueDriverOptions } from "@src/core/domains/events-legacy/drivers/QueueDriver"; -import SynchronousDriver from "@src/core/domains/events-legacy/drivers/SynchronousDriver"; -import { IEventDrivers, ISubscribers } from "@src/core/domains/events-legacy/interfaces/IEventConfig"; -import WorkerLegacyModel from "@src/core/domains/events-legacy/models/WorkerLegacyModel"; -import DriverOptions from "@src/core/domains/events-legacy/services/QueueDriverOptions"; - -/** - * Default Event Driver Configuration - * - * This setting determines which event driver will be used by default when no specific - * driver is defined for an event. The value is read from the APP_EVENT_DRIVER - * environment variable, falling back to 'sync' if not set. - * - * Options: - * - 'sync': Events are processed immediately. - * - 'queue': Events are queued for background processing. - */ -export const defaultEventDriver: string = process.env.APP_EVENT_DRIVER ?? 'sync'; - -/** - * Event Drivers Configuration - * - * This object defines the available event drivers and their configurations. - * Each driver can have its own set of options to customize its behavior. - * - * Structure: - * { - * [driverName: string]: { - * driver: Class extending IEventDriver, - * options?: DriverOptions object - * } - * } - */ -export const eventDrivers: IEventDrivers = { - // Synchronous Driver: Processes events immediately - sync: { - driverCtor: SynchronousDriver - }, - // Queue Driver: Saves events for background processing - queue: { - driverCtor: QueueDriver, - options: new DriverOptions({ - queueName: 'default', // Name of the queue - retries: 3, // Number of retry attempts for failed events - failedCollection: 'failedWorkers', // Collection to store failed events - runAfterSeconds: 10, // Delay before processing queued events - workerModelCtor: WorkerLegacyModel // Constructor for the Worker model - }) - } -} as const; - -/** - * Event Subscribers Configuration - * - * This object maps event names to arrays of listener classes. When an event - * is dispatched, all listeners registered for that event will be executed. - * - * Structure: - * { - * [eventName: string]: Array - * } - * - * Example usage: - * When an 'OnExample' event is dispatched, the ExampleListener will be triggered. - */ -export const eventSubscribers: ISubscribers = { - 'OnExample': [ - - ] -} \ No newline at end of file From eb007ccfbebd6546574dc910f3040e7f04cc0b62 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:11:46 +0000 Subject: [PATCH 24/31] deleted legacy worker command --- .../console/commands/WorkerLegacyCommand.ts | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/core/domains/console/commands/WorkerLegacyCommand.ts diff --git a/src/core/domains/console/commands/WorkerLegacyCommand.ts b/src/core/domains/console/commands/WorkerLegacyCommand.ts deleted file mode 100644 index ccd245051..000000000 --- a/src/core/domains/console/commands/WorkerLegacyCommand.ts +++ /dev/null @@ -1,54 +0,0 @@ -import BaseCommand from "@src/core/domains/console/base/BaseCommand"; -import WorkerLegacy from "@src/core/domains/events-legacy/services/WorkerLegacy"; -import { App } from "@src/core/services/App"; - -export default class WorkerLegacyCommand extends BaseCommand { - - /** - * The signature of the command - */ - signature: string = 'worker:legacy'; - - description = 'Run the worker to process queued event items'; - - /** - * Whether to keep the process alive after command execution - */ - public keepProcessAlive = true; - - /** - * Execute the command - */ - - async execute() { - const driver = this.getDriverName(); - const worker = WorkerLegacy.getInstance() - worker.setDriver(driver) - - App.container('logger').console('Running worker...', worker.options) - - await worker.work(); - - if (worker.options.runOnce) { - return; - } - - setInterval(async () => { - await worker.work() - App.container('logger').console('Running worker again in ' + worker.options.runAfterSeconds.toString() + ' seconds') - }, worker.options.runAfterSeconds * 1000) - } - - /** - * Get the driver name based on the environment - */ - - getDriverName() { - if (App.env() === 'testing') { - return 'testing'; - } - - return process.env.APP_WORKER_DRIVER ?? 'queue'; - } - -} \ No newline at end of file From 03f9c7a956f9cea1d8d539709720757d085ee8e1 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:15:14 +0000 Subject: [PATCH 25/31] updated non use of useful variable (newAttempt in worker concern) --- src/core/domains/events/concerns/EventWorkerConcern.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/domains/events/concerns/EventWorkerConcern.ts b/src/core/domains/events/concerns/EventWorkerConcern.ts index c995fb360..53c8f5f40 100644 --- a/src/core/domains/events/concerns/EventWorkerConcern.ts +++ b/src/core/domains/events/concerns/EventWorkerConcern.ts @@ -81,7 +81,7 @@ const EventWorkerConcern = (Base: ICtor) => { const newAttempt = attempt + 1 const retries = workerModel.getAttribute('retries') ?? 0 - await workerModel.attr('attempt', attempt + 1) + await workerModel.attr('attempt', newAttempt) if(newAttempt >= retries) { await this.handleFailedWorkerModel(workerModel, options) From 15b62fe99fb71e9272a847ddb4ea6228e7427f1e Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:17:34 +0000 Subject: [PATCH 26/31] removed unused interface --- src/core/domains/events/interfaces/IEventPayload.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/core/domains/events/interfaces/IEventPayload.ts b/src/core/domains/events/interfaces/IEventPayload.ts index 6a3d0b611..c227aef40 100644 --- a/src/core/domains/events/interfaces/IEventPayload.ts +++ b/src/core/domains/events/interfaces/IEventPayload.ts @@ -1,8 +1,3 @@ export type TSerializableValues = number | string | boolean | undefined | null; -export type TISerializablePayload = Record | TSerializableValues; - -export interface IEventPayloadWithDriver { - driver: string; - payload: TISerializablePayload -} \ No newline at end of file +export type TISerializablePayload = Record | TSerializableValues; \ No newline at end of file From d69c76a39556f0fa33f7c3fe6e376e2500c6ce82 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:20:56 +0000 Subject: [PATCH 27/31] removed useless interfaces --- src/core/domains/events/interfaces/IEventService.ts | 5 ++--- .../domains/events/interfaces/IHasDispatcherConcern.ts | 7 ------- src/core/domains/events/interfaces/IHasListenerConcern.ts | 3 --- 3 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 src/core/domains/events/interfaces/IHasDispatcherConcern.ts delete mode 100644 src/core/domains/events/interfaces/IHasListenerConcern.ts diff --git a/src/core/domains/events/interfaces/IEventService.ts b/src/core/domains/events/interfaces/IEventService.ts index 14abd5fc8..3498bebba 100644 --- a/src/core/domains/events/interfaces/IEventService.ts +++ b/src/core/domains/events/interfaces/IEventService.ts @@ -5,13 +5,12 @@ import { TListenersConfigOption } from "@src/core/domains/events/interfaces/conf import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; import { IEventWorkerConcern } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; -import { IHasDispatcherConcern } from "@src/core/domains/events/interfaces/IHasDispatcherConcern"; -import { IHasListenerConcern } from "@src/core/domains/events/interfaces/IHasListenerConcern"; import { IMockableConcern } from "@src/core/domains/events/interfaces/IMockableConcern"; +import { IDispatchable } from "@src/core/interfaces/concerns/IDispatchable"; import { IHasRegisterableConcern } from "@src/core/interfaces/concerns/IHasRegisterableConcern"; import { ICtor } from "@src/core/interfaces/ICtor"; -export interface IEventService extends IHasRegisterableConcern, IHasDispatcherConcern, IHasListenerConcern, IEventWorkerConcern, IMockableConcern +export interface IEventService extends IHasRegisterableConcern, IDispatchable, IEventWorkerConcern, IMockableConcern { getConfig(): IEventConfig; diff --git a/src/core/domains/events/interfaces/IHasDispatcherConcern.ts b/src/core/domains/events/interfaces/IHasDispatcherConcern.ts deleted file mode 100644 index 2fb4e9430..000000000 --- a/src/core/domains/events/interfaces/IHasDispatcherConcern.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; - -export interface IHasDispatcherConcern { - - dispatch: (event: IBaseEvent) => Promise; -} \ No newline at end of file diff --git a/src/core/domains/events/interfaces/IHasListenerConcern.ts b/src/core/domains/events/interfaces/IHasListenerConcern.ts deleted file mode 100644 index 942526bbe..000000000 --- a/src/core/domains/events/interfaces/IHasListenerConcern.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IHasListenerConcern { - -} \ No newline at end of file From 9d2375a19158ff86b069ed5fbe0562513f8cd9a1 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:25:32 +0000 Subject: [PATCH 28/31] removed legacy events from ICoreContainers --- src/core/interfaces/ICoreContainers.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/interfaces/ICoreContainers.ts b/src/core/interfaces/ICoreContainers.ts index 25f0ea864..140c65179 100644 --- a/src/core/interfaces/ICoreContainers.ts +++ b/src/core/interfaces/ICoreContainers.ts @@ -1,7 +1,6 @@ import { IAuthService } from '@src/core/domains/auth/interfaces/IAuthService'; import ICommandService from '@src/core/domains/console/interfaces/ICommandService'; import { IDatabaseService } from '@src/core/domains/database/interfaces/IDatabaseService'; -import { IEventLegacyService } from '@src/core/domains/events-legacy/interfaces/IEventService'; import { IEventService } from '@src/core/domains/events/interfaces/IEventService'; import { IRequestContext } from '@src/core/domains/express/interfaces/ICurrentRequest'; import IExpressService from '@src/core/domains/express/interfaces/IExpressService'; @@ -16,8 +15,6 @@ export interface ICoreContainers { * Event Dispatcher Service * Provided by '@src/core/domains/events/providers/EventProvider' */ - eventsLegacy: IEventLegacyService; - events: IEventService; /** From b690567e0bd31466bfe5f3ddb4c772fc1e036405 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:33:02 +0000 Subject: [PATCH 29/31] removed comment from test --- src/tests/events/eventQueableFailed.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/events/eventQueableFailed.test.ts b/src/tests/events/eventQueableFailed.test.ts index cdc04c253..ddc457d1b 100644 --- a/src/tests/events/eventQueableFailed.test.ts +++ b/src/tests/events/eventQueableFailed.test.ts @@ -62,7 +62,6 @@ describe('mock queable event failed', () => { for(let i = 0; i < attempts; i++) { App.container('events').mockEvent(TestEventQueueAlwaysFailsEvent); - // todo: missing await within this logic I think await App.container('console').reader(['worker', '--queue=testQueue']).handle(); expect(App.container('events').assertDispatched(TestEventQueueAlwaysFailsEvent)).toBeTruthy() From 50de6f3370ee710073f9b80da3fc2ddc73573a17 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:34:33 +0000 Subject: [PATCH 30/31] drop worker tables after test, removed test legacy file --- .../events/eventAuthUserRegistered.test.ts | 4 +- src/tests/events/eventQueableFailed.test.ts | 2 +- src/tests/events/eventQueableSuccess.test.ts | 2 +- src/tests/events/eventQueueLegacy.ts | 83 ------------------- 4 files changed, 5 insertions(+), 86 deletions(-) delete mode 100644 src/tests/events/eventQueueLegacy.ts diff --git a/src/tests/events/eventAuthUserRegistered.test.ts b/src/tests/events/eventAuthUserRegistered.test.ts index f9bb77c20..cfb9e095a 100644 --- a/src/tests/events/eventAuthUserRegistered.test.ts +++ b/src/tests/events/eventAuthUserRegistered.test.ts @@ -11,6 +11,8 @@ import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; +import { dropWorkerTables } from './helpers/createWorketTables'; + describe('mock queable event', () => { @@ -30,7 +32,7 @@ describe('mock queable event', () => { }) afterAll(async () => { - // await dropWorkerTables(); + await dropWorkerTables(); }) diff --git a/src/tests/events/eventQueableFailed.test.ts b/src/tests/events/eventQueableFailed.test.ts index ddc457d1b..b5d90ed49 100644 --- a/src/tests/events/eventQueableFailed.test.ts +++ b/src/tests/events/eventQueableFailed.test.ts @@ -34,7 +34,7 @@ describe('mock queable event failed', () => { }) afterAll(async () => { - // await dropWorkerTables(); + await dropWorkerTables(); }) diff --git a/src/tests/events/eventQueableSuccess.test.ts b/src/tests/events/eventQueableSuccess.test.ts index 0e34e010c..8c23c1594 100644 --- a/src/tests/events/eventQueableSuccess.test.ts +++ b/src/tests/events/eventQueableSuccess.test.ts @@ -31,7 +31,7 @@ describe('mock queable event', () => { }) afterAll(async () => { - // await dropWorkerTables(); + await dropWorkerTables(); }) diff --git a/src/tests/events/eventQueueLegacy.ts b/src/tests/events/eventQueueLegacy.ts deleted file mode 100644 index 1367da680..000000000 --- a/src/tests/events/eventQueueLegacy.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable no-undef */ -import { beforeAll, describe, test } from '@jest/globals'; -import Repository from '@src/core/base/Repository'; -import Kernel from '@src/core/Kernel'; -import { App } from '@src/core/services/App'; -import testAppConfig from '@src/tests/config/testConfig'; -import TestQueueSubscriber from '@src/tests/events/subscribers/TestQueueSubscriberLegacy'; -import { TestMovieModel } from '@src/tests/models/models/TestMovie'; -import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; -import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; -import TestEventProvider from '@src/tests/providers/TestEventLegacyProvider'; -import 'dotenv/config'; -import { DataTypes } from 'sequelize'; - -const createTable = async () => { - await App.container('db').schema().createTable('testsWorker', { - queueName: DataTypes.STRING, - eventName: DataTypes.STRING, - payload: DataTypes.JSON, - attempt: DataTypes.INTEGER, - retries: DataTypes.INTEGER, - createdAt: DataTypes.DATE - }); - - await App.container('db').schema().createTable('tests', { - name: DataTypes.STRING, - createdAt: DataTypes.DATE, - updatedAt: DataTypes.DATE - }); -} - -const dropTable = async () => { - await App.container('db').schema().dropTable('tests') - await App.container('db').schema().dropTable('testsWorker') -} - -const movieName = 'testMovie'; - -describe('mock event service', () => { - - /** - * Setup MongoDB - * Setup Kernel with test Console and Event provider - */ - beforeAll(async () => { - await Kernel.boot({ - ...testAppConfig, - providers: [ - ...testAppConfig.providers, - new TestDatabaseProvider(), - new TestConsoleProvider(), - new TestEventProvider() - ] - }, {}); - }); - - - /** - * Dispatches the TestQueueSubscriber event to the worker - */ - test('dispatch a test event', async () => { - await dropTable() - await createTable() - - // Dispatch an event - const events = App.container('events'); - await events.dispatch(new TestQueueSubscriber({ name: movieName })); - - // Wait for the event to be processed - await App.container('console').reader(['worker']).handle(); - - // Check if the movie was created - const repository = new Repository(TestMovieModel); - const movie = await repository.findOne({ name: movieName }); - expect(typeof movie?.getId() === 'string').toBe(true) - expect(movie?.getAttribute('name')).toBe(movieName); - - await movie?.delete(); - expect(movie?.attributes).toBeNull(); - }); - - -}); \ No newline at end of file From e28072f123399986d56074393ab89410cecd9a99 Mon Sep 17 00:00:00 2001 From: "BENJAMIN\\bensh" Date: Wed, 6 Nov 2024 21:48:57 +0000 Subject: [PATCH 31/31] created seperated models, factories, observers for eventAuthRegistered test (todo refactor auth tests) --- .../events/eventAuthUserRegistered.test.ts | 6 +- .../events/auth/TestUserCreatedListener.ts | 6 + .../factory/factories/TestUserFactory.ts | 21 +++ src/tests/models/models/TestUser.ts | 129 ++++++++++++++++++ src/tests/observers/TestUserObserver.ts | 76 +++++++++++ src/tests/providers/TestEventProvider.ts | 9 +- 6 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 src/tests/factory/factories/TestUserFactory.ts create mode 100644 src/tests/models/models/TestUser.ts create mode 100644 src/tests/observers/TestUserObserver.ts diff --git a/src/tests/events/eventAuthUserRegistered.test.ts b/src/tests/events/eventAuthUserRegistered.test.ts index cfb9e095a..063707629 100644 --- a/src/tests/events/eventAuthUserRegistered.test.ts +++ b/src/tests/events/eventAuthUserRegistered.test.ts @@ -1,7 +1,6 @@ /* eslint-disable no-undef */ import { describe } from '@jest/globals'; import { IUserData } from '@src/app/models/auth/User'; -import UserFactory from '@src/core/domains/auth/factory/userFactory'; import Kernel from '@src/core/Kernel'; import { App } from '@src/core/services/App'; import testAppConfig from '@src/tests/config/testConfig'; @@ -11,6 +10,7 @@ import TestConsoleProvider from '@src/tests/providers/TestConsoleProvider'; import TestDatabaseProvider from '@src/tests/providers/TestDatabaseProvider'; import TestEventProvider from '@src/tests/providers/TestEventProvider'; +import TestUserFactory from '../factory/factories/TestUserFactory'; import { dropWorkerTables } from './helpers/createWorketTables'; @@ -26,7 +26,7 @@ describe('mock queable event', () => { ...testAppConfig.providers, new TestConsoleProvider(), new TestDatabaseProvider(), - new TestEventProvider() + new TestEventProvider(), ] }, {}) }) @@ -49,7 +49,7 @@ describe('mock queable event', () => { eventService.mockEvent(TestUserCreatedListener) eventService.mockEvent(TestUserCreatedSubscriber) - const testUser = new UserFactory().createWithData({ + const testUser = new TestUserFactory().createWithData({ email: 'test@example.com', hashedPassword: 'password', roles: [], diff --git a/src/tests/events/events/auth/TestUserCreatedListener.ts b/src/tests/events/events/auth/TestUserCreatedListener.ts index e0cc66592..319d2ad9f 100644 --- a/src/tests/events/events/auth/TestUserCreatedListener.ts +++ b/src/tests/events/events/auth/TestUserCreatedListener.ts @@ -2,10 +2,16 @@ import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; export class TestUserCreatedListener extends BaseEventListener { + static readonly eventName = 'TestUserCreatedListener'; + protected namespace: string = 'testing'; async execute(): Promise { console.log('Executed TestUserCreatedListener', this.getPayload(), this.getName()) } + getName(): string { + return TestUserCreatedListener.eventName + } + } \ No newline at end of file diff --git a/src/tests/factory/factories/TestUserFactory.ts b/src/tests/factory/factories/TestUserFactory.ts new file mode 100644 index 000000000..5f47df103 --- /dev/null +++ b/src/tests/factory/factories/TestUserFactory.ts @@ -0,0 +1,21 @@ +import Factory from '@src/core/base/Factory'; +import TestUser from '@src/tests/models/models/TestUser'; + +/** + * Factory for creating User models. + * + * @class UserFactory + * @extends {Factory} + */ +export default class TestUserFactory extends Factory { + + /** + * Constructor + * + * @constructor + */ + constructor() { + super(TestUser) + } + +} diff --git a/src/tests/models/models/TestUser.ts b/src/tests/models/models/TestUser.ts new file mode 100644 index 000000000..d2f6b5036 --- /dev/null +++ b/src/tests/models/models/TestUser.ts @@ -0,0 +1,129 @@ +import ApiToken from "@src/app/models/auth/ApiToken"; +import Model from "@src/core/base/Model"; +import IUserModel from "@src/core/domains/auth/interfaces/IUserModel"; +import IModelAttributes from "@src/core/interfaces/IModelData"; +import TestUserObserver from "@src/tests/observers/TestUserObserver"; + +/** + * User structure + */ +export interface IUserData extends IModelAttributes { + email: string; + password?: string; + hashedPassword: string; + roles: string[]; + groups: string[]; + firstName?: string; + lastName?: string; + createdAt?: Date; + updatedAt?: Date; +} + +/** + * User model + * + * Represents a user in the database. + */ +export default class TestUser extends Model implements IUserModel { + + /** + * Table name + */ + public table: string = 'users'; + + /** + * @param data User data + */ + constructor(data: IUserData | null = null) { + super(data); + this.observeWith(TestUserObserver); + } + + /** + * Guarded fields + * + * These fields cannot be set directly. + */ + guarded: string[] = [ + 'hashedPassword', + 'password', + 'roles', + 'groups', + ]; + + /** + * The fields that are allowed to be set directly + * + * These fields can be set directly on the model. + */ + fields: string[] = [ + 'email', + 'password', + 'hashedPassword', + 'roles', + 'firstName', + 'lastName', + 'createdAt', + 'updatedAt', + ] + + /** + * Fields that should be returned as JSON + * + * These fields will be returned as JSON when the model is serialized. + */ + json = [ + 'groups', + 'roles' + ] + + /** + * Checks if the user has the given role + * + * @param role The role to check + * @returns True if the user has the role, false otherwise + */ + hasRole(roles: string | string[]): boolean { + roles = typeof roles === 'string' ? [roles] : roles; + const userRoles = this.getAttribute('roles') ?? []; + + for(const role of roles) { + if(!userRoles.includes(role)) return false; + } + + return true; + } + + /** + * Checks if the user has the given role + * + * @param role The role to check + * @returns True if the user has the role, false otherwise + */ + hasGroup(groups: string | string[]): boolean { + groups = typeof groups === 'string' ? [groups] : groups; + const userGroups = this.getAttribute('groups') ?? []; + + for(const group of groups) { + if(!userGroups.includes(group)) return false; + } + + return true; + } + + /** + * @returns The tokens associated with this user + * + * Retrieves the ApiToken models associated with this user. + */ + async tokens(active: boolean = true): Promise { + const filters = active ? { revokedAt: null } : {}; + + return this.hasMany(ApiToken, { + localKey: 'id', + foreignKey: 'userId', + filters + }) + } + +} diff --git a/src/tests/observers/TestUserObserver.ts b/src/tests/observers/TestUserObserver.ts new file mode 100644 index 000000000..704f0273c --- /dev/null +++ b/src/tests/observers/TestUserObserver.ts @@ -0,0 +1,76 @@ +import { IUserData } from "@src/app/models/auth/User"; +import hashPassword from "@src/core/domains/auth/utils/hashPassword"; +import Observer from "@src/core/domains/observer/services/Observer"; +import { App } from "@src/core/services/App"; + +import { TestUserCreatedListener } from "../events/events/auth/TestUserCreatedListener"; + +/** + * Observer for the User model. + * + * Automatically hashes the password on create/update if it is provided. + */ +export default class TestUserObserver extends Observer { + + /** + * Called when the User model is being created. + * Automatically hashes the password if it is provided. + * @param data The User data being created. + * @returns The processed User data. + */ + async creating(data: IUserData): Promise { + data = this.onPasswordChange(data) + data = await this.updateRoles(data) + return data + } + + /** + * Called after the User model has been created. + * @param data The User data that has been created. + * @returns The processed User data. + */ + async created(data: IUserData): Promise { + await App.container('events').dispatch(new TestUserCreatedListener(data)) + return data + } + + /** + * Updates the roles of the user based on the groups they belong to. + * Retrieves the roles associated with each group the user belongs to from the permissions configuration. + * @param data The User data being created/updated. + * @returns The processed User data with the updated roles. + */ + async updateRoles(data: IUserData): Promise { + let updatedRoles: string[] = []; + + for(const group of data.groups) { + const relatedRoles = App.container('auth').config.permissions.groups.find(g => g.name === group)?.roles ?? []; + + updatedRoles = [ + ...updatedRoles, + ...relatedRoles + ] + } + + data.roles = updatedRoles + + return data + } + + /** + * Automatically hashes the password if it is provided. + * @param data The User data being created/updated. + * @returns The processed User data. + */ + onPasswordChange(data: IUserData): IUserData { + if(!data.password) { + return data + } + + data.hashedPassword = hashPassword(data.password); + delete data.password; + + return data + } + +} diff --git a/src/tests/providers/TestEventProvider.ts b/src/tests/providers/TestEventProvider.ts index f82ea9e6d..0b32a67ba 100644 --- a/src/tests/providers/TestEventProvider.ts +++ b/src/tests/providers/TestEventProvider.ts @@ -1,5 +1,3 @@ -import { UserCreatedListener } from '@src/app/events/listeners/UserCreatedListener'; -import UserCreatedSubscriber from '@src/app/events/subscribers/UserCreatedSubscriber'; import { EVENT_DRIVERS } from '@src/config/events'; import QueueableDriver, { TQueueDriverOptions } from '@src/core/domains/events/drivers/QueableDriver'; import SyncDriver from '@src/core/domains/events/drivers/SyncDriver'; @@ -17,6 +15,9 @@ import TestSubscriber from '@src/tests/events/subscribers/TestSubscriber'; import TestFailedWorkerModel from '@src/tests/models/models/TestFailedWorkerModel'; import TestWorkerModel from "@src/tests/models/models/TestWorkerModel"; +import { TestUserCreatedListener } from '../events/events/auth/TestUserCreatedListener'; +import TestUserCreatedSubscriber from '../events/events/auth/TestUserCreatedSubscriber'; + class TestEventProvider extends EventProvider { protected config: IEventConfig = { @@ -56,9 +57,9 @@ class TestEventProvider extends EventProvider { ] }, { - listener: UserCreatedListener, + listener: TestUserCreatedListener, subscribers: [ - UserCreatedSubscriber + TestUserCreatedSubscriber ] } ])