# 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.
## Permissions: `can_read`, `can_create`, `can_update`, `can_delete`
```elixir
can_read :books, fn query, context -> query end # → Ecto.Query
can_create :books, fn context -> true end # → boolean
can_update :books, fn book, context -> true end # → boolean
can_delete :books, fn _book, context -> true end # → boolean
```
`can_read` is applied as a query filter *before* `Repo.all`/`Repo.get`
so restricted users never see forbidden rows. The other three return
booleans; `false` short-circuits the context function with
`{:error, :unauthorized}`.
To use query macros like `where` / `from` inside `can_read`, 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 / permissions with the wrong function arity
8. Hooks / permissions referring to unknown entities
9. Duplicate hook / permission for the same (action, entity)
10. Version strings that don't match `~r/^v\d+$/`
11. Manual `tenant_id` fields in a `multi_tenant: true` domain