# {{projectName}} / {{appName}}

Voltro backend scaffold (template: **api-durable**).

A cohesive **order-fulfillment** domain that exercises the framework's
entire **durable-execution surface** in one app — durable workflows with
a human-approval gate, event triggers, a cron schedule, a table
subscriber, a materialized aggregate, and a run-once startup hook. It
boots with **zero infrastructure** (`store: 'memory'`).

## Boot

```bash
pnpm install                                  # at the repo root
pnpm --filter @{{projectName}}/{{appName}} dev
# → http://localhost:4000
# → ws://localhost:4000/ws
```

No env or services required. `store: 'memory'` resets on every
supervised restart — switch `app.config.ts` to `store: 'postgres'`
(and `docker compose up` from the repo root) when you want durable
state + multi-instance cluster workflow runners.

## What it demonstrates

| File | Primitive | Role |
|---|---|---|
| `app.config.ts`                                | app config        | `store: 'memory'`, zero-infra boot. |
| `database/schema.ts`                           | schema            | core `actors`/`tenants` + an `orders` table (`tenant()` + `.reactive()`). |
| `mutations/orders.place.mutation.*`            | mutation          | insert an order and **publish `order.placed`** on commit via `ctx.events.publish`. Tenant write-guard. |
| `events/orders.event.ts`                       | event             | the declared `order.placed` — one value shared by the publisher, the trigger and any client. |
| `triggers/order.placed.trigger.tsx`            | event trigger     | maps event `order.placed` → **start** the `orders.fulfill` workflow. |
| `workflows/order.fulfill.workflow.*`           | durable workflow  | `step('reserve-stock')` (with **`withCompensation`** saga rollback) → `sleep` → **`awaitSignal('approval')`** human gate → `step('ship')` / mark rejected. |
| `mutations/orders.approve.mutation.*`          | mutation          | inject the `approval` signal into the parked run via `ctx.workflows.signal(...)`. |
| `schedules/nightlyReport.cron.tsx`             | schedule          | `defineSchedule` cron `0 2 * * *` (UTC), tallies open orders. |
| `subscribers/orderChanges.subscribe.ts`        | subscriber        | `defineSubscriber({ table: 'orders', on: 'any' })` post-commit reaction. |
| `aggregates/orderStats.aggregate.ts`           | aggregate         | `defineAggregate` materialized per-tenant/per-status order roll-up, refreshed every minute. |
| `startup/warm.startup.tsx`                      | startup hook      | run-once boot hook holding a resource + `onShutdown` teardown. |

## The end-to-end flow

1. **Place an order** → `orders.place` inserts the row and emits
   `order.placed` *after the transaction commits* (so a rollback never
   starts a workflow for a non-existent order).
2. **The trigger fires** → `order.placed.trigger.tsx` starts the durable
   `orders.fulfill` workflow with `{ orderId, tenantId }`.
3. **The workflow runs durably** → reserves stock, waits a (short)
   journaled delay, then **parks on `awaitSignal('approval')`**.
4. **A human approves** → `orders.approve` derives the workflow's
   deterministic executionId (from its `idempotencyKey`) and injects the
   `approval` signal; the parked run wakes (sub-second) and either ships
   or marks the order rejected.

Meanwhile the **subscriber** logs every `orders` write, the **aggregate**
keeps a live per-status tally, the **schedule** reports nightly, and the
**startup hook** holds a process-lifetime resource.

## Try it

Over the rpc surface (e.g. from a `voltro dev` web client, or
`POST /rpc` / the inspect `invoke` endpoint):

```jsonc
// 1. place — starts the fulfillment workflow via order.placed
{ "tag": "orders.place",   "input": { "tenantId": "acme", "customerName": "Ada", "amountCents": 4200 } }
// 2. approve — wakes the parked awaitSignal('approval') gate
{ "tag": "orders.approve", "input": { "tenantId": "acme", "orderId": "order_…", "approved": true } }
```

Watch the run in the dashboard's **Workflows** tab (the `awaitSignal`
gate also has a "Send signal…" button), and the order's status walk
`placed → reserved → shipped` live.

## Notes

- All primitives are **auto-discovered by file convention** — nothing is
  registered in `app.config.ts`.
- `query` / `mutation` / `action` / `workflow` keep the **descriptor /
  executor split**: the browser-safe `*.ts` / `*.tsx` descriptor never
  imports `@voltro/database`, `@voltro/runtime`, `node:*`, or the
  workflow executor's server graph. The `.server.*` executors do.
