dexkit 0.5.0 → 0.7.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 +31 -0
- data/README.md +69 -2
- data/guides/llm/EVENT.md +95 -9
- data/guides/llm/OPERATION.md +246 -49
- data/lib/dex/context_setup.rb +64 -0
- data/lib/dex/event/handler.rb +20 -1
- data/lib/dex/event.rb +1 -2
- data/lib/dex/executable.rb +44 -0
- data/lib/dex/operation/async_proxy.rb +18 -2
- data/lib/dex/operation/guard_wrapper.rb +138 -0
- data/lib/dex/operation/jobs.rb +18 -11
- data/lib/dex/operation/once_wrapper.rb +240 -0
- data/lib/dex/operation/record_backend.rb +75 -0
- data/lib/dex/operation/record_wrapper.rb +87 -20
- data/lib/dex/operation/transaction_wrapper.rb +2 -1
- data/lib/dex/operation.rb +16 -36
- data/lib/dex/pipeline.rb +58 -0
- data/lib/dex/test_helpers/assertions.rb +23 -0
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +19 -0
- metadata +11 -6
- data/lib/dex/operation/pipeline.rb +0 -60
- /data/lib/dex/{operation/settings.rb → settings.rb} +0 -0
|
@@ -34,6 +34,22 @@ module Dex
|
|
|
34
34
|
record_class.find(id).update!(safe_attributes(attributes))
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def find_by_once_key(key)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find_expired_once_key(key)
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update_record_by_once_key(key, **attributes)
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def unique_constraint_error?(exception)
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
end
|
|
52
|
+
|
|
37
53
|
def safe_attributes(attributes)
|
|
38
54
|
attributes.select { |key, _| has_field?(key.to_s) }
|
|
39
55
|
end
|
|
@@ -49,12 +65,71 @@ module Dex
|
|
|
49
65
|
@column_set = record_class.column_names.to_set
|
|
50
66
|
end
|
|
51
67
|
|
|
68
|
+
def find_by_once_key(key)
|
|
69
|
+
scope = record_class.where(once_key: key, status: %w[completed error])
|
|
70
|
+
scope = scope.where("once_key_expires_at IS NULL OR once_key_expires_at >= ?", Time.now) if has_field?("once_key_expires_at")
|
|
71
|
+
scope.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def find_expired_once_key(key)
|
|
75
|
+
return nil unless has_field?("once_key_expires_at")
|
|
76
|
+
|
|
77
|
+
record_class
|
|
78
|
+
.where(once_key: key, status: %w[completed error])
|
|
79
|
+
.where("once_key_expires_at IS NOT NULL AND once_key_expires_at < ?", Time.now)
|
|
80
|
+
.first
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def update_record_by_once_key(key, **attributes)
|
|
84
|
+
record = record_class.where(once_key: key, status: %w[completed error]).first
|
|
85
|
+
record&.update!(safe_attributes(attributes))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def unique_constraint_error?(exception)
|
|
89
|
+
defined?(ActiveRecord::RecordNotUnique) && exception.is_a?(ActiveRecord::RecordNotUnique)
|
|
90
|
+
end
|
|
91
|
+
|
|
52
92
|
def has_field?(field_name)
|
|
53
93
|
@column_set.include?(field_name.to_s)
|
|
54
94
|
end
|
|
55
95
|
end
|
|
56
96
|
|
|
57
97
|
class MongoidAdapter < Base
|
|
98
|
+
def find_by_once_key(key)
|
|
99
|
+
now = Time.now
|
|
100
|
+
record_class.where(
|
|
101
|
+
:once_key => key,
|
|
102
|
+
:status.in => %w[completed error]
|
|
103
|
+
).and(
|
|
104
|
+
record_class.or(
|
|
105
|
+
{ once_key_expires_at: nil },
|
|
106
|
+
{ :once_key_expires_at.gte => now }
|
|
107
|
+
)
|
|
108
|
+
).first
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def find_expired_once_key(key)
|
|
112
|
+
return nil unless has_field?("once_key_expires_at")
|
|
113
|
+
|
|
114
|
+
record_class.where(
|
|
115
|
+
:once_key => key,
|
|
116
|
+
:status.in => %w[completed error],
|
|
117
|
+
:once_key_expires_at.ne => nil,
|
|
118
|
+
:once_key_expires_at.lt => Time.now
|
|
119
|
+
).first
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def update_record_by_once_key(key, **attributes)
|
|
123
|
+
record = record_class.where(:once_key => key, :status.in => %w[completed error]).first
|
|
124
|
+
record&.update!(safe_attributes(attributes))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unique_constraint_error?(exception)
|
|
128
|
+
defined?(Mongo::Error::OperationFailure) &&
|
|
129
|
+
exception.is_a?(Mongo::Error::OperationFailure) &&
|
|
130
|
+
exception.code == 11_000
|
|
131
|
+
end
|
|
132
|
+
|
|
58
133
|
def has_field?(field_name)
|
|
59
134
|
record_class.fields.key?(field_name.to_s)
|
|
60
135
|
end
|
|
@@ -7,26 +7,27 @@ module Dex
|
|
|
7
7
|
def _record_wrap
|
|
8
8
|
interceptor = Operation::HaltInterceptor.new { yield }
|
|
9
9
|
|
|
10
|
-
if
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
_record_save!(interceptor.result)
|
|
15
|
-
end
|
|
10
|
+
if _record_has_pending_record?
|
|
11
|
+
_record_update_outcome!(interceptor)
|
|
12
|
+
elsif _record_enabled?
|
|
13
|
+
_record_save!(interceptor)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
interceptor.rethrow!
|
|
19
17
|
interceptor.result
|
|
18
|
+
rescue => e
|
|
19
|
+
_record_failure!(e) if _record_has_pending_record? || _record_enabled?
|
|
20
|
+
raise
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
module ClassMethods
|
|
23
24
|
def record(enabled = nil, **options)
|
|
24
|
-
validate_options!(options, %i[params
|
|
25
|
+
validate_options!(options, %i[params result], :record)
|
|
25
26
|
|
|
26
27
|
if enabled == false
|
|
27
28
|
set :record, enabled: false
|
|
28
29
|
elsif enabled == true || enabled.nil?
|
|
29
|
-
merged = { enabled: true, params: true,
|
|
30
|
+
merged = { enabled: true, params: true, result: true }.merge(options)
|
|
30
31
|
set :record, **merged
|
|
31
32
|
else
|
|
32
33
|
raise ArgumentError,
|
|
@@ -49,27 +50,78 @@ module Dex
|
|
|
49
50
|
defined?(@_dex_record_id) && @_dex_record_id
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
def _record_save!(
|
|
53
|
-
|
|
53
|
+
def _record_save!(interceptor)
|
|
54
|
+
attrs = _record_base_attrs
|
|
55
|
+
if interceptor.error?
|
|
56
|
+
attrs.merge!(_record_error_attrs(code: interceptor.halt.error_code,
|
|
57
|
+
message: interceptor.halt.error_message,
|
|
58
|
+
details: interceptor.halt.error_details))
|
|
59
|
+
else
|
|
60
|
+
attrs.merge!(_record_success_attrs(interceptor.result))
|
|
61
|
+
end
|
|
62
|
+
Dex.record_backend.create_record(attrs)
|
|
54
63
|
rescue => e
|
|
55
64
|
_record_handle_error(e)
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
def
|
|
59
|
-
attrs =
|
|
60
|
-
|
|
67
|
+
def _record_update_outcome!(interceptor)
|
|
68
|
+
attrs = if interceptor.error?
|
|
69
|
+
_record_error_attrs(code: interceptor.halt.error_code,
|
|
70
|
+
message: interceptor.halt.error_message,
|
|
71
|
+
details: interceptor.halt.error_details)
|
|
72
|
+
else
|
|
73
|
+
_record_success_attrs(interceptor.result)
|
|
74
|
+
end
|
|
61
75
|
Dex.record_backend.update_record(@_dex_record_id, attrs)
|
|
62
76
|
rescue => e
|
|
63
77
|
_record_handle_error(e)
|
|
64
78
|
end
|
|
65
79
|
|
|
66
|
-
def
|
|
67
|
-
attrs =
|
|
80
|
+
def _record_failure!(exception)
|
|
81
|
+
attrs = if exception.is_a?(Dex::Error)
|
|
82
|
+
_record_error_attrs(code: exception.code, message: exception.message, details: exception.details)
|
|
83
|
+
else
|
|
84
|
+
{
|
|
85
|
+
status: "failed",
|
|
86
|
+
error_code: exception.class.name,
|
|
87
|
+
error_message: exception.message,
|
|
88
|
+
performed_at: _record_current_time
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
attrs[:once_key] = nil if defined?(@_once_key) || self.class.settings_for(:once).fetch(:defined, false)
|
|
93
|
+
|
|
94
|
+
if _record_has_pending_record?
|
|
95
|
+
Dex.record_backend.update_record(@_dex_record_id, attrs)
|
|
96
|
+
else
|
|
97
|
+
Dex.record_backend.create_record(_record_base_attrs.merge(attrs))
|
|
98
|
+
end
|
|
99
|
+
rescue => e
|
|
100
|
+
_record_handle_error(e)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def _record_base_attrs
|
|
104
|
+
attrs = { name: self.class.name }
|
|
68
105
|
attrs[:params] = _record_params? ? _record_params : nil
|
|
69
|
-
attrs[:response] = _record_response? ? _record_response(result) : nil
|
|
70
106
|
attrs
|
|
71
107
|
end
|
|
72
108
|
|
|
109
|
+
def _record_success_attrs(result)
|
|
110
|
+
attrs = { status: "completed", performed_at: _record_current_time }
|
|
111
|
+
attrs[:result] = _record_result(result) if _record_result?
|
|
112
|
+
attrs
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _record_error_attrs(code:, message:, details:)
|
|
116
|
+
{
|
|
117
|
+
status: "error",
|
|
118
|
+
error_code: code.to_s,
|
|
119
|
+
error_message: message || code.to_s,
|
|
120
|
+
error_details: _record_sanitize_details(details),
|
|
121
|
+
performed_at: _record_current_time
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
73
125
|
def _record_params
|
|
74
126
|
_props_as_json
|
|
75
127
|
end
|
|
@@ -78,11 +130,11 @@ module Dex
|
|
|
78
130
|
self.class.settings_for(:record).fetch(:params, true)
|
|
79
131
|
end
|
|
80
132
|
|
|
81
|
-
def
|
|
82
|
-
self.class.settings_for(:record).fetch(:
|
|
133
|
+
def _record_result?
|
|
134
|
+
self.class.settings_for(:record).fetch(:result, true)
|
|
83
135
|
end
|
|
84
136
|
|
|
85
|
-
def
|
|
137
|
+
def _record_result(result)
|
|
86
138
|
success_type = self.class.respond_to?(:_success_type) && self.class._success_type
|
|
87
139
|
|
|
88
140
|
if success_type
|
|
@@ -91,7 +143,7 @@ module Dex
|
|
|
91
143
|
case result
|
|
92
144
|
when nil then nil
|
|
93
145
|
when Hash then result
|
|
94
|
-
else {
|
|
146
|
+
else { _dex_value: result } # namespaced key so replay can distinguish wrapped primitives from user hashes
|
|
95
147
|
end
|
|
96
148
|
end
|
|
97
149
|
end
|
|
@@ -100,6 +152,21 @@ module Dex
|
|
|
100
152
|
Time.respond_to?(:current) ? Time.current : Time.now
|
|
101
153
|
end
|
|
102
154
|
|
|
155
|
+
def _record_sanitize_details(details)
|
|
156
|
+
_record_sanitize_value(details)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def _record_sanitize_value(value)
|
|
160
|
+
case value
|
|
161
|
+
when NilClass, String, Integer, Float, TrueClass, FalseClass then value
|
|
162
|
+
when Symbol then value.to_s
|
|
163
|
+
when Hash then value.transform_values { |v| _record_sanitize_value(v) }
|
|
164
|
+
when Array then value.map { |v| _record_sanitize_value(v) }
|
|
165
|
+
when Exception then "#{value.class}: #{value.message}"
|
|
166
|
+
else value.to_s
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
103
170
|
def _record_handle_error(error)
|
|
104
171
|
Dex.warn("Failed to record operation: #{error.message}")
|
|
105
172
|
end
|
|
@@ -121,7 +121,8 @@ module Dex
|
|
|
121
121
|
return if callbacks.empty?
|
|
122
122
|
|
|
123
123
|
flush = -> { callbacks.each(&:call) }
|
|
124
|
-
|
|
124
|
+
enabled = self.class.settings_for(:transaction).fetch(:enabled, true)
|
|
125
|
+
adapter = enabled && _transaction_adapter
|
|
125
126
|
if adapter
|
|
126
127
|
adapter.after_commit(&flush)
|
|
127
128
|
else
|
data/lib/dex/operation.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Wrapper modules (loaded before class body so `include`/`use` can find them)
|
|
4
|
-
require_relative "operation/settings"
|
|
5
4
|
require_relative "operation/result_wrapper"
|
|
5
|
+
require_relative "operation/once_wrapper"
|
|
6
6
|
require_relative "operation/record_wrapper"
|
|
7
7
|
require_relative "operation/transaction_wrapper"
|
|
8
8
|
require_relative "operation/lock_wrapper"
|
|
@@ -10,9 +10,7 @@ require_relative "operation/async_wrapper"
|
|
|
10
10
|
require_relative "operation/safe_wrapper"
|
|
11
11
|
require_relative "operation/rescue_wrapper"
|
|
12
12
|
require_relative "operation/callback_wrapper"
|
|
13
|
-
|
|
14
|
-
# Pipeline (referenced inside class body)
|
|
15
|
-
require_relative "operation/pipeline"
|
|
13
|
+
require_relative "operation/guard_wrapper"
|
|
16
14
|
|
|
17
15
|
module Dex
|
|
18
16
|
class Operation
|
|
@@ -44,38 +42,25 @@ module Dex
|
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
RESERVED_PROP_NAMES = %i[call perform async safe initialize].to_set.freeze
|
|
45
|
+
RESERVED_PROP_NAMES = %i[call perform async safe once initialize].to_set.freeze
|
|
48
46
|
|
|
47
|
+
include Executable
|
|
49
48
|
include PropsSetup
|
|
50
49
|
include TypeCoercion
|
|
50
|
+
include ContextSetup
|
|
51
51
|
|
|
52
|
-
Contract = Data.define(:params, :success, :errors)
|
|
52
|
+
Contract = Data.define(:params, :success, :errors, :guards)
|
|
53
53
|
|
|
54
54
|
class << self
|
|
55
55
|
def contract
|
|
56
56
|
Contract.new(
|
|
57
57
|
params: _contract_params,
|
|
58
58
|
success: _success_type,
|
|
59
|
-
errors: _declared_errors
|
|
59
|
+
errors: _declared_errors,
|
|
60
|
+
guards: _contract_guards
|
|
60
61
|
)
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
def inherited(subclass)
|
|
64
|
-
subclass.instance_variable_set(:@_pipeline, pipeline.dup)
|
|
65
|
-
super
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def pipeline
|
|
69
|
-
@_pipeline ||= Pipeline.new
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def use(mod, as: nil, wrap: nil, before: nil, after: nil, at: nil)
|
|
73
|
-
step_name = as || _derive_step_name(mod)
|
|
74
|
-
wrap_method = wrap || :"_#{step_name}_wrap"
|
|
75
|
-
pipeline.add(step_name, method: wrap_method, before: before, after: after, at: at)
|
|
76
|
-
include mod
|
|
77
|
-
end
|
|
78
|
-
|
|
79
64
|
private
|
|
80
65
|
|
|
81
66
|
def _contract_params
|
|
@@ -86,24 +71,18 @@ module Dex
|
|
|
86
71
|
end
|
|
87
72
|
end
|
|
88
73
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
raise ArgumentError, "anonymous modules require explicit as: parameter" unless base
|
|
74
|
+
def _contract_guards
|
|
75
|
+
return [] unless respond_to?(:_guard_list)
|
|
92
76
|
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
.to_sym
|
|
77
|
+
_guard_list.map do |g|
|
|
78
|
+
{ name: g.name, message: g.message, requires: g.requires }
|
|
79
|
+
end
|
|
97
80
|
end
|
|
98
81
|
end
|
|
99
82
|
|
|
100
83
|
def perform(*, **)
|
|
101
84
|
end
|
|
102
85
|
|
|
103
|
-
def call
|
|
104
|
-
self.class.pipeline.execute(self) { perform }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
86
|
def self.method_added(method_name)
|
|
108
87
|
super
|
|
109
88
|
return unless method_name == :perform
|
|
@@ -117,14 +96,15 @@ module Dex
|
|
|
117
96
|
new(**kwargs).call
|
|
118
97
|
end
|
|
119
98
|
|
|
120
|
-
include Settings
|
|
121
99
|
include AsyncWrapper
|
|
122
100
|
include SafeWrapper
|
|
123
101
|
|
|
124
102
|
use ResultWrapper
|
|
103
|
+
use GuardWrapper
|
|
104
|
+
use OnceWrapper
|
|
125
105
|
use LockWrapper
|
|
126
|
-
use TransactionWrapper
|
|
127
106
|
use RecordWrapper
|
|
107
|
+
use TransactionWrapper
|
|
128
108
|
use RescueWrapper
|
|
129
109
|
use CallbackWrapper
|
|
130
110
|
end
|
data/lib/dex/pipeline.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Pipeline
|
|
5
|
+
Step = Data.define(:name, :method)
|
|
6
|
+
|
|
7
|
+
def initialize(steps = [])
|
|
8
|
+
@steps = steps.dup
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dup
|
|
12
|
+
self.class.new(@steps)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def steps
|
|
16
|
+
@steps.dup.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add(name, method: :"_#{name}_wrap", before: nil, after: nil, at: nil)
|
|
20
|
+
validate_positioning!(before, after, at)
|
|
21
|
+
step = Step.new(name: name, method: method)
|
|
22
|
+
|
|
23
|
+
if at == :outer then @steps.unshift(step)
|
|
24
|
+
elsif at == :inner then @steps.push(step)
|
|
25
|
+
elsif before then @steps.insert(find_index!(before), step)
|
|
26
|
+
elsif after then @steps.insert(find_index!(after) + 1, step)
|
|
27
|
+
else @steps.push(step)
|
|
28
|
+
end
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove(name)
|
|
33
|
+
@steps.reject! { |s| s.name == name }
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def execute(target)
|
|
38
|
+
chain = @steps.reverse_each.reduce(-> { yield }) do |next_step, step|
|
|
39
|
+
-> { target.send(step.method, &next_step) }
|
|
40
|
+
end
|
|
41
|
+
chain.call
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def validate_positioning!(before, after, at)
|
|
47
|
+
count = [before, after, at].count { |v| !v.nil? }
|
|
48
|
+
raise ArgumentError, "specify only one of before:, after:, at:" if count > 1
|
|
49
|
+
raise ArgumentError, "at: must be :outer or :inner" if at && !%i[outer inner].include?(at)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_index!(name)
|
|
53
|
+
idx = @steps.index { |s| s.name == name }
|
|
54
|
+
raise ArgumentError, "pipeline step :#{name} not found" unless idx
|
|
55
|
+
idx
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -214,6 +214,29 @@ module Dex
|
|
|
214
214
|
"Expected #{model_class.name} count to increase, but it stayed at #{count_before}"
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
+
# --- Guard assertions ---
|
|
218
|
+
|
|
219
|
+
def assert_callable(*args, **params)
|
|
220
|
+
klass = _dex_resolve_subject(args)
|
|
221
|
+
result = klass.callable(**params)
|
|
222
|
+
assert result.ok?, "Expected operation to be callable, but guards failed:\n#{_dex_format_err(result)}"
|
|
223
|
+
result
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def refute_callable(*args, **params)
|
|
227
|
+
klass_args, codes = _dex_split_class_and_symbols(args)
|
|
228
|
+
klass = _dex_resolve_subject(klass_args)
|
|
229
|
+
code = codes.first
|
|
230
|
+
result = klass.callable(**params)
|
|
231
|
+
refute result.ok?, "Expected operation to NOT be callable, but all guards passed"
|
|
232
|
+
if code
|
|
233
|
+
failed_codes = result.details.map { |f| f[:guard] }
|
|
234
|
+
assert_includes failed_codes, code,
|
|
235
|
+
"Expected guard :#{code} to fail, but it didn't.\n Failed guards: #{failed_codes.inspect}"
|
|
236
|
+
end
|
|
237
|
+
result
|
|
238
|
+
end
|
|
239
|
+
|
|
217
240
|
# --- Batch assertions ---
|
|
218
241
|
|
|
219
242
|
def assert_all_succeed(*args, params_list:)
|
data/lib/dex/version.rb
CHANGED
data/lib/dexkit.rb
CHANGED
|
@@ -14,7 +14,11 @@ require_relative "dex/concern"
|
|
|
14
14
|
require_relative "dex/ref_type"
|
|
15
15
|
require_relative "dex/type_coercion"
|
|
16
16
|
require_relative "dex/props_setup"
|
|
17
|
+
require_relative "dex/context_setup"
|
|
17
18
|
require_relative "dex/error"
|
|
19
|
+
require_relative "dex/settings"
|
|
20
|
+
require_relative "dex/pipeline"
|
|
21
|
+
require_relative "dex/executable"
|
|
18
22
|
require_relative "dex/operation"
|
|
19
23
|
require_relative "dex/event"
|
|
20
24
|
require_relative "dex/form"
|
|
@@ -68,5 +72,20 @@ module Dex
|
|
|
68
72
|
def transaction_adapter=(adapter)
|
|
69
73
|
configuration.transaction_adapter = adapter
|
|
70
74
|
end
|
|
75
|
+
|
|
76
|
+
CONTEXT_KEY = :_dex_context
|
|
77
|
+
EMPTY_CONTEXT = {}.freeze
|
|
78
|
+
|
|
79
|
+
def context
|
|
80
|
+
Fiber[CONTEXT_KEY] || EMPTY_CONTEXT
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def with_context(**values)
|
|
84
|
+
previous = Fiber[CONTEXT_KEY]
|
|
85
|
+
Fiber[CONTEXT_KEY] = (previous || {}).merge(values)
|
|
86
|
+
yield
|
|
87
|
+
ensure
|
|
88
|
+
Fiber[CONTEXT_KEY] = previous
|
|
89
|
+
end
|
|
71
90
|
end
|
|
72
91
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dexkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jacek Galanciak
|
|
@@ -166,6 +166,7 @@ files:
|
|
|
166
166
|
- guides/llm/OPERATION.md
|
|
167
167
|
- guides/llm/QUERY.md
|
|
168
168
|
- lib/dex/concern.rb
|
|
169
|
+
- lib/dex/context_setup.rb
|
|
169
170
|
- lib/dex/error.rb
|
|
170
171
|
- lib/dex/event.rb
|
|
171
172
|
- lib/dex/event/bus.rb
|
|
@@ -177,6 +178,7 @@ files:
|
|
|
177
178
|
- lib/dex/event/trace.rb
|
|
178
179
|
- lib/dex/event_test_helpers.rb
|
|
179
180
|
- lib/dex/event_test_helpers/assertions.rb
|
|
181
|
+
- lib/dex/executable.rb
|
|
180
182
|
- lib/dex/form.rb
|
|
181
183
|
- lib/dex/form/nesting.rb
|
|
182
184
|
- lib/dex/form/uniqueness_validator.rb
|
|
@@ -185,24 +187,26 @@ files:
|
|
|
185
187
|
- lib/dex/operation/async_proxy.rb
|
|
186
188
|
- lib/dex/operation/async_wrapper.rb
|
|
187
189
|
- lib/dex/operation/callback_wrapper.rb
|
|
190
|
+
- lib/dex/operation/guard_wrapper.rb
|
|
188
191
|
- lib/dex/operation/jobs.rb
|
|
189
192
|
- lib/dex/operation/lock_wrapper.rb
|
|
193
|
+
- lib/dex/operation/once_wrapper.rb
|
|
190
194
|
- lib/dex/operation/outcome.rb
|
|
191
|
-
- lib/dex/operation/pipeline.rb
|
|
192
195
|
- lib/dex/operation/record_backend.rb
|
|
193
196
|
- lib/dex/operation/record_wrapper.rb
|
|
194
197
|
- lib/dex/operation/rescue_wrapper.rb
|
|
195
198
|
- lib/dex/operation/result_wrapper.rb
|
|
196
199
|
- lib/dex/operation/safe_wrapper.rb
|
|
197
|
-
- lib/dex/operation/settings.rb
|
|
198
200
|
- lib/dex/operation/transaction_adapter.rb
|
|
199
201
|
- lib/dex/operation/transaction_wrapper.rb
|
|
202
|
+
- lib/dex/pipeline.rb
|
|
200
203
|
- lib/dex/props_setup.rb
|
|
201
204
|
- lib/dex/query.rb
|
|
202
205
|
- lib/dex/query/backend.rb
|
|
203
206
|
- lib/dex/query/filtering.rb
|
|
204
207
|
- lib/dex/query/sorting.rb
|
|
205
208
|
- lib/dex/ref_type.rb
|
|
209
|
+
- lib/dex/settings.rb
|
|
206
210
|
- lib/dex/test_helpers.rb
|
|
207
211
|
- lib/dex/test_helpers/assertions.rb
|
|
208
212
|
- lib/dex/test_helpers/execution.rb
|
|
@@ -211,14 +215,15 @@ files:
|
|
|
211
215
|
- lib/dex/type_coercion.rb
|
|
212
216
|
- lib/dex/version.rb
|
|
213
217
|
- lib/dexkit.rb
|
|
214
|
-
homepage: https://
|
|
218
|
+
homepage: https://dex.razorjack.net/
|
|
215
219
|
licenses:
|
|
216
220
|
- MIT
|
|
217
221
|
metadata:
|
|
218
222
|
allowed_push_host: https://rubygems.org
|
|
219
|
-
homepage_uri: https://
|
|
223
|
+
homepage_uri: https://dex.razorjack.net/
|
|
220
224
|
source_code_uri: https://github.com/razorjack/dexkit
|
|
221
225
|
changelog_uri: https://github.com/razorjack/dexkit/blob/master/CHANGELOG.md
|
|
226
|
+
documentation_uri: https://dex.razorjack.net/
|
|
222
227
|
rdoc_options: []
|
|
223
228
|
require_paths:
|
|
224
229
|
- lib
|
|
@@ -235,5 +240,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
235
240
|
requirements: []
|
|
236
241
|
rubygems_version: 4.0.3
|
|
237
242
|
specification_version: 4
|
|
238
|
-
summary: '
|
|
243
|
+
summary: 'dexkit: Rails Patterns Toolbelt. Equip to gain +4 DEX'
|
|
239
244
|
test_files: []
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dex
|
|
4
|
-
class Operation
|
|
5
|
-
class Pipeline
|
|
6
|
-
Step = Data.define(:name, :method)
|
|
7
|
-
|
|
8
|
-
def initialize(steps = [])
|
|
9
|
-
@steps = steps.dup
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def dup
|
|
13
|
-
self.class.new(@steps)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def steps
|
|
17
|
-
@steps.dup.freeze
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def add(name, method: :"_#{name}_wrap", before: nil, after: nil, at: nil)
|
|
21
|
-
validate_positioning!(before, after, at)
|
|
22
|
-
step = Step.new(name: name, method: method)
|
|
23
|
-
|
|
24
|
-
if at == :outer then @steps.unshift(step)
|
|
25
|
-
elsif at == :inner then @steps.push(step)
|
|
26
|
-
elsif before then @steps.insert(find_index!(before), step)
|
|
27
|
-
elsif after then @steps.insert(find_index!(after) + 1, step)
|
|
28
|
-
else @steps.push(step)
|
|
29
|
-
end
|
|
30
|
-
self
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def remove(name)
|
|
34
|
-
@steps.reject! { |s| s.name == name }
|
|
35
|
-
self
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def execute(operation)
|
|
39
|
-
chain = @steps.reverse_each.reduce(-> { yield }) do |next_step, step|
|
|
40
|
-
-> { operation.send(step.method, &next_step) }
|
|
41
|
-
end
|
|
42
|
-
chain.call
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def validate_positioning!(before, after, at)
|
|
48
|
-
count = [before, after, at].count { |v| !v.nil? }
|
|
49
|
-
raise ArgumentError, "specify only one of before:, after:, at:" if count > 1
|
|
50
|
-
raise ArgumentError, "at: must be :outer or :inner" if at && !%i[outer inner].include?(at)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def find_index!(name)
|
|
54
|
-
idx = @steps.index { |s| s.name == name }
|
|
55
|
-
raise ArgumentError, "pipeline step :#{name} not found" unless idx
|
|
56
|
-
idx
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
File without changes
|