@graph-compose/client 1.0.0

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/README.md ADDED
@@ -0,0 +1,646 @@
1
+ # @graph-compose/client
2
+
3
+ Welcome! 👋 This is the Graph Compose client SDK, designed to help you build, validate, and execute workflow graphs using a fluent, type-safe builder pattern in TypeScript.
4
+
5
+ This package works hand-in-hand with `@graph-compose/core`, which defines the underlying structure and validation schemas for workflows. You'll use `@graph-compose/client` to programmatically construct these workflow graphs, starting with basic HTTP operations and dependencies.
6
+
7
+
8
+ ## Features
9
+
10
+ * 🏗️ **Fluent Builder API:** Easily define nodes (like `http` requests) and their configurations using method chaining.
11
+ * 🔗 **Dependency Management:** Explicitly define dependencies between nodes.
12
+ * ✅ **Built-in Validation:** Leverages `@graph-compose/core` schemas to validate your graph definition locally.
13
+ * 🚀 **Workflow Execution:** Provides methods to run your defined workflows asynchronously (`execute`, recommended) or synchronously (`executeSync`).
14
+ * 🔒 **Type Safety:** Full TypeScript support for confidence in your workflow definitions.
15
+ * 🔄 **Expression Support:** Utilize `{{ }}` expressions within node configurations (e.g., URLs, headers), powered by `@graph-compose/core`.
16
+ * *(Support for Agents and Tools is also available - see advanced documentation)*
17
+ * ⏱️ **Timeouts & Retries:** Configure detailed retry policies and timeouts (`startToClose`, `scheduleToClose`) for nodes and tools.
18
+ * ⚙️ **Workflow Configuration:** Set global workflow settings like execution timeouts (`withWorkflowConfig`).
19
+ * 🔀 **Conditional Execution:** Define complex branching logic for HTTP nodes (`withConditions`).
20
+ * 🛡️ **Schema Validation:** Apply Zod-based input/output validation schemas to HTTP nodes (`withValidation`).
21
+
22
+ ## API Reference & Documentation
23
+
24
+ > **Note:** This document focuses specifically on the `@graph-compose/client` SDK for building workflows programmatically. For broader platform documentation, conceptual guides, API schemas, and examples, please visit the main Graph Compose site: [graphcompose.io](https://graphcompose.io).
25
+
26
+ * **Guides & Overviews:** For conceptual guides, integration examples, and broader platform documentation, visit the main Graph Compose documentation site: [graphcompose.io/docs](https://graphcompose.io/docs).
27
+ * **Workflow API Schemas:** For the definitive source of truth on the backend Workflow API request/response schemas (based on Zod/OpenAPI), see: [graphcompose.io/workflows](https://graphcompose.io/workflows).
28
+
29
+ ## Table of Contents
30
+
31
+ - [Features](#features)
32
+ - [API Reference & Documentation](#api-reference--documentation)
33
+ - [Installation](#installation)
34
+ - [Quick Start: Building a Simple HTTP Workflow](#quick-start-building-a-simple-http-workflow)
35
+ - [Method Summary](#method-summary)
36
+ - [Core Concepts](#core-concepts)
37
+ - [Defining Node Types](#defining-node-types)
38
+ - [Defining Tool Types (for Agents)](#defining-tool-types-for-agents)
39
+ - [Workflow Configuration](#workflow-configuration)
40
+ - [Validation](#validation)
41
+ - [Execution](#execution)
42
+ - [Getting the Workflow Definition](#getting-the-workflow-definition)
43
+ - [Interacting with Existing Workflows (API)](#interacting-with-existing-workflows-api)
44
+ - [Full Example: Putting it Together](#full-example-putting-it-together)
45
+ - [Contributing](#contributing)
46
+ - [License](#license)
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ npm install @graph-compose/client @graph-compose/core
52
+ # or
53
+ yarn add @graph-compose/client @graph-compose/core
54
+ ```
55
+ *(Note: `@graph-compose/core` is a required peer dependency.)*
56
+
57
+ ## Quick Start: Building a Simple HTTP Workflow
58
+
59
+ ```typescript
60
+ import { GraphCompose } from '@graph-compose/client';
61
+ import { z } from 'zod'; // Often used with core schemas
62
+
63
+ // Initialize the client (token is required)
64
+ // Reads from process.env.GRAPH_COMPOSE_TOKEN if not provided
65
+ const graph = new GraphCompose({
66
+ token: process.env.GRAPH_COMPOSE_TOKEN || 'your-api-token',
67
+ });
68
+
69
+ // Define a simple workflow with three dependent HTTP steps
70
+ try {
71
+ // Node 1: Fetch initial user data
72
+ graph.node('fetch_user')
73
+ .get('https://api.example.com/users/{{ context.userId }}')
74
+ .withHeaders({ 'X-Request-ID': '{{ $uuid() }}' })
75
+ .withStartToCloseTimeout('5s')
76
+ .end(); // <-- Required: Finalize the 'fetch_user' node definition
77
+
78
+ // Node 2: Fetch user's orders, depending on the first node
79
+ graph.node('fetch_orders')
80
+ .withDependencies(['fetch_user']) // This node runs after 'fetch_user' completes
81
+ .get('https://api.example.com/orders?userId={{ results.fetch_user.id }}')
82
+ .end(); // <-- Required: Finalize the 'fetch_orders' node definition
83
+
84
+ // Node 3: Post-process orders
85
+ graph.node('process_orders')
86
+ .withDependencies(['fetch_orders']) // Depends on 'fetch_orders'
87
+ .post('https://api.example.com/process')
88
+ .withBody({
89
+ orderData: '{{ results.fetch_orders }}',
90
+ processingTimestamp: '{{ $now() }}'
91
+ })
92
+ .end(); // <-- Required: Finalize the 'process_orders' node definition
93
+
94
+ // (Optional) Explicitly validate the workflow definition locally
95
+ // Requires all nodes/tools to be finalized with .end()
96
+ const validation = graph.validate();
97
+ if (!validation.isValid) {
98
+ console.error("Workflow validation failed:", validation.errors);
99
+ throw new Error("Invalid workflow definition");
100
+ } else {
101
+ console.log("Local validation successful!");
102
+ }
103
+
104
+ // Execute the workflow
105
+ // Requires all nodes/tools to be finalized with .end()
106
+ console.log('Executing workflow...');
107
+ const apiResponse = await graph.execute({
108
+ context: { // Initial data for the workflow run
109
+ userId: 'user-789'
110
+ },
111
+ // webhookUrl: 'https://my-service.com/updates' // Optional webhook
112
+ });
113
+
114
+ if (apiResponse.success && apiResponse.data) {
115
+ console.log('Workflow execution started successfully:', apiResponse.data);
116
+ // Access workflowId via apiResponse.data.workflowId
117
+ } else {
118
+ console.error(`Workflow execution failed: ${apiResponse.message}`);
119
+ }
120
+
121
+ } catch (error) {
122
+ console.error('Error building or executing workflow:', error);
123
+ // Handle potential ZodErrors during build if schemas are used directly
124
+ if (error instanceof z.ZodError) {
125
+ console.error("Detailed Zod Errors:", error.errors);
126
+ }
127
+ }
128
+ ```
129
+
130
+
131
+ ## Method Summary
132
+
133
+ This section provides a quick overview of the primary methods available on the `GraphCompose` instance, grouped by purpose.
134
+
135
+ ### Initialization
136
+
137
+ | Method | Purpose |
138
+ | :-------------------------- | :----------------------------------------------------- |
139
+ | `new GraphCompose(options?)` | Creates a new workflow builder instance. Requires API token. |
140
+
141
+ ### Component Definition
142
+
143
+ These methods start the definition of a new workflow component and return a **builder instance**. You **must** call `.end()` on the builder to finalize the component.
144
+
145
+ | Method | Purpose |
146
+ | :---------------------------------- | :------------------------------------------------ |
147
+ | `node(id)` | Starts defining a standard HTTP node. |
148
+ | `agent(id)` | Starts defining an Agent node, which orchestrates calls to your custom HTTP endpoint. |
149
+ | `errorBoundary(id, protectedNodes)` | Starts defining an Error Boundary node. |
150
+ | `tool(id)` | Starts defining a Tool (HTTP or Graph based). |
151
+
152
+ ### Node/Tool Configuration (on Builder)
153
+
154
+ These are examples of methods called on the **builder instance** returned by the definition methods above to configure the specific component.
155
+
156
+ | Method | Purpose |
157
+ | :---------------------------- | :--------------------------------------------------------------------------- |
158
+ | `.get(url)`, `.post(url)`, etc. | Sets the HTTP method and URL (HTTP Nodes/Tools). |
159
+ | `.withHeaders(headers)` | Adds HTTP headers (HTTP Nodes/Tools). |
160
+ | `.withBody(body)` | Sets the request body (HTTP Nodes/Tools). |
161
+ | `.withDependencies(ids)` | Specifies prerequisite nodes (HTTP & Agent Nodes). |
162
+ | `.withRetries(policy)` | Configures automatic retry behavior (HTTP Nodes/Tools). |
163
+ | `.withTools(ids)` | (Agent builder) Declares tools available for the agent's logic to call. |
164
+ | `.addTool(tool)` | (Agent builder) Adds a single tool available for the agent's logic to call. |
165
+ | `.withMaxIterations(n)` | (Agent builder) Sets the maximum number of agent iterations. |
166
+ | `.graph(callback)` | (Tool builder) Defines a sub-graph for a Graph tool. |
167
+ | `.description(text)` | (Tool builder) Sets the description used by agents to select the tool. |
168
+ | `.withConditions(conds)` | (Node builder) Adds conditional execution logic (HTTP Nodes only). |
169
+ | `.withValidation(schema)` | (Node builder) Adds input/output schema validation (HTTP Nodes only). |
170
+ | `.withStartToCloseTimeout(d)` | Sets max time for one execution attempt (HTTP Nodes/Tools). |
171
+ | `.withScheduleToCloseTimeout(d)`| Sets max time from schedule to completion (incl. queue) (HTTP Nodes/Tools). |
172
+
173
+ ### Finalization (on Builder)
174
+
175
+ This method is called on the **builder instance** to finalize the component's definition.
176
+
177
+ | Method | Purpose |
178
+ | :------- | :-------------------------------------------------------------------- |
179
+ | `.end()` | Finalizes the current node/tool definition and adds it to the graph. |
180
+
181
+ ### Workflow Configuration
182
+
183
+ These methods configure the overall workflow. They must be called when no node or tool builder is active.
184
+
185
+ | Method | Purpose |
186
+ | :--------------------------- | :------------------------------------------------ |
187
+ | `withContext(context)` | Sets global context data accessible to all nodes. |
188
+ | `withWebhookUrl(url)` | Sets a webhook URL for workflow event updates. |
189
+ | `withWorkflowConfig(config)` | Sets global workflow settings (e.g., timeouts). |
190
+
191
+ ### Validation
192
+
193
+ These methods validate the workflow definition. They must be called when no node or tool builder is active.
194
+
195
+ | Method | Purpose |
196
+ | :-------------- | :------------------------------------------------------- |
197
+ | `validate()` | Performs local, client-side validation of the structure. |
198
+ | `validateApi()` | Sends the definition to the API for server-side validation. |
199
+
200
+ ### Execution
201
+
202
+ These methods execute the workflow. They must be called when no node or tool builder is active.
203
+
204
+ | Method | Purpose |
205
+ | :--------------------- | :-------------------------------------------------------------------- |
206
+ | `execute(options?)` | Executes the workflow asynchronously, returning immediately. |
207
+ | `executeSync(options?)`| Executes the workflow synchronously, waiting for completion. |
208
+
209
+ ### Retrieving Definition
210
+
211
+ These methods retrieve the built workflow definition. They must be called when no node or tool builder is active.
212
+
213
+ | Method | Purpose |
214
+ | :-------------- | :------------------------------------------------------------------------ |
215
+ | `getWorkflow()` | Returns the complete workflow definition object. |
216
+ | `toJSON()` | Alias for `getWorkflow()`; standard method for `JSON.stringify()`. |
217
+
218
+ ### API Interaction
219
+
220
+ These methods interact with existing workflow instances via the API.
221
+
222
+ | Method | Purpose |
223
+ | :------------------------------------ | :--------------------------------------------------- |
224
+ | `getWorkflowStatus(workflowId)` | Retrieves the current status and details of a workflow. |
225
+ | `terminateWorkflow(workflowId, opts?)`| Requests termination of a running workflow. |
226
+
227
+ ## Core Concepts
228
+
229
+ ### The `GraphCompose` Builder
230
+
231
+ This is the main class you'll instantiate. It holds the state of the workflow being built.
232
+
233
+ ```typescript
234
+ import { GraphCompose } from '@graph-compose/client';
235
+
236
+ // Reads from process.env.GRAPH_COMPOSE_TOKEN if not provided
237
+ const graph = new GraphCompose({
238
+ token: process.env.GRAPH_COMPOSE_TOKEN || 'your-api-token',
239
+ });
240
+ ```
241
+
242
+ ### Defining Nodes and Tools - The `.end()` Method
243
+
244
+ You define the components of your workflow (nodes and tools) by chaining methods starting with `graph.node('node_id')`, `graph.agent('agent_id')`, `graph.errorBoundary('boundary_id', [...])`, or `graph.tool('tool_id')`.
245
+
246
+ * **Configuration:** Chain methods like `.get()`, `.post()`, `.withHeaders()`, `.withBody()`, `.withRetries()`, `.withDependencies()`, etc., to configure the node or tool.
247
+ * **Finalization:** **Crucially, you MUST call `.end()`** after configuring each node or tool. This finalizes its definition, adds it to the graph, and returns control to the main `GraphCompose` instance. Attempting to start defining a new component or perform graph-level operations (like `validate` or `execute`) before calling `.end()` on the *current* component builder will result in an error.
248
+
249
+ ```typescript
250
+ // Correct usage with .end()
251
+ graph
252
+ .node('fetch_data') // Start defining node 'fetch_data'
253
+ .get('https://api.example.com/data')
254
+ .withStartToCloseTimeout('10s')
255
+ .end(); // Finalize 'fetch_data' and return to the main graph builder
256
+
257
+ graph
258
+ .node('process_data') // Start defining node 'process_data'
259
+ .withDependencies(['fetch_data']) // Depends on 'fetch_data'
260
+ .post('https://api.example.com/process')
261
+ .withBody({ input: '{{ results.fetch_data }}' })
262
+ .end(); // Finalize 'process_data'
263
+
264
+ // Now it's safe to perform graph-level operations like validate or execute
265
+ // const validation = graph.validate();
266
+ // const result = await graph.execute();
267
+ ```
268
+
269
+ ## Defining Node Types
270
+
271
+ Nodes represent the primary steps or actions within your workflow. Remember to always finish defining a node with `.end()`.
272
+
273
+ ### HTTP Nodes
274
+
275
+ These are the most basic type, used for making HTTP requests.
276
+
277
+ ```typescript
278
+ graph.node("fetch_user_data")
279
+ .description("Retrieves user details based on context ID.") // Optional description
280
+ .get("https://api.example.com/users/{{context.userId}}")
281
+ .withHeaders({ "Authorization": "Bearer YOUR_SERVICE_TOKEN" })
282
+ .withStartToCloseTimeout('5s') // Example timeout
283
+ .end(); // Required!
284
+ ```
285
+
286
+ ### Agent Nodes
287
+
288
+ Agent nodes orchestrate iterative calls to a user-defined HTTP endpoint. This endpoint implements the agent's logic (which might involve AI models) to process inputs, make decisions, and determine which Tools (other workflow nodes) to use.
289
+
290
+ ```typescript
291
+ graph.agent("process_user_request")
292
+ .description("Handles user queries using available tools.") // Optional
293
+ .withMaxIterations(5) // Limit the number of tool calls/LLM turns
294
+ .withTools(["search_web", "summarize_content"]) // Reference tool IDs (defined separately)
295
+ .post("https://ai.example.com/process") // The endpoint hosting the agent logic
296
+ .withBody({ query: "{{context.userInput}}" })
297
+ .withDependencies(["fetch_user_data"]) // Can depend on other nodes
298
+ .end(); // Required!
299
+ ```
300
+
301
+ ### Error Boundary Nodes
302
+
303
+ These nodes execute *only* if one of the nodes they monitor fails. They are used for error handling and cleanup.
304
+
305
+ ```typescript
306
+ graph.errorBoundary("api_error_handler", ["fetch_user_data", "process_user_request"]) // List of node IDs to monitor
307
+ .description("Logs errors from API calls.") // Optional
308
+ .post("https://my-service.com/log-error")
309
+ .withBody({
310
+ failedNodeId: '{{ error.nodeId }}',
311
+ errorMessage: '{{ error.message }}',
312
+ timestamp: '{{ $now() }}'
313
+ })
314
+ .end(); // Required!
315
+ ```
316
+
317
+ ## Defining Tool Types (for Agents)
318
+
319
+ Tools are functions or services that Agent Nodes can call to perform specific actions. Remember to always finish defining a tool with `.end()`.
320
+
321
+ ### HTTP Tools
322
+
323
+ These are simple tools that make an HTTP request when called by an agent.
324
+
325
+ ```typescript
326
+ graph.tool("search_web")
327
+ .description("Searches the web for a given query.") // Crucial for the agent to understand the tool
328
+ .post("https://api.search-provider.com/search")
329
+ .withBody({ query: "{{input.query}}" }) // 'input' contains agent-provided parameters
330
+ .withScheduleToCloseTimeout('15s') // Example timeout
331
+ .end(); // Required!
332
+
333
+ graph.tool("summarize_content")
334
+ .description("Summarizes a provided block of text.")
335
+ .post("https://ai.example.com/summarize")
336
+ .withBody({ text: "{{input.text_to_summarize}}" })
337
+ .end(); // Required!
338
+ ```
339
+
340
+ ### Graph Tools
341
+
342
+ These allow encapsulating a *sub-graph* as a tool. This sub-graph can contain multiple HTTP nodes.
343
+
344
+ ```typescript
345
+ graph.tool('data_processor')
346
+ .description('Validates and transforms input data using a multi-step process.')
347
+ .graph((subGraph) => { // Define the sub-graph structure
348
+ subGraph
349
+ .node('validate') // Nodes within the tool's sub-graph
350
+ .post('https://api.example.com/validate')
351
+ .withBody('{{ context.example_context }}')
352
+ .end(); // End sub-graph node
353
+ subGraph
354
+ .node('transform')
355
+ .withDependencies(['validate']) // Dependencies within the sub-graph
356
+ .post('https://api.example.com/transform')
357
+ .withBody('{{ results.validate }}') // Results refer to nodes within the sub-graph
358
+ .end(); // End sub-graph node
359
+ }) // End of sub-graph definition
360
+ .end(); // <-- Required: Finalize the tool itself
361
+ ```
362
+
363
+ **Graph Tool Validation Rules:**
364
+ - Graph tools can only contain HTTP nodes.
365
+ - Graph tools must have at least one node defined (and ended).
366
+ - Each graph tool runs as an isolated sub-workflow when invoked by an agent.
367
+
368
+ ### Assigning Tools to Agents
369
+
370
+ You declare which Tools (defined elsewhere in the workflow) are available for your agent logic to request by referencing their IDs when defining the agent node.
371
+
372
+ ```typescript
373
+ graph.agent('assistant')
374
+ .withMaxIterations(5)
375
+ .withTools(['search_web', 'data_processor']) // Reference the tools by their defined IDs
376
+ .post('https://api.example.com/agent')
377
+ // ... other agent configurations ...
378
+ .end(); // Required!
379
+ ```
380
+
381
+ ## Workflow Configuration
382
+
383
+ These methods modify the overall graph configuration and require that no node or tool builder is currently active (i.e., the previous builder must have called `.end()`).
384
+
385
+ ```typescript
386
+ // Assuming previous node/tool builders have called .end()
387
+
388
+ // Set initial data available to all nodes via 'context'
389
+ graph.withContext({
390
+ userId: "user-123",
391
+ userInput: "Tell me about GraphCompose",
392
+ });
393
+
394
+ // Specify a URL to receive webhook updates about the workflow's progress
395
+ graph.withWebhookUrl("https://my-app.com/graph-compose-webhook");
396
+
397
+ // Set global workflow limits
398
+ graph.withWorkflowConfig({
399
+ workflowExecutionTimeout: "1 hour" // Max total execution time
400
+ // workflowTaskTimeout: "1m" // Max time for a single step/task
401
+ });
402
+ ```
403
+
404
+ ## Validation
405
+
406
+ Ensuring your workflow definition is correct before execution is crucial.
407
+
408
+ ### Local Validation
409
+
410
+ The client performs structural and configuration validation locally when you call `graph.validate()`. This includes checking for:
411
+ * Valid node/tool IDs and names.
412
+ * Dependency cycles.
413
+ * Correct agent configuration (like `max_iterations`).
414
+ * Valid JSONata expressions used in configurations.
415
+ * Graph Tool structure rules.
416
+
417
+ This method requires all nodes and tools to have been finalized with `.end()`. Execution methods (`.execute()`, `.executeSync()`) also perform this validation internally.
418
+
419
+ ```typescript
420
+ // Assuming all node/tool builders have called .end()
421
+ const validationResult = graph.validate();
422
+ if (!validationResult.isValid) {
423
+ console.error("Workflow validation failed locally:", validationResult.errors);
424
+ } else {
425
+ console.log("Local validation successful!");
426
+ }
427
+ ```
428
+
429
+ ### API Validation
430
+
431
+ You can also send the *current* workflow definition to the Graph Compose API for validation. This might catch additional issues specific to the execution environment.
432
+
433
+ ```typescript
434
+ // Assuming all node/tool builders have called .end()
435
+ async function validateWithApi() {
436
+ try {
437
+ // validateApi returns ApiResponse<ApiValidationResult | null>
438
+ const apiResponse = await graph.validateApi();
439
+
440
+ if (apiResponse.success && apiResponse.data) {
441
+ // Check the validation result within the data field
442
+ if (apiResponse.data.isValid) {
443
+ console.log("API validation successful!");
444
+ } else {
445
+ console.error("API validation failed:", apiResponse.data.errors);
446
+ }
447
+ } else {
448
+ // Handle cases where the API call itself failed (e.g., network error, auth error)
449
+ console.error(`API validation request failed: ${apiResponse.message}`);
450
+ }
451
+ } catch (error) {
452
+ console.error("Failed to call validation API:", error);
453
+ }
454
+ }
455
+ validateWithApi();
456
+ ```
457
+
458
+ ## Execution
459
+
460
+ Once defined and validated, you can run your workflow. Execution methods require all nodes and tools to have been finalized with `.end()`.
461
+
462
+ * **Asynchronous (`execute`)**: This is the recommended method. It starts the workflow on the Graph Compose platform and immediately returns a `workflowId`. You can monitor progress via webhooks or by polling the status API.
463
+
464
+ ```typescript
465
+ // Assuming all node/tool builders have called .end()
466
+ async function startWorkflowAsync() {
467
+ try {
468
+ // Pass any runtime context needed
469
+ // execute returns ApiResponse<WorkflowResponse | null>
470
+ const apiResponse = await graph.execute({ context: { batchId: "batch-789" } });
471
+
472
+ if (apiResponse.success && apiResponse.data) {
473
+ console.log("Workflow started successfully with ID:", apiResponse.data.workflowId);
474
+ // Use apiResponse.data.workflowId and apiResponse.data.runId
475
+ } else {
476
+ console.error(`Failed to start async workflow: ${apiResponse.message}`);
477
+ }
478
+ // Use workflowId to track status or receive webhook updates
479
+ } catch (error) {
480
+ console.error("Failed to start async workflow:", error);
481
+ }
482
+ }
483
+ startWorkflowAsync();
484
+ ```
485
+
486
+ * **Synchronous (`executeSync`)**: This method starts the workflow and *waits* for it to complete (or fail) before returning the final result or error. This is simpler for short-running workflows but can time out for longer processes.
487
+
488
+ ```typescript
489
+ // Assuming all node/tool builders have called .end()
490
+ async function runWorkflowAndWait() {
491
+ try {
492
+ // Pass any runtime context needed
493
+ // executeSync returns ApiResponse<WorkflowResponse | null>
494
+ const apiResponse = await graph.executeSync({ context: { overrideUserId: "new-user-456" } });
495
+
496
+ if (apiResponse.success && apiResponse.data) {
497
+ console.log("Workflow completed synchronously:", apiResponse.data);
498
+ // apiResponse.data contains status, executionState, error etc.
499
+ } else {
500
+ console.error(`Synchronous workflow execution failed: ${apiResponse.message}`);
501
+ }
502
+ } catch (error) {
503
+ console.error("Synchronous workflow execution failed:", error);
504
+ }
505
+ }
506
+ runWorkflowAndWait();
507
+ ```
508
+
509
+ ## Getting the Workflow Definition
510
+
511
+ After finalizing all nodes and tools with `.end()`, you can retrieve the raw workflow definition object that the client has built. This is useful for debugging, saving, or inspecting the structure.
512
+
513
+ * `graph.getWorkflow()`: Returns the complete `WorkflowGraph` object.
514
+ * `graph.toJSON()`: An alias for `getWorkflow()`, providing the standard JavaScript method for serialization.
515
+
516
+ Both methods will throw an error if any node or tool builder is still active (i.e., missing a final `.end()` call).
517
+
518
+ ```typescript
519
+ // Assuming all builders are finalized
520
+
521
+ const workflowDefinition = graph.getWorkflow();
522
+ console.log("Raw Workflow Definition Object:", workflowDefinition);
523
+
524
+ // Equivalent using toJSON() for easy serialization
525
+ const workflowJsonString = JSON.stringify(graph.toJSON(), null, 2);
526
+ console.log("Workflow JSON:", workflowJsonString);
527
+ ```
528
+
529
+ ## Interacting with Existing Workflows (API)
530
+
531
+ These methods interact directly with the Graph Compose API using a workflow ID, and don't depend on the local builder state.
532
+
533
+ * **Get Workflow Status:** Check the current status (running, completed, failed, etc.) of a previously started workflow.
534
+ ```typescript
535
+ // Does not depend on local builder state
536
+ async function checkStatus(workflowId: string) {
537
+ try {
538
+ const statusResult = await graph.getWorkflowStatus(workflowId);
539
+ console.log(`Status for ${workflowId}:`, statusResult.status);
540
+ // Handle other details in statusResult like results or errors
541
+ } catch (error) {
542
+ console.error("Failed to get workflow status:", error);
543
+ }
544
+ }
545
+ checkStatus("existing-workflow-id-123");
546
+ ```
547
+
548
+ * **Terminate Workflow:** Stop a currently running workflow.
549
+ ```typescript
550
+ // Does not depend on local builder state
551
+ async function stopWorkflow(workflowId: string, reason?: string) {
552
+ try {
553
+ const terminateResult = await graph.terminateWorkflow(workflowId, { reason });
554
+ console.log(`Termination request for ${workflowId}:`, terminateResult);
555
+ } catch (error) {
556
+ console.error("Failed to terminate workflow:", error);
557
+ }
558
+ }
559
+ stopWorkflow("running-workflow-id-456", "User cancelled operation");
560
+ ```
561
+
562
+ ## Full Example: Putting it Together
563
+
564
+ This example demonstrates defining nodes, setting context, validating, and executing.
565
+
566
+ ```typescript
567
+ import { GraphCompose } from "@graph-compose/client";
568
+
569
+ async function main() {
570
+ const graph = new GraphCompose({ token: process.env.GRAPH_COMPOSE_TOKEN || "" });
571
+
572
+ // Define Node 1
573
+ graph.node("get_ip")
574
+ .description("Fetches the public IP address of the caller.")
575
+ .get("https://httpbin.org/ip")
576
+ .end(); // Required!
577
+
578
+ // Define Node 2 (depends on Node 1)
579
+ graph.node("echo_ip")
580
+ .description("Echoes the IP address back.")
581
+ .post("https://httpbin.org/post")
582
+ .withBody({ theIpAddress: "{{results.get_ip.origin}}" }) // Access results from 'get_ip'
583
+ .withDependencies(["get_ip"])
584
+ .end(); // Required!
585
+
586
+ // Set context *after* ending the last node builder
587
+ graph.withContext({ requestSource: "client-example-readme" });
588
+
589
+ console.log("Validating workflow locally...");
590
+ // Validate *after* ending all node builders and setting context/config
591
+ const validation = graph.validate();
592
+ if (!validation.isValid) {
593
+ console.error("Local Validation Failed:", validation.errors);
594
+ return;
595
+ }
596
+ console.log("Local Validation Successful!");
597
+
598
+ console.log("Executing workflow asynchronously...");
599
+ // Execute *after* ending all node builders and setting context/config
600
+ try {
601
+ // execute returns ApiResponse<WorkflowResponse | null>
602
+ const apiResponse = await graph.execute(); // Using async execution
603
+
604
+ if (apiResponse.success && apiResponse.data) {
605
+ const workflowId = apiResponse.data.workflowId;
606
+ console.log("Workflow started with ID:", workflowId);
607
+ console.log("Monitor status using `getWorkflowStatus(workflowId)` or webhooks.");
608
+ // Example: Poll status after a short delay
609
+ // setTimeout(async () => {
610
+ // await checkStatus(workflowId); // Assumes checkStatus function from above is defined
611
+ // }, 5000);
612
+ } else {
613
+ console.error(`Failed to start workflow: ${apiResponse.message}`);
614
+ }
615
+ } catch (error) {
616
+ console.error("Execution Failed:", error);
617
+ }
618
+ }
619
+
620
+ // Helper function for the example (copy from 'Interacting with Existing Workflows' section)
621
+ async function checkStatus(workflowId: string) {
622
+ // Re-initialize graph object or ensure it's accessible in this scope
623
+ const graph = new GraphCompose({ token: process.env.GRAPH_COMPOSE_TOKEN || "" });
624
+ try {
625
+ const statusResult = await graph.getWorkflowStatus(workflowId);
626
+ console.log(`[Status Check] Status for ${workflowId}:`, statusResult.status);
627
+ if (statusResult.status === 'COMPLETED') {
628
+ console.log('[Status Check] Results:', statusResult.results);
629
+ } else if (statusResult.status === 'FAILED') {
630
+ console.error('[Status Check] Error:', statusResult.error);
631
+ }
632
+ } catch (error) {
633
+ console.error("[Status Check] Failed to get workflow status:", error);
634
+ }
635
+ }
636
+
637
+ main();
638
+ ```
639
+
640
+ ## Contributing
641
+
642
+ Contributions are welcome! Please refer to the main repository's contribution guidelines.
643
+
644
+ ## License
645
+
646
+ This project is licensed under the MIT License. See the LICENSE file in the main repository for details.