event_store_client 0.2.8 → 1.0.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 +29 -37
- data/lib/event_store_client.rb +0 -2
- data/lib/event_store_client/adapters/grpc.rb +21 -0
- data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +149 -0
- data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +44 -0
- data/lib/event_store_client/adapters/grpc/Protos/operations.proto +45 -0
- data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +180 -0
- data/lib/event_store_client/adapters/grpc/Protos/projections.proto +174 -0
- data/lib/event_store_client/adapters/grpc/Protos/shared.proto +22 -0
- data/lib/event_store_client/adapters/grpc/Protos/streams.proto +242 -0
- data/lib/event_store_client/adapters/grpc/Protos/users.proto +119 -0
- data/lib/event_store_client/adapters/grpc/client.rb +119 -0
- data/lib/event_store_client/adapters/grpc/command_registrar.rb +32 -0
- data/lib/event_store_client/adapters/grpc/commands/command.rb +43 -0
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +46 -0
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +34 -0
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +66 -0
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +38 -0
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +48 -0
- data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +45 -0
- data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +34 -0
- data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +42 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +57 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +35 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +53 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +80 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +43 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +35 -0
- data/lib/event_store_client/adapters/grpc/connection.rb +50 -0
- data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +140 -0
- data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +46 -0
- data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +53 -0
- data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +49 -0
- data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +31 -0
- data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +213 -0
- data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +29 -0
- data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +193 -0
- data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +34 -0
- data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +35 -0
- data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +283 -0
- data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +29 -0
- data/lib/event_store_client/adapters/grpc/generated/users_pb.rb +126 -0
- data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +33 -0
- data/lib/event_store_client/adapters/http.rb +16 -0
- data/lib/event_store_client/adapters/http/README.md +16 -0
- data/lib/event_store_client/adapters/http/client.rb +160 -0
- data/lib/event_store_client/adapters/http/commands/command.rb +27 -0
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +15 -0
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +31 -0
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +57 -0
- data/lib/event_store_client/adapters/http/commands/projections/create.rb +30 -0
- data/lib/event_store_client/adapters/http/commands/streams/append.rb +49 -0
- data/lib/event_store_client/adapters/http/commands/streams/delete.rb +16 -0
- data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +49 -0
- data/lib/event_store_client/adapters/http/commands/streams/read.rb +53 -0
- data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +17 -0
- data/lib/event_store_client/adapters/http/connection.rb +46 -0
- data/lib/event_store_client/adapters/http/request_method.rb +28 -0
- data/lib/event_store_client/adapters/in_memory.rb +139 -0
- data/lib/event_store_client/broker.rb +16 -5
- data/lib/event_store_client/client.rb +17 -69
- data/lib/event_store_client/configuration.rb +28 -14
- data/lib/event_store_client/data_decryptor.rb +13 -8
- data/lib/event_store_client/data_encryptor.rb +7 -6
- data/lib/event_store_client/deserialized_event.rb +4 -1
- data/lib/event_store_client/event.rb +2 -2
- data/lib/event_store_client/mapper/default.rb +0 -1
- data/lib/event_store_client/serializer/json.rb +2 -0
- data/lib/event_store_client/subscriptions.rb +4 -13
- data/lib/event_store_client/types.rb +3 -1
- data/lib/event_store_client/value_objects/read_direction.rb +43 -0
- data/lib/event_store_client/version.rb +1 -1
- metadata +97 -15
- data/lib/event_store_client/store_adapter.rb +0 -10
- data/lib/event_store_client/store_adapter/api/client.rb +0 -224
- data/lib/event_store_client/store_adapter/api/connection.rb +0 -43
- data/lib/event_store_client/store_adapter/api/request_method.rb +0 -30
- data/lib/event_store_client/store_adapter/in_memory.rb +0 -160
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'event_store_client/adapters/http/commands/persistent_subscriptions/ack'
|
|
4
|
+
module EventStoreClient
|
|
5
|
+
module HTTP
|
|
6
|
+
module Commands
|
|
7
|
+
module PersistentSubscriptions
|
|
8
|
+
class Read < Command
|
|
9
|
+
include Configuration
|
|
10
|
+
|
|
11
|
+
def call(stream_name, subscription_name, options: {})
|
|
12
|
+
count = options[:count] || 20
|
|
13
|
+
long_poll = options[:long_poll].to_i
|
|
14
|
+
headers = long_poll.positive? ? { 'ES-LongPoll' => long_poll.to_s } : {}
|
|
15
|
+
headers['Content-Type'] = 'application/vnd.eventstore.competingatom+json'
|
|
16
|
+
headers['Accept'] = 'application/vnd.eventstore.competingatom+json'
|
|
17
|
+
headers['ES-ResolveLinktos'] = (options[:resolve_links] || true).to_s
|
|
18
|
+
|
|
19
|
+
response = connection.call(
|
|
20
|
+
:get,
|
|
21
|
+
"/subscriptions/#{stream_name}/#{subscription_name}/#{count}",
|
|
22
|
+
headers: headers
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return { events: [] } if response.body.nil? || response.body.empty?
|
|
26
|
+
|
|
27
|
+
body = JSON.parse(response.body)
|
|
28
|
+
|
|
29
|
+
ack_info = body['links'].find { |link| link['relation'] == 'ackAll' }
|
|
30
|
+
return { events: [] } unless ack_info
|
|
31
|
+
body['entries'].map do |entry|
|
|
32
|
+
yield deserialize_event(entry)
|
|
33
|
+
end
|
|
34
|
+
Ack.new(connection).call(ack_info['uri'])
|
|
35
|
+
Success()
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def deserialize_event(entry)
|
|
41
|
+
event = EventStoreClient::Event.new(
|
|
42
|
+
id: entry['eventId'],
|
|
43
|
+
title: entry['title'],
|
|
44
|
+
type: entry['eventType'],
|
|
45
|
+
data: entry['data'] || '{}',
|
|
46
|
+
metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
config.mapper.deserialize(event)
|
|
50
|
+
rescue EventStoreClient::DeserializedEvent::InvalidDataError
|
|
51
|
+
event
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Projections
|
|
7
|
+
class Create < Command
|
|
8
|
+
def call(name, streams, options: {})
|
|
9
|
+
data =
|
|
10
|
+
<<~STRING
|
|
11
|
+
fromStreams(#{streams})
|
|
12
|
+
.when({
|
|
13
|
+
$any: function(s,e) {
|
|
14
|
+
linkTo("#{name}", e)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
STRING
|
|
18
|
+
|
|
19
|
+
connection.call(
|
|
20
|
+
:post,
|
|
21
|
+
"/projections/continuous?name=#{name}&type=js&enabled=yes&emit=true&trackemittedstreams=true", # rubocop:disable Metrics/LineLength
|
|
22
|
+
body: data,
|
|
23
|
+
headers: {}
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Streams
|
|
7
|
+
class Append < Command
|
|
8
|
+
include Configuration
|
|
9
|
+
|
|
10
|
+
def call(stream_name, events, options: {})
|
|
11
|
+
expected_version = options[:expected_version]
|
|
12
|
+
serialized_events = events.map { |event| config.mapper.serialize(event) }
|
|
13
|
+
headers = {
|
|
14
|
+
'ES-ExpectedVersion' => expected_version&.to_s
|
|
15
|
+
}.reject { |_key, val| val.nil? || val.empty? }
|
|
16
|
+
|
|
17
|
+
data = build_events_data(serialized_events)
|
|
18
|
+
response =
|
|
19
|
+
connection.call(:post, "/streams/#{stream_name}", body: data, headers: headers)
|
|
20
|
+
validate_response(response, expected_version)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def build_events_data(events)
|
|
26
|
+
[events].flatten.map do |event|
|
|
27
|
+
{
|
|
28
|
+
eventId: event.id,
|
|
29
|
+
eventType: event.type,
|
|
30
|
+
data: event.data,
|
|
31
|
+
metadata: event.metadata
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def validate_response(resp, expected_version)
|
|
37
|
+
wrong_version = resp.status == 400 && resp.reason_phrase == 'Wrong expected EventNumber'
|
|
38
|
+
return Success() unless wrong_version
|
|
39
|
+
|
|
40
|
+
Failure(
|
|
41
|
+
"current version: #{resp.headers.fetch('es-currentversion')} | "\
|
|
42
|
+
"expected: #{expected_version}"
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Streams
|
|
7
|
+
class Delete < Command
|
|
8
|
+
def call(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
|
|
9
|
+
connection.call(:delete, "/streams/#{stream_name}", body: {})
|
|
10
|
+
Success()
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Streams
|
|
7
|
+
class LinkTo < Command
|
|
8
|
+
def call(stream_name, events, options: {})
|
|
9
|
+
expected_version = options[:expected_version]
|
|
10
|
+
data = build_linking_data(events)
|
|
11
|
+
headers = {
|
|
12
|
+
'ES-ExpectedVersion' => expected_version&.to_s
|
|
13
|
+
}.reject { |_key, val| val.nil? || val.empty? }
|
|
14
|
+
|
|
15
|
+
response = connection.call(
|
|
16
|
+
:post,
|
|
17
|
+
"/streams/#{stream_name}",
|
|
18
|
+
body: data,
|
|
19
|
+
headers: headers
|
|
20
|
+
)
|
|
21
|
+
validate_response(response, expected_version)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def validate_response(resp, expected_version)
|
|
27
|
+
wrong_version = resp.status == 400 && resp.reason_phrase == 'Wrong expected EventNumber'
|
|
28
|
+
return Success() unless wrong_version
|
|
29
|
+
|
|
30
|
+
Failure(
|
|
31
|
+
"current version: #{resp.headers.fetch('es-currentversion')} | "\
|
|
32
|
+
"expected: #{expected_version}"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_linking_data(events)
|
|
37
|
+
[events].flatten.map do |event|
|
|
38
|
+
{
|
|
39
|
+
eventId: event.id,
|
|
40
|
+
eventType: '$>',
|
|
41
|
+
data: event.title
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Streams
|
|
7
|
+
class Read < Command
|
|
8
|
+
include Configuration
|
|
9
|
+
|
|
10
|
+
def call(stream_name, options: {})
|
|
11
|
+
count = options[:count] || config.per_page
|
|
12
|
+
start = options[:start].to_i
|
|
13
|
+
direction = options[:direction] || 'forward'
|
|
14
|
+
headers = {
|
|
15
|
+
'ES-ResolveLinkTos' => options[:resolve_links].to_s,
|
|
16
|
+
'Accept' => 'application/vnd.eventstore.atom+json'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
response =
|
|
20
|
+
connection.call(
|
|
21
|
+
:get,
|
|
22
|
+
"/streams/#{stream_name}/#{start}/#{direction}/#{count}",
|
|
23
|
+
headers: headers
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return Failure(:stream_not_found) unless response.success? || response.status == 404
|
|
27
|
+
return Failure(:connection_failed) if response.body.nil? || response.body.empty?
|
|
28
|
+
entries = JSON.parse(response.body)['entries'].map do |entry|
|
|
29
|
+
deserialize_event(entry)
|
|
30
|
+
end.reverse
|
|
31
|
+
Success(entries)
|
|
32
|
+
rescue Faraday::ConnectionFailed
|
|
33
|
+
Failure(:connection_failed)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def deserialize_event(entry)
|
|
39
|
+
event = EventStoreClient::Event.new(
|
|
40
|
+
id: entry['eventId'],
|
|
41
|
+
title: entry['title'],
|
|
42
|
+
type: entry['eventType'],
|
|
43
|
+
data: entry['data'] || '{}',
|
|
44
|
+
metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
config.mapper.deserialize(event)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
module Commands
|
|
6
|
+
module Streams
|
|
7
|
+
class Tombstone < Command
|
|
8
|
+
def call(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
|
|
9
|
+
headers = { 'ES-HardDelete' => 'true' }
|
|
10
|
+
connection.call(:delete, "/streams/#{stream_name}", body: {}, headers: headers)
|
|
11
|
+
Success()
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'event_store_client/adapters/http/request_method'
|
|
5
|
+
|
|
6
|
+
module EventStoreClient
|
|
7
|
+
module HTTP
|
|
8
|
+
class Connection
|
|
9
|
+
include Configuration
|
|
10
|
+
|
|
11
|
+
def call(method_name, path, body: {}, headers: {})
|
|
12
|
+
method = RequestMethod.new(method_name)
|
|
13
|
+
connection.send(method.to_s, path) do |req|
|
|
14
|
+
req.headers = req.headers.merge(headers)
|
|
15
|
+
req.body = body.is_a?(String) ? body : body.to_json
|
|
16
|
+
req.params['embed'] = 'body' if method == :get
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def initialize(uri, options = {})
|
|
23
|
+
@connection = set_connection(uri, options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :options, :connection
|
|
27
|
+
|
|
28
|
+
DEFAULT_HEADERS = {
|
|
29
|
+
'Content-Type' => 'application/vnd.eventstore.events+json'
|
|
30
|
+
# 'Accept' => 'application/vnd.eventstore.atom+json',
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
def set_connection(uri, connection_options)
|
|
34
|
+
Faraday.new(
|
|
35
|
+
{
|
|
36
|
+
url: uri.to_s,
|
|
37
|
+
headers: DEFAULT_HEADERS
|
|
38
|
+
}.merge(connection_options)
|
|
39
|
+
) do |conn|
|
|
40
|
+
conn.basic_auth(config.eventstore_user, config.eventstore_password)
|
|
41
|
+
conn.adapter Faraday.default_adapter
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
module HTTP
|
|
5
|
+
class RequestMethod
|
|
6
|
+
InvalidMethodError = Class.new(StandardError)
|
|
7
|
+
def ==(other)
|
|
8
|
+
name == other.to_s
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :name
|
|
18
|
+
|
|
19
|
+
SUPPORTED_METHODS = %w[get post put delete].freeze
|
|
20
|
+
|
|
21
|
+
def initialize(name)
|
|
22
|
+
raise InvalidMethodError unless SUPPORTED_METHODS.include?(name.to_s)
|
|
23
|
+
|
|
24
|
+
@name = name.to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EventStoreClient
|
|
4
|
+
class InMemory
|
|
5
|
+
Response = Struct.new(:body, :status) do
|
|
6
|
+
def success?
|
|
7
|
+
status == 200
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :event_store
|
|
12
|
+
|
|
13
|
+
def append_to_stream(stream_name, events, options: {}) # rubocop:disable Lint/UnusedMethodArgument,Metrics/LineLength
|
|
14
|
+
event_store[stream_name] = [] unless event_store.key?(stream_name)
|
|
15
|
+
[events].flatten.each do |event|
|
|
16
|
+
event_store[stream_name].unshift(
|
|
17
|
+
'eventId' => event.id,
|
|
18
|
+
'data' => event.data,
|
|
19
|
+
'eventType' => event.type,
|
|
20
|
+
'metaData' => event.metadata,
|
|
21
|
+
'positionEventNumber' => event_store[stream_name].length
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete_stream(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
|
|
27
|
+
event_store.delete(stream_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tombstone_stream(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
|
|
31
|
+
event_store.delete(stream_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read(stream_name, options: {})
|
|
35
|
+
start = options[:start] == 'head' ? options[:start] : options[:start].to_i
|
|
36
|
+
direction = options[:direction] || 'forward'
|
|
37
|
+
response =
|
|
38
|
+
if %w[forward forwards].include?(direction)
|
|
39
|
+
read_stream_forward(stream_name, start: start)
|
|
40
|
+
else
|
|
41
|
+
read_stream_backward(stream_name, start: start)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
res = Response.new(response.to_json, 200)
|
|
45
|
+
|
|
46
|
+
return [] if res.body.nil? || res.body.empty?
|
|
47
|
+
JSON.parse(res.body)['entries'].map do |entry|
|
|
48
|
+
deserialize_event(entry)
|
|
49
|
+
end.reverse
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def read_all_from_stream(stream_name, options: {})
|
|
53
|
+
read(stream_name, options: options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def subscribe_to_stream(subscription, **)
|
|
57
|
+
# TODO: implement method body
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def link_to(stream_name, events, **)
|
|
61
|
+
append_to_stream(stream_name, events)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def listen(subscription, options: {})
|
|
65
|
+
# TODO: implement method body
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
attr_reader :per_page, :mapper
|
|
71
|
+
|
|
72
|
+
def initialize(mapper:, per_page: 20)
|
|
73
|
+
@per_page = per_page
|
|
74
|
+
@mapper = mapper
|
|
75
|
+
@event_store = {}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def read_stream_backward(stream_name, start: 0)
|
|
79
|
+
response = {
|
|
80
|
+
'entries' => [],
|
|
81
|
+
'links' => []
|
|
82
|
+
}
|
|
83
|
+
return response unless event_store.key?(stream_name)
|
|
84
|
+
|
|
85
|
+
start = start == 'head' ? event_store[stream_name].length - 1 : start
|
|
86
|
+
last_index = (start - per_page)
|
|
87
|
+
response['entries'] = event_store[stream_name].select do |event|
|
|
88
|
+
event['positionEventNumber'] > last_index &&
|
|
89
|
+
event['positionEventNumber'] <= start
|
|
90
|
+
end
|
|
91
|
+
response['links'] = links(stream_name, last_index, 'next', response['entries'], per_page)
|
|
92
|
+
|
|
93
|
+
response
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def read_stream_forward(stream_name, start: 0)
|
|
97
|
+
response = {
|
|
98
|
+
'entries' => [],
|
|
99
|
+
'links' => []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return response unless event_store.key?(stream_name)
|
|
103
|
+
|
|
104
|
+
last_index = start + per_page
|
|
105
|
+
response['entries'] = event_store[stream_name].select do |event|
|
|
106
|
+
event['positionEventNumber'] < last_index &&
|
|
107
|
+
event['positionEventNumber'] >= start
|
|
108
|
+
end
|
|
109
|
+
response['links'] =
|
|
110
|
+
links(stream_name, last_index, 'previous', response['entries'], per_page)
|
|
111
|
+
|
|
112
|
+
response
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def links(stream_name, batch_size, direction, entries, count)
|
|
116
|
+
if entries.empty? || batch_size.negative?
|
|
117
|
+
[]
|
|
118
|
+
else
|
|
119
|
+
[{
|
|
120
|
+
'uri' =>
|
|
121
|
+
"/streams/#{stream_name}/#{batch_size}/#{direction}/#{count}",
|
|
122
|
+
'relation' => direction
|
|
123
|
+
}]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def deserialize_event(entry)
|
|
128
|
+
event = EventStoreClient::Event.new(
|
|
129
|
+
id: entry['eventId'],
|
|
130
|
+
title: entry['title'],
|
|
131
|
+
type: entry['eventType'],
|
|
132
|
+
data: entry['data'].to_json || '{}',
|
|
133
|
+
metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
mapper.deserialize(event)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|