tcb 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77ac348140af6f18f4416932f320bc474f3131c14903c592cf83f352a02e1297
4
- data.tar.gz: 4f5462a8425eb01612a3e6ffb3b89c8c848504db97d8a2fb913c7961abbc67b2
3
+ metadata.gz: 3d004e21f0c4555988f0a5b84e40a4ae4710933b3e862ab964a6a4bc07cd02cf
4
+ data.tar.gz: 684c9a07870739ebec829c872f6c4c71d455b0919d369d0f9badd000fceb28d3
5
5
  SHA512:
6
- metadata.gz: 2eed8f7a67fcef1a356262a854b707964a2d5eb15c910a5a508368fc602e7f0892742e0ff24035a26f7542ae9de14740288db7fe07655d7e13e25a444db8799e
7
- data.tar.gz: d41fd603ec8b6cc729396ea7ac5b9b692bd983b080f59e8f1df23faab88976563bd460513ed0634d25fba6a3911f77e583e8508c0ce455997347ed25eef00e12
6
+ metadata.gz: 71b0fe98490c7a0c1d995b7eaf7612976690306c55c1ccc904f4038089f11c3f6389e03afd9474fd1a058c28de4905fdfca811cb4e88fcf77119a361cde3ff3f
7
+ data.tar.gz: a433451a28f9fe1bd295db4fe40e4760069d4d4915ec8807f8f35f2791df6821dd86f1e24b7c57bbb470dc9b4d618f540e26862a61461e71af9c839bc881a661
data/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.2] - 2026-05-07
9
+
10
+ ### Fixed
11
+
12
+ - `tcb:install` generator — `TCB.domain_modules=` moved into `Rails.application.config.to_prepare` block; bare initializer runs before Zeitwerk loads application constants, causing `NameError` when domain modules reference Rails classes such as `ApplicationJob` or `ApplicationRecord`
13
+
8
14
  ## [0.6.1] - 2026-05-06
9
15
 
10
16
  ### Fixed
data/README.md CHANGED
@@ -1,10 +1,36 @@
1
1
  # TCB
2
2
 
3
- A lightweight, thread-safe event and command runtime for Domain-Driven Design on Rails.
3
+ Is your codebase using every form of coupling except the one architects actually recommend?
4
+ How much does a feature request cost you now?
5
+
6
+ TCB gives each concern its own place, a clean domain language, and a full record
7
+ of everything that happened, so you can understand and evolve your business logic
8
+ with confidence.
9
+
10
+ Imagine the following scenario. An order is placed. Stock gets reserved. The customer is notified.
11
+ Now imagine each piece in its own domain, reacting independently, easy to test in isolation,
12
+ simple to evolve as your business grows, and the full picture always visible.
13
+
14
+ ```ruby
15
+ PlaceOrder = Data.define(:order_id, :customer)
16
+ def Sales.place!(order_id:, customer:) = TCB.dispatch(PlaceOrder.new(order_id:, customer:))
4
17
 
5
- TCB gives Rails applications a clean domain language. Events, aggregates, and handlers are plain Ruby. No framework inheritance, no infrastructure details leaking into your domain.
18
+ correlation_id = Sales.place!(order_id: 42, customer: "Alice")
19
+ # Sales → OrderPlaced persisted, published
20
+ # Warehouse → StockReserved reacts automatically, same correlation
21
+ # Notifications → CustomerNotified reacts automatically, same correlation
22
+
23
+ TCB.read_correlation(correlation_id).to_a # => all three events, across all three domains, in order
24
+ ```
25
+
26
+ A lightweight, thread-safe event and command runtime for Domain-Driven Design on Rails.
27
+ Events, aggregates, and handlers are plain Ruby. No framework inheritance, no infrastructure
28
+ details leaking into your domain.
6
29
 
7
- TCB uses a command and event bus as an architectural coordination mechanism. Commands are decisions routed to exactly one handler. Events are facts broadcast to any number of reactions. The goal is to isolate side-effects, allow independent evolution of behaviors, and support an increasing number of business-significant events.
30
+ Rails can change. Your domains should change only when your business demands it.
31
+ Clean domain code pays compound interest. It's easier to reason about, easier to test,
32
+ and easier for AI agents to work with. TCB keeps your domain that way.
33
+ Rails takes care of the rest.
8
34
 
9
35
  ## Installation
10
36
 
@@ -46,11 +72,11 @@ bus.publish(UserRegistered.new(id: 1, email: "alice@example.com"))
46
72
 
47
73
  ### Execution model
48
74
 
49
- TCB::EventBus uses a single background thread to process events. Publishing is non-blocking the event is placed on a queue and control returns to the caller immediately. The dispatcher thread processes events in FIFO order. Handlers for a given event execute sequentially within the dispatcher thread.
75
+ TCB::EventBus uses a single background thread to process events. Publishing is non-blocking. The event is placed on a queue and control returns to the caller immediately. The dispatcher thread processes events in FIFO order. Handlers for a given event execute sequentially within the dispatcher thread.
50
76
 
51
77
  This design favors determinism and simplicity: events are always processed in the order they were published, and handlers cannot race with each other.
52
78
 
53
- For tests and simple use cases, `sync: true` executes handlers in the caller thread immediately no background thread, no queue:
79
+ For tests and simple use cases, `sync: true` executes handlers in the caller thread immediately, no background thread, no queue:
54
80
 
55
81
  ```ruby
56
82
  bus = TCB::EventBus.new(sync: true)
@@ -77,7 +103,7 @@ The event queue is unbounded by default. If handlers are slower than the rate of
77
103
  TCB::EventBus.new(max_queue_size: 10_000)
78
104
  ```
79
105
 
80
- When the queue is full, `publish` blocks until space is available. The right value depends on your event volume and handler latency measure before deciding.
106
+ When the queue is full, `publish` blocks until space is available. The right value depends on your event volume and handler latency. Measure before deciding.
81
107
 
82
108
  ---
83
109
 
@@ -140,7 +166,7 @@ end
140
166
  Event classes can come from anywhere. Cross-module reactions are the norm, not the exception. Each handler is isolated. Ine failure does not prevent others from executing.
141
167
 
142
168
  Domain modules are declared once at the top level, before infrastructure is configured.
143
- This is the only place TCB needs to know about your bounded contexts all reactions,
169
+ This is the only place TCB needs to know about your bounded contexts. All reactions,
144
170
  persistence rules, and handler mappings live inside each module itself.
145
171
 
146
172
  ```ruby
@@ -154,7 +180,7 @@ end
154
180
 
155
181
  `TCB.domain_modules=` wires up subscriptions and command routing from all modules.
156
182
  `TCB.configure` provides the infrastructure they run on. The two are intentionally
157
- separate domain modules don't change between environments, infrastructure does.
183
+ separate. Domain modules don't change between environments, infrastructure does.
158
184
 
159
185
  ---
160
186
 
@@ -370,7 +396,7 @@ end
370
396
 
371
397
  ### Domain modules
372
398
 
373
- Domain modules are the bounded contexts of your application. Declare them once, at the top level before infrastructure is configured:
399
+ Domain modules are the bounded contexts of your application. Declare them once, at the top level, before infrastructure is configured:
374
400
 
375
401
  ```ruby
376
402
  # config/initializers/tcb.rb
@@ -425,7 +451,7 @@ Rails.application.config.after_initialize do
425
451
  end
426
452
  ```
427
453
 
428
- `sync: true` executes handlers in the caller thread no background thread, no polling. `after_initialize` runs once at boot. Between tests, call `TCB.reset!` to get a fresh bus and store.
454
+ `sync: true` executes handlers in the caller thread. No background thread, no polling. `after_initialize` runs once at boot. Between tests, call `TCB.reset!` to get a fresh bus and store.
429
455
 
430
456
  Each domain module gets its own database table. Domains stay isolated at the persistence level:
431
457
 
@@ -483,7 +509,7 @@ envelope.causation_id # UUID string, event_id of the triggering event; nil for
483
509
 
484
510
  ## Correlation and causation tracking
485
511
 
486
- Every `TCB.dispatch` generates a `correlation_id` and returns it to the caller. All events produced within that dispatch chain share the same `correlation_id`, regardless of how deep the reactive chain goes. `causation_id` identifies the direct cause the `event_id` of the envelope that triggered the handler.
512
+ Every `TCB.dispatch` generates a `correlation_id` and returns it to the caller. All events produced within that dispatch chain share the same `correlation_id`, regardless of how deep the reactive chain goes. `causation_id` identifies the direct cause, the `event_id` of the envelope that triggered the handler.
487
513
 
488
514
  ```
489
515
  Sales.place!(order_id: 42, customer: "Alice")
@@ -529,7 +555,7 @@ TCB.read_correlation("req-abc").between(1.hour.ago, Time.now).to_a
529
555
 
530
556
  Results are ordered by `occurred_at` across all domains. Each result is a `TCB::Envelope` with `correlation_id` and `causation_id` populated.
531
557
 
532
- `across:` defaults to all configured domain modules that have persistence registrations. Domains without persistence like `Notifications` in the example above are excluded automatically.
558
+ `across:` defaults to all configured domain modules that have persistence registrations. Domains without persistence, like `Notifications` in the example above, are excluded automatically.
533
559
 
534
560
  ---
535
561
 
@@ -598,17 +624,21 @@ Generates:
598
624
  | `--skip-migration` | Skip migration (event_store only) |
599
625
  | `--no-comments` | Generate without inline guidance comments |
600
626
 
601
- After generating, add your module to config/initializers/tcb.rb. Domain modules don't change between environments infrastructure does. Keeping them separate means your bounded contexts are declared once, while the bus and store are configured per environment:
627
+ After generating, add your module to config/initializers/tcb.rb. Domain modules don't change between environments. Infrastructure does. Keeping them separate means your bounded contexts are declared once, while the bus and store are configured per environment:
602
628
 
603
629
  ```ruby
604
630
  # config/initializers/tcb.rb
605
- TCB.domain_modules = [
606
- Sales,
607
- Warehouse,
608
- Notifications
609
- ]
631
+ Rails.application.config.to_prepare do
632
+ TCB.domain_modules = [
633
+ Sales,
634
+ Warehouse,
635
+ Notifications
636
+ ]
637
+ end
610
638
  ```
611
639
 
640
+ `to_prepare` runs after Zeitwerk loads all application constants. Use it instead of a bare initializer. Domain modules typically reference Rails classes (ApplicationJob, ApplicationRecord, etc.) that are not yet available at initializer time.
641
+
612
642
  ---
613
643
 
614
644
  ## Error Handling
@@ -645,7 +675,7 @@ bus.force_shutdown
645
675
 
646
676
  Configure TCB once at boot in `config/environments/test.rb` (see Configuration above). Between tests, call `TCB.reset!` to get a fresh event bus and a clean event store.
647
677
 
648
- `TCB.reset!` shuts down the current bus, clears the event store, and clears all subscriptions. The next test starts with a clean slate. Domain modules do not need to be re-declared they are set once at the top level and persist across resets.
678
+ `TCB.reset!` shuts down the current bus, clears the event store, and clears all subscriptions. The next test starts with a clean slate. Domain modules do not need to be re-declared. They are set once at the top level and persist across resets.
649
679
 
650
680
  ### Synchronous mode
651
681
 
@@ -661,12 +691,20 @@ Rails.application.config.after_initialize do
661
691
  end
662
692
  ```
663
693
 
694
+ `after_initialize` runs once at boot. Tests do not reload Rails, so `to_prepare` is unnecessary. Between tests, TCB.reset! shuts down the current bus and clears the store, but also wipes the configuration. Restore it in your test teardown so every test starts with a clean, fully configured bus.
695
+
664
696
  ### Minitest
665
697
 
666
698
  ```ruby
667
699
  class OrdersTest < Minitest::Test
668
700
  include TCB::MinitestHelpers
669
- def teardown = TCB.reset!
701
+ def teardown
702
+ TCB.reset!
703
+ TCB.configure do |c|
704
+ c.event_bus = TCB::EventBus.new(sync: true)
705
+ c.event_store = TCB::EventStore::InMemory.new
706
+ end
707
+ end
670
708
 
671
709
  def test_placing_order_publishes_event
672
710
  assert_published(Orders::OrderPlaced) do
@@ -687,7 +725,7 @@ assert_published(Orders::OrderPlaced, within: 0.5) { Orders.place!(...) }
687
725
 
688
726
  #### poll_assert
689
727
 
690
- Only needed when using an async bus. With `TCB::EventBus.new(sync: true)`, handlers execute in the caller thread and results are available immediately no polling required.
728
+ Only needed when using an async bus. With `TCB::EventBus.new(sync: true)`, handlers execute in the caller thread and results are available immediately. No polling required.
691
729
 
692
730
  ```ruby
693
731
  poll_assert("reserve inventory called") { CALLED.include?(:reserve_inventory) }
@@ -699,7 +737,13 @@ poll_assert("payment processed", within: 2.0) { Payment.completed? }
699
737
  ```ruby
700
738
  # spec/support/tcb.rb
701
739
  RSpec.configure do |config|
702
- config.after(:each) { TCB.reset! }
740
+ config.after(:each) do
741
+ TCB.reset!
742
+ TCB.configure do |c|
743
+ c.event_bus = TCB::EventBus.new(sync: true)
744
+ c.event_store = TCB::EventStore::InMemory.new
745
+ end
746
+ end
703
747
  end
704
748
  ```
705
749
 
@@ -2,19 +2,27 @@
2
2
  # Add your domain modules here after generating them:
3
3
  # rails generate tcb:event_store orders place_order:order_id,customer
4
4
  # rails generate tcb:domain notifications send_welcome_email:user_id,email
5
- TCB.domain_modules = [
6
- # Orders,
7
- # Notifications,
8
- ]
9
-
10
- # Infrastructure — how events are transported and stored
11
- # Runs on every Rails reload in development
5
+ #
6
+ # to_prepare runs after Zeitwerk loads all application constants.
7
+ # Domain modules typically reference Rails classes (ApplicationJob, ApplicationRecord, etc.)
8
+ # that are not yet available at initializer time.
12
9
  Rails.application.config.to_prepare do
13
- TCB.configure do |c|
14
- c.event_bus = TCB::EventBus.new(
15
- handle_signals: true,
16
- shutdown_timeout: 10.0
17
- )
18
- c.event_store = TCB::EventStore::ActiveRecord.new
19
- end
10
+ TCB.domain_modules = [
11
+ # Orders,
12
+ # Notifications,
13
+ ]
20
14
  end
15
+
16
+ # Infrastructure — how events are transported and stored
17
+ # Configure per environment in config/environments/*.rb so differences are explicit.
18
+ # Example for development:
19
+ #
20
+ # Rails.application.config.to_prepare do
21
+ # TCB.reset!
22
+ # TCB.configure do |c|
23
+ # c.event_bus = TCB::EventBus.new(handle_signals: false, shutdown_timeout: 10.0)
24
+ # c.event_store = TCB::EventStore::ActiveRecord.new
25
+ # end
26
+ # end
27
+ #
28
+ # See README for production and test examples.
data/lib/tcb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TCB
4
- VERSION = "0.6.1"
4
+ VERSION = "0.6.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ljubomir Marković
@@ -192,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
192
  - !ruby/object:Gem::Version
193
193
  version: '0'
194
194
  requirements: []
195
- rubygems_version: 3.6.9
195
+ rubygems_version: 4.0.6
196
196
  specification_version: 4
197
197
  summary: Lightweight DDD runtime for Rails — events, commands, and aggregates
198
198
  test_files: []