yes-core 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/lib/yes/core/active_job_serializers/command_group_serializer.rb +29 -0
- data/lib/yes/core/active_job_serializers/dry_struct_serializer.rb +57 -0
- data/lib/yes/core/aggregate/draftable.rb +205 -0
- data/lib/yes/core/aggregate/dsl/attribute_data.rb +37 -0
- data/lib/yes/core/aggregate/dsl/attribute_definer.rb +54 -0
- data/lib/yes/core/aggregate/dsl/attribute_definers/aggregate.rb +36 -0
- data/lib/yes/core/aggregate/dsl/attribute_definers/standard.rb +36 -0
- data/lib/yes/core/aggregate/dsl/class_name_convention.rb +80 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/authorizer.rb +132 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/base.rb +80 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/authorizer.rb +30 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/authorizer_factory.rb +34 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/base.rb +38 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/cerbos_authorizer.rb +114 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/command.rb +70 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/event.rb +88 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/guard_evaluator.rb +84 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/simple_authorizer.rb +50 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/state_updater.rb +46 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model.rb +75 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model_filter.rb +88 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model_serializer.rb +76 -0
- data/lib/yes/core/aggregate/dsl/command_data.rb +54 -0
- data/lib/yes/core/aggregate/dsl/command_definer.rb +263 -0
- data/lib/yes/core/aggregate/dsl/command_shortcut_expander.rb +233 -0
- data/lib/yes/core/aggregate/dsl/constant_resolver.rb +67 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/accessor.rb +28 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/aggregate_accessor.rb +36 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/base.rb +42 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/base.rb +42 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/can_command.rb +41 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/command.rb +50 -0
- data/lib/yes/core/aggregate/has_authorizer.rb +86 -0
- data/lib/yes/core/aggregate/has_read_model.rb +169 -0
- data/lib/yes/core/aggregate/read_model_rebuilder.rb +40 -0
- data/lib/yes/core/aggregate/shared_read_model_rebuilder.rb +158 -0
- data/lib/yes/core/aggregate.rb +404 -0
- data/lib/yes/core/authentication_error.rb +8 -0
- data/lib/yes/core/authorization/cerbos_client_provider.rb +27 -0
- data/lib/yes/core/authorization/command_authorizer.rb +40 -0
- data/lib/yes/core/authorization/command_cerbos_authorizer.rb +182 -0
- data/lib/yes/core/authorization/read_model_authorizer.rb +22 -0
- data/lib/yes/core/authorization/read_models_authorizer.rb +49 -0
- data/lib/yes/core/authorization/read_request_authorizer.rb +32 -0
- data/lib/yes/core/authorization/read_request_cerbos_authorizer.rb +112 -0
- data/lib/yes/core/command.rb +35 -0
- data/lib/yes/core/command_handling/aggregate_tracker.rb +33 -0
- data/lib/yes/core/command_handling/command_executor.rb +171 -0
- data/lib/yes/core/command_handling/command_handler.rb +124 -0
- data/lib/yes/core/command_handling/event_publisher.rb +189 -0
- data/lib/yes/core/command_handling/guard_evaluator.rb +165 -0
- data/lib/yes/core/command_handling/guard_runner.rb +76 -0
- data/lib/yes/core/command_handling/payload_proxy.rb +159 -0
- data/lib/yes/core/command_handling/read_model_recovery_service.rb +264 -0
- data/lib/yes/core/command_handling/read_model_revision_guard.rb +198 -0
- data/lib/yes/core/command_handling/read_model_updater.rb +103 -0
- data/lib/yes/core/command_handling/state_updater.rb +113 -0
- data/lib/yes/core/commands/bus.rb +46 -0
- data/lib/yes/core/commands/group.rb +135 -0
- data/lib/yes/core/commands/group_response.rb +13 -0
- data/lib/yes/core/commands/helper.rb +126 -0
- data/lib/yes/core/commands/notifier.rb +65 -0
- data/lib/yes/core/commands/processor.rb +137 -0
- data/lib/yes/core/commands/response.rb +63 -0
- data/lib/yes/core/commands/stateless/group_handler.rb +186 -0
- data/lib/yes/core/commands/stateless/group_response.rb +15 -0
- data/lib/yes/core/commands/stateless/handler.rb +292 -0
- data/lib/yes/core/commands/stateless/handler_helpers.rb +321 -0
- data/lib/yes/core/commands/stateless/response.rb +14 -0
- data/lib/yes/core/commands/stateless/subject.rb +41 -0
- data/lib/yes/core/commands/validator.rb +28 -0
- data/lib/yes/core/configuration.rb +432 -0
- data/lib/yes/core/data_decryptor.rb +59 -0
- data/lib/yes/core/data_encryptor.rb +60 -0
- data/lib/yes/core/encryption_metadata.rb +33 -0
- data/lib/yes/core/error.rb +14 -0
- data/lib/yes/core/error_messages.rb +37 -0
- data/lib/yes/core/event.rb +222 -0
- data/lib/yes/core/event_class_resolver.rb +40 -0
- data/lib/yes/core/generators/read_models/add_pending_update_tracking_generator.rb +43 -0
- data/lib/yes/core/generators/read_models/templates/add_pending_update_tracking.rb.erb +122 -0
- data/lib/yes/core/generators/read_models/templates/migration.rb.erb +9 -0
- data/lib/yes/core/generators/read_models/update_generator.rb +147 -0
- data/lib/yes/core/jobs/read_model_recovery_job.rb +219 -0
- data/lib/yes/core/middlewares/encryptor.rb +48 -0
- data/lib/yes/core/middlewares/timestamp.rb +29 -0
- data/lib/yes/core/middlewares/with_indifferent_access.rb +22 -0
- data/lib/yes/core/models/application_record.rb +9 -0
- data/lib/yes/core/open_telemetry/otl_span.rb +110 -0
- data/lib/yes/core/open_telemetry/trackable.rb +101 -0
- data/lib/yes/core/payload_store/base.rb +33 -0
- data/lib/yes/core/payload_store/errors.rb +13 -0
- data/lib/yes/core/payload_store/lookup.rb +44 -0
- data/lib/yes/core/process_managers/access_token_client.rb +107 -0
- data/lib/yes/core/process_managers/base.rb +40 -0
- data/lib/yes/core/process_managers/command_runner.rb +109 -0
- data/lib/yes/core/process_managers/service_client.rb +57 -0
- data/lib/yes/core/process_managers/state.rb +118 -0
- data/lib/yes/core/railtie.rb +58 -0
- data/lib/yes/core/read_model/builder.rb +267 -0
- data/lib/yes/core/read_model/event_handler.rb +64 -0
- data/lib/yes/core/read_model/filter.rb +118 -0
- data/lib/yes/core/read_model/filter_query_builder.rb +104 -0
- data/lib/yes/core/serializer.rb +21 -0
- data/lib/yes/core/subscriptions.rb +94 -0
- data/lib/yes/core/test_support/event_helpers.rb +27 -0
- data/lib/yes/core/test_support/jwt_helpers.rb +30 -0
- data/lib/yes/core/test_support/subscriptions_helper.rb +88 -0
- data/lib/yes/core/test_support/test_helper.rb +27 -0
- data/lib/yes/core/test_support.rb +5 -0
- data/lib/yes/core/transaction_details.rb +90 -0
- data/lib/yes/core/type_lookup.rb +88 -0
- data/lib/yes/core/types.rb +110 -0
- data/lib/yes/core/utils/aggregate_shortcuts.rb +164 -0
- data/lib/yes/core/utils/caller_utils.rb +37 -0
- data/lib/yes/core/utils/command_utils.rb +226 -0
- data/lib/yes/core/utils/error_notifier.rb +101 -0
- data/lib/yes/core/utils/event_name_resolver.rb +67 -0
- data/lib/yes/core/utils/exponential_retrier.rb +180 -0
- data/lib/yes/core/utils/hash_utils.rb +63 -0
- data/lib/yes/core/version.rb +7 -0
- data/lib/yes/core.rb +85 -0
- data/lib/yes.rb +0 -0
- metadata +324 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
module Commands
|
|
6
|
+
module Stateless
|
|
7
|
+
# Provides helper methods for checking event stream state in stateless command handlers.
|
|
8
|
+
# Includes methods for checking if subjects have been added, removed, published, etc.
|
|
9
|
+
module HandlerHelpers
|
|
10
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
11
|
+
# @param options [Hash]
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
def removed?(subject, options = {})
|
|
14
|
+
event_in_stream?(subject, 'Removed', options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
18
|
+
# @param options [Hash]
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def added?(subject, options = {})
|
|
21
|
+
event_in_stream?(subject, 'Added', options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
25
|
+
# @param payload [Hash]
|
|
26
|
+
# @param event_name [String]
|
|
27
|
+
# @param options [Hash]
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def item_added?(subject, payload, event_name, options = {})
|
|
30
|
+
added_event = last_event(subject, event_name, options.merge(payload: { equal: payload }))
|
|
31
|
+
return false unless added_event
|
|
32
|
+
|
|
33
|
+
removed_event = last_event(subject, event_name.sub('Added', 'Removed'),
|
|
34
|
+
options.merge(payload: { equal: payload }))
|
|
35
|
+
return true unless removed_event
|
|
36
|
+
|
|
37
|
+
added_event.stream_revision > removed_event.stream_revision
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
41
|
+
# @param payload [Hash]
|
|
42
|
+
# @param event_name [String]
|
|
43
|
+
# @param options [Hash]
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def item_removed?(subject, payload, event_name, options = {})
|
|
46
|
+
removed_event = last_event(subject, event_name, options.merge(payload: { equal: payload }))
|
|
47
|
+
return false unless removed_event
|
|
48
|
+
|
|
49
|
+
added_event = last_event(subject, event_name.sub('Removed', 'Added'),
|
|
50
|
+
options.merge(payload: { equal: payload }))
|
|
51
|
+
return true unless added_event
|
|
52
|
+
|
|
53
|
+
removed_event.stream_revision > added_event.stream_revision
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
57
|
+
# @param payload [Hash]
|
|
58
|
+
# @param event_name [String]
|
|
59
|
+
# @param options [Hash]
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def item_assigned?(subject, payload, event_name, options = {})
|
|
62
|
+
assigned_event = last_event(subject, event_name, options.merge(payload: { equal: payload }))
|
|
63
|
+
return false unless assigned_event
|
|
64
|
+
|
|
65
|
+
removed_event = last_event(subject, event_name.sub('Assigned', 'Unassigned'),
|
|
66
|
+
options.merge(payload: { equal: payload }))
|
|
67
|
+
return true unless removed_event
|
|
68
|
+
|
|
69
|
+
assigned_event.stream_revision > removed_event.stream_revision
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
73
|
+
# @param payload [Hash]
|
|
74
|
+
# @param event_name [String]
|
|
75
|
+
# @param options [Hash]
|
|
76
|
+
# @return [Boolean]
|
|
77
|
+
def item_unassigned?(subject, payload, event_name, options = {})
|
|
78
|
+
unassigned_event = last_event(subject, event_name, options.merge(payload: { equal: payload }))
|
|
79
|
+
return false unless unassigned_event
|
|
80
|
+
|
|
81
|
+
added_event = last_event(subject, event_name.sub('Unassigned', 'Assigned'),
|
|
82
|
+
options.merge(payload: { equal: payload }))
|
|
83
|
+
return true unless added_event
|
|
84
|
+
|
|
85
|
+
unassigned_event.stream_revision > added_event.stream_revision
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
89
|
+
# @param options [Hash]
|
|
90
|
+
# @return [Boolean]
|
|
91
|
+
def unpublished?(subject, options = {})
|
|
92
|
+
published_event = last_event(subject, 'Published', options)
|
|
93
|
+
return true unless published_event
|
|
94
|
+
|
|
95
|
+
unpublished_event = last_event(subject, 'Unpublished', options)
|
|
96
|
+
return false unless unpublished_event
|
|
97
|
+
|
|
98
|
+
unpublished_event.stream_revision > published_event.stream_revision
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @see #unpublished?
|
|
102
|
+
def published?(...)
|
|
103
|
+
!unpublished?(...)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
107
|
+
# @param event_name [String]
|
|
108
|
+
# @param options [Hash]
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
def activated?(subject, event_name, options = {})
|
|
111
|
+
activated_event = last_event(subject, event_name, options)
|
|
112
|
+
return false unless activated_event
|
|
113
|
+
|
|
114
|
+
deactivated_event = last_event(subject, event_name.sub('Activated', 'Deactivated'), options)
|
|
115
|
+
return true unless deactivated_event
|
|
116
|
+
|
|
117
|
+
activated_event.stream_revision > deactivated_event.stream_revision
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
121
|
+
# @param event_name [String]
|
|
122
|
+
# @param options [Hash]
|
|
123
|
+
# @return [Boolean]
|
|
124
|
+
def deactivated?(subject, event_name, options = {})
|
|
125
|
+
deactivated_event = last_event(subject, event_name, options)
|
|
126
|
+
return false unless deactivated_event
|
|
127
|
+
|
|
128
|
+
activated_event = last_event(subject, event_name.sub('Deactivated', 'Activated'), options)
|
|
129
|
+
return true unless activated_event
|
|
130
|
+
|
|
131
|
+
deactivated_event.stream_revision > activated_event.stream_revision
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
135
|
+
# @param event_name [String]
|
|
136
|
+
# @param options [Hash]
|
|
137
|
+
# @return [Boolean]
|
|
138
|
+
def enabled?(subject, event_name, options = {})
|
|
139
|
+
enabled_event = last_event(subject, event_name, options)
|
|
140
|
+
return false unless enabled_event
|
|
141
|
+
|
|
142
|
+
disabled_event = last_event(subject, event_name.sub('Enabled', 'Disabled'), options)
|
|
143
|
+
return true unless disabled_event
|
|
144
|
+
|
|
145
|
+
enabled_event.stream_revision > disabled_event.stream_revision
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
149
|
+
# @param event_name [String]
|
|
150
|
+
# @param options [Hash]
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
def disabled?(subject, event_name, options = {})
|
|
153
|
+
disabled_event = last_event(subject, event_name, options)
|
|
154
|
+
return false unless disabled_event
|
|
155
|
+
|
|
156
|
+
enabled_event = last_event(subject, event_name.sub('Disabled', 'Enabled'), options)
|
|
157
|
+
return true unless enabled_event
|
|
158
|
+
|
|
159
|
+
disabled_event.stream_revision > enabled_event.stream_revision
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
163
|
+
# @param payload [Hash]
|
|
164
|
+
# @param event_name [String]
|
|
165
|
+
# @param options [Hash]
|
|
166
|
+
# @return [Boolean]
|
|
167
|
+
def event_exists_with_payload?(subject, payload, event_name, options = {})
|
|
168
|
+
event_in_stream?(subject, event_name, options.merge(payload:))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
172
|
+
# @param event_name [String]
|
|
173
|
+
# @param options [Hash]
|
|
174
|
+
# @return [Boolean]
|
|
175
|
+
def event_in_stream?(subject, event_name, options = {})
|
|
176
|
+
last_event(subject, event_name, options).present?
|
|
177
|
+
end
|
|
178
|
+
alias changed? event_in_stream?
|
|
179
|
+
|
|
180
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
181
|
+
# @param payload [Hash]
|
|
182
|
+
# @param event_name [String]
|
|
183
|
+
# @return [Boolean]
|
|
184
|
+
def no_change?(subject, payload, event_name)
|
|
185
|
+
options = { resolve_payload: true }
|
|
186
|
+
options[:locale] = payload[:locale] if payload[:locale]
|
|
187
|
+
event = last_event(
|
|
188
|
+
subject,
|
|
189
|
+
event_name,
|
|
190
|
+
options,
|
|
191
|
+
skip_decryption: false
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return false unless event
|
|
195
|
+
|
|
196
|
+
payload = payload.as_json
|
|
197
|
+
event.data.all? { |k, v| payload[k] == v }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
201
|
+
# @param payload_field_key [String]
|
|
202
|
+
# @param event_name [String]
|
|
203
|
+
# @param options [Hash]
|
|
204
|
+
# @option options [Hash] :payload
|
|
205
|
+
# @option options [String] :locale
|
|
206
|
+
# @option options [Boolean] :resolve_payload
|
|
207
|
+
# @return [Boolean]
|
|
208
|
+
def partial_payload_field_changed?(subject, payload_field_key, event_name, options = {})
|
|
209
|
+
stream = subject.stream
|
|
210
|
+
enumerable_events = load_events(stream, options: { direction: 'Forwards' })
|
|
211
|
+
|
|
212
|
+
partial_data = {}
|
|
213
|
+
event_type = "#{subject.context}::#{subject.subject}#{event_name}"
|
|
214
|
+
|
|
215
|
+
enumerable_events.each do |result|
|
|
216
|
+
result.each do |event|
|
|
217
|
+
next unless event.type == event_type
|
|
218
|
+
next if options[:locale] && skip?(options[:locale], event)
|
|
219
|
+
|
|
220
|
+
event = resolve_payloads(event) if options[:payload] || options[:resolve_payload]
|
|
221
|
+
|
|
222
|
+
event.data[payload_field_key.to_s].each do |key, value|
|
|
223
|
+
partial_data[key] = value
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
partial_data.stringify_keys.merge(options[:payload].stringify_keys) != partial_data.stringify_keys
|
|
229
|
+
rescue PgEventstore::StreamNotFoundError
|
|
230
|
+
true
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @param locale [String]
|
|
234
|
+
# @param event [PgEventstore::Event]
|
|
235
|
+
# @return [Boolean]
|
|
236
|
+
def skip?(locale, event)
|
|
237
|
+
event_locale = event.data.transform_keys(&:to_s)['locale'].to_s
|
|
238
|
+
return false if event_locale.empty?
|
|
239
|
+
|
|
240
|
+
event_locale != locale.to_s
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
# Returns the last event of the given event_name for the given subject and context
|
|
246
|
+
# @param subject [Yes::Core::Commands::Stateless::Subject]
|
|
247
|
+
# @param event_name [String]
|
|
248
|
+
# @param options [Hash]
|
|
249
|
+
# @option options [Hash] :payload the payload to match, divided into :equal and :not_equal
|
|
250
|
+
# example: { equal: { medium_id: '123' }, not_equal: { position: 3 } }
|
|
251
|
+
# @param skip_decryption [Boolean]
|
|
252
|
+
# @return [PgEventstore::Event, nil]
|
|
253
|
+
def last_event(subject, event_name, options = {}, skip_decryption: true)
|
|
254
|
+
event_type = "#{subject.context}::#{subject.subject}#{event_name}"
|
|
255
|
+
stream = subject.stream
|
|
256
|
+
|
|
257
|
+
events_cache[stream] ||= {}
|
|
258
|
+
return events_cache[stream][event_type] if !options[:payload] && events_cache[stream][event_type]
|
|
259
|
+
|
|
260
|
+
events = load_events(stream, options:, skip_decryption:)
|
|
261
|
+
|
|
262
|
+
events.each do |result|
|
|
263
|
+
result.each do |event|
|
|
264
|
+
next unless event.type == event_type
|
|
265
|
+
next if options[:locale] && skip?(options[:locale], event)
|
|
266
|
+
|
|
267
|
+
event = resolve_payloads(event) if options[:payload] || options[:resolve_payload]
|
|
268
|
+
|
|
269
|
+
events_cache[stream][event.type] ||= event
|
|
270
|
+
|
|
271
|
+
next if options[:payload] && !payload_matches?(event.data, options[:payload])
|
|
272
|
+
|
|
273
|
+
return event
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
nil
|
|
278
|
+
rescue PgEventstore::StreamNotFoundError
|
|
279
|
+
nil
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# @param stream [PgEventstore::Stream]
|
|
283
|
+
# @param options [Hash]
|
|
284
|
+
# @option options [String] :direction 'Backwards' or 'Forwards'
|
|
285
|
+
# @option options [Symbol] :from_revision :start or :end
|
|
286
|
+
# @param skip_decryption [Boolean]
|
|
287
|
+
# @return [Enumerator]
|
|
288
|
+
def load_events(stream, options: {}, skip_decryption: true)
|
|
289
|
+
options = { direction: 'Backwards' }.merge(options)
|
|
290
|
+
middlewares = Middlewares.without(:encryptor) if skip_decryption
|
|
291
|
+
PgEventstore.client.read_paginated(stream, options:, middlewares:)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# @param event [PgEventstore::Event]
|
|
295
|
+
# @return [PgEventstore::Event]
|
|
296
|
+
def resolve_payloads(event)
|
|
297
|
+
Yes::Core::PayloadStore::Lookup.new.call(event).each do |key, value|
|
|
298
|
+
event.data[key.to_s] = value
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
event
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# @param event_data [Hash]
|
|
305
|
+
# @param payload [Hash]
|
|
306
|
+
# @return [Boolean]
|
|
307
|
+
def payload_matches?(event_data, payload)
|
|
308
|
+
(payload[:equal] || {}).all? { |k, v| event_data[k.to_s] == v } &&
|
|
309
|
+
(payload[:not_equal] || {}).all? { |k, v| event_data[k.to_s] != v }
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Define the implementation in your class.
|
|
313
|
+
# @return [Hash]
|
|
314
|
+
def events_cache
|
|
315
|
+
raise NotImplementedError
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
module Commands
|
|
6
|
+
module Stateless
|
|
7
|
+
# Response object for stateless commands
|
|
8
|
+
class Response < Yes::Core::Commands::Response
|
|
9
|
+
attribute? :error, Types.Instance(Stateless::Handler::TransitionError).optional
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
module Commands
|
|
6
|
+
module Stateless
|
|
7
|
+
# Subject object is responsible for holding subject data
|
|
8
|
+
#
|
|
9
|
+
# @attr subject [String]
|
|
10
|
+
# @attr aggregate_id [String]
|
|
11
|
+
# @attr context [String]
|
|
12
|
+
# @attr stream_prefix [String] value is optional
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# Yes::Core::Commands::Stateless::Subject.new(
|
|
16
|
+
# context: 'ApprenticeshipPresentation',
|
|
17
|
+
# subject: 'Apprenticeship',
|
|
18
|
+
# aggregate_id: '123'
|
|
19
|
+
# )
|
|
20
|
+
Subject = Data.define(:subject, :aggregate_id, :context, :stream_prefix) do
|
|
21
|
+
OPTIONAL_FIELDS = %i[stream_prefix].index_with(nil).freeze
|
|
22
|
+
|
|
23
|
+
def initialize(**attrs)
|
|
24
|
+
super(**OPTIONAL_FIELDS.merge(attrs))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [PgEventstore::Stream]
|
|
28
|
+
def stream
|
|
29
|
+
parts = computed_stream_prefix.split('::')
|
|
30
|
+
PgEventstore::Stream.new(context: parts[0], stream_name: parts[1..].join('::'), stream_id: aggregate_id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String]
|
|
34
|
+
def computed_stream_prefix
|
|
35
|
+
stream_prefix || "#{context}::#{subject}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
module Commands
|
|
6
|
+
# @abstract Subclass and override {.call} to implement a custom command validator.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# class MyCommandValidator < Yes::Core::Commands::Validator
|
|
10
|
+
# def self.call(command)
|
|
11
|
+
# raise CommandInvalid, 'Name is required' if command.payload[:name].blank?
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
class Validator
|
|
15
|
+
CommandInvalid = Class.new(Yes::Core::Error)
|
|
16
|
+
|
|
17
|
+
# Validates the given command. Must be implemented by subclasses.
|
|
18
|
+
#
|
|
19
|
+
# @param command [Yes::Core::Command] command to validate
|
|
20
|
+
# @raise [CommandInvalid] if command is invalid
|
|
21
|
+
# @raise [NotImplementedError] if not overridden
|
|
22
|
+
def self.call(command)
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|