event_store_client 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9e8e4782cfd1b70376d4531ae7452d721e7af3078f23a2c6d345e654fed5d1d
4
- data.tar.gz: b7de94d902e0ff87747b5f58f3740c580473514638901ed666fc07d98fc47b7f
3
+ metadata.gz: 3d4a4dc809c49fb6d9ab52ea68c6bfcb58217cb463d64e7591d5de7e0e5c80af
4
+ data.tar.gz: 29c38f7e04dd6b4b73a61966353ec51246037dfb6ffd25d38a8ebfc06c9faa8a
5
5
  SHA512:
6
- metadata.gz: 03015ede0f74da75119247433579079ee2d71a9ab9f347d23a2f52915c5bc3441574f098a9509843325c5dd568c95c6c77681a7aaa81c364b90397e95181f929
7
- data.tar.gz: 10f9a45843bc18f0e2e35294b9dee0c7e0c1f9a33e1e4c41881f407c82ee5df4714aaef6f6d4879b271f7ef6ea032e7e765d0f97ff23b99046a68ff2b230bffb
6
+ metadata.gz: 4c15bb242df2456c57fbddd4dbe553113e803d26062081e1261ba134b64db2e445ae0f6a56a218555f3007e858c030212285749765aa941152fc6e395325acf6
7
+ data.tar.gz: 04654757ca1d4ed722255ac16d53cfa6415a7dc098ded62fbc91479fef681ed66f80ecd61e408f7a2f8bdafbf724cc4e1b9766c39f015cd79a799b327f15a3b3
data/README.md CHANGED
@@ -9,7 +9,7 @@ An easy-to use GRPC API client for connecting ruby applications with [EventStore
9
9
 
10
10
  `event_store_client` gem requires:
11
11
 
12
- - ruby 2.7 or newer.
12
+ - ruby 3.0 or newer.
13
13
  - EventstoreDB version `>= "20.*"`.
14
14
 
15
15
  ## Installation
@@ -1,6 +1,4 @@
1
- # @title Appending events
2
-
3
- # Append your first event
1
+ # Appending Events
4
2
 
5
3
  ## Append your first event
6
4
 
@@ -15,10 +13,10 @@ event = SomethingHappened.new(
15
13
  )
16
14
 
17
15
  begin
18
- EventStoreClient.client.append_to_stream('some-stream', event)
16
+ EventStoreClient.client.append_to_stream('some-stream', event)
19
17
  # => EventStore::Client::Streams::AppendResp
20
18
  rescue EventStoreClient::WrongExpectedVersionError => e
21
- puts e.message
19
+ puts e.message
22
20
  end
23
21
  ```
24
22
 
@@ -38,7 +36,7 @@ event2 = SomethingHappened.new(
38
36
  )
39
37
 
40
38
  begin
41
- EventStoreClient.client.append_to_stream('some-stream', [event1, event2])
39
+ EventStoreClient.client.append_to_stream('some-stream', [event1, event2])
42
40
  # => Array<EventStore::Client::Streams::AppendResp>
43
41
  rescue EventStoreClient::WrongExpectedVersionError => e
44
42
  puts e.message
@@ -1,6 +1,4 @@
1
- # @title Catch-up subscriptions
2
-
3
- # Catch-up subscriptions
1
+ # Catch-up Subscriptions
4
2
 
5
3
  Subscriptions allow you to subscribe to a stream and receive notifications about new events added to the stream.
6
4
 
@@ -111,7 +109,7 @@ An application, which hosts the subscription, can go offline for a period of tim
111
109
  checkpoint = :start
112
110
  handler = proc do |event|
113
111
  handle_event(event)
114
- checkpoint = event.stream_revision
112
+ checkpoint = event.stream_revision
115
113
  end
116
114
 
117
115
  EventStoreClient.client.subscribe_to_stream('some-stream', handler: handler, options: { from_revision: checkpoint })
@@ -1,19 +1,18 @@
1
- # @title Configuration
2
-
3
1
  # Configuration
4
2
 
5
3
  Configuration options:
6
4
 
7
- | name | value | default value | description |
8
- |----------------------|--------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
9
- | eventstore_url | String | `'esdb://localhost:2113'` | Connection string. See description of possible values below. |
10
- | per_page | Integer | `20` | Number of events to return in one response. |
11
- | mapper | `EventStoreClient::Mapper::Default.new` or `EventStoreClient::Mapper::Encrypted.new` | `EventStoreClient::Mapper::Default.new` | An object that is responsible for serialization / deserialization and encryption / decryption of events. |
12
- | default_event_class | `DeserializedEvent` or any class, inherited from it | `DeserializedEvent` | This class will be used during the deserialization process when deserializer fails to resolve an event's class from response. |
13
- | logger | `Logger` | `nil` | A logger that would log messages from `event_store_client` and `grpc` gems. |
14
- | skip_deserialization | Boolean | `false` | Whether to skip event deserialization using the given `mapper` setting. If you set it to `true` decryption will be skipped as well. It is useful when you want to defer deserialization and handle it later by yourself. |
15
- | skip_decryption | Boolean | `false` | Whether to skip decrypting encrypted event payloads. |
16
- | channel_args | Hash | `{ 'grpc.min_reconnect_backoff_ms' => 100, 'grpc.max_reconnect_backoff_ms' => 100, 'grpc.initial_reconnect_backoff_ms' => 100 }` | GRPC-specific connection options. This hash will be passed into the `:channel_args` argument of a Stub class of your request. More GRPC options can be found [here](https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h). |
5
+ | name | value | default value | description |
6
+ |----------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
7
+ | eventstore_url | String | `'esdb://localhost:2113'` | Connection string. See description of possible values below. |
8
+ | per_page | Integer | `20` | Number of events to return in one response. |
9
+ | mapper | `EventStoreClient::Mapper::Default.new` or `EventStoreClient::Mapper::Encrypted.new` | `EventStoreClient::Mapper::Default.new` | An object that is responsible for serialization / deserialization and encryption / decryption of events. |
10
+ | default_event_class | `DeserializedEvent` or any class, inherited from it | `EventStoreClient::DeserializedEvent` | This class will be used during the deserialization process when deserializer fails to resolve an event's class from response. |
11
+ | event_class_resolver | `Proc` | `nil` | A Proc that accepts a string and returns event class. |
12
+ | logger | `Logger` | `nil` | A logger that would log messages from `event_store_client` and `grpc` gems. |
13
+ | skip_deserialization | Boolean | `false` | Whether to skip event deserialization using the given `mapper` setting. If you set it to `true` decryption will be skipped as well. It is useful when you want to defer deserialization and handle it later by yourself. |
14
+ | skip_decryption | Boolean | `false` | Whether to skip decrypting encrypted event payloads. |
15
+ | channel_args | Hash | `{ 'grpc.min_reconnect_backoff_ms' => 100, 'grpc.max_reconnect_backoff_ms' => 100, 'grpc.initial_reconnect_backoff_ms' => 100 }` | GRPC-specific connection options. This hash will be passed into the `:channel_args` argument of a Stub class of your request. More GRPC options can be found [here](https://github.com/grpc/grpc/blob/v1.55.1/include/grpc/impl/grpc_types.h#L141). |
17
16
 
18
17
  ## A note about channel_args option
19
18
 
@@ -28,9 +27,9 @@ end
28
27
  the resulting `channel_args` value will be
29
28
 
30
29
  ```ruby
31
- {
32
- "grpc.min_reconnect_backoff_ms" => 200,
33
- "grpc.max_reconnect_backoff_ms" => 100,
30
+ {
31
+ "grpc.min_reconnect_backoff_ms" => 200,
32
+ "grpc.max_reconnect_backoff_ms" => 100,
34
33
  "grpc.initial_reconnect_backoff_ms" => 100
35
34
  }
36
35
  ```
@@ -39,7 +38,7 @@ This behaviour is intentional. So, if you want to override them all - you should
39
38
 
40
39
  ```ruby
41
40
  EventStoreClient.configure do |config|
42
- config.channel_args = {
41
+ config.channel_args = {
43
42
  'grpc.min_reconnect_backoff_ms' => 500,
44
43
  'grpc.max_reconnect_backoff_ms' => 500,
45
44
  'grpc.initial_reconnect_backoff_ms' => 500
@@ -139,7 +138,7 @@ EventStoreClient.configure(name: :es_db_1) do |config|
139
138
  end
140
139
  EventStoreClient.configure(name: :es_db_2) do |config|
141
140
  # adjust your second config here
142
- config.eventstore_url = 'esdb://localhost:2114'
141
+ config.eventstore_url = 'esdb://localhost:2114'
143
142
  end
144
143
  ```
145
144
 
@@ -162,7 +161,7 @@ Setup your default config:
162
161
  ```ruby
163
162
  EventStoreClient.configure do |config|
164
163
  # config goes here
165
- config.eventstore_url = 'esdb://localhost:2113'
164
+ config.eventstore_url = 'esdb://localhost:2113'
166
165
  end
167
166
  ```
168
167
 
@@ -1,6 +1,4 @@
1
- # @title Deleting streams
2
-
3
- # Deleting streams
1
+ # Deleting Streams
4
2
 
5
3
  ## Soft deleting streams
6
4
 
@@ -1,6 +1,4 @@
1
- # @title Encrypting events
2
-
3
- # Encrypting events
1
+ # Encrypting Events
4
2
 
5
3
  To encrypt/decrypt events payload, you can use an encrypted mapper.
6
4
 
@@ -1,6 +1,4 @@
1
- # @title Linking events
2
-
3
- # Linking events
1
+ # Linking Events
4
2
 
5
3
  ## Linking single event
6
4
 
@@ -20,7 +18,7 @@ EventStoreClient.client.append_to_stream(stream_name_1, event)
20
18
  # Get persisted event
21
19
  event = EventStoreClient.client.read(stream_name_1).first
22
20
  # Link event from first stream into second stream
23
- EventStoreClient.client.link_to(stream_name_2, event)
21
+ EventStoreClient.client.link_to(stream_name_2, event)
24
22
  # => EventStore::Client::Streams::AppendResp
25
23
  ```
26
24
 
@@ -55,7 +53,7 @@ end
55
53
  # Get persisted events
56
54
  events = EventStoreClient.client.read(stream_name_1)
57
55
  # Link events from first stream into second stream one by one
58
- EventStoreClient.client.link_to(stream_name_2, events)
56
+ EventStoreClient.client.link_to(stream_name_2, events)
59
57
  # => Array<EventStore::Client::Streams::AppendResp>
60
58
  ```
61
59
 
@@ -86,7 +84,7 @@ events = EventStoreClient.client.read(stream_name_1)
86
84
  begin
87
85
  EventStoreClient.client.link_to(stream_name_2, events, options: { expected_revision: :no_stream })
88
86
  rescue EventStoreClient::WrongExpectedVersionError => e
89
- puts e.message
87
+ puts e.message
90
88
  end
91
89
  ```
92
90
 
@@ -1,6 +1,4 @@
1
- # @title Reading events
2
-
3
- # Reading events
1
+ # Reading Events
4
2
 
5
3
  ## Reading from a stream
6
4
 
@@ -63,7 +61,7 @@ In case a stream with given name does not exist - `EventStoreClient::StreamNotFo
63
61
 
64
62
  ```ruby
65
63
  begin
66
- EventStoreClient.client.read('non-existing-stream')
64
+ EventStoreClient.client.read('non-existing-stream')
67
65
  rescue EventStoreClient::StreamNotFoundError => e
68
66
  puts e.message # => Stream "non-existing-stream" does not exist.
69
67
  puts e.stream_name # => "non-existing-stream"
@@ -23,6 +23,7 @@ module EventStoreClient
23
23
  # https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h
24
24
  option(:channel_args) # Hash
25
25
  option(:name) { :default }
26
+ option(:event_class_resolver) # Proc that excepts a string and returns a class
26
27
 
27
28
  def eventstore_url=(value)
28
29
  @eventstore_url =
@@ -48,28 +48,43 @@ module EventStoreClient
48
48
 
49
49
  # @return [String]
50
50
  def user_friendly_message
51
- if wrong_expected_version.expected_stream_exists
52
- return "Expected stream to exist, but it doesn't."
53
- end
54
- if wrong_expected_version.expected_no_stream
55
- return "Expected stream to be absent, but it actually exists."
56
- end
57
- if wrong_expected_version.current_no_stream
58
- return <<~TEXT.strip
59
- Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
60
- stream does not exist.
61
- TEXT
62
- end
51
+ return expected_stream_exists if wrong_expected_version.expected_stream_exists
52
+ return expected_no_stream if wrong_expected_version.expected_no_stream
53
+ return current_no_stream if wrong_expected_version.current_no_stream
63
54
  unless wrong_expected_version.expected_revision == wrong_expected_version.current_revision
64
- return <<~TEXT.strip
65
- Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
66
- actual stream revision is #{wrong_expected_version.current_revision.inspect}.
67
- TEXT
55
+ return unmatched_stream_revision
68
56
  end
57
+
69
58
  # Unhandled case. Could happen if something else would be added to proto and I don't add it
70
59
  # here.
71
60
  self.class.to_s
72
61
  end
62
+
63
+ # @return [String]
64
+ def expected_stream_exists
65
+ "Expected stream to exist, but it doesn't."
66
+ end
67
+
68
+ # @return [String]
69
+ def expected_no_stream
70
+ "Expected stream to be absent, but it actually exists."
71
+ end
72
+
73
+ # @return [String]
74
+ def current_no_stream
75
+ <<~TEXT.strip
76
+ Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
77
+ stream does not exist.
78
+ TEXT
79
+ end
80
+
81
+ # @return [String]
82
+ def unmatched_stream_revision
83
+ <<~TEXT.strip
84
+ Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
85
+ actual stream revision is #{wrong_expected_version.current_revision.inspect}.
86
+ TEXT
87
+ end
73
88
  end
74
89
 
75
90
  class StreamDeletionError < Error
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ class EventClassResolver
5
+ attr_reader :config
6
+ private :config
7
+
8
+ # @param config [EventStoreClient::Config]
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ # @param event_type [String, nil]
14
+ # @return [Class<EventStoreClient::DeserializedEvent>]
15
+ def resolve(event_type)
16
+ return config.event_class_resolver.(event_type) || config.default_event_class if config.event_class_resolver
17
+
18
+ Object.const_get(event_type)
19
+ rescue NameError, TypeError
20
+ config.logger&.debug(<<~TEXT.strip)
21
+ Unable to resolve class by `#{event_type}' event type. \
22
+ Picking default `#{config.default_event_class}' event class to instantiate the event.
23
+ TEXT
24
+ config.default_event_class
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This matcher is defined to test options which are defined by using
4
+ # EventStoreClient::Extensions::OptionsExtension option. Example:
5
+ # Let's say you have next class
6
+ # class SomeClass
7
+ # include EventStoreClient::Extensions::OptionsExtension
8
+ #
9
+ # option(:some_opt) { '1' }
10
+ # end
11
+ #
12
+ # To test that its instance has the proper option with the proper default value you can use this
13
+ # matcher:
14
+ # RSpec.describe SomeClass do
15
+ # subject { described_class.new }
16
+ #
17
+ # # Check that :some_opt is present
18
+ # it { is_expected.to have_option(:some_opt) }
19
+ # # Check that :some_opt is present and has the correct default value
20
+ # it { is_expected.to have_option(:some_opt).with_default_value('1') }
21
+ # end
22
+ #
23
+ # If you have more complex implementation of default value of your option - you should handle it
24
+ # customly. For example:
25
+ # class SomeClass
26
+ # include EventStoreClient::Extensions::OptionsExtension
27
+ #
28
+ # option(:some_opt) { calc_value }
29
+ # end
30
+ # You could test it like so:
31
+ # RSpec.described SomeClass do
32
+ # let(:instance) { described_class.new }
33
+ #
34
+ # describe ':some_opt default value' do
35
+ # subject { instance.some_opt }
36
+ #
37
+ # let(:value) { 'some val' }
38
+ #
39
+ # before do
40
+ # allow(instance).to receive(:calc_value).and_return(value)
41
+ # end
42
+ #
43
+ # it { is_expected.to eq(value) }
44
+ # end
45
+ # end
46
+ RSpec::Matchers.define :has_option do |option_name|
47
+ match do |obj|
48
+ option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
49
+ if @default_value
50
+ option_presence && obj.class.allocate.public_send(option_name) == @default_value
51
+ else
52
+ option_presence
53
+ end
54
+ end
55
+
56
+ failure_message do |obj|
57
+ option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
58
+ if option_presence && @default_value
59
+ msg = "Expected #{obj.class} to have `#{option_name.inspect}' option with #{@default_value.inspect}"
60
+ msg += ' default value, but default value is'
61
+ msg += " #{obj.class.allocate.public_send(option_name).inspect}"
62
+ else
63
+ msg = "Expected #{obj} to have `#{option_name.inspect}' option."
64
+ end
65
+
66
+ msg
67
+ end
68
+
69
+ description do
70
+ expected_list = RSpec::Matchers::EnglishPhrasing.list(expected)
71
+ sentences =
72
+ @chained_method_clauses.map do |(method_name, method_args)|
73
+ next '' if method_name == :required_kwargs
74
+
75
+ english_name = RSpec::Matchers::EnglishPhrasing.split_words(method_name)
76
+ arg_list = RSpec::Matchers::EnglishPhrasing.list(method_args)
77
+ " #{english_name}#{arg_list}"
78
+ end.join
79
+
80
+ "have#{expected_list} option#{sentences}"
81
+ end
82
+
83
+ chain :with_default_value do |val|
84
+ @default_value = val
85
+ end
86
+ end
87
+
88
+ RSpec::Matchers.alias_matcher :have_option, :has_option
@@ -32,7 +32,8 @@ module EventStoreClient
32
32
  custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
33
33
  metadata = raw_event.metadata.to_h
34
34
 
35
- event_class(metadata['type']).new(
35
+ event_class = EventStoreClient::EventClassResolver.new(config).resolve(metadata['type'])
36
+ event_class.new(
36
37
  skip_validation: true,
37
38
  id: raw_event.id.string,
38
39
  title: "#{raw_event.stream_revision}@#{raw_event.stream_identifier.stream_name}",
@@ -49,18 +50,6 @@ module EventStoreClient
49
50
 
50
51
  private
51
52
 
52
- # @param event_type [String]
53
- # @return [Class<EventStoreClient::DeserializedEvent>]
54
- def event_class(event_type)
55
- Object.const_get(event_type)
56
- rescue NameError, TypeError
57
- config.logger&.debug(<<~TEXT.strip)
58
- Unable to resolve class by `#{event_type}' event type. \
59
- Picking default `#{config.default_event_class}' event class to instantiate the event.
60
- TEXT
61
- config.default_event_class
62
- end
63
-
64
53
  # @param raw_data [String]
65
54
  # @return [String]
66
55
  def normalize_serialized(raw_data)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '3.0.0'
4
+ VERSION = '3.2.0'
5
5
  end
@@ -17,6 +17,7 @@ require 'event_store_client/connection/url'
17
17
  require 'event_store_client/connection/url_parser'
18
18
  require 'event_store_client/deserialized_event'
19
19
  require 'event_store_client/serialized_event'
20
+ require 'event_store_client/event_class_resolver'
20
21
  require 'event_store_client/config'
21
22
 
22
23
  require 'event_store_client/mapper'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_store_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Wilgosz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-06 00:00:00.000000000 Z
11
+ date: 2023-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grpc
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.11'
47
+ version: '3.12'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.11'
54
+ version: '3.12'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -222,10 +222,12 @@ files:
222
222
  - lib/event_store_client/deserialized_event.rb
223
223
  - lib/event_store_client/encryption_metadata.rb
224
224
  - lib/event_store_client/errors.rb
225
+ - lib/event_store_client/event_class_resolver.rb
225
226
  - lib/event_store_client/extensions/options_extension.rb
226
227
  - lib/event_store_client/mapper.rb
227
228
  - lib/event_store_client/mapper/default.rb
228
229
  - lib/event_store_client/mapper/encrypted.rb
230
+ - lib/event_store_client/rspec/has_option_matcher.rb
229
231
  - lib/event_store_client/serialized_event.rb
230
232
  - lib/event_store_client/serializer/event_deserializer.rb
231
233
  - lib/event_store_client/serializer/event_serializer.rb
@@ -245,14 +247,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
245
247
  requirements:
246
248
  - - ">="
247
249
  - !ruby/object:Gem::Version
248
- version: '0'
250
+ version: 3.0.0
249
251
  required_rubygems_version: !ruby/object:Gem::Requirement
250
252
  requirements:
251
253
  - - ">="
252
254
  - !ruby/object:Gem::Version
253
255
  version: '0'
254
256
  requirements: []
255
- rubygems_version: 3.3.7
257
+ rubygems_version: 3.4.10
256
258
  signing_key:
257
259
  specification_version: 4
258
260
  summary: Ruby integration for https://eventstore.org