Skip to main content

Runtime decorators

Beyond core @Api, @Database, @Storage, and @Webhook, @ductape/nestjs exposes decorators for events, notifications, features, agents, sessions, and resilience. All runtime decorators are executed by DuctapeMethodInterceptor in integration mode.

The first method argument becomes SDK input / message (same convention as @Api), except for @Events.Consumer which receives incoming messages.

Events, notifications, cache, graph, vector, feature, jobs, secrets

Method decorators

DecoratorSDK call
@Events.Produce({ event, product?, env?, session?, cache? })events.produce
@Events.Dispatch({ broker, event, schedule?, … })events.dispatch
@Events.Consumer({ event, product?, env? })events.consume (wired at startup)
@Notification.Send({ notification, event?, channel?, … })notifications.send / channel helpers
@Notification.Dispatch({ notification, event, schedule?, … })notifications.dispatch
@Feature.Execute({ tag, product?, env? })feature.execute
@Feature.Dispatch({ tag, schedule?, … })feature.dispatch
@Job.Run({ event, retries?, start_at?, … })processor processJob
@Secret('KEY') on constructor paraminject SecretHandle (via DuctapeSecretsModule)

Injectable handles

ModuleDecoratorHandle
DuctapeCacheModule.register({ tags: ['user-cache'] })@Cache('user-cache')CacheHandle
DuctapeGraphModule.register({ tags: ['social'] })@Graph('social')GraphHandle
DuctapeVectorModule.register({ tags: ['embeddings'] })@Vector('embeddings')VectorHandle
DuctapeSecretsModule.register({ keys: ['API_KEY'] })@Secret('API_KEY')SecretHandle

Producing events

@Events.Produce publishes the returned value as the event message immediately:

import { Injectable } from '@nestjs/common';
import { Events } from '@ductape/nestjs';

@Injectable()
export class OrdersService {
@Events.Produce({ event: 'order-events:order-created' })
emitOrderCreated(payload: { orderId: string; total: number }) {
return payload;
}
}

Calling ordersService.emitOrderCreated(payload) publishes payload to the topic and returns the SDK result.

Dispatching (scheduled) events

@Events.Dispatch schedules a publish through a Ductape job. The schedule can be static (fixed in the decorator) or dynamic (derived from the method return value):

import { Injectable } from '@nestjs/common';
import { Events } from '@ductape/nestjs';

@Injectable()
export class SchedulerService {
// Static schedule — known at deploy time:
@Events.Dispatch({
broker: 'order-events',
event: 'order-events:reminder-due',
schedule: { every: 86400000 }, // every 24 h
})
scheduleReminder(payload: { message: { orderId: string } }) {
return payload;
}

// Dynamic schedule — derived from data at call time:
@Events.Dispatch({ broker: 'order-events', event: 'order-events:fulfillment-due' })
scheduleFulfillment(order: { id: string; items: unknown[]; expectedAt: number }) {
return {
message: { orderId: order.id, items: order.items },
schedule: { start_at: order.expectedAt },
retries: 3,
};
}
}

When the method return value contains a message key, the decorator reads message, schedule, and retries from it — overriding anything specified in the decorator. If the return value has no message key, the first argument is used as-is (backward-compatible with static-schedule usage).

For schedules where even the broker or event must vary at call time, use ctx.sdk.events.dispatch() directly.

Consuming events

@Events.Consumer registers a subscription that fires for each incoming message. No onModuleInit boilerplate is required — DuctapeModule wires it up automatically at startup:

import { Injectable } from '@nestjs/common';
import { Events } from '@ductape/nestjs';

@Injectable()
export class OrderConsumerService {
@Events.Consumer({ event: 'order-events:order-created' })
async onOrderCreated(message: { orderId: string; total: number }) {
await this.processOrder(message);
// Return to acknowledge; throw to nack (triggers retry / DLQ)
}

private async processOrder(message: { orderId: string; total: number }) {
// business logic
}
}

The product and env used are the defaults from DuctapeModule.forIntegration(...). Override them per method if needed:

@Events.Consumer({ event: 'order-events:order-created', product: 'my-product', env: 'prd' })

Feature execution

import { Injectable } from '@nestjs/common';
import { Feature } from '@ductape/nestjs';

@Injectable()
export class FulfillmentService {
@Feature.Execute({ tag: 'fulfillment' })
runFulfillment(input: Record<string, unknown>) {
return input;
}
}

Related platform docs: Message brokers, Notifications, Jobs, Graphs, Vectors.

Agents, warehouse, sessions, health, quota, fallback

Method decorators

DecoratorSDK call
@Agent.Run({ tag, product?, env?, session?, cache? })agents.getService().run()
@Agent.Dispatch({ tag, schedule?, retries?, … })agents.getService().dispatch()
@Warehouse.Query({ product?, env? })warehouse.query — method arg is query object
@Session.Start({ tag, … })sessions.start
@Session.Verify({ tag, … })sessions.verify — body { token }
@Session.Refresh({ tag, … })sessions.refresh — body { refreshToken }
@Session.Revoke({ tag, … })sessions.revoke
@Health.Run({ tag, … })health.run
@Health.Status({ tag, … })health.status
@Quota.Run({ tag, … })quota.run
@Quota.Dispatch({ tag, schedule?, … })quota.dispatch
@Fallback.Run({ tag, … })fallback.run
@Fallback.Dispatch({ tag, schedule?, … })fallback.dispatch

Injectable handles

ModuleDecoratorHandle
DuctapeAgentModule.register({ tags: ['support'] })@Agent('support')AgentHandle (.run(), .dispatch())
DuctapeWarehouseModule.register()@Warehouse()WarehouseHandle (.query(), .select(), …)

Example:

@Injectable()
export class SupportService {
constructor(@Agent('support') private readonly agent: AgentHandle) {}

@Session.Start({ tag: 'user-session' })
startSession(credentials: { userId: string }) {
return credentials;
}

async ask(question: string) {
return this.agent.run({ input: { question } });
}
}

Related platform docs: Sessions, Quotas, Fallbacks, Warehouse.

Unwrapped SDK APIs

Use @InjectContext() and ctx.sdk.* for modules without Nest decorators yet (logs, cloud, operators, …).