@ductape/mcp 0.1.13 → 0.1.14
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 +1121 -0
- package/package.json +1 -1
- package/src/index.ts +1153 -0
package/dist/index.js
CHANGED
|
@@ -372,6 +372,15 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
372
372
|
databases.action.delete [action_tag]
|
|
373
373
|
databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database, action})
|
|
374
374
|
databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch")
|
|
375
|
+
databases.beginTransaction [{ product, env, database, isolationLevel?: "READ_COMMITTED"|"REPEATABLE_READ"|"SERIALIZABLE" }]
|
|
376
|
+
→ returns a transaction object; pass it to insert/update/delete/upsert/query calls as the last argument.
|
|
377
|
+
Commit with: transaction.commit() Rollback with: transaction.rollback()
|
|
378
|
+
BEFORE using transactions: ask the user which database type and tier they are running.
|
|
379
|
+
Transaction support varies by database — call ductape_docs({ topic: "transactions" }) for the full matrix.
|
|
380
|
+
databases.transaction [{ product, env, database, isolationLevel? }, async (tx) => { … }]
|
|
381
|
+
→ managed transaction: commits automatically on success, rolls back on error.
|
|
382
|
+
Prefer this over beginTransaction when the callback is self-contained.
|
|
383
|
+
See ductape_docs({ topic: "transactions" }) for database/tier compatibility before proceeding.
|
|
375
384
|
|
|
376
385
|
━━━ MODULE: graph ━━━
|
|
377
386
|
graph.create [{ product, tag, name, description?, type: "neo4j"|"nebula"|"arangodb", envs: [{slug, connection_url, username?, password?}] }]
|
|
@@ -914,6 +923,1080 @@ function runCli(command) {
|
|
|
914
923
|
return { success: false, output: msg };
|
|
915
924
|
}
|
|
916
925
|
}
|
|
926
|
+
const docsInputSchema = z.object({
|
|
927
|
+
topic: z.string().describe('Feature topic to look up. Supported: ' +
|
|
928
|
+
'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
929
|
+
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
930
|
+
'notifications, resilience, features, events, logs'),
|
|
931
|
+
});
|
|
932
|
+
const DOCS = {
|
|
933
|
+
transactions: `
|
|
934
|
+
DUCTAPE DATABASE TRANSACTIONS
|
|
935
|
+
|
|
936
|
+
Ask the user which database type and tier they are using before advising on transaction support.
|
|
937
|
+
|
|
938
|
+
Support matrix:
|
|
939
|
+
PostgreSQL — full ACID; all isolation levels (READ_UNCOMMITTED, READ_COMMITTED,
|
|
940
|
+
REPEATABLE_READ, SERIALIZABLE). Always supported.
|
|
941
|
+
MySQL — full ACID; same isolation levels as PostgreSQL.
|
|
942
|
+
MongoDB — multi-document transactions require a replica set.
|
|
943
|
+
Atlas M10+ (dedicated): supported.
|
|
944
|
+
Atlas M0/M2/M5 (free/shared): NOT supported — all writes are
|
|
945
|
+
single-document atomic only. Confirm tier with user before proceeding.
|
|
946
|
+
Self-hosted: supported if replica set mode is enabled.
|
|
947
|
+
Cassandra — does NOT support ACID transactions. Lightweight transactions (LWT)
|
|
948
|
+
only for single-partition compare-and-set.
|
|
949
|
+
DynamoDB — transactional API supported (TransactWrite) but NOT via Ductape's
|
|
950
|
+
transaction abstraction; use single operations with condition expressions.
|
|
951
|
+
|
|
952
|
+
Usage patterns:
|
|
953
|
+
// Managed (preferred) — auto-commit on success, auto-rollback on error:
|
|
954
|
+
await ductape.database.transaction(
|
|
955
|
+
{ product, env, database, isolationLevel: 'READ_COMMITTED' },
|
|
956
|
+
async (tx) => {
|
|
957
|
+
await ductape.database.insert({ ..., transaction: tx });
|
|
958
|
+
await ductape.database.update({ ..., transaction: tx });
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
// Manual — commit/rollback yourself:
|
|
963
|
+
const tx = await ductape.database.beginTransaction({ product, env, database });
|
|
964
|
+
try {
|
|
965
|
+
await ductape.database.insert({ ..., transaction: tx });
|
|
966
|
+
await tx.commit();
|
|
967
|
+
} catch (e) {
|
|
968
|
+
await tx.rollback();
|
|
969
|
+
throw e;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
Via ductape_execute:
|
|
973
|
+
ductape_execute("databases.beginTransaction", [{ product, env, database, isolationLevel? }])
|
|
974
|
+
→ returns a transaction reference you can pass to subsequent insert/update/delete calls.
|
|
975
|
+
`.trim(),
|
|
976
|
+
presave: `
|
|
977
|
+
DUCTAPE PRE-SAVE HOOKS
|
|
978
|
+
|
|
979
|
+
Pre-save operations transform or validate data before it is written to the database.
|
|
980
|
+
Pass a preSave array to insert or update operations. Operations run in priority order.
|
|
981
|
+
|
|
982
|
+
Available operations (PreSaveOperationType):
|
|
983
|
+
encrypt — AES-256 encrypt field (use for PII, secrets)
|
|
984
|
+
hash — one-way hash (bcrypt/argon2 for passwords)
|
|
985
|
+
mask — mask sensitive data (e.g. **** **** **** 1234)
|
|
986
|
+
trim — strip leading/trailing whitespace
|
|
987
|
+
lowercase — convert string to lowercase
|
|
988
|
+
uppercase — convert string to uppercase
|
|
989
|
+
sanitize — strip HTML/script tags
|
|
990
|
+
validate — throw if field value fails a rule
|
|
991
|
+
transform — apply a custom mapping function
|
|
992
|
+
uuid — generate a UUID for the field
|
|
993
|
+
slug — generate a URL slug from another field
|
|
994
|
+
timestamp — set field to current timestamp (ISO 8601)
|
|
995
|
+
round — round number to N decimal places
|
|
996
|
+
clamp — clamp number to a min/max range
|
|
997
|
+
truncate — truncate string to max length
|
|
998
|
+
default — set a default value if null/undefined
|
|
999
|
+
normalizePhone — normalise phone number format
|
|
1000
|
+
normalizeEmail — lowercase + trim email
|
|
1001
|
+
parseJson — parse JSON string to object
|
|
1002
|
+
stringifyJson — serialise object to JSON string
|
|
1003
|
+
compute — derive field value from other fields (runs before validate)
|
|
1004
|
+
|
|
1005
|
+
Execution order is fixed regardless of declaration order:
|
|
1006
|
+
default → compute → validate → trim → lowercase/uppercase/normalize* →
|
|
1007
|
+
sanitize → truncate → slug → round/clamp → parseJson/stringifyJson → uuid/timestamp → transform
|
|
1008
|
+
|
|
1009
|
+
Example:
|
|
1010
|
+
await ductape.database.insert({
|
|
1011
|
+
product, env, database: 'users', entity: 'users',
|
|
1012
|
+
data: { email: ' User@Example.com ', password: 'secret', username: 'My App User' },
|
|
1013
|
+
preSave: [
|
|
1014
|
+
{ field: 'email', operation: 'normalizeEmail' },
|
|
1015
|
+
{ field: 'password', operation: 'hash' },
|
|
1016
|
+
{ field: 'username', operation: 'slug', target: 'slug' },
|
|
1017
|
+
],
|
|
1018
|
+
});
|
|
1019
|
+
`.trim(),
|
|
1020
|
+
triggers: `
|
|
1021
|
+
DUCTAPE DATABASE TRIGGERS
|
|
1022
|
+
|
|
1023
|
+
Triggers fire automatically in response to database write events.
|
|
1024
|
+
Defined via the Workbench UI or databases.trigger.create (admin SDK).
|
|
1025
|
+
|
|
1026
|
+
Events (TriggerEvent):
|
|
1027
|
+
beforeInsert / afterInsert
|
|
1028
|
+
beforeUpdate / afterUpdate
|
|
1029
|
+
beforeDelete / afterDelete
|
|
1030
|
+
beforeWrite / afterWrite (any write)
|
|
1031
|
+
|
|
1032
|
+
Timing (TriggerTiming):
|
|
1033
|
+
sync — block the write until trigger completes (adds latency)
|
|
1034
|
+
async — fire and forget (default for notifications, cache, broker)
|
|
1035
|
+
queued — enqueue for background processing
|
|
1036
|
+
|
|
1037
|
+
Action types (TriggerActionType):
|
|
1038
|
+
database.* — insert/update/delete/query another collection
|
|
1039
|
+
storage.* — upload/delete/copy a file
|
|
1040
|
+
notification.* — email/SMS/push/callback
|
|
1041
|
+
broker.publish — publish to a message broker topic
|
|
1042
|
+
cache.* — set/invalidate/delete a cache key
|
|
1043
|
+
feature.* — execute or dispatch a Ductape feature
|
|
1044
|
+
action.execute — call an app action
|
|
1045
|
+
agent.run — run an AI agent
|
|
1046
|
+
quota.run / fallback.run / healthcheck.run
|
|
1047
|
+
vector.upsert / vector.delete
|
|
1048
|
+
session.revoke
|
|
1049
|
+
log.create
|
|
1050
|
+
custom.function / custom.http
|
|
1051
|
+
|
|
1052
|
+
All triggers support an optional condition (field comparisons) to gate execution.
|
|
1053
|
+
All triggers support retry config: { maxAttempts, delay, backoff: "fixed"|"exponential" }.
|
|
1054
|
+
|
|
1055
|
+
Triggers are admin-only configuration — they cannot be set at runtime via ductape_execute.
|
|
1056
|
+
Use ductape_cli with the Workbench or databases.trigger.create via the admin SDK.
|
|
1057
|
+
`.trim(),
|
|
1058
|
+
aggregations: `
|
|
1059
|
+
DUCTAPE DATABASE AGGREGATIONS
|
|
1060
|
+
|
|
1061
|
+
databases.aggregate [{ product, env, database, entity, aggregations, where?, groupBy? }]
|
|
1062
|
+
|
|
1063
|
+
aggregations array — each entry:
|
|
1064
|
+
{ type: "count"|"sum"|"avg"|"min"|"max", field?: string, alias: string }
|
|
1065
|
+
|
|
1066
|
+
Example — total revenue grouped by category:
|
|
1067
|
+
ductape_execute("databases.aggregate", [{
|
|
1068
|
+
product: "my-product",
|
|
1069
|
+
env: "prd",
|
|
1070
|
+
database: "sales-db",
|
|
1071
|
+
entity: "orders",
|
|
1072
|
+
aggregations: [
|
|
1073
|
+
{ type: "sum", field: "amount", alias: "totalRevenue" },
|
|
1074
|
+
{ type: "count", alias: "orderCount" },
|
|
1075
|
+
{ type: "avg", field: "amount", alias: "avgOrderValue" },
|
|
1076
|
+
],
|
|
1077
|
+
groupBy: ["category"],
|
|
1078
|
+
where: { status: { $eq: "completed" } },
|
|
1079
|
+
}])
|
|
1080
|
+
|
|
1081
|
+
Supported where operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $like, $ilike
|
|
1082
|
+
groupBy: array of field names to group by (omit for global aggregation)
|
|
1083
|
+
`.trim(),
|
|
1084
|
+
migrations: `
|
|
1085
|
+
DUCTAPE DATABASE MIGRATIONS
|
|
1086
|
+
|
|
1087
|
+
Migrations are versioned SQL/NoSQL schema change scripts managed per database component.
|
|
1088
|
+
|
|
1089
|
+
Create a migration:
|
|
1090
|
+
ductape_execute("databases.migration.create", [{
|
|
1091
|
+
product: "my-product",
|
|
1092
|
+
database: "core-db",
|
|
1093
|
+
data: {
|
|
1094
|
+
name: "add users table",
|
|
1095
|
+
tag: "001-add-users",
|
|
1096
|
+
value: {
|
|
1097
|
+
up: ["CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE)"],
|
|
1098
|
+
down: ["DROP TABLE users"],
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
}])
|
|
1102
|
+
|
|
1103
|
+
Run migrations (applies all pending):
|
|
1104
|
+
ductape_execute("databases.migration.run", [migrations, { env: "prd" }])
|
|
1105
|
+
|
|
1106
|
+
Rollback:
|
|
1107
|
+
ductape_execute("databases.migration.rollback", [migrations, 1]) // roll back 1
|
|
1108
|
+
|
|
1109
|
+
Check status:
|
|
1110
|
+
ductape_execute("databases.migration.status", [migrations])
|
|
1111
|
+
|
|
1112
|
+
History:
|
|
1113
|
+
ductape_execute("databases.migration.history", [])
|
|
1114
|
+
|
|
1115
|
+
Via CLI:
|
|
1116
|
+
ductape_cli("db migrate") // run pending
|
|
1117
|
+
ductape_cli("db migrate rollback") // roll back last
|
|
1118
|
+
ductape_cli("db migrate rollback -n 3")
|
|
1119
|
+
|
|
1120
|
+
MongoDB: migrations run as raw Mongo shell commands in the up/down arrays.
|
|
1121
|
+
`.trim(),
|
|
1122
|
+
indexes: `
|
|
1123
|
+
DUCTAPE DATABASE INDEXES
|
|
1124
|
+
|
|
1125
|
+
Create an index:
|
|
1126
|
+
ductape_execute("databases.schema.createIndex", [
|
|
1127
|
+
"collection_name",
|
|
1128
|
+
["field1", "field2"], // simple array of field names
|
|
1129
|
+
{ unique: true, name: "idx_email_unique" } // options (optional)
|
|
1130
|
+
])
|
|
1131
|
+
|
|
1132
|
+
// Ordered index:
|
|
1133
|
+
ductape_execute("databases.schema.createIndex", [
|
|
1134
|
+
"orders",
|
|
1135
|
+
[{ field: "createdAt", order: "DESC" }, { field: "userId", order: "ASC" }],
|
|
1136
|
+
{ name: "idx_orders_date_user" }
|
|
1137
|
+
])
|
|
1138
|
+
|
|
1139
|
+
Drop an index:
|
|
1140
|
+
ductape_execute("databases.schema.dropIndex", ["collection_name", "index_name"])
|
|
1141
|
+
|
|
1142
|
+
List indexes on a collection:
|
|
1143
|
+
ductape_execute("databases.schema.indexes", ["collection_name"])
|
|
1144
|
+
|
|
1145
|
+
Performance guidance:
|
|
1146
|
+
- Index fields used in WHERE clauses, JOIN conditions, and ORDER BY.
|
|
1147
|
+
- Compound indexes: put high-cardinality fields first.
|
|
1148
|
+
- Unique indexes enforce uniqueness at the database level.
|
|
1149
|
+
- MongoDB: compound indexes support prefix queries; order matters.
|
|
1150
|
+
- PostgreSQL/MySQL: B-tree indexes by default; use partial indexes for sparse columns.
|
|
1151
|
+
- Avoid over-indexing writes-heavy collections — each index slows inserts/updates.
|
|
1152
|
+
|
|
1153
|
+
Before adding an index ask the user which fields they query/filter on and expected data volume.
|
|
1154
|
+
`.trim(),
|
|
1155
|
+
performance: `
|
|
1156
|
+
DUCTAPE DATABASE PERFORMANCE GUIDANCE
|
|
1157
|
+
|
|
1158
|
+
1. Queries
|
|
1159
|
+
- Always pass select: ["field1", "field2"] to avoid fetching unused columns.
|
|
1160
|
+
- Use limit + offset for pagination; add an index on the ORDER BY field.
|
|
1161
|
+
- Use count/sum/avg/aggregate instead of fetching all rows and computing in code.
|
|
1162
|
+
|
|
1163
|
+
2. Indexes (see ductape_docs({ topic: "indexes" }) for syntax)
|
|
1164
|
+
- Index every WHERE / ORDER BY field.
|
|
1165
|
+
- Compound index order: equality fields first, range fields last.
|
|
1166
|
+
- For MongoDB: covered queries (index holds all projected fields) skip the document read.
|
|
1167
|
+
|
|
1168
|
+
3. Connections
|
|
1169
|
+
- The SDK pools connections per product+env+database triple.
|
|
1170
|
+
- Call databases.connect once per process; the pool reuses the connection.
|
|
1171
|
+
- databases.disconnect releases the pool — call only on shutdown.
|
|
1172
|
+
|
|
1173
|
+
4. Transactions (see ductape_docs({ topic: "transactions" }))
|
|
1174
|
+
- Keep transactions short; long-lived transactions block row locks (PostgreSQL/MySQL).
|
|
1175
|
+
- For MongoDB on M10+, use sessions for multi-document writes — not for reads.
|
|
1176
|
+
|
|
1177
|
+
5. Caching
|
|
1178
|
+
- For read-heavy, rarely-changing data use caches.get before databases.query.
|
|
1179
|
+
- Invalidate cache keys in an afterWrite trigger (see ductape_docs({ topic: "triggers" })).
|
|
1180
|
+
`.trim(),
|
|
1181
|
+
actions: `
|
|
1182
|
+
DUCTAPE DATABASE ACTIONS
|
|
1183
|
+
|
|
1184
|
+
A database action is a saved query or mutation (SQL string or NoSQL command) stored
|
|
1185
|
+
on the Ductape product and executed by tag at runtime.
|
|
1186
|
+
|
|
1187
|
+
Create an action (admin):
|
|
1188
|
+
ductape_execute("databases.action.create", [{
|
|
1189
|
+
product: "my-product",
|
|
1190
|
+
database: "core-db",
|
|
1191
|
+
data: {
|
|
1192
|
+
tag: "get-active-users",
|
|
1193
|
+
name: "Get active users",
|
|
1194
|
+
description: "Returns all users with status=active",
|
|
1195
|
+
type: "sql", // "sql" or "nosql"
|
|
1196
|
+
query: "SELECT * FROM users WHERE status = :status",
|
|
1197
|
+
},
|
|
1198
|
+
}])
|
|
1199
|
+
|
|
1200
|
+
Dispatch an action at runtime:
|
|
1201
|
+
→ CALL ductape_generate_payload FIRST to get the canonical input shape.
|
|
1202
|
+
ductape_execute("databases.action.dispatch", [{
|
|
1203
|
+
product: "my-product",
|
|
1204
|
+
env: "prd",
|
|
1205
|
+
database: "core-db",
|
|
1206
|
+
action: "get-active-users",
|
|
1207
|
+
input: { status: "active" },
|
|
1208
|
+
}])
|
|
1209
|
+
|
|
1210
|
+
List actions for a database:
|
|
1211
|
+
ductape_execute("databases.action.list", ["database_tag"])
|
|
1212
|
+
|
|
1213
|
+
Fetch / update / delete:
|
|
1214
|
+
ductape_execute("databases.action.fetch", ["action_tag"])
|
|
1215
|
+
ductape_execute("databases.action.update", ["my-product", "action_tag", { query: "..." }])
|
|
1216
|
+
ductape_execute("databases.action.delete", ["action_tag"])
|
|
1217
|
+
|
|
1218
|
+
Actions are the preferred way to encapsulate complex or reused queries — they can be
|
|
1219
|
+
scheduled, dispatched with retries, and audited via logs.
|
|
1220
|
+
`.trim(),
|
|
1221
|
+
graphs: `
|
|
1222
|
+
DUCTAPE GRAPH DATABASES
|
|
1223
|
+
|
|
1224
|
+
Supported engines: neo4j | neptune | cosmos-gremlin | spanner-graph | arangodb | memgraph
|
|
1225
|
+
|
|
1226
|
+
Registration (admin — ductape_cli):
|
|
1227
|
+
ductape_cli("resources graphs create -f graph.json")
|
|
1228
|
+
File: { name, tag, type, envs: [{ slug, connection_url, username?, password?, graphName?, region? }] }
|
|
1229
|
+
Sensitive fields (connection_url, username, password) are auto-wrapped as $Secret{...} when
|
|
1230
|
+
a productTag is supplied — do NOT pre-wrap them yourself.
|
|
1231
|
+
|
|
1232
|
+
Node operations:
|
|
1233
|
+
graph.createNode [{ labels: string[], properties: { key: value } }, transaction?]
|
|
1234
|
+
graph.findNodes [{ labels?, where?, limit?, skip? }, transaction?]
|
|
1235
|
+
graph.findNodeById [id, transaction?]
|
|
1236
|
+
graph.updateNode [{ id, properties }, transaction?]
|
|
1237
|
+
graph.deleteNode [{ id, detach?: boolean }, transaction?]
|
|
1238
|
+
graph.mergeNode [{ labels, matchProps, setProps? }, transaction?]
|
|
1239
|
+
graph.addLabels / removeLabels / setLabels [{ id, labels }, transaction?]
|
|
1240
|
+
|
|
1241
|
+
Relationship operations:
|
|
1242
|
+
graph.createRelationship [{ fromId, toId, type, properties? }, transaction?]
|
|
1243
|
+
graph.findRelationships [{ type?, where?, limit? }, transaction?]
|
|
1244
|
+
graph.updateRelationship [{ id, properties }, transaction?]
|
|
1245
|
+
graph.deleteRelationship [{ id }, transaction?]
|
|
1246
|
+
graph.mergeRelationship [{ fromId, toId, type, matchProps?, setProps? }, transaction?]
|
|
1247
|
+
|
|
1248
|
+
Traversal:
|
|
1249
|
+
graph.traverse [{ startId, direction: "in"|"out"|"both", relationshipTypes?, maxDepth?, where? }]
|
|
1250
|
+
graph.shortestPath [{ fromId, toId, relationshipType?, maxDepth? }]
|
|
1251
|
+
graph.allPaths [{ fromId, toId, relationshipType?, maxDepth? }]
|
|
1252
|
+
graph.getNeighborhood [{ id, depth?, relationshipTypes? }]
|
|
1253
|
+
graph.findConnectedComponents [{ labels? }]
|
|
1254
|
+
|
|
1255
|
+
Raw query (provider-native Cypher/Gremlin):
|
|
1256
|
+
graph.query [cypher_query: string, params?: { key: value }, transaction?]
|
|
1257
|
+
|
|
1258
|
+
Full-text and vector search (where supported):
|
|
1259
|
+
graph.fullTextSearch [{ index, query, limit? }]
|
|
1260
|
+
graph.vectorSearch [{ index, vector: number[], topK? }]
|
|
1261
|
+
|
|
1262
|
+
Transactions:
|
|
1263
|
+
graph.beginTransaction [options?: { isolationLevel?, timeout? }] → transaction object
|
|
1264
|
+
graph.commitTransaction [transaction]
|
|
1265
|
+
graph.rollbackTransaction [transaction]
|
|
1266
|
+
Or use withTransaction (managed — auto-commit/rollback):
|
|
1267
|
+
graph.withTransaction [graphTag, env, callback, options?]
|
|
1268
|
+
Transaction support varies by engine — ask the user which engine they use before enabling.
|
|
1269
|
+
neo4j: full ACID; neptune: limited; arangodb: multi-document; memgraph: full ACID.
|
|
1270
|
+
|
|
1271
|
+
Schema management:
|
|
1272
|
+
graph.createNodeIndex [{ label, field, type: "btree"|"fulltext"|"vector" }]
|
|
1273
|
+
graph.createNodeConstraint [{ label, field, type: "unique"|"exists" }]
|
|
1274
|
+
graph.dropIndex / dropConstraint / listIndexes / listConstraints
|
|
1275
|
+
|
|
1276
|
+
Saved actions (parameterized queries stored on the product):
|
|
1277
|
+
graph.createAction [{ graphTag, tag, name, query, params? }, productTag?]
|
|
1278
|
+
graph.listActions [graphTag?, productTag?]
|
|
1279
|
+
graph.dispatch [data] ← call ductape_generate_payload FIRST
|
|
1280
|
+
|
|
1281
|
+
Supported index types: btree | fulltext | vector | range | point | text
|
|
1282
|
+
Supported constraint types: UNIQUE | EXISTS | NODE_KEY
|
|
1283
|
+
`.trim(),
|
|
1284
|
+
storage: `
|
|
1285
|
+
DUCTAPE FILE STORAGE
|
|
1286
|
+
|
|
1287
|
+
Supported providers: aws (S3) | gcp (GCS) | azure (Blob Storage)
|
|
1288
|
+
|
|
1289
|
+
Registration (admin — ductape_cli):
|
|
1290
|
+
ductape_cli("resources storage create -f storage.json")
|
|
1291
|
+
File: { name, tag, envs: [{ slug, type: "aws"|"gcp"|"azure", config: { ... } }] }
|
|
1292
|
+
For cloud-linked envs: set config.cloud to the connection tag; omit raw credentials.
|
|
1293
|
+
For all envs in the product must be covered — see ductape_docs({ topic: "cloud" }) for
|
|
1294
|
+
import-persist-all workflow when connecting existing buckets.
|
|
1295
|
+
|
|
1296
|
+
Operations (runtime):
|
|
1297
|
+
storage.upload [{ product, env, storage, fileName, buffer: string|Buffer, mimeType? }]
|
|
1298
|
+
storage.download [{ product, env, storage, fileName }] → { content, size, mimeType }
|
|
1299
|
+
storage.remove [{ product, env, storage, fileName }]
|
|
1300
|
+
storage.listFiles [{ product, env, storage, prefix?, limit?, continuationToken? }]
|
|
1301
|
+
storage.getSignedUrl [{ product, env, storage, fileName, expiresIn?: number, action?: "read"|"write" }]
|
|
1302
|
+
storage.stats [{ product, env, storage, prefix?, cache? }] → file counts by type
|
|
1303
|
+
storage.testConnection [{ product, env, storage }]
|
|
1304
|
+
|
|
1305
|
+
Background dispatch (fire-and-forget):
|
|
1306
|
+
storage.dispatch [{ product, env, storage, operation, input, schedule? }]
|
|
1307
|
+
→ CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch")
|
|
1308
|
+
→ Returns { jobId, status: "queued" } immediately; actual operation runs in background.
|
|
1309
|
+
→ schedule: { start_at?, cron?, every?, limit?, endDate?, tz? }
|
|
1310
|
+
|
|
1311
|
+
Caching:
|
|
1312
|
+
download, listFiles, getSignedUrl, and stats all support a cache option.
|
|
1313
|
+
Pass cache: "cache_tag" to check the product cache before hitting the provider.
|
|
1314
|
+
Cached results are stored fire-and-forget on miss.
|
|
1315
|
+
|
|
1316
|
+
Notes:
|
|
1317
|
+
- buffer can be a Node.js Buffer (server) or a base64 string (JSON proxy clients).
|
|
1318
|
+
- Cloud-linked configs inject runtime credentials via the cloud connection; no plaintext keys stored.
|
|
1319
|
+
- $Secret{} references in config are resolved at runtime if cloud-link resolution fails.
|
|
1320
|
+
`.trim(),
|
|
1321
|
+
cloud: `
|
|
1322
|
+
DUCTAPE CLOUD CONNECTIONS
|
|
1323
|
+
|
|
1324
|
+
Cloud connections link your workspace to an external cloud provider so Ductape can manage
|
|
1325
|
+
resources (databases, storage, brokers, graphs, vectors) on your behalf.
|
|
1326
|
+
|
|
1327
|
+
Supported providers: aws | gcp | azure | mongodb_atlas | neo4j_aura
|
|
1328
|
+
Auth modes: iam_role | oauth | service_principal | workload_identity | api_key
|
|
1329
|
+
|
|
1330
|
+
Create a connection (admin — ductape_cli):
|
|
1331
|
+
ductape_cli("cloud connections create --provider aws --name my-aws-conn")
|
|
1332
|
+
Returns: external_id, trust_policy, setup_instructions, setup_url
|
|
1333
|
+
AWS: the caller must create an IAM role using the returned trust policy, then complete:
|
|
1334
|
+
ductape_cli("cloud connections complete <tag> --role-arn arn:aws:iam::...")
|
|
1335
|
+
GCP: complete with project_id, service_account_email, service_account_json
|
|
1336
|
+
Azure: complete with tenant_id, subscription_id, client_id, client_secret, default_location
|
|
1337
|
+
Atlas: complete with atlas_public_key, atlas_private_key
|
|
1338
|
+
Aura (Neo4j): complete with aura_client_id, aura_client_secret, aura_instance_id
|
|
1339
|
+
|
|
1340
|
+
Validate a connection:
|
|
1341
|
+
ductape_cli("cloud connections validate <tag>")
|
|
1342
|
+
→ { valid, status, message?, tested_at? }
|
|
1343
|
+
|
|
1344
|
+
List / fetch / delete:
|
|
1345
|
+
ductape_cli("cloud connections list")
|
|
1346
|
+
ductape_cli("cloud connections fetch <tag>")
|
|
1347
|
+
|
|
1348
|
+
Discover resources on a connection:
|
|
1349
|
+
ductape_cli("cloud resources list -f query.json --json")
|
|
1350
|
+
File: { cloud: "<tag>", service: "gcs"|"s3"|"blob"|"rds"|"atlas-cluster"|..., region? }
|
|
1351
|
+
→ returns list of available resources (buckets, clusters, instances, etc.)
|
|
1352
|
+
|
|
1353
|
+
Import an existing resource and register it on the product:
|
|
1354
|
+
Use import-persist-all for multi-env products (required):
|
|
1355
|
+
ductape_cli("cloud resources import-persist-all -f all-envs.json --json")
|
|
1356
|
+
File is a JSON ARRAY — one entry per env, same product + component tag across all entries.
|
|
1357
|
+
Each entry: { cloud, service, type, product, component, env, resource, region?, dbName? }
|
|
1358
|
+
Supported service identifiers: s3, gcs, blob, rds, postgresql, cloudsql, sqs, pubsub,
|
|
1359
|
+
servicebus, neptune, cosmos-gremlin, opensearch, azure-search, atlas-cluster, aura-instance
|
|
1360
|
+
|
|
1361
|
+
Provision a brand-new resource and register it:
|
|
1362
|
+
ductape_cli("cloud resources provision-persist-all -f all-envs.json --json")
|
|
1363
|
+
Additional per-entry fields: tier, region/location, waitForReady.
|
|
1364
|
+
List available tiers first: ductape_cli("cloud tiers --provider aws --type database --db-type postgresql --json")
|
|
1365
|
+
NEVER infer region, tier, or cost — always list and confirm with the user first.
|
|
1366
|
+
Atlas and Neo4j Aura are IMPORT-ONLY — provision is not supported for these providers.
|
|
1367
|
+
|
|
1368
|
+
VPC connector (private networking):
|
|
1369
|
+
ductape_cli("cloud connections vpc update <tag> --vpc-id vpc-xxx --subnet-ids subnet-1,subnet-2")
|
|
1370
|
+
ductape_cli("cloud connections vpc status <tag>")
|
|
1371
|
+
VPC connector enables Ductape to reach resources in a private VPC; agent_status:
|
|
1372
|
+
pending → connected → disconnected
|
|
1373
|
+
|
|
1374
|
+
Scopes a connection can cover: storage | broker | database | graph | vector | cache
|
|
1375
|
+
|
|
1376
|
+
Component types accepted by import/provision:
|
|
1377
|
+
storage | messageBrokers | databases | graphs | vectors | caches
|
|
1378
|
+
`.trim(),
|
|
1379
|
+
vector: `
|
|
1380
|
+
DUCTAPE VECTOR DATABASES
|
|
1381
|
+
|
|
1382
|
+
Supported adapters: pinecone | qdrant | weaviate | opensearch | azure-search | vertex-vector-search
|
|
1383
|
+
(chroma, milvus, pgvector are declared but not yet implemented — do not use them)
|
|
1384
|
+
|
|
1385
|
+
Registration (admin — ductape_cli):
|
|
1386
|
+
ductape_cli("resources vectors create -f vector.json")
|
|
1387
|
+
File: { name, tag, type, dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct",
|
|
1388
|
+
envs: [{ slug, endpoint?, apiKey?, region?, index?, namespace? }] }
|
|
1389
|
+
|
|
1390
|
+
Runtime operations:
|
|
1391
|
+
vector.upsert [{ product, env, tag, vectors: [{id, values: number[], metadata?}], namespace? }]
|
|
1392
|
+
vector.upsertOne [{ product, env, tag, id, values: number[], metadata?, namespace? }]
|
|
1393
|
+
vector.query [{ product, env, tag, vector: number[], topK?, filter?, namespace?,
|
|
1394
|
+
includeValues?, includeMetadata? }]
|
|
1395
|
+
vector.findSimilar [{ product, env, vector, values: number[], topK?, filter?, namespace? }]
|
|
1396
|
+
vector.fetchOne [{ product, env, vector, id, namespace? }]
|
|
1397
|
+
vector.fetchVectors [{ product, env, vector, ids: string[], namespace? }]
|
|
1398
|
+
vector.updateVector [{ product, env, vector, id, values?, setMetadata?, mergeMetadata?, namespace? }]
|
|
1399
|
+
vector.updateMetadata [{ product, env, vector, id, metadata: { key: value }, merge?, namespace? }]
|
|
1400
|
+
merge: true → deep-merges metadata rather than replacing it
|
|
1401
|
+
vector.deleteByIds [{ product, env, vector, ids: string[], namespace? }]
|
|
1402
|
+
vector.deleteAll [{ product, env, vector, namespace? }]
|
|
1403
|
+
vector.count [{ product, env, vector, namespace? }]
|
|
1404
|
+
|
|
1405
|
+
Namespace management:
|
|
1406
|
+
vector.listNamespaces [{ product, env, vector }]
|
|
1407
|
+
vector.deleteNamespace [{ product, env, vector, namespace }]
|
|
1408
|
+
|
|
1409
|
+
Listing (paginated):
|
|
1410
|
+
vector.listVectors [{ product, env, vector, namespace?, prefix?, limit?, cursor? }]
|
|
1411
|
+
vector.listAllVectors [{ product, env, vector, namespace?, prefix? }]
|
|
1412
|
+
listAllVectors auto-paginates until cursor exhausted — avoid on large indexes.
|
|
1413
|
+
|
|
1414
|
+
Index management:
|
|
1415
|
+
vector.describeIndex [{ product, env, vector }]
|
|
1416
|
+
vector.getStats [{ product, env, vector }] → totalVectorCount per namespace
|
|
1417
|
+
vector.createIndex [{ product, env, vector, name, dimensions, metric?, replicas?, shards? }]
|
|
1418
|
+
vector.deleteIndex [{ product, env, vector, name }]
|
|
1419
|
+
vector.listIndexes [{ product, env, vector }]
|
|
1420
|
+
|
|
1421
|
+
Distance metrics: cosine | euclidean | dotproduct | manhattan | hamming
|
|
1422
|
+
Index types: flat | ivf | hnsw | pq | ivf_pq | annoy
|
|
1423
|
+
Feature flags per adapter: metadata_filtering | namespaces | hybrid_search | batch_operations |
|
|
1424
|
+
index_management | vector_updates | sparse_vectors | multi_vector | aggregations
|
|
1425
|
+
|
|
1426
|
+
Call vector.supportsFeature(feature) or vector.getSupportedFeatures() to check what the
|
|
1427
|
+
chosen adapter supports before using a feature.
|
|
1428
|
+
`.trim(),
|
|
1429
|
+
warehouse: `
|
|
1430
|
+
DUCTAPE WAREHOUSE
|
|
1431
|
+
|
|
1432
|
+
The Warehouse is a unified query layer over the three structured data stores: Database, Graph, and Vector.
|
|
1433
|
+
It provides a single API for cross-store operations without having to address each service separately.
|
|
1434
|
+
|
|
1435
|
+
Warehouse is available on the SDK instance as ductape.warehouse (TypeScript) or Warehouse (C#/Go/Java).
|
|
1436
|
+
It is NOT a separate Ductape resource — it wraps the existing database, graph, and vector components
|
|
1437
|
+
that are already registered on the product.
|
|
1438
|
+
|
|
1439
|
+
Key use cases:
|
|
1440
|
+
- Run a relational query, a graph traversal, and a vector similarity search in a single call.
|
|
1441
|
+
- Build recommendation pipelines: graph neighbors → vector re-rank → database hydration.
|
|
1442
|
+
- Federated search across multiple store types with a single await.
|
|
1443
|
+
|
|
1444
|
+
Usage (TypeScript SDK):
|
|
1445
|
+
const result = await ductape.warehouse.query({
|
|
1446
|
+
product: "my-product",
|
|
1447
|
+
env: "prd",
|
|
1448
|
+
database: { tag: "core-db", entity: "products", where: { active: { $eq: true } }, select: ["id", "name"] },
|
|
1449
|
+
graph: { tag: "reco-graph", startId: userId, direction: "out", maxDepth: 2 },
|
|
1450
|
+
vector: { tag: "embedding-store", vector: queryEmbedding, topK: 10 },
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
result.database → relational rows
|
|
1454
|
+
result.graph → traversal nodes/relationships
|
|
1455
|
+
result.vector → similarity hits with scores
|
|
1456
|
+
|
|
1457
|
+
Warehouse context is set at SDK init time (env + workspaceId) and shared across all three stores.
|
|
1458
|
+
Underlying store calls use the same auth and product context; individual store errors are surfaced per store.
|
|
1459
|
+
|
|
1460
|
+
Before using Warehouse:
|
|
1461
|
+
1. Ensure the database, graph, and vector components are registered on the product.
|
|
1462
|
+
2. Confirm the env slug exists on all three stores.
|
|
1463
|
+
3. Each store can be queried independently — pass only the fields for the stores you need.
|
|
1464
|
+
|
|
1465
|
+
Warehouse does not support writes — use the individual service APIs (database.insert, graph.createNode,
|
|
1466
|
+
vector.upsert) for mutations.
|
|
1467
|
+
`.trim(),
|
|
1468
|
+
secrets: `
|
|
1469
|
+
DUCTAPE SECRETS
|
|
1470
|
+
|
|
1471
|
+
Secrets are workspace-level encrypted key-value pairs. They are referenced in resource configs,
|
|
1472
|
+
connection URLs, and any string field using the $Secret{KEY_NAME} syntax.
|
|
1473
|
+
|
|
1474
|
+
Create (admin — ductape_cli or ductape_execute):
|
|
1475
|
+
ductape_execute("secrets.create", [{
|
|
1476
|
+
key: "STRIPE_API_KEY",
|
|
1477
|
+
value: "sk_live_...", // plaintext — encrypted AES-256-GCM client-side before send
|
|
1478
|
+
description: "Stripe live key",
|
|
1479
|
+
token_type: "api", // "api" | "password" | "certificate"
|
|
1480
|
+
scope: ["my-product"], // which products can read this secret
|
|
1481
|
+
envs: ["prd"], // which env slugs can read this secret
|
|
1482
|
+
expires_at: 1800000000, // optional epoch ms
|
|
1483
|
+
}])
|
|
1484
|
+
The server never receives the plaintext value. Encryption uses the workspace private key.
|
|
1485
|
+
|
|
1486
|
+
Fetch / resolve:
|
|
1487
|
+
ductape_execute("secrets.fetch", ["STRIPE_API_KEY"]) → decrypted string value
|
|
1488
|
+
ductape_execute("secrets.exists", ["STRIPE_API_KEY"]) → boolean
|
|
1489
|
+
ductape_execute("secrets.validate", ["$Secret{STRIPE_API_KEY}"]) → { valid, missingKeys, existingKeys }
|
|
1490
|
+
ductape_execute("secrets.resolve", ["$Secret{STRIPE_API_KEY}", { env: "prd" }]) → resolved string
|
|
1491
|
+
|
|
1492
|
+
$Secret{} reference syntax:
|
|
1493
|
+
- Embed anywhere a string is accepted: "postgresql://$Secret{DB_USER}:$Secret{DB_PASS}@host/db"
|
|
1494
|
+
- Resolved at runtime before the value is used by the consuming service (storage, broker, graph, etc.)
|
|
1495
|
+
- The in-memory cache stores the encrypted form only; decryption happens on each cache hit.
|
|
1496
|
+
- Cache TTL: 5 minutes. Clear with: secrets.clearCache()
|
|
1497
|
+
|
|
1498
|
+
Lifecycle:
|
|
1499
|
+
ductape_execute("secrets.revoke", ["KEY"]) → disables without deleting (recoverable)
|
|
1500
|
+
ductape_execute("secrets.delete", ["KEY"]) → permanent deletion (irreversible)
|
|
1501
|
+
ductape_execute("secrets.update", ["KEY", { value: "new_value", expires_at: ... }])
|
|
1502
|
+
|
|
1503
|
+
List all secrets (keys only — values are not returned in list):
|
|
1504
|
+
ductape_execute("secrets.list", [])
|
|
1505
|
+
|
|
1506
|
+
Important:
|
|
1507
|
+
- Secrets are workspace-scoped, not product-scoped. scope[] and envs[] control access.
|
|
1508
|
+
- Other services (storage, broker, graph, etc.) resolve $Secret{} references automatically
|
|
1509
|
+
using the singleton secrets service — no manual resolution needed in most cases.
|
|
1510
|
+
- Never log or return resolved secret values to end users.
|
|
1511
|
+
`.trim(),
|
|
1512
|
+
apps: `
|
|
1513
|
+
DUCTAPE APPS
|
|
1514
|
+
|
|
1515
|
+
An app is a versioned API integration definition. It contains environments (base URLs), actions
|
|
1516
|
+
(individual endpoint specs), auth schemes, webhooks, variables, and constants.
|
|
1517
|
+
|
|
1518
|
+
Create an app (admin — ductape_cli):
|
|
1519
|
+
ductape_cli("apps create --name \\"Email Service\\" --description \\"Transactional email\\"")
|
|
1520
|
+
ductape_cli("app.init", ["app_tag"]) → loads app into builder state
|
|
1521
|
+
|
|
1522
|
+
Import from a file:
|
|
1523
|
+
ductape_cli("apps import <file.json> -t postman|openapi")
|
|
1524
|
+
Supports Postman v2.1 collection and OpenAPI 3.0 spec.
|
|
1525
|
+
|
|
1526
|
+
Manage environments (base URLs per stage):
|
|
1527
|
+
ductape_execute("app.environments.create", [app_tag, { slug, env_name, base_url }])
|
|
1528
|
+
ductape_execute("app.environments.list", [app_tag])
|
|
1529
|
+
|
|
1530
|
+
Manage actions (individual API endpoints):
|
|
1531
|
+
ductape_execute("actions.create", [app_tag, {
|
|
1532
|
+
tag, name, resource: "/users/{id}", method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE",
|
|
1533
|
+
body?: { fieldName: { type, required? } },
|
|
1534
|
+
params?: { id: { type: "string" } },
|
|
1535
|
+
query?: { filter: { type: "string" } },
|
|
1536
|
+
headers?: { Authorization: { type: "string" } },
|
|
1537
|
+
response?: { status_code: 200, success: true, body: { ... }, response_format: "json" },
|
|
1538
|
+
}])
|
|
1539
|
+
ductape_execute("actions.update", [app_tag, action_tag, data])
|
|
1540
|
+
ductape_execute("actions.list", [app_tag])
|
|
1541
|
+
ductape_execute("actions.fetch", [app_tag, action_tag])
|
|
1542
|
+
|
|
1543
|
+
Run an action at runtime:
|
|
1544
|
+
→ CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
|
|
1545
|
+
ductape_execute("actions.run", [{ product, env, app, action, input: { "body:field": value } }])
|
|
1546
|
+
ductape_execute("actions.dispatch", [{ product, env, app, action, input, schedule? }])
|
|
1547
|
+
|
|
1548
|
+
Auth schemes (how the app authenticates outbound requests):
|
|
1549
|
+
Setup types: header | bearer | basic | oauth2 | apikey
|
|
1550
|
+
ductape_execute("auths.create", [app_tag, { tag, name, setup_type, expiry, period, action_tag? }])
|
|
1551
|
+
ductape_execute("auths.list", [app_tag])
|
|
1552
|
+
|
|
1553
|
+
Webhooks (inbound events from the external service):
|
|
1554
|
+
ductape_execute("webhooks.create", [app_tag, { tag, name, description, envs: [{ slug, registration_url?, method? }] }])
|
|
1555
|
+
ductape_execute("webhooks.events.create", [app_tag, { tag, name, selector, description, sample }])
|
|
1556
|
+
|
|
1557
|
+
Variables (per-env mutable values) and Constants (fixed values):
|
|
1558
|
+
ductape_execute("app.variables.create", [app_tag, { key, value, env_slug }])
|
|
1559
|
+
ductape_execute("app.constants.create", [app_tag, { key, value }])
|
|
1560
|
+
|
|
1561
|
+
Connecting an app to a product (after creation):
|
|
1562
|
+
ductape_execute("product.apps.add", [product_tag, {
|
|
1563
|
+
access_tag: "app_access_tag",
|
|
1564
|
+
envs: [{ app_env_slug: "production", product_env_slug: "prd",
|
|
1565
|
+
variables: [{ key: "BASE_URL", value: "https://api.example.com" }],
|
|
1566
|
+
auth: { auth_tag: "api-key-auth", data: "$Secret{API_KEY}" } }]
|
|
1567
|
+
}])
|
|
1568
|
+
ductape_execute("product.apps.list", [product_tag])
|
|
1569
|
+
ductape_execute("product.apps.fetch", [product_tag, access_tag])
|
|
1570
|
+
`.trim(),
|
|
1571
|
+
products: `
|
|
1572
|
+
DUCTAPE PRODUCTS
|
|
1573
|
+
|
|
1574
|
+
A product is the top-level namespace for all Ductape infrastructure: apps, databases, graphs,
|
|
1575
|
+
vectors, storage, brokers, sessions, caches, notifications, resilience, features, jobs, and envs.
|
|
1576
|
+
Every SDK service call resolves within a product context.
|
|
1577
|
+
|
|
1578
|
+
Create a product:
|
|
1579
|
+
ductape_cli("products create --name \\"My App\\" --tag my-app")
|
|
1580
|
+
ductape_execute("product.create", [{ name: "My App", tag: "my-app",
|
|
1581
|
+
envs: [{ slug: "dev", name: "Development" }, { slug: "prd", name: "Production" }] }])
|
|
1582
|
+
|
|
1583
|
+
Environments — every resource's envs array MUST cover all product env slugs:
|
|
1584
|
+
ductape_execute("product.environments.create", [product_tag, { slug, env_name, description, active? }])
|
|
1585
|
+
ductape_execute("product.environments.list", [product_tag])
|
|
1586
|
+
ductape_execute("product.environments.fetch", [product_tag, slug])
|
|
1587
|
+
BEFORE registering any resource, always run environments.list and collect all slugs.
|
|
1588
|
+
|
|
1589
|
+
Fetch / update:
|
|
1590
|
+
ductape_execute("product.fetch", [product_tag])
|
|
1591
|
+
ductape_execute("product.update", [product_tag, { name?, description? }])
|
|
1592
|
+
|
|
1593
|
+
Connect apps to a product:
|
|
1594
|
+
See ductape_docs({ topic: "apps" }) for product.apps.add / product.apps.list.
|
|
1595
|
+
|
|
1596
|
+
Resource registration (all via ductape_cli or ductape_execute — see per-topic docs):
|
|
1597
|
+
databases → ductape_docs({ topic: "transactions" })
|
|
1598
|
+
storage → ductape_docs({ topic: "storage" })
|
|
1599
|
+
graphs → ductape_docs({ topic: "graphs" })
|
|
1600
|
+
vectors → ductape_docs({ topic: "vector" })
|
|
1601
|
+
events → ductape_docs({ topic: "events" })
|
|
1602
|
+
caches → ductape_docs({ topic: "caches" })
|
|
1603
|
+
notifications → ductape_docs({ topic: "notifications" })
|
|
1604
|
+
sessions → ductape_docs({ topic: "sessions" })
|
|
1605
|
+
resilience → ductape_docs({ topic: "resilience" })
|
|
1606
|
+
features → ductape_docs({ topic: "features" })
|
|
1607
|
+
|
|
1608
|
+
Product structure (IProduct fields):
|
|
1609
|
+
_id, workspace_id, name, tag, description, private_key,
|
|
1610
|
+
apps[], envs[], databases[], graphs[], vectors[], storage[], messageBrokers[],
|
|
1611
|
+
caches[], sessions[], notifications[], quota[], fallback[], healthchecks[],
|
|
1612
|
+
workflows[] (features), models[], agents[], jobs[]
|
|
1613
|
+
|
|
1614
|
+
Bootstrap (single API call returning product context + component config + private key):
|
|
1615
|
+
Each service makes a single bootstrap call at first use; results are cached in BootstrapCache
|
|
1616
|
+
(Redis when available). This avoids repeated round-trips in high-frequency paths.
|
|
1617
|
+
`.trim(),
|
|
1618
|
+
sessions: `
|
|
1619
|
+
DUCTAPE SESSIONS
|
|
1620
|
+
|
|
1621
|
+
A session is a named JWT schema on a product. It defines: a tag, an expiry duration, a selector
|
|
1622
|
+
(which field in the data object is the user identifier), and a schema (the shape of the JWT payload).
|
|
1623
|
+
|
|
1624
|
+
Define a session (admin — ductape_execute):
|
|
1625
|
+
ductape_execute("sessions.create", [product_tag, {
|
|
1626
|
+
tag: "user-session",
|
|
1627
|
+
name: "User Session",
|
|
1628
|
+
expiry: 24,
|
|
1629
|
+
period: "hours", // "seconds" | "minutes" | "hours" | "days"
|
|
1630
|
+
selector: "userId", // field in data that identifies the user
|
|
1631
|
+
schema: { userId: "string", role: "string", email: "string" },
|
|
1632
|
+
}])
|
|
1633
|
+
|
|
1634
|
+
Runtime — create a session (sign a JWT):
|
|
1635
|
+
→ CALL ductape_generate_payload FIRST (operation_family="session", method="start", targets={tag})
|
|
1636
|
+
ductape_execute("sessions.start", [{ product, env, tag: "user-session",
|
|
1637
|
+
data: { userId: "u_123", role: "admin", email: "user@example.com" } }])
|
|
1638
|
+
→ returns token: "user-session:eyJ..." ← format is always "session_tag:jwt"
|
|
1639
|
+
|
|
1640
|
+
Verify a token:
|
|
1641
|
+
ductape_execute("sessions.verify", [{ product, env, tag: "user-session", token: "user-session:eyJ..." }])
|
|
1642
|
+
→ decodes JWT + checks revocation blacklist; throws if revoked or expired
|
|
1643
|
+
|
|
1644
|
+
Refresh (atomic token rotation):
|
|
1645
|
+
ductape_execute("sessions.refresh", [{ product, env, tag: "user-session", refreshToken: "..." }])
|
|
1646
|
+
Old refresh token is invalidated server-side atomically — no partial state possible.
|
|
1647
|
+
The refresh token is AES-encrypted JSON (NOT a JWT).
|
|
1648
|
+
|
|
1649
|
+
Revoke:
|
|
1650
|
+
ductape_execute("sessions.revoke", [{ product, env, tag, sessionId?, identifier? }])
|
|
1651
|
+
Requires at least one of sessionId or identifier.
|
|
1652
|
+
Revocation is enforced via a ProcessorAPI blacklist check on every verify() call.
|
|
1653
|
+
|
|
1654
|
+
Analytics:
|
|
1655
|
+
sessions.listActive [{ product, env, tag, identifier?, page?, limit? }]
|
|
1656
|
+
sessions.fetchUsers [{ product, session, env?, page?, limit? }]
|
|
1657
|
+
sessions.fetchUserDetails [{ product, session, identifier, env? }]
|
|
1658
|
+
sessions.fetchDashboard [{ product, session, env? }]
|
|
1659
|
+
→ { DAU, WAU, MAU, activityTimeline, peakHours, environmentBreakdown, avgSessionDuration }
|
|
1660
|
+
|
|
1661
|
+
Token format: "session_tag:jwt_token" — pass this format verbatim wherever session is expected.
|
|
1662
|
+
JWT is signed with the product private key (not a symmetric shared secret).
|
|
1663
|
+
`.trim(),
|
|
1664
|
+
caches: `
|
|
1665
|
+
DUCTAPE CACHES
|
|
1666
|
+
|
|
1667
|
+
Caches are product-level Redis (or in-memory) stores for temporary key-value data with optional TTL.
|
|
1668
|
+
|
|
1669
|
+
Registration (admin — ductape_cli):
|
|
1670
|
+
ductape_cli("resources caches create -f cache.json")
|
|
1671
|
+
File: { name, tag, type: "redis"|"memcached"|"in-memory",
|
|
1672
|
+
envs: [{ slug, connection_url }] }
|
|
1673
|
+
|
|
1674
|
+
Operations:
|
|
1675
|
+
caches.set [{ product, cache, key, value: string, expiry?: Date, env }]
|
|
1676
|
+
→ Writes to Redis synchronously, then fires remote API write in background (non-blocking).
|
|
1677
|
+
caches.get [{ key: string }]
|
|
1678
|
+
→ Checks Redis first; on miss falls through to remote API; on hit from API, populates Redis.
|
|
1679
|
+
→ Enforces TTL by comparing stored expiry field against current time.
|
|
1680
|
+
caches.clear [{ key: string }]
|
|
1681
|
+
→ Deletes from Redis and from remote API.
|
|
1682
|
+
caches.clearAll [{ product, cache, env? }]
|
|
1683
|
+
→ Bulk delete via remote API only. Redis may still have stale keys until naturally evicted.
|
|
1684
|
+
caches.fetchValues [{ product, cache, env?, page?, limit?,
|
|
1685
|
+
expiryFilter?: "all"|"expiring"|"permanent"|"expired" }]
|
|
1686
|
+
caches.fetchDashboard [{ product, cache, env? }]
|
|
1687
|
+
→ { totalValues, activeValues, expiredValues, totalSize }
|
|
1688
|
+
|
|
1689
|
+
Tier architecture (three tiers applied automatically):
|
|
1690
|
+
Tier 1: in-process Map (SDK metadata cache, 5-min TTL — not for user data)
|
|
1691
|
+
Tier 2: Redis hash (hSet/hGetAll) with optional EXPIRE
|
|
1692
|
+
Tier 3: Remote Ductape API
|
|
1693
|
+
|
|
1694
|
+
Important:
|
|
1695
|
+
- Redis is optional; without it all reads/writes go through the remote API.
|
|
1696
|
+
- expiry is a stored Date field, not a Redis TTL — expiry check happens client-side.
|
|
1697
|
+
- clearAll only clears via remote API; Redis retains stale entries until accessed and found expired.
|
|
1698
|
+
- Cache entries are stored as Redis hashes (not plain strings).
|
|
1699
|
+
- Other services (storage, graph, notifications, sessions) also use the CacheManager for
|
|
1700
|
+
their own result caching — configure a shared Redis URL at SDK init to share the pool.
|
|
1701
|
+
`.trim(),
|
|
1702
|
+
notifications: `
|
|
1703
|
+
DUCTAPE NOTIFICATIONS
|
|
1704
|
+
|
|
1705
|
+
Notifications send messages across multiple channels: email, SMS, push, or HTTP callback.
|
|
1706
|
+
|
|
1707
|
+
Create a notification (admin — ductape_execute):
|
|
1708
|
+
ductape_execute("notifications.create", [product_tag, {
|
|
1709
|
+
tag: "welcome-email",
|
|
1710
|
+
name: "Welcome Email",
|
|
1711
|
+
type: "email", // optional hint; actual channels configured per env
|
|
1712
|
+
}])
|
|
1713
|
+
|
|
1714
|
+
Create a message template:
|
|
1715
|
+
ductape_execute("notifications.messages.create", [product_tag, {
|
|
1716
|
+
tag: "welcome-email:default", // format: "notification_tag:message_tag"
|
|
1717
|
+
notification: "welcome-email",
|
|
1718
|
+
subject: { template: "Welcome, {{name}}!", data: { name: "" } },
|
|
1719
|
+
body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
|
|
1720
|
+
}])
|
|
1721
|
+
|
|
1722
|
+
Send at runtime (one channel at a time):
|
|
1723
|
+
→ CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
|
|
1724
|
+
notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
|
|
1725
|
+
notifications.push.send [{ product, env, notification, input: { device_tokens, title?, body?, data? } }]
|
|
1726
|
+
notifications.sms.send [{ product, env, notification, input: { recipients, body? } }]
|
|
1727
|
+
notifications.callback.send [{ product, env, notification, input: { query?, headers?, params?, body? } }]
|
|
1728
|
+
|
|
1729
|
+
Multi-channel send (all channels in parallel):
|
|
1730
|
+
notifications.send [{ product, env, event: "notif_tag:message_tag", input: { ... } }]
|
|
1731
|
+
→ success is true if at least one channel succeeds; channel failures do not abort siblings.
|
|
1732
|
+
|
|
1733
|
+
Background dispatch with scheduling:
|
|
1734
|
+
notifications.dispatch [{ product, env, notification, event, input,
|
|
1735
|
+
schedule?: { start_at?, cron?, every?, limit?, endDate?, tz? } }]
|
|
1736
|
+
→ Returns { job_id, status: "queued", scheduled_at, recurring, next_run_at }
|
|
1737
|
+
|
|
1738
|
+
Query delivery logs:
|
|
1739
|
+
notifications.getMessages [{ product_tag?, env?, notification_tag?, status?,
|
|
1740
|
+
type?, start_date?, end_date?, page?, limit? }]
|
|
1741
|
+
Status values: pending | sent | failed | reprocessing
|
|
1742
|
+
|
|
1743
|
+
Supported providers:
|
|
1744
|
+
Email: smtp | mailgun | sendgrid | postmark | brevo
|
|
1745
|
+
SMS: twilio | nexmo | plivo | other
|
|
1746
|
+
Push: firebase | expo
|
|
1747
|
+
Callback: any HTTP endpoint
|
|
1748
|
+
|
|
1749
|
+
Channel configuration (envs per notification) is done in the Workbench UI, not via CLI.
|
|
1750
|
+
Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
|
|
1751
|
+
`.trim(),
|
|
1752
|
+
resilience: `
|
|
1753
|
+
DUCTAPE RESILIENCE
|
|
1754
|
+
|
|
1755
|
+
Resilience covers three mechanisms: quotas (rate-limited provider pools), fallbacks (automatic
|
|
1756
|
+
provider switching), and healthchecks (continuous probe monitoring with failure actions).
|
|
1757
|
+
|
|
1758
|
+
QUOTAS — rate-limited multi-provider pools:
|
|
1759
|
+
ductape_execute("quotas.create", [product_tag, {
|
|
1760
|
+
tag: "sms-quota",
|
|
1761
|
+
name: "SMS Provider Pool",
|
|
1762
|
+
input: { to: { type: "string", required: true }, message: { type: "string" } },
|
|
1763
|
+
options: [
|
|
1764
|
+
{ provider: "twilio", app: "twilio-app", type: "action", event: "send-sms",
|
|
1765
|
+
quota: 1000, uses: 0, retries: 2,
|
|
1766
|
+
input: { "body:to": "$Input{to}", "body:message": "$Input{message}" },
|
|
1767
|
+
output: {} },
|
|
1768
|
+
{ provider: "nexmo", app: "nexmo-app", type: "action", event: "send-sms",
|
|
1769
|
+
quota: 500, uses: 0, retries: 1,
|
|
1770
|
+
input: { "body:to": "$Input{to}", "body:body": "$Input{message}" },
|
|
1771
|
+
output: {} },
|
|
1772
|
+
],
|
|
1773
|
+
}])
|
|
1774
|
+
Providers are tried in order until quota is not exhausted.
|
|
1775
|
+
quotas.run [{ product, env, tag, input }] ← CALL ductape_generate_payload FIRST
|
|
1776
|
+
quotas.dispatch [{ product, env, tag, input, schedule? }]
|
|
1777
|
+
|
|
1778
|
+
FALLBACKS — automatic provider switching on failure:
|
|
1779
|
+
Same schema as quotas but options are ordered: primary first, then fallback(s).
|
|
1780
|
+
Primary is used first; on failure, the next provider is tried automatically.
|
|
1781
|
+
ductape_execute("fallback.create", [product_tag, { tag, name, input: { ... }, options: [...] }])
|
|
1782
|
+
fallback.run [{ product, env, tag, input }] ← CALL ductape_generate_payload FIRST
|
|
1783
|
+
fallback.dispatch [{ product, env, tag, input, schedule? }]
|
|
1784
|
+
|
|
1785
|
+
HEALTHCHECKS — continuous probe with failure notifications:
|
|
1786
|
+
ductape_execute("health.create", [product_tag, {
|
|
1787
|
+
tag: "payment-health",
|
|
1788
|
+
name: "Payment Service Health",
|
|
1789
|
+
probe: { type: "app", app: "stripe-app", event: "ping" },
|
|
1790
|
+
interval: 30000, // ms between checks
|
|
1791
|
+
retries: 3,
|
|
1792
|
+
envs: [{ slug: "prd", input: {} }],
|
|
1793
|
+
onFailure: {
|
|
1794
|
+
notifications: [{ notification: "ops-alerts", message: "payment-down",
|
|
1795
|
+
channels: { email: { recipients: ["ops@example.com"] } } }],
|
|
1796
|
+
webhooks: [{ url: "https://hooks.example.com/alert", method: "POST" }],
|
|
1797
|
+
},
|
|
1798
|
+
}])
|
|
1799
|
+
health.run [{ product, env, tag }] → triggers an immediate probe
|
|
1800
|
+
health.check [{ product, env, tag }] → same as run
|
|
1801
|
+
health.status [{ product, env, tag }] → current health status
|
|
1802
|
+
|
|
1803
|
+
Probe types: app | database | feature | graph | message_broker | storage
|
|
1804
|
+
Failure actions: notification channels, HTTP webhooks, and/or message broker emit — all can
|
|
1805
|
+
be configured simultaneously on the same healthcheck.
|
|
1806
|
+
Input template references: $Input{field} → maps declared input to the probe's action input.
|
|
1807
|
+
Provider status: available | unavailable
|
|
1808
|
+
`.trim(),
|
|
1809
|
+
features: `
|
|
1810
|
+
DUCTAPE FEATURES
|
|
1811
|
+
|
|
1812
|
+
A feature is an orchestrated workflow of durable steps. Steps can call app actions, database
|
|
1813
|
+
operations, graph queries, storage uploads, notifications, broker publishes, child features,
|
|
1814
|
+
quotas, fallbacks, and more. Features support rollback, signals, checkpoints, and sleep.
|
|
1815
|
+
|
|
1816
|
+
Step types: action | database | graph | notification | storage | produce | quota | fallback |
|
|
1817
|
+
vector | child_feature | sleep | wait_for_signal | checkpoint
|
|
1818
|
+
|
|
1819
|
+
Create a feature (admin — ductape_execute):
|
|
1820
|
+
ductape_execute("features.create", [product_tag, {
|
|
1821
|
+
tag: "onboard-user",
|
|
1822
|
+
name: "Onboard User",
|
|
1823
|
+
input: { userId: { type: "string", required: true }, email: { type: "string" } },
|
|
1824
|
+
steps: [
|
|
1825
|
+
{
|
|
1826
|
+
tag: "create-account",
|
|
1827
|
+
type: "database",
|
|
1828
|
+
database: "core-db",
|
|
1829
|
+
event: "insert-user",
|
|
1830
|
+
input: { "body:userId": "$Input{userId}", "body:email": "$Input{email}" },
|
|
1831
|
+
options: { retries: 2, timeout: 5000 },
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
tag: "send-welcome",
|
|
1835
|
+
type: "notification",
|
|
1836
|
+
notification: "welcome-email",
|
|
1837
|
+
event: "welcome-email:default",
|
|
1838
|
+
input: { "body:email": "$Input{email}" },
|
|
1839
|
+
dependsOn: ["create-account"],
|
|
1840
|
+
options: { allow_fail: true }, // welcome email failure won't abort the feature
|
|
1841
|
+
},
|
|
1842
|
+
],
|
|
1843
|
+
}])
|
|
1844
|
+
|
|
1845
|
+
Execute at runtime:
|
|
1846
|
+
→ CALL ductape_generate_payload FIRST (operation_family="features", method="execute", targets={tag})
|
|
1847
|
+
features.execute [{ product, env, tag, input: { userId: "u_123", email: "..." },
|
|
1848
|
+
idempotency_key?: string, retries?: number, timeout?: number }]
|
|
1849
|
+
|
|
1850
|
+
Background dispatch with scheduling:
|
|
1851
|
+
features.dispatch [{ product, env, feature, input,
|
|
1852
|
+
schedule?: { start_at?, cron?, every?, limit?, endDate?, tz? } }]
|
|
1853
|
+
→ Returns { job_id, status: "queued"|"scheduled", scheduled_at, next_run_at }
|
|
1854
|
+
|
|
1855
|
+
Execution management:
|
|
1856
|
+
features.status [executionId] → { status, current_step, completed_steps, output, error }
|
|
1857
|
+
features.cancel [executionId, reason?] → triggers rollback of running steps
|
|
1858
|
+
features.replay [executionId, options?] → re-run with original input
|
|
1859
|
+
features.restart [executionId] → re-run with new input
|
|
1860
|
+
features.resume [executionId] → continue from a checkpoint
|
|
1861
|
+
features.replayFromStep [executionId, stepTag] → replay from a specific step
|
|
1862
|
+
features.history [executionId] → { events[], checkpoints[], replays[], restarts[] }
|
|
1863
|
+
features.stepDetail [executionId, stepTag] → per-step input/output/error/timing
|
|
1864
|
+
|
|
1865
|
+
Signals and queries (for long-running features):
|
|
1866
|
+
features.signal [{ product, env, feature_id, signal: "payment-confirmed", payload? }]
|
|
1867
|
+
features.query [{ product, env, feature_id, query: "current-status", params? }]
|
|
1868
|
+
|
|
1869
|
+
Step input references:
|
|
1870
|
+
$Input{field} → map from feature's declared input
|
|
1871
|
+
$Step{stepTag}{field} → output field from a prior step
|
|
1872
|
+
$StepOutput{field} → current step's own return value
|
|
1873
|
+
$Concat([...parts], delim) → string interpolation
|
|
1874
|
+
|
|
1875
|
+
Rollback strategies: reverse_all | reverse_critical | compensate | none
|
|
1876
|
+
Feature statuses: pending | running | completed | failed | rolled_back | rolling_back | paused
|
|
1877
|
+
|
|
1878
|
+
Code-first (define API — compiles async handler to JSON step schema):
|
|
1879
|
+
features.define({ tag, name, input, handler: async (ctx) => { ... } })
|
|
1880
|
+
ctx provides: ctx.step(), ctx.action.run(), ctx.database.query/insert(),
|
|
1881
|
+
ctx.graph.execute(), ctx.notification.send(), ctx.storage.upload(),
|
|
1882
|
+
ctx.messaging.produce(), ctx.quota.execute(), ctx.fallback.execute(),
|
|
1883
|
+
ctx.sleep(), ctx.waitForSignal(), ctx.setState/getState(), ctx.feature()
|
|
1884
|
+
`.trim(),
|
|
1885
|
+
events: `
|
|
1886
|
+
DUCTAPE EVENTS (MESSAGE BROKERS)
|
|
1887
|
+
|
|
1888
|
+
Supported broker types: kafka | rabbitmq | redis | aws_sqs | azure_servicebus | google_pubsub | nats
|
|
1889
|
+
|
|
1890
|
+
CLI aliases for the messageBrokers module: events, event, broker, brokers, message-brokers
|
|
1891
|
+
ductape_cli("resources events list <product_tag> --json")
|
|
1892
|
+
|
|
1893
|
+
Registration (admin — ductape_cli):
|
|
1894
|
+
Message brokers are IMPORT-ONLY (no provision-persist). Use import-persist-all:
|
|
1895
|
+
ductape_cli("cloud resources import-persist-all -f brokers.json --json")
|
|
1896
|
+
File is a JSON ARRAY — one entry per env.
|
|
1897
|
+
service identifiers: pubsub (GCP Pub/Sub) | sqs (AWS SQS) | servicebus (Azure Service Bus)
|
|
1898
|
+
type field: "messageBrokers" (not "messagebrokers" or "events")
|
|
1899
|
+
|
|
1900
|
+
After importing, create topics:
|
|
1901
|
+
ductape_execute("messageBrokers.topics.create", [product_tag, {
|
|
1902
|
+
tag: "user-events", name: "User Events", broker: "broker-tag",
|
|
1903
|
+
type: "producer"|"consumer"|"both",
|
|
1904
|
+
}])
|
|
1905
|
+
|
|
1906
|
+
Produce a message (runtime):
|
|
1907
|
+
→ CALL ductape_generate_payload FIRST (operation_family="messaging", method="produce")
|
|
1908
|
+
messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value } }]
|
|
1909
|
+
Idempotent publish:
|
|
1910
|
+
messageBrokers.publishIdempotent [{ product, env, event, message, idempotency_key, ttl? }]
|
|
1911
|
+
→ checks if key was already processed; returns cached result if so.
|
|
1912
|
+
|
|
1913
|
+
Consume a message (subscribe):
|
|
1914
|
+
messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag",
|
|
1915
|
+
callback: async (message) => { ... } }]
|
|
1916
|
+
Callback tracking is deferred (setImmediate) so user callback latency is unaffected.
|
|
1917
|
+
Callback errors are re-thrown so the broker can nack/retry.
|
|
1918
|
+
|
|
1919
|
+
Background dispatch with scheduling:
|
|
1920
|
+
messageBrokers.dispatch [{ product, env, broker, event, input: { message },
|
|
1921
|
+
schedule?: { start_at?, cron?, every?, limit?, endDate?, tz? } }]
|
|
1922
|
+
→ CALL ductape_generate_payload FIRST (operation_family="messaging", method="dispatch")
|
|
1923
|
+
|
|
1924
|
+
Event tracking and observability:
|
|
1925
|
+
messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
|
|
1926
|
+
messageBrokers.messages.getStats [{ product, env, brokerTag }]
|
|
1927
|
+
→ { total_events, success_count, failed_count, dead_letter_count, events_by_topic }
|
|
1928
|
+
messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
|
|
1929
|
+
messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, limit? }]
|
|
1930
|
+
messageBrokers.replayEvent [{ product, env, eventId, force? }]
|
|
1931
|
+
messageBrokers.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
1932
|
+
messageBrokers.checkIdempotency [{ product, env, brokerTag, idempotency_key }] → { exists, event_id? }
|
|
1933
|
+
|
|
1934
|
+
Event format string: "broker_tag:topic_tag" — always colon-separated; parsed by the SDK.
|
|
1935
|
+
Message payload is AES-encrypted before the tracking API call — tracking endpoint never sees plaintext.
|
|
1936
|
+
Connection pool: deduplicates live connections across BrokersService instances by workspace+product+config.
|
|
1937
|
+
`.trim(),
|
|
1938
|
+
logs: `
|
|
1939
|
+
DUCTAPE LOGS
|
|
1940
|
+
|
|
1941
|
+
Logs record every SDK operation — actions, features, database queries, notifications, sessions,
|
|
1942
|
+
brokers, storage, cache hits, graph, vector, and resilience events.
|
|
1943
|
+
|
|
1944
|
+
Fetch logs (runtime — ductape_execute):
|
|
1945
|
+
ductape_execute("logs.fetch", [{
|
|
1946
|
+
app_id?: string, // query by app (requires component: "app")
|
|
1947
|
+
product_id?: string, // query by product (requires component: "product")
|
|
1948
|
+
env?: string,
|
|
1949
|
+
start_date?: string, // ISO 8601
|
|
1950
|
+
end_date?: string,
|
|
1951
|
+
page?: number,
|
|
1952
|
+
limit?: number,
|
|
1953
|
+
}])
|
|
1954
|
+
component "app" or "product" is required — there is no default.
|
|
1955
|
+
When component="app", tag is only valid with type: "actions".
|
|
1956
|
+
|
|
1957
|
+
Log data fields (ILogData):
|
|
1958
|
+
process_id, product_tag, env, type (LogEventType), status, data (encrypted if private key set)
|
|
1959
|
+
message?, parent_tag?, child_tag?, app_id?, action?, method?,
|
|
1960
|
+
cache_tag?, cache_key?, cache_status (true = hit),
|
|
1961
|
+
start, end, latency (auto-calculated from start/end),
|
|
1962
|
+
session_user_id?, session_id?, session_tag?, visitor_id?,
|
|
1963
|
+
ip_address?, language?, data_encrypted?, successful_execution?, failed_execution?
|
|
1964
|
+
|
|
1965
|
+
Log event types (LogEventTypes):
|
|
1966
|
+
notifications | push | email | sms | callbacks | slack | discord |
|
|
1967
|
+
database_actions | actions | functions | storage | webhook | jobs |
|
|
1968
|
+
message_broker | producer | consumer | quota | fallback | database_migration |
|
|
1969
|
+
feature | feature_step | database | graph | session | vector | cache | frontend
|
|
1970
|
+
|
|
1971
|
+
Log statuses: success | fail | waiting | processing
|
|
1972
|
+
|
|
1973
|
+
Encryption:
|
|
1974
|
+
When workspace_private_key is provided at SDK init, each log entry's data field is
|
|
1975
|
+
AES-encrypted client-side before transmission. data_encrypted: true is set on the entry.
|
|
1976
|
+
The backend can decrypt when returning logs to the Workbench.
|
|
1977
|
+
|
|
1978
|
+
Emit a log manually (SDK only — not via MCP):
|
|
1979
|
+
logs.add({ process_id, product_tag, env, type, status, data, message?, ... })
|
|
1980
|
+
logs.publish() → flushes all buffered entries to the API in one call
|
|
1981
|
+
|
|
1982
|
+
Notes:
|
|
1983
|
+
- Every SDK service (storage, broker, feature, graph, notifications, sessions, vector, cache,
|
|
1984
|
+
resilience) emits its own logs automatically — manual add/publish is only needed for custom logs.
|
|
1985
|
+
- language defaults to "typescript" so the backend knows which SDK emitted the entry.
|
|
1986
|
+
- Logs are batched in memory and sent in a single publish() call per operation.
|
|
1987
|
+
`.trim(),
|
|
1988
|
+
};
|
|
1989
|
+
const docsHandler = async (args) => {
|
|
1990
|
+
const key = args.topic.toLowerCase().trim();
|
|
1991
|
+
const doc = DOCS[key];
|
|
1992
|
+
if (!doc) {
|
|
1993
|
+
const available = Object.keys(DOCS).join(', ');
|
|
1994
|
+
return {
|
|
1995
|
+
content: [{ type: 'text', text: `Unknown topic "${args.topic}". Available topics: ${available}` }],
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
return { content: [{ type: 'text', text: doc }] };
|
|
1999
|
+
};
|
|
917
2000
|
const cliInputSchema = z.object({
|
|
918
2001
|
command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
|
|
919
2002
|
'Examples: "products list", "products create --name \\"My Product\\" --tag my-product", ' +
|
|
@@ -1154,6 +2237,17 @@ async function main() {
|
|
|
1154
2237
|
'unless the chosen variant explicitly marks them required.',
|
|
1155
2238
|
inputSchema: schemaInputSchema,
|
|
1156
2239
|
}, schemaHandler);
|
|
2240
|
+
server.registerTool('ductape_docs', {
|
|
2241
|
+
title: 'Ductape Feature Docs',
|
|
2242
|
+
description: 'Look up detailed documentation for a specific Ductape SDK feature.\n\n' +
|
|
2243
|
+
'Call this when you need guidance on how a feature works before using it — ' +
|
|
2244
|
+
'especially for features that require configuration decisions (tier, isolation level, ' +
|
|
2245
|
+
'index strategy, operation types) that should be confirmed with the user first.\n\n' +
|
|
2246
|
+
'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
2247
|
+
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
2248
|
+
'notifications, resilience, features, events, logs',
|
|
2249
|
+
inputSchema: docsInputSchema,
|
|
2250
|
+
}, docsHandler);
|
|
1157
2251
|
server.registerTool('ductape_cli', {
|
|
1158
2252
|
title: 'Ductape CLI',
|
|
1159
2253
|
description: 'Run a Ductape CLI command for administrative operations.\n\n' +
|
|
@@ -1212,6 +2306,32 @@ async function main() {
|
|
|
1212
2306
|
' Supported service/component pairs include: rds→database, aurora→database, gcs→storage, s3→storage,\n' +
|
|
1213
2307
|
' azure-blob→storage, cloud-sql→database, neptune→graph, opensearch→vector. Attempting an unsupported pair\n' +
|
|
1214
2308
|
' will return an error; do not retry with a different tier — report the limitation to the user.\n' +
|
|
2309
|
+
' - Atlas (MongoDB Atlas) import flow — service identifier is "atlas-cluster" (required, not optional):\n' +
|
|
2310
|
+
' Step 1 — discover the cluster name:\n' +
|
|
2311
|
+
' ductape_cli("cloud resources list -f /tmp/atlas-list.json --json")\n' +
|
|
2312
|
+
' File: {"cloud": "<atlas-connection-tag>", "service": "atlas-cluster"}\n' +
|
|
2313
|
+
' Returns a list of clusters; note the "name" field (this is your resource identifier).\n' +
|
|
2314
|
+
' Step 2 — check if the database component already exists:\n' +
|
|
2315
|
+
' ductape_cli("resources databases list <product_tag> --json")\n' +
|
|
2316
|
+
' If a component already uses the same Atlas cluster, do NOT re-import — instead update it:\n' +
|
|
2317
|
+
' ductape_execute("databases.updateDatabase", [product_tag, db_tag, { envs: [...updated envs...] }])\n' +
|
|
2318
|
+
' Add or change the dbName in the env\'s connection_url to switch databases on the same cluster.\n' +
|
|
2319
|
+
' Step 3 — import (only if no existing component uses this cluster):\n' +
|
|
2320
|
+
' Use import-persist-all with one entry per product env. Required fields per entry:\n' +
|
|
2321
|
+
' cloud (connection tag), service: "atlas-cluster", type: "databases",\n' +
|
|
2322
|
+
' product, component (new tag), env, resource (cluster name from Step 1),\n' +
|
|
2323
|
+
' dbName (the MongoDB database name to connect to — required for Atlas).\n' +
|
|
2324
|
+
' Example: [{"cloud":"atlas-tag","service":"atlas-cluster","type":"databases",\n' +
|
|
2325
|
+
' "product":"my-product","component":"core-db","env":"snd","resource":"Cluster0","dbName":"myapp_snd"},\n' +
|
|
2326
|
+
' {"cloud":"atlas-tag","service":"atlas-cluster","type":"databases",\n' +
|
|
2327
|
+
' "product":"my-product","component":"core-db","env":"prd","resource":"Cluster0","dbName":"myapp_prd"}]\n' +
|
|
2328
|
+
' - Message broker / event broker import:\n' +
|
|
2329
|
+
' CLI accepts these aliases for the messageBrokers module: events, event, broker, brokers, message-brokers.\n' +
|
|
2330
|
+
' List existing brokers: ductape_cli("resources events list <product_tag> --json")\n' +
|
|
2331
|
+
' GCP Pub/Sub service identifier is "pubsub". AWS SQS is "sqs". Azure Service Bus is "servicebus".\n' +
|
|
2332
|
+
' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
|
|
2333
|
+
' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
|
|
2334
|
+
' After importing, create topics via: ductape_execute("messageBrokers.topics.create", [product_tag, data])\n' +
|
|
1215
2335
|
' - Listing workspaces, products, secrets\n' +
|
|
1216
2336
|
' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
|
|
1217
2337
|
' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
|
|
@@ -1230,6 +2350,7 @@ async function main() {
|
|
|
1230
2350
|
server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
|
|
1231
2351
|
server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
|
|
1232
2352
|
server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
|
|
2353
|
+
server.tool('ductape_docs', docsInputSchema.shape, docsHandler);
|
|
1233
2354
|
server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
|
|
1234
2355
|
}
|
|
1235
2356
|
else {
|