event_store_client 0.1.0 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -20
- data/lib/event_store_client.rb +5 -0
- data/lib/event_store_client/client.rb +32 -23
- data/lib/event_store_client/configuration.rb +1 -1
- data/lib/event_store_client/connection.rb +57 -13
- data/lib/event_store_client/deserialized_event.rb +31 -0
- data/lib/event_store_client/endpoint.rb +3 -5
- data/lib/event_store_client/event.rb +4 -3
- data/lib/event_store_client/mapper/default.rb +15 -5
- data/lib/event_store_client/store_adapter/api/client.rb +52 -17
- data/lib/event_store_client/store_adapter/api/request_method.rb +3 -3
- data/lib/event_store_client/store_adapter/in_memory.rb +45 -23
- data/lib/event_store_client/subscription.rb +1 -0
- data/lib/event_store_client/types.rb +6 -0
- data/lib/event_store_client/version.rb +1 -1
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d057f39fbb77d08d7c29b5f56e38e3aee78d6b39578e472afc736ad5bbd9576
|
4
|
+
data.tar.gz: 3fa3ce102d2f8f6866bef59760e76fe21038ded6b1f022e78c79918753d6911b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91d278acb0560b2b1e5166a7ef0ecdbd398f9da524dd550edc496ddc41efeb260a056a43e6e4acde752e100321fef9d6120d2eeb50047072c428f374d504a309
|
7
|
+
data.tar.gz: f8e821b0ff1ca1f74bcc9252d31d83f84af2722c596651ca96e923ad701b13a2a97f7537fe57b86ac97a15790987bd762507b61eb707a8c70211e9ae11c54613
|
data/README.md
CHANGED
@@ -50,29 +50,20 @@ Before you start, add this to the `initializer` or to the top of your script:
|
|
50
50
|
To test out the behavior, you'll need a sample event and handler to work with:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
# Sample Event using dry-struct (recommended)
|
54
|
-
require 'dry-struct'
|
55
|
-
class SomethingHappened < Dry::Struct
|
56
|
-
attribute :data, EventStoreClient::Types::Strict::Hash
|
57
|
-
attribute :metadata, EventStoreClient::Types::Strict::Hash
|
58
|
-
end
|
59
|
-
|
60
|
-
# Sample Event without types check (not recommended)
|
61
|
-
|
62
|
-
class SomethingHappened < Dry::Struct
|
63
|
-
attr_reader :data, :metadata
|
64
53
|
|
65
|
-
|
54
|
+
require 'securerandom'
|
66
55
|
|
67
|
-
|
68
|
-
|
69
|
-
|
56
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
57
|
+
def schema
|
58
|
+
Dry::Schema.Params do
|
59
|
+
required(:user_id).value(:string)
|
60
|
+
required(:title).value(:string)
|
61
|
+
end
|
70
62
|
end
|
71
63
|
end
|
72
64
|
|
73
65
|
event = SomethingHappened.new(
|
74
66
|
data: { user_id: SecureRandom.uuid, title: "Something happened" },
|
75
|
-
metadata: {}
|
76
67
|
)
|
77
68
|
```
|
78
69
|
|
@@ -113,8 +104,6 @@ events = client.read('newstream', direction: 'backward') #default 'forward'
|
|
113
104
|
|
114
105
|
### Subscribing to events
|
115
106
|
|
116
|
-
# Using automatic pooling
|
117
|
-
|
118
107
|
```ruby
|
119
108
|
client.subscribe(DummyHandler, to: [SomethingHappened])
|
120
109
|
|
@@ -129,12 +118,25 @@ client.publish(stream: 'newstream', events: events)
|
|
129
118
|
# .... wait a little bit ... Your handler should be called for every single event you publish
|
130
119
|
```
|
131
120
|
|
132
|
-
### Stop
|
121
|
+
### Stop polling for new events
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
client.stop_polling
|
125
|
+
```
|
126
|
+
|
127
|
+
### Linking existing events to the streem
|
128
|
+
|
129
|
+
Event to be linked properly has to coantians original event id.
|
130
|
+
Real events could be mixed with linked events in the same stream.
|
133
131
|
|
134
132
|
```ruby
|
135
|
-
client.
|
133
|
+
exisiting_event1 = client.read('newstream').last
|
134
|
+
client.link_to(stream: 'anotherstream', events: [exisiting_event1, ...])
|
136
135
|
```
|
137
136
|
|
137
|
+
When you read from stream where links are placed. By default Event Store Client always resolve links for you returning the event that points to the link. You can use the ES-ResolveLinkTos: false HTTP header during readin stream to tell Event Store Client to return you the actual link and to not resolve it.
|
138
|
+
More info: [ES-ResolveLinkTos](https://eventstore.org/docs/http-api/optional-http-headers/resolve-linkto/index.html?tabs=tabid-1%2Ctabid-3).
|
139
|
+
|
138
140
|
## Contributing
|
139
141
|
|
140
142
|
Do you want to contribute? Welcome!
|
data/lib/event_store_client.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EventStoreClient
|
4
|
+
def self.configure(&block)
|
5
|
+
config = Configuration.instance
|
6
|
+
config.configure(&block)
|
7
|
+
end
|
4
8
|
end
|
5
9
|
|
6
10
|
require 'event_store_client/configuration'
|
7
11
|
require 'event_store_client/types'
|
8
12
|
require 'event_store_client/event'
|
13
|
+
require 'event_store_client/deserialized_event'
|
9
14
|
|
10
15
|
require 'event_store_client/serializer/json'
|
11
16
|
|
@@ -10,55 +10,64 @@ module EventStoreClient
|
|
10
10
|
connection.publish(stream: stream, events: events, expected_version: expected_version)
|
11
11
|
end
|
12
12
|
|
13
|
-
def read(stream, direction: 'forward')
|
14
|
-
connection.read(stream, direction: direction)
|
13
|
+
def read(stream, direction: 'forward', start: 0, all: false)
|
14
|
+
connection.read(stream, direction: direction, start: start, all: all)
|
15
15
|
end
|
16
16
|
|
17
|
-
def subscribe(subscriber, to: [],
|
17
|
+
def subscribe(subscriber, to: [], polling: true)
|
18
18
|
raise NoCallMethodOnSubscriber unless subscriber.respond_to?(:call)
|
19
19
|
@subscriptions.create(subscriber, to)
|
20
|
-
|
20
|
+
poll if polling
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
return if @
|
25
|
-
@
|
23
|
+
def poll(interval: 5)
|
24
|
+
return if @polling_started
|
25
|
+
@polling_started = true
|
26
26
|
thread1 = Thread.new do
|
27
27
|
loop do
|
28
28
|
create_pid_file
|
29
|
-
Thread.handle_interrupt(Interrupt => :never)
|
30
|
-
begin
|
31
|
-
Thread.handle_interrupt(Interrupt => :immediate)
|
29
|
+
Thread.handle_interrupt(Interrupt => :never) do
|
30
|
+
begin # rubocop:disable Style/RedundantBegin
|
31
|
+
Thread.handle_interrupt(Interrupt => :immediate) do
|
32
32
|
broker.call(subscriptions)
|
33
|
-
|
34
|
-
rescue Exception => e
|
33
|
+
end
|
34
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
35
35
|
# When the thread had been interrupted or broker.call returned an error
|
36
36
|
sleep(interval) # wait for events to be processed
|
37
37
|
delete_pid_file
|
38
|
-
error_handler
|
38
|
+
error_handler&.call(e)
|
39
39
|
ensure
|
40
40
|
# this code is run always
|
41
41
|
Thread.stop
|
42
42
|
end
|
43
|
-
|
43
|
+
end
|
44
44
|
end
|
45
45
|
end
|
46
46
|
thread2 = Thread.new do
|
47
|
-
loop
|
47
|
+
loop do
|
48
|
+
sleep 1
|
49
|
+
break unless thread1.alive?
|
50
|
+
thread1.run
|
51
|
+
end
|
48
52
|
end
|
49
53
|
@threads = [thread1, thread2]
|
50
54
|
nil
|
51
55
|
end
|
52
56
|
|
53
|
-
def
|
57
|
+
def stop_polling
|
54
58
|
return if @threads.none?
|
55
|
-
@threads.each
|
56
|
-
|
57
|
-
end
|
58
|
-
@pooling_started = false
|
59
|
+
@threads.each(&:kill)
|
60
|
+
@polling_started = false
|
59
61
|
nil
|
60
62
|
end
|
61
63
|
|
64
|
+
def link_to(stream:, events:)
|
65
|
+
raise ArgumentError if !stream || stream == ''
|
66
|
+
raise ArgumentError if events.nil? || (events.is_a?(Array) && events.empty?)
|
67
|
+
|
68
|
+
connection.link_to(stream, events)
|
69
|
+
end
|
70
|
+
|
62
71
|
attr_accessor :connection, :service_name
|
63
72
|
|
64
73
|
private
|
@@ -79,12 +88,12 @@ module EventStoreClient
|
|
79
88
|
end
|
80
89
|
|
81
90
|
def create_pid_file
|
82
|
-
|
83
|
-
File.open(config.pid_path, 'w') { |file| file.write(
|
91
|
+
Dir.mkdir('tmp') unless File.exist?('tmp')
|
92
|
+
File.open(config.pid_path, 'w') { |file| file.write(Process.pid) }
|
84
93
|
end
|
85
94
|
|
86
95
|
def delete_pid_file
|
87
|
-
File.delete(config.pid_path)
|
96
|
+
File.delete(config.pid_path) if File.exist?(config.pid_path)
|
88
97
|
end
|
89
98
|
end
|
90
99
|
end
|
@@ -10,19 +10,11 @@ module EventStoreClient
|
|
10
10
|
serialized_events
|
11
11
|
end
|
12
12
|
|
13
|
-
def read(stream, direction:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
event = EventStoreClient::Event.new(
|
19
|
-
id: entry['eventId'],
|
20
|
-
type: entry['eventType'],
|
21
|
-
data: entry['data'],
|
22
|
-
metadata: entry['metaData']
|
23
|
-
)
|
24
|
-
mapper.deserialize(event)
|
25
|
-
end
|
13
|
+
def read(stream, direction:, start:, all:, resolve_links: true)
|
14
|
+
return read_all_from_stream(stream, start: start, resolve_links: resolve_links) if all
|
15
|
+
read_from_stream(
|
16
|
+
stream, direction: direction, start: start, resolve_links: resolve_links
|
17
|
+
)
|
26
18
|
end
|
27
19
|
|
28
20
|
def delete_stream(stream); end
|
@@ -51,6 +43,12 @@ module EventStoreClient
|
|
51
43
|
events
|
52
44
|
end
|
53
45
|
|
46
|
+
def link_to(stream, events)
|
47
|
+
client.link_to(stream, events)
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
54
52
|
private
|
55
53
|
|
56
54
|
attr_reader :host, :port, :mapper, :per_page
|
@@ -72,5 +70,51 @@ module EventStoreClient
|
|
72
70
|
host: host, port: port, per_page: per_page
|
73
71
|
)
|
74
72
|
end
|
73
|
+
|
74
|
+
def read_from_stream(stream, direction:, start:, resolve_links:)
|
75
|
+
response =
|
76
|
+
client.read(
|
77
|
+
stream, start: start, direction: direction, resolve_links: resolve_links
|
78
|
+
)
|
79
|
+
return [] if response.body.nil? || response.body.empty?
|
80
|
+
JSON.parse(response.body)['entries'].map do |entry|
|
81
|
+
deserialize_event(entry)
|
82
|
+
end.reverse
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_all_from_stream(stream, start:, resolve_links:)
|
86
|
+
count = per_page
|
87
|
+
events = []
|
88
|
+
failed_requests_count = 0
|
89
|
+
|
90
|
+
while failed_requests_count < 3
|
91
|
+
begin
|
92
|
+
response =
|
93
|
+
client.read(stream, start: start, direction: 'forward', resolve_links: resolve_links)
|
94
|
+
failed_requests_count += 1 && next unless response.success?
|
95
|
+
rescue Faraday::ConnectionFailed
|
96
|
+
failed_requests_count += 1
|
97
|
+
next
|
98
|
+
end
|
99
|
+
failed_requests_count = 0
|
100
|
+
break if response.body.nil? || response.body.empty?
|
101
|
+
entries = JSON.parse(response.body)['entries']
|
102
|
+
break if entries.empty?
|
103
|
+
events += entries.map { |entry| deserialize_event(entry) }.reverse
|
104
|
+
start += count
|
105
|
+
end
|
106
|
+
events
|
107
|
+
end
|
108
|
+
|
109
|
+
def deserialize_event(entry)
|
110
|
+
event = EventStoreClient::Event.new(
|
111
|
+
id: entry['eventId'],
|
112
|
+
title: entry['title'],
|
113
|
+
type: entry['eventType'],
|
114
|
+
data: entry['data'],
|
115
|
+
metadata: entry['metaData']
|
116
|
+
)
|
117
|
+
mapper.deserialize(event)
|
118
|
+
end
|
75
119
|
end
|
76
120
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/schema'
|
4
|
+
|
5
|
+
module EventStoreClient
|
6
|
+
class DeserializedEvent
|
7
|
+
InvalidDataError = Class.new(StandardError)
|
8
|
+
|
9
|
+
attr_reader :id
|
10
|
+
attr_reader :type
|
11
|
+
attr_reader :title
|
12
|
+
attr_reader :data
|
13
|
+
attr_reader :metadata
|
14
|
+
|
15
|
+
def schema
|
16
|
+
Dry::Schema.Params do
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(**args)
|
21
|
+
validation = schema.call(args[:data] || {})
|
22
|
+
raise InvalidDataError.new(message: validation.errors.to_h) if validation.errors.any?
|
23
|
+
|
24
|
+
@data = args.fetch(:data) { {} }
|
25
|
+
@metadata = args.fetch(:metadata) { {} }
|
26
|
+
@type = args[:type] || self.class.name
|
27
|
+
@title = args[:title]
|
28
|
+
@id = args[:id]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,13 +4,11 @@ require 'dry-struct'
|
|
4
4
|
|
5
5
|
module EventStoreClient
|
6
6
|
class Endpoint < Dry::Struct
|
7
|
+
attribute :host, Types::String
|
8
|
+
attribute :port, Types::Coercible::Integer
|
9
|
+
|
7
10
|
def url
|
8
11
|
"#{host}:#{port}"
|
9
12
|
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
attribute :host, Types::String
|
14
|
-
attribute :port, Types::Coercible::Integer
|
15
13
|
end
|
16
14
|
end
|
@@ -8,16 +8,17 @@ module EventStoreClient
|
|
8
8
|
class Event < Dry::Struct
|
9
9
|
attr_reader :id
|
10
10
|
|
11
|
+
attribute :type, Types::Strict::String
|
12
|
+
attribute :title, Types::Strict::String.optional.default(nil)
|
11
13
|
attribute :data, Types::Strict::String.default('{}')
|
12
14
|
attribute :metadata, Types::Strict::String.default('{}')
|
13
|
-
attribute :type, Types::Strict::String
|
14
15
|
|
15
16
|
private
|
16
17
|
|
17
18
|
def initialize(**args)
|
18
19
|
@id = SecureRandom.uuid
|
19
|
-
hash_meta =
|
20
|
-
|
20
|
+
hash_meta = JSON.parse(args[:metadata] || '{}')
|
21
|
+
hash_meta['created_at'] ||= Time.now
|
21
22
|
args[:metadata] = JSON.generate(hash_meta)
|
22
23
|
super(args)
|
23
24
|
end
|
@@ -5,9 +5,9 @@ module EventStoreClient
|
|
5
5
|
class Default
|
6
6
|
def serialize(event)
|
7
7
|
Event.new(
|
8
|
-
|
8
|
+
type: event.class.to_s,
|
9
9
|
data: serializer.serialize(event.data),
|
10
|
-
|
10
|
+
metadata: serializer.serialize(event.metadata)
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
@@ -15,9 +15,19 @@ module EventStoreClient
|
|
15
15
|
metadata = serializer.deserialize(event.metadata)
|
16
16
|
data = serializer.deserialize(event.data)
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
event_class =
|
19
|
+
begin
|
20
|
+
Object.const_get(event.type)
|
21
|
+
rescue NameError
|
22
|
+
EventStoreClient::DeserializedEvent
|
23
|
+
end
|
24
|
+
|
25
|
+
event_class.new(
|
26
|
+
id: event.id,
|
27
|
+
type: event.type,
|
28
|
+
title: event.title,
|
29
|
+
data: data,
|
30
|
+
metadata: metadata
|
21
31
|
)
|
22
32
|
end
|
23
33
|
|
@@ -9,25 +9,30 @@ module EventStoreClient
|
|
9
9
|
'ES-ExpectedVersion' => expected_version.to_s
|
10
10
|
}.reject { |_key, val| val.empty? }
|
11
11
|
|
12
|
-
data =
|
13
|
-
{
|
14
|
-
eventId: event.id,
|
15
|
-
eventType: event.type,
|
16
|
-
data: event.data,
|
17
|
-
metadata: event.metadata
|
18
|
-
}
|
19
|
-
end
|
12
|
+
data = build_events_data(events)
|
20
13
|
|
21
14
|
make_request(:post, "/streams/#{stream_name}", body: data, headers: headers)
|
22
15
|
end
|
23
16
|
|
24
17
|
def delete_stream(stream_name, hard_delete)
|
25
|
-
headers =
|
26
|
-
|
18
|
+
headers = {
|
19
|
+
'ES-HardDelete' => hard_delete.to_s
|
20
|
+
}.reject { |_key, val| val.empty? }
|
21
|
+
|
22
|
+
make_request(:delete, "/streams/#{stream_name}", body: {}, headers: headers)
|
27
23
|
end
|
28
24
|
|
29
|
-
def read(stream_name, direction: 'forward', start: 0, count: per_page)
|
30
|
-
|
25
|
+
def read(stream_name, direction: 'forward', start: 0, count: per_page, resolve_links: true)
|
26
|
+
headers = {
|
27
|
+
'ES-ResolveLinkTos' => resolve_links.to_s,
|
28
|
+
'Accept' => 'application/vnd.eventstore.atom+json'
|
29
|
+
}
|
30
|
+
|
31
|
+
make_request(
|
32
|
+
:get,
|
33
|
+
"/streams/#{stream_name}/#{start}/#{direction}/#{count}",
|
34
|
+
headers: headers
|
35
|
+
)
|
31
36
|
end
|
32
37
|
|
33
38
|
def subscribe_to_stream(
|
@@ -43,7 +48,7 @@ module EventStoreClient
|
|
43
48
|
resolveLinkTos: true
|
44
49
|
},
|
45
50
|
headers: {
|
46
|
-
|
51
|
+
'Content-Type' => 'application/json'
|
47
52
|
}
|
48
53
|
)
|
49
54
|
end
|
@@ -51,20 +56,29 @@ module EventStoreClient
|
|
51
56
|
def consume_feed(
|
52
57
|
stream_name,
|
53
58
|
subscription_name,
|
54
|
-
start: 0,
|
55
59
|
count: 1,
|
56
|
-
|
60
|
+
long_poll: 0
|
57
61
|
)
|
58
|
-
headers =
|
62
|
+
headers = long_poll.positive? ? { 'ES-LongPoll' => long_poll.to_s } : {}
|
59
63
|
headers['Content-Type'] = 'application/vnd.eventstore.competingatom+json'
|
60
64
|
headers['Accept'] = 'application/vnd.eventstore.competingatom+json'
|
61
65
|
make_request(
|
62
|
-
|
66
|
+
:get,
|
63
67
|
"/subscriptions/#{stream_name}/#{subscription_name}/#{count}",
|
64
68
|
headers: headers
|
65
69
|
)
|
66
70
|
end
|
67
71
|
|
72
|
+
def link_to(stream_name, events)
|
73
|
+
data = build_linkig_data(events)
|
74
|
+
|
75
|
+
make_request(
|
76
|
+
:post,
|
77
|
+
"/streams/#{stream_name}",
|
78
|
+
body: data
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
68
82
|
def ack(url)
|
69
83
|
make_request(:post, url)
|
70
84
|
end
|
@@ -78,6 +92,27 @@ module EventStoreClient
|
|
78
92
|
@per_page = per_page
|
79
93
|
end
|
80
94
|
|
95
|
+
def build_events_data(events)
|
96
|
+
[events].flatten.map do |event|
|
97
|
+
{
|
98
|
+
eventId: event.id,
|
99
|
+
eventType: event.type,
|
100
|
+
data: event.data,
|
101
|
+
metadata: event.metadata
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_linkig_data(events)
|
107
|
+
[events].flatten.map do |event|
|
108
|
+
{
|
109
|
+
eventId: event.id,
|
110
|
+
eventType: '$>',
|
111
|
+
data: event.title,
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
81
116
|
def make_request(method_name, path, body: {}, headers: {})
|
82
117
|
method = RequestMethod.new(method_name)
|
83
118
|
connection.send(method.to_s, path) do |req|
|
@@ -17,13 +17,13 @@ module EventStoreClient
|
|
17
17
|
|
18
18
|
attr_reader :name
|
19
19
|
|
20
|
+
SUPPORTED_METHODS = %w[get post put delete].freeze
|
21
|
+
|
20
22
|
def initialize(name)
|
21
|
-
raise InvalidMethodError unless name.to_s
|
23
|
+
raise InvalidMethodError unless SUPPORTED_METHODS.include?(name.to_s)
|
22
24
|
|
23
25
|
@name = name.to_s
|
24
26
|
end
|
25
|
-
|
26
|
-
SUPPORTED_METHODS = %w[get post put].freeze
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -3,9 +3,15 @@
|
|
3
3
|
module EventStoreClient
|
4
4
|
module StoreAdapter
|
5
5
|
class InMemory
|
6
|
+
Response = Struct.new(:body, :status) do
|
7
|
+
def success?
|
8
|
+
status == 200
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
attr_reader :event_store
|
7
13
|
|
8
|
-
def append_to_stream(stream_name, events, expected_version: nil)
|
14
|
+
def append_to_stream(stream_name, events, expected_version: nil) # rubocop:disable Lint/UnusedMethodArgument,Metrics/LineLength
|
9
15
|
event_store[stream_name] = [] unless event_store.key?(stream_name)
|
10
16
|
|
11
17
|
[events].flatten.each do |event|
|
@@ -13,61 +19,77 @@ module EventStoreClient
|
|
13
19
|
'eventId' => event.id,
|
14
20
|
'data' => event.data,
|
15
21
|
'eventType' => event.type,
|
16
|
-
'
|
22
|
+
'metaData' => event.metadata,
|
17
23
|
'positionEventNumber' => event_store[stream_name].length
|
18
24
|
)
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
|
-
def
|
28
|
+
def read(stream_name, direction: 'forward', start: 0, resolve_links: nil)
|
29
|
+
response =
|
30
|
+
if direction == 'forward'
|
31
|
+
read_stream_forward(stream_name, start: start)
|
32
|
+
else
|
33
|
+
read_stream_backward(stream_name, start: start)
|
34
|
+
end
|
35
|
+
|
36
|
+
Response.new(response.to_json, 200)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_stream(stream_name, hard_delete: false) # rubocop:disable Lint/UnusedMethodArgument
|
23
40
|
event_store.delete(stream_name)
|
24
41
|
end
|
25
42
|
|
26
|
-
def
|
43
|
+
def link_to(stream_name, events)
|
44
|
+
append_to_stream(stream_name, events)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :endpoint, :per_page
|
50
|
+
|
51
|
+
def initialize(host:, port:, per_page: 20)
|
52
|
+
@endpoint = Endpoint.new(host: host, port: port)
|
53
|
+
@per_page = per_page
|
54
|
+
@event_store = {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_stream_backward(stream_name, start: 0)
|
27
58
|
return [] unless event_store.key?(stream_name)
|
28
59
|
|
29
|
-
start = start ==
|
30
|
-
last_index = start -
|
60
|
+
start = start == 'head' ? event_store[stream_name].length - 1 : start
|
61
|
+
last_index = start - per_page
|
31
62
|
entries = event_store[stream_name].select do |event|
|
32
63
|
event['positionEventNumber'] > last_index &&
|
33
64
|
event['positionEventNumber'] <= start
|
34
65
|
end
|
35
66
|
{
|
36
67
|
'entries' => entries,
|
37
|
-
'links' => links(stream_name, last_index, 'next', entries,
|
68
|
+
'links' => links(stream_name, last_index, 'next', entries, per_page)
|
38
69
|
}
|
39
70
|
end
|
40
71
|
|
41
|
-
def read_stream_forward(stream_name, start: 0
|
72
|
+
def read_stream_forward(stream_name, start: 0)
|
42
73
|
return [] unless event_store.key?(stream_name)
|
43
74
|
|
44
|
-
last_index = start +
|
45
|
-
entries = event_store[stream_name].
|
75
|
+
last_index = start + per_page
|
76
|
+
entries = event_store[stream_name].select do |event|
|
46
77
|
event['positionEventNumber'] < last_index &&
|
47
78
|
event['positionEventNumber'] >= start
|
48
79
|
end
|
49
80
|
{
|
50
81
|
'entries' => entries,
|
51
|
-
'links' => links(stream_name, last_index, 'previous', entries,
|
82
|
+
'links' => links(stream_name, last_index, 'previous', entries, per_page)
|
52
83
|
}
|
53
84
|
end
|
54
85
|
|
55
|
-
private
|
56
|
-
|
57
|
-
attr_reader :endpoint, :per_page
|
58
|
-
|
59
|
-
def initialize(host:, port:, per_page: 20)
|
60
|
-
@endpoint = Endpoint.new(host: host, port: port)
|
61
|
-
@per_page = per_page
|
62
|
-
@event_store = {}
|
63
|
-
end
|
64
|
-
|
65
86
|
def links(stream_name, batch_size, direction, entries, count)
|
66
|
-
if entries.empty? || batch_size
|
87
|
+
if entries.empty? || batch_size.negative?
|
67
88
|
[]
|
68
89
|
else
|
69
90
|
[{
|
70
|
-
'uri' =>
|
91
|
+
'uri' =>
|
92
|
+
"http://#{endpoint.url}/streams/#{stream_name}/#{batch_size}/#{direction}/#{count}",
|
71
93
|
'relation' => direction
|
72
94
|
}]
|
73
95
|
end
|
@@ -4,6 +4,12 @@ require 'dry-types'
|
|
4
4
|
|
5
5
|
module EventStoreClient
|
6
6
|
module Types
|
7
|
+
UUID_REGEXP = /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\z/i
|
8
|
+
|
7
9
|
include Dry.Types()
|
10
|
+
|
11
|
+
UUID = Types::Strict::String.constrained(
|
12
|
+
format: UUID_REGEXP
|
13
|
+
)
|
8
14
|
end
|
9
15
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_store_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Wilgosz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-schema
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.4.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: dry-struct
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
description: Easy to use client for event-sources applications written in ruby
|
70
98
|
email:
|
71
99
|
- sebastian@driggl.com
|
@@ -81,6 +109,7 @@ files:
|
|
81
109
|
- lib/event_store_client/client.rb
|
82
110
|
- lib/event_store_client/configuration.rb
|
83
111
|
- lib/event_store_client/connection.rb
|
112
|
+
- lib/event_store_client/deserialized_event.rb
|
84
113
|
- lib/event_store_client/endpoint.rb
|
85
114
|
- lib/event_store_client/event.rb
|
86
115
|
- lib/event_store_client/mapper.rb
|
@@ -115,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
144
|
- !ruby/object:Gem::Version
|
116
145
|
version: '0'
|
117
146
|
requirements: []
|
118
|
-
rubygems_version: 3.0.
|
147
|
+
rubygems_version: 3.0.6
|
119
148
|
signing_key:
|
120
149
|
specification_version: 4
|
121
150
|
summary: Ruby integration for https://eventstore.org
|