KurrentDB Erlang
An Erlang runtime backend for the sans-IO kurrentdb package.
gleam add kurrentdb_erlang@1
kurrentdb_erlang uses gleam_hackney for HTTP/2 transport and gleam_otp for
operation workers. A Connection is an OTP factory supervisor. Each KurrentDB
operation starts a temporary worker that builds the request, sends it, decodes
the response, and replies to the caller.
Starting a Connection
For scripts, tests, or small applications, start the backend supervisor directly.
import gleam/hackney
import gleam/option
import kurrentdb
import kurrentdb_erlang
pub fn start() {
let assert Ok(client) =
kurrentdb.from_connection_string(
"kurrentdb://admin:changeit@localhost:2113?tls=false",
)
kurrentdb_erlang.start(client, hackney.configure(), option.None)
}
For applications, add the backend to your OTP supervision tree before any children that need to use KurrentDB.
import gleam/erlang/process
import gleam/hackney
import gleam/otp/factory_supervisor as factory
import gleam/otp/static_supervisor as supervisor
import kurrentdb
import kurrentdb_erlang
pub fn start_application_supervisor(
name: process.Name(factory.Message(
kurrentdb_erlang.WorkerStart,
process.Subject(kurrentdb_erlang.WorkerMessage),
)),
) {
let client = kurrentdb.new("localhost", 2113, kurrentdb.TlsDisabled)
let kurrentdb_child =
kurrentdb_erlang.supervised(
hackney.configure(),
client,
name,
)
supervisor.new(supervisor.RestForOne)
|> supervisor.add(kurrentdb_child)
// |> supervisor.add(other)
// |> supervisor.add(application)
// |> supervisor.add(children)
|> supervisor.start
}
pub fn connection(
name: process.Name(factory.Message(
kurrentdb_erlang.WorkerStart,
process.Subject(kurrentdb_erlang.WorkerMessage),
)),
) -> kurrentdb_erlang.Connection {
kurrentdb_erlang.from_name(name)
}
The name type is the same Name used by supervised and from_name:
process.Name(
factory.Message(
kurrentdb_erlang.WorkerStart,
process.Subject(kurrentdb_erlang.WorkerMessage),
),
)
You normally create that name once at your application boundary and pass it to
both supervised and from_name.
Appending Events
Unary operations return a Task. Use await to receive the result.
import gleam/json
import kurrentdb/operation/append_to_stream
import kurrentdb_erlang
import youid/uuid
pub fn append_order(connection: kurrentdb_erlang.Connection) {
let event =
append_to_stream.json_event(
uuid: uuid.v7(),
event_type: "OrderPlaced",
data: json.object([
#("order_id", json.string("order-123")),
#("total", json.float(49.95)),
]),
)
let task =
kurrentdb_erlang.append_to_stream(
connection,
stream: "orders-123",
events: [event],
config: append_to_stream.configure(),
)
kurrentdb_erlang.await(task, within: 5000)
}
Reading Streams
Streaming operations return a Stream. Use receive to pull messages and
close when the stream is no longer needed.
import kurrentdb
import kurrentdb/operation/read_stream
import kurrentdb_erlang
pub fn read_one(
connection: kurrentdb_erlang.Connection,
client: kurrentdb.Client,
) {
let stream =
kurrentdb_erlang.read_stream(
connection,
client,
stream: "orders-123",
config: read_stream.configure() |> read_stream.max_count(1),
)
let result = kurrentdb_erlang.receive(stream, within: 5000)
kurrentdb_erlang.close(stream)
result
}
receive returns StreamMessage values. ReadMessage contains the full
KurrentDB read message. ReadEvent is also emitted as a convenience whenever a
read message contains an event.
Subscriptions
You can subscribe by polling messages yourself.
import kurrentdb
import kurrentdb/operation/subscribe_to_stream
import kurrentdb_erlang
pub fn subscribe(
connection: kurrentdb_erlang.Connection,
client: kurrentdb.Client,
) {
kurrentdb_erlang.subscribe_to_stream(
connection,
client,
stream: "orders-123",
config: subscribe_to_stream.configure(),
)
}
Or you can use a callback-style event subscription when you only need events. Non-event messages such as confirmations and checkpoints are ignored by this API.
import kurrentdb
import kurrentdb/operation/subscribe_to_all
import kurrentdb_erlang
pub fn subscribe_to_all_events(
connection: kurrentdb_erlang.Connection,
client: kurrentdb.Client,
) {
kurrentdb_erlang.subscribe_to_all_events(
connection,
client,
config: subscribe_to_all.configure(),
on_event: fn(event) {
// Handle event here.
Nil
},
)
}
Close callback subscriptions with close_subscription.
Stream Metadata
Metadata is written by appending a $metadata event to the stream’s metadata
stream.
import gleam/json
import kurrentdb/operation/append_to_stream
import kurrentdb/stream_metadata
import kurrentdb_erlang
import youid/uuid
pub fn set_metadata(connection: kurrentdb_erlang.Connection) {
let metadata =
stream_metadata.new()
|> stream_metadata.max_count(100)
|> stream_metadata.custom("owner", json.string("billing"))
let task =
kurrentdb_erlang.set_stream_metadata(
connection,
stream: "orders-123",
metadata: metadata,
uuid: uuid.v7(),
config: append_to_stream.configure(),
)
kurrentdb_erlang.await(task, within: 5000)
}
TLS
The kurrentdb.Client controls whether generated requests use HTTP or HTTPS.
The hackney.Configuration controls transport details such as certificate
verification.
For local TLS development with the included Docker setup, generate certificates and configure Hackney to trust the generated CA certificate.
let http_config =
hackney.configure()
|> hackney.verify_ca_certificate_file("certs/ca.crt")
Development
./scripts/generate-dev-certs.sh
docker compose -f docker-compose.yml up -d
gleam test
Further documentation can be found at https://hexdocs.pm/kurrentdb_erlang.