@ductape/mcp 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +116 -72
- package/package.json +1 -1
- package/src/index.ts +113 -69
package/dist/index.js
CHANGED
|
@@ -249,21 +249,46 @@ CONTROLLER DECORATORS:
|
|
|
249
249
|
@Webhook.Register(...) — register a webhook consumer URL
|
|
250
250
|
@Webhook.Consumer(...) — mark inbound handler for forwarded webhook payloads
|
|
251
251
|
|
|
252
|
-
FOR MESSAGING (events.produce / events.consume):
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
event: 'broker-tag:topic-tag'
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
252
|
+
FOR MESSAGING (events.produce / events.consume / events.dispatch):
|
|
253
|
+
|
|
254
|
+
In @ductape/nestjs, inject DuctapeContextService (not raw Ductape):
|
|
255
|
+
constructor(private readonly ductape: DuctapeContextService) {}
|
|
256
|
+
|
|
257
|
+
Produce immediately (method decorator — returns payload as message):
|
|
258
|
+
@Events.Produce({ event: 'broker-tag:topic-tag' })
|
|
259
|
+
emitOrderCreated(payload: { orderId: string }) { return payload; }
|
|
260
|
+
|
|
261
|
+
Dispatch (scheduled or immediate) — static schedule in decorator:
|
|
262
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
|
|
263
|
+
dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
|
|
264
|
+
|
|
265
|
+
Dispatch with dynamic schedule — method returns { message, schedule?, retries? }:
|
|
266
|
+
@Events.Dispatch({ broker: 'statecraft-events', event: 'statecraft-events:boundary-due' })
|
|
267
|
+
scheduleBoundary(match: MatchLifecycle) {
|
|
268
|
+
return {
|
|
269
|
+
message: buildBoundaryCommand(match),
|
|
270
|
+
schedule: { start_at: match.nextBoundaryAt },
|
|
271
|
+
retries: 5,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// method return takes precedence over decorator schedule; use sdk.events.dispatch() directly
|
|
275
|
+
// when even the broker/event must vary at call time.
|
|
276
|
+
|
|
277
|
+
Consume (method decorator — method is called for each incoming message):
|
|
278
|
+
@Events.Consumer({ event: 'order-events:order-created' })
|
|
279
|
+
async onOrderCreated(message: { orderId: string; total: number }) {
|
|
280
|
+
await this.processOrder(message);
|
|
281
|
+
// return to ack; throw to nack
|
|
282
|
+
}
|
|
283
|
+
// DuctapeEventsConsumerService wires this up automatically at module init.
|
|
284
|
+
// No manual onModuleInit needed when using the decorator.
|
|
285
|
+
|
|
286
|
+
Low-level SDK access (for produce only — not needed for consume with the decorator):
|
|
287
|
+
await this.ductape.sdk.events.produce({
|
|
288
|
+
product: 'my-product', env: 'prd',
|
|
289
|
+
event: 'broker-tag:topic-tag',
|
|
290
|
+
message: { ... },
|
|
291
|
+
});
|
|
267
292
|
|
|
268
293
|
SPECIALIZED MODULES (for injecting handles directly without @InjectContext):
|
|
269
294
|
|
|
@@ -464,18 +489,18 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
464
489
|
messageBrokers.fetch [product_tag, broker_tag]
|
|
465
490
|
messageBrokers.list [product_tag]
|
|
466
491
|
messageBrokers.delete [product_tag, broker_tag]
|
|
467
|
-
messageBrokers.topics.create
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
←
|
|
472
|
-
|
|
473
|
-
←
|
|
474
|
-
|
|
475
|
-
messageBrokers.topics.
|
|
476
|
-
|
|
477
|
-
messageBrokers.topics.fetch [product_tag, topic_tag]
|
|
478
|
-
messageBrokers.topics.list [product_tag, broker_tag]
|
|
492
|
+
messageBrokers.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
|
|
493
|
+
ductape_cli("events topics create -f topic.json")
|
|
494
|
+
topic.json: { tag, name, broker, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
495
|
+
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
496
|
+
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
497
|
+
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
498
|
+
messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
499
|
+
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
500
|
+
messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
501
|
+
ductape_cli("events topics delete --tag broker:topic")
|
|
502
|
+
messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
|
|
503
|
+
messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
|
|
479
504
|
messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
|
|
480
505
|
messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
|
|
481
506
|
messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
|
|
@@ -2422,22 +2447,34 @@ Import (register an EXISTING cloud resource):
|
|
|
2422
2447
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2423
2448
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2424
2449
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
name: "Order Created",
|
|
2428
|
-
broker: "order-events", // broker component tag
|
|
2429
|
-
description?: string,
|
|
2430
|
-
sample: { orderId: "string", total: 0 }, // documents expected message shape
|
|
2431
|
-
idempotent?: boolean, // if true, Ductape deduplicates by idempotency_key
|
|
2432
|
-
// AWS SQS only — must supply per-env queue URL:
|
|
2433
|
-
queueUrls?: [
|
|
2434
|
-
{ env_slug: "snd", url: "https://sqs.us-east-1.amazonaws.com/123/queue-snd" },
|
|
2435
|
-
{ env_slug: "prd", url: "https://sqs.us-east-1.amazonaws.com/123/queue-prd" }
|
|
2436
|
-
]
|
|
2437
|
-
}])
|
|
2450
|
+
IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
|
|
2451
|
+
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2438
2452
|
|
|
2439
|
-
|
|
2440
|
-
|
|
2453
|
+
Write a topic.json file, then:
|
|
2454
|
+
ductape_cli("events topics create -f topic.json")
|
|
2455
|
+
|
|
2456
|
+
topic.json schema:
|
|
2457
|
+
{
|
|
2458
|
+
"tag": "order-created", // topic tag only — NOT "broker:topic"
|
|
2459
|
+
"name": "Order Created",
|
|
2460
|
+
"broker": "order-events", // broker component tag
|
|
2461
|
+
"description": "...", // optional
|
|
2462
|
+
"sample": { "orderId": "string", "total": 0 }, // expected message shape
|
|
2463
|
+
"idempotent": false, // optional — deduplicates by idempotency_key when true
|
|
2464
|
+
// AWS SQS only — per-env queue URL:
|
|
2465
|
+
"queueUrls": [
|
|
2466
|
+
{ "env_slug": "snd", "url": "https://sqs.us-east-1.amazonaws.com/123/queue-snd" },
|
|
2467
|
+
{ "env_slug": "prd", "url": "https://sqs.us-east-1.amazonaws.com/123/queue-prd" }
|
|
2468
|
+
]
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
Other topic operations (all require access key via ductape_cli):
|
|
2472
|
+
ductape_cli("events topics list --tag order-events") → list topics for a broker
|
|
2473
|
+
ductape_cli("events topics get --tag order-events:order-created")
|
|
2474
|
+
ductape_cli("events topics update --tag order-events:order-created -f patch.json")
|
|
2475
|
+
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2476
|
+
|
|
2477
|
+
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2441
2478
|
ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2442
2479
|
|
|
2443
2480
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
@@ -2464,16 +2501,30 @@ Import (register an EXISTING cloud resource):
|
|
|
2464
2501
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2465
2502
|
});
|
|
2466
2503
|
|
|
2467
|
-
NESTJS — method
|
|
2468
|
-
import {
|
|
2504
|
+
NESTJS — method decorators:
|
|
2505
|
+
import { Events } from '@ductape/nestjs';
|
|
2469
2506
|
@Injectable() export class OrdersService {
|
|
2470
|
-
|
|
2507
|
+
// Immediate produce — method returns the message payload:
|
|
2508
|
+
@Events.Produce({ event: 'order-events:order-created' })
|
|
2471
2509
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2472
2510
|
|
|
2473
|
-
//
|
|
2474
|
-
@
|
|
2475
|
-
|
|
2476
|
-
|
|
2511
|
+
// Dispatch with static schedule (known at deploy time):
|
|
2512
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2513
|
+
schedule: { every: 86400000 } })
|
|
2514
|
+
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
2515
|
+
|
|
2516
|
+
// Dispatch with dynamic schedule (known at call time) — method returns { message, schedule?, retries? }:
|
|
2517
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:fulfillment-due' })
|
|
2518
|
+
scheduleFulfillment(order: Order) {
|
|
2519
|
+
return {
|
|
2520
|
+
message: { orderId: order.id, items: order.items },
|
|
2521
|
+
schedule: { start_at: order.expectedAt },
|
|
2522
|
+
retries: 3,
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
// Called as: await this.ordersService.scheduleFulfillment(order);
|
|
2526
|
+
// When method return has a 'message' key, schedule/retries from return take precedence over decorator config.
|
|
2527
|
+
// For even more control (dynamic broker/event), use sdk.events.dispatch() directly.
|
|
2477
2528
|
}
|
|
2478
2529
|
|
|
2479
2530
|
CLIENT-SIDE (browser — publishable key):
|
|
@@ -2510,11 +2561,6 @@ Import (register an EXISTING cloud resource):
|
|
|
2510
2561
|
|
|
2511
2562
|
━━━ STEP 4: CONSUME — WRITTEN IN APPLICATION CODE ━━━
|
|
2512
2563
|
|
|
2513
|
-
Consumers are auto-registered by the SDK on first consume call.
|
|
2514
|
-
Consumer registration options (all optional — used for tracking in Workbench):
|
|
2515
|
-
consumer?: { tag?: string, name?: string, description?: string }
|
|
2516
|
-
If tag is omitted, Ductape generates one: "consumer-<brokerTag>-<topicTag>".
|
|
2517
|
-
|
|
2518
2564
|
ACK BEHAVIOR (automatic):
|
|
2519
2565
|
- Callback returns successfully → message is acknowledged (ack)
|
|
2520
2566
|
- Callback throws → message is tracked as failed; broker nacks/retries per provider behavior
|
|
@@ -2533,34 +2579,32 @@ Import (register an EXISTING cloud resource):
|
|
|
2533
2579
|
Run multiple instances of your service to scale consumption.
|
|
2534
2580
|
|
|
2535
2581
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2536
|
-
Start consuming in your module init or service startup:
|
|
2537
2582
|
await ductape.events.consume({
|
|
2538
2583
|
product: "my-product",
|
|
2539
2584
|
env: "prd",
|
|
2540
|
-
event: "order-events:order-created",
|
|
2585
|
+
event: "order-events:order-created", // ALWAYS "broker-tag:topic-tag"
|
|
2541
2586
|
callback: async (message) => {
|
|
2542
|
-
// All real processing logic goes here.
|
|
2543
2587
|
// Throw to nack. Return to ack.
|
|
2544
2588
|
await processOrder(message as { orderId: string; total: number });
|
|
2545
2589
|
},
|
|
2546
|
-
consumer?: { tag: "order-processor", name: "Order Processor" },
|
|
2547
2590
|
});
|
|
2548
2591
|
|
|
2549
|
-
NESTJS — use
|
|
2592
|
+
NESTJS — use @Events.Consumer decorator (preferred):
|
|
2593
|
+
import { Events } from '@ductape/nestjs';
|
|
2550
2594
|
@Injectable()
|
|
2551
|
-
export class OrderConsumerService
|
|
2552
|
-
|
|
2553
|
-
async
|
|
2554
|
-
await this.
|
|
2555
|
-
|
|
2556
|
-
env: process.env.DUCTAPE_ENV || 'prd',
|
|
2557
|
-
event: "order-events:order-created",
|
|
2558
|
-
callback: async (message) => { await this.handle(message); },
|
|
2559
|
-
consumer: { tag: "order-consumer", name: "Order Consumer" },
|
|
2560
|
-
});
|
|
2595
|
+
export class OrderConsumerService {
|
|
2596
|
+
@Events.Consumer({ event: 'order-events:order-created' })
|
|
2597
|
+
async onOrderCreated(message: { orderId: string; total: number }) {
|
|
2598
|
+
await this.processOrder(message);
|
|
2599
|
+
// return to ack; throw to nack
|
|
2561
2600
|
}
|
|
2562
|
-
private async
|
|
2601
|
+
private async processOrder(msg: { orderId: string; total: number }) { /* ... */ }
|
|
2563
2602
|
}
|
|
2603
|
+
// DuctapeEventsConsumerService (auto-registered by DuctapeModule) wires this up at startup.
|
|
2604
|
+
// No manual onModuleInit needed.
|
|
2605
|
+
|
|
2606
|
+
// If you need to override product/env for a specific consumer:
|
|
2607
|
+
@Events.Consumer({ event: 'order-events:order-created', product: 'my-product', env: 'prd' })
|
|
2564
2608
|
|
|
2565
2609
|
CLIENT-SIDE: Clients CANNOT consume. Event consumption is always server-side only.
|
|
2566
2610
|
This is the key distinction between server topics (produce + consume) and client-observable
|
|
@@ -3519,11 +3563,11 @@ async function main() {
|
|
|
3519
3563
|
' "product":"my-product","component":"core-db","env":"prd","resource":"Cluster0","dbName":"myapp_prd"}]\n' +
|
|
3520
3564
|
' - Message broker / event broker import:\n' +
|
|
3521
3565
|
' CLI accepts these aliases for the messageBrokers module: events, event, broker, brokers, message-brokers.\n' +
|
|
3522
|
-
' List existing brokers: ductape_cli("resources events list
|
|
3566
|
+
' List existing brokers: ductape_cli("resources events list --json")\n' +
|
|
3523
3567
|
' GCP Pub/Sub service identifier is "pubsub". AWS SQS is "sqs". Azure Service Bus is "servicebus".\n' +
|
|
3524
3568
|
' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
|
|
3525
3569
|
' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
|
|
3526
|
-
' After importing, create
|
|
3570
|
+
' After importing, create topics first with ductape_cli("events topics create -f topic.json") — SQS requires explicit topic creation with queueUrls. For other providers, topics auto-register on first produce but should still be created explicitly before any consumer subscribes.\n' +
|
|
3527
3571
|
' - Listing workspaces, products, secrets\n' +
|
|
3528
3572
|
' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
|
|
3529
3573
|
' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -260,21 +260,46 @@ CONTROLLER DECORATORS:
|
|
|
260
260
|
@Webhook.Register(...) — register a webhook consumer URL
|
|
261
261
|
@Webhook.Consumer(...) — mark inbound handler for forwarded webhook payloads
|
|
262
262
|
|
|
263
|
-
FOR MESSAGING (events.produce / events.consume):
|
|
263
|
+
FOR MESSAGING (events.produce / events.consume / events.dispatch):
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
In @ductape/nestjs, inject DuctapeContextService (not raw Ductape):
|
|
266
|
+
constructor(private readonly ductape: DuctapeContextService) {}
|
|
266
267
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
message: { ... },
|
|
271
|
-
});
|
|
268
|
+
Produce immediately (method decorator — returns payload as message):
|
|
269
|
+
@Events.Produce({ event: 'broker-tag:topic-tag' })
|
|
270
|
+
emitOrderCreated(payload: { orderId: string }) { return payload; }
|
|
272
271
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
272
|
+
Dispatch (scheduled or immediate) — static schedule in decorator:
|
|
273
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
|
|
274
|
+
dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
|
|
275
|
+
|
|
276
|
+
Dispatch with dynamic schedule — method returns { message, schedule?, retries? }:
|
|
277
|
+
@Events.Dispatch({ broker: 'statecraft-events', event: 'statecraft-events:boundary-due' })
|
|
278
|
+
scheduleBoundary(match: MatchLifecycle) {
|
|
279
|
+
return {
|
|
280
|
+
message: buildBoundaryCommand(match),
|
|
281
|
+
schedule: { start_at: match.nextBoundaryAt },
|
|
282
|
+
retries: 5,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// method return takes precedence over decorator schedule; use sdk.events.dispatch() directly
|
|
286
|
+
// when even the broker/event must vary at call time.
|
|
287
|
+
|
|
288
|
+
Consume (method decorator — method is called for each incoming message):
|
|
289
|
+
@Events.Consumer({ event: 'order-events:order-created' })
|
|
290
|
+
async onOrderCreated(message: { orderId: string; total: number }) {
|
|
291
|
+
await this.processOrder(message);
|
|
292
|
+
// return to ack; throw to nack
|
|
293
|
+
}
|
|
294
|
+
// DuctapeEventsConsumerService wires this up automatically at module init.
|
|
295
|
+
// No manual onModuleInit needed when using the decorator.
|
|
296
|
+
|
|
297
|
+
Low-level SDK access (for produce only — not needed for consume with the decorator):
|
|
298
|
+
await this.ductape.sdk.events.produce({
|
|
299
|
+
product: 'my-product', env: 'prd',
|
|
300
|
+
event: 'broker-tag:topic-tag',
|
|
301
|
+
message: { ... },
|
|
302
|
+
});
|
|
278
303
|
|
|
279
304
|
SPECIALIZED MODULES (for injecting handles directly without @InjectContext):
|
|
280
305
|
|
|
@@ -475,18 +500,18 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
475
500
|
messageBrokers.fetch [product_tag, broker_tag]
|
|
476
501
|
messageBrokers.list [product_tag]
|
|
477
502
|
messageBrokers.delete [product_tag, broker_tag]
|
|
478
|
-
messageBrokers.topics.create
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
←
|
|
483
|
-
|
|
484
|
-
←
|
|
485
|
-
|
|
486
|
-
messageBrokers.topics.
|
|
487
|
-
|
|
488
|
-
messageBrokers.topics.fetch [product_tag, topic_tag]
|
|
489
|
-
messageBrokers.topics.list [product_tag, broker_tag]
|
|
503
|
+
messageBrokers.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
|
|
504
|
+
ductape_cli("events topics create -f topic.json")
|
|
505
|
+
topic.json: { tag, name, broker, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
506
|
+
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
507
|
+
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
508
|
+
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
509
|
+
messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
510
|
+
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
511
|
+
messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
512
|
+
ductape_cli("events topics delete --tag broker:topic")
|
|
513
|
+
messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
|
|
514
|
+
messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
|
|
490
515
|
messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
|
|
491
516
|
messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
|
|
492
517
|
messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
|
|
@@ -2495,22 +2520,34 @@ Import (register an EXISTING cloud resource):
|
|
|
2495
2520
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2496
2521
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2497
2522
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
name: "Order Created",
|
|
2501
|
-
broker: "order-events", // broker component tag
|
|
2502
|
-
description?: string,
|
|
2503
|
-
sample: { orderId: "string", total: 0 }, // documents expected message shape
|
|
2504
|
-
idempotent?: boolean, // if true, Ductape deduplicates by idempotency_key
|
|
2505
|
-
// AWS SQS only — must supply per-env queue URL:
|
|
2506
|
-
queueUrls?: [
|
|
2507
|
-
{ env_slug: "snd", url: "https://sqs.us-east-1.amazonaws.com/123/queue-snd" },
|
|
2508
|
-
{ env_slug: "prd", url: "https://sqs.us-east-1.amazonaws.com/123/queue-prd" }
|
|
2509
|
-
]
|
|
2510
|
-
}])
|
|
2523
|
+
IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
|
|
2524
|
+
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2511
2525
|
|
|
2512
|
-
|
|
2513
|
-
|
|
2526
|
+
Write a topic.json file, then:
|
|
2527
|
+
ductape_cli("events topics create -f topic.json")
|
|
2528
|
+
|
|
2529
|
+
topic.json schema:
|
|
2530
|
+
{
|
|
2531
|
+
"tag": "order-created", // topic tag only — NOT "broker:topic"
|
|
2532
|
+
"name": "Order Created",
|
|
2533
|
+
"broker": "order-events", // broker component tag
|
|
2534
|
+
"description": "...", // optional
|
|
2535
|
+
"sample": { "orderId": "string", "total": 0 }, // expected message shape
|
|
2536
|
+
"idempotent": false, // optional — deduplicates by idempotency_key when true
|
|
2537
|
+
// AWS SQS only — per-env queue URL:
|
|
2538
|
+
"queueUrls": [
|
|
2539
|
+
{ "env_slug": "snd", "url": "https://sqs.us-east-1.amazonaws.com/123/queue-snd" },
|
|
2540
|
+
{ "env_slug": "prd", "url": "https://sqs.us-east-1.amazonaws.com/123/queue-prd" }
|
|
2541
|
+
]
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
Other topic operations (all require access key via ductape_cli):
|
|
2545
|
+
ductape_cli("events topics list --tag order-events") → list topics for a broker
|
|
2546
|
+
ductape_cli("events topics get --tag order-events:order-created")
|
|
2547
|
+
ductape_cli("events topics update --tag order-events:order-created -f patch.json")
|
|
2548
|
+
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2549
|
+
|
|
2550
|
+
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2514
2551
|
ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2515
2552
|
|
|
2516
2553
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
@@ -2537,16 +2574,30 @@ Import (register an EXISTING cloud resource):
|
|
|
2537
2574
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2538
2575
|
});
|
|
2539
2576
|
|
|
2540
|
-
NESTJS — method
|
|
2541
|
-
import {
|
|
2577
|
+
NESTJS — method decorators:
|
|
2578
|
+
import { Events } from '@ductape/nestjs';
|
|
2542
2579
|
@Injectable() export class OrdersService {
|
|
2543
|
-
|
|
2580
|
+
// Immediate produce — method returns the message payload:
|
|
2581
|
+
@Events.Produce({ event: 'order-events:order-created' })
|
|
2544
2582
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2545
2583
|
|
|
2546
|
-
//
|
|
2547
|
-
@
|
|
2548
|
-
|
|
2549
|
-
|
|
2584
|
+
// Dispatch with static schedule (known at deploy time):
|
|
2585
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2586
|
+
schedule: { every: 86400000 } })
|
|
2587
|
+
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
2588
|
+
|
|
2589
|
+
// Dispatch with dynamic schedule (known at call time) — method returns { message, schedule?, retries? }:
|
|
2590
|
+
@Events.Dispatch({ broker: 'order-events', event: 'order-events:fulfillment-due' })
|
|
2591
|
+
scheduleFulfillment(order: Order) {
|
|
2592
|
+
return {
|
|
2593
|
+
message: { orderId: order.id, items: order.items },
|
|
2594
|
+
schedule: { start_at: order.expectedAt },
|
|
2595
|
+
retries: 3,
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
// Called as: await this.ordersService.scheduleFulfillment(order);
|
|
2599
|
+
// When method return has a 'message' key, schedule/retries from return take precedence over decorator config.
|
|
2600
|
+
// For even more control (dynamic broker/event), use sdk.events.dispatch() directly.
|
|
2550
2601
|
}
|
|
2551
2602
|
|
|
2552
2603
|
CLIENT-SIDE (browser — publishable key):
|
|
@@ -2583,11 +2634,6 @@ Import (register an EXISTING cloud resource):
|
|
|
2583
2634
|
|
|
2584
2635
|
━━━ STEP 4: CONSUME — WRITTEN IN APPLICATION CODE ━━━
|
|
2585
2636
|
|
|
2586
|
-
Consumers are auto-registered by the SDK on first consume call.
|
|
2587
|
-
Consumer registration options (all optional — used for tracking in Workbench):
|
|
2588
|
-
consumer?: { tag?: string, name?: string, description?: string }
|
|
2589
|
-
If tag is omitted, Ductape generates one: "consumer-<brokerTag>-<topicTag>".
|
|
2590
|
-
|
|
2591
2637
|
ACK BEHAVIOR (automatic):
|
|
2592
2638
|
- Callback returns successfully → message is acknowledged (ack)
|
|
2593
2639
|
- Callback throws → message is tracked as failed; broker nacks/retries per provider behavior
|
|
@@ -2606,34 +2652,32 @@ Import (register an EXISTING cloud resource):
|
|
|
2606
2652
|
Run multiple instances of your service to scale consumption.
|
|
2607
2653
|
|
|
2608
2654
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2609
|
-
Start consuming in your module init or service startup:
|
|
2610
2655
|
await ductape.events.consume({
|
|
2611
2656
|
product: "my-product",
|
|
2612
2657
|
env: "prd",
|
|
2613
|
-
event: "order-events:order-created",
|
|
2658
|
+
event: "order-events:order-created", // ALWAYS "broker-tag:topic-tag"
|
|
2614
2659
|
callback: async (message) => {
|
|
2615
|
-
// All real processing logic goes here.
|
|
2616
2660
|
// Throw to nack. Return to ack.
|
|
2617
2661
|
await processOrder(message as { orderId: string; total: number });
|
|
2618
2662
|
},
|
|
2619
|
-
consumer?: { tag: "order-processor", name: "Order Processor" },
|
|
2620
2663
|
});
|
|
2621
2664
|
|
|
2622
|
-
NESTJS — use
|
|
2665
|
+
NESTJS — use @Events.Consumer decorator (preferred):
|
|
2666
|
+
import { Events } from '@ductape/nestjs';
|
|
2623
2667
|
@Injectable()
|
|
2624
|
-
export class OrderConsumerService
|
|
2625
|
-
|
|
2626
|
-
async
|
|
2627
|
-
await this.
|
|
2628
|
-
|
|
2629
|
-
env: process.env.DUCTAPE_ENV || 'prd',
|
|
2630
|
-
event: "order-events:order-created",
|
|
2631
|
-
callback: async (message) => { await this.handle(message); },
|
|
2632
|
-
consumer: { tag: "order-consumer", name: "Order Consumer" },
|
|
2633
|
-
});
|
|
2668
|
+
export class OrderConsumerService {
|
|
2669
|
+
@Events.Consumer({ event: 'order-events:order-created' })
|
|
2670
|
+
async onOrderCreated(message: { orderId: string; total: number }) {
|
|
2671
|
+
await this.processOrder(message);
|
|
2672
|
+
// return to ack; throw to nack
|
|
2634
2673
|
}
|
|
2635
|
-
private async
|
|
2674
|
+
private async processOrder(msg: { orderId: string; total: number }) { /* ... */ }
|
|
2636
2675
|
}
|
|
2676
|
+
// DuctapeEventsConsumerService (auto-registered by DuctapeModule) wires this up at startup.
|
|
2677
|
+
// No manual onModuleInit needed.
|
|
2678
|
+
|
|
2679
|
+
// If you need to override product/env for a specific consumer:
|
|
2680
|
+
@Events.Consumer({ event: 'order-events:order-created', product: 'my-product', env: 'prd' })
|
|
2637
2681
|
|
|
2638
2682
|
CLIENT-SIDE: Clients CANNOT consume. Event consumption is always server-side only.
|
|
2639
2683
|
This is the key distinction between server topics (produce + consume) and client-observable
|
|
@@ -3655,11 +3699,11 @@ async function main() {
|
|
|
3655
3699
|
' "product":"my-product","component":"core-db","env":"prd","resource":"Cluster0","dbName":"myapp_prd"}]\n' +
|
|
3656
3700
|
' - Message broker / event broker import:\n' +
|
|
3657
3701
|
' CLI accepts these aliases for the messageBrokers module: events, event, broker, brokers, message-brokers.\n' +
|
|
3658
|
-
' List existing brokers: ductape_cli("resources events list
|
|
3702
|
+
' List existing brokers: ductape_cli("resources events list --json")\n' +
|
|
3659
3703
|
' GCP Pub/Sub service identifier is "pubsub". AWS SQS is "sqs". Azure Service Bus is "servicebus".\n' +
|
|
3660
3704
|
' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
|
|
3661
3705
|
' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
|
|
3662
|
-
' After importing, create
|
|
3706
|
+
' After importing, create topics first with ductape_cli("events topics create -f topic.json") — SQS requires explicit topic creation with queueUrls. For other providers, topics auto-register on first produce but should still be created explicitly before any consumer subscribes.\n' +
|
|
3663
3707
|
' - Listing workspaces, products, secrets\n' +
|
|
3664
3708
|
' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
|
|
3665
3709
|
' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
|