@ductape/mcp 0.1.27 → 0.1.28
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 +527 -1
- package/package.json +1 -1
- package/src/index.ts +530 -1
package/dist/index.js
CHANGED
|
@@ -2341,6 +2341,532 @@ Notes:
|
|
|
2341
2341
|
resilience) emits its own logs automatically — manual add/publish is only needed for custom logs.
|
|
2342
2342
|
- language defaults to "typescript" so the backend knows which SDK emitted the entry.
|
|
2343
2343
|
- Logs are batched in memory and sent in a single publish() call per operation.
|
|
2344
|
+
`.trim(),
|
|
2345
|
+
client: `
|
|
2346
|
+
DUCTAPE CLIENT SDK (@ductape/client)
|
|
2347
|
+
|
|
2348
|
+
The client SDK is for frontend applications (React, Vue, Svelte, vanilla JS). All HTTP
|
|
2349
|
+
operations are routed through the Ductape proxy using a publishableKey. WebSocket subscriptions
|
|
2350
|
+
connect to the proxy's /realtime gateway. Never use this package on the server — use @ductape/sdk
|
|
2351
|
+
for server-side code.
|
|
2352
|
+
|
|
2353
|
+
INSTALL
|
|
2354
|
+
npm install @ductape/client
|
|
2355
|
+
|
|
2356
|
+
INITIALIZATION
|
|
2357
|
+
import { createClient } from '@ductape/client';
|
|
2358
|
+
// or: import Ductape from '@ductape/client';
|
|
2359
|
+
|
|
2360
|
+
const ductape = createClient({
|
|
2361
|
+
publishableKey: 'pk_live_...', // from Workbench → Settings → Publishable Keys
|
|
2362
|
+
baseUrl: 'https://your-proxy.example.com', // your deployed proxy URL
|
|
2363
|
+
product: 'my-product', // default product tag (can be overridden per call)
|
|
2364
|
+
env: 'prd', // default environment slug
|
|
2365
|
+
});
|
|
2366
|
+
|
|
2367
|
+
When publishableKey is set:
|
|
2368
|
+
- HTTP calls go to baseUrl/proxy/v1/sdk-proxy/execute
|
|
2369
|
+
- WebSocket connects to wss://your-proxy.example.com/realtime?token=pk_live_...
|
|
2370
|
+
- No server secret is exposed to the browser
|
|
2371
|
+
|
|
2372
|
+
Use accessKey only for server-side or trusted environments — never in a browser bundle.
|
|
2373
|
+
Override wsUrl to point to a custom WebSocket endpoint.
|
|
2374
|
+
|
|
2375
|
+
REAL-TIME CONNECTION
|
|
2376
|
+
await ductape.connect(); // opens WebSocket; required before any .subscribe() call
|
|
2377
|
+
ductape.disconnect(); // closes WebSocket and clears all subscriptions
|
|
2378
|
+
ductape.isConnected // boolean
|
|
2379
|
+
ductape.connectionState // 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
|
|
2380
|
+
ductape.onConnectionChange(cb) // listen to state changes; returns unsubscribe fn
|
|
2381
|
+
|
|
2382
|
+
SERVICES
|
|
2383
|
+
ductape.databases DatabaseService — CRUD + real-time query subscriptions
|
|
2384
|
+
ductape.features FeatureService — execute features, subscribe to execution status
|
|
2385
|
+
ductape.agents AgentService — run AI agents, subscribe to output stream
|
|
2386
|
+
ductape.vectors VectorService — vector upsert / search
|
|
2387
|
+
ductape.graphs GraphService — graph queries + subscriptions
|
|
2388
|
+
ductape.brokers BrokerService — publish / subscribe to message brokers
|
|
2389
|
+
ductape.sessions SessionService — verify, refresh, revoke sessions
|
|
2390
|
+
ductape.resilience ResilienceService — subscribe to healthcheck status
|
|
2391
|
+
ductape.storage StorageService — upload / download files
|
|
2392
|
+
ductape.warehouse WarehouseService — cross-store federated queries
|
|
2393
|
+
ductape.cache CacheService — key-value cache get / set / clear
|
|
2394
|
+
ductape.api ApiService — run app actions with OAuth credential support
|
|
2395
|
+
ductape.notifications NotificationsService — send push / email / SMS / callback
|
|
2396
|
+
ductape.analytics AnalyticsService — track pageviews, clicks, custom events
|
|
2397
|
+
ductape.workflows (deprecated alias → features)
|
|
2398
|
+
ductape.actions (deprecated alias → api)
|
|
2399
|
+
|
|
2400
|
+
DATABASE SERVICE
|
|
2401
|
+
await ductape.databases.connect({ database: 'core-db' });
|
|
2402
|
+
const { data } = await ductape.databases.query({ entity: 'users', where: { active: true } });
|
|
2403
|
+
await ductape.databases.insert({ entity: 'users', data: { name: 'Alice', email: 'a@b.com' } });
|
|
2404
|
+
await ductape.databases.update({ entity: 'users', where: { id: 1 }, data: { name: 'Bob' } });
|
|
2405
|
+
await ductape.databases.delete({ entity: 'users', where: { id: 1 } });
|
|
2406
|
+
await ductape.databases.upsert({ entity: 'users', data: { email: 'a@b.com' }, conflict: ['email'] });
|
|
2407
|
+
const total = await ductape.databases.count({ entity: 'users', where: { active: true } });
|
|
2408
|
+
await ductape.databases.raw({ query: 'SELECT * FROM users WHERE id = $1', params: [1] });
|
|
2409
|
+
await ductape.databases.transaction(async (tx) => {
|
|
2410
|
+
await tx.insert({ entity: 'orders', data: { ... } });
|
|
2411
|
+
await tx.update({ entity: 'inventory', where: { ... }, data: { ... } });
|
|
2412
|
+
});
|
|
2413
|
+
await ductape.databases.disconnect();
|
|
2414
|
+
|
|
2415
|
+
Real-time subscription (ductape.connect() must be called first):
|
|
2416
|
+
const sub = ductape.databases.subscribe(
|
|
2417
|
+
{ entity: 'orders', where: { status: 'pending' } },
|
|
2418
|
+
(rows) => console.log('live update', rows),
|
|
2419
|
+
);
|
|
2420
|
+
sub.unsubscribe(); // stop receiving updates
|
|
2421
|
+
|
|
2422
|
+
FEATURE SERVICE
|
|
2423
|
+
const { executionId } = await ductape.features.execute({ feature: 'onboard-user', input: { userId: 'u_1' } });
|
|
2424
|
+
const status = await ductape.features.status({ feature: 'onboard-user', executionId });
|
|
2425
|
+
await ductape.features.cancel({ feature: 'onboard-user', executionId, reason: 'user request' });
|
|
2426
|
+
await ductape.features.signal({ feature: 'onboard-user', executionId, signal: 'payment-confirmed' });
|
|
2427
|
+
const history = await ductape.features.history({ feature: 'onboard-user', executionId });
|
|
2428
|
+
const { executions } = await ductape.features.list({ status: 'running', limit: 20 });
|
|
2429
|
+
|
|
2430
|
+
Real-time status subscription:
|
|
2431
|
+
const sub = ductape.features.subscribe(
|
|
2432
|
+
{ feature: 'onboard-user', executionId },
|
|
2433
|
+
(events) => {
|
|
2434
|
+
const ev = events[0]; // { executionId, feature, status, currentStep, output, error }
|
|
2435
|
+
console.log(ev.status, ev.currentStep);
|
|
2436
|
+
if (ev.status === 'completed' || ev.status === 'failed') sub.unsubscribe();
|
|
2437
|
+
},
|
|
2438
|
+
);
|
|
2439
|
+
|
|
2440
|
+
AGENT SERVICE
|
|
2441
|
+
const result = await ductape.agents.run({ agent: 'support-bot', input: { message: 'Help!' } });
|
|
2442
|
+
const sub = ductape.agents.subscribe(
|
|
2443
|
+
{ agent: 'support-bot', executionId: result.executionId },
|
|
2444
|
+
(events) => console.log('chunk', events[0]),
|
|
2445
|
+
);
|
|
2446
|
+
sub.unsubscribe();
|
|
2447
|
+
|
|
2448
|
+
BROKER SERVICE
|
|
2449
|
+
await ductape.brokers.connect({ broker: 'notifications-broker', session: 'user-session:eyJ...' });
|
|
2450
|
+
await ductape.brokers.publish({ topic: 'chat.message', message: { text: 'Hello', userId: 'u_1' } });
|
|
2451
|
+
const sub = ductape.brokers.subscribe(
|
|
2452
|
+
{ topic: 'chat.message' },
|
|
2453
|
+
(msgs) => {
|
|
2454
|
+
const m = msgs[0]; // { topic, message, headers?, key?, timestamp, offset?, partition? }
|
|
2455
|
+
console.log('new message', m.message);
|
|
2456
|
+
},
|
|
2457
|
+
);
|
|
2458
|
+
sub.unsubscribe();
|
|
2459
|
+
await ductape.brokers.disconnect();
|
|
2460
|
+
|
|
2461
|
+
Passing session scopes delivery to a specific end-user so the server applies session-level
|
|
2462
|
+
authorization. Without session, messages for the whole product+env are delivered.
|
|
2463
|
+
|
|
2464
|
+
GRAPH SERVICE
|
|
2465
|
+
const nodes = await ductape.graphs.findNodes({ labels: ['User'], where: { active: true } });
|
|
2466
|
+
const sub = ductape.graphs.subscribe({ labels: ['Order'] }, (nodes) => console.log(nodes));
|
|
2467
|
+
sub.unsubscribe();
|
|
2468
|
+
|
|
2469
|
+
RESILIENCE SERVICE (health subscriptions)
|
|
2470
|
+
const sub = ductape.resilience.subscribe(
|
|
2471
|
+
{ tag: 'payment-health' },
|
|
2472
|
+
(events) => console.log('health change', events[0]),
|
|
2473
|
+
);
|
|
2474
|
+
sub.unsubscribe();
|
|
2475
|
+
|
|
2476
|
+
SESSIONS SERVICE
|
|
2477
|
+
const { valid } = await ductape.sessions.verify({ token: 'user-session:eyJ...' });
|
|
2478
|
+
await ductape.sessions.revoke({ token: 'user-session:eyJ...' });
|
|
2479
|
+
const { token } = await ductape.sessions.refresh({ refreshToken: '...' });
|
|
2480
|
+
|
|
2481
|
+
ANALYTICS SERVICE
|
|
2482
|
+
ductape.analytics.pageview({ page: '/dashboard' });
|
|
2483
|
+
ductape.analytics.track('button_click', { button: 'sign-up' });
|
|
2484
|
+
ductape.analytics.identify({ userId: 'u_123', traits: { plan: 'pro' } });
|
|
2485
|
+
|
|
2486
|
+
FRAMEWORK-SPECIFIC PACKAGES
|
|
2487
|
+
For React and Vue projects, use the dedicated packages instead of managing the client manually:
|
|
2488
|
+
@ductape/react — DuctapeProvider + hooks (useDatabaseQuery, useFeatureSubscription, etc.)
|
|
2489
|
+
See: ductape_docs({ topic: "react" })
|
|
2490
|
+
@ductape/vue — createDuctape() plugin + composables (useDatabaseQuery, useFeatureSubscription, etc.)
|
|
2491
|
+
See: ductape_docs({ topic: "vue" })
|
|
2492
|
+
|
|
2493
|
+
Use @ductape/client directly only for vanilla JS, Svelte, Angular, or custom integrations.
|
|
2494
|
+
|
|
2495
|
+
IMPORTANT
|
|
2496
|
+
- Call ductape.connect() before any .subscribe() — it throws if not connected.
|
|
2497
|
+
- Always clean up (sub.unsubscribe()) in component teardown to prevent memory leaks.
|
|
2498
|
+
- The client auto-resubscribes after a WebSocket reconnect — no manual retry needed.
|
|
2499
|
+
- publishableKey is safe in browser bundles; it grants only proxy-authorized operations.
|
|
2500
|
+
- For SSR, skip connect() on the server; call it only client-side (useEffect / onMounted).
|
|
2501
|
+
- The client SDK does NOT expose secrets, access keys, or workspace admin operations.
|
|
2502
|
+
- Pass session (format session_tag:jwt) to broker connect/subscribe for per-user scoping.
|
|
2503
|
+
`.trim(),
|
|
2504
|
+
react: `
|
|
2505
|
+
DUCTAPE REACT (@ductape/react)
|
|
2506
|
+
|
|
2507
|
+
React hooks and context provider for Ductape. Wraps @ductape/client.
|
|
2508
|
+
Requires react >= 17.
|
|
2509
|
+
|
|
2510
|
+
INSTALL
|
|
2511
|
+
npm install @ductape/react
|
|
2512
|
+
|
|
2513
|
+
SETUP — wrap your app root with DuctapeProvider
|
|
2514
|
+
import { DuctapeProvider } from '@ductape/react';
|
|
2515
|
+
|
|
2516
|
+
function App() {
|
|
2517
|
+
return (
|
|
2518
|
+
<DuctapeProvider
|
|
2519
|
+
config={{
|
|
2520
|
+
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
|
|
2521
|
+
baseUrl: import.meta.env.VITE_PROXY_URL,
|
|
2522
|
+
product: 'my-product',
|
|
2523
|
+
env: 'prd',
|
|
2524
|
+
}}
|
|
2525
|
+
autoConnect={false}
|
|
2526
|
+
>
|
|
2527
|
+
<MyApp />
|
|
2528
|
+
</DuctapeProvider>
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
DuctapeProvider props:
|
|
2533
|
+
config IDuctapeClientConfig — publishableKey (or accessKey), baseUrl, product, env
|
|
2534
|
+
autoConnect boolean (default false) — connect WebSocket automatically on mount
|
|
2535
|
+
onConnected () => void
|
|
2536
|
+
onDisconnected () => void
|
|
2537
|
+
onError (error: Error) => void
|
|
2538
|
+
onConnectionChange (state: ConnectionState) => void
|
|
2539
|
+
|
|
2540
|
+
CORE HOOKS
|
|
2541
|
+
useDuctape() → { client, isConnected, connectionState, connect, disconnect, isReady }
|
|
2542
|
+
useDuctapeContext() → same; throws if called outside DuctapeProvider
|
|
2543
|
+
|
|
2544
|
+
DATABASE HOOKS
|
|
2545
|
+
useDatabase(database)
|
|
2546
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2547
|
+
Call connect() in a useEffect to open the database session.
|
|
2548
|
+
|
|
2549
|
+
useDatabaseQuery(key, queryOptions, hookOptions?)
|
|
2550
|
+
→ { data: IQueryResult<T>, isLoading, error, refetch }
|
|
2551
|
+
key: string | string[] — deduplication key
|
|
2552
|
+
queryOptions: { table, where?, select?, limit?, offset?, sort? }
|
|
2553
|
+
|
|
2554
|
+
useDatabaseInsert(hookOptions?) → { mutate({ table, data }), isLoading, error, data }
|
|
2555
|
+
useDatabaseUpdate(hookOptions?) → { mutate({ table, where, data }), isLoading, error, data }
|
|
2556
|
+
useDatabaseDelete(hookOptions?) → { mutate({ table, where }), isLoading, error, data }
|
|
2557
|
+
|
|
2558
|
+
useDatabaseSubscription(subscribeOptions, hookOptions?)
|
|
2559
|
+
→ { data: T[] | null, isSubscribed, error, unsubscribe, resubscribe }
|
|
2560
|
+
subscribeOptions: { table, where?, select? }
|
|
2561
|
+
Requires autoConnect: true (or manual connect()) and databases.connect() before subscribing.
|
|
2562
|
+
|
|
2563
|
+
FEATURE HOOKS
|
|
2564
|
+
useFeatureExecute(hookOptions?)
|
|
2565
|
+
→ { mutate({ feature, input?, product?, env? }), isLoading, error, data }
|
|
2566
|
+
useFeatureStatus(statusInput, hookOptions?)
|
|
2567
|
+
→ { data: FeatureStatus, isLoading, error, refetch }
|
|
2568
|
+
statusInput: { feature, executionId, product?, env? }
|
|
2569
|
+
useFeatureSubscription(subscribeOptions, hookOptions?)
|
|
2570
|
+
→ { data: FeatureStatusEvent[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2571
|
+
subscribeOptions: { feature, executionId, product?, env? }
|
|
2572
|
+
useFeatureSignal(hookOptions?) → { mutate({ feature, executionId, signal, data? }), isLoading }
|
|
2573
|
+
useFeatureCancel(hookOptions?) → { mutate({ feature, executionId, reason? }), isLoading }
|
|
2574
|
+
|
|
2575
|
+
AGENT HOOKS
|
|
2576
|
+
useAgentRun(hookOptions?)
|
|
2577
|
+
→ { mutate({ tag, input?, sessionId? }), isLoading, error, data: IAgentExecutionResult }
|
|
2578
|
+
useAgentStream(tag, input?, hookOptions?)
|
|
2579
|
+
→ { events, content, isStreaming, isComplete, error, start, stop, reset }
|
|
2580
|
+
content: accumulated string from all stream text events
|
|
2581
|
+
useAgentStatus(statusInput, hookOptions?) → { data, isLoading, error, refetch }
|
|
2582
|
+
useAgentSignal(hookOptions?) → { mutate, isLoading, error }
|
|
2583
|
+
|
|
2584
|
+
BROKER HOOKS
|
|
2585
|
+
useBroker(broker, options?)
|
|
2586
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2587
|
+
useBrokerPublish(hookOptions?)
|
|
2588
|
+
→ { mutate({ topic, message, headers?, key? }), isLoading, error, data }
|
|
2589
|
+
useBrokerSubscription(subscribeOptions, hookOptions?)
|
|
2590
|
+
→ { data: BrokerMessage[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2591
|
+
subscribeOptions: { topic, group? }
|
|
2592
|
+
|
|
2593
|
+
GRAPH HOOKS
|
|
2594
|
+
useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2595
|
+
useCreateNode(hookOptions?) → { mutate({ labels, properties }), isLoading, error, data }
|
|
2596
|
+
useUpdateNode(hookOptions?) → { mutate({ id, properties }), isLoading, error, data }
|
|
2597
|
+
useDeleteNode(hookOptions?) → { mutate({ id, detach? }), isLoading, error, data }
|
|
2598
|
+
useQueryNodes(key, options, hookOptions?) → { data, isLoading, error }
|
|
2599
|
+
useCreateRelationship(hookOptions?) → { mutate, isLoading, error, data }
|
|
2600
|
+
useDeleteRelationship(hookOptions?) → { mutate, isLoading, error }
|
|
2601
|
+
useQueryRelationships(key, options, hookOptions?) → { data, isLoading, error }
|
|
2602
|
+
useTraverse(key, options, hookOptions?) → { data, isLoading, error }
|
|
2603
|
+
useShortestPath(key, options, hookOptions?) → { data, isLoading, error }
|
|
2604
|
+
useGraphSubscription(subscribeOptions, hookOptions?)
|
|
2605
|
+
→ { data, isSubscribed, error, unsubscribe, resubscribe }
|
|
2606
|
+
|
|
2607
|
+
VECTOR HOOKS
|
|
2608
|
+
useVector(vectorTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2609
|
+
useVectorQuery(key, options, hookOptions?) → { data, isLoading, error }
|
|
2610
|
+
useVectorSearch(key, options, hookOptions?) → { data, isLoading, error }
|
|
2611
|
+
useVectorUpsert(hookOptions?) → { mutate({ id, values, metadata? }), isLoading, error, data }
|
|
2612
|
+
useVectorDelete(hookOptions?) → { mutate({ ids }), isLoading, error }
|
|
2613
|
+
useVectorFetch(key, options, hookOptions?) → { data: VectorFetchResult, isLoading, error }
|
|
2614
|
+
useVectorStats(key, options, hookOptions?) → { data: VectorStatsResult, isLoading, error }
|
|
2615
|
+
useVectorNamespaces(key, options, hookOptions?) → { data, isLoading, error }
|
|
2616
|
+
|
|
2617
|
+
STORAGE HOOKS
|
|
2618
|
+
useUpload(hookOptions?)
|
|
2619
|
+
→ { upload({ storage, fileName, buffer, mimeType? }), isLoading, progress, error, data }
|
|
2620
|
+
progress: 0–100 number
|
|
2621
|
+
useDownload(hookOptions?) → { mutate, isLoading, error, data }
|
|
2622
|
+
useStorageDelete(hookOptions?) → { mutate, isLoading, error }
|
|
2623
|
+
useListFiles(key, options, hookOptions?) → { data, isLoading, error }
|
|
2624
|
+
useSignedUrl(key, options, hookOptions?) → { data: string, isLoading, error }
|
|
2625
|
+
|
|
2626
|
+
SESSION HOOKS
|
|
2627
|
+
useSessionStart(hookOptions?) → { mutate, isLoading, error, data: { token, refreshToken } }
|
|
2628
|
+
useSessionVerify(hookOptions?) → { mutate, isLoading, error, data }
|
|
2629
|
+
useSessionRefresh(hookOptions?) → { mutate, isLoading, error, data }
|
|
2630
|
+
useSessionRevoke(hookOptions?) → { mutate, isLoading, error }
|
|
2631
|
+
useSessionRevokeAll(hookOptions?) → { mutate, isLoading, error }
|
|
2632
|
+
useSessionList(key, options, hookOptions?) → { data, isLoading, error }
|
|
2633
|
+
useSessionAutoRefresh(config)
|
|
2634
|
+
→ { token, isRefreshing }; auto-refreshes before expiry; no manual call needed
|
|
2635
|
+
|
|
2636
|
+
CACHE HOOKS
|
|
2637
|
+
useCacheGet(key, options, hookOptions?) → { data, isLoading, error }
|
|
2638
|
+
useCacheSet(hookOptions?) → { mutate({ key, value, expiry? }), isLoading, error }
|
|
2639
|
+
useCacheDelete(hookOptions?) → { mutate({ key }), isLoading, error }
|
|
2640
|
+
useCacheExists(key, options, hookOptions?) → { data: boolean, isLoading, error }
|
|
2641
|
+
useCacheGetMany / useCacheSetMany / useCacheDeleteMany
|
|
2642
|
+
|
|
2643
|
+
RESILIENCE HOOKS
|
|
2644
|
+
useHealthStatus(key, options, hookOptions?)
|
|
2645
|
+
→ { data: HealthStatusResult, isLoading, error, refetch }
|
|
2646
|
+
useHealthSubscription(subscribeOptions, hookOptions?)
|
|
2647
|
+
→ { data: HealthChangeEvent[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2648
|
+
useQuotaCheck(hookOptions?) → { mutate, isLoading, error, data: QuotaCheckResult }
|
|
2649
|
+
useQuotaStatus(key, options, hookOptions?) → { data, isLoading, error }
|
|
2650
|
+
useQuotaSubscription(subscribeOptions, hookOptions?) → { data, isSubscribed, error }
|
|
2651
|
+
|
|
2652
|
+
WAREHOUSE HOOKS
|
|
2653
|
+
useWarehouseQuery / useWarehouseSelect / useWarehouseInsert
|
|
2654
|
+
useWarehouseUpdate / useWarehouseDelete / useWarehouseUpsert / useWarehouseTransaction
|
|
2655
|
+
|
|
2656
|
+
ACTIONS HOOKS
|
|
2657
|
+
useActions(app) → { isConnected, connect, disconnect }
|
|
2658
|
+
useActionRun(hookOptions?) → { mutate, isLoading, error, data }
|
|
2659
|
+
|
|
2660
|
+
ANALYTICS HOOK
|
|
2661
|
+
useAnalytics() → { pageview, track, identify }
|
|
2662
|
+
|
|
2663
|
+
hookOptions pattern (applies to all hooks):
|
|
2664
|
+
enabled? boolean — false skips auto-fetch/subscribe
|
|
2665
|
+
onSuccess? (data) => void
|
|
2666
|
+
onError? (error: Error) => void
|
|
2667
|
+
Subscriptions also accept: onData, onSubscribe, onUnsubscribe
|
|
2668
|
+
|
|
2669
|
+
EXAMPLE
|
|
2670
|
+
// main.tsx
|
|
2671
|
+
import { DuctapeProvider } from '@ductape/react';
|
|
2672
|
+
root.render(
|
|
2673
|
+
<DuctapeProvider config={{ publishableKey: 'pk_...', baseUrl: '...', product: 'my-app', env: 'prd' }} autoConnect>
|
|
2674
|
+
<App />
|
|
2675
|
+
</DuctapeProvider>
|
|
2676
|
+
);
|
|
2677
|
+
|
|
2678
|
+
// UsersList.tsx
|
|
2679
|
+
import { useDatabaseQuery } from '@ductape/react';
|
|
2680
|
+
function UsersList() {
|
|
2681
|
+
const { data, isLoading } = useDatabaseQuery('users', { table: 'users', limit: 20 });
|
|
2682
|
+
if (isLoading) return <p>Loading...</p>;
|
|
2683
|
+
return <ul>{data?.rows.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// FeatureTracker.tsx
|
|
2687
|
+
import { useFeatureSubscription } from '@ductape/react';
|
|
2688
|
+
function FeatureTracker({ executionId }) {
|
|
2689
|
+
const { data: events } = useFeatureSubscription(
|
|
2690
|
+
{ feature: 'onboard-user', executionId },
|
|
2691
|
+
{ onData: (evs) => console.log('status:', evs[0].status) },
|
|
2692
|
+
);
|
|
2693
|
+
return <p>{events?.[0]?.status ?? 'waiting'}</p>;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
DEPRECATED ALIASES
|
|
2697
|
+
useWorkflowExecute → useFeatureExecute
|
|
2698
|
+
useWorkflowStatus → useFeatureStatus
|
|
2699
|
+
useWorkflowSubscription → useFeatureSubscription
|
|
2700
|
+
useWorkflowSignal → useFeatureSignal
|
|
2701
|
+
useWorkflowCancel → useFeatureCancel
|
|
2702
|
+
`.trim(),
|
|
2703
|
+
vue: `
|
|
2704
|
+
DUCTAPE VUE (@ductape/vue)
|
|
2705
|
+
|
|
2706
|
+
Vue 3 composables and plugin for Ductape. Wraps @ductape/client.
|
|
2707
|
+
Requires vue >= 3.
|
|
2708
|
+
|
|
2709
|
+
INSTALL
|
|
2710
|
+
npm install @ductape/vue
|
|
2711
|
+
|
|
2712
|
+
SETUP — install the plugin at app root
|
|
2713
|
+
// main.ts
|
|
2714
|
+
import { createApp } from 'vue';
|
|
2715
|
+
import { createDuctape } from '@ductape/vue';
|
|
2716
|
+
import App from './App.vue';
|
|
2717
|
+
|
|
2718
|
+
const app = createApp(App);
|
|
2719
|
+
|
|
2720
|
+
app.use(createDuctape({
|
|
2721
|
+
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
|
|
2722
|
+
baseUrl: import.meta.env.VITE_PROXY_URL,
|
|
2723
|
+
product: 'my-product',
|
|
2724
|
+
env: 'prd',
|
|
2725
|
+
autoConnect: false, // true = connect WebSocket when plugin installs
|
|
2726
|
+
}));
|
|
2727
|
+
|
|
2728
|
+
app.mount('#app');
|
|
2729
|
+
|
|
2730
|
+
Plugin options (extends IDuctapeClientConfig):
|
|
2731
|
+
publishableKey / accessKey — auth
|
|
2732
|
+
baseUrl — proxy URL
|
|
2733
|
+
product, env — defaults for all composables
|
|
2734
|
+
autoConnect — boolean (default false)
|
|
2735
|
+
|
|
2736
|
+
The client is provided via inject(DUCTAPE_INJECTION_KEY) and is available on
|
|
2737
|
+
this.$ductape in Options API components.
|
|
2738
|
+
|
|
2739
|
+
CORE COMPOSABLE
|
|
2740
|
+
useDuctape()
|
|
2741
|
+
→ { client, isReady, isConnected: Ref<boolean>, connectionState: Ref<ConnectionState>,
|
|
2742
|
+
error: Ref<Error|null>, connect, disconnect }
|
|
2743
|
+
|
|
2744
|
+
DATABASE COMPOSABLES
|
|
2745
|
+
useDatabase(database)
|
|
2746
|
+
→ { isConnected, isConnecting, error, connect, disconnect } — all Refs
|
|
2747
|
+
Call connect() in onMounted to open the database session.
|
|
2748
|
+
|
|
2749
|
+
useDatabaseQuery(key, queryOptions, composableOptions?)
|
|
2750
|
+
→ { data: Ref<IQueryResult<T>|null>, isLoading: Ref<boolean>, error: Ref<Error|null>, refetch }
|
|
2751
|
+
|
|
2752
|
+
useDatabaseInsert(composableOptions?) → { mutate({ table, data }), isLoading, error, data }
|
|
2753
|
+
useDatabaseUpdate(composableOptions?) → { mutate({ table, where, data }), isLoading, error, data }
|
|
2754
|
+
useDatabaseDelete(composableOptions?) → { mutate({ table, where }), isLoading, error, data }
|
|
2755
|
+
|
|
2756
|
+
useDatabaseSubscription(subscribeOptions, composableOptions?)
|
|
2757
|
+
→ { data: Ref<T[]|null>, isSubscribed: Ref<boolean>, error, unsubscribe, resubscribe }
|
|
2758
|
+
subscribeOptions: { table, where?, select? }
|
|
2759
|
+
|
|
2760
|
+
FEATURE COMPOSABLES
|
|
2761
|
+
useFeatureExecute(composableOptions?)
|
|
2762
|
+
→ { mutate({ feature, input?, product?, env? }), isLoading, error, data }
|
|
2763
|
+
useFeatureStatus(statusInput, composableOptions?)
|
|
2764
|
+
→ { data: Ref<FeatureStatus|null>, isLoading, error, refetch }
|
|
2765
|
+
useFeatureSubscription(subscribeOptions, composableOptions?)
|
|
2766
|
+
→ { data: Ref<FeatureStatusEvent[]|null>, isSubscribed, error, unsubscribe, resubscribe }
|
|
2767
|
+
subscribeOptions: { feature, executionId, product?, env? }
|
|
2768
|
+
useFeatureSignal(composableOptions?) → { mutate, isLoading, error }
|
|
2769
|
+
useFeatureCancel(composableOptions?) → { mutate, isLoading, error }
|
|
2770
|
+
|
|
2771
|
+
AGENT COMPOSABLES
|
|
2772
|
+
useAgentRun(composableOptions?)
|
|
2773
|
+
→ { mutate({ tag, input?, sessionId? }), isLoading, error, data }
|
|
2774
|
+
useAgentStream(tag, input?, composableOptions?)
|
|
2775
|
+
→ { events, content, isStreaming, isComplete, error, start, stop, reset }
|
|
2776
|
+
content: Ref<string> accumulated from stream text events
|
|
2777
|
+
useAgentStatus(statusInput, composableOptions?) → { data, isLoading, error, refetch }
|
|
2778
|
+
useAgentSignal(composableOptions?) → { mutate, isLoading, error }
|
|
2779
|
+
|
|
2780
|
+
BROKER COMPOSABLES
|
|
2781
|
+
useBroker(broker, options?)
|
|
2782
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2783
|
+
useBrokerPublish(composableOptions?)
|
|
2784
|
+
→ { mutate({ topic, message, headers?, key? }), isLoading, error, data }
|
|
2785
|
+
useBrokerSubscription(subscribeOptions, composableOptions?)
|
|
2786
|
+
→ { data: Ref<BrokerMessage[]|null>, isSubscribed, error, unsubscribe, resubscribe }
|
|
2787
|
+
subscribeOptions: { topic, group? }
|
|
2788
|
+
|
|
2789
|
+
GRAPH COMPOSABLES
|
|
2790
|
+
useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2791
|
+
useCreateNode / useUpdateNode / useDeleteNode / useQueryNodes
|
|
2792
|
+
useCreateRelationship / useDeleteRelationship / useQueryRelationships
|
|
2793
|
+
useTraverse / useShortestPath
|
|
2794
|
+
useGraphSubscription(subscribeOptions, composableOptions?)
|
|
2795
|
+
→ { data, isSubscribed, error, unsubscribe, resubscribe }
|
|
2796
|
+
|
|
2797
|
+
VECTOR COMPOSABLES
|
|
2798
|
+
useVector(vectorTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2799
|
+
useVectorQuery / useVectorSearch / useVectorUpsert / useVectorDelete
|
|
2800
|
+
useVectorFetch / useVectorStats / useVectorNamespaces
|
|
2801
|
+
|
|
2802
|
+
STORAGE COMPOSABLES
|
|
2803
|
+
useUpload(composableOptions?)
|
|
2804
|
+
→ { upload({ storage, fileName, buffer, mimeType? }), isLoading, progress: Ref<number>, error, data }
|
|
2805
|
+
useDownload / useStorageDelete / useListFiles / useSignedUrl
|
|
2806
|
+
|
|
2807
|
+
SESSION COMPOSABLES
|
|
2808
|
+
useSessionStart / useSessionVerify / useSessionRefresh
|
|
2809
|
+
useSessionRevoke / useSessionRevokeAll / useSessionList
|
|
2810
|
+
useSessionAutoRefresh(config) → { token: Ref<string|null>, isRefreshing: Ref<boolean> }
|
|
2811
|
+
|
|
2812
|
+
CACHE COMPOSABLES
|
|
2813
|
+
useCacheGet / useCacheSet / useCacheDelete / useCacheExists
|
|
2814
|
+
useCacheGetMany / useCacheSetMany / useCacheDeleteMany
|
|
2815
|
+
|
|
2816
|
+
RESILIENCE COMPOSABLES
|
|
2817
|
+
useHealthStatus / useHealthSubscription
|
|
2818
|
+
useQuotaRun / useQuotaCheck / useQuotaStatus / useQuotaSubscription
|
|
2819
|
+
useFallbackRun
|
|
2820
|
+
|
|
2821
|
+
WAREHOUSE COMPOSABLES
|
|
2822
|
+
useWarehouseQuery / useWarehouseSelect / useWarehouseInsert
|
|
2823
|
+
useWarehouseUpdate / useWarehouseDelete / useWarehouseUpsert / useWarehouseTransaction
|
|
2824
|
+
|
|
2825
|
+
ACTIONS COMPOSABLES
|
|
2826
|
+
useActions(app) → { isConnected, connect, disconnect }
|
|
2827
|
+
useActionRun(composableOptions?) → { mutate, isLoading, error, data }
|
|
2828
|
+
|
|
2829
|
+
ANALYTICS COMPOSABLE
|
|
2830
|
+
useAnalytics() → { pageview, track, identify }
|
|
2831
|
+
|
|
2832
|
+
composableOptions pattern (applies to all composables):
|
|
2833
|
+
enabled? boolean — false skips auto-fetch/subscribe
|
|
2834
|
+
onSuccess? (data) => void
|
|
2835
|
+
onError? (error: Error) => void
|
|
2836
|
+
Subscriptions also accept: onData, onSubscribe, onUnsubscribe
|
|
2837
|
+
|
|
2838
|
+
EXAMPLE
|
|
2839
|
+
<!-- UsersList.vue -->
|
|
2840
|
+
<script setup>
|
|
2841
|
+
import { useDatabaseQuery } from '@ductape/vue';
|
|
2842
|
+
const { data, isLoading, error } = useDatabaseQuery('users', { table: 'users', limit: 20 });
|
|
2843
|
+
</script>
|
|
2844
|
+
<template>
|
|
2845
|
+
<div v-if="isLoading">Loading...</div>
|
|
2846
|
+
<div v-else-if="error">{{ error.message }}</div>
|
|
2847
|
+
<ul v-else>
|
|
2848
|
+
<li v-for="user in data?.rows" :key="user.id">{{ user.name }}</li>
|
|
2849
|
+
</ul>
|
|
2850
|
+
</template>
|
|
2851
|
+
|
|
2852
|
+
<!-- FeatureTracker.vue -->
|
|
2853
|
+
<script setup>
|
|
2854
|
+
import { useFeatureSubscription } from '@ductape/vue';
|
|
2855
|
+
const props = defineProps(['executionId']);
|
|
2856
|
+
const { data: events, isSubscribed } = useFeatureSubscription(
|
|
2857
|
+
{ feature: 'onboard-user', executionId: props.executionId },
|
|
2858
|
+
);
|
|
2859
|
+
</script>
|
|
2860
|
+
<template>
|
|
2861
|
+
<p>{{ events?.[0]?.status ?? 'waiting' }}</p>
|
|
2862
|
+
</template>
|
|
2863
|
+
|
|
2864
|
+
DEPRECATED ALIASES
|
|
2865
|
+
useWorkflowExecute → useFeatureExecute
|
|
2866
|
+
useWorkflowStatus → useFeatureStatus
|
|
2867
|
+
useWorkflowSubscription → useFeatureSubscription
|
|
2868
|
+
useWorkflowSignal → useFeatureSignal
|
|
2869
|
+
useWorkflowCancel → useFeatureCancel
|
|
2344
2870
|
`.trim(),
|
|
2345
2871
|
};
|
|
2346
2872
|
const docsHandler = async (args) => {
|
|
@@ -2602,7 +3128,7 @@ async function main() {
|
|
|
2602
3128
|
'index strategy, operation types) that should be confirmed with the user first.\n\n' +
|
|
2603
3129
|
'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
2604
3130
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
2605
|
-
'notifications, resilience, features, events, logs',
|
|
3131
|
+
'notifications, resilience, features, events, logs, client, react, vue',
|
|
2606
3132
|
inputSchema: docsInputSchema,
|
|
2607
3133
|
}, docsHandler);
|
|
2608
3134
|
server.registerTool('ductape_cli', {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2412,6 +2412,535 @@ Notes:
|
|
|
2412
2412
|
- language defaults to "typescript" so the backend knows which SDK emitted the entry.
|
|
2413
2413
|
- Logs are batched in memory and sent in a single publish() call per operation.
|
|
2414
2414
|
`.trim(),
|
|
2415
|
+
|
|
2416
|
+
client: `
|
|
2417
|
+
DUCTAPE CLIENT SDK (@ductape/client)
|
|
2418
|
+
|
|
2419
|
+
The client SDK is for frontend applications (React, Vue, Svelte, vanilla JS). All HTTP
|
|
2420
|
+
operations are routed through the Ductape proxy using a publishableKey. WebSocket subscriptions
|
|
2421
|
+
connect to the proxy's /realtime gateway. Never use this package on the server — use @ductape/sdk
|
|
2422
|
+
for server-side code.
|
|
2423
|
+
|
|
2424
|
+
INSTALL
|
|
2425
|
+
npm install @ductape/client
|
|
2426
|
+
|
|
2427
|
+
INITIALIZATION
|
|
2428
|
+
import { createClient } from '@ductape/client';
|
|
2429
|
+
// or: import Ductape from '@ductape/client';
|
|
2430
|
+
|
|
2431
|
+
const ductape = createClient({
|
|
2432
|
+
publishableKey: 'pk_live_...', // from Workbench → Settings → Publishable Keys
|
|
2433
|
+
baseUrl: 'https://your-proxy.example.com', // your deployed proxy URL
|
|
2434
|
+
product: 'my-product', // default product tag (can be overridden per call)
|
|
2435
|
+
env: 'prd', // default environment slug
|
|
2436
|
+
});
|
|
2437
|
+
|
|
2438
|
+
When publishableKey is set:
|
|
2439
|
+
- HTTP calls go to baseUrl/proxy/v1/sdk-proxy/execute
|
|
2440
|
+
- WebSocket connects to wss://your-proxy.example.com/realtime?token=pk_live_...
|
|
2441
|
+
- No server secret is exposed to the browser
|
|
2442
|
+
|
|
2443
|
+
Use accessKey only for server-side or trusted environments — never in a browser bundle.
|
|
2444
|
+
Override wsUrl to point to a custom WebSocket endpoint.
|
|
2445
|
+
|
|
2446
|
+
REAL-TIME CONNECTION
|
|
2447
|
+
await ductape.connect(); // opens WebSocket; required before any .subscribe() call
|
|
2448
|
+
ductape.disconnect(); // closes WebSocket and clears all subscriptions
|
|
2449
|
+
ductape.isConnected // boolean
|
|
2450
|
+
ductape.connectionState // 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
|
|
2451
|
+
ductape.onConnectionChange(cb) // listen to state changes; returns unsubscribe fn
|
|
2452
|
+
|
|
2453
|
+
SERVICES
|
|
2454
|
+
ductape.databases DatabaseService — CRUD + real-time query subscriptions
|
|
2455
|
+
ductape.features FeatureService — execute features, subscribe to execution status
|
|
2456
|
+
ductape.agents AgentService — run AI agents, subscribe to output stream
|
|
2457
|
+
ductape.vectors VectorService — vector upsert / search
|
|
2458
|
+
ductape.graphs GraphService — graph queries + subscriptions
|
|
2459
|
+
ductape.brokers BrokerService — publish / subscribe to message brokers
|
|
2460
|
+
ductape.sessions SessionService — verify, refresh, revoke sessions
|
|
2461
|
+
ductape.resilience ResilienceService — subscribe to healthcheck status
|
|
2462
|
+
ductape.storage StorageService — upload / download files
|
|
2463
|
+
ductape.warehouse WarehouseService — cross-store federated queries
|
|
2464
|
+
ductape.cache CacheService — key-value cache get / set / clear
|
|
2465
|
+
ductape.api ApiService — run app actions with OAuth credential support
|
|
2466
|
+
ductape.notifications NotificationsService — send push / email / SMS / callback
|
|
2467
|
+
ductape.analytics AnalyticsService — track pageviews, clicks, custom events
|
|
2468
|
+
ductape.workflows (deprecated alias → features)
|
|
2469
|
+
ductape.actions (deprecated alias → api)
|
|
2470
|
+
|
|
2471
|
+
DATABASE SERVICE
|
|
2472
|
+
await ductape.databases.connect({ database: 'core-db' });
|
|
2473
|
+
const { data } = await ductape.databases.query({ entity: 'users', where: { active: true } });
|
|
2474
|
+
await ductape.databases.insert({ entity: 'users', data: { name: 'Alice', email: 'a@b.com' } });
|
|
2475
|
+
await ductape.databases.update({ entity: 'users', where: { id: 1 }, data: { name: 'Bob' } });
|
|
2476
|
+
await ductape.databases.delete({ entity: 'users', where: { id: 1 } });
|
|
2477
|
+
await ductape.databases.upsert({ entity: 'users', data: { email: 'a@b.com' }, conflict: ['email'] });
|
|
2478
|
+
const total = await ductape.databases.count({ entity: 'users', where: { active: true } });
|
|
2479
|
+
await ductape.databases.raw({ query: 'SELECT * FROM users WHERE id = $1', params: [1] });
|
|
2480
|
+
await ductape.databases.transaction(async (tx) => {
|
|
2481
|
+
await tx.insert({ entity: 'orders', data: { ... } });
|
|
2482
|
+
await tx.update({ entity: 'inventory', where: { ... }, data: { ... } });
|
|
2483
|
+
});
|
|
2484
|
+
await ductape.databases.disconnect();
|
|
2485
|
+
|
|
2486
|
+
Real-time subscription (ductape.connect() must be called first):
|
|
2487
|
+
const sub = ductape.databases.subscribe(
|
|
2488
|
+
{ entity: 'orders', where: { status: 'pending' } },
|
|
2489
|
+
(rows) => console.log('live update', rows),
|
|
2490
|
+
);
|
|
2491
|
+
sub.unsubscribe(); // stop receiving updates
|
|
2492
|
+
|
|
2493
|
+
FEATURE SERVICE
|
|
2494
|
+
const { executionId } = await ductape.features.execute({ feature: 'onboard-user', input: { userId: 'u_1' } });
|
|
2495
|
+
const status = await ductape.features.status({ feature: 'onboard-user', executionId });
|
|
2496
|
+
await ductape.features.cancel({ feature: 'onboard-user', executionId, reason: 'user request' });
|
|
2497
|
+
await ductape.features.signal({ feature: 'onboard-user', executionId, signal: 'payment-confirmed' });
|
|
2498
|
+
const history = await ductape.features.history({ feature: 'onboard-user', executionId });
|
|
2499
|
+
const { executions } = await ductape.features.list({ status: 'running', limit: 20 });
|
|
2500
|
+
|
|
2501
|
+
Real-time status subscription:
|
|
2502
|
+
const sub = ductape.features.subscribe(
|
|
2503
|
+
{ feature: 'onboard-user', executionId },
|
|
2504
|
+
(events) => {
|
|
2505
|
+
const ev = events[0]; // { executionId, feature, status, currentStep, output, error }
|
|
2506
|
+
console.log(ev.status, ev.currentStep);
|
|
2507
|
+
if (ev.status === 'completed' || ev.status === 'failed') sub.unsubscribe();
|
|
2508
|
+
},
|
|
2509
|
+
);
|
|
2510
|
+
|
|
2511
|
+
AGENT SERVICE
|
|
2512
|
+
const result = await ductape.agents.run({ agent: 'support-bot', input: { message: 'Help!' } });
|
|
2513
|
+
const sub = ductape.agents.subscribe(
|
|
2514
|
+
{ agent: 'support-bot', executionId: result.executionId },
|
|
2515
|
+
(events) => console.log('chunk', events[0]),
|
|
2516
|
+
);
|
|
2517
|
+
sub.unsubscribe();
|
|
2518
|
+
|
|
2519
|
+
BROKER SERVICE
|
|
2520
|
+
await ductape.brokers.connect({ broker: 'notifications-broker', session: 'user-session:eyJ...' });
|
|
2521
|
+
await ductape.brokers.publish({ topic: 'chat.message', message: { text: 'Hello', userId: 'u_1' } });
|
|
2522
|
+
const sub = ductape.brokers.subscribe(
|
|
2523
|
+
{ topic: 'chat.message' },
|
|
2524
|
+
(msgs) => {
|
|
2525
|
+
const m = msgs[0]; // { topic, message, headers?, key?, timestamp, offset?, partition? }
|
|
2526
|
+
console.log('new message', m.message);
|
|
2527
|
+
},
|
|
2528
|
+
);
|
|
2529
|
+
sub.unsubscribe();
|
|
2530
|
+
await ductape.brokers.disconnect();
|
|
2531
|
+
|
|
2532
|
+
Passing session scopes delivery to a specific end-user so the server applies session-level
|
|
2533
|
+
authorization. Without session, messages for the whole product+env are delivered.
|
|
2534
|
+
|
|
2535
|
+
GRAPH SERVICE
|
|
2536
|
+
const nodes = await ductape.graphs.findNodes({ labels: ['User'], where: { active: true } });
|
|
2537
|
+
const sub = ductape.graphs.subscribe({ labels: ['Order'] }, (nodes) => console.log(nodes));
|
|
2538
|
+
sub.unsubscribe();
|
|
2539
|
+
|
|
2540
|
+
RESILIENCE SERVICE (health subscriptions)
|
|
2541
|
+
const sub = ductape.resilience.subscribe(
|
|
2542
|
+
{ tag: 'payment-health' },
|
|
2543
|
+
(events) => console.log('health change', events[0]),
|
|
2544
|
+
);
|
|
2545
|
+
sub.unsubscribe();
|
|
2546
|
+
|
|
2547
|
+
SESSIONS SERVICE
|
|
2548
|
+
const { valid } = await ductape.sessions.verify({ token: 'user-session:eyJ...' });
|
|
2549
|
+
await ductape.sessions.revoke({ token: 'user-session:eyJ...' });
|
|
2550
|
+
const { token } = await ductape.sessions.refresh({ refreshToken: '...' });
|
|
2551
|
+
|
|
2552
|
+
ANALYTICS SERVICE
|
|
2553
|
+
ductape.analytics.pageview({ page: '/dashboard' });
|
|
2554
|
+
ductape.analytics.track('button_click', { button: 'sign-up' });
|
|
2555
|
+
ductape.analytics.identify({ userId: 'u_123', traits: { plan: 'pro' } });
|
|
2556
|
+
|
|
2557
|
+
FRAMEWORK-SPECIFIC PACKAGES
|
|
2558
|
+
For React and Vue projects, use the dedicated packages instead of managing the client manually:
|
|
2559
|
+
@ductape/react — DuctapeProvider + hooks (useDatabaseQuery, useFeatureSubscription, etc.)
|
|
2560
|
+
See: ductape_docs({ topic: "react" })
|
|
2561
|
+
@ductape/vue — createDuctape() plugin + composables (useDatabaseQuery, useFeatureSubscription, etc.)
|
|
2562
|
+
See: ductape_docs({ topic: "vue" })
|
|
2563
|
+
|
|
2564
|
+
Use @ductape/client directly only for vanilla JS, Svelte, Angular, or custom integrations.
|
|
2565
|
+
|
|
2566
|
+
IMPORTANT
|
|
2567
|
+
- Call ductape.connect() before any .subscribe() — it throws if not connected.
|
|
2568
|
+
- Always clean up (sub.unsubscribe()) in component teardown to prevent memory leaks.
|
|
2569
|
+
- The client auto-resubscribes after a WebSocket reconnect — no manual retry needed.
|
|
2570
|
+
- publishableKey is safe in browser bundles; it grants only proxy-authorized operations.
|
|
2571
|
+
- For SSR, skip connect() on the server; call it only client-side (useEffect / onMounted).
|
|
2572
|
+
- The client SDK does NOT expose secrets, access keys, or workspace admin operations.
|
|
2573
|
+
- Pass session (format session_tag:jwt) to broker connect/subscribe for per-user scoping.
|
|
2574
|
+
`.trim(),
|
|
2575
|
+
|
|
2576
|
+
react: `
|
|
2577
|
+
DUCTAPE REACT (@ductape/react)
|
|
2578
|
+
|
|
2579
|
+
React hooks and context provider for Ductape. Wraps @ductape/client.
|
|
2580
|
+
Requires react >= 17.
|
|
2581
|
+
|
|
2582
|
+
INSTALL
|
|
2583
|
+
npm install @ductape/react
|
|
2584
|
+
|
|
2585
|
+
SETUP — wrap your app root with DuctapeProvider
|
|
2586
|
+
import { DuctapeProvider } from '@ductape/react';
|
|
2587
|
+
|
|
2588
|
+
function App() {
|
|
2589
|
+
return (
|
|
2590
|
+
<DuctapeProvider
|
|
2591
|
+
config={{
|
|
2592
|
+
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
|
|
2593
|
+
baseUrl: import.meta.env.VITE_PROXY_URL,
|
|
2594
|
+
product: 'my-product',
|
|
2595
|
+
env: 'prd',
|
|
2596
|
+
}}
|
|
2597
|
+
autoConnect={false}
|
|
2598
|
+
>
|
|
2599
|
+
<MyApp />
|
|
2600
|
+
</DuctapeProvider>
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
DuctapeProvider props:
|
|
2605
|
+
config IDuctapeClientConfig — publishableKey (or accessKey), baseUrl, product, env
|
|
2606
|
+
autoConnect boolean (default false) — connect WebSocket automatically on mount
|
|
2607
|
+
onConnected () => void
|
|
2608
|
+
onDisconnected () => void
|
|
2609
|
+
onError (error: Error) => void
|
|
2610
|
+
onConnectionChange (state: ConnectionState) => void
|
|
2611
|
+
|
|
2612
|
+
CORE HOOKS
|
|
2613
|
+
useDuctape() → { client, isConnected, connectionState, connect, disconnect, isReady }
|
|
2614
|
+
useDuctapeContext() → same; throws if called outside DuctapeProvider
|
|
2615
|
+
|
|
2616
|
+
DATABASE HOOKS
|
|
2617
|
+
useDatabase(database)
|
|
2618
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2619
|
+
Call connect() in a useEffect to open the database session.
|
|
2620
|
+
|
|
2621
|
+
useDatabaseQuery(key, queryOptions, hookOptions?)
|
|
2622
|
+
→ { data: IQueryResult<T>, isLoading, error, refetch }
|
|
2623
|
+
key: string | string[] — deduplication key
|
|
2624
|
+
queryOptions: { table, where?, select?, limit?, offset?, sort? }
|
|
2625
|
+
|
|
2626
|
+
useDatabaseInsert(hookOptions?) → { mutate({ table, data }), isLoading, error, data }
|
|
2627
|
+
useDatabaseUpdate(hookOptions?) → { mutate({ table, where, data }), isLoading, error, data }
|
|
2628
|
+
useDatabaseDelete(hookOptions?) → { mutate({ table, where }), isLoading, error, data }
|
|
2629
|
+
|
|
2630
|
+
useDatabaseSubscription(subscribeOptions, hookOptions?)
|
|
2631
|
+
→ { data: T[] | null, isSubscribed, error, unsubscribe, resubscribe }
|
|
2632
|
+
subscribeOptions: { table, where?, select? }
|
|
2633
|
+
Requires autoConnect: true (or manual connect()) and databases.connect() before subscribing.
|
|
2634
|
+
|
|
2635
|
+
FEATURE HOOKS
|
|
2636
|
+
useFeatureExecute(hookOptions?)
|
|
2637
|
+
→ { mutate({ feature, input?, product?, env? }), isLoading, error, data }
|
|
2638
|
+
useFeatureStatus(statusInput, hookOptions?)
|
|
2639
|
+
→ { data: FeatureStatus, isLoading, error, refetch }
|
|
2640
|
+
statusInput: { feature, executionId, product?, env? }
|
|
2641
|
+
useFeatureSubscription(subscribeOptions, hookOptions?)
|
|
2642
|
+
→ { data: FeatureStatusEvent[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2643
|
+
subscribeOptions: { feature, executionId, product?, env? }
|
|
2644
|
+
useFeatureSignal(hookOptions?) → { mutate({ feature, executionId, signal, data? }), isLoading }
|
|
2645
|
+
useFeatureCancel(hookOptions?) → { mutate({ feature, executionId, reason? }), isLoading }
|
|
2646
|
+
|
|
2647
|
+
AGENT HOOKS
|
|
2648
|
+
useAgentRun(hookOptions?)
|
|
2649
|
+
→ { mutate({ tag, input?, sessionId? }), isLoading, error, data: IAgentExecutionResult }
|
|
2650
|
+
useAgentStream(tag, input?, hookOptions?)
|
|
2651
|
+
→ { events, content, isStreaming, isComplete, error, start, stop, reset }
|
|
2652
|
+
content: accumulated string from all stream text events
|
|
2653
|
+
useAgentStatus(statusInput, hookOptions?) → { data, isLoading, error, refetch }
|
|
2654
|
+
useAgentSignal(hookOptions?) → { mutate, isLoading, error }
|
|
2655
|
+
|
|
2656
|
+
BROKER HOOKS
|
|
2657
|
+
useBroker(broker, options?)
|
|
2658
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2659
|
+
useBrokerPublish(hookOptions?)
|
|
2660
|
+
→ { mutate({ topic, message, headers?, key? }), isLoading, error, data }
|
|
2661
|
+
useBrokerSubscription(subscribeOptions, hookOptions?)
|
|
2662
|
+
→ { data: BrokerMessage[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2663
|
+
subscribeOptions: { topic, group? }
|
|
2664
|
+
|
|
2665
|
+
GRAPH HOOKS
|
|
2666
|
+
useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2667
|
+
useCreateNode(hookOptions?) → { mutate({ labels, properties }), isLoading, error, data }
|
|
2668
|
+
useUpdateNode(hookOptions?) → { mutate({ id, properties }), isLoading, error, data }
|
|
2669
|
+
useDeleteNode(hookOptions?) → { mutate({ id, detach? }), isLoading, error, data }
|
|
2670
|
+
useQueryNodes(key, options, hookOptions?) → { data, isLoading, error }
|
|
2671
|
+
useCreateRelationship(hookOptions?) → { mutate, isLoading, error, data }
|
|
2672
|
+
useDeleteRelationship(hookOptions?) → { mutate, isLoading, error }
|
|
2673
|
+
useQueryRelationships(key, options, hookOptions?) → { data, isLoading, error }
|
|
2674
|
+
useTraverse(key, options, hookOptions?) → { data, isLoading, error }
|
|
2675
|
+
useShortestPath(key, options, hookOptions?) → { data, isLoading, error }
|
|
2676
|
+
useGraphSubscription(subscribeOptions, hookOptions?)
|
|
2677
|
+
→ { data, isSubscribed, error, unsubscribe, resubscribe }
|
|
2678
|
+
|
|
2679
|
+
VECTOR HOOKS
|
|
2680
|
+
useVector(vectorTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2681
|
+
useVectorQuery(key, options, hookOptions?) → { data, isLoading, error }
|
|
2682
|
+
useVectorSearch(key, options, hookOptions?) → { data, isLoading, error }
|
|
2683
|
+
useVectorUpsert(hookOptions?) → { mutate({ id, values, metadata? }), isLoading, error, data }
|
|
2684
|
+
useVectorDelete(hookOptions?) → { mutate({ ids }), isLoading, error }
|
|
2685
|
+
useVectorFetch(key, options, hookOptions?) → { data: VectorFetchResult, isLoading, error }
|
|
2686
|
+
useVectorStats(key, options, hookOptions?) → { data: VectorStatsResult, isLoading, error }
|
|
2687
|
+
useVectorNamespaces(key, options, hookOptions?) → { data, isLoading, error }
|
|
2688
|
+
|
|
2689
|
+
STORAGE HOOKS
|
|
2690
|
+
useUpload(hookOptions?)
|
|
2691
|
+
→ { upload({ storage, fileName, buffer, mimeType? }), isLoading, progress, error, data }
|
|
2692
|
+
progress: 0–100 number
|
|
2693
|
+
useDownload(hookOptions?) → { mutate, isLoading, error, data }
|
|
2694
|
+
useStorageDelete(hookOptions?) → { mutate, isLoading, error }
|
|
2695
|
+
useListFiles(key, options, hookOptions?) → { data, isLoading, error }
|
|
2696
|
+
useSignedUrl(key, options, hookOptions?) → { data: string, isLoading, error }
|
|
2697
|
+
|
|
2698
|
+
SESSION HOOKS
|
|
2699
|
+
useSessionStart(hookOptions?) → { mutate, isLoading, error, data: { token, refreshToken } }
|
|
2700
|
+
useSessionVerify(hookOptions?) → { mutate, isLoading, error, data }
|
|
2701
|
+
useSessionRefresh(hookOptions?) → { mutate, isLoading, error, data }
|
|
2702
|
+
useSessionRevoke(hookOptions?) → { mutate, isLoading, error }
|
|
2703
|
+
useSessionRevokeAll(hookOptions?) → { mutate, isLoading, error }
|
|
2704
|
+
useSessionList(key, options, hookOptions?) → { data, isLoading, error }
|
|
2705
|
+
useSessionAutoRefresh(config)
|
|
2706
|
+
→ { token, isRefreshing }; auto-refreshes before expiry; no manual call needed
|
|
2707
|
+
|
|
2708
|
+
CACHE HOOKS
|
|
2709
|
+
useCacheGet(key, options, hookOptions?) → { data, isLoading, error }
|
|
2710
|
+
useCacheSet(hookOptions?) → { mutate({ key, value, expiry? }), isLoading, error }
|
|
2711
|
+
useCacheDelete(hookOptions?) → { mutate({ key }), isLoading, error }
|
|
2712
|
+
useCacheExists(key, options, hookOptions?) → { data: boolean, isLoading, error }
|
|
2713
|
+
useCacheGetMany / useCacheSetMany / useCacheDeleteMany
|
|
2714
|
+
|
|
2715
|
+
RESILIENCE HOOKS
|
|
2716
|
+
useHealthStatus(key, options, hookOptions?)
|
|
2717
|
+
→ { data: HealthStatusResult, isLoading, error, refetch }
|
|
2718
|
+
useHealthSubscription(subscribeOptions, hookOptions?)
|
|
2719
|
+
→ { data: HealthChangeEvent[], isSubscribed, error, unsubscribe, resubscribe }
|
|
2720
|
+
useQuotaCheck(hookOptions?) → { mutate, isLoading, error, data: QuotaCheckResult }
|
|
2721
|
+
useQuotaStatus(key, options, hookOptions?) → { data, isLoading, error }
|
|
2722
|
+
useQuotaSubscription(subscribeOptions, hookOptions?) → { data, isSubscribed, error }
|
|
2723
|
+
|
|
2724
|
+
WAREHOUSE HOOKS
|
|
2725
|
+
useWarehouseQuery / useWarehouseSelect / useWarehouseInsert
|
|
2726
|
+
useWarehouseUpdate / useWarehouseDelete / useWarehouseUpsert / useWarehouseTransaction
|
|
2727
|
+
|
|
2728
|
+
ACTIONS HOOKS
|
|
2729
|
+
useActions(app) → { isConnected, connect, disconnect }
|
|
2730
|
+
useActionRun(hookOptions?) → { mutate, isLoading, error, data }
|
|
2731
|
+
|
|
2732
|
+
ANALYTICS HOOK
|
|
2733
|
+
useAnalytics() → { pageview, track, identify }
|
|
2734
|
+
|
|
2735
|
+
hookOptions pattern (applies to all hooks):
|
|
2736
|
+
enabled? boolean — false skips auto-fetch/subscribe
|
|
2737
|
+
onSuccess? (data) => void
|
|
2738
|
+
onError? (error: Error) => void
|
|
2739
|
+
Subscriptions also accept: onData, onSubscribe, onUnsubscribe
|
|
2740
|
+
|
|
2741
|
+
EXAMPLE
|
|
2742
|
+
// main.tsx
|
|
2743
|
+
import { DuctapeProvider } from '@ductape/react';
|
|
2744
|
+
root.render(
|
|
2745
|
+
<DuctapeProvider config={{ publishableKey: 'pk_...', baseUrl: '...', product: 'my-app', env: 'prd' }} autoConnect>
|
|
2746
|
+
<App />
|
|
2747
|
+
</DuctapeProvider>
|
|
2748
|
+
);
|
|
2749
|
+
|
|
2750
|
+
// UsersList.tsx
|
|
2751
|
+
import { useDatabaseQuery } from '@ductape/react';
|
|
2752
|
+
function UsersList() {
|
|
2753
|
+
const { data, isLoading } = useDatabaseQuery('users', { table: 'users', limit: 20 });
|
|
2754
|
+
if (isLoading) return <p>Loading...</p>;
|
|
2755
|
+
return <ul>{data?.rows.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
// FeatureTracker.tsx
|
|
2759
|
+
import { useFeatureSubscription } from '@ductape/react';
|
|
2760
|
+
function FeatureTracker({ executionId }) {
|
|
2761
|
+
const { data: events } = useFeatureSubscription(
|
|
2762
|
+
{ feature: 'onboard-user', executionId },
|
|
2763
|
+
{ onData: (evs) => console.log('status:', evs[0].status) },
|
|
2764
|
+
);
|
|
2765
|
+
return <p>{events?.[0]?.status ?? 'waiting'}</p>;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
DEPRECATED ALIASES
|
|
2769
|
+
useWorkflowExecute → useFeatureExecute
|
|
2770
|
+
useWorkflowStatus → useFeatureStatus
|
|
2771
|
+
useWorkflowSubscription → useFeatureSubscription
|
|
2772
|
+
useWorkflowSignal → useFeatureSignal
|
|
2773
|
+
useWorkflowCancel → useFeatureCancel
|
|
2774
|
+
`.trim(),
|
|
2775
|
+
|
|
2776
|
+
vue: `
|
|
2777
|
+
DUCTAPE VUE (@ductape/vue)
|
|
2778
|
+
|
|
2779
|
+
Vue 3 composables and plugin for Ductape. Wraps @ductape/client.
|
|
2780
|
+
Requires vue >= 3.
|
|
2781
|
+
|
|
2782
|
+
INSTALL
|
|
2783
|
+
npm install @ductape/vue
|
|
2784
|
+
|
|
2785
|
+
SETUP — install the plugin at app root
|
|
2786
|
+
// main.ts
|
|
2787
|
+
import { createApp } from 'vue';
|
|
2788
|
+
import { createDuctape } from '@ductape/vue';
|
|
2789
|
+
import App from './App.vue';
|
|
2790
|
+
|
|
2791
|
+
const app = createApp(App);
|
|
2792
|
+
|
|
2793
|
+
app.use(createDuctape({
|
|
2794
|
+
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
|
|
2795
|
+
baseUrl: import.meta.env.VITE_PROXY_URL,
|
|
2796
|
+
product: 'my-product',
|
|
2797
|
+
env: 'prd',
|
|
2798
|
+
autoConnect: false, // true = connect WebSocket when plugin installs
|
|
2799
|
+
}));
|
|
2800
|
+
|
|
2801
|
+
app.mount('#app');
|
|
2802
|
+
|
|
2803
|
+
Plugin options (extends IDuctapeClientConfig):
|
|
2804
|
+
publishableKey / accessKey — auth
|
|
2805
|
+
baseUrl — proxy URL
|
|
2806
|
+
product, env — defaults for all composables
|
|
2807
|
+
autoConnect — boolean (default false)
|
|
2808
|
+
|
|
2809
|
+
The client is provided via inject(DUCTAPE_INJECTION_KEY) and is available on
|
|
2810
|
+
this.$ductape in Options API components.
|
|
2811
|
+
|
|
2812
|
+
CORE COMPOSABLE
|
|
2813
|
+
useDuctape()
|
|
2814
|
+
→ { client, isReady, isConnected: Ref<boolean>, connectionState: Ref<ConnectionState>,
|
|
2815
|
+
error: Ref<Error|null>, connect, disconnect }
|
|
2816
|
+
|
|
2817
|
+
DATABASE COMPOSABLES
|
|
2818
|
+
useDatabase(database)
|
|
2819
|
+
→ { isConnected, isConnecting, error, connect, disconnect } — all Refs
|
|
2820
|
+
Call connect() in onMounted to open the database session.
|
|
2821
|
+
|
|
2822
|
+
useDatabaseQuery(key, queryOptions, composableOptions?)
|
|
2823
|
+
→ { data: Ref<IQueryResult<T>|null>, isLoading: Ref<boolean>, error: Ref<Error|null>, refetch }
|
|
2824
|
+
|
|
2825
|
+
useDatabaseInsert(composableOptions?) → { mutate({ table, data }), isLoading, error, data }
|
|
2826
|
+
useDatabaseUpdate(composableOptions?) → { mutate({ table, where, data }), isLoading, error, data }
|
|
2827
|
+
useDatabaseDelete(composableOptions?) → { mutate({ table, where }), isLoading, error, data }
|
|
2828
|
+
|
|
2829
|
+
useDatabaseSubscription(subscribeOptions, composableOptions?)
|
|
2830
|
+
→ { data: Ref<T[]|null>, isSubscribed: Ref<boolean>, error, unsubscribe, resubscribe }
|
|
2831
|
+
subscribeOptions: { table, where?, select? }
|
|
2832
|
+
|
|
2833
|
+
FEATURE COMPOSABLES
|
|
2834
|
+
useFeatureExecute(composableOptions?)
|
|
2835
|
+
→ { mutate({ feature, input?, product?, env? }), isLoading, error, data }
|
|
2836
|
+
useFeatureStatus(statusInput, composableOptions?)
|
|
2837
|
+
→ { data: Ref<FeatureStatus|null>, isLoading, error, refetch }
|
|
2838
|
+
useFeatureSubscription(subscribeOptions, composableOptions?)
|
|
2839
|
+
→ { data: Ref<FeatureStatusEvent[]|null>, isSubscribed, error, unsubscribe, resubscribe }
|
|
2840
|
+
subscribeOptions: { feature, executionId, product?, env? }
|
|
2841
|
+
useFeatureSignal(composableOptions?) → { mutate, isLoading, error }
|
|
2842
|
+
useFeatureCancel(composableOptions?) → { mutate, isLoading, error }
|
|
2843
|
+
|
|
2844
|
+
AGENT COMPOSABLES
|
|
2845
|
+
useAgentRun(composableOptions?)
|
|
2846
|
+
→ { mutate({ tag, input?, sessionId? }), isLoading, error, data }
|
|
2847
|
+
useAgentStream(tag, input?, composableOptions?)
|
|
2848
|
+
→ { events, content, isStreaming, isComplete, error, start, stop, reset }
|
|
2849
|
+
content: Ref<string> accumulated from stream text events
|
|
2850
|
+
useAgentStatus(statusInput, composableOptions?) → { data, isLoading, error, refetch }
|
|
2851
|
+
useAgentSignal(composableOptions?) → { mutate, isLoading, error }
|
|
2852
|
+
|
|
2853
|
+
BROKER COMPOSABLES
|
|
2854
|
+
useBroker(broker, options?)
|
|
2855
|
+
→ { isConnected, isConnecting, error, connect, disconnect }
|
|
2856
|
+
useBrokerPublish(composableOptions?)
|
|
2857
|
+
→ { mutate({ topic, message, headers?, key? }), isLoading, error, data }
|
|
2858
|
+
useBrokerSubscription(subscribeOptions, composableOptions?)
|
|
2859
|
+
→ { data: Ref<BrokerMessage[]|null>, isSubscribed, error, unsubscribe, resubscribe }
|
|
2860
|
+
subscribeOptions: { topic, group? }
|
|
2861
|
+
|
|
2862
|
+
GRAPH COMPOSABLES
|
|
2863
|
+
useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2864
|
+
useCreateNode / useUpdateNode / useDeleteNode / useQueryNodes
|
|
2865
|
+
useCreateRelationship / useDeleteRelationship / useQueryRelationships
|
|
2866
|
+
useTraverse / useShortestPath
|
|
2867
|
+
useGraphSubscription(subscribeOptions, composableOptions?)
|
|
2868
|
+
→ { data, isSubscribed, error, unsubscribe, resubscribe }
|
|
2869
|
+
|
|
2870
|
+
VECTOR COMPOSABLES
|
|
2871
|
+
useVector(vectorTag) → { isConnected, isConnecting, error, connect, disconnect }
|
|
2872
|
+
useVectorQuery / useVectorSearch / useVectorUpsert / useVectorDelete
|
|
2873
|
+
useVectorFetch / useVectorStats / useVectorNamespaces
|
|
2874
|
+
|
|
2875
|
+
STORAGE COMPOSABLES
|
|
2876
|
+
useUpload(composableOptions?)
|
|
2877
|
+
→ { upload({ storage, fileName, buffer, mimeType? }), isLoading, progress: Ref<number>, error, data }
|
|
2878
|
+
useDownload / useStorageDelete / useListFiles / useSignedUrl
|
|
2879
|
+
|
|
2880
|
+
SESSION COMPOSABLES
|
|
2881
|
+
useSessionStart / useSessionVerify / useSessionRefresh
|
|
2882
|
+
useSessionRevoke / useSessionRevokeAll / useSessionList
|
|
2883
|
+
useSessionAutoRefresh(config) → { token: Ref<string|null>, isRefreshing: Ref<boolean> }
|
|
2884
|
+
|
|
2885
|
+
CACHE COMPOSABLES
|
|
2886
|
+
useCacheGet / useCacheSet / useCacheDelete / useCacheExists
|
|
2887
|
+
useCacheGetMany / useCacheSetMany / useCacheDeleteMany
|
|
2888
|
+
|
|
2889
|
+
RESILIENCE COMPOSABLES
|
|
2890
|
+
useHealthStatus / useHealthSubscription
|
|
2891
|
+
useQuotaRun / useQuotaCheck / useQuotaStatus / useQuotaSubscription
|
|
2892
|
+
useFallbackRun
|
|
2893
|
+
|
|
2894
|
+
WAREHOUSE COMPOSABLES
|
|
2895
|
+
useWarehouseQuery / useWarehouseSelect / useWarehouseInsert
|
|
2896
|
+
useWarehouseUpdate / useWarehouseDelete / useWarehouseUpsert / useWarehouseTransaction
|
|
2897
|
+
|
|
2898
|
+
ACTIONS COMPOSABLES
|
|
2899
|
+
useActions(app) → { isConnected, connect, disconnect }
|
|
2900
|
+
useActionRun(composableOptions?) → { mutate, isLoading, error, data }
|
|
2901
|
+
|
|
2902
|
+
ANALYTICS COMPOSABLE
|
|
2903
|
+
useAnalytics() → { pageview, track, identify }
|
|
2904
|
+
|
|
2905
|
+
composableOptions pattern (applies to all composables):
|
|
2906
|
+
enabled? boolean — false skips auto-fetch/subscribe
|
|
2907
|
+
onSuccess? (data) => void
|
|
2908
|
+
onError? (error: Error) => void
|
|
2909
|
+
Subscriptions also accept: onData, onSubscribe, onUnsubscribe
|
|
2910
|
+
|
|
2911
|
+
EXAMPLE
|
|
2912
|
+
<!-- UsersList.vue -->
|
|
2913
|
+
<script setup>
|
|
2914
|
+
import { useDatabaseQuery } from '@ductape/vue';
|
|
2915
|
+
const { data, isLoading, error } = useDatabaseQuery('users', { table: 'users', limit: 20 });
|
|
2916
|
+
</script>
|
|
2917
|
+
<template>
|
|
2918
|
+
<div v-if="isLoading">Loading...</div>
|
|
2919
|
+
<div v-else-if="error">{{ error.message }}</div>
|
|
2920
|
+
<ul v-else>
|
|
2921
|
+
<li v-for="user in data?.rows" :key="user.id">{{ user.name }}</li>
|
|
2922
|
+
</ul>
|
|
2923
|
+
</template>
|
|
2924
|
+
|
|
2925
|
+
<!-- FeatureTracker.vue -->
|
|
2926
|
+
<script setup>
|
|
2927
|
+
import { useFeatureSubscription } from '@ductape/vue';
|
|
2928
|
+
const props = defineProps(['executionId']);
|
|
2929
|
+
const { data: events, isSubscribed } = useFeatureSubscription(
|
|
2930
|
+
{ feature: 'onboard-user', executionId: props.executionId },
|
|
2931
|
+
);
|
|
2932
|
+
</script>
|
|
2933
|
+
<template>
|
|
2934
|
+
<p>{{ events?.[0]?.status ?? 'waiting' }}</p>
|
|
2935
|
+
</template>
|
|
2936
|
+
|
|
2937
|
+
DEPRECATED ALIASES
|
|
2938
|
+
useWorkflowExecute → useFeatureExecute
|
|
2939
|
+
useWorkflowStatus → useFeatureStatus
|
|
2940
|
+
useWorkflowSubscription → useFeatureSubscription
|
|
2941
|
+
useWorkflowSignal → useFeatureSignal
|
|
2942
|
+
useWorkflowCancel → useFeatureCancel
|
|
2943
|
+
`.trim(),
|
|
2415
2944
|
};
|
|
2416
2945
|
|
|
2417
2946
|
const docsHandler = async (args: { topic: string }) => {
|
|
@@ -2723,7 +3252,7 @@ async function main() {
|
|
|
2723
3252
|
'index strategy, operation types) that should be confirmed with the user first.\n\n' +
|
|
2724
3253
|
'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
2725
3254
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
2726
|
-
'notifications, resilience, features, events, logs',
|
|
3255
|
+
'notifications, resilience, features, events, logs, client, react, vue',
|
|
2727
3256
|
inputSchema: docsInputSchema,
|
|
2728
3257
|
},
|
|
2729
3258
|
docsHandler,
|