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

Voltro collaborative-editing backend (template: **api-collab**).

Real-time **convergent** editing on a `crdtText()` column — concurrent edits
from many clients merge without a last-write-wins loser, authoritatively on the
server, then broadcast over the reactive engine. Zero infra (`store: 'memory'`):
the merge and the broadcast are both in-process, so two browser tabs pointed at
one `voltro dev` collaborate with no database and no external service.

## Boot

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

## Pair it with a frontend

`api-collab` exposes the `documents.list` subscription + `documents.create` /
`documents.setBody` mutations that [`frontend-collab`](../frontend-collab)
binds a collaborative editor to. Scaffold both together:

```bash
voltro create-project collab --api=api-collab --web=frontend-collab
```

## What's in here

| File | Role |
|---|---|
| `app.config.ts`                              | App declaration (`store: 'memory'`). |
| `database/schema.ts`                         | `documents` table — a `crdtText()` `body`, plus `tenant()` + `localFirst()`. |
| `mutations/documents.create.mutation.ts`     | Create a document (title only; `body` starts empty). |
| `mutations/documents.setBody.mutation.ts`    | The CRDT write — a client's encoded update; the runtime merges it. |
| `queries/documents.query.ts`                 | `documents.list` streaming subscription. |
| `tests/documents.setBody.test.ts`            | Convergence proof — two concurrent edits merge, order-independently. |

## How the CRDT merge works

`body: crdtText()` stores the encoded CRDT state as an opaque `bytes` blob. A
client edits its LOCAL handle and sends the encoded update to
`documents.setBody`. The handler writes it like any column — but because `body`
is CRDT-managed, the runtime's MutationStore intercepts the write, reads the
stored state, and folds the incoming update in with `mergeCrdtStates` (from
`@voltro/local-first`) **before** persisting. That authoritative server merge is
the convergence guarantee; the reactive engine then pushes the merged row to
every subscriber.

```
 Client A ──update A──►┐
                       ├─► documents.setBody ─► store.update('documents', id, { body })
 Client B ──update B──►┘                          └─ runtime folds update into STORED
                                                     state (mergeCrdtStates) → converges
                                                        └─► reactive delta ─► all subscribers
```

`tests/documents.setBody.test.ts` proves it with no database: `ctx.store` from
`@voltro/testing` is the same mixin-wrapped store as production, so the merge
runs exactly as it does at runtime.

## Multi-tenancy note

The dev `AuthMiddleware` resolves the tenant from the `x-tenant` header,
defaulting to `'acme'`. The `documents` table carries the `tenant()` mixin, so
reads/writes are auto-scoped and `documents.create` guards that the submitted
`tenantId` matches the caller's (a typed `TenantMismatch` otherwise). Wire real
auth before production (see the authentication docs).
