### 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
{task.name}
``` -------------------------------- ### 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() ```