event_store_client 2.3.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/appending_events.md +16 -22
- data/docs/catch_up_subscriptions.md +35 -70
- data/docs/configuration.md +6 -10
- data/docs/deleting_streams.md +14 -4
- data/docs/encrypting_events.md +2 -6
- data/docs/linking_events.md +20 -26
- data/docs/reading_events.md +33 -62
- data/lib/event_store_client/adapters/grpc/client.rb +13 -9
- data/lib/event_store_client/adapters/grpc/commands/command.rb +0 -2
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +1 -1
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +11 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +2 -6
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +7 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +7 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +5 -10
- data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +5 -8
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +3 -5
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +6 -8
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +11 -13
- data/lib/event_store_client/adapters/grpc.rb +0 -1
- data/lib/event_store_client/deserialized_event.rb +21 -3
- data/lib/event_store_client/errors.rb +110 -0
- data/lib/event_store_client/mapper/default.rb +1 -1
- data/lib/event_store_client/mapper/encrypted.rb +2 -2
- data/lib/event_store_client/rspec/has_option_matcher.rb +88 -0
- data/lib/event_store_client/serializer/event_deserializer.rb +4 -2
- data/lib/event_store_client/serializer/event_serializer.rb +41 -17
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +3 -1
- metadata +24 -22
data/docs/reading_events.md
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Reading events
|
1
|
+
# Reading Events
|
4
2
|
|
5
3
|
## Reading from a stream
|
6
4
|
|
@@ -8,18 +6,7 @@ The simplest way to read a stream forwards is to supply a stream name.
|
|
8
6
|
|
9
7
|
```ruby
|
10
8
|
EventStoreClient.client.read('some-stream')
|
11
|
-
# =>
|
12
|
-
```
|
13
|
-
|
14
|
-
This will return either `Dry::Monads::Success` with the list of events attached or `Dry::Monads::Failure` with an error. You can handle the result like this:
|
15
|
-
|
16
|
-
```ruby
|
17
|
-
result = EventStoreClient.client.read('some-stream')
|
18
|
-
if result.success?
|
19
|
-
result.success.each do |event|
|
20
|
-
# do something with an event
|
21
|
-
end
|
22
|
-
end
|
9
|
+
# => [#<EventStoreClient::DeserializedEvent 0x1>, #<EventStoreClient::DeserializedEvent 0x1>]
|
23
10
|
```
|
24
11
|
|
25
12
|
### Request options customization
|
@@ -68,17 +55,17 @@ As well as being able to read a stream forwards you can also go backwards. This
|
|
68
55
|
EventStoreClient.client.read('some-stream', options: { direction: 'Backwards', from_revision: :end })
|
69
56
|
```
|
70
57
|
|
71
|
-
## Checking if
|
58
|
+
## Checking if stream exists
|
72
59
|
|
73
|
-
In case a stream with given name does not exist
|
60
|
+
In case a stream with given name does not exist - `EventStoreClient::StreamNotFoundError` error will be raised:
|
74
61
|
|
75
62
|
```ruby
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# =>
|
80
|
-
|
81
|
-
|
63
|
+
begin
|
64
|
+
EventStoreClient.client.read('non-existing-stream')
|
65
|
+
rescue EventStoreClient::StreamNotFoundError => e
|
66
|
+
puts e.message # => Stream "non-existing-stream" does not exist.
|
67
|
+
puts e.stream_name # => "non-existing-stream"
|
68
|
+
end
|
82
69
|
```
|
83
70
|
|
84
71
|
## Reading from the $all stream
|
@@ -103,46 +90,40 @@ If you would like to skip deserialization of the `#read` result, you should use
|
|
103
90
|
|
104
91
|
```ruby
|
105
92
|
EventStoreClient.client.read('some-stream', skip_deserialization: true)
|
106
|
-
# =>
|
93
|
+
# => [<EventStore::Client::Streams::ReadResp ...>]
|
107
94
|
```
|
108
95
|
|
109
96
|
## Filtering
|
110
97
|
|
111
|
-
The filtering feature is only available for the`$all` stream.
|
98
|
+
The filtering feature is only available for the `$all` stream.
|
112
99
|
|
113
100
|
Retrieve events from streams with name starting with `some-stream`:
|
114
101
|
|
115
102
|
```ruby
|
116
|
-
|
103
|
+
events =
|
117
104
|
EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream'] } } })
|
118
|
-
|
119
|
-
|
120
|
-
# iterate through events
|
121
|
-
end
|
105
|
+
events.each do |event|
|
106
|
+
# iterate through events
|
122
107
|
end
|
123
108
|
```
|
124
109
|
|
125
110
|
Retrieve events with name starting with `some-event`:
|
126
111
|
|
127
112
|
```ruby
|
128
|
-
|
113
|
+
events =
|
129
114
|
EventStoreClient.client.read('$all', options: { event_type: { prefix: ['some-event'] } })
|
130
|
-
|
131
|
-
|
132
|
-
# iterate through events
|
133
|
-
end
|
115
|
+
events.each do |event|
|
116
|
+
# iterate through events
|
134
117
|
end
|
135
118
|
```
|
136
119
|
|
137
120
|
Retrieving events from stream `some-stream-1` and `some-stream-2`:
|
138
121
|
|
139
122
|
```ruby
|
140
|
-
|
123
|
+
events =
|
141
124
|
EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream-1', 'some-stream-2'] } } })
|
142
|
-
|
143
|
-
|
144
|
-
# iterate through events
|
145
|
-
end
|
125
|
+
events.each do |event|
|
126
|
+
# iterate through events
|
146
127
|
end
|
147
128
|
```
|
148
129
|
|
@@ -151,43 +132,33 @@ end
|
|
151
132
|
You can use `#read_paginated`, the ready-to-go implementation of pagination which returns an array of result pages:
|
152
133
|
|
153
134
|
```ruby
|
154
|
-
EventStoreClient.client.read_paginated('some-stream').each do |
|
155
|
-
|
156
|
-
|
157
|
-
# do something with event
|
158
|
-
end
|
135
|
+
EventStoreClient.client.read_paginated('some-stream').each do |events|
|
136
|
+
events.each do |event|
|
137
|
+
# iterate through events
|
159
138
|
end
|
160
139
|
end
|
161
140
|
|
162
|
-
EventStoreClient.client.read_paginated('$all').each do |
|
163
|
-
|
164
|
-
|
165
|
-
# do something with event
|
166
|
-
end
|
141
|
+
EventStoreClient.client.read_paginated('$all').each do |events|
|
142
|
+
events.each do |event|
|
143
|
+
# iterate through events
|
167
144
|
end
|
168
145
|
end
|
169
146
|
```
|
170
147
|
|
171
|
-
|
172
|
-
|
173
148
|
### Paginating backward reads
|
174
149
|
|
175
150
|
Just supply a call with `:direction` option and with `:from_position`/`:from_revision` option(depending on what stream you read from):
|
176
151
|
|
177
152
|
```ruby
|
178
|
-
EventStoreClient.client.read_paginated('some-stream', options: { direction: 'Backwards', from_revision: :end }).each do |
|
179
|
-
|
180
|
-
|
181
|
-
# do something with event
|
182
|
-
end
|
153
|
+
EventStoreClient.client.read_paginated('some-stream', options: { direction: 'Backwards', from_revision: :end }).each do |events|
|
154
|
+
events.each do |event|
|
155
|
+
# iterate through events
|
183
156
|
end
|
184
157
|
end
|
185
158
|
|
186
|
-
EventStoreClient.client.read_paginated('$all', options: { direction: 'Backwards', from_position: :end }).each do |
|
187
|
-
|
188
|
-
|
189
|
-
# do something with event
|
190
|
-
end
|
159
|
+
EventStoreClient.client.read_paginated('$all', options: { direction: 'Backwards', from_position: :end }).each do |events|
|
160
|
+
events.each do |event|
|
161
|
+
# iterate through events
|
191
162
|
end
|
192
163
|
end
|
193
164
|
```
|
@@ -31,8 +31,8 @@ module EventStoreClient
|
|
31
31
|
# puts proposed_msg_opts.proposed_message
|
32
32
|
# end
|
33
33
|
# ```
|
34
|
-
# @return [
|
35
|
-
#
|
34
|
+
# @return [EventStore::Client::Streams::AppendResp, Array<EventStore::Client::Streams::AppendResp>]
|
35
|
+
# @raise [EventStoreClient::WrongExpectedVersionError]
|
36
36
|
def append_to_stream(stream_name, events_or_event, options: {}, credentials: {}, &blk)
|
37
37
|
if events_or_event.is_a?(Array)
|
38
38
|
Commands::Streams::AppendMultiple.new(config: config, **credentials).call(
|
@@ -93,7 +93,8 @@ module EventStoreClient
|
|
93
93
|
# )
|
94
94
|
# end
|
95
95
|
# ```
|
96
|
-
# @return [
|
96
|
+
# @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
|
97
|
+
# @raise [EventStoreClient::StreamNotFoundError]
|
97
98
|
def read(stream_name, options: {}, skip_deserialization: config.skip_deserialization,
|
98
99
|
skip_decryption: config.skip_decryption, credentials: {}, &blk)
|
99
100
|
Commands::Streams::Read.new(config: config, **credentials).call(
|
@@ -106,8 +107,9 @@ module EventStoreClient
|
|
106
107
|
end
|
107
108
|
|
108
109
|
# @see {#read} for available params
|
109
|
-
# @return [Enumerator] enumerator will yield
|
110
|
-
#
|
110
|
+
# @return [Enumerator] enumerator will yield EventStoreClient::DeserializedEvent or
|
111
|
+
# EventStore::Client::Streams::ReadResp on each iteration depending on `:skip_deserialization`
|
112
|
+
# argument value
|
111
113
|
def read_paginated(stream_name, options: {}, credentials: {},
|
112
114
|
skip_deserialization: config.skip_deserialization,
|
113
115
|
skip_decryption: config.skip_decryption, &blk)
|
@@ -136,7 +138,8 @@ module EventStoreClient
|
|
136
138
|
# opts.stream_identifier.stream_name = 'overridden-stream-name'
|
137
139
|
# end
|
138
140
|
# ```
|
139
|
-
# @return [
|
141
|
+
# @return [EventStore::Client::Streams::DeleteResp]
|
142
|
+
# @raise [EventStoreClient::StreamDeletionError]
|
140
143
|
def hard_delete_stream(stream_name, options: {}, credentials: {}, &blk)
|
141
144
|
Commands::Streams::HardDelete.
|
142
145
|
new(config: config, **credentials).
|
@@ -159,7 +162,8 @@ module EventStoreClient
|
|
159
162
|
# opts.stream_identifier.stream_name = 'overridden-stream-name'
|
160
163
|
# end
|
161
164
|
# ```
|
162
|
-
# @return [
|
165
|
+
# @return [EventStore::Client::Streams::DeleteResp]
|
166
|
+
# @raise [EventStoreClient::StreamDeletionError]
|
163
167
|
def delete_stream(stream_name, options: {}, credentials: {}, &blk)
|
164
168
|
Commands::Streams::Delete.
|
165
169
|
new(config: config, **credentials).
|
@@ -218,7 +222,7 @@ module EventStoreClient
|
|
218
222
|
# )
|
219
223
|
# end
|
220
224
|
# ```
|
221
|
-
# @return [
|
225
|
+
# @return [void] will block current Thread until killed
|
222
226
|
def subscribe_to_stream(stream_name, handler:, options: {}, credentials: {},
|
223
227
|
skip_deserialization: config.skip_deserialization,
|
224
228
|
skip_decryption: config.skip_decryption, &blk)
|
@@ -274,7 +278,7 @@ module EventStoreClient
|
|
274
278
|
# @param credentials [Hash]
|
275
279
|
# @option credentials [String] :username
|
276
280
|
# @option credentials [String] :password
|
277
|
-
# @return [
|
281
|
+
# @return [EventStore::Client::Gossip::ClusterInfo]
|
278
282
|
def cluster_info(credentials: {})
|
279
283
|
Commands::Gossip::ClusterInfo.new(config: config, **credentials).call
|
280
284
|
end
|
@@ -11,7 +11,7 @@ module EventStoreClient
|
|
11
11
|
# @api private
|
12
12
|
# @see {EventStoreClient::GRPC::Client#cluster_info}
|
13
13
|
def call
|
14
|
-
|
14
|
+
retry_request { service.read(request.new, metadata: metadata) }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -20,7 +20,7 @@ module EventStoreClient
|
|
20
20
|
response = retry_request(skip_retry: config.eventstore_url.throw_on_append_failure) do
|
21
21
|
service.append(payload, metadata: metadata)
|
22
22
|
end
|
23
|
-
validate_response(response)
|
23
|
+
validate_response(response, caused_by: event)
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -41,11 +41,17 @@ module EventStoreClient
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# @param resp [EventStore::Client::Streams::AppendResp]
|
44
|
-
# @
|
45
|
-
|
46
|
-
|
44
|
+
# @param caused_by [EventStoreClient::DeserializedEvent]
|
45
|
+
# @return [EventStore::Client::Streams::AppendResp]
|
46
|
+
# @raise [EventStoreClient::WrongExpectedVersionError]
|
47
|
+
def validate_response(resp, caused_by:)
|
48
|
+
return resp if resp.success
|
47
49
|
|
48
|
-
|
50
|
+
error = WrongExpectedVersionError.new(
|
51
|
+
resp.wrong_expected_version,
|
52
|
+
caused_by: caused_by
|
53
|
+
)
|
54
|
+
raise error
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
@@ -10,9 +10,8 @@ module EventStoreClient
|
|
10
10
|
# @api private
|
11
11
|
# @see {EventStoreClient::GRPC::Client#append_to_stream}
|
12
12
|
def call(stream_name, events, options:, &blk)
|
13
|
-
|
14
|
-
|
15
|
-
response = Commands::Streams::Append.new(
|
13
|
+
events.map.with_index do |event, index|
|
14
|
+
Commands::Streams::Append.new(
|
16
15
|
config: config, **connection_options
|
17
16
|
).call(
|
18
17
|
stream_name, event, options: options
|
@@ -21,10 +20,7 @@ module EventStoreClient
|
|
21
20
|
|
22
21
|
yield(req_opts, proposed_msg_opts) if blk
|
23
22
|
end
|
24
|
-
result.push(response)
|
25
|
-
break if response.failure?
|
26
23
|
end
|
27
|
-
result
|
28
24
|
end
|
29
25
|
|
30
26
|
private
|
@@ -13,11 +13,13 @@ module EventStoreClient
|
|
13
13
|
def call(stream_name, options:, &blk)
|
14
14
|
options = normalize_options(stream_name, options)
|
15
15
|
yield options if blk
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
retry_request { service.delete(request.new(options: options), metadata: metadata) }
|
17
|
+
rescue ::GRPC::FailedPrecondition => e
|
18
|
+
# GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
|
19
|
+
# be existing, or :expected_revision option value does not match the current state of
|
20
|
+
# the stream. So, re-raise our own error, and pass there the original message - just in
|
21
|
+
# case.
|
22
|
+
raise StreamDeletionError.new(stream_name, details: e.message)
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
@@ -13,11 +13,13 @@ module EventStoreClient
|
|
13
13
|
def call(stream_name, options:, &blk)
|
14
14
|
options = normalize_options(stream_name, options)
|
15
15
|
yield options if blk
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
retry_request { service.delete(request.new(options: options), metadata: metadata) }
|
17
|
+
rescue ::GRPC::FailedPrecondition => e
|
18
|
+
# GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
|
19
|
+
# be existing, or :expected_revision option value does not match the current state of
|
20
|
+
# the stream. So, re-raise our own error, and pass there the original message - just in
|
21
|
+
# case.
|
22
|
+
raise StreamDeletionError.new(stream_name, details: e.message)
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
@@ -10,18 +10,13 @@ module EventStoreClient
|
|
10
10
|
# @api private
|
11
11
|
# @see {EventStoreClient::GRPC::Client#link_to}
|
12
12
|
def call(stream_name, events, options:, &blk)
|
13
|
-
result = []
|
14
13
|
link_cmd = Commands::Streams::LinkTo.new(config: config, **connection_options)
|
15
|
-
events.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
result.push(response)
|
22
|
-
break if response.failure?
|
14
|
+
events.map.with_index do |event, index|
|
15
|
+
link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
|
16
|
+
req_opts.options.revision += index if has_revision_option?(req_opts.options)
|
17
|
+
yield(req_opts, proposed_msg_opts) if blk
|
18
|
+
end
|
23
19
|
end
|
24
|
-
result
|
25
20
|
end
|
26
21
|
|
27
22
|
private
|
@@ -40,22 +40,19 @@ module EventStoreClient
|
|
40
40
|
|
41
41
|
paginate_options(opts, position)
|
42
42
|
end
|
43
|
-
unless response.success?
|
44
|
-
yielder << response
|
45
|
-
raise StopIteration
|
46
|
-
end
|
47
43
|
processed_response =
|
48
44
|
EventStoreClient::GRPC::Shared::Streams::ProcessResponses.
|
49
45
|
new(config: config).
|
50
46
|
call(
|
51
|
-
response
|
47
|
+
response,
|
52
48
|
skip_deserialization,
|
53
49
|
skip_decryption
|
54
50
|
)
|
55
|
-
yielder << processed_response if processed_response.success.any?
|
56
|
-
raise StopIteration if end_reached?(response.success, max_count)
|
57
51
|
|
58
|
-
|
52
|
+
yielder << processed_response if processed_response.any?
|
53
|
+
raise StopIteration if end_reached?(response, max_count)
|
54
|
+
|
55
|
+
position = calc_next_position(response, direction, stream_name)
|
59
56
|
raise StopIteration if position.negative?
|
60
57
|
end
|
61
58
|
end
|
@@ -28,11 +28,9 @@ module EventStoreClient
|
|
28
28
|
|
29
29
|
handler.call(result) if result
|
30
30
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
)
|
31
|
+
retry_request do
|
32
|
+
service.read(request.new(options: options), metadata: metadata, &callback)
|
33
|
+
end
|
36
34
|
end
|
37
35
|
|
38
36
|
private
|
@@ -5,8 +5,6 @@ module EventStoreClient
|
|
5
5
|
module Shared
|
6
6
|
module Streams
|
7
7
|
class ProcessResponse
|
8
|
-
include Dry::Monads[:result]
|
9
|
-
|
10
8
|
attr_reader :config
|
11
9
|
private :config
|
12
10
|
|
@@ -19,15 +17,15 @@ module EventStoreClient
|
|
19
17
|
# @param response [EventStore::Client::Streams::ReadResp]
|
20
18
|
# @param skip_deserialization [Boolean]
|
21
19
|
# @param skip_decryption [Boolean]
|
22
|
-
# @return [
|
20
|
+
# @return [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp, nil]
|
21
|
+
# @raise [EventStoreClient::StreamNotFoundError]
|
23
22
|
def call(response, skip_deserialization, skip_decryption)
|
24
|
-
|
25
|
-
|
23
|
+
non_existing_stream = response.stream_not_found&.stream_identifier&.stream_name
|
24
|
+
raise StreamNotFoundError, non_existing_stream if non_existing_stream
|
25
|
+
return response if skip_deserialization
|
26
26
|
return unless response.event&.event
|
27
27
|
|
28
|
-
|
29
|
-
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
30
|
-
)
|
28
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
@@ -5,8 +5,6 @@ module EventStoreClient
|
|
5
5
|
module Shared
|
6
6
|
module Streams
|
7
7
|
class ProcessResponses
|
8
|
-
include Dry::Monads[:result]
|
9
|
-
|
10
8
|
attr_reader :config
|
11
9
|
private :config
|
12
10
|
|
@@ -19,20 +17,20 @@ module EventStoreClient
|
|
19
17
|
# @param responses [Array<EventStore::Client::Streams::ReadResp>]
|
20
18
|
# @param skip_deserialization [Boolean]
|
21
19
|
# @param skip_decryption [Boolean]
|
22
|
-
# @return [
|
20
|
+
# @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
|
21
|
+
# @raise [EventStoreClient::StreamNotFoundError]
|
23
22
|
def call(responses, skip_deserialization, skip_decryption)
|
24
|
-
|
25
|
-
|
23
|
+
non_existing_stream = responses.first&.stream_not_found&.stream_identifier&.stream_name
|
24
|
+
raise StreamNotFoundError, non_existing_stream if non_existing_stream
|
25
|
+
return responses if skip_deserialization
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
next unless response.event&.event
|
27
|
+
responses.map do |response|
|
28
|
+
# It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
|
29
|
+
# example. Such responses should be skipped. See generated files for more info.
|
30
|
+
next unless response.event&.event
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
Success(events.compact)
|
32
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
33
|
+
end.compact
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
@@ -7,8 +7,8 @@ module EventStoreClient
|
|
7
7
|
InvalidDataError = Class.new(StandardError)
|
8
8
|
private_constant :InvalidDataError
|
9
9
|
|
10
|
-
attr_reader :id, :type, :title, :data, :metadata, :
|
11
|
-
:prepare_position, :commit_position
|
10
|
+
attr_reader :id, :type, :title, :data, :metadata, :custom_metadata, :stream_name,
|
11
|
+
:stream_revision, :prepare_position, :commit_position
|
12
12
|
|
13
13
|
# @args [Hash] opts
|
14
14
|
# @option opts [Boolean] :skip_validation
|
@@ -34,6 +34,7 @@ module EventStoreClient
|
|
34
34
|
'type' => @type,
|
35
35
|
'content-type' => payload_content_type
|
36
36
|
)
|
37
|
+
@custom_metadata = args[:custom_metadata] || {}
|
37
38
|
@stream_name = args[:stream_name]
|
38
39
|
@stream_revision = args[:stream_revision]
|
39
40
|
@prepare_position = args[:prepare_position]
|
@@ -57,7 +58,7 @@ module EventStoreClient
|
|
57
58
|
def ==(other)
|
58
59
|
return false unless other.is_a?(EventStoreClient::DeserializedEvent)
|
59
60
|
|
60
|
-
to_h == other.to_h
|
61
|
+
meaningful_attrs(to_h) == meaningful_attrs(other.to_h)
|
61
62
|
end
|
62
63
|
|
63
64
|
# @return [Hash]
|
@@ -75,8 +76,25 @@ module EventStoreClient
|
|
75
76
|
type == LINK_TYPE
|
76
77
|
end
|
77
78
|
|
79
|
+
# Detect whether an event is a system event
|
80
|
+
# @return [Boolean]
|
81
|
+
def system?
|
82
|
+
return false unless type
|
83
|
+
|
84
|
+
type.start_with?('$')
|
85
|
+
end
|
86
|
+
|
78
87
|
private
|
79
88
|
|
89
|
+
# When comparing two events - we drop commit_position and prepare_position from attributes list
|
90
|
+
# to compare. This is because their value are different when retrieving the same event from
|
91
|
+
# '$all' stream and from specific stream.
|
92
|
+
# @param hash [Hash]
|
93
|
+
# @return [Hash]
|
94
|
+
def meaningful_attrs(hash)
|
95
|
+
hash.delete_if { |key, _val| %i(commit_position prepare_position).include?(key) }
|
96
|
+
end
|
97
|
+
|
80
98
|
def validate(data)
|
81
99
|
return unless schema
|
82
100
|
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
class Error < StandardError
|
5
|
+
# @return [Hash]
|
6
|
+
def as_json(*)
|
7
|
+
to_h.transform_keys(&:to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Hash]
|
11
|
+
def to_h
|
12
|
+
hash =
|
13
|
+
instance_variables.each_with_object({}) do |var, result|
|
14
|
+
key = var.to_s
|
15
|
+
key[0] = '' # remove @ sign
|
16
|
+
result[key.to_sym] = instance_variable_get(var)
|
17
|
+
end
|
18
|
+
hash[:message] = message
|
19
|
+
hash[:backtrace] = backtrace
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class StreamNotFoundError < Error
|
25
|
+
attr_reader :stream_name
|
26
|
+
|
27
|
+
# @param stream_name [String]
|
28
|
+
def initialize(stream_name)
|
29
|
+
@stream_name = stream_name
|
30
|
+
super("Stream #{stream_name.inspect} does not exist.")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class WrongExpectedVersionError < Error
|
35
|
+
attr_reader :wrong_expected_version, :caused_by
|
36
|
+
|
37
|
+
# @param wrong_expected_version [EventStore::Client::Streams::AppendResp::WrongExpectedVersion]
|
38
|
+
# @param caused_by [EventStoreClient::DeserializedEvent] an event on which
|
39
|
+
# WrongExpectedVersionError error happened. It can be useful when appending array of events -
|
40
|
+
# based on it you will know which events were appended and which weren't.
|
41
|
+
def initialize(wrong_expected_version, caused_by:)
|
42
|
+
@wrong_expected_version = wrong_expected_version
|
43
|
+
@caused_by = caused_by
|
44
|
+
super(user_friendly_message)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [String]
|
50
|
+
def user_friendly_message
|
51
|
+
return expected_stream_exists if wrong_expected_version.expected_stream_exists
|
52
|
+
return expected_no_stream if wrong_expected_version.expected_no_stream
|
53
|
+
return current_no_stream if wrong_expected_version.current_no_stream
|
54
|
+
unless wrong_expected_version.expected_revision == wrong_expected_version.current_revision
|
55
|
+
return unmatched_stream_revision
|
56
|
+
end
|
57
|
+
|
58
|
+
# Unhandled case. Could happen if something else would be added to proto and I don't add it
|
59
|
+
# here.
|
60
|
+
self.class.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
def expected_stream_exists
|
65
|
+
"Expected stream to exist, but it doesn't."
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String]
|
69
|
+
def expected_no_stream
|
70
|
+
"Expected stream to be absent, but it actually exists."
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String]
|
74
|
+
def current_no_stream
|
75
|
+
<<~TEXT.strip
|
76
|
+
Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
|
77
|
+
stream does not exist.
|
78
|
+
TEXT
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [String]
|
82
|
+
def unmatched_stream_revision
|
83
|
+
<<~TEXT.strip
|
84
|
+
Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
|
85
|
+
actual stream revision is #{wrong_expected_version.current_revision.inspect}.
|
86
|
+
TEXT
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class StreamDeletionError < Error
|
91
|
+
attr_reader :stream_name, :details
|
92
|
+
|
93
|
+
# @param stream_name [String]
|
94
|
+
# @param details [String]
|
95
|
+
def initialize(stream_name, details:)
|
96
|
+
@stream_name = stream_name
|
97
|
+
@details = details
|
98
|
+
super(user_friendly_message)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String]
|
102
|
+
def user_friendly_message
|
103
|
+
<<~TEXT.strip
|
104
|
+
Could not delete #{stream_name.inspect} stream. It seems that a stream with that \
|
105
|
+
name does not exist, has already been deleted or its state does not match the \
|
106
|
+
provided :expected_revision option. Please check #details for more info.
|
107
|
+
TEXT
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|