event_store_client 2.3.0 → 3.0.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/docs/appending_events.md +15 -19
  3. data/docs/catch_up_subscriptions.md +34 -67
  4. data/docs/configuration.md +0 -2
  5. data/docs/deleting_streams.md +13 -1
  6. data/docs/encrypting_events.md +1 -3
  7. data/docs/linking_events.md +19 -23
  8. data/docs/reading_events.md +32 -59
  9. data/lib/event_store_client/adapters/grpc/client.rb +13 -9
  10. data/lib/event_store_client/adapters/grpc/commands/command.rb +0 -2
  11. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +1 -1
  12. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +11 -5
  13. data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +2 -6
  14. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +7 -5
  15. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +7 -5
  16. data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +5 -10
  17. data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +5 -8
  18. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +3 -5
  19. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +6 -8
  20. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +11 -13
  21. data/lib/event_store_client/adapters/grpc.rb +0 -1
  22. data/lib/event_store_client/deserialized_event.rb +21 -3
  23. data/lib/event_store_client/errors.rb +95 -0
  24. data/lib/event_store_client/mapper/default.rb +1 -1
  25. data/lib/event_store_client/mapper/encrypted.rb +2 -2
  26. data/lib/event_store_client/serializer/event_deserializer.rb +4 -2
  27. data/lib/event_store_client/serializer/event_serializer.rb +41 -17
  28. data/lib/event_store_client/version.rb +1 -1
  29. data/lib/event_store_client.rb +3 -1
  30. metadata +19 -18
@@ -14,8 +14,6 @@ module EventStoreClient
14
14
  end
15
15
  end
16
16
 
17
- include Dry::Monads[:try, :result]
18
-
19
17
  attr_reader :connection, :config
20
18
  private :connection, :config
21
19
 
@@ -11,7 +11,7 @@ module EventStoreClient
11
11
  # @api private
12
12
  # @see {EventStoreClient::GRPC::Client#cluster_info}
13
13
  def call
14
- Success(retry_request { service.read(request.new, metadata: metadata) })
14
+ retry_request { service.read(request.new, metadata: metadata) }
15
15
  end
16
16
  end
17
17
  end
@@ -20,7 +20,7 @@ module EventStoreClient
20
20
  response = retry_request(skip_retry: config.eventstore_url.throw_on_append_failure) do
21
21
  service.append(payload, metadata: metadata)
22
22
  end
23
- validate_response(response)
23
+ validate_response(response, caused_by: event)
24
24
  end
25
25
 
26
26
  private
@@ -41,11 +41,17 @@ module EventStoreClient
41
41
  end
42
42
 
43
43
  # @param resp [EventStore::Client::Streams::AppendResp]
44
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
45
- def validate_response(resp)
46
- return Success(resp) if resp.success
44
+ # @param caused_by [EventStoreClient::DeserializedEvent]
45
+ # @return [EventStore::Client::Streams::AppendResp]
46
+ # @raise [EventStoreClient::WrongExpectedVersionError]
47
+ def validate_response(resp, caused_by:)
48
+ return resp if resp.success
47
49
 
48
- Failure(resp.wrong_expected_version)
50
+ error = WrongExpectedVersionError.new(
51
+ resp.wrong_expected_version,
52
+ caused_by: caused_by
53
+ )
54
+ raise error
49
55
  end
50
56
  end
51
57
  end
@@ -10,9 +10,8 @@ module EventStoreClient
10
10
  # @api private
11
11
  # @see {EventStoreClient::GRPC::Client#append_to_stream}
12
12
  def call(stream_name, events, options:, &blk)
13
- result = []
14
- events.each.with_index do |event, index|
15
- response = Commands::Streams::Append.new(
13
+ events.map.with_index do |event, index|
14
+ Commands::Streams::Append.new(
16
15
  config: config, **connection_options
17
16
  ).call(
18
17
  stream_name, event, options: options
@@ -21,10 +20,7 @@ module EventStoreClient
21
20
 
22
21
  yield(req_opts, proposed_msg_opts) if blk
23
22
  end
24
- result.push(response)
25
- break if response.failure?
26
23
  end
27
- result
28
24
  end
29
25
 
30
26
  private
@@ -13,11 +13,13 @@ module EventStoreClient
13
13
  def call(stream_name, options:, &blk)
14
14
  options = normalize_options(stream_name, options)
15
15
  yield options if blk
16
- Success(
17
- retry_request { service.delete(request.new(options: options), metadata: metadata) }
18
- )
19
- rescue ::GRPC::FailedPrecondition
20
- Failure(:stream_not_found)
16
+ retry_request { service.delete(request.new(options: options), metadata: metadata) }
17
+ rescue ::GRPC::FailedPrecondition => e
18
+ # GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
19
+ # be existing, or :expected_revision option value does not match the current state of
20
+ # the stream. So, re-raise our own error, and pass there the original message - just in
21
+ # case.
22
+ raise StreamDeletionError.new(stream_name, details: e.message)
21
23
  end
22
24
 
23
25
  private
@@ -13,11 +13,13 @@ module EventStoreClient
13
13
  def call(stream_name, options:, &blk)
14
14
  options = normalize_options(stream_name, options)
15
15
  yield options if blk
16
- Success(
17
- retry_request { service.delete(request.new(options: options), metadata: metadata) }
18
- )
19
- rescue ::GRPC::FailedPrecondition
20
- Failure(:stream_not_found)
16
+ retry_request { service.delete(request.new(options: options), metadata: metadata) }
17
+ rescue ::GRPC::FailedPrecondition => e
18
+ # GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
19
+ # be existing, or :expected_revision option value does not match the current state of
20
+ # the stream. So, re-raise our own error, and pass there the original message - just in
21
+ # case.
22
+ raise StreamDeletionError.new(stream_name, details: e.message)
21
23
  end
22
24
 
23
25
  private
@@ -10,18 +10,13 @@ module EventStoreClient
10
10
  # @api private
11
11
  # @see {EventStoreClient::GRPC::Client#link_to}
12
12
  def call(stream_name, events, options:, &blk)
13
- result = []
14
13
  link_cmd = Commands::Streams::LinkTo.new(config: config, **connection_options)
15
- events.each.with_index do |event, index|
16
- response =
17
- link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
18
- req_opts.options.revision += index if has_revision_option?(req_opts.options)
19
- yield(req_opts, proposed_msg_opts) if blk
20
- end
21
- result.push(response)
22
- break if response.failure?
14
+ events.map.with_index do |event, index|
15
+ link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
16
+ req_opts.options.revision += index if has_revision_option?(req_opts.options)
17
+ yield(req_opts, proposed_msg_opts) if blk
18
+ end
23
19
  end
24
- result
25
20
  end
26
21
 
27
22
  private
@@ -40,22 +40,19 @@ module EventStoreClient
40
40
 
41
41
  paginate_options(opts, position)
42
42
  end
43
- unless response.success?
44
- yielder << response
45
- raise StopIteration
46
- end
47
43
  processed_response =
48
44
  EventStoreClient::GRPC::Shared::Streams::ProcessResponses.
49
45
  new(config: config).
50
46
  call(
51
- response.success,
47
+ response,
52
48
  skip_deserialization,
53
49
  skip_decryption
54
50
  )
55
- yielder << processed_response if processed_response.success.any?
56
- raise StopIteration if end_reached?(response.success, max_count)
57
51
 
58
- position = calc_next_position(response.success, direction, stream_name)
52
+ yielder << processed_response if processed_response.any?
53
+ raise StopIteration if end_reached?(response, max_count)
54
+
55
+ position = calc_next_position(response, direction, stream_name)
59
56
  raise StopIteration if position.negative?
60
57
  end
61
58
  end
@@ -28,11 +28,9 @@ module EventStoreClient
28
28
 
29
29
  handler.call(result) if result
30
30
  end
31
- Success(
32
- retry_request do
33
- service.read(request.new(options: options), metadata: metadata, &callback)
34
- end
35
- )
31
+ retry_request do
32
+ service.read(request.new(options: options), metadata: metadata, &callback)
33
+ end
36
34
  end
37
35
 
38
36
  private
@@ -5,8 +5,6 @@ module EventStoreClient
5
5
  module Shared
6
6
  module Streams
7
7
  class ProcessResponse
8
- include Dry::Monads[:result]
9
-
10
8
  attr_reader :config
11
9
  private :config
12
10
 
@@ -19,15 +17,15 @@ module EventStoreClient
19
17
  # @param response [EventStore::Client::Streams::ReadResp]
20
18
  # @param skip_deserialization [Boolean]
21
19
  # @param skip_decryption [Boolean]
22
- # @return [Dry::Monads::Success, Dry::Monads::Failure, nil]
20
+ # @return [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp, nil]
21
+ # @raise [EventStoreClient::StreamNotFoundError]
23
22
  def call(response, skip_deserialization, skip_decryption)
24
- return Failure(:stream_not_found) if response.stream_not_found
25
- return Success(response) if skip_deserialization
23
+ non_existing_stream = response.stream_not_found&.stream_identifier&.stream_name
24
+ raise StreamNotFoundError, non_existing_stream if non_existing_stream
25
+ return response if skip_deserialization
26
26
  return unless response.event&.event
27
27
 
28
- Success(
29
- config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
30
- )
28
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
31
29
  end
32
30
  end
33
31
  end
@@ -5,8 +5,6 @@ module EventStoreClient
5
5
  module Shared
6
6
  module Streams
7
7
  class ProcessResponses
8
- include Dry::Monads[:result]
9
-
10
8
  attr_reader :config
11
9
  private :config
12
10
 
@@ -19,20 +17,20 @@ module EventStoreClient
19
17
  # @param responses [Array<EventStore::Client::Streams::ReadResp>]
20
18
  # @param skip_deserialization [Boolean]
21
19
  # @param skip_decryption [Boolean]
22
- # @return [Dry::Monads::Success, Dry::Monads::Failure]
20
+ # @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
21
+ # @raise [EventStoreClient::StreamNotFoundError]
23
22
  def call(responses, skip_deserialization, skip_decryption)
24
- return Failure(:stream_not_found) if responses.first&.stream_not_found
25
- return Success(responses) if skip_deserialization
23
+ non_existing_stream = responses.first&.stream_not_found&.stream_identifier&.stream_name
24
+ raise StreamNotFoundError, non_existing_stream if non_existing_stream
25
+ return responses if skip_deserialization
26
26
 
27
- events =
28
- responses.map do |response|
29
- # It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
30
- # example. Such responses should be skipped. See generated files for more info.
31
- next unless response.event&.event
27
+ responses.map do |response|
28
+ # It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
29
+ # example. Such responses should be skipped. See generated files for more info.
30
+ next unless response.event&.event
32
31
 
33
- config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
34
- end
35
- Success(events.compact)
32
+ config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
33
+ end.compact
36
34
  end
37
35
  end
38
36
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc'
4
- require 'dry-monads'
5
4
 
6
5
  # Load all generated by google-protobuf files
7
6
  Dir[File.expand_path('grpc/generated/*.rb', __dir__)].each { |f| require f }
@@ -7,8 +7,8 @@ module EventStoreClient
7
7
  InvalidDataError = Class.new(StandardError)
8
8
  private_constant :InvalidDataError
9
9
 
10
- attr_reader :id, :type, :title, :data, :metadata, :stream_name, :stream_revision,
11
- :prepare_position, :commit_position
10
+ attr_reader :id, :type, :title, :data, :metadata, :custom_metadata, :stream_name,
11
+ :stream_revision, :prepare_position, :commit_position
12
12
 
13
13
  # @args [Hash] opts
14
14
  # @option opts [Boolean] :skip_validation
@@ -34,6 +34,7 @@ module EventStoreClient
34
34
  'type' => @type,
35
35
  'content-type' => payload_content_type
36
36
  )
37
+ @custom_metadata = args[:custom_metadata] || {}
37
38
  @stream_name = args[:stream_name]
38
39
  @stream_revision = args[:stream_revision]
39
40
  @prepare_position = args[:prepare_position]
@@ -57,7 +58,7 @@ module EventStoreClient
57
58
  def ==(other)
58
59
  return false unless other.is_a?(EventStoreClient::DeserializedEvent)
59
60
 
60
- to_h == other.to_h
61
+ meaningful_attrs(to_h) == meaningful_attrs(other.to_h)
61
62
  end
62
63
 
63
64
  # @return [Hash]
@@ -75,8 +76,25 @@ module EventStoreClient
75
76
  type == LINK_TYPE
76
77
  end
77
78
 
79
+ # Detect whether an event is a system event
80
+ # @return [Boolean]
81
+ def system?
82
+ return false unless type
83
+
84
+ type.start_with?('$')
85
+ end
86
+
78
87
  private
79
88
 
89
+ # When comparing two events - we drop commit_position and prepare_position from attributes list
90
+ # to compare. This is because their value are different when retrieving the same event from
91
+ # '$all' stream and from specific stream.
92
+ # @param hash [Hash]
93
+ # @return [Hash]
94
+ def meaningful_attrs(hash)
95
+ hash.delete_if { |key, _val| %i(commit_position prepare_position).include?(key) }
96
+ end
97
+
80
98
  def validate(data)
81
99
  return unless schema
82
100
 
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ class Error < StandardError
5
+ # @return [Hash]
6
+ def as_json(*)
7
+ to_h.transform_keys(&:to_s)
8
+ end
9
+
10
+ # @return [Hash]
11
+ def to_h
12
+ hash =
13
+ instance_variables.each_with_object({}) do |var, result|
14
+ key = var.to_s
15
+ key[0] = '' # remove @ sign
16
+ result[key.to_sym] = instance_variable_get(var)
17
+ end
18
+ hash[:message] = message
19
+ hash[:backtrace] = backtrace
20
+ hash
21
+ end
22
+ end
23
+
24
+ class StreamNotFoundError < Error
25
+ attr_reader :stream_name
26
+
27
+ # @param stream_name [String]
28
+ def initialize(stream_name)
29
+ @stream_name = stream_name
30
+ super("Stream #{stream_name.inspect} does not exist.")
31
+ end
32
+ end
33
+
34
+ class WrongExpectedVersionError < Error
35
+ attr_reader :wrong_expected_version, :caused_by
36
+
37
+ # @param wrong_expected_version [EventStore::Client::Streams::AppendResp::WrongExpectedVersion]
38
+ # @param caused_by [EventStoreClient::DeserializedEvent] an event on which
39
+ # WrongExpectedVersionError error happened. It can be useful when appending array of events -
40
+ # based on it you will know which events were appended and which weren't.
41
+ def initialize(wrong_expected_version, caused_by:)
42
+ @wrong_expected_version = wrong_expected_version
43
+ @caused_by = caused_by
44
+ super(user_friendly_message)
45
+ end
46
+
47
+ private
48
+
49
+ # @return [String]
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
63
+ 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
68
+ end
69
+ # Unhandled case. Could happen if something else would be added to proto and I don't add it
70
+ # here.
71
+ self.class.to_s
72
+ end
73
+ end
74
+
75
+ class StreamDeletionError < Error
76
+ attr_reader :stream_name, :details
77
+
78
+ # @param stream_name [String]
79
+ # @param details [String]
80
+ def initialize(stream_name, details:)
81
+ @stream_name = stream_name
82
+ @details = details
83
+ super(user_friendly_message)
84
+ end
85
+
86
+ # @return [String]
87
+ def user_friendly_message
88
+ <<~TEXT.strip
89
+ Could not delete #{stream_name.inspect} stream. It seems that a stream with that \
90
+ name does not exist, has already been deleted or its state does not match the \
91
+ provided :expected_revision option. Please check #details for more info.
92
+ TEXT
93
+ end
94
+ end
95
+ end
@@ -18,7 +18,7 @@ module EventStoreClient
18
18
  # @param event [EventStoreClient::DeserializedEvent]
19
19
  # @return [EventStoreClient::SerializedEvent]
20
20
  def serialize(event)
21
- Serializer::EventSerializer.call(event, serializer: serializer)
21
+ Serializer::EventSerializer.call(event, serializer: serializer, config: config)
22
22
  end
23
23
 
24
24
  # @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
@@ -32,7 +32,7 @@ module EventStoreClient
32
32
  # Links don't need to be encrypted
33
33
  return Default.new(serializer: serializer, config: config).serialize(event) if event.link?
34
34
 
35
- serialized = Serializer::EventSerializer.call(event, serializer: serializer)
35
+ serialized = Serializer::EventSerializer.call(event, serializer: serializer, config: config)
36
36
  encryption_schema =
37
37
  if event.class.respond_to?(:encryption_schema)
38
38
  event.class.encryption_schema
@@ -70,7 +70,7 @@ module EventStoreClient
70
70
  decrypted_data =
71
71
  EventStoreClient::DataDecryptor.new(
72
72
  data: event.data,
73
- schema: event.metadata['encryption'],
73
+ schema: event.custom_metadata['encryption'],
74
74
  repository: key_repository
75
75
  ).call
76
76
  event.class.new(**event.to_h.merge(data: decrypted_data, skip_validation: true))
@@ -7,8 +7,8 @@ module EventStoreClient
7
7
  class EventDeserializer
8
8
  class << self
9
9
  # @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
10
- # @param serializer [#serialize, #deserialize]
11
10
  # @param config [EventStoreClient::Config]
11
+ # @param serializer [#serialize, #deserialize]
12
12
  # @return [EventStoreClient::DeserializedEvent]
13
13
  def call(raw_event, config:, serializer: Serializer::Json)
14
14
  new(config: config, serializer: serializer).call(raw_event)
@@ -19,6 +19,7 @@ module EventStoreClient
19
19
  private :serializer, :config
20
20
 
21
21
  # @param serializer [#serialize, #deserialize]
22
+ # @param config [EventStoreClient::Config]
22
23
  def initialize(serializer:, config:)
23
24
  @serializer = serializer
24
25
  @config = config
@@ -29,7 +30,7 @@ module EventStoreClient
29
30
  def call(raw_event)
30
31
  data = serializer.deserialize(normalize_serialized(raw_event.data))
31
32
  custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
32
- metadata = custom_metadata.merge(raw_event.metadata.to_h)
33
+ metadata = raw_event.metadata.to_h
33
34
 
34
35
  event_class(metadata['type']).new(
35
36
  skip_validation: true,
@@ -38,6 +39,7 @@ module EventStoreClient
38
39
  type: metadata['type'],
39
40
  data: data,
40
41
  metadata: metadata,
42
+ custom_metadata: custom_metadata,
41
43
  stream_revision: raw_event.stream_revision,
42
44
  commit_position: raw_event.commit_position,
43
45
  prepare_position: raw_event.prepare_position,
@@ -3,35 +3,39 @@
3
3
  module EventStoreClient
4
4
  module Serializer
5
5
  class EventSerializer
6
- ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
6
+ # So far there are only these keys can be persisted in the metadata. You can pass **whatever**
7
+ # you want into a metadata hash, but all keys, except these - will be rejected. Define
8
+ # whitelisted keys and cut unwanted keys explicitly(later in this class).
9
+ ALLOWED_EVENT_METADATA = %w[type content-type].freeze
7
10
 
8
11
  class << self
9
12
  # @param event [EventStoreClient::DeserializedEvent]
13
+ # @param config [EventStoreClient::Config]
10
14
  # @param serializer [#serialize, #deserialize]
11
15
  # @return [EventStoreClient::SerializedEvent]
12
- def call(event, serializer: Serializer::Json)
13
- new(serializer: serializer).call(event)
16
+ def call(event, config:, serializer: Serializer::Json)
17
+ new(serializer: serializer, config: config).call(event)
14
18
  end
15
19
  end
16
20
 
17
- attr_reader :serializer
18
- private :serializer
21
+ attr_reader :serializer, :config
22
+ private :serializer, :config
19
23
 
20
24
  # @param serializer [#serialize, #deserialize]
21
- def initialize(serializer:)
25
+ # @param config [EventStoreClient::Config]
26
+ def initialize(serializer:, config:)
22
27
  @serializer = serializer
28
+ @config = config
23
29
  end
24
30
 
25
31
  # @param event [EventStoreClient::DeserializedEvent]
26
32
  # @return [EventStoreClient::SerializedEvent]
27
33
  def call(event)
28
- event_metadata = metadata(event)
29
- event_custom_metadata = custom_metadata(event, event_metadata)
30
34
  SerializedEvent.new(
31
35
  id: event.id || SecureRandom.uuid,
32
36
  data: data(event),
33
- custom_metadata: event_custom_metadata,
34
- metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA),
37
+ metadata: metadata(event),
38
+ custom_metadata: custom_metadata(event),
35
39
  serializer: serializer
36
40
  )
37
41
  end
@@ -42,17 +46,24 @@ module EventStoreClient
42
46
  # @return [Hash]
43
47
  def metadata(event)
44
48
  metadata = serializer.deserialize(serializer.serialize(event.metadata))
45
- metadata['created_at'] ||= Time.now.utc.to_s
46
- metadata
49
+ # 'created' is returned in the metadata hash of the event when reading from a stream. It,
50
+ # however, can not be overridden - it is always defined automatically by EventStore db when
51
+ # appending new event. Thus, just ignore it - no need even to mention it in the
52
+ # #log_metadata_difference method's message.
53
+ metadata = metadata.slice(*(metadata.keys - ['created']))
54
+ filtered_metadata = metadata.slice(*ALLOWED_EVENT_METADATA)
55
+ log_metadata_difference(metadata) unless filtered_metadata == metadata
56
+ filtered_metadata
47
57
  end
48
58
 
59
+ # Compute custom metadata for the event. **Exactly these** values you can see in ES admin's
60
+ # web UI under "Metadata" section of the event.
49
61
  # @param event [EventStoreClient::DeserializedEvent]
50
- # @param metadata [Hash]
51
62
  # @return [Hash]
52
- def custom_metadata(event, metadata)
53
- metadata.
54
- slice('created_at', 'encryption', 'content-type', 'transaction').
55
- merge('type' => event.type.to_s)
63
+ def custom_metadata(event)
64
+ custom_metadata = serializer.deserialize(serializer.serialize(event.custom_metadata))
65
+ custom_metadata['created_at'] ||= Time.now.utc.to_s
66
+ custom_metadata
56
67
  end
57
68
 
58
69
  # @param event [EventStoreClient::DeserializedEvent]
@@ -64,6 +75,19 @@ module EventStoreClient
64
75
 
65
76
  serializer.deserialize(serializer.serialize(event.data))
66
77
  end
78
+
79
+ # @param metadata [Hash]
80
+ # @return [void]
81
+ def log_metadata_difference(metadata)
82
+ rest_hash = metadata.slice(*(metadata.keys - ALLOWED_EVENT_METADATA))
83
+ debug_message = <<~TEXT
84
+ Next keys were filtered from metadata during serialization: \
85
+ #{(metadata.keys - ALLOWED_EVENT_METADATA).map(&:inspect).join(', ')}. If you would like \
86
+ to provide your custom values in the metadata - please provide them via custom_metadata. \
87
+ Example: EventStoreClient::DeserializedEvent.new(custom_metadata: #{rest_hash.inspect})
88
+ TEXT
89
+ config.logger&.debug(debug_message)
90
+ end
67
91
  end
68
92
  end
69
93
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '2.3.0'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
  require 'set'
5
+ require 'securerandom'
5
6
 
7
+ require 'event_store_client/errors'
6
8
  require 'event_store_client/serializer/json'
7
9
  require 'event_store_client/serializer/event_serializer'
8
10
  require 'event_store_client/serializer/event_deserializer'
@@ -29,7 +31,7 @@ module EventStoreClient
29
31
  end
30
32
 
31
33
  # @param name [Symbol, String]
32
- # @return [EventStore::Config]
34
+ # @return [EventStoreClient::Config]
33
35
  def config(name = :default)
34
36
  @config[name] ||= Config.new(name: name)
35
37
  end