@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.
Files changed (3) hide show
  1. package/dist/index.js +527 -1
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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,