@ductape/nestjs
v0.1.6
Published
NestJS decorators and modules for @ductape/sdk — @Api, @Database, @Storage, @Webhook, and more
Maintainers
Readme
@ductape/nestjs
NestJS integration for Ductape. Use decorator-driven access to databases, storage, caching, messaging, sessions, agents, and more — wired directly into your controllers and services.
Install
npm install @ductape/nestjs @ductape/sdk @nestjs/common @nestjs/core reflect-metadata rxjsSetup
Register the module once in your root AppModule:
import { Module } from '@nestjs/common';
import { DuctapeModule, DuctapeDatabaseModule, DuctapeStorageModule } from '@ductape/nestjs';
@Module({
imports: [
DuctapeModule.forIntegration({
accessKey: process.env.DUCTAPE_ACCESS_KEY,
product: 'my-product',
env: process.env.NODE_ENV === 'production' ? 'prd' : 'dev',
}),
DuctapeDatabaseModule.register({ tags: ['orders-db'] }),
DuctapeStorageModule.register({ tags: ['receipts'] }),
],
})
export class AppModule {}Use forRootAsync to load config from a provider:
DuctapeModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
accessKey: config.getOrThrow('DUCTAPE_ACCESS_KEY'),
product: 'my-product',
env: config.get('NODE_ENV') === 'production' ? 'prd' : 'dev',
}),
})Using decorators in a service
import { Injectable } from '@nestjs/common';
import {
Api,
ApiConfig,
Database,
Storage,
Product,
Env,
type DatabaseHandle,
type StorageHandle,
} from '@ductape/nestjs';
@Injectable()
@Product('my-product')
@Env('prd')
@ApiConfig({
product: 'my-product',
app: 'stripe',
env: 'prd',
credentials: { 'headers:Authorization': '$Secret{STRIPE_API_KEY}' },
})
export class OrdersService {
constructor(
@Database('orders-db') private readonly db: DatabaseHandle,
@Storage('receipts') private readonly storage: StorageHandle,
) {}
// Interceptor runs the Stripe action automatically — return value is the action result
@Api({ app: 'stripe', action: 'create-charge' })
async charge(input: { amount: number; currency: string }) {
return input;
}
async saveOrder(row: Record<string, unknown>) {
return this.db.insert({ table: 'orders', data: row });
}
async uploadReceipt(fileName: string, buffer: Buffer) {
return this.storage.upload({ fileName, buffer, mimeType: 'application/pdf' });
}
}Decorators
Third-party app actions
| Decorator | What it does |
|---|---|
| @Api({ app, action }) | Run an app action (e.g. call Stripe, Paystack) |
| @ApiDispatch({ app, action, schedule? }) | Schedule an app action |
| @ApiConfig({ app, env, credentials }) | Set shared credentials for an app — apply at class level |
Injecting resource handles
Register the module, then inject the handle with its tag:
| Module | Decorator | Handle methods |
|---|---|---|
| DuctapeDatabaseModule.register({ tags }) | @Database('tag') | .query() .insert() .update() .delete() |
| DuctapeStorageModule.register({ tags }) | @Storage('tag') | .upload() .download() .list() .delete() |
| DuctapeCacheModule.register({ tags }) | @Cache('tag') | .get() .set() .del() |
| DuctapeGraphModule.register({ tags }) | @Graph('tag') | .query() .run() |
| DuctapeVectorModule.register({ tags }) | @Vector('tag') | .upsert() .query() |
| DuctapeAgentModule.register({ tags }) | @Agent('tag') | .run() .dispatch() |
| DuctapeWarehouseModule.register() | @Warehouse() | .query() .select() |
| DuctapeSecretsModule.register({ keys }) | @Secret('KEY') | resolved string value |
Messaging and events
@Events.Produce({ event: 'order-events:order-created' })
async notifyOrderCreated(payload: { orderId: string }) {
return payload;
}| Decorator | What it does |
|---|---|
| @Events.Produce({ event }) | Publish a message to a broker topic |
| @Events.Dispatch({ broker, event, schedule? }) | Schedule a message |
Notifications
| Decorator | What it does |
|---|---|
| @Notification.Send({ notification, event? }) | Send an email, SMS, or push notification |
| @Notification.Dispatch({ notification, event, schedule? }) | Schedule a notification |
Sessions
@Post('login')
@Session.Start({ tag: 'user-session' })
async login(@Body() body: { userId: string; email: string }) {
return body; // interceptor calls sessions.start, returns { token, refreshToken, sessionId }
}| Decorator | What it does |
|---|---|
| @Session.Start({ tag }) | Start a session |
| @Session.Verify({ tag }) | Verify a token — method receives { token } |
| @Session.Refresh({ tag }) | Refresh a token — method receives { refreshToken } |
| @Session.Revoke({ tag }) | Revoke a session |
Features
@Post('process-order')
@Feature.Execute({ tag: 'process-order' })
async processOrder(@Body() body: { orderId: string }) {
return body;
}| Decorator | What it does |
|---|---|
| @Feature.Execute({ tag }) | Execute a feature flow |
| @Feature.Dispatch({ tag, schedule? }) | Schedule a feature flow |
Jobs
| Decorator | What it does |
|---|---|
| @Job.Run({ event }) | Trigger a background job |
Agents
| Decorator | What it does |
|---|---|
| @Agent.Run({ tag }) | Run an LLM agent |
| @Agent.Dispatch({ tag, schedule? }) | Schedule an agent run |
Resilience
| Decorator | What it does |
|---|---|
| @Quota.Run({ tag }) | Check and enforce a quota |
| @Quota.Dispatch({ tag, schedule? }) | Schedule a quota run |
| @Fallback.Run({ tag }) | Execute a fallback |
| @Fallback.Dispatch({ tag, schedule? }) | Schedule a fallback |
| @Health.Run({ tag }) | Run a healthcheck |
| @Health.Status({ tag }) | Get healthcheck status |
Webhooks
@Controller('hooks')
export class WebhooksController {
@Post('orders')
@Webhook.Consumer({ webhook: 'order_events', event: 'order_created' })
handleOrder(@WebhookBodyFromRequest() body: unknown) {
return { received: true };
}
}| Decorator | What it does |
|---|---|
| @Webhook.Register({ webhook, url, method, env? }) | Register your consumer URL with Ductape |
| @Webhook.Consumer({ webhook, event? }) | Mark a method as the inbound handler |
| @Webhook.List({ accessTag? }) | List registered webhooks |
Context overrides
| Decorator | What it does |
|---|---|
| @Product('tag') | Override the default product for this class or method |
| @Env('prd') | Override the default env for this class or method |
| @AccessTag('admin') | Set the product-app access tag |
| @InjectContext() | Inject the full DuctapeContext service |
Accessing the SDK directly
import { Injectable } from '@nestjs/common';
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
@Injectable()
export class MyService {
constructor(@InjectContext() private readonly ctx: DuctapeContext) {}
async getSecrets() {
return this.ctx.sdk.secrets.resolve({ keys: ['MY_KEY'] });
}
}ctx.sdk gives you the full @ductape/sdk instance for anything not covered by a decorator.
