Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/event",
"version": "5.0.0",
"version": "5.1.0",
"description": "The Athenna events handler with queue store. Based on Emittery syntax.",
"license": "MIT",
"author": "João Lenon <lenon@athenna.io>",
Expand Down Expand Up @@ -170,11 +170,14 @@
},
"athenna": {
"bootLogs": false,
"listeners": [
"#tests/fixtures/listeners/AnnotatedListener",
"#tests/fixtures/listeners/HelloListener",
"#tests/fixtures/listeners/ProductListener"
],
"events": {
"path": "#tests/fixtures/events/index",
"listeners": [
"#tests/fixtures/listeners/AnnotatedListener",
"#tests/fixtures/listeners/HelloListener",
"#tests/fixtures/listeners/ProductListener"
]
},
"templates": {
"listener": "./templates/listener.edge"
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/MakeListenerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export class MakeListenerCommand extends BaseCommand {

const importPath = this.generator.getImportPath()

await this.rc.pushTo('listeners', importPath).save()
await this.rc.pushTo('events.listeners', importPath).save()

this.logger.success(
`Athenna RC updated: ({dim,yellow} [ listeners += "${importPath}" ])`
`Athenna RC updated: ({dim,yellow} [ events.listeners += "${importPath}" ])`
)
}
}
15 changes: 12 additions & 3 deletions src/events/EventImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Config } from '@athenna/config'
import { Macroable } from '@athenna/common'
import { Is, Macroable } from '@athenna/common'
import { Listener } from '#src/events/Listener'
import { QueueImpl, type ConnectionOptions } from '@athenna/queue'
import type { EventClosure, Context } from '#src/types'
Expand Down Expand Up @@ -89,11 +89,20 @@ export class EventImpl extends Macroable {
*
* @example
* ```ts
* Event.on('user.created', 'UserCreatedListener')
* // or
* Event.on('user.created', user => console.log(user))
* ```
*/
public on(event: string, closure: EventClosure) {
const listener = new Listener(event, closure)
public on(event: string, closure: EventClosure | string) {
let listener = null

if (Is.String(closure)) {
listener = new Listener(event, ctx => ioc.safeUse(closure).handle(ctx))
} else {
listener = new Listener(event, closure)
}

const set = this.listeners.get(event) ?? new Set<string>()

set.add(listener.id)
Expand Down
47 changes: 47 additions & 0 deletions src/providers/EventProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,25 @@

import { ServiceProvider } from '@athenna/ioc'
import { EventImpl } from '#src/events/EventImpl'
import { Module, Path, File } from '@athenna/common'
import { sep, isAbsolute, resolve } from 'node:path'

export class EventProvider extends ServiceProvider {
public async register() {
this.container.singleton('Athenna/Core/Event', EventImpl)

const listeners = Config.get<string[]>('rc.events.listeners', [])

await this.container.loadModules(listeners, {
addCamelAlias: true,
parentURL: this.getMeta()
})

const eventsPath = Config.get<string>('rc.events.path', null)

if (eventsPath) {
await this.registerEvents(eventsPath)
}
}

public async shutdown() {
Expand All @@ -27,4 +42,36 @@ export class EventProvider extends ServiceProvider {

await event.queue.closeAll()
}

/**
* Get the meta URL of the project.
*/
private getMeta() {
return Config.get('rc.parentURL', Path.toHref(Path.pwd() + sep))
}

/**
* Register the events file by importing the file.
*/
private async registerEvents(path: string) {
// Bust ESM import cache to guarantee the events file re-runs (tests/providers
// may register/shutdown provider multiple times in same process).
const resolvedPath = `${path}?version=${Math.random()}`

if (path.startsWith('#')) {
await Module.resolve(resolvedPath, this.getMeta())

return
}

if (!isAbsolute(path)) {
path = resolve(path)
}

if (!(await File.exists(path))) {
return
}

await Module.resolve(`${path}?version=${Math.random()}`, this.getMeta())
}
}
6 changes: 4 additions & 2 deletions tests/fixtures/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const constants = {
helloListener: false,
productListener: false,
annotatedListener: false,
decoratedListener: false
}
decoratedListener: false,
closureListener: false
},
LAST_EVENT: null
}
20 changes: 20 additions & 0 deletions tests/fixtures/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @athenna/event
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Event } from '#src'
import { constants } from '#tests/fixtures/constants/index'

Event.on('annotatedListener', 'annotatedListener')
Event.on('helloListener', 'helloListener')
Event.on('productListener', 'productListener')

Event.on('closureListener', ctx => {
constants.RUN_MAP.closureListener = true
constants.LAST_EVENT = ctx
})
23 changes: 23 additions & 0 deletions tests/fixtures/events/listeners/AnnotatedListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @athenna/event
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Listener } from '#src'
import { constants } from '#tests/fixtures/constants/index'

@Listener({
type: 'singleton',
alias: 'decoratedListener',
camelAlias: 'annotatedListener'
})
export class AnnotatedListener {
public async handle() {
constants.RUN_MAP.decoratedListener = true
constants.RUN_MAP.annotatedListener = true
}
}
18 changes: 18 additions & 0 deletions tests/fixtures/events/listeners/HelloListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @athenna/event
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { constants } from '#tests/fixtures/constants/index'
import { Listener } from '#src'

@Listener({ connection: 'memoryA' })
export class HelloListener {
public async handle() {
constants.RUN_MAP.helloListener = true
}
}
27 changes: 27 additions & 0 deletions tests/fixtures/events/listeners/ProductListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @athenna/event
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Listener, type Context } from '#src'
import { constants } from '#tests/fixtures/constants/index'

@Listener({ connection: 'memory' })
export class ProductListener {
public async handle(ctx: Context) {
if (ctx.data.failOnAllAttempts) {
throw new Error('testing')
}

if (ctx.data.failOnFirstAttemptOnly && ctx.attempts >= 1) {
throw new Error('testing')
}

constants.PRODUCTS.push(ctx.data)
constants.RUN_MAP.productListener = true
}
}
2 changes: 2 additions & 0 deletions tests/fixtures/listeners/HelloListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
* file that was distributed with this source code.
*/

import { Listener } from '#src'
import { constants } from '#tests/fixtures/constants/index'

@Listener({ connection: 'memory' })
export class HelloListener {
public async handle() {
constants.RUN_MAP.helloListener = true
Expand Down
10 changes: 6 additions & 4 deletions tests/unit/commands/MakeListenerCommandTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ export default class MakeListenerCommandTest extends BaseCommandTest {
output.assertSucceeded()
output.assertLogged('[ MAKING LISTENER ]')
output.assertLogged('[ success ] Listener "TestListener" successfully created.')
output.assertLogged('[ success ] Athenna RC updated: [ listeners += "#src/events/listeners/TestListener" ]')
output.assertLogged(
'[ success ] Athenna RC updated: [ events.listeners += "#src/events/listeners/TestListener" ]'
)

const { athenna } = await new File(Path.pwd('package.json')).getContentAsJson()

assert.isTrue(await File.exists(Path.listeners('TestListener.ts')))
assert.containSubset(athenna.listeners, ['#src/events/listeners/TestListener'])
assert.containSubset(athenna.events.listeners, ['#src/events/listeners/TestListener'])
}

@Test()
Expand All @@ -39,13 +41,13 @@ export default class MakeListenerCommandTest extends BaseCommandTest {
output.assertLogged('[ MAKING LISTENER ]')
output.assertLogged('[ success ] Listener "TestListener" successfully created.')
output.assertLogged(
'[ success ] Athenna RC updated: [ listeners += "#tests/fixtures/storage/listeners/TestListener" ]'
'[ success ] Athenna RC updated: [ events.listeners += "#tests/fixtures/storage/listeners/TestListener" ]'
)

const { athenna } = await new File(Path.pwd('package.json')).getContentAsJson()

assert.isTrue(await File.exists(Path.fixtures('storage/listeners/TestListener.ts')))
assert.containSubset(athenna.listeners, ['#tests/fixtures/storage/listeners/TestListener'])
assert.containSubset(athenna.events.listeners, ['#tests/fixtures/storage/listeners/TestListener'])
}

@Test()
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/events/EventImplTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ export class EventImplTest {
public async beforeEach() {
await Config.loadAll(Path.fixtures('config'))

new LoggerProvider().register()
new QueueProvider().register()
new EventProvider().register()
await new LoggerProvider().register()
await new QueueProvider().register()
await new EventProvider().register()
}

@AfterEach()
public async afterEach() {
Event.clearListeners()

new QueueProvider().shutdown()
new EventProvider().shutdown()
new LoggerProvider().shutdown()
await new QueueProvider().shutdown()
await new EventProvider().shutdown()
await new LoggerProvider().shutdown()

Config.clear()
ioc.reconstruct()
Expand Down
Loading
Loading