@flux-control/effect-modbus-rs 0.1.1 → 0.3.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.
@@ -0,0 +1,60 @@
1
+ import { Effect, Layer } from 'effect';
2
+ import type { WasmWsTransportOptions } from 'modbus-rs/web';
3
+ import { SlaveDeviceDefinitions } from './mocks';
4
+ import type { MockFaultOptions } from './mocks';
5
+ import type { TransportResilienceOptions } from './shared-transport';
6
+ declare const WasmWsTransportService_base: Effect.Service.Class<WasmWsTransportService, "WasmWsTransportService", {
7
+ readonly scoped: (options: WasmWsTransportOptions & TransportResilienceOptions) => Effect.Effect<{
8
+ connectionState: import("effect/SubscriptionRef").SubscriptionRef<{
9
+ readonly _tag: "Connected";
10
+ } | {
11
+ readonly _tag: "Disconnected";
12
+ } | {
13
+ readonly _tag: "Down";
14
+ readonly cause: import("./errors").ModbusError;
15
+ } | {
16
+ readonly _tag: "Reconnecting";
17
+ readonly attempt: number;
18
+ }>;
19
+ withClient: (unitId: number, clientOptions?: {
20
+ readonly retry?: import("./retry").ModbusRetryPolicy;
21
+ } | undefined) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
22
+ setRequestTimeout: (timeoutMs: number) => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
23
+ clearRequestTimeout: () => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
24
+ reconnect: () => Effect.Effect<undefined, import("./errors").ModbusError, never>;
25
+ close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
26
+ hasPendingRequests: () => boolean;
27
+ }, never, import("effect/Scope").Scope>;
28
+ }>;
29
+ /**
30
+ * Scoped Effect service wrapping `modbus-rs`'s browser {@link WasmWsTransport}
31
+ * for Modbus TCP over a WebSocket gateway (browsers can't open raw TCP sockets).
32
+ *
33
+ * The transport connection is opened lazily on the first call to
34
+ * `withClient(unitId)` and automatically closed when the consuming
35
+ * {@link Effect.Scope | Scope} finalizes.
36
+ *
37
+ * Clients are created per `unitId` via {@link WasmWsTransport.createClient} and
38
+ * cached, so repeated requests for the same unit ID reuse the same client.
39
+ *
40
+ * @see WasmWsTransport — Upstream `modbus-rs` browser WebSocket transport.
41
+ * @see WasmWsTransportOptions — Configuration for the WebSocket gateway connection.
42
+ * @see makeTransportScoped — Generic lifecycle logic from shared-transport.
43
+ */
44
+ export declare class WasmWsTransportService extends WasmWsTransportService_base {
45
+ /**
46
+ * Creates a {@link Layer} providing an in-memory mock
47
+ * {@link WasmWsTransportService} for testing or development.
48
+ *
49
+ * Accepts an array of {@link SlaveDeviceDefinition} describing the
50
+ * simulated Modbus slaves and their register/coil maps.
51
+ *
52
+ * @param devices - Slave device definitions for the mock.
53
+ * @returns A function that takes {@link WasmWsTransportOptions} and
54
+ * returns a scoped {@link Layer} providing the mock service.
55
+ *
56
+ * @see makeMockTransport — The underlying mock factory.
57
+ */
58
+ static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmWsTransportOptions & TransportResilienceOptions & MockFaultOptions) => Layer.Layer<WasmWsTransportService, never, never>;
59
+ }
60
+ export {};
@@ -0,0 +1,170 @@
1
+ import { Data, Duration, Effect, SubscriptionRef } from 'effect';
2
+ import { ModbusCircuitOpenError, type ModbusError } from './errors';
3
+ import { type ModbusErrorTag, type ModbusRetryPolicy } from './retry';
4
+ /**
5
+ * Live connection state of a transport.
6
+ *
7
+ * Owned by the transport and published through
8
+ * {@link TransportServiceApi.connectionState}, so an application can show link
9
+ * status without inferring it from failed reads.
10
+ *
11
+ * - `Disconnected` — never opened, or closed. Operations open it lazily.
12
+ * - `Connected` — usable.
13
+ * - `Reconnecting` — the supervisor is re-establishing the link. Operations are
14
+ * refused with {@link ModbusCircuitOpenError} rather than queued on a dead bus.
15
+ * - `Down` — reconnect attempts were exhausted; the supervisor is waiting out
16
+ * `resetAfter` before probing again. Operations are refused.
17
+ */
18
+ export type ConnectionState = Data.TaggedEnum<{
19
+ Disconnected: object;
20
+ Connected: object;
21
+ Reconnecting: {
22
+ readonly attempt: number;
23
+ };
24
+ Down: {
25
+ readonly cause: ModbusError;
26
+ };
27
+ }>;
28
+ /** Constructors and matchers for {@link ConnectionState}. */
29
+ export declare const ConnectionState: {
30
+ readonly $is: <Tag extends "Connected" | "Disconnected" | "Down" | "Reconnecting">(tag: Tag) => (u: unknown) => u is Extract<{
31
+ readonly _tag: "Connected";
32
+ }, {
33
+ readonly _tag: Tag;
34
+ }> | Extract<{
35
+ readonly _tag: "Disconnected";
36
+ }, {
37
+ readonly _tag: Tag;
38
+ }> | Extract<{
39
+ readonly _tag: "Down";
40
+ readonly cause: ModbusError;
41
+ }, {
42
+ readonly _tag: Tag;
43
+ }> | Extract<{
44
+ readonly _tag: "Reconnecting";
45
+ readonly attempt: number;
46
+ }, {
47
+ readonly _tag: Tag;
48
+ }>;
49
+ readonly $match: {
50
+ <const Cases extends { readonly [Tag in "Connected" | "Disconnected" | "Down" | "Reconnecting"]: (args: Extract<{
51
+ readonly _tag: "Connected";
52
+ } | {
53
+ readonly _tag: "Disconnected";
54
+ } | {
55
+ readonly _tag: "Down";
56
+ readonly cause: ModbusError;
57
+ } | {
58
+ readonly _tag: "Reconnecting";
59
+ readonly attempt: number;
60
+ }, {
61
+ readonly _tag: Tag;
62
+ }>) => any; }>(cases: Cases & { [K in Exclude<keyof Cases, "Connected" | "Disconnected" | "Down" | "Reconnecting">]: never; }): (value: {
63
+ readonly _tag: "Connected";
64
+ } | {
65
+ readonly _tag: "Disconnected";
66
+ } | {
67
+ readonly _tag: "Down";
68
+ readonly cause: ModbusError;
69
+ } | {
70
+ readonly _tag: "Reconnecting";
71
+ readonly attempt: number;
72
+ }) => import("effect/Unify").Unify<ReturnType<Cases["Connected" | "Disconnected" | "Down" | "Reconnecting"]>>;
73
+ <const Cases extends { readonly [Tag in "Connected" | "Disconnected" | "Down" | "Reconnecting"]: (args: Extract<{
74
+ readonly _tag: "Connected";
75
+ } | {
76
+ readonly _tag: "Disconnected";
77
+ } | {
78
+ readonly _tag: "Down";
79
+ readonly cause: ModbusError;
80
+ } | {
81
+ readonly _tag: "Reconnecting";
82
+ readonly attempt: number;
83
+ }, {
84
+ readonly _tag: Tag;
85
+ }>) => any; }>(value: {
86
+ readonly _tag: "Connected";
87
+ } | {
88
+ readonly _tag: "Disconnected";
89
+ } | {
90
+ readonly _tag: "Down";
91
+ readonly cause: ModbusError;
92
+ } | {
93
+ readonly _tag: "Reconnecting";
94
+ readonly attempt: number;
95
+ }, cases: Cases & { [K in Exclude<keyof Cases, "Connected" | "Disconnected" | "Down" | "Reconnecting">]: never; }): import("effect/Unify").Unify<ReturnType<Cases["Connected" | "Disconnected" | "Down" | "Reconnecting"]>>;
96
+ };
97
+ readonly Connected: Data.Case.Constructor<{
98
+ readonly _tag: "Connected";
99
+ }, "_tag">;
100
+ readonly Disconnected: Data.Case.Constructor<{
101
+ readonly _tag: "Disconnected";
102
+ }, "_tag">;
103
+ readonly Down: Data.Case.Constructor<{
104
+ readonly _tag: "Down";
105
+ readonly cause: ModbusError;
106
+ }, "_tag">;
107
+ readonly Reconnecting: Data.Case.Constructor<{
108
+ readonly _tag: "Reconnecting";
109
+ readonly attempt: number;
110
+ }, "_tag">;
111
+ };
112
+ /**
113
+ * Transport-level reconnection and circuit-breaking configuration.
114
+ *
115
+ * Supplying this on a transport hands reconnection to a supervisor fiber owned
116
+ * by that transport: one reconnect for the whole application rather than one
117
+ * per failing call site. Omit it and the transport keeps its manual behaviour —
118
+ * `reconnect()` still works, nothing happens on its own.
119
+ */
120
+ export interface ReconnectOptions {
121
+ /**
122
+ * How reconnect attempts are spaced. Defaults to 5 attempts, `250 millis`
123
+ * base, factor 2, `10 seconds` ceiling, jittered.
124
+ */
125
+ readonly policy?: ModbusRetryPolicy;
126
+ /**
127
+ * How long the circuit stays open after attempts are exhausted, before the
128
+ * supervisor probes again. Default `30 seconds`.
129
+ */
130
+ readonly resetAfter?: Duration.DurationInput;
131
+ /**
132
+ * Which operation failures hand control to the supervisor.
133
+ * Default `["ModbusConnectionClosedError", "ModbusTransportError"]`.
134
+ */
135
+ readonly triggerOn?: ReadonlyArray<ModbusErrorTag>;
136
+ }
137
+ /** {@link ReconnectOptions} with defaults applied. */
138
+ export interface ResolvedReconnect {
139
+ readonly policy: ModbusRetryPolicy;
140
+ readonly resetAfter: Duration.Duration;
141
+ readonly triggers: (error: ModbusError) => boolean;
142
+ }
143
+ /** Applies defaults to {@link ReconnectOptions}. */
144
+ export declare const resolveReconnect: (options: ReconnectOptions) => ResolvedReconnect;
145
+ /**
146
+ * Refuses an operation while the link is being re-established.
147
+ *
148
+ * `Disconnected` is allowed through so the first call still opens the
149
+ * transport lazily; only `Reconnecting` and `Down` are refused.
150
+ *
151
+ * @param state - The transport's connection state.
152
+ * @returns An Effect failing with {@link ModbusCircuitOpenError} when the
153
+ * circuit is open, and succeeding otherwise.
154
+ */
155
+ export declare const guardCircuit: (state: SubscriptionRef.SubscriptionRef<ConnectionState>) => Effect.Effect<void, ModbusCircuitOpenError>;
156
+ /**
157
+ * The supervisor loop: re-establishes the link, then keeps probing for as long
158
+ * as the transport lives.
159
+ *
160
+ * Each round runs `reconnect` under the configured policy. Success publishes
161
+ * `Connected` and ends the loop; exhausting the policy publishes `Down` and
162
+ * waits out `resetAfter` before the next round, so an unplugged device is
163
+ * retried at a steady cadence instead of being hammered or given up on.
164
+ *
165
+ * @param reconnect - The transport's own reconnect operation.
166
+ * @param state - The state cell to publish transitions to.
167
+ * @param resolved - Reconnect configuration with defaults applied.
168
+ * @returns An Effect that runs until the link is restored.
169
+ */
170
+ export declare const superviseReconnect: (reconnect: Effect.Effect<void, ModbusError>, state: SubscriptionRef.SubscriptionRef<ConnectionState>, resolved: ResolvedReconnect) => Effect.Effect<void>;
@@ -135,6 +135,30 @@ export declare class ModbusNotConnectedError extends ModbusNotConnectedError_bas
135
135
  readonly message: string;
136
136
  }> {
137
137
  }
138
+ declare const ModbusCircuitOpenError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
139
+ readonly _tag: "ModbusCircuitOpenError";
140
+ } & Readonly<A>;
141
+ /**
142
+ * Error indicating the transport's circuit breaker is open: the device is
143
+ * unreachable and the transport is reconnecting (or waiting to probe again),
144
+ * so the request was refused without touching the wire.
145
+ *
146
+ * This is a **local** error — it is never returned by `modbus-rs`. It is raised
147
+ * by the transport when its connection state is `Reconnecting` or `Down`,
148
+ * which keeps a dead device from being hammered by every caller at once.
149
+ *
150
+ * Retryable by default: a policy with enough budget rides out the outage
151
+ * cheaply, since each refused attempt costs nothing on the bus.
152
+ *
153
+ * @see ConnectionState — The transport state that produces this error.
154
+ */
155
+ export declare class ModbusCircuitOpenError extends ModbusCircuitOpenError_base<{
156
+ /** The failure that opened the circuit, or a descriptive error. */
157
+ readonly cause: Error;
158
+ /** Human-readable explanation of the error. */
159
+ readonly message: string;
160
+ }> {
161
+ }
138
162
  /**
139
163
  * Union of all typed Modbus errors emitted by this library.
140
164
  *
@@ -152,7 +176,7 @@ export declare class ModbusNotConnectedError extends ModbusNotConnectedError_bas
152
176
  *
153
177
  * @see ModbusErrorCode — The upstream error code enum driving this mapping.
154
178
  */
155
- export type ModbusError = ModbusExceptionError | ModbusTimeoutError | ModbusTransportError | ModbusInvalidArgumentError | ModbusConnectionClosedError | ModbusNotConnectedError | ModbusInternalError;
179
+ export type ModbusError = ModbusExceptionError | ModbusTimeoutError | ModbusTransportError | ModbusInvalidArgumentError | ModbusConnectionClosedError | ModbusNotConnectedError | ModbusCircuitOpenError | ModbusInternalError;
156
180
  /**
157
181
  * Converts a raw `Error` from `modbus-rs` into a typed {@link ModbusError}.
158
182
  *
@@ -1,7 +1,34 @@
1
- import { Effect, Schema } from "effect";
2
- import type { AsciiTransportOptions, RtuTransportOptions, TcpTransportOptions } from "modbus-rs";
3
- import { ModbusInvalidArgumentError, type ModbusError } from "./errors";
4
- import type { EffectModbusClient } from "./modbus-client";
1
+ import { Effect, Schema, SubscriptionRef } from 'effect';
2
+ import { type AsciiTransportOptions, type RtuTransportOptions, type TcpTransportOptions } from 'modbus-rs';
3
+ import type { WasmWsTransportOptions, WasmSerialTransportOptions } from 'modbus-rs/web';
4
+ import { ModbusInvalidArgumentError, type ModbusError } from './errors';
5
+ import type { ModbusRetryPolicy } from './retry';
6
+ import type { TransportResilienceOptions, WithoutUpstreamRetry } from './shared-transport';
7
+ /**
8
+ * Mock-only fault injection.
9
+ *
10
+ * Lets a test or example drive retry policies, backoff, and circuit-breaker
11
+ * behaviour end to end without hardware: the hook runs before each operation
12
+ * *attempt*, so returning an error is indistinguishable from a device that
13
+ * refused that attempt.
14
+ */
15
+ export interface MockFaultOptions {
16
+ /**
17
+ * Called before every operation attempt. Return an error to fail that
18
+ * attempt, or `undefined` to let it through.
19
+ *
20
+ * ```ts
21
+ * let remaining = 2;
22
+ * const fault = () => (remaining-- > 0 ? new ModbusTimeoutError({ ... }) : undefined);
23
+ * ```
24
+ */
25
+ readonly fault?: () => ModbusError | undefined;
26
+ /**
27
+ * Called before every mock reconnect attempt. Return an error to keep the
28
+ * link down, or `undefined` to let the reconnect succeed.
29
+ */
30
+ readonly reconnectFault?: () => ModbusError | undefined;
31
+ }
5
32
  /**
6
33
  * Schema for a single coil (digital output) definition.
7
34
  *
@@ -114,8 +141,8 @@ export type SlaveDeviceDefinitions = Schema.Schema.Type<typeof SlaveDeviceDefini
114
141
  * Accepts an array of {@link SlaveDeviceDefinition} that describe the
115
142
  * simulated Modbus slaves, their register maps, and coil states.
116
143
  * The returned factory matches the signature expected by the transport
117
- * service constructors (`RtuTransportOptions | AsciiTransportOptions |
118
- * TcpTransportOptions`) so it can be injected into any service layer.
144
+ * service constructors (`RtuTransportOpenOptions | AsciiTransportOpenOptions |
145
+ * TcpTransportOpenOptions`) so it can be injected into any service layer.
119
146
  *
120
147
  * Unsupported function codes (FIFO queue, file records) return
121
148
  * {@link ModbusInvalidArgumentError}.
@@ -124,11 +151,24 @@ export type SlaveDeviceDefinitions = Schema.Schema.Type<typeof SlaveDeviceDefini
124
151
  * @returns A transport factory function that returns a scoped Effect
125
152
  * providing the mock transport.
126
153
  */
127
- export declare const makeMockTransport: (devices: SlaveDeviceDefinitions) => (_options: RtuTransportOptions | AsciiTransportOptions | TcpTransportOptions) => Effect.Effect<{
128
- withClient: (unitId: number) => Effect.Effect<EffectModbusClient, ModbusInvalidArgumentError, never>;
154
+ export declare const makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: (WithoutUpstreamRetry<RtuTransportOptions> | WithoutUpstreamRetry<AsciiTransportOptions> | WithoutUpstreamRetry<TcpTransportOptions> | WasmWsTransportOptions | WasmSerialTransportOptions) & TransportResilienceOptions & MockFaultOptions) => Effect.Effect<{
155
+ connectionState: SubscriptionRef.SubscriptionRef<{
156
+ readonly _tag: "Connected";
157
+ } | {
158
+ readonly _tag: "Disconnected";
159
+ } | {
160
+ readonly _tag: "Down";
161
+ readonly cause: ModbusError;
162
+ } | {
163
+ readonly _tag: "Reconnecting";
164
+ readonly attempt: number;
165
+ }>;
166
+ withClient: (unitId: number, clientOptions?: {
167
+ readonly retry?: ModbusRetryPolicy;
168
+ } | undefined) => Effect.Effect<import("./modbus-client").EffectModbusClient, ModbusInvalidArgumentError, never>;
129
169
  setRequestTimeout: (_timeoutMs: number) => Effect.Effect<void, never, never>;
130
170
  clearRequestTimeout: () => Effect.Effect<void, never, never>;
131
171
  reconnect: () => Effect.Effect<void, ModbusError, never>;
132
172
  close: () => Effect.Effect<void, ModbusError, never>;
133
173
  hasPendingRequests: () => false;
134
- }, never, never>;
174
+ }, never, import("effect/Scope").Scope>;
@@ -1,13 +1,20 @@
1
- import type { ReadRegistersOptions, WriteSingleRegisterOptions, WriteMultipleRegistersOptions, ReadWriteMultipleRegistersOptions, ReadBitsOptions, WriteSingleCoilOptions, WriteMultipleCoilsOptions, ReadFifoQueueOptions, ReadFileRecordOptions, WriteFileRecordOptions, DiagnosticsOptions, ReadDeviceIdentificationOptions, FifoQueueResponse, DiagnosticsResponse, DeviceIdentificationResponse, AsyncSerialModbusClient, AsyncTcpModbusClient } from "modbus-rs";
2
- import { Effect } from "effect";
3
- import type { ModbusError } from "./errors";
4
- export type AnyModbusClient = AsyncSerialModbusClient | AsyncTcpModbusClient;
1
+ import { Effect } from 'effect';
2
+ import type { ReadRegistersOptions, WriteSingleRegisterOptions, WriteMultipleRegistersOptions, ReadWriteMultipleRegistersOptions, ReadBitsOptions, WriteSingleCoilOptions, WriteMultipleCoilsOptions, ReadFifoQueueOptions, ReadFileRecordOptions, WriteFileRecordOptions, DiagnosticsOptions, ReadDeviceIdentificationOptions, FifoQueueResponse, DiagnosticsResponse, DeviceIdentificationResponse, AsyncSerialModbusClient, AsyncTcpModbusClient, CoilState } from 'modbus-rs';
3
+ import type { WasmWsModbusClient, WasmSerialModbusClient } from 'modbus-rs/web';
4
+ import type { ModbusError } from './errors';
5
+ import { type ModbusRetryPolicy } from './retry';
6
+ /** The two native (napi) clients — same method surface, sharing one factory. */
7
+ export type NativeModbusClient = AsyncSerialModbusClient | AsyncTcpModbusClient;
8
+ /** The two browser (WASM) clients — same method surface, sharing one factory. */
9
+ export type WasmModbusClient = WasmSerialModbusClient | WasmWsModbusClient;
10
+ /** Any client this package knows how to wrap into an {@link EffectModbusClient}. */
11
+ export type AnyModbusClient = NativeModbusClient | WasmModbusClient;
5
12
  /**
6
- * Effect-ified Modbus client wrapping a `modbus-rs` transport client.
13
+ * The Modbus function-code surface, before any resilience is layered on.
7
14
  *
8
- * Each method delegates to the equivalent `AsyncSerialModbusClient` or
9
- * `AsyncTcpModbusClient` method, converting the Promise-based API into
10
- * an {@link Effect.Effect} with typed {@link ModbusError} failures.
15
+ * Each method delegates to the equivalent method on the underlying native
16
+ * or WASM client, converting the Promise-based API into an
17
+ * {@link Effect.Effect} with typed {@link ModbusError} failures.
11
18
  *
12
19
  * Thrown errors are classified using {@link toModbusError}, mapping
13
20
  * `modbus-rs` error codes (timeout, transport, exception, etc.) into
@@ -17,7 +24,7 @@ export type AnyModbusClient = AsyncSerialModbusClient | AsyncTcpModbusClient;
17
24
  * @see AsyncSerialModbusClient — Upstream `modbus-rs` serial client API.
18
25
  * @see AsyncTcpModbusClient — Upstream `modbus-rs` TCP client API.
19
26
  */
20
- export interface EffectModbusClient {
27
+ export interface ModbusOperations {
21
28
  /**
22
29
  * Reads holding registers from the Modbus device (FC03).
23
30
  *
@@ -27,7 +34,7 @@ export interface EffectModbusClient {
27
34
  * @see ReadRegistersOptions — Options shape from `modbus-rs`.
28
35
  * @see AsyncSerialModbusClient.readHoldingRegisters — Upstream implementation.
29
36
  */
30
- readHoldingRegisters(opts: ReadRegistersOptions): Effect.Effect<number[], ModbusError>;
37
+ readHoldingRegisters(opts: ReadRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
31
38
  /**
32
39
  * Reads input registers from the Modbus device (FC04).
33
40
  *
@@ -37,7 +44,7 @@ export interface EffectModbusClient {
37
44
  * @see ReadRegistersOptions — Options shape from `modbus-rs`.
38
45
  * @see AsyncSerialModbusClient.readInputRegisters — Upstream implementation.
39
46
  */
40
- readInputRegisters(opts: ReadRegistersOptions): Effect.Effect<number[], ModbusError>;
47
+ readInputRegisters(opts: ReadRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
41
48
  /**
42
49
  * Writes a single holding register (FC06).
43
50
  *
@@ -67,7 +74,7 @@ export interface EffectModbusClient {
67
74
  *
68
75
  * @see ReadWriteMultipleRegistersOptions — Options shape from `modbus-rs`.
69
76
  */
70
- readWriteMultipleRegisters(opts: ReadWriteMultipleRegistersOptions): Effect.Effect<number[], ModbusError>;
77
+ readWriteMultipleRegisters(opts: ReadWriteMultipleRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
71
78
  /**
72
79
  * Reads coils (digital outputs) from the Modbus device (FC01).
73
80
  *
@@ -76,7 +83,7 @@ export interface EffectModbusClient {
76
83
  *
77
84
  * @see ReadBitsOptions — Options shape from `modbus-rs`.
78
85
  */
79
- readCoils(opts: ReadBitsOptions): Effect.Effect<boolean[], ModbusError>;
86
+ readCoils(opts: ReadBitsOptions): Effect.Effect<CoilState[], ModbusError>;
80
87
  /**
81
88
  * Writes a single coil (digital output) (FC05).
82
89
  *
@@ -103,7 +110,7 @@ export interface EffectModbusClient {
103
110
  *
104
111
  * @see ReadBitsOptions — Options shape from `modbus-rs`.
105
112
  */
106
- readDiscreteInputs(opts: ReadBitsOptions): Effect.Effect<boolean[], ModbusError>;
113
+ readDiscreteInputs(opts: ReadBitsOptions): Effect.Effect<CoilState[], ModbusError>;
107
114
  /**
108
115
  * Reads the FIFO queue from the Modbus device (FC24).
109
116
  *
@@ -122,7 +129,7 @@ export interface EffectModbusClient {
122
129
  *
123
130
  * @see ReadFileRecordOptions — Options shape from `modbus-rs`.
124
131
  */
125
- readFileRecord(opts: ReadFileRecordOptions): Effect.Effect<number[][], ModbusError>;
132
+ readFileRecord(opts: ReadFileRecordOptions): Effect.Effect<Uint16Array[], ModbusError>;
126
133
  /**
127
134
  * Writes file records to the Modbus device (FC21).
128
135
  *
@@ -162,21 +169,73 @@ export interface EffectModbusClient {
162
169
  readDeviceIdentification(opts: ReadDeviceIdentificationOptions): Effect.Effect<DeviceIdentificationResponse, ModbusError>;
163
170
  }
164
171
  /**
165
- * Wraps a raw `modbus-rs` client into an {@link EffectModbusClient}.
172
+ * Wraps a raw `modbus-rs` client — native (napi) or browser (WASM) — into an
173
+ * {@link EffectModbusClient}.
166
174
  *
167
175
  * Each method converts a Promise-based call from the upstream client
168
176
  * into an `Effect` via {@link Effect.tryPromise}, routing errors through
169
177
  * {@link toModbusError} for typed error discrimination.
170
178
  *
171
- * Accepts both serial (`AsyncSerialModbusClient`) and TCP
172
- * (`AsyncTcpModbusClient`) clients since they share the same method
173
- * signatures.
179
+ * The native and WASM clients share the same method surface (same options
180
+ * shapes, same resolved value shapes `CoilState[]`, full `FifoQueueResponse`,
181
+ * full `DeviceIdentificationResponse`), so one factory covers both; no
182
+ * transport-specific reshaping is needed.
174
183
  *
175
- * @param client - The upstream `modbus-rs` client instance.
184
+ * @param client - The upstream `modbus-rs` or `modbus-rs/web` client instance.
176
185
  * @returns An `EffectModbusClient` that can be used within Effect
177
186
  * workflows.
178
187
  *
179
- * @see AsyncSerialModbusClient — Upstream serial client API.
180
- * @see AsyncTcpModbusClient — Upstream TCP client API.
188
+ * @see AsyncSerialModbusClient — Upstream native serial client API.
189
+ * @see AsyncTcpModbusClient — Upstream native TCP client API.
181
190
  */
182
- export declare const makeEffectModbusClient: (client: AsyncSerialModbusClient | AsyncTcpModbusClient) => EffectModbusClient;
191
+ export declare const makeEffectModbusClient: (client: AnyModbusClient) => ModbusOperations;
192
+ /**
193
+ * Transport-owned resilience applied to every operation of a client.
194
+ *
195
+ * Assembled by the transport, not by call sites: the guard and the failure
196
+ * report both consult state that belongs to the transport, so a single
197
+ * reconnect serves every client derived from it.
198
+ */
199
+ export interface ClientResilience {
200
+ /** Refuses the operation while the transport's circuit is open. */
201
+ readonly guard: Effect.Effect<void, ModbusError>;
202
+ /** Reports a failure so the transport can decide whether to reconnect. */
203
+ readonly report: (error: ModbusError) => Effect.Effect<void>;
204
+ /** Retry policy applied to each operation, if any. */
205
+ readonly policy?: ModbusRetryPolicy;
206
+ }
207
+ /**
208
+ * A Modbus client with the transport's resilience already applied.
209
+ *
210
+ * Obtained from `transport.withClient(unitId)`. Every operation is guarded by
211
+ * the transport's circuit breaker, reports connection failures to the
212
+ * transport's reconnect supervisor, and carries whatever retry policy the
213
+ * transport or the `withClient` call attached.
214
+ */
215
+ export interface EffectModbusClient extends ModbusOperations {
216
+ /**
217
+ * Returns an equivalent client whose operations use `policy` **instead of**
218
+ * the one this client carries.
219
+ *
220
+ * Replaces rather than composes, so an override cannot accidentally multiply
221
+ * attempt counts. `RetryPolicies.none()` opts a call site out entirely.
222
+ *
223
+ * ```ts
224
+ * yield* client.withRetry(RetryPolicies.none()).writeSingleCoil({ address: 0, value })
225
+ * ```
226
+ */
227
+ withRetry(policy: ModbusRetryPolicy): EffectModbusClient;
228
+ }
229
+ /**
230
+ * Layers transport-owned resilience over a raw {@link ModbusOperations}.
231
+ *
232
+ * Each operation runs as: circuit guard → operation → failure report, with the
233
+ * retry policy (if any) wrapped around the whole sequence. Ordering matters —
234
+ * the guard runs per *attempt*, so once the breaker opens, a retrying operation
235
+ * costs nothing on the wire while it waits for the link to come back.
236
+ *
237
+ * @param operations - The unwrapped function-code surface.
238
+ * @param resilience - Guard, failure report, and optional retry policy.
239
+ * @returns A client with resilience applied to every operation.
240
+ */
241
+ export declare const withResilience: (operations: ModbusOperations, resilience: ClientResilience) => EffectModbusClient;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};