README
Copy Markdown
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
With Igniter (recommended)
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}
]
endThen 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
]
endCreate 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.termStandalone 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
endconsumemakes Gleam functions callable from ElixirMyApp.MathBridge.add(2, 3)exposegenerates 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
- Add
AshGleam.Resourceto your resource and declare agleamblock:
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- 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
- 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)
}- Add
AshGleam.Actionsto your resource and declare agleam.actionsblock 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- 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!()- 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
- 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- 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
endRun
mix ash_gleam.codegenUse 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) } ```` |