eventsimple 2.0.0 → 2.0.1
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/CHANGELOG.md +6 -0
- data/Gemfile.lock +8 -1
- data/README.md +11 -11
- data/eventsimple.gemspec +1 -0
- data/lib/eventsimple/message.rb +44 -171
- data/lib/eventsimple/metadata.rb +4 -2
- data/lib/eventsimple/types/encrypted_type.rb +6 -0
- data/lib/eventsimple/types.rb +33 -14
- data/lib/eventsimple/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ef0bd1c392a5de3db450d1ba8f29a933ec2fd885ff2c5cc170b0b9e661788ea
|
|
4
|
+
data.tar.gz: 0ccb0b0a628c852c4a79077a7659e179be9b16e3bef54b3b09d30f102892763e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cbb0c7fe1c90d56ea050ee03ccac8b29e2180842b43fdafe6c91e73d76fe5d92ba6caee5dd401260da63525bd67612f7d27c9c6896594c77cf1e1e98e78cc1c8
|
|
7
|
+
data.tar.gz: f874bffb16acb3a98c90d1b7f525614fb6611554c2931f3cb20009ef60ef4dd22750d007fba7cbe1d5d37b462a6bdd47e471f3f3b91afed1367fb72d0c31b57a
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 2.0.1 - 2025-12-11
|
|
10
|
+
### Changed
|
|
11
|
+
- Switch back to using dry-struct for Message class, for better compatibility with existing code.
|
|
12
|
+
- Fix Eventsimple type enforcement in Message class.
|
|
13
|
+
- Fix wiki links in README.md
|
|
14
|
+
|
|
9
15
|
## 2.0.0 - 2025-12-07
|
|
10
16
|
### Changed
|
|
11
17
|
- Remove dry-types dependency and replace with custom type system.
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
eventsimple (2.0.
|
|
4
|
+
eventsimple (2.0.1)
|
|
5
5
|
concurrent-ruby (>= 1.2.3)
|
|
6
|
+
dry-struct (>= 1.8.0)
|
|
6
7
|
dry-types (>= 1.7.0)
|
|
7
8
|
pg (~> 1.4)
|
|
8
9
|
rails (>= 7.0, < 9.0)
|
|
@@ -114,6 +115,11 @@ GEM
|
|
|
114
115
|
concurrent-ruby (~> 1.0)
|
|
115
116
|
dry-core (~> 1.1)
|
|
116
117
|
zeitwerk (~> 2.6)
|
|
118
|
+
dry-struct (1.8.0)
|
|
119
|
+
dry-core (~> 1.1)
|
|
120
|
+
dry-types (~> 1.8, >= 1.8.2)
|
|
121
|
+
ice_nine (~> 0.11)
|
|
122
|
+
zeitwerk (~> 2.6)
|
|
117
123
|
dry-types (1.8.3)
|
|
118
124
|
bigdecimal (~> 3.0)
|
|
119
125
|
concurrent-ruby (~> 1.0)
|
|
@@ -160,6 +166,7 @@ GEM
|
|
|
160
166
|
rspec (>= 2.99.0, < 4.0)
|
|
161
167
|
i18n (1.14.7)
|
|
162
168
|
concurrent-ruby (~> 1.0)
|
|
169
|
+
ice_nine (0.11.2)
|
|
163
170
|
io-console (0.8.1)
|
|
164
171
|
irb (1.15.3)
|
|
165
172
|
pp (>= 0.6.0)
|
data/README.md
CHANGED
|
@@ -54,14 +54,14 @@ end
|
|
|
54
54
|
|
|
55
55
|
## Quick Start
|
|
56
56
|
|
|
57
|
-
- **[Home](Home)** - Installation, configuration, and getting started
|
|
58
|
-
- **[Usage-Events](Usage-Events)** - How to create and use events
|
|
59
|
-
- **[Usage-Reactors](Usage-Reactors)** - Handle side effects with sync and async reactors
|
|
60
|
-
- **[Encryption](Encryption)** - Encrypt sensitive data in event messages
|
|
61
|
-
- **[Outbox-Pattern](Outbox-Pattern)** - Implement ordered event processing
|
|
62
|
-
- **[Best-Practices](Best-Practices)** - Development guidelines and best practices
|
|
63
|
-
- **[Testing](Testing)** - Testing best practices for events and reactors
|
|
64
|
-
- **[Helper-Methods](Helper-Methods)** - Convenience methods for common tasks
|
|
65
|
-
- **[Data-Migrations](Data-Migrations)** - Migrating event data
|
|
66
|
-
- **[Existing-Models](Existing-Models)** - Adding Eventsimple to existing models
|
|
67
|
-
- **[Factory-Bot-Compatibility](Factory-Bot-Compatibility)** - Using Factory Bot with Eventsimple
|
|
57
|
+
- **[Home](https://github.com/wealthsimple/eventsimple/wiki/Home)** - Installation, configuration, and getting started
|
|
58
|
+
- **[Usage-Events](https://github.com/wealthsimple/eventsimple/wiki/Usage-Events)** - How to create and use events
|
|
59
|
+
- **[Usage-Reactors](https://github.com/wealthsimple/eventsimple/wiki/Usage-Reactors)** - Handle side effects with sync and async reactors
|
|
60
|
+
- **[Encryption](https://github.com/wealthsimple/eventsimple/wiki/Encryption)** - Encrypt sensitive data in event messages
|
|
61
|
+
- **[Outbox-Pattern](https://github.com/wealthsimple/eventsimple/wiki/Outbox-Pattern)** - Implement ordered event processing
|
|
62
|
+
- **[Best-Practices](https://github.com/wealthsimple/eventsimple/wiki/Best-Practices)** - Development guidelines and best practices
|
|
63
|
+
- **[Testing](https://github.com/wealthsimple/eventsimple/wiki/Testing)** - Testing best practices for events and reactors
|
|
64
|
+
- **[Helper-Methods](https://github.com/wealthsimple/eventsimple/wiki/Helper-Methods)** - Convenience methods for common tasks
|
|
65
|
+
- **[Data-Migrations](https://github.com/wealthsimple/eventsimple/wiki/Data-Migrations)** - Migrating event data
|
|
66
|
+
- **[Existing-Models](https://github.com/wealthsimple/eventsimple/wiki/Existing-Models)** - Adding Eventsimple to existing models
|
|
67
|
+
- **[Factory-Bot-Compatibility](https://github.com/wealthsimple/eventsimple/wiki/Factory-Bot-Compatibility)** - Using Factory Bot with Eventsimple
|
data/eventsimple.gemspec
CHANGED
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.require_paths = ['lib']
|
|
25
25
|
|
|
26
26
|
spec.add_runtime_dependency 'concurrent-ruby', '>= 1.2.3'
|
|
27
|
+
spec.add_runtime_dependency 'dry-struct', '>= 1.8.0'
|
|
27
28
|
spec.add_runtime_dependency 'dry-types', '>= 1.7.0'
|
|
28
29
|
spec.add_runtime_dependency 'pg', '~> 1.4'
|
|
29
30
|
spec.add_runtime_dependency 'rails', '>= 7.0', '< 9.0'
|
data/lib/eventsimple/message.rb
CHANGED
|
@@ -1,193 +1,66 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'dry-struct'
|
|
4
|
+
|
|
3
5
|
module Eventsimple
|
|
4
|
-
class Message
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
class Message < Dry::Struct
|
|
7
|
+
transform_keys(&:to_sym)
|
|
8
|
+
|
|
9
|
+
# dry types will apply default values only on missing keys
|
|
10
|
+
# modify the behaviour so the default is used even when the key is present but nil
|
|
11
|
+
transform_types do |type|
|
|
12
|
+
if type.default?
|
|
13
|
+
type.constructor do |value|
|
|
14
|
+
value.nil? ? Dry::Types::Undefined : value
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
type
|
|
10
18
|
end
|
|
19
|
+
end
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
define_attribute(name.to_sym, type, optional: optional, required: false)
|
|
16
|
-
end
|
|
21
|
+
def inspect
|
|
22
|
+
as_json
|
|
23
|
+
end
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Schema.new
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
+
def self.attribute(name, type = Dry::Types::Any, **options)
|
|
26
|
+
validate_type(type)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
def self.attribute?(name, type = Dry::Types::Any, **options)
|
|
31
|
+
validate_type(type)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
27
34
|
|
|
35
|
+
class << self
|
|
28
36
|
def validate_type(type)
|
|
29
37
|
return if valid_eventsimple_type?(type)
|
|
30
38
|
|
|
39
|
+
source_location = begin
|
|
40
|
+
source = const_source_location(name)&.first if respond_to?(:const_source_location) && name
|
|
41
|
+
source || caller_locations.find { |loc| !loc.path.include?('eventsimple') }&.path
|
|
42
|
+
rescue StandardError
|
|
43
|
+
'unknown'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
message = [
|
|
47
|
+
"Only Eventsimple::Types are allowed in Message attributes. \n",
|
|
48
|
+
"File: #{source_location} \n",
|
|
49
|
+
"Received: #{type.inspect} \n",
|
|
50
|
+
"Use Eventsimple::Types::String, Eventsimple::Types::Integer, etc. \n",
|
|
51
|
+
].join(' ')
|
|
52
|
+
|
|
31
53
|
deprecator = ActiveSupport::Deprecation.new
|
|
32
54
|
deprecator.behavior = Rails.application.config.active_support.deprecation
|
|
33
|
-
|
|
34
|
-
deprecator.warn(
|
|
35
|
-
"Only Eventsimple::Types are allowed in Message attributes. " \
|
|
36
|
-
"Received: #{type.inspect}. " \
|
|
37
|
-
"Use Eventsimple::Types::String, Eventsimple::Types::Integer, etc.",
|
|
38
|
-
)
|
|
55
|
+
deprecator.warn(message)
|
|
39
56
|
end
|
|
40
57
|
|
|
41
58
|
def valid_eventsimple_type?(type)
|
|
42
|
-
return true if
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const = Eventsimple::Types.const_get(const_name, false)
|
|
46
|
-
const == type || const.equal?(type)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# For Default types, check the underlying type
|
|
50
|
-
if type.respond_to?(:default?) && type.default? && type.respond_to?(:type)
|
|
51
|
-
underlying_type = type.type
|
|
52
|
-
return valid_eventsimple_type?(underlying_type)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# For Sum types (optional), check the right side
|
|
56
|
-
if type.respond_to?(:right)
|
|
57
|
-
return valid_eventsimple_type?(type.right)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# For EncryptedType, check the wrapped type
|
|
61
|
-
if type.is_a?(Eventsimple::Types::EncryptedType)
|
|
62
|
-
return valid_eventsimple_type?(type.instance_variable_get(:@type))
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# For Enum types, check the underlying type
|
|
66
|
-
if type.respond_to?(:type) && type.respond_to?(:values)
|
|
67
|
-
return valid_eventsimple_type?(type.type)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Check if the type's primitive matches any base type's primitive
|
|
71
|
-
# This allows Enum, Constrained, and other derived types
|
|
72
|
-
if type.respond_to?(:primitive)
|
|
73
|
-
type_primitive = type.primitive
|
|
74
|
-
return true if Eventsimple::Types.constants(false).any? { |const_name|
|
|
75
|
-
next if const_name == :BuilderExtension
|
|
76
|
-
|
|
77
|
-
base_type = Eventsimple::Types.const_get(const_name, false)
|
|
78
|
-
base_type.respond_to?(:primitive) && base_type.primitive == type_primitive
|
|
79
|
-
}
|
|
80
|
-
end
|
|
59
|
+
return true if type.respond_to?(:meta) && type.meta[:eventsimple] == true
|
|
60
|
+
return true if type.is_a?(Class) && type < Eventsimple::Message
|
|
81
61
|
|
|
82
62
|
false
|
|
83
63
|
end
|
|
84
|
-
|
|
85
|
-
def define_attribute(name, type, optional:, required:)
|
|
86
|
-
schema.register(name, type, optional: optional, required: required)
|
|
87
|
-
define_method(name) { @attributes[name] }
|
|
88
|
-
define_method("#{name}=") do |value|
|
|
89
|
-
new_value = coerce(name, value, type)
|
|
90
|
-
@attributes[name] = new_value
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
class Schema
|
|
96
|
-
def initialize(parent_schema = nil)
|
|
97
|
-
@definitions = parent_schema ? parent_schema.definitions.dup : {}
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def register(name, type, optional:, required:)
|
|
101
|
-
@definitions[name] = { type: type, optional: optional, required: required }
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def keys
|
|
105
|
-
@definitions.map { |name, definition| AttributeKey.new(name, definition[:type]) }
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def [](name) # rubocop:disable Rails/Delegate
|
|
109
|
-
@definitions[name]
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
attr_reader :definitions
|
|
113
|
-
|
|
114
|
-
class AttributeKey
|
|
115
|
-
attr_reader :name, :type
|
|
116
|
-
|
|
117
|
-
def initialize(name, type)
|
|
118
|
-
@name = name
|
|
119
|
-
@type = type
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def initialize(attributes = {})
|
|
125
|
-
@attributes = {}
|
|
126
|
-
attributes = attributes.transform_keys(&:to_sym)
|
|
127
|
-
|
|
128
|
-
self.class.schema.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
|
129
|
-
name = key.name
|
|
130
|
-
definition = self.class.schema[name]
|
|
131
|
-
|
|
132
|
-
if !definition[:required] && !attributes.key?(name)
|
|
133
|
-
next unless default?(definition[:type])
|
|
134
|
-
|
|
135
|
-
value = nil
|
|
136
|
-
else
|
|
137
|
-
value = attributes[name]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
@attributes[name] = coerce(name, value, definition[:type])
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def attributes
|
|
145
|
-
@attributes.dup
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def to_h
|
|
149
|
-
attributes
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def as_json(*)
|
|
153
|
-
attributes.transform_keys(&:to_s)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def inspect
|
|
157
|
-
"#<#{self.class.name} #{as_json}>"
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def ==(other)
|
|
161
|
-
return false unless other.is_a?(self.class)
|
|
162
|
-
|
|
163
|
-
attributes == other.attributes
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
private
|
|
167
|
-
|
|
168
|
-
def coerce(name, value, type)
|
|
169
|
-
return get_default_value(type) if value.nil? && default?(type)
|
|
170
|
-
return nil if value.nil? && optional?(type)
|
|
171
|
-
raise ArgumentError, "Missing required attribute: #{name}" if value.nil?
|
|
172
|
-
|
|
173
|
-
type.call(value)
|
|
174
|
-
rescue StandardError => e
|
|
175
|
-
raise ArgumentError, "Invalid value for #{name}: #{e.message}"
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def get_default_value(type)
|
|
179
|
-
return type.default_value if type.respond_to?(:default_value)
|
|
180
|
-
|
|
181
|
-
default_val = type.value
|
|
182
|
-
default_val.respond_to?(:call) ? default_val.call : default_val
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def default?(type)
|
|
186
|
-
type.respond_to?(:default?) && type.default?
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def optional?(type)
|
|
190
|
-
type.respond_to?(:optional?) && type.optional?
|
|
191
64
|
end
|
|
192
65
|
end
|
|
193
66
|
end
|
data/lib/eventsimple/metadata.rb
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
# Event metadata store information on the event, for example the user who triggered the event.
|
|
4
4
|
module Eventsimple
|
|
5
5
|
class Metadata < Eventsimple::Message
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
schema schema.strict
|
|
7
|
+
|
|
8
|
+
attribute? :actor_id, Eventsimple::Types::String.optional
|
|
9
|
+
attribute? :reason, Eventsimple::Types::String.optional
|
|
8
10
|
end
|
|
9
11
|
end
|
data/lib/eventsimple/types.rb
CHANGED
|
@@ -8,25 +8,44 @@ module Eventsimple
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
module Types
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
EVENTSIMPLE_META = { eventsimple: true }.freeze
|
|
12
|
+
|
|
13
|
+
# Extension that preserves eventsimple meta through all type transformations
|
|
14
|
+
module MetaPreservingBuilder
|
|
15
|
+
CHAINABLE_METHODS = %i[optional default enum constrained of].freeze
|
|
16
|
+
|
|
17
|
+
CHAINABLE_METHODS.each do |method_name|
|
|
18
|
+
define_method(method_name) do |*args, &block|
|
|
19
|
+
result = super(*args, &block)
|
|
20
|
+
# Re-apply eventsimple meta if the original type had it
|
|
21
|
+
if respond_to?(:meta) && meta[:eventsimple]
|
|
22
|
+
result.meta(EVENTSIMPLE_META)
|
|
23
|
+
else
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
23
29
|
def encrypted
|
|
24
|
-
|
|
30
|
+
is_string_type = respond_to?(:primitive) && primitive == ::String
|
|
31
|
+
raise ArgumentError, "encrypted is only supported for String types" unless is_string_type
|
|
25
32
|
|
|
26
33
|
EncryptedType.new(self)
|
|
27
34
|
end
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
Dry::Types
|
|
37
|
+
# Include the meta-preserving builder in Dry::Types
|
|
38
|
+
Dry::Types::Builder.prepend(MetaPreservingBuilder)
|
|
39
|
+
|
|
40
|
+
Bool = DryTypes::Strict::Bool.meta(EVENTSIMPLE_META)
|
|
41
|
+
Array = DryTypes::Strict::Array.meta(EVENTSIMPLE_META)
|
|
42
|
+
Hash = DryTypes::Strict::Hash.meta(EVENTSIMPLE_META)
|
|
43
|
+
Integer = DryTypes::Strict::Integer.meta(EVENTSIMPLE_META)
|
|
44
|
+
String = DryTypes::Strict::String.meta(EVENTSIMPLE_META)
|
|
45
|
+
|
|
46
|
+
Decimal = DryTypes::JSON::Decimal.meta(EVENTSIMPLE_META)
|
|
47
|
+
Date = DryTypes::JSON::Date.meta(EVENTSIMPLE_META)
|
|
48
|
+
DateTime = DryTypes::JSON::DateTime.meta(EVENTSIMPLE_META)
|
|
49
|
+
Time = DryTypes::JSON::Time.meta(EVENTSIMPLE_META)
|
|
31
50
|
end
|
|
32
51
|
end
|
data/lib/eventsimple/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eventsimple
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Zulfiqar Ali
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: 1.2.3
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: dry-struct
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.8.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.8.0
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: dry-types
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|