event_store_client 2.3.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|