Uni Ecto Plugin
Yes, there's a Slack CLI. SlackCLI is an open-source command-line tool to read and send Slack messages from your terminal — built for AI agents and automation.
On this page ▾
Uni Ecto Plugin
Before we touch a single line of Ecto code, we must understand Uni. Uni is not a framework—it is a design pattern with a reference implementation.
At its core, Uni solves a simple problem: How do we structure business logic without leaking implementation details?
Traditional Phoenix contexts often mix concerns:
Uni introduces the concept of Steps. A step is a single, pure (or controlled-effect) unit of work. Steps are composed into Pipelines. Uni guarantees that if a step fails (returns :error, term), the pipeline halts immediately—similar to a with statement but on steroids.
The Uni Ecto Plugin provides a set of pre-built, reusable steps specifically for database operations. Instead of writing Repo.insert(user_changeset) wrapped in a custom function, you call Uni.Ecto.insert() as a step in your pipeline. uni ecto plugin
A typical UNI is represented as:
uni://<type>/<origin>/<local_id>
Example:
uni://customer/stripe/cus_123xyz
Components:
# lib/uni/ecto/type.ex defmodule UNI.Ecto.Type do @behaviour Ecto.Typedef type, do: :string
def cast(uni) when is_binary(uni) do case UNI.parse(uni) do {:ok, %UNI{} = uni_struct} -> :ok, uni_struct _ -> :error end end def cast(%UNI{} = uni), do: :ok, uni def cast(_), do: :error
def load(data) when is_binary(data), do: cast(data) def dump(%UNI{} = uni), do: :ok, UNI.to_string(uni) def dump(_), do: :error end
def all_tenants do # Could be a DB query or a static list ["public", "tenant_customer_a", "tenant_customer_b"] end end Before we touch a single line of Ecto
We plan to evaluate Uni Ecto Plugin against three criteria:
| Criterion | Vanilla Ecto | Uni Plugin | Notes |
|-----------|--------------|------------|-------|
| Lines of code for 4 plugins (soft-delete, encrypt, audit, tenant) | ~240 LOC | ~60 LOC | Across 5 schemas |
| Plugin conflict resolution | Manual | Ordered priority | User defines order |
| Query composition (e.g., soft-delete + tenant) | Manual where | Automatic via plugin chain | modify_query composes |
| Runtime overhead | 0 | <5% | Macro-expanded per plugin |
Error: (Ecto.NoResultsError) expected query to return a prefix, but none was set
Fix: Ensure your TenantResolver plug runs before any database calls in your controller pipeline.