Caravela.Domain (Caravela v0.7.0)

Copy Markdown View Source

DSL entry point. use Caravela.Domain in a module to declare a domain:

defmodule MyApp.Domains.Library do
  use Caravela.Domain

  entity :authors do
    field :name, :string, required: true
    field :bio, :text
  end

  entity :books do
    field :title, :string, required: true, min_length: 3
    field :isbn, :string, format: ~r/^\d{13}$/
  end

  relation :authors, :books, type: :has_many

  on_create :books, fn changeset, _context ->
    Ecto.Changeset.validate_required(changeset, [:title])
  end

  policy :books do
    scope fn q, actor ->
      if actor.role == :admin, do: q, else: where(q, [b], b.published)
    end

    allow :create, fn actor -> actor.role in [:admin, :editor] end
  end
end

After compilation the module exposes __caravela_domain__/0, returning the validated Caravela.Schema.Domain IR, plus __caravela_hook__/4 and the __caravela_policy_*__ clauses emitted by each policy block.

Summary

Functions

Declare the authenticatable trait on the enclosing entity.

Enable email confirmation inside an authenticatable block.

Declare an entity (a table/schema) with a do block of fields.

Declare a field inside an entity block.

Declare a create-time hook for an entity. The hook receives the Ecto.Changeset and the caller's context map, and must return an Ecto.Changeset.

Declare a delete-time hook. The hook receives the loaded entity and the context, and must return :ok or {:error, reason}.

Custom post-login logic. Receives the loaded user and the caller's context, returns :ok or {:error, reason}.

Custom registration logic. Receives the changeset and the caller's context map, returns the changeset.

Declare an update-time hook. Same shape as on_create/2.

Declare a triple-target policy for entity.

Declare a relation between two entities.

Enable password reset inside an authenticatable block.

Configure session management inside an authenticatable block.

Declare a credential strategy inside an authenticatable block.

Declare the API version for this domain. Must match ~r/^v\d+$/.

Functions

authenticatable(list)

(macro)

Declare the authenticatable trait on the enclosing entity.

entity :users do
  field :email, :string, required: true, unique: true
  field :name, :string, required: true

  authenticatable do
    strategy :password
    strategy :api_token, scopes: [:read, :write], ttl: {90, :days}
    session :token, ttl: {30, :days}, remember_me: {365, :days}
    confirm :email, token_ttl: {24, :hours}
    reset :password, token_ttl: {1, :hour}

    on_register fn changeset, _ctx -> changeset end
    on_login fn user, _ctx ->
      if user.suspended, do: {:error, :suspended}, else: :ok
    end
  end
end

See Caravela.Schema.AuthConfig for the parsed IR.

confirm(kind, opts \\ [])

(macro)

Enable email confirmation inside an authenticatable block.

entity(name, list)

(macro)

Declare an entity (a table/schema) with a do block of fields.

entity :books do
  field :title, :string, required: true
end

field(name, type, opts \\ [])

(macro)

Declare a field inside an entity block.

field :title, :string, required: true, min_length: 3

on_create(entity, fun)

(macro)

Declare a create-time hook for an entity. The hook receives the Ecto.Changeset and the caller's context map, and must return an Ecto.Changeset.

on_create :books, fn changeset, _context ->
  Ecto.Changeset.validate_required(changeset, [:title])
end

on_delete(entity, fun)

(macro)

Declare a delete-time hook. The hook receives the loaded entity and the context, and must return :ok or {:error, reason}.

on_delete :authors, fn author, _context ->
  if author.published?, do: {:error, :has_published_books}, else: :ok
end

on_login(fun)

(macro)

Custom post-login logic. Receives the loaded user and the caller's context, returns :ok or {:error, reason}.

on_register(fun)

(macro)

Custom registration logic. Receives the changeset and the caller's context map, returns the changeset.

on_update(entity, fun)

(macro)

Declare an update-time hook. Same shape as on_create/2.

on_update :books, fn changeset, _context ->
  changeset
end

policy(entity, list)

(macro)

Declare a triple-target policy for entity.

policy :books do
  scope fn query, actor ->
    case actor.role do
      :admin -> query
      _ -> where(query, [b], b.published == true)
    end
  end

  field :internal_notes, visible: fn actor -> actor.role == :admin end
  field :author_email,   visible: fn actor, record ->
    actor.role == :admin or actor.id == record.author_id
  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

Each declared rule compiles into a clause of one of three dispatch functions on the domain module:

  • __caravela_policy_scope__/3(entity, query, actor)
  • __caravela_policy_field_visible__/3(entity, field, actor)
  • __caravela_policy_field_visible__/4(entity, field, actor, record)
  • __caravela_policy_allow__/3(entity, action, actor)
  • __caravela_policy_allow__/4(entity, action, actor, record)

The generated context, controller, GraphQL resolvers, and LiveView modules then invoke these to produce (1) Ecto WHERE clauses, (2) projected JSON responses with invisible fields removed, and (3) a typed field_access prop flowing to Svelte components.

relation(from, to, opts)

(macro)

Declare a relation between two entities.

relation :authors, :books, type: :has_many
relation :books, :publishers, type: :belongs_to

reset(kind, opts \\ [])

(macro)

Enable password reset inside an authenticatable block.

session(kind, opts \\ [])

(macro)

Configure session management inside an authenticatable block.

strategy(name, opts \\ [])

(macro)

Declare a credential strategy inside an authenticatable block.

version(v)

(macro)

Declare the API version for this domain. Must match ~r/^v\d+$/.

version "v1"

When set, all generated modules are namespaced under the version (MyApp.Library.V1.Book) and controller routes are prefixed with /api/v1/.