Caravela.Domain (Caravela v0.6.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

  can_create :books, fn context ->
    context.current_user.role in [:admin, :editor]
  end
end

After compilation the module exposes __caravela_domain__/0, returning the validated Caravela.Schema.Domain IR, plus __caravela_hook__/4 and __caravela_permission__ clauses for every declared hook and permission.

Summary

Functions

Declare the authenticatable trait on the enclosing entity.

Authorize creation of an entity. Receives only the context and must return a boolean.

Authorize deletion of an entity. Same shape as can_update/2.

Filter a read query by authorization context. Must return an Ecto.Query.

Authorize updating an entity. Receives the loaded entity and the context. Must return a boolean.

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.

can_create(entity, fun)

(macro)

Authorize creation of an entity. Receives only the context and must return a boolean.

can_create :books, fn context ->
  context.current_user.role in [:admin, :editor]
end

can_delete(entity, fun)

(macro)

Authorize deletion of an entity. Same shape as can_update/2.

can_read(entity, fun)

(macro)

Filter a read query by authorization context. Must return an Ecto.Query.

can_read :books, fn query, context ->
  case context.current_user.role do
    :admin -> query
    _ -> where(query, [b], b.published == true)
  end
end

can_update(entity, fun)

(macro)

Authorize updating an entity. Receives the loaded entity and the context. Must return a boolean.

can_update :books, fn book, context ->
  context.current_user.role == :admin or book.author_id == context.current_user.author_id
end

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/.