event_store_client 1.2.3 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f72e92a631da57d27b68c2ba2644a1a089f3cd60ef910151801afd5d74bb3f1
4
- data.tar.gz: c92a76c6955aec9296bd4cbf57d29584672dea95887c2d9e531238dd8adfe347
3
+ metadata.gz: 1f3c838968ea465d5b8c8facea48379e2cd8deab75622df62e124e68e52a123e
4
+ data.tar.gz: 7ab8a39443e9eaa15a4dccb95c32309fbfc1081de9bc854faef9b433a58f54a5
5
5
  SHA512:
6
- metadata.gz: 25e44c8b41e5c22e2b469e096db5789e6c2aad0b18bfb82283a0959ddf73860058a700095aaf614fb931722f6e7184b91c050516cc208b7bf72b11d4cb277023
7
- data.tar.gz: 00e87f69e1da44a28995c187397d42f19cbec0701120b6961a64f3e489c4a6c6598fcfc0701f5a2954a46019ea81b3efd0d7f17d07fb44af7ac514892adec2b9
6
+ metadata.gz: 80ccafc122496126f946ff52437a0fe1fbe856628e508f97c96cf8f802350b94399c557ce276042a4dd4ef0ee5f669952f131bddaeac08f9da73707382bb15bc
7
+ data.tar.gz: cd3c8af6fb3dfe71cc710ed2bfdc5cce89a954afc9bedbe2381438564a9584e777741bb43bd9c69e2fb3d2822cf1449048e1baba80617f695e93cdcaad10ebe3
@@ -11,7 +11,7 @@ module EventStoreClient
11
11
  #
12
12
  def append_to_stream(stream_name, events, options: {})
13
13
  Commands::Streams::Append.new.call(
14
- stream_name, events, options: {}
14
+ stream_name, events, options: options
15
15
  )
16
16
  end
17
17
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc'
4
- require 'event_store_client/adapters/grpc/generated/streams_pb.rb'
5
- require 'event_store_client/adapters/grpc/generated/streams_services_pb.rb'
4
+ require 'event_store_client/adapters/grpc/generated/streams_pb'
5
+ require 'event_store_client/adapters/grpc/generated/streams_services_pb'
6
6
 
7
7
  require 'event_store_client/adapters/grpc/commands/command'
8
8
 
@@ -14,45 +14,94 @@ module EventStoreClient
14
14
  use_request EventStore::Client::Streams::AppendReq
15
15
  use_service EventStore::Client::Streams::Streams::Stub
16
16
 
17
+ ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
18
+
17
19
  # @api private
18
- # TODO: Add support to verify the expected version
19
- def call(stream, events, options: {}) # rubocop:disable Lint/UnusedMethodArgument,Metrics/LineLength
20
+ def call(stream, events, options: {})
21
+ return unless events.any?
22
+
20
23
  serialized_events = events.map { |event| config.mapper.serialize(event) }
21
24
 
22
- serialized_events.each do |event|
23
- event_metadata = JSON.parse(event.metadata)
24
- custom_metadata = {
25
- "type": event.type,
26
- "created_at": Time.now,
27
- 'content-type': event_metadata['content-type']
28
- }
29
- custom_metadata['encryption'] = event_metadata['encryption'] unless event_metadata['encryption'].nil?
30
- custom_metadata['transaction'] = event_metadata['transaction'] unless event_metadata['transaction'].nil?
31
- event_metadata = event_metadata.select { |k| ['type', 'content-type', 'created_at'].include?(k) }
32
-
33
- payload = [
34
- request.new(
35
- options: {
36
- stream_identifier: {
37
- streamName: stream
38
- },
39
- any: {}
40
- }
41
- ),
42
- request.new(
43
- proposed_message: {
44
- id: {
45
- string: SecureRandom.uuid
46
- },
47
- data: event.data.b,
48
- custom_metadata: JSON.generate(custom_metadata),
49
- metadata: event_metadata
50
- }
51
- )
52
- ]
53
- service.append(payload, metadata: metadata)
25
+ expected_version = options[:expected_version]
26
+
27
+ res = nil
28
+ serialized_events.each_with_index do |event, i|
29
+ expected_version += i if expected_version
30
+ res = append(stream, event, expected_version)
31
+ break if res.failure?
54
32
  end
55
- Success(events)
33
+
34
+ res.success? ? Success(events) : Failure(res.failure)
35
+ end
36
+
37
+ private
38
+
39
+ def append(stream, event, expected_version)
40
+ event_metadata = JSON.parse(event.metadata)
41
+
42
+ payload = append_request_payload(
43
+ options(stream, expected_version),
44
+ message(
45
+ data: event.data.b,
46
+ event_metadata: event_metadata.select { |k| ALLOWED_EVENT_METADATA.include?(k) },
47
+ custom_metadata: custom_metadata(event.type, event_metadata)
48
+ )
49
+ )
50
+
51
+ resp = service.append(payload, metadata: metadata)
52
+
53
+ validate_response(resp)
54
+ end
55
+
56
+ def custom_metadata(event_type, event_metadata)
57
+ {
58
+ type: event_type,
59
+ created_at: Time.current,
60
+ encryption: event_metadata['encryption'],
61
+ 'content-type': event_metadata['content-type'],
62
+ transaction: event_metadata['transaction']
63
+ }.compact
64
+ end
65
+
66
+ def append_request_payload(options, message)
67
+ [
68
+ request.new(
69
+ options: options
70
+ ),
71
+ request.new(
72
+ proposed_message: message
73
+ )
74
+ ]
75
+ end
76
+
77
+ def options(stream, expected_version)
78
+ {
79
+ stream_identifier: {
80
+ streamName: stream
81
+ },
82
+ revision: expected_version,
83
+ any: (expected_version ? nil : {})
84
+ }.compact
85
+ end
86
+
87
+ def message(data:, event_metadata:, custom_metadata:)
88
+ {
89
+ id: {
90
+ string: SecureRandom.uuid
91
+ },
92
+ data: data,
93
+ custom_metadata: JSON.generate(custom_metadata),
94
+ metadata: event_metadata
95
+ }
96
+ end
97
+
98
+ def validate_response(resp)
99
+ return Success() if resp.success
100
+
101
+ Failure(
102
+ "current version: #{resp.wrong_expected_version.current_revision} | "\
103
+ "expected: #{resp.wrong_expected_version.expected_revision}"
104
+ )
56
105
  end
57
106
  end
58
107
  end
@@ -44,7 +44,12 @@ module EventStoreClient
44
44
  end
45
45
 
46
46
  skip_decryption = options[:skip_decryption] || false
47
- events = read_stream(opts, skip_decryption)
47
+ events =
48
+ if options[:skip_deserialization]
49
+ read_stream_raw(opts)
50
+ else
51
+ read_stream(opts, skip_decryption)
52
+ end
48
53
  Success(events)
49
54
  rescue StreamNotFound
50
55
  Failure(:not_found)
@@ -64,6 +69,18 @@ module EventStoreClient
64
69
  raise GRPCUnavailableRetryFailed
65
70
  end
66
71
 
72
+ def read_stream_raw(options)
73
+ retries ||= 0
74
+ service.read(request.new(options: options), metadata: metadata).map do |res|
75
+ raise StreamNotFound if res.stream_not_found
76
+ res.event.event
77
+ end
78
+ rescue ::GRPC::Unavailable
79
+ sleep config.grpc_unavailable_retry_sleep
80
+ retry if (retries += 1) <= config.grpc_unavailable_retry_count
81
+ raise GRPCUnavailableRetryFailed
82
+ end
83
+
67
84
  def deserialize_event(entry, skip_decryption: false)
68
85
  data = (entry.data.nil? || entry.data.empty?) ? '{}' : entry.data
69
86
 
@@ -8,35 +8,35 @@ module EventStoreClient
8
8
 
9
9
  # Supported adapters: %i[api in_memory grpc]
10
10
  #
11
- setting :adapter, :grpc
12
- setting :verify_ssl, true
11
+ setting :adapter, default: :grpc
12
+ setting :verify_ssl, default: true
13
13
 
14
- setting :error_handler, ErrorHandler.new
15
- setting :eventstore_url, 'http://localhost:2113' do |value|
14
+ setting :error_handler, default: ErrorHandler.new
15
+ setting :eventstore_url, default: 'http://localhost:2113' do |value|
16
16
  value.is_a?(URI) ? value : URI(value)
17
17
  end
18
18
 
19
- setting :eventstore_user, 'admin'
20
- setting :eventstore_password, 'changeit'
19
+ setting :eventstore_user, default: 'admin'
20
+ setting :eventstore_password, default: 'changeit'
21
21
 
22
- setting :db_port, 2113
22
+ setting :db_port, default: 2113
23
23
 
24
- setting :per_page, 20
25
- setting :pid_path, 'tmp/poll.pid'
24
+ setting :per_page, default: 20
25
+ setting :pid_path, default: 'tmp/poll.pid'
26
26
 
27
- setting :service_name, 'default'
27
+ setting :service_name, default: 'default'
28
28
 
29
- setting :mapper, Mapper::Default.new
29
+ setting :mapper, default: Mapper::Default.new
30
30
 
31
31
  setting :subscriptions_repo
32
32
 
33
33
  setting :logger
34
34
 
35
- setting :socket_error_retry_sleep, 0.5
36
- setting :socket_error_retry_count, 3
35
+ setting :socket_error_retry_sleep, default: 0.5
36
+ setting :socket_error_retry_count, default: 3
37
37
 
38
- setting :grpc_unavailable_retry_sleep, 0.5
39
- setting :grpc_unavailable_retry_count, 3
38
+ setting :grpc_unavailable_retry_sleep, default: 0.5
39
+ setting :grpc_unavailable_retry_count, default: 3
40
40
 
41
41
  def self.configure
42
42
  yield(config) if block_given?
@@ -4,34 +4,55 @@ require 'dry/schema'
4
4
 
5
5
  module EventStoreClient
6
6
  class DeserializedEvent
7
- attr_reader :id
8
- attr_reader :type
9
- attr_reader :title
10
- attr_reader :data
11
- attr_reader :metadata
12
-
13
- def schema
14
- Dry::Schema.Params do
15
- end
16
- end
7
+ InvalidDataError = Class.new(StandardError)
8
+ private_constant :InvalidDataError
9
+
10
+ attr_reader :id, :type, :title, :data, :metadata
11
+
12
+ # @args [Hash] opts
13
+ # @option opts [Boolean] :skip_validation
14
+ # @option opts [Hash] :data
15
+ # @option opts [Hash] :metadata
16
+ # @option opts [String] :type
17
+ # @option opts [String] :title
18
+ # @option opts [UUID] :id
19
+ #
20
+ def initialize(args = {})
21
+ validate(args[:data]) unless args[:skip_validation]
17
22
 
18
- def initialize(**args)
19
- validation = schema.call(args[:data] || {})
20
23
  @data = args.fetch(:data) { {} }
21
- @metadata = args.fetch(:metadata) { {} }.merge(
22
- 'type' => self.class.name,
23
- 'content-type' => content_type
24
- )
25
- @metadata.merge!('validation-errors' => validation.errors.to_h) if validation.errors.any?
24
+ @metadata =
25
+ args.fetch(:metadata) { {} }
26
+ .merge(
27
+ 'type' => self.class.name,
28
+ 'content-type' => payload_content_type
29
+ )
30
+
26
31
  @type = args[:type] || self.class.name
27
32
  @title = args[:title]
28
33
  @id = args[:id]
29
34
  end
30
35
 
31
- def content_type
36
+ # event schema
37
+ def schema; end
38
+
39
+ # content type of the event data
40
+ def payload_content_type
32
41
  return 'application/json' if EventStoreClient.config.adapter == :grpc
33
42
 
34
43
  'application/vnd.eventstore.events+json'
35
44
  end
45
+
46
+ private
47
+
48
+ def validate(data)
49
+ return unless schema
50
+
51
+ validation = schema.call(data || {})
52
+
53
+ return unless validation.errors.any?
54
+
55
+ raise(InvalidDataError.new(message: "#{schema.class.name} #{validation.errors.to_h}"))
56
+ end
36
57
  end
37
58
  end
@@ -23,6 +23,7 @@ module EventStoreClient
23
23
  EventStoreClient::DeserializedEvent
24
24
  end
25
25
  event_class.new(
26
+ skip_validation: true,
26
27
  id: event.id,
27
28
  type: event.type,
28
29
  title: event.title,
@@ -51,7 +51,7 @@ module EventStoreClient
51
51
 
52
52
  def deserialize(event, skip_decryption: false)
53
53
  metadata = serializer.deserialize(event.metadata)
54
- encryption_schema = serializer.deserialize(event.metadata)['encryption']
54
+ encryption_schema = metadata['encryption']
55
55
 
56
56
  decrypted_data =
57
57
  if skip_decryption
@@ -72,6 +72,7 @@ module EventStoreClient
72
72
  end
73
73
 
74
74
  event_class.new(
75
+ skip_validation: true,
75
76
  id: event.id,
76
77
  type: event.type,
77
78
  title: event.title,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '1.2.3'
4
+ VERSION = '1.4.2'
5
5
  end
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: 1.2.3
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Wilgosz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-04 00:00:00.000000000 Z
11
+ date: 2021-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable