ruby_event_store 2.2.0 → 2.4.1

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_event_store/batch_enumerator.rb +3 -3
  3. data/lib/ruby_event_store/broker.rb +5 -4
  4. data/lib/ruby_event_store/client.rb +75 -46
  5. data/lib/ruby_event_store/composed_dispatcher.rb +1 -3
  6. data/lib/ruby_event_store/correlated_commands.rb +4 -15
  7. data/lib/ruby_event_store/errors.rb +11 -10
  8. data/lib/ruby_event_store/event.rb +9 -14
  9. data/lib/ruby_event_store/expected_version.rb +3 -7
  10. data/lib/ruby_event_store/in_memory_repository.rb +100 -37
  11. data/lib/ruby_event_store/instrumented_dispatcher.rb +11 -2
  12. data/lib/ruby_event_store/instrumented_repository.rb +13 -8
  13. data/lib/ruby_event_store/link_by_metadata.rb +4 -21
  14. data/lib/ruby_event_store/mappers/default.rb +6 -4
  15. data/lib/ruby_event_store/mappers/encryption_key.rb +7 -16
  16. data/lib/ruby_event_store/mappers/encryption_mapper.rb +6 -6
  17. data/lib/ruby_event_store/mappers/forgotten_data.rb +1 -1
  18. data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +1 -1
  19. data/lib/ruby_event_store/mappers/null_mapper.rb +0 -1
  20. data/lib/ruby_event_store/mappers/pipeline.rb +3 -10
  21. data/lib/ruby_event_store/mappers/pipeline_mapper.rb +1 -0
  22. data/lib/ruby_event_store/mappers/transformation/domain_event.rb +23 -13
  23. data/lib/ruby_event_store/mappers/transformation/encryption.rb +21 -25
  24. data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +6 -5
  25. data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +6 -5
  26. data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +6 -5
  27. data/lib/ruby_event_store/mappers/transformation/upcast.rb +2 -6
  28. data/lib/ruby_event_store/metadata.rb +46 -17
  29. data/lib/ruby_event_store/projection.rb +12 -20
  30. data/lib/ruby_event_store/record.rb +14 -26
  31. data/lib/ruby_event_store/serialized_record.rb +14 -26
  32. data/lib/ruby_event_store/serializers/yaml.rb +17 -0
  33. data/lib/ruby_event_store/spec/broker_lint.rb +38 -28
  34. data/lib/ruby_event_store/spec/event_lint.rb +10 -10
  35. data/lib/ruby_event_store/spec/event_repository_lint.rb +745 -741
  36. data/lib/ruby_event_store/spec/mapper_lint.rb +2 -2
  37. data/lib/ruby_event_store/spec/subscriptions_lint.rb +58 -57
  38. data/lib/ruby_event_store/specification.rb +20 -16
  39. data/lib/ruby_event_store/specification_reader.rb +2 -3
  40. data/lib/ruby_event_store/specification_result.rb +52 -46
  41. data/lib/ruby_event_store/stream.rb +3 -7
  42. data/lib/ruby_event_store/subscriptions.rb +14 -15
  43. data/lib/ruby_event_store/transform_keys.rb +1 -1
  44. data/lib/ruby_event_store/version.rb +1 -1
  45. data/lib/ruby_event_store.rb +44 -43
  46. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28fef13a18c820b5e2e67ffef0aa6ee04b171a427fe9f7a9a4323dbab9db61bb
4
- data.tar.gz: 8aa4bcf65629ae5e1912651d08be52de870f0dc0c274bd046796400d6a50bf34
3
+ metadata.gz: 3960a57258392156fd3d8ee9811d2c23a2b0a348d7938a830ae51f5edec01bc7
4
+ data.tar.gz: e4c4ef6136891a68ed94609f1fbd58ac0ce6ec854a1a843f7b167573b2cc6070
5
5
  SHA512:
6
- metadata.gz: 2717723ff31551edb75469a884d88a5e64f97432f218a54c71fe84245bdd91ff770215bd54b90a2ee5dd56054a6bba29ed4ed766c551bb7d7c6f66806f23e207
7
- data.tar.gz: 4f509c7e557271f414aad29db6dd04063aa9d29a45a49985fb12e89192e88d5bee5c8ad73a561faad3d196e04c4ff3ad2f1471ea3f67f0bd63f54060518cac27
6
+ metadata.gz: 239185c1c4a70275d394c1c7e702b1afaa8c8cdd22b2ff0ba25a1b042ca036a1e3eddc26aeb9b8ce638c9f8e81b372cd4eccdd230238b74ffe28060e754707ad
7
+ data.tar.gz: 1dd1af400141ed99a0ccf31234f48311d935f23d7ac8367eb48f44e0f845193af973a6c4b00dc554c45ae07c25067d83828fba2a151df1d20082394541a24fd5
@@ -3,9 +3,9 @@
3
3
  module RubyEventStore
4
4
  class BatchEnumerator
5
5
  def initialize(batch_size, total_limit, reader)
6
- @batch_size = batch_size
6
+ @batch_size = batch_size
7
7
  @total_limit = total_limit
8
- @reader = reader
8
+ @reader = reader
9
9
  end
10
10
 
11
11
  def each
@@ -13,7 +13,7 @@ module RubyEventStore
13
13
 
14
14
  0.step(total_limit - 1, batch_size) do |batch_offset|
15
15
  batch_limit = [batch_size, total_limit - batch_offset].min
16
- result = reader.call(batch_offset, batch_limit)
16
+ result = reader.call(batch_offset, batch_limit)
17
17
 
18
18
  break if result.empty?
19
19
  yield result
@@ -9,9 +9,7 @@ module RubyEventStore
9
9
 
10
10
  def call(event, record)
11
11
  subscribers = subscriptions.all_for(event.event_type)
12
- subscribers.each do |subscriber|
13
- dispatcher.call(subscriber, event, record)
14
- end
12
+ subscribers.each { |subscriber| dispatcher.call(subscriber, event, record) }
15
13
  end
16
14
 
17
15
  def add_subscription(subscriber, event_types)
@@ -35,11 +33,14 @@ module RubyEventStore
35
33
  end
36
34
 
37
35
  private
36
+
38
37
  attr_reader :dispatcher, :subscriptions
39
38
 
40
39
  def verify_subscription(subscriber)
41
40
  raise SubscriberNotExist, "subscriber must be first argument or block" unless subscriber
42
- raise InvalidHandler.new("Handler #{subscriber} is invalid for dispatcher #{dispatcher}") unless dispatcher.verify(subscriber)
41
+ unless dispatcher.verify(subscriber)
42
+ raise InvalidHandler.new("Handler #{subscriber} is invalid for dispatcher #{dispatcher}")
43
+ end
43
44
  end
44
45
  end
45
46
  end
@@ -1,27 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent'
3
+ require "concurrent"
4
4
 
5
5
  module RubyEventStore
6
6
  class Client
7
- def initialize(repository:,
8
- mapper: Mappers::Default.new,
9
- subscriptions: Subscriptions.new,
10
- dispatcher: Dispatcher.new,
11
- clock: default_clock,
12
- correlation_id_generator: default_correlation_id_generator)
13
-
14
-
15
- @repository = repository
16
- @mapper = mapper
17
- @subscriptions = subscriptions
18
- @broker = Broker.new(subscriptions: subscriptions, dispatcher: dispatcher)
19
- @clock = clock
20
- @metadata = Concurrent::ThreadLocalVar.new
7
+ def initialize(
8
+ repository:,
9
+ mapper: Mappers::Default.new,
10
+ subscriptions: Subscriptions.new,
11
+ dispatcher: Dispatcher.new,
12
+ clock: default_clock,
13
+ correlation_id_generator: default_correlation_id_generator
14
+ )
15
+ @repository = repository
16
+ @mapper = mapper
17
+ @subscriptions = subscriptions
18
+ @broker = Broker.new(subscriptions: subscriptions, dispatcher: dispatcher)
19
+ @clock = clock
20
+ @metadata = Concurrent::ThreadLocalVar.new
21
21
  @correlation_id_generator = correlation_id_generator
22
22
  end
23
23
 
24
-
25
24
  # Persists events and notifies subscribed handlers about them
26
25
  #
27
26
  # @param events [Array<Event>, Event] event(s)
@@ -30,13 +29,10 @@ module RubyEventStore
30
29
  # @return [self]
31
30
  def publish(events, stream_name: GLOBAL_STREAM, expected_version: :any)
32
31
  enriched_events = enrich_events_metadata(events)
33
- records = transform(enriched_events)
32
+ records = transform(enriched_events)
34
33
  append_records_to_stream(records, stream_name: stream_name, expected_version: expected_version)
35
34
  enriched_events.zip(records) do |event, record|
36
- with_metadata(
37
- correlation_id: event.metadata.fetch(:correlation_id),
38
- causation_id: event.event_id,
39
- ) do
35
+ with_metadata(correlation_id: event.metadata.fetch(:correlation_id), causation_id: event.event_id) do
40
36
  broker.(event, record)
41
37
  end
42
38
  end
@@ -94,6 +90,43 @@ module RubyEventStore
94
90
  repository.streams_of(event_id)
95
91
  end
96
92
 
93
+ # Gets position of the event in given stream
94
+ #
95
+ # The position is always nonnegative.
96
+ # Returns nil if the event has no specific position in stream.
97
+ # Raise error if event is not present in stream.
98
+ #
99
+ # @param event_id [String]
100
+ # @param stream_name [String]
101
+ # @return [Integer] nonnegative integer position of event in stream
102
+ # @raise [EventNotInStream]
103
+ def position_in_stream(event_id, stream_name)
104
+ repository.position_in_stream(event_id, Stream.new(stream_name))
105
+ end
106
+
107
+ # Gets position of the event in global stream
108
+ #
109
+ # The position is always nonnegative.
110
+ # Global position may have gaps, meaning, there may be event at
111
+ # position 40, but no event at position 39.
112
+ #
113
+ # @param event_id [String]
114
+ # @raise [EventNotFound]
115
+ # @return [Integer] nonnegno ative integer position of event in global stream
116
+ def global_position(event_id)
117
+ repository.global_position(event_id)
118
+ end
119
+
120
+ # Checks whether event is linked in given stream
121
+ #
122
+ # @param event_id [String]
123
+ # @param stream_name [String]
124
+ # @return [Boolean] true if event is linked to given stream, false otherwise
125
+ def event_in_stream?(event_id, stream_name)
126
+ stream = Stream.new(stream_name)
127
+ stream.global? ? repository.has_event?(event_id) : repository.event_in_stream?(event_id, stream)
128
+ end
129
+
97
130
  # Subscribes a handler (subscriber) that will be invoked for published events of provided type.
98
131
  #
99
132
  # @overload subscribe(subscriber, to:)
@@ -143,7 +176,7 @@ module RubyEventStore
143
176
  @block = block
144
177
  @broker = broker
145
178
  @global_subscribers = []
146
- @subscribers = Hash.new {[]}
179
+ @subscribers = Hash.new { [] }
147
180
  end
148
181
 
149
182
  # Subscribes temporary handlers that
@@ -175,7 +208,7 @@ module RubyEventStore
175
208
  # @param to [Array<Class>] types of events to subscribe
176
209
  # @param handler [Proc] handler passed as proc
177
210
  # @return [self]
178
- def subscribe(handler=nil, to:, &handler2)
211
+ def subscribe(handler = nil, to:, &handler2)
179
212
  raise ArgumentError if handler && handler2
180
213
  @subscribers[handler || handler2] += Array(to)
181
214
  self
@@ -187,7 +220,7 @@ module RubyEventStore
187
220
  #
188
221
  # @return [Object] value returned by the invoked block of code
189
222
  def call
190
- unsubs = add_thread_global_subscribers
223
+ unsubs = add_thread_global_subscribers
191
224
  unsubs += add_thread_subscribers
192
225
  @block.call
193
226
  ensure
@@ -197,15 +230,11 @@ module RubyEventStore
197
230
  private
198
231
 
199
232
  def add_thread_subscribers
200
- @subscribers.map do |subscriber, types|
201
- @broker.add_thread_subscription(subscriber, types)
202
- end
233
+ @subscribers.map { |subscriber, types| @broker.add_thread_subscription(subscriber, types) }
203
234
  end
204
235
 
205
236
  def add_thread_global_subscribers
206
- @global_subscribers.map do |subscriber|
207
- @broker.add_thread_global_subscription(subscriber)
208
- end
237
+ @global_subscribers.map { |subscriber| @broker.add_thread_global_subscription(subscriber) }
209
238
  end
210
239
  end
211
240
 
@@ -238,19 +267,19 @@ module RubyEventStore
238
267
  #
239
268
  # @return [Event] deserialized event
240
269
  def deserialize(serializer:, event_type:, event_id:, data:, metadata:, timestamp: nil, valid_at: nil)
241
- extract_timestamp = lambda do |m|
242
- (m[:timestamp] || Time.parse(m.fetch('timestamp'))).iso8601
243
- end
270
+ extract_timestamp = lambda { |m| (m[:timestamp] || Time.parse(m.fetch("timestamp"))).iso8601 }
244
271
 
245
272
  mapper.record_to_event(
246
- SerializedRecord.new(
247
- event_type: event_type,
248
- event_id: event_id,
249
- data: data,
250
- metadata: metadata,
251
- timestamp: timestamp || timestamp_ = extract_timestamp[serializer.load(metadata)],
252
- valid_at: valid_at || timestamp_,
253
- ).deserialize(serializer)
273
+ SerializedRecord
274
+ .new(
275
+ event_type: event_type,
276
+ event_id: event_id,
277
+ data: data,
278
+ metadata: metadata,
279
+ timestamp: timestamp || timestamp_ = extract_timestamp[serializer.load(metadata)],
280
+ valid_at: valid_at || timestamp_
281
+ )
282
+ .deserialize(serializer)
254
283
  )
255
284
  end
256
285
 
@@ -311,14 +340,14 @@ module RubyEventStore
311
340
 
312
341
  def enrich_events_metadata(events)
313
342
  events = Array(events)
314
- events.each{|event| enrich_event_metadata(event) }
343
+ events.each { |event| enrich_event_metadata(event) }
315
344
  events
316
345
  end
317
346
 
318
347
  def enrich_event_metadata(event)
319
348
  metadata.each { |key, value| event.metadata[key] ||= value }
320
- event.metadata[:timestamp] ||= clock.call
321
- event.metadata[:valid_at] ||= event.metadata.fetch(:timestamp)
349
+ event.metadata[:timestamp] ||= clock.call
350
+ event.metadata[:valid_at] ||= event.metadata.fetch(:timestamp)
322
351
  event.metadata[:correlation_id] ||= correlation_id_generator.call
323
352
  end
324
353
 
@@ -337,11 +366,11 @@ module RubyEventStore
337
366
  end
338
367
 
339
368
  def default_clock
340
- ->{ Time.now.utc.round(TIMESTAMP_PRECISION) }
369
+ -> { Time.now.utc.round(TIMESTAMP_PRECISION) }
341
370
  end
342
371
 
343
372
  def default_correlation_id_generator
344
- ->{ SecureRandom.uuid }
373
+ -> { SecureRandom.uuid }
345
374
  end
346
375
 
347
376
  attr_reader :repository, :mapper, :subscriptions, :broker, :clock, :correlation_id_generator
@@ -16,9 +16,7 @@ module RubyEventStore
16
16
  end
17
17
 
18
18
  def verify(subscriber)
19
- @dispatchers.any? do |dispatcher|
20
- dispatcher.verify(subscriber)
21
- end
19
+ @dispatchers.any? { |dispatcher| dispatcher.verify(subscriber) }
22
20
  end
23
21
  end
24
22
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  class CorrelatedCommands
5
-
6
5
  def initialize(event_store, command_bus)
7
6
  @event_store = event_store
8
7
  @command_bus = command_bus
@@ -13,23 +12,13 @@ module RubyEventStore
13
12
 
14
13
  def call(command)
15
14
  correlation_id = event_store.metadata[:correlation_id]
16
- causation_id = event_store.metadata[:causation_id]
15
+ causation_id = event_store.metadata[:causation_id]
17
16
 
18
17
  if correlation_id && causation_id
19
- command.correlate_with(MiniEvent.new(
20
- correlation_id,
21
- causation_id,
22
- )) if command.respond_to?(:correlate_with)
23
- event_store.with_metadata(
24
- causation_id: command.message_id,
25
- ) do
26
- command_bus.call(command)
27
- end
18
+ command.correlate_with(MiniEvent.new(correlation_id, causation_id)) if command.respond_to?(:correlate_with)
19
+ event_store.with_metadata(causation_id: command.message_id) { command_bus.call(command) }
28
20
  else
29
- event_store.with_metadata(
30
- correlation_id: command.message_id,
31
- causation_id: command.message_id,
32
- ) do
21
+ event_store.with_metadata(correlation_id: command.message_id, causation_id: command.message_id) do
33
22
  command_bus.call(command)
34
23
  end
35
24
  end
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyEventStore
4
- Error = Class.new(StandardError)
4
+ Error = Class.new(StandardError)
5
5
  WrongExpectedEventVersion = Class.new(Error)
6
- InvalidExpectedVersion = Class.new(Error)
7
- IncorrectStreamData = Class.new(Error)
8
- SubscriberNotExist = Class.new(Error)
9
- InvalidPageStart = Class.new(Error)
10
- InvalidPageStop = Class.new(Error)
11
- InvalidPageSize = Class.new(Error)
12
- EventDuplicatedInStream = Class.new(Error)
13
- ReservedInternalName = Class.new(Error)
14
- InvalidHandler = Class.new(Error)
6
+ InvalidExpectedVersion = Class.new(Error)
7
+ IncorrectStreamData = Class.new(Error)
8
+ SubscriberNotExist = Class.new(Error)
9
+ InvalidPageStart = Class.new(Error)
10
+ InvalidPageStop = Class.new(Error)
11
+ InvalidPageSize = Class.new(Error)
12
+ EventDuplicatedInStream = Class.new(Error)
13
+ ReservedInternalName = Class.new(Error)
14
+ InvalidHandler = Class.new(Error)
15
+ EventNotFoundInStream = Class.new(Error)
15
16
 
16
17
  class EventNotFound < Error
17
18
  attr_reader :event_id
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module RubyEventStore
6
-
7
6
  # Data structure representing an event
8
7
  class Event
9
8
  # Instantiates a new event
@@ -17,7 +16,7 @@ module RubyEventStore
17
16
  def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
18
17
  @event_id = event_id.to_s
19
18
  @metadata = Metadata.new(metadata.to_h)
20
- @data = data
19
+ @data = data
21
20
  end
22
21
 
23
22
  attr_reader :event_id, :metadata, :data
@@ -31,7 +30,7 @@ module RubyEventStore
31
30
  # Type of event. Used when matching with subscribed handlers.
32
31
  # @return [String]
33
32
  def event_type
34
- self.class.name
33
+ metadata[:event_type] || self.class.name
35
34
  end
36
35
 
37
36
  # Timestamp from metadata
@@ -50,6 +49,7 @@ module RubyEventStore
50
49
 
51
50
  # Two events are equal if:
52
51
  # * they are of the same class
52
+ # * have identical event type
53
53
  # * have identical event id
54
54
  # * have identical data (verified with eql? method)
55
55
  #
@@ -58,9 +58,8 @@ module RubyEventStore
58
58
  # Event equality ignores metadata!
59
59
  # @return [TrueClass, FalseClass]
60
60
  def ==(other_event)
61
- other_event.instance_of?(self.class) &&
62
- other_event.event_id.eql?(event_id) &&
63
- other_event.data.eql?(data)
61
+ other_event.instance_of?(self.class) && other_event.event_type.eql?(event_type) &&
62
+ other_event.event_id.eql?(event_id) && other_event.data.eql?(data)
64
63
  end
65
64
 
66
65
  # @private
@@ -78,11 +77,7 @@ module RubyEventStore
78
77
  # * data
79
78
  def hash
80
79
  # We don't use metadata because == does not use metadata
81
- [
82
- self.class,
83
- event_id,
84
- data
85
- ].hash ^ BIG_VALUE
80
+ [self.class, event_type, event_id, data].hash ^ BIG_VALUE
86
81
  end
87
82
 
88
83
  # Reads correlation_id from metadata.
@@ -116,7 +111,7 @@ module RubyEventStore
116
111
  # @param val [String]
117
112
  # @return [String]
118
113
  def causation_id=(val)
119
- metadata[:causation_id]= val
114
+ metadata[:causation_id] = val
120
115
  end
121
116
 
122
117
  # Sets correlation_id and causation_id in metadata based
@@ -127,7 +122,7 @@ module RubyEventStore
127
122
  # @return [String] set causation_id
128
123
  def correlate_with(other_message)
129
124
  self.correlation_id = other_message.correlation_id || other_message.message_id
130
- self.causation_id = other_message.message_id
125
+ self.causation_id = other_message.message_id
131
126
  self
132
127
  end
133
128
 
@@ -21,7 +21,7 @@ module RubyEventStore
21
21
 
22
22
  def initialize(version)
23
23
  @version = version
24
- invalid_version! unless [Integer, :any, :none, :auto].any? {|i| i === version}
24
+ invalid_version! unless [Integer, :any, :none, :auto].any? { |i| i === version }
25
25
  end
26
26
 
27
27
  def any?
@@ -53,15 +53,11 @@ module RubyEventStore
53
53
  private_constant :BIG_VALUE
54
54
 
55
55
  def hash
56
- [
57
- self.class,
58
- version
59
- ].hash ^ BIG_VALUE
56
+ [self.class, version].hash ^ BIG_VALUE
60
57
  end
61
58
 
62
59
  def ==(other_expected_version)
63
- other_expected_version.instance_of?(self.class) &&
64
- other_expected_version.version.equal?(version)
60
+ other_expected_version.instance_of?(self.class) && other_expected_version.version.equal?(version)
65
61
  end
66
62
 
67
63
  alias_method :eql?, :==
@@ -1,26 +1,49 @@
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
- @mutex = Mutex.new
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
+ unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
40
+ raise WrongExpectedEventVersion
41
+ end
19
42
 
20
- serialized_records.each do |serialized_record|
43
+ serialized_records.each_with_index do |serialized_record, index|
21
44
  raise EventDuplicatedInStream if has_event?(serialized_record.event_id)
22
45
  storage[serialized_record.event_id] = serialized_record
23
- streams[stream.name] << serialized_record.event_id
46
+ add_to_stream(stream, serialized_record, resolved_version, index)
24
47
  end
25
48
  end
26
49
  self
@@ -30,11 +53,14 @@ module RubyEventStore
30
53
  serialized_records = event_ids.map { |id| read_event(id) }
31
54
 
32
55
  with_synchronize(expected_version, stream) do |resolved_version|
33
- raise WrongExpectedEventVersion unless last_stream_version(stream).equal?(resolved_version)
56
+ ensure_supported_any_usage(resolved_version, stream)
57
+ unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
58
+ raise WrongExpectedEventVersion
59
+ end
34
60
 
35
- serialized_records.each do |serialized_record|
61
+ serialized_records.each_with_index do |serialized_record, index|
36
62
  raise EventDuplicatedInStream if has_event_in_stream?(serialized_record.event_id, stream.name)
37
- streams[stream.name] << serialized_record.event_id
63
+ add_to_stream(stream, serialized_record, resolved_version, index)
38
64
  end
39
65
  end
40
66
  self
@@ -69,9 +95,7 @@ module RubyEventStore
69
95
  serialized_records.last&.deserialize(serializer)
70
96
  else
71
97
  Enumerator.new do |y|
72
- serialized_records.each do |serialized_record|
73
- y << serialized_record.deserialize(serializer)
74
- end
98
+ serialized_records.each { |serialized_record| y << serialized_record.deserialize(serializer) }
75
99
  end
76
100
  end
77
101
  end
@@ -84,38 +108,61 @@ module RubyEventStore
84
108
  records.each do |record|
85
109
  read_event(record.event_id)
86
110
  serialized_record =
87
- Record.new(
88
- event_id: record.event_id,
89
- event_type: record.event_type,
90
- data: record.data,
91
- metadata: record.metadata,
92
- timestamp: Time.iso8601(storage.fetch(record.event_id).timestamp),
93
- valid_at: record.valid_at,
94
- ).serialize(serializer)
111
+ Record
112
+ .new(
113
+ event_id: record.event_id,
114
+ event_type: record.event_type,
115
+ data: record.data,
116
+ metadata: record.metadata,
117
+ timestamp: Time.iso8601(storage.fetch(record.event_id).timestamp),
118
+ valid_at: record.valid_at
119
+ )
120
+ .serialize(serializer)
95
121
  storage[record.event_id] = serialized_record
96
122
  end
97
123
  end
98
124
 
99
125
  def streams_of(event_id)
100
- streams
101
- .select { |name,| has_event_in_stream?(event_id, name) }
102
- .map { |name,| Stream.new(name) }
126
+ streams.select { |name,| has_event_in_stream?(event_id, name) }.map { |name,| Stream.new(name) }
127
+ end
128
+
129
+ def position_in_stream(event_id, stream)
130
+ event_in_stream = streams[stream.name].find { |event_in_stream| event_in_stream.event_id.eql?(event_id) }
131
+ raise EventNotFoundInStream if event_in_stream.nil?
132
+ event_in_stream.position
133
+ end
134
+
135
+ def global_position(event_id)
136
+ storage.keys.index(event_id) or raise EventNotFound.new(event_id)
137
+ end
138
+
139
+ def event_in_stream?(event_id, stream)
140
+ !streams[stream.name].find { |event_in_stream| event_in_stream.event_id.eql?(event_id) }.nil?
103
141
  end
104
142
 
105
143
  private
144
+
106
145
  def read_scope(spec)
107
146
  serialized_records = serialized_records_of_stream(spec.stream)
108
147
  serialized_records = ordered(serialized_records, spec)
109
- serialized_records = serialized_records.select{|e| spec.with_ids.any?{|x| x.eql?(e.event_id)}} if spec.with_ids?
110
- serialized_records = serialized_records.select{|e| spec.with_types.any?{|x| x.eql?(e.event_type)}} if spec.with_types?
148
+ serialized_records = serialized_records.select { |e| spec.with_ids.any? { |x| x.eql?(e.event_id) } } if spec
149
+ .with_ids?
150
+ serialized_records = serialized_records.select { |e| spec.with_types.any? { |x| x.eql?(e.event_type) } } if spec
151
+ .with_types?
111
152
  serialized_records = serialized_records.reverse if spec.backward?
112
153
  serialized_records = serialized_records.drop(index_of(serialized_records, spec.start) + 1) if spec.start
113
154
  serialized_records = serialized_records.take(index_of(serialized_records, spec.stop)) if spec.stop
114
155
  serialized_records = serialized_records.take(spec.limit) if spec.limit?
115
- serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) < spec.older_than } if spec.older_than
116
- serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) <= spec.older_than_or_equal } if spec.older_than_or_equal
117
- serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) > spec.newer_than } if spec.newer_than
118
- serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) >= spec.newer_than_or_equal } if spec.newer_than_or_equal
156
+ serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) < spec.older_than } if spec
157
+ .older_than
158
+ serialized_records =
159
+ serialized_records.select { |sr| Time.iso8601(sr.timestamp) <= spec.older_than_or_equal } if spec
160
+ .older_than_or_equal
161
+ serialized_records = serialized_records.select { |sr| Time.iso8601(sr.timestamp) > spec.newer_than } if spec
162
+ .newer_than
163
+ serialized_records =
164
+ serialized_records.select { |sr| Time.iso8601(sr.timestamp) >= spec.newer_than_or_equal } if spec
165
+ .newer_than_or_equal
119
166
  serialized_records
120
167
  end
121
168
 
@@ -124,7 +171,7 @@ module RubyEventStore
124
171
  end
125
172
 
126
173
  def event_ids_of_stream(stream)
127
- streams.fetch(stream.name, Array.new)
174
+ streams.fetch(stream.name, Array.new).map(&:event_id)
128
175
  end
129
176
 
130
177
  def serialized_records_of_stream(stream)
@@ -143,7 +190,7 @@ module RubyEventStore
143
190
  end
144
191
 
145
192
  def last_stream_version(stream)
146
- event_ids_of_stream(stream).size - 1
193
+ streams.fetch(stream.name, Array.new).size - 1
147
194
  end
148
195
 
149
196
  def with_synchronize(expected_version, stream, &block)
@@ -157,18 +204,34 @@ module RubyEventStore
157
204
  # conditions more likely. And we only use mutex.synchronize for writing
158
205
  # not for the whole read+write algorithm.
159
206
  Thread.pass
160
- mutex.synchronize do
161
- resolved_version = last_stream_version(stream) if expected_version.any?
162
- block.call(resolved_version)
163
- end
207
+ mutex.synchronize { block.call(resolved_version) }
164
208
  end
165
209
 
166
210
  def has_event_in_stream?(event_id, stream_name)
167
- streams.fetch(stream_name, Array.new).any? { |id| id.eql?(event_id) }
211
+ streams.fetch(stream_name, Array.new).any? { |event_in_stream| event_in_stream.event_id.eql?(event_id) }
168
212
  end
169
213
 
170
214
  def index_of(source, event_id)
171
- source.index {|item| item.event_id.eql?(event_id)}
215
+ source.index { |item| item.event_id.eql?(event_id) }
216
+ end
217
+
218
+ def compute_position(resolved_version, index)
219
+ resolved_version + index + 1 unless resolved_version.nil?
220
+ end
221
+
222
+ def add_to_stream(stream, serialized_record, resolved_version, index)
223
+ streams[stream.name] << EventInStream.new(serialized_record.event_id, compute_position(resolved_version, index))
224
+ end
225
+
226
+ def ensure_supported_any_usage(resolved_version, stream)
227
+ if @ensure_supported_any_usage
228
+ stream_positions = streams.fetch(stream.name, Array.new).map(&:position)
229
+ if resolved_version.nil?
230
+ raise UnsupportedVersionAnyUsage if !stream_positions.compact.empty?
231
+ else
232
+ raise UnsupportedVersionAnyUsage if stream_positions.include?(nil)
233
+ end
234
+ end
172
235
  end
173
236
 
174
237
  attr_reader :streams, :mutex, :storage, :serializer