dexkit 0.10.0 → 0.11.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.
@@ -111,7 +111,7 @@ Optional declarations documenting intent and catching mistakes at runtime.
111
111
  success _Ref(User) # perform must return a User (or nil)
112
112
  ```
113
113
 
114
- **`error(*codes)`** — restricts which codes `error!`/`assert!` accept (raises `ArgumentError` on undeclared):
114
+ **`error(*codes)`** — restricts which codes `error!` accepts (raises `ArgumentError` on undeclared):
115
115
 
116
116
  ```ruby
117
117
  error :email_taken, :invalid_email
@@ -297,13 +297,6 @@ success!(user) # return value early
297
297
  success!(name: "John", age: 30) # kwargs become Hash
298
298
  ```
299
299
 
300
- **`assert!(code, &block)` / `assert!(value, code)`** — returns value if truthy, otherwise `error!(code)`:
301
-
302
- ```ruby
303
- user = assert!(:not_found) { User.find_by(id: id) }
304
- assert!(user.active?, :inactive)
305
- ```
306
-
307
300
  **Dex::Error** has `code` (Symbol), `message` (String, defaults to code.to_s), `details` (any). Pattern matching:
308
301
 
309
302
  ```ruby
@@ -317,7 +310,7 @@ rescue Dex::Error => e
317
310
  end
318
311
  ```
319
312
 
320
- **Key differences:** `error!`/`assert!` roll back transaction, skip `after` callbacks, but are still recorded (status `error`). `success!` commits, runs `after` callbacks, records normally (status `completed`).
313
+ **Key differences:** `error!` rolls back transaction, skips `after` callbacks, but is still recorded (status `error`). `success!` commits, runs `after` callbacks, records normally (status `completed`).
321
314
 
322
315
  ---
323
316
 
@@ -351,6 +344,10 @@ end
351
344
 
352
345
  `Ok`/`Err` are available inside operations without prefix. In other contexts (controllers, POROs), use `Dex::Ok`/`Dex::Err` or `include Dex::Match`.
353
346
 
347
+ **Deconstruct forms:**
348
+ - Hash: `in Dex::Ok(key:)` — destructures value's keys. `in Dex::Err(code:, message:)` — named error fields.
349
+ - Array: `in Dex::Ok[value]` — binds entire value. `in Dex::Err[error]` — binds the `Dex::Error` instance.
350
+
354
351
  ---
355
352
 
356
353
  ## Rescue Mapping
@@ -464,16 +461,82 @@ On timeout: raises `Dex::Error(code: :lock_timeout)`. Works with `.safe`.
464
461
  Enqueue as background jobs (requires ActiveJob):
465
462
 
466
463
  ```ruby
467
- CreateUser.new(email: "a@b.com", name: "Alice").async.call
468
- CreateUser.new(email: "a@b.com", name: "Alice").async(queue: "urgent").call
469
- CreateUser.new(email: "a@b.com", name: "Alice").async(in: 5.minutes).call
470
- CreateUser.new(email: "a@b.com", name: "Alice").async(at: 1.hour.from_now).call
464
+ ticket = CreateUser.new(email: "a@b.com", name: "Alice").async.call
465
+ ticket = CreateUser.new(email: "a@b.com", name: "Alice").async(queue: "urgent").call
466
+ ticket = CreateUser.new(email: "a@b.com", name: "Alice").async(in: 5.minutes).call
467
+ ticket = CreateUser.new(email: "a@b.com", name: "Alice").async(at: 1.hour.from_now).call
471
468
  ```
472
469
 
473
470
  Class-level defaults: `async queue: "mailers"`. Runtime options override.
474
471
 
475
472
  Props serialize/deserialize automatically (Date, Time, BigDecimal, Symbol, `_Ref` — all handled). Non-serializable props raise `ArgumentError` at enqueue time.
476
473
 
474
+ ### Ticket
475
+
476
+ `async.call` returns a `Dex::Operation::Ticket` with `record` (operation record if recording enabled) and `job` (the enqueued job).
477
+
478
+ ```ruby
479
+ ticket.id # record ID
480
+ ticket.operation_name # operation class name
481
+ ticket.status # "pending"/"running"/"completed"/"error"/"failed"
482
+ ticket.recorded? # true if record strategy was used
483
+ ticket.pending? # status predicates
484
+ ticket.terminal? # completed? || error? || failed?
485
+ ticket.reload # refresh from DB, returns self
486
+ ticket.to_param # id.to_s (Rails path helpers)
487
+ ticket.as_json # { "id": ..., "name": ..., "status": ..., "result"?: ..., "error"?: ... }
488
+ ```
489
+
490
+ `Ticket.from_record(record)` constructs from any operation record (for polling endpoints/dashboards).
491
+
492
+ Record-dependent methods raise `ArgumentError` when `record` is nil (direct strategy without recording).
493
+
494
+ ### Outcome Reconstruction
495
+
496
+ `ticket.outcome` reconstructs `Ok`/`Err` from the record — same types as `.safe.call`:
497
+
498
+ - `completed` → `Ok(result)` with deep-symbolized keys
499
+ - `error` → `Err(Dex::Error)` with symbolized code/details
500
+ - `failed`/`pending`/`running` → `nil`
501
+
502
+ Never raises, never reloads. Call `reload` first for fresh data.
503
+
504
+ ### wait / wait!
505
+
506
+ Speculative sync — poll until terminal or timeout:
507
+
508
+ ```ruby
509
+ # Safe mode (Ok/Err/nil)
510
+ case ticket.wait(3.seconds)
511
+ in Dex::Ok(url:)
512
+ redirect_to url
513
+ in Dex::Err(code:, message:)
514
+ flash[:error] = message
515
+ redirect_to fallback_path
516
+ else
517
+ redirect_to pending_path(ticket)
518
+ end
519
+
520
+ # Strict mode (value or exception)
521
+ result = ticket.wait!(3.seconds)
522
+ redirect_to result[:url]
523
+ ```
524
+
525
+ | | Success | Business Error | Infra Crash | Timeout |
526
+ |---|---|---|---|---|
527
+ | `call` | value | `Dex::Error` | exception | n/a |
528
+ | `safe.call` | `Ok` | `Err` | exception | n/a |
529
+ | `wait!(t)` | value | `Dex::Error` | `OperationFailed` | `Dex::Timeout` |
530
+ | `wait(t)` | `Ok` | `Err` | `OperationFailed` | `nil` |
531
+
532
+ Options: `wait(timeout, interval: 0.2)`. Interval accepts a number or callable (`->(n) { ... }`).
533
+
534
+ `Dex::OperationFailed` (inherits `StandardError`): `operation_name`, `exception_class`, `exception_message`.
535
+ `Dex::Timeout` (inherits `StandardError`): `timeout`, `ticket_id`, `operation_name`.
536
+ Neither inherits `Dex::Error` — `rescue Dex::Error` never catches them.
537
+
538
+ `safe` and `async` are non-composable — `op.safe.async` and `op.async.safe` raise `NoMethodError` with prescriptive messages.
539
+
477
540
  ---
478
541
 
479
542
  ## Recording
@@ -544,10 +607,17 @@ end
544
607
  Dex::Trace.trace_id # => "tr_..."
545
608
  Dex::Trace.current # => [{ type: :actor, ... }, { type: :operation, ... }]
546
609
  Dex::Trace.to_s # => "user:42 > Order::Place(op_2nFg7K)"
610
+ Dex.actor # => { type: "user", id: "123" } or nil
547
611
  ```
548
612
 
613
+ `Dex.actor` returns the actor hash (reconstituted to the shape you passed in), or `nil`. Use it in `perform` when you need to write actor info into domain models.
614
+
615
+ `Dex.system(name = nil)` is a convenience for background jobs: `Dex::Trace.start(actor: Dex.system("payroll")) { ... }`.
616
+
549
617
  Tracing is always on – no opt-in needed. Async operations serialize and restore the trace automatically. When recording is enabled, `trace_id`, `actor_type`, `actor_id`, and `trace` are persisted alongside the usual record fields.
550
618
 
619
+ IDs are generated by `Dex::Id` – a general-purpose Stripe-style ID generator. Use it for your own models: `Dex::Id.generate("ord_")`. Parse with `Dex::Id.parse(id)` to extract prefix, timestamp, and random components.
620
+
551
621
  ---
552
622
 
553
623
  ## Idempotency (once)
@@ -673,19 +743,6 @@ refute_err result
673
743
  refute_err result, :not_found # Ok OR different code
674
744
  ```
675
745
 
676
- ### One-Liner Assertions
677
-
678
- Call + assert in one step:
679
-
680
- ```ruby
681
- assert_operation(email: "a@b.com", name: "Alice") # Ok
682
- assert_operation(CreateUser, email: "a@b.com", name: "Alice") # explicit class
683
- assert_operation(email: "a@b.com", name: "Alice", returns: user) # check value
684
-
685
- assert_operation_error(:invalid_email, email: "bad", name: "A")
686
- assert_operation_error(CreateUser, :email_taken, email: "taken@b.com", name: "A")
687
- ```
688
-
689
746
  ### Contract Assertions
690
747
 
691
748
  ```ruby
@@ -712,7 +769,7 @@ refute_callable(:unauthorized, post: post, user: user) # specific guar
712
769
  refute_callable(PublishPost, :unauthorized, post: post, user: user)
713
770
  ```
714
771
 
715
- Guard failures on the normal `call` path produce `Dex::Error`, so `assert_operation_error` and `assert_err` also work.
772
+ Guard failures on the normal `call` path produce `Dex::Error`, so `assert_err` also works.
716
773
 
717
774
  ### Param Validation
718
775
 
@@ -738,21 +795,6 @@ assert_rolls_back(User) { CreateUser.call(email: "bad", name: "A") }
738
795
  assert_commits(User) { CreateUser.call(email: "ok@b.com", name: "A") }
739
796
  ```
740
797
 
741
- ### Batch Assertions
742
-
743
- ```ruby
744
- assert_all_succeed(params_list: [
745
- { email: "a@b.com", name: "A" },
746
- { email: "b@b.com", name: "B" }
747
- ])
748
-
749
- assert_all_fail(code: :invalid_email, params_list: [
750
- { email: "", name: "A" },
751
- { email: "no-at", name: "B" }
752
- ])
753
- # Also supports message: and details: options
754
- ```
755
-
756
798
  ### Stubbing
757
799
 
758
800
  Replace an operation within a block. Bypasses all wrappers, not recorded in TestLog:
@@ -822,19 +864,9 @@ class CreateUserTest < Minitest::Test
822
864
  assert_ok(result) { |user| assert_equal "Alice", user.name }
823
865
  end
824
866
 
825
- def test_one_liner
826
- assert_operation(email: "a@b.com", name: "Alice")
827
- end
828
-
829
867
  def test_rejects_bad_email
830
- assert_operation_error(:invalid_email, email: "bad", name: "A")
831
- end
832
-
833
- def test_batch_rejects
834
- assert_all_fail(code: :invalid_email, params_list: [
835
- { email: "", name: "A" },
836
- { email: "no-at", name: "B" }
837
- ])
868
+ result = call_operation(email: "bad", name: "A")
869
+ assert_err result, :invalid_email
838
870
  end
839
871
 
840
872
  def test_stubs_dependency
@@ -911,6 +943,8 @@ end
911
943
 
912
944
  Requires `gem 'ruby_llm'` in your Gemfile. Lazy-loaded — ruby-llm is only required when you call `Dex::Tool`.
913
945
 
946
+ See `TOOL.md` for the full Tool reference including query tools.
947
+
914
948
  ---
915
949
 
916
950
  **End of reference.**
data/guides/llm/QUERY.md CHANGED
@@ -396,3 +396,9 @@ class UserSearchTest < Minitest::Test
396
396
  end
397
397
  end
398
398
  ```
399
+
400
+ ---
401
+
402
+ ## LLM Tools
403
+
404
+ To expose a query as an LLM-callable tool, see `TOOL.md`.
@@ -0,0 +1,308 @@
1
+ # Dex::Tool — LLM Reference
2
+
3
+ Install with `rake dex:guides` or copy manually to `AGENTS.md`.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ `Dex::Tool` bridges Dex primitives to LLM tool calling via ruby-llm. It accepts Operation or Query classes and returns `RubyLLM::Tool` instances ready for `chat.with_tools(...)`.
10
+
11
+ Requires `gem "ruby_llm"` in your Gemfile. Lazy-loaded — ruby-llm is only required when you call `Dex::Tool`.
12
+
13
+ ---
14
+
15
+ ## Operation Tools
16
+
17
+ ### Creating
18
+
19
+ ```ruby
20
+ tool = Dex::Tool.from(Orders::Place) # single operation
21
+ tools = Dex::Tool.all # all registered operations
22
+ tools = Dex::Tool.from_namespace("Orders") # operations under Orders::
23
+ ```
24
+
25
+ `from` accepts no options for Operation classes. `all` and `from_namespace` return sorted arrays.
26
+
27
+ ### Tool Schema
28
+
29
+ The tool name is derived from the class name: `Orders::Place` becomes `dex_orders_place`.
30
+
31
+ The description includes:
32
+ - The operation's `description` (or class name if none)
33
+ - Guard preconditions (if any)
34
+ - Declared error codes (if any)
35
+
36
+ The params schema is the operation's `contract.to_json_schema`.
37
+
38
+ ### Execution Flow
39
+
40
+ 1. Params are symbolized
41
+ 2. Operation is instantiated with params
42
+ 3. Called via `.safe.call` (returns `Ok` or `Err`, never raises)
43
+ 4. `Ok` — returns `value.as_json` (or raw value if no `as_json`)
44
+ 5. `Err` — returns `{ error:, message:, details: }`
45
+
46
+ ### Example
47
+
48
+ ```ruby
49
+ class Orders::Place < Dex::Operation
50
+ description "Place a new order for a customer"
51
+
52
+ prop :customer_id, _Ref(Customer)
53
+ prop :product_id, _Ref(Product)
54
+ prop? :quantity, Integer, default: 1
55
+
56
+ error :out_of_stock, :invalid_quantity
57
+
58
+ guard :sufficient_stock, "Product must be in stock" do
59
+ Product.find(product_id).stock >= quantity
60
+ end
61
+
62
+ def perform
63
+ error!(:invalid_quantity) if quantity <= 0
64
+ Order.create!(customer_id: customer_id, product_id: product_id, quantity: quantity)
65
+ end
66
+ end
67
+
68
+ tool = Dex::Tool.from(Orders::Place)
69
+ chat = RubyLLM.chat.with_tools(tool)
70
+ chat.ask("Place an order for customer #12, product #42, quantity 3")
71
+ ```
72
+
73
+ ### Error Shape
74
+
75
+ When an operation returns `Err`, the tool returns:
76
+
77
+ ```ruby
78
+ { error: :out_of_stock, message: "out_of_stock", details: nil }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Explain Tool
84
+
85
+ A meta-tool that checks whether an operation can execute with given params, without running it:
86
+
87
+ ```ruby
88
+ tool = Dex::Tool.explain_tool
89
+ chat = RubyLLM.chat.with_tools(tool)
90
+ chat.ask("Can I place an order for product #42?")
91
+ ```
92
+
93
+ The LLM calls it with `{ operation: "Orders::Place", params: { product_id: 42 } }`. Returns:
94
+
95
+ ```ruby
96
+ { callable: true, guards: [{ name: :sufficient_stock, passed: true }], once: nil, lock: nil }
97
+ ```
98
+
99
+ If the operation is not in the registry: `{ error: "unknown_operation", message: "..." }`.
100
+
101
+ ---
102
+
103
+ ## Query Tools
104
+
105
+ ### Creating
106
+
107
+ ```ruby
108
+ tool = Dex::Tool.from(Product::Query,
109
+ scope: -> { Current.user.products },
110
+ serialize: ->(record) { record.as_json(only: %i[id name price stock]) })
111
+ ```
112
+
113
+ ### Required Options
114
+
115
+ Both `scope:` and `serialize:` are mandatory for Query tools.
116
+
117
+ **`scope:`** — a lambda returning the base relation. Called at execution time:
118
+
119
+ ```ruby
120
+ Dex::Tool.from(Order::Query,
121
+ scope: -> { Current.user.orders },
122
+ serialize: ->(r) { r.as_json })
123
+
124
+ Dex::Tool.from(Product::Query,
125
+ scope: -> { Product.where(active: true) },
126
+ serialize: ->(r) { r.as_json })
127
+ ```
128
+
129
+ **`serialize:`** — a lambda converting each record to a hash:
130
+
131
+ ```ruby
132
+ Dex::Tool.from(Product::Query,
133
+ scope: -> { Product.all },
134
+ serialize: ->(r) { r.as_json(only: %i[id name price]) })
135
+
136
+ Dex::Tool.from(Order::Query,
137
+ scope: -> { Current.user.orders },
138
+ serialize: ->(r) { { id: r.id, total: r.total, status: r.status } })
139
+ ```
140
+
141
+ ### Optional Restrictions
142
+
143
+ **`limit:`** — max results per page (default: 50). The LLM can request fewer but never more:
144
+
145
+ ```ruby
146
+ Dex::Tool.from(Product::Query, scope: -> { Product.all }, serialize: ->(r) { r.as_json }, limit: 25)
147
+ ```
148
+
149
+ **`only_filters:`** — allowlist of filters exposed to the LLM:
150
+
151
+ ```ruby
152
+ Dex::Tool.from(Product::Query,
153
+ scope: -> { Product.all },
154
+ serialize: ->(r) { r.as_json },
155
+ only_filters: %i[name category])
156
+ ```
157
+
158
+ **`except_filters:`** — denylist of filters hidden from the LLM (mutually exclusive with `only_filters:`):
159
+
160
+ ```ruby
161
+ Dex::Tool.from(Product::Query,
162
+ scope: -> { Product.all },
163
+ serialize: ->(r) { r.as_json },
164
+ except_filters: %i[internal_code])
165
+ ```
166
+
167
+ **`only_sorts:`** — allowlist of sort columns. Must include the query's default sort if one exists:
168
+
169
+ ```ruby
170
+ Dex::Tool.from(Product::Query,
171
+ scope: -> { Product.all },
172
+ serialize: ->(r) { r.as_json },
173
+ only_sorts: %i[name price created_at])
174
+ ```
175
+
176
+ ### Auto-Exclusions
177
+
178
+ These props are automatically excluded from the tool schema (the LLM never sees them):
179
+
180
+ - Props mapped via `context` (filled from ambient context)
181
+ - `_Ref` typed props (model references)
182
+ - Props for hidden filters (via `only_filters:` / `except_filters:`)
183
+
184
+ If an auto-excluded prop is required with no default and no context mapping, `from` raises `ArgumentError` at build time.
185
+
186
+ ### Tool Schema
187
+
188
+ The tool name is `dex_query_{class_name}` (lowercased, `::` replaced with `_`).
189
+
190
+ The description includes:
191
+ - The query's `description` (or class name)
192
+ - Available filters with type hints and enum values
193
+ - Available sorts with default indicator
194
+ - Max results per page
195
+
196
+ The params schema includes visible filter props plus:
197
+
198
+ - `sort` — enum of allowed sort values (prefix with `-` for descending; custom sorts have no `-` variant)
199
+ - `limit` — integer, max results
200
+ - `offset` — integer, skip N results (for pagination)
201
+
202
+ ### Execution Flow
203
+
204
+ 1. Extract `limit`, `offset`, `sort` from params
205
+ 2. Clamp `limit` to max (default 50); zero or negative resets to max
206
+ 3. Floor `offset` at 0
207
+ 4. Validate sort value against allowed sorts; drop invalid (falls back to query default)
208
+ 5. Custom sorts reject `-` prefix (direction is baked into the block)
209
+ 6. Strip context-mapped and excluded filter params
210
+ 7. Inject scope from `scope:` lambda
211
+ 8. Build query via `from_params` (coercion, blank stripping, validation)
212
+ 9. Resolve, count total, apply offset/limit
213
+ 10. Serialize each record via `serialize:` lambda
214
+
215
+ ### Return Shape
216
+
217
+ ```json
218
+ {
219
+ "records": [{ "id": 1, "name": "Widget", "price": 9.99 }],
220
+ "total": 142,
221
+ "limit": 50,
222
+ "offset": 0
223
+ }
224
+ ```
225
+
226
+ `total` is `nil` if the count query fails (e.g., complex GROUP BY).
227
+
228
+ ### Error Handling
229
+
230
+ Invalid params or type errors:
231
+
232
+ ```ruby
233
+ { error: "invalid_params", message: "..." }
234
+ ```
235
+
236
+ Any other error:
237
+
238
+ ```ruby
239
+ { error: "query_failed", message: "..." }
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Context
245
+
246
+ `Dex.with_context` provides ambient values to both Operation and Query tools. Props with `context` mappings are auto-filled and hidden from the LLM:
247
+
248
+ ```ruby
249
+ class Orders::Place < Dex::Operation
250
+ prop :customer_id, _Ref(Customer)
251
+ context customer_id: :current_customer_id
252
+ # ...
253
+ end
254
+
255
+ class Order::Query < Dex::Query
256
+ scope { Order.all }
257
+ prop? :customer_id, _Ref(Customer)
258
+ context customer_id: :current_customer_id
259
+ filter :customer_id
260
+ # ...
261
+ end
262
+
263
+ Dex.with_context(current_customer_id: current_user.customer_id) do
264
+ chat.ask("Show me my recent orders")
265
+ end
266
+ ```
267
+
268
+ The LLM never sees `customer_id` in either tool's schema — it is injected from context.
269
+
270
+ ---
271
+
272
+ ## Security Model
273
+
274
+ Five layers protect against misuse:
275
+
276
+ 1. **Scope lambda** — called at execution time, applies authorization (`Current.user.orders`, `policy_scope(...)`)
277
+ 2. **Context injection** — security-sensitive props (tenant, user) are filled from ambient context, invisible to the LLM
278
+ 3. **Filter restrictions** — `only_filters:` / `except_filters:` control what the LLM can search on
279
+ 4. **Sort restrictions** — `only_sorts:` limits available sort columns
280
+ 5. **Limit cap** — `limit:` sets a hard ceiling on results per page; the LLM cannot exceed it
281
+
282
+ ---
283
+
284
+ ## Combining Operation + Query Tools
285
+
286
+ ```ruby
287
+ order_tools = Dex::Tool.from_namespace("Orders")
288
+
289
+ search_tool = Dex::Tool.from(Product::Query,
290
+ scope: -> { Current.user.products },
291
+ serialize: ->(r) { r.as_json(only: %i[id name price stock]) },
292
+ limit: 20,
293
+ only_filters: %i[name category],
294
+ only_sorts: %i[name price])
295
+
296
+ explain = Dex::Tool.explain_tool
297
+
298
+ chat = RubyLLM.chat
299
+ chat.with_tools(*order_tools, search_tool, explain)
300
+
301
+ Dex.with_context(current_customer_id: current_user.customer_id) do
302
+ chat.ask("Find products under $50, then place an order for the cheapest one")
303
+ end
304
+ ```
305
+
306
+ ---
307
+
308
+ **End of reference.**
data/lib/dex/event/bus.rb CHANGED
@@ -89,15 +89,13 @@ module Dex
89
89
 
90
90
  def enqueue(handler_class, event, trace_data)
91
91
  ensure_active_job_loaded!
92
- ctx = event.context
93
92
 
94
93
  Dex::Event::Processor.perform_later(
95
94
  handler_class: handler_class.name,
96
95
  event_class: event.class.name,
97
96
  payload: event._props_as_json,
98
97
  metadata: event.metadata.as_json,
99
- trace: trace_data,
100
- context: ctx
98
+ trace: trace_data
101
99
  )
102
100
  end
103
101
 
@@ -113,8 +113,7 @@ module Dex
113
113
  timestamp: Time.parse(metadata_hash["timestamp"]),
114
114
  trace_id: metadata_hash["trace_id"],
115
115
  caused_by_id: metadata_hash["caused_by_id"],
116
- event_ancestry: metadata_hash["event_ancestry"] || [],
117
- context: metadata_hash["context"]
116
+ event_ancestry: metadata_hash["event_ancestry"] || []
118
117
  )
119
118
  instance.instance_variable_set(:@metadata, metadata)
120
119
  instance.freeze
@@ -3,15 +3,14 @@
3
3
  module Dex
4
4
  class Event
5
5
  class Metadata
6
- attr_reader :id, :timestamp, :trace_id, :caused_by_id, :event_ancestry, :context
6
+ attr_reader :id, :timestamp, :trace_id, :caused_by_id, :event_ancestry
7
7
 
8
- def initialize(id:, timestamp:, trace_id:, caused_by_id:, event_ancestry:, context:)
8
+ def initialize(id:, timestamp:, trace_id:, caused_by_id:, event_ancestry:)
9
9
  @id = id
10
10
  @timestamp = timestamp
11
11
  @trace_id = trace_id
12
12
  @caused_by_id = caused_by_id
13
13
  @event_ancestry = event_ancestry
14
- @context = context
15
14
  freeze
16
15
  end
17
16
 
@@ -26,22 +25,12 @@ module Dex
26
25
  []
27
26
  end
28
27
 
29
- ctx = if Dex.configuration.event_context
30
- begin
31
- Dex.configuration.event_context.call
32
- rescue => e
33
- Event._warn("event_context failed: #{e.message}")
34
- nil
35
- end
36
- end
37
-
38
28
  new(
39
29
  id: id,
40
30
  timestamp: Time.now.utc,
41
31
  trace_id: trace_id,
42
32
  caused_by_id: caused,
43
- event_ancestry: ancestry,
44
- context: ctx
33
+ event_ancestry: ancestry
45
34
  )
46
35
  end
47
36
 
@@ -53,7 +42,6 @@ module Dex
53
42
  "event_ancestry" => @event_ancestry
54
43
  }
55
44
  h["caused_by_id"] = @caused_by_id if @caused_by_id
56
- h["context"] = @context if @context
57
45
  h
58
46
  end
59
47
  end
@@ -7,9 +7,7 @@ module Dex
7
7
  return super unless name == :Processor && defined?(ActiveJob::Base)
8
8
 
9
9
  const_set(:Processor, Class.new(ActiveJob::Base) do
10
- def perform(handler_class:, event_class:, payload:, metadata:, trace: nil, context: nil, attempt_number: 1)
11
- restore_context(context)
12
-
10
+ def perform(handler_class:, event_class:, payload:, metadata:, trace: nil, attempt_number: 1)
13
11
  handler = Object.const_get(handler_class)
14
12
  retry_config = handler._event_handler_retry_config
15
13
 
@@ -25,7 +23,6 @@ module Dex
25
23
  payload: payload,
26
24
  metadata: metadata,
27
25
  trace: trace,
28
- context: context,
29
26
  attempt_number: attempt_number + 1
30
27
  )
31
28
  else
@@ -35,17 +32,6 @@ module Dex
35
32
 
36
33
  private
37
34
 
38
- def restore_context(context)
39
- return unless context
40
-
41
- restorer = Dex.configuration.restore_event_context
42
- return unless restorer
43
-
44
- restorer.call(context)
45
- rescue => e
46
- Dex::Event._warn("restore_event_context failed: #{e.message}")
47
- end
48
-
49
35
  def compute_delay(config, attempt)
50
36
  wait = config[:wait]
51
37
  case wait
data/lib/dex/event.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  # Modules loaded before class body (no reference to Dex::Event needed)
4
4
  require_relative "event/execution_state"
5
5
  require_relative "event/metadata"
6
- require_relative "event/trace"
7
6
  require_relative "event/suppression"
8
7
 
9
8
  module Dex
@@ -68,7 +67,6 @@ module Dex
68
67
  def trace_id = metadata.trace_id
69
68
  def caused_by_id = metadata.caused_by_id
70
69
  def event_ancestry = metadata.event_ancestry
71
- def context = metadata.context
72
70
 
73
71
  # Publishing
74
72
  def publish(sync: false)
@@ -77,7 +75,7 @@ module Dex
77
75
 
78
76
  def self.publish(sync: false, caused_by: nil, **kwargs)
79
77
  if caused_by
80
- Trace.with_event(caused_by) do
78
+ Dex::Trace.with_event_context(caused_by) do
81
79
  new(**kwargs).publish(sync: sync)
82
80
  end
83
81
  else