> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getgriffinapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Graph Builder

> Build complex monitor flows with explicit control over execution order

The graph builder gives you full control over execution flow. You define nodes (HTTP requests, browser steps, waits, assertions) and edges (the order they run in). This is the right choice when you need branching, parallel paths, or non-linear flows.

For simple linear monitors, see the [Sequential Builder](/writing-tests/sequential-builder). For browser-based monitoring, see [Browser Monitors](/writing-tests/browser-monitors).

## Basic example

```typescript theme={null}
import {
  createGraphBuilder, HttpRequest, Assertion,
  GET, Json, START, END, Frequency, Assert
} from "@griffin-app/griffin";

const monitor = createGraphBuilder({
  name: "health-check",
  frequency: Frequency.every(5).minutes(),
})
  .addNode("check", HttpRequest({
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/health",
  }))
  .addEdge(START, "check")
  .addEdge("check", END)
  .build();

export default monitor;
```

## Configuration

```typescript theme={null}
createGraphBuilder({
  name: "my-test",                          // Required: unique monitor name
  frequency: Frequency.every(1).minute(),   // Required: how often to run
  locations: ["us-east-1", "eu-west-1"],    // Optional: where to execute
  notifications: [                          // Optional: alert rules
    notify.onFailure().toSlack("#alerts"),
  ],
})
```

## Adding nodes

Use `addNode(name, node)` to register steps. Four node types are available:

### HTTP requests

```typescript theme={null}
import { HttpRequest, GET, POST, Json } from "@griffin-app/griffin";

builder.addNode("get-users", HttpRequest({
  method: GET,
  base: "https://api.example.com",
  response_format: Json,
  path: "/users",
}))

builder.addNode("create-user", HttpRequest({
  method: POST,
  base: "https://api.example.com",
  response_format: Json,
  path: "/users",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token",
  },
  body: { name: "Test User", email: "test@example.com" },
}))
```

**HttpRequest config:**

| Field             | Type                                    | Required | Description                                                 |
| ----------------- | --------------------------------------- | -------- | ----------------------------------------------------------- |
| `method`          | `GET`, `POST`, `PUT`, `DELETE`, `PATCH` | Yes      | HTTP method                                                 |
| `base`            | `string`, `variable()`, or `template`   | Yes      | Base URL (e.g., `"https://api.example.com"`)                |
| `path`            | `string`, `variable()`, or `template`   | Yes      | URL path (e.g., `"/users"`)                                 |
| `response_format` | `Json`, `Xml`, `Text`, `NoContent`      | Yes      | Expected response format (`NoContent` for 204 with no body) |
| `headers`         | `Record<string, any>`                   | No       | Request headers                                             |
| `body`            | `any`                                   | No       | Request body                                                |

### Browser steps

```typescript theme={null}
import {
  BrowserAction, navigate, waitForSelector, extractText,
} from "@griffin-app/griffin";

builder.addNode("check_page", BrowserAction({
  browser: "chromium",
  steps: [
    navigate("https://example.com"),
    waitForSelector("h1"),
    extractText("heading", "h1"),
  ],
}))
```

See [Browser Monitors](/writing-tests/browser-monitors) for the full step reference and BrowserAction config.

### Waits

```typescript theme={null}
import { Wait } from "@griffin-app/griffin";

builder.addNode("pause", Wait({ seconds: 2 }))

// Also accepts:
Wait(1000)            // milliseconds
Wait({ minutes: 1 })  // minutes
```

### Assertions

```typescript theme={null}
import { Assertion, Assert } from "@griffin-app/griffin";

// Create a state proxy to reference previous node results
import { createStateProxy } from "@griffin-app/griffin";
const state = createStateProxy(["get-users"]);

builder.addNode("validate", Assertion([
  Assert(state["get-users"].status).equals(200),
  Assert(state["get-users"].body["data"]).not.isEmpty(),
]))
```

See [Assertions](/writing-tests/assertions) for the full assertion API.

## Adding edges

Edges define execution order. Use `addEdge(from, to)` with `START` and `END` constants:

```typescript theme={null}
import { START, END } from "@griffin-app/griffin";

builder
  .addEdge(START, "create-user")   // Start with create
  .addEdge("create-user", "pause") // Then wait
  .addEdge("pause", "get-user")    // Then fetch
  .addEdge("get-user", "validate") // Then assert
  .addEdge("validate", END)        // Done
```

**Rules:**

* Every graph must have at least one edge from `START`
* Every graph must have at least one edge to `END`
* Every node must have at least one incoming and one outgoing edge
* The TypeScript compiler enforces these constraints — you'll get a type error if a node is unconnected

## Compile-time safety

The graph builder uses TypeScript's type system to catch errors at compile time:

```typescript theme={null}
// Type error: "get-user" has no outgoing edge
builder
  .addNode("get-user", HttpRequest({ ... }))
  .addEdge(START, "get-user")
  .build()  // Error: "Some nodes have no outgoing edges"
```

## Complete example

```typescript theme={null}
import {
  createGraphBuilder, HttpRequest, Wait, Assertion,
  GET, POST, DELETE, Json, START, END,
  Frequency, Assert, createStateProxy, secret
} from "@griffin-app/griffin";

const state = createStateProxy(["create", "get", "delete"]);

const monitor = createGraphBuilder({
  name: "user-crud",
  frequency: Frequency.every(15).minutes(),
})
  // Define steps
  .addNode("create", HttpRequest({
    method: POST,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users",
    headers: { "Authorization": secret("API_KEY") },
    body: { name: "Test User" },
  }))
  .addNode("pause", Wait({ seconds: 1 }))
  .addNode("get", HttpRequest({
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users/1",
  }))
  .addNode("validate", Assertion([
    Assert(state["create"].status).equals(201),
    Assert(state["get"].status).equals(200),
    Assert(state["get"].body["name"]).equals("Test User"),
  ]))
  .addNode("delete", HttpRequest({
    method: DELETE,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users/1",
  }))
  // Define flow
  .addEdge(START, "create")
  .addEdge("create", "pause")
  .addEdge("pause", "get")
  .addEdge("get", "validate")
  .addEdge("validate", "delete")
  .addEdge("delete", END)
  .build();

export default monitor;
```
