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
1,662 changes: 1,525 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/core",
"version": "5.25.0",
"version": "5.26.0",
"description": "One foundation for multiple applications.",
"license": "MIT",
"author": "João Lenon <lenon@athenna.io>",
Expand Down Expand Up @@ -88,6 +88,7 @@
"@athenna/http": "^5.38.0",
"@athenna/ioc": "^5.2.0",
"@athenna/logger": "^5.8.0",
"@athenna/queue": "^5.11.0",
"@athenna/test": "^5.5.0",
"@athenna/tsconfig": "^5.0.0",
"@athenna/view": "^5.4.0",
Expand Down Expand Up @@ -227,6 +228,9 @@
"directories": {
"bootstrap": "bin"
},
"workers": [
"#tests/fixtures/workers/HelloWorker"
],
"schedulers": [
"#tests/fixtures/schedulers/HelloScheduler"
],
Expand Down
72 changes: 72 additions & 0 deletions src/applications/Worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @athenna/core
*
* (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 { debug } from '#src/debug'
import { Log } from '@athenna/logger'
import type { WorkerImpl } from '@athenna/queue'
import { Path, Module, Options } from '@athenna/common'
import type { CronOptions } from '#src/types/CronOptions'

export class Worker {
/**
* Boot the Worker application.
*/
public static async boot(options?: CronOptions): Promise<WorkerImpl> {
options = Options.create(options, {
routePath: Config.get(
'rc.worker.route',
Path.routes(`worker.${Path.ext()}`)
),
kernelPath: Config.get(
'rc.worker.kernel',
'@athenna/queue/kernels/WorkerKernel'
)
})

const worker = ioc.safeUse('Athenna/Core/Worker')

debug('booting worker application with options %o', options)

await this.resolveKernel(options)

if (Config.notExists('rc.bootLogs') || Config.is('rc.bootLogs', false)) {
return worker
}

Log.channelOrVanilla('application').success(
`Worker application successfully started`
)

return worker
}

/**
* Resolve the kernel by importing it and calling the methods to register
* worker tasks and plugins.
*/
private static async resolveKernel(options?: CronOptions) {
const Kernel = await Module.resolve(
options.kernelPath,
Config.get('rc.parentURL')
)

const kernel = new Kernel()

await kernel.registerLogger()
await kernel.registerRTracer()
await kernel.registerWorkers()
await kernel.registerRoutes(options.routePath)

if (Config.is('rc.bootLogs', true)) {
Log.channelOrVanilla('application').success(
`Kernel ({yellow} ${Kernel.name}) successfully booted`
)
}
}
}
21 changes: 19 additions & 2 deletions src/ignite/Ignite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import type {
SemverNode,
CronOptions,
HttpOptions,
WorkerOptions,
IgniteOptions,
ConsoleOptions
ConsoleOptions,
AWSLambdaHandler
} from '#src/types'

import { Ioc } from '@athenna/ioc'
Expand All @@ -21,13 +23,13 @@ import { Http } from '#src/applications/Http'
import type { ServerImpl } from '@athenna/http'
import { EnvHelper, Rc } from '@athenna/config'
import { isAbsolute, resolve } from 'node:path'
import { Worker } from '#src/applications/Worker'
import type { ReplImpl } from '#src/repl/ReplImpl'
import { Console } from '#src/applications/Console'
import { CommanderHandler } from '@athenna/artisan'
import { LoadHelper } from '#src/helpers/LoadHelper'
import { Log, LoggerProvider } from '@athenna/logger'
import { Repl as ReplApp } from '#src/applications/Repl'
import type { AWSLambdaHandler } from '#src/types/AWSLambdaHandler'
import { parse as semverParse, satisfies as semverSatisfies } from 'semver'
import { Is, Path, File, Module, Options, Macroable } from '@athenna/common'
import { NotSatisfiedNodeVersion } from '#src/exceptions/NotSatisfiedNodeVersion'
Expand Down Expand Up @@ -174,6 +176,21 @@ export class Ignite extends Macroable {
}
}

/**
* Ignite the Worker application.
*/
public async worker(options?: WorkerOptions) {
try {
this.options.environments.push('worker')

await this.fire()

return await Worker.boot(options)
} catch (err) {
await this.handleError(err)
}
}

/**
* Fire the application configuring the env variables file, configuration files
* providers and preload files.
Expand Down
27 changes: 27 additions & 0 deletions src/types/WorkerOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @athenna/core
*
* (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.
*/

export type WorkerOptions = {
/**
* The path to the worker routes.
*
* @default Path.routes(`worker.${Path.ext()}`)
*/
routePath?: string

/**
* The path to the WorkerKernel. The worker kernel is responsible to register controllers,
* all kind of middlewares, plugins and the exception handler. By default,
* Athenna will use the built in Kernel. But you can do your own implementation
* extending the "WorkerKernel" class from Http and setting the path to it here.
*
* @default '@athenna/queue/kernels/WorkerKernel'
*/
kernelPath?: string
}
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from '#src/types/RcOptions'
export * from '#src/types/SemverNode'
export * from '#src/types/CronOptions'
export * from '#src/types/HttpOptions'
export * from '#src/types/WorkerOptions'
export * from '#src/types/IgniteOptions'
export * from '#src/types/ConsoleOptions'
export * from '#src/types/AWSLambdaHandler'
47 changes: 47 additions & 0 deletions tests/fixtures/config/queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @athenna/core
*
* (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 { Env } from '@athenna/config'

export default {
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Athenna's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/

default: Env('QUEUE_CONNECTION', 'memory'),

/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Athenna. You are free to add more.
|
| Drivers: "memory", "database", "awsSqs", "fake"
|
*/

connections: {
memory: {
driver: 'memory',
queue: 'queue_name',
deadletter: 'deadletter_queue_name',
attempts: 1
}
}
}
15 changes: 15 additions & 0 deletions tests/fixtures/kernels/CustomWorkerKernel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @athenna/core
*
* (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 { Log } from '@athenna/logger'
import { WorkerKernel } from '@athenna/queue/kernels/WorkerKernel'

Log.info('importing CustomWorkerKernel')

export class CustomWorkerKernel extends WorkerKernel {}
15 changes: 15 additions & 0 deletions tests/fixtures/routes/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @athenna/core
*
* (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 { Queue } from '@athenna/queue'

Queue.worker()
.task()
.name('worker:task')
.handler(() => {})
17 changes: 17 additions & 0 deletions tests/fixtures/workers/HelloWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @athenna/core
*
* (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 { Worker, type Context } from '@athenna/queue'

@Worker()
export class HelloWorker {
public async handle(ctx: Context) {
console.log(ctx)
}
}
80 changes: 80 additions & 0 deletions tests/unit/applications/WorkerTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* @athenna/core
*
* (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 { Rc } from '@athenna/config'
import { Path } from '@athenna/common'
import { Worker } from '#src/applications/Worker'
import { CommanderHandler } from '@athenna/artisan'
import { Log, LoggerProvider } from '@athenna/logger'
import { Test, type Context, BeforeEach, AfterEach, Mock } from '@athenna/test'
import { Queue, QueueProvider, WorkerProvider, WorkerTaskBuilder } from '@athenna/queue'

export default class WorkerTest {
private workerTaskBuilder: WorkerTaskBuilder

@BeforeEach()
public async beforeEach() {
new LoggerProvider().register()
new QueueProvider().register()
new WorkerProvider().register()

await Config.loadAll(Path.fixtures('config'))

this.workerTaskBuilder = new WorkerTaskBuilder()
Mock.when(this.workerTaskBuilder, 'handler').return(undefined)
Queue.worker().when('task').return(this.workerTaskBuilder)

await Rc.setFile(Path.fixtures('rcs/.athennarc.json'))
}

@AfterEach()
public async afterEach() {
ioc.reconstruct()
Config.clear()
Mock.restoreAll()
CommanderHandler.reconstruct()
}

@Test()
public async shouldBeAbleToBootAWorkerApplication({ assert }: Context) {
await Worker.boot()

assert.called(this.workerTaskBuilder.handler)
}

@Test()
public async shouldBeAbleToBootAWorkerApplicationWithDifferentWorkerKernel({ assert }: Context) {
Log.when('info').return(undefined)

await Worker.boot({ kernelPath: Path.fixtures('kernels/CustomWorkerKernel.ts') })

assert.calledOnceWith(Log.info, 'importing CustomWorkerKernel')
}

@Test()
public async shouldBeAbleToBootAWorkerApplicationAndRegisterTheRouteFile({ assert }: Context) {
await Worker.boot({ routePath: Path.fixtures('routes/worker.ts') })

assert.called(this.workerTaskBuilder.handler)
}

@Test()
public async shouldBeAbleToBootAWorkerApplicationAndLogTheBootstrapInfos({ assert }: Context) {
Config.set('rc.bootLogs', true)
const successMock = Mock.fake()
Log.when('channelOrVanilla').return({
success: successMock
})

await Worker.boot()

assert.calledWith(successMock, 'Worker application successfully started')
assert.calledWith(successMock, 'Kernel ({yellow} WorkerKernel) successfully booted')
}
}
Loading