Logo

License: MIT Hex version badge Hexdocs badge

AshGleam

Type-safe Gleam interop for Ash resources

AshGleam integrates Elixir's Ash framework and Gleam into a single, cohesive system. It enables you to move data and execution across the boundary with compile-time guarantees.

You can:

Installation

mix igniter.install ash_gleam
mix igniter.install ash_gleam
# If testing locally:
mix igniter.install ash_gleam@path:..

This automatically configures your mix.exs with all the settings required by MixGleam: compilers, erlc_paths, erlc_include_path, prune_code_paths, the deps.get alias, and the gleam_stdlib / gleeunit dependencies. It also creates the src/ directory and adds build/ to your .gitignore.

You will still need to install the Gleam compiler and the MixGleam archive:

# Install the Gleam compiler — see https://gleam.run/getting-started/installing-gleam.html

# Install the MixGleam Mix archive
mix archive.install hex mix_gleam

Manual setup

Add ash_gleam to your dependencies:

# mix.exs
defp deps do
  [
    {:ash_gleam, "~> 0.1"},
    {:gleam_stdlib, "~> 0.34 or ~> 1.0"},
    {:gleeunit, "~> 1.0", only: [:dev, :test], runtime: false}
  ]
end

Then follow the MixGleam README to configure your project:

# mix.exs
@app :my_app

def project do
  [
    app: @app,
    # ...
    archives: [mix_gleam: "~> 0.6"],
    compilers: [:gleam | Mix.compilers()],
    aliases: [
      "deps.get": ["deps.get", "gleam.deps.get"]
    ],
    erlc_paths: [
      "_build/dev/lib/#{@app}/_gleam_artefacts",
      "_build/dev/lib/#{@app}/build"
    ],
    erlc_include_path: "_build/dev/lib/#{@app}/include",
    prune_code_paths: false
  ]
end

Create a src/ directory for your Gleam source files and add the following to your .gitignore:

# gleam build files
/build/

# intermediate generation file
/src/generated/manifest.term

Standalone Elixir and Gleam bridges

If you only want Elixir↔Gleam interop and do not want to hook into Ash, use AshGleam.GleamBridge.

defmodule MyApp.MathBridge do
  use AshGleam.GleamBridge

  gleam do
    consume do
      function :add_in_gleam, :integer do
        argument :a, :integer, allow_nil?: false
        argument :b, :integer, allow_nil?: false

        run &:my_gleam_module.add/2
      end
    end

    expose do
      function :add, :integer do
        argument :a, :integer, allow_nil?: false
        argument :b, :integer, allow_nil?: false

        run fn a, b -> {:ok, a + b} end
      end
    end
  end
end
  • consume makes Gleam functions callable from Elixir MyApp.MathBridge.add(2, 3)
  • expose generates Gleam-callable functions backed by Elixir. math_bridge.add_in_elixr(2, 3)

Run mix ash_gleam.codegen to generate both sides.

Generate Gleam types from your Ash resources

  1. Add AshGleam.Resource to your resource and declare a gleam block:
defmodule MyApp.Todo do
  use Ash.Resource,
    ...,
    extensions: [AshGleam.Resource, AshGleam.Actions]

  gleam do
    type_name "Todo"       # required — the Gleam type name
    module_name "todo_item" # optional — overrides the generated file name
  end

  attributes do
    uuid_primary_key :id
    attribute :title, :string, allow_nil?: false, public?: true
    attribute :completed, :boolean, default: false, public?: true
  end
end
  1. Run mix ash_gleam.codegen
// generated at src/generated/src/todo_item

pub type TicTacToe {
  TicTacToe(
    id: String,
    title: String,
    completed: Boolean,
  )
}

Only public?: true attributes are included in the generated Gleam type.

Expose Gleam functions as Ash actions

  1. Create a gleam function (make sure you have run the generator first)
// Import the generated Todo type
import src/generated/src/todo_item.{type Todo, Todo}

pub fn mark_completed(item: Todo) -> Todo {
  Todo(..item, completed: True)
}
  1. Add AshGleam.Actions to your resource and declare a gleam.actions block for that function
defmodule MyApp.Todo do
  use Ash.Resource,
    ...,
    extensions: [AshGleam.Resource, AshGleam.Actions]


  gleam do
    type_name "Todo"
    module_name "todo_item"

    actions do
      action :mark_completed, __MODULE__ do
        update? true
        argument :todo, __MODULE__, allow_nil?: false

        run &:test_gleam.mark_completed/1
      end
    end
  end
end
  1. Use the exposed function
todo = # create a todo

# mark_completed in memory
assert {:ok, updated} = MyApp.Todo.mark_completed(%{todo: todo})

# mark_completed and persist
{:ok, changeset} =
  todo
  |> AshGleam.Changeset.for_update(:mark_completed, %{}, action: :update)
  |> Ash.update!()
  1. If you want a code interface that does the update for you, update your domain
defmodule MyApp.Domain do
  use Ash.Domain,
    otp_app: :my_app,
    extensions: [AshGleam.Domain]

  gleam do
    code_interface do
      resource AshGleam.TestTodo do
        define_gleam_update :mark_completed, action: :update
      end
    end
  end
end

# mark_completed and persist
{:ok, updated} = MyApp.Domain.mark_completed(todo)

Expose Ash actions to Gleam

  1. Add the resource actions you want to expose to Gleam
defmodule MyApp.Todo do
  use Ash.Resource,
    ...,
    extensions: [AshGleam.Resource, AshGleam.Actions]

  ...

  actions do
    defaults [:read]

    create :create do
      accept [:title, :completed, :priority]
    end

    update :update do
      accept [:title, :completed, :priority]
      require_atomic? false
    end

    destroy :destroy

    read :get do
      get_by [:id]
    end

    read :first_completed do
      get? true
      filter expr(completed == true)
      prepare build(sort: [title: :asc], limit: 1)
    end
  end
end
  1. Create an entry in gleam.ffi for your resource actions
defmodule MyApp.Domain do
  use Ash.Domain,
    otp_app: :my_app,
    extensions: [AshGleam.Domain]

  gleam do
    ffi do
      resource MyApp.Todo do
        action :list_todos, :read
        action :create_todo, :create
        action :get_todo, :get
        action :destroy_todo, :destroy
        action :first_completed, :first_completed
      end
    end
  end
end
  1. Run mix ash_gleam.codegen

  2. Use the generated gleam functions

import myapp/generated/src/list_todos
import myapp/generated/src/todo_item.{type TodoFilter, type TodoSort}

pub fn fetch_incomplete_todo_titles(): Result(List(String), String) {
  list_todos.new()
  |> list_todos.filter([todo_item.CompletedEq(False)])
  |> list_todos.sort([todo_item.Title(Asc)])
  |> list_todos.limit(option.Some(10))
  |> list_todos.run()
  |> result.map(fn (todo_item) {
    todo_item.title
  })
}

Represent Gleam custom-types in Elixir

You can define the equivalent to Gleam's custom types using AshSumType from ash_sum_type.

Elixir Generated Gleam
```elixir defmodule MyApp.Mark do use AshSumType, variants: [:x, :o] end ``` ```gleam pub type Mark { X O } ```
```elixir defmodule MyApp.Mark do use AshSumType variant :x variant :o end ``` ```gleam pub type Mark { X O } ```
```elixir defmodule MyApp.LookupOutcome do use AshSumType variant :found do field :value, MyApp.Todo, allow_nil?: false end variant :missing do field :error, :string, allow_nil?: false end end ```` ```gleam pub type LookupOutcome { Found(Todo) Missing(String) } ````
## Embedded resources Resources with the `:embedded` data layer work as field types in other resources. The embedded resource gets its own Gleam type and is imported automatically in the parent resource's generated file. ```elixir defmodule MyApp.Tag do use Ash.Resource, domain: MyApp.Domain, data_layer: :embedded, extensions: [AshGleam.Resource] gleam do type_name "Tag" end attributes do attribute :label, :string, allow_nil?: false, public?: true attribute :color, :string, allow_nil?: false, public?: true end end defmodule MyApp.Todo do use Ash.Resource, ..., extensions: [AshGleam.Resource] attributes do ... # Gleam type List(Tag) attribute :tags, {:array, MyApp.Tag}, allow_nil?: false, default: [], public?: true # Gleam type List(Option(Tag)) attribute :nullable_tags, {:array, MyApp.Tag}, allow_nil?: false, default: [], public?: true, nil_items?: true end end ``` ## Requirements - Elixir 1.15+ - Ash 3.0+ - Gleam (with `mix_gleam` configured) ## Contributing 1. Fork the repository 2. Create a feature branch 3. Add tests for any new behaviour 4. Run `mix test` and `mix format` 5. Open a pull request ## License MIT — see [LICENSES/MIT.txt](https://github.com/NduatiK/ash_gleam/blob/main/LICENSES/MIT.txt). ## Links - **Hex**: [https://hex.pm/packages/ash_gleam](https://hex.pm/packages/ash_gleam) - **Docs**: [https://hexdocs.pm/ash_gleam](https://hexdocs.pm/ash_gleam) - **Issues**: [https://github.com/NduatiK/ash_gleam/issues](https://github.com/NduatiK/ash_gleam/issues)