event_store_client 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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