@ductape/mcp 0.1.23 → 0.1.24
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 +162 -0
- package/package.json +1 -1
- package/src/index.ts +162 -0
package/dist/index.js
CHANGED
|
@@ -54,6 +54,67 @@ There are THREE categories of operations. Use the right tool for each:
|
|
|
54
54
|
ductape_cli("db migrate rollback") # roll back last migration
|
|
55
55
|
ductape_cli("db migrate rollback -n 3") # roll back last 3 migrations
|
|
56
56
|
|
|
57
|
+
DATABASE SCHEMA FILE — ductape/database/schema.json
|
|
58
|
+
─────────────────────────────────────────────────────────────────────────
|
|
59
|
+
IMPORTANT: "db schema generate" READS schema.json and emits migration files.
|
|
60
|
+
It does NOT create schema.json from scratch. You must write this file first.
|
|
61
|
+
|
|
62
|
+
If schema.json does not exist, "db schema generate" now auto-creates it and
|
|
63
|
+
attempts to hydrate it from the cloud (pass --db <tag> to enable cloud hydration):
|
|
64
|
+
ductape_cli("db schema generate --db statecraft-core")
|
|
65
|
+
If the cloud database is empty, schema.json will be created as [] and you must
|
|
66
|
+
populate it before running the command again.
|
|
67
|
+
|
|
68
|
+
FILE FORMAT — ductape/database/schema.json is a JSON array:
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
"db": "<database-component-tag>",
|
|
72
|
+
"tables": {
|
|
73
|
+
"<collection_name>": {
|
|
74
|
+
"<field_name>": {
|
|
75
|
+
"type": "string|number|boolean|date|datetime|timestamp|uuid|object|array|json|text|decimal|bigint|smallint|double|binary|blob|time",
|
|
76
|
+
"required": true, // false by default (nullable)
|
|
77
|
+
"unique": true, // unique constraint on this field
|
|
78
|
+
"index": true, // create an index on this field (use with unique for unique index)
|
|
79
|
+
"default": "value", // default value ("now"/"NOW" → CURRENT_TIMESTAMP)
|
|
80
|
+
"maxlength": 255, // max length for string fields
|
|
81
|
+
"enum": ["a", "b", "c"], // restrict to enum values (sets type to string automatically)
|
|
82
|
+
"primaryKey": true, // mark as primary key
|
|
83
|
+
"autoGenerate": true, // auto-generate value on insert
|
|
84
|
+
"float": true // treat number as float (use with type: "number")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
SINGLE-FIELD INDEX: set index: true (+ unique: true for unique constraint)
|
|
92
|
+
"matchId": { "type": "string", "required": true, "unique": true, "index": true }
|
|
93
|
+
|
|
94
|
+
COMPOSITE INDEX: schema.json does not support composite indexes.
|
|
95
|
+
After running db schema generate, add a manual migration file in
|
|
96
|
+
ductape/migrations/<db-tag>/ with type "createIndex" and a fields array:
|
|
97
|
+
{
|
|
98
|
+
"tag": "create_map_events_match_seq_idx",
|
|
99
|
+
"name": "Composite unique index on matchId+sequence",
|
|
100
|
+
"up": [{
|
|
101
|
+
"type": "createIndex",
|
|
102
|
+
"collection": "map_events",
|
|
103
|
+
"name": "map_events_matchId_sequence_idx",
|
|
104
|
+
"fields": [{ "name": "matchId" }, { "name": "sequence" }],
|
|
105
|
+
"unique": true,
|
|
106
|
+
"ifNotExists": true
|
|
107
|
+
}],
|
|
108
|
+
"down": [{ "type": "dropIndex", "collection": "map_events", "name": "map_events_matchId_sequence_idx", "ifExists": true }],
|
|
109
|
+
"createdAt": "<ISO timestamp>"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
WORKFLOW:
|
|
113
|
+
1. Create/edit ductape/database/schema.json with your collection definitions
|
|
114
|
+
2. ductape_cli("db schema generate --db <tag>") → writes migration files
|
|
115
|
+
3. ductape_cli("db migrate --db <tag>") → applies pending migrations
|
|
116
|
+
4. Add manual migration files for composite/partial indexes not expressible in schema.json
|
|
117
|
+
|
|
57
118
|
1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
|
|
58
119
|
ALL creation and update operations require an access key and CANNOT go through ductape_execute.
|
|
59
120
|
→ Use ductape_cli for every create/update operation. Examples:
|
|
@@ -111,6 +172,107 @@ There are THREE categories of operations. Use the right tool for each:
|
|
|
111
172
|
|
|
112
173
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
113
174
|
|
|
175
|
+
━━━ NESTJS INTEGRATION — use @ductape/nestjs ━━━
|
|
176
|
+
|
|
177
|
+
When the target application is a NestJS service or controller, always use @ductape/nestjs
|
|
178
|
+
instead of instantiating @ductape/sdk directly. It provides NestJS DI integration,
|
|
179
|
+
global interceptors, decorators, and type-safe resource handles.
|
|
180
|
+
|
|
181
|
+
SETUP — register once in AppModule:
|
|
182
|
+
|
|
183
|
+
import { DuctapeModule } from '@ductape/nestjs';
|
|
184
|
+
|
|
185
|
+
@Module({
|
|
186
|
+
imports: [
|
|
187
|
+
DuctapeModule.forIntegration({
|
|
188
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
189
|
+
product: 'my-product', // optional default — overridable per controller
|
|
190
|
+
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
})
|
|
194
|
+
export class AppModule {}
|
|
195
|
+
|
|
196
|
+
// Async (e.g. pulling key from ConfigService):
|
|
197
|
+
DuctapeModule.forRootAsync({
|
|
198
|
+
imports: [ConfigModule],
|
|
199
|
+
inject: [ConfigService],
|
|
200
|
+
useFactory: (cfg: ConfigService) => ({
|
|
201
|
+
accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
|
|
202
|
+
product: cfg.get('DUCTAPE_PRODUCT'),
|
|
203
|
+
env: cfg.get('DUCTAPE_ENV'),
|
|
204
|
+
}),
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
|
|
208
|
+
|
|
209
|
+
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
|
|
210
|
+
|
|
211
|
+
@Injectable()
|
|
212
|
+
export class MatchService {
|
|
213
|
+
constructor(@InjectContext() private readonly ductape: DuctapeContext) {}
|
|
214
|
+
|
|
215
|
+
async publishMatchState(matchId: string, state: object) {
|
|
216
|
+
await this.ductape.sdk.events.produce({
|
|
217
|
+
product: 'my-product',
|
|
218
|
+
env: 'prd',
|
|
219
|
+
event: 'statecraft-events:match-state',
|
|
220
|
+
message: { matchId, ...state },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async queryDatabase(env: string) {
|
|
225
|
+
const db = await this.ductape.database('core-db', { env });
|
|
226
|
+
return db.query({ table: 'matches', where: { active: true } });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
AVAILABLE ON DuctapeContext:
|
|
231
|
+
|
|
232
|
+
ctx.sdk — full @ductape/sdk instance (events, sessions, etc.)
|
|
233
|
+
ctx.database(tag, overrides?) — connected DatabaseHandle (query, insert, update, delete)
|
|
234
|
+
ctx.storage(tag, overrides?) — StorageHandle (upload, download, remove)
|
|
235
|
+
ctx.cache(tag, overrides?) — CacheHandle (get, set, delete)
|
|
236
|
+
ctx.graph(tag, overrides?) — GraphHandle
|
|
237
|
+
ctx.vector(tag, overrides?) — VectorHandle
|
|
238
|
+
ctx.agent(tag, overrides?) — AgentHandle
|
|
239
|
+
ctx.warehouse(overrides?) — WarehouseHandle
|
|
240
|
+
ctx.cloudTiers(query?) — query available cloud resource tiers and pricing
|
|
241
|
+
ctx.runJob({ product, env, event, input? }) — trigger a product job
|
|
242
|
+
|
|
243
|
+
CONTROLLER DECORATORS:
|
|
244
|
+
|
|
245
|
+
@Product('my-product') — override default product at class or method level
|
|
246
|
+
@Env('prd') — override default env
|
|
247
|
+
@ApiRun({ app, action }) — bind a controller method to an app action (interceptor runs it automatically)
|
|
248
|
+
@ApiConfig({ app, ... }) — shared app credentials at class level
|
|
249
|
+
@Webhook.Register(...) — register a webhook consumer URL
|
|
250
|
+
@Webhook.Consumer(...) — mark inbound handler for forwarded webhook payloads
|
|
251
|
+
|
|
252
|
+
FOR MESSAGING (events.produce / events.consume):
|
|
253
|
+
|
|
254
|
+
Access via ctx.sdk.events — no dedicated NestJS handle, the SDK instance is sufficient:
|
|
255
|
+
|
|
256
|
+
await this.ductape.sdk.events.produce({
|
|
257
|
+
product: 'my-product', env: 'prd',
|
|
258
|
+
event: 'broker-tag:topic-tag',
|
|
259
|
+
message: { ... },
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await this.ductape.sdk.events.consume({
|
|
263
|
+
product: 'my-product', env: 'prd',
|
|
264
|
+
event: 'broker-tag:topic-tag',
|
|
265
|
+
callback: async (message) => { /* handle */ },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
SPECIALIZED MODULES (for injecting handles directly without @InjectContext):
|
|
269
|
+
|
|
270
|
+
DuctapeDatabaseModule.forTags(['core-db']) → inject with @Database('core-db')
|
|
271
|
+
DuctapeStorageModule.forTags(['assets']) → inject with @Storage('assets')
|
|
272
|
+
DuctapeCacheModule, DuctapeGraphModule, DuctapeVectorModule — same pattern
|
|
273
|
+
|
|
274
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
275
|
+
|
|
114
276
|
ALL params are passed as a JSON array in positional order matching the SDK signature.
|
|
115
277
|
|
|
116
278
|
━━━ MODULE: product ━━━
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -65,6 +65,67 @@ There are THREE categories of operations. Use the right tool for each:
|
|
|
65
65
|
ductape_cli("db migrate rollback") # roll back last migration
|
|
66
66
|
ductape_cli("db migrate rollback -n 3") # roll back last 3 migrations
|
|
67
67
|
|
|
68
|
+
DATABASE SCHEMA FILE — ductape/database/schema.json
|
|
69
|
+
─────────────────────────────────────────────────────────────────────────
|
|
70
|
+
IMPORTANT: "db schema generate" READS schema.json and emits migration files.
|
|
71
|
+
It does NOT create schema.json from scratch. You must write this file first.
|
|
72
|
+
|
|
73
|
+
If schema.json does not exist, "db schema generate" now auto-creates it and
|
|
74
|
+
attempts to hydrate it from the cloud (pass --db <tag> to enable cloud hydration):
|
|
75
|
+
ductape_cli("db schema generate --db statecraft-core")
|
|
76
|
+
If the cloud database is empty, schema.json will be created as [] and you must
|
|
77
|
+
populate it before running the command again.
|
|
78
|
+
|
|
79
|
+
FILE FORMAT — ductape/database/schema.json is a JSON array:
|
|
80
|
+
[
|
|
81
|
+
{
|
|
82
|
+
"db": "<database-component-tag>",
|
|
83
|
+
"tables": {
|
|
84
|
+
"<collection_name>": {
|
|
85
|
+
"<field_name>": {
|
|
86
|
+
"type": "string|number|boolean|date|datetime|timestamp|uuid|object|array|json|text|decimal|bigint|smallint|double|binary|blob|time",
|
|
87
|
+
"required": true, // false by default (nullable)
|
|
88
|
+
"unique": true, // unique constraint on this field
|
|
89
|
+
"index": true, // create an index on this field (use with unique for unique index)
|
|
90
|
+
"default": "value", // default value ("now"/"NOW" → CURRENT_TIMESTAMP)
|
|
91
|
+
"maxlength": 255, // max length for string fields
|
|
92
|
+
"enum": ["a", "b", "c"], // restrict to enum values (sets type to string automatically)
|
|
93
|
+
"primaryKey": true, // mark as primary key
|
|
94
|
+
"autoGenerate": true, // auto-generate value on insert
|
|
95
|
+
"float": true // treat number as float (use with type: "number")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
SINGLE-FIELD INDEX: set index: true (+ unique: true for unique constraint)
|
|
103
|
+
"matchId": { "type": "string", "required": true, "unique": true, "index": true }
|
|
104
|
+
|
|
105
|
+
COMPOSITE INDEX: schema.json does not support composite indexes.
|
|
106
|
+
After running db schema generate, add a manual migration file in
|
|
107
|
+
ductape/migrations/<db-tag>/ with type "createIndex" and a fields array:
|
|
108
|
+
{
|
|
109
|
+
"tag": "create_map_events_match_seq_idx",
|
|
110
|
+
"name": "Composite unique index on matchId+sequence",
|
|
111
|
+
"up": [{
|
|
112
|
+
"type": "createIndex",
|
|
113
|
+
"collection": "map_events",
|
|
114
|
+
"name": "map_events_matchId_sequence_idx",
|
|
115
|
+
"fields": [{ "name": "matchId" }, { "name": "sequence" }],
|
|
116
|
+
"unique": true,
|
|
117
|
+
"ifNotExists": true
|
|
118
|
+
}],
|
|
119
|
+
"down": [{ "type": "dropIndex", "collection": "map_events", "name": "map_events_matchId_sequence_idx", "ifExists": true }],
|
|
120
|
+
"createdAt": "<ISO timestamp>"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
WORKFLOW:
|
|
124
|
+
1. Create/edit ductape/database/schema.json with your collection definitions
|
|
125
|
+
2. ductape_cli("db schema generate --db <tag>") → writes migration files
|
|
126
|
+
3. ductape_cli("db migrate --db <tag>") → applies pending migrations
|
|
127
|
+
4. Add manual migration files for composite/partial indexes not expressible in schema.json
|
|
128
|
+
|
|
68
129
|
1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
|
|
69
130
|
ALL creation and update operations require an access key and CANNOT go through ductape_execute.
|
|
70
131
|
→ Use ductape_cli for every create/update operation. Examples:
|
|
@@ -122,6 +183,107 @@ There are THREE categories of operations. Use the right tool for each:
|
|
|
122
183
|
|
|
123
184
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
124
185
|
|
|
186
|
+
━━━ NESTJS INTEGRATION — use @ductape/nestjs ━━━
|
|
187
|
+
|
|
188
|
+
When the target application is a NestJS service or controller, always use @ductape/nestjs
|
|
189
|
+
instead of instantiating @ductape/sdk directly. It provides NestJS DI integration,
|
|
190
|
+
global interceptors, decorators, and type-safe resource handles.
|
|
191
|
+
|
|
192
|
+
SETUP — register once in AppModule:
|
|
193
|
+
|
|
194
|
+
import { DuctapeModule } from '@ductape/nestjs';
|
|
195
|
+
|
|
196
|
+
@Module({
|
|
197
|
+
imports: [
|
|
198
|
+
DuctapeModule.forIntegration({
|
|
199
|
+
accessKey: process.env.DUCTAPE_ACCESS_KEY,
|
|
200
|
+
product: 'my-product', // optional default — overridable per controller
|
|
201
|
+
env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
|
|
202
|
+
}),
|
|
203
|
+
],
|
|
204
|
+
})
|
|
205
|
+
export class AppModule {}
|
|
206
|
+
|
|
207
|
+
// Async (e.g. pulling key from ConfigService):
|
|
208
|
+
DuctapeModule.forRootAsync({
|
|
209
|
+
imports: [ConfigModule],
|
|
210
|
+
inject: [ConfigService],
|
|
211
|
+
useFactory: (cfg: ConfigService) => ({
|
|
212
|
+
accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
|
|
213
|
+
product: cfg.get('DUCTAPE_PRODUCT'),
|
|
214
|
+
env: cfg.get('DUCTAPE_ENV'),
|
|
215
|
+
}),
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
|
|
219
|
+
|
|
220
|
+
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
|
|
221
|
+
|
|
222
|
+
@Injectable()
|
|
223
|
+
export class MatchService {
|
|
224
|
+
constructor(@InjectContext() private readonly ductape: DuctapeContext) {}
|
|
225
|
+
|
|
226
|
+
async publishMatchState(matchId: string, state: object) {
|
|
227
|
+
await this.ductape.sdk.events.produce({
|
|
228
|
+
product: 'my-product',
|
|
229
|
+
env: 'prd',
|
|
230
|
+
event: 'statecraft-events:match-state',
|
|
231
|
+
message: { matchId, ...state },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async queryDatabase(env: string) {
|
|
236
|
+
const db = await this.ductape.database('core-db', { env });
|
|
237
|
+
return db.query({ table: 'matches', where: { active: true } });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
AVAILABLE ON DuctapeContext:
|
|
242
|
+
|
|
243
|
+
ctx.sdk — full @ductape/sdk instance (events, sessions, etc.)
|
|
244
|
+
ctx.database(tag, overrides?) — connected DatabaseHandle (query, insert, update, delete)
|
|
245
|
+
ctx.storage(tag, overrides?) — StorageHandle (upload, download, remove)
|
|
246
|
+
ctx.cache(tag, overrides?) — CacheHandle (get, set, delete)
|
|
247
|
+
ctx.graph(tag, overrides?) — GraphHandle
|
|
248
|
+
ctx.vector(tag, overrides?) — VectorHandle
|
|
249
|
+
ctx.agent(tag, overrides?) — AgentHandle
|
|
250
|
+
ctx.warehouse(overrides?) — WarehouseHandle
|
|
251
|
+
ctx.cloudTiers(query?) — query available cloud resource tiers and pricing
|
|
252
|
+
ctx.runJob({ product, env, event, input? }) — trigger a product job
|
|
253
|
+
|
|
254
|
+
CONTROLLER DECORATORS:
|
|
255
|
+
|
|
256
|
+
@Product('my-product') — override default product at class or method level
|
|
257
|
+
@Env('prd') — override default env
|
|
258
|
+
@ApiRun({ app, action }) — bind a controller method to an app action (interceptor runs it automatically)
|
|
259
|
+
@ApiConfig({ app, ... }) — shared app credentials at class level
|
|
260
|
+
@Webhook.Register(...) — register a webhook consumer URL
|
|
261
|
+
@Webhook.Consumer(...) — mark inbound handler for forwarded webhook payloads
|
|
262
|
+
|
|
263
|
+
FOR MESSAGING (events.produce / events.consume):
|
|
264
|
+
|
|
265
|
+
Access via ctx.sdk.events — no dedicated NestJS handle, the SDK instance is sufficient:
|
|
266
|
+
|
|
267
|
+
await this.ductape.sdk.events.produce({
|
|
268
|
+
product: 'my-product', env: 'prd',
|
|
269
|
+
event: 'broker-tag:topic-tag',
|
|
270
|
+
message: { ... },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await this.ductape.sdk.events.consume({
|
|
274
|
+
product: 'my-product', env: 'prd',
|
|
275
|
+
event: 'broker-tag:topic-tag',
|
|
276
|
+
callback: async (message) => { /* handle */ },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
SPECIALIZED MODULES (for injecting handles directly without @InjectContext):
|
|
280
|
+
|
|
281
|
+
DuctapeDatabaseModule.forTags(['core-db']) → inject with @Database('core-db')
|
|
282
|
+
DuctapeStorageModule.forTags(['assets']) → inject with @Storage('assets')
|
|
283
|
+
DuctapeCacheModule, DuctapeGraphModule, DuctapeVectorModule — same pattern
|
|
284
|
+
|
|
285
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
286
|
+
|
|
125
287
|
ALL params are passed as a JSON array in positional order matching the SDK signature.
|
|
126
288
|
|
|
127
289
|
━━━ MODULE: product ━━━
|