Packages
Opinionated CQRS application framework for Phoenix and Plug on Cratis Chronicle, with model-bound commands and queries, validation, authorization, observable queries and opt-in TypeScript proxy generation
Current section
Files
Jump to
Current section
Files
cratis_arc
README.md
README.md
# Arc for Elixir
Arc for Elixir is an opinionated CQRS application framework for Phoenix and Plug on [Cratis Chronicle](https://github.com/Cratis/Chronicle), with model-bound commands and queries, validation, authorization, observable queries and opt-in TypeScript proxies for React.
[](https://hex.pm/packages/cratis_arc)
[](https://hexdocs.pm/cratis_arc)
[](LICENSE)
[](https://discord.gg/kt4AMpV8WV)
> **Source preview.** The package is built and tested here, but the API may still change, and nothing is claimed beyond what [Limits and gaps](Documentation/limits-and-gaps.md) says is verified.
## Overview
You write a command or a query as a plain module. Arc binds, validates, authorizes and runs it, records the events it returns in Chronicle, and serves it to React, LiveView or any HTTP client.
```elixir
defmodule Library.Authors.Registration.RegisterAuthor do
@moduledoc "Registers a new author."
use Arc.Command
schema do
field :id, Library.Authors.AuthorId, key: true, required: true
field :name, :string, required: true, min_length: 2, max_length: 50
end
def handle(%__MODULE__{name: name}, _ctx),
do: {:append, %Library.Authors.AuthorRegistered{name: name}}
end
```
- **`use Arc.Command` / `use Arc.ReadModel`** — declare commands and queries; fields, rules and authorization are declarations, `handle/2` is the logic
- **The Arc HTTP contract on Plug** — `@cratis/arc`, `@cratis/arc.react` and Cratis Components work against it unchanged
- **Phoenix and LiveView** — the same commands and queries, in process, with forms that show pipeline errors on the right field
- **Observable queries** — re-run when Chronicle (or MongoDB) changes a read model, pushed over the multiplexed WebSocket or Server-Sent Events hub, in full or delta mode
- **Tenancy** — header, query string, claim or subdomain resolvers select the tenant, which scopes the Chronicle namespace and the MongoDB database
- **MongoDB** — `cratis_arc_mongodb`: tenant-aware databases, naming and BSON conventions, connection resolution and change stream observation
- **Cross-cutting filters** — authorize a whole namespace of commands and queries in one place
- **Opt-in TypeScript proxies** — `mix arc.gen.proxies` generates typed commands and queries, with the same validation as the server, only if you add the compiler; they can sit beside the Elixir they come from, as [vertical slices](Documentation/vertical-slices.md)
- **Identity** — `/.cratis/me`, typed identity details, development users for Lens, signed forwarded identity and on-behalf-of, as Arc on .NET
- **The exact result envelopes** — `CommandResult` and `QueryResult` match the Arc contract field for field, pinned by specs
- **Chronicle underneath** — returned events are appended with the caller's identity and correlation; constraint rejections reach the client as validation results
- **`Arc.Test`** — the real pipeline against Chronicle's in-memory kernel
Arc for Elixir works with Chronicle only. It is part of the Cratis family of Arc implementations for [.NET](https://github.com/Cratis/Arc), [Kotlin and Java](https://github.com/Cratis/Arc.Kotlin), [Go](https://github.com/Cratis/Arc.Go) and [TypeScript](https://github.com/Cratis/Arc.TypeScript), and follows the same [HTTP contract](https://github.com/Cratis/Arc/blob/main/Documentation/http-contract.md).
## Install
```elixir
defp deps do
[
{:cratis_arc, "~> 0.1"}
]
end
```
Arc needs Elixir 1.18 or later and brings `cratis_chronicle` with it. Phoenix, Phoenix.HTML and Phoenix LiveView are optional: the LiveView support compiles only when `phoenix_live_view` is a dependency. CI builds and tests with Elixir 1.19.5 on Erlang/OTP 28.5.
Add `import_deps: [:cratis_arc]` to `.formatter.exs` so `field :name, :string` is not rewritten with parentheses.
## Prerequisite: Chronicle running
```bash
docker pull cratis/chronicle:latest-development
docker run -d --name chronicle \
-p 127.0.0.1:35000:35000 \
-p 127.0.0.1:27017:27017 \
cratis/chronicle:latest-development
```
The `127.0.0.1:` prefixes keep both ports on your machine: the development kernel accepts well-known credentials and its MongoDB has no authentication.
## Getting started
[Get started](Documentation/get-started.md) builds an authors feature end to end: events, a command, a query, the HTTP API, a LiveView page and a test.
## Documentation
- [How Arc works](Documentation/concepts.md)
- [Result envelopes](Documentation/results.md), [Commands](Documentation/commands.md), [Queries and read models](Documentation/queries.md), [Validation](Documentation/validation.md), [Authorization and identity](Documentation/authorization-and-identity.md)
- [Hosting on Plug](Documentation/hosting/plug.md) and [on Phoenix](Documentation/hosting/phoenix.md)
- [LiveView](Documentation/live-view.md)
- [React and generated proxies](Documentation/react-proxies.md)
- [Testing](Documentation/testing.md), [Observability](Documentation/observability.md), [Configuration](Documentation/configuration.md)
- [Samples](Documentation/samples.md) and [Limits and gaps](Documentation/limits-and-gaps.md)
- [Build and release](Documentation/contributing/build-and-release.md) — the pull request checks and the publishing pipeline
## Samples
| Sample | Shows |
| --- | --- |
| [`Samples/plug`](Samples/plug) | The smallest Arc application: Bandit and a Plug router |
| [`Samples/phoenix`](Samples/phoenix) | The HTTP contract, LiveView and a React app served from one Phoenix endpoint |
| [`Samples/frontend`](Samples/frontend) | A React app on the generated proxies |
`Samples/run-tests.sh` builds and tests all three.
## Contributing
```bash
cd Source/arc
mix deps.get
mix format --check-formatted
MIX_ENV=test mix compile --warnings-as-errors
mix test
cd ../..
Samples/run-tests.sh
ContractTests/run.sh
```
Pull requests need exactly one release label (`major`, `minor`, `patch` or `no-release`) and a description written as a release note; [Build and release](Documentation/contributing/build-and-release.md) explains both. Design decisions are recorded in [`decisions`](decisions/index.md). Questions and ideas are welcome on [Discord](https://discord.gg/kt4AMpV8WV); report confirmed bugs as [issues](https://github.com/Cratis/Arc.Elixir/issues).
## License
MIT. See [LICENSE](LICENSE).