@flux-control/effect-modbus-rs 0.8.0 → 0.8.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.
Files changed (33) hide show
  1. package/README.md +498 -831
  2. package/dist/index.d.ts +56 -113
  3. package/dist/index.js +234 -116
  4. package/dist/src/AsciiTransportService.d.ts +15 -14
  5. package/dist/src/RtuTransportService.d.ts +15 -14
  6. package/dist/src/SerialModbusServerService.d.ts +3 -2
  7. package/dist/src/SerialTransportService.d.ts +8 -2
  8. package/dist/src/TcpGatewayService.d.ts +2 -1
  9. package/dist/src/TcpModbusServerService.d.ts +2 -1
  10. package/dist/src/TcpTransportService.d.ts +6 -5
  11. package/dist/src/WasmAsciiTransportService.d.ts +13 -7
  12. package/dist/src/WasmRtuTransportService.d.ts +13 -7
  13. package/dist/src/WasmSerialModbusServerService.d.ts +4 -2
  14. package/dist/src/WasmSerialPort.d.ts +6 -4
  15. package/dist/src/WasmSerialTransportService.d.ts +9 -2
  16. package/dist/src/WasmTcpServerService.d.ts +2 -1
  17. package/dist/src/WasmWsTransportService.d.ts +9 -5
  18. package/dist/src/batching-client.d.ts +61 -28
  19. package/dist/src/connection.d.ts +6 -2
  20. package/dist/src/errors.d.ts +11 -12
  21. package/dist/src/mocks.d.ts +3 -2
  22. package/dist/src/modbus-client.d.ts +14 -10
  23. package/dist/src/read-debouncer.d.ts +8 -22
  24. package/dist/src/register-cache.d.ts +1 -7
  25. package/dist/src/register-plan.d.ts +2 -12
  26. package/dist/src/retry.d.ts +1 -0
  27. package/dist/src/serial-port-path.d.ts +21 -0
  28. package/dist/src/shared-transport.d.ts +25 -5
  29. package/dist/src/span-attributes.d.ts +1 -1
  30. package/dist/src/write-debouncer.d.ts +15 -49
  31. package/dist/test/native-transport.test.d.ts +1 -0
  32. package/dist/test/serial-port-path.test.d.ts +1 -0
  33. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,144 +1,86 @@
1
1
  /**
2
2
  * # @flux-control/effect-modbus-rs
3
3
  *
4
- * Type-safe Modbus communication via Effect-TS, wrapping the `modbus-rs`
5
- * npm bindings (Rust `napi-rs` under the hood).
4
+ * Provides typed Effect services for native and browser Modbus transports and servers.
5
+ * Transport services use `Context.Service` and scoped layers. They open on first
6
+ * client access and close when the provided scope ends.
6
7
  *
7
- * ## Transport services
8
+ * Build the package with `bun run build` before using package imports. The build
9
+ * creates `dist/`, which the package export map uses.
8
10
  *
9
- * - {@link SerialTransportService} — Abstract serial transport (ASCII or RTU).
10
- * - {@link RtuTransportService} — Serial RTU transport (RS-232/485).
11
- * - {@link AsciiTransportService} — Serial ASCII transport.
12
- * - {@link TcpTransportService} — TCP/IP transport (Modbus/TCP).
11
+ * Native transports: {@link SerialTransportService}, {@link RtuTransportService},
12
+ * {@link AsciiTransportService}, and {@link TcpTransportService}. Browser
13
+ * transports: {@link WasmSerialTransportService}, {@link WasmRtuTransportService},
14
+ * {@link WasmAsciiTransportService}, and {@link WasmWsTransportService}.
15
+ * Call {@link requestSerialPort} from a user-gesture handler to request a Web Serial port.
13
16
  *
14
- * ## Browser (WASM) transport services
17
+ * Server layers include {@link serialRtuServerLayer}, {@link serialAsciiServerLayer},
18
+ * {@link tcpServerLayer}, and {@link tcpGatewayLayer}. Browser server layers are
19
+ * experimental upstream surfaces.
15
20
  *
16
- * - {@link WasmSerialTransportService} — Abstract Web Serial transport (ASCII or RTU).
17
- * - {@link WasmRtuTransportService} — Web Serial RTU transport.
18
- * - {@link WasmAsciiTransportService} — Web Serial ASCII transport.
19
- * - {@link WasmWsTransportService} — TCP-over-WebSocket transport (Modbus/TCP via a WS gateway).
20
- * - {@link requestSerialPort} — Requests a Web Serial port handle (user-gesture gated).
21
+ * ## Raw operations and result types
21
22
  *
22
- * ## Server layers
23
+ * `transport.withClient(unitId)` returns a typed client. Register reads return
24
+ * `Uint16Array`; coil and discrete-input reads return `CoilState[]`. Operations
25
+ * fail with a variant of {@link ModbusError}. Use `Effect.catchTags` to handle
26
+ * specific variants. The union has eight variants, including
27
+ * {@link ModbusCircuitOpenError}. The batching declaration error is separate.
23
28
  *
24
- * Run a server layer with {@link Layer.launch} and execute with a runtime:
29
+ * ## Register batching
25
30
  *
26
- * ```ts
27
- * Layer.launch(tcpServerLayer({ host: "0.0.0.0", port: 502, unitId: 1 }, handlers)).pipe(Effect.runPromise)
28
- * ```
29
- *
30
- * - {@link serialRtuServerLayer} — Serial RTU server.
31
- * - {@link serialAsciiServerLayer} — Serial ASCII server.
32
- * - {@link tcpServerLayer} — TCP server.
33
- * - {@link tcpGatewayLayer} — TCP gateway.
34
- * - {@link wasmWsServerLayer} — Browser WS-gateway server (experimental upstream surface).
35
- * - {@link wasmSerialRtuServerLayer} / {@link wasmSerialAsciiServerLayer} — Browser Web Serial servers (experimental).
36
- *
37
- * ## Transaction batching
38
- *
39
- * A caller that derives each register independently produces one transaction per
40
- * register, which is the dominant cost on a half-duplex bus. Three pieces bring
41
- * that count down, and each one is usable without the others.
42
- *
43
- * {@link planWrites} and {@link planReads} pack neighbouring addresses into the
44
- * fewest transactions that cover them. They hold no state and run no I/O:
31
+ * {@link planWrites} and {@link planReads} plan the supplied registers without
32
+ * state or I/O. For example:
45
33
  *
46
34
  * ```ts
47
35
  * planWrites([{ address: 2000, value: 10 }, { address: 2001, value: 20 }]);
48
- * // [{ kind: "multiple", address: 2000, values: Uint16Array [10, 20] }]
36
+ * // One multiple-register write step for addresses 2000 and 2001.
49
37
  * ```
50
38
  *
51
- * A planner only packs what a caller holds at one moment, and a caller with one
52
- * fiber per register never holds two values at once. {@link createWriteDebouncer}
53
- * and {@link createReadDebouncer} are the collection point that gives a planner
54
- * something to pack, holding an operation for a window so the ones that arrive
55
- * near it travel with it. Each caller still awaits its own operation.
56
- *
57
- * {@link createRegisterCache} drops a write whose value the device already holds.
58
- * It records only what this process wrote, so it never answers a read.
59
- *
60
- * `transport.withBatchingClient(unitId, options)` puts the three together. It is
61
- * the sibling of `withClient`, not a replacement for it: `withClient` issues the
62
- * transaction a caller names, and a batching client decides the transactions for
63
- * a caller that names registers instead.
39
+ * {@link createWriteDebouncer} and {@link createReadDebouncer} collect separate
40
+ * calls only when configured with positive windows. Without a window, calls are
41
+ * not debounced. `writeAll` and `readAll` also collect when their windows are
42
+ * positive. Their `Now` variants flush the pending group immediately.
43
+ * Grouping does not make separate application operations atomic. A pending
44
+ * write can be superseded, and the cache can suppress a write based on its
45
+ * recorded value. Therefore, each call is not guaranteed to produce a separate
46
+ * device write. The cache records acknowledged writes from this process; it
47
+ * does not answer reads.
48
+ *
49
+ * `withBatchingClient` combines planning, optional collection, and optional
50
+ * caching. It is separate from `withClient`, which exposes raw Modbus operations.
51
+ * Once a batching client exists for a unit, raw FC06, FC16, and FC23 operations
52
+ * for that unit fail. Raw reads, coil operations, and other operations remain
53
+ * available. A batching client does not extend {@link ModbusOperations}.
64
54
  *
65
55
  * ```ts
66
- * const client = yield* transport.withClient(3); // exact read
67
- * yield* client.readHoldingRegisters({ address: 2000, quantity: 2 });
56
+ * const raw = yield* transport.withClient(3);
57
+ * const registers = yield* raw.readHoldingRegisters({ address: 2000, quantity: 2 });
58
+ * // registers is Uint16Array.
68
59
  *
69
- * const batched = yield* transport.withBatchingClient(3, { // decides the transactions
70
- * debounce: { writes: { window: "250 millis", maxHold: "1 second" } },
60
+ * const batch = yield* transport.withBatchingClient(3, {
61
+ * debounce: { writes: { window: '250 millis', maxHold: '1 second' } },
71
62
  * });
72
- * yield* batched.write({ address: 2000, value: 512 });
73
- * yield* batched.readAll([0x0000, 0x0001, 0x0020]);
74
- * ```
75
- *
76
- * A {@link BatchingModbusClient} deliberately does not extend
77
- * {@link ModbusOperations}: a raw write on the same object would go around the
78
- * cache and around the batch. Once a batching client exists for a unit, raw
79
- * FC06, FC16, and FC23 operations on that unit fail. The raw client remains
80
- * available for exact reads, coils, and other non-register-write operations.
81
- *
82
- * Nothing is debounced unless `debounce` asks for it, matching the rest of this
83
- * package. `writeAll` and `readAll` still plan, so a caller that holds a group of
84
- * registers gets packed transactions with no window at all.
85
- *
86
- * ## Errors
87
- *
88
- * All Modbus operations fail with a {@link ModbusError} discriminated union.
89
- * Use `Effect.catchTags` to handle specific variants:
90
- *
91
- * ```ts
92
- * Effect.catchTags(effect, {
93
- * ModbusTimeoutError: ...,
94
- * ModbusTransportError: ...,
95
- * })
63
+ * yield* batch.write({ address: 2000, value: 512 });
64
+ * yield* batch.readAll([0, 1, 32]);
96
65
  * ```
97
66
  *
98
- * ## Resilience
67
+ * ## Retry and reconnection
99
68
  *
100
- * Nothing retries or reconnects implicitly — a transport behaves exactly as it
101
- * always has until a policy is attached, so timing stays predictable by
102
- * default. Resilience is configured on the **transport**, which owns it for
103
- * every client derived from it:
104
- *
105
- * ```ts
106
- * TcpTransportService.make({
107
- * host, port,
108
- * retry: RetryPolicies.tcp(), // applied to every operation
109
- * reconnect: {}, // supervised reconnect + circuit breaker
110
- * })
111
- * ```
112
- *
113
- * Policies are error-aware: transient failures (timeouts, framing errors, a
114
- * busy device) back off exponentially with jitter (on by default), while
115
- * deterministic ones (illegal address, invalid argument) fail immediately.
116
- *
117
- * Override per client — one bus, several device types — or per operation.
118
- * Both replace the policy rather than composing with it:
69
+ * Retry and reconnection are opt-in. A transport retry policy applies to its
70
+ * clients. Client and operation overrides replace that policy. For example:
119
71
  *
120
72
  * ```ts
73
+ * TcpTransportService.make({ host, port, retry: RetryPolicies.tcp(), reconnect: {} });
121
74
  * const meter = yield* transport.withClient(1, { retry: RetryPolicies.serial() });
122
75
  * yield* meter.withRetry(RetryPolicies.none()).writeSingleCoil({ address: 0, value });
123
76
  * ```
124
77
  *
125
- * With `reconnect` enabled, the transport runs one supervised reconnect for the
126
- * whole application and refuses operations with {@link ModbusCircuitOpenError}
127
- * while the link is down, instead of letting every caller queue requests onto a
128
- * dead bus. Watch {@link ConnectionState} via `transport.connectionState`.
129
- *
130
- * {@link retryModbus} remains for retrying a compound operation — a
131
- * read-modify-write driven as a unit — over a `RetryPolicies.none()` client.
132
- * Note that it **wraps** rather than replaces: unlike the two overrides above,
133
- * it is piped around an effect the client has already wrapped in its own retry,
134
- * so over a policied client the two nest and attempt counts multiply.
135
- *
136
- * Resilience lives at this layer and only at this layer. `modbus-rs`'s own
137
- * transport-level `retryAttempts` / `retryDelayMs` / `retryBackoffStrategy` are
138
- * **not accepted** by any transport constructor here: they retry beneath the
139
- * Effect boundary where neither the policy, the circuit breaker, nor the logs
140
- * can see them, and they reconnect inline, racing the supervisor fiber that
141
- * owns reconnection. See {@link UpstreamRetryOptionKey}.
78
+ * A configured reconnect supervisor owns reconnection for the transport and
79
+ * guards operations while the connection is down. Watch {@link ConnectionState}
80
+ * through `transport.connectionState`. {@link retryModbus} wraps an Effect; use
81
+ * it with a `RetryPolicies.none()` client for a compound operation. It does not
82
+ * replace a client's policy. Upstream transport retry options are excluded;
83
+ * see {@link UpstreamRetryOptionKey}.
142
84
  *
143
85
  * @module @flux-control/effect-modbus-rs
144
86
  */
@@ -169,6 +111,7 @@ export { TcpTransportService } from './src/TcpTransportService';
169
111
  export type { TcpTransportOpenOptions } from './src/TcpTransportService';
170
112
  export { RtuTransportService } from './src/RtuTransportService';
171
113
  export type { RtuTransportOpenOptions } from './src/RtuTransportService';
114
+ export { MAX_SERIAL_PORT_PATH, resolveSerialPortPath } from './src/serial-port-path';
172
115
  export { serialRtuServerLayer, serialAsciiServerLayer } from './src/SerialModbusServerService';
173
116
  export { tcpServerLayer } from './src/TcpModbusServerService';
174
117
  export { tcpGatewayLayer } from './src/TcpGatewayService';