sourced-message 0.2.2 → 0.4.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/CHANGELOG.md +24 -0
- data/lib/sourced/message/codec.rb +9 -5
- data/lib/sourced/message.rb +41 -4
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12ddf9298bf827a4f20e9f2db6b9edababd24efa73323326e5cd5ff6bbedebc1
|
|
4
|
+
data.tar.gz: a88ab37ddb627868b20cd9a03615334a01d1f957fc3dedae25db4486623b983f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 154e306175767857b9471c5700fe12ec3f73c17292af5c30826e2f61afa0dc217853e56950cddbf3decf8b38c38f5837e3cad76a822c6078cfd8904ef912093b
|
|
7
|
+
data.tar.gz: 0640b5e2560fa1dfd331ee8b015dde0b5195bf1ec55efaa1cb0e98cf4b3713ca8544b4bc784495cb09f08d32d12bc2dc4c60977b60df99b021bf6f71a6e63c76
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0]
|
|
4
|
+
|
|
5
|
+
- Requires plumb 0.4, whose codecs rewrite Hash keys like values: `#encode` now emits
|
|
6
|
+
String keys (`{'type' => ..., 'payload' => {'name' => ...}}`), and decoders accept
|
|
7
|
+
String or Symbol keys. `JSON.dump` output is unchanged.
|
|
8
|
+
- `Codec#decode` reads the envelope's `type` and `id` under either key form, so
|
|
9
|
+
`decode(encode(message))` works without a JSON round trip.
|
|
10
|
+
- `metadata` is typed as `Types::Metadata`: Symbol keys and `Types::JSONValue` values
|
|
11
|
+
(String, Numeric, boolean, nil, and Arrays/Symbol-keyed Hashes of those, at any depth).
|
|
12
|
+
Codecs now restore nested metadata keys as Symbols. A non-JSON value (a Time, a
|
|
13
|
+
Symbol) makes the message invalid and fails `#encode`, where `JSON.dump` used to
|
|
14
|
+
stringify it silently.
|
|
15
|
+
- `Sourced::Message::VERSION` is now `0.4.0`.
|
|
16
|
+
|
|
17
|
+
## [0.3.0]
|
|
18
|
+
|
|
19
|
+
- Add `Sourced::Message#correlation_type`, the type counterpart of `correlation_id`:
|
|
20
|
+
the type of the message at the root of a causal chain. `#correlate` now records the
|
|
21
|
+
source's `correlation_type` under `metadata[:correlation_type]` on the target, so
|
|
22
|
+
every consequence of a command carries the command's type. A message that was never
|
|
23
|
+
correlated answers with its own `type`. A target whose metadata already holds a
|
|
24
|
+
`correlation_type` keeps it, which starts a new chain.
|
|
25
|
+
- `Sourced::Message::VERSION` is now `0.3.0`.
|
|
26
|
+
|
|
3
27
|
## [0.2.0]
|
|
4
28
|
|
|
5
29
|
- Add `Sourced::Message::Codec`, the abstract serializer: it compiles a
|
|
@@ -182,23 +182,27 @@ module Sourced
|
|
|
182
182
|
# Rebuild a message from decoded attributes. An unknown type raises: a process
|
|
183
183
|
# reading types it doesn't know about is missing the class.
|
|
184
184
|
#
|
|
185
|
-
# @param attrs [Hash]
|
|
185
|
+
# @param attrs [Hash] message attributes, String- (as #encode emits them) or Symbol-keyed
|
|
186
186
|
# @return [Sourced::Message]
|
|
187
187
|
# @raise [UnknownMessageError] if the type isn't in the registry
|
|
188
188
|
# @raise [UnregisteredTypeError] if the type was not compiled
|
|
189
189
|
# @raise [DecodeError] if the attributes don't satisfy the schema
|
|
190
190
|
def decode(attrs)
|
|
191
|
-
type = attrs
|
|
191
|
+
type = envelope(attrs, :type)
|
|
192
|
+
id = envelope(attrs, :id)
|
|
192
193
|
klass = @registry[type]
|
|
193
|
-
raise UnknownMessageError, "Unknown message type: #{label(type,
|
|
194
|
+
raise UnknownMessageError, "Unknown message type: #{label(type, id)}" unless klass
|
|
194
195
|
|
|
195
|
-
build(klass, attrs, pair(type,
|
|
196
|
+
build(klass, attrs, pair(type, id).decoder)
|
|
196
197
|
rescue Plumb::ParseError => e
|
|
197
|
-
raise DecodeError, "cannot decode #{label(type,
|
|
198
|
+
raise DecodeError, "cannot decode #{label(type, id)}: #{e.message}"
|
|
198
199
|
end
|
|
199
200
|
|
|
200
201
|
private
|
|
201
202
|
|
|
203
|
+
# Read before the decoder runs, so it must accept both key forms the decoder does.
|
|
204
|
+
def envelope(attrs, key) = attrs.fetch(key) { attrs[key.to_s] }
|
|
205
|
+
|
|
202
206
|
# --- subclass seams -------------------------------------------------------
|
|
203
207
|
|
|
204
208
|
# @param klass [Class<Sourced::Message>]
|
data/lib/sourced/message.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Sourced
|
|
|
22
22
|
# +Sourced::Message.registry[type]+ resolves a type registered under any
|
|
23
23
|
# subclass. Resolve from this root to see the whole tree.
|
|
24
24
|
class Message < Plumb::Types::Data
|
|
25
|
-
VERSION = '0.
|
|
25
|
+
VERSION = '0.4.0'
|
|
26
26
|
|
|
27
27
|
EMPTY_ARRAY = [].freeze
|
|
28
28
|
|
|
@@ -34,6 +34,15 @@ module Sourced
|
|
|
34
34
|
|
|
35
35
|
# Accepts a UUID string or generates a new one when none is provided.
|
|
36
36
|
AutoUUID = UUID::V4.default { SecureRandom.uuid }
|
|
37
|
+
|
|
38
|
+
# A JSON value, with Symbol-keyed objects at every depth. Typed all the way
|
|
39
|
+
# down so a codec decodes nested keys too. The self-references are deferred
|
|
40
|
+
# inside the union, not the constant itself: naming a type here freezes it,
|
|
41
|
+
# and a frozen Deferred can't materialize.
|
|
42
|
+
JSONValue = String | Numeric | Boolean | Nil |
|
|
43
|
+
Array[Any.defer { JSONValue }] | Hash[Symbol, Any.defer { JSONValue }]
|
|
44
|
+
|
|
45
|
+
Metadata = Hash[Symbol, JSONValue]
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
# Raised by {.from} when a type string isn't registered.
|
|
@@ -47,7 +56,7 @@ module Sourced
|
|
|
47
56
|
attribute? :causation_id, Types::UUID::V4
|
|
48
57
|
attribute? :correlation_id, Types::UUID::V4
|
|
49
58
|
attribute :created_at, LaxTime.default { Time.now }
|
|
50
|
-
attribute :metadata, Types::
|
|
59
|
+
attribute :metadata, Types::Metadata.default(Plumb::BLANK_HASH)
|
|
51
60
|
attribute :payload, Types::Static[nil]
|
|
52
61
|
|
|
53
62
|
# Lookup table mapping type strings to message subclasses.
|
|
@@ -231,8 +240,33 @@ module Sourced
|
|
|
231
240
|
|
|
232
241
|
alias in at
|
|
233
242
|
|
|
243
|
+
# Metadata key under which {#correlate} records the {#correlation_type}.
|
|
244
|
+
CORRELATION_TYPE_KEY = :correlation_type
|
|
245
|
+
|
|
246
|
+
# The type of the message at the root of this message's causal chain —
|
|
247
|
+
# the type counterpart of {#correlation_id}.
|
|
248
|
+
#
|
|
249
|
+
# A message that was not produced by {#correlate} is its own root, so
|
|
250
|
+
# this is its own {#type}. A correlated message inherits its source's
|
|
251
|
+
# +correlation_type+, so every consequence of a command — the events it
|
|
252
|
+
# produced, the commands those events triggered, their events in turn —
|
|
253
|
+
# answers with the originating command's type. This is what lets a
|
|
254
|
+
# subscriber that only knows the command it sent recognise everything
|
|
255
|
+
# that came of it, without knowing the shape of each downstream message.
|
|
256
|
+
#
|
|
257
|
+
# Setting +metadata[:correlation_type]+ explicitly on a message before it
|
|
258
|
+
# is correlated starts a new chain from it: the target's own metadata wins
|
|
259
|
+
# the merge in {#correlate}.
|
|
260
|
+
#
|
|
261
|
+
# @return [String] a message type string
|
|
262
|
+
def correlation_type
|
|
263
|
+
metadata.fetch(CORRELATION_TYPE_KEY, type)
|
|
264
|
+
end
|
|
265
|
+
|
|
234
266
|
# Set causation and correlation IDs on another message, establishing
|
|
235
|
-
# a causal link from this message to +message+. Merges metadata
|
|
267
|
+
# a causal link from this message to +message+. Merges metadata, with the
|
|
268
|
+
# target's keys winning, and records this message's {#correlation_type}
|
|
269
|
+
# under {CORRELATION_TYPE_KEY} unless the target already carries one.
|
|
236
270
|
#
|
|
237
271
|
# @param message [Message] the message to correlate
|
|
238
272
|
# @return [Message] a copy of +message+ with causation/correlation set
|
|
@@ -241,11 +275,14 @@ module Sourced
|
|
|
241
275
|
# caused = source_event.correlate(SomeCommand.new(payload: { ... }))
|
|
242
276
|
# caused.causation_id # => source_event.id
|
|
243
277
|
# caused.correlation_id # => source_event.correlation_id
|
|
278
|
+
# caused.correlation_type # => source_event.correlation_type
|
|
244
279
|
def correlate(message)
|
|
245
280
|
attrs = {
|
|
246
281
|
causation_id: id,
|
|
247
282
|
correlation_id: correlation_id,
|
|
248
|
-
metadata:
|
|
283
|
+
metadata: { CORRELATION_TYPE_KEY => correlation_type }
|
|
284
|
+
.merge(metadata)
|
|
285
|
+
.merge(message.metadata || Plumb::BLANK_HASH)
|
|
249
286
|
}
|
|
250
287
|
message.with(attrs)
|
|
251
288
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sourced-message
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ismael Celis
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.4'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.4'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: fugit
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
75
|
version: '0'
|
|
76
76
|
requirements: []
|
|
77
|
-
rubygems_version: 4.0.
|
|
77
|
+
rubygems_version: 4.0.21
|
|
78
78
|
specification_version: 4
|
|
79
79
|
summary: Canonical typed Message class and registry shared by Sourced and Sidereal.
|
|
80
80
|
test_files: []
|