dexkit 0.7.0 → 0.9.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +40 -7
  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 +60 -5
  7. data/guides/llm/FORM.md +3 -3
  8. data/guides/llm/OPERATION.md +127 -18
  9. data/guides/llm/QUERY.md +3 -3
  10. data/lib/dex/event/bus.rb +7 -0
  11. data/lib/dex/event/export.rb +56 -0
  12. data/lib/dex/event/handler.rb +33 -0
  13. data/lib/dex/event/test_helpers.rb +88 -0
  14. data/lib/dex/event.rb +27 -0
  15. data/lib/dex/event_test_helpers.rb +1 -86
  16. data/lib/dex/form/uniqueness_validator.rb +17 -1
  17. data/lib/dex/operation/async_proxy.rb +1 -0
  18. data/lib/dex/operation/explain.rb +208 -0
  19. data/lib/dex/operation/export.rb +144 -0
  20. data/lib/dex/operation/guard_wrapper.rb +15 -4
  21. data/lib/dex/operation/lock_wrapper.rb +15 -2
  22. data/lib/dex/operation/once_wrapper.rb +23 -15
  23. data/lib/dex/operation/record_backend.rb +25 -0
  24. data/lib/dex/operation/record_wrapper.rb +29 -4
  25. data/lib/dex/operation/test_helpers/assertions.rb +335 -0
  26. data/lib/dex/operation/test_helpers/execution.rb +30 -0
  27. data/lib/dex/operation/test_helpers/stubbing.rb +61 -0
  28. data/lib/dex/operation/test_helpers.rb +150 -0
  29. data/lib/dex/operation/transaction_adapter.rb +29 -68
  30. data/lib/dex/operation/transaction_wrapper.rb +10 -16
  31. data/lib/dex/operation.rb +46 -2
  32. data/lib/dex/props_setup.rb +25 -2
  33. data/lib/dex/query/backend.rb +13 -0
  34. data/lib/dex/query.rb +9 -5
  35. data/lib/dex/railtie.rb +84 -0
  36. data/lib/dex/ref_type.rb +4 -0
  37. data/lib/dex/registry.rb +63 -0
  38. data/lib/dex/test_helpers.rb +4 -139
  39. data/lib/dex/tool.rb +115 -0
  40. data/lib/dex/type_coercion.rb +4 -1
  41. data/lib/dex/type_serializer.rb +132 -0
  42. data/lib/dex/version.rb +1 -1
  43. data/lib/dexkit.rb +11 -5
  44. metadata +16 -5
  45. data/lib/dex/test_helpers/assertions.rb +0 -333
  46. data/lib/dex/test_helpers/execution.rb +0 -28
  47. data/lib/dex/test_helpers/stubbing.rb +0 -59
  48. /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: 34e5457c2e95efc96b2350babe7d7059854caa42ce6da92087e003f76acfb6d8
4
- data.tar.gz: cf0466311827f2706fe883167b88d27a2571029c029777a1a5f37e0f44ae080a
3
+ metadata.gz: 41c8e4455fb4a4cca73b1d12da366b53bdbbada2cbae773917a12d344a150dd4
4
+ data.tar.gz: 86c4e76004df8b968c094b7b252a988eafe2f7aee035505ddb7bbeae1f25e04a
5
5
  SHA512:
6
- metadata.gz: 3885a4464112b937aa88bd2661ad03aca35af5128ba9b292c7345fccd69bbd8c53a363d5f1805c0c2173c097ff06f8f98ec7fc657115fd530b84a910af9b5759
7
- data.tar.gz: 25ce509ae466f259b102b4eedb8d828eb27435d6223bd052c87682b60b43584a6ccaf223ed0bec3f2141988fadebaac4638a204c4025554870e70c389ac9d4e2
6
+ metadata.gz: 89a9f6075f3955300dbe3944a9adc9b80ab56c3b4dc60d9458ea370f68c5c83ab5b3f2daad26c6f852d6f423a498ccf068914853e9866c05442765539ac6ac91
7
+ data.tar.gz: cd541ee35700cf9aa877b3630d66f0b9580263d0fddc7f381200afe32c638f832d941112387f646fa59500c26bb552461ad5bf0f0ec79dcf7bb97d76ebcbfb86
data/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.9.0] - 2026-03-09
4
+
5
+ ### Breaking
6
+
7
+ - **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`
8
+ - **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
9
+
10
+ ### Fixed
11
+
12
+ - **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`
13
+ - **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
14
+ - **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
15
+ - **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`
16
+
17
+ ## [0.8.0] - 2026-03-09
18
+
19
+ ### Added
20
+
21
+ - **Registry** — `Dex::Operation.registry`, `Dex::Event.registry`, and `Dex::Event::Handler.registry` return frozen Sets of all named subclasses. Populated automatically via `inherited`; anonymous and stale (unreachable after code reload) classes are excluded. `deregister(klass)` removes entries. `clear!` empties the registry. Zeitwerk-compatible — registries reflect loaded classes; eager-load to get the full list
22
+ - **Description & prop descriptions** — `description "text"` class-level DSL for operations and events. `desc:` keyword on `prop`/`prop?` for per-property descriptions (validated as String). Both appear in `contract.to_h`, `to_json_schema`, and `explain` output. Optional — no error or warning when omitted
23
+ - **`contract.to_h` export** — serializes the full operation contract to a plain Ruby Hash: `name`, `description`, `params` (with typed strings and `desc`), `success`, `errors`, `guards`, `context`, `pipeline`, `settings`. Types are human-readable strings (`"String"`, `"Integer(1..)"`, `"Ref(Product)"`, `"Nilable(String)"`). Omits nil/empty fields
24
+ - **`contract.to_json_schema` export** — generates JSON Schema (Draft 2020-12) from the operation contract. Default section is `:params` (input schema for LLM tools, form generation, API validation). Also supports `:success`, `:errors`, and `:full` sections
25
+ - **Event export** — `Event.to_h` and `Event.to_json_schema` class methods for serializing event definitions. Same type serialization as operations
26
+ - **Handler export** — `Handler.to_h` returns name, events (array), retries, transaction, and pipeline metadata. `handled_events` returns all subscribed event classes
27
+ - **Bulk export** — `Dex::Operation.export(format: :hash|:json_schema)`, `Dex::Event.export(format: :hash|:json_schema)`, `Dex::Event::Handler.export(format: :hash)`. Returns arrays sorted by name — directly serializable with `JSON.generate`
28
+ - **`Dex::Tool` — ruby-llm integration** — bridges dexkit operations to [ruby-llm](https://rubyllm.com/) tools. `Dex::Tool.from(Op)` generates a `RubyLLM::Tool` from an operation's contract. `Dex::Tool.all` converts all registered operations. `Dex::Tool.from_namespace("Order")` filters by namespace. `Dex::Tool.explain_tool` provides a built-in preflight check tool. Lazy-loaded — ruby-llm is only required when you call `Dex::Tool`
29
+ - **`Dex::TypeSerializer`** — converts Literal types to human-readable strings and JSON Schema. Handles `String`, `Integer`, `Float`, `Boolean`, `Symbol`, `Hash`, `Date`, `Time`, `DateTime`, `BigDecimal`, `_Nilable`, `_Array`, `_Union`, `_Ref`, and range-constrained types (`_Integer(1..)`)
30
+ - **Rake task `dex:export`** — `rake dex:export` with `FORMAT=hash|json_schema`, `SECTION=operations|events|handlers`, `FILE=path` environment variables. Auto-loaded via Railtie in Rails apps
31
+ - **Rake task `dex:guides`** — `rake dex:guides` installs LLM-optimized guides as `AGENTS.md` files in app directories (`app/operations/`, `app/events/`, `app/event_handlers/`, `app/forms/`, `app/queries/`). Only writes to directories that exist. Stamps each file with the installed dexkit version. The event guide is installed to both `app/events/` and `app/event_handlers/` when either exists. Existing hand-written `AGENTS.md` files are detected and skipped (`FORCE=1` to overwrite). Override paths with `OPERATIONS_PATH`, `EVENTS_PATH`, `EVENT_HANDLERS_PATH`, `FORMS_PATH`, `QUERIES_PATH` environment variables
32
+ - **`explain` includes `description`** — `explain` output now contains `:description` when set on the operation
33
+ - **`explain` class method for operations** — `MyOp.explain(**kwargs)` returns a frozen Hash with the full preflight state: resolved props, context source tracking (`:explicit`/`:ambient`/`:default`), per-guard pass/fail results with messages, once key and status (`:fresh`/`:exists`/`:expired`/`:pending`/`:invalid`/`:misconfigured`/`:unavailable`), advisory lock key, record/transaction/rescue/callback settings, pipeline steps, and overall `callable` verdict (accounts for both guard failures and once blocking statuses). No side effects — `perform` is never called. Gracefully handles invalid props — returns partial results with `error` key instead of raising, class-level information always available. Respects pipeline customization — removed steps report inactive. Custom middleware can contribute via `_name_explain` class methods
34
+
35
+ ### Breaking
36
+
37
+ - **`contract.to_h` returns rich format** — `contract.to_h` now returns a comprehensive serialized Hash with string-typed params, description, context, pipeline, and settings instead of the raw `Data#to_h` shape. Before: `contract.to_h[:success]` returned `String` (the class). After: it returns `"String"` (a string). Code doing type comparisons like `contract.to_h[:success] == String` must update to use `contract.success` (which still returns raw types) or compare against `"String"`. The raw Ruby types remain accessible via `contract.params`, `contract.success`, `contract.errors`, `contract.guards`
38
+ - **`_Ref` JSON Schema type changed from `"integer"` to `"string"`** — `_Ref(Model)` now serializes as `{ type: "string" }` in JSON Schema. IDs are treated as opaque strings to support Mongoid BSON::ObjectId, UUIDs, and other non-integer primary key formats. Code that relied on `type: "integer"` for Ref params must update
39
+
40
+ ### Fixed
41
+
42
+ - **`Handler.deregister` now unsubscribes from Bus** — `Dex::Event::Handler.deregister(klass)` removes the handler from both the registry and the event Bus. Previously, deregistered handlers remained subscribed and would still fire on published events
43
+ - **Registry prunes stale entries** — `registry` now removes unreachable class references from the backing Set during each call, preventing memory leaks from code reload cycles
44
+ - **`description(false)` and `desc: false` now raise `ArgumentError`** — previously accepted as "missing" values due to falsey evaluation. Both DSL methods now validate with `!text.nil?` / `!desc.nil?` to enforce the String requirement, matching the library's fail-fast convention
45
+ - **`prop_descriptions` no longer leaks parent descriptions for redeclared props** — when a child class redefines a prop without `desc:`, the parent's description is cleared instead of being inherited. Providing a new `desc:` on the child works as before
46
+ - **Rake task validates handler format** — `rake dex:export SECTION=handlers FORMAT=json_schema` now raises a clear error instead of hitting `Handler.export`'s `ArgumentError`
47
+
3
48
  ## [0.7.0] - 2026-03-08
4
49
 
5
50
  ### Breaking
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)
@@ -132,6 +136,30 @@ end
132
136
  Order::Place.call(product: product, customer: customer)
133
137
  ```
134
138
 
139
+ **Explain** – full preflight check in one call. Context, guards, idempotency, locks, settings – everything the operation would do, without doing it:
140
+
141
+ ```ruby
142
+ info = Order::Place.explain(product: product, customer: customer, quantity: 2)
143
+ info[:callable] # => true (all guards pass)
144
+ info[:once][:status] # => :fresh (would execute, not replay)
145
+ info[:context][:source] # => { customer: :ambient }
146
+ ```
147
+
148
+ **Registry & Export** — list all operations, export contracts as JSON or JSON Schema, and bridge to LLM function-calling via [ruby-llm](https://rubyllm.com/):
149
+
150
+ ```ruby
151
+ # List all operations
152
+ Dex::Operation.registry # => #<Set: {Order::Place, Order::Cancel, ...}>
153
+
154
+ # Export contracts
155
+ Dex::Operation.export(format: :json_schema)
156
+
157
+ # LLM tools (requires ruby-llm gem)
158
+ chat = RubyLLM.chat
159
+ chat.with_tools(*Dex::Tool.all)
160
+ chat.ask("Place an order for 2 units of product #42")
161
+ ```
162
+
135
163
  **Transactions** on by default, **advisory locking**, **recording** to database, **callbacks**, and a customizable **pipeline** – all composable, all optional.
136
164
 
137
165
  ### Testing
@@ -180,7 +208,7 @@ Order::Placed.publish(order_id: 1, total: 99.99)
180
208
 
181
209
  **Zero-config pub/sub** — define events and handlers, publish. No bus setup needed.
182
210
 
183
- **Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline.
211
+ **Async by default** — handlers dispatched via ActiveJob. `sync: true` for inline. If ActiveJob is not loaded, async publish raises `LoadError`.
184
212
 
185
213
  **Causality tracing** — link events in chains with shared `trace_id`:
186
214
 
@@ -273,7 +301,7 @@ end
273
301
 
274
302
  ## Queries
275
303
 
276
- Declarative query objects for filtering and sorting ActiveRecord relations and Mongoid criteria.
304
+ Declarative query objects for filtering and sorting ActiveRecord and Mongoid scopes.
277
305
 
278
306
  ```ruby
279
307
  class Order::Query < Dex::Query
@@ -326,13 +354,18 @@ Full documentation at **[dex.razorjack.net](https://dex.razorjack.net)**.
326
354
 
327
355
  ## AI Coding Assistant Setup
328
356
 
329
- dexkit ships LLM-optimized guides. Copy them into your project so AI agents automatically know the API:
357
+ dexkit ships LLM-optimized guides. Install them as `AGENTS.md` files in your app directories so AI coding agents automatically know the API:
358
+
359
+ ```bash
360
+ rake dex:guides
361
+ ```
362
+
363
+ This copies guides into directories that exist (`app/operations/`, `app/events/`, `app/event_handlers/`, `app/forms/`, `app/queries/`), stamped with the installed dexkit version. Re-run after upgrading dexkit to sync. Existing hand-written `AGENTS.md` files are never overwritten (use `FORCE=1` to override).
364
+
365
+ Override paths for non-standard directory names:
330
366
 
331
367
  ```bash
332
- cp $(bundle show dexkit)/guides/llm/OPERATION.md app/operations/CLAUDE.md
333
- cp $(bundle show dexkit)/guides/llm/EVENT.md app/event_handlers/CLAUDE.md
334
- cp $(bundle show dexkit)/guides/llm/FORM.md app/forms/CLAUDE.md
335
- cp $(bundle show dexkit)/guides/llm/QUERY.md app/queries/CLAUDE.md
368
+ rake dex:guides OPERATIONS_PATH=app/services
336
369
  ```
337
370
 
338
371
  ## License
@@ -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.8.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.8.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
@@ -1,6 +1,6 @@
1
1
  # Dex::Event — LLM Reference
2
2
 
3
- Copy this to your app's event handlers directory (e.g., `app/event_handlers/AGENTS.md`) so coding agents know the full API when implementing and testing events.
3
+ Install with `rake dex:guides` or copy manually to `app/events/AGENTS.md`.
4
4
 
5
5
  ---
6
6
 
@@ -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
 
@@ -109,7 +109,7 @@ class ProcessPayment < Dex::Event::Handler
109
109
  end
110
110
  ```
111
111
 
112
- When retries exhausted, exception propagates normally.
112
+ When retries exhausted, exception propagates normally. Async handlers and retries require ActiveJob to be loaded.
113
113
 
114
114
  ### Callbacks
115
115
 
@@ -157,7 +157,7 @@ class FulfillOrder < Dex::Event::Handler
157
157
  end
158
158
  ```
159
159
 
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.
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. Transactions are ActiveRecord-only – in Mongoid-only apps, `after_commit` fires immediately after handler success.
161
161
 
162
162
  ### Custom Pipeline
163
163
 
@@ -231,6 +231,19 @@ create_table :event_records do |t|
231
231
  end
232
232
  ```
233
233
 
234
+ Mongoid stores work too:
235
+
236
+ ```ruby
237
+ class EventRecord
238
+ include Mongoid::Document
239
+ include Mongoid::Timestamps
240
+
241
+ field :event_type, type: String
242
+ field :payload, type: Hash
243
+ field :metadata, type: Hash
244
+ end
245
+ ```
246
+
234
247
  Persistence failures are silently rescued — they never halt event publishing.
235
248
 
236
249
  ---
@@ -294,7 +307,7 @@ Everything works without configuration. All three settings are optional.
294
307
 
295
308
  ```ruby
296
309
  # test/test_helper.rb
297
- require "dex/event_test_helpers"
310
+ require "dex/event/test_helpers"
298
311
 
299
312
  class Minitest::Test
300
313
  include Dex::Event::TestHelpers
@@ -383,4 +396,46 @@ end
383
396
 
384
397
  ---
385
398
 
399
+ ## Registry, Export & Description
400
+
401
+ ### Description
402
+
403
+ Events can declare a human-readable description. Props can include `desc:`:
404
+
405
+ ```ruby
406
+ class Order::Placed < Dex::Event
407
+ description "Emitted after an order is successfully placed"
408
+
409
+ prop :order_id, Integer, desc: "The placed order"
410
+ prop :total, BigDecimal, desc: "Order total"
411
+ end
412
+ ```
413
+
414
+ ### Registry
415
+
416
+ ```ruby
417
+ Dex::Event.registry # => #<Set: {Order::Placed, Order::Cancelled, ...}>
418
+ Dex::Event::Handler.registry # => #<Set: {NotifyWarehouse, SendConfirmation, ...}>
419
+ Dex::Event.deregister(klass)
420
+ Dex::Event::Handler.deregister(klass)
421
+ ```
422
+
423
+ ### Export
424
+
425
+ ```ruby
426
+ Order::Placed.to_h
427
+ # => { name: "Order::Placed", description: "...", props: { order_id: { type: "Integer", ... } } }
428
+
429
+ Order::Placed.to_json_schema # JSON Schema (Draft 2020-12)
430
+
431
+ NotifyWarehouse.to_h
432
+ # => { name: "NotifyWarehouse", events: ["Order::Placed"], retries: 3, ... }
433
+
434
+ Dex::Event.export # all events as hashes
435
+ Dex::Event.export(format: :json_schema) # all as JSON Schema
436
+ Dex::Event::Handler.export # all handlers as hashes
437
+ ```
438
+
439
+ ---
440
+
386
441
  **End of reference.**
data/guides/llm/FORM.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Dex::Form — LLM Reference
2
2
 
3
- Copy this to your app's forms directory (e.g., `app/forms/AGENTS.md`) so coding agents know the full API when implementing and testing forms.
3
+ Install with `rake dex:guides` or copy manually to `app/forms/AGENTS.md`.
4
4
 
5
5
  ---
6
6
 
@@ -224,7 +224,7 @@ validates :email, uniqueness: true
224
224
  | `model:` | Explicit model class | `uniqueness: { model: User }` |
225
225
  | `attribute:` | Column name if different | `uniqueness: { attribute: :email }` |
226
226
  | `scope:` | Scoped uniqueness | `uniqueness: { scope: :tenant_id }` |
227
- | `case_sensitive:` | Case-insensitive check | `uniqueness: { case_sensitive: false }` |
227
+ | `case_sensitive:` | Case-insensitive check (`LOWER()` on ActiveRecord, case-insensitive regex on Mongoid) | `uniqueness: { case_sensitive: false }` |
228
228
  | `conditions:` | Extra query conditions | `uniqueness: { conditions: -> { where(active: true) } }` |
229
229
  | `message:` | Custom error message | `uniqueness: { message: "already registered" }` |
230
230
 
@@ -237,7 +237,7 @@ validates :email, uniqueness: true
237
237
 
238
238
  ### Record exclusion
239
239
 
240
- When `form.record` is persisted, the current record is excluded from the uniqueness check (for updates).
240
+ When `form.record` is persisted, the current record is excluded from the uniqueness check (for updates) on both ActiveRecord and Mongoid-backed forms.
241
241
 
242
242
  ---
243
243