@ductape/mcp 0.1.49 → 0.1.51
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 +412 -130
- package/dist/proxy-client.d.ts +1 -0
- package/dist/proxy-client.d.ts.map +1 -1
- package/package.json +2 -1
- package/scripts/check-frontend-analytics-guidance.mjs +57 -0
- package/src/index.ts +434 -130
- package/src/proxy-client.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -932,7 +932,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
932
932
|
"storage.create", "storage.update"
|
|
933
933
|
|
|
934
934
|
When to use this tool:
|
|
935
|
-
- Before
|
|
935
|
+
- Before constructing an administrative asset file for ductape_cli or Workbench — use this to discover required fields
|
|
936
936
|
- To enumerate valid enum values (e.g. which DatabaseTypes or AuthTypes are accepted)
|
|
937
937
|
- To understand nested object shapes without reading SDK docs
|
|
938
938
|
`;
|
|
@@ -973,6 +973,9 @@ const payloadGenerateInputSchema = z.object({
|
|
|
973
973
|
'For messaging: { broker: "broker_tag", topic?: "topic_tag" }.'),
|
|
974
974
|
include_session: z.boolean().optional().default(true).describe('Include a session placeholder inside the generated input object. ' +
|
|
975
975
|
'The placeholder is named "<session_tag_token>" to indicate it expects the runtime JWT, not the tag name.'),
|
|
976
|
+
execution_context: z.enum(['user', 'delegated', 'system']).optional().default('user').describe('Actor intent for this runtime operation. "user" means an active request initiated by the authenticated user; ' +
|
|
977
|
+
'"delegated" means work acting on behalf of a user with a short-lived delegated identity or application-owned ' +
|
|
978
|
+
'immutable actor context; "system" means intentionally unattributed background work. This drives session warnings.'),
|
|
976
979
|
include_cache: z.boolean().optional().default(true).describe('Include the cache tag inside the generated input object so the caller knows which cache to reference for this query.'),
|
|
977
980
|
schema_mode: z.enum(['strict', 'best_effort']).optional().default('best_effort').describe('"strict" — fail if any required field cannot be resolved. ' +
|
|
978
981
|
'"best_effort" — fill what is known, leave unknowns as null/placeholder. Use best_effort when exploring.'),
|
|
@@ -1061,6 +1064,43 @@ function buildSdkInvocationArgs(payload) {
|
|
|
1061
1064
|
...(payload?.cache ? { cache: payload.cache } : {}),
|
|
1062
1065
|
};
|
|
1063
1066
|
}
|
|
1067
|
+
function operationAcceptsSession(operationFamily, method) {
|
|
1068
|
+
const family = operationFamily.toLowerCase();
|
|
1069
|
+
const sessionFamilies = new Set([
|
|
1070
|
+
'action', 'features', 'feature', 'database', 'graph', 'vector', 'storage',
|
|
1071
|
+
'notification', 'messaging', 'broker', 'events', 'event', 'quota', 'fallback',
|
|
1072
|
+
]);
|
|
1073
|
+
if (!sessionFamilies.has(family))
|
|
1074
|
+
return false;
|
|
1075
|
+
return !['consume', 'status', 'check', 'fetch', 'list'].includes(method.toLowerCase());
|
|
1076
|
+
}
|
|
1077
|
+
function addSessionAwarenessMetadata(generated, args) {
|
|
1078
|
+
const acceptsSession = operationAcceptsSession(args.operation_family, args.method);
|
|
1079
|
+
const executionContext = args.execution_context ?? 'user';
|
|
1080
|
+
const payloadHasSession = Boolean(generated?.payload?.session || generated?.payload?.input?.session);
|
|
1081
|
+
const sessionRequested = args.include_session !== false;
|
|
1082
|
+
const warnings = [];
|
|
1083
|
+
if (acceptsSession && executionContext !== 'system' && (!sessionRequested || !payloadHasSession)) {
|
|
1084
|
+
warnings.push(`Session attribution is missing for a ${executionContext}-context operation. ` +
|
|
1085
|
+
'Pass the original full session token for immediate user work, or an approved delegated identity/immutable actor context for delayed work.');
|
|
1086
|
+
}
|
|
1087
|
+
if (executionContext === 'system' && payloadHasSession) {
|
|
1088
|
+
warnings.push('This operation is marked system-context but contains a session placeholder. Remove it unless the work is actually user/delegated.');
|
|
1089
|
+
}
|
|
1090
|
+
generated.meta = {
|
|
1091
|
+
...(generated?.meta ?? {}),
|
|
1092
|
+
session_awareness: {
|
|
1093
|
+
accepts_session: acceptsSession,
|
|
1094
|
+
execution_context: executionContext,
|
|
1095
|
+
appears_user_context: executionContext === 'user',
|
|
1096
|
+
session_requested: sessionRequested,
|
|
1097
|
+
session_present_in_payload: payloadHasSession,
|
|
1098
|
+
intentionally_system_context: executionContext === 'system',
|
|
1099
|
+
warnings,
|
|
1100
|
+
},
|
|
1101
|
+
};
|
|
1102
|
+
return generated;
|
|
1103
|
+
}
|
|
1064
1104
|
function buildTypeScriptSnippet(payload, operationFamily, method) {
|
|
1065
1105
|
const callPath = resolveSdkCallPath(operationFamily, method);
|
|
1066
1106
|
const invocationArgs = buildSdkInvocationArgs(payload);
|
|
@@ -1192,7 +1232,7 @@ const docsInputSchema = z.object({
|
|
|
1192
1232
|
topic: z.string().describe('Feature topic to look up. Supported: ' +
|
|
1193
1233
|
'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
1194
1234
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
1195
|
-
'notifications, resilience, features, events, logs, frontend, client, react, vue'),
|
|
1235
|
+
'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue'),
|
|
1196
1236
|
});
|
|
1197
1237
|
const DOCS = {
|
|
1198
1238
|
frontend: `
|
|
@@ -1228,6 +1268,11 @@ SHARED APPLICATION LIFECYCLE
|
|
|
1228
1268
|
connectionState to render disconnected/reconnecting UI; do not create duplicate subscriptions.
|
|
1229
1269
|
7. Disconnect resource sessions and the root client when the owning application scope is torn down.
|
|
1230
1270
|
|
|
1271
|
+
PRODUCT ANALYTICS
|
|
1272
|
+
Frontend product analytics complements backend session propagation; neither replaces the other.
|
|
1273
|
+
Continue with ductape_docs({ topic: "frontend-analytics" }) for identity lifecycle, pageviews,
|
|
1274
|
+
custom events, privacy masking, hidden-state safety, trace correlation, and package-version checks.
|
|
1275
|
+
|
|
1231
1276
|
AUTHENTICATION AND SECURITY
|
|
1232
1277
|
- Use publishableKey in browser applications. Never ship workspace private keys or privileged
|
|
1233
1278
|
access keys in frontend bundles.
|
|
@@ -1400,33 +1445,9 @@ DUCTAPE DATABASE MIGRATIONS
|
|
|
1400
1445
|
|
|
1401
1446
|
Migrations are versioned SQL/NoSQL schema change scripts managed per database component.
|
|
1402
1447
|
|
|
1403
|
-
Create
|
|
1404
|
-
ductape_execute("databases.migration.create", [{
|
|
1405
|
-
product: "my-product",
|
|
1406
|
-
database: "core-db",
|
|
1407
|
-
data: {
|
|
1408
|
-
name: "add users table",
|
|
1409
|
-
tag: "001-add-users",
|
|
1410
|
-
value: {
|
|
1411
|
-
up: ["CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE)"],
|
|
1412
|
-
down: ["DROP TABLE users"],
|
|
1413
|
-
},
|
|
1414
|
-
},
|
|
1415
|
-
}])
|
|
1416
|
-
|
|
1417
|
-
Run migrations (applies all pending):
|
|
1418
|
-
ductape_execute("databases.migration.run", [migrations, { env: "prd" }])
|
|
1448
|
+
Create migrations through the project migration files and ductape_cli, never ductape_execute.
|
|
1419
1449
|
|
|
1420
|
-
|
|
1421
|
-
ductape_execute("databases.migration.rollback", [migrations, 1]) // roll back 1
|
|
1422
|
-
|
|
1423
|
-
Check status:
|
|
1424
|
-
ductape_execute("databases.migration.status", [migrations])
|
|
1425
|
-
|
|
1426
|
-
History:
|
|
1427
|
-
ductape_execute("databases.migration.history", [])
|
|
1428
|
-
|
|
1429
|
-
Via CLI:
|
|
1450
|
+
Use the access-key administrative CLI for running, rolling back, and inspecting migrations:
|
|
1430
1451
|
ductape_cli("db migrate") // run pending
|
|
1431
1452
|
ductape_cli("db migrate rollback") // roll back last
|
|
1432
1453
|
ductape_cli("db migrate rollback -n 3")
|
|
@@ -1436,25 +1457,13 @@ MongoDB: migrations run as raw Mongo shell commands in the up/down arrays.
|
|
|
1436
1457
|
indexes: `
|
|
1437
1458
|
DUCTAPE DATABASE INDEXES
|
|
1438
1459
|
|
|
1439
|
-
Create
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
{ unique: true, name: "idx_email_unique" } // options (optional)
|
|
1444
|
-
])
|
|
1445
|
-
|
|
1446
|
-
// Ordered index:
|
|
1447
|
-
ductape_execute("databases.schema.createIndex", [
|
|
1448
|
-
"orders",
|
|
1449
|
-
[{ field: "createdAt", order: "DESC" }, { field: "userId", order: "ASC" }],
|
|
1450
|
-
{ name: "idx_orders_date_user" }
|
|
1451
|
-
])
|
|
1460
|
+
Create or update indexes through migration files and ductape_cli("db migrate"), not the
|
|
1461
|
+
publishable-key runtime proxy. Example definitions may use ["field1", "field2"] with
|
|
1462
|
+
{ unique: true, name: "idx_email_unique" }, or ordered fields such as
|
|
1463
|
+
[{ field: "createdAt", order: "DESC" }, { field: "userId", order: "ASC" }].
|
|
1452
1464
|
|
|
1453
|
-
Drop
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
List indexes on a collection:
|
|
1457
|
-
ductape_execute("databases.schema.indexes", ["collection_name"])
|
|
1465
|
+
Drop and inspect indexes through migration/CLI tooling or the database administration surface,
|
|
1466
|
+
not ductape_execute.
|
|
1458
1467
|
|
|
1459
1468
|
Performance guidance:
|
|
1460
1469
|
- Index fields used in WHERE clauses, JOIN conditions, and ORDER BY.
|
|
@@ -1498,18 +1507,7 @@ DUCTAPE DATABASE ACTIONS
|
|
|
1498
1507
|
A database action is a saved query or mutation (SQL string or NoSQL command) stored
|
|
1499
1508
|
on the Ductape product and executed by tag at runtime.
|
|
1500
1509
|
|
|
1501
|
-
Create
|
|
1502
|
-
ductape_execute("databases.action.create", [{
|
|
1503
|
-
product: "my-product",
|
|
1504
|
-
database: "core-db",
|
|
1505
|
-
data: {
|
|
1506
|
-
tag: "get-active-users",
|
|
1507
|
-
name: "Get active users",
|
|
1508
|
-
description: "Returns all users with status=active",
|
|
1509
|
-
type: "sql", // "sql" or "nosql"
|
|
1510
|
-
query: "SELECT * FROM users WHERE status = :status",
|
|
1511
|
-
},
|
|
1512
|
-
}])
|
|
1510
|
+
Create/update actions in Workbench (administrative access), never through ductape_execute.
|
|
1513
1511
|
|
|
1514
1512
|
Dispatch an action at runtime:
|
|
1515
1513
|
→ CALL ductape_generate_payload FIRST to get the canonical input shape.
|
|
@@ -1524,10 +1522,9 @@ Dispatch an action at runtime:
|
|
|
1524
1522
|
List actions for a database:
|
|
1525
1523
|
ductape_execute("databases.action.list", ["database_tag"])
|
|
1526
1524
|
|
|
1527
|
-
Fetch
|
|
1525
|
+
Fetch:
|
|
1528
1526
|
ductape_execute("databases.action.fetch", ["action_tag"])
|
|
1529
|
-
|
|
1530
|
-
ductape_execute("databases.action.delete", ["action_tag"])
|
|
1527
|
+
Update/delete are administrative and must be performed in Workbench.
|
|
1531
1528
|
|
|
1532
1529
|
Actions are the preferred way to encapsulate complex or reused queries — they can be
|
|
1533
1530
|
scheduled, dispatched with retries, and audited via logs.
|
|
@@ -1785,16 +1782,8 @@ DUCTAPE SECRETS
|
|
|
1785
1782
|
Secrets are workspace-level encrypted key-value pairs. They are referenced in resource configs,
|
|
1786
1783
|
connection URLs, and any string field using the $Secret{KEY_NAME} syntax.
|
|
1787
1784
|
|
|
1788
|
-
Create
|
|
1789
|
-
|
|
1790
|
-
key: "STRIPE_API_KEY",
|
|
1791
|
-
value: "sk_live_...", // plaintext — encrypted AES-256-GCM client-side before send
|
|
1792
|
-
description: "Stripe live key",
|
|
1793
|
-
token_type: "api", // "api" | "password" | "certificate"
|
|
1794
|
-
scope: ["my-product"], // which products can read this secret
|
|
1795
|
-
envs: ["prd"], // which env slugs can read this secret
|
|
1796
|
-
expires_at: 1800000000, // optional epoch ms
|
|
1797
|
-
}])
|
|
1785
|
+
Create/update secrets with ductape_cli or Workbench (administrative access), never ductape_execute.
|
|
1786
|
+
Definition fields include key, value, description, token_type, scope, envs, and expires_at.
|
|
1798
1787
|
The server never receives the plaintext value. Encryption uses the workspace private key.
|
|
1799
1788
|
|
|
1800
1789
|
Fetch / resolve:
|
|
@@ -1809,10 +1798,7 @@ $Secret{} reference syntax:
|
|
|
1809
1798
|
- The in-memory cache stores the encrypted form only; decryption happens on each cache hit.
|
|
1810
1799
|
- Cache TTL: 5 minutes. Clear with: secrets.clearCache()
|
|
1811
1800
|
|
|
1812
|
-
Lifecycle:
|
|
1813
|
-
ductape_execute("secrets.revoke", ["KEY"]) → disables without deleting (recoverable)
|
|
1814
|
-
ductape_execute("secrets.delete", ["KEY"]) → permanent deletion (irreversible)
|
|
1815
|
-
ductape_execute("secrets.update", ["KEY", { value: "new_value", expires_at: ... }])
|
|
1801
|
+
Lifecycle mutations (revoke/delete/update) are administrative: use ductape_cli or Workbench.
|
|
1816
1802
|
|
|
1817
1803
|
List all secrets (keys only — values are not returned in list):
|
|
1818
1804
|
ductape_execute("secrets.list", [])
|
|
@@ -1862,9 +1848,7 @@ Import from a file:
|
|
|
1862
1848
|
ductape_cli("apps import <file.json> -t postman|openapi")
|
|
1863
1849
|
Supports Postman v2.1 collection and OpenAPI 3.0 spec.
|
|
1864
1850
|
|
|
1865
|
-
Manage environments (base URLs per stage)
|
|
1866
|
-
ductape_execute("app.environments.create", [app_tag, { slug, env_name, base_url }])
|
|
1867
|
-
ductape_execute("app.environments.list", [app_tag])
|
|
1851
|
+
Manage environments (base URLs per stage) in Workbench; this currently has no CLI command.
|
|
1868
1852
|
|
|
1869
1853
|
Discover apps in a product and their actions (ALWAYS do this before writing any ctx.api.run call):
|
|
1870
1854
|
Step 1 — list apps connected to the product:
|
|
@@ -1881,16 +1865,8 @@ Discover apps in a product and their actions (ALWAYS do this before writing any
|
|
|
1881
1865
|
app resources (not for runtime input — use actions.fetch or ductape_generate_payload for that)
|
|
1882
1866
|
NEVER assume action input field names. Always fetch the action definition first.
|
|
1883
1867
|
|
|
1884
|
-
Manage actions (individual API endpoints)
|
|
1885
|
-
|
|
1886
|
-
tag, name, resource: "/users/{id}", method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE",
|
|
1887
|
-
body?: { fieldName: { type, required? } },
|
|
1888
|
-
params?: { id: { type: "string" } },
|
|
1889
|
-
query?: { filter: { type: "string" } },
|
|
1890
|
-
headers?: { Authorization: { type: "string" } },
|
|
1891
|
-
response?: { status_code: 200, success: true, body: { ... }, response_format: "json" },
|
|
1892
|
-
}])
|
|
1893
|
-
ductape_execute("actions.update", [app_tag, action_tag, data])
|
|
1868
|
+
Manage actions (individual API endpoints) in Workbench or import an OpenAPI/Postman file.
|
|
1869
|
+
Creation/update are administrative and must never use ductape_execute.
|
|
1894
1870
|
ductape_execute("actions.list", [app_tag])
|
|
1895
1871
|
ductape_execute("actions.fetch", [app_tag, action_tag])
|
|
1896
1872
|
|
|
@@ -1911,16 +1887,14 @@ Run an action at runtime:
|
|
|
1911
1887
|
|
|
1912
1888
|
Auth schemes (how the app authenticates outbound requests):
|
|
1913
1889
|
Setup types: header | bearer | basic | oauth2 | apikey
|
|
1914
|
-
|
|
1890
|
+
Configure auth in Workbench (administrative).
|
|
1915
1891
|
ductape_execute("auths.list", [app_tag])
|
|
1916
1892
|
|
|
1917
1893
|
Webhooks (inbound events from the external service):
|
|
1918
|
-
|
|
1919
|
-
ductape_execute("webhooks.events.create", [app_tag, { tag, name, selector, description, sample }])
|
|
1894
|
+
Configure webhooks and webhook events in Workbench (administrative).
|
|
1920
1895
|
|
|
1921
1896
|
Variables (per-env mutable values) and Constants (fixed values):
|
|
1922
|
-
|
|
1923
|
-
ductape_execute("app.constants.create", [app_tag, { key, value }])
|
|
1897
|
+
Configure variables and constants in Workbench (administrative).
|
|
1924
1898
|
|
|
1925
1899
|
Connecting an app to a product (after creation):
|
|
1926
1900
|
NOTE: All product.* module methods require the access key and CANNOT use ductape_execute.
|
|
@@ -1928,14 +1902,13 @@ Connecting an app to a product (after creation):
|
|
|
1928
1902
|
|
|
1929
1903
|
FULL FLOW to make an app callable from a product:
|
|
1930
1904
|
1. Create the app: ductape_cli("apps create --name \\"Service Name\\" --description \\"...\\"")
|
|
1931
|
-
2. Add environments:
|
|
1932
|
-
3. Configure auth:
|
|
1933
|
-
4. Define actions:
|
|
1905
|
+
2. Add environments: Workbench
|
|
1906
|
+
3. Configure auth: Workbench
|
|
1907
|
+
4. Define actions: Workbench
|
|
1934
1908
|
OR import: ductape_cli("apps import <file.json> -t postman|openapi")
|
|
1935
1909
|
5. Connect to product: Requires the product_id (from ductape_cli("products get --tag <tag> --json")).
|
|
1936
1910
|
There is no CLI command for this step — the SDK product.apps.add method requires
|
|
1937
|
-
an access key which only the backend can provide.
|
|
1938
|
-
admin-authenticated context, or connect via the Workbench UI.
|
|
1911
|
+
an access key which only the backend can provide. Connect via Workbench.
|
|
1939
1912
|
6. Verify: ductape_cli("products get --tag <product_tag> --json") → check apps[] contains the app
|
|
1940
1913
|
ductape_execute("actions.list", [app_tag]) → verify actions are registered
|
|
1941
1914
|
`.trim(),
|
|
@@ -1973,6 +1946,7 @@ Resource registration (all via ductape_cli or ductape_execute — see per-topic
|
|
|
1973
1946
|
caches → ductape_docs({ topic: "caches" })
|
|
1974
1947
|
notifications → ductape_docs({ topic: "notifications" })
|
|
1975
1948
|
sessions → ductape_docs({ topic: "sessions" })
|
|
1949
|
+
frontend analytics → ductape_docs({ topic: "frontend-analytics" })
|
|
1976
1950
|
resilience → ductape_docs({ topic: "resilience" })
|
|
1977
1951
|
features → ductape_docs({ topic: "features" })
|
|
1978
1952
|
|
|
@@ -2009,8 +1983,8 @@ IMPORTANT — selector must be "$Session{fieldName}" format:
|
|
|
2009
1983
|
CORRECT: selector: "$Session{playerId}"
|
|
2010
1984
|
INCORRECT: selector: "playerId" ← WILL FAIL with "Selector should be in the format $Session{...}{key}"
|
|
2011
1985
|
|
|
2012
|
-
Example (
|
|
2013
|
-
|
|
1986
|
+
Example definition (configure via declarative apply or Workbench, not ductape_execute):
|
|
1987
|
+
{
|
|
2014
1988
|
tag: "player-session",
|
|
2015
1989
|
name: "Player Session",
|
|
2016
1990
|
expiry: 24,
|
|
@@ -2022,7 +1996,7 @@ Example (game product — player identity in JWT):
|
|
|
2022
1996
|
role: "player",
|
|
2023
1997
|
accountId: "acct_xyz",
|
|
2024
1998
|
},
|
|
2025
|
-
}
|
|
1999
|
+
}
|
|
2026
2000
|
|
|
2027
2001
|
Runtime — create a session (sign a JWT):
|
|
2028
2002
|
→ CALL ductape_generate_payload FIRST (operation_family="session", method="start", targets={tag})
|
|
@@ -2060,20 +2034,51 @@ Analytics:
|
|
|
2060
2034
|
|
|
2061
2035
|
Token format: "session_tag:jwt_token" — pass this format verbatim wherever session is expected.
|
|
2062
2036
|
JWT is signed with the product private key (not a symmetric shared secret).
|
|
2037
|
+
|
|
2038
|
+
SESSION PROPAGATION
|
|
2039
|
+
Extract the original full "session_tag:jwt" token once at the HTTP/transport boundary and retain
|
|
2040
|
+
it separately from verified/decoded claims. Claims are authorization data; they are not a token.
|
|
2041
|
+
Store a request ActorContext in a NestJS request-scoped provider or AsyncLocalStorage:
|
|
2042
|
+
type ActorContext =
|
|
2043
|
+
| { kind: "user"; session: string; actorId: string }
|
|
2044
|
+
| { kind: "delegated"; delegatedIdentity: string; actorId: string; initiatedAt: string }
|
|
2045
|
+
| { kind: "system"; reason: string };
|
|
2046
|
+
Pass ActorContext explicitly through service boundaries. At each immediate Ductape runtime call,
|
|
2047
|
+
pass actor.kind === "user" ? actor.session : the SDK-approved delegated identity. Put only
|
|
2048
|
+
non-secret actor metadata (actorId, kind, initiatedAt, correlationId) in Event envelopes.
|
|
2049
|
+
Feature execution should receive the same explicit actor classification. Never log, serialize
|
|
2050
|
+
into business payloads, or attach the raw session/refresh token to traces or error messages.
|
|
2051
|
+
|
|
2052
|
+
SECURITY BOUNDARY FOR DURABLE WORK
|
|
2053
|
+
The current SDK accepts a session string on dispatch, but it does not expose an immutable
|
|
2054
|
+
attribution snapshot API, an expired-token attribution contract, or a general delegated-session
|
|
2055
|
+
issuer. Therefore do NOT persist reusable JWTs indefinitely or assume an expired initiating token
|
|
2056
|
+
will remain valid when delayed work executes. Pass the full token only for immediate work within
|
|
2057
|
+
its validity window. For delayed/recurring work, classify execution as system-context, or use an
|
|
2058
|
+
application-owned immutable actor-context envelope plus a short-lived delegated identity issued
|
|
2059
|
+
by an explicitly approved auth design. Until the SDK provides that design, actor metadata is for
|
|
2060
|
+
audit/correlation and must not be treated as authorization.
|
|
2061
|
+
|
|
2062
|
+
Distinguish contexts deliberately:
|
|
2063
|
+
user — active request; original valid full token is required when the operation accepts it
|
|
2064
|
+
delegated — delayed/on-behalf-of work; approved short-lived credential + immutable actor metadata
|
|
2065
|
+
system — scheduler/maintenance with no user authority; omit session and record a reason
|
|
2066
|
+
|
|
2067
|
+
Backend propagation attributes component activity to an actor, but it does not record frontend
|
|
2068
|
+
pageviews, navigation, UI intent, funnels, or client failures. Continue immediately with
|
|
2069
|
+
ductape_docs({ topic: "frontend-analytics" }); both layers are required for a complete picture.
|
|
2063
2070
|
`.trim(),
|
|
2064
2071
|
caches: `
|
|
2065
2072
|
DUCTAPE CACHES
|
|
2066
2073
|
|
|
2067
2074
|
Caches are product-level Redis (or in-memory) stores for temporary key-value data with optional TTL.
|
|
2068
2075
|
|
|
2069
|
-
Registration (admin — ductape_cli
|
|
2076
|
+
Registration (admin — ductape_cli):
|
|
2070
2077
|
ductape_cli("resources caches create -f cache.json")
|
|
2071
2078
|
File: { name, tag, description?, expiry: <milliseconds> }
|
|
2072
2079
|
No type or envs — Ductape manages the store infrastructure.
|
|
2073
2080
|
expiry is in MILLISECONDS: 3600000 = 1 hour, 86400000 = 1 day, 604800000 = 1 week.
|
|
2074
2081
|
|
|
2075
|
-
SDK: ductape_execute("caches.create", [product_tag, { name, tag, description?, expiry: 3600000 }])
|
|
2076
|
-
|
|
2077
2082
|
Operations:
|
|
2078
2083
|
caches.set [{ product, cache, key, value: string, expiry?: string (ISO 8601), env }]
|
|
2079
2084
|
expiry is an ABSOLUTE TIMESTAMP (not a duration).
|
|
@@ -2121,20 +2126,20 @@ DUCTAPE NOTIFICATIONS
|
|
|
2121
2126
|
|
|
2122
2127
|
Notifications send messages across multiple channels: email, SMS, push, or HTTP callback.
|
|
2123
2128
|
|
|
2124
|
-
Create a notification
|
|
2125
|
-
|
|
2129
|
+
Create a notification through declarative apply or Workbench (administrative), using:
|
|
2130
|
+
{
|
|
2126
2131
|
tag: "welcome-email",
|
|
2127
2132
|
name: "Welcome Email",
|
|
2128
2133
|
type: "email", // optional hint; actual channels configured per env
|
|
2129
|
-
}
|
|
2134
|
+
}
|
|
2130
2135
|
|
|
2131
|
-
Create a message template:
|
|
2132
|
-
|
|
2136
|
+
Create a message template through declarative apply or Workbench:
|
|
2137
|
+
{
|
|
2133
2138
|
tag: "welcome-email:default", // format: "notification_tag:message_tag"
|
|
2134
2139
|
notification: "welcome-email",
|
|
2135
2140
|
subject: { template: "Welcome, {{name}}!", data: { name: "" } },
|
|
2136
2141
|
body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
|
|
2137
|
-
}
|
|
2142
|
+
}
|
|
2138
2143
|
|
|
2139
2144
|
Send at runtime (one channel at a time):
|
|
2140
2145
|
→ CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
|
|
@@ -2172,8 +2177,15 @@ DUCTAPE RESILIENCE
|
|
|
2172
2177
|
Resilience covers three mechanisms: quotas (rate-limited provider pools), fallbacks (automatic
|
|
2173
2178
|
provider switching), and healthchecks (continuous probe monitoring with failure actions).
|
|
2174
2179
|
|
|
2180
|
+
CONFIGURATION BOUNDARY
|
|
2181
|
+
Quotas, fallbacks, and health checks are administrative product configuration. Configure them in
|
|
2182
|
+
Workbench (or a future access-key administrative tool explicitly documented for the asset).
|
|
2183
|
+
Never route administrative create/update methods through ductape_execute: its publishable-key
|
|
2184
|
+
runtime proxy will fail.
|
|
2185
|
+
|
|
2175
2186
|
QUOTAS — rate-limited multi-provider pools:
|
|
2176
|
-
|
|
2187
|
+
Workbench definition shape:
|
|
2188
|
+
{
|
|
2177
2189
|
tag: "sms-quota",
|
|
2178
2190
|
name: "SMS Provider Pool",
|
|
2179
2191
|
input: { to: { type: "string", required: true }, message: { type: "string" } },
|
|
@@ -2187,7 +2199,7 @@ QUOTAS — rate-limited multi-provider pools:
|
|
|
2187
2199
|
input: { "body:to": "$Input{to}", "body:body": "$Input{message}" },
|
|
2188
2200
|
output: {} },
|
|
2189
2201
|
],
|
|
2190
|
-
}
|
|
2202
|
+
}
|
|
2191
2203
|
Providers are tried in order until quota is not exhausted.
|
|
2192
2204
|
quotas.run [{ product, env, tag, input }] ← CALL ductape_generate_payload FIRST
|
|
2193
2205
|
quotas.dispatch [{ product, env, tag, input, schedule? }]
|
|
@@ -2195,12 +2207,13 @@ QUOTAS — rate-limited multi-provider pools:
|
|
|
2195
2207
|
FALLBACKS — automatic provider switching on failure:
|
|
2196
2208
|
Same schema as quotas but options are ordered: primary first, then fallback(s).
|
|
2197
2209
|
Primary is used first; on failure, the next provider is tried automatically.
|
|
2198
|
-
|
|
2210
|
+
Configure in Workbench: { tag, name, input: { ... }, options: [...] }
|
|
2199
2211
|
fallback.run [{ product, env, tag, input }] ← CALL ductape_generate_payload FIRST
|
|
2200
2212
|
fallback.dispatch [{ product, env, tag, input, schedule? }]
|
|
2201
2213
|
|
|
2202
2214
|
HEALTHCHECKS — continuous probe with failure notifications:
|
|
2203
|
-
|
|
2215
|
+
Workbench definition shape:
|
|
2216
|
+
{
|
|
2204
2217
|
tag: "payment-health",
|
|
2205
2218
|
name: "Payment Service Health",
|
|
2206
2219
|
probe: { type: "app", app: "stripe-app", event: "ping" },
|
|
@@ -2212,7 +2225,7 @@ HEALTHCHECKS — continuous probe with failure notifications:
|
|
|
2212
2225
|
channels: { email: { recipients: ["ops@example.com"] } } }],
|
|
2213
2226
|
webhooks: [{ url: "https://hooks.example.com/alert", method: "POST" }],
|
|
2214
2227
|
},
|
|
2215
|
-
}
|
|
2228
|
+
}
|
|
2216
2229
|
health.run [{ product, env, tag }] → triggers an immediate probe
|
|
2217
2230
|
health.check [{ product, env, tag }] → same as run
|
|
2218
2231
|
health.status [{ product, env, tag }] → current health status
|
|
@@ -2222,6 +2235,239 @@ Failure actions: notification channels, HTTP webhooks, and/or message broker emi
|
|
|
2222
2235
|
be configured simultaneously on the same healthcheck.
|
|
2223
2236
|
Input template references: $Input{field} → maps declared input to the probe's action input.
|
|
2224
2237
|
Provider status: available | unavailable
|
|
2238
|
+
|
|
2239
|
+
DECISION MATRIX
|
|
2240
|
+
Rate/capacity allocation across providers → quota
|
|
2241
|
+
Equivalent provider after operational failure → fallback
|
|
2242
|
+
Detect failure before routing provider traffic → health check
|
|
2243
|
+
Transient failure of one operation → bounded retry + idempotency policy
|
|
2244
|
+
Multi-step business recovery/compensation → Feature
|
|
2245
|
+
Database atomicity → database transaction, not Feature rollback
|
|
2246
|
+
Scheduled single operation → that component's dispatch
|
|
2247
|
+
Scheduled multi-step process → Feature dispatch
|
|
2248
|
+
|
|
2249
|
+
COMBINING MECHANISMS
|
|
2250
|
+
A health check may keep an unhealthy provider out of a fallback/quota pool; the pool controls
|
|
2251
|
+
provider selection; each operation may use bounded retries and an idempotency key; a Feature
|
|
2252
|
+
coordinates business steps and compensation around those resilient operations. Do not stack them
|
|
2253
|
+
reflexively: each layer needs a distinct failure it owns, bounded retry budgets, and observable
|
|
2254
|
+
terminal behavior. Database transactions remain the atomicity boundary for related DB writes.
|
|
2255
|
+
`.trim(),
|
|
2256
|
+
'frontend-analytics': `
|
|
2257
|
+
DUCTAPE FRONTEND PRODUCT ANALYTICS AND SESSION ACTIVITY
|
|
2258
|
+
|
|
2259
|
+
Frontend analytics and backend session attribution are complementary; neither replaces the other.
|
|
2260
|
+
|
|
2261
|
+
BACKEND OPERATION ATTRIBUTION
|
|
2262
|
+
Pass the original full Ductape session token to every user-initiated database, Event, Feature,
|
|
2263
|
+
notification, storage, graph, vector, action, and other runtime operation that accepts session.
|
|
2264
|
+
This attributes server work to the authenticated actor. Durable work should retain the initiating
|
|
2265
|
+
session; intentional system/background work should remain sessionless.
|
|
2266
|
+
|
|
2267
|
+
FRONTEND PRODUCT ANALYTICS
|
|
2268
|
+
Use the browser Analytics service for anonymous visits, authenticated page views, navigation,
|
|
2269
|
+
UI interactions, funnels, client errors, realtime state, and product-surface engagement.
|
|
2270
|
+
|
|
2271
|
+
IMPORTANT:
|
|
2272
|
+
Passing session to backend component calls does not replace frontend analytics.
|
|
2273
|
+
Calling analytics.identify(sessionToken) and tracking frontend events does not replace backend
|
|
2274
|
+
session propagation. A complete activity picture requires both.
|
|
2275
|
+
|
|
2276
|
+
VERSION AND DOCUMENTATION PRECEDENCE
|
|
2277
|
+
1. Installed package types and exports determine what can be called now.
|
|
2278
|
+
2. Version-matched package documentation explains intended usage.
|
|
2279
|
+
3. Current docs/docs/Frontend describes the latest supported design.
|
|
2280
|
+
4. If they disagree, report the mismatch; never fabricate compatibility.
|
|
2281
|
+
|
|
2282
|
+
Inspect package.json plus package exports/type declarations before recommending framework hooks.
|
|
2283
|
+
The current repository exports useAnalytics from @ductape/react and @ductape/vue. Their installed
|
|
2284
|
+
hook/composable exposes track, pageview, identify, visitorId, enableAutoCapture, and flush.
|
|
2285
|
+
clearSession and disableAutoCapture are available on client.analytics but are not currently
|
|
2286
|
+
returned by those framework wrappers. Use useDuctape().client.analytics for those calls, or
|
|
2287
|
+
recommend a package version that exports them; do not generate a hook method that does not exist.
|
|
2288
|
+
|
|
2289
|
+
CLIENT API — IDENTIFY AFTER AUTHENTICATION
|
|
2290
|
+
ductape.analytics.identify(sessionToken);
|
|
2291
|
+
|
|
2292
|
+
sessionToken is the complete value returned by Ductape, in "player-session:jwt" format.
|
|
2293
|
+
It is NOT a player ID, session tag, session ID, decoded claims, or refresh token.
|
|
2294
|
+
identify links subsequent frontend analytics to the authenticated Ductape session and lets
|
|
2295
|
+
supported analytics correlate anonymous pre-login activity with authenticated activity.
|
|
2296
|
+
|
|
2297
|
+
LOGOUT / ACCOUNT SWITCHING
|
|
2298
|
+
await ductape.analytics.flush();
|
|
2299
|
+
ductape.analytics.clearSession();
|
|
2300
|
+
|
|
2301
|
+
Clear analytics identity when logout or revocation succeeds, refresh fails irrecoverably, local
|
|
2302
|
+
authentication is removed, or a different user is about to authenticate in the same browser.
|
|
2303
|
+
Removing only the application token can leave later anonymous/next-user events associated with
|
|
2304
|
+
the previous analytics identity. Flush is best-effort; browser shutdown does not guarantee it.
|
|
2305
|
+
|
|
2306
|
+
CUSTOM EVENTS
|
|
2307
|
+
await ductape.analytics.track({
|
|
2308
|
+
event: 'order_submitted',
|
|
2309
|
+
traceId,
|
|
2310
|
+
properties: { matchId, orderType },
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
IAnalyticsTrackOptions:
|
|
2314
|
+
event: string
|
|
2315
|
+
properties?: Record<string, unknown>
|
|
2316
|
+
session?: string
|
|
2317
|
+
product?: string
|
|
2318
|
+
env?: string
|
|
2319
|
+
traceId?: string
|
|
2320
|
+
context?: { url?, path?, referrer?, locale?, screen?: { width, height } }
|
|
2321
|
+
|
|
2322
|
+
identify establishes the default analytics session. An individual event may explicitly provide
|
|
2323
|
+
session. Normally use product/env from client configuration. traceId correlates frontend intent
|
|
2324
|
+
with backend logs, Events, Features, and order processing.
|
|
2325
|
+
|
|
2326
|
+
PAGE VIEWS
|
|
2327
|
+
await ductape.analytics.pageview({
|
|
2328
|
+
path: location.pathname,
|
|
2329
|
+
title: document.title,
|
|
2330
|
+
properties: { matchId, screen: 'governance' },
|
|
2331
|
+
});
|
|
2332
|
+
|
|
2333
|
+
IAnalyticsPageviewOptions:
|
|
2334
|
+
path?: string
|
|
2335
|
+
title?: string
|
|
2336
|
+
session?: string
|
|
2337
|
+
product?: string
|
|
2338
|
+
env?: string
|
|
2339
|
+
properties?: Record<string, unknown>
|
|
2340
|
+
|
|
2341
|
+
AUTO-CAPTURE — OPT IN ONLY AFTER A PRIVACY AUDIT
|
|
2342
|
+
const stopAutoCapture = ductape.analytics.enableAutoCapture({
|
|
2343
|
+
session: () => authSession?.token,
|
|
2344
|
+
clicks: false,
|
|
2345
|
+
pageviews: true,
|
|
2346
|
+
maskTextSelectors: [
|
|
2347
|
+
'[data-private]',
|
|
2348
|
+
'[data-secret]',
|
|
2349
|
+
'[data-player-message]',
|
|
2350
|
+
'[data-intelligence-report]',
|
|
2351
|
+
],
|
|
2352
|
+
});
|
|
2353
|
+
stopAutoCapture(); // or ductape.analytics.disableAutoCapture()
|
|
2354
|
+
|
|
2355
|
+
Mark sensitive UI with data-private/data-secret attributes. Text masking may not mask attributes,
|
|
2356
|
+
IDs, URLs, element names, custom properties, console errors, or network errors. Inspect actual
|
|
2357
|
+
payloads before production. For hidden-information products, begin with reviewed automatic
|
|
2358
|
+
pageviews, clicks disabled, and preferred custom named events.
|
|
2359
|
+
|
|
2360
|
+
VISITOR ID
|
|
2361
|
+
const visitorId = ductape.analytics.getVisitorId();
|
|
2362
|
+
A visitor ID is anonymous analytics identity, not authentication or authorization.
|
|
2363
|
+
|
|
2364
|
+
FRONTEND SESSION LIFECYCLE
|
|
2365
|
+
Before login:
|
|
2366
|
+
- Track anonymous pageviews/onboarding; do not invent a session.
|
|
2367
|
+
After login/registration:
|
|
2368
|
+
- Store the session securely, identify(fullSessionToken), track success, start authenticated
|
|
2369
|
+
pageviews, and pass the same token to user-context realtime/component operations.
|
|
2370
|
+
After refresh:
|
|
2371
|
+
- Replace the old token, identify(newSessionToken), update realtime connections/subscriptions,
|
|
2372
|
+
and use the refreshed token for future backend operations.
|
|
2373
|
+
Logout:
|
|
2374
|
+
- Optionally track logout_initiated, flush, revoke, disconnect realtime, clearSession, then
|
|
2375
|
+
remove local authentication.
|
|
2376
|
+
Refresh failure/revocation:
|
|
2377
|
+
- Disconnect user-context clients, clearSession, clear local auth, navigate to authentication,
|
|
2378
|
+
and track only anonymous events afterward.
|
|
2379
|
+
|
|
2380
|
+
REACT ROUTE TRACKING (verify installed exports first)
|
|
2381
|
+
import { useAnalytics, useDuctape } from '@ductape/react';
|
|
2382
|
+
import { useEffect } from 'react';
|
|
2383
|
+
import { useLocation } from 'react-router-dom';
|
|
2384
|
+
|
|
2385
|
+
function ProductAnalytics({ sessionToken }: { sessionToken?: string }) {
|
|
2386
|
+
const analytics = useAnalytics();
|
|
2387
|
+
const { client } = useDuctape();
|
|
2388
|
+
const location = useLocation();
|
|
2389
|
+
useEffect(() => {
|
|
2390
|
+
if (sessionToken) analytics.identify(sessionToken);
|
|
2391
|
+
else client.analytics.clearSession();
|
|
2392
|
+
}, [analytics, client, sessionToken]);
|
|
2393
|
+
useEffect(() => {
|
|
2394
|
+
void analytics.pageview({ path: location.pathname, title: document.title });
|
|
2395
|
+
}, [analytics, location.pathname]);
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
SAFE EVENT TAXONOMY
|
|
2400
|
+
Define stable names centrally; do not invent variants throughout components.
|
|
2401
|
+
Authentication:
|
|
2402
|
+
registration_started, registration_completed, registration_failed,
|
|
2403
|
+
login_started, login_completed, login_failed, session_refreshed,
|
|
2404
|
+
session_refresh_failed, logout_completed
|
|
2405
|
+
Match:
|
|
2406
|
+
match_list_viewed, match_creation_started, match_created, match_joined, lobby_viewed,
|
|
2407
|
+
player_marked_ready, match_preparation_started, world_loaded, match_reconnected,
|
|
2408
|
+
match_completed, endgame_viewed
|
|
2409
|
+
Orders:
|
|
2410
|
+
order_form_opened, order_previewed, order_submission_started, order_submitted,
|
|
2411
|
+
order_submission_failed, order_cancelled, boundary_result_viewed
|
|
2412
|
+
Realtime:
|
|
2413
|
+
realtime_connect_started, realtime_connected, realtime_disconnected,
|
|
2414
|
+
realtime_reconnect_attempted, realtime_subscription_failed, projection_refresh_failed,
|
|
2415
|
+
client_error
|
|
2416
|
+
Funnels:
|
|
2417
|
+
tutorial_started, tutorial_step_completed, tutorial_abandoned, first_match_created,
|
|
2418
|
+
first_order_submitted, first_boundary_viewed, first_match_completed
|
|
2419
|
+
|
|
2420
|
+
Safe properties include matchId, orderType, screen, tick, result, and errorCategory.
|
|
2421
|
+
|
|
2422
|
+
HIDDEN AND SENSITIVE DATA — NEVER SEND TO ANALYTICS
|
|
2423
|
+
Do not track exact hidden formations, operative/handler identities, secret operation payloads,
|
|
2424
|
+
false-report truth markers, undiscovered evasion details, invisible treaties, canonical hidden
|
|
2425
|
+
map state, private messages, passwords, authorization headers, session/refresh tokens in event
|
|
2426
|
+
properties, or full errors/records that may contain secrets. Analytics must observe usage, not
|
|
2427
|
+
become a hidden-state side channel.
|
|
2428
|
+
|
|
2429
|
+
CORRELATION
|
|
2430
|
+
Frontend: create traceId = crypto.randomUUID(), track intent with traceId, and send traceId with
|
|
2431
|
+
the actual request. Backend logs the same traceId and propagates session to Ductape operations.
|
|
2432
|
+
Track completion with the same traceId.
|
|
2433
|
+
|
|
2434
|
+
traceId = correlation
|
|
2435
|
+
session = actor attribution
|
|
2436
|
+
idempotencyKey = duplicate prevention
|
|
2437
|
+
matchId/orderId = domain identity
|
|
2438
|
+
These values are not interchangeable.
|
|
2439
|
+
|
|
2440
|
+
OWNERSHIP BOUNDARIES
|
|
2441
|
+
Analytics is never an authoritative order, authorization proof, or gameplay source of truth.
|
|
2442
|
+
Analytics failure must not block or alter gameplay. The server verifies sessions independently;
|
|
2443
|
+
authoritative database and Event streams remain the source of truth. Prefer non-blocking
|
|
2444
|
+
analytics except explicit best-effort flushes at safe lifecycle transitions.
|
|
2445
|
+
|
|
2446
|
+
FRONTEND PROJECT AUDIT
|
|
2447
|
+
Inspect installed @ductape/client/react/vue versions and actual exports; client/provider setup;
|
|
2448
|
+
identify after login and refresh; clearSession on logout/account switch; SPA pageviews; auto-
|
|
2449
|
+
capture and masking; taxonomy consistency; sensitive custom properties; and shared trace IDs.
|
|
2450
|
+
Report capability as Present/Missing/Partial/Not applicable with concrete findings.
|
|
2451
|
+
|
|
2452
|
+
WHEN “SESSION ACTIVITY IS MISSING FROM THE DASHBOARD”
|
|
2453
|
+
Investigate both tracks before blaming the SDK.
|
|
2454
|
+
Backend: start/verify format, correct env, session on database/Event/Feature/notification/etc.,
|
|
2455
|
+
and intentional background sessionlessness.
|
|
2456
|
+
Frontend: identify after login/refresh, clearSession on logout, pageviews/custom events, flush,
|
|
2457
|
+
correct publishable key/product/env, installed API compatibility, browser failures, and privacy
|
|
2458
|
+
controls that may suppress events.
|
|
2459
|
+
|
|
2460
|
+
PAYLOAD RECIPES
|
|
2461
|
+
Anonymous:
|
|
2462
|
+
await ductape.analytics.pageview({ path: window.location.pathname, title: document.title });
|
|
2463
|
+
Authenticated:
|
|
2464
|
+
ductape.analytics.identify(playerSessionToken);
|
|
2465
|
+
await ductape.analytics.track({
|
|
2466
|
+
event: 'match_created', session: playerSessionToken, traceId, properties: { matchId },
|
|
2467
|
+
});
|
|
2468
|
+
Logout:
|
|
2469
|
+
await ductape.analytics.flush();
|
|
2470
|
+
ductape.analytics.clearSession();
|
|
2225
2471
|
`.trim(),
|
|
2226
2472
|
features: `
|
|
2227
2473
|
DUCTAPE FEATURES
|
|
@@ -2285,9 +2531,11 @@ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating
|
|
|
2285
2531
|
Wait for confirmation before writing code or calling any create tool.
|
|
2286
2532
|
|
|
2287
2533
|
STEP 5 — CREATE missing components (only with user approval)
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2534
|
+
Administrative assets must never be created through ductape_execute. Use ductape_cli for products,
|
|
2535
|
+
apps, supported resources, broker topics, cloud connections, secrets, and declarative apply flows.
|
|
2536
|
+
App actions, auths, Features, quotas, fallbacks, health checks, and other assets for which the CLI
|
|
2537
|
+
has no command must be configured in Workbench. Do not generate an impossible publishable-key
|
|
2538
|
+
create/update call. For a missing database action, configure it in Workbench, then verify it exists.
|
|
2291
2539
|
For a missing child feature, recursively apply this same workflow.
|
|
2292
2540
|
Tell the user what you are about to create before each tool call.
|
|
2293
2541
|
|
|
@@ -2411,7 +2659,11 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
2411
2659
|
|
|
2412
2660
|
Invoke internal application business logic (your own NestJS/backend service code):
|
|
2413
2661
|
→ produce a broker event (ctx.messaging.produce or ductape.events.produce)
|
|
2414
|
-
→
|
|
2662
|
+
→ follow ductape_docs({ topic: "events" }) and use the canonical NestJS decorator:
|
|
2663
|
+
@Events.Consumer({ event: "broker-tag:topic-tag" })
|
|
2664
|
+
async handle(message: MessageShape) { /* injected-service business logic; throw to nack */ }
|
|
2665
|
+
→ DuctapeModule auto-registers the decorated consumer; no manual onModuleInit is needed
|
|
2666
|
+
→ events.consume() remains a supported lower-level alternative for plain TypeScript/Node.js
|
|
2415
2667
|
→ your service method runs with full access to DI, DB transactions, etc.
|
|
2416
2668
|
Do NOT create an App Action just to call your own service over HTTP.
|
|
2417
2669
|
|
|
@@ -2907,9 +3159,15 @@ SESSIONS SERVICE
|
|
|
2907
3159
|
const { token } = await ductape.sessions.refresh({ refreshToken: '...' });
|
|
2908
3160
|
|
|
2909
3161
|
ANALYTICS SERVICE
|
|
2910
|
-
ductape.analytics.
|
|
2911
|
-
ductape.analytics.
|
|
2912
|
-
ductape.analytics.
|
|
3162
|
+
ductape.analytics.identify('player-session:eyJ...'); // full Ductape session token
|
|
3163
|
+
await ductape.analytics.pageview({ path: '/dashboard', title: document.title });
|
|
3164
|
+
await ductape.analytics.track({
|
|
3165
|
+
event: 'button_clicked',
|
|
3166
|
+
properties: { button: 'sign-up' },
|
|
3167
|
+
});
|
|
3168
|
+
await ductape.analytics.flush();
|
|
3169
|
+
ductape.analytics.clearSession(); // logout/account switch
|
|
3170
|
+
See ductape_docs({ topic: "frontend-analytics" }) before enabling auto-capture.
|
|
2913
3171
|
|
|
2914
3172
|
FRAMEWORK-SPECIFIC PACKAGES
|
|
2915
3173
|
For React and Vue projects, use the dedicated packages instead of managing the client manually:
|
|
@@ -3010,13 +3268,15 @@ AGENT HOOKS
|
|
|
3010
3268
|
useAgentSignal(hookOptions?) → { mutate, isLoading, error }
|
|
3011
3269
|
|
|
3012
3270
|
BROKER HOOKS
|
|
3013
|
-
useBroker(
|
|
3271
|
+
useBroker()
|
|
3014
3272
|
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
3273
|
+
connect({ broker, session?, product?, env? }) forwards the complete options object to
|
|
3274
|
+
@ductape/client. Pass session for player-scoped authorization.
|
|
3015
3275
|
useBrokerPublish(hookOptions?)
|
|
3016
3276
|
→ { mutate({ topic, message, headers?, key? }), isLoading, error, data }
|
|
3017
3277
|
useBrokerSubscription(subscribeOptions, hookOptions?)
|
|
3018
3278
|
→ { data: BrokerMessage[], isSubscribed, error, unsubscribe, resubscribe }
|
|
3019
|
-
subscribeOptions: { topic, group? }
|
|
3279
|
+
subscribeOptions: { broker?, topic, group?, session?, product?, env? }
|
|
3020
3280
|
|
|
3021
3281
|
GRAPH HOOKS
|
|
3022
3282
|
useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
@@ -3086,7 +3346,10 @@ ACTIONS HOOKS
|
|
|
3086
3346
|
useActionRun(hookOptions?) → { mutate, isLoading, error, data }
|
|
3087
3347
|
|
|
3088
3348
|
ANALYTICS HOOK
|
|
3089
|
-
useAnalytics() → { pageview, track, identify }
|
|
3349
|
+
useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
|
|
3350
|
+
The currently installed hook does NOT return clearSession or disableAutoCapture.
|
|
3351
|
+
Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
|
|
3352
|
+
package export before generating those hook calls. See ductape_docs({ topic: "frontend-analytics" }).
|
|
3090
3353
|
|
|
3091
3354
|
hookOptions pattern (applies to all hooks):
|
|
3092
3355
|
enabled? boolean — false skips auto-fetch/subscribe
|
|
@@ -3255,7 +3518,10 @@ ACTIONS COMPOSABLES
|
|
|
3255
3518
|
useActionRun(composableOptions?) → { mutate, isLoading, error, data }
|
|
3256
3519
|
|
|
3257
3520
|
ANALYTICS COMPOSABLE
|
|
3258
|
-
useAnalytics() → { pageview, track, identify }
|
|
3521
|
+
useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
|
|
3522
|
+
The currently installed composable does NOT return clearSession or disableAutoCapture.
|
|
3523
|
+
Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
|
|
3524
|
+
package export before generating those composable calls. See ductape_docs({ topic: "frontend-analytics" }).
|
|
3259
3525
|
|
|
3260
3526
|
composableOptions pattern (applies to all composables):
|
|
3261
3527
|
enabled? boolean — false skips auto-fetch/subscribe
|
|
@@ -3443,6 +3709,22 @@ async function main() {
|
|
|
3443
3709
|
};
|
|
3444
3710
|
const executeHandler = async (args) => {
|
|
3445
3711
|
try {
|
|
3712
|
+
const runtimeMutationMethods = {
|
|
3713
|
+
databases: new Set(['insert', 'update', 'delete', 'upsert']),
|
|
3714
|
+
graph: new Set(['insert', 'update', 'delete']),
|
|
3715
|
+
vector: new Set(['insert', 'upsert', 'upsertOne', 'delete']),
|
|
3716
|
+
sessions: new Set(['revoke']),
|
|
3717
|
+
};
|
|
3718
|
+
const isRuntimeDataMutation = runtimeMutationMethods[args.module]?.has(args.method) === true;
|
|
3719
|
+
const isAdministrativeMutation = (/^migration\./.test(args.method) ||
|
|
3720
|
+
/^schema\.(create|drop|add|remove|update)/.test(args.method) ||
|
|
3721
|
+
/(^|\.)(create|update|delete|configure|add|remove|revoke)$/.test(args.method)) &&
|
|
3722
|
+
!isRuntimeDataMutation;
|
|
3723
|
+
if (isAdministrativeMutation) {
|
|
3724
|
+
throw new Error(`Administrative operation "${args.module}.${args.method}" is blocked in ductape_execute. ` +
|
|
3725
|
+
'Use ductape_cli when that resource is supported by the CLI; otherwise configure it in Workbench. ' +
|
|
3726
|
+
'The runtime proxy uses a publishable key and cannot administer platform assets.');
|
|
3727
|
+
}
|
|
3446
3728
|
const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
|
|
3447
3729
|
if (!key) {
|
|
3448
3730
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
@@ -3489,7 +3771,7 @@ async function main() {
|
|
|
3489
3771
|
if (!key) {
|
|
3490
3772
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3491
3773
|
}
|
|
3492
|
-
const result = await generateExecutablePayload({ ...args, publishable_key: key });
|
|
3774
|
+
const result = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, publishable_key: key }), args);
|
|
3493
3775
|
let text = JSON.stringify(result ?? null, null, 2);
|
|
3494
3776
|
if (args.operation_family === 'database') {
|
|
3495
3777
|
const meta = result?.meta ?? {};
|
|
@@ -3516,7 +3798,7 @@ async function main() {
|
|
|
3516
3798
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3517
3799
|
}
|
|
3518
3800
|
ensureSupportedSnippetOperation(args.operation_family, args.method);
|
|
3519
|
-
const generated = await generateExecutablePayload({ ...args, publishable_key: key });
|
|
3801
|
+
const generated = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, publishable_key: key }), args);
|
|
3520
3802
|
const payload = generated?.payload ?? {};
|
|
3521
3803
|
const snippet = buildSnippet(args.language, payload, args.operation_family, args.method);
|
|
3522
3804
|
return {
|
|
@@ -3598,7 +3880,7 @@ async function main() {
|
|
|
3598
3880
|
'index strategy, operation types) that should be confirmed with the user first.\n\n' +
|
|
3599
3881
|
'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
3600
3882
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
3601
|
-
'notifications, resilience, features, events, logs, frontend, client, react, vue',
|
|
3883
|
+
'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue',
|
|
3602
3884
|
inputSchema: docsInputSchema,
|
|
3603
3885
|
}, docsHandler);
|
|
3604
3886
|
server.registerTool('ductape_cli', {
|
|
@@ -3669,7 +3951,7 @@ async function main() {
|
|
|
3669
3951
|
' Step 2 — check if the database component already exists:\n' +
|
|
3670
3952
|
' ductape_cli("resources databases list <product_tag> --json")\n' +
|
|
3671
3953
|
' If a component already uses the same Atlas cluster, do NOT re-import — instead update it:\n' +
|
|
3672
|
-
'
|
|
3954
|
+
' use ductape_cli resource update when supported; otherwise update it in Workbench\n' +
|
|
3673
3955
|
' Add or change the dbName in the env\'s connection_url to switch databases on the same cluster.\n' +
|
|
3674
3956
|
' Step 3 — import (only if no existing component uses this cluster):\n' +
|
|
3675
3957
|
' Use import-persist-all with one entry per product env. Required fields per entry:\n' +
|