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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a58e5f588a5c8c0d2b25fedf68429869effc0dd
4
- data.tar.gz: f59bc201d6f2c2fcc9dcc5f1d2bb825a70f8aac7
3
+ metadata.gz: 788e47dab75704db758ce692febbfe443f923cf8
4
+ data.tar.gz: 7f9e72691c2199897cd933b58fff8069535d0a24
5
5
  SHA512:
6
- metadata.gz: 19c2507e00eca563cb754a645cc4b6a2dde55346a1f99d6a8b82ca406b30d73128e8e2ca5ea5761733514d33464b8a3fc16e7d4ba0618b6c5fae0fa2e08716ab
7
- data.tar.gz: 43e2a30135b4b708cd9593e41a51692de8501b4b736db77e2aaf0a281caa7af64f84de2de090192daa626ce5e3b493fce2c298a005b0e0ac97ade2fc310066df
6
+ metadata.gz: 936915386686cddcd4c6a03c07e237007ed42db68216b4e0abced32680fff2b41947286df3e0bdd15a3b5e8ce09be9b7f0816bbc40b0f114b91f458433f686e6
7
+ data.tar.gz: 1d433f68f8c8abea1a10d7cdb6a2eb034a7f60280596e4d3851e5acd09e10a190d0461fd669e015bca74aee5aec4f87cbefb8b0a792e545d4d0b50dabd1c0427
@@ -7,5 +7,69 @@ AllCops:
7
7
  Metrics/LineLength:
8
8
  Max: 140
9
9
 
10
- # Style/Documentation:
11
- # Enabled: false
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
@@ -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
- # Eventstore
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
- [![Join the chat at https://gitter.im/mathieuravaux/eventstore-ruby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mathieuravaux/eventstore-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8
+ # Estore
4
9
 
5
- Ruby client library Eventstore
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
- Eventstore is an open-source, functional database
8
- with Complex Event Processing in JavaScript
18
+ An [Event Store](http://geteventstore.com/) driver for Ruby
9
19
 
10
- [![Circle CI](https://circleci.com/gh/mathieuravaux/eventstore-ruby.svg?style=svg)](https://circleci.com/gh/mathieuravaux/eventstore-ruby)
20
+ ## Installation
11
21
 
12
- ## Install
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
- ## Contributing
44
+ ## Credits
29
45
 
30
- 1. Fork it ( https://github.com/mathieuravaux/eventstore/fork )
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/gem_tasks'
1
+ require 'bundler'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- VENDORED_PROTO = 'vendor/proto/ClientMessageDtos.proto'
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
- task default: :spec
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
- system("mkdir -p #{PROTO_DIR}")
16
- beefcake_bin = Bundler.bin_path.join('protoc-gen-beefcake').to_s
17
- if system("BEEFCAKE_NAMESPACE=Eventstore protoc --plugin=#{beefcake_bin} --beefcake_out lib/estore #{VENDORED_PROTO}")
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/' lib/estore/messages.rb")
36
+ system("sed -i '' 's/module Eventstore/class Eventstore/' "\
37
+ "lib/estore/messages.rb")
20
38
  end
21
39
  end
@@ -5,24 +5,23 @@ require 'estore/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'estore'
8
- spec.version = EventStore::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 = 'Ruby client API for the Event Store.'
12
- spec.description = 'Event Store is an open-source, functional database with Complex Event Processing in JavaScript.'
13
- spec.homepage = 'https://github.com/rom-eventstore/eventstore-ruby'
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(/^bin\//) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(/^spec\//)
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', '~> 1.7'
25
- spec.add_development_dependency 'rake', '~> 10.0'
26
- spec.add_development_dependency 'rspec'
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
@@ -1,92 +1,11 @@
1
- require 'securerandom'
2
-
3
- # The Eventstore class is responsible for maintaining a full-duplex connection
4
- # between the client and the Event Store server.
5
- # EventStore is thread-safe, and it is recommended that only one instance per application is created.
6
- #
7
- # All operations are handled fully asynchronously, returning a promise.
8
- # If you need to execute synchronously, simply call .sync on the returned promise.
9
- #
10
- # To get maximum performance from the connection, it is recommended that it be used asynchronously.
11
- class Eventstore
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 Eventstore
1
+ class Estore
2
2
  # Catch-Up Subscriptions
3
3
  #
4
- # This kind of subscription specifies a starting point, in the form of an event
5
- # number or transaction file position. The given function will be called for events
6
- # from the starting point until the end of the stream, and then for subsequently written events.
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(eventstore, stream, from, resolve_link_tos: true, batch_size: MAX_READ_BATCH)
18
- super(eventstore, stream, resolve_link_tos: resolve_link_tos)
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 = eventstore.read_stream_events_forward(stream, from, @batch_size)
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
@@ -1,16 +1,14 @@
1
- class Eventstore
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 received packages,
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, :error_handler
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
- # puts "Sending #{command} command with correlation id #{correlation_id}"
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, Metrics/MethodLength
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' then context.fulfilled_command(uuid, 'Pong')
48
- when 'HeartbeatRequestCommand' then send_command('HeartbeatResponseCommand')
49
- when 'SubscriptionConfirmation' then context.fulfilled_command(uuid, decode(SubscriptionConfirmation, message))
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.fulfilled_command(uuid, decode(ReadStreamEventsCompleted, message))
48
+ context.fulfill(uuid, decode(ReadStreamEventsCompleted, message))
52
49
  when 'StreamEventAppeared'
53
50
  resolved_event = decode(StreamEventAppeared, message).event
54
- context.trigger(uuid, 'event_appeared', resolved_event)
55
- when 'WriteEventsCompleted' then on_write_events_completed(uuid, decode(WriteEventsCompleted, message))
56
- else fail command
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, Metrics/MethodLength
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.fulfilled_command(uuid, response)
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 #{host.inspect}:#{port.inspect} (#{$ERROR_INFO.class})"
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 Eventstore
1
+ class Estore
2
2
  class Connection
3
- # Buffer receives data from the TCP connection, and parses the binary packages.
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 bytes.respond_to? :force_encoding
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 = Eventstore::Connection.command_name(code)
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,4 +1,4 @@
1
- class Eventstore
1
+ class Estore
2
2
  # Mapping between command names and codes
3
3
  # From https://github.com/EventStore/EventStore/blob/master/src/EventStore.ClientAPI/SystemData/TcpCommand.cs
4
4
  class Connection
@@ -1,11 +1,12 @@
1
1
  require 'promise'
2
2
 
3
- class Eventstore
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/AbcSize, Metrics/MethodLength
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 fail("Unknown command #{command}")
42
+ else raise "Unknown command #{command}"
43
43
  end
44
44
  end
45
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
45
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
46
46
 
47
- def fulfilled_command(uuid, value)
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
- # p fn: "fulfilled_command", uuid: uuid, prom: prom, requests: requests
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
- # p fn: "fulfilled_command", uuid: uuid, prom: prom, requests: requests
63
+
62
64
  prom.reject(error) if prom
63
65
  end
64
66
 
@@ -1,4 +1,4 @@
1
- class Eventstore
1
+ class Estore
2
2
  class CannotConnectError < RuntimeError; end
3
3
  class DisconnectionError < RuntimeError; end
4
4
  end
@@ -1,4 +1,4 @@
1
- class Eventstore
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
- Eventstore::ResolvedEvent.include(Eventstore::OriginalEventMixin)
19
- Eventstore::ResolvedIndexedEvent.include(Eventstore::OriginalEventMixin)
18
+ Estore::ResolvedEvent.send(:include, Estore::OriginalEventMixin)
19
+ Estore::ResolvedIndexedEvent.send(:include, Estore::OriginalEventMixin)
@@ -1,7 +1,7 @@
1
1
  ## Generated from vendor/proto/ClientMessageDtos.proto for EventStore.Client.Messages
2
2
  require 'beefcake'
3
3
 
4
- class Eventstore
4
+ class Estore
5
5
  module OperationResult
6
6
  Success = 0
7
7
  PrepareTimeout = 1
@@ -1,4 +1,4 @@
1
- class Eventstore
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 = *bytes.unpack('n*').map { |n| n.to_s(16) }.map { |n| n.rjust(4, '0') }
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
@@ -1,4 +1,4 @@
1
- class Eventstore
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 :eventstore, :stream, :resolve_link_tos, :id, :position
11
+ attr_reader :id, :stream, :resolve_link_tos, :position
12
12
 
13
- def initialize(eventstore, stream, resolve_link_tos: true)
14
- @eventstore = eventstore
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
- eventstore.unsubscribe_from_stream(id) if id
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 = eventstore.subscribe_to_stream(self, stream, resolve_link_tos)
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
@@ -1,3 +1,3 @@
1
- module EventStore
2
- VERSION = '0.0.1'
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
@@ -1,7 +1,10 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
- require 'estore'
1
+ # encoding: utf-8
2
+ if RUBY_ENGINE == 'rbx'
3
+ require 'codeclimate-test-reporter'
4
+ CodeClimate::TestReporter.start
5
+ end
3
6
 
4
- require 'json'
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.1
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-03-31 00:00:00.000000000 Z
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: '1.7'
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: '1.7'
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: pry
71
+ name: rubocop
86
72
  requirement: !ruby/object:Gem::Requirement
87
73
  requirements:
88
- - - ">="
74
+ - - "~>"
89
75
  - !ruby/object:Gem::Version
90
- version: '0'
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: '0'
98
- description: Event Store is an open-source, functional database with Complex Event
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/db/.gitkeep
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/eventstore-ruby
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: Ruby client API for the Event Store.
141
+ summary: An Event Store driver for Ruby
157
142
  test_files:
158
- - spec/db/.gitkeep
159
- - spec/eventstore_spec.rb
143
+ - spec/integration/session_spec.rb
160
144
  - spec/spec_helper.rb
data/circle.yml DELETED
@@ -1,3 +0,0 @@
1
- notify:
2
- webhooks:
3
- - url: https://webhooks.gitter.im/e/e20bfa93dc2e11895805
File without changes
@@ -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