@@ -123,6 +123,96 @@ This requires no internal runtime imports. See the
123123The gem's strict payload target checks the actual constructors; its packaged
124124consumer test also verifies that missing keys and incorrect field types fail.
125125
126+ ### Actor-specific dispatch signatures
127+
128+ Typed applications can opt into ` SolidObjects::ActorSignatures ` to check ordinary
129+ ` schedule ` , ` transmit ` , and effect callback names inside actor methods. Add ` rbs `
130+ and ` steep ` to the application's development dependencies. This tool is loaded
131+ explicitly and is not required by workers or ordinary Ruby applications.
132+
133+ Declare application operation types first, including inherited operations and
134+ block-defined ` message ` operations. Inline RBS can generate this input, or keep
135+ handwritten declarations in a separate directory such as ` sig/actors ` :
136+
137+ ``` rbs
138+ class ChatRun < SolidObjects::Actor
139+ def recover_if_stuck: (generation: Integer) -> nil
140+ def fail_turn: (effect_id: String, arguments: Hash[String, untyped], error: Hash[String, untyped]) -> nil
141+ def start: () -> nil
142+ end
143+ ```
144+
145+ After loading the application's actor classes, generate a separate output file:
146+
147+ ``` ruby
148+ require " solid_objects/actor_signatures"
149+
150+ Rails .application.reloader.wrap do
151+ signatures = SolidObjects ::ActorSignatures .generate(
152+ actors: [ChatRun ],
153+ signatures: [Rails .root.join(" sig/actors" ).to_s]
154+ )
155+ FileUtils .mkdir_p(Rails .root.join(" sig/generated" ))
156+ File .write(Rails .root.join(" sig/generated/solid_objects.rbs" ), signatures)
157+ end
158+ ```
159+
160+ Run that script with ` bin/rails runner ` during development or CI. Outside Rails,
161+ require the actor definitions and call ` generate ` directly. The generator returns
162+ a string and does not write files, execute actor operations, start workers, or run
163+ migrations. Rails boot follows the application's normal loading configuration;
164+ the explicit actor list resolves its autoloaded classes within the reloader boundary.
165+ Keep generated output out of the input signature paths, and regenerate after a
166+ message is renamed or removed. Output is deterministic; handwritten signatures
167+ remain separate.
168+
169+ Load the gem and both application signature directories in ` Steepfile ` :
170+
171+ ``` ruby
172+ target :actors do
173+ library " solid_objects"
174+ signature " sig/actors"
175+ signature " sig/generated"
176+ check " app/actors"
177+ configure_code_diagnostics(Diagnostic ::Ruby .strict)
178+ end
179+ ```
180+
181+ The usual Ruby code now passes Steep without a dispatcher cast:
182+
183+ ``` ruby
184+ schedule(at: Time .now, key: " watchdog" ).recover_if_stuck(generation: 1 )
185+ transmit.recover_if_stuck(generation: 1 )
186+ emit :run_model , generation: 1 , on_failure: :fail_turn
187+ ```
188+
189+ Misspelled operations/callbacks, queries, attributes, private methods, infrastructure
190+ methods, and incorrect keyword arguments fail the strict check. Both string and
191+ symbol callback literals work. Staging returns ` nil ` even when an operation's own
192+ return type differs. Effect and commit-action names remain global registry names;
193+ registry contract inference is separate work.
194+
195+ Reflection supplies message names only. Values come from declared RBS signatures;
196+ missing signatures and positional/block arguments are rejected. Block-defined
197+ messages require explicit method declarations in RBS; annotate their block-local
198+ values separately when checking the block body. Generic actor classes currently
199+ require application-owned dispatcher signatures. The generator preserves method
200+ overloads and method type parameters.
201+
202+ Deliberately dynamic names can use Ruby's explicit dynamic dispatch:
203+
204+ ``` ruby
205+ schedule(at: Time .now).public_send(operation_name, generation: generation)
206+ public_send(:emit , :run_model , on_failure: callback_name, generation: generation)
207+ ```
208+
209+ Those calls opt out of name/argument checking and retain the existing runtime
210+ validation. Ordinary calls on the generated dispatcher have no string-name fallback.
211+ ` send_to ` , ` Reference#async ` , direct calls, and queries retain their existing
212+ signatures and runtime behavior; this generator does not provide complete reference
213+ typing or Sorbet/Tapioca actor-specific RBI generation. The corresponding TypeScript
214+ work is tracked in [ solid-objects-js #46 ] ( https://github.com/cardmagic/solid-objects-js/issues/46 ) .
215+
126216## Formatting and security
127217
128218``` bash
0 commit comments