@graph-compose/client
v1.0.4
Published
Client SDK for Graph Compose
Readme
@graph-compose/client
A fluent TypeScript client for building declarative, Temporal-orchestrated workflows. Compose HTTP-based microservices or multi-agent AI systems with a simple, type-safe API.
Features
- HTTP Workflow Orchestration - Chain HTTP services with dependency management
- Multi-Agent AI Workflows - Build ADK (Agent Development Kit) hierarchies with LLMs, tools, and orchestrators
- Type-Safe Builder Pattern - Fluent API with full TypeScript support
- JSONata Templating - Dynamic parameter resolution with
{{ }}expressions - Automatic Retry & Error Handling - Configure retry policies and error boundaries
- Built-in Validation - Zod-based schema validation for workflows
- Temporal Integration - Native support for Temporal workflow execution
Installation
npm install @graph-compose/client
# or
pnpm add @graph-compose/client
# or
yarn add @graph-compose/clientAuthentication
The client requires an API token to execute workflows against the GraphCompose API:
// Option 1: Pass token in constructor
const graph = new GraphCompose({
token: 'your-api-token'
});
// Option 2: Set environment variable
// GRAPH_COMPOSE_TOKEN=your-api-token
const graph = new GraphCompose();API Endpoint: https://api.graphcompose.io/api/v1
Get your API token from the GraphCompose Dashboard
Quick Start
End-to-End Example
import { GraphCompose } from '@graph-compose/client';
// 1. Build your workflow
const graph = new GraphCompose({
token: process.env.GRAPH_COMPOSE_TOKEN
});
graph
.node('fetchData')
.get('https://api.example.com/data')
.end()
.node('processData')
.post('https://api.example.com/process')
.withBody({ data: '{{results.fetchData}}' })
.withDependencies(['fetchData'])
.end();
// 2. Validate it
const validation = graph.validate();
if (!validation.isValid) {
throw new Error(`Invalid workflow: ${validation.errors.map(e => e.message).join(', ')}`);
}
// 3. Execute it
const result = await graph.execute({
context: { userId: '123' }
});
// 4. Poll for completion and get results
if (result.success && result.data) {
const workflowId = result.data.workflowId;
// Poll until complete
let status = 'RUNNING';
while (status === 'RUNNING') {
await new Promise(resolve => setTimeout(resolve, 1000));
const statusResult = await graph.getWorkflowStatus(workflowId);
if (statusResult.success && statusResult.data) {
status = statusResult.data.status;
}
}
// Get final results
const finalResult = await graph.getWorkflowStatus(workflowId);
if (finalResult.success && finalResult.data?.status === 'COMPLETED') {
console.log('Results:', finalResult.data.execution_state);
}
}Traditional HTTP Workflow
Build a workflow that chains multiple HTTP services:
import { GraphCompose } from '@graph-compose/client';
const graph = new GraphCompose({
token: 'your-api-token'
});
graph
.node('fetchUser')
.get('https://api.example.com/users/{{userId}}')
.withHeaders({
'Authorization': 'Bearer {{$secret("api_token")}}'
})
.end()
.node('enrichProfile')
.post('https://api.example.com/enrich')
.withBody({
userId: '{{results.fetchUser.id}}',
email: '{{results.fetchUser.email}}'
})
.withDependencies(['fetchUser'])
.end();ADK Multi-Agent Workflow
Build an AI agent workflow with tools and orchestration:
import {
GraphCompose,
createLlmAgent,
createHttpTool
} from '@graph-compose/client';
const graph = new GraphCompose();
graph
.adk('aiAgent')
.withWorkflow(builder =>
builder
.rootAgent('assistant')
.agent(createLlmAgent({
id: 'assistant',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST'
},
tools: ['searchTool'],
outputKey: 'assistant_response'
}))
.httpTool(createHttpTool({
id: 'searchTool',
httpConfig: {
url: 'https://api.example.com/search',
method: 'POST',
body: {
query: '{{state.user_query}}'
}
}
}))
.build()
)
.withInitialPrompt('Hello! How can I help you today?')
.withMaxCycles(10)
.end();HTTP Workflow Builder
Basic HTTP Methods
const graph = new GraphCompose();
// GET request
graph.node('getUser')
.get('https://api.example.com/users/123')
.end();
// POST request
graph.node('createUser')
.post('https://api.example.com/users')
.withBody({ name: 'John', email: '[email protected]' })
.end();
// PUT, PATCH, DELETE
graph.node('updateUser').put('https://api.example.com/users/123').end();
graph.node('patchUser').patch('https://api.example.com/users/123').end();
graph.node('deleteUser').delete('https://api.example.com/users/123').end();Headers and Authentication
graph.node('secureRequest')
.post('https://api.example.com/data')
.withHeaders({
'Authorization': 'Bearer {{$secret("api_token")}}',
'X-Request-ID': '{{context.requestId}}',
'Content-Type': 'application/json'
})
.end();Headers also support JSONata parameter objects for dynamic values:
graph.node('dynamicRequest')
.post('https://api.example.com/data')
.withHeaders({
'Authorization': { jsonataExpression: '"Bearer " & $secret("api_token")' }
})
.end();Dynamic Body
graph.node('processOrder')
.post('https://api.example.com/orders')
.withBody({
orderId: '{{results.createOrder.id}}',
userId: '{{context.userId}}',
items: '{{results.fetchCart.items}}',
total: '{{results.calculateTotal.amount}}'
})
.end();Dependencies and Ordering
// Sequential execution
graph
.node('step1').get('https://api.example.com/step1').end()
.node('step2').post('https://api.example.com/step2').withDependencies(['step1']).end()
.node('step3').post('https://api.example.com/step3').withDependencies(['step2']).end();
// Parallel execution (no dependencies)
graph
.node('fetchUserData').get('https://api.example.com/users').end()
.node('fetchOrderData').get('https://api.example.com/orders').end()
.node('fetchPreferences').get('https://api.example.com/prefs').end();
// Fan-out then fan-in
graph
.node('fetchData1').get('https://api.example.com/data1').end()
.node('fetchData2').get('https://api.example.com/data2').end()
.node('aggregate')
.post('https://api.example.com/aggregate')
.withDependencies(['fetchData1', 'fetchData2'])
.end();Activity Configuration
Configure Temporal activity behavior:
graph.node('reliableCall')
.post('https://api.example.com/process')
.withStartToCloseTimeout('30s')
.withScheduleToCloseTimeout('5m')
.withRetries({
maximumAttempts: 5,
initialInterval: '1s',
backoffCoefficient: 2.0,
maximumInterval: '30s'
})
.end();Input/Output Validation
Validate node inputs and outputs using Zod schemas:
import { z } from 'zod';
graph.node('validateRequest')
.post('https://api.example.com/process')
.withBody({ data: '{{context.input}}' })
.withValidation({
input: z.object({
data: z.string().min(1)
}),
output: z.object({
result: z.string(),
status: z.number()
})
})
.end();Error Boundaries
Wrap nodes with error handling. Error boundaries protect specified nodes and execute their own HTTP endpoint when an error occurs:
graph
.node('riskyOperation')
.post('https://api.example.com/risky')
.end()
.errorBoundary('handleError', ['riskyOperation'])
.post('https://api.example.com/fallback')
.withBody({ error: '{{results.error}}', nodeId: '{{results.nodeId}}' })
.end();Conditional Branching
Use withConditions() for conditional workflow control flow:
graph
.node('checkStatus')
.get('https://api.example.com/status')
.withConditions({
// Terminate the workflow when a condition is met
terminateWhen: ['{{results.checkStatus.state = "done"}}'],
// Continue to specific nodes based on conditions
continueTo: [
{ to: 'processData', when: '{{results.checkStatus.state = "process"}}' },
{ to: 'reviewData', when: '{{results.checkStatus.state = "review"}}' }
],
// Poll until a condition is met
pollUntil: ['{{results.checkStatus.ready = true}}']
})
.end()
.node('processData')
.post('https://api.example.com/process')
.withDependencies(['checkStatus'])
.end()
.node('reviewData')
.post('https://api.example.com/review')
.withDependencies(['checkStatus'])
.end();ADK Agent Workflows
Agent Types
LLM Agent
The primary agent that interacts with language models:
import { createLlmAgent } from '@graph-compose/client';
createLlmAgent({
id: 'assistant',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST'
},
instructions: 'You are a helpful assistant.',
tools: ['searchTool', 'calculatorTool'],
outputKey: 'assistant_response',
activityConfig: {
startToCloseTimeout: '30s',
retryPolicy: {
maximumAttempts: 3
}
}
})Sequential Agent
Executes sub-agents in order:
import { createSequentialAgent, createSubAgentReference } from '@graph-compose/client';
createSequentialAgent({
id: 'pipeline',
subAgents: [
createSubAgentReference('extractor'),
createSubAgentReference('transformer'),
createSubAgentReference('loader')
],
outputKey: 'pipeline_result'
})Note: SequentialAgent runs natively in the workflow (not as an HTTP activity).
Parallel Agent
Executes sub-agents concurrently:
import { createParallelAgent, createSubAgentReference } from '@graph-compose/client';
createParallelAgent({
id: 'fanOut',
subAgents: [
createSubAgentReference('analyzer1'),
createSubAgentReference('analyzer2'),
createSubAgentReference('analyzer3')
]
})Note: ParallelAgent does NOT support
outputKey. Individual sub-agents should have their ownoutputKeyvalues. ParallelAgent runs natively in the workflow.
Loop Agent
Repeats sub-agents with iteration limits:
import { createLoopAgent, createSubAgentReference } from '@graph-compose/client';
createLoopAgent({
id: 'retryLoop',
subAgents: [
createSubAgentReference('worker')
],
maxAgentLoopIterations: 5,
loopExitCondition: '{{state.task_completed = true}}',
outputKey: 'loop_result'
})Note: LoopAgent runs natively in the workflow (not as an HTTP activity).
Tools
HTTP Tool
Execute external HTTP services:
import { createHttpTool } from '@graph-compose/client';
createHttpTool({
id: 'searchTool',
httpConfig: {
url: 'https://api.example.com/search',
method: 'POST',
body: {
query: '{{state.user_query}}',
filters: '{{state.filters}}'
},
headers: {
'Authorization': 'Bearer {{$secret("search_api_key")}}'
}
},
outputKey: 'search_results'
})Agent Tool
Delegate to another agent (specialist pattern):
import { createAgentTool } from '@graph-compose/client';
createAgentTool({
id: 'specialistTool',
targetAgentId: 'specialist',
skipSummarization: false,
outputKey: 'specialist_result'
})Complete ADK Example
import {
GraphCompose,
createLlmAgent,
createHttpTool,
createAgentTool,
createSubAgentReference
} from '@graph-compose/client';
const graph = new GraphCompose();
graph
.adk('aiWorkflow')
.withWorkflow(builder =>
builder
// Set the entry point
.rootAgent('coordinator')
// Main coordinator agent
.agent(createLlmAgent({
id: 'coordinator',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST'
},
tools: ['researchTool', 'analysisTool'],
outputKey: 'coordinator_response'
}))
// Research specialist
.agent(createLlmAgent({
id: 'researcher',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST'
},
tools: ['searchTool'],
outputKey: 'research_results'
}))
// Analysis specialist
.agent(createLlmAgent({
id: 'analyst',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST'
},
outputKey: 'analysis_results'
}))
// HTTP search tool
.httpTool(createHttpTool({
id: 'searchTool',
httpConfig: {
url: 'https://api.example.com/search',
method: 'POST'
},
outputKey: 'search_data'
}))
// Agent tools for delegation
.agentTool(createAgentTool({
id: 'researchTool',
targetAgentId: 'researcher',
skipSummarization: false
}))
.agentTool(createAgentTool({
id: 'analysisTool',
targetAgentId: 'analyst',
skipSummarization: false
}))
// Configure workflow-level settings
.withMaxCycles(20)
.build()
)
.withInitialPrompt('Research and analyze recent AI trends')
.withState({
user_id: 'user_123',
context: 'technology'
})
.end();Advanced Patterns
Agent Handoff Pattern
Nested sub-agents for dynamic routing:
createLlmAgent({
id: 'router',
httpConfig: { url: 'https://api.example.com/llm', method: 'POST' },
subAgents: [
createLlmAgent({
id: 'sales_specialist',
httpConfig: { url: 'https://api.example.com/llm', method: 'POST' }
}),
createLlmAgent({
id: 'support_specialist',
httpConfig: { url: 'https://api.example.com/llm', method: 'POST' }
})
]
})Human-in-the-Loop (HITL)
Agents can request human approval:
// Your agent HTTP service returns:
{
"content": [{"type": "text", "text": "Requesting approval..."}],
"hitl_request": {
"type": "approval",
"message": "Should I proceed with this action?",
"options": ["approve", "reject", "modify"]
}
}State Management
Access shared state across agents:
graph
.adk('workflow')
.withWorkflow(builder =>
builder
.rootAgent('processor')
.agent(createLlmAgent({
id: 'processor',
httpConfig: {
url: 'https://api.example.com/llm',
method: 'POST',
body: {
user_theme: '{{state.user_preferences.theme}}',
auth_status: '{{state.session_data.authenticated}}'
}
},
outputKey: 'processed_data' // Saved to state.processed_data
}))
.build()
)
// Seed initial state
.withState({
user_preferences: { theme: 'dark' },
session_data: { authenticated: true }
})
.withInitialPrompt('Process user data')
.end();Exit Flow Control
Agents can terminate the workflow early:
// Your agent HTTP service returns:
{
"content": [{"type": "text", "text": "Task completed!"}],
"exit_flow": true // Stops workflow execution
}JSONata Templating
All string fields support JSONata expressions with {{ }} delimiters:
Accessing Results
'{{results.fetchUser.id}}' // Node result
'{{results.fetchUser.profile.name}}' // Nested propertyAccessing Context
'{{context.userId}}' // Workflow context
'{{context.tenantId}}' // Any context valueAccessing State (ADK)
'{{state.user_query}}' // Session state
'{{state.preferences.language}}' // Nested stateSecrets
'{{$secret("api_key")}}' // Secret reference
'Bearer {{$secret("auth_token")}}' // In stringsJSONata Operators
// Conditionals
'{{results.status.code = 200 ? "success" : "failure"}}'
// Arithmetic
'{{results.price.amount * 1.1}}'
// String operations
'{{$uppercase(results.user.name)}}'
'{{$substring(results.text, 0, 100)}}'
// Array operations
'{{$count(results.items)}}'
'{{results.items[0].id}}'Validation
Client-Side Validation
The builder performs comprehensive validation:
const validation = graph.validate();
if (!validation.isValid) {
validation.errors.forEach(err => {
console.error(`[${err.name}] ${err.message}`);
});
}Validation Checks
- Node IDs: Unique and valid format (alphanumeric with underscores)
- Dependencies: Valid references, no circular deps
- HTTP Config: Valid URLs, methods, headers
- JSONata: Syntax validation in templates
- ADK Structure: Agent/tool references, type constraints
- Schema Compliance: Zod validation against core schemas
Standalone Validation
import { validateWorkflow } from '@graph-compose/client';
const workflow = graph.getWorkflow();
const result = validateWorkflow(workflow);
if (!result.isValid) {
console.error('Validation failed:', result.errors);
}Server-Side Validation
Validate against the GraphCompose API (includes tier limits and quota checks):
try {
const apiResult = await graph.validateApi();
if (apiResult.success && apiResult.data?.isValid) {
console.log('Workflow passed server validation');
} else {
console.error('Validation failed:', apiResult.data?.errors);
}
} catch (error) {
console.error('API error:', error);
}Endpoint: POST /workflows/validate
Executing Workflows
Async Execution
Start workflow execution and return immediately (recommended for long-running workflows):
const graph = new GraphCompose({ token: 'your-token' });
graph.node('processData')
.post('https://api.example.com/process')
.withBody({ data: 'example' })
.end();
try {
const result = await graph.execute({
context: {
userId: '123',
requestId: 'req-456'
},
webhookUrl: 'https://your-app.com/webhook' // Optional
});
if (result.success && result.data) {
console.log('Workflow started!');
console.log('Workflow ID:', result.data.workflowId);
console.log('Run ID:', result.data.runId);
console.log('Status:', result.data.status); // "RUNNING"
}
} catch (error) {
console.error('Execution failed:', error);
}Endpoint: POST /workflows
Check Workflow Status
Poll for workflow status after async execution:
const workflowId = 'wf_abc123';
try {
const statusResult = await graph.getWorkflowStatus(workflowId);
if (statusResult.success && statusResult.data) {
console.log('Status:', statusResult.data.status);
// Possible values: "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED"
// | "TERMINATED" | "CONTINUED_AS_NEW" | "TIMED_OUT"
console.log('Started at:', statusResult.data.started_at);
if (statusResult.data.status === 'COMPLETED') {
console.log('Results:', statusResult.data.execution_state);
} else if (statusResult.data.status === 'FAILED') {
console.log('Error:', statusResult.data.error);
}
}
} catch (error) {
console.error('Failed to get status:', error);
}Endpoint: GET /workflows/{workflowId}
Terminate Running Workflow
Stop a workflow that's currently executing:
const workflowId = 'wf_abc123';
try {
const result = await graph.terminateWorkflow(workflowId, {
runId: 'run_xyz789', // Optional: specific run ID
reason: 'User requested cancellation' // Optional
});
if (result.success) {
console.log('Workflow terminated successfully');
}
} catch (error) {
console.error('Failed to terminate:', error);
}Endpoint: POST /workflows/{workflowId}/terminate
Webhook Notifications
Receive notifications when async workflows complete:
const graph = new GraphCompose({ token: 'your-token' });
graph.node('longProcess')
.post('https://api.example.com/long-process')
.end();
graph.withWebhookUrl('https://your-app.com/webhook');API Response Format
All API methods return a consistent response structure:
interface ApiResponse<T> {
success: boolean;
message: string;
data: T | null;
}Workflow Status Types
type WorkflowStatus =
| "RUNNING" // Workflow is currently executing
| "COMPLETED" // Workflow finished successfully
| "FAILED" // Workflow encountered an error
| "CANCELLED" // Workflow was cancelled
| "TERMINATED" // Workflow was manually stopped
| "CONTINUED_AS_NEW" // Workflow continued as a new execution
| "TIMED_OUT"; // Workflow exceeded timeoutComplete Execution Example
import { GraphCompose } from '@graph-compose/client';
async function runWorkflow() {
// Build the workflow
const graph = new GraphCompose({ token: process.env.GRAPH_COMPOSE_TOKEN });
graph
.node('fetchUser')
.get('https://api.example.com/users/{{context.userId}}')
.end()
.node('enrichProfile')
.post('https://api.example.com/enrich')
.withBody({
userId: '{{results.fetchUser.id}}',
email: '{{results.fetchUser.email}}'
})
.withDependencies(['fetchUser'])
.end();
// Validate locally
const validation = graph.validate();
if (!validation.isValid) {
throw new Error(`Invalid workflow: ${validation.errors.map(e => e.message).join(', ')}`);
}
// Execute async
const execution = await graph.execute({
context: { userId: '123' },
webhookUrl: 'https://myapp.com/webhook'
});
if (!execution.success || !execution.data) {
throw new Error(`Execution failed: ${execution.message}`);
}
const workflowId = execution.data.workflowId;
console.log('Workflow started:', workflowId);
// Poll for completion
let status = 'RUNNING';
while (status === 'RUNNING') {
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
const statusResult = await graph.getWorkflowStatus(workflowId);
if (statusResult.success && statusResult.data) {
status = statusResult.data.status;
console.log('Current status:', status);
}
}
// Get final results
const finalResult = await graph.getWorkflowStatus(workflowId);
if (finalResult.success && finalResult.data) {
if (finalResult.data.status === 'COMPLETED') {
console.log('Workflow completed successfully!');
console.log('Results:', finalResult.data.execution_state);
} else {
console.error('Workflow failed:', finalResult.data.error);
}
}
}
runWorkflow().catch(console.error);API Reference
GraphCompose
Main workflow builder class.
Constructor
new GraphCompose(options?: { token?: string })options.token- API token (or setGRAPH_COMPOSE_TOKENenv var)
Building Methods
node(id: string): NodeBuilder- Create HTTP nodeadk(id: string): AdkNodeBuilder- Create ADK agent nodeerrorBoundary(id: string, protectedNodes: string[]): NodeBuilder- Create error boundaryaddNode(node: Node): GraphCompose- Add a pre-configured node
Workflow Access Methods
getWorkflow(): WorkflowGraph- Get the complete workflow definitiontoJSON(): WorkflowGraph- Alias forgetWorkflow(), suitable for serialization
Configuration Methods
withContext(context: Record<string, any>): GraphCompose- Set workflow contextwithWebhookUrl(url: string): GraphCompose- Set webhook for async notificationswithWorkflowConfig(config: WorkflowConfig): GraphCompose- Set workflow-level config
Validation Methods
validate(): ClientValidationResult- Client-side validationvalidateApi(): Promise<ApiResponse>- Server-side validation with tier checks
Execution Methods
execute(options?): Promise<ApiResponse>- Execute workflow (returns immediately with workflow ID)getWorkflowStatus(workflowId: string): Promise<ApiResponse>- Check workflow statusterminateWorkflow(workflowId: string, options?): Promise<ApiResponse>- Stop workflow
NodeBuilder
Builder for HTTP and error boundary nodes.
HTTP Methods
get(url: string)- GET requestpost(url: string)- POST requestput(url: string)- PUT requestpatch(url: string)- PATCH requestdelete(url: string)- DELETE request
Configuration
withHeaders(headers: Record<string, string | JsonataParam>)- Set headerswithBody(body: Record<string, any> | string)- Set request bodywithRetries(policy: Partial<RetryPolicy>)- Configure retry policywithStartToCloseTimeout(timeout: string)- Set activity timeoutwithScheduleToCloseTimeout(timeout: string)- Set schedule-to-close timeoutwithDependencies(nodeIds: string[])- Set dependencieswithConditions(conditions: NodeConditions)- Conditional executionwithValidation(config: { input?: ZodType, output?: ZodType })- Input/output validation
Completion
end(): GraphCompose- Finalize node
AdkNodeBuilder
Builder for ADK agent nodes.
Methods
withWorkflow(builderFn: (builder: AdkWorkflowBuilder) => ADKWorkflowDefinition)- Define agent workflow via builder callbackwithInitialPrompt(prompt: string)- Set initial promptwithMaxCycles(max: number)- Set max orchestration cycleswithState(state: Record<string, any>)- Seed initial statewithDependencies(...deps: string[])- Set dependencies (variadic)withActivityConfig(config: ActivityConfig)- Set Temporal activity configend(): GraphCompose- Finalize node
AdkWorkflowBuilder
Builder for agent workflow definitions (passed to withWorkflow() callback).
Methods
rootAgent(agentId: string)- Set entry point agentagent(config: AgentConfig)- Add agenthttpTool(config: GlobalToolDefinition)- Add HTTP toolagentTool(config: GlobalToolDefinition)- Add agent toolwithMaxCycles(max: number)- Set workflow-level max cyclesbuild(): ADKWorkflowDefinition- Build the workflow
Helper Functions
Agent Helpers
createLlmAgent(config)- Create LLM agent configcreateSequentialAgent(config)- Create sequential orchestratorcreateParallelAgent(config)- Create parallel orchestratorcreateLoopAgent(config)- Create loop orchestratorcreateSubAgentReference(agentId)- Create agent reference
Tool Helpers
createHttpTool(config)- Create HTTP tool configcreateAgentTool(config)- Create agent tool config
TypeScript Types
Full TypeScript support with exported types. All commonly used types are available directly from the client package:
import type {
// Core workflow types (re-exported from @graph-compose/core)
WorkflowGraph,
HttpNode,
AdkNode,
ErrorBoundaryNode,
Node,
WorkflowConfig,
RetryPolicy,
ActivityConfig,
NodeConditions,
ADKWorkflowDefinition,
AgentConfig,
GraphWorkflowState,
GlobalToolDefinition,
// Client-specific types
ApiResponse,
ClientValidationResult,
ExecuteOptions,
WorkflowResponse,
GetWorkflowResponse,
TerminateWorkflowResponse,
} from '@graph-compose/client';Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
