event_store_client 0.2.8 → 1.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -37
  3. data/lib/event_store_client.rb +0 -2
  4. data/lib/event_store_client/adapters/grpc.rb +21 -0
  5. data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +149 -0
  6. data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +44 -0
  7. data/lib/event_store_client/adapters/grpc/Protos/operations.proto +45 -0
  8. data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +180 -0
  9. data/lib/event_store_client/adapters/grpc/Protos/projections.proto +174 -0
  10. data/lib/event_store_client/adapters/grpc/Protos/shared.proto +22 -0
  11. data/lib/event_store_client/adapters/grpc/Protos/streams.proto +242 -0
  12. data/lib/event_store_client/adapters/grpc/Protos/users.proto +119 -0
  13. data/lib/event_store_client/adapters/grpc/client.rb +119 -0
  14. data/lib/event_store_client/adapters/grpc/command_registrar.rb +32 -0
  15. data/lib/event_store_client/adapters/grpc/commands/command.rb +43 -0
  16. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +46 -0
  17. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +34 -0
  18. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +66 -0
  19. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +38 -0
  20. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +48 -0
  21. data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +45 -0
  22. data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +34 -0
  23. data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +42 -0
  24. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +57 -0
  25. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +35 -0
  26. data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +53 -0
  27. data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +80 -0
  28. data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +43 -0
  29. data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +35 -0
  30. data/lib/event_store_client/adapters/grpc/connection.rb +50 -0
  31. data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +140 -0
  32. data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +46 -0
  33. data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +53 -0
  34. data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +26 -0
  35. data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +49 -0
  36. data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +31 -0
  37. data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +213 -0
  38. data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +29 -0
  39. data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +193 -0
  40. data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +34 -0
  41. data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +35 -0
  42. data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +283 -0
  43. data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +29 -0
  44. data/lib/event_store_client/adapters/grpc/generated/users_pb.rb +126 -0
  45. data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +33 -0
  46. data/lib/event_store_client/adapters/http.rb +16 -0
  47. data/lib/event_store_client/adapters/http/README.md +16 -0
  48. data/lib/event_store_client/adapters/http/client.rb +160 -0
  49. data/lib/event_store_client/adapters/http/commands/command.rb +27 -0
  50. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +15 -0
  51. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +31 -0
  52. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +57 -0
  53. data/lib/event_store_client/adapters/http/commands/projections/create.rb +30 -0
  54. data/lib/event_store_client/adapters/http/commands/streams/append.rb +49 -0
  55. data/lib/event_store_client/adapters/http/commands/streams/delete.rb +16 -0
  56. data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +49 -0
  57. data/lib/event_store_client/adapters/http/commands/streams/read.rb +53 -0
  58. data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +17 -0
  59. data/lib/event_store_client/adapters/http/connection.rb +46 -0
  60. data/lib/event_store_client/adapters/http/request_method.rb +28 -0
  61. data/lib/event_store_client/adapters/in_memory.rb +139 -0
  62. data/lib/event_store_client/broker.rb +16 -5
  63. data/lib/event_store_client/client.rb +17 -69
  64. data/lib/event_store_client/configuration.rb +28 -14
  65. data/lib/event_store_client/data_decryptor.rb +13 -8
  66. data/lib/event_store_client/data_encryptor.rb +7 -6
  67. data/lib/event_store_client/deserialized_event.rb +4 -1
  68. data/lib/event_store_client/event.rb +2 -2
  69. data/lib/event_store_client/mapper/default.rb +0 -1
  70. data/lib/event_store_client/serializer/json.rb +2 -0
  71. data/lib/event_store_client/subscriptions.rb +4 -13
  72. data/lib/event_store_client/types.rb +3 -1
  73. data/lib/event_store_client/value_objects/read_direction.rb +43 -0
  74. data/lib/event_store_client/version.rb +1 -1
  75. metadata +97 -15
  76. data/lib/event_store_client/store_adapter.rb +0 -10
  77. data/lib/event_store_client/store_adapter/api/client.rb +0 -224
  78. data/lib/event_store_client/store_adapter/api/connection.rb +0 -43
  79. data/lib/event_store_client/store_adapter/api/request_method.rb +0 -30
  80. data/lib/event_store_client/store_adapter/in_memory.rb +0 -160
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'event_store_client/adapters/http/commands/persistent_subscriptions/ack'
4
+ module EventStoreClient
5
+ module HTTP
6
+ module Commands
7
+ module PersistentSubscriptions
8
+ class Read < Command
9
+ include Configuration
10
+
11
+ def call(stream_name, subscription_name, options: {})
12
+ count = options[:count] || 20
13
+ long_poll = options[:long_poll].to_i
14
+ headers = long_poll.positive? ? { 'ES-LongPoll' => long_poll.to_s } : {}
15
+ headers['Content-Type'] = 'application/vnd.eventstore.competingatom+json'
16
+ headers['Accept'] = 'application/vnd.eventstore.competingatom+json'
17
+ headers['ES-ResolveLinktos'] = (options[:resolve_links] || true).to_s
18
+
19
+ response = connection.call(
20
+ :get,
21
+ "/subscriptions/#{stream_name}/#{subscription_name}/#{count}",
22
+ headers: headers
23
+ )
24
+
25
+ return { events: [] } if response.body.nil? || response.body.empty?
26
+
27
+ body = JSON.parse(response.body)
28
+
29
+ ack_info = body['links'].find { |link| link['relation'] == 'ackAll' }
30
+ return { events: [] } unless ack_info
31
+ body['entries'].map do |entry|
32
+ yield deserialize_event(entry)
33
+ end
34
+ Ack.new(connection).call(ack_info['uri'])
35
+ Success()
36
+ end
37
+
38
+ private
39
+
40
+ def deserialize_event(entry)
41
+ event = EventStoreClient::Event.new(
42
+ id: entry['eventId'],
43
+ title: entry['title'],
44
+ type: entry['eventType'],
45
+ data: entry['data'] || '{}',
46
+ metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
47
+ )
48
+
49
+ config.mapper.deserialize(event)
50
+ rescue EventStoreClient::DeserializedEvent::InvalidDataError
51
+ event
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Projections
7
+ class Create < Command
8
+ def call(name, streams, options: {})
9
+ data =
10
+ <<~STRING
11
+ fromStreams(#{streams})
12
+ .when({
13
+ $any: function(s,e) {
14
+ linkTo("#{name}", e)
15
+ }
16
+ })
17
+ STRING
18
+
19
+ connection.call(
20
+ :post,
21
+ "/projections/continuous?name=#{name}&type=js&enabled=yes&emit=true&trackemittedstreams=true", # rubocop:disable Metrics/LineLength
22
+ body: data,
23
+ headers: {}
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Streams
7
+ class Append < Command
8
+ include Configuration
9
+
10
+ def call(stream_name, events, options: {})
11
+ expected_version = options[:expected_version]
12
+ serialized_events = events.map { |event| config.mapper.serialize(event) }
13
+ headers = {
14
+ 'ES-ExpectedVersion' => expected_version&.to_s
15
+ }.reject { |_key, val| val.nil? || val.empty? }
16
+
17
+ data = build_events_data(serialized_events)
18
+ response =
19
+ connection.call(:post, "/streams/#{stream_name}", body: data, headers: headers)
20
+ validate_response(response, expected_version)
21
+ end
22
+
23
+ private
24
+
25
+ def build_events_data(events)
26
+ [events].flatten.map do |event|
27
+ {
28
+ eventId: event.id,
29
+ eventType: event.type,
30
+ data: event.data,
31
+ metadata: event.metadata
32
+ }
33
+ end
34
+ end
35
+
36
+ def validate_response(resp, expected_version)
37
+ wrong_version = resp.status == 400 && resp.reason_phrase == 'Wrong expected EventNumber'
38
+ return Success() unless wrong_version
39
+
40
+ Failure(
41
+ "current version: #{resp.headers.fetch('es-currentversion')} | "\
42
+ "expected: #{expected_version}"
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Streams
7
+ class Delete < Command
8
+ def call(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
9
+ connection.call(:delete, "/streams/#{stream_name}", body: {})
10
+ Success()
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Streams
7
+ class LinkTo < Command
8
+ def call(stream_name, events, options: {})
9
+ expected_version = options[:expected_version]
10
+ data = build_linking_data(events)
11
+ headers = {
12
+ 'ES-ExpectedVersion' => expected_version&.to_s
13
+ }.reject { |_key, val| val.nil? || val.empty? }
14
+
15
+ response = connection.call(
16
+ :post,
17
+ "/streams/#{stream_name}",
18
+ body: data,
19
+ headers: headers
20
+ )
21
+ validate_response(response, expected_version)
22
+ end
23
+
24
+ private
25
+
26
+ def validate_response(resp, expected_version)
27
+ wrong_version = resp.status == 400 && resp.reason_phrase == 'Wrong expected EventNumber'
28
+ return Success() unless wrong_version
29
+
30
+ Failure(
31
+ "current version: #{resp.headers.fetch('es-currentversion')} | "\
32
+ "expected: #{expected_version}"
33
+ )
34
+ end
35
+
36
+ def build_linking_data(events)
37
+ [events].flatten.map do |event|
38
+ {
39
+ eventId: event.id,
40
+ eventType: '$>',
41
+ data: event.title
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Streams
7
+ class Read < Command
8
+ include Configuration
9
+
10
+ def call(stream_name, options: {})
11
+ count = options[:count] || config.per_page
12
+ start = options[:start].to_i
13
+ direction = options[:direction] || 'forward'
14
+ headers = {
15
+ 'ES-ResolveLinkTos' => options[:resolve_links].to_s,
16
+ 'Accept' => 'application/vnd.eventstore.atom+json'
17
+ }
18
+
19
+ response =
20
+ connection.call(
21
+ :get,
22
+ "/streams/#{stream_name}/#{start}/#{direction}/#{count}",
23
+ headers: headers
24
+ )
25
+
26
+ return Failure(:stream_not_found) unless response.success? || response.status == 404
27
+ return Failure(:connection_failed) if response.body.nil? || response.body.empty?
28
+ entries = JSON.parse(response.body)['entries'].map do |entry|
29
+ deserialize_event(entry)
30
+ end.reverse
31
+ Success(entries)
32
+ rescue Faraday::ConnectionFailed
33
+ Failure(:connection_failed)
34
+ end
35
+
36
+ private
37
+
38
+ def deserialize_event(entry)
39
+ event = EventStoreClient::Event.new(
40
+ id: entry['eventId'],
41
+ title: entry['title'],
42
+ type: entry['eventType'],
43
+ data: entry['data'] || '{}',
44
+ metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
45
+ )
46
+
47
+ config.mapper.deserialize(event)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ module Commands
6
+ module Streams
7
+ class Tombstone < Command
8
+ def call(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
9
+ headers = { 'ES-HardDelete' => 'true' }
10
+ connection.call(:delete, "/streams/#{stream_name}", body: {}, headers: headers)
11
+ Success()
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'event_store_client/adapters/http/request_method'
5
+
6
+ module EventStoreClient
7
+ module HTTP
8
+ class Connection
9
+ include Configuration
10
+
11
+ def call(method_name, path, body: {}, headers: {})
12
+ method = RequestMethod.new(method_name)
13
+ connection.send(method.to_s, path) do |req|
14
+ req.headers = req.headers.merge(headers)
15
+ req.body = body.is_a?(String) ? body : body.to_json
16
+ req.params['embed'] = 'body' if method == :get
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def initialize(uri, options = {})
23
+ @connection = set_connection(uri, options)
24
+ end
25
+
26
+ attr_reader :options, :connection
27
+
28
+ DEFAULT_HEADERS = {
29
+ 'Content-Type' => 'application/vnd.eventstore.events+json'
30
+ # 'Accept' => 'application/vnd.eventstore.atom+json',
31
+ }.freeze
32
+
33
+ def set_connection(uri, connection_options)
34
+ Faraday.new(
35
+ {
36
+ url: uri.to_s,
37
+ headers: DEFAULT_HEADERS
38
+ }.merge(connection_options)
39
+ ) do |conn|
40
+ conn.basic_auth(config.eventstore_user, config.eventstore_password)
41
+ conn.adapter Faraday.default_adapter
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ module HTTP
5
+ class RequestMethod
6
+ InvalidMethodError = Class.new(StandardError)
7
+ def ==(other)
8
+ name == other.to_s
9
+ end
10
+
11
+ def to_s
12
+ name
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :name
18
+
19
+ SUPPORTED_METHODS = %w[get post put delete].freeze
20
+
21
+ def initialize(name)
22
+ raise InvalidMethodError unless SUPPORTED_METHODS.include?(name.to_s)
23
+
24
+ @name = name.to_s
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventStoreClient
4
+ class InMemory
5
+ Response = Struct.new(:body, :status) do
6
+ def success?
7
+ status == 200
8
+ end
9
+ end
10
+
11
+ attr_reader :event_store
12
+
13
+ def append_to_stream(stream_name, events, options: {}) # rubocop:disable Lint/UnusedMethodArgument,Metrics/LineLength
14
+ event_store[stream_name] = [] unless event_store.key?(stream_name)
15
+ [events].flatten.each do |event|
16
+ event_store[stream_name].unshift(
17
+ 'eventId' => event.id,
18
+ 'data' => event.data,
19
+ 'eventType' => event.type,
20
+ 'metaData' => event.metadata,
21
+ 'positionEventNumber' => event_store[stream_name].length
22
+ )
23
+ end
24
+ end
25
+
26
+ def delete_stream(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
27
+ event_store.delete(stream_name)
28
+ end
29
+
30
+ def tombstone_stream(stream_name, options: {}) # rubocop:disable Lint/UnusedMethodArgument
31
+ event_store.delete(stream_name)
32
+ end
33
+
34
+ def read(stream_name, options: {})
35
+ start = options[:start] == 'head' ? options[:start] : options[:start].to_i
36
+ direction = options[:direction] || 'forward'
37
+ response =
38
+ if %w[forward forwards].include?(direction)
39
+ read_stream_forward(stream_name, start: start)
40
+ else
41
+ read_stream_backward(stream_name, start: start)
42
+ end
43
+
44
+ res = Response.new(response.to_json, 200)
45
+
46
+ return [] if res.body.nil? || res.body.empty?
47
+ JSON.parse(res.body)['entries'].map do |entry|
48
+ deserialize_event(entry)
49
+ end.reverse
50
+ end
51
+
52
+ def read_all_from_stream(stream_name, options: {})
53
+ read(stream_name, options: options)
54
+ end
55
+
56
+ def subscribe_to_stream(subscription, **)
57
+ # TODO: implement method body
58
+ end
59
+
60
+ def link_to(stream_name, events, **)
61
+ append_to_stream(stream_name, events)
62
+ end
63
+
64
+ def listen(subscription, options: {})
65
+ # TODO: implement method body
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :per_page, :mapper
71
+
72
+ def initialize(mapper:, per_page: 20)
73
+ @per_page = per_page
74
+ @mapper = mapper
75
+ @event_store = {}
76
+ end
77
+
78
+ def read_stream_backward(stream_name, start: 0)
79
+ response = {
80
+ 'entries' => [],
81
+ 'links' => []
82
+ }
83
+ return response unless event_store.key?(stream_name)
84
+
85
+ start = start == 'head' ? event_store[stream_name].length - 1 : start
86
+ last_index = (start - per_page)
87
+ response['entries'] = event_store[stream_name].select do |event|
88
+ event['positionEventNumber'] > last_index &&
89
+ event['positionEventNumber'] <= start
90
+ end
91
+ response['links'] = links(stream_name, last_index, 'next', response['entries'], per_page)
92
+
93
+ response
94
+ end
95
+
96
+ def read_stream_forward(stream_name, start: 0)
97
+ response = {
98
+ 'entries' => [],
99
+ 'links' => []
100
+ }
101
+
102
+ return response unless event_store.key?(stream_name)
103
+
104
+ last_index = start + per_page
105
+ response['entries'] = event_store[stream_name].select do |event|
106
+ event['positionEventNumber'] < last_index &&
107
+ event['positionEventNumber'] >= start
108
+ end
109
+ response['links'] =
110
+ links(stream_name, last_index, 'previous', response['entries'], per_page)
111
+
112
+ response
113
+ end
114
+
115
+ def links(stream_name, batch_size, direction, entries, count)
116
+ if entries.empty? || batch_size.negative?
117
+ []
118
+ else
119
+ [{
120
+ 'uri' =>
121
+ "/streams/#{stream_name}/#{batch_size}/#{direction}/#{count}",
122
+ 'relation' => direction
123
+ }]
124
+ end
125
+ end
126
+
127
+ def deserialize_event(entry)
128
+ event = EventStoreClient::Event.new(
129
+ id: entry['eventId'],
130
+ title: entry['title'],
131
+ type: entry['eventType'],
132
+ data: entry['data'].to_json || '{}',
133
+ metadata: entry['isMetaData'] ? entry['metaData'] : '{}'
134
+ )
135
+
136
+ mapper.deserialize(event)
137
+ end
138
+ end
139
+ end