Current section

Files

Jump to
jido_signal usage-rules.md
Raw

usage-rules.md

# Jido Signal Usage Rules
CloudEvents v1.0.2 compliant signal library for Elixir agent systems.
## Signal Creation
```elixir
# Basic signal
# Preferred: positional constructor (type, data, attrs)
{:ok, signal} = Jido.Signal.new("user.created", %{user_id: 123}, source: "/auth/service")
# Also available: Map/keyword constructor (backwards compatible)
{:ok, signal} = Jido.Signal.new(%{
type: "user.created",
source: "/auth/service",
data: %{user_id: 123}
})
# Custom signal type (preferred)
defmodule UserCreated do
use Jido.Signal,
type: "user.created",
schema: [user_id: [type: :integer, required: true]]
end
{:ok, signal} = UserCreated.new(%{user_id: 123})
```
**Signal Types**: Use dot notation (`"user.created"`, `"payment.processed"`), not camelCase/underscores.
## Dispatch
```elixir
# Direct to PID
:ok = Jido.Signal.Dispatch.dispatch(signal, {:pid, target: pid})
# Multiple destinations
configs = [
{:pid, target: pid},
{:logger, level: :info},
{:http, url: "https://webhook.example.com"}
]
:ok = Jido.Signal.Dispatch.dispatch(signal, configs)
```
**Adapters**: `:pid`, `:pubsub`, `:logger`, `:http`, `:webhook`, `:console`, `:noop`
## Event Bus
```elixir
# Start bus
{:ok, _} = Jido.Signal.Bus.start_link(name: :my_bus)
# Subscribe with patterns
{:ok, sub_id} = Bus.subscribe(:my_bus, "user.*",
dispatch: {:pid, target: self()})
# Publish (always as list)
Bus.publish(:my_bus, [signal])
```
**Patterns**: `"user.created"` (exact), `"user.*"` (single), `"user.**"` (multi-level)
## Signal Router
High-performance trie-based routing for pattern matching and handler dispatch.
```elixir
# Create router with routes
{:ok, router} = Jido.Signal.Router.new([
# Exact match
{"user.created", :handle_user_created},
# Single wildcard (matches one segment)
{"user.*.updated", :handle_user_update},
# Multi-level wildcard (matches zero or more segments)
{"audit.**", :audit_logger, 100}, # priority: -100 to 100
# Pattern matching with function
{"payment.processed",
fn signal -> signal.data.amount > 1000 end,
:handle_large_payment,
90},
# Multiple dispatch targets
{"system.error", [
{:logger, [level: :error]},
{:metrics, [type: :error_count]},
{:alert, [priority: :high]}
]}
])
# Route signal to handlers
{:ok, handlers} = Router.route(router, signal)
# Check if route exists
Router.has_route?(router, "user.created") # => true
# Check if signal matches pattern
Router.matches?("user.123", "user.*") # => true
# Filter signals by pattern
filtered = Router.filter(signals, "user.*")
# Dynamic route management
{:ok, router} = Router.add(router, {"metrics.**", :metrics_handler})
{:ok, router} = Router.remove(router, "metrics.**")
```
**Path Patterns**:
- Exact: `"user.created"` (matches exact type)
- Single wildcard: `"user.*"` (matches one segment)
- Multi-level: `"audit.**"` (matches zero or more segments)
**Handler Ordering**: Handlers execute by:
1. **Complexity** (exact > single wildcard > multi-wildcard)
2. **Priority** (-100 to 100, higher first)
3. **Registration order** (FIFO for equal complexity/priority)
**Performance**: Optimized for high-throughput pattern matching:
- O(k) routing where k = number of segments
- Direct segment matching (no trie build per match)
- Efficient multi-wildcard traversal
## Error Handling
```elixir
case Jido.Signal.Dispatch.dispatch(signal, config) do
:ok -> :success
{:error, %Jido.Signal.Error.DispatchError{} = error} ->
Logger.error("Failed: #{error.message}")
end
```
## Anti-Patterns
**❌ Avoid:**
- Generic types: `"event"`, `"message"`
- Bypassing bus: `send(pid, signal)`
- Ignoring errors: `Dispatch.dispatch(signal, config)`
**✅ Use:**
- Specific types: `"user.created"`, `"order.shipped"`
- Bus routing: `Bus.publish(:my_bus, [signal])`
- Error handling: `case Dispatch.dispatch(...) do`
## Advanced Features
### Middleware
```elixir
defmodule MyMiddleware do
use Jido.Signal.Bus.Middleware
def before_publish(signals, _ctx, state), do: {:cont, signals, state}
end
Bus.start_link(name: :bus, middleware: [{MyMiddleware, []}])
```
### Persistent Subscriptions
```elixir
{:ok, sub_id} = Bus.subscribe(:bus, "order.*", persistent: true)
:ok = Bus.ack(:bus, sub_id, signal_id)
```
### Journal & Causality
```elixir
journal = Jido.Signal.Journal.new()
{:ok, journal} = Journal.record(journal, signal)
effects = Journal.get_effects(journal, signal.id)
```
### Serialization
```elixir
{:ok, json} = Jido.Signal.Serialization.JsonSerializer.serialize(signal)
{:ok, signal} = Jido.Signal.Serialization.JsonSerializer.deserialize(json)
```
### Snapshots & Replay
```elixir
{:ok, snapshot_ref} = Bus.snapshot_create(:bus, "user.*")
{:ok, signals} = Bus.replay(:bus, "user.*", from_timestamp)
```
## Testing
```elixir
# Create test signal
signal = Jido.Signal.new!("test.event", %{data: "value"})
# Test dispatch
Bus.subscribe(:test_bus, "test.*", dispatch: {:pid, target: self()})
Bus.publish(:test_bus, [signal])
assert_receive {:signal, ^signal}
# Use :noop adapter for testing
dispatch: :noop
```