event_store_client 1.4.9 → 2.0.0
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/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?
|