estore 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 Version](https://badge.fury.io/rb/estore.svg)][gem]
|
11
|
+
[![Build Status](https://travis-ci.org/rom-eventstore/estore.svg?branch=master)][travis]
|
12
|
+
[![Dependency Status](https://gemnasium.com/rom-eventstore/estore.png)][gemnasium]
|
13
|
+
[![Code Climate](https://codeclimate.com/github/rom-eventstore/estore/badges/gpa.svg)][codeclimate]
|
14
|
+
[![Test Coverage](https://codeclimate.com/github/rom-eventstore/estore/badges/coverage.svg)][codeclimate]
|
15
|
+
[![Inline docs](http://inch-ci.org/github/rom-eventstore/estore.svg?branch=master)][inchpages]
|
16
|
+
[![Join the chat at https://gitter.im/rom-eventstore/estore](https://badges.gitter.im/Join%20Chat.svg)][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
|