# folk-plugin-jobs

Queue consumer plugin for Folk — pulls jobs from memory or Redis and dispatches to PHP workers.

> **Status:** in active development. See [folk-spec](https://github.com/Folk-Project/folk-spec) for the roadmap.

## Requirements

- Rust 1.85+
- [folk-api](https://github.com/Folk-Project/folk-api)
- Redis (for the `redis` driver only)

## Installation

```toml
# Cargo.toml
folk-plugin-jobs = "0.1"
```

## Quick start

Add to `folk.build.toml`:

```toml
[[plugin]]
crate_name = "folk-plugin-jobs"
version = "0.1"
config_key = "jobs"
```

Configure in `folk.toml`:

```toml
[jobs]
driver = "redis"
redis_url = "redis://127.0.0.1:6379"

[[jobs.queues]]
name = "default"
concurrency = 4
max_retries = 3

[[jobs.queues]]
name = "emails"
concurrency = 2
max_retries = 5
```

Write a PHP job handler:

```php
<?php
use Folk\Sdk\Jobs\JobsModeHandler;

class AppJobHandler implements JobsModeHandler
{
    public function process(mixed $payload): mixed
    {
        $job = $payload['job'];   // e.g., "App\\Jobs\\SendEmail"
        $data = $payload['data']; // job-specific data

        // Dispatch to the appropriate job class
        $instance = new $job();
        $instance->handle($data);

        return ['status' => 'ok'];
    }
}
```

Register in the worker entry point:

```php
$loop = new \Folk\Sdk\Worker\WorkerLoop();
$loop->registerJobsHandler(new AppJobHandler());
$loop->run();
```

Dispatch a job from Laravel:

```php
// Jobs are pushed to Redis via RPUSH to the queue name
Redis::rpush('default', msgpack_pack([
    'job'  => 'App\\Jobs\\SendEmail',
    'data' => ['to' => 'user@example.com'],
]));
```

## Configuration

### Top-level `[jobs]`

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `driver` | `"memory"` \| `"redis"` | `"memory"` | Queue backend. `memory` is for development only (no persistence). |
| `redis_url` | `String` | `"redis://127.0.0.1:6379"` | Redis connection URL (only used with `redis` driver). |

### Per-queue `[[jobs.queues]]`

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `name` | `String` | `"default"` | Queue name. Maps to a Redis list key. |
| `concurrency` | `usize` | `4` | Number of concurrent consumer tasks for this queue. |
| `max_retries` | `u32` | `3` | Maximum retry attempts before discarding a failed job. |

## How it works

### Drivers

**Memory driver** — In-process `VecDeque` per queue. FIFO ordering. No persistence — all jobs are lost on restart. Use for development and testing only.

**Redis driver** — Uses `BLPOP` for blocking consumption and `RPUSH` to enqueue. Each queue maps to a Redis list. Jobs are consumed with at-least-once semantics: a job is popped from the list, then dispatched to a PHP worker. If the worker fails, the job is retried in-process (not re-queued).

### Consumer loop

For each queue, the plugin spawns `concurrency` consumer tasks. Each task:

1. Calls `driver.pop(queue)` — blocks until a job is available.
2. Wraps the payload in a MessagePack envelope with `method: "jobs.process"`.
3. Sends it to a PHP worker via `executor.execute()`.
4. On failure, retries with exponential backoff (2s, 4s, 8s, ... capped at 32s).
5. After `max_retries` failures, the job is discarded and logged as an error.

### RPC method

**`jobs.stats`** — Returns queue depth for each configured queue. Example output via `folk admin rpc jobs.stats`:

```
default: depth=12
emails: depth=3
```

## License

MIT
