# DSL reference
Every Caravela domain is a module that `use`s `Caravela.Domain`. The
DSL declares entities, relations, lifecycle hooks, and authorization
rules; the compiler builds an IR and validates it before generators
run.
## `entity :<name> do ... end`
Declares one entity (one database table). The name is plural
(`:books`); the generator derives a singular module name (`Book`), a
plural table name (`library_books`), and a path
(`lib/<app>/library/book.ex`).
```elixir
entity :books do
field :title, :string, required: true
field :isbn, :string
end
```
## `field :<name>, <type>, opts`
| option | applies to | effect |
|----------------------------|-------------------|-------------------------------------|
| `required` | any | `null: false` + `validate_required` |
| `default` | any | column default |
| `min`, `max` | numeric | `validate_number` |
| `min_length`, `max_length` | string-like | `validate_length` |
| `format` | string-like | `validate_format` (regex) |
| `precision`, `scale` | numeric | decimal precision/scale |
**Recognised types:** `:string`, `:text`, `:integer`, `:bigint`,
`:float`, `:decimal`, `:boolean`, `:date`, `:time`, `:naive_datetime`,
`:utc_datetime`, `:binary`, `:binary_id`, `:uuid`, `:map`, `:json`,
`:jsonb`.
## `relation :<from>, :<to>, type: <t>`
`t` is one of `:has_many`, `:has_one`, `:belongs_to`, `:many_to_many`.
Declare either side of a relationship — Caravela infers the other.
```elixir
relation :authors, :books, type: :has_many
relation :books, :publishers, type: :belongs_to, required: true
```
`required: true` on a `:belongs_to` produces `null: false` on the FK
and `on_delete: :delete_all` in the migration.
## `version "v<n>"` *(optional)*
Declares the domain's API version. When set, generated modules and
routes are namespaced under the version segment. See
[versioning](versioning.md).
```elixir
use Caravela.Domain
version "v1"
```
## `use Caravela.Domain, multi_tenant: true` *(optional)*
Opts the domain into row-level multi-tenancy: a `:tenant_id`
(`:binary_id`, `null: false`) field is auto-injected into every
entity, and the generated context scopes reads/writes by
`context.tenant.id`. See [multi-tenancy](multi_tenancy.md).
## Hooks: `on_create`, `on_update`, `on_delete`
Hooks run inside the generated context, between authorization and the
final `Repo` call:
```elixir
on_create :books, fn changeset, context -> ... end # → changeset
on_update :books, fn changeset, context -> ... end # → changeset
on_delete :authors, fn author, context -> ... end # → :ok | {:error, reason}
```
`context` is whatever map you pass to the context function. In the
generated controllers it defaults to `%{current_user: …, conn: conn}`
(plus `tenant:` when `multi_tenant: true`).
If `{:error, reason}` is returned from `on_delete`, the delete is
aborted and the tuple propagates back to the caller.
## Authorization: `policy` blocks
Caravela's authorization is declared via `policy :entity do … end`
blocks. A single policy compiles into three enforcement targets —
Ecto `WHERE` clauses, field-level projection on API responses, and a
typed `field_access` Svelte prop — so UI, API, and database stay in
sync automatically. See [Policies](policies.md) for the full guide.
```elixir
policy :books do
scope fn query, actor ->
if actor.role == :admin, do: query, else: where(query, [b], b.published)
end
field :price, visible: fn actor -> actor.role in [:admin, :editor] end
allow :create, fn actor -> actor.role in [:admin, :editor] end
allow :update, fn actor, record ->
actor.role == :admin or actor.id == record.author_id
end
allow :delete, fn actor -> actor.role == :admin end
end
```
To use query macros like `where` / `from` inside `scope`, add
`import Ecto.Query` at the top of your domain module.
## Compile-time validations
Every rule raises a `CompileError` pointing at the offending line:
1. Unknown field types (`:widget` etc.)
2. Numeric constraints on non-numeric fields (and vice versa)
3. Duplicate entity names
4. Relations referencing undeclared entities
5. Incompatible cardinality (e.g. both sides `:has_many`)
6. Circular chains of required `belongs_to` (unsatisfiable inserts)
7. Hooks / policy rules with the wrong function arity
8. Hooks / policies referring to unknown entities or fields
9. Duplicate hook for the same (action, entity), or duplicate `policy` block
10. Version strings that don't match `~r/^v\d+$/`
11. Manual `tenant_id` fields in a `multi_tenant: true` domain