event_store_client 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +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
|