estore 0.0.1 → 0.0.2
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/.rubocop.yml +66 -2
- data/.travis.yml +26 -0
- data/Gemfile +13 -0
- data/README.md +27 -15
- data/Rakefile +27 -9
- data/estore.gemspec +9 -10
- data/lib/estore.rb +11 -92
- data/lib/estore/catchup_subscription.rb +15 -22
- data/lib/estore/connection.rb +24 -23
- data/lib/estore/connection/buffer.rb +7 -4
- data/lib/estore/connection/commands.rb +1 -1
- data/lib/estore/connection_context.rb +10 -8
- data/lib/estore/errors.rb +1 -1
- data/lib/estore/message_extensions.rb +3 -3
- data/lib/estore/messages.rb +1 -1
- data/lib/estore/package.rb +4 -2
- data/lib/estore/session.rb +100 -0
- data/lib/estore/subscription.rb +7 -7
- data/lib/estore/version.rb +2 -2
- data/spec/integration/session_spec.rb +106 -0
- data/spec/spec_helper.rb +6 -3
- metadata +18 -34
- data/circle.yml +0 -3
- data/spec/db/.gitkeep +0 -0
- data/spec/eventstore_spec.rb +0 -103
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 788e47dab75704db758ce692febbfe443f923cf8
|
4
|
+
data.tar.gz: 7f9e72691c2199897cd933b58fff8069535d0a24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 936915386686cddcd4c6a03c07e237007ed42db68216b4e0abced32680fff2b41947286df3e0bdd15a3b5e8ce09be9b7f0816bbc40b0f114b91f458433f686e6
|
7
|
+
data.tar.gz: 1d433f68f8c8abea1a10d7cdb6a2eb034a7f60280596e4d3851e5acd09e10a190d0461fd669e015bca74aee5aec4f87cbefb8b0a792e545d4d0b50dabd1c0427
|
data/.rubocop.yml
CHANGED
@@ -7,5 +7,69 @@ AllCops:
|
|
7
7
|
Metrics/LineLength:
|
8
8
|
Max: 140
|
9
9
|
|
10
|
-
#
|
11
|
-
|
10
|
+
# It’s quite readable when we know what we are doing
|
11
|
+
Lint/AssignmentInCondition:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# No need to handle LoadError in Rakefile
|
15
|
+
Lint/HandleExceptions:
|
16
|
+
Exclude:
|
17
|
+
- Rakefile
|
18
|
+
|
19
|
+
# gemspec is a special snowflake
|
20
|
+
Metrics/LineLength:
|
21
|
+
Exclude:
|
22
|
+
- rom-event_store.gemspec
|
23
|
+
|
24
|
+
# The enforced style doesn’t match Vim’s defaults
|
25
|
+
Style/AlignParameters:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
# UTF-8 is perfectly fine in comments
|
29
|
+
Style/AsciiComments:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
# Allow using braces for value-returning blocks
|
33
|
+
Style/Blocks:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
# Documentation checked by Inch CI
|
37
|
+
Style/Documentation:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
# Early returns have their vices
|
41
|
+
Style/GuardClause:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
# Need to be skipped for >-> usage
|
45
|
+
Style/Lambda:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
# Multiline block chains are ok
|
49
|
+
Style/MultilineBlockChain:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
# Even a single escaped slash can be confusing
|
53
|
+
Style/RegexpLiteral:
|
54
|
+
MaxSlashes: 0
|
55
|
+
|
56
|
+
# Don’t introduce semantic fail/raise distinction
|
57
|
+
Style/SignalException:
|
58
|
+
EnforcedStyle: only_raise
|
59
|
+
|
60
|
+
# Need to be skipped for >-> usage
|
61
|
+
Style/SpaceAroundOperators:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
# Accept both single and double quotes
|
65
|
+
Style/StringLiterals:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
# Allow def self.foo; @foo; end
|
69
|
+
Style/TrivialAccessors:
|
70
|
+
Enabled: false
|
71
|
+
|
72
|
+
# This is a shim file for those who require 'rom-mongo'
|
73
|
+
Style/FileName:
|
74
|
+
Exclude:
|
75
|
+
- lib/rom-event_store.rb
|
data/.travis.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
bundler_args: --without yard guard benchmarks tools
|
5
|
+
env:
|
6
|
+
global:
|
7
|
+
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
8
|
+
- CODECLIMATE_REPO_TOKEN=86bb9d558da8217d85126a02c4a264dc8789bb4cc8d3949e8fcbabca0440f2fa
|
9
|
+
install:
|
10
|
+
- "wget -nc http://download.geteventstore.com/binaries/EventStore-OSS-Linux-v3.0.3.tar.gz"
|
11
|
+
- "tar -xvzf EventStore-OSS-Linux-v3.0.3.tar.gz"
|
12
|
+
- "mv EventStore-OSS-Linux-v3.0.3 .event_store"
|
13
|
+
- "cd .event_store && ./run-node.sh --db ./ESData &"
|
14
|
+
- "bundle install"
|
15
|
+
script: "bundle exec rake ci"
|
16
|
+
rvm:
|
17
|
+
- 2.0
|
18
|
+
- 2.1
|
19
|
+
- rbx-2
|
20
|
+
- jruby
|
21
|
+
- jruby-head
|
22
|
+
- ruby-head
|
23
|
+
matrix:
|
24
|
+
allow_failures:
|
25
|
+
- rvm: ruby-head
|
26
|
+
- rvm: jruby-head
|
data/Gemfile
CHANGED
@@ -2,3 +2,16 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in estore.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rspec', '~> 3.1'
|
8
|
+
gem 'codeclimate-test-reporter', require: false
|
9
|
+
end
|
10
|
+
|
11
|
+
group :tools do
|
12
|
+
gem 'rubocop'
|
13
|
+
|
14
|
+
gem 'guard'
|
15
|
+
gem 'guard-rspec'
|
16
|
+
gem 'guard-rubocop'
|
17
|
+
end
|
data/README.md
CHANGED
@@ -1,34 +1,46 @@
|
|
1
|
-
|
1
|
+
[gem]: https://rubygems.org/gems/estore
|
2
|
+
[travis]: https://travis-ci.org/rom-eventstore/estore
|
3
|
+
[gemnasium]: https://gemnasium.com/rom-eventstore/estore
|
4
|
+
[codeclimate]: https://codeclimate.com/github/rom-eventstore/estore
|
5
|
+
[inchpages]: http://inch-ci.org/github/rom-eventstore/estore
|
6
|
+
[gitter]: https://gitter.im/rom-eventstore/estore?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
2
7
|
|
3
|
-
|
8
|
+
# Estore
|
4
9
|
|
5
|
-
|
10
|
+
[][gem]
|
11
|
+
[][travis]
|
12
|
+
[][gemnasium]
|
13
|
+
[][codeclimate]
|
14
|
+
[][codeclimate]
|
15
|
+
[][inchpages]
|
16
|
+
[][gitter]
|
6
17
|
|
7
|
-
|
8
|
-
with Complex Event Processing in JavaScript
|
18
|
+
An [Event Store](http://geteventstore.com/) driver for Ruby
|
9
19
|
|
10
|
-
|
20
|
+
## Installation
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
Add this line to your application's Gemfile and run Bundler:
|
22
|
+
Add this line to your application's Gemfile:
|
15
23
|
|
16
24
|
```ruby
|
17
25
|
gem 'estore'
|
18
26
|
```
|
19
27
|
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
20
32
|
Or install it yourself as:
|
21
33
|
|
22
34
|
$ gem install estore
|
23
35
|
|
24
36
|
## Usage
|
25
37
|
|
38
|
+
See [spec/integration/session_spec.rb](spec/integration/session_spec.rb) for a sample usage.
|
39
|
+
|
40
|
+
## License
|
26
41
|
|
42
|
+
See [LICENSE](LICENSE) file.
|
27
43
|
|
28
|
-
##
|
44
|
+
## Credits
|
29
45
|
|
30
|
-
|
31
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
-
5. Create a new Pull Request
|
46
|
+
* [Mathieu Ravaux](https://github.com/mathieuravaux)
|
data/Rakefile
CHANGED
@@ -1,21 +1,39 @@
|
|
1
|
-
require 'bundler
|
1
|
+
require 'bundler'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
|
5
|
-
PROTO_URL = 'https://raw.githubusercontent.com/EventStore/EventStore/oss-v3.0.1/src/Protos/ClientAPI/ClientMessageDtos.proto'
|
6
|
-
PROTO_DIR = 'lib/estore'
|
4
|
+
Bundler.setup
|
7
5
|
|
8
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
task default: [:ci]
|
8
|
+
|
9
|
+
desc 'Run CI tasks'
|
10
|
+
task ci: [:spec]
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rubocop/rake_task'
|
14
|
+
|
15
|
+
Rake::Task[:default].enhance [:rubocop]
|
16
|
+
|
17
|
+
RuboCop::RakeTask.new do |task|
|
18
|
+
task.options << '--display-cop-names'
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
end
|
9
22
|
|
10
|
-
|
23
|
+
VENDORED_PROTO = 'vendor/proto/ClientMessageDtos.proto'
|
24
|
+
PROTO_URL = 'https://raw.githubusercontent.com/EventStore/EventStore/'\
|
25
|
+
'oss-v3.0.1/src/Protos/ClientAPI/ClientMessageDtos.proto'
|
11
26
|
|
12
27
|
desc 'Update the protobuf messages definition'
|
13
28
|
task :proto do
|
14
29
|
system("wget -O #{VENDORED_PROTO} #{PROTO_URL}")
|
15
|
-
|
16
|
-
|
17
|
-
|
30
|
+
|
31
|
+
beefcake = Bundler.bin_path.join('protoc-gen-beefcake').to_s
|
32
|
+
|
33
|
+
if system("BEEFCAKE_NAMESPACE=Estore protoc --plugin=#{beefcake} "\
|
34
|
+
"--beefcake_out lib/estore #{VENDORED_PROTO}")
|
18
35
|
FileUtils.mv('lib/estore/ClientMessageDtos.pb.rb', 'lib/estore/messages.rb')
|
19
|
-
system("sed -i '' 's/module Eventstore/class Eventstore/'
|
36
|
+
system("sed -i '' 's/module Eventstore/class Eventstore/' "\
|
37
|
+
"lib/estore/messages.rb")
|
20
38
|
end
|
21
39
|
end
|
data/estore.gemspec
CHANGED
@@ -5,24 +5,23 @@ require 'estore/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'estore'
|
8
|
-
spec.version =
|
8
|
+
spec.version = Estore::VERSION
|
9
9
|
spec.authors = ['Mathieu Ravaux', 'Héctor Ramón']
|
10
10
|
spec.email = ['mathieu.ravaux@gmail.com', 'hector0193@gmail.com']
|
11
|
-
spec.summary = '
|
12
|
-
spec.description =
|
13
|
-
spec.homepage = 'https://github.com/rom-eventstore/
|
11
|
+
spec.summary = 'An Event Store driver for Ruby'
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = 'https://github.com/rom-eventstore/estore'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.executables = spec.files.grep(
|
18
|
-
spec.test_files = spec.files.grep(
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_dependency 'beefcake', '~> 1.1.0.pre1'
|
22
22
|
spec.add_dependency 'promise.rb', '~> 0.6.1'
|
23
23
|
|
24
|
-
spec.add_development_dependency 'bundler'
|
25
|
-
spec.add_development_dependency 'rake'
|
26
|
-
spec.add_development_dependency '
|
27
|
-
spec.add_development_dependency 'pry'
|
24
|
+
spec.add_development_dependency 'bundler'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rubocop', '~> 0.28.0'
|
28
27
|
end
|
data/lib/estore.rb
CHANGED
@@ -1,92 +1,11 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
attr_reader :host, :port, :connection, :context, :error_handler
|
13
|
-
def initialize(host, port = 2113)
|
14
|
-
@host = host
|
15
|
-
@port = port
|
16
|
-
@context = ConnectionContext.new
|
17
|
-
@connection = Connection.new(host, port, context)
|
18
|
-
end
|
19
|
-
|
20
|
-
def on_error(error = nil, &block)
|
21
|
-
context.on_error(error, &block)
|
22
|
-
end
|
23
|
-
|
24
|
-
def close
|
25
|
-
connection.close
|
26
|
-
end
|
27
|
-
|
28
|
-
def ping
|
29
|
-
command('Ping')
|
30
|
-
end
|
31
|
-
|
32
|
-
def new_event(event_type, data, content_type: 'json', uuid: nil)
|
33
|
-
uuid ||= SecureRandom.uuid
|
34
|
-
content_type_code = { 'json' => 1 }.fetch(content_type, 0)
|
35
|
-
NewEvent.new(
|
36
|
-
event_id: Package.encode_uuid(uuid),
|
37
|
-
event_type: event_type,
|
38
|
-
data: data,
|
39
|
-
data_content_type: content_type_code,
|
40
|
-
metadata_content_type: 1
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
def write_events(stream, events)
|
45
|
-
events = Array(events)
|
46
|
-
msg = WriteEvents.new(
|
47
|
-
event_stream_id: stream,
|
48
|
-
expected_version: -2,
|
49
|
-
events: events,
|
50
|
-
require_master: true
|
51
|
-
)
|
52
|
-
command('WriteEvents', msg)
|
53
|
-
end
|
54
|
-
|
55
|
-
def read_stream_events_forward(stream, start, max)
|
56
|
-
msg = ReadStreamEvents.new(
|
57
|
-
event_stream_id: stream,
|
58
|
-
from_event_number: start,
|
59
|
-
max_count: max,
|
60
|
-
resolve_link_tos: true,
|
61
|
-
require_master: false
|
62
|
-
)
|
63
|
-
command('ReadStreamEventsForward', msg)
|
64
|
-
end
|
65
|
-
|
66
|
-
def subscribe_to_stream(handler, stream, resolve_link_tos = false)
|
67
|
-
msg = SubscribeToStream.new(event_stream_id: stream, resolve_link_tos: resolve_link_tos)
|
68
|
-
command('SubscribeToStream', msg, handler)
|
69
|
-
end
|
70
|
-
|
71
|
-
def unsubscribe_from_stream(subscription_uuid)
|
72
|
-
msg = UnsubscribeFromStream.new
|
73
|
-
command('UnsubscribeFromStream', msg, uuid: subscription_uuid)
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def command(*args)
|
79
|
-
connection.send_command(*args)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
require_relative 'estore/errors'
|
84
|
-
require_relative 'estore/package'
|
85
|
-
require_relative 'estore/messages'
|
86
|
-
require_relative 'estore/message_extensions'
|
87
|
-
require_relative 'estore/connection_context'
|
88
|
-
require_relative 'estore/connection'
|
89
|
-
require_relative 'estore/connection/buffer'
|
90
|
-
require_relative 'estore/connection/commands'
|
91
|
-
require_relative 'estore/subscription'
|
92
|
-
require_relative 'estore/catchup_subscription'
|
1
|
+
require 'estore/session'
|
2
|
+
require 'estore/errors'
|
3
|
+
require 'estore/package'
|
4
|
+
require 'estore/messages'
|
5
|
+
require 'estore/message_extensions'
|
6
|
+
require 'estore/connection_context'
|
7
|
+
require 'estore/connection'
|
8
|
+
require 'estore/connection/buffer'
|
9
|
+
require 'estore/connection/commands'
|
10
|
+
require 'estore/subscription'
|
11
|
+
require 'estore/catchup_subscription'
|
@@ -1,28 +1,29 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
# Catch-Up Subscriptions
|
3
3
|
#
|
4
|
-
# This kind of subscription specifies a starting point, in the form of an
|
5
|
-
# number or transaction file position. The given function will be
|
6
|
-
# from the starting point until the end of the stream,
|
7
|
-
#
|
8
|
-
# For example, if a starting point of 50 is specified when a stream has 100 events in it,
|
9
|
-
# the subscriber can expect to see events 51 through 100, and then any events subsequently
|
10
|
-
# written until such time as the subscription is dropped or closed.
|
4
|
+
# This kind of subscription specifies a starting point, in the form of an
|
5
|
+
# event number or transaction file position. The given function will be
|
6
|
+
# called for events from the starting point until the end of the stream,
|
7
|
+
# and then for subsequently written events.
|
11
8
|
#
|
9
|
+
# For example, if a starting point of 50 is specified when a stream has 100
|
10
|
+
# events in it, the subscriber can expect to see events 51 through 100, and
|
11
|
+
# then any events subsequently written until such time as the subscription is
|
12
|
+
# dropped or closed.
|
12
13
|
class CatchUpSubscription < Subscription
|
13
14
|
MAX_READ_BATCH = 100
|
14
15
|
|
15
16
|
attr_reader :from, :caught_up
|
16
17
|
|
17
|
-
def initialize(
|
18
|
-
super(
|
18
|
+
def initialize(estore, stream, from, options = {})
|
19
|
+
super(estore, stream, options)
|
20
|
+
|
19
21
|
@from = from
|
20
22
|
@caught_up = false
|
21
|
-
|
22
23
|
@mutex = Mutex.new
|
23
24
|
@queue = []
|
24
|
-
@position = from
|
25
|
-
@batch_size = batch_size
|
25
|
+
@position = from - 1
|
26
|
+
@batch_size = options[:batch_size] || 100
|
26
27
|
end
|
27
28
|
|
28
29
|
def on_catchup(&block)
|
@@ -48,17 +49,14 @@ class Eventstore
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def switch_to_live
|
51
|
-
log("fn=switch_to_live id=#{@id} stream=#{stream} at=start position=#{@position} queue_size=#{@queue.size}")
|
52
52
|
@mutex.synchronize do
|
53
53
|
dispatch_events(received_while_backfilling)
|
54
54
|
@queue = nil
|
55
55
|
@caught_up = true
|
56
56
|
end
|
57
|
-
log("fn=switch_to_live id=#{@id} stream=#{stream} at=finish position=#{@position}")
|
58
57
|
end
|
59
58
|
|
60
59
|
def backfill
|
61
|
-
log("fn=backfill at=start position=#{@position}")
|
62
60
|
loop do
|
63
61
|
events, finished = fetch_batch(@position + 1)
|
64
62
|
@mutex.synchronize do
|
@@ -66,7 +64,6 @@ class Eventstore
|
|
66
64
|
end
|
67
65
|
break if finished
|
68
66
|
end
|
69
|
-
log("fn=backfill at=finish position=#{@position}")
|
70
67
|
end
|
71
68
|
|
72
69
|
def dispatch_events(events)
|
@@ -74,7 +71,7 @@ class Eventstore
|
|
74
71
|
end
|
75
72
|
|
76
73
|
def fetch_batch(from)
|
77
|
-
prom =
|
74
|
+
prom = @estore.read(stream, from, @batch_size)
|
78
75
|
response = prom.sync
|
79
76
|
[Array(response.events), response.is_end_of_stream]
|
80
77
|
end
|
@@ -86,9 +83,5 @@ class Eventstore
|
|
86
83
|
def call_on_catchup
|
87
84
|
@on_catchup.call if @on_catchup
|
88
85
|
end
|
89
|
-
|
90
|
-
def log(msg)
|
91
|
-
puts(msg)
|
92
|
-
end
|
93
86
|
end
|
94
87
|
end
|
data/lib/estore/connection.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
# Connection owns the TCP socket, formats and sends commands over the socket.
|
3
|
-
# It also starts a background thread to read from the TCP socket and handle
|
4
|
-
# dispatching them to the calling app.
|
3
|
+
# It also starts a background thread to read from the TCP socket and handle
|
4
|
+
# received packages, dispatching them to the calling app.
|
5
5
|
class Connection
|
6
|
-
attr_reader :host, :port, :context, :
|
7
|
-
attr_reader :buffer, :mutex
|
6
|
+
attr_reader :host, :port, :context, :buffer, :mutex
|
8
7
|
|
9
8
|
def initialize(host, port, context)
|
10
9
|
@host = host
|
11
10
|
@port = Integer(port)
|
12
11
|
@context = context
|
13
|
-
|
14
12
|
@buffer = Buffer.new(&method(:on_received_package))
|
15
13
|
@mutex = Mutex.new
|
16
14
|
end
|
@@ -29,34 +27,36 @@ class Eventstore
|
|
29
27
|
|
30
28
|
mutex.synchronize do
|
31
29
|
promise = context.register_command(correlation_id, command, handler)
|
32
|
-
|
33
|
-
# puts "Sending to socket: #{frame.length} #{frame.inspect}"
|
34
|
-
to_write = frame.to_s
|
35
|
-
socket.write(to_write)
|
30
|
+
socket.write(frame.to_s)
|
36
31
|
promise
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
40
35
|
private
|
41
36
|
|
42
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
37
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
38
|
+
# rubocop:disable Metrics/MethodLength
|
43
39
|
def on_received_package(command, message, uuid, _flags)
|
44
|
-
# p(fn: "on_received_package", command: command)
|
45
|
-
# callback = context.received_package(uuid, command, message)
|
46
40
|
case command
|
47
|
-
when 'Pong'
|
48
|
-
|
49
|
-
when '
|
41
|
+
when 'Pong'
|
42
|
+
context.fulfill(uuid, 'Pong')
|
43
|
+
when 'HeartbeatRequestCommand'
|
44
|
+
send_command('HeartbeatResponseCommand')
|
45
|
+
when 'SubscriptionConfirmation'
|
46
|
+
context.fulfill(uuid, decode(SubscriptionConfirmation, message))
|
50
47
|
when 'ReadStreamEventsForwardCompleted'
|
51
|
-
context.
|
48
|
+
context.fulfill(uuid, decode(ReadStreamEventsCompleted, message))
|
52
49
|
when 'StreamEventAppeared'
|
53
50
|
resolved_event = decode(StreamEventAppeared, message).event
|
54
|
-
context.trigger(uuid,
|
55
|
-
when 'WriteEventsCompleted'
|
56
|
-
|
51
|
+
context.trigger(uuid, :event_appeared, resolved_event)
|
52
|
+
when 'WriteEventsCompleted'
|
53
|
+
on_write_events_completed(uuid, decode(WriteEventsCompleted, message))
|
54
|
+
else
|
55
|
+
raise command
|
57
56
|
end
|
58
57
|
end
|
59
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
58
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
60
60
|
|
61
61
|
def on_write_events_completed(uuid, response)
|
62
62
|
if response.result != OperationResult::Success
|
@@ -65,7 +65,7 @@ class Eventstore
|
|
65
65
|
return
|
66
66
|
end
|
67
67
|
|
68
|
-
context.
|
68
|
+
context.fulfill(uuid, response)
|
69
69
|
end
|
70
70
|
|
71
71
|
def decode(type, message)
|
@@ -91,7 +91,8 @@ class Eventstore
|
|
91
91
|
@socket
|
92
92
|
rescue TimeoutError, Errno::ECONNREFUSED, Errno::EHOSTDOWN,
|
93
93
|
Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ETIMEDOUT
|
94
|
-
raise CannotConnectError, "Error connecting to Eventstore on
|
94
|
+
raise CannotConnectError, "Error connecting to Eventstore on "\
|
95
|
+
"#{host.inspect}:#{port.inspect} (#{$ERROR_INFO.class})"
|
95
96
|
end
|
96
97
|
|
97
98
|
def process_downstream
|
@@ -1,6 +1,7 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
class Connection
|
3
|
-
# Buffer receives data from the TCP connection, and parses the binary
|
3
|
+
# Buffer receives data from the TCP connection, and parses the binary
|
4
|
+
# packages.
|
4
5
|
# Parsed packages are given back to the given handler as they are decoded.
|
5
6
|
class Buffer
|
6
7
|
attr_reader :buffer, :handler, :mutex
|
@@ -11,7 +12,9 @@ class Eventstore
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def <<(bytes)
|
14
|
-
bytes = bytes.force_encoding('BINARY') if
|
15
|
+
bytes = bytes.force_encoding('BINARY') if
|
16
|
+
bytes.respond_to? :force_encoding
|
17
|
+
|
15
18
|
mutex.synchronize do
|
16
19
|
@buffer << bytes
|
17
20
|
end
|
@@ -50,7 +53,7 @@ class Eventstore
|
|
50
53
|
|
51
54
|
def handle(pkg)
|
52
55
|
code, flags, uuid_bytes, message = parse(pkg)
|
53
|
-
command =
|
56
|
+
command = Estore::Connection.command_name(code)
|
54
57
|
handler.call(command, message, Package.parse_uuid(uuid_bytes), flags)
|
55
58
|
end
|
56
59
|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'promise'
|
2
2
|
|
3
|
-
class
|
3
|
+
class Estore
|
4
4
|
# Extension of a Ruby implementation of the Promises/A+ spec
|
5
5
|
# that carries the correlation id of the command.
|
6
6
|
# @see https://github.com/lgierth/promise.rb
|
7
7
|
class Promise < ::Promise
|
8
8
|
attr_reader :correlation_id
|
9
|
+
|
9
10
|
def initialize(correlation_id)
|
10
11
|
super()
|
11
12
|
@correlation_id = correlation_id
|
@@ -29,9 +30,8 @@ class Eventstore
|
|
29
30
|
@targets = {}
|
30
31
|
end
|
31
32
|
|
32
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/
|
33
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
33
34
|
def register_command(uuid, command, target = nil)
|
34
|
-
# p fn: "register_command", uuid: uuid, command: command
|
35
35
|
case command
|
36
36
|
when 'Ping' then promise(uuid)
|
37
37
|
when 'ReadStreamEventsForward' then promise(uuid)
|
@@ -39,26 +39,28 @@ class Eventstore
|
|
39
39
|
when 'WriteEvents' then promise(uuid)
|
40
40
|
when 'HeartbeatResponseCommand' then :nothing_to_do
|
41
41
|
when 'UnsubscribeFromStream' then :nothing_to_do
|
42
|
-
else
|
42
|
+
else raise "Unknown command #{command}"
|
43
43
|
end
|
44
44
|
end
|
45
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/
|
45
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
46
46
|
|
47
|
-
def
|
47
|
+
def fulfill(uuid, value)
|
48
48
|
prom = nil
|
49
|
+
|
49
50
|
mutex.synchronize do
|
50
51
|
prom = requests.delete(uuid)
|
51
52
|
end
|
52
|
-
|
53
|
+
|
53
54
|
prom.fulfill(value) if prom
|
54
55
|
end
|
55
56
|
|
56
57
|
def rejected_command(uuid, error)
|
57
58
|
prom = nil
|
59
|
+
|
58
60
|
mutex.synchronize do
|
59
61
|
prom = requests.delete(uuid)
|
60
62
|
end
|
61
|
-
|
63
|
+
|
62
64
|
prom.reject(error) if prom
|
63
65
|
end
|
64
66
|
|
data/lib/estore/errors.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
# @see https://github.com/EventStore/EventStore/blob/master/src/EventStore.Core/Data/ResolvedEvent.cs#L9
|
3
3
|
module OriginalEventMixin
|
4
4
|
def original_event
|
@@ -15,5 +15,5 @@ class Eventstore
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
Estore::ResolvedEvent.send(:include, Estore::OriginalEventMixin)
|
19
|
+
Estore::ResolvedIndexedEvent.send(:include, Estore::OriginalEventMixin)
|
data/lib/estore/messages.rb
CHANGED
data/lib/estore/package.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
# Package is a length-prefixed binary frame transferred over TCP
|
3
3
|
class Package
|
4
4
|
def self.encode(code, correlation_id, msg)
|
@@ -24,7 +24,9 @@ class Eventstore
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.parse_uuid(bytes)
|
27
|
-
a, b, c, d, e, f, g, h =
|
27
|
+
a, b, c, d, e, f, g, h =
|
28
|
+
*bytes.unpack('n*').map { |n| n.to_s(16) }.map { |n| n.rjust(4, '0') }
|
29
|
+
|
28
30
|
[a, b, '-', c, '-', d, '-', e, '-', f, g, h].join('')
|
29
31
|
end
|
30
32
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
class Estore # TODO: Change to module
|
4
|
+
# The Session class is responsible for maintaining a full-duplex connection
|
5
|
+
# between the client and the Event Store server.
|
6
|
+
# An Estore session is thread-safe, and it is recommended to only have one
|
7
|
+
# instance per application.
|
8
|
+
#
|
9
|
+
# All operations are handled fully asynchronously, returning a promise.
|
10
|
+
# If you need to execute synchronously, simply call .sync on the returned
|
11
|
+
# promise.
|
12
|
+
#
|
13
|
+
# To get maximum performance from the connection, it is recommended to use it
|
14
|
+
# asynchronously.
|
15
|
+
class Session
|
16
|
+
attr_reader :host, :port, :connection, :context
|
17
|
+
|
18
|
+
def initialize(host, port = 2113)
|
19
|
+
@host = host
|
20
|
+
@port = port
|
21
|
+
@context = ConnectionContext.new
|
22
|
+
@connection = Connection.new(host, port, context)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_error(error = nil, &block)
|
26
|
+
context.on_error(error, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
connection.close
|
31
|
+
end
|
32
|
+
|
33
|
+
def ping
|
34
|
+
command('Ping')
|
35
|
+
end
|
36
|
+
|
37
|
+
def read(stream, start, limit)
|
38
|
+
msg = ReadStreamEvents.new(
|
39
|
+
event_stream_id: stream,
|
40
|
+
from_event_number: start,
|
41
|
+
max_count: limit,
|
42
|
+
resolve_link_tos: true,
|
43
|
+
require_master: false
|
44
|
+
)
|
45
|
+
|
46
|
+
command('ReadStreamEventsForward', msg)
|
47
|
+
end
|
48
|
+
|
49
|
+
def append(stream, events, options = {})
|
50
|
+
msg = WriteEvents.new(
|
51
|
+
event_stream_id: stream,
|
52
|
+
expected_version: options[:expected_version] || -2,
|
53
|
+
events: Array(events).map { |event| new_event(event) },
|
54
|
+
require_master: true
|
55
|
+
)
|
56
|
+
|
57
|
+
command('WriteEvents', msg)
|
58
|
+
end
|
59
|
+
|
60
|
+
def subscribe(stream, handler, options = {})
|
61
|
+
msg = SubscribeToStream.new(
|
62
|
+
event_stream_id: stream,
|
63
|
+
resolve_link_tos: options[:resolve_link_tos]
|
64
|
+
)
|
65
|
+
|
66
|
+
command('SubscribeToStream', msg, handler)
|
67
|
+
end
|
68
|
+
|
69
|
+
def subscription(stream, options = {})
|
70
|
+
if options[:catch_up_from]
|
71
|
+
CatchUpSubscription.new(self, stream, options[:catch_up_from], options)
|
72
|
+
else
|
73
|
+
Subscription.new(self, stream, options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
CONTENT_TYPES = {
|
80
|
+
json: 1
|
81
|
+
}
|
82
|
+
|
83
|
+
def new_event(event)
|
84
|
+
uuid = event[:id] || SecureRandom.uuid
|
85
|
+
content_type = event.fetch(:content_type, :json)
|
86
|
+
|
87
|
+
NewEvent.new(
|
88
|
+
event_id: Package.encode_uuid(uuid),
|
89
|
+
event_type: event[:type],
|
90
|
+
data: event[:data],
|
91
|
+
data_content_type: CONTENT_TYPES.fetch(content_type, 0),
|
92
|
+
metadata_content_type: 1
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def command(*args)
|
97
|
+
connection.send_command(*args)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/estore/subscription.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class Estore
|
2
2
|
# Volatile Subscriptions
|
3
3
|
#
|
4
4
|
# This kind of subscription calls a given function for events written
|
@@ -8,12 +8,12 @@ class Eventstore
|
|
8
8
|
# the subscriber can expect to see event number 101 onwards until the time
|
9
9
|
# the subscription is closed or dropped.
|
10
10
|
class Subscription
|
11
|
-
attr_reader :
|
11
|
+
attr_reader :id, :stream, :resolve_link_tos, :position
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@
|
13
|
+
def initialize(estore, stream, options = {})
|
14
|
+
@estore = estore
|
15
15
|
@stream = stream
|
16
|
-
@resolve_link_tos = resolve_link_tos
|
16
|
+
@resolve_link_tos = options.fetch(:resolve_link_tos, true)
|
17
17
|
end
|
18
18
|
|
19
19
|
def on_error(&block)
|
@@ -29,14 +29,14 @@ class Eventstore
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def stop
|
32
|
-
|
32
|
+
@estore.unsubscribe(id) if id
|
33
33
|
@id = nil
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def subscribe
|
39
|
-
prom =
|
39
|
+
prom = @estore.subscribe(stream, self, resolve_link_tos: resolve_link_tos)
|
40
40
|
@id = prom.correlation_id
|
41
41
|
prom.sync
|
42
42
|
end
|
data/lib/estore/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.0.
|
1
|
+
class Estore
|
2
|
+
VERSION = '0.0.2'
|
3
3
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
describe Estore::Session do
|
6
|
+
subject(:session) { Estore::Session.new('127.0.0.1', 1113) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
@id = -1
|
10
|
+
end
|
11
|
+
|
12
|
+
def data
|
13
|
+
@id += 1
|
14
|
+
{ 'id' => @id }
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_data(wrapper)
|
18
|
+
JSON.parse(wrapper.event.data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def event
|
22
|
+
{
|
23
|
+
type: 'TestEvent',
|
24
|
+
data: data.to_json
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def events(n)
|
29
|
+
(1..n).map { event }
|
30
|
+
end
|
31
|
+
|
32
|
+
def random_stream
|
33
|
+
"test-#{SecureRandom.uuid}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def populate(count, stream = nil)
|
37
|
+
stream ||= random_stream
|
38
|
+
session.append(stream, events(count)).sync
|
39
|
+
stream
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'reads events from a stream' do
|
43
|
+
stream = populate(20)
|
44
|
+
read = session.read(stream, 0, 20).sync
|
45
|
+
|
46
|
+
expect(read.events.size).to be(20)
|
47
|
+
|
48
|
+
read.events.each_with_index do |event, index|
|
49
|
+
expect(parse_data(event)).to eql('id' => index)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'allows to make a live subscription' do
|
54
|
+
stream = random_stream
|
55
|
+
received = 0
|
56
|
+
|
57
|
+
sub = session.subscription(stream)
|
58
|
+
sub.on_error { |error| raise error.inspect }
|
59
|
+
|
60
|
+
sub.on_event do |event|
|
61
|
+
expect(parse_data(event)).to eql('id' => received)
|
62
|
+
received += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
sub.start
|
66
|
+
|
67
|
+
populate(50, stream)
|
68
|
+
|
69
|
+
Timeout.timeout(5) do
|
70
|
+
loop do
|
71
|
+
break if received >= 50
|
72
|
+
sleep(0.1)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'allows to make a catchup subscription' do
|
78
|
+
stream = random_stream
|
79
|
+
received = 0
|
80
|
+
|
81
|
+
populate(50, stream)
|
82
|
+
|
83
|
+
sub = session.subscription(stream, catch_up_from: 20)
|
84
|
+
sub.on_error { |error| raise error.inspect }
|
85
|
+
|
86
|
+
sub.on_event do |event|
|
87
|
+
expect(parse_data(event)).to eql('id' => received + 20)
|
88
|
+
received += 1
|
89
|
+
end
|
90
|
+
|
91
|
+
sub.start
|
92
|
+
|
93
|
+
Thread.new do
|
94
|
+
50.times do
|
95
|
+
populate(2, stream)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Timeout.timeout(5) do
|
100
|
+
loop do
|
101
|
+
break if received >= 130
|
102
|
+
sleep(0.1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
if RUBY_ENGINE == 'rbx'
|
3
|
+
require 'codeclimate-test-reporter'
|
4
|
+
CodeClimate::TestReporter.start
|
5
|
+
end
|
3
6
|
|
4
|
-
require '
|
7
|
+
require 'estore'
|
5
8
|
|
6
9
|
trap 'TTIN' do
|
7
10
|
Thread.list.each do |thread|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: estore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mathieu Ravaux
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-04-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: beefcake
|
@@ -43,32 +43,18 @@ dependencies:
|
|
43
43
|
name: bundler
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - "
|
46
|
+
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
48
|
+
version: '0'
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - "
|
53
|
+
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
55
|
+
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: rake
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - "~>"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '10.0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '10.0'
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: rspec
|
72
58
|
requirement: !ruby/object:Gem::Requirement
|
73
59
|
requirements:
|
74
60
|
- - ">="
|
@@ -82,21 +68,20 @@ dependencies:
|
|
82
68
|
- !ruby/object:Gem::Version
|
83
69
|
version: '0'
|
84
70
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
71
|
+
name: rubocop
|
86
72
|
requirement: !ruby/object:Gem::Requirement
|
87
73
|
requirements:
|
88
|
-
- - "
|
74
|
+
- - "~>"
|
89
75
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
76
|
+
version: 0.28.0
|
91
77
|
type: :development
|
92
78
|
prerelease: false
|
93
79
|
version_requirements: !ruby/object:Gem::Requirement
|
94
80
|
requirements:
|
95
|
-
- - "
|
81
|
+
- - "~>"
|
96
82
|
- !ruby/object:Gem::Version
|
97
|
-
version:
|
98
|
-
description: Event Store
|
99
|
-
Processing in JavaScript.
|
83
|
+
version: 0.28.0
|
84
|
+
description: An Event Store driver for Ruby
|
100
85
|
email:
|
101
86
|
- mathieu.ravaux@gmail.com
|
102
87
|
- hector0193@gmail.com
|
@@ -106,13 +91,13 @@ extra_rdoc_files: []
|
|
106
91
|
files:
|
107
92
|
- ".gitignore"
|
108
93
|
- ".rubocop.yml"
|
94
|
+
- ".travis.yml"
|
109
95
|
- ".yardopts"
|
110
96
|
- CHANGELOG.md
|
111
97
|
- Gemfile
|
112
98
|
- LICENSE.txt
|
113
99
|
- README.md
|
114
100
|
- Rakefile
|
115
|
-
- circle.yml
|
116
101
|
- estore.gemspec
|
117
102
|
- lib/estore.rb
|
118
103
|
- lib/estore/catchup_subscription.rb
|
@@ -124,13 +109,13 @@ files:
|
|
124
109
|
- lib/estore/message_extensions.rb
|
125
110
|
- lib/estore/messages.rb
|
126
111
|
- lib/estore/package.rb
|
112
|
+
- lib/estore/session.rb
|
127
113
|
- lib/estore/subscription.rb
|
128
114
|
- lib/estore/version.rb
|
129
|
-
- spec/
|
130
|
-
- spec/eventstore_spec.rb
|
115
|
+
- spec/integration/session_spec.rb
|
131
116
|
- spec/spec_helper.rb
|
132
117
|
- vendor/proto/ClientMessageDtos.proto
|
133
|
-
homepage: https://github.com/rom-eventstore/
|
118
|
+
homepage: https://github.com/rom-eventstore/estore
|
134
119
|
licenses:
|
135
120
|
- MIT
|
136
121
|
metadata: {}
|
@@ -153,8 +138,7 @@ rubyforge_project:
|
|
153
138
|
rubygems_version: 2.4.6
|
154
139
|
signing_key:
|
155
140
|
specification_version: 4
|
156
|
-
summary:
|
141
|
+
summary: An Event Store driver for Ruby
|
157
142
|
test_files:
|
158
|
-
- spec/
|
159
|
-
- spec/eventstore_spec.rb
|
143
|
+
- spec/integration/session_spec.rb
|
160
144
|
- spec/spec_helper.rb
|
data/circle.yml
DELETED
data/spec/db/.gitkeep
DELETED
File without changes
|
data/spec/eventstore_spec.rb
DELETED
@@ -1,103 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Eventstore do
|
4
|
-
let(:es) { Eventstore.new('localhost', 1113) }
|
5
|
-
subject { new_estore }
|
6
|
-
let(:injector) { new_estore }
|
7
|
-
|
8
|
-
def new_estore
|
9
|
-
es = Eventstore.new('localhost', 1113)
|
10
|
-
es.on_error { |error| Thread.main.raise(error) }
|
11
|
-
es
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'supports the PING command' do
|
15
|
-
Timeout.timeout(1) do
|
16
|
-
promise = es.ping
|
17
|
-
result = promise.sync
|
18
|
-
expect(result).to eql 'Pong'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def inject_event(stream)
|
23
|
-
event_type = 'TestEvent'
|
24
|
-
data = JSON.generate(at: Time.now.to_i, foo: 'bar')
|
25
|
-
event = injector.new_event(event_type, data)
|
26
|
-
# puts ">#{stream}\t\t#{event.inspect}"
|
27
|
-
prom = injector.write_events(stream, event)
|
28
|
-
prom.sync
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'dumps the content of the outlet stream from the last checkpoint' do
|
32
|
-
inject_events('outlet', 50)
|
33
|
-
events = subject.read_stream_events_forward('outlet', 1, 20).sync
|
34
|
-
expect(events).to be_kind_of(Eventstore::ReadStreamEventsCompleted)
|
35
|
-
events.events.each do |event|
|
36
|
-
expect(event).to be_kind_of(Eventstore::ResolvedIndexedEvent)
|
37
|
-
JSON.parse(event.event.data)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def inject_events_async(stream, target)
|
42
|
-
Thread.new do
|
43
|
-
begin
|
44
|
-
inject_events(stream, target)
|
45
|
-
rescue => error
|
46
|
-
puts(error.inspect)
|
47
|
-
puts(*error.backtrace)
|
48
|
-
Thread.main.raise(error)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def inject_events(stream, target)
|
54
|
-
target.times do |_i|
|
55
|
-
inject_event(stream)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'allows to make a live subscription' do
|
60
|
-
stream = "catchup-test-#{SecureRandom.uuid}"
|
61
|
-
received = 0
|
62
|
-
|
63
|
-
sub = subject.new_subscription(stream)
|
64
|
-
sub.on_event { |_event| received += 1 }
|
65
|
-
sub.on_error { |error| fail(error.inspect) }
|
66
|
-
sub.start
|
67
|
-
|
68
|
-
inject_events(stream, 50)
|
69
|
-
|
70
|
-
Timeout.timeout(20) do
|
71
|
-
loop do
|
72
|
-
break if received >= 50
|
73
|
-
sleep(0.1)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'allows to make a catch-up subscription' do
|
79
|
-
stream = "catchup-test-#{SecureRandom.uuid}"
|
80
|
-
received = 0
|
81
|
-
mutex = Mutex.new
|
82
|
-
|
83
|
-
expect(subject.ping.sync).to eql 'Pong'
|
84
|
-
|
85
|
-
# puts "stream: #{stream}"
|
86
|
-
|
87
|
-
inject_events(stream, 1220)
|
88
|
-
|
89
|
-
sub = subject.new_catchup_subscription(stream, -1)
|
90
|
-
sub.on_event { |_event| mutex.synchronize { received += 1 } }
|
91
|
-
sub.on_error { |error| fail error.inspect }
|
92
|
-
sub.start
|
93
|
-
|
94
|
-
inject_events_async(stream, 780)
|
95
|
-
|
96
|
-
Timeout.timeout(10) do
|
97
|
-
loop do
|
98
|
-
break if received >= 2000
|
99
|
-
sleep(0.1)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|