# How Agents Access Parlel Pool

Three entry points depending on your use case.

---

## 1. `createTestContext()` — For Test Suites

The simplest path. Spins up Redis + Postgres, returns helpers.

```typescript
import { createTestContext } from "@parlel/parlel-pool/agent";

const ctx = await createTestContext();
```

### What's on `ctx`

| Property | Type | Description |
|----------|------|-------------|
| `ctx.redis` | `RedisHelper` | `set`, `get`, `del`, `incr`, `expire`, `flushdb` |
| `ctx.postgres` | `PostgresHelper` | `query`, `execute`, `cleanup` |
| `ctx.seed` | `SeedExecutor` | Load data from files |
| `ctx.inspector` | `StateInspector` | Dump service state |
| `ctx.chaos` | `ChaosMonkey` | Inject failures |
| `ctx.recovery` | `CrashRecoveryManager` | Crash detection + replay |
| `ctx.snapshot()` | `() => Promise<void>` | Save current state |
| `ctx.restore()` | `() => Promise<void>` | Restore to snapshot |
| `ctx.dump(service?)` | `Promise<object>` | Inspect state |
| `ctx.cleanup()` | `() => Promise<void>` | Tear down everything |

### Custom Services

```typescript
const ctx = await createTestContext({
  services: ["redis", "postgres", "kafka"],
  config: { base_port: 11000 },
  stateDir: "./my-state",
  logDir: "./my-logs",
});
```

---

## 2. `startParallel()` — For Full Control

Lower-level. Returns a `ParallelInstance` with everything.

```typescript
import { startParallel } from "@parlel/parlel-pool/agent/sdk";

const instance = await startParallel({
  services: [
    { name: "redis", port: 6379 },
    { name: "postgres", env: { POSTGRES_DB: "myapp" } },
    { name: "kafka" },
  ],
  logDir: "./logs",
  stateDir: "./state",
  recovery: { enabled: true, max_retries: 3 },
});
```

### What's on `instance`

| Property | Type | Description |
|----------|------|-------------|
| `instance.services` | `Map<string, ServiceInstance>` | Running services |
| `instance.stop()` | `() => Promise<void>` | Stop all |
| `instance.status()` | `object` | Port + status per service |
| `instance.recovery` | `CrashRecoveryManager` | Crash recovery |
| `instance.chaos` | `ChaosMonkey` | Failure injection |
| `instance.inspector` | `StateInspector` | State inspection |
| `instance.seed` | `SeedExecutor` | Seed data |
| `instance.dump(service?)` | `Promise<object>` | Inspect state |

---

## 3. CLI — For Scripts and CI

```bash
parlel up redis postgres kafka
parlel status
parlel seed postgres --file=./seeds/schema.sql
parlel down
```

---

## Seed Executor

Load data into running services.

```typescript
// From file
await ctx.seed.executeFile("redis", "./seeds/cache.txt", 6379);
await ctx.seed.executeFile("postgres", "./seeds/schema.sql", 5432);

// Inline
await ctx.seed.executeContent("redis", "SET user:1 alice\nSET user:2 bob", 6379);
await ctx.seed.executeContent("postgres", "INSERT INTO users (name) VALUES ('alice')", 5432);
```

### Seed File Formats

**Redis** — one command per line:
```
SET session:abc '{"user":"alice"}'
SET session:def '{"user":"bob"}'
EXPIRE session:abc 3600
```

**Postgres** — semicolon-separated SQL:
```sql
CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT, name TEXT);
INSERT INTO users (email, name) VALUES ('alice@test.com', 'Alice');
INSERT INTO users (email, name) VALUES ('bob@test.com', 'Bob');
```

**Kafka** — JSON array:
```json
[
  {"topic": "events", "key": "user1", "value": {"action": "login"}},
  {"topic": "events", "key": "user2", "value": {"action": "signup"}}
]
```

---

## State Inspector

Peek into what's inside each service.

```typescript
// Dump everything
const all = await ctx.dump();
// {
//   redis: { keyCount: 5, keys: [...], data: { "user:1": { value: "alice", type: "string", ttl: null } } },
//   postgres: { tableCount: 2, tables: { users: { columns: [...], rowCount: 3, sample: [...] } } }
// }

// Dump specific service
const redisState = await ctx.dump("redis");
const pgState = await ctx.dump("postgres");
```

### Using Inspector for Assertions

```typescript
it("should cache user in Redis", async () => {
  await service.createUser("alice@test.com", "Alice");

  const state = await ctx.dump("redis");
  expect(state.keys).toContain("user:1");
  expect(state.data["user:1"].value).toContain("Alice");
});

it("should persist to Postgres", async () => {
  await service.createUser("bob@test.com", "Bob");

  const state = await ctx.dump("postgres");
  expect(state.tables.users.rowCount).toBe(1);
});
```

---

## Chaos Monkey

Inject failures to test how your code handles them.

### Presets

```typescript
import { PRESET_RULES } from "@parlel/parlel-pool/agent";

// Redis goes down
ctx.chaos.configure(PRESET_RULES.redisDown);
ctx.chaos.activate();

// Your code should handle the error
await expect(service.getUser("1")).rejects.toThrow();
```

### Available Presets

| Preset | Effect |
|--------|--------|
| `PRESET_RULES.redisDown` | All Redis connections dropped |
| `PRESET_RULES.redisSlow` | Redis takes 2s per command |
| `PRESET_RULES.redisTimeout` | Redis hangs for 10s |
| `PRESET_RULES.redisSetFails` | SET/MSET/SETEX return errors |
| `PRESET_RULES.postgresDown` | Postgres returns connection errors |
| `PRESET_RULES.postgresSlowWrites` | INSERT/UPDATE/DELETE take 3s |
| `PRESET_RULES.postgresEmptySelects` | SELECT always returns empty |
| `PRESET_RULES.intermittentFailure` | 30% of all ops fail randomly |
| `PRESET_RULES.networkPartition` | Connections drop after 5 ops |

### Custom Rules

```typescript
ctx.chaos.configure({
  name: "break_user_cache",
  service: "redis",
  command: ["SET"],
  pattern: "user:",          // only keys matching "user:"
  action: "error",
  errorResponse: "-ERR READONLY\r\n",
});

ctx.chaos.configure({
  name: "slow_after_10_ops",
  action: "slow",
  slowMs: 5000,
  afterOps: 10,              // kicks in after 10 intercepted ops
});

ctx.chaos.configure({
  name: "random_corruption",
  probability: 0.1,          // 10% chance
  action: "corrupt",
});
```

### Actions

| Action | What it does |
|--------|-------------|
| `drop_connection` | Destroys the socket |
| `error` | Returns an error response |
| `delay` | Waits `delayMs` before responding |
| `slow` | Adds `slowMs` latency |
| `corrupt` | Truncates the response |
| `skip` | Silently drops the command |

### Monitoring Chaos

```typescript
ctx.chaos.on("intercepted", (op) => {
  console.log(`Intercepted: ${op.service} ${op.command}`);
});

ctx.chaos.on("action", (op) => {
  console.log(`Action: ${op.action} on ${op.service} ${op.command}`);
});

// See what was intercepted
const ops = ctx.chaos.getInterceptedOps();
```

---

## Crash Recovery

Automatic. When a service crashes, recovery manager:

1. Detects the crash via health check
2. Marks the crash in state recorder
3. Restarts the service
4. Replays all logged operations from last snapshot
5. Restores exact pre-crash state

```typescript
// Manual recovery
await ctx.recovery.forceRecovery("redis");

// Check recovery state
const state = ctx.recovery.getRecoveryState("redis");
console.log(state.retries, state.lastCrash);

// Listen for events
ctx.recovery.on("recovery_success", ({ service }) => {
  console.log(`${service} recovered`);
});

ctx.recovery.on("replay_complete", ({ service, operationsReplayed }) => {
  console.log(`${service}: replayed ${operationsReplayed} ops`);
});
```

---

## Snapshot / Restore

Isolate tests from each other.

```typescript
beforeEach(async () => {
  await ctx.snapshot();
});

afterEach(async () => {
  await ctx.restore();
});

it("test A", async () => {
  await ctx.redis.set("key", "a");
  // ...
});

it("test B", async () => {
  // "key" from test A is gone
  const val = await ctx.redis.get("key");
  expect(val).toBeNull();
});
```

---

## Full Example

```typescript
import { createTestContext, PRESET_RULES } from "@parlel/parlel-pool/agent";

const ctx = await createTestContext({
  services: ["redis", "postgres"],
});

// Seed initial data
await ctx.seed.executeContent("postgres", `
  CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT, name TEXT);
  INSERT INTO users (email, name) VALUES ('seed@test.com', 'Seeded');
`, 5432);

// Inspect
const state = await ctx.dump("postgres");
console.log(state.tables.users.rowCount); // 1

// Test with chaos
ctx.chaos.configure(PRESET_RULES.redisSlow);
ctx.chaos.activate();

// ... test slow-path handling ...

ctx.chaos.deactivate();

// Cleanup
await ctx.cleanup();
```
