estore 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/estore/catchup_subscription.rb +87 -0
- data/lib/estore/connection/buffer.rb +20 -10
- data/lib/estore/connection/{protocol.rb → commands.rb} +0 -0
- data/lib/estore/connection.rb +54 -18
- data/lib/estore/connection_context.rb +75 -11
- data/lib/estore/errors.rb +2 -2
- data/lib/estore/session.rb +47 -21
- data/lib/estore/subscription.rb +57 -0
- data/lib/estore/version.rb +1 -1
- data/lib/estore.rb +3 -2
- data/spec/integration/session_spec.rb +30 -44
- metadata +4 -11
- data/lib/estore/commands/append.rb +0 -56
- data/lib/estore/commands/base.rb +0 -51
- data/lib/estore/commands/catch_up_subscription.rb +0 -60
- data/lib/estore/commands/ping.rb +0 -16
- data/lib/estore/commands/promise.rb +0 -19
- data/lib/estore/commands/read_batch.rb +0 -25
- data/lib/estore/commands/read_forward.rb +0 -39
- data/lib/estore/commands/subscription.rb +0 -53
- data/lib/estore/commands.rb +0 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4ffaed9c9b3874fa4950a194ebc95a84474f5891
         | 
| 4 | 
            +
              data.tar.gz: aa3d8f02bba1e085557a7fc0567b619d28b97467
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1dc0acb4b9d8c53e5c58547cbdf610343c221646444f44341b0365d082e327f32c2c8234ac4efe1b8d7ee4faa3670b4c0abd4add84182cd5668085aa61de584e
         | 
| 7 | 
            +
              data.tar.gz: 329da382ffb6ae45ad9c0033598232a2b757d0461a7071b569f479b1c3b75fc48e1fdb918b48eb7672b170732ef8659f5550b2ccabac3ed6cb48c807810f74db
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            module Estore
         | 
| 2 | 
            +
              # Catch-Up Subscriptions
         | 
| 3 | 
            +
              #
         | 
| 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.
         | 
| 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.
         | 
| 13 | 
            +
              class CatchUpSubscription < Subscription
         | 
| 14 | 
            +
                MAX_READ_BATCH = 100
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                attr_reader :from, :caught_up
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def initialize(estore, stream, from, options = {})
         | 
| 19 | 
            +
                  super(estore, stream, options)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  @from = from
         | 
| 22 | 
            +
                  @caught_up = false
         | 
| 23 | 
            +
                  @mutex = Mutex.new
         | 
| 24 | 
            +
                  @queue = []
         | 
| 25 | 
            +
                  @position = from - 1
         | 
| 26 | 
            +
                  @batch_size = options[:batch_size] || 100
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def on_catchup(&block)
         | 
| 30 | 
            +
                  @on_catchup = block if block
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def start
         | 
| 34 | 
            +
                  subscribe
         | 
| 35 | 
            +
                  backfill
         | 
| 36 | 
            +
                  switch_to_live
         | 
| 37 | 
            +
                  call_on_catchup
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                private
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def event_appeared(event)
         | 
| 43 | 
            +
                  unless caught_up
         | 
| 44 | 
            +
                    @mutex.synchronize do
         | 
| 45 | 
            +
                      @queue.push(event) unless caught_up
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  dispatch(event) if caught_up
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def switch_to_live
         | 
| 52 | 
            +
                  @mutex.synchronize do
         | 
| 53 | 
            +
                    dispatch_events(received_while_backfilling)
         | 
| 54 | 
            +
                    @queue = nil
         | 
| 55 | 
            +
                    @caught_up = true
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def backfill
         | 
| 60 | 
            +
                  loop do
         | 
| 61 | 
            +
                    events, finished = fetch_batch(@position + 1)
         | 
| 62 | 
            +
                    @mutex.synchronize do
         | 
| 63 | 
            +
                      dispatch_events(events)
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                    break if finished
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def dispatch_events(events)
         | 
| 70 | 
            +
                  events.each { |e| dispatch(e) }
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def fetch_batch(from)
         | 
| 74 | 
            +
                  prom = @estore.read(stream, from, @batch_size)
         | 
| 75 | 
            +
                  response = prom.sync
         | 
| 76 | 
            +
                  [Array(response.events), response.is_end_of_stream]
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def received_while_backfilling
         | 
| 80 | 
            +
                  @queue.find_all { |event| event.original_event_number > @position }
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def call_on_catchup
         | 
| 84 | 
            +
                  @on_catchup.call if @on_catchup
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -4,6 +4,7 @@ module Estore | |
| 4 4 | 
             
                # packages.
         | 
| 5 5 | 
             
                # Parsed packages are given back to the given handler as they are decoded.
         | 
| 6 6 | 
             
                class Buffer
         | 
| 7 | 
            +
                  attr_reader :buffer, :handler, :mutex
         | 
| 7 8 | 
             
                  def initialize(&block)
         | 
| 8 9 | 
             
                    @mutex = Mutex.new
         | 
| 9 10 | 
             
                    @buffer = ''.force_encoding('BINARY')
         | 
| @@ -14,37 +15,46 @@ module Estore | |
| 14 15 | 
             
                    bytes = bytes.force_encoding('BINARY') if
         | 
| 15 16 | 
             
                      bytes.respond_to? :force_encoding
         | 
| 16 17 |  | 
| 17 | 
            -
                     | 
| 18 | 
            +
                    mutex.synchronize do
         | 
| 18 19 | 
             
                      @buffer << bytes
         | 
| 19 20 | 
             
                    end
         | 
| 20 21 |  | 
| 21 | 
            -
                     | 
| 22 | 
            +
                    consume_available_packages
         | 
| 22 23 | 
             
                  end
         | 
| 23 24 |  | 
| 24 | 
            -
                  def  | 
| 25 | 
            -
                    while  | 
| 25 | 
            +
                  def consume_available_packages
         | 
| 26 | 
            +
                    while consume_package
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def consume_package
         | 
| 31 | 
            +
                    pkg = read_package
         | 
| 32 | 
            +
                    if pkg
         | 
| 26 33 | 
             
                      handle(pkg)
         | 
| 27 34 | 
             
                      discard_bytes(pkg)
         | 
| 35 | 
            +
                      true
         | 
| 36 | 
            +
                    else
         | 
| 37 | 
            +
                      false
         | 
| 28 38 | 
             
                    end
         | 
| 29 39 | 
             
                  end
         | 
| 30 40 |  | 
| 31 41 | 
             
                  def read_package
         | 
| 32 | 
            -
                    return nil if  | 
| 33 | 
            -
                    package_length =  | 
| 34 | 
            -
                    bytes =  | 
| 42 | 
            +
                    return nil if buffer.length < 4
         | 
| 43 | 
            +
                    package_length = buffer[0...4].unpack('l<').first
         | 
| 44 | 
            +
                    bytes = buffer[4...(4 + package_length)].dup
         | 
| 35 45 | 
             
                    bytes if bytes.bytesize >= package_length
         | 
| 36 46 | 
             
                  end
         | 
| 37 47 |  | 
| 38 48 | 
             
                  def discard_bytes(pkg)
         | 
| 39 | 
            -
                     | 
| 40 | 
            -
                      @buffer =  | 
| 49 | 
            +
                    mutex.synchronize do
         | 
| 50 | 
            +
                      @buffer = buffer[(4 + pkg.bytesize)..-1]
         | 
| 41 51 | 
             
                    end
         | 
| 42 52 | 
             
                  end
         | 
| 43 53 |  | 
| 44 54 | 
             
                  def handle(pkg)
         | 
| 45 55 | 
             
                    code, flags, uuid_bytes, message = parse(pkg)
         | 
| 46 56 | 
             
                    command = Estore::Connection.command_name(code)
         | 
| 47 | 
            -
                     | 
| 57 | 
            +
                    handler.call(command, message, Package.parse_uuid(uuid_bytes), flags)
         | 
| 48 58 | 
             
                  end
         | 
| 49 59 |  | 
| 50 60 | 
             
                  def parse(pkg)
         | 
| 
            File without changes
         | 
    
        data/lib/estore/connection.rb
    CHANGED
    
    | @@ -3,9 +3,7 @@ module Estore | |
| 3 3 | 
             
              # It also starts a background thread to read from the TCP socket and handle
         | 
| 4 4 | 
             
              # received packages, dispatching them to the calling app.
         | 
| 5 5 | 
             
              class Connection
         | 
| 6 | 
            -
                 | 
| 7 | 
            -
             | 
| 8 | 
            -
                delegate [:register, :remove] => :@context
         | 
| 6 | 
            +
                attr_reader :host, :port, :context, :buffer, :mutex
         | 
| 9 7 |  | 
| 10 8 | 
             
                def initialize(host, port, context)
         | 
| 11 9 | 
             
                  @host = host
         | 
| @@ -20,25 +18,65 @@ module Estore | |
| 20 18 | 
             
                  socket.close
         | 
| 21 19 | 
             
                end
         | 
| 22 20 |  | 
| 23 | 
            -
                def  | 
| 21 | 
            +
                def send_command(command, msg = nil, handler = nil, uuid = nil)
         | 
| 22 | 
            +
                  code = COMMANDS.fetch(command)
         | 
| 24 23 | 
             
                  msg.validate! if msg
         | 
| 25 24 |  | 
| 26 | 
            -
                   | 
| 27 | 
            -
                  frame = Package.encode(code,  | 
| 25 | 
            +
                  correlation_id = uuid || SecureRandom.uuid
         | 
| 26 | 
            +
                  frame = Package.encode(code, correlation_id, msg)
         | 
| 28 27 |  | 
| 29 | 
            -
                   | 
| 28 | 
            +
                  mutex.synchronize do
         | 
| 29 | 
            +
                    promise = context.register_command(correlation_id, command, handler)
         | 
| 30 30 | 
             
                    socket.write(frame.to_s)
         | 
| 31 | 
            +
                    promise
         | 
| 31 32 | 
             
                  end
         | 
| 32 33 | 
             
                end
         | 
| 33 34 |  | 
| 34 35 | 
             
                private
         | 
| 35 36 |  | 
| 36 | 
            -
                 | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 37 | 
            +
                # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
         | 
| 38 | 
            +
                # rubocop:disable Metrics/MethodLength
         | 
| 39 | 
            +
                def on_received_package(command, message, uuid, _flags)
         | 
| 40 | 
            +
                  case command
         | 
| 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))
         | 
| 47 | 
            +
                  when 'ReadStreamEventsForwardCompleted'
         | 
| 48 | 
            +
                    context.fulfill(uuid, decode(ReadStreamEventsCompleted, message))
         | 
| 49 | 
            +
                  when 'StreamEventAppeared'
         | 
| 50 | 
            +
                    resolved_event = decode(StreamEventAppeared, message).event
         | 
| 51 | 
            +
                    context.trigger(uuid, :event_appeared, resolved_event)
         | 
| 52 | 
            +
                  when 'WriteEventsCompleted'
         | 
| 53 | 
            +
                    on_write_events_completed(uuid, decode(WriteEventsCompleted, message))
         | 
| 39 54 | 
             
                  else
         | 
| 40 | 
            -
                     | 
| 55 | 
            +
                    raise command
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
         | 
| 59 | 
            +
                # rubocop:enable Metrics/MethodLength
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def on_write_events_completed(uuid, response)
         | 
| 62 | 
            +
                  if response.result != OperationResult::Success
         | 
| 63 | 
            +
                    p fn: 'on_write_events_completed', at: error, result: response.result
         | 
| 64 | 
            +
                    context.rejected_command(uuid, response)
         | 
| 65 | 
            +
                    return
         | 
| 41 66 | 
             
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  context.fulfill(uuid, response)
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def decode(type, message)
         | 
| 72 | 
            +
                  type.decode(message)
         | 
| 73 | 
            +
                rescue => error
         | 
| 74 | 
            +
                  puts "Protobuf decoding error on connection #{object_id}"
         | 
| 75 | 
            +
                  puts error.inspect
         | 
| 76 | 
            +
                  p type: type, message: message
         | 
| 77 | 
            +
                  puts "\n\n"
         | 
| 78 | 
            +
                  puts(*error.backtrace)
         | 
| 79 | 
            +
                  raise error
         | 
| 42 80 | 
             
                end
         | 
| 43 81 |  | 
| 44 82 | 
             
                def socket
         | 
| @@ -46,7 +84,7 @@ module Estore | |
| 46 84 | 
             
                end
         | 
| 47 85 |  | 
| 48 86 | 
             
                def connect
         | 
| 49 | 
            -
                  @socket = TCPSocket.open( | 
| 87 | 
            +
                  @socket = TCPSocket.open(host, port)
         | 
| 50 88 | 
             
                  Thread.new do
         | 
| 51 89 | 
             
                    process_downstream
         | 
| 52 90 | 
             
                  end
         | 
| @@ -54,12 +92,12 @@ module Estore | |
| 54 92 | 
             
                rescue TimeoutError, Errno::ECONNREFUSED, Errno::EHOSTDOWN,
         | 
| 55 93 | 
             
                       Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ETIMEDOUT
         | 
| 56 94 | 
             
                  raise CannotConnectError, "Error connecting to Eventstore on "\
         | 
| 57 | 
            -
                    "#{ | 
| 95 | 
            +
                    "#{host.inspect}:#{port.inspect} (#{$ERROR_INFO.class})"
         | 
| 58 96 | 
             
                end
         | 
| 59 97 |  | 
| 60 98 | 
             
                def process_downstream
         | 
| 61 99 | 
             
                  loop do
         | 
| 62 | 
            -
                     | 
| 100 | 
            +
                    buffer << socket.sysread(4096)
         | 
| 63 101 | 
             
                  end
         | 
| 64 102 | 
             
                rescue IOError, EOFError
         | 
| 65 103 | 
             
                  on_disconnect
         | 
| @@ -74,10 +112,8 @@ module Estore | |
| 74 112 | 
             
                end
         | 
| 75 113 |  | 
| 76 114 | 
             
                def on_exception(error)
         | 
| 77 | 
            -
                  puts "process_downstream_error"
         | 
| 78 | 
            -
                   | 
| 79 | 
            -
                  puts error.backtrace
         | 
| 80 | 
            -
                  @context.on_error(error)
         | 
| 115 | 
            +
                  puts "process_downstream_error #{error.inspect}"
         | 
| 116 | 
            +
                  context.on_error(error)
         | 
| 81 117 | 
             
                end
         | 
| 82 118 | 
             
              end
         | 
| 83 119 | 
             
            end
         | 
| @@ -1,28 +1,92 @@ | |
| 1 1 | 
             
            require 'promise'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Estore
         | 
| 4 | 
            -
              #  | 
| 4 | 
            +
              # Extension of a Ruby implementation of the Promises/A+ spec
         | 
| 5 | 
            +
              # that carries the correlation id of the command.
         | 
| 6 | 
            +
              # @see https://github.com/lgierth/promise.rb
         | 
| 7 | 
            +
              class Promise < ::Promise
         | 
| 8 | 
            +
                attr_reader :correlation_id
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(correlation_id)
         | 
| 11 | 
            +
                  super()
         | 
| 12 | 
            +
                  @correlation_id = correlation_id
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def wait
         | 
| 16 | 
            +
                  t = Thread.current
         | 
| 17 | 
            +
                  resume = proc { t.wakeup }
         | 
| 18 | 
            +
                  self.then(resume, resume)
         | 
| 19 | 
            +
                  sleep
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Registry storing handlers for the outstanding commands and
         | 
| 24 | 
            +
              # current subscriptions
         | 
| 5 25 | 
             
              class ConnectionContext
         | 
| 26 | 
            +
                attr_reader :mutex,  :requests, :targets
         | 
| 6 27 | 
             
                def initialize
         | 
| 7 28 | 
             
                  @mutex = Mutex.new
         | 
| 8 | 
            -
                  @ | 
| 29 | 
            +
                  @requests = {}
         | 
| 30 | 
            +
                  @targets = {}
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
         | 
| 34 | 
            +
                def register_command(uuid, command, target = nil)
         | 
| 35 | 
            +
                  case command
         | 
| 36 | 
            +
                  when 'Ping' then promise(uuid)
         | 
| 37 | 
            +
                  when 'ReadStreamEventsForward' then promise(uuid)
         | 
| 38 | 
            +
                  when 'SubscribeToStream' then promise(uuid, target)
         | 
| 39 | 
            +
                  when 'WriteEvents' then promise(uuid)
         | 
| 40 | 
            +
                  when 'HeartbeatResponseCommand' then :nothing_to_do
         | 
| 41 | 
            +
                  when 'UnsubscribeFromStream' then :nothing_to_do
         | 
| 42 | 
            +
                  else raise "Unknown command #{command}"
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def fulfill(uuid, value)
         | 
| 48 | 
            +
                  prom = nil
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  mutex.synchronize do
         | 
| 51 | 
            +
                    prom = requests.delete(uuid)
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  prom.fulfill(value) if prom
         | 
| 9 55 | 
             
                end
         | 
| 10 56 |  | 
| 11 | 
            -
                def  | 
| 12 | 
            -
                   | 
| 13 | 
            -
             | 
| 57 | 
            +
                def rejected_command(uuid, error)
         | 
| 58 | 
            +
                  prom = nil
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  mutex.synchronize do
         | 
| 61 | 
            +
                    prom = requests.delete(uuid)
         | 
| 14 62 | 
             
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  prom.reject(error) if prom
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def trigger(uuid, method, *args)
         | 
| 68 | 
            +
                  target = mutex.synchronize { targets[uuid] }
         | 
| 69 | 
            +
                  return if target.nil?
         | 
| 70 | 
            +
                  target.__send__(method, *args)
         | 
| 15 71 | 
             
                end
         | 
| 16 72 |  | 
| 17 | 
            -
                def  | 
| 18 | 
            -
                   | 
| 19 | 
            -
                    @ | 
| 73 | 
            +
                def on_error(error = nil, &block)
         | 
| 74 | 
            +
                  if block
         | 
| 75 | 
            +
                    @error_handler = block
         | 
| 76 | 
            +
                  else
         | 
| 77 | 
            +
                    @error_handler.call(error) if @error_handler
         | 
| 20 78 | 
             
                  end
         | 
| 21 79 | 
             
                end
         | 
| 22 80 |  | 
| 23 | 
            -
                 | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 81 | 
            +
                private
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def promise(uuid, target = nil)
         | 
| 84 | 
            +
                  prom = Promise.new(uuid)
         | 
| 85 | 
            +
                  mutex.synchronize do
         | 
| 86 | 
            +
                    requests[uuid] = prom
         | 
| 87 | 
            +
                    targets[uuid] = target
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                  prom
         | 
| 26 90 | 
             
                end
         | 
| 27 91 | 
             
              end
         | 
| 28 92 | 
             
            end
         | 
    
        data/lib/estore/errors.rb
    CHANGED
    
    
    
        data/lib/estore/session.rb
    CHANGED
    
    | @@ -31,44 +31,70 @@ module Estore | |
| 31 31 | 
             
                end
         | 
| 32 32 |  | 
| 33 33 | 
             
                def ping
         | 
| 34 | 
            -
                  command( | 
| 34 | 
            +
                  command('Ping')
         | 
| 35 35 | 
             
                end
         | 
| 36 36 |  | 
| 37 | 
            -
                def read(stream,  | 
| 38 | 
            -
                   | 
| 39 | 
            -
             | 
| 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 | 
            +
                  )
         | 
| 40 45 |  | 
| 41 | 
            -
                   | 
| 42 | 
            -
                    read_batch(stream, from, limit)
         | 
| 43 | 
            -
                  else
         | 
| 44 | 
            -
                    read_forward(stream, from)
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            +
                  command('ReadStreamEventsForward', msg)
         | 
| 46 47 | 
             
                end
         | 
| 47 48 |  | 
| 48 | 
            -
                def  | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 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 | 
            +
                  )
         | 
| 51 56 |  | 
| 52 | 
            -
             | 
| 53 | 
            -
                  command(Commands::ReadForward, stream, from, batch_size, &block).call
         | 
| 57 | 
            +
                  command('WriteEvents', msg)
         | 
| 54 58 | 
             
                end
         | 
| 55 59 |  | 
| 56 | 
            -
                def  | 
| 57 | 
            -
                   | 
| 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)
         | 
| 58 67 | 
             
                end
         | 
| 59 68 |  | 
| 60 69 | 
             
                def subscription(stream, options = {})
         | 
| 61 | 
            -
                  if options[: | 
| 62 | 
            -
                     | 
| 70 | 
            +
                  if options[:catch_up_from]
         | 
| 71 | 
            +
                    CatchUpSubscription.new(self, stream, options[:catch_up_from], options)
         | 
| 63 72 | 
             
                  else
         | 
| 64 | 
            -
                     | 
| 73 | 
            +
                    Subscription.new(self, stream, options)
         | 
| 65 74 | 
             
                  end
         | 
| 66 75 | 
             
                end
         | 
| 67 76 |  | 
| 68 77 | 
             
                private
         | 
| 69 78 |  | 
| 70 | 
            -
                 | 
| 71 | 
            -
                   | 
| 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)
         | 
| 72 98 | 
             
                end
         | 
| 73 99 | 
             
              end
         | 
| 74 100 | 
             
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            module Estore
         | 
| 2 | 
            +
              # Volatile Subscriptions
         | 
| 3 | 
            +
              #
         | 
| 4 | 
            +
              # This kind of subscription calls a given function for events written
         | 
| 5 | 
            +
              # after the subscription is established.
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # For example, if a stream has 100 events in it when a subscriber connects,
         | 
| 8 | 
            +
              # the subscriber can expect to see event number 101 onwards until the time
         | 
| 9 | 
            +
              # the subscription is closed or dropped.
         | 
| 10 | 
            +
              class Subscription
         | 
| 11 | 
            +
                attr_reader :id, :stream, :resolve_link_tos, :position
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(estore, stream, options = {})
         | 
| 14 | 
            +
                  @estore = estore
         | 
| 15 | 
            +
                  @stream = stream
         | 
| 16 | 
            +
                  @resolve_link_tos = options.fetch(:resolve_link_tos, true)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def on_error(&block)
         | 
| 20 | 
            +
                  @on_error = block if block
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def on_event(&block)
         | 
| 24 | 
            +
                  @on_event = block if block
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def start
         | 
| 28 | 
            +
                  subscribe
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def stop
         | 
| 32 | 
            +
                  @estore.unsubscribe(id) if id
         | 
| 33 | 
            +
                  @id = nil
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def subscribe
         | 
| 39 | 
            +
                  prom = @estore.subscribe(stream, self, resolve_link_tos: resolve_link_tos)
         | 
| 40 | 
            +
                  @id = prom.correlation_id
         | 
| 41 | 
            +
                  prom.sync
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def call_on_error(error)
         | 
| 45 | 
            +
                  @on_error.call(error) if @on_error
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def dispatch(event)
         | 
| 49 | 
            +
                  @on_event.call(event) if @on_event
         | 
| 50 | 
            +
                  @position = event.original_event_number
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def event_appeared(event)
         | 
| 54 | 
            +
                  dispatch(event)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
    
        data/lib/estore/version.rb
    CHANGED
    
    
    
        data/lib/estore.rb
    CHANGED
    
    | @@ -6,5 +6,6 @@ require 'estore/message_extensions' | |
| 6 6 | 
             
            require 'estore/connection_context'
         | 
| 7 7 | 
             
            require 'estore/connection'
         | 
| 8 8 | 
             
            require 'estore/connection/buffer'
         | 
| 9 | 
            -
            require 'estore/connection/ | 
| 10 | 
            -
            require 'estore/ | 
| 9 | 
            +
            require 'estore/connection/commands'
         | 
| 10 | 
            +
            require 'estore/subscription'
         | 
| 11 | 
            +
            require 'estore/catchup_subscription'
         | 
| @@ -33,88 +33,74 @@ describe Estore::Session do | |
| 33 33 | 
             
                "test-#{SecureRandom.uuid}"
         | 
| 34 34 | 
             
              end
         | 
| 35 35 |  | 
| 36 | 
            -
              def  | 
| 36 | 
            +
              def populate(count, stream = nil)
         | 
| 37 37 | 
             
                stream ||= random_stream
         | 
| 38 38 | 
             
                session.append(stream, events(count)).sync
         | 
| 39 39 | 
             
                stream
         | 
| 40 40 | 
             
              end
         | 
| 41 41 |  | 
| 42 | 
            -
               | 
| 43 | 
            -
                 | 
| 44 | 
            -
             | 
| 45 | 
            -
                    expect(parse_data(event)).to eql('id' => index + start)
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
              end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
              it 'reads all the events from a stream' do
         | 
| 51 | 
            -
                events = session.read(stream_with(200)).sync
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                expect(events.size).to be(200)
         | 
| 54 | 
            -
                expect(events).to start_from(0)
         | 
| 55 | 
            -
              end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
              it 'reads all the events forward from a stream' do
         | 
| 58 | 
            -
                events = session.read(stream_with(100), from: 20).sync
         | 
| 42 | 
            +
              it 'reads events from a stream' do
         | 
| 43 | 
            +
                stream = populate(20)
         | 
| 44 | 
            +
                read = session.read(stream, 0, 20).sync
         | 
| 59 45 |  | 
| 60 | 
            -
                expect(events.size).to be( | 
| 61 | 
            -
                expect(events).to start_from(20)
         | 
| 62 | 
            -
              end
         | 
| 46 | 
            +
                expect(read.events.size).to be(20)
         | 
| 63 47 |  | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
                expect(events.size).to be(15)
         | 
| 68 | 
            -
                expect(events).to start_from(10)
         | 
| 48 | 
            +
                read.events.each_with_index do |event, index|
         | 
| 49 | 
            +
                  expect(parse_data(event)).to eql('id' => index)
         | 
| 50 | 
            +
                end
         | 
| 69 51 | 
             
              end
         | 
| 70 52 |  | 
| 71 53 | 
             
              it 'allows to make a live subscription' do
         | 
| 72 54 | 
             
                stream = random_stream
         | 
| 73 | 
            -
                received =  | 
| 74 | 
            -
             | 
| 75 | 
            -
                stream_with(20, stream)
         | 
| 55 | 
            +
                received = 0
         | 
| 76 56 |  | 
| 77 57 | 
             
                sub = session.subscription(stream)
         | 
| 78 | 
            -
                sub. | 
| 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 | 
            +
             | 
| 79 65 | 
             
                sub.start
         | 
| 80 66 |  | 
| 81 | 
            -
                 | 
| 67 | 
            +
                populate(50, stream)
         | 
| 82 68 |  | 
| 83 69 | 
             
                Timeout.timeout(5) do
         | 
| 84 70 | 
             
                  loop do
         | 
| 85 | 
            -
                    break if received | 
| 71 | 
            +
                    break if received >= 50
         | 
| 86 72 | 
             
                    sleep(0.1)
         | 
| 87 73 | 
             
                  end
         | 
| 88 74 | 
             
                end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                expect(received.size).to be(50)
         | 
| 91 | 
            -
                expect(received).to start_from(20)
         | 
| 92 75 | 
             
              end
         | 
| 93 76 |  | 
| 94 77 | 
             
              it 'allows to make a catchup subscription' do
         | 
| 95 78 | 
             
                stream = random_stream
         | 
| 96 | 
            -
                received =  | 
| 79 | 
            +
                received = 0
         | 
| 97 80 |  | 
| 98 | 
            -
                 | 
| 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
         | 
| 99 90 |  | 
| 100 | 
            -
                sub = session.subscription(stream, from: 20)
         | 
| 101 | 
            -
                sub.on_event { |event| received << event }
         | 
| 102 91 | 
             
                sub.start
         | 
| 103 92 |  | 
| 104 93 | 
             
                Thread.new do
         | 
| 105 94 | 
             
                  50.times do
         | 
| 106 | 
            -
                     | 
| 95 | 
            +
                    populate(2, stream)
         | 
| 107 96 | 
             
                  end
         | 
| 108 97 | 
             
                end
         | 
| 109 98 |  | 
| 110 99 | 
             
                Timeout.timeout(5) do
         | 
| 111 100 | 
             
                  loop do
         | 
| 112 | 
            -
                    break if received | 
| 101 | 
            +
                    break if received >= 130
         | 
| 113 102 | 
             
                    sleep(0.1)
         | 
| 114 103 | 
             
                  end
         | 
| 115 104 | 
             
                end
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                expect(received.size).to be(2180)
         | 
| 118 | 
            -
                expect(received).to start_from(20)
         | 
| 119 105 | 
             
              end
         | 
| 120 106 | 
             
            end
         | 
    
        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.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Mathieu Ravaux
         | 
| @@ -100,24 +100,17 @@ files: | |
| 100 100 | 
             
            - Rakefile
         | 
| 101 101 | 
             
            - estore.gemspec
         | 
| 102 102 | 
             
            - lib/estore.rb
         | 
| 103 | 
            -
            - lib/estore/ | 
| 104 | 
            -
            - lib/estore/commands/append.rb
         | 
| 105 | 
            -
            - lib/estore/commands/base.rb
         | 
| 106 | 
            -
            - lib/estore/commands/catch_up_subscription.rb
         | 
| 107 | 
            -
            - lib/estore/commands/ping.rb
         | 
| 108 | 
            -
            - lib/estore/commands/promise.rb
         | 
| 109 | 
            -
            - lib/estore/commands/read_batch.rb
         | 
| 110 | 
            -
            - lib/estore/commands/read_forward.rb
         | 
| 111 | 
            -
            - lib/estore/commands/subscription.rb
         | 
| 103 | 
            +
            - lib/estore/catchup_subscription.rb
         | 
| 112 104 | 
             
            - lib/estore/connection.rb
         | 
| 113 105 | 
             
            - lib/estore/connection/buffer.rb
         | 
| 114 | 
            -
            - lib/estore/connection/ | 
| 106 | 
            +
            - lib/estore/connection/commands.rb
         | 
| 115 107 | 
             
            - lib/estore/connection_context.rb
         | 
| 116 108 | 
             
            - lib/estore/errors.rb
         | 
| 117 109 | 
             
            - lib/estore/message_extensions.rb
         | 
| 118 110 | 
             
            - lib/estore/messages.rb
         | 
| 119 111 | 
             
            - lib/estore/package.rb
         | 
| 120 112 | 
             
            - lib/estore/session.rb
         | 
| 113 | 
            +
            - lib/estore/subscription.rb
         | 
| 121 114 | 
             
            - lib/estore/version.rb
         | 
| 122 115 | 
             
            - spec/integration/session_spec.rb
         | 
| 123 116 | 
             
            - spec/spec_helper.rb
         | 
| @@ -1,56 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class Append
         | 
| 4 | 
            -
                  include Command
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  CONTENT_TYPES = {
         | 
| 7 | 
            -
                    json: 1
         | 
| 8 | 
            -
                  }
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                  def initialize(connection, stream, events, options = {})
         | 
| 11 | 
            -
                    super(connection)
         | 
| 12 | 
            -
                    @stream, @events, @options = stream, events, options
         | 
| 13 | 
            -
                  end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                  def call
         | 
| 16 | 
            -
                    msg = WriteEvents.new(
         | 
| 17 | 
            -
                      event_stream_id: @stream,
         | 
| 18 | 
            -
                      expected_version: @options[:expected_version] || -2,
         | 
| 19 | 
            -
                      events: Array(@events).map { |event| new_event(event) },
         | 
| 20 | 
            -
                      require_master: true
         | 
| 21 | 
            -
                    )
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                    register!
         | 
| 24 | 
            -
                    write('WriteEvents', msg)
         | 
| 25 | 
            -
                    promise
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  def handle(message, *)
         | 
| 29 | 
            -
                    response = decode(WriteEventsCompleted, message)
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    if response.result != OperationResult::Success
         | 
| 32 | 
            -
                      # TODO: Create custom exceptions
         | 
| 33 | 
            -
                      raise "WriteEvents command failed with uuid #{@uuid}"
         | 
| 34 | 
            -
                    end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                    remove!
         | 
| 37 | 
            -
                    promise.fulfill(response)
         | 
| 38 | 
            -
                  end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                  private
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  def new_event(event)
         | 
| 43 | 
            -
                    uuid = event[:id] || SecureRandom.uuid
         | 
| 44 | 
            -
                    content_type = event.fetch(:content_type, :json)
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                    NewEvent.new(
         | 
| 47 | 
            -
                      event_id: Package.encode_uuid(uuid),
         | 
| 48 | 
            -
                      event_type: event[:type],
         | 
| 49 | 
            -
                      data: event[:data],
         | 
| 50 | 
            -
                      data_content_type: CONTENT_TYPES.fetch(content_type, 0),
         | 
| 51 | 
            -
                      metadata_content_type: 1
         | 
| 52 | 
            -
                    )
         | 
| 53 | 
            -
                  end
         | 
| 54 | 
            -
                end
         | 
| 55 | 
            -
              end
         | 
| 56 | 
            -
            end
         | 
    
        data/lib/estore/commands/base.rb
    DELETED
    
    | @@ -1,51 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                module Command
         | 
| 4 | 
            -
                  attr_reader :uuid
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def initialize(connection)
         | 
| 7 | 
            -
                    @connection = connection
         | 
| 8 | 
            -
                    @uuid = SecureRandom.uuid
         | 
| 9 | 
            -
                  end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                  def register!
         | 
| 12 | 
            -
                    @connection.register(self)
         | 
| 13 | 
            -
                  end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                  def remove!
         | 
| 16 | 
            -
                    @connection.remove(self)
         | 
| 17 | 
            -
                  end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                  def write(command, message = nil)
         | 
| 20 | 
            -
                    @connection.write(@uuid, command, message)
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  def promise
         | 
| 24 | 
            -
                    @promise ||= Promise.new(@uuid)
         | 
| 25 | 
            -
                  end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  def decode(type, message)
         | 
| 28 | 
            -
                    type.decode(message)
         | 
| 29 | 
            -
                  rescue => error
         | 
| 30 | 
            -
                    puts "Protobuf decoding error on connection #{object_id}"
         | 
| 31 | 
            -
                    puts type: type, message: message
         | 
| 32 | 
            -
                    puts error.backtrace
         | 
| 33 | 
            -
                    raise error
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  module ReadStreamForward
         | 
| 37 | 
            -
                    def read(stream, from, limit)
         | 
| 38 | 
            -
                      msg = ReadStreamEvents.new(
         | 
| 39 | 
            -
                        event_stream_id: stream,
         | 
| 40 | 
            -
                        from_event_number: from,
         | 
| 41 | 
            -
                        max_count: limit,
         | 
| 42 | 
            -
                        resolve_link_tos: true,
         | 
| 43 | 
            -
                        require_master: false
         | 
| 44 | 
            -
                      )
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                      write('ReadStreamEventsForward', msg)
         | 
| 47 | 
            -
                    end
         | 
| 48 | 
            -
                  end
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
              end
         | 
| 51 | 
            -
            end
         | 
| @@ -1,60 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class CatchUpSubscription < Subscription
         | 
| 4 | 
            -
                  include Command
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def initialize(connection, stream, from, options = {})
         | 
| 7 | 
            -
                    super(connection, options)
         | 
| 8 | 
            -
                    @stream = stream
         | 
| 9 | 
            -
                    @from = from
         | 
| 10 | 
            -
                    @batch = options[:batch_size]
         | 
| 11 | 
            -
                    @mutex = Mutex.new
         | 
| 12 | 
            -
                    @queue = []
         | 
| 13 | 
            -
                    @caught_up = false
         | 
| 14 | 
            -
                    @last_worker = nil
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def start
         | 
| 18 | 
            -
                    super
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                    # TODO: Think about doing something more clever?
         | 
| 21 | 
            -
                    read = ReadForward.new(@connection, @stream, @from, @batch) do |events|
         | 
| 22 | 
            -
                      @last_worker = Thread.new(@last_worker) do |last_worker|
         | 
| 23 | 
            -
                        last_worker.join if last_worker
         | 
| 24 | 
            -
                        events.each { |event| dispatch(event) }
         | 
| 25 | 
            -
                      end
         | 
| 26 | 
            -
                    end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                    read.call.sync
         | 
| 29 | 
            -
                    @last_worker.join if @last_worker
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    switch_to_live
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  def switch_to_live
         | 
| 35 | 
            -
                    @mutex.synchronize do
         | 
| 36 | 
            -
                      queued_events.each { |event| dispatch(event) }
         | 
| 37 | 
            -
                      @caught_up = true
         | 
| 38 | 
            -
                    end
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                  def queued_events
         | 
| 42 | 
            -
                    @queue.find_all { |event| event.original_event_number > @position }
         | 
| 43 | 
            -
                  end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  def handle(message, type)
         | 
| 46 | 
            -
                    if type == 'StreamEventAppeared'
         | 
| 47 | 
            -
                      event = decode(StreamEventAppeared, message).event
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                      unless @caught_up
         | 
| 50 | 
            -
                        @mutex.synchronize do
         | 
| 51 | 
            -
                          @queue << event unless @caught_up
         | 
| 52 | 
            -
                        end
         | 
| 53 | 
            -
                      end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                      dispatch(event) if @caught_up
         | 
| 56 | 
            -
                    end
         | 
| 57 | 
            -
                  end
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
              end
         | 
| 60 | 
            -
            end
         | 
    
        data/lib/estore/commands/ping.rb
    DELETED
    
    
| @@ -1,19 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class Promise < ::Promise
         | 
| 4 | 
            -
                  attr_reader :correlation_id
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def initialize(correlation_id)
         | 
| 7 | 
            -
                    super()
         | 
| 8 | 
            -
                    @correlation_id = correlation_id
         | 
| 9 | 
            -
                  end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                  def wait
         | 
| 12 | 
            -
                    t = Thread.current
         | 
| 13 | 
            -
                    resume = proc { t.wakeup }
         | 
| 14 | 
            -
                    self.then(resume, resume)
         | 
| 15 | 
            -
                    sleep
         | 
| 16 | 
            -
                  end
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
              end
         | 
| 19 | 
            -
            end
         | 
| @@ -1,25 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class ReadBatch
         | 
| 4 | 
            -
                  include Command
         | 
| 5 | 
            -
                  include Command::ReadStreamForward
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                  def initialize(connection, stream, from, limit)
         | 
| 8 | 
            -
                    super(connection)
         | 
| 9 | 
            -
                    @stream, @from, @limit = stream, from, limit
         | 
| 10 | 
            -
                  end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                  def call
         | 
| 13 | 
            -
                    register!
         | 
| 14 | 
            -
                    read(@stream, @from, @limit)
         | 
| 15 | 
            -
                    promise
         | 
| 16 | 
            -
                  end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                  def handle(message, *)
         | 
| 19 | 
            -
                    remove!
         | 
| 20 | 
            -
                    response = decode(ReadStreamEventsCompleted, message)
         | 
| 21 | 
            -
                    promise.fulfill(Array(response.events))
         | 
| 22 | 
            -
                  end
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
              end
         | 
| 25 | 
            -
            end
         | 
| @@ -1,39 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class ReadForward
         | 
| 4 | 
            -
                  include Command
         | 
| 5 | 
            -
                  include Command::ReadStreamForward
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                  def initialize(connection, stream, from, batch_size = nil, &block)
         | 
| 8 | 
            -
                    super(connection)
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                    @stream = stream
         | 
| 11 | 
            -
                    @from = from
         | 
| 12 | 
            -
                    @batch_size = batch_size || 1000
         | 
| 13 | 
            -
                    @block = block
         | 
| 14 | 
            -
                    @events = []
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def call
         | 
| 18 | 
            -
                    register!
         | 
| 19 | 
            -
                    read(@stream, @from, @batch_size)
         | 
| 20 | 
            -
                    promise
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  def handle(message, *)
         | 
| 24 | 
            -
                    response = decode(ReadStreamEventsCompleted, message)
         | 
| 25 | 
            -
                    events = Array(response.events)
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                    @from += events.size
         | 
| 28 | 
            -
                    read(@stream, @from, @batch_size) unless response.is_end_of_stream
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                    @block ? @block.call(events) : @events.push(*events)
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    if response.is_end_of_stream
         | 
| 33 | 
            -
                      remove!
         | 
| 34 | 
            -
                      promise.fulfill(@block ? nil : @events)
         | 
| 35 | 
            -
                    end
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
            end
         | 
| @@ -1,53 +0,0 @@ | |
| 1 | 
            -
            module Estore
         | 
| 2 | 
            -
              module Commands
         | 
| 3 | 
            -
                class Subscription
         | 
| 4 | 
            -
                  include Command
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                  def initialize(connection, stream, options = {})
         | 
| 7 | 
            -
                    super(connection)
         | 
| 8 | 
            -
                    @has_finished = false
         | 
| 9 | 
            -
                    @stream = stream
         | 
| 10 | 
            -
                    @resolve_link_tos = options.fetch(:resolve_link_tos, true)
         | 
| 11 | 
            -
                  end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  def finished?
         | 
| 14 | 
            -
                    @has_finished
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def call
         | 
| 18 | 
            -
                    start
         | 
| 19 | 
            -
                  end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  def start
         | 
| 22 | 
            -
                    raise 'Subscription block not defined' unless @handler
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                    msg = SubscribeToStream.new(
         | 
| 25 | 
            -
                      event_stream_id: @stream,
         | 
| 26 | 
            -
                      resolve_link_tos: @resolve_link_tos
         | 
| 27 | 
            -
                    )
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                    register!
         | 
| 30 | 
            -
                    write('SubscribeToStream', msg)
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                  def close
         | 
| 34 | 
            -
                    write('UnsubscribeFromStream', UnsubscribeFromStream.new)
         | 
| 35 | 
            -
                    remove!
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  def on_event(&block)
         | 
| 39 | 
            -
                    @handler = block
         | 
| 40 | 
            -
                  end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  def handle(message, type)
         | 
| 43 | 
            -
                    dispatch(decode(StreamEventAppeared, message).event) if
         | 
| 44 | 
            -
                      type == 'StreamEventAppeared'
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  def dispatch(event)
         | 
| 48 | 
            -
                    @position = event.original_event_number
         | 
| 49 | 
            -
                    @handler.call(event)
         | 
| 50 | 
            -
                  end
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
            end
         | 
    
        data/lib/estore/commands.rb
    DELETED
    
    | @@ -1,8 +0,0 @@ | |
| 1 | 
            -
            require 'estore/commands/promise'
         | 
| 2 | 
            -
            require 'estore/commands/base'
         | 
| 3 | 
            -
            require 'estore/commands/append'
         | 
| 4 | 
            -
            require 'estore/commands/ping'
         | 
| 5 | 
            -
            require 'estore/commands/read_batch'
         | 
| 6 | 
            -
            require 'estore/commands/read_forward'
         | 
| 7 | 
            -
            require 'estore/commands/subscription'
         | 
| 8 | 
            -
            require 'estore/commands/catch_up_subscription'
         |