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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +50 -18
  4. data/gemfiles/mongoid_no_ar.gemfile +10 -0
  5. data/gemfiles/mongoid_no_ar.gemfile.lock +232 -0
  6. data/guides/llm/EVENT.md +41 -23
  7. data/guides/llm/FORM.md +202 -61
  8. data/guides/llm/OPERATION.md +49 -20
  9. data/guides/llm/QUERY.md +52 -2
  10. data/lib/dex/context_dsl.rb +56 -0
  11. data/lib/dex/context_setup.rb +2 -33
  12. data/lib/dex/event/bus.rb +85 -8
  13. data/lib/dex/event/handler.rb +18 -0
  14. data/lib/dex/event/metadata.rb +16 -9
  15. data/lib/dex/event/processor.rb +1 -1
  16. data/lib/dex/event/test_helpers.rb +88 -0
  17. data/lib/dex/event/trace.rb +14 -27
  18. data/lib/dex/event.rb +2 -7
  19. data/lib/dex/event_test_helpers.rb +1 -86
  20. data/lib/dex/form/context.rb +27 -0
  21. data/lib/dex/form/export.rb +128 -0
  22. data/lib/dex/form/nesting.rb +2 -0
  23. data/lib/dex/form/uniqueness_validator.rb +17 -1
  24. data/lib/dex/form.rb +119 -3
  25. data/lib/dex/id.rb +38 -0
  26. data/lib/dex/operation/async_proxy.rb +13 -2
  27. data/lib/dex/operation/explain.rb +11 -7
  28. data/lib/dex/operation/jobs.rb +5 -4
  29. data/lib/dex/operation/lock_wrapper.rb +15 -2
  30. data/lib/dex/operation/once_wrapper.rb +24 -15
  31. data/lib/dex/operation/record_backend.rb +15 -1
  32. data/lib/dex/operation/record_wrapper.rb +43 -8
  33. data/lib/dex/operation/test_helpers/assertions.rb +359 -0
  34. data/lib/dex/operation/test_helpers/execution.rb +30 -0
  35. data/lib/dex/operation/test_helpers/stubbing.rb +61 -0
  36. data/lib/dex/operation/test_helpers.rb +160 -0
  37. data/lib/dex/operation/trace_wrapper.rb +20 -0
  38. data/lib/dex/operation/transaction_adapter.rb +29 -68
  39. data/lib/dex/operation/transaction_wrapper.rb +10 -16
  40. data/lib/dex/operation.rb +2 -0
  41. data/lib/dex/query/backend.rb +13 -0
  42. data/lib/dex/query/export.rb +64 -0
  43. data/lib/dex/query.rb +50 -5
  44. data/lib/dex/ref_type.rb +4 -0
  45. data/lib/dex/test_helpers.rb +4 -139
  46. data/lib/dex/test_log.rb +62 -4
  47. data/lib/dex/trace.rb +291 -0
  48. data/lib/dex/type_coercion.rb +4 -1
  49. data/lib/dex/version.rb +1 -1
  50. data/lib/dexkit.rb +9 -5
  51. metadata +16 -5
  52. data/lib/dex/test_helpers/assertions.rb +0 -333
  53. data/lib/dex/test_helpers/execution.rb +0 -28
  54. data/lib/dex/test_helpers/stubbing.rb +0 -59
  55. /data/lib/dex/{event_test_helpers → event/test_helpers}/assertions.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1009624f8d508e4d6b61d76c8d54f32fc04a11f445163915c027ebc1bdd30b5e
4
- data.tar.gz: 1b5a0755af0be468d67c3c5447f1322deff584364a493fb7665687d1417189b3
3
+ metadata.gz: 493f1873cf71e1f8e2348f4f15e3e508962d9ae310b85f7b4aac8a756396de05
4
+ data.tar.gz: ad50fd8349a45e4c4bc536f68bb2c74ee10b59e8b80856fca2823bb9f9a998cf
5
5
  SHA512:
6
- metadata.gz: '08552d04b1e9ddf5991f1454f9491fcabe80047d9606f0432be411f09d7b632a797435fabf55d8e9b190ddbc1a70daa5e73c19aa08c2bde88540559c3d35275e'
7
- data.tar.gz: 33d58dd5b421a1e7f41d3ab133c1800787d2d1f6c22327e7db7faf9053222d62d087c4a563105fb39075fbefc1a73f04984b45511595a0e3976eda9833de0cdf
6
+ metadata.gz: 18ca385b0d66155e35c3f08164d3b558e85f3f29a586b492d664c72c24f746e6e70c0d6692faedb348e7c849096eb0f3ddfd98a5515e2de5b5ce05084cab3d5e
7
+ data.tar.gz: 2e7c2e59443b2dff7fb3a9de4c3296ce1b71c4f870bf47d2ce503f5d5c23105a1cee5549618451741a21fb6d0c8fcb0eba53b94d4edcf8e999e1164e9eabf96f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.10.0] - 2026-03-09
4
+
5
+ ### Added
6
+
7
+ - **`field` / `field?` DSL for forms** – `field :name, :string` declares a required field with auto-presence validation; unconditional explicit presence validators deduplicate with it, while scoped/conditional validators still layer on top. `field? :notes, :string` declares an optional field (nil by default). Both support `desc:` for metadata and `default:` for defaults. Raw `attribute` remains available as an escape hatch
8
+ - **Form registry and description** – `Dex::Form` now extends `Registry`, giving forms `description`, `Dex::Form.registry`, and `deregister` – the same ecosystem as Operation, Event, and Handler
9
+ - **Form ambient context** – forms support the same `context` DSL as Operation and Event. `context :locale` auto-fills from `Dex.context` during initialization. Uses the same shared `ContextDSL` module with Form-specific injection
10
+ - **Form export** – `Form.to_h` (class-level schema), `Form.to_json_schema`, and `Dex::Form.export(format:)` for bulk export. Nested forms are recursively included in both formats, and bulk export returns top-level named forms without listing nested helper classes separately
11
+ - **Query registry, description, context, and export** – `Dex::Query` now extends `Registry` (giving `description`, `Dex::Query.registry`, `deregister`), includes `ContextSetup` (enabling `context :tenant` to auto-fill from `Dex.with_context`), and adds `Query.to_h`, `Query.to_json_schema`, `Dex::Query.export(format:)`. Query is now a full citizen alongside Operation, Event, and Form
12
+
13
+ ### Changed
14
+
15
+ - **Shared context DSL** – extracted `Dex::ContextDSL` as a shared module used by both `ContextSetup` (Operation/Event) and `Form::Context`. No behavior change for Operation or Event
16
+
17
+ ## [0.9.0] - 2026-03-09
18
+
19
+ ### Breaking
20
+
21
+ - **Unified execution tracing replaces event-only tracing** – `Dex::Trace` is a new fiber-local trace that spans operations, events, and handlers. Operations get `op_...` execution IDs, events get `ev_...` IDs (replacing UUIDs), handlers get `hd_...` IDs, and traces are correlated with `tr_...` IDs. `event.trace { }` is removed – use `caused_by:` for explicit event causality and `Dex::Trace.start(actor:)` at request/job boundaries. `Dex::Event::Trace` remains as a thin delegation layer
22
+ - **Operation record primary keys are now string IDs** – records use the operation's `op_...` execution ID as a string primary key instead of auto-increment integers. The recording schema adds `trace_id`, `actor_type`, `actor_id`, and `trace` columns. Existing tables need a migration to adopt the new schema
23
+ - **Mongoid transaction support removed** — `transaction :mongoid` and `config.transaction_adapter = :mongoid` are no longer valid. Dex no longer ships a Mongoid transaction adapter. Before: Mongoid transactions could be enabled via configuration or per-operation DSL. After: both forms raise `ArgumentError` immediately at declaration/configuration time. Mongoid-only apps continue to work — transactions are automatically disabled (no adapter detected), and `after_commit` fires immediately after success. If you need Mongoid multi-document transactions, call `Mongoid.transaction` directly inside `perform`
24
+ - **Recording backends now validate required attributes before use** — Dex no longer silently drops missing `params`, `result`, `status`, or `once` attributes from `record_class`. Before: partial ActiveRecord/Mongoid recording models could appear to work while losing status transitions, replay data, or async params. After: Dex raises `ArgumentError` naming the missing attributes required by core recording, async record jobs, or `once`. Apps using minimal recording models must add the required columns/fields or explicitly disable the features that need them
25
+
26
+ ### Added
27
+
28
+ - **`Dex::Trace` API** – `start(actor:, trace_id:)`, `.trace_id`, `.current`, `.current_id`, `.actor`, `.to_s`, `.dump`, `.restore`. Fiber-local, auto-starts when no trace is active, serializes across async job boundaries
29
+ - **Trace persistence** – operation records and event stores persist `id`, `trace_id`, `actor_type`, `actor_id`, and `trace` when the columns exist. Event metadata includes `event_ancestry` for materialized-path tree queries
30
+ - **`Dex::Id`** – Stripe-style prefixed ID generation with embedded timestamps for sortability
31
+
32
+ ### Fixed
33
+
34
+ - **Mongoid-only Rails compatibility** — Dex boots and runs cleanly in Mongoid-only Rails apps without `activerecord` loaded, with prescriptive `LoadError`s for unsupported paths such as `advisory_lock` and async event dispatch without `ActiveJob`
35
+ - **ActiveRecord transaction auto-detection is stricter** — Dex now enables the ActiveRecord transaction adapter only when an ActiveRecord connection pool actually exists. Before: merely loading `activerecord` could make Mongoid-backed operations try to open an ActiveRecord transaction and fail with `ActiveRecord::ConnectionNotDefined`. After: unconfigured ActiveRecord no longer activates transactions implicitly
36
+ - **Mongoid async/recording serialization** — `_Ref(Model)` serializes IDs via `id.as_json`, so `BSON::ObjectId` values round-trip through async operations, async events, and recording without `ActiveJob::SerializationError`. Recording and `once` sanitize untyped Mongoid document results to JSON-safe payloads
37
+ - **Mongoid query and form parity** — query adapter detection and scope merging normalize Mongoid association scopes to `Mongoid::Criteria`, uniqueness validation excludes persisted Mongoid records correctly and uses a case-insensitive regex path for `case_sensitive: false`, and `_Ref(lock: true)` fails fast for model classes that do not support `.lock`
38
+
3
39
  ## [0.8.0] - 2026-03-09
4
40
 
5
41
  ### Added
data/README.md CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  Rails patterns toolbelt. Equip to gain +4 DEX.
4
4
 
5
+ > **Active development.** dexkit is pre-1.0 and evolving rapidly. The public API may change between minor versions as the library matures.
6
+
5
7
  **[Documentation](https://dex.razorjack.net)**
6
8
 
7
9
  ## Operations
8
10
 
9
11
  Service objects with typed properties, transactions, error handling, and more.
10
12
 
13
+ Mongoid-only Rails apps work too – queries, recording, events, and forms all adapt automatically. Transactions are ActiveRecord-only (Mongoid users who need transactions can call `Mongoid.transaction` inside `perform`); `advisory_lock` is also ActiveRecord-only. Operation/event store models can be Mongoid documents; recording models must define the fields required by the enabled recording features.
14
+
11
15
  ```ruby
12
16
  class Order::Place < Dex::Operation
13
17
  prop :customer, _Ref(Customer)
@@ -69,6 +73,17 @@ end
69
73
  Order::Fulfill.new(order_id: 123).async(queue: "fulfillment").call
70
74
  ```
71
75
 
76
+ **Execution tracing** – every operation gets a prefixed ID and joins a unified trace across operations, events, and handlers:
77
+
78
+ ```ruby
79
+ Dex::Trace.start(actor: { type: :user, id: current_user.id }) do
80
+ Order::Place.call(customer: 42, product: 7, quantity: 2)
81
+ end
82
+
83
+ Dex::Trace.trace_id # => "tr_..."
84
+ Dex::Trace.current # => [{ type: :actor, ... }, { type: :operation, ... }]
85
+ ```
86
+
72
87
  **Idempotency** with `once` — run an operation at most once for a given key. Results are replayed on duplicates:
73
88
 
74
89
  ```ruby
@@ -204,13 +219,14 @@ Order::Placed.publish(order_id: 1, total: 99.99)
204
219
 
205
220
  **Zero-config pub/sub** — define events and handlers, publish. No bus setup needed.
206
221
 
207
- **Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline.
222
+ **Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline. If ActiveJob is not loaded, async publish raises `LoadError`.
208
223
 
209
- **Causality tracing** link events in chains with shared `trace_id`:
224
+ **Causality tracing** events join the unified execution trace, and child events link to their cause:
210
225
 
211
226
  ```ruby
212
- order_placed.trace do
213
- Shipment::Reserved.publish(order_id: 1)
227
+ Dex::Trace.start(actor: { type: :user, id: 42 }) do
228
+ order_placed = Order::Placed.new(order_id: 1, total: 99.99)
229
+ Shipment::Reserved.publish(order_id: 1, caused_by: order_placed)
214
230
  end
215
231
  ```
216
232
 
@@ -237,25 +253,29 @@ end
237
253
 
238
254
  ## Forms
239
255
 
240
- Form objects with typed attributes, normalization, nested forms, and Rails form builder compatibility.
256
+ Form objects with typed fields, normalization, nested forms, ambient context, JSON Schema export, and Rails form builder compatibility.
241
257
 
242
258
  ```ruby
243
259
  class Employee::Form < Dex::Form
260
+ description "Employee onboarding form"
244
261
  model Employee
245
262
 
246
- attribute :first_name, :string
247
- attribute :last_name, :string
248
- attribute :email, :string
263
+ field :first_name, :string
264
+ field :last_name, :string
265
+ field :email, :string
266
+ field :locale, :string
267
+ field? :notes, :string
268
+
269
+ context :locale
249
270
 
250
271
  normalizes :email, with: -> { _1&.strip&.downcase.presence }
251
272
 
252
- validates :email, presence: true, uniqueness: true
253
- validates :first_name, :last_name, presence: true
273
+ validates :email, uniqueness: true
254
274
 
255
275
  nested_one :address do
256
- attribute :street, :string
257
- attribute :city, :string
258
- validates :street, :city, presence: true
276
+ field :street, :string
277
+ field :city, :string
278
+ field? :apartment, :string
259
279
  end
260
280
  end
261
281
 
@@ -266,18 +286,21 @@ form.valid?
266
286
 
267
287
  ### What you get out of the box
268
288
 
269
- **ActiveModel attributes** with type casting, normalization, and full Rails validation DSL.
289
+ **`field` / `field?`** — required and optional fields with auto-presence validation, `desc:` metadata, and defaults. Backed by ActiveModel attributes with type casting and normalization. Unconditional `validates :attr, presence: true` deduplicates with `field`; scoped validations still layer on top.
270
290
 
271
291
  **Nested forms** — `nested_one` and `nested_many` with automatic Hash coercion, `_destroy` support, and error propagation:
272
292
 
273
293
  ```ruby
274
294
  nested_many :emergency_contacts do
275
- attribute :name, :string
276
- attribute :phone, :string
277
- validates :name, :phone, presence: true
295
+ field :name, :string
296
+ field :phone, :string
278
297
  end
279
298
  ```
280
299
 
300
+ **Ambient context** — auto-fill fields from `Dex.context`, same DSL as Operation and Event.
301
+
302
+ **Registry & Export** — `description`, `to_json_schema`, class-level `to_h`, and `Dex::Form.export` for schema introspection. Nested form schemas recurse in both export formats, and bulk export includes only top-level named forms.
303
+
281
304
  **Rails form compatibility** — works with `form_with`, `fields_for`, and nested attributes out of the box.
282
305
 
283
306
  **Uniqueness validation** against the database, with scope, case-sensitivity, and current-record exclusion.
@@ -297,15 +320,20 @@ end
297
320
 
298
321
  ## Queries
299
322
 
300
- Declarative query objects for filtering and sorting ActiveRecord relations and Mongoid criteria.
323
+ Declarative query objects for filtering and sorting ActiveRecord and Mongoid scopes.
301
324
 
302
325
  ```ruby
303
326
  class Order::Query < Dex::Query
327
+ description "Search orders"
328
+
304
329
  scope { Order.all }
305
330
 
306
331
  prop? :status, String
307
332
  prop? :customer, _Ref(Customer)
308
333
  prop? :total_min, Integer
334
+ prop? :tenant, String
335
+
336
+ context tenant: :current_tenant
309
337
 
310
338
  filter :status
311
339
  filter :customer
@@ -319,6 +347,10 @@ orders = Order::Query.call(status: "pending", sort: "-total")
319
347
 
320
348
  ### What you get out of the box
321
349
 
350
+ **Registry, description, and context** — same ecosystem as Operation, Event, and Form. `Dex::Query.registry` discovers all query classes, `description` documents intent, and `context` auto-fills props from `Dex.with_context`.
351
+
352
+ **Export** — `Query.to_h`, `Query.to_json_schema`, `Dex::Query.export(format:)` for introspection and bulk export.
353
+
322
354
  **11 built-in filter strategies** — `:eq`, `:not_eq`, `:contains`, `:starts_with`, `:ends_with`, `:gt`, `:gte`, `:lt`, `:lte`, `:in`, `:not_in`. Custom blocks for complex logic.
323
355
 
324
356
  **Sorting** with ascending/descending column sorts, custom sort blocks, and defaults.
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "dexkit", path: ".."
4
+
5
+ gem "activejob", ">= 6.1"
6
+ gem "actionpack", ">= 6.1"
7
+ gem "activesupport", ">= 6.1"
8
+ gem "mongoid", ">= 8.0"
9
+ gem "ostruct"
10
+ gem "railties", ">= 6.1"
@@ -0,0 +1,232 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ dexkit (0.10.0)
5
+ activemodel (>= 6.1)
6
+ literal (~> 1.9)
7
+ zeitwerk (~> 2.6)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (8.1.2)
13
+ actionview (= 8.1.2)
14
+ activesupport (= 8.1.2)
15
+ nokogiri (>= 1.8.5)
16
+ rack (>= 2.2.4)
17
+ rack-session (>= 1.0.1)
18
+ rack-test (>= 0.6.3)
19
+ rails-dom-testing (~> 2.2)
20
+ rails-html-sanitizer (~> 1.6)
21
+ useragent (~> 0.16)
22
+ actionview (8.1.2)
23
+ activesupport (= 8.1.2)
24
+ builder (~> 3.1)
25
+ erubi (~> 1.11)
26
+ rails-dom-testing (~> 2.2)
27
+ rails-html-sanitizer (~> 1.6)
28
+ activejob (8.1.2)
29
+ activesupport (= 8.1.2)
30
+ globalid (>= 0.3.6)
31
+ activemodel (8.1.2)
32
+ activesupport (= 8.1.2)
33
+ activesupport (8.1.2)
34
+ base64
35
+ bigdecimal
36
+ concurrent-ruby (~> 1.0, >= 1.3.1)
37
+ connection_pool (>= 2.2.5)
38
+ drb
39
+ i18n (>= 1.6, < 2)
40
+ json
41
+ logger (>= 1.4.2)
42
+ minitest (>= 5.1)
43
+ securerandom (>= 0.3)
44
+ tzinfo (~> 2.0, >= 2.0.5)
45
+ uri (>= 0.13.1)
46
+ base64 (0.3.0)
47
+ bigdecimal (4.0.1)
48
+ bson (5.2.0)
49
+ builder (3.3.0)
50
+ concurrent-ruby (1.3.6)
51
+ connection_pool (3.0.2)
52
+ crass (1.0.6)
53
+ date (3.5.1)
54
+ drb (2.2.3)
55
+ erb (6.0.2)
56
+ erubi (1.13.1)
57
+ globalid (1.3.0)
58
+ activesupport (>= 6.1)
59
+ i18n (1.14.8)
60
+ concurrent-ruby (~> 1.0)
61
+ io-console (0.8.2)
62
+ irb (1.17.0)
63
+ pp (>= 0.6.0)
64
+ prism (>= 1.3.0)
65
+ rdoc (>= 4.0.0)
66
+ reline (>= 0.4.2)
67
+ json (2.19.1)
68
+ literal (1.9.0)
69
+ zeitwerk
70
+ logger (1.7.0)
71
+ loofah (2.25.0)
72
+ crass (~> 1.0.2)
73
+ nokogiri (>= 1.12.0)
74
+ minitest (6.0.2)
75
+ drb (~> 2.0)
76
+ prism (~> 1.5)
77
+ mongo (2.23.0)
78
+ base64
79
+ bson (>= 4.14.1, < 6.0.0)
80
+ mongoid (9.0.10)
81
+ activemodel (>= 5.1, < 8.2, != 7.0.0)
82
+ concurrent-ruby (>= 1.0.5, < 2.0)
83
+ mongo (>= 2.18.0, < 3.0.0)
84
+ nokogiri (1.19.1-aarch64-linux-gnu)
85
+ racc (~> 1.4)
86
+ nokogiri (1.19.1-aarch64-linux-musl)
87
+ racc (~> 1.4)
88
+ nokogiri (1.19.1-arm-linux-gnu)
89
+ racc (~> 1.4)
90
+ nokogiri (1.19.1-arm-linux-musl)
91
+ racc (~> 1.4)
92
+ nokogiri (1.19.1-arm64-darwin)
93
+ racc (~> 1.4)
94
+ nokogiri (1.19.1-x86_64-darwin)
95
+ racc (~> 1.4)
96
+ nokogiri (1.19.1-x86_64-linux-gnu)
97
+ racc (~> 1.4)
98
+ nokogiri (1.19.1-x86_64-linux-musl)
99
+ racc (~> 1.4)
100
+ ostruct (0.6.3)
101
+ pp (0.6.3)
102
+ prettyprint
103
+ prettyprint (0.2.0)
104
+ prism (1.9.0)
105
+ psych (5.3.1)
106
+ date
107
+ stringio
108
+ racc (1.8.1)
109
+ rack (3.2.5)
110
+ rack-session (2.1.1)
111
+ base64 (>= 0.1.0)
112
+ rack (>= 3.0.0)
113
+ rack-test (2.2.0)
114
+ rack (>= 1.3)
115
+ rackup (2.3.1)
116
+ rack (>= 3)
117
+ rails-dom-testing (2.3.0)
118
+ activesupport (>= 5.0.0)
119
+ minitest
120
+ nokogiri (>= 1.6)
121
+ rails-html-sanitizer (1.7.0)
122
+ loofah (~> 2.25)
123
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
124
+ railties (8.1.2)
125
+ actionpack (= 8.1.2)
126
+ activesupport (= 8.1.2)
127
+ irb (~> 1.13)
128
+ rackup (>= 1.0.0)
129
+ rake (>= 12.2)
130
+ thor (~> 1.0, >= 1.2.2)
131
+ tsort (>= 0.2)
132
+ zeitwerk (~> 2.6)
133
+ rake (13.3.1)
134
+ rdoc (7.2.0)
135
+ erb
136
+ psych (>= 4.0.0)
137
+ tsort
138
+ reline (0.6.3)
139
+ io-console (~> 0.5)
140
+ securerandom (0.4.1)
141
+ stringio (3.2.0)
142
+ thor (1.5.0)
143
+ tsort (0.2.0)
144
+ tzinfo (2.0.6)
145
+ concurrent-ruby (~> 1.0)
146
+ uri (1.1.1)
147
+ useragent (0.16.11)
148
+ zeitwerk (2.7.5)
149
+
150
+ PLATFORMS
151
+ aarch64-linux-gnu
152
+ aarch64-linux-musl
153
+ arm-linux-gnu
154
+ arm-linux-musl
155
+ arm64-darwin
156
+ x86_64-darwin
157
+ x86_64-linux-gnu
158
+ x86_64-linux-musl
159
+
160
+ DEPENDENCIES
161
+ actionpack (>= 6.1)
162
+ activejob (>= 6.1)
163
+ activesupport (>= 6.1)
164
+ dexkit!
165
+ mongoid (>= 8.0)
166
+ ostruct
167
+ railties (>= 6.1)
168
+
169
+ CHECKSUMS
170
+ actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423
171
+ actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b
172
+ activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
173
+ activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e
174
+ activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
175
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
176
+ bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
177
+ bson (5.2.0) sha256=c468c1e8a3cfa1e80531cc519a890f85586986721d8e305f83465cc36bb82608
178
+ builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
179
+ concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
180
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
181
+ crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
182
+ date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
183
+ dexkit (0.10.0)
184
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
185
+ erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b
186
+ erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
187
+ globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
188
+ i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
189
+ io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
190
+ irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
191
+ json (2.19.1) sha256=dd94fdc59e48bff85913829a32350b3148156bc4fd2a95a2568a78b11344082d
192
+ literal (1.9.0) sha256=b1dfac91931e71e1c4ebfddd4b459306f2973e9f749e077a647fece6ea15414a
193
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
194
+ loofah (2.25.0) sha256=df5ed7ac3bac6a4ec802df3877ee5cc86d027299f8952e6243b3dac446b060e6
195
+ minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
196
+ mongo (2.23.0) sha256=be2fe4cc6f7119fa6b79e82a1963b2406856b4dc92d0ccfb74db543897be3109
197
+ mongoid (9.0.10) sha256=351192e70027276748f3c946b8926fad9254356e36021dacb5cec08d0740f21d
198
+ nokogiri (1.19.1-aarch64-linux-gnu) sha256=cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32
199
+ nokogiri (1.19.1-aarch64-linux-musl) sha256=1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5
200
+ nokogiri (1.19.1-arm-linux-gnu) sha256=0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3
201
+ nokogiri (1.19.1-arm-linux-musl) sha256=3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d
202
+ nokogiri (1.19.1-arm64-darwin) sha256=dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e
203
+ nokogiri (1.19.1-x86_64-darwin) sha256=7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf
204
+ nokogiri (1.19.1-x86_64-linux-gnu) sha256=1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a
205
+ nokogiri (1.19.1-x86_64-linux-musl) sha256=4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23
206
+ ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
207
+ pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
208
+ prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
209
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
210
+ psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
211
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
212
+ rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
213
+ rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
214
+ rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
215
+ rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
216
+ rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
217
+ rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
218
+ railties (8.1.2) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055
219
+ rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
220
+ rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
221
+ reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
222
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
223
+ stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
224
+ thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
225
+ tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
226
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
227
+ uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
228
+ useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
229
+ zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
230
+
231
+ BUNDLED WITH
232
+ 4.0.4
data/guides/llm/EVENT.md CHANGED
@@ -30,9 +30,9 @@ class UserCreated < Dex::Event
30
30
  end
31
31
  ```
32
32
 
33
- Reserved names: `id`, `timestamp`, `trace_id`, `caused_by_id`, `caused_by`, `context`, `publish`, `metadata`, `sync`.
33
+ Reserved names: `id`, `timestamp`, `trace_id`, `caused_by_id`, `caused_by`, `event_ancestry`, `context`, `publish`, `metadata`, `sync`.
34
34
 
35
- Events are frozen after creation. Each gets auto-generated `id` (UUID), `timestamp` (UTC), `trace_id`, and optional `caused_by_id`.
35
+ Events are frozen after creation. Each gets auto-generated `id` (`ev_...`), `timestamp` (UTC), `trace_id` (`tr_...` by default), optional `caused_by_id`, and `event_ancestry` (ordered ancestor event IDs).
36
36
 
37
37
  ### Literal Types Cheatsheet
38
38
 
@@ -56,7 +56,7 @@ event.publish(sync: true) # sync
56
56
  OrderPlaced.publish(order_id: 1, total: 99.99, caused_by: parent_event)
57
57
  ```
58
58
 
59
- **Async** (default): handlers dispatched via ActiveJob. **Sync**: handlers called inline.
59
+ **Async** (default): handlers dispatched via ActiveJob. If ActiveJob is not loaded, `publish(sync: false)` raises `LoadError`. **Sync**: handlers called inline.
60
60
 
61
61
  ---
62
62
 
@@ -72,10 +72,11 @@ class NotifyWarehouse < Dex::Event::Handler
72
72
  def perform
73
73
  event # accessor — the event instance
74
74
  event.order_id # typed props
75
- event.id # UUID
75
+ event.id # prefixed event ID (ev_...)
76
76
  event.timestamp # Time (UTC)
77
77
  event.caused_by_id # parent event ID (if traced)
78
- event.trace_id # shared trace ID across causal chain
78
+ event.trace_id # shared trace / correlation ID
79
+ event.event_ancestry # ordered ancestor event IDs
79
80
  end
80
81
  end
81
82
  ```
@@ -109,7 +110,7 @@ class ProcessPayment < Dex::Event::Handler
109
110
  end
110
111
  ```
111
112
 
112
- When retries exhausted, exception propagates normally.
113
+ When retries exhausted, exception propagates normally. Async handlers and retries require ActiveJob to be loaded.
113
114
 
114
115
  ### Callbacks
115
116
 
@@ -157,7 +158,7 @@ class FulfillOrder < Dex::Event::Handler
157
158
  end
158
159
  ```
159
160
 
160
- Transactions are **disabled by default** on handlers (unlike operations). Opt in with `transaction`. The `after_commit` block defers until the transaction commits; on exception, deferred blocks are discarded.
161
+ Transactions are **disabled by default** on handlers (unlike operations). Opt in with `transaction`. The `after_commit` block defers until the transaction commits; on exception, deferred blocks are discarded. Transactions are ActiveRecord-only – in Mongoid-only apps, `after_commit` fires immediately after handler success.
161
162
 
162
163
  ### Custom Pipeline
163
164
 
@@ -179,22 +180,20 @@ Default handler pipeline: `[:transaction, :callback]`.
179
180
 
180
181
  ## Tracing (Causality)
181
182
 
182
- Link events in a causal chain. All events in a chain share the same `trace_id`.
183
+ Events participate in the unified `Dex::Trace` used by operations and handlers. All events in a trace share the same `trace_id`.
183
184
 
184
185
  ```ruby
185
- order_placed = OrderPlaced.new(order_id: 1, total: 99.99)
186
-
187
- # Option 1: trace block
188
- order_placed.trace do
189
- InventoryReserved.publish(order_id: 1) # caused_by_id = order_placed.id
190
- ShippingRequested.publish(order_id: 1) # same trace_id
186
+ # Start a trace at the request/job boundary
187
+ Dex::Trace.start(actor: { type: :user, id: current_user.id }) do
188
+ OrderPlaced.publish(order_id: 1, total: 99.99)
191
189
  end
192
190
 
193
- # Option 2: caused_by keyword
191
+ # Explicit event-to-event causality
192
+ order_placed = OrderPlaced.new(order_id: 1, total: 99.99)
194
193
  InventoryReserved.publish(order_id: 1, caused_by: order_placed)
195
194
  ```
196
195
 
197
- Nesting works each child gets the nearest parent's `id` as `caused_by_id`, and the root's `trace_id`.
196
+ When an event is published inside a handler, the handler's event becomes the cause automatically. Explicit `caused_by:` sets `caused_by_id` and appends to `event_ancestry`.
198
197
 
199
198
  ---
200
199
 
@@ -218,12 +217,16 @@ Store events to database when configured:
218
217
 
219
218
  ```ruby
220
219
  Dex.configure do |c|
221
- c.event_store = EventRecord # any model with create!(event_type:, payload:, metadata:)
220
+ c.event_store = EventRecord # Dex passes trace fields when the model supports them
222
221
  end
223
222
  ```
224
223
 
225
224
  ```ruby
226
- create_table :event_records do |t|
225
+ create_table :event_records, id: :string do |t|
226
+ t.string :trace_id
227
+ t.string :actor_type
228
+ t.string :actor_id
229
+ t.jsonb :trace
227
230
  t.string :event_type
228
231
  t.jsonb :payload
229
232
  t.jsonb :metadata
@@ -231,6 +234,24 @@ create_table :event_records do |t|
231
234
  end
232
235
  ```
233
236
 
237
+ Mongoid stores work too:
238
+
239
+ ```ruby
240
+ class EventRecord
241
+ include Mongoid::Document
242
+ include Mongoid::Timestamps
243
+
244
+ field :_id, type: String
245
+ field :trace_id, type: String
246
+ field :actor_type, type: String
247
+ field :actor_id, type: String
248
+ field :trace, type: Array
249
+ field :event_type, type: String
250
+ field :payload, type: Hash
251
+ field :metadata, type: Hash
252
+ end
253
+ ```
254
+
234
255
  Persistence failures are silently rescued — they never halt event publishing.
235
256
 
236
257
  ---
@@ -294,7 +315,7 @@ Everything works without configuration. All three settings are optional.
294
315
 
295
316
  ```ruby
296
317
  # test/test_helper.rb
297
- require "dex/event_test_helpers"
318
+ require "dex/event/test_helpers"
298
319
 
299
320
  class Minitest::Test
300
321
  include Dex::Event::TestHelpers
@@ -368,10 +389,7 @@ class CreateOrderTest < Minitest::Test
368
389
  def test_trace_chain
369
390
  capture_events do
370
391
  parent = OrderPlaced.new(order_id: 1, total: 99.99)
371
-
372
- parent.trace do
373
- InventoryReserved.publish(order_id: 1)
374
- end
392
+ InventoryReserved.publish(order_id: 1, caused_by: parent)
375
393
 
376
394
  child = _dex_published_events.last
377
395
  assert_event_trace(parent, child)