@ductape/mcp 0.1.44 → 0.1.46
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 +84 -47
- package/dist/proxy-client.d.ts +1 -1
- package/dist/proxy-client.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +86 -47
- package/src/proxy-client.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { z } from 'zod';
|
|
|
15
15
|
import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
|
|
16
16
|
const MODULES = [
|
|
17
17
|
'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
|
|
18
|
-
'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
18
|
+
'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
19
19
|
'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
|
|
20
20
|
];
|
|
21
21
|
// ─── Exhaustive SDK Method & Params Reference ────────────────────────────────
|
|
@@ -184,7 +184,7 @@ SETUP — register once in AppModule:
|
|
|
184
184
|
║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
|
|
185
185
|
║ ║
|
|
186
186
|
║ Every dispatch() call (actions, features, events, databases, storage, ║
|
|
187
|
-
║ graph, notifications,
|
|
187
|
+
║ graph, notifications, quotas, fallback) enqueues jobs ║
|
|
188
188
|
║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
|
|
189
189
|
║ "Queues not configured. dispatch() requires a queue connection." ║
|
|
190
190
|
║ ║
|
|
@@ -502,33 +502,33 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
502
502
|
notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
|
|
503
503
|
notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
|
|
504
504
|
|
|
505
|
-
━━━ MODULE: messageBrokers ━━━
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
505
|
+
━━━ MODULE: events (alias: messageBrokers — both accepted; events matches the TS SDK naming) ━━━
|
|
506
|
+
events.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
|
|
507
|
+
events.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
|
|
508
|
+
events.fetch [product_tag, broker_tag]
|
|
509
|
+
events.list [product_tag]
|
|
510
|
+
events.delete [product_tag, broker_tag]
|
|
511
|
+
events.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
|
|
512
512
|
ductape_cli("events topics create -f topic.json")
|
|
513
513
|
topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
514
514
|
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
515
515
|
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
516
516
|
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
517
|
-
|
|
517
|
+
events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
518
518
|
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
519
|
-
|
|
519
|
+
events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
520
520
|
ductape_cli("events topics delete --tag broker:topic")
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
521
|
+
events.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
|
|
522
|
+
events.topics.list [product_tag, broker_tag] ← safe via ductape_execute
|
|
523
|
+
events.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
|
|
524
|
+
events.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
|
|
525
|
+
events.dispatch [{ product, env, event: "broker_tag:topic_tag", input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — event is always the fully-qualified "broker:topic" string; do NOT pass broker separately — requires redisUrl in ductape initialization
|
|
526
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
|
|
527
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
528
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
529
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
|
|
530
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
531
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
532
532
|
|
|
533
533
|
━━━ MODULE: storage ━━━
|
|
534
534
|
storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
|
|
@@ -1027,12 +1027,12 @@ function resolveSdkCallPath(operationFamily, method) {
|
|
|
1027
1027
|
return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
|
|
1028
1028
|
if (family === 'notification')
|
|
1029
1029
|
return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
|
|
1030
|
-
if (family === 'messaging' || family === 'broker') {
|
|
1030
|
+
if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
|
|
1031
1031
|
if (m === 'dispatch')
|
|
1032
|
-
return '
|
|
1032
|
+
return 'events.dispatch';
|
|
1033
1033
|
if (m === 'send' || m === 'publish' || m === 'produce')
|
|
1034
|
-
return '
|
|
1035
|
-
return `
|
|
1034
|
+
return 'events.produce';
|
|
1035
|
+
return `events.${m}`;
|
|
1036
1036
|
}
|
|
1037
1037
|
if (family === 'quota')
|
|
1038
1038
|
return `quotas.${m}`;
|
|
@@ -2467,7 +2467,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2467
2467
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2468
2468
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2469
2469
|
|
|
2470
|
-
IMPORTANT:
|
|
2470
|
+
IMPORTANT: events.topics.create requires an access key (admin operation).
|
|
2471
2471
|
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2472
2472
|
|
|
2473
2473
|
Write a topic.json file, then:
|
|
@@ -2494,7 +2494,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2494
2494
|
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2495
2495
|
|
|
2496
2496
|
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2497
|
-
ductape_execute("
|
|
2497
|
+
ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2498
2498
|
|
|
2499
2499
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
2500
2500
|
|
|
@@ -2505,7 +2505,12 @@ Import (register an EXISTING cloud resource):
|
|
|
2505
2505
|
|
|
2506
2506
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2507
2507
|
import Ductape from '@ductape/sdk';
|
|
2508
|
-
|
|
2508
|
+
// produce() does not need redis_url.
|
|
2509
|
+
// dispatch() requires redis_url in the Ductape initialization options — it throws at runtime without it.
|
|
2510
|
+
const ductape = new Ductape({
|
|
2511
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
2512
|
+
redis_url: process.env.DUCTAPE_REDIS_URL, // required for any dispatch(); omit only if never dispatching
|
|
2513
|
+
});
|
|
2509
2514
|
await ductape.events.produce({
|
|
2510
2515
|
product: "my-product",
|
|
2511
2516
|
env: "prd",
|
|
@@ -2520,14 +2525,26 @@ Import (register an EXISTING cloud resource):
|
|
|
2520
2525
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2521
2526
|
});
|
|
2522
2527
|
|
|
2523
|
-
NESTJS — method decorators:
|
|
2528
|
+
NESTJS — initialization + method decorators:
|
|
2529
|
+
// AppModule — redisUrl is required whenever any *.dispatch() is used:
|
|
2530
|
+
DuctapeModule.forRootAsync({
|
|
2531
|
+
useFactory: () => ({
|
|
2532
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
2533
|
+
product: 'my-product',
|
|
2534
|
+
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
2535
|
+
redisUrl: process.env.DUCTAPE_REDIS_URL, // required — dispatch() throws without this
|
|
2536
|
+
}),
|
|
2537
|
+
});
|
|
2538
|
+
// Environment: DUCTAPE_REDIS_URL=redis://localhost:6379 (local) or rediss://:<pw>@host:6380 (managed)
|
|
2539
|
+
// produce() and @Events.Consumer do NOT need DUCTAPE_REDIS_URL — only dispatch() does.
|
|
2540
|
+
|
|
2524
2541
|
import { Events } from '@ductape/nestjs';
|
|
2525
2542
|
@Injectable() export class OrdersService {
|
|
2526
2543
|
// Immediate produce — method returns the message payload:
|
|
2527
2544
|
@Events.Produce({ event: 'order-events:order-created' })
|
|
2528
2545
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2529
2546
|
|
|
2530
|
-
// Dispatch with static schedule
|
|
2547
|
+
// Dispatch with static schedule — requires redisUrl in DuctapeModule initialization:
|
|
2531
2548
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2532
2549
|
schedule: { every: 86400000 } })
|
|
2533
2550
|
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
@@ -2560,10 +2577,9 @@ Import (register an EXISTING cloud resource):
|
|
|
2560
2577
|
});
|
|
2561
2578
|
|
|
2562
2579
|
SCHEDULED DISPATCH (background job):
|
|
2563
|
-
ductape_execute("
|
|
2580
|
+
ductape_execute("events.dispatch", [{
|
|
2564
2581
|
product, env,
|
|
2565
|
-
|
|
2566
|
-
event: "order-events:reminder-due", // "broker:topic"
|
|
2582
|
+
event: "order-events:reminder-due", // fully-qualified "broker-tag:topic-tag" — no separate broker field
|
|
2567
2583
|
input: { message: { orderId: "123" } },
|
|
2568
2584
|
retries?: 3,
|
|
2569
2585
|
session?: "session-tag:jwt",
|
|
@@ -2631,24 +2647,24 @@ Import (register an EXISTING cloud resource):
|
|
|
2631
2647
|
|
|
2632
2648
|
DEAD-LETTER QUEUE (DLQ):
|
|
2633
2649
|
Messages whose callbacks consistently throw are automatically moved to the DLQ.
|
|
2634
|
-
Query: ductape_execute("
|
|
2650
|
+
Query: ductape_execute("events.messages.getDeadLetters",
|
|
2635
2651
|
[{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
|
|
2636
|
-
Reprocess: ductape_execute("
|
|
2652
|
+
Reprocess: ductape_execute("events.reprocessDLQ",
|
|
2637
2653
|
[{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
|
|
2638
|
-
Replay: ductape_execute("
|
|
2654
|
+
Replay: ductape_execute("events.replayEvent",
|
|
2639
2655
|
[{ product, env, eventId, force? }])
|
|
2640
2656
|
|
|
2641
2657
|
━━━ OBSERVABILITY ━━━
|
|
2642
2658
|
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2659
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
|
|
2660
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
2661
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
2662
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
2663
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2664
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2665
|
+
events.replayEvent [{ product, env, eventId, force? }]
|
|
2666
|
+
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
2667
|
+
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
2652
2668
|
`.trim(),
|
|
2653
2669
|
logs: `
|
|
2654
2670
|
DUCTAPE LOGS
|
|
@@ -3377,7 +3393,28 @@ async function main() {
|
|
|
3377
3393
|
if (!key) {
|
|
3378
3394
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3379
3395
|
}
|
|
3380
|
-
|
|
3396
|
+
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3397
|
+
const proxyModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
3398
|
+
// Normalize events.dispatch params: event must be fully-qualified "broker:topic".
|
|
3399
|
+
// If an agent passes both broker and event="broker:topic", strip broker and de-duplicate.
|
|
3400
|
+
let params = args.params;
|
|
3401
|
+
if ((args.module === 'events' || args.module === 'messageBrokers') && args.method === 'dispatch') {
|
|
3402
|
+
params = params.map((p) => {
|
|
3403
|
+
if (p && typeof p === 'object' && !Array.isArray(p)) {
|
|
3404
|
+
const obj = p;
|
|
3405
|
+
const broker = typeof obj['broker'] === 'string' ? obj['broker'] : '';
|
|
3406
|
+
const event = typeof obj['event'] === 'string' ? obj['event'] : '';
|
|
3407
|
+
if (broker && event) {
|
|
3408
|
+
// Strip broker prefix if event is already "broker:..." to avoid "broker:broker:topic"
|
|
3409
|
+
const normalized = event.startsWith(broker + ':') ? event : `${broker}:${event}`;
|
|
3410
|
+
const { broker: _removed, ...rest } = obj;
|
|
3411
|
+
return { ...rest, event: normalized };
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
return p;
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
const result = await executeViaProxy(key, proxyModule, args.method, params);
|
|
3381
3418
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3382
3419
|
}
|
|
3383
3420
|
catch (err) {
|
package/dist/proxy-client.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Client for the Ductape backend SDK proxy using Publishable Key.
|
|
3
3
|
*/
|
|
4
4
|
export declare const API_BASE_URL = "https://api.ductape.app";
|
|
5
|
-
export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' | 'notifications' | 'messageBrokers' | 'storage' | 'vector' | 'caches' | 'sessions' | 'quotas' | 'actions' | 'features' | 'jobs' | 'logs' | 'resilience' | 'health' | 'fallback' | 'secrets';
|
|
5
|
+
export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' | 'notifications' | 'messageBrokers' | 'events' | 'storage' | 'vector' | 'caches' | 'sessions' | 'quotas' | 'actions' | 'features' | 'jobs' | 'logs' | 'resilience' | 'health' | 'fallback' | 'secrets';
|
|
6
6
|
/**
|
|
7
7
|
* Execute an SDK operation via the backend proxy using a Publishable Key.
|
|
8
8
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
|
|
1
|
+
{"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
|
|
23
23
|
const MODULES: SDKModule[] = [
|
|
24
24
|
'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
|
|
25
|
-
'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
25
|
+
'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
26
26
|
'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
|
|
27
27
|
];
|
|
28
28
|
|
|
@@ -195,7 +195,7 @@ SETUP — register once in AppModule:
|
|
|
195
195
|
║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
|
|
196
196
|
║ ║
|
|
197
197
|
║ Every dispatch() call (actions, features, events, databases, storage, ║
|
|
198
|
-
║ graph, notifications,
|
|
198
|
+
║ graph, notifications, quotas, fallback) enqueues jobs ║
|
|
199
199
|
║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
|
|
200
200
|
║ "Queues not configured. dispatch() requires a queue connection." ║
|
|
201
201
|
║ ║
|
|
@@ -513,33 +513,33 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
513
513
|
notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
|
|
514
514
|
notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
|
|
515
515
|
|
|
516
|
-
━━━ MODULE: messageBrokers ━━━
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
516
|
+
━━━ MODULE: events (alias: messageBrokers — both accepted; events matches the TS SDK naming) ━━━
|
|
517
|
+
events.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
|
|
518
|
+
events.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
|
|
519
|
+
events.fetch [product_tag, broker_tag]
|
|
520
|
+
events.list [product_tag]
|
|
521
|
+
events.delete [product_tag, broker_tag]
|
|
522
|
+
events.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
|
|
523
523
|
ductape_cli("events topics create -f topic.json")
|
|
524
524
|
topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
525
525
|
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
526
526
|
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
527
527
|
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
528
|
-
|
|
528
|
+
events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
529
529
|
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
530
|
-
|
|
530
|
+
events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
531
531
|
ductape_cli("events topics delete --tag broker:topic")
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
532
|
+
events.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
|
|
533
|
+
events.topics.list [product_tag, broker_tag] ← safe via ductape_execute
|
|
534
|
+
events.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
|
|
535
|
+
events.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
|
|
536
|
+
events.dispatch [{ product, env, event: "broker_tag:topic_tag", input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — event is always the fully-qualified "broker:topic" string; do NOT pass broker separately — requires redisUrl in ductape initialization
|
|
537
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
|
|
538
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
539
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
540
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
|
|
541
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
542
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
543
543
|
|
|
544
544
|
━━━ MODULE: storage ━━━
|
|
545
545
|
storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
|
|
@@ -1062,10 +1062,10 @@ function resolveSdkCallPath(operationFamily: string, method: string): string {
|
|
|
1062
1062
|
if (family === 'vector') return `vector.${m}`;
|
|
1063
1063
|
if (family === 'storage') return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
|
|
1064
1064
|
if (family === 'notification') return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
|
|
1065
|
-
if (family === 'messaging' || family === 'broker') {
|
|
1066
|
-
if (m === 'dispatch') return '
|
|
1067
|
-
if (m === 'send' || m === 'publish' || m === 'produce') return '
|
|
1068
|
-
return `
|
|
1065
|
+
if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
|
|
1066
|
+
if (m === 'dispatch') return 'events.dispatch';
|
|
1067
|
+
if (m === 'send' || m === 'publish' || m === 'produce') return 'events.produce';
|
|
1068
|
+
return `events.${m}`;
|
|
1069
1069
|
}
|
|
1070
1070
|
if (family === 'quota') return `quotas.${m}`;
|
|
1071
1071
|
if (family === 'fallback') return `fallback.${m}`;
|
|
@@ -2540,7 +2540,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2540
2540
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2541
2541
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2542
2542
|
|
|
2543
|
-
IMPORTANT:
|
|
2543
|
+
IMPORTANT: events.topics.create requires an access key (admin operation).
|
|
2544
2544
|
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2545
2545
|
|
|
2546
2546
|
Write a topic.json file, then:
|
|
@@ -2567,7 +2567,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2567
2567
|
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2568
2568
|
|
|
2569
2569
|
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2570
|
-
ductape_execute("
|
|
2570
|
+
ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2571
2571
|
|
|
2572
2572
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
2573
2573
|
|
|
@@ -2578,7 +2578,12 @@ Import (register an EXISTING cloud resource):
|
|
|
2578
2578
|
|
|
2579
2579
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2580
2580
|
import Ductape from '@ductape/sdk';
|
|
2581
|
-
|
|
2581
|
+
// produce() does not need redis_url.
|
|
2582
|
+
// dispatch() requires redis_url in the Ductape initialization options — it throws at runtime without it.
|
|
2583
|
+
const ductape = new Ductape({
|
|
2584
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
2585
|
+
redis_url: process.env.DUCTAPE_REDIS_URL, // required for any dispatch(); omit only if never dispatching
|
|
2586
|
+
});
|
|
2582
2587
|
await ductape.events.produce({
|
|
2583
2588
|
product: "my-product",
|
|
2584
2589
|
env: "prd",
|
|
@@ -2593,14 +2598,26 @@ Import (register an EXISTING cloud resource):
|
|
|
2593
2598
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2594
2599
|
});
|
|
2595
2600
|
|
|
2596
|
-
NESTJS — method decorators:
|
|
2601
|
+
NESTJS — initialization + method decorators:
|
|
2602
|
+
// AppModule — redisUrl is required whenever any *.dispatch() is used:
|
|
2603
|
+
DuctapeModule.forRootAsync({
|
|
2604
|
+
useFactory: () => ({
|
|
2605
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
2606
|
+
product: 'my-product',
|
|
2607
|
+
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
2608
|
+
redisUrl: process.env.DUCTAPE_REDIS_URL, // required — dispatch() throws without this
|
|
2609
|
+
}),
|
|
2610
|
+
});
|
|
2611
|
+
// Environment: DUCTAPE_REDIS_URL=redis://localhost:6379 (local) or rediss://:<pw>@host:6380 (managed)
|
|
2612
|
+
// produce() and @Events.Consumer do NOT need DUCTAPE_REDIS_URL — only dispatch() does.
|
|
2613
|
+
|
|
2597
2614
|
import { Events } from '@ductape/nestjs';
|
|
2598
2615
|
@Injectable() export class OrdersService {
|
|
2599
2616
|
// Immediate produce — method returns the message payload:
|
|
2600
2617
|
@Events.Produce({ event: 'order-events:order-created' })
|
|
2601
2618
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2602
2619
|
|
|
2603
|
-
// Dispatch with static schedule
|
|
2620
|
+
// Dispatch with static schedule — requires redisUrl in DuctapeModule initialization:
|
|
2604
2621
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2605
2622
|
schedule: { every: 86400000 } })
|
|
2606
2623
|
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
@@ -2633,10 +2650,9 @@ Import (register an EXISTING cloud resource):
|
|
|
2633
2650
|
});
|
|
2634
2651
|
|
|
2635
2652
|
SCHEDULED DISPATCH (background job):
|
|
2636
|
-
ductape_execute("
|
|
2653
|
+
ductape_execute("events.dispatch", [{
|
|
2637
2654
|
product, env,
|
|
2638
|
-
|
|
2639
|
-
event: "order-events:reminder-due", // "broker:topic"
|
|
2655
|
+
event: "order-events:reminder-due", // fully-qualified "broker-tag:topic-tag" — no separate broker field
|
|
2640
2656
|
input: { message: { orderId: "123" } },
|
|
2641
2657
|
retries?: 3,
|
|
2642
2658
|
session?: "session-tag:jwt",
|
|
@@ -2704,24 +2720,24 @@ Import (register an EXISTING cloud resource):
|
|
|
2704
2720
|
|
|
2705
2721
|
DEAD-LETTER QUEUE (DLQ):
|
|
2706
2722
|
Messages whose callbacks consistently throw are automatically moved to the DLQ.
|
|
2707
|
-
Query: ductape_execute("
|
|
2723
|
+
Query: ductape_execute("events.messages.getDeadLetters",
|
|
2708
2724
|
[{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
|
|
2709
|
-
Reprocess: ductape_execute("
|
|
2725
|
+
Reprocess: ductape_execute("events.reprocessDLQ",
|
|
2710
2726
|
[{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
|
|
2711
|
-
Replay: ductape_execute("
|
|
2727
|
+
Replay: ductape_execute("events.replayEvent",
|
|
2712
2728
|
[{ product, env, eventId, force? }])
|
|
2713
2729
|
|
|
2714
2730
|
━━━ OBSERVABILITY ━━━
|
|
2715
2731
|
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2732
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
|
|
2733
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
2734
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
2735
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
2736
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2737
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2738
|
+
events.replayEvent [{ product, env, eventId, force? }]
|
|
2739
|
+
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
2740
|
+
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
2725
2741
|
`.trim(),
|
|
2726
2742
|
|
|
2727
2743
|
logs: `
|
|
@@ -3475,7 +3491,30 @@ async function main() {
|
|
|
3475
3491
|
if (!key) {
|
|
3476
3492
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3477
3493
|
}
|
|
3478
|
-
|
|
3494
|
+
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3495
|
+
const proxyModule: SDKModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
3496
|
+
|
|
3497
|
+
// Normalize events.dispatch params: event must be fully-qualified "broker:topic".
|
|
3498
|
+
// If an agent passes both broker and event="broker:topic", strip broker and de-duplicate.
|
|
3499
|
+
let params = args.params;
|
|
3500
|
+
if ((args.module === 'events' || args.module === 'messageBrokers') && args.method === 'dispatch') {
|
|
3501
|
+
params = params.map((p) => {
|
|
3502
|
+
if (p && typeof p === 'object' && !Array.isArray(p)) {
|
|
3503
|
+
const obj = p as Record<string, unknown>;
|
|
3504
|
+
const broker = typeof obj['broker'] === 'string' ? obj['broker'] : '';
|
|
3505
|
+
const event = typeof obj['event'] === 'string' ? obj['event'] : '';
|
|
3506
|
+
if (broker && event) {
|
|
3507
|
+
// Strip broker prefix if event is already "broker:..." to avoid "broker:broker:topic"
|
|
3508
|
+
const normalized = event.startsWith(broker + ':') ? event : `${broker}:${event}`;
|
|
3509
|
+
const { broker: _removed, ...rest } = obj;
|
|
3510
|
+
return { ...rest, event: normalized };
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
return p;
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
const result = await executeViaProxy(key, proxyModule, args.method, params);
|
|
3479
3518
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3480
3519
|
} catch (err) {
|
|
3481
3520
|
const message = err instanceof Error ? err.message : String(err);
|