ruby_event_store 2.2.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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