Nothing retries or reconnects implicitly — a transport behaves exactly as it
always has until a policy is attached, so timing stays predictable by
default. Resilience is configured on the transport, which owns it for
every client derived from it:
TcpTransportService.Default({ host, port, retry:RetryPolicies.tcp(), // applied to every operation reconnect: {}, // supervised reconnect + circuit breaker })
Policies are error-aware: transient failures (timeouts, framing errors, a
busy device) back off exponentially with jitter (on by default), while
deterministic ones (illegal address, invalid argument) fail immediately.
Override per client — one bus, several device types — or per operation.
Both replace the policy rather than composing with it:
constmeter = yield*transport.withClient(1, { retry:RetryPolicies.serial() }); yield*meter.withRetry(RetryPolicies.none()).writeSingleCoil({ address:0, value });
With reconnect enabled, the transport runs one supervised reconnect for the
whole application and refuses operations with ModbusCircuitOpenError
while the link is down, instead of letting every caller queue requests onto a
dead bus. Watch ConnectionState via transport.connectionState.
retryModbus remains for retrying a compound operation — a
read-modify-write driven as a unit — over a RetryPolicies.none() client.
Note that it wraps rather than replaces: unlike the two overrides above,
it is piped around an effect the client has already wrapped in its own retry,
so over a policied client the two nest and attempt counts multiply.
Resilience lives at this layer and only at this layer. modbus-rs's own
transport-level retryAttempts / retryDelayMs / retryBackoffStrategy are
not accepted by any transport constructor here: they retry beneath the
Effect boundary where neither the policy, the circuit breaker, nor the logs
can see them, and they reconnect inline, racing the supervisor fiber that
owns reconnection. See UpstreamRetryOptionKey.
@flux-control/effect-modbus-rs
Type-safe Modbus communication via Effect-TS, wrapping the
modbus-rsnpm bindings (Rustnapi-rsunder the hood).Transport services
Browser (WASM) transport services
Server layers
Run a server layer with Layer.launch and execute with a runtime:
Errors
All Modbus operations fail with a ModbusError discriminated union. Use
Effect.catchTagsto handle specific variants:Resilience
Nothing retries or reconnects implicitly — a transport behaves exactly as it always has until a policy is attached, so timing stays predictable by default. Resilience is configured on the transport, which owns it for every client derived from it:
Policies are error-aware: transient failures (timeouts, framing errors, a busy device) back off exponentially with jitter (on by default), while deterministic ones (illegal address, invalid argument) fail immediately.
Override per client — one bus, several device types — or per operation. Both replace the policy rather than composing with it:
With
reconnectenabled, the transport runs one supervised reconnect for the whole application and refuses operations with ModbusCircuitOpenError while the link is down, instead of letting every caller queue requests onto a dead bus. Watch ConnectionState viatransport.connectionState.retryModbus remains for retrying a compound operation — a read-modify-write driven as a unit — over a
RetryPolicies.none()client. Note that it wraps rather than replaces: unlike the two overrides above, it is piped around an effect the client has already wrapped in its own retry, so over a policied client the two nest and attempt counts multiply.Resilience lives at this layer and only at this layer.
modbus-rs's own transport-levelretryAttempts/retryDelayMs/retryBackoffStrategyare not accepted by any transport constructor here: they retry beneath the Effect boundary where neither the policy, the circuit breaker, nor the logs can see them, and they reconnect inline, racing the supervisor fiber that owns reconnection. See UpstreamRetryOptionKey.