@ductape/mcp 0.1.43 → 0.1.45
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 +94 -66
- 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 +94 -66
- package/src/proxy-client.ts +1 -0
- package/tsconfig.json +2 -1
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 ────────────────────────────────
|
|
@@ -180,29 +180,33 @@ global interceptors, decorators, and type-safe resource handles.
|
|
|
180
180
|
|
|
181
181
|
SETUP — register once in AppModule:
|
|
182
182
|
|
|
183
|
+
╔══════════════════════════════════════════════════════════════════════════╗
|
|
184
|
+
║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
|
|
185
|
+
║ ║
|
|
186
|
+
║ Every dispatch() call (actions, features, events, databases, storage, ║
|
|
187
|
+
║ graph, notifications, quotas, fallback) enqueues jobs ║
|
|
188
|
+
║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
|
|
189
|
+
║ "Queues not configured. dispatch() requires a queue connection." ║
|
|
190
|
+
║ ║
|
|
191
|
+
║ *.run(), events.produce(), and @Events.Consumer do NOT need Redis. ║
|
|
192
|
+
║ Only dispatch() does — and it is non-negotiable. ║
|
|
193
|
+
╚══════════════════════════════════════════════════════════════════════════╝
|
|
194
|
+
|
|
183
195
|
import { DuctapeModule } from '@ductape/nestjs';
|
|
184
196
|
|
|
185
197
|
@Module({
|
|
186
198
|
imports: [
|
|
187
199
|
DuctapeModule.forIntegration({
|
|
188
200
|
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
189
|
-
product: 'my-product',
|
|
201
|
+
product: 'my-product',
|
|
190
202
|
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
191
|
-
redisUrl: process.env.
|
|
203
|
+
redisUrl: process.env.DUCTAPE_REDIS_URL, // required — no dispatch() works without this
|
|
192
204
|
}),
|
|
193
205
|
],
|
|
194
206
|
})
|
|
195
207
|
export class AppModule {}
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
ALL *.dispatch() calls (actions.dispatch, features.dispatch, events.dispatch, databases.dispatch,
|
|
199
|
-
storage.dispatch, graph.dispatch, notifications.dispatch, messageBrokers.dispatch, quotas.dispatch,
|
|
200
|
-
fallback.dispatch, etc.) enqueue jobs via BullMQ, which requires a Redis connection.
|
|
201
|
-
Without redisUrl ANY dispatch() call throws "Queues not configured. dispatch() requires a queue connection."
|
|
202
|
-
Set REDIS_URL (e.g. redis://localhost:6379 or a managed Redis connection string) and pass it as redisUrl.
|
|
203
|
-
*.run(), events.produce(), and @Events.Consumer do NOT require Redis — only dispatch() does.
|
|
204
|
-
|
|
205
|
-
// Async (e.g. pulling key from ConfigService):
|
|
209
|
+
// Async (e.g. pulling from ConfigService):
|
|
206
210
|
DuctapeModule.forRootAsync({
|
|
207
211
|
imports: [ConfigModule],
|
|
208
212
|
inject: [ConfigService],
|
|
@@ -210,10 +214,14 @@ SETUP — register once in AppModule:
|
|
|
210
214
|
accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
|
|
211
215
|
product: cfg.get('DUCTAPE_PRODUCT'),
|
|
212
216
|
env: cfg.get('DUCTAPE_ENV'),
|
|
213
|
-
redisUrl: cfg.get('
|
|
217
|
+
redisUrl: cfg.get('DUCTAPE_REDIS_URL'), // required — no dispatch() works without this
|
|
214
218
|
}),
|
|
215
219
|
})
|
|
216
220
|
|
|
221
|
+
Environment variable (add to .env and deployment secrets):
|
|
222
|
+
DUCTAPE_REDIS_URL=redis://localhost:6379 # local dev
|
|
223
|
+
DUCTAPE_REDIS_URL=rediss://:<password>@host:6380 # managed Redis (TLS)
|
|
224
|
+
|
|
217
225
|
INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
|
|
218
226
|
|
|
219
227
|
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
|
|
@@ -268,7 +276,7 @@ FOR MESSAGING (events.produce / events.consume / events.dispatch):
|
|
|
268
276
|
@Events.Produce({ event: 'broker-tag:topic-tag' })
|
|
269
277
|
emitOrderCreated(payload: { orderId: string }) { return payload; }
|
|
270
278
|
|
|
271
|
-
Dispatch (scheduled or immediate) —
|
|
279
|
+
Dispatch (scheduled or immediate) — dispatch() CANNOT be used without redisUrl in forIntegration:
|
|
272
280
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
|
|
273
281
|
dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
|
|
274
282
|
|
|
@@ -343,7 +351,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
343
351
|
actions.fetch [app_tag, action_tag]
|
|
344
352
|
actions.list [app_tag]
|
|
345
353
|
actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
|
|
346
|
-
actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch")
|
|
354
|
+
actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch") — requires redisUrl in ductape initialization
|
|
347
355
|
|
|
348
356
|
━━━ MODULE: auths ━━━
|
|
349
357
|
auths.create [app_tag, data: { tag: string, name: string, setup_type: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry: number, period: "seconds"|"minutes"|"hours"|"days", description: string, action_tag?: string }]
|
|
@@ -421,7 +429,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
421
429
|
quotas.list [product_tag]
|
|
422
430
|
quotas.delete [product_tag, quota_tag]
|
|
423
431
|
quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="run", targets={tag})
|
|
424
|
-
quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch")
|
|
432
|
+
quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch") — requires redisUrl in ductape initialization
|
|
425
433
|
|
|
426
434
|
━━━ MODULE: fallback ━━━
|
|
427
435
|
fallback.create [product_tag, data: {
|
|
@@ -464,7 +472,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
464
472
|
fallback.list [product_tag]
|
|
465
473
|
fallback.delete [product_tag, fallback_tag]
|
|
466
474
|
fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="run", targets={tag})
|
|
467
|
-
fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch")
|
|
475
|
+
fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch") — requires redisUrl in ductape initialization
|
|
468
476
|
|
|
469
477
|
━━━ MODULE: health ━━━
|
|
470
478
|
health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
|
|
@@ -491,36 +499,36 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
491
499
|
notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
|
|
492
500
|
notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
|
|
493
501
|
notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
|
|
494
|
-
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")
|
|
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
|
|
495
503
|
notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
|
|
496
504
|
|
|
497
|
-
━━━ MODULE: messageBrokers ━━━
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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:
|
|
504
512
|
ductape_cli("events topics create -f topic.json")
|
|
505
513
|
topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
506
514
|
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
507
515
|
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
508
516
|
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
509
|
-
|
|
517
|
+
events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
510
518
|
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
511
|
-
|
|
519
|
+
events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
512
520
|
ductape_cli("events topics delete --tag broker:topic")
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — 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 }]
|
|
524
532
|
|
|
525
533
|
━━━ MODULE: storage ━━━
|
|
526
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 } }] }]
|
|
@@ -540,7 +548,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
540
548
|
storage.files.delete [{ product, env, storage, fileName }]
|
|
541
549
|
storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
|
|
542
550
|
storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
|
|
543
|
-
storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage})
|
|
551
|
+
storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage}) — requires redisUrl in ductape initialization
|
|
544
552
|
|
|
545
553
|
━━━ MODULE: databases ━━━
|
|
546
554
|
databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite",
|
|
@@ -608,8 +616,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
608
616
|
databases.action.fetch [action_tag]
|
|
609
617
|
databases.action.list [database_tag]
|
|
610
618
|
databases.action.delete [action_tag]
|
|
611
|
-
databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
|
|
612
|
-
databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
|
|
619
|
+
databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
|
|
620
|
+
databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
|
|
613
621
|
databases.beginTransaction [{ product, env, database, isolationLevel?: "READ_COMMITTED"|"REPEATABLE_READ"|"SERIALIZABLE" }]
|
|
614
622
|
→ returns a transaction object; pass it to insert/update/delete/upsert/query calls as the last argument.
|
|
615
623
|
Commit with: transaction.commit() Rollback with: transaction.rollback()
|
|
@@ -671,7 +679,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
671
679
|
graph.beginTransaction [options?]
|
|
672
680
|
graph.commitTransaction [transaction]
|
|
673
681
|
graph.rollbackTransaction [transaction]
|
|
674
|
-
graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph})
|
|
682
|
+
graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph}) — requires redisUrl in ductape initialization
|
|
675
683
|
|
|
676
684
|
━━━ MODULE: vector ━━━
|
|
677
685
|
vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
|
|
@@ -791,7 +799,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
791
799
|
timeout?: number
|
|
792
800
|
}]
|
|
793
801
|
|
|
794
|
-
features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature})
|
|
802
|
+
features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature}) — requires redisUrl in ductape initialization
|
|
795
803
|
product: string,
|
|
796
804
|
env: string,
|
|
797
805
|
feature: string,
|
|
@@ -1019,12 +1027,12 @@ function resolveSdkCallPath(operationFamily, method) {
|
|
|
1019
1027
|
return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
|
|
1020
1028
|
if (family === 'notification')
|
|
1021
1029
|
return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
|
|
1022
|
-
if (family === 'messaging' || family === 'broker') {
|
|
1030
|
+
if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
|
|
1023
1031
|
if (m === 'dispatch')
|
|
1024
|
-
return '
|
|
1032
|
+
return 'events.dispatch';
|
|
1025
1033
|
if (m === 'send' || m === 'publish' || m === 'produce')
|
|
1026
|
-
return '
|
|
1027
|
-
return `
|
|
1034
|
+
return 'events.produce';
|
|
1035
|
+
return `events.${m}`;
|
|
1028
1036
|
}
|
|
1029
1037
|
if (family === 'quota')
|
|
1030
1038
|
return `quotas.${m}`;
|
|
@@ -2459,7 +2467,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2459
2467
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2460
2468
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2461
2469
|
|
|
2462
|
-
IMPORTANT:
|
|
2470
|
+
IMPORTANT: events.topics.create requires an access key (admin operation).
|
|
2463
2471
|
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2464
2472
|
|
|
2465
2473
|
Write a topic.json file, then:
|
|
@@ -2486,7 +2494,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2486
2494
|
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2487
2495
|
|
|
2488
2496
|
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2489
|
-
ductape_execute("
|
|
2497
|
+
ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2490
2498
|
|
|
2491
2499
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
2492
2500
|
|
|
@@ -2497,7 +2505,12 @@ Import (register an EXISTING cloud resource):
|
|
|
2497
2505
|
|
|
2498
2506
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2499
2507
|
import Ductape from '@ductape/sdk';
|
|
2500
|
-
|
|
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
|
+
});
|
|
2501
2514
|
await ductape.events.produce({
|
|
2502
2515
|
product: "my-product",
|
|
2503
2516
|
env: "prd",
|
|
@@ -2512,14 +2525,26 @@ Import (register an EXISTING cloud resource):
|
|
|
2512
2525
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2513
2526
|
});
|
|
2514
2527
|
|
|
2515
|
-
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
|
+
|
|
2516
2541
|
import { Events } from '@ductape/nestjs';
|
|
2517
2542
|
@Injectable() export class OrdersService {
|
|
2518
2543
|
// Immediate produce — method returns the message payload:
|
|
2519
2544
|
@Events.Produce({ event: 'order-events:order-created' })
|
|
2520
2545
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2521
2546
|
|
|
2522
|
-
// Dispatch with static schedule
|
|
2547
|
+
// Dispatch with static schedule — requires redisUrl in DuctapeModule initialization:
|
|
2523
2548
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2524
2549
|
schedule: { every: 86400000 } })
|
|
2525
2550
|
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
@@ -2552,7 +2577,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2552
2577
|
});
|
|
2553
2578
|
|
|
2554
2579
|
SCHEDULED DISPATCH (background job):
|
|
2555
|
-
ductape_execute("
|
|
2580
|
+
ductape_execute("events.dispatch", [{
|
|
2556
2581
|
product, env,
|
|
2557
2582
|
broker: "order-events", // broker tag
|
|
2558
2583
|
event: "order-events:reminder-due", // "broker:topic"
|
|
@@ -2623,24 +2648,24 @@ Import (register an EXISTING cloud resource):
|
|
|
2623
2648
|
|
|
2624
2649
|
DEAD-LETTER QUEUE (DLQ):
|
|
2625
2650
|
Messages whose callbacks consistently throw are automatically moved to the DLQ.
|
|
2626
|
-
Query: ductape_execute("
|
|
2651
|
+
Query: ductape_execute("events.messages.getDeadLetters",
|
|
2627
2652
|
[{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
|
|
2628
|
-
Reprocess: ductape_execute("
|
|
2653
|
+
Reprocess: ductape_execute("events.reprocessDLQ",
|
|
2629
2654
|
[{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
|
|
2630
|
-
Replay: ductape_execute("
|
|
2655
|
+
Replay: ductape_execute("events.replayEvent",
|
|
2631
2656
|
[{ product, env, eventId, force? }])
|
|
2632
2657
|
|
|
2633
2658
|
━━━ OBSERVABILITY ━━━
|
|
2634
2659
|
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2660
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
|
|
2661
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
2662
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
2663
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
2664
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2665
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2666
|
+
events.replayEvent [{ product, env, eventId, force? }]
|
|
2667
|
+
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
2668
|
+
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
2644
2669
|
`.trim(),
|
|
2645
2670
|
logs: `
|
|
2646
2671
|
DUCTAPE LOGS
|
|
@@ -3268,6 +3293,7 @@ async function loadMcpSdk() {
|
|
|
3268
3293
|
console.error('Failed to load MCP SDK. Install: npm install @modelcontextprotocol/sdk zod\n' +
|
|
3269
3294
|
'Or v2 alpha: npm install @modelcontextprotocol/server zod @cfworker/json-schema');
|
|
3270
3295
|
process.exit(1);
|
|
3296
|
+
throw new Error('MCP SDK not available');
|
|
3271
3297
|
}
|
|
3272
3298
|
function handleCliFlags() {
|
|
3273
3299
|
const arg = process.argv[2];
|
|
@@ -3368,7 +3394,9 @@ async function main() {
|
|
|
3368
3394
|
if (!key) {
|
|
3369
3395
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3370
3396
|
}
|
|
3371
|
-
|
|
3397
|
+
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3398
|
+
const proxyModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
3399
|
+
const result = await executeViaProxy(key, proxyModule, args.method, args.params);
|
|
3372
3400
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3373
3401
|
}
|
|
3374
3402
|
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
|
|
|
@@ -191,29 +191,33 @@ global interceptors, decorators, and type-safe resource handles.
|
|
|
191
191
|
|
|
192
192
|
SETUP — register once in AppModule:
|
|
193
193
|
|
|
194
|
+
╔══════════════════════════════════════════════════════════════════════════╗
|
|
195
|
+
║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
|
|
196
|
+
║ ║
|
|
197
|
+
║ Every dispatch() call (actions, features, events, databases, storage, ║
|
|
198
|
+
║ graph, notifications, quotas, fallback) enqueues jobs ║
|
|
199
|
+
║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
|
|
200
|
+
║ "Queues not configured. dispatch() requires a queue connection." ║
|
|
201
|
+
║ ║
|
|
202
|
+
║ *.run(), events.produce(), and @Events.Consumer do NOT need Redis. ║
|
|
203
|
+
║ Only dispatch() does — and it is non-negotiable. ║
|
|
204
|
+
╚══════════════════════════════════════════════════════════════════════════╝
|
|
205
|
+
|
|
194
206
|
import { DuctapeModule } from '@ductape/nestjs';
|
|
195
207
|
|
|
196
208
|
@Module({
|
|
197
209
|
imports: [
|
|
198
210
|
DuctapeModule.forIntegration({
|
|
199
211
|
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
200
|
-
product: 'my-product',
|
|
212
|
+
product: 'my-product',
|
|
201
213
|
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
202
|
-
redisUrl: process.env.
|
|
214
|
+
redisUrl: process.env.DUCTAPE_REDIS_URL, // required — no dispatch() works without this
|
|
203
215
|
}),
|
|
204
216
|
],
|
|
205
217
|
})
|
|
206
218
|
export class AppModule {}
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
ALL *.dispatch() calls (actions.dispatch, features.dispatch, events.dispatch, databases.dispatch,
|
|
210
|
-
storage.dispatch, graph.dispatch, notifications.dispatch, messageBrokers.dispatch, quotas.dispatch,
|
|
211
|
-
fallback.dispatch, etc.) enqueue jobs via BullMQ, which requires a Redis connection.
|
|
212
|
-
Without redisUrl ANY dispatch() call throws "Queues not configured. dispatch() requires a queue connection."
|
|
213
|
-
Set REDIS_URL (e.g. redis://localhost:6379 or a managed Redis connection string) and pass it as redisUrl.
|
|
214
|
-
*.run(), events.produce(), and @Events.Consumer do NOT require Redis — only dispatch() does.
|
|
215
|
-
|
|
216
|
-
// Async (e.g. pulling key from ConfigService):
|
|
220
|
+
// Async (e.g. pulling from ConfigService):
|
|
217
221
|
DuctapeModule.forRootAsync({
|
|
218
222
|
imports: [ConfigModule],
|
|
219
223
|
inject: [ConfigService],
|
|
@@ -221,10 +225,14 @@ SETUP — register once in AppModule:
|
|
|
221
225
|
accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
|
|
222
226
|
product: cfg.get('DUCTAPE_PRODUCT'),
|
|
223
227
|
env: cfg.get('DUCTAPE_ENV'),
|
|
224
|
-
redisUrl: cfg.get('
|
|
228
|
+
redisUrl: cfg.get('DUCTAPE_REDIS_URL'), // required — no dispatch() works without this
|
|
225
229
|
}),
|
|
226
230
|
})
|
|
227
231
|
|
|
232
|
+
Environment variable (add to .env and deployment secrets):
|
|
233
|
+
DUCTAPE_REDIS_URL=redis://localhost:6379 # local dev
|
|
234
|
+
DUCTAPE_REDIS_URL=rediss://:<password>@host:6380 # managed Redis (TLS)
|
|
235
|
+
|
|
228
236
|
INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
|
|
229
237
|
|
|
230
238
|
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
|
|
@@ -279,7 +287,7 @@ FOR MESSAGING (events.produce / events.consume / events.dispatch):
|
|
|
279
287
|
@Events.Produce({ event: 'broker-tag:topic-tag' })
|
|
280
288
|
emitOrderCreated(payload: { orderId: string }) { return payload; }
|
|
281
289
|
|
|
282
|
-
Dispatch (scheduled or immediate) —
|
|
290
|
+
Dispatch (scheduled or immediate) — dispatch() CANNOT be used without redisUrl in forIntegration:
|
|
283
291
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
|
|
284
292
|
dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
|
|
285
293
|
|
|
@@ -354,7 +362,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
354
362
|
actions.fetch [app_tag, action_tag]
|
|
355
363
|
actions.list [app_tag]
|
|
356
364
|
actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
|
|
357
|
-
actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch")
|
|
365
|
+
actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch") — requires redisUrl in ductape initialization
|
|
358
366
|
|
|
359
367
|
━━━ MODULE: auths ━━━
|
|
360
368
|
auths.create [app_tag, data: { tag: string, name: string, setup_type: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry: number, period: "seconds"|"minutes"|"hours"|"days", description: string, action_tag?: string }]
|
|
@@ -432,7 +440,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
432
440
|
quotas.list [product_tag]
|
|
433
441
|
quotas.delete [product_tag, quota_tag]
|
|
434
442
|
quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="run", targets={tag})
|
|
435
|
-
quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch")
|
|
443
|
+
quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch") — requires redisUrl in ductape initialization
|
|
436
444
|
|
|
437
445
|
━━━ MODULE: fallback ━━━
|
|
438
446
|
fallback.create [product_tag, data: {
|
|
@@ -475,7 +483,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
475
483
|
fallback.list [product_tag]
|
|
476
484
|
fallback.delete [product_tag, fallback_tag]
|
|
477
485
|
fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="run", targets={tag})
|
|
478
|
-
fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch")
|
|
486
|
+
fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch") — requires redisUrl in ductape initialization
|
|
479
487
|
|
|
480
488
|
━━━ MODULE: health ━━━
|
|
481
489
|
health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
|
|
@@ -502,36 +510,36 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
502
510
|
notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
|
|
503
511
|
notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
|
|
504
512
|
notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
|
|
505
|
-
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")
|
|
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
|
|
506
514
|
notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
|
|
507
515
|
|
|
508
|
-
━━━ MODULE: messageBrokers ━━━
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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:
|
|
515
523
|
ductape_cli("events topics create -f topic.json")
|
|
516
524
|
topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
517
525
|
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
518
526
|
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
519
527
|
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
520
|
-
|
|
528
|
+
events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
521
529
|
ductape_cli("events topics update --tag broker:topic -f patch.json")
|
|
522
|
-
|
|
530
|
+
events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
|
|
523
531
|
ductape_cli("events topics delete --tag broker:topic")
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — 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 }]
|
|
535
543
|
|
|
536
544
|
━━━ MODULE: storage ━━━
|
|
537
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 } }] }]
|
|
@@ -551,7 +559,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
551
559
|
storage.files.delete [{ product, env, storage, fileName }]
|
|
552
560
|
storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
|
|
553
561
|
storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
|
|
554
|
-
storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage})
|
|
562
|
+
storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage}) — requires redisUrl in ductape initialization
|
|
555
563
|
|
|
556
564
|
━━━ MODULE: databases ━━━
|
|
557
565
|
databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite",
|
|
@@ -619,8 +627,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
619
627
|
databases.action.fetch [action_tag]
|
|
620
628
|
databases.action.list [database_tag]
|
|
621
629
|
databases.action.delete [action_tag]
|
|
622
|
-
databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
|
|
623
|
-
databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
|
|
630
|
+
databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
|
|
631
|
+
databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
|
|
624
632
|
databases.beginTransaction [{ product, env, database, isolationLevel?: "READ_COMMITTED"|"REPEATABLE_READ"|"SERIALIZABLE" }]
|
|
625
633
|
→ returns a transaction object; pass it to insert/update/delete/upsert/query calls as the last argument.
|
|
626
634
|
Commit with: transaction.commit() Rollback with: transaction.rollback()
|
|
@@ -682,7 +690,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
682
690
|
graph.beginTransaction [options?]
|
|
683
691
|
graph.commitTransaction [transaction]
|
|
684
692
|
graph.rollbackTransaction [transaction]
|
|
685
|
-
graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph})
|
|
693
|
+
graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph}) — requires redisUrl in ductape initialization
|
|
686
694
|
|
|
687
695
|
━━━ MODULE: vector ━━━
|
|
688
696
|
vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
|
|
@@ -802,7 +810,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
802
810
|
timeout?: number
|
|
803
811
|
}]
|
|
804
812
|
|
|
805
|
-
features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature})
|
|
813
|
+
features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature}) — requires redisUrl in ductape initialization
|
|
806
814
|
product: string,
|
|
807
815
|
env: string,
|
|
808
816
|
feature: string,
|
|
@@ -1054,10 +1062,10 @@ function resolveSdkCallPath(operationFamily: string, method: string): string {
|
|
|
1054
1062
|
if (family === 'vector') return `vector.${m}`;
|
|
1055
1063
|
if (family === 'storage') return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
|
|
1056
1064
|
if (family === 'notification') return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
|
|
1057
|
-
if (family === 'messaging' || family === 'broker') {
|
|
1058
|
-
if (m === 'dispatch') return '
|
|
1059
|
-
if (m === 'send' || m === 'publish' || m === 'produce') return '
|
|
1060
|
-
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}`;
|
|
1061
1069
|
}
|
|
1062
1070
|
if (family === 'quota') return `quotas.${m}`;
|
|
1063
1071
|
if (family === 'fallback') return `fallback.${m}`;
|
|
@@ -2532,7 +2540,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2532
2540
|
Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
|
|
2533
2541
|
auto-registration for consume paths. Always create topics explicitly.
|
|
2534
2542
|
|
|
2535
|
-
IMPORTANT:
|
|
2543
|
+
IMPORTANT: events.topics.create requires an access key (admin operation).
|
|
2536
2544
|
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
2537
2545
|
|
|
2538
2546
|
Write a topic.json file, then:
|
|
@@ -2559,7 +2567,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2559
2567
|
ductape_cli("events topics delete --tag order-events:order-created")
|
|
2560
2568
|
|
|
2561
2569
|
Read-only fetches (safe with publishable key via ductape_execute):
|
|
2562
|
-
ductape_execute("
|
|
2570
|
+
ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
|
|
2563
2571
|
|
|
2564
2572
|
━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
|
|
2565
2573
|
|
|
@@ -2570,7 +2578,12 @@ Import (register an EXISTING cloud resource):
|
|
|
2570
2578
|
|
|
2571
2579
|
GENERAL BACKEND (TypeScript/Node.js — not NestJS):
|
|
2572
2580
|
import Ductape from '@ductape/sdk';
|
|
2573
|
-
|
|
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
|
+
});
|
|
2574
2587
|
await ductape.events.produce({
|
|
2575
2588
|
product: "my-product",
|
|
2576
2589
|
env: "prd",
|
|
@@ -2585,14 +2598,26 @@ Import (register an EXISTING cloud resource):
|
|
|
2585
2598
|
idempotencyTtl?: 86400, // seconds; default 86400 (24h)
|
|
2586
2599
|
});
|
|
2587
2600
|
|
|
2588
|
-
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
|
+
|
|
2589
2614
|
import { Events } from '@ductape/nestjs';
|
|
2590
2615
|
@Injectable() export class OrdersService {
|
|
2591
2616
|
// Immediate produce — method returns the message payload:
|
|
2592
2617
|
@Events.Produce({ event: 'order-events:order-created' })
|
|
2593
2618
|
emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
|
|
2594
2619
|
|
|
2595
|
-
// Dispatch with static schedule
|
|
2620
|
+
// Dispatch with static schedule — requires redisUrl in DuctapeModule initialization:
|
|
2596
2621
|
@Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
|
|
2597
2622
|
schedule: { every: 86400000 } })
|
|
2598
2623
|
scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
|
|
@@ -2625,7 +2650,7 @@ Import (register an EXISTING cloud resource):
|
|
|
2625
2650
|
});
|
|
2626
2651
|
|
|
2627
2652
|
SCHEDULED DISPATCH (background job):
|
|
2628
|
-
ductape_execute("
|
|
2653
|
+
ductape_execute("events.dispatch", [{
|
|
2629
2654
|
product, env,
|
|
2630
2655
|
broker: "order-events", // broker tag
|
|
2631
2656
|
event: "order-events:reminder-due", // "broker:topic"
|
|
@@ -2696,24 +2721,24 @@ Import (register an EXISTING cloud resource):
|
|
|
2696
2721
|
|
|
2697
2722
|
DEAD-LETTER QUEUE (DLQ):
|
|
2698
2723
|
Messages whose callbacks consistently throw are automatically moved to the DLQ.
|
|
2699
|
-
Query: ductape_execute("
|
|
2724
|
+
Query: ductape_execute("events.messages.getDeadLetters",
|
|
2700
2725
|
[{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
|
|
2701
|
-
Reprocess: ductape_execute("
|
|
2726
|
+
Reprocess: ductape_execute("events.reprocessDLQ",
|
|
2702
2727
|
[{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
|
|
2703
|
-
Replay: ductape_execute("
|
|
2728
|
+
Replay: ductape_execute("events.replayEvent",
|
|
2704
2729
|
[{ product, env, eventId, force? }])
|
|
2705
2730
|
|
|
2706
2731
|
━━━ OBSERVABILITY ━━━
|
|
2707
2732
|
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2733
|
+
events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
|
|
2734
|
+
events.messages.getStats [{ product, env, brokerTag }]
|
|
2735
|
+
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
2736
|
+
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
2737
|
+
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2738
|
+
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
2739
|
+
events.replayEvent [{ product, env, eventId, force? }]
|
|
2740
|
+
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
2741
|
+
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
2717
2742
|
`.trim(),
|
|
2718
2743
|
|
|
2719
2744
|
logs: `
|
|
@@ -3355,6 +3380,7 @@ async function loadMcpSdk(): Promise<{
|
|
|
3355
3380
|
'Or v2 alpha: npm install @modelcontextprotocol/server zod @cfworker/json-schema',
|
|
3356
3381
|
);
|
|
3357
3382
|
process.exit(1);
|
|
3383
|
+
throw new Error('MCP SDK not available');
|
|
3358
3384
|
}
|
|
3359
3385
|
|
|
3360
3386
|
function handleCliFlags(): boolean {
|
|
@@ -3466,7 +3492,9 @@ async function main() {
|
|
|
3466
3492
|
if (!key) {
|
|
3467
3493
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3468
3494
|
}
|
|
3469
|
-
|
|
3495
|
+
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3496
|
+
const proxyModule: SDKModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
3497
|
+
const result = await executeViaProxy(key, proxyModule, args.method, args.params);
|
|
3470
3498
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3471
3499
|
} catch (err) {
|
|
3472
3500
|
const message = err instanceof Error ? err.message : String(err);
|
package/src/proxy-client.ts
CHANGED