Decorators

Messaging Decorators

Define TypeScript event subscriptions, event publication, queues, background jobs, enqueueing, dead-letter handling, and message processing with AxilJS.

6 min readDocumentationEdit this page

Messaging Decorators

AxilJS messaging decorators describe event-driven and queue-based behavior as semantic metadata.

They define what an operation consumes, produces, queues, or processes without coupling application code to a specific message broker or queue implementation.

The core messaging decorators are:

  • @tOnEvent
  • @tEmit
  • @tOnce
  • @tQueue
  • @tJob
  • @tEnqueue
  • @tDeadLetter
  • @tMessage

@tOnEvent

Subscribes a method to events matching a specified event name or pattern.

typescript
import { tOnEvent } from '@axiljs/decorator'
 
@tOnEvent('user.created')
async sendWelcomeEmail(event: UserCreatedEvent) {}
 
@tOnEvent('order.*')
async onAnyOrder(event: OrderEvent) {}

Event patterns allow a handler to subscribe to a family of related events.

Options

OptionTypeDescription
asyncbooleanRun the handler asynchronously
prioritynumberHandler priority; higher values execute first
typescript
@tOnEvent('payment.failed', {
  async: true,
  priority: 10,
})
async handlePaymentFailure(event: PaymentFailedEvent) {}

The decorator describes the subscription. The event consumer determines how the subscription is registered with the underlying messaging infrastructure.

@tEmit

Declares that an event should be published after a method completes successfully.

The method's return value can become the event payload.

typescript
import { tEmit } from '@axiljs/decorator'
 
@tEmit('order.created')
async createOrder(dto: OrderDto) {
  return this.orders.create(dto)
}

This allows event publication to remain separate from broker-specific publishing code.

For operations that modify persistent state, @tEmit can also be combined with transactional messaging semantics such as @tOutbox.

typescript
@tTransaction()
@tOutbox()
@tEmit('order.created')
async createOrder(dto: OrderDto) {
  return this.orders.create(dto)
}

@tOnce

Declares a one-time event subscription.

typescript
import { tOnce } from '@axiljs/decorator'
 
@tOnce('app.ready')
async warmUpCache() {}

The subscription is removed after the first invocation.

This is useful for initialization, startup tasks, and one-time application events.

@tQueue

Declares a class as a queue processor.

typescript
import {
  tQueue,
  tJob,
} from '@axiljs/decorator'
 
@tQueue('emails', {
  concurrency: 5,
})
class EmailWorker {
  @tJob('send-welcome', {
    attempts: 3,
  })
  async sendWelcome(job: Job<WelcomeData>) {}
}

The queue metadata defines the processing context, while individual job decorators define the work performed within that queue.

@tJob

Declares a background job handler within a queue.

typescript
import { tJob } from '@axiljs/decorator'
 
@tJob('process-image', {
  attempts: 3,
  backoff: 'exponential',
  timeout: 30_000,
})
async processImage(job: Job<ImageData>) {}

Job configuration can describe retry behavior, backoff strategy, and execution timeout.

The queue consumer interprets this metadata and maps it to the underlying job-processing infrastructure.

@tEnqueue

Declares that a job should be enqueued after an operation completes.

typescript
import { tEnqueue } from '@axiljs/decorator'
 
@tEnqueue('emails', 'send-welcome')
async createUser(dto: CreateUserDto) {
  return this.users.create(dto)
}

This separates the business operation from direct interaction with the queue implementation.

For example, creating a user can declare that a welcome-email job should be queued without importing a broker-specific queue client into the business service.

@tDeadLetter

Declares dead-letter handling for terminally failed messages.

typescript
import { tDeadLetter } from '@axiljs/decorator'
 
@tDeadLetter({
  channel: 'failed-payments',
  maxAttempts: 5,
})
async processPayment(job: Job) {}

After the configured failure policy is exhausted, the messaging consumer can route the message to the declared dead-letter channel.

Dead-letter semantics are useful for isolating messages that require investigation or manual recovery.

@tMessage

Declares a handler for a messaging channel.

This semantic is distinct from event pub/sub and is intended for direct message-channel processing.

typescript
import { tMessage } from '@axiljs/decorator'
 
@tMessage('payments')
async processPayment(message: PaymentMessage) {
  // Message processing
}

The messaging consumer determines how the channel is connected to the underlying broker.

Event-Driven Example

A typical event-driven flow can combine event publication and subscription:

typescript
import {
  tEmit,
  tOnEvent,
} from '@axiljs/decorator'
 
class OrderService {
  @tEmit('order.created')
  async createOrder(dto: CreateOrderDto) {
    return this.orders.create(dto)
  }
}
 
class NotificationService {
  @tOnEvent('order.created')
  async sendOrderConfirmation(event: OrderCreatedEvent) {
    await this.email.sendConfirmation(event.payload)
  }
}

The producer declares the event it emits, while the consumer declares the event it handles.

Neither service needs to encode broker-specific subscription or publication logic into the semantic declaration.

Queue Processing Example

Queue-based workloads can be expressed through a queue, job, and dead-letter policy:

typescript
import {
  tQueue,
  tJob,
  tDeadLetter,
} from '@axiljs/decorator'
 
@tQueue('payments', {
  concurrency: 10,
})
class PaymentWorker {
  @tJob('process-payment', {
    attempts: 5,
    backoff: 'exponential',
    timeout: 30_000,
  })
  @tDeadLetter({
    channel: 'failed-payments',
    maxAttempts: 5,
  })
  async processPayment(job: Job<PaymentData>) {
    return this.paymentService.process(job.data)
  }
}

The metadata describes:

  • The queue being processed.
  • The maximum processing concurrency.
  • The job identity.
  • Retry behavior.
  • Execution timeout.
  • Terminal failure routing.

The queue infrastructure can implement these policies independently of the worker's business logic.

Combining Messaging and Distributed Semantics

Messaging decorators can be combined with distributed-system decorators when reliable event processing is required.

Producer:

typescript
@tTransaction()
@tOutbox()
async createOrder(dto: CreateOrderDto) {
  const order = await this.orders.create(dto)
 
  return {
    order,
    events: [
      {
        type: 'order.created',
        payload: order,
      },
    ],
  }
}

Consumer:

typescript
@tInbox({
  ttl: 86_400_000,
})
@tOnEvent('order.created')
async handleOrder(event: OrderCreatedEvent) {
  await this.fulfillment.create(event.payload)
}

This separates:

  • Database transaction semantics.
  • Reliable event publication.
  • Event subscription.
  • Duplicate-message handling.
  • Business processing.

Messaging Decorator Reference

DecoratorPurpose
@tOnEventSubscribes to an event or event pattern
@tEmitPublishes an event after successful execution
@tOnceHandles an event exactly once
@tQueueDeclares a queue processor
@tJobDeclares a background job handler
@tEnqueueDeclares job enqueueing
@tDeadLetterDefines terminal failure routing
@tMessageDeclares a messaging-channel handler

These decorators form the messaging and queue category in the AxilJS decorator system.

Design Principle

Messaging decorators describe messaging intent rather than broker implementation.

For example:

typescript
@tOnEvent('order.created')
async handleOrder(event: OrderCreatedEvent) {}

The method declares what it consumes. It does not need to know whether the underlying infrastructure uses Kafka, RabbitMQ, Redis Streams, an in-process event bus, or another messaging system.

The consumer is responsible for translating the semantic metadata into the appropriate messaging infrastructure.

This preserves the AxilJS principle of independent consumers: decorators declare behavior, while infrastructure components implement it. The documentation describes this consumer model explicitly for AxilJS metadata.

Help improve the documentation

AxilJS is open source and documentation improvements are welcome.

AxilJS DocumentationMIT License · Built by SyntaxilitY