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