# roundabout_orm
# roundabout

A macro-driven, [Payload](https://payloadcms.com/)-style **CQRS ORM** for Rust. You declare
entities as plain structs; a procedural macro analyses them at build time and writes a JSON
schema manifest. At runtime the framework reads that manifest and **auto-generates the HTTP +
WebSocket API** — exposing exactly the operations each field permits.

```toml
[dependencies]
roundabout_orm = "0.1"
tokio = { version = "1", features = ["full"] }
```

## Declare an entity

A field's *type* declares its policy. The macro records it in the manifest:

| Type            | read | update | WebSocket alert on change |
|-----------------|:----:|:------:|:-------------------------:|
| `ReadOnly<T>`   |  ✓   |   ✗    |             ✗             |
| `Updatable<T>`  |  ✓   |   ✓    |             ✗             |
| `Live<T>`       |  ✓   |   ✓    |             ✓             |
| `Relation<T>`   |  ✓   |   ✗    |             ✗             |

```rust
use roundabout_orm::types::{Live, ReadOnly, Updatable};
use roundabout_orm::{entity, App};

#[entity(table = "products")]
struct Product {
    id: ReadOnly<i64>,        // primary key, never updatable
    name: Updatable<String>,  // editable, no live notification
    price: Live<f64>,         // editable AND broadcasts a WebSocket alert
}

#[tokio::main]
async fn main() {
    let app = App::from_schema_dir("./schema").await;
    app.serve("127.0.0.1:8080").await;
}
```

Building emits `schema/Product.json` next to your crate (a `schema/` directory appears at
build time). The runtime reads it and serves:

* `GET  /products` and `GET /products/{id}` — read the readable fields from Postgres
* `PATCH /products/{id}` — validate against each field's policy, stage the write (`202`)
* `GET  /ws/products` — subscribe to live alerts (registered only because `price` is `Live`)

## Architecture (CQRS)

* **Reads** are served directly from **PostgreSQL** (the query side).
* **Writes** land first in a local **SQLite** outbox and are acknowledged immediately (the
  command side). A **background worker** migrates each staged write to PostgreSQL, and on
  success **broadcasts a WebSocket alert** for every changed `Live<T>` field.

## Automatic migrations

On startup the runtime diffs the manifest against a committed `migrations/.snapshot.json`,
writes a timestamped `migrations/<ts>_auto.sql` for any change, and applies pending files
(tracked in a `_roundabout_migrations` table). Migrations are **additive-only by default** —
`CREATE TABLE` / `ADD COLUMN` run automatically, while drops and type changes are written as
commented-out SQL (with a warning) unless `ROUND_ABOUT_DESTRUCTIVE=1` is set. A
docker-compose for a local Postgres is provided at the repo root.

## Configuration

| Env var                     | Default                                                   |
|-----------------------------|-----------------------------------------------------------|
| `ROUND_ABOUT_PG`            | `host=localhost user=postgres password=pw dbname=postgres`|
| `ROUND_ABOUT_SQLITE`        | `roundabout_orm_outbox.db`                                |
| `ROUND_ABOUT_MIGRATIONS_DIR`| `./migrations`                                            |
| `ROUND_ABOUT_MIGRATE`       | on (set to `off` to skip generate + apply)                |
| `ROUND_ABOUT_DESTRUCTIVE`   | unset (set to `1` to apply drops / type changes)          |

## Companion crates

* [`roundabout_orm_macros`](https://crates.io/crates/roundabout_orm_macros) — the `#[entity]`
  macro (re-exported here; you do not need to depend on it directly).
* [`roundabout_orm_schema`](https://crates.io/crates/roundabout_orm_schema) — the shared
  manifest model (re-exported as `roundabout::schema`).

## Status

Early (`0.1`). The proc-macro writes the manifest as a build side effect, so the JSON
refreshes when an entity's source changes or after `cargo clean`.

## License

Licensed under either of [MIT](../../LICENSE-MIT) or [Apache-2.0](../../LICENSE-APACHE) at
your option.
