|
1 | | -# Transmit ingest |
2 | | - |
3 | | -`SolidObjects::Transmission.receive(envelope)` is the server side of the |
4 | | -browser transmit family in |
5 | | -[solid-objects-js](https://github.com/cardmagic/solid-objects-js). A |
6 | | -solid-objects-js actor in a browser stages a transmit intent with |
7 | | -`this.transmit().increment({ amount })` in the same transaction as its state |
8 | | -change. The browser's effect worker drains that outbox with at-least-once |
9 | | -delivery, per-actor order, and retry backoff. It posts one JSON envelope per |
10 | | -effect to a route the host application owns. `Transmission.receive` replays |
11 | | -that envelope onto a server actor. |
| 1 | +# The transmit family |
| 2 | + |
| 3 | +The transmit family replays one runtime's actor operations onto another |
| 4 | +runtime over a shared wire contract. The Ruby gem holds both sides: |
| 5 | + |
| 6 | +- `Actor#transmit` and `SolidObjects.register_transmit` stage and deliver |
| 7 | + envelopes. This is the sending side. |
| 8 | +- `SolidObjects::Transmission.receive(envelope)` ingests envelopes. This is |
| 9 | + the receiving side. |
| 10 | + |
| 11 | +[solid-objects-js](https://github.com/cardmagic/solid-objects-js) holds the |
| 12 | +same two sides for the browser and Node. A browser actor stages a transmit |
| 13 | +intent with `this.transmit().increment({ amount })` in the same transaction |
| 14 | +as its state change; its effect worker drains that outbox with |
| 15 | +at-least-once delivery, per-actor order, and retry backoff, and posts one |
| 16 | +JSON envelope per effect to a route the host application owns. A Rails |
| 17 | +actor does the same with `transmit.increment(amount:)`. Either ingest |
| 18 | +accepts either sender, so Rails-to-Rails, Rails-to-Node, Node-to-Rails, |
| 19 | +and browser-to-Rails replication all ride one contract. |
| 20 | + |
| 21 | +## The sending side |
| 22 | + |
| 23 | +```ruby |
| 24 | +class Counter < SolidObjects::Actor |
| 25 | + actor_type "counters" |
| 26 | + |
| 27 | + attribute :count, default: 0 |
| 28 | + |
| 29 | + def increment(amount: 1) |
| 30 | + self.count += amount |
| 31 | + transmit.increment(amount:) |
| 32 | + end |
| 33 | +end |
| 34 | + |
| 35 | +SolidObjects.register_transmit do |envelope| |
| 36 | + DeliverToUpstream.call(envelope) |
| 37 | +end |
| 38 | +``` |
| 39 | + |
| 40 | +`transmit` returns the same fluent dispatcher `schedule` returns. It stages |
| 41 | +one `solid-objects.transmit` effect in the same commit as the state change, |
| 42 | +targeting the same operation on the same actor in the receiving runtime. For |
| 43 | +a different target, stage the effect directly: |
| 44 | + |
| 45 | +```ruby |
| 46 | +emit "solid-objects.transmit", |
| 47 | + operation: "increment", |
| 48 | + arguments: { amount: 2 }, |
| 49 | + actorType: "other-counters", |
| 50 | + actorId: "counter-1" |
| 51 | +``` |
| 52 | + |
| 53 | +`SolidObjects.register_transmit(&deliver)` registers the drain handler for |
| 54 | +that effect. The block receives one camelCase envelope per staged effect. |
| 55 | +Raise inside the block while the upstream is unreachable; the effect |
| 56 | +retries with backoff and dead-letters on exhaustion, like any other effect. |
| 57 | + |
| 58 | +The drain keeps per-actor order across failures: a claimed transmit effect |
| 59 | +delivers every undelivered sibling for its actor up to its own mailbox |
| 60 | +sequence, oldest first. The receiving side dedups on `transmit:<effectId>`, |
| 61 | +so a redelivered envelope applies once. |
12 | 62 |
|
13 | 63 | ## Wire contract |
14 | 64 |
|
@@ -79,7 +129,6 @@ SolidObjects::Transmission.receive( |
79 | 129 |
|
80 | 130 | ## Scope |
81 | 131 |
|
82 | | -`receive` is ingest only. The staging side in Ruby, an `actor.transmit` for |
83 | | -Ruby-to-Ruby replication, is a separate feature. An engine-mounted route |
84 | | -with an authentication hook is a possible follow-up; it stays out because |
85 | | -it carries authentication, CSRF, and rate-limit decisions of its own. |
| 132 | +An engine-mounted route with an authentication hook is a possible |
| 133 | +follow-up; it stays out because it carries authentication, CSRF, and |
| 134 | +rate-limit decisions of its own. |
0 commit comments