@flux-control/effect-modbus-rs 0.3.0 → 0.3.1

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.
package/README.md CHANGED
@@ -193,7 +193,32 @@ program.pipe(
193
193
  // );
194
194
  ```
195
195
 
196
- It also supports `makeMockTransport` for testing:
196
+ `WasmSerialTransportService` is the browser equivalent. Provide it with `fromRtu` or `fromAscii`, and give it a port handle from `requestSerialPort`.
197
+
198
+ Both abstract tags also have `makeMockTransport` for tests. The option set is the same as the option set of the concrete tags. Thus a test that keeps the framing abstract can also set `retry`, `reconnect`, and the mock fault hooks:
199
+
200
+ ```ts
201
+ import {
202
+ ModbusTimeoutError,
203
+ RetryPolicies,
204
+ SerialTransportService,
205
+ } from '@flux-control/effect-modbus-rs';
206
+
207
+ let attempts = 0;
208
+
209
+ const layer = SerialTransportService.makeMockTransport([device])({
210
+ portPath: '/dev/ttyUSB0',
211
+ baudRate: 9600,
212
+ retry: RetryPolicies.serial(),
213
+ // The first two attempts of each operation fail. The policy retries them.
214
+ fault: () =>
215
+ attempts++ < 2
216
+ ? new ModbusTimeoutError({ message: 'no response', cause: new Error('timeout') })
217
+ : undefined,
218
+ });
219
+ ```
220
+
221
+ See [Testing with mocks](#testing-with-mocks) for the `fault` hook and the `reconnectFault` hook.
197
222
 
198
223
  ## Client API
199
224
 
@@ -475,10 +500,41 @@ const mockLayer = RtuTransportService.makeMockTransport([device])({
475
500
  program.pipe(Effect.provide(mockLayer), Effect.scoped, Effect.runPromise);
476
501
  ```
477
502
 
478
- The mock factory is identical for all three transports; swap `RtuTransportService` for `TcpTransportService` or `AsciiTransportService` and adjust the options shape accordingly each exposes a static `makeMockTransport` method.
503
+ The mock factory is the same for every transport. Each tag has a static `makeMockTransport` method, and each accepts the same options: the open options of that transport, the resilience options (`retry` and `reconnect`), and the two fault hooks below. To change transport, use a different tag and adjust the shape of the open options.
479
504
 
480
505
  See `examples/rtu-mock.ts`, `examples/tcp-mock.ts`, and `examples/ascii-mock.ts` for full walkthroughs covering read, write, multi-device access, and error-case testing.
481
506
 
507
+ ### Fault injection
508
+
509
+ Two mock-only hooks make a policy testable without hardware:
510
+
511
+ | Hook | When it runs | Return value |
512
+ | ---------------- | ------------------------------ | ---------------------------------------------------------------------------- |
513
+ | `fault` | Before every operation attempt | A `ModbusError` fails that attempt. `undefined` lets it through. |
514
+ | `reconnectFault` | Before every reconnect attempt | A `ModbusError` keeps the link down. `undefined` lets the reconnect succeed. |
515
+
516
+ Because `fault` runs before each _attempt_, an error from it is the same as a device that refused that attempt. A retry policy, the backoff, and the circuit breaker therefore behave as they do on a real bus:
517
+
518
+ ```ts
519
+ import {
520
+ ModbusTimeoutError,
521
+ RetryPolicies,
522
+ RtuTransportService,
523
+ } from '@flux-control/effect-modbus-rs';
524
+
525
+ let attempts = 0;
526
+
527
+ const mockLayer = RtuTransportService.makeMockTransport([device])({
528
+ portPath: '/dev/ttyUSB0',
529
+ baudRate: 9600,
530
+ retry: RetryPolicies.serial(),
531
+ fault: () =>
532
+ attempts++ < 2
533
+ ? new ModbusTimeoutError({ message: 'no response', cause: new Error('timeout') })
534
+ : undefined,
535
+ });
536
+ ```
537
+
482
538
  ### Slave device schema
483
539
 
484
540
  | Property | Type | Default |
@@ -1,6 +1,6 @@
1
1
  import { Context, Layer } from 'effect';
2
2
  import { type AsciiTransportOpenOptions } from './AsciiTransportService';
3
- import { type SlaveDeviceDefinitions } from './mocks';
3
+ import { type MockFaultOptions, type SlaveDeviceDefinitions } from './mocks';
4
4
  import { type RtuTransportOpenOptions } from './RtuTransportService';
5
5
  import type { TransportResilienceOptions, TransportServiceApi } from './shared-transport';
6
6
  declare const SerialTransportService_base: Context.TagClass<SerialTransportService, "SerialTransportService", TransportServiceApi>;
@@ -35,12 +35,22 @@ export declare class SerialTransportService extends SerialTransportService_base
35
35
  */
36
36
  static fromRtu(options: RtuTransportOpenOptions & TransportResilienceOptions): Layer.Layer<SerialTransportService>;
37
37
  /**
38
- * Creates a mock {@link Layer} providing {@link SerialTransportService}
39
- * for testing or development.
38
+ * Creates a mock {@link Layer} that provides {@link SerialTransportService}
39
+ * for tests or development.
40
40
  *
41
- * Accepts an array of {@link SlaveDeviceDefinition} describing the
42
- * simulated Modbus slaves and their register/coil maps.
41
+ * The `devices` parameter is an array of {@link SlaveDeviceDefinition}. Each
42
+ * definition gives the coil map and the register map of one simulated slave.
43
+ *
44
+ * The option set is the same as the option set of the concrete tags. Thus a
45
+ * test that keeps the framing abstract can also set `retry`, `reconnect`,
46
+ * `fault`, and `reconnectFault`.
47
+ *
48
+ * @param devices - The slave device definitions for the mock.
49
+ * @returns A function that takes the mock options and gives a scoped
50
+ * {@link Layer} that provides the mock service.
51
+ * @see MockFaultOptions — The `fault` hook and the `reconnectFault` hook.
52
+ * @see makeMockTransport — The mock factory that this method uses.
43
53
  */
44
- static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: (AsciiTransportOpenOptions | RtuTransportOpenOptions) & TransportResilienceOptions) => Layer.Layer<SerialTransportService>;
54
+ static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: (AsciiTransportOpenOptions | RtuTransportOpenOptions) & TransportResilienceOptions & MockFaultOptions) => Layer.Layer<SerialTransportService>;
45
55
  }
46
56
  export {};
@@ -1,6 +1,6 @@
1
1
  import { Context, Layer } from 'effect';
2
- import { type SlaveDeviceDefinitions } from './mocks';
3
- import type { TransportServiceApi } from './shared-transport';
2
+ import { type MockFaultOptions, type SlaveDeviceDefinitions } from './mocks';
3
+ import type { TransportResilienceOptions, TransportServiceApi } from './shared-transport';
4
4
  import { type WasmAsciiTransportOpenOptions } from './WasmAsciiTransportService';
5
5
  import { type WasmRtuTransportOpenOptions } from './WasmRtuTransportService';
6
6
  declare const WasmSerialTransportService_base: Context.TagClass<WasmSerialTransportService, "WasmSerialTransportService", TransportServiceApi>;
@@ -30,19 +30,29 @@ export declare class WasmSerialTransportService extends WasmSerialTransportServi
30
30
  * Creates a {@link Layer} providing {@link WasmSerialTransportService}
31
31
  * backed by an ASCII transport.
32
32
  */
33
- static fromAscii(options: WasmAsciiTransportOpenOptions): Layer.Layer<WasmSerialTransportService>;
33
+ static fromAscii(options: WasmAsciiTransportOpenOptions & TransportResilienceOptions): Layer.Layer<WasmSerialTransportService>;
34
34
  /**
35
35
  * Creates a {@link Layer} providing {@link WasmSerialTransportService}
36
36
  * backed by an RTU transport.
37
37
  */
38
- static fromRtu(options: WasmRtuTransportOpenOptions): Layer.Layer<WasmSerialTransportService>;
38
+ static fromRtu(options: WasmRtuTransportOpenOptions & TransportResilienceOptions): Layer.Layer<WasmSerialTransportService>;
39
39
  /**
40
- * Creates a mock {@link Layer} providing {@link WasmSerialTransportService}
41
- * for testing or development.
40
+ * Creates a mock {@link Layer} that provides {@link WasmSerialTransportService}
41
+ * for tests or development.
42
42
  *
43
- * Accepts an array of {@link SlaveDeviceDefinition} describing the
44
- * simulated Modbus slaves and their register/coil maps.
43
+ * The `devices` parameter is an array of {@link SlaveDeviceDefinition}. Each
44
+ * definition gives the coil map and the register map of one simulated slave.
45
+ *
46
+ * The option set is the same as the option set of the concrete tags. Thus a
47
+ * test that keeps the framing abstract can also set `retry`, `reconnect`,
48
+ * `fault`, and `reconnectFault`.
49
+ *
50
+ * @param devices - The slave device definitions for the mock.
51
+ * @returns A function that takes the mock options and gives a scoped
52
+ * {@link Layer} that provides the mock service.
53
+ * @see MockFaultOptions — The `fault` hook and the `reconnectFault` hook.
54
+ * @see makeMockTransport — The mock factory that this method uses.
45
55
  */
46
- static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmAsciiTransportOpenOptions | WasmRtuTransportOpenOptions) => Layer.Layer<WasmSerialTransportService>;
56
+ static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: (WasmAsciiTransportOpenOptions | WasmRtuTransportOpenOptions) & TransportResilienceOptions & MockFaultOptions) => Layer.Layer<WasmSerialTransportService>;
47
57
  }
48
58
  export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flux-control/effect-modbus-rs",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Type-safe Modbus communication via Effect-TS, wrapping the modbus-rs npm bindings.",
5
5
  "license": "GPL-3.0",
6
6
  "repository": {