README.md

# eventsourcingdb

The official Elixir client SDK for [EventSourcingDB](https://www.eventsourcingdb.io) – a purpose-built database for event sourcing.

EventSourcingDB enables you to build and operate event-driven applications with
native support for writing, reading, and observing events. This client SDK
provides convenient access to its capabilities in Elixir (read the [Elixir SDK documentation](https://hexdocs.pm/eventsourcingdb)).

For more information on EventSourcingDB, see its [official documentation](https://docs.eventsourcingdb.io/).

This client SDK includes support for [Testcontainers](https://testcontainers.com/) to spin up EventSourcingDB instances in integration tests. For details, see [Using Testcontainers](#using-testcontainers).

## Getting Started

The package can be installed by adding `eventsourcingdb` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:eventsourcingdb, "~> 0.1.0"}
  ]
end
```

Start with a `Client` that holds the connection parameters to your
EventSourcingDB instance:

```elixir
base_url = "localhost:3000"
api_token = "secret"
client = Eventsourcingdb.Client.new(base_url, api_token)
```

Now every request will take the client as its first param.

## Writing Events

Call the `write_events` function and hand over a list with one or more events. You do not have to provide all event fields – some are automatically added by the server.

Specify `source`, `subject`, `type`, and `data` according to the
[CloudEvents](https://docs.eventsourcingdb.io/fundamentals/cloud-events/)
format.

The function returns the written events, including the fields added by the
server:

```elixir
event = %Eventsourcingdb.EventCandidate{
  source: "https://library.eventsourcingdb.io",
  subject: "/books/42",
  type: "io.eventsourcingdb.library.book-acquired",
  data: %{
    "title" => "2001 - A Space Odyssey",
    "author" => "Arthur C. Clarke",
    "isbn" => "978-0756906788",
  }
}

written = Eventsourcingdb.write_events(client, [event])

case written do
  {:ok, events} -> # ...
  {:error, type, reason} -> # ..
end
```

### Using the `IsSubjectPristine` precondition

If you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristine` precondition to create a precondition and pass it in a vector as the second argument:

```elixir
written = Eventsourcingdb.write_events(
  client, 
  [event], 
  [%Eventsourcingdb.IsSubjectPristine{subject: "/books/42"}]
)

case written do
  {:ok, events} -> # ...
  {:error, type, reason} -> # ..
end
```

### Using the `IsSubjectPopulated` precondition

If you only want to write events in case a subject (such as `/books/42`) already has at least one event, use the `IsSubjectPopulated` precondition to create a precondition and pass it in a vector as the second argument:

```elixir
written = Eventsourcingdb.write_events(
  client, 
  [event], 
  [%Eventsourcingdb.IsSubjectPopulated{subject: "/books/42"}]
)

case written do
  {:ok, events} -> # ...
  {:error, type, reason} -> # ..
end
```

### Using the `IsSubjectOnEventId` precondition

If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), use the `IsSubjectOnEventId` precondition to create a precondition and pass it in a vector as the second argument:

```elixir
written = Eventsourcingdb.write_events(
  client, 
  [event], 
  [%Eventsourcingdb.IsSubjectOnEventId{subject: "/books/42", event_id: "0"}]
)

case written do
  {:ok, events} -> # ...
  {:error, type, reason} -> # ..
end
```

*Note that according to the CloudEvents standard, event IDs must be of type string.*

### Using the `IsEventQLQueryTrue` precondition

If you want to write events depending on an EventQL query, use the `IsEventQLQueryTrue` precondition to create a precondition and pass it in a vector as the second argument:

```elixir
written = Eventsourcingdb.write_events(
  client, 
  [event], 
  [%Eventsourcingdb.IsEventQLQueryTrue{
    query: "FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-borrowed' PROJECT INTO COUNT () < 10"
   }]
)

case written do
  {:ok, events} -> # ...
  {:error, type, reason} -> # ..
end
```

## Reading Events

To read all events of a subject, call the `read_events` function with the
subject and an options object.

The function returns a stream from which you can retrieve one event at a time:

```elixir
result = Eventsourcingdb.read_events(client, "/books/42")

case result do
  {:ok, events} -> Enum.to_list(events)
  {:error, type, reason} -> # handle error here
end
```

### Reading From Subjects Recursively

If you want to read not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`:

```elixir
result = Eventsourcingdb.read_events(
  client, 
  "/books/42", 
  %Eventsourcingdb.ReadEventsOptions{recursive: true}
)
```

### Reading in Anti-Chronological Order

By default, events are read in chronological order. To read in anti-chronological order, provide the `order` option and set it using the `:antichronological` ordering:

```elixir
result = Eventsourcingdb.read_events(
  client, 
  "/books/42", 
  %Eventsourcingdb.ReadEventsOptions{
    recursive: false,
    order: :antichronological
  }
)
```

*Note that you can also use the `Chronological` ordering to explicitly enforce the default order.*

### Specifying Bounds

Sometimes you do not want to read all events, but only a range of events. For that, you can specify the `lower_bound` and `upper_bound` options – either one of them or even both at the same time.

Specify the ID and whether to include or exclude it, for both the lower and upper bound:

```elixir
result = Eventsourcingdb.read_events(
  client, 
  "/books/42", 
  %Eventsourcingdb.ReadEventsOptions{
    recursive: false,
    lower_bound: %Eventsourcingdb.BoundOptions{
      type: :inclusive,
      id: "100"
    },
    upper_bound: %Eventsourcingdb.BoundOptions{
      type: :exclusive,
      id: "200"
    }
  }
)
```

### Starting From the Latest Event of a Given Type

To read starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists.

Possible options are `:read_nothing`, which skips reading entirely, or `:read_everything`, which effectively behaves as if `from_latest_event` was not specified:

```elixir
result = Eventsourcingdb.read_events(
  client, 
  "/books/42", 
  %Eventsourcingdb.ReadEventsOptions{
    recursive: false,
    from_latest_event: %Eventsourcingdb.FromLatestEventOptions{
      subject: "/books/42",
      type: "io.eventsourcingdb.library.book-borrowed"
      if_event_is_missing: :read_everything
    }
  }
)
```

*Note that `from_latest_event` and `lower_bound` can not be provided at the sametime.*

## Running EventQL Queries

To run an EventQL query, call the `run_eventql_query` function and provide the query as argument. The function returns a stream.

```elixir
result = Eventsourcingdb.run_eventql_query(client, "FROM e IN events PROJECT INTO e")

case result do
  {:ok, events} -> Enum.to_list(events)
  {:error, type, reason} -> # handle error here
end
```

## Observing Events

To observe all events of a subject, call the `observe_events` function with the subject.

The function returns a stream from which you can retrieve one event at a time:

```elixir
result = Eventsourcingdb.observe_events(client, "/books/42")

case result do
  {:ok, events} -> Enum.to_list(events)
  {:error, type, reason} -> # handle error here
end
```

### Observing From Subjects Recursively

If you want to observe not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`:

```elixir
result = Eventsourcingdb.observe_events(
  client,
  "/books/42",
  %Eventsourcingdb.ObserveEventsOptions{
    recursive: true
  }
)
```

This also allows you to observe *all* events ever written. To do so, provide `/`
as the subject and set `recursive` to `true`, since all subjects are nested
under the root subject.

### Specifying Bounds

Sometimes you do not want to observe all events, but only a range of events. For that, you can specify the `lower_bound` option.

Specify the ID and whether to include or exclude it:

```elixir
result = Eventsourcingdb.observe_events(
  client,
  "/books/42",
  %Eventsourcingdb.ObserveEventsOptions{
    recursive: false,
    lower_bound: %Eventsourcingdb.BoundOptions{
      type: :inclusive,
      id: "100"
    }
  }
)
```

### Starting From the Latest Event of a Given Type

To observe starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists.

Possible options are `:wait_for_event`, which waits for an event of the given type to happen, or `:read_everything`, which effectively behaves as if `from_latest_event` was not specified:

```elixir
result = Eventsourcingdb.observe_events(
  client,
  "/books/42",
  %Eventsourcingdb.ObserveEventsOptions{
    recursive: false,
    from_latest_event: %Eventsourcingdb.FromLatestEvevntOptions{
      subject: "/books/42",
      type: "io.eventsourcingdb.library.book-borrowed",
      if_event_is_missing: :read_everything
    }
  }
)
```

*Note that `from_latest_event` and `lower_bound` can not be provided at the same time.*

## Registering an Event Schema

To register an event schema, call the `register_event_schema` function and hand over an event type and the desired schema:

```elixir
Eventsourcingdb.register_event_schema(
  client,
  "io.eventsourcingdb.library.book-acquired",
  %{
    "type" => "object",
    "properties" => %{
      "title" =>  %{ "type": "string" },
      "author" => %{ "type": "string" },
      "isbn" =>   %{ "type": "string" },
    },
    "required" => [
      "title",
      "author",
      "isbn",
    ],
    "additionalProperties" => false,
  }),
)
```

## Reading Subjects

To list all subjects, call the `list_subjects` function with `/` as the base subject. The function returns a stream from which you can retrieve one subject at a time:

```elixir
result = Eventsourcingdb.read_subjects(client, "/")

case result do
  {:ok, subjects} -> Enum.to_list(subjects)
  {:error, type, reason} -> # handle error here
end
```

If you only want to list subjects within a specific branch, provide the desired base subject instead:

```elixir
result = Eventsourcingdb.read_subjects(client, "/books")
```

## Reading a Specific Event Type

To list a specific event type, call the `read_event_type` function. The function returns the detailed event type, which includes the schema:

```elixir
result = Eventsourcingdb.read_event_types(client, "io.eventsourcingdb.library.book-acquired")

case result do
  {:ok, event_types} -> Enum.to_list(event_types)
  {:error, error_type, reason} -> # ...
end
```

## Verifying an Event's Hash

TODO

## Verifying an Event's Signature

TODO

## Using Testcontainers

Follow the instructions to [setup test containers for elixir](https://github.com/testcontainers/testcontainers-elixir).

Then you are ready to use the provideded `TestContainer` in your tests:

```elixir
defmodule YourTest do
  alias Eventsourcingdb.TestContainer
  use ExUnit.Case

  import Testcontainers.ExUnit

  container(:esdb, TestContainer.new(())

  test "ping", %{esdb: esdb} do
    client = TestContainer.get_client(esdb)

    # do sth with client

    assert Eventsourcingdb.ping(client) == :ok
  end
end
```

### Configuring the Container Instance

By default, `TestContainer` uses the `latest` tag of the official EventSourcingDB Docker image. To change that use the provided builder and call the `with_image_tag` function.

```elixir
container(
  :esdb, 
  TestContainer.new() 
  |> TestContainer.with_image_tag("1.0.0")
)
```

Similarly, you can configure the port to use and the API token. Call the `with_port` or the `with_api_token` function respectively:

```elixir
container(
  :esdb, 
  TestContainer.new()
  |> TestContainer.with_port(4000)
  |> TestContainer.with_api_token("secret")
)
```