ruby_event_store 2.2.0 → 2.3.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.
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