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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -145
  3. data/docs/appending_events.md +155 -0
  4. data/docs/catch_up_subscriptions.md +253 -0
  5. data/docs/configuration.md +83 -0
  6. data/docs/deleting_streams.md +25 -0
  7. data/docs/encrypting_events.md +84 -0
  8. data/docs/linking_events.md +149 -0
  9. data/docs/reading_events.md +200 -0
  10. data/lib/event_store_client/adapters/grpc/client.rb +244 -105
  11. data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
  12. data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
  13. data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
  14. data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
  15. data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
  16. data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
  17. data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
  18. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
  19. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
  20. data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
  21. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
  22. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
  23. data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
  24. data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
  25. data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
  26. data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
  27. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
  28. data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
  29. data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
  30. data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
  31. data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
  32. data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
  33. data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
  34. data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
  35. data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
  36. data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
  37. data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
  38. data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
  39. data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
  40. data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
  41. data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
  42. data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
  43. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
  44. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
  45. data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
  46. data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
  47. data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
  48. data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
  49. data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
  50. data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
  51. data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
  52. data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
  53. data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
  54. data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
  55. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
  56. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
  57. data/lib/event_store_client/adapters/grpc.rb +28 -12
  58. data/lib/event_store_client/configuration.rb +39 -54
  59. data/lib/event_store_client/connection/url.rb +57 -0
  60. data/lib/event_store_client/connection/url_parser.rb +144 -0
  61. data/lib/event_store_client/data_decryptor.rb +2 -9
  62. data/lib/event_store_client/deserialized_event.rb +35 -10
  63. data/lib/event_store_client/encryption_metadata.rb +0 -1
  64. data/lib/event_store_client/event.rb +4 -2
  65. data/lib/event_store_client/extensions/options_extension.rb +87 -0
  66. data/lib/event_store_client/mapper/default.rb +12 -9
  67. data/lib/event_store_client/mapper/encrypted.rb +18 -17
  68. data/lib/event_store_client/types.rb +1 -1
  69. data/lib/event_store_client/utils.rb +30 -0
  70. data/lib/event_store_client/version.rb +1 -1
  71. data/lib/event_store_client.rb +8 -7
  72. metadata +74 -83
  73. data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
  74. data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
  75. data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
  76. data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
  77. data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
  78. data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
  79. data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
  80. data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
  81. data/lib/event_store_client/adapters/grpc/README.md +0 -16
  82. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
  83. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
  84. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
  85. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
  86. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
  87. data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
  88. data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
  89. data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
  90. data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
  91. data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
  92. data/lib/event_store_client/adapters/http/README.md +0 -16
  93. data/lib/event_store_client/adapters/http/client.rb +0 -161
  94. data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
  95. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
  96. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
  97. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
  98. data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
  99. data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
  100. data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
  101. data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
  102. data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
  103. data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
  104. data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
  105. data/lib/event_store_client/adapters/http/connection.rb +0 -46
  106. data/lib/event_store_client/adapters/http/request_method.rb +0 -28
  107. data/lib/event_store_client/adapters/http.rb +0 -17
  108. data/lib/event_store_client/adapters/in_memory.rb +0 -144
  109. data/lib/event_store_client/broker.rb +0 -40
  110. data/lib/event_store_client/catch_up_subscription.rb +0 -42
  111. data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
  112. data/lib/event_store_client/client.rb +0 -73
  113. data/lib/event_store_client/error_handler.rb +0 -10
  114. data/lib/event_store_client/subscription.rb +0 -23
  115. data/lib/event_store_client/subscriptions.rb +0 -38
  116. 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 'event_store_client/value_objects/read_direction.rb'
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/read_all'
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
- extend Dry::Configurable
9
-
10
- # Supported adapters: %i[api in_memory grpc]
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
- def self.adapter
48
- @adapter =
49
- case config.adapter
50
- when :http
51
- require 'event_store_client/adapters/http'
52
- HTTP::Client.new
53
- when :grpc
54
- require 'event_store_client/adapters/grpc'
55
- GRPC::Client.new
56
- else
57
- require 'event_store_client/adapters/in_memory'
58
- InMemory.new(
59
- mapper: config.mapper, per_page: config.per_page
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
- key =
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
- return 'application/json' if EventStoreClient.config.adapter == :grpc
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
- 'application/vnd.eventstore.events+json'
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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module EventStoreClient
5
4
  class EncryptionMetadata
6
5
  def call
@@ -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
- private
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?