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.
- checksums.yaml +4 -4
- data/lib/ruby_event_store/batch_enumerator.rb +3 -3
- data/lib/ruby_event_store/broker.rb +5 -4
- data/lib/ruby_event_store/client.rb +75 -46
- data/lib/ruby_event_store/composed_dispatcher.rb +1 -3
- data/lib/ruby_event_store/correlated_commands.rb +4 -15
- data/lib/ruby_event_store/errors.rb +11 -10
- data/lib/ruby_event_store/event.rb +9 -14
- data/lib/ruby_event_store/expected_version.rb +3 -7
- data/lib/ruby_event_store/in_memory_repository.rb +100 -37
- data/lib/ruby_event_store/instrumented_dispatcher.rb +11 -2
- data/lib/ruby_event_store/instrumented_repository.rb +13 -8
- data/lib/ruby_event_store/link_by_metadata.rb +4 -21
- data/lib/ruby_event_store/mappers/default.rb +6 -4
- data/lib/ruby_event_store/mappers/encryption_key.rb +7 -16
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +6 -6
- data/lib/ruby_event_store/mappers/forgotten_data.rb +1 -1
- data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +1 -1
- data/lib/ruby_event_store/mappers/null_mapper.rb +0 -1
- data/lib/ruby_event_store/mappers/pipeline.rb +3 -10
- data/lib/ruby_event_store/mappers/pipeline_mapper.rb +1 -0
- data/lib/ruby_event_store/mappers/transformation/domain_event.rb +23 -13
- data/lib/ruby_event_store/mappers/transformation/encryption.rb +21 -25
- data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +6 -5
- data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +6 -5
- data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +6 -5
- data/lib/ruby_event_store/mappers/transformation/upcast.rb +2 -6
- data/lib/ruby_event_store/metadata.rb +46 -17
- data/lib/ruby_event_store/projection.rb +12 -20
- data/lib/ruby_event_store/record.rb +14 -26
- data/lib/ruby_event_store/serialized_record.rb +14 -26
- data/lib/ruby_event_store/serializers/yaml.rb +17 -0
- data/lib/ruby_event_store/spec/broker_lint.rb +38 -28
- data/lib/ruby_event_store/spec/event_lint.rb +10 -10
- data/lib/ruby_event_store/spec/event_repository_lint.rb +745 -741
- data/lib/ruby_event_store/spec/mapper_lint.rb +2 -2
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +58 -57
- data/lib/ruby_event_store/specification.rb +20 -16
- data/lib/ruby_event_store/specification_reader.rb +2 -3
- data/lib/ruby_event_store/specification_result.rb +52 -46
- data/lib/ruby_event_store/stream.rb +3 -7
- data/lib/ruby_event_store/subscriptions.rb +14 -15
- data/lib/ruby_event_store/transform_keys.rb +1 -1
- data/lib/ruby_event_store/version.rb +1 -1
- data/lib/ruby_event_store.rb +44 -43
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3960a57258392156fd3d8ee9811d2c23a2b0a348d7938a830ae51f5edec01bc7
|
4
|
+
data.tar.gz: e4c4ef6136891a68ed94609f1fbd58ac0ce6ec854a1a843f7b167573b2cc6070
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
6
|
+
@batch_size = batch_size
|
7
7
|
@total_limit = total_limit
|
8
|
-
@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
|
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
|
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
|
-
|
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
|
3
|
+
require "concurrent"
|
4
4
|
|
5
5
|
module RubyEventStore
|
6
6
|
class Client
|
7
|
-
def initialize(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@repository
|
16
|
-
@mapper
|
17
|
-
@subscriptions
|
18
|
-
@broker
|
19
|
-
@clock
|
20
|
-
@metadata
|
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
|
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
|
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
|
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
|
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
|
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
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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]
|
321
|
-
event.metadata[:valid_at]
|
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
|
@@ -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
|
15
|
+
causation_id = event_store.metadata[:causation_id]
|
17
16
|
|
18
17
|
if correlation_id && causation_id
|
19
|
-
command.correlate_with(MiniEvent.new(
|
20
|
-
|
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
|
4
|
+
Error = Class.new(StandardError)
|
5
5
|
WrongExpectedEventVersion = Class.new(Error)
|
6
|
-
InvalidExpectedVersion
|
7
|
-
IncorrectStreamData
|
8
|
-
SubscriberNotExist
|
9
|
-
InvalidPageStart
|
10
|
-
InvalidPageStop
|
11
|
-
InvalidPageSize
|
12
|
-
EventDuplicatedInStream
|
13
|
-
ReservedInternalName
|
14
|
-
InvalidHandler
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
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
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
102
|
-
|
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
|
110
|
-
|
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
|
116
|
-
|
117
|
-
serialized_records =
|
118
|
-
|
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
|
-
|
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
|
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? { |
|
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
|