dexkit 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +50 -18
- data/gemfiles/mongoid_no_ar.gemfile +10 -0
- data/gemfiles/mongoid_no_ar.gemfile.lock +232 -0
- data/guides/llm/EVENT.md +41 -23
- data/guides/llm/FORM.md +202 -61
- data/guides/llm/OPERATION.md +49 -20
- data/guides/llm/QUERY.md +52 -2
- data/lib/dex/context_dsl.rb +56 -0
- data/lib/dex/context_setup.rb +2 -33
- data/lib/dex/event/bus.rb +85 -8
- data/lib/dex/event/handler.rb +18 -0
- data/lib/dex/event/metadata.rb +16 -9
- data/lib/dex/event/processor.rb +1 -1
- data/lib/dex/event/test_helpers.rb +88 -0
- data/lib/dex/event/trace.rb +14 -27
- data/lib/dex/event.rb +2 -7
- data/lib/dex/event_test_helpers.rb +1 -86
- data/lib/dex/form/context.rb +27 -0
- data/lib/dex/form/export.rb +128 -0
- data/lib/dex/form/nesting.rb +2 -0
- data/lib/dex/form/uniqueness_validator.rb +17 -1
- data/lib/dex/form.rb +119 -3
- data/lib/dex/id.rb +38 -0
- data/lib/dex/operation/async_proxy.rb +13 -2
- data/lib/dex/operation/explain.rb +11 -7
- data/lib/dex/operation/jobs.rb +5 -4
- data/lib/dex/operation/lock_wrapper.rb +15 -2
- data/lib/dex/operation/once_wrapper.rb +24 -15
- data/lib/dex/operation/record_backend.rb +15 -1
- data/lib/dex/operation/record_wrapper.rb +43 -8
- data/lib/dex/operation/test_helpers/assertions.rb +359 -0
- data/lib/dex/operation/test_helpers/execution.rb +30 -0
- data/lib/dex/operation/test_helpers/stubbing.rb +61 -0
- data/lib/dex/operation/test_helpers.rb +160 -0
- data/lib/dex/operation/trace_wrapper.rb +20 -0
- data/lib/dex/operation/transaction_adapter.rb +29 -68
- data/lib/dex/operation/transaction_wrapper.rb +10 -16
- data/lib/dex/operation.rb +2 -0
- data/lib/dex/query/backend.rb +13 -0
- data/lib/dex/query/export.rb +64 -0
- data/lib/dex/query.rb +50 -5
- data/lib/dex/ref_type.rb +4 -0
- data/lib/dex/test_helpers.rb +4 -139
- data/lib/dex/test_log.rb +62 -4
- data/lib/dex/trace.rb +291 -0
- data/lib/dex/type_coercion.rb +4 -1
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +9 -5
- metadata +16 -5
- data/lib/dex/test_helpers/assertions.rb +0 -333
- data/lib/dex/test_helpers/execution.rb +0 -28
- data/lib/dex/test_helpers/stubbing.rb +0 -59
- /data/lib/dex/{event_test_helpers → event/test_helpers}/assertions.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 493f1873cf71e1f8e2348f4f15e3e508962d9ae310b85f7b4aac8a756396de05
|
|
4
|
+
data.tar.gz: ad50fd8349a45e4c4bc536f68bb2c74ee10b59e8b80856fca2823bb9f9a998cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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**
|
|
224
|
+
**Causality tracing** – events join the unified execution trace, and child events link to their cause:
|
|
210
225
|
|
|
211
226
|
```ruby
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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,
|
|
253
|
-
validates :first_name, :last_name, presence: true
|
|
273
|
+
validates :email, uniqueness: true
|
|
254
274
|
|
|
255
275
|
nested_one :address do
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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
|
|
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,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` (
|
|
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 #
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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 #
|
|
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/
|
|
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)
|