feat: add services package

This commit is contained in:
Karol Sójko
2022-07-05 20:51:42 +02:00
parent b614c71e79
commit fbfed0a05c
85 changed files with 2418 additions and 28 deletions

View File

@@ -0,0 +1,117 @@
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
import { InternalEventBus } from './InternalEventBus'
import { InternalEventPublishStrategy } from './InternalEventPublishStrategy'
describe('InternalEventBus', () => {
let eventHandler1: InternalEventHandlerInterface
let eventHandler2: InternalEventHandlerInterface
let eventHandler3: InternalEventHandlerInterface
const createEventBus = () => new InternalEventBus()
beforeEach(() => {
eventHandler1 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler1.handleEvent = jest.fn()
eventHandler2 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler2.handleEvent = jest.fn()
eventHandler3 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler3.handleEvent = jest.fn()
})
it('should trigger appropriate event handlers upon event publishing', () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')
eventBus.publish({ type: 'test_event_2', payload: { foo: 'bar' } })
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
})
it('should do nothing if there are no appropriate event handlers', () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')
eventBus.publish({ type: 'test_event_4', payload: { foo: 'bar' } })
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).not.toHaveBeenCalled()
expect(eventHandler3.handleEvent).not.toHaveBeenCalled()
})
it('should handle event synchronously in a sequential order', async () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')
await eventBus.publishSync({ type: 'test_event_2', payload: { foo: 'bar' } }, InternalEventPublishStrategy.SEQUENCE)
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
})
it('should handle event synchronously in a random order', async () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')
await eventBus.publishSync({ type: 'test_event_2', payload: { foo: 'bar' } }, InternalEventPublishStrategy.ASYNC)
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
type: 'test_event_2',
payload: { foo: 'bar' },
})
})
it('should do nothing if there are no appropriate event handlers for synchronous handling', async () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')
await eventBus.publishSync({ type: 'test_event_4', payload: { foo: 'bar' } }, InternalEventPublishStrategy.ASYNC)
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).not.toHaveBeenCalled()
expect(eventHandler3.handleEvent).not.toHaveBeenCalled()
})
it('should clear event observers on deinit', async () => {
const eventBus = createEventBus()
eventBus.deinit()
expect(eventBus['eventHandlers']).toBeUndefined
})
})

View File

@@ -0,0 +1,61 @@
import { InternalEventBusInterface } from './InternalEventBusInterface'
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
import { InternalEventInterface } from './InternalEventInterface'
import { InternalEventPublishStrategy } from './InternalEventPublishStrategy'
import { InternalEventType } from './InternalEventType'
export class InternalEventBus implements InternalEventBusInterface {
private eventHandlers: Map<InternalEventType, InternalEventHandlerInterface[]>
constructor() {
this.eventHandlers = new Map<InternalEventType, InternalEventHandlerInterface[]>()
}
deinit(): void {
;(this.eventHandlers as unknown) = undefined
}
addEventHandler(handler: InternalEventHandlerInterface, eventType: string): void {
let handlersForEventType = this.eventHandlers.get(eventType)
if (handlersForEventType === undefined) {
handlersForEventType = []
}
handlersForEventType.push(handler)
this.eventHandlers.set(eventType, handlersForEventType)
}
publish(event: InternalEventInterface): void {
const handlersForEventType = this.eventHandlers.get(event.type)
if (handlersForEventType === undefined) {
return
}
for (const handlerForEventType of handlersForEventType) {
void handlerForEventType.handleEvent(event)
}
}
async publishSync(event: InternalEventInterface, strategy: InternalEventPublishStrategy): Promise<void> {
const handlersForEventType = this.eventHandlers.get(event.type)
if (handlersForEventType === undefined) {
return
}
if (strategy === InternalEventPublishStrategy.SEQUENCE) {
for (const handlerForEventType of handlersForEventType) {
await handlerForEventType.handleEvent(event)
}
}
if (strategy === InternalEventPublishStrategy.ASYNC) {
const handlerPromises = []
for (const handlerForEventType of handlersForEventType) {
handlerPromises.push(handlerForEventType.handleEvent(event))
}
await Promise.all(handlerPromises)
}
}
}

View File

@@ -0,0 +1,28 @@
import { InternalEventInterface } from './InternalEventInterface'
import { InternalEventType } from './InternalEventType'
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
import { InternalEventPublishStrategy } from '..'
export interface InternalEventBusInterface {
/**
* Associate an event handler with a certain event type
* @param handler event handler instance
* @param eventType event type to associate with
*/
addEventHandler(handler: InternalEventHandlerInterface, eventType: InternalEventType): void
/**
* Asynchronously publish an event for handling
* @param event internal event object
*/
publish(event: InternalEventInterface): void
/**
* Synchronously publish an event for handling.
* This will await for all handlers to finish processing the event.
* @param event internal event object
* @param strategy strategy with which the handlers will process the event.
* Either all handlers will start at once or they will do it sequentially.
*/
publishSync(event: InternalEventInterface, strategy: InternalEventPublishStrategy): Promise<void>
deinit(): void
}

View File

@@ -0,0 +1,5 @@
import { InternalEventInterface } from './InternalEventInterface'
export interface InternalEventHandlerInterface {
handleEvent(event: InternalEventInterface): Promise<void>
}

View File

@@ -0,0 +1,6 @@
import { InternalEventType } from './InternalEventType'
export interface InternalEventInterface {
type: InternalEventType
payload: unknown
}

View File

@@ -0,0 +1,4 @@
export enum InternalEventPublishStrategy {
ASYNC = 'ASYNC',
SEQUENCE = 'SEQUENCE',
}

View File

@@ -0,0 +1 @@
export type InternalEventType = string