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 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