dexkit 0.8.0 → 0.10.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 +36 -0
- data/README.md +50 -18
- data/gemfiles/mongoid_no_ar.gemfile +10 -0
- data/gemfiles/mongoid_no_ar.gemfile.lock +232 -0
- data/guides/llm/EVENT.md +41 -23
- data/guides/llm/FORM.md +202 -61
- data/guides/llm/OPERATION.md +49 -20
- data/guides/llm/QUERY.md +52 -2
- data/lib/dex/context_dsl.rb +56 -0
- data/lib/dex/context_setup.rb +2 -33
- data/lib/dex/event/bus.rb +85 -8
- data/lib/dex/event/handler.rb +18 -0
- data/lib/dex/event/metadata.rb +16 -9
- data/lib/dex/event/processor.rb +1 -1
- data/lib/dex/event/test_helpers.rb +88 -0
- data/lib/dex/event/trace.rb +14 -27
- data/lib/dex/event.rb +2 -7
- data/lib/dex/event_test_helpers.rb +1 -86
- data/lib/dex/form/context.rb +27 -0
- data/lib/dex/form/export.rb +128 -0
- data/lib/dex/form/nesting.rb +2 -0
- data/lib/dex/form/uniqueness_validator.rb +17 -1
- data/lib/dex/form.rb +119 -3
- data/lib/dex/id.rb +38 -0
- data/lib/dex/operation/async_proxy.rb +13 -2
- data/lib/dex/operation/explain.rb +11 -7
- data/lib/dex/operation/jobs.rb +5 -4
- data/lib/dex/operation/lock_wrapper.rb +15 -2
- data/lib/dex/operation/once_wrapper.rb +24 -15
- data/lib/dex/operation/record_backend.rb +15 -1
- data/lib/dex/operation/record_wrapper.rb +43 -8
- data/lib/dex/operation/test_helpers/assertions.rb +359 -0
- data/lib/dex/operation/test_helpers/execution.rb +30 -0
- data/lib/dex/operation/test_helpers/stubbing.rb +61 -0
- data/lib/dex/operation/test_helpers.rb +160 -0
- data/lib/dex/operation/trace_wrapper.rb +20 -0
- data/lib/dex/operation/transaction_adapter.rb +29 -68
- data/lib/dex/operation/transaction_wrapper.rb +10 -16
- data/lib/dex/operation.rb +2 -0
- data/lib/dex/query/backend.rb +13 -0
- data/lib/dex/query/export.rb +64 -0
- data/lib/dex/query.rb +50 -5
- data/lib/dex/ref_type.rb +4 -0
- data/lib/dex/test_helpers.rb +4 -139
- data/lib/dex/test_log.rb +62 -4
- data/lib/dex/trace.rb +291 -0
- data/lib/dex/type_coercion.rb +4 -1
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +9 -5
- metadata +16 -5
- data/lib/dex/test_helpers/assertions.rb +0 -333
- data/lib/dex/test_helpers/execution.rb +0 -28
- data/lib/dex/test_helpers/stubbing.rb +0 -59
- /data/lib/dex/{event_test_helpers → event/test_helpers}/assertions.rb +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
# Shared context DSL extracted from ContextSetup.
|
|
5
|
+
#
|
|
6
|
+
# Provides the `context` class method, `context_mappings` inheritance,
|
|
7
|
+
# and `_context_own` storage. Includers must implement:
|
|
8
|
+
# _context_prop_declared?(name) → true/false
|
|
9
|
+
# and may override:
|
|
10
|
+
# _context_field_label → "prop" | "field" (used in error messages)
|
|
11
|
+
module ContextDSL
|
|
12
|
+
def context(*names, **mappings)
|
|
13
|
+
names.each do |name|
|
|
14
|
+
unless name.is_a?(Symbol)
|
|
15
|
+
raise ArgumentError, "context shorthand must be a Symbol, got: #{name.inspect}"
|
|
16
|
+
end
|
|
17
|
+
mappings[name] = name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
raise ArgumentError, "context requires at least one mapping" if mappings.empty?
|
|
21
|
+
|
|
22
|
+
label = _context_field_label
|
|
23
|
+
mappings.each do |prop_name, context_key|
|
|
24
|
+
unless _context_prop_declared?(prop_name)
|
|
25
|
+
raise ArgumentError,
|
|
26
|
+
"context references undeclared #{label} :#{prop_name}. Declare the #{label} before calling context."
|
|
27
|
+
end
|
|
28
|
+
unless context_key.is_a?(Symbol)
|
|
29
|
+
raise ArgumentError,
|
|
30
|
+
"context key must be a Symbol, got: #{context_key.inspect} for #{label} :#{prop_name}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
_context_own.merge!(mappings)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def context_mappings
|
|
38
|
+
parent = superclass.respond_to?(:context_mappings) ? superclass.context_mappings : {}
|
|
39
|
+
parent.merge(_context_own)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def _context_own
|
|
45
|
+
@_context_own_mappings ||= {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def _context_prop_declared?(_name)
|
|
49
|
+
raise NotImplementedError, "#{self} must implement _context_prop_declared?"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _context_field_label
|
|
53
|
+
"prop"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/dex/context_setup.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Dex
|
|
4
|
-
#
|
|
4
|
+
# Context DSL for Operation and Event (Literal::Properties-backed).
|
|
5
5
|
#
|
|
6
6
|
# Maps declared props to ambient context keys so they can be auto-filled
|
|
7
7
|
# from Dex.context when not passed explicitly as kwargs.
|
|
@@ -9,34 +9,7 @@ module Dex
|
|
|
9
9
|
extend Dex::Concern
|
|
10
10
|
|
|
11
11
|
module ClassMethods
|
|
12
|
-
|
|
13
|
-
names.each do |name|
|
|
14
|
-
unless name.is_a?(Symbol)
|
|
15
|
-
raise ArgumentError, "context shorthand must be a Symbol, got: #{name.inspect}"
|
|
16
|
-
end
|
|
17
|
-
mappings[name] = name
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
raise ArgumentError, "context requires at least one mapping" if mappings.empty?
|
|
21
|
-
|
|
22
|
-
mappings.each do |prop_name, context_key|
|
|
23
|
-
unless _context_prop_declared?(prop_name)
|
|
24
|
-
raise ArgumentError,
|
|
25
|
-
"context references undeclared prop :#{prop_name}. Declare the prop before calling context."
|
|
26
|
-
end
|
|
27
|
-
unless context_key.is_a?(Symbol)
|
|
28
|
-
raise ArgumentError,
|
|
29
|
-
"context key must be a Symbol, got: #{context_key.inspect} for prop :#{prop_name}"
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
_context_own.merge!(mappings)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def context_mappings
|
|
37
|
-
parent = superclass.respond_to?(:context_mappings) ? superclass.context_mappings : {}
|
|
38
|
-
parent.merge(_context_own)
|
|
39
|
-
end
|
|
12
|
+
include ContextDSL
|
|
40
13
|
|
|
41
14
|
def new(**kwargs)
|
|
42
15
|
mappings = context_mappings
|
|
@@ -52,10 +25,6 @@ module Dex
|
|
|
52
25
|
|
|
53
26
|
private
|
|
54
27
|
|
|
55
|
-
def _context_own
|
|
56
|
-
@_context_own_mappings ||= {}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
28
|
def _context_prop_declared?(name)
|
|
60
29
|
respond_to?(:literal_properties) && literal_properties.any? { |p| p.name == name }
|
|
61
30
|
end
|
data/lib/dex/event/bus.rb
CHANGED
|
@@ -44,19 +44,18 @@ module Dex
|
|
|
44
44
|
def publish(event, sync:)
|
|
45
45
|
return if Suppression.suppressed?(event.class)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
trace_data = trace_data_for(event)
|
|
48
|
+
persist(event, trace_data)
|
|
48
49
|
handlers = subscribers_for(event.class)
|
|
49
50
|
return if handlers.empty?
|
|
50
51
|
|
|
51
|
-
event_frame = event.trace_frame
|
|
52
|
-
|
|
53
52
|
handlers.each do |handler_class|
|
|
54
53
|
if sync
|
|
55
|
-
Trace.restore(
|
|
54
|
+
Dex::Trace.restore(trace_data) do
|
|
56
55
|
handler_class._event_handle(event)
|
|
57
56
|
end
|
|
58
57
|
else
|
|
59
|
-
enqueue(handler_class, event,
|
|
58
|
+
enqueue(handler_class, event, trace_data)
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
end
|
|
@@ -67,20 +66,29 @@ module Dex
|
|
|
67
66
|
|
|
68
67
|
private
|
|
69
68
|
|
|
70
|
-
def persist(event)
|
|
69
|
+
def persist(event, trace_data)
|
|
71
70
|
store = Dex.configuration.event_store
|
|
72
71
|
return unless store
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
actor = actor_from_trace(trace_data[:frames])
|
|
74
|
+
attrs = safe_store_attributes(store, {
|
|
75
|
+
id: event.id,
|
|
76
|
+
trace_id: event.trace_id,
|
|
77
|
+
actor_type: actor&.dig(:actor_type),
|
|
78
|
+
actor_id: actor&.dig(:id),
|
|
79
|
+
trace: trace_data[:frames],
|
|
75
80
|
event_type: event.class.name,
|
|
76
81
|
payload: event._props_as_json,
|
|
77
82
|
metadata: event.metadata.as_json
|
|
78
|
-
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
store.create!(**attrs)
|
|
79
86
|
rescue => e
|
|
80
87
|
Event._warn("Failed to persist event: #{e.message}")
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def enqueue(handler_class, event, trace_data)
|
|
91
|
+
ensure_active_job_loaded!
|
|
84
92
|
ctx = event.context
|
|
85
93
|
|
|
86
94
|
Dex::Event::Processor.perform_later(
|
|
@@ -92,6 +100,75 @@ module Dex
|
|
|
92
100
|
context: ctx
|
|
93
101
|
)
|
|
94
102
|
end
|
|
103
|
+
|
|
104
|
+
def trace_data_for(event)
|
|
105
|
+
ambient = Dex::Trace.dump
|
|
106
|
+
frames = if ambient && trace_matches_event?(ambient, event)
|
|
107
|
+
trace_frames(ambient)
|
|
108
|
+
elsif ambient
|
|
109
|
+
actor_frames(trace_frames(ambient))
|
|
110
|
+
else
|
|
111
|
+
[]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
trace_id: event.trace_id,
|
|
116
|
+
frames: frames,
|
|
117
|
+
event_context: event_context_for(event)
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def actor_from_trace(frames)
|
|
122
|
+
Array(frames).find do |frame|
|
|
123
|
+
frame_type = frame[:type] || frame["type"]
|
|
124
|
+
frame_type && frame_type.to_sym == :actor
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def actor_frames(frames)
|
|
129
|
+
Array(frames).select do |frame|
|
|
130
|
+
frame_type = frame[:type] || frame["type"]
|
|
131
|
+
frame_type && frame_type.to_sym == :actor
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def trace_frames(trace_data)
|
|
136
|
+
Array(trace_data[:frames] || trace_data["frames"])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def trace_matches_event?(trace_data, event)
|
|
140
|
+
trace_id = trace_data[:trace_id] || trace_data["trace_id"]
|
|
141
|
+
trace_id.to_s == event.trace_id.to_s
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def event_context_for(event)
|
|
145
|
+
{
|
|
146
|
+
id: event.id,
|
|
147
|
+
trace_id: event.trace_id,
|
|
148
|
+
event_class: event.class.name,
|
|
149
|
+
event_ancestry: event.event_ancestry
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def safe_store_attributes(store, attributes)
|
|
154
|
+
if store.respond_to?(:column_names)
|
|
155
|
+
allowed = store.column_names.to_set
|
|
156
|
+
attributes.select { |key, _| allowed.include?(key.to_s) }
|
|
157
|
+
elsif store.respond_to?(:fields)
|
|
158
|
+
attributes.select do |key, _|
|
|
159
|
+
field_name = key.to_s
|
|
160
|
+
store.fields.key?(field_name) || (field_name == "id" && store.fields.key?("_id"))
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
attributes
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def ensure_active_job_loaded!
|
|
168
|
+
return if defined?(ActiveJob::Base)
|
|
169
|
+
|
|
170
|
+
raise LoadError, "ActiveJob is required for async event handlers. Add 'activejob' to your Gemfile."
|
|
171
|
+
end
|
|
95
172
|
end
|
|
96
173
|
end
|
|
97
174
|
end
|
data/lib/dex/event/handler.rb
CHANGED
|
@@ -69,9 +69,26 @@ module Dex
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def self._event_handle(event)
|
|
72
|
+
execution_id = Dex::Id.generate("hd_")
|
|
73
|
+
auto_started = Dex::Trace.ensure_started!(trace_id: event.trace_id)
|
|
74
|
+
pushed = false
|
|
75
|
+
Dex::Trace.push(
|
|
76
|
+
type: :handler,
|
|
77
|
+
id: execution_id,
|
|
78
|
+
class: name,
|
|
79
|
+
event_class: event.class.name,
|
|
80
|
+
event_id: event.id,
|
|
81
|
+
event_ancestry: event.metadata.event_ancestry
|
|
82
|
+
)
|
|
83
|
+
pushed = true
|
|
84
|
+
|
|
72
85
|
instance = new
|
|
73
86
|
instance.instance_variable_set(:@event, event)
|
|
87
|
+
instance.instance_variable_set(:@_dex_execution_id, execution_id)
|
|
74
88
|
instance.send(:call)
|
|
89
|
+
ensure
|
|
90
|
+
Dex::Trace.pop if pushed
|
|
91
|
+
Dex::Trace.stop! if auto_started
|
|
75
92
|
end
|
|
76
93
|
|
|
77
94
|
def self._event_handle_from_payload(event_class_name, payload, metadata_hash)
|
|
@@ -96,6 +113,7 @@ module Dex
|
|
|
96
113
|
timestamp: Time.parse(metadata_hash["timestamp"]),
|
|
97
114
|
trace_id: metadata_hash["trace_id"],
|
|
98
115
|
caused_by_id: metadata_hash["caused_by_id"],
|
|
116
|
+
event_ancestry: metadata_hash["event_ancestry"] || [],
|
|
99
117
|
context: metadata_hash["context"]
|
|
100
118
|
)
|
|
101
119
|
instance.instance_variable_set(:@metadata, metadata)
|
data/lib/dex/event/metadata.rb
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "securerandom"
|
|
4
|
-
|
|
5
3
|
module Dex
|
|
6
4
|
class Event
|
|
7
5
|
class Metadata
|
|
8
|
-
attr_reader :id, :timestamp, :trace_id, :caused_by_id, :context
|
|
6
|
+
attr_reader :id, :timestamp, :trace_id, :caused_by_id, :event_ancestry, :context
|
|
9
7
|
|
|
10
|
-
def initialize(id:, timestamp:, trace_id:, caused_by_id:, context:)
|
|
8
|
+
def initialize(id:, timestamp:, trace_id:, caused_by_id:, event_ancestry:, context:)
|
|
11
9
|
@id = id
|
|
12
10
|
@timestamp = timestamp
|
|
13
11
|
@trace_id = trace_id
|
|
14
12
|
@caused_by_id = caused_by_id
|
|
13
|
+
@event_ancestry = event_ancestry
|
|
15
14
|
@context = context
|
|
16
15
|
freeze
|
|
17
16
|
end
|
|
18
17
|
|
|
19
|
-
def self.build
|
|
20
|
-
id =
|
|
21
|
-
trace_id = Trace.
|
|
22
|
-
|
|
18
|
+
def self.build
|
|
19
|
+
id = Dex::Id.generate("ev_")
|
|
20
|
+
trace_id = Dex::Trace.trace_id || Dex::Id.generate("tr_")
|
|
21
|
+
current_event = Dex::Trace.current_event_context
|
|
22
|
+
caused = current_event&.dig(:id)
|
|
23
|
+
ancestry = if current_event
|
|
24
|
+
Array(current_event[:event_ancestry]) + [caused].compact
|
|
25
|
+
else
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
23
28
|
|
|
24
29
|
ctx = if Dex.configuration.event_context
|
|
25
30
|
begin
|
|
@@ -35,6 +40,7 @@ module Dex
|
|
|
35
40
|
timestamp: Time.now.utc,
|
|
36
41
|
trace_id: trace_id,
|
|
37
42
|
caused_by_id: caused,
|
|
43
|
+
event_ancestry: ancestry,
|
|
38
44
|
context: ctx
|
|
39
45
|
)
|
|
40
46
|
end
|
|
@@ -43,7 +49,8 @@ module Dex
|
|
|
43
49
|
h = {
|
|
44
50
|
"id" => @id,
|
|
45
51
|
"timestamp" => @timestamp.iso8601(6),
|
|
46
|
-
"trace_id" => @trace_id
|
|
52
|
+
"trace_id" => @trace_id,
|
|
53
|
+
"event_ancestry" => @event_ancestry
|
|
47
54
|
}
|
|
48
55
|
h["caused_by_id"] = @caused_by_id if @caused_by_id
|
|
49
56
|
h["context"] = @context if @context
|
data/lib/dex/event/processor.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Dex
|
|
|
13
13
|
handler = Object.const_get(handler_class)
|
|
14
14
|
retry_config = handler._event_handler_retry_config
|
|
15
15
|
|
|
16
|
-
Dex::
|
|
16
|
+
Dex::Trace.restore(trace) do
|
|
17
17
|
handler._event_handle_from_payload(event_class, payload, metadata)
|
|
18
18
|
end
|
|
19
19
|
rescue => _e
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module EventTestWrapper
|
|
6
|
+
CAPTURING_KEY = :_dex_event_capturing
|
|
7
|
+
PUBLISHED_KEY = :_dex_event_published
|
|
8
|
+
|
|
9
|
+
@_installed = false
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
include ExecutionState
|
|
13
|
+
|
|
14
|
+
def install!
|
|
15
|
+
return if @_installed
|
|
16
|
+
|
|
17
|
+
Dex::Event::Bus.singleton_class.prepend(BusInterceptor)
|
|
18
|
+
@_installed = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def installed?
|
|
22
|
+
@_installed
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def capturing?
|
|
26
|
+
(_execution_state[CAPTURING_KEY] || 0) > 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def begin_capture!
|
|
30
|
+
_execution_state[CAPTURING_KEY] = (_execution_state[CAPTURING_KEY] || 0) + 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def end_capture!
|
|
34
|
+
depth = (_execution_state[CAPTURING_KEY] || 0) - 1
|
|
35
|
+
_execution_state[CAPTURING_KEY] = [depth, 0].max
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def published_events
|
|
39
|
+
_execution_state[PUBLISHED_KEY] ||= []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clear_published!
|
|
43
|
+
_execution_state[PUBLISHED_KEY] = []
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module BusInterceptor
|
|
48
|
+
def publish(event, sync:)
|
|
49
|
+
if Dex::Event::EventTestWrapper.capturing?
|
|
50
|
+
return if Dex::Event::Suppression.suppressed?(event.class)
|
|
51
|
+
|
|
52
|
+
Dex::Event::EventTestWrapper.published_events << event
|
|
53
|
+
else
|
|
54
|
+
super(event, sync: true)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module TestHelpers
|
|
61
|
+
def self.included(base)
|
|
62
|
+
EventTestWrapper.install!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup
|
|
66
|
+
super
|
|
67
|
+
EventTestWrapper.clear_published!
|
|
68
|
+
Dex::Trace.clear!
|
|
69
|
+
Dex::Event::Suppression.clear!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def capture_events
|
|
73
|
+
EventTestWrapper.begin_capture!
|
|
74
|
+
yield
|
|
75
|
+
ensure
|
|
76
|
+
EventTestWrapper.end_capture!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def _dex_published_events
|
|
82
|
+
EventTestWrapper.published_events
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
require_relative "test_helpers/assertions"
|
data/lib/dex/event/trace.rb
CHANGED
|
@@ -3,52 +3,39 @@
|
|
|
3
3
|
module Dex
|
|
4
4
|
class Event
|
|
5
5
|
module Trace
|
|
6
|
-
STACK_KEY = :_dex_event_trace_stack
|
|
7
|
-
|
|
8
6
|
class << self
|
|
9
|
-
include ExecutionState
|
|
10
|
-
|
|
11
7
|
def with_event(event, &block)
|
|
12
|
-
|
|
13
|
-
stack.push(event.trace_frame)
|
|
14
|
-
yield
|
|
15
|
-
ensure
|
|
16
|
-
stack.pop
|
|
8
|
+
Dex::Trace.with_event_context(event, &block)
|
|
17
9
|
end
|
|
18
10
|
|
|
19
11
|
def current_event_id
|
|
20
|
-
|
|
12
|
+
Dex::Trace.current_event_id
|
|
21
13
|
end
|
|
22
14
|
|
|
23
15
|
def current_trace_id
|
|
24
|
-
|
|
16
|
+
Dex::Trace.trace_id
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
def dump
|
|
28
|
-
|
|
29
|
-
return nil unless frame
|
|
30
|
-
|
|
31
|
-
{ id: frame[:id], trace_id: frame[:trace_id] }
|
|
20
|
+
Dex::Trace.dump
|
|
32
21
|
end
|
|
33
22
|
|
|
34
23
|
def restore(data, &block)
|
|
35
24
|
return yield unless data
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
if data.is_a?(Hash) && (data.key?(:frames) || data.key?("frames"))
|
|
27
|
+
Dex::Trace.restore(data, &block)
|
|
28
|
+
else
|
|
29
|
+
Dex::Trace.restore_event_context(
|
|
30
|
+
event_id: data[:id] || data["id"],
|
|
31
|
+
trace_id: data[:trace_id] || data["trace_id"],
|
|
32
|
+
&block
|
|
33
|
+
)
|
|
34
|
+
end
|
|
42
35
|
end
|
|
43
36
|
|
|
44
37
|
def clear!
|
|
45
|
-
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def _stack
|
|
51
|
-
_execution_state[STACK_KEY] ||= []
|
|
38
|
+
Dex::Trace.clear!
|
|
52
39
|
end
|
|
53
40
|
end
|
|
54
41
|
end
|
data/lib/dex/event.rb
CHANGED
|
@@ -9,7 +9,7 @@ require_relative "event/suppression"
|
|
|
9
9
|
module Dex
|
|
10
10
|
class Event
|
|
11
11
|
RESERVED_PROP_NAMES = %i[
|
|
12
|
-
id timestamp trace_id caused_by_id caused_by
|
|
12
|
+
id timestamp trace_id caused_by_id caused_by event_ancestry
|
|
13
13
|
context publish metadata sync
|
|
14
14
|
].to_set.freeze
|
|
15
15
|
|
|
@@ -67,8 +67,8 @@ module Dex
|
|
|
67
67
|
def timestamp = metadata.timestamp
|
|
68
68
|
def trace_id = metadata.trace_id
|
|
69
69
|
def caused_by_id = metadata.caused_by_id
|
|
70
|
+
def event_ancestry = metadata.event_ancestry
|
|
70
71
|
def context = metadata.context
|
|
71
|
-
def trace_frame = { id: id, trace_id: trace_id }
|
|
72
72
|
|
|
73
73
|
# Publishing
|
|
74
74
|
def publish(sync: false)
|
|
@@ -85,11 +85,6 @@ module Dex
|
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
# Tracing
|
|
89
|
-
def trace(&block)
|
|
90
|
-
Trace.with_event(self, &block)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
88
|
# Suppression
|
|
94
89
|
def self.suppress(*classes, &block)
|
|
95
90
|
Suppression.suppress(*classes, &block)
|
|
@@ -1,88 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class Event
|
|
5
|
-
module EventTestWrapper
|
|
6
|
-
CAPTURING_KEY = :_dex_event_capturing
|
|
7
|
-
PUBLISHED_KEY = :_dex_event_published
|
|
8
|
-
|
|
9
|
-
@_installed = false
|
|
10
|
-
|
|
11
|
-
class << self
|
|
12
|
-
include ExecutionState
|
|
13
|
-
|
|
14
|
-
def install!
|
|
15
|
-
return if @_installed
|
|
16
|
-
|
|
17
|
-
Dex::Event::Bus.singleton_class.prepend(BusInterceptor)
|
|
18
|
-
@_installed = true
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def installed?
|
|
22
|
-
@_installed
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def capturing?
|
|
26
|
-
(_execution_state[CAPTURING_KEY] || 0) > 0
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def begin_capture!
|
|
30
|
-
_execution_state[CAPTURING_KEY] = (_execution_state[CAPTURING_KEY] || 0) + 1
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def end_capture!
|
|
34
|
-
depth = (_execution_state[CAPTURING_KEY] || 0) - 1
|
|
35
|
-
_execution_state[CAPTURING_KEY] = [depth, 0].max
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def published_events
|
|
39
|
-
_execution_state[PUBLISHED_KEY] ||= []
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def clear_published!
|
|
43
|
-
_execution_state[PUBLISHED_KEY] = []
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
module BusInterceptor
|
|
48
|
-
def publish(event, sync:)
|
|
49
|
-
if Dex::Event::EventTestWrapper.capturing?
|
|
50
|
-
return if Dex::Event::Suppression.suppressed?(event.class)
|
|
51
|
-
|
|
52
|
-
Dex::Event::EventTestWrapper.published_events << event
|
|
53
|
-
else
|
|
54
|
-
super(event, sync: true)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
module TestHelpers
|
|
61
|
-
def self.included(base)
|
|
62
|
-
EventTestWrapper.install!
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def setup
|
|
66
|
-
super
|
|
67
|
-
EventTestWrapper.clear_published!
|
|
68
|
-
Dex::Event::Trace.clear!
|
|
69
|
-
Dex::Event::Suppression.clear!
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def capture_events
|
|
73
|
-
EventTestWrapper.begin_capture!
|
|
74
|
-
yield
|
|
75
|
-
ensure
|
|
76
|
-
EventTestWrapper.end_capture!
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
def _dex_published_events
|
|
82
|
-
EventTestWrapper.published_events
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
require_relative "event_test_helpers/assertions"
|
|
3
|
+
require_relative "event/test_helpers"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Form
|
|
5
|
+
# Context DSL for Form (ActiveModel::Attributes-backed).
|
|
6
|
+
#
|
|
7
|
+
# Same DSL as Operation/Event context, but checks attribute_names
|
|
8
|
+
# instead of literal_properties. Injection happens in Form#initialize.
|
|
9
|
+
module Context
|
|
10
|
+
extend Dex::Concern
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
include ContextDSL
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def _context_prop_declared?(name)
|
|
18
|
+
attribute_names.include?(name.to_s)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _context_field_label
|
|
22
|
+
"field"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|