-
Notifications
You must be signed in to change notification settings - Fork 0
fix: Improve client quality and align with Go client #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4f142e5
3e92ad3
f06bd2b
340acc2
772fc7e
f54d4c0
5849e51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,13 +200,13 @@ defmodule EventSourcingDB do | |
|
|
||
| """ | ||
| @spec write_events(Client.t(), nonempty_list(EventCandidate.t()), [precondition()]) :: | ||
| response(Event.t()) | ||
| response([Event.t()]) | ||
| def write_events(client, events, preconditions \\ []) when is_list(events) do | ||
| request_one_shot(client, WriteEvents.new(events, preconditions)) | ||
| end | ||
|
|
||
| @spec write_events!(Client.t(), nonempty_list(EventCandidate.t()), [precondition()]) :: | ||
| response!(Event.t()) | ||
| response!([Event.t()]) | ||
| def write_events!(client, events, preconditions \\ []) when is_list(events) do | ||
| request_one_shot!(client, WriteEvents.new(events, preconditions)) | ||
| end | ||
|
|
@@ -292,7 +292,7 @@ defmodule EventSourcingDB do | |
| "/books/42", | ||
| %EventSourcingDB.ReadEventsOptions{ | ||
| recursive: false, | ||
| from_latest_event: %EventSourcingDB.FromLatestEventOptions{ | ||
| from_latest_event: %EventSourcingDB.ReadFromLatestEventOptions{ | ||
| subject: "/books/42", | ||
| type: "io.eventsourcingdb.library.book-borrowed", | ||
| if_event_is_missing: :read_everything | ||
|
|
@@ -303,6 +303,10 @@ defmodule EventSourcingDB do | |
|
|
||
| *Note that `from_latest_event` and `lower_bound` can not be provided at the same time.* | ||
|
|
||
| ### Aborting Reading | ||
|
|
||
| Since `read_events` returns a lazy stream, you can stop consuming it at any time. Use `Enum.take/2` to limit the number of events, or simply stop iterating. If you are consuming the stream in a separate process, you can abort by terminating that process. | ||
|
|
||
| """ | ||
| @spec read_events(Client.t(), String.t(), ReadEventsOptions.t() | nil) :: | ||
| stream_response(Event.t()) | ||
|
|
@@ -378,7 +382,7 @@ defmodule EventSourcingDB do | |
| "/books/42", | ||
| %EventSourcingDB.ObserveEventsOptions{ | ||
| recursive: false, | ||
| from_latest_event: %EventSourcingDB.FromLatestEventOptions{ | ||
| from_latest_event: %EventSourcingDB.ObserveFromLatestEventOptions{ | ||
| subject: "/books/42", | ||
| type: "io.eventsourcingdb.library.book-borrowed", | ||
| if_event_is_missing: :read_everything | ||
|
|
@@ -389,6 +393,10 @@ defmodule EventSourcingDB do | |
|
|
||
| *Note that `from_latest_event` and `lower_bound` can not be provided at the same time.* | ||
|
|
||
| ### Aborting Observing | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same for all the others here... |
||
|
|
||
| Since `observe_events` returns a lazy stream, you can stop consuming it at any time. Use `Enum.take/2` to limit the number of events, or simply stop iterating. If you are consuming the stream in a separate process, you can abort by terminating that process. | ||
|
|
||
| """ | ||
| @spec observe_events(Client.t(), String.t(), ObserveEventsOptions.t() | nil) :: | ||
| stream_response(Event.t()) | ||
|
|
@@ -416,6 +424,10 @@ defmodule EventSourcingDB do | |
|
|
||
| *Note that each row returned by the stream matches the projection specified in your query.* | ||
|
|
||
| ### Aborting a Query | ||
|
|
||
| Since `run_eventql_query` returns a lazy stream, you can stop consuming it at any time. Use `Enum.take/2` to limit the number of rows, or simply stop iterating. | ||
|
|
||
| """ | ||
| @spec run_eventql_query(Client.t(), String.t()) :: stream_response(any()) | ||
| def run_eventql_query(client, query) do | ||
|
|
@@ -479,6 +491,11 @@ defmodule EventSourcingDB do | |
| ```elixir | ||
| EventSourcingDB.read_subjects(client, "/books") | ||
| ``` | ||
|
|
||
| ### Aborting Listing | ||
|
|
||
| Since `read_subjects` returns a lazy stream, you can stop consuming it at any time by simply stopping iteration or using `Enum.take/2`. | ||
|
|
||
| """ | ||
| @spec read_subjects(Client.t(), String.t()) :: stream_response(String.t()) | ||
| def read_subjects(client, base_subject) do | ||
|
|
@@ -523,6 +540,11 @@ defmodule EventSourcingDB do | |
| {:error, reason} -> # ... | ||
| end | ||
| ``` | ||
|
|
||
| ### Aborting Listing | ||
|
|
||
| Since `read_event_types` returns a lazy stream, you can stop consuming it at any time by simply stopping iteration or using `Enum.take/2`. | ||
|
|
||
| """ | ||
| @spec read_event_types(Client.t()) :: stream_response(EventType.t()) | ||
| def read_event_types(client) do | ||
|
|
@@ -609,15 +631,15 @@ defmodule EventSourcingDB do | |
| {[message], response} | ||
|
|
||
| {:error, reason} -> | ||
| {:error, reason} | ||
| raise(reason) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is no good here and unexpected. There is Please don't throw in here, leave that to the bang versions of that functions (that's the reason for their existence). |
||
|
|
||
| # handle heartbeat case | ||
| nil -> | ||
| {[], response} | ||
| end | ||
|
|
||
| {:error, reason} -> | ||
| {:error, reason} | ||
| raise(reason) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also in this case (and the one above). Is the proper return code to |
||
|
|
||
| # This is returned when the stream is done. | ||
| {:ok, [:done]} -> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,15 +15,21 @@ defmodule EventSourcingDB.Event do | |
|
|
||
| @signature_prefix "esdb:signature:v1:" | ||
|
|
||
| @key_mapping %{ | ||
| "predecessorhash" => :predecessor_hash, | ||
| "datacontenttype" => :data_content_type, | ||
| "specversion" => :spec_version | ||
| } | ||
|
|
||
| typedstruct do | ||
| field :data, any(), enforce: true | ||
| field :datacontenttype, String.t(), enforce: true | ||
| field :data_content_type, String.t(), enforce: true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took rust impl as reference which keeps the name wo/ underscore. In the end eases handling those fields. You can underscore name them to introduce extra work. |
||
| field :hash, String.t(), enforce: true | ||
| field :id, String.t(), enforce: true | ||
| field :predecessorhash, String.t(), enforce: true | ||
| field :predecessor_hash, String.t(), enforce: true | ||
| field :signature, String.t() | ||
| field :source, String.t(), enforce: true | ||
| field :specversion, String.t(), enforce: true | ||
| field :spec_version, String.t(), enforce: true | ||
| field :subject, String.t(), enforce: true | ||
| field :time, String.t(), enforce: true | ||
| field :traceparent, String.t() | ||
|
|
@@ -33,7 +39,14 @@ defmodule EventSourcingDB.Event do | |
|
|
||
| @spec new(map()) :: Event.t() | ||
| def new(value \\ %{}) do | ||
| struct!(__MODULE__, value |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)) | ||
| struct!( | ||
| __MODULE__, | ||
| value | ||
| |> Map.new(fn {k, v} -> | ||
| key = Map.get_lazy(@key_mapping, k, fn -> String.to_existing_atom(k) end) | ||
| {key, v} | ||
| end) | ||
| ) | ||
| end | ||
|
|
||
| @doc """ | ||
|
|
@@ -55,7 +68,7 @@ defmodule EventSourcingDB.Event do | |
| @spec verify_hash(Event.t()) :: :ok | {:error, HashVerificationFailed.t()} | ||
| def verify_hash(event) do | ||
| metadata = | ||
| "#{event.specversion}|#{event.id}|#{event.predecessorhash}|#{event.time}|#{event.source}|#{event.subject}|#{event.type}|#{event.datacontenttype}" | ||
| "#{event.spec_version}|#{event.id}|#{event.predecessor_hash}|#{event.time}|#{event.source}|#{event.subject}|#{event.type}|#{event.data_content_type}" | ||
|
|
||
| metadata_hash = :crypto.hash(:sha256, metadata) |> Base.encode16(case: :lower) | ||
| data_hash = :crypto.hash(:sha256, Jason.encode!(event.data)) |> Base.encode16(case: :lower) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| defmodule EventSourcingDB.ObserveFromLatestEventOptions do | ||
| @moduledoc """ | ||
| Options for observing events starting from the latest event of a given type. | ||
| """ | ||
| use TypedStruct | ||
|
|
||
| typedstruct enforce: true do | ||
| field :if_event_is_missing, :read_everything | :wait_for_event | ||
| field :subject, String.t() | ||
| field :type, String.t() | ||
| end | ||
|
|
||
| @spec new(keyword()) :: t() | ||
| def new(options) do | ||
| options = | ||
| options | ||
| |> Keyword.validate!([:if_event_is_missing, :subject, :type]) | ||
|
|
||
| struct!(__MODULE__, options) | ||
| end | ||
|
|
||
| defimpl Jason.Encoder do | ||
| @spec encode(EventSourcingDB.ObserveFromLatestEventOptions.t(), Jason.Encode.opts()) :: | ||
| iodata() | ||
| def encode(value, opts) do | ||
| Jason.Encode.map( | ||
| %{ | ||
| "ifEventIsMissing" => | ||
| case value.if_event_is_missing do | ||
| :read_everything -> "read-everything" | ||
| :wait_for_event -> "wait-for-event" | ||
| end, | ||
| "subject" => value.subject, | ||
| "type" => value.type | ||
| }, | ||
| opts | ||
| ) | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's implicit. And I consider it general knowledge about elixir when handling stream vs list. It's one of the first things you learn.
The comment is stating the obvious and isn't needed.