dexkit 0.6.0 → 0.8.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 +49 -0
- data/README.md +97 -5
- data/guides/llm/EVENT.md +74 -10
- data/guides/llm/FORM.md +1 -1
- data/guides/llm/OPERATION.md +352 -51
- data/guides/llm/QUERY.md +1 -1
- data/lib/dex/context_setup.rb +64 -0
- data/lib/dex/event/export.rb +56 -0
- data/lib/dex/event/handler.rb +33 -0
- data/lib/dex/event.rb +28 -0
- data/lib/dex/operation/async_proxy.rb +18 -2
- data/lib/dex/operation/explain.rb +204 -0
- data/lib/dex/operation/export.rb +144 -0
- data/lib/dex/operation/guard_wrapper.rb +149 -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 +87 -0
- data/lib/dex/operation/record_wrapper.rb +87 -20
- data/lib/dex/operation.rb +62 -4
- data/lib/dex/props_setup.rb +25 -2
- data/lib/dex/railtie.rb +84 -0
- data/lib/dex/registry.rb +63 -0
- data/lib/dex/test_helpers/assertions.rb +23 -0
- data/lib/dex/tool.rb +115 -0
- data/lib/dex/type_serializer.rb +132 -0
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +21 -0
- metadata +11 -1
data/lib/dex/railtie.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
rake_tasks do
|
|
6
|
+
namespace :dex do
|
|
7
|
+
desc "Export operation/event/handler contracts (FORMAT=hash|json_schema SECTION=operations|events|handlers FILE=path)"
|
|
8
|
+
task export: :environment do
|
|
9
|
+
Rails.application.eager_load!
|
|
10
|
+
|
|
11
|
+
format = (ENV["FORMAT"] || "hash").to_sym
|
|
12
|
+
section = ENV["SECTION"] || "operations"
|
|
13
|
+
file = ENV["FILE"]
|
|
14
|
+
|
|
15
|
+
data = case section
|
|
16
|
+
when "operations" then Dex::Operation.export(format: format)
|
|
17
|
+
when "events" then Dex::Event.export(format: format)
|
|
18
|
+
when "handlers"
|
|
19
|
+
if format != :hash
|
|
20
|
+
raise "Handlers only support FORMAT=hash (got #{format})"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Dex::Event::Handler.export(format: format)
|
|
24
|
+
else
|
|
25
|
+
raise "Unknown SECTION=#{section}. Known: operations, events, handlers"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
json = JSON.pretty_generate(data)
|
|
29
|
+
|
|
30
|
+
if file
|
|
31
|
+
File.write(file, json)
|
|
32
|
+
puts "Wrote #{data.size} #{section} to #{file}"
|
|
33
|
+
else
|
|
34
|
+
puts json
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Install LLM guides as AGENTS.md in app directories (FORCE=1 to overwrite app-owned files)"
|
|
39
|
+
task :guides do
|
|
40
|
+
gem_root = File.expand_path("../..", __dir__)
|
|
41
|
+
guide_dir = File.join(gem_root, "guides", "llm")
|
|
42
|
+
marker = "<!-- dexkit v#{Dex::VERSION} | Auto-generated by: rake dex:guides -->"
|
|
43
|
+
force = ENV["FORCE"] == "1"
|
|
44
|
+
written = 0
|
|
45
|
+
|
|
46
|
+
# [source_file, env_var, default_path]
|
|
47
|
+
mapping = [
|
|
48
|
+
["OPERATION.md", "OPERATIONS_PATH", "app/operations"],
|
|
49
|
+
["EVENT.md", "EVENTS_PATH", "app/events"],
|
|
50
|
+
["EVENT.md", "EVENT_HANDLERS_PATH", "app/event_handlers"],
|
|
51
|
+
["FORM.md", "FORMS_PATH", "app/forms"],
|
|
52
|
+
["QUERY.md", "QUERIES_PATH", "app/queries"]
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
mapping.each do |source_file, env_var, default_path|
|
|
56
|
+
target_dir = ENV[env_var] || default_path
|
|
57
|
+
|
|
58
|
+
unless File.directory?(target_dir)
|
|
59
|
+
puts " #{target_dir}/AGENTS.md (skipped — directory not found)"
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
target = File.join(target_dir, "AGENTS.md")
|
|
64
|
+
|
|
65
|
+
if File.exist?(target) && !force
|
|
66
|
+
first_line = File.open(target, &:readline).chomp rescue "" # rubocop:disable Style/RescueModifier
|
|
67
|
+
unless first_line.start_with?("<!-- dexkit v")
|
|
68
|
+
puts " #{target} (skipped — not generated by dexkit, use FORCE=1 to overwrite)"
|
|
69
|
+
next
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
source = File.join(guide_dir, source_file)
|
|
74
|
+
File.write(target, "#{marker}\n\n#{File.read(source)}")
|
|
75
|
+
written += 1
|
|
76
|
+
puts " #{target} ← #{source_file}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts "\n#{written} guide(s) installed."
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/dex/registry.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
module Registry
|
|
5
|
+
def self.extended(base)
|
|
6
|
+
base.instance_variable_set(:@_registry, Set.new)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def inherited(subclass)
|
|
10
|
+
super
|
|
11
|
+
_dex_registry.add(subclass)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def registry
|
|
15
|
+
live = _dex_registry.select { |k| k.name && _dex_reachable?(k) }
|
|
16
|
+
_dex_registry.replace(live)
|
|
17
|
+
live.to_set.freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def deregister(klass)
|
|
21
|
+
_dex_registry.delete(klass)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear!
|
|
25
|
+
_dex_registry.clear
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def description(text = nil)
|
|
29
|
+
if !text.nil?
|
|
30
|
+
raise ArgumentError, "description must be a String" unless text.is_a?(String)
|
|
31
|
+
|
|
32
|
+
@_description = text
|
|
33
|
+
else
|
|
34
|
+
defined?(@_description) ? @_description : _dex_parent_description
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def _dex_reachable?(klass)
|
|
41
|
+
Object.const_get(klass.name) == klass
|
|
42
|
+
rescue NameError
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def _dex_registry
|
|
47
|
+
if instance_variable_defined?(:@_registry)
|
|
48
|
+
@_registry
|
|
49
|
+
elsif superclass.respond_to?(:_dex_registry, true)
|
|
50
|
+
superclass.send(:_dex_registry)
|
|
51
|
+
else
|
|
52
|
+
@_registry = Set.new
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def _dex_parent_description
|
|
57
|
+
return nil unless superclass.respond_to?(:description)
|
|
58
|
+
return nil if superclass.instance_variable_defined?(:@_registry)
|
|
59
|
+
|
|
60
|
+
superclass.description
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
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/tool.rb
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
module Tool
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def from(operation_class)
|
|
8
|
+
_require_ruby_llm!
|
|
9
|
+
_build_tool(operation_class)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def all
|
|
13
|
+
_require_ruby_llm!
|
|
14
|
+
Operation.registry.sort_by(&:name).map { |klass| _build_tool(klass) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def from_namespace(namespace)
|
|
18
|
+
_require_ruby_llm!
|
|
19
|
+
prefix = "#{namespace}::"
|
|
20
|
+
Operation.registry
|
|
21
|
+
.select { |op| op.name&.start_with?(prefix) }
|
|
22
|
+
.sort_by(&:name)
|
|
23
|
+
.map { |klass| _build_tool(klass) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def explain_tool
|
|
27
|
+
_require_ruby_llm!
|
|
28
|
+
_build_explain_tool
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _require_ruby_llm!
|
|
32
|
+
require "ruby_llm"
|
|
33
|
+
rescue LoadError
|
|
34
|
+
raise LoadError,
|
|
35
|
+
"Dex::Tool requires the ruby-llm gem. Add `gem 'ruby_llm'` to your Gemfile."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _build_tool(operation_class) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
39
|
+
op = operation_class
|
|
40
|
+
schema = op.contract.to_json_schema
|
|
41
|
+
tool_description = _tool_description(op)
|
|
42
|
+
|
|
43
|
+
Class.new(RubyLLM::Tool) do
|
|
44
|
+
define_method(:name) { "dex_#{op.name.gsub("::", "_").downcase}" }
|
|
45
|
+
define_method(:description) { tool_description }
|
|
46
|
+
define_method(:params_schema) { schema }
|
|
47
|
+
|
|
48
|
+
define_method(:execute) do |**params|
|
|
49
|
+
coerced = params.transform_keys(&:to_sym)
|
|
50
|
+
result = op.new(**coerced).safe.call
|
|
51
|
+
case result
|
|
52
|
+
when Dex::Operation::Ok
|
|
53
|
+
value = result.value
|
|
54
|
+
value.respond_to?(:as_json) ? value.as_json : value
|
|
55
|
+
when Dex::Operation::Err
|
|
56
|
+
{ error: result.code, message: result.message, details: result.details }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def _tool_description(op)
|
|
63
|
+
parts = []
|
|
64
|
+
desc = op.description
|
|
65
|
+
parts << desc if desc
|
|
66
|
+
parts << op.name unless desc
|
|
67
|
+
|
|
68
|
+
guards = op.contract.guards
|
|
69
|
+
if guards.any?
|
|
70
|
+
messages = guards.map { |g| g[:message] || g[:name].to_s }
|
|
71
|
+
parts << "Preconditions: #{messages.join("; ")}."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
errors = op.contract.errors
|
|
75
|
+
parts << "Errors: #{errors.join(", ")}." if errors.any?
|
|
76
|
+
|
|
77
|
+
parts.join("\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def _build_explain_tool # rubocop:disable Metrics/MethodLength
|
|
81
|
+
Class.new(RubyLLM::Tool) do
|
|
82
|
+
define_method(:name) { "dex_explain" }
|
|
83
|
+
define_method(:description) { "Check if an operation can be executed with given params, without running it." }
|
|
84
|
+
define_method(:params_schema) do
|
|
85
|
+
{
|
|
86
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
"operation" => { type: "string", description: "Operation class name (e.g. 'Order::Place')" },
|
|
90
|
+
"params" => { type: "object", description: "Params to check" }
|
|
91
|
+
},
|
|
92
|
+
required: ["operation"],
|
|
93
|
+
additionalProperties: false
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
define_method(:execute) do |operation:, params: {}|
|
|
98
|
+
op_class = Dex::Operation.registry.find { |klass| klass.name == operation }
|
|
99
|
+
return { error: "unknown_operation", message: "Operation '#{operation}' not found in registry" } unless op_class
|
|
100
|
+
|
|
101
|
+
coerced = params.transform_keys(&:to_sym)
|
|
102
|
+
info = op_class.explain(**coerced)
|
|
103
|
+
{
|
|
104
|
+
callable: info[:callable],
|
|
105
|
+
guards: info[:guards],
|
|
106
|
+
once: info[:once],
|
|
107
|
+
lock: info[:lock]
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
end.new
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private_class_method :_require_ruby_llm!, :_build_tool, :_tool_description, :_build_explain_tool
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
module TypeSerializer
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# --- Human-readable string ---
|
|
8
|
+
|
|
9
|
+
def to_string(type)
|
|
10
|
+
case type
|
|
11
|
+
when Dex::RefType
|
|
12
|
+
"Ref(#{type.model_class.name})"
|
|
13
|
+
when Literal::Types::NilableType
|
|
14
|
+
"Nilable(#{to_string(type.type)})"
|
|
15
|
+
when Literal::Types::ArrayType
|
|
16
|
+
"Array(#{to_string(type.type)})"
|
|
17
|
+
when Literal::Types::UnionType
|
|
18
|
+
_union_string(type)
|
|
19
|
+
when Literal::Types::ConstraintType
|
|
20
|
+
_constraint_string(type)
|
|
21
|
+
when Literal::Types::BooleanType
|
|
22
|
+
"Boolean"
|
|
23
|
+
when Class
|
|
24
|
+
type.name || type.to_s
|
|
25
|
+
else
|
|
26
|
+
type.inspect
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# --- JSON Schema ---
|
|
31
|
+
|
|
32
|
+
BIGDECIMAL_PATTERN = '^-?\d+\.?\d*$'
|
|
33
|
+
|
|
34
|
+
def to_json_schema(type, desc: nil)
|
|
35
|
+
schema = _type_to_schema(type)
|
|
36
|
+
schema[:description] = desc if desc
|
|
37
|
+
schema
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def _type_to_schema(type) # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity
|
|
41
|
+
case type
|
|
42
|
+
when Dex::RefType
|
|
43
|
+
{ type: "string", description: "#{type.model_class.name} ID" }
|
|
44
|
+
when Literal::Types::NilableType
|
|
45
|
+
{ oneOf: [_type_to_schema(type.type), { type: "null" }] }
|
|
46
|
+
when Literal::Types::ArrayType
|
|
47
|
+
{ type: "array", items: _type_to_schema(type.type) }
|
|
48
|
+
when Literal::Types::UnionType
|
|
49
|
+
_union_schema(type)
|
|
50
|
+
when Literal::Types::ConstraintType
|
|
51
|
+
_constraint_schema(type)
|
|
52
|
+
when Literal::Types::BooleanType
|
|
53
|
+
{ type: "boolean" }
|
|
54
|
+
when Class
|
|
55
|
+
_class_schema(type)
|
|
56
|
+
else
|
|
57
|
+
{}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def _union_string(type)
|
|
62
|
+
items = if type.primitives&.any?
|
|
63
|
+
type.primitives.map(&:inspect)
|
|
64
|
+
else
|
|
65
|
+
type.types.map { |t| to_string(t) }
|
|
66
|
+
end
|
|
67
|
+
"Union(#{items.join(", ")})"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def _constraint_string(type)
|
|
71
|
+
constraints = type.object_constraints
|
|
72
|
+
return "{}" if constraints.empty?
|
|
73
|
+
|
|
74
|
+
base = constraints.first
|
|
75
|
+
rest = constraints[1..]
|
|
76
|
+
base_str = base.is_a?(Class) ? (base.name || base.to_s) : base.inspect
|
|
77
|
+
if rest.empty?
|
|
78
|
+
base_str
|
|
79
|
+
else
|
|
80
|
+
"#{base_str}(#{rest.map(&:inspect).join(", ")})"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def _union_schema(type)
|
|
85
|
+
if type.primitives&.any?
|
|
86
|
+
{ enum: type.primitives.to_a }
|
|
87
|
+
else
|
|
88
|
+
{ oneOf: type.types.map { |t| _type_to_schema(t) } }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def _constraint_schema(type)
|
|
93
|
+
constraints = type.object_constraints
|
|
94
|
+
return {} if constraints.empty?
|
|
95
|
+
|
|
96
|
+
base = constraints.first
|
|
97
|
+
schema = base.is_a?(Class) ? _class_schema(base) : {}
|
|
98
|
+
_apply_range_constraints(schema, constraints[1..])
|
|
99
|
+
schema
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def _apply_range_constraints(schema, constraints)
|
|
103
|
+
constraints.each do |c|
|
|
104
|
+
next unless c.is_a?(Range)
|
|
105
|
+
|
|
106
|
+
schema[:minimum] = c.begin if c.begin
|
|
107
|
+
if c.end
|
|
108
|
+
schema[c.exclude_end? ? :exclusiveMaximum : :maximum] = c.end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def _class_schema(type) # rubocop:disable Metrics/MethodLength
|
|
114
|
+
case type.name
|
|
115
|
+
when "String" then { type: "string" }
|
|
116
|
+
when "Integer" then { type: "integer" }
|
|
117
|
+
when "Float" then { type: "number" }
|
|
118
|
+
when "TrueClass", "FalseClass" then { type: "boolean" }
|
|
119
|
+
when "Symbol" then { type: "string" }
|
|
120
|
+
when "Hash" then { type: "object" }
|
|
121
|
+
when "Date" then { type: "string", format: "date" }
|
|
122
|
+
when "Time" then { type: "string", format: "date-time" }
|
|
123
|
+
when "DateTime" then { type: "string", format: "date-time" }
|
|
124
|
+
when "BigDecimal" then { type: "string", pattern: BIGDECIMAL_PATTERN }
|
|
125
|
+
else {}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private_class_method :_type_to_schema, :_union_string, :_constraint_string,
|
|
130
|
+
:_union_schema, :_constraint_schema, :_apply_range_constraints, :_class_schema
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/dex/version.rb
CHANGED
data/lib/dexkit.rb
CHANGED
|
@@ -14,12 +14,16 @@ 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"
|
|
18
19
|
require_relative "dex/settings"
|
|
19
20
|
require_relative "dex/pipeline"
|
|
20
21
|
require_relative "dex/executable"
|
|
22
|
+
require_relative "dex/registry"
|
|
23
|
+
require_relative "dex/type_serializer"
|
|
21
24
|
require_relative "dex/operation"
|
|
22
25
|
require_relative "dex/event"
|
|
26
|
+
require_relative "dex/tool"
|
|
23
27
|
require_relative "dex/form"
|
|
24
28
|
require_relative "dex/query"
|
|
25
29
|
|
|
@@ -71,5 +75,22 @@ module Dex
|
|
|
71
75
|
def transaction_adapter=(adapter)
|
|
72
76
|
configuration.transaction_adapter = adapter
|
|
73
77
|
end
|
|
78
|
+
|
|
79
|
+
CONTEXT_KEY = :_dex_context
|
|
80
|
+
EMPTY_CONTEXT = {}.freeze
|
|
81
|
+
|
|
82
|
+
def context
|
|
83
|
+
Fiber[CONTEXT_KEY] || EMPTY_CONTEXT
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def with_context(**values)
|
|
87
|
+
previous = Fiber[CONTEXT_KEY]
|
|
88
|
+
Fiber[CONTEXT_KEY] = (previous || {}).merge(values)
|
|
89
|
+
yield
|
|
90
|
+
ensure
|
|
91
|
+
Fiber[CONTEXT_KEY] = previous
|
|
92
|
+
end
|
|
74
93
|
end
|
|
75
94
|
end
|
|
95
|
+
|
|
96
|
+
require_relative "dex/railtie" if defined?(Rails::Railtie)
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jacek Galanciak
|
|
@@ -166,10 +166,12 @@ 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
|
|
172
173
|
- lib/dex/event/execution_state.rb
|
|
174
|
+
- lib/dex/event/export.rb
|
|
173
175
|
- lib/dex/event/handler.rb
|
|
174
176
|
- lib/dex/event/metadata.rb
|
|
175
177
|
- lib/dex/event/processor.rb
|
|
@@ -186,8 +188,12 @@ files:
|
|
|
186
188
|
- lib/dex/operation/async_proxy.rb
|
|
187
189
|
- lib/dex/operation/async_wrapper.rb
|
|
188
190
|
- lib/dex/operation/callback_wrapper.rb
|
|
191
|
+
- lib/dex/operation/explain.rb
|
|
192
|
+
- lib/dex/operation/export.rb
|
|
193
|
+
- lib/dex/operation/guard_wrapper.rb
|
|
189
194
|
- lib/dex/operation/jobs.rb
|
|
190
195
|
- lib/dex/operation/lock_wrapper.rb
|
|
196
|
+
- lib/dex/operation/once_wrapper.rb
|
|
191
197
|
- lib/dex/operation/outcome.rb
|
|
192
198
|
- lib/dex/operation/record_backend.rb
|
|
193
199
|
- lib/dex/operation/record_wrapper.rb
|
|
@@ -202,14 +208,18 @@ files:
|
|
|
202
208
|
- lib/dex/query/backend.rb
|
|
203
209
|
- lib/dex/query/filtering.rb
|
|
204
210
|
- lib/dex/query/sorting.rb
|
|
211
|
+
- lib/dex/railtie.rb
|
|
205
212
|
- lib/dex/ref_type.rb
|
|
213
|
+
- lib/dex/registry.rb
|
|
206
214
|
- lib/dex/settings.rb
|
|
207
215
|
- lib/dex/test_helpers.rb
|
|
208
216
|
- lib/dex/test_helpers/assertions.rb
|
|
209
217
|
- lib/dex/test_helpers/execution.rb
|
|
210
218
|
- lib/dex/test_helpers/stubbing.rb
|
|
211
219
|
- lib/dex/test_log.rb
|
|
220
|
+
- lib/dex/tool.rb
|
|
212
221
|
- lib/dex/type_coercion.rb
|
|
222
|
+
- lib/dex/type_serializer.rb
|
|
213
223
|
- lib/dex/version.rb
|
|
214
224
|
- lib/dexkit.rb
|
|
215
225
|
homepage: https://dex.razorjack.net/
|