FerricStore Elixir SDK
Elixir SDK for FerricStore and FerricFlow over the native ferric:// protocol.
Status: public alpha 0.1.0. APIs may change before 1.0, but the SDK is
covered by command-construction tests, architecture tests, Docker-backed
integration tests, and local benchmark scripts.
FerricFlow keeps each workflow or job's state and history in one durable place. It is an explicit durable state pipeline, not a hidden deterministic replay engine:
create -> claim -> handler -> transition/complete/retry/fail
Handlers should be idempotent because work can be retried after lease expiry, worker crash, or explicit retry.
Durability is the default contract. A workflow command returns success only after the state change is accepted by FerricStore and written through its durable path.
First 10 minutes
1. Install
def deps do
[
{:ferricstore_sdk, "~> 0.1.0"}
]
end
For local SDK development:
mix deps.get
mix test
2. Start FerricStore
For local development, use the Docker image:
docker run --rm \
-e FERRICSTORE_PROTECTED_MODE=false \
-p 6388:6388 \
ghcr.io/ferricstore/ferricstore:0.5.2
The SDK examples assume:
ferric://127.0.0.1:6388
3. Connect
{:ok, client} = FerricStore.start_link(url: "ferric://127.0.0.1:6388")
:ok = FerricStore.set(client, "hello", "world")
"world" = FerricStore.get(client, "hello")
4. Create a durable queue item
queue = FerricStore.Queue.new(client, "email", worker: "email-worker")
FerricStore.Queue.enqueue(queue, "email-1",
payload: "welcome:user-1",
attributes: %{tenant: "acme", campaign: "summer"}
)
Attributes are small indexed metadata. They are useful for search, filtering, and debugging. They are not payload bytes.
5. Process one queue batch
FerricStore.Queue.run_once(queue, fn job ->
send_email(job["payload"])
"sent"
end)
run_once/3 claims due work and completes or fails the job based on the handler
result. For a long-running worker, call it from a supervised process with your
own shutdown and concurrency policy.
6. Create a workflow/state machine
Use workflows when one durable flow moves through named states.
workflow = FerricStore.Workflow.new(client, "order", initial_state: "created")
FerricStore.Workflow.start(workflow, "order-1",
payload: "order payload",
attributes: %{tenant: "acme"},
values: %{order: :erlang.term_to_binary(%{total: 120})}
)
Claim, transition, and complete explicitly:
[job | _] = FerricStore.Workflow.claim(workflow, "created", limit: 1)
FerricStore.Workflow.transition(workflow, job["id"], "running", "charged",
partition_key: job["partition_key"],
lease_token: job["lease_token"],
fencing_token: job["fencing_token"],
payload: "charged"
)
[job | _] = FerricStore.Workflow.claim(workflow, "charged", limit: 1)
FerricStore.Workflow.complete(workflow, job["id"],
partition_key: job["partition_key"],
lease_token: job["lease_token"],
fencing_token: job["fencing_token"],
result: "ok"
)
After claim_due, the current durable state is running; the original claimed
state is tracked as run state. Pass from_state: "running" when transitioning a
claimed job.
7. Store and fetch named values
Use named values/value refs when different states need different pieces of data. Values are only hydrated when requested.
meta = FerricStore.Flow.value_put(client, "large invoice bytes",
owner_flow_id: "order-1",
name: "invoice_pdf",
override: false
)
ref = meta["ref"]
["large invoice bytes"] = FerricStore.Flow.value_mget(client, [ref])
Keep override: false for normal first-write values. Use override: true only
when replacing a value is intentional.
8. Inspect state and history
record = FerricStore.Flow.get(client, "order-1", payload: true)
history = FerricStore.Flow.history(client, "order-1")
History is for debugging and audit. Handlers should use claimed job data and requested values, not history replay.
What you use
FerricStorefor native protocol connection and KV/data-structure helpers.FerricStore.Flowfor exact FerricFlow command-level control.FerricStore.Queuefor simple durable queue helpers.FerricStore.Workflowfor explicit state-machine helpers.FerricStore.Codec.Rawby default.FerricStore.Codec.Termfor Elixir-only term payloads.FerricStore.command/4as the low-level command escape hatch.
Production shape
Use one process/service to create work and a separate long-lived worker service to claim and complete work.
Phoenix/API/serverless producer -> FerricStore -> supervised worker service
Before production, configure timeouts, lease duration, backpressure behavior,
graceful shutdown, and value hydration caps. The ferric:// transport uses one
multiplexed native socket per SDK client process; create more client processes
only after profiling shows client-side saturation.
Docs
- Documentation index
- Quickstart
- Client API
- Workflow and queue APIs
- Data, attributes, and value refs
- Configuration
- Production readiness
- Use cases
- Web and serverless usage
- Testing
- Troubleshooting
- Benchmark notes
- Development checks
Integration tests
Integration tests are explicit ExUnit integration tests. They run against the same Docker image used by CI:
docker run --rm \
-e FERRICSTORE_PROTECTED_MODE=false \
-p 6388:6388 \
ghcr.io/ferricstore/ferricstore:0.5.2
mix test --only integration