ruby_event_store 2.2.0 → 2.3.0

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: 28fef13a18c820b5e2e67ffef0aa6ee04b171a427fe9f7a9a4323dbab9db61bb
4
- data.tar.gz: 8aa4bcf65629ae5e1912651d08be52de870f0dc0c274bd046796400d6a50bf34
3
+ metadata.gz: 9170662f3f550d541b79d403aeacfdf734a41a2c48c90752a7a35218913c0ead
4
+ data.tar.gz: 3fa3e5b0e60d839391da9669a74228847aa921903c5d5a7db35363945af5b0f9
5
5
  SHA512:
6
- metadata.gz: 2717723ff31551edb75469a884d88a5e64f97432f218a54c71fe84245bdd91ff770215bd54b90a2ee5dd56054a6bba29ed4ed766c551bb7d7c6f66806f23e207
7
- data.tar.gz: 4f509c7e557271f414aad29db6dd04063aa9d29a45a49985fb12e89192e88d5bee5c8ad73a561faad3d196e04c4ff3ad2f1471ea3f67f0bd63f54060518cac27
6
+ metadata.gz: f753b40d9b216d372dba8692c69b855ac8bb382664f853be628e094557aa391fe1076c98a934f437158622b65a2cf6b5353e6672facadb408ae43441576f49ce
7
+ data.tar.gz: 7b909ee630098c28449eae7ca5dc0b018158ee3a8e240074a25bf71d602bb0529873487dd5d6f6f0ff495cd06b9b1ddfa3b1538fe42f91556fcd1a7cba666232
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent'
3
+ require "concurrent"
4
4
 
5
5
  module RubyEventStore
6
6
  class Client
@@ -94,6 +94,33 @@ module RubyEventStore
94
94
  repository.streams_of(event_id)
95
95
  end
96
96
 
97
+ # Gets position of the event in given stream
98
+ #
99
+ # The position is always nonnegative.
100
+ # Returns nil if the event has no specific position in stream.
101
+ # Raise error if event is not present in stream.
102
+ #
103
+ # @param event_id [String]
104
+ # @param stream_name [String]
105
+ # @return [Integer] nonnegative integer position of event in stream
106
+ # @raise [EventNotInStream]
107
+ def position_in_stream(event_id, stream_name)
108
+ repository.position_in_stream(event_id, Stream.new(stream_name))
109
+ end
110
+
111
+ # Gets position of the event in global stream
112
+ #
113
+ # The position is always nonnegative.
114
+ # Global position may have gaps, meaning, there may be event at
115
+ # position 40, but no event at position 39.
116
+ #
117
+ # @param event_id [String]
118
+ # @raise [EventNotFound]
119
+ # @return [Integer] nonnegno ative integer position of event in global stream
120
+ def global_position(event_id)
121
+ repository.global_position(event_id)
122
+ end
123
+
97
124
  # Subscribes a handler (subscriber) that will be invoked for published events of provided type.
98
125
  #
99
126
  # @overload subscribe(subscriber, to:)
@@ -239,7 +266,7 @@ module RubyEventStore
239
266
  # @return [Event] deserialized event
240
267
  def deserialize(serializer:, event_type:, event_id:, data:, metadata:, timestamp: nil, valid_at: nil)
241
268
  extract_timestamp = lambda do |m|
242
- (m[:timestamp] || Time.parse(m.fetch('timestamp'))).iso8601
269
+ (m[:timestamp] || Time.parse(m.fetch("timestamp"))).iso8601
243
270
  end
244
271
 
245
272
  mapper.record_to_event(
@@ -12,6 +12,7 @@ module RubyEventStore
12
12
  EventDuplicatedInStream = Class.new(Error)
13
13
  ReservedInternalName = Class.new(Error)
14
14
  InvalidHandler = Class.new(Error)
15
+ EventNotFoundInStream = Class.new(Error)
15
16
 
16
17
  class EventNotFound < Error
17
18
  attr_reader :event_id
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module RubyEventStore
6
6
 
@@ -31,7 +31,7 @@ module RubyEventStore
31
31
  # Type of event. Used when matching with subscribed handlers.
32
32
  # @return [String]
33
33
  def event_type
34
- self.class.name
34
+ metadata[:event_type] || self.class.name
35
35
  end
36
36
 
37
37
  # Timestamp from metadata
@@ -50,6 +50,7 @@ module RubyEventStore
50
50
 
51
51
  # Two events are equal if:
52
52
  # * they are of the same class
53
+ # * have identical event type
53
54
  # * have identical event id
54
55
  # * have identical data (verified with eql? method)
55
56
  #
@@ -59,6 +60,7 @@ module RubyEventStore
59
60
  # @return [TrueClass, FalseClass]
60
61
  def ==(other_event)
61
62
  other_event.instance_of?(self.class) &&
63
+ other_event.event_type.eql?(event_type) &&
62
64
  other_event.event_id.eql?(event_id) &&
63
65
  other_event.data.eql?(data)
64
66
  end
@@ -80,6 +82,7 @@ module RubyEventStore
80
82
  # We don't use metadata because == does not use metadata
81
83
  [
82
84
  self.class,
85
+ event_type,
83
86
  event_id,
84
87
  data
85
88
  ].hash ^ BIG_VALUE
@@ -1,26 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
3
+ require "ostruct"
4
4
  module RubyEventStore
5
5
  class InMemoryRepository
6
+ class UnsupportedVersionAnyUsage < StandardError
7
+ def initialize
8
+ super <<~EOS
9
+ Mixing expected version :any and specific position (or :auto) is unsupported.
10
+
11
+ Read more about expected versions here:
12
+ https://railseventstore.org/docs/v2/expected_version/
13
+ EOS
14
+ end
15
+ end
16
+
17
+ class EventInStream
18
+ def initialize(event_id, position)
19
+ @event_id = event_id
20
+ @position = position
21
+ end
22
+
23
+ attr_reader :event_id, :position
24
+ end
6
25
 
7
- def initialize(serializer: NULL)
26
+ def initialize(serializer: NULL, ensure_supported_any_usage: false)
8
27
  @serializer = serializer
9
28
  @streams = Hash.new { |h, k| h[k] = Array.new }
10
29
  @mutex = Mutex.new
11
30
  @storage = Hash.new
31
+ @ensure_supported_any_usage = ensure_supported_any_usage
12
32
  end
13
33
 
14
34
  def append_to_stream(records, stream, expected_version)
15
35
  serialized_records = records.map { |record| record.serialize(serializer) }
16
36
 
17
37
  with_synchronize(expected_version, stream) do |resolved_version|
18
- raise WrongExpectedEventVersion unless last_stream_version(stream).equal?(resolved_version)
38
+ ensure_supported_any_usage(resolved_version, stream)
39
+ raise WrongExpectedEventVersion unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
19
40
 
20
- serialized_records.each do |serialized_record|
41
+ serialized_records.each_with_index do |serialized_record, index|
21
42
  raise EventDuplicatedInStream if has_event?(serialized_record.event_id)
22
43
  storage[serialized_record.event_id] = serialized_record
23
- streams[stream.name] << serialized_record.event_id
44
+ add_to_stream(stream, serialized_record, resolved_version, index)
24
45
  end
25
46
  end
26
47
  self
@@ -30,11 +51,12 @@ module RubyEventStore
30
51
  serialized_records = event_ids.map { |id| read_event(id) }
31
52
 
32
53
  with_synchronize(expected_version, stream) do |resolved_version|
33
- raise WrongExpectedEventVersion unless last_stream_version(stream).equal?(resolved_version)
54
+ ensure_supported_any_usage(resolved_version, stream)
55
+ raise WrongExpectedEventVersion unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
34
56
 
35
- serialized_records.each do |serialized_record|
57
+ serialized_records.each_with_index do |serialized_record, index|
36
58
  raise EventDuplicatedInStream if has_event_in_stream?(serialized_record.event_id, stream.name)
37
- streams[stream.name] << serialized_record.event_id
59
+ add_to_stream(stream, serialized_record, resolved_version, index)
38
60
  end
39
61
  end
40
62
  self
@@ -102,6 +124,16 @@ module RubyEventStore
102
124
  .map { |name,| Stream.new(name) }
103
125
  end
104
126
 
127
+ def position_in_stream(event_id, stream)
128
+ event_in_stream = streams[stream.name].find {|event_in_stream| event_in_stream.event_id.eql?(event_id) }
129
+ raise EventNotFoundInStream if event_in_stream.nil?
130
+ event_in_stream.position
131
+ end
132
+
133
+ def global_position(event_id)
134
+ storage.keys.index(event_id) or raise EventNotFound.new(event_id)
135
+ end
136
+
105
137
  private
106
138
  def read_scope(spec)
107
139
  serialized_records = serialized_records_of_stream(spec.stream)
@@ -124,7 +156,7 @@ module RubyEventStore
124
156
  end
125
157
 
126
158
  def event_ids_of_stream(stream)
127
- streams.fetch(stream.name, Array.new)
159
+ streams.fetch(stream.name, Array.new).map(&:event_id)
128
160
  end
129
161
 
130
162
  def serialized_records_of_stream(stream)
@@ -143,7 +175,7 @@ module RubyEventStore
143
175
  end
144
176
 
145
177
  def last_stream_version(stream)
146
- event_ids_of_stream(stream).size - 1
178
+ streams.fetch(stream.name, Array.new).size - 1
147
179
  end
148
180
 
149
181
  def with_synchronize(expected_version, stream, &block)
@@ -158,19 +190,39 @@ module RubyEventStore
158
190
  # not for the whole read+write algorithm.
159
191
  Thread.pass
160
192
  mutex.synchronize do
161
- resolved_version = last_stream_version(stream) if expected_version.any?
162
193
  block.call(resolved_version)
163
194
  end
164
195
  end
165
196
 
166
197
  def has_event_in_stream?(event_id, stream_name)
167
- streams.fetch(stream_name, Array.new).any? { |id| id.eql?(event_id) }
198
+ streams.fetch(stream_name, Array.new).any? { |event_in_stream| event_in_stream.event_id.eql?(event_id) }
168
199
  end
169
200
 
170
201
  def index_of(source, event_id)
171
202
  source.index {|item| item.event_id.eql?(event_id)}
172
203
  end
173
204
 
205
+ def compute_position(resolved_version, index)
206
+ unless resolved_version.nil?
207
+ resolved_version + index + 1
208
+ end
209
+ end
210
+
211
+ def add_to_stream(stream, serialized_record, resolved_version, index)
212
+ streams[stream.name] << EventInStream.new(serialized_record.event_id, compute_position(resolved_version, index))
213
+ end
214
+
215
+ def ensure_supported_any_usage(resolved_version, stream)
216
+ if @ensure_supported_any_usage
217
+ stream_positions = streams.fetch(stream.name, Array.new).map(&:position)
218
+ if resolved_version.nil?
219
+ raise UnsupportedVersionAnyUsage if !stream_positions.compact.empty?
220
+ else
221
+ raise UnsupportedVersionAnyUsage if stream_positions.include?(nil)
222
+ end
223
+ end
224
+ end
225
+
174
226
  attr_reader :streams, :mutex, :storage, :serializer
175
227
  end
176
228
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby2_keywords"
4
+
3
5
  module RubyEventStore
4
6
  class InstrumentedDispatcher
5
7
  def initialize(dispatcher, instrumentation)
@@ -13,8 +15,16 @@ module RubyEventStore
13
15
  end
14
16
  end
15
17
 
16
- def verify(subscriber)
17
- dispatcher.verify(subscriber)
18
+ ruby2_keywords def method_missing(method_name, *arguments, &block)
19
+ if respond_to?(method_name)
20
+ dispatcher.public_send(method_name, *arguments, &block)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def respond_to_missing?(method_name, _include_private)
27
+ dispatcher.respond_to?(method_name)
18
28
  end
19
29
 
20
30
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby2_keywords"
4
+
3
5
  module RubyEventStore
4
6
  class InstrumentedRepository
5
7
  def initialize(repository, instrumentation)
@@ -25,14 +27,6 @@ module RubyEventStore
25
27
  end
26
28
  end
27
29
 
28
- def has_event?(event_id)
29
- repository.has_event?(event_id)
30
- end
31
-
32
- def last_stream_event(stream)
33
- repository.last_stream_event(stream)
34
- end
35
-
36
30
  def read(specification)
37
31
  instrumentation.instrument("read.repository.rails_event_store", specification: specification) do
38
32
  repository.read(specification)
@@ -57,6 +51,18 @@ module RubyEventStore
57
51
  end
58
52
  end
59
53
 
54
+ ruby2_keywords def method_missing(method_name, *arguments, &block)
55
+ if respond_to?(method_name)
56
+ repository.public_send(method_name, *arguments, &block)
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def respond_to_missing?(method_name, _include_private)
63
+ repository.respond_to?(method_name)
64
+ end
65
+
60
66
  private
61
67
  attr_reader :repository, :instrumentation
62
68
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
3
+ require "yaml"
4
4
 
5
5
  module RubyEventStore
6
6
  module Mappers
@@ -3,7 +3,7 @@
3
3
  module RubyEventStore
4
4
  module Mappers
5
5
  class ForgottenData
6
- FORGOTTEN_DATA = 'FORGOTTEN_DATA'.freeze
6
+ FORGOTTEN_DATA = "FORGOTTEN_DATA".freeze
7
7
 
8
8
  def initialize(string = FORGOTTEN_DATA)
9
9
  @string = string
@@ -3,7 +3,7 @@
3
3
  module RubyEventStore
4
4
  module Mappers
5
5
  class InMemoryEncryptionKeyRepository
6
- DEFAULT_CIPHER = 'aes-256-gcm'.freeze
6
+ DEFAULT_CIPHER = "aes-256-gcm".freeze
7
7
 
8
8
  def initialize
9
9
  @keys = {}
@@ -21,11 +21,21 @@ module RubyEventStore
21
21
  def load(record)
22
22
  Object.const_get(record.event_type).new(
23
23
  event_id: record.event_id,
24
+ data: record.data,
24
25
  metadata: record.metadata.merge(
25
26
  timestamp: record.timestamp,
26
- valid_at: record.valid_at,
27
+ valid_at: record.valid_at,
27
28
  ),
29
+ )
30
+ rescue NameError
31
+ Event.new(
32
+ event_id: record.event_id,
28
33
  data: record.data,
34
+ metadata: record.metadata.merge(
35
+ timestamp: record.timestamp,
36
+ valid_at: record.valid_at,
37
+ event_type: record.event_type,
38
+ ),
29
39
  )
30
40
  end
31
41
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'time'
5
- require 'forwardable'
3
+ require "date"
4
+ require "time"
5
+ require "forwardable"
6
6
 
7
7
  module RubyEventStore
8
8
  class Metadata
@@ -72,14 +72,14 @@ module RubyEventStore
72
72
  end
73
73
 
74
74
  def reduce_from_streams(event_store, start, count)
75
- raise ArgumentError.new('Start must be an array with event ids') unless valid_starting_point?(start)
75
+ raise ArgumentError.new("Start must be an array with event ids") unless valid_starting_point?(start)
76
76
  streams.zip(start_events(start)).reduce(initial_state) do |state, (stream_name, start_event_id)|
77
77
  read_scope(event_store, stream_name, count, start_event_id).reduce(state, &method(:transition))
78
78
  end
79
79
  end
80
80
 
81
81
  def reduce_from_all_streams(event_store, start, count)
82
- raise ArgumentError.new('Start must be valid event id') unless valid_starting_point?(start)
82
+ raise ArgumentError.new("Start must be valid event id") unless valid_starting_point?(start)
83
83
  read_scope(event_store, nil, count, start).reduce(initial_state, &method(:transition))
84
84
  end
85
85
 
@@ -1,5 +1,5 @@
1
1
  RSpec.shared_examples :broker do |broker_klass|
2
- let(:event) { instance_double(::RubyEventStore::Event, event_type: 'EventType') }
2
+ let(:event) { instance_double(::RubyEventStore::Event, event_type: "EventType") }
3
3
  let(:record) { instance_double(::RubyEventStore::Record) }
4
4
  let(:handler) { HandlerClass.new }
5
5
  let(:subscriptions) { ::RubyEventStore::Subscriptions.new }
@@ -7,38 +7,38 @@ RSpec.shared_examples :broker do |broker_klass|
7
7
  let(:broker) { broker_klass.new(subscriptions: subscriptions, dispatcher: dispatcher) }
8
8
 
9
9
  specify "no dispatch when no subscriptions" do
10
- expect(subscriptions).to receive(:all_for).with('EventType').and_return([])
10
+ expect(subscriptions).to receive(:all_for).with("EventType").and_return([])
11
11
  expect(dispatcher).not_to receive(:call)
12
12
  broker.call(event, record)
13
13
  end
14
14
 
15
15
  specify "calls subscription" do
16
- expect(subscriptions).to receive(:all_for).with('EventType').and_return([handler])
16
+ expect(subscriptions).to receive(:all_for).with("EventType").and_return([handler])
17
17
  expect(dispatcher).to receive(:call).with(handler, event, record)
18
18
  broker.call(event, record)
19
19
  end
20
20
 
21
21
  specify "calls subscribed class" do
22
- expect(subscriptions).to receive(:all_for).with('EventType').and_return([HandlerClass])
22
+ expect(subscriptions).to receive(:all_for).with("EventType").and_return([HandlerClass])
23
23
  expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
24
24
  broker.call(event, record)
25
25
  end
26
26
 
27
27
  specify "calls all subscriptions" do
28
- expect(subscriptions).to receive(:all_for).with('EventType').and_return([handler, HandlerClass])
28
+ expect(subscriptions).to receive(:all_for).with("EventType").and_return([handler, HandlerClass])
29
29
  expect(dispatcher).to receive(:call).with(handler, event, record)
30
30
  expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
31
31
  broker.call(event, record)
32
32
  end
33
33
 
34
- specify 'raise error when no subscriber' do
34
+ specify "raise error when no subscriber" do
35
35
  expect { broker.add_subscription(nil, [])}.to raise_error(RubyEventStore::SubscriberNotExist, "subscriber must be first argument or block")
36
36
  expect { broker.add_global_subscription(nil)}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
37
37
  expect { broker.add_thread_subscription(nil, []).call}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
38
38
  expect { broker.add_thread_global_subscription(nil).call}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
39
39
  end
40
40
 
41
- specify 'raise error when wrong subscriber' do
41
+ specify "raise error when wrong subscriber" do
42
42
  allow(dispatcher).to receive(:verify).and_return(false)
43
43
  expect do
44
44
  broker.add_subscription(HandlerClass, [])
@@ -56,8 +56,8 @@ RSpec.shared_examples :broker do |broker_klass|
56
56
 
57
57
  specify "verify and add - local subscriptions" do
58
58
  expect(dispatcher).to receive(:verify).with(handler).and_return(true)
59
- expect(subscriptions).to receive(:add_subscription).with(handler, ['EventType'])
60
- broker.add_subscription(handler, ['EventType'])
59
+ expect(subscriptions).to receive(:add_subscription).with(handler, ["EventType"])
60
+ broker.add_subscription(handler, ["EventType"])
61
61
  end
62
62
 
63
63
  specify "verify and add - global subscriptions" do
@@ -68,8 +68,8 @@ RSpec.shared_examples :broker do |broker_klass|
68
68
 
69
69
  specify "verify and add - thread local subscriptions" do
70
70
  expect(dispatcher).to receive(:verify).with(handler).and_return(true)
71
- expect(subscriptions).to receive(:add_thread_subscription).with(handler, ['EventType'])
72
- broker.add_thread_subscription(handler, ['EventType'])
71
+ expect(subscriptions).to receive(:add_thread_subscription).with(handler, ["EventType"])
72
+ broker.add_thread_subscription(handler, ["EventType"])
73
73
  end
74
74
 
75
75
  specify "verify and add - thread global subscriptions" do