event_store_client 1.4.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -145
- data/docs/appending_events.md +155 -0
- data/docs/catch_up_subscriptions.md +253 -0
- data/docs/configuration.md +83 -0
- data/docs/deleting_streams.md +25 -0
- data/docs/encrypting_events.md +84 -0
- data/docs/linking_events.md +149 -0
- data/docs/reading_events.md +200 -0
- data/lib/event_store_client/adapters/grpc/client.rb +244 -105
- data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
- data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
- data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
- data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
- data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
- data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
- data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
- data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
- data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
- data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
- data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
- data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
- data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
- data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
- data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
- data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
- data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
- data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
- data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
- data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
- data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
- data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
- data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
- data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
- data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
- data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
- data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
- data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
- data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
- data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
- data/lib/event_store_client/adapters/grpc.rb +28 -12
- data/lib/event_store_client/configuration.rb +39 -54
- data/lib/event_store_client/connection/url.rb +57 -0
- data/lib/event_store_client/connection/url_parser.rb +144 -0
- data/lib/event_store_client/data_decryptor.rb +2 -9
- data/lib/event_store_client/deserialized_event.rb +35 -10
- data/lib/event_store_client/encryption_metadata.rb +0 -1
- data/lib/event_store_client/event.rb +4 -2
- data/lib/event_store_client/extensions/options_extension.rb +87 -0
- data/lib/event_store_client/mapper/default.rb +12 -9
- data/lib/event_store_client/mapper/encrypted.rb +18 -17
- data/lib/event_store_client/types.rb +1 -1
- data/lib/event_store_client/utils.rb +30 -0
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +8 -7
- metadata +74 -83
- data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
- data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
- data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
- data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
- data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
- data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
- data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
- data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
- data/lib/event_store_client/adapters/grpc/README.md +0 -16
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
- data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
- data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
- data/lib/event_store_client/adapters/http/README.md +0 -16
- data/lib/event_store_client/adapters/http/client.rb +0 -161
- data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
- data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
- data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
- data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
- data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
- data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
- data/lib/event_store_client/adapters/http/connection.rb +0 -46
- data/lib/event_store_client/adapters/http/request_method.rb +0 -28
- data/lib/event_store_client/adapters/http.rb +0 -17
- data/lib/event_store_client/adapters/in_memory.rb +0 -144
- data/lib/event_store_client/broker.rb +0 -40
- data/lib/event_store_client/catch_up_subscription.rb +0 -42
- data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
- data/lib/event_store_client/client.rb +0 -73
- data/lib/event_store_client/error_handler.rb +0 -10
- data/lib/event_store_client/subscription.rb +0 -23
- data/lib/event_store_client/subscriptions.rb +0 -38
- data/lib/event_store_client/value_objects/read_direction.rb +0 -43
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Shared
|
6
|
+
module Options
|
7
|
+
class StreamOptions
|
8
|
+
attr_reader :stream_name, :options
|
9
|
+
private :stream_name, :options
|
10
|
+
|
11
|
+
# @param stream_name [String]
|
12
|
+
# @param options [Hash]
|
13
|
+
# @option options [Integer, Symbol] :from_revision. If number is provided - it is threaded
|
14
|
+
# as starting revision number. Alternatively you can provide :start or :end value to
|
15
|
+
# define a stream revision. **Use this option when stream name is a normal stream name**
|
16
|
+
# @option options [Hash, Symbol] :from_position. If hash is provided - you should supply
|
17
|
+
# it with :commit_position and/or :prepare_position keys. Alternatively you can provide
|
18
|
+
# :start or :end value to define a stream position. **Use this option when stream name
|
19
|
+
# is "$all"**
|
20
|
+
def initialize(stream_name, options)
|
21
|
+
@stream_name = stream_name
|
22
|
+
@options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Hash]
|
26
|
+
def request_options
|
27
|
+
stream_name == '$all' ? all_stream : stream
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Hash]
|
33
|
+
# Examples:
|
34
|
+
# ```ruby
|
35
|
+
# { all: { start: EventStore::Client::Empty.new } }
|
36
|
+
# ```
|
37
|
+
# ```ruby
|
38
|
+
# { all: { end: EventStore::Client::Empty.new } }
|
39
|
+
# ```
|
40
|
+
# ```ruby
|
41
|
+
# { all: { position: { commit_position: 1, prepare_position: 1 } } }
|
42
|
+
# ```
|
43
|
+
def all_stream
|
44
|
+
position_opt =
|
45
|
+
case options[:from_position]
|
46
|
+
when :start, :end
|
47
|
+
{ options[:from_position] => EventStore::Client::Empty.new }
|
48
|
+
when Hash
|
49
|
+
{ position: options[:from_position] }
|
50
|
+
else
|
51
|
+
{ start: EventStore::Client::Empty.new }
|
52
|
+
end
|
53
|
+
{ all: position_opt }
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Hash]
|
57
|
+
# Examples:
|
58
|
+
# ```ruby
|
59
|
+
# { stream: {
|
60
|
+
# start: EventStore::Client::Empty.new,
|
61
|
+
# stream_identifier: { stream_name: 'some-stream' }
|
62
|
+
# }
|
63
|
+
# }
|
64
|
+
# ```
|
65
|
+
# ```ruby
|
66
|
+
# { stream: {
|
67
|
+
# end: EventStore::Client::Empty.new,
|
68
|
+
# stream_identifier: { stream_name: 'some-stream' }
|
69
|
+
# }
|
70
|
+
# }
|
71
|
+
# ```
|
72
|
+
# ```ruby
|
73
|
+
# { stream: { revision: 1, stream_identifier: { stream_name: 'some-stream' } } }
|
74
|
+
# ```
|
75
|
+
def stream
|
76
|
+
revision_opt =
|
77
|
+
case options[:from_revision]
|
78
|
+
when :start, :end
|
79
|
+
{ options[:from_revision] => EventStore::Client::Empty.new }
|
80
|
+
when Integer
|
81
|
+
{ revision: options[:from_revision] }
|
82
|
+
else
|
83
|
+
{ start: EventStore::Client::Empty.new }
|
84
|
+
end
|
85
|
+
{ stream: revision_opt.merge(stream_identifier: { stream_name: stream_name }) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Shared
|
6
|
+
module Streams
|
7
|
+
class ProcessResponse
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
# @param response [EventStore::Client::Streams::ReadResp]
|
12
|
+
# @param skip_deserialization [Boolean]
|
13
|
+
# @param skip_decryption [Boolean]
|
14
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure, nil]
|
15
|
+
def call(response, skip_deserialization, skip_decryption)
|
16
|
+
return Failure(:stream_not_found) if response.stream_not_found
|
17
|
+
return Success(response) if skip_deserialization
|
18
|
+
return unless response.event&.event
|
19
|
+
|
20
|
+
Success(
|
21
|
+
EventDeserializer.new.call(response.event.event, skip_decryption: skip_decryption)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Shared
|
6
|
+
module Streams
|
7
|
+
class ProcessResponses
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
# @param responses [Array<EventStore::Client::Streams::ReadResp>]
|
12
|
+
# @param skip_deserialization [Boolean]
|
13
|
+
# @param skip_decryption [Boolean]
|
14
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
15
|
+
def call(responses, skip_deserialization, skip_decryption)
|
16
|
+
return Failure(:stream_not_found) if responses.first&.stream_not_found
|
17
|
+
return Success(responses) if skip_deserialization
|
18
|
+
|
19
|
+
events =
|
20
|
+
responses.map do |read_resp|
|
21
|
+
# It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
|
22
|
+
# example. Such responses should be skipped. See generated files for more info.
|
23
|
+
next unless read_resp.event&.event
|
24
|
+
|
25
|
+
EventDeserializer.new.call(read_resp.event.event, skip_decryption: skip_decryption)
|
26
|
+
end
|
27
|
+
Success(events.compact)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,22 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'grpc'
|
4
|
+
require 'dry-monads'
|
5
|
+
|
6
|
+
require 'event_store_client/adapters/grpc/options/streams/read_options'
|
7
|
+
require 'event_store_client/adapters/grpc/options/streams/write_options'
|
8
|
+
|
9
|
+
require 'event_store_client/adapters/grpc/shared/event_deserializer'
|
10
|
+
require 'event_store_client/adapters/grpc/shared/options/stream_options'
|
11
|
+
require 'event_store_client/adapters/grpc/shared/options/filter_options'
|
12
|
+
require 'event_store_client/adapters/grpc/shared/streams/process_response'
|
13
|
+
require 'event_store_client/adapters/grpc/shared/streams/process_responses'
|
14
|
+
|
15
|
+
require 'event_store_client/adapters/grpc/connection'
|
16
|
+
require 'event_store_client/adapters/grpc/discover'
|
17
|
+
require 'event_store_client/adapters/grpc/cluster/insecure_connection'
|
18
|
+
require 'event_store_client/adapters/grpc/cluster/secure_connection'
|
19
|
+
require 'event_store_client/adapters/grpc/cluster/queryless_discover'
|
20
|
+
require 'event_store_client/adapters/grpc/cluster/gossip_discover'
|
21
|
+
require 'event_store_client/adapters/grpc/cluster/member'
|
22
|
+
|
23
|
+
require 'event_store_client/adapters/grpc/command_registrar'
|
24
|
+
require 'event_store_client/adapters/grpc/commands/command'
|
25
|
+
|
26
|
+
require 'event_store_client/adapters/grpc/commands/gossip/cluster_info'
|
4
27
|
|
5
28
|
require 'event_store_client/adapters/grpc/commands/streams/append'
|
29
|
+
require 'event_store_client/adapters/grpc/commands/streams/append_multiple'
|
6
30
|
require 'event_store_client/adapters/grpc/commands/streams/delete'
|
31
|
+
require 'event_store_client/adapters/grpc/commands/streams/hard_delete'
|
7
32
|
require 'event_store_client/adapters/grpc/commands/streams/link_to'
|
33
|
+
require 'event_store_client/adapters/grpc/commands/streams/link_to_multiple'
|
8
34
|
require 'event_store_client/adapters/grpc/commands/streams/read'
|
9
|
-
require 'event_store_client/adapters/grpc/commands/streams/
|
35
|
+
require 'event_store_client/adapters/grpc/commands/streams/read_paginated'
|
10
36
|
require 'event_store_client/adapters/grpc/commands/streams/subscribe'
|
11
|
-
require 'event_store_client/adapters/grpc/commands/streams/tombstone'
|
12
|
-
|
13
|
-
require 'event_store_client/adapters/grpc/commands/persistent_subscriptions/create'
|
14
|
-
require 'event_store_client/adapters/grpc/commands/persistent_subscriptions/update'
|
15
|
-
require 'event_store_client/adapters/grpc/commands/persistent_subscriptions/delete'
|
16
|
-
require 'event_store_client/adapters/grpc/commands/persistent_subscriptions/read'
|
17
|
-
|
18
|
-
require 'event_store_client/adapters/grpc/commands/projections/create'
|
19
|
-
require 'event_store_client/adapters/grpc/commands/projections/update'
|
20
|
-
require 'event_store_client/adapters/grpc/commands/projections/delete'
|
21
37
|
|
22
38
|
require 'event_store_client/adapters/grpc/client'
|
@@ -1,64 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry-configurable'
|
4
|
-
require 'event_store_client/error_handler'
|
5
|
-
require 'event_store_client/deserialized_event'
|
6
4
|
|
7
5
|
module EventStoreClient
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
setting :adapter, default: :grpc
|
13
|
-
setting :verify_ssl, default: true
|
14
|
-
|
15
|
-
setting :error_handler, default: ErrorHandler.new
|
16
|
-
setting :eventstore_url,
|
17
|
-
default: 'http://localhost:2113',
|
18
|
-
constructor: proc { |value| value.is_a?(URI) ? value : URI(value) }
|
19
|
-
setting :eventstore_user, default: 'admin'
|
20
|
-
setting :eventstore_password, default: 'changeit'
|
21
|
-
|
22
|
-
setting :db_port, default: 2113
|
23
|
-
|
24
|
-
setting :per_page, default: 20
|
25
|
-
setting :pid_path, default: 'tmp/poll.pid'
|
26
|
-
|
27
|
-
setting :service_name, default: 'default'
|
28
|
-
|
29
|
-
setting :mapper, default: Mapper::Default.new
|
30
|
-
|
31
|
-
setting :default_event_class, default: DeserializedEvent
|
32
|
-
|
33
|
-
setting :subscriptions_repo
|
34
|
-
|
35
|
-
setting :logger
|
36
|
-
|
37
|
-
setting :socket_error_retry_sleep, default: 0.5
|
38
|
-
setting :socket_error_retry_count, default: 3
|
39
|
-
|
40
|
-
setting :grpc_unavailable_retry_sleep, default: 0.5
|
41
|
-
setting :grpc_unavailable_retry_count, default: 3
|
42
|
-
|
43
|
-
def self.configure
|
44
|
-
yield(config) if block_given?
|
45
|
-
end
|
6
|
+
class << self
|
7
|
+
def configure
|
8
|
+
yield(config) if block_given?
|
9
|
+
end
|
46
10
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
11
|
+
def config
|
12
|
+
@config ||= Class.new do
|
13
|
+
extend Dry::Configurable
|
14
|
+
|
15
|
+
# @param logger [Logger, nil]
|
16
|
+
# @return [Logger, nil]
|
17
|
+
def self.assign_grpc_logger(logger)
|
18
|
+
::GRPC.define_singleton_method :logger do
|
19
|
+
@logger ||= logger.nil? ? ::GRPC::DefaultLogger::NoopLogger.new : logger
|
20
|
+
end
|
21
|
+
logger
|
22
|
+
end
|
23
|
+
|
24
|
+
setting :eventstore_url,
|
25
|
+
default: 'esdb://localhost:2113',
|
26
|
+
constructor:
|
27
|
+
proc { |value|
|
28
|
+
value.is_a?(Connection::Url) ? value : Connection::UrlParser.new.call(value)
|
29
|
+
}
|
30
|
+
setting :per_page, default: 20
|
31
|
+
|
32
|
+
setting :mapper, default: Mapper::Default.new
|
33
|
+
|
34
|
+
setting :default_event_class, default: DeserializedEvent
|
35
|
+
|
36
|
+
setting :logger, constructor: method(:assign_grpc_logger).to_proc
|
37
|
+
|
38
|
+
setting :skip_deserialization, default: false
|
39
|
+
setting :skip_decryption, default: false
|
61
40
|
end
|
41
|
+
@config.config
|
42
|
+
end
|
43
|
+
|
44
|
+
def client
|
45
|
+
GRPC::Client.new
|
46
|
+
end
|
62
47
|
end
|
63
48
|
|
64
49
|
# Configuration module to be included in classes required configured variables
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module Connection
|
5
|
+
# Structured representation of connection string. You should not use it directly. If you would
|
6
|
+
# like to parse connection string into an instance of this class - use
|
7
|
+
# EventStoreClient::Connection::UrlParser instead.
|
8
|
+
# @api private
|
9
|
+
class Url
|
10
|
+
include Extensions::OptionsExtension
|
11
|
+
|
12
|
+
NODE_PREFERENCES = %i[Leader Follower ReadOnlyReplica].freeze
|
13
|
+
Node = Struct.new(:host, :port)
|
14
|
+
|
15
|
+
# This option will allow you to perform the discovery by only one host
|
16
|
+
# https://developers.eventstore.com/server/v21.10/cluster.html#cluster-with-dns
|
17
|
+
option(:dns_discover) { false }
|
18
|
+
option(:username) { 'admin' }
|
19
|
+
option(:password) { 'changeit' }
|
20
|
+
# Defines if append request should raise error immediately. If set to `false`, in case of
|
21
|
+
# server error - request will be retried.
|
22
|
+
option(:throw_on_append_failure) { true }
|
23
|
+
# Whether to use secure connection
|
24
|
+
option(:tls) { true }
|
25
|
+
# Whether to verify a certificate
|
26
|
+
option(:tls_verify_cert) { false }
|
27
|
+
# A path to certificate file
|
28
|
+
option(:tls_ca_file)
|
29
|
+
# Interval between X.509 certificate lookup attempts. This option is useful when you set tls
|
30
|
+
# option to true, but you didn't provide tls_ca_file option. In this case the certificate
|
31
|
+
# will be retrieved using Net::HTTP#peer_cert method.
|
32
|
+
option(:ca_lookup_interval) { 100 } # milliseconds
|
33
|
+
# Number of attempts of lookup of X.509 certificate
|
34
|
+
option(:ca_lookup_attempts) { 3 }
|
35
|
+
# Discovery request timeout. Only useful when there are several nodes or when dns_discover
|
36
|
+
# option is true
|
37
|
+
option(:gossip_timeout) { 200 } # milliseconds
|
38
|
+
# Max attempts before giving up to find a suitable cluster member. Only useful when there are
|
39
|
+
# several nodes or when dns_discover option is true
|
40
|
+
option(:max_discover_attempts) { 10 }
|
41
|
+
# Interval between discover attempts
|
42
|
+
option(:discover_interval) { 100 } # milliseconds
|
43
|
+
# Response timeout
|
44
|
+
option(:timeout) # milliseconds
|
45
|
+
# During the discovery - set which state will be taken in prio during cluster members look up
|
46
|
+
option(:node_preference) { NODE_PREFERENCES.first }
|
47
|
+
# A list of nodes to discover. It is represented as an array of
|
48
|
+
# EventStoreClient::Connection::Url::Node instances
|
49
|
+
option(:nodes) { Set.new }
|
50
|
+
# Number of time to retry GRPC request. Does not apply to discover request. Final number of
|
51
|
+
# requests in cases of error will be initial request + grpc_retry_attempts.
|
52
|
+
option(:grpc_retry_attempts) { 3 }
|
53
|
+
# Delay between GRPC request retries
|
54
|
+
option(:grpc_retry_interval) { 100 } # milliseconds
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module Connection
|
5
|
+
class UrlParser
|
6
|
+
class << self
|
7
|
+
def boolean_param(value)
|
8
|
+
return unless %w[true false].include?(value)
|
9
|
+
|
10
|
+
value == 'true'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ParsedUrl = Struct.new(:scheme, :host, :port, :user, :password, :params)
|
15
|
+
# It is used to detect if string starts from some scheme. E.g. "esdb://", "esdb+discover://",
|
16
|
+
# "http://", "https://" and so on
|
17
|
+
SCHEME_REGEXP = %r{\A[\w|+]*://}.freeze
|
18
|
+
|
19
|
+
# Define a set of rules to translate connection string into
|
20
|
+
# EventStoreClient::Connection::Url options.
|
21
|
+
#
|
22
|
+
# "First url" means first extracted from the connection string url. It may contain schema,
|
23
|
+
# discover flag, user name and password.
|
24
|
+
# Example. Let's say a developer provided
|
25
|
+
# "esdb+discover://admin:some-password@localhost:2112,localhost:2113" connection string. Then,
|
26
|
+
# during parsing, the first url will be 'esdb+discover://admin:some-password@localhost:2112'
|
27
|
+
FIRST_URL_RULES = {
|
28
|
+
dns_discover: ->(parsed_url) { parsed_url.scheme&.include?('+discover') },
|
29
|
+
username: ->(parsed_url) { parsed_url.user },
|
30
|
+
password: ->(parsed_url) { parsed_url.password }
|
31
|
+
}.freeze
|
32
|
+
# "Last url" means the latest extracted url from the connections string. It contains params.
|
33
|
+
# So LAST_URL_RULES rules defines rules how to translate params into
|
34
|
+
# EventStoreClient::Connection::Url options.
|
35
|
+
# Example. Let's say a developer provided
|
36
|
+
# "esdb+discover://admin:some-password@localhost:2112,localhost:2113/?tls=false" connection
|
37
|
+
# string. Then, during parsing, last url will be 'localhost:2113/?tls=false'.
|
38
|
+
LAST_URL_RULES = {
|
39
|
+
throw_on_append_failure: lambda { |parsed_url|
|
40
|
+
boolean_param(parsed_url.params['throwOnAppendFailure'])
|
41
|
+
},
|
42
|
+
tls: ->(parsed_url) { boolean_param(parsed_url.params['tls']) },
|
43
|
+
tls_verify_cert: ->(parsed_url) { boolean_param(parsed_url.params['tlsVerifyCert']) },
|
44
|
+
tls_ca_file: ->(parsed_url) { parsed_url.params['tlsCAFile'] },
|
45
|
+
gossip_timeout: ->(parsed_url) { parsed_url.params['gossipTimeout']&.to_i },
|
46
|
+
discover_interval: ->(parsed_url) { parsed_url.params['discoverInterval']&.to_i },
|
47
|
+
max_discover_attempts: ->(parsed_url) { parsed_url.params['maxDiscoverAttempts']&.to_i },
|
48
|
+
ca_lookup_interval: ->(parsed_url) { parsed_url.params['caLookupInterval']&.to_i },
|
49
|
+
ca_lookup_attempts: ->(parsed_url) { parsed_url.params['caLookupAttempts']&.to_i },
|
50
|
+
node_preference: lambda { |parsed_url|
|
51
|
+
value = parsed_url.params['nodePreference']&.dup
|
52
|
+
if value
|
53
|
+
value[0] = value[0]&.upcase
|
54
|
+
value = value.to_sym
|
55
|
+
end
|
56
|
+
value if Url::NODE_PREFERENCES.include?(value)
|
57
|
+
},
|
58
|
+
timeout: ->(parsed_url) { parsed_url.params['timeout']&.to_i },
|
59
|
+
grpc_retry_attempts: ->(parsed_url) { parsed_url.params['grpcRetryAttempts']&.to_i },
|
60
|
+
grpc_retry_interval: ->(parsed_url) { parsed_url.params['grpcRetryInterval']&.to_i }
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
# @param connection_str [String] EventStore DB connection string
|
64
|
+
# @return [EventStoreClient::Connection::Url]
|
65
|
+
def call(connection_str)
|
66
|
+
urls = connection_str.split(',')
|
67
|
+
return Url.new if urls.empty?
|
68
|
+
|
69
|
+
first_url, *other, last_url = urls
|
70
|
+
|
71
|
+
es_url = Url.new
|
72
|
+
options_from_first(es_url, first_url)
|
73
|
+
if last_url.nil? # We are dealing with one node in the url
|
74
|
+
options_from_last(es_url, first_url)
|
75
|
+
else
|
76
|
+
options_from_other(es_url, other)
|
77
|
+
options_from_last(es_url, last_url)
|
78
|
+
end
|
79
|
+
|
80
|
+
es_url
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @param es_url [EventStoreClient::Connection::Url]
|
86
|
+
# @param first_url [String]
|
87
|
+
# @return [void]
|
88
|
+
def options_from_first(es_url, first_url)
|
89
|
+
parsed_url = parse(first_url)
|
90
|
+
return unless parsed_url
|
91
|
+
|
92
|
+
FIRST_URL_RULES.each do |opt, rule|
|
93
|
+
value = rule.call(parsed_url)
|
94
|
+
es_url.public_send("#{opt}=", value) unless value.nil?
|
95
|
+
end
|
96
|
+
es_url.nodes.add(Url::Node.new(parsed_url.host, parsed_url.port))
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param es_url [EventStoreClient::Connection::Url]
|
100
|
+
# @param last_url [String, nil]
|
101
|
+
# @return [void]
|
102
|
+
def options_from_last(es_url, last_url)
|
103
|
+
parsed_url = parse(last_url)
|
104
|
+
return unless parsed_url
|
105
|
+
|
106
|
+
LAST_URL_RULES.each do |opt, rule|
|
107
|
+
value = rule.call(parsed_url)
|
108
|
+
es_url.public_send("#{opt}=", value) unless value.nil?
|
109
|
+
end
|
110
|
+
es_url.nodes.add(Url::Node.new(parsed_url.host, parsed_url.port))
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param es_url [EventStoreClient::Connection::Url]
|
114
|
+
# @param urls [Array<String>]
|
115
|
+
# @return [void]
|
116
|
+
def options_from_other(es_url, urls)
|
117
|
+
urls.each do |url|
|
118
|
+
parsed_url = parse(url)
|
119
|
+
next unless parsed_url
|
120
|
+
|
121
|
+
es_url.nodes.add(Url::Node.new(parsed_url.host, parsed_url.port))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param url [String, nil]
|
126
|
+
# @return [EventStoreClient::Connection::UrlParser::ParsedUrl, nil]
|
127
|
+
def parse(url)
|
128
|
+
return unless url
|
129
|
+
|
130
|
+
url = "esdb://#{url}" unless url.start_with?(SCHEME_REGEXP)
|
131
|
+
uri = URI.parse(url)
|
132
|
+
|
133
|
+
ParsedUrl.new(
|
134
|
+
uri.scheme,
|
135
|
+
uri.host,
|
136
|
+
uri.port,
|
137
|
+
uri.user,
|
138
|
+
uri.password,
|
139
|
+
URI.decode_www_form(uri.query.to_s).to_h
|
140
|
+
)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -43,21 +43,14 @@ module EventStoreClient
|
|
43
43
|
|
44
44
|
def deep_dup(hash)
|
45
45
|
return hash unless hash.instance_of?(Hash)
|
46
|
+
|
46
47
|
dupl = hash.dup
|
47
48
|
dupl.each { |k, v| dupl[k] = v.instance_of?(Hash) ? deep_dup(v) : v }
|
48
49
|
dupl
|
49
50
|
end
|
50
51
|
|
51
52
|
def find_key(identifier)
|
52
|
-
|
53
|
-
begin
|
54
|
-
key_repository.find(identifier).value!
|
55
|
-
rescue StandardError => e
|
56
|
-
config.error_handler&.call(e)
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
|
60
|
-
key
|
53
|
+
key_repository.find(identifier).value!
|
61
54
|
end
|
62
55
|
end
|
63
56
|
end
|
@@ -7,7 +7,8 @@ module EventStoreClient
|
|
7
7
|
InvalidDataError = Class.new(StandardError)
|
8
8
|
private_constant :InvalidDataError
|
9
9
|
|
10
|
-
attr_reader :id, :type, :title, :data, :metadata
|
10
|
+
attr_reader :id, :type, :title, :data, :metadata, :stream_name, :stream_revision,
|
11
|
+
:prepare_position, :commit_position
|
11
12
|
|
12
13
|
# @args [Hash] opts
|
13
14
|
# @option opts [Boolean] :skip_validation
|
@@ -15,20 +16,27 @@ module EventStoreClient
|
|
15
16
|
# @option opts [Hash] :metadata
|
16
17
|
# @option opts [String] :type
|
17
18
|
# @option opts [String] :title
|
19
|
+
# @option opts [String] :stream_name
|
20
|
+
# @option opts [Integer] :stream_revision
|
21
|
+
# @option opts [Integer] :prepare_position
|
22
|
+
# @option opts [Integer] :commit_position
|
18
23
|
# @option opts [UUID] :id
|
19
24
|
#
|
20
25
|
def initialize(args = {})
|
21
26
|
validate(args[:data]) unless args[:skip_validation]
|
22
27
|
|
23
28
|
@data = args.fetch(:data) { {} }
|
24
|
-
@metadata =
|
25
|
-
args.fetch(:metadata) { {} }
|
26
|
-
.merge(
|
27
|
-
'type' => self.class.name,
|
28
|
-
'content-type' => payload_content_type
|
29
|
-
)
|
30
|
-
|
31
29
|
@type = args[:type] || self.class.name
|
30
|
+
@metadata =
|
31
|
+
args.fetch(:metadata) { {} }.
|
32
|
+
merge(
|
33
|
+
'type' => @type,
|
34
|
+
'content-type' => payload_content_type
|
35
|
+
)
|
36
|
+
@stream_name = args[:stream_name]
|
37
|
+
@stream_revision = args[:stream_revision]
|
38
|
+
@prepare_position = args[:prepare_position]
|
39
|
+
@commit_position = args[:commit_position]
|
32
40
|
@title = args[:title]
|
33
41
|
@id = args[:id]
|
34
42
|
end
|
@@ -38,9 +46,26 @@ module EventStoreClient
|
|
38
46
|
|
39
47
|
# content type of the event data
|
40
48
|
def payload_content_type
|
41
|
-
|
49
|
+
'application/json'
|
50
|
+
end
|
51
|
+
|
52
|
+
# Implements comparison of `EventStoreClient::DeserializedEvent`-s. Two events matches if all of
|
53
|
+
# their attributes matches
|
54
|
+
# @param other [Object, EventStoreClient::DeserializedEvent]
|
55
|
+
# @return [Boolean]
|
56
|
+
def ==(other)
|
57
|
+
return false unless other.is_a?(EventStoreClient::DeserializedEvent)
|
58
|
+
|
59
|
+
to_h == other.to_h
|
60
|
+
end
|
42
61
|
|
43
|
-
|
62
|
+
# @return [Hash]
|
63
|
+
def to_h
|
64
|
+
instance_variables.each_with_object({}) do |var, result|
|
65
|
+
key = var.to_s
|
66
|
+
key[0] = '' # remove @ sign
|
67
|
+
result[key.to_sym] = instance_variable_get(var)
|
68
|
+
end
|
44
69
|
end
|
45
70
|
|
46
71
|
private
|
@@ -11,8 +11,10 @@ module EventStoreClient
|
|
11
11
|
attribute :title, Types::Strict::String.optional.default(nil)
|
12
12
|
attribute :data, Types::Strict::String.default('{}')
|
13
13
|
attribute :metadata, Types::Strict::String.default('{}')
|
14
|
-
|
15
|
-
|
14
|
+
attribute :stream_name, Types::Strict::String.optional.default(nil)
|
15
|
+
attribute :stream_revision, Types::Strict::Integer.optional.default(nil)
|
16
|
+
attribute :commit_position, Types::Strict::Integer.optional.default(nil)
|
17
|
+
attribute :prepare_position, Types::Strict::Integer.optional.default(nil)
|
16
18
|
|
17
19
|
def initialize(args = {})
|
18
20
|
args[:id] = SecureRandom.uuid if args[:id].nil?
|