event_store_client 1.1.0 → 1.1.5

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: 301a92ed47fa04e52d45989042f82571e30eeba810c73b7c0f73af55a95304f0
4
- data.tar.gz: 0a58c9b4dc19bb56a2f2082b06552de05f39e0983f5c7379a1e058cbe5c65332
3
+ metadata.gz: 2624a6c8e08a686f5e307eccfb00ac9321eb5e2e32aed9093c0c67e8cb01b9e5
4
+ data.tar.gz: 0b4ff333a547924f54aa182e0bb1f821a7382db93c5d9304038c43a514ee6d3b
5
5
  SHA512:
6
- metadata.gz: f87b1b7516fdae3a51a23970485ad6d492a50b91550208eee10d3d01530cbda57641941c0dc7a9c2696f25c2f442b87bfc02dc70149fdea499dbb11e02bc8d73
7
- data.tar.gz: 903f6f6b64725911be55f7d4730f988f1a4596b4f4c8de05c226a77fb6ac477f90063854c0ae2b0981abba5cc78928feebc3aff52c38f6b5863f12408f8bcc84
6
+ metadata.gz: dfda05e2c763e85977c63a3b0e27034050b37c7ded273750b1aadec42e55fc459475c4a8d50167fe66952bb39c7145242ed017b5548abbece412beaa4e98d37a
7
+ data.tar.gz: ca27251198df7d78a9b60482ee8dfef7f8b6cfdbda08d3a36f0af1723ee487b85239d0d046a6d777e5c6e5161725faaa4b4fac6aa3b650460eb622a428dc0960
@@ -110,7 +110,10 @@ module EventStoreClient
110
110
  # @return Dry::Monads::Result::Success or Dry::Monads::Result::Failure
111
111
  #
112
112
  def join_streams(name, streams)
113
- Commands::Projections::Create.new.call(name, streams)
113
+ res = Commands::Projections::Create.new.call(name, streams)
114
+ return if res.success?
115
+
116
+ Commands::Projections::Update.new.call(name, streams)
114
117
  end
115
118
 
116
119
  # @api private
@@ -12,7 +12,7 @@ module EventStoreClient
12
12
  def self.inherited(klass)
13
13
  super
14
14
  klass.class_eval do
15
- include Dry::Monads[:result]
15
+ include Dry::Monads[:try, :result]
16
16
 
17
17
  def self.use_request(request_klass)
18
18
  CommandRegistrar.register_request(self, request: request_klass)
@@ -38,16 +38,17 @@ module EventStoreClient
38
38
 
39
39
  requests = [request.new(options: opts)] # please notice that it's an array. Should be?
40
40
 
41
+ skip_decryption = options[:skip_decryption] || false
41
42
  service.read(requests, metadata: metadata).each do |res|
42
43
  next if res.subscription_confirmation
43
- yield deserialize_event(res.event.event) if block_given?
44
+ yield deserialize_event(res.event.event, skip_decryption: skip_decryption) if block_given?
44
45
  end
45
46
  Success()
46
47
  end
47
48
 
48
49
  private
49
50
 
50
- def deserialize_event(entry)
51
+ def deserialize_event(entry, skip_decryption: false)
51
52
  id = entry.id.string
52
53
  id = SecureRandom.uuid if id.nil? || id.empty?
53
54
 
@@ -65,7 +66,8 @@ module EventStoreClient
65
66
  type: entry.metadata['type'],
66
67
  data: data,
67
68
  metadata: metadata
68
- )
69
+ ),
70
+ skip_decryption: skip_decryption
69
71
  )
70
72
  end
71
73
  end
@@ -33,8 +33,11 @@ module EventStoreClient
33
33
  }
34
34
  }
35
35
 
36
- service.create(request.new(options: options), metadata: metadata)
37
- Success()
36
+ res = Try do
37
+ service.create(request.new(options: options), metadata: metadata)
38
+ end
39
+
40
+ res.error? ? res.to_result : Success()
38
41
  rescue ::GRPC::Unknown => e
39
42
  Failure(:conflict) if e.message.include?('Conflict')
40
43
  end
@@ -30,7 +30,9 @@ module EventStoreClient
30
30
  name: name,
31
31
  emit_enabled: true
32
32
  }
33
+
33
34
  service.update(request.new(options: options), metadata: metadata)
35
+
34
36
  Success()
35
37
  rescue ::GRPC::AlreadyExists
36
38
  Failure(:conflict)
@@ -43,10 +43,10 @@ module EventStoreClient
43
43
  opts[:stream][:revision] = options[:start]
44
44
  end
45
45
 
46
+ skip_decryption = options[:skip_decryption] || false
46
47
  events = service.read(request.new(options: opts), metadata: metadata).map do |res|
47
48
  raise StreamNotFound if res.stream_not_found
48
-
49
- deserialize_event(res.event.event)
49
+ deserialize_event(res.event.event, skip_decryption: skip_decryption)
50
50
  end
51
51
  Success(events)
52
52
  rescue StreamNotFound
@@ -55,7 +55,7 @@ module EventStoreClient
55
55
 
56
56
  private
57
57
 
58
- def deserialize_event(entry)
58
+ def deserialize_event(entry, skip_decryption: false)
59
59
  data = (entry.data.nil? || entry.data.empty?) ? '{}' : entry.data
60
60
 
61
61
  metadata =
@@ -71,7 +71,7 @@ module EventStoreClient
71
71
  metadata: metadata
72
72
  )
73
73
 
74
- config.mapper.deserialize(event)
74
+ config.mapper.deserialize(event, skip_decryption: skip_decryption)
75
75
  end
76
76
  end
77
77
  end
@@ -6,6 +6,7 @@ require 'event_store_client/adapters/http/commands/persistent_subscriptions/crea
6
6
  require 'event_store_client/adapters/http/commands/persistent_subscriptions/read'
7
7
 
8
8
  require 'event_store_client/adapters/http/commands/projections/create'
9
+ require 'event_store_client/adapters/http/commands/projections/update'
9
10
 
10
11
  require 'event_store_client/adapters/http/commands/streams/append'
11
12
  require 'event_store_client/adapters/http/commands/streams/delete'
@@ -137,7 +137,10 @@ module EventStoreClient
137
137
  # @return Dry::Monads::Result::Success or Dry::Monads::Result::Failure
138
138
  #
139
139
  def join_streams(name, streams, options: {})
140
- Commands::Projections::Create.new(connection).call(name, streams, options: options)
140
+ res = Commands::Projections::Create.new(connection).call(name, streams, options: options)
141
+ return res if res.success?
142
+
143
+ Commands::Projections::Update.new(connection).call(name, streams, options: options)
141
144
  end
142
145
 
143
146
  # @api private
@@ -28,16 +28,19 @@ module EventStoreClient
28
28
 
29
29
  ack_info = body['links'].find { |link| link['relation'] == 'ackAll' }
30
30
  return { events: [] } unless ack_info
31
+
32
+ skip_decryption = options[:skip_decryption] || false
31
33
  body['entries'].map do |entry|
32
- yield deserialize_event(entry)
34
+ yield deserialize_event(entry, skip_decryption: skip_decryption)
33
35
  end
36
+
34
37
  Ack.new(connection).call(ack_info['uri'])
35
38
  Success()
36
39
  end
37
40
 
38
41
  private
39
42
 
40
- def deserialize_event(entry)
43
+ def deserialize_event(entry, skip_decryption: false)
41
44
  event = EventStoreClient::Event.new(
42
45
  id: entry['eventId'],
43
46
  title: entry['title'],
@@ -46,7 +49,7 @@ module EventStoreClient
46
49
  metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
47
50
  )
48
51
 
49
- config.mapper.deserialize(event)
52
+ config.mapper.deserialize(event, skip_decryption: skip_decryption)
50
53
  rescue EventStoreClient::DeserializedEvent::InvalidDataError
51
54
  event
52
55
  end
@@ -16,12 +16,15 @@ module EventStoreClient
16
16
  })
17
17
  STRING
18
18
 
19
- connection.call(
19
+
20
+ res = connection.call(
20
21
  :post,
21
22
  "/projections/continuous?name=#{name}&type=js&enabled=yes&emit=true&trackemittedstreams=true", # rubocop:disable Metrics/LineLength
22
23
  body: data,
23
24
  headers: {}
24
25
  )
26
+
27
+ (200...300).cover?(res.status) ? Success() : Failure(res)
25
28
  end
26
29
  end
27
30
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Projections
7
+ class Update < Command
8
+ def call(name, streams, options: {})
9
+ data =
10
+ <<~STRING
11
+ fromStreams(#{streams})
12
+ .when({
13
+ $any: function(s,e) {
14
+ linkTo("#{name}", e)
15
+ }
16
+ })
17
+ STRING
18
+ res = connection.call(
19
+ :put,
20
+ "/projection/#{name}/query?type=js&enabled=yes&emit=true&trackemittedstreams=true", # rubocop:disable Metrics/LineLength
21
+ body: data,
22
+ headers: {}
23
+ )
24
+
25
+ (200...300).cover?(res.status) ? Success() : Failure(res)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -23,8 +23,9 @@ module EventStoreClient
23
23
 
24
24
  return Failure(:stream_not_found) unless response.success? || response.status == 404
25
25
  return Failure(:connection_failed) if response.body.nil? || response.body.empty?
26
+ skip_decryption = options[:skip_decryption] || false
26
27
  entries = JSON.parse(response.body)['entries'].map do |entry|
27
- deserialize_event(entry)
28
+ deserialize_event(entry, skip_decryption: skip_decryption)
28
29
  end.reverse
29
30
  Success(entries)
30
31
  rescue Faraday::ConnectionFailed
@@ -33,7 +34,7 @@ module EventStoreClient
33
34
 
34
35
  private
35
36
 
36
- def deserialize_event(entry)
37
+ def deserialize_event(entry, skip_decryption: false)
37
38
  event = EventStoreClient::Event.new(
38
39
  id: entry['eventId'],
39
40
  title: entry['title'],
@@ -42,7 +43,7 @@ module EventStoreClient
42
43
  metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
43
44
  )
44
45
 
45
- config.mapper.deserialize(event)
46
+ config.mapper.deserialize(event, skip_decryption: skip_decryption)
46
47
  end
47
48
  end
48
49
  end
@@ -46,8 +46,9 @@ module EventStoreClient
46
46
  res = Response.new(response.to_json, 200)
47
47
 
48
48
  return [] if res.body.nil? || res.body.empty?
49
+ skip_decryption = options[:skip_decryption] || false
49
50
  events = JSON.parse(res.body)['entries'].map do |entry|
50
- deserialize_event(entry)
51
+ deserialize_event(entry, skip_decryption: skip_decryption)
51
52
  end.reverse
52
53
  Dry::Monads::Success(events)
53
54
  end
@@ -127,7 +128,7 @@ module EventStoreClient
127
128
  end
128
129
  end
129
130
 
130
- def deserialize_event(entry)
131
+ def deserialize_event(entry, skip_decryption: false)
131
132
  event = EventStoreClient::Event.new(
132
133
  id: entry['eventId'],
133
134
  title: entry['title'],
@@ -136,7 +137,7 @@ module EventStoreClient
136
137
  metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
137
138
  )
138
139
 
139
- mapper.deserialize(event)
140
+ mapper.deserialize(event, skip_decryption: skip_decryption)
140
141
  end
141
142
  end
142
143
  end
@@ -10,6 +10,14 @@ module EventStoreClient
10
10
  # main app process (useful for debugging)
11
11
  #
12
12
  def call(subscriptions, wait: false)
13
+ Signal.trap('TERM') do
14
+ Thread.new { logger&.info('Broker: TERM Signal has been received') }
15
+ threads.each do |thread|
16
+ thread.thread_variable_set(:terminate, true)
17
+ end
18
+ Thread.new { logger&.info('Broker: Terminate variable for subscription threads set') }
19
+ end
20
+
13
21
  subscriptions.each do |subscription|
14
22
  threads << Thread.new do
15
23
  subscriptions.listen(subscription)
@@ -20,12 +28,13 @@ module EventStoreClient
20
28
 
21
29
  private
22
30
 
23
- attr_reader :connection
31
+ attr_reader :connection, :logger
24
32
  attr_accessor :threads
25
33
 
26
34
  def initialize(connection:)
27
35
  @connection = connection
28
36
  @threads = []
37
+ @logger = EventStoreClient.config.logger
29
38
  end
30
39
  end
31
40
  end
@@ -34,6 +34,13 @@ module EventStoreClient
34
34
  next unless event
35
35
 
36
36
  subscription.subscriber.call(event)
37
+
38
+ if Thread.current.thread_variable_get(:terminate)
39
+ msg =
40
+ "CatchUpSubscriptions: Terminating subscription listener for #{subscription.subscriber}"
41
+ logger&.info(msg)
42
+ break
43
+ end
37
44
  rescue StandardError
38
45
  subscription.position = old_position
39
46
  subscription_store.update_position(subscription)
@@ -45,14 +52,19 @@ module EventStoreClient
45
52
  subscription_store.clean_unused(subscriptions.map(&:name))
46
53
  end
47
54
 
55
+ def reset
56
+ subscription_store.reset(subscriptions)
57
+ end
58
+
48
59
  private
49
60
 
50
- attr_reader :connection, :subscriptions, :subscription_store
61
+ attr_reader :connection, :subscriptions, :subscription_store, :logger
51
62
 
52
63
  def initialize(connection:, subscription_store:)
53
64
  @connection = connection
54
65
  @subscription_store = subscription_store
55
66
  @subscriptions = []
67
+ @logger = EventStoreClient.config.logger
56
68
  end
57
69
 
58
70
  def confirmation?(event_data)
@@ -33,6 +33,12 @@ module EventStoreClient
33
33
  @subscriptions.create_or_load(subscriber, filter: filter)
34
34
  end
35
35
 
36
+ def reset_subscriptions
37
+ return unless @subscriptions.respond_to?(:reset)
38
+
39
+ @subscriptions.reset
40
+ end
41
+
36
42
  def listen(wait: false)
37
43
  broker.call(@subscriptions, wait: wait)
38
44
  end
@@ -30,6 +30,8 @@ module EventStoreClient
30
30
 
31
31
  setting :subscriptions_repo
32
32
 
33
+ setting :logger
34
+
33
35
  def self.configure
34
36
  yield(config) if block_given?
35
37
  end
@@ -9,28 +9,28 @@ module EventStoreClient
9
9
  KeyNotFoundError = Class.new(StandardError)
10
10
 
11
11
  def call
12
- return decrypted_data if encryption_metadata.empty?
12
+ return encrypted_data if encryption_metadata.empty?
13
13
 
14
14
  decrypt_attributes(
15
15
  key: find_key(encryption_metadata['key']),
16
- data: decrypted_data,
16
+ data: encrypted_data,
17
17
  attributes: encryption_metadata['attributes']
18
18
  )
19
19
  end
20
20
 
21
- attr_reader :decrypted_data, :encryption_metadata
22
-
23
21
  private
24
22
 
25
- attr_reader :key_repository
23
+ attr_reader :key_repository, :encryption_metadata, :encrypted_data
26
24
 
27
25
  def initialize(data:, schema:, repository:)
28
- @decrypted_data = deep_dup(data).transform_keys!(&:to_s)
26
+ @encrypted_data = deep_dup(data).transform_keys!(&:to_s)
29
27
  @key_repository = repository
30
28
  @encryption_metadata = schema&.transform_keys(&:to_s) || {}
31
29
  end
32
30
 
33
31
  def decrypt_attributes(key:, data:, attributes: {}) # rubocop:disable Lint/UnusedMethodArgument
32
+ return data unless key
33
+
34
34
  res = key_repository.decrypt(key: key, message: data['es_encrypted'])
35
35
  return data if res.failure?
36
36
 
@@ -42,6 +42,7 @@ module EventStoreClient
42
42
  end
43
43
 
44
44
  def deep_dup(hash)
45
+ return hash unless hash.instance_of?(Hash)
45
46
  dupl = hash.dup
46
47
  dupl.each { |k, v| dupl[k] = v.instance_of?(Hash) ? deep_dup(v) : v }
47
48
  dupl
@@ -53,8 +54,8 @@ module EventStoreClient
53
54
  key_repository.find(identifier).value!
54
55
  rescue StandardError => e
55
56
  config.error_handler&.call(e)
57
+ nil
56
58
  end
57
- raise KeyNotFoundError unless key
58
59
 
59
60
  key
60
61
  end
@@ -12,7 +12,7 @@ module EventStoreClient
12
12
  )
13
13
  end
14
14
 
15
- def deserialize(event)
15
+ def deserialize(event, **)
16
16
  metadata = serializer.deserialize(event.metadata)
17
17
  data = serializer.deserialize(event.data)
18
18
 
@@ -49,14 +49,20 @@ module EventStoreClient
49
49
  # which key should be used as an identifier.
50
50
  # *Returns*: Specific event class with all data decrypted
51
51
 
52
- def deserialize(event)
52
+ def deserialize(event, skip_decryption: false)
53
53
  metadata = serializer.deserialize(event.metadata)
54
54
  encryption_schema = serializer.deserialize(event.metadata)['encryption']
55
- decrypted_data = EventStoreClient::DataDecryptor.new(
56
- data: serializer.deserialize(event.data),
57
- schema: encryption_schema,
58
- repository: key_repository
59
- ).call
55
+
56
+ decrypted_data =
57
+ if skip_decryption
58
+ serializer.deserialize(event.data)
59
+ else
60
+ EventStoreClient::DataDecryptor.new(
61
+ data: serializer.deserialize(event.data),
62
+ schema: encryption_schema,
63
+ repository: key_repository
64
+ ).call
65
+ end
60
66
 
61
67
  event_class =
62
68
  begin
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EventStoreClient
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.5'
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.1.0
4
+ version: 1.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Wilgosz
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-07 00:00:00.000000000 Z
11
+ date: 2021-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 0.2.8
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.14'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.14'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rspec
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -199,6 +213,7 @@ files:
199
213
  - lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb
200
214
  - lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb
201
215
  - lib/event_store_client/adapters/http/commands/projections/create.rb
216
+ - lib/event_store_client/adapters/http/commands/projections/update.rb
202
217
  - lib/event_store_client/adapters/http/commands/streams/append.rb
203
218
  - lib/event_store_client/adapters/http/commands/streams/delete.rb
204
219
  - lib/event_store_client/adapters/http/commands/streams/link_to.rb
@@ -232,7 +247,7 @@ licenses:
232
247
  - MIT
233
248
  metadata:
234
249
  allowed_push_host: https://rubygems.org
235
- post_install_message:
250
+ post_install_message:
236
251
  rdoc_options: []
237
252
  require_paths:
238
253
  - lib
@@ -247,8 +262,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
247
262
  - !ruby/object:Gem::Version
248
263
  version: '0'
249
264
  requirements: []
250
- rubygems_version: 3.0.4
251
- signing_key:
265
+ rubygems_version: 3.1.4
266
+ signing_key:
252
267
  specification_version: 4
253
268
  summary: Ruby integration for https://eventstore.org
254
269
  test_files: []