# State & Scheduling

Fetch `docs/state.md` and `docs/scheduling.md` from `https://github.com/cloudflare/agents/tree/main/docs` for complete documentation.

## State Management

State persists to SQLite and broadcasts to connected clients automatically.

### Define Typed State

```typescript
type State = { 
  count: number;
  items: string[];
};

export class MyAgent extends Agent<Env, State> {
  initialState: State = { count: 0, items: [] };
}
```

### Read and Update

```typescript
// Read (lazy-loaded from SQLite)
const count = this.state.count;

// Write (sync, persists, broadcasts)
this.setState({ count: this.state.count + 1 });
```

### Validation Hook

`validateStateChange()` runs synchronously before state persists. Throw to reject the update.

```typescript
validateStateChange(nextState: State, source: Connection | "server") {
  if (nextState.count < 0) {
    throw new Error("Count cannot be negative");
  }
}
```

### Execution Order

1. `validateStateChange(nextState, source)` - sync, gating
2. State persisted to SQLite
3. State broadcast to connected clients
4. `onStateUpdate(nextState, source)` - async via `ctx.waitUntil`, non-gating

### Client-Side Sync (React)

```tsx
import { useAgent } from "agents/react";

function App() {
  const [state, setLocalState] = useState<State>({ count: 0 });
  
  const agent = useAgent<State>({
    agent: "MyAgent",
    name: "instance-1",
    onStateUpdate: (newState) => setLocalState(newState)
  });

  return <button onClick={() => agent.setState({ count: state.count + 1 })}>
    Count: {state.count}
  </button>;
}
```

## SQL API

Direct SQLite access for custom queries:

```typescript
// Create table
this.sql`
  CREATE TABLE IF NOT EXISTS items (
    id TEXT PRIMARY KEY,
    name TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`;

// Insert
this.sql`INSERT INTO items (id, name) VALUES (${id}, ${name})`;

// Query with types
const items = this.sql<{ id: string; name: string }>`
  SELECT * FROM items WHERE name LIKE ${`%${search}%`}
`;
```

## Scheduling

### Schedule Types

| Mode | Syntax | Use Case |
|------|--------|----------|
| Delay | `this.schedule(60, ...)` | Run in 60 seconds |
| Date | `this.schedule(new Date(...), ...)` | Run at specific time |
| Cron | `this.schedule("0 8 * * *", ...)` | Recurring schedule |
| Interval | `this.scheduleEvery(30, ...)` | Fixed interval (every 30s) |

### Examples

```typescript
// Delay (seconds)
await this.schedule(60, "checkStatus", { id: "abc123" });

// Specific date
await this.schedule(new Date("2025-12-25T00:00:00Z"), "sendGreeting", { to: "user" });

// Cron (recurring)
await this.schedule("0 9 * * 1-5", "weekdayReport", {});

// Fixed interval (every 30 seconds, overlap prevention built-in)
await this.scheduleEvery(30, "pollUpdates");
await this.scheduleEvery(300, "syncData", { source: "api" });
```

### Handler

```typescript
async sendGreeting(payload: { to: string }, schedule: Schedule) {
  console.log(`Sending greeting to ${payload.to}`);
  // Cron schedules auto-reschedule; one-time schedules are deleted
}
```

### Manage Schedules

```typescript
const schedules = this.getSchedules();
const crons = this.getSchedules({ type: "cron" });
await this.cancelSchedule(schedule.id);
```

## Lifecycle Callbacks

```typescript
export class MyAgent extends Agent<Env, State> {
  async onStart() {
    // Agent started or woke from hibernation
  }

  onConnect(conn: Connection, ctx: ConnectionContext) {
    // WebSocket connected
  }

  onMessage(conn: Connection, message: WSMessage) {
    // WebSocket message (non-RPC)
  }

  onStateUpdate(state: State, source: Connection | "server") {
    // State changed (async, non-blocking)
  }

  onError(error: unknown) {
    // Error handler
    throw error; // Re-throw to propagate
  }
}
```
