### Example Workflow Start and Query in Elixir
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Shows how to initiate a workflow execution using `DurableWorkflow.start` and subsequently query its status and final context using `DurableWorkflow.get_execution`. This demonstrates the basic interaction with the executor.
```elixir
# Start a workflow
{:ok, workflow_id} = DurableWorkflow.start(MyApp.OrderWorkflow, %{order_id: 123})
# Query execution
{:ok, execution} = DurableWorkflow.get_execution(workflow_id)
IO.inspect(execution.status)
# => :completed
IO.inspect(execution.context)
# => %{order_id: 123, total: 99.99, ...}
```
--------------------------------
### Start Phoenix Server
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/README.md
Command to compile and start the Phoenix application server.
```bash
mix phx.server
```
--------------------------------
### Durable Workflow Testing Helpers
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Demonstrates the usage of `DurableWorkflow.TestCase` for writing tests for workflows. Includes examples of starting workflows, asserting their completion, and retrieving execution details. Requires ExUnit.
```elixir
defmodule MyApp.WorkflowTest do
use DurableWorkflow.TestCase
test "order workflow completes" do
{:ok, workflow_id} = start_workflow(OrderWorkflow, %{order_id: 123})
assert_workflow_completed(workflow_id, timeout: 5000)
execution = get_execution(workflow_id)
assert execution.context.processed == true
end
end
```
--------------------------------
### Start PostgreSQL Database with Docker Compose
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/README.md
Command to start the PostgreSQL database using Docker Compose. This is a prerequisite for setting up the Phoenix application.
```bash
cd examples/phoenix_demo
docker-compose up -d
```
--------------------------------
### Install Elixir Dependencies
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/README.md
Command to fetch and install project dependencies using Mix, the Elixir build tool.
```bash
mix deps.get
```
--------------------------------
### Start a Durable Workflow
Source: https://github.com/wavezync/durable/blob/main/agents/arch.md
Demonstrates how to initiate a Durable workflow by calling `Durable.start` with the workflow module and initial input parameters. This example starts a `DocumentIngestionWorkflow`.
```elixir
{:ok, workflow_id} = Durable.start(DocumentIngestionWorkflow, %{
"doc_id" => "doc_123",
"callback_url" => "https://api.example.com/webhooks/doc-processed"
})
```
--------------------------------
### Elixir Test Process Supervision with start_supervised!
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates the recommended way to start processes in Elixir tests using `start_supervised!/1`. This function ensures proper cleanup of processes between test runs.
```elixir
start_supervised!(...)
```
--------------------------------
### Elixir Branching with Pattern Matching Examples
Source: https://github.com/wavezync/durable/blob/main/guides/branching.md
Illustrates various ways to use pattern matching within Elixir Durable workflows for conditional branching. It shows examples of matching atoms, strings, booleans, and integers, demonstrating the flexibility of the `branch` macro.
```elixir
# Matching atoms
branch on: fn ctx -> ctx.status end do
:active ->
step :handle_active, fn ctx -> {:ok, ctx} end
:pending ->
step :handle_pending, fn ctx -> {:ok, ctx} end
_ ->
step :handle_other, fn ctx -> {:ok, ctx} end
end
# Matching strings
branch on: fn ctx -> ctx.format end do
"pdf" ->
step :process_pdf, fn ctx -> {:ok, ctx} end
"docx" ->
step :process_docx, fn ctx -> {:ok, ctx} end
_ ->
step :unsupported, fn ctx -> {:ok, ctx} end
end
# Matching booleans
branch on: fn ctx -> ctx.is_premium end do
true ->
step :premium_flow, fn ctx -> {:ok, ctx} end
false ->
step :standard_flow, fn ctx -> {:ok, ctx} end
end
# Matching integers
branch on: fn ctx -> ctx.tier end do
1 ->
step :tier_one, fn ctx -> {:ok, ctx} end
2 ->
step :tier_two, fn ctx -> {:ok, ctx} end
3 ->
step :tier_three, fn ctx -> {:ok, ctx} end
end
```
--------------------------------
### Install Durable as Embeddable Library (Elixir)
Source: https://github.com/wavezync/durable/blob/main/agents/conversations/embeddable-library-transformation/README.md
Demonstrates the three-step process to integrate Durable into an existing Elixir application. This includes running migrations, adding Durable to the supervision tree, and starting workflows. It assumes the use of a custom Ecto repository.
```elixir
defmodule MyApp.Repo.Migrations.AddDurable do
use Ecto.Migration
def up, do: Durable.Migration.up()
def down, do: Durable.Migration.down()
end
children = [
MyApp.Repo,
{Durable, repo: MyApp.Repo, queues: %{default: [concurrency: 10]}}
]
Durable.start(MyWorkflow, %{input: "data"})
```
--------------------------------
### Basic LiveView Template Layout
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Shows the mandatory starting structure for Phoenix LiveView templates using the app layout. It includes passing the flash assigns to the layout component.
```html
```
--------------------------------
### Elixir Step Definition with Retry Options
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Demonstrates how to configure retry logic directly within a step definition. This example shows setting maximum attempts, the backoff strategy (e.g., :exponential), and associated parameters like base and max_backoff for a specific step.
```elixir
step :charge_payment,
retry: [
max_attempts: 3,
backoff: :exponential,
base: 2,
max_backoff: 3600_000 # 1 hour cap
],
timeout: minutes(2) do
PaymentService.charge(context())
end
```
--------------------------------
### Durable Workflow DSL Example Usage
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
This Elixir code demonstrates how to define a workflow using the Durable Workflow DSL. It showcases the `use DurableWorkflow`, `workflow`, and `step` macros, along with example usage for registering and retrieving workflow definitions.
```elixir
defmodule MyApp.OrderWorkflow do
use DurableWorkflow
workflow "process_order", timeout: hours(2) do
step :validate do
Logger.info("Validating...")
{:ok, :validated}
end
step :process, retry: [max_attempts: 3] do
# Process logic
end
end
end
# Should compile and register the workflow
MyApp.OrderWorkflow.__workflows__()
# => ["process_order"]
MyApp.OrderWorkflow.__workflow_definition__("process_order")
# => %DurableWorkflow.Definition{...}
```
--------------------------------
### Elixir Branch with Multiple Sequential Steps
Source: https://github.com/wavezync/durable/blob/main/guides/branching.md
Shows how to define multiple sequential steps within a single branch of an Elixir Durable workflow. This example demonstrates a subscription order type that involves validation, billing setup, and renewal scheduling.
```elixir
branch on: fn ctx -> ctx.order_type end do
:subscription ->
step :validate_subscription, fn ctx ->
{:ok, assign(ctx, :validated, validate_recurring_payment(ctx))}
end
step :setup_billing, fn ctx ->
{:ok, assign(ctx, :billing, create_subscription_billing(ctx))}
end
step :schedule_renewals, fn ctx ->
{:ok, assign(ctx, :renewal_scheduled, schedule_monthly_charge(ctx))}
end
:one_time ->
step :process_payment, fn ctx ->
{:ok, assign(ctx, :charged, charge_once(ctx))}
end
end
```
--------------------------------
### Example Workflow Definition with Context Usage in Elixir
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Demonstrates how to define a workflow using `DurableWorkflow` and `DurableWorkflow.Context`. It shows the usage of context functions like `input`, `put_context`, and `get_context` within workflow steps to manage and access state during execution.
```elixir
defmodule MyApp.OrderWorkflow do
use DurableWorkflow
use DurableWorkflow.Context
workflow "process_order" do
step :init do
order = input().order
put_context(:order_id, order.id)
put_context(:items, order.items)
end
step :calculate_total do
items = get_context(:items)
total = Enum.sum(Enum.map(items, & &1.price))
put_context(:total, total)
end
step :finalize do
%{
order_id: get_context(:order_id),
total: get_context(:total)
}
end
end
end
```
--------------------------------
### Multi-Stage Approval Workflow in Elixir
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
An example workflow demonstrating multi-stage approval processes, where each stage might require human input using `wait_for_approval` based on certain conditions.
```elixir
workflow "expense_approval" do
step :submit, fn ctx ->
{:ok, %{expense: ctx, amount: ctx["amount"]}}
end
step :manager_review, fn ctx ->
if ctx.amount > 1000 do
result = wait_for_approval("manager_approval",
prompt: "Approve expense of $#{ctx.amount}?",
timeout: days(2)
)
{:ok, assign(ctx, :manager_approved, result == :approved)}
else
{:ok, assign(ctx, :manager_approved, true)}
end
end
step :finance_review, fn ctx ->
if ctx.amount > 5000 and ctx.manager_approved do
result = wait_for_approval("finance_approval",
timeout: days(3)
)
{:ok, assign(ctx, :finance_approved, result == :approved)}
else
{:ok, assign(ctx, :finance_approved, true)}
end
end
step :finalize, fn ctx ->
if ctx.manager_approved and ctx.finance_approved do
Expenses.approve(ctx.expense)
else
Expenses.reject(ctx.expense)
end
{:ok, ctx}
end
end
```
--------------------------------
### Elixir DynamicSupervisor Child Specification
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Shows the correct way to define a child specification for Elixir's `DynamicSupervisor`, including the required `name` option. It also demonstrates how to start a child process using the supervisor's name.
```elixir
{DynamicSupervisor, name: MyApp.MyDynamicSup}
DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)
```
--------------------------------
### Elixir Durable Workflow Setup
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
Sets up an Elixir module to use Durable, including its helpers and wait functions. This is a foundational step for defining workflows that utilize wait primitives.
```elixir
defmodule MyApp.MyWorkflow do
use Durable
use Durable.Helpers
use Durable.Wait # Import wait functions
end
```
--------------------------------
### Elixir Workflow: Use Convenience Wrappers
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
This Elixir example highlights the use of convenience wrappers for waiting in workflows, which improve code clarity and intent. It shows preferred methods like `wait_for_approval` and `wait_for_choice` over more generic `wait_for_input` calls with type parameters.
```elixir
# Good - clear intent
wait_for_approval("manager_approval", prompt: "Approve?")
wait_for_choice("priority", choices: [...])
wait_for_text("comments")
wait_for_form("details", fields: [...])
# Also works but less clear
wait_for_input("approval", type: :approval)
```
--------------------------------
### Start and Manage Workflows with Durable API
Source: https://context7.com/wavezync/durable/llms.txt
Illustrates how to initiate workflow executions using `Durable.start/3`, including options for queues, priorities, and scheduled execution. Also shows how to query workflow status and list executions.
```elixir
# Basic start
{:ok, workflow_id} = Durable.start(MyApp.OrderWorkflow, %{
"id" => "order_123",
"items" => [%{"name" => "Widget", "price" => 29.99}],
"customer_id" => "cust_456"
})
# With queue and priority options
{:ok, workflow_id} = Durable.start(
MyApp.OrderWorkflow,
%{"id" => "order_789"},
queue: :high_priority,
priority: 10
)
# Scheduled for future execution
{:ok, workflow_id} = Durable.start(
MyApp.OrderWorkflow,
%{"id" => "order_scheduled"},
scheduled_at: DateTime.add(DateTime.utc_now(), 3600, :second)
)
# Query execution status
{:ok, execution} = Durable.get_execution(workflow_id)
IO.inspect(execution.status) # :running, :completed, :failed, :waiting, etc.
# List executions with filtersexecutions = Durable.list_executions(status: :running, limit: 100)
```
--------------------------------
### Durable Best Practice: Focused Branches
Source: https://github.com/wavezync/durable/blob/main/guides/branching.md
Highlights the best practice of keeping branches focused on a single responsibility. This example shows a good practice where each branch performs a distinct action, contrasting with an anti-pattern of excessively long branches.
```elixir
# Good - each branch does one thing
branch on: fn ctx -> ctx.payment_method end do
:card ->
step :charge_card, fn ctx -> {:ok, ctx} end
:bank ->
step :initiate_transfer, fn ctx -> {:ok, ctx} end
:crypto ->
step :process_crypto, fn ctx -> {:ok, ctx} end
end
# Avoid - too much logic in branches
branch on: fn ctx -> ctx.type end do
:a ->
step :step1, fn ctx -> {:ok, ctx} end
step :step2, fn ctx -> {:ok, ctx} end
step :step3, fn ctx -> {:ok, ctx} end
step :step4, fn ctx -> {:ok, ctx} end
step :step5, fn ctx -> {:ok, ctx} end
# Consider extracting to separate workflow
end
```
--------------------------------
### Durable Best Practice: Descriptive Keys
Source: https://github.com/wavezync/durable/blob/main/guides/branching.md
Emphasizes the importance of using descriptive keys in Durable workflows for better readability and maintainability. This example contrasts a good practice of using meaningful keys with an anti-pattern of using ambiguous abbreviations.
```elixir
# Good
step :classify, fn ctx ->
{:ok, assign(ctx, :document_type, :invoice)}
end
branch on: fn ctx -> ctx.document_type end do ... end
# Avoid
step :classify, fn ctx ->
{:ok, assign(ctx, :t, :i)}
end
branch on: fn ctx -> ctx.t end do ... end
```
--------------------------------
### Render LiveView Form in Template
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Shows how to render a form in a Phoenix LiveView template using the `<.form>` function component. The form assign generated by `to_form/2` is passed to the `for` attribute. It also includes an example of a form input and specifies the importance of unique DOM IDs for forms.
```heex
<.form for={@form} id="todo-form" phx-change="validate" phx-submit="save">
<.input field={@form[:field]} type="text" />
```
--------------------------------
### LiveView Form Handling with `to_form/1`
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates how to create a formassign for LiveView templates from parameters received in `handle_event`. The `to_form/1` function converts a map of parameters into a form struct, which can be used with LiveView's form helpers. It also shows how to nest parameters using the `as:` option.
```elixir
def handle_event("submitted", params, socket) do
{:noreply, assign(socket, form: to_form(params))}
end
def handle_event("submitted", %{"user" => user_params}, socket) do
{:noreply, assign(socket, form: to_form(user_params, as: :user))}
end
```
--------------------------------
### Elixir Durable API Functions
Source: https://github.com/wavezync/durable/blob/main/README.md
Provides examples of core API functions for interacting with the Durable orchestration engine in Elixir. These functions cover starting, retrieving, listing, cancelling, sending events to, and providing input for workflows.
```elixir
Durable.start(Module, input)
Durable.start(Module, input, queue: :priority, scheduled_at: datetime)
Durable.get_execution(id)
Durable.list_executions(workflow: Module, status: :running)
Durable.cancel(id, "reason")
Durable.send_event(id, "event", payload)
Durable.provide_input(id, "input_name", data)
```
--------------------------------
### LiveView Testing with `Phoenix.LiveViewTest`
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Provides guidance on writing LiveView tests using `Phoenix.LiveViewTest` and `LazyHTML`. It emphasizes testing element presence over text content, referencing element IDs, and using functions like `element/2` and `has_element/2`. Debugging tips using `LazyHTML.filter` are also included.
```elixir
assert has_element?(view, "#my-form")
html = render(view)
document = LazyHTML.from_fragment(html)
matches = LazyHTML.filter(document, "your-complex-selector")
IO.inspect(matches, label: "Matches")
```
--------------------------------
### Independent Parallel Steps Example (Elixir)
Source: https://github.com/wavezync/durable/blob/main/guides/parallel.md
This Elixir snippet illustrates the best practice of keeping parallel steps independent. The 'Good' example shows two steps, `:a` and `:b`, that operate on their own data without relying on each other's results. The 'Bad' example highlights a common mistake where a step attempts to access data from a previous parallel step, which is not possible due to step isolation, resulting in `nil`.
```elixir
# Good - independent operations
parallel do
step :a, fn ctx -> {:ok, %{result_a: compute_a()}} end
step :b, fn ctx -> {:ok, %{result_b: compute_b()}} end
end
# Bad - step b depends on step a's data
parallel do
step :a, fn ctx -> {:ok, Map.put(ctx, :value, 42)} end
step :b, fn ctx ->
# ctx doesn't have :value - steps are isolated!
x = ctx[:value] # Returns nil
{:ok, ctx}
end
end
```
--------------------------------
### CI/CD Configuration Files
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Details the configuration files for Continuous Integration and Continuous Deployment, including GitHub Actions for automated tasks and asdf for managing tool versions.
```yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-elixir@v1
with:
elixir-version: '1.15'
- name: Install dependencies
run: mix deps.get
- name: Format check
run: mix format --check-formatted
- name: Linting
run: mix credo --strict
- name: Dialyzer
run: mix dialyzer --halt-exit-code
- name: Run tests
run: mix test
# .tool-versions
elixir 1.15.7
erlang 26.1.1
```
--------------------------------
### Using Heroicons with the <.icon> component
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates the recommended way to use hero icons in Phoenix LiveView templates. It emphasizes using the provided <.icon> component for consistency and to avoid direct use of Heroicons modules.
```html
<.icon name="hero-x-mark" class="w-5 h-5"/>
```
--------------------------------
### Start Workflow and Queue Operations (Elixir)
Source: https://github.com/wavezync/durable/blob/main/agents/arch.md
Demonstrates how to start a Durable workflow with specific queue options, priority, and scheduling. It also shows basic queue operations like pausing, resuming, and retrieving statistics for a given queue.
```elixir
# Start workflow with queue options
{:ok, workflow_id} = Durable.start(
OrderWorkflow,
%{order_id: 123},
queue: :high_priority,
priority: 10,
scheduled_at: DateTime.add(DateTime.utc_now(), 3600, :second)
)
# Queue operations
Durable.Queue.pause(:low_priority)
Durable.Queue.resume(:low_priority)
Durable.Queue.get_stats(:default)
# => %{running: 7, pending: 23, concurrency: 10}
```
--------------------------------
### Elixir Workflow: Use Meaningful Event Names
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
This Elixir example illustrates the importance of using descriptive and namespaced event names in workflows. It contrasts good examples like `payment.confirmed` and `shipment.delivered` with generic and less informative names such as `done` or `event`.
```elixir
# Good - descriptive and namespaced
wait_for_event("payment.confirmed")
wait_for_event("shipment.delivered")
wait_for_event("user.verified_email")
# Avoid - too generic
wait_for_event("done")
wait_for_event("event")
```
--------------------------------
### Handling Empty States with LiveView Streams
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates a technique for displaying empty states with LiveView streams using Tailwind CSS classes. This approach relies on the empty state being the sole HTML block alongside the stream's for-comprehension.
```HEEx
```
--------------------------------
### Elixir Approval Workflow with Durable
Source: https://github.com/wavezync/durable/blob/main/README.md
This Elixir example demonstrates an approval workflow using Durable, featuring a step that waits for human input with a timeout. It includes branching logic based on the approval decision and provides an example of how to externally provide input to a waiting workflow.
```elixir
defmodule MyApp.ExpenseApproval do
use Durable
use Durable.Helpers
use Durable.Wait
workflow "expense_approval" do
step :request_approval, fn ctx ->
result = wait_for_approval("manager",
prompt: "Approve $#{ctx["amount"]} expense?",
timeout: days(3),
timeout_value: :auto_rejected
)
{:ok, assign(ctx, :decision, result)}
end
branch on: fn ctx -> ctx.decision end do
:approved ->
step :process, fn ctx ->
Expenses.reimburse(ctx["employee_id"], ctx["amount"])
{:ok, assign(ctx, :status, :reimbursed)}
end
_ ->
step :notify_rejection, fn ctx ->
Mailer.send_rejection(ctx["employee_id"])
{:ok, assign(ctx, :status, :rejected)}
end
end
end
end
# Approve externally
Durable.provide_input(workflow_id, "manager", :approved)
```
--------------------------------
### Start New Workflows using Durable API
Source: https://github.com/wavezync/durable/blob/main/agents/arch.md
Demonstrates how to initiate new workflow executions using the Durable.start function. Supports basic starting with a workflow module and input, as well as advanced options like specifying a workflow name, queue, priority, and scheduled execution time.
```elixir
# Basic
{:ok, workflow_id} = Durable.start(
OrderWorkflow,
%{order_id: 123}
)
# With options
{:ok, workflow_id} = Durable.start(
OrderWorkflow,
%{order_id: 123},
workflow: "process_order",
queue: :high_priority,
priority: 10,
scheduled_at: ~U[2025-12-25 00:00:00Z]
)
```
--------------------------------
### Create and Migrate Ecto Database
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/README.md
Commands to create the database and run Ecto migrations for the Phoenix application. This sets up the necessary database schema.
```bash
mix ecto.create
mix ecto.migrate
```
--------------------------------
### Return Focused Data from Elixir Steps
Source: https://github.com/wavezync/durable/blob/main/guides/parallel.md
Illustrates the best practice in Elixir for returning data from workflow steps. It emphasizes returning only the necessary data produced by the step, rather than the entire context, to maintain clarity and reduce overhead. The 'Good' example shows returning a map with specific user details, while the 'Less ideal' example demonstrates returning a modified context map, which is less focused.
```elixir
# Good - return just the new data
step :fetch_user, fn ctx ->
user = Users.get(ctx.user_id)
{:ok, %{name: user.name, email: user.email}}
end
# Less ideal - returning modified context
step :fetch_user, fn ctx ->
user = Users.get(ctx.user_id)
{:ok, Map.put(ctx, :user, user)} # Works but adds unnecessary data
end
```
--------------------------------
### Mix Test Debugging
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Illustrates commands for debugging test failures in Mix projects. It shows how to run tests for a specific file and how to re-run all previously failed tests.
```bash
mix test test/my_test.exs
mix test --failed
```
--------------------------------
### Starting Workflows
Source: https://github.com/wavezync/durable/blob/main/agents/arch.md
Initiates a new workflow execution. You can provide basic details or advanced options like queue, priority, and scheduled time.
```APIDOC
## Starting Workflows
### Description
Initiates a new workflow execution. You can provide basic details or advanced options like queue, priority, and scheduled time.
### Method
`Durable.start/3`
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```elixir
# Basic
{:ok, workflow_id} = Durable.start(
OrderWorkflow,
%{order_id: 123}
)
# With options
{:ok, workflow_id} = Durable.start(
OrderWorkflow,
%{order_id: 123},
workflow: "process_order",
queue: :high_priority,
priority: 10,
scheduled_at: ~U[2025-12-25 00:00:00Z]
)
```
### Response
#### Success Response (200)
- **workflow_id** (string) - The unique identifier for the started workflow execution.
#### Response Example
```elixir
{:ok, "some-unique-workflow-id"}
```
```
--------------------------------
### Ecto Query Preloading
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates how to preload Ecto associations in queries to avoid N+1 query problems when accessing nested data in templates. This is crucial for performance when displaying related information.
```elixir
import Ecto.Query
# Example: Preloading user for messages
query = from m in Message, join: u in User, on: m.user_id == u.id, preload: [user: u]
```
--------------------------------
### Install Durable and Configure
Source: https://context7.com/wavezync/durable/llms.txt
Instructions for adding Durable to Elixir project dependencies, creating necessary database migrations, and integrating it into the application's supervision tree.
```elixir
defmodule MyApp.Repo.Migrations.AddDurable do
use Ecto.Migration
def up, do: Durable.Migration.up()
def down, do: Durable.Migration.down()
end
children = [
MyApp.Repo,
{Durable, repo: MyApp.Repo, queues: %{default: [concurrency: 10]}}}
]
```
--------------------------------
### LiveView Navigation with Link Component
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Replaces deprecated live_redirect and live_patch with the modern `<.link>` component for navigation and patching in LiveView templates. This ensures compatibility with newer LiveView versions and promotes cleaner code.
```HEEx
<.link navigate={@some_href}>Navigate
<.link patch={@some_href}>Patch
```
--------------------------------
### Using the <.input> component for form inputs
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Illustrates the usage of the imported <.input> component from core_components.ex for form inputs. This component simplifies form handling and ensures consistent styling. Overriding default classes requires full styling.
```html
<.input field={{d.f.email}} type="email" placeholder="Email" />
```
--------------------------------
### Wait for Single Choice Selection with `wait_for_choice` in Elixir
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
A convenience wrapper for `wait_for_input` to get a single choice selection from a list of options. Returns the selected choice's value.
```elixir
step :select_shipping, fn ctx ->
method = wait_for_choice("shipping_method",
prompt: "Select shipping method:",
choices: [
%{value: :express, label: "Express ($15)"},
%{value: :standard, label: "Standard (Free)"}
],
timeout: hours(24),
timeout_value: :standard
)
{:ok, assign(ctx, :shipping, method)}
end
```
--------------------------------
### Phoenix HEEx List Comprehension for Templates
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Illustrates the correct way to generate template content using list comprehensions in HEEx, emphasizing the use of `<%= for ... %>`. It explicitly states that `<% Enum.each %>` should not be used for this purpose.
```html
<%!-- Correct usage --%>
<%= for item <- @collection do %>
<%= item.name %>
<% end %>
```
--------------------------------
### Elixir DataCase for Durable Database Tests
Source: https://github.com/wavezync/durable/blob/main/CLAUDE.md
Shows how to configure tests for Durable using `Durable.DataCase`. This setup ensures database isolation for tests, typically using `Ecto.Adapters.SQL.Sandbox`.
```elixir
# Use DataCase for database tests
use Durable.DataCase, async: false
```
--------------------------------
### Pushing Events from Client to Server with Reply
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Shows how a JavaScript hook can push an event to the server and receive a reply using `this.pushEvent`. The server-side `handle_event` function must return a `{:reply, reply_data, socket}` tuple to send data back to the client.
```javascript
mounted() {
this.el.addEventListener("click", e => {
this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply));
})
}
```
```elixir
def handle_event("my_event", %{"one" => 1}, socket) do
{:reply, %{two: 2}, socket}
end
```
--------------------------------
### Elixir List Access with Enum.at
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates the correct way to access elements in an Elixir list by index using `Enum.at`. It highlights the invalid approach of using bracket notation and emphasizes the preferred method for index-based access.
```elixir
i = 0
mylist = ["blue", "green"]
Enum.at(mylist, i)
```
--------------------------------
### Create Step Executions Table Migration (Elixir)
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
This Elixir migration sets up the 'step_executions' table, designed to store details about individual steps within a workflow. It utilizes JSONB for the 'logs' field and includes a GIN index for efficient log searching. Indexes are created for workflow and step identification, as well as status tracking.
```elixir
# With JSONB for logs and GIN index for efficient querying
create table(:step_executions, primary_key: false) do
add :id, :uuid, primary_key: true
add :workflow_id, references(:workflow_executions, type: :uuid), null: false
add :step_name, :string, null: false
add :step_type, :string, null: false # step, decision, parallel, etc.
add :attempt, :integer, null: false, default: 1
add :status, :string, null: false, default: "pending"
add :input, :map
add :output, :map
add :error, :map
add :logs, :jsonb, null: false, default: "[]"
add :started_at, :utc_datetime_usec
add :completed_at, :utc_datetime_usec
add :duration_ms, :integer
timestamps(type: :utc_datetime_usec)
end
create index(:step_executions, [:workflow_id, :step_name])
create index(:step_executions, [:workflow_id, :status])
execute "CREATE INDEX step_executions_logs_gin ON step_executions USING GIN (logs)"
```
--------------------------------
### Install Durable Elixir Package
Source: https://github.com/wavezync/durable/blob/main/README.md
This snippet shows how to add the Durable Elixir package to your project's dependencies. It specifies the version constraint for the `durable` package.
```elixir
def deps do
[{:durable, "~> 0.0.0-alpha"}]
end
```
--------------------------------
### Elixir Send Event to Resume Workflow
Source: https://github.com/wavezync/durable/blob/main/guides/waiting.md
Provides an example of how to send an event in Elixir to resume a suspended workflow. This function is typically called from external systems like webhooks or API endpoints.
```elixir
# Send event to resume workflow
Durable.Wait.send_event(
workflow_id,
"payment_confirmed",
%{status: "success", transaction_id: "txn_123"}
)
```
--------------------------------
### External JS Hooks Configuration
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Explains how to set up external JavaScript hooks for LiveView. Hooks should be placed in `assets/js/` and then passed to the `LiveSocket` constructor. This allows for more organized and reusable JavaScript logic separate from the templates.
```javascript
const MyHook = {
mounted() { ... }
}
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { MyHook }
});
```
--------------------------------
### Elixir Concurrent Enumeration with Task.async_stream
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Provides an example of using `Task.async_stream` for concurrent enumeration in Elixir, emphasizing the use of `timeout: :infinity` for continuous operation. This function is suitable for scenarios requiring back-pressure.
```elixir
Task.async_stream(collection, callback, timeout: :infinity)
```
--------------------------------
### Get Workflow Graph with Execution State in Elixir
Source: https://github.com/wavezync/durable/blob/main/agents/IMPLEMENTATION_PLAN.md
Retrieves a workflow graph and overlays the current execution state onto its nodes. This function fetches both the graph structure and execution details, then merges them to provide a state-aware graph representation.
```elixir
def get_graph_with_execution(module, workflow_name, workflow_id) do
graph = DurableWorkflow.Graph.generate(module, workflow_name)
execution = DurableWorkflow.get_execution(workflow_id, include_steps: true)
nodes_with_state = Enum.map(graph.nodes, fn node ->
step_exec = find_step_execution(execution.steps, node.id)
Map.put(node, :execution_state, step_exec_to_state(step_exec))
end)
%{graph | nodes: nodes_with_state}
end
```
--------------------------------
### Compile Elixir Project
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/README.md
Command to compile the Elixir project, including all its modules and dependencies.
```bash
mix compile
```
--------------------------------
### Elixir: Start a Durable Workflow
Source: https://github.com/wavezync/durable/blob/main/agents/conversations/embeddable-library-transformation/sessions/2026-01-02-session-01.md
This Elixir code snippet illustrates how to initiate a Durable workflow. It takes the workflow module and its input data as arguments, enabling the execution of background jobs through the Durable library.
```elixir
Durable.start(MyWorkflow, %{input: "data"})
```
--------------------------------
### Create LiveView Form from Changeset
Source: https://github.com/wavezync/durable/blob/main/examples/phoenix_demo/AGENTS.md
Demonstrates how to create a form assign from an Ecto changeset using the `to_form/2` function. This form assign is then used in LiveView templates for rendering form elements. It assumes a User schema is defined.
```elixir
defmodule MyApp.Users.User do
use Ecto.Schema
...
end
# In a LiveView module or controller:
%MyApp.Users.User{}
|> Ecto.Changeset.change()
|> to_form()
```