event_store_client 2.0.4 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +0 -4
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +0 -4
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +1 -32
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +0 -3
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +0 -3
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +2 -2
- data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +0 -3
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +0 -3
- data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +0 -2
- data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +0 -2
- data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +0 -2
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +2 -1
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +4 -3
- data/lib/event_store_client/adapters/grpc.rb +3 -1
- data/lib/event_store_client/data_decryptor.rb +5 -2
- data/lib/event_store_client/deserialized_event.rb +9 -2
- data/lib/event_store_client/mapper/default.rb +12 -27
- data/lib/event_store_client/mapper/encrypted.rb +38 -62
- data/lib/event_store_client/serialized_event.rb +43 -0
- data/lib/event_store_client/serializer/event_deserializer.rb +74 -0
- data/lib/event_store_client/serializer/event_serializer.rb +69 -0
- data/lib/event_store_client/serializer/json.rb +20 -11
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +4 -1
- metadata +19 -32
- data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +0 -52
- data/lib/event_store_client/event.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 215ed29dc778024dfc7122cc295ec29e112284f67cb3ed4e74960d47a1e0a32e
|
4
|
+
data.tar.gz: 0be0bb78dfe0ce97fb3d171a9d145d82091e81978944c1dfb891d2609df12356
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba84c9bd5b6148f4c8eaa4332e4a264364edcdf2642ef680a403a29d0406306e19db1f70422e711601ffe349df50c8b54a458f201d45288f711864aa6823c683
|
7
|
+
data.tar.gz: d2932967bcd5018fe330b0923231ee83062c3a8a713bcd6e6e92d8ddfa5bd633609415dd6ecf6b76d479d115b34cdd02a4de3f3f8b5c57a4010e29b40e0d8b6e
|
data/README.md
CHANGED
@@ -44,6 +44,10 @@ See documentation chapters for the usage reference:
|
|
44
44
|
- [Deleting streams](docs/deleting_streams.md)
|
45
45
|
- [Encrypting events](docs/encrypting_events.md)
|
46
46
|
|
47
|
+
### Subscriptions
|
48
|
+
|
49
|
+
We have written a gem that helps you to manage and handle Catch-up Subscriptions - `event_store_subscription`. You could check it [here](https://github.com/yousty/event_store_subscriptions).
|
50
|
+
|
47
51
|
## Contributing
|
48
52
|
|
49
53
|
Do you want to contribute? Welcome!
|
@@ -2,10 +2,6 @@
|
|
2
2
|
|
3
3
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
4
4
|
|
5
|
-
require 'event_store_client/adapters/grpc/generated/shared_pb'
|
6
|
-
require 'event_store_client/adapters/grpc/generated/gossip_pb'
|
7
|
-
require 'event_store_client/adapters/grpc/generated/gossip_services_pb'
|
8
|
-
|
9
5
|
module EventStoreClient
|
10
6
|
module GRPC
|
11
7
|
module Cluster
|
@@ -1,9 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'event_store_client/adapters/grpc/generated/shared_pb'
|
4
|
-
require 'event_store_client/adapters/grpc/generated/gossip_pb'
|
5
|
-
require 'event_store_client/adapters/grpc/generated/gossip_services_pb'
|
6
|
-
|
7
3
|
module EventStoreClient
|
8
4
|
module GRPC
|
9
5
|
module Commands
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'event_store_client/adapters/grpc/generated/streams_pb'
|
4
|
-
require 'event_store_client/adapters/grpc/generated/streams_services_pb'
|
5
|
-
|
6
3
|
module EventStoreClient
|
7
4
|
module GRPC
|
8
5
|
module Commands
|
@@ -11,8 +8,6 @@ module EventStoreClient
|
|
11
8
|
use_request EventStore::Client::Streams::AppendReq
|
12
9
|
use_service EventStore::Client::Streams::Streams::Stub
|
13
10
|
|
14
|
-
ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
|
15
|
-
|
16
11
|
# @api private
|
17
12
|
# @see {EventStoreClient::GRPC::Client#append_to_stream}
|
18
13
|
def call(stream_name, event, options:, &blk)
|
@@ -26,8 +21,6 @@ module EventStoreClient
|
|
26
21
|
service.append(payload, metadata: metadata)
|
27
22
|
end
|
28
23
|
validate_response(response)
|
29
|
-
rescue ::GRPC::Unavailable => e
|
30
|
-
Failure(e)
|
31
24
|
end
|
32
25
|
|
33
26
|
private
|
@@ -36,31 +29,7 @@ module EventStoreClient
|
|
36
29
|
# @return [EventStore::Client::Streams::AppendReq::ProposedMessage]
|
37
30
|
def proposed_message(event)
|
38
31
|
serialized_event = config.mapper.serialize(event)
|
39
|
-
|
40
|
-
custom_metadata = custom_metadata(serialized_event.type, event_metadata)
|
41
|
-
opts =
|
42
|
-
{
|
43
|
-
id: {
|
44
|
-
string: serialized_event.id
|
45
|
-
},
|
46
|
-
data: serialized_event.data.b,
|
47
|
-
custom_metadata: custom_metadata.to_json,
|
48
|
-
metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA)
|
49
|
-
}
|
50
|
-
EventStore::Client::Streams::AppendReq::ProposedMessage.new(opts)
|
51
|
-
end
|
52
|
-
|
53
|
-
# @param event_type [String]
|
54
|
-
# @param event_metadata [Hash]
|
55
|
-
# @return [Hash]
|
56
|
-
def custom_metadata(event_type, event_metadata)
|
57
|
-
{
|
58
|
-
type: event_type,
|
59
|
-
created_at: Time.now.utc,
|
60
|
-
encryption: event_metadata['encryption'],
|
61
|
-
'content-type': event_metadata['content-type'],
|
62
|
-
transaction: event_metadata['transaction']
|
63
|
-
}.compact
|
32
|
+
EventStore::Client::Streams::AppendReq::ProposedMessage.new(serialized_event.to_grpc)
|
64
33
|
end
|
65
34
|
|
66
35
|
# @param stream_name [String]
|
@@ -8,8 +8,8 @@ module EventStoreClient
|
|
8
8
|
# @see {EventStoreClient::GRPC::Client#hard_delete_stream}
|
9
9
|
def call(stream_name, event, options:, &blk)
|
10
10
|
append_cmd = Append.new(**connection_options)
|
11
|
-
link_event =
|
12
|
-
id: event.id, type:
|
11
|
+
link_event = DeserializedEvent.new(
|
12
|
+
id: event.id, type: DeserializedEvent::LINK_TYPE, data: event.title
|
13
13
|
)
|
14
14
|
append_cmd.call(stream_name, link_event, options: options, &blk)
|
15
15
|
end
|
@@ -6,6 +6,7 @@ module EventStoreClient
|
|
6
6
|
module Streams
|
7
7
|
class ProcessResponse
|
8
8
|
include Dry::Monads[:result]
|
9
|
+
include Configuration
|
9
10
|
|
10
11
|
# @api private
|
11
12
|
# @param response [EventStore::Client::Streams::ReadResp]
|
@@ -18,7 +19,7 @@ module EventStoreClient
|
|
18
19
|
return unless response.event&.event
|
19
20
|
|
20
21
|
Success(
|
21
|
-
|
22
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
22
23
|
)
|
23
24
|
end
|
24
25
|
end
|
@@ -6,6 +6,7 @@ module EventStoreClient
|
|
6
6
|
module Streams
|
7
7
|
class ProcessResponses
|
8
8
|
include Dry::Monads[:result]
|
9
|
+
include Configuration
|
9
10
|
|
10
11
|
# @api private
|
11
12
|
# @param responses [Array<EventStore::Client::Streams::ReadResp>]
|
@@ -17,12 +18,12 @@ module EventStoreClient
|
|
17
18
|
return Success(responses) if skip_deserialization
|
18
19
|
|
19
20
|
events =
|
20
|
-
responses.map do |
|
21
|
+
responses.map do |response|
|
21
22
|
# It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
|
22
23
|
# example. Such responses should be skipped. See generated files for more info.
|
23
|
-
next unless
|
24
|
+
next unless response.event&.event
|
24
25
|
|
25
|
-
|
26
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
26
27
|
end
|
27
28
|
Success(events.compact)
|
28
29
|
end
|
@@ -3,10 +3,12 @@
|
|
3
3
|
require 'grpc'
|
4
4
|
require 'dry-monads'
|
5
5
|
|
6
|
+
# Load all generated by google-protobuf files
|
7
|
+
Dir[File.expand_path('grpc/generated/*.rb', __dir__)].each { |f| require f }
|
8
|
+
|
6
9
|
require 'event_store_client/adapters/grpc/options/streams/read_options'
|
7
10
|
require 'event_store_client/adapters/grpc/options/streams/write_options'
|
8
11
|
|
9
|
-
require 'event_store_client/adapters/grpc/shared/event_deserializer'
|
10
12
|
require 'event_store_client/adapters/grpc/shared/options/stream_options'
|
11
13
|
require 'event_store_client/adapters/grpc/shared/options/filter_options'
|
12
14
|
require 'event_store_client/adapters/grpc/shared/streams/process_response'
|
@@ -10,9 +10,11 @@ module EventStoreClient
|
|
10
10
|
|
11
11
|
def call
|
12
12
|
return encrypted_data if encryption_metadata.empty?
|
13
|
+
result = find_key(encryption_metadata['key'])
|
14
|
+
return encrypted_data unless result.success?
|
13
15
|
|
14
16
|
decrypt_attributes(
|
15
|
-
key:
|
17
|
+
key: result.value!,
|
16
18
|
data: encrypted_data,
|
17
19
|
attributes: encryption_metadata['attributes']
|
18
20
|
)
|
@@ -49,8 +51,9 @@ module EventStoreClient
|
|
49
51
|
dupl
|
50
52
|
end
|
51
53
|
|
54
|
+
# @return [Dry::Monads::Result]
|
52
55
|
def find_key(identifier)
|
53
|
-
key_repository.find(identifier)
|
56
|
+
key_repository.find(identifier)
|
54
57
|
end
|
55
58
|
end
|
56
59
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry/schema'
|
4
|
-
|
5
3
|
module EventStoreClient
|
6
4
|
class DeserializedEvent
|
5
|
+
LINK_TYPE = '$>'
|
6
|
+
|
7
7
|
InvalidDataError = Class.new(StandardError)
|
8
8
|
private_constant :InvalidDataError
|
9
9
|
|
@@ -12,6 +12,7 @@ module EventStoreClient
|
|
12
12
|
|
13
13
|
# @args [Hash] opts
|
14
14
|
# @option opts [Boolean] :skip_validation
|
15
|
+
# @option opts [UUID] :id
|
15
16
|
# @option opts [Hash] :data
|
16
17
|
# @option opts [Hash] :metadata
|
17
18
|
# @option opts [String] :type
|
@@ -68,6 +69,12 @@ module EventStoreClient
|
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
72
|
+
# Detect whether an event is a link event
|
73
|
+
# @return [Boolean]
|
74
|
+
def link?
|
75
|
+
type == LINK_TYPE
|
76
|
+
end
|
77
|
+
|
71
78
|
private
|
72
79
|
|
73
80
|
def validate(data)
|
@@ -1,47 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Layout/LineLength
|
4
|
+
|
3
5
|
module EventStoreClient
|
4
6
|
module Mapper
|
5
7
|
class Default
|
6
8
|
attr_reader :serializer
|
7
9
|
private :serializer
|
8
10
|
|
11
|
+
# @param serializer [#serialize, #deserialize]
|
9
12
|
def initialize(serializer: Serializer::Json)
|
10
13
|
@serializer = serializer
|
11
14
|
end
|
12
15
|
|
16
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
17
|
+
# @return [EventStoreClient::SerializedEvent]
|
13
18
|
def serialize(event)
|
14
|
-
|
15
|
-
id: event.respond_to?(:id) ? event.id : nil,
|
16
|
-
type: (event.respond_to?(:type) ? event.type : nil) || event.class.to_s,
|
17
|
-
data: serializer.serialize(event.data),
|
18
|
-
metadata: serializer.serialize(event.metadata)
|
19
|
-
)
|
19
|
+
Serializer::EventSerializer.call(event, serializer: serializer)
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
# @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
23
|
+
# @return event [EventStoreClient::DeserializedEvent]
|
24
|
+
def deserialize(event_or_raw_event, **)
|
25
|
+
return event_or_raw_event if event_or_raw_event.is_a?(EventStoreClient::DeserializedEvent)
|
25
26
|
|
26
|
-
|
27
|
-
begin
|
28
|
-
Object.const_get(event.type)
|
29
|
-
rescue NameError
|
30
|
-
EventStoreClient.config.default_event_class
|
31
|
-
end
|
32
|
-
event_class.new(
|
33
|
-
skip_validation: true,
|
34
|
-
id: event.id,
|
35
|
-
type: event.type,
|
36
|
-
title: event.title,
|
37
|
-
data: data,
|
38
|
-
metadata: metadata,
|
39
|
-
stream_revision: event.stream_revision,
|
40
|
-
commit_position: event.commit_position,
|
41
|
-
prepare_position: event.prepare_position,
|
42
|
-
stream_name: event.stream_name
|
43
|
-
)
|
27
|
+
Serializer::EventDeserializer.call(event_or_raw_event, serializer: serializer)
|
44
28
|
end
|
45
29
|
end
|
46
30
|
end
|
47
31
|
end
|
32
|
+
# rubocop:enable Layout/LineLength
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Metrics/AbcSize, Layout/LineLength, Style/IfUnlessModifier
|
4
|
+
|
3
5
|
require 'event_store_client/encryption_metadata'
|
4
6
|
require 'event_store_client/data_encryptor'
|
5
7
|
require 'event_store_client/data_decryptor'
|
@@ -15,88 +17,62 @@ module EventStoreClient
|
|
15
17
|
attr_reader :key_repository, :serializer
|
16
18
|
private :key_repository, :serializer
|
17
19
|
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# * +serializer+ - object used to serialize data. By default JSON serializer is used.
|
20
|
+
# @param key_repository [#find, #create, #encrypt, #decrypt]
|
21
|
+
# See spec/support/dummy_repository.rb for the example of simple in-memory implementation
|
22
|
+
# @param serializer [#serialize, #deserialize]
|
22
23
|
def initialize(key_repository, serializer: Serializer::Json)
|
23
24
|
@key_repository = key_repository
|
24
25
|
@serializer = serializer
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
#
|
29
|
-
# Accepts specific event class instance with:
|
30
|
-
# * +#data+ - hash with non-encrypted values.
|
31
|
-
# * encryption_schema - hash with information which data to encrypt and
|
32
|
-
# which key should be used as an identifier.
|
33
|
-
# *Returns*: General +Event+ instance with encrypted data
|
28
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
29
|
+
# @return [Hash]
|
34
30
|
def serialize(event)
|
35
|
-
# Links
|
36
|
-
return Default.new(serializer: serializer).serialize(event) if event.
|
31
|
+
# Links don't need to be encrypted
|
32
|
+
return Default.new(serializer: serializer).serialize(event) if event.link?
|
33
|
+
|
34
|
+
serialized = Serializer::EventSerializer.call(event, serializer: serializer)
|
35
|
+
encryption_schema =
|
36
|
+
if event.class.respond_to?(:encryption_schema)
|
37
|
+
event.class.encryption_schema
|
38
|
+
end
|
37
39
|
|
38
|
-
encryption_schema = (
|
39
|
-
event.class.respond_to?(:encryption_schema) &&
|
40
|
-
event.class.encryption_schema
|
41
|
-
)
|
42
40
|
encryptor = EventStoreClient::DataEncryptor.new(
|
43
|
-
data:
|
41
|
+
data: serialized.data,
|
44
42
|
schema: encryption_schema,
|
45
43
|
repository: key_repository
|
46
44
|
)
|
47
45
|
encryptor.call
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
metadata: serializer.serialize(
|
52
|
-
event.metadata.merge(encryption: encryptor.encryption_metadata)
|
53
|
-
),
|
54
|
-
type: event.class.to_s
|
55
|
-
)
|
46
|
+
serialized.data = encryptor.encrypted_data
|
47
|
+
serialized.custom_metadata['encryption'] = encryptor.encryption_metadata
|
48
|
+
serialized
|
56
49
|
end
|
57
50
|
|
58
|
-
##
|
59
51
|
# Decrypts the given event's subset of data.
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
encryption_schema = metadata['encryption']
|
52
|
+
# @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
53
|
+
# @param skip_decryption [Boolean]
|
54
|
+
# @return event [EventStoreClient::DeserializedEvent]
|
55
|
+
def deserialize(event_or_raw_event, skip_decryption: false)
|
56
|
+
if skip_decryption
|
57
|
+
return Default.new(serializer: serializer).deserialize(event_or_raw_event)
|
58
|
+
end
|
68
59
|
|
69
|
-
|
70
|
-
if
|
71
|
-
|
60
|
+
event =
|
61
|
+
if event_or_raw_event.is_a?(EventStoreClient::DeserializedEvent)
|
62
|
+
event_or_raw_event
|
72
63
|
else
|
73
|
-
|
74
|
-
data: serializer.deserialize(event.data),
|
75
|
-
schema: encryption_schema,
|
76
|
-
repository: key_repository
|
77
|
-
).call
|
78
|
-
end
|
79
|
-
|
80
|
-
event_class =
|
81
|
-
begin
|
82
|
-
Object.const_get(event.type)
|
83
|
-
rescue NameError
|
84
|
-
EventStoreClient.config.default_event_class
|
64
|
+
Serializer::EventDeserializer.call(event_or_raw_event, serializer: serializer)
|
85
65
|
end
|
86
66
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
stream_revision: event.stream_revision,
|
95
|
-
commit_position: event.commit_position,
|
96
|
-
prepare_position: event.prepare_position,
|
97
|
-
stream_name: event.stream_name
|
98
|
-
)
|
67
|
+
decrypted_data =
|
68
|
+
EventStoreClient::DataDecryptor.new(
|
69
|
+
data: event.data,
|
70
|
+
schema: event.metadata['encryption'],
|
71
|
+
repository: key_repository
|
72
|
+
).call
|
73
|
+
event.class.new(**event.to_h.merge(data: decrypted_data, skip_validation: true))
|
99
74
|
end
|
100
75
|
end
|
101
76
|
end
|
102
77
|
end
|
78
|
+
# rubocop:enable Metrics/AbcSize, Layout/LineLength, Style/IfUnlessModifier
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
class SerializedEvent
|
5
|
+
include Extensions::OptionsExtension
|
6
|
+
|
7
|
+
option(:id)
|
8
|
+
option(:data)
|
9
|
+
option(:custom_metadata)
|
10
|
+
option(:metadata)
|
11
|
+
option(:serializer)
|
12
|
+
|
13
|
+
# Constructs a hash that can be passed directly in the proposed_message attribute of the append
|
14
|
+
# request, or it can be used to instantiate the raw EventStore event.
|
15
|
+
# Example:
|
16
|
+
# ```ruby
|
17
|
+
# serialized_event = EventStoreClient::SerializedEvent.new(
|
18
|
+
# id: 'some id',
|
19
|
+
# data: { foo: :bar },
|
20
|
+
# custom_metadata: { bar: :baz },
|
21
|
+
# metadata: { baz: :foo },
|
22
|
+
# serializer: EventStoreClient::Serializer::Json
|
23
|
+
# )
|
24
|
+
# # Compute proposed_message
|
25
|
+
# EventStore::Client::Streams::AppendReq::ProposedMessage.new(
|
26
|
+
# serialized_event.to_grpc
|
27
|
+
# )
|
28
|
+
# # Compute raw event
|
29
|
+
# EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent.new(
|
30
|
+
# serialized_event.to_grpc
|
31
|
+
# )
|
32
|
+
# ```
|
33
|
+
# @return [Hash]
|
34
|
+
def to_grpc
|
35
|
+
{
|
36
|
+
id: { string: id },
|
37
|
+
data: serializer.serialize(data),
|
38
|
+
custom_metadata: serializer.serialize(custom_metadata),
|
39
|
+
metadata: metadata
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/AbcSize, Layout/LineLength
|
4
|
+
|
5
|
+
module EventStoreClient
|
6
|
+
module Serializer
|
7
|
+
class EventDeserializer
|
8
|
+
class << self
|
9
|
+
# @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
10
|
+
# @param serializer [#serialize, #deserialize]
|
11
|
+
# @return [EventStoreClient::DeserializedEvent]
|
12
|
+
def call(raw_event, serializer: Serializer::Json)
|
13
|
+
new(serializer: serializer).call(raw_event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :serializer
|
18
|
+
private :serializer
|
19
|
+
|
20
|
+
# @param serializer [#serialize, #deserialize]
|
21
|
+
def initialize(serializer:)
|
22
|
+
@serializer = serializer
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
26
|
+
# @return [EventStoreClient::DeserializedEvent]
|
27
|
+
def call(raw_event)
|
28
|
+
data = serializer.deserialize(normalize_serialized(raw_event.data))
|
29
|
+
custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
|
30
|
+
metadata = custom_metadata.merge(raw_event.metadata.to_h)
|
31
|
+
|
32
|
+
event_class(metadata['type']).new(
|
33
|
+
skip_validation: true,
|
34
|
+
id: raw_event.id.string,
|
35
|
+
title: "#{raw_event.stream_revision}@#{raw_event.stream_identifier.stream_name}",
|
36
|
+
type: metadata['type'],
|
37
|
+
data: data,
|
38
|
+
metadata: metadata,
|
39
|
+
stream_revision: raw_event.stream_revision,
|
40
|
+
commit_position: raw_event.commit_position,
|
41
|
+
prepare_position: raw_event.prepare_position,
|
42
|
+
stream_name: raw_event.stream_identifier.stream_name
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @param event_type [String]
|
49
|
+
# @return [Class<EventStoreClient::DeserializedEvent>]
|
50
|
+
def event_class(event_type)
|
51
|
+
Object.const_get(event_type)
|
52
|
+
rescue NameError, TypeError
|
53
|
+
config.logger&.debug(<<~TEXT.strip)
|
54
|
+
Unable to resolve class by `#{event_type}' event type. \
|
55
|
+
Picking default `#{config.default_event_class}' event class to instantiate the event.
|
56
|
+
TEXT
|
57
|
+
config.default_event_class
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param raw_data [String]
|
61
|
+
# @return [String]
|
62
|
+
def normalize_serialized(raw_data)
|
63
|
+
return serializer.serialize({}) if raw_data.empty?
|
64
|
+
|
65
|
+
raw_data
|
66
|
+
end
|
67
|
+
|
68
|
+
def config
|
69
|
+
EventStoreClient.config
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# rubocop:enable Metrics/AbcSize, Layout/LineLength
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module Serializer
|
5
|
+
class EventSerializer
|
6
|
+
ALLOWED_EVENT_METADATA = %w[type content-type created_at].freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
10
|
+
# @param serializer [#serialize, #deserialize]
|
11
|
+
# @return [EventStoreClient::SerializedEvent]
|
12
|
+
def call(event, serializer: Serializer::Json)
|
13
|
+
new(serializer: serializer).call(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :serializer
|
18
|
+
private :serializer
|
19
|
+
|
20
|
+
# @param serializer [#serialize, #deserialize]
|
21
|
+
def initialize(serializer:)
|
22
|
+
@serializer = serializer
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
26
|
+
# @return [EventStoreClient::SerializedEvent]
|
27
|
+
def call(event)
|
28
|
+
event_metadata = metadata(event)
|
29
|
+
event_custom_metadata = custom_metadata(event, event_metadata)
|
30
|
+
SerializedEvent.new(
|
31
|
+
id: event.id || SecureRandom.uuid,
|
32
|
+
data: data(event),
|
33
|
+
custom_metadata: event_custom_metadata,
|
34
|
+
metadata: event_metadata.slice(*ALLOWED_EVENT_METADATA),
|
35
|
+
serializer: serializer
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
42
|
+
# @return [Hash]
|
43
|
+
def metadata(event)
|
44
|
+
metadata = serializer.deserialize(serializer.serialize(event.metadata))
|
45
|
+
metadata['created_at'] ||= Time.now.utc.to_s
|
46
|
+
metadata
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
50
|
+
# @param metadata [Hash]
|
51
|
+
# @return [Hash]
|
52
|
+
def custom_metadata(event, metadata)
|
53
|
+
metadata.
|
54
|
+
slice('created_at', 'encryption', 'content-type', 'transaction').
|
55
|
+
merge('type' => event.type.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param event [EventStoreClient::DeserializedEvent]
|
59
|
+
# @return [Hash, String]
|
60
|
+
def data(event)
|
61
|
+
# Link events are special events. They contain special string value which shouldn't be
|
62
|
+
# serialized.
|
63
|
+
return event.data if event.link?
|
64
|
+
|
65
|
+
serializer.deserialize(serializer.serialize(event.data))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,19 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
4
|
-
module
|
5
|
-
|
6
|
-
|
3
|
+
module EventStoreClient
|
4
|
+
module Serializer
|
5
|
+
module Json
|
6
|
+
# @param data [String, Hash]
|
7
|
+
# @return [Hash]
|
8
|
+
def self.deserialize(data)
|
9
|
+
return data if data.is_a?(Hash)
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
result = JSON.parse(data)
|
12
|
+
return result if result.is_a?(Hash)
|
13
|
+
|
14
|
+
{ 'message' => result }
|
15
|
+
rescue JSON::ParserError
|
16
|
+
{ 'message' => data }
|
17
|
+
end
|
12
18
|
|
13
|
-
|
14
|
-
return
|
19
|
+
# @param data [String, Object]
|
20
|
+
# @return [String]
|
21
|
+
def self.serialize(data)
|
22
|
+
return data if data.is_a?(String)
|
15
23
|
|
16
|
-
|
24
|
+
JSON.generate(data)
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
data/lib/event_store_client.rb
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
module EventStoreClient
|
4
4
|
end
|
5
5
|
|
6
|
+
require 'json'
|
6
7
|
require 'event_store_client/types'
|
7
8
|
|
8
9
|
require 'event_store_client/serializer/json'
|
10
|
+
require 'event_store_client/serializer/event_serializer'
|
11
|
+
require 'event_store_client/serializer/event_deserializer'
|
9
12
|
|
10
13
|
require 'event_store_client/mapper'
|
11
14
|
|
@@ -15,8 +18,8 @@ require 'event_store_client/utils'
|
|
15
18
|
|
16
19
|
require 'event_store_client/connection/url'
|
17
20
|
require 'event_store_client/connection/url_parser'
|
18
|
-
require 'event_store_client/event'
|
19
21
|
require 'event_store_client/deserialized_event'
|
22
|
+
require 'event_store_client/serialized_event'
|
20
23
|
require 'event_store_client/configuration'
|
21
24
|
|
22
25
|
require 'event_store_client/adapters/grpc'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_store_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Wilgosz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-configurable
|
@@ -38,34 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: dry-schema
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: dry-struct
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
42
|
name: grpc
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +150,20 @@ dependencies:
|
|
178
150
|
- - "~>"
|
179
151
|
- !ruby/object:Gem::Version
|
180
152
|
version: 0.9.5
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: dry-schema
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '1'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '1'
|
181
167
|
description: Easy to use client for event-sources applications written in ruby
|
182
168
|
email:
|
183
169
|
- sebastian@driggl.com
|
@@ -240,7 +226,6 @@ files:
|
|
240
226
|
- lib/event_store_client/adapters/grpc/generated/users_services_pb.rb
|
241
227
|
- lib/event_store_client/adapters/grpc/options/streams/read_options.rb
|
242
228
|
- lib/event_store_client/adapters/grpc/options/streams/write_options.rb
|
243
|
-
- lib/event_store_client/adapters/grpc/shared/event_deserializer.rb
|
244
229
|
- lib/event_store_client/adapters/grpc/shared/options/filter_options.rb
|
245
230
|
- lib/event_store_client/adapters/grpc/shared/options/stream_options.rb
|
246
231
|
- lib/event_store_client/adapters/grpc/shared/streams/process_response.rb
|
@@ -252,11 +237,13 @@ files:
|
|
252
237
|
- lib/event_store_client/data_encryptor.rb
|
253
238
|
- lib/event_store_client/deserialized_event.rb
|
254
239
|
- lib/event_store_client/encryption_metadata.rb
|
255
|
-
- lib/event_store_client/event.rb
|
256
240
|
- lib/event_store_client/extensions/options_extension.rb
|
257
241
|
- lib/event_store_client/mapper.rb
|
258
242
|
- lib/event_store_client/mapper/default.rb
|
259
243
|
- lib/event_store_client/mapper/encrypted.rb
|
244
|
+
- lib/event_store_client/serialized_event.rb
|
245
|
+
- lib/event_store_client/serializer/event_deserializer.rb
|
246
|
+
- lib/event_store_client/serializer/event_serializer.rb
|
260
247
|
- lib/event_store_client/serializer/json.rb
|
261
248
|
- lib/event_store_client/types.rb
|
262
249
|
- lib/event_store_client/utils.rb
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# rubocop:disable Metrics/AbcSize
|
4
|
-
|
5
|
-
module EventStoreClient
|
6
|
-
module GRPC
|
7
|
-
module Shared
|
8
|
-
class EventDeserializer
|
9
|
-
include Configuration
|
10
|
-
|
11
|
-
# @param raw_event [
|
12
|
-
# Array<EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent>,
|
13
|
-
# Array<EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent>
|
14
|
-
# ]
|
15
|
-
# @param skip_decryption [Boolean]
|
16
|
-
# @return [EventStoreClient::DeserializedEvent]
|
17
|
-
def call(raw_event, skip_decryption: false)
|
18
|
-
data = normalize_serialized(raw_event.data)
|
19
|
-
custom_metadata = normalize_serialized(raw_event.custom_metadata)
|
20
|
-
|
21
|
-
metadata =
|
22
|
-
JSON.parse(custom_metadata).merge(raw_event.metadata.to_h).to_json
|
23
|
-
|
24
|
-
event = EventStoreClient::Event.new(
|
25
|
-
id: raw_event.id.string,
|
26
|
-
title: "#{raw_event.stream_revision}@#{raw_event.stream_identifier.stream_name}",
|
27
|
-
type: raw_event.metadata['type'],
|
28
|
-
data: data,
|
29
|
-
metadata: metadata,
|
30
|
-
stream_revision: raw_event.stream_revision,
|
31
|
-
commit_position: raw_event.commit_position,
|
32
|
-
prepare_position: raw_event.prepare_position,
|
33
|
-
stream_name: raw_event.stream_identifier.stream_name
|
34
|
-
)
|
35
|
-
|
36
|
-
config.mapper.deserialize(event, skip_decryption: skip_decryption)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# @param raw_data [String, nil]
|
42
|
-
# @return [String]
|
43
|
-
def normalize_serialized(raw_data)
|
44
|
-
return {}.to_json if raw_data.nil? || raw_data.empty?
|
45
|
-
|
46
|
-
raw_data
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
# rubocop:enable Metrics/AbcSize
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'dry-struct'
|
4
|
-
require 'securerandom'
|
5
|
-
require 'json'
|
6
|
-
|
7
|
-
module EventStoreClient
|
8
|
-
class Event < Dry::Struct
|
9
|
-
attribute :id, Types::Strict::String.optional.default(nil)
|
10
|
-
attribute :type, Types::Strict::String
|
11
|
-
attribute :title, Types::Strict::String.optional.default(nil)
|
12
|
-
attribute :data, Types::Strict::String.default('{}')
|
13
|
-
attribute :metadata, Types::Strict::String.default('{}')
|
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)
|
18
|
-
|
19
|
-
def initialize(args = {})
|
20
|
-
args[:id] = SecureRandom.uuid if args[:id].nil?
|
21
|
-
hash_meta = JSON.parse(args[:metadata] || '{}')
|
22
|
-
hash_meta['created_at'] ||= Time.now
|
23
|
-
args[:metadata] = JSON.generate(hash_meta)
|
24
|
-
super(args)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|