@flux-control/effect-modbus-rs 0.3.1 → 0.4.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 +16 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +124 -131
- package/dist/src/AsciiTransportService.d.ts +31 -22
- package/dist/src/RtuTransportService.d.ts +31 -22
- package/dist/src/SerialTransportService.d.ts +1 -1
- package/dist/src/TcpTransportService.d.ts +31 -22
- package/dist/src/WasmAsciiTransportService.d.ts +30 -21
- package/dist/src/WasmRtuTransportService.d.ts +30 -21
- package/dist/src/WasmSerialPort.d.ts +2 -2
- package/dist/src/WasmSerialTransportService.d.ts +1 -1
- package/dist/src/WasmWsTransportService.d.ts +30 -21
- package/dist/src/connection.d.ts +34 -10
- package/dist/src/mocks.d.ts +41 -57
- package/dist/src/retry.d.ts +11 -8
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
For the complete API reference, see the [GitHub Pages documentation](https://flux-control-solutions.github.io/Effect-modbus-rs/).
|
|
6
6
|
|
|
7
|
-
Provides scoped [`
|
|
7
|
+
Provides scoped [`Context.Service`](https://effect.website) constructors for RTU (serial), TCP, and ASCII Modbus transports. Clients expose a typed `Effect`-based API for all standard Modbus function codes.
|
|
8
8
|
|
|
9
9
|
> This project is under active development. Its API may change before the 1.0 release.
|
|
10
10
|
|
|
@@ -44,7 +44,7 @@ program.pipe(
|
|
|
44
44
|
ModbusInvalidArgumentError: (err) => Console.log(`Invalid argument: ${err.message}`),
|
|
45
45
|
}),
|
|
46
46
|
Effect.catchAll((err) => Console.log(`Unhandled error: ${err.message}`)),
|
|
47
|
-
Effect.provide(RtuTransportService.
|
|
47
|
+
Effect.provide(RtuTransportService.make({ portPath: '/dev/ttyUSB0', baudRate: 9600 })),
|
|
48
48
|
Effect.scoped,
|
|
49
49
|
Effect.runPromise,
|
|
50
50
|
);
|
|
@@ -64,7 +64,7 @@ const program = Effect.gen(function* () {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
program.pipe(
|
|
67
|
-
Effect.provide(TcpTransportService.
|
|
67
|
+
Effect.provide(TcpTransportService.make({ host: '192.168.1.100', port: 502 })),
|
|
68
68
|
Effect.scoped,
|
|
69
69
|
Effect.runPromise,
|
|
70
70
|
);
|
|
@@ -87,7 +87,7 @@ const program = Effect.gen(function* () {
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
program.pipe(
|
|
90
|
-
Effect.provide(AsciiTransportService.
|
|
90
|
+
Effect.provide(AsciiTransportService.make({ portPath: '/dev/ttyUSB0', baudRate: 9600 })),
|
|
91
91
|
Effect.scoped,
|
|
92
92
|
Effect.runPromise,
|
|
93
93
|
);
|
|
@@ -112,7 +112,7 @@ const program = Effect.gen(function* () {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
program.pipe(
|
|
115
|
-
Effect.provide(WasmWsTransportService.
|
|
115
|
+
Effect.provide(WasmWsTransportService.make({ wsUrl: 'ws://localhost:8080' })),
|
|
116
116
|
Effect.scoped,
|
|
117
117
|
Effect.runPromise,
|
|
118
118
|
);
|
|
@@ -129,7 +129,7 @@ connectButton.addEventListener('click', () => {
|
|
|
129
129
|
Effect.gen(function* () {
|
|
130
130
|
const port = yield* requestSerialPort();
|
|
131
131
|
yield* program.pipe(
|
|
132
|
-
Effect.provide(WasmRtuTransportService.
|
|
132
|
+
Effect.provide(WasmRtuTransportService.make({ port, baudRate: 19200 })),
|
|
133
133
|
Effect.scoped,
|
|
134
134
|
);
|
|
135
135
|
}),
|
|
@@ -150,7 +150,7 @@ Not demonstrated in `examples/wasm/` (see that app's README) — the same `impor
|
|
|
150
150
|
|
|
151
151
|
## Transports
|
|
152
152
|
|
|
153
|
-
Each transport is a scoped `
|
|
153
|
+
Each transport is a scoped `Context.Service`. You provide it with `Effect.provide`, and the connection is opened on service access and closed when the scope ends.
|
|
154
154
|
|
|
155
155
|
| Service | Options | Connection |
|
|
156
156
|
| ------------------------------------- | ------------------------------ | ----------------------------- |
|
|
@@ -279,7 +279,7 @@ Handle with `Effect.catchTags`. The `ModbusError` union type covers all seven va
|
|
|
279
279
|
Resilience belongs to the **transport**, not to call sites. Attach a policy where the transport is created and every client derived from it carries it:
|
|
280
280
|
|
|
281
281
|
```ts
|
|
282
|
-
const layer = TcpTransportService.
|
|
282
|
+
const layer = TcpTransportService.make({
|
|
283
283
|
host: '192.168.1.50',
|
|
284
284
|
port: 502,
|
|
285
285
|
retry: RetryPolicies.tcp(), // applied to every operation
|
|
@@ -393,7 +393,7 @@ So `RetryPolicies.tcp()` waits roughly 80–120 ms before its first retry, not e
|
|
|
393
393
|
Passing `reconnect` hands reconnection to a supervisor fiber owned by the transport — **one reconnect for the whole application**, however many fibers were in flight when the link dropped:
|
|
394
394
|
|
|
395
395
|
```ts
|
|
396
|
-
TcpTransportService.
|
|
396
|
+
TcpTransportService.make({
|
|
397
397
|
host,
|
|
398
398
|
port,
|
|
399
399
|
reconnect: {
|
|
@@ -445,7 +445,7 @@ See `examples/retry-policies.ts` for a runnable walkthrough.
|
|
|
445
445
|
`modbus-rs` exposes `retryAttempts`, `retryDelayMs`, and `retryBackoffStrategy` on its transport options. This package removes all three from every transport constructor, so setting one is a compile error rather than a documented hazard:
|
|
446
446
|
|
|
447
447
|
```ts
|
|
448
|
-
TcpTransportService.
|
|
448
|
+
TcpTransportService.make({ host, port, retryAttempts: 3 });
|
|
449
449
|
// ^^^^^^^^^^^^^ Object literal may only specify
|
|
450
450
|
// known properties, and 'retryAttempts' does not
|
|
451
451
|
// exist in type 'TcpTransportOpenOptions & …'
|
|
@@ -568,17 +568,17 @@ src/
|
|
|
568
568
|
connection.ts — Connection state machine, reconnect supervisor, circuit breaker
|
|
569
569
|
retry.ts — Opt-in retry policies (backoff, jitter, per-error rules)
|
|
570
570
|
shared-transport.ts — Generic scoped transport lifecycle management, WithoutUpstreamRetry
|
|
571
|
-
RtuTransportService.ts — Scoped
|
|
572
|
-
TcpTransportService.ts — Scoped
|
|
573
|
-
AsciiTransportService.ts — Scoped
|
|
571
|
+
RtuTransportService.ts — Scoped Context.Service wrapping AsyncRtuTransport
|
|
572
|
+
TcpTransportService.ts — Scoped Context.Service wrapping AsyncTcpTransport
|
|
573
|
+
AsciiTransportService.ts — Scoped Context.Service wrapping AsyncAsciiTransport
|
|
574
574
|
SerialTransportService.ts — Abstract serial transport (RTU/ASCII) tag
|
|
575
575
|
TcpModbusServerService.ts — tcpServerLayer
|
|
576
576
|
SerialModbusServerService.ts — serialRtuServerLayer / serialAsciiServerLayer
|
|
577
577
|
TcpGatewayService.ts — tcpGatewayLayer
|
|
578
578
|
WasmSerialPort.ts — requestSerialPort() Effect helper (browser, user-gesture gated)
|
|
579
|
-
WasmWsTransportService.ts — Scoped
|
|
580
|
-
WasmRtuTransportService.ts — Scoped
|
|
581
|
-
WasmAsciiTransportService.ts — Scoped
|
|
579
|
+
WasmWsTransportService.ts — Scoped Context.Service wrapping WasmWsTransport (browser, WS gateway)
|
|
580
|
+
WasmRtuTransportService.ts — Scoped Context.Service wrapping WasmRtuTransport (browser, Web Serial RTU)
|
|
581
|
+
WasmAsciiTransportService.ts — Scoped Context.Service wrapping WasmAsciiTransport (browser, Web Serial ASCII)
|
|
582
582
|
WasmSerialTransportService.ts — Abstract browser serial transport (RTU/ASCII) tag
|
|
583
583
|
WasmTcpServerService.ts — wasmWsServerLayer (experimental)
|
|
584
584
|
WasmSerialModbusServerService.ts — wasmSerialRtuServerLayer / wasmSerialAsciiServerLayer (experimental)
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var toModbusError = (cause) => {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
// src/retry.ts
|
|
58
|
-
import { Duration, Effect, Schedule } from "effect";
|
|
58
|
+
import { Duration, Effect, Random, Schedule } from "effect";
|
|
59
59
|
var retryableExceptionCodes = [5, 6, 10, 11];
|
|
60
60
|
var defaultRetryableTags = {
|
|
61
61
|
ModbusTimeoutError: true,
|
|
@@ -68,7 +68,7 @@ var defaultRetryableTags = {
|
|
|
68
68
|
ModbusInternalError: false
|
|
69
69
|
};
|
|
70
70
|
var allTags = Object.keys(defaultRetryableTags);
|
|
71
|
-
var toMillis = (input) => Duration.toMillis(Duration.
|
|
71
|
+
var toMillis = (input) => Duration.toMillis(Duration.fromInputUnsafe(input));
|
|
72
72
|
var resolveRetryableTags = (options) => {
|
|
73
73
|
const retryable = {};
|
|
74
74
|
for (const tag of allTags) {
|
|
@@ -112,10 +112,14 @@ var makeRetryPolicy = (options = {}) => {
|
|
|
112
112
|
const { baseMs, factor, maxMs } = delays[error._tag];
|
|
113
113
|
return Duration.millis(Math.min(maxMs, baseMs * factor ** retryIndex));
|
|
114
114
|
};
|
|
115
|
-
const base = Schedule.recurs(options.maxRetries ?? 3).pipe(Schedule.
|
|
115
|
+
const base = Schedule.recurs(options.maxRetries ?? 3).pipe(Schedule.setInputType(), Schedule.modifyDelay((metadata) => Effect.succeed(delayFor(metadata.input, metadata.attempt - 1))), Schedule.while((metadata) => Effect.succeed(isRetryable(metadata.input))));
|
|
116
116
|
const jitter = options.jitter ?? true;
|
|
117
|
-
const jittered = jitter === false ? base :
|
|
118
|
-
|
|
117
|
+
const jittered = jitter === false ? base : jitter === true ? Schedule.jittered(base) : Schedule.modifyDelay(base, (metadata) => Effect.map(Random.next, (r) => {
|
|
118
|
+
const min = jitter.min ?? 0.8;
|
|
119
|
+
const max = jitter.max ?? 1.2;
|
|
120
|
+
return Duration.millis(Duration.toMillis(metadata.duration) * (min + r * (max - min)));
|
|
121
|
+
}));
|
|
122
|
+
const schedule = options.maxElapsed === undefined ? jittered : Schedule.upTo(jittered, { duration: options.maxElapsed });
|
|
119
123
|
return { schedule, isRetryable };
|
|
120
124
|
};
|
|
121
125
|
var RetryPolicies = {
|
|
@@ -146,7 +150,7 @@ var RetryPolicies = {
|
|
|
146
150
|
};
|
|
147
151
|
var retryModbus = (policy) => (self) => Effect.retry(self, policy.schedule);
|
|
148
152
|
// src/connection.ts
|
|
149
|
-
import { Data as Data2, Duration as Duration2, Effect as Effect2,
|
|
153
|
+
import { Data as Data2, Duration as Duration2, Effect as Effect2, Result, SubscriptionRef } from "effect";
|
|
150
154
|
var ConnectionState = Data2.taggedEnum();
|
|
151
155
|
var defaultReconnectPolicy = makeRetryPolicy({
|
|
152
156
|
maxRetries: 5,
|
|
@@ -162,7 +166,7 @@ var resolveReconnect = (options) => {
|
|
|
162
166
|
const triggerTags = options.triggerOn ?? defaultTriggers;
|
|
163
167
|
return {
|
|
164
168
|
policy: options.policy ?? defaultReconnectPolicy,
|
|
165
|
-
resetAfter: Duration2.
|
|
169
|
+
resetAfter: Duration2.fromInputUnsafe(options.resetAfter ?? "30 seconds"),
|
|
166
170
|
triggers: (error) => triggerTags.includes(error._tag)
|
|
167
171
|
};
|
|
168
172
|
};
|
|
@@ -181,21 +185,29 @@ var guardCircuit = (state) => Effect2.flatMap(SubscriptionRef.get(state), (curre
|
|
|
181
185
|
var superviseReconnect = (reconnect, state, resolved) => Effect2.gen(function* () {
|
|
182
186
|
while (true) {
|
|
183
187
|
const attempt = reconnect.pipe(Effect2.tapError(() => SubscriptionRef.update(state, (current) => ConnectionState.$is("Reconnecting")(current) ? ConnectionState.Reconnecting({ attempt: current.attempt + 1 }) : current)), retryModbus(resolved.policy));
|
|
184
|
-
const result = yield* Effect2.
|
|
185
|
-
if (
|
|
188
|
+
const result = yield* Effect2.result(attempt);
|
|
189
|
+
if (Result.isSuccess(result)) {
|
|
186
190
|
yield* SubscriptionRef.set(state, ConnectionState.Connected());
|
|
187
191
|
return;
|
|
188
192
|
}
|
|
189
|
-
yield* Effect2.logDebug(`Reconnect attempts exhausted: ${result.
|
|
190
|
-
yield* SubscriptionRef.set(state, ConnectionState.Down({ cause: result.
|
|
193
|
+
yield* Effect2.logDebug(`Reconnect attempts exhausted: ${result.failure.message}`);
|
|
194
|
+
yield* SubscriptionRef.set(state, ConnectionState.Down({ cause: result.failure }));
|
|
191
195
|
yield* Effect2.sleep(resolved.resetAfter);
|
|
192
196
|
const probe = yield* SubscriptionRef.modify(state, (current) => ConnectionState.$is("Down")(current) ? [true, ConnectionState.Reconnecting({ attempt: 0 })] : [false, current]);
|
|
193
197
|
if (!probe)
|
|
194
198
|
return;
|
|
195
199
|
}
|
|
196
200
|
});
|
|
201
|
+
var claimReconnect = (reconnect, state, resolved, scope, onClaim) => Effect2.gen(function* () {
|
|
202
|
+
const claimed = yield* SubscriptionRef.modify(state, (current) => ConnectionState.$is("Connected")(current) ? [true, ConnectionState.Reconnecting({ attempt: 0 })] : [false, current]);
|
|
203
|
+
if (!claimed)
|
|
204
|
+
return;
|
|
205
|
+
if (onClaim)
|
|
206
|
+
yield* onClaim;
|
|
207
|
+
yield* Effect2.forkIn(superviseReconnect(reconnect, state, resolved), scope);
|
|
208
|
+
});
|
|
197
209
|
// src/AsciiTransportService.ts
|
|
198
|
-
import {
|
|
210
|
+
import { Context, Layer } from "effect";
|
|
199
211
|
|
|
200
212
|
// src/mocks.ts
|
|
201
213
|
import { Effect as Effect4, Schema, SubscriptionRef as SubscriptionRef2 } from "effect";
|
|
@@ -228,7 +240,7 @@ var makeEffectModbusClient = (client) => ({
|
|
|
228
240
|
});
|
|
229
241
|
var withResilience = (operations, resilience) => {
|
|
230
242
|
const run = (operation) => {
|
|
231
|
-
const attempt = Effect3.
|
|
243
|
+
const attempt = Effect3.andThen(resilience.guard, Effect3.suspend(operation)).pipe(Effect3.tapError((error) => resilience.report(error)));
|
|
232
244
|
return resilience.policy ? retryModbus(resilience.policy)(attempt) : attempt;
|
|
233
245
|
};
|
|
234
246
|
return {
|
|
@@ -266,18 +278,10 @@ var RegisterDefinition = Schema.Struct({
|
|
|
266
278
|
});
|
|
267
279
|
var SlaveDeviceDefinition = Schema.Struct({
|
|
268
280
|
unitId: Schema.Number,
|
|
269
|
-
coils: Schema.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
default: () => []
|
|
274
|
-
}),
|
|
275
|
-
holdingRegisters: Schema.optionalWith(Schema.Array(RegisterDefinition), {
|
|
276
|
-
default: () => []
|
|
277
|
-
}),
|
|
278
|
-
inputRegisters: Schema.optionalWith(Schema.Array(RegisterDefinition), {
|
|
279
|
-
default: () => []
|
|
280
|
-
})
|
|
281
|
+
coils: Schema.Array(CoilDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
282
|
+
discreteInputs: Schema.Array(DiscreteInputDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
283
|
+
holdingRegisters: Schema.Array(RegisterDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
284
|
+
inputRegisters: Schema.Array(RegisterDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([])))
|
|
281
285
|
});
|
|
282
286
|
var SlaveDeviceDefinitions = Schema.Array(SlaveDeviceDefinition);
|
|
283
287
|
var buildCoils = (defs) => {
|
|
@@ -458,21 +462,16 @@ var makeMockTransport = (devices) => {
|
|
|
458
462
|
const connectionState = yield* SubscriptionRef2.make(ConnectionState.Connected());
|
|
459
463
|
const supervised = options.reconnect ? resolveReconnect(options.reconnect) : null;
|
|
460
464
|
const serviceScope = yield* Effect4.scope;
|
|
461
|
-
const reconnectOnce = Effect4.
|
|
465
|
+
const reconnectOnce = Effect4.andThen(Effect4.logDebug("Mock: reconnecting"), Effect4.suspend(() => {
|
|
462
466
|
const injected = options.reconnectFault?.();
|
|
463
467
|
return injected ? Effect4.fail(injected) : Effect4.void;
|
|
464
468
|
}));
|
|
465
469
|
const report = (error) => {
|
|
466
470
|
if (!supervised || !supervised.triggers(error))
|
|
467
471
|
return Effect4.void;
|
|
468
|
-
return
|
|
469
|
-
const claimed = yield* SubscriptionRef2.modify(connectionState, (current) => ConnectionState.$is("Connected")(current) ? [true, ConnectionState.Reconnecting({ attempt: 0 })] : [false, current]);
|
|
470
|
-
if (!claimed)
|
|
471
|
-
return;
|
|
472
|
-
yield* Effect4.forkIn(superviseReconnect(reconnectOnce, connectionState, supervised), serviceScope);
|
|
473
|
-
});
|
|
472
|
+
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope);
|
|
474
473
|
};
|
|
475
|
-
const guard = Effect4.
|
|
474
|
+
const guard = Effect4.andThen(supervised ? guardCircuit(connectionState) : Effect4.void, Effect4.suspend(() => {
|
|
476
475
|
const injected = options.fault?.();
|
|
477
476
|
return injected ? Effect4.fail(injected) : Effect4.void;
|
|
478
477
|
}));
|
|
@@ -494,8 +493,8 @@ var makeMockTransport = (devices) => {
|
|
|
494
493
|
}),
|
|
495
494
|
setRequestTimeout: (_timeoutMs) => Effect4.void,
|
|
496
495
|
clearRequestTimeout: () => Effect4.void,
|
|
497
|
-
reconnect: () => Effect4.
|
|
498
|
-
close: () => Effect4.
|
|
496
|
+
reconnect: () => Effect4.andThen(reconnectOnce, SubscriptionRef2.set(connectionState, ConnectionState.Connected())),
|
|
497
|
+
close: () => Effect4.andThen(Effect4.logDebug("Mock: closing transport"), SubscriptionRef2.set(connectionState, ConnectionState.Disconnected())),
|
|
499
498
|
hasPendingRequests: () => false
|
|
500
499
|
};
|
|
501
500
|
});
|
|
@@ -511,7 +510,7 @@ var singleFlight = (inFlight, work) => Effect5.uninterruptibleMask((restore) =>
|
|
|
511
510
|
onNone: () => [[fresh, true], Option.some(fresh)]
|
|
512
511
|
}));
|
|
513
512
|
if (isLeader) {
|
|
514
|
-
yield* Effect5.
|
|
513
|
+
yield* Effect5.forkDetach(Effect5.interruptible(work).pipe(Effect5.onExit((exit) => Effect5.andThen(Ref.set(inFlight, Option.none()), Deferred.done(deferred, exit)))));
|
|
515
514
|
}
|
|
516
515
|
return yield* restore(Deferred.await(deferred));
|
|
517
516
|
}));
|
|
@@ -533,11 +532,11 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
533
532
|
cause: new Error("Transport has been closed"),
|
|
534
533
|
message: "Transport has been closed"
|
|
535
534
|
});
|
|
536
|
-
const closeOrphan = (t) => Effect5.
|
|
535
|
+
const closeOrphan = (t) => Effect5.forkDetach(Effect5.ignore(Effect5.tryPromise(() => t.close())));
|
|
537
536
|
const openTransport = Effect5.tryPromise({
|
|
538
537
|
try: () => openMethod(TC, openOptions),
|
|
539
538
|
catch: (error) => toModbusError(error)
|
|
540
|
-
}).pipe(Effect5.tap((t) => closed ? closeOrphan(t) : Effect5.
|
|
539
|
+
}).pipe(Effect5.tap((t) => closed ? closeOrphan(t) : Effect5.andThen(Effect5.sync(() => {
|
|
541
540
|
transport = t;
|
|
542
541
|
}), SubscriptionRef3.set(connectionState, ConnectionState.Connected()))));
|
|
543
542
|
const ensureOpen = Effect5.fnUntraced(function* () {
|
|
@@ -557,7 +556,7 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
557
556
|
const t = transport;
|
|
558
557
|
if (!t)
|
|
559
558
|
return SubscriptionRef3.set(connectionState, ConnectionState.Disconnected());
|
|
560
|
-
return Effect5.andThen(Effect5.logDebug(`Closing ${serviceName}`), Effect5.
|
|
559
|
+
return Effect5.andThen(Effect5.logDebug(`Closing ${serviceName}`), Effect5.andThen(Effect5.promise(() => t.close()), SubscriptionRef3.set(connectionState, ConnectionState.Disconnected())));
|
|
561
560
|
});
|
|
562
561
|
const notConnectedMsg = "Transport is not connected. Call withClient() first.";
|
|
563
562
|
const reconnectOnce = Effect5.suspend(() => {
|
|
@@ -572,13 +571,7 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
572
571
|
const report = (error) => {
|
|
573
572
|
if (!supervised || closed || !supervised.triggers(error))
|
|
574
573
|
return Effect5.void;
|
|
575
|
-
return Effect5.
|
|
576
|
-
const claimed = yield* SubscriptionRef3.modify(connectionState, (current) => ConnectionState.$is("Connected")(current) ? [true, ConnectionState.Reconnecting({ attempt: 0 })] : [false, current]);
|
|
577
|
-
if (!claimed)
|
|
578
|
-
return;
|
|
579
|
-
yield* Effect5.logDebug(`${serviceName}: reconnecting after ${error.message}`);
|
|
580
|
-
yield* Effect5.forkIn(superviseReconnect(reconnectOnce, connectionState, supervised), serviceScope);
|
|
581
|
-
});
|
|
574
|
+
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope, Effect5.logDebug(`${serviceName}: reconnecting after ${error.message}`));
|
|
582
575
|
};
|
|
583
576
|
const resilience = {
|
|
584
577
|
guard: supervised ? guardCircuit(connectionState) : Effect5.void,
|
|
@@ -665,54 +658,54 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
665
658
|
}
|
|
666
659
|
|
|
667
660
|
// src/AsciiTransportService.ts
|
|
668
|
-
class AsciiTransportService extends
|
|
669
|
-
|
|
670
|
-
|
|
661
|
+
class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
662
|
+
static makeScoped = makeTransportScoped("AsyncAsciiTransport", (TC, options) => TC.open(options), "AsciiTransportService");
|
|
663
|
+
static make = (options) => Layer.effect(AsciiTransportService, AsciiTransportService.makeScoped(options));
|
|
671
664
|
static makeMockTransport = (devices) => {
|
|
672
665
|
const factory = makeMockTransport(devices);
|
|
673
|
-
return (options) => Layer.
|
|
666
|
+
return (options) => Layer.effect(AsciiTransportService, factory(options));
|
|
674
667
|
};
|
|
675
668
|
}
|
|
676
669
|
// src/SerialTransportService.ts
|
|
677
|
-
import { Context, Layer as Layer3 } from "effect";
|
|
670
|
+
import { Context as Context3, Layer as Layer3 } from "effect";
|
|
678
671
|
|
|
679
672
|
// src/RtuTransportService.ts
|
|
680
|
-
import {
|
|
681
|
-
class RtuTransportService extends
|
|
682
|
-
|
|
683
|
-
|
|
673
|
+
import { Context as Context2, Layer as Layer2 } from "effect";
|
|
674
|
+
class RtuTransportService extends Context2.Service()("RtuTransportService") {
|
|
675
|
+
static makeScoped = makeTransportScoped("AsyncRtuTransport", (TC, options) => TC.open(options), "RtuTransportService");
|
|
676
|
+
static make = (options) => Layer2.effect(RtuTransportService, RtuTransportService.makeScoped(options));
|
|
684
677
|
static makeMockTransport = (devices) => {
|
|
685
678
|
const factory = makeMockTransport(devices);
|
|
686
|
-
return (options) => Layer2.
|
|
679
|
+
return (options) => Layer2.effect(RtuTransportService, factory(options));
|
|
687
680
|
};
|
|
688
681
|
}
|
|
689
682
|
|
|
690
683
|
// src/SerialTransportService.ts
|
|
691
|
-
class SerialTransportService extends
|
|
684
|
+
class SerialTransportService extends Context3.Service()("SerialTransportService") {
|
|
692
685
|
static fromAscii(options) {
|
|
693
|
-
return Layer3.
|
|
686
|
+
return Layer3.flatMap(AsciiTransportService.make(options), (context) => Layer3.succeed(SerialTransportService, Context3.get(context, AsciiTransportService)));
|
|
694
687
|
}
|
|
695
688
|
static fromRtu(options) {
|
|
696
|
-
return Layer3.
|
|
689
|
+
return Layer3.flatMap(RtuTransportService.make(options), (context) => Layer3.succeed(SerialTransportService, Context3.get(context, RtuTransportService)));
|
|
697
690
|
}
|
|
698
691
|
static makeMockTransport = (devices) => {
|
|
699
692
|
const factory = makeMockTransport(devices);
|
|
700
|
-
return (options) => Layer3.
|
|
693
|
+
return (options) => Layer3.effect(SerialTransportService, factory(options));
|
|
701
694
|
};
|
|
702
695
|
}
|
|
703
696
|
// src/TcpTransportService.ts
|
|
704
|
-
import {
|
|
705
|
-
class TcpTransportService extends
|
|
706
|
-
|
|
707
|
-
|
|
697
|
+
import { Context as Context4, Layer as Layer4 } from "effect";
|
|
698
|
+
class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
699
|
+
static makeScoped = makeTransportScoped("AsyncTcpTransport", (TC, options) => TC.connect(options), "TcpTransportService");
|
|
700
|
+
static make = (options) => Layer4.effect(TcpTransportService, TcpTransportService.makeScoped(options));
|
|
708
701
|
static makeMockTransport = (devices) => {
|
|
709
702
|
const factory = makeMockTransport(devices);
|
|
710
|
-
return (options) => Layer4.
|
|
703
|
+
return (options) => Layer4.effect(TcpTransportService, factory(options));
|
|
711
704
|
};
|
|
712
705
|
}
|
|
713
706
|
// src/SerialModbusServerService.ts
|
|
714
707
|
import { Effect as Effect9, Layer as Layer5 } from "effect";
|
|
715
|
-
var serialRtuServerLayer = (options, handlers) => Layer5.
|
|
708
|
+
var serialRtuServerLayer = (options, handlers) => Layer5.effectDiscard(Effect9.gen(function* () {
|
|
716
709
|
const { AsyncSerialModbusServer } = yield* Effect9.promise(() => import("modbus-rs"));
|
|
717
710
|
const server = yield* Effect9.tryPromise({
|
|
718
711
|
try: () => AsyncSerialModbusServer.bindRtu(options, handlers),
|
|
@@ -722,9 +715,9 @@ var serialRtuServerLayer = (options, handlers) => Layer5.scopedDiscard(Effect9.g
|
|
|
722
715
|
yield* Effect9.addFinalizer(() => Effect9.logDebug("Serial RTU server shutting down").pipe(Effect9.andThen(Effect9.tryPromise({
|
|
723
716
|
try: () => server.shutdown(),
|
|
724
717
|
catch: (error) => toModbusError(error)
|
|
725
|
-
})), Effect9.
|
|
718
|
+
})), Effect9.catch(() => Effect9.void)));
|
|
726
719
|
}));
|
|
727
|
-
var serialAsciiServerLayer = (options, handlers) => Layer5.
|
|
720
|
+
var serialAsciiServerLayer = (options, handlers) => Layer5.effectDiscard(Effect9.gen(function* () {
|
|
728
721
|
const { AsyncSerialModbusServer } = yield* Effect9.promise(() => import("modbus-rs"));
|
|
729
722
|
const server = yield* Effect9.tryPromise({
|
|
730
723
|
try: () => AsyncSerialModbusServer.bindAscii(options, handlers),
|
|
@@ -734,11 +727,11 @@ var serialAsciiServerLayer = (options, handlers) => Layer5.scopedDiscard(Effect9
|
|
|
734
727
|
yield* Effect9.addFinalizer(() => Effect9.logDebug("Serial ASCII server shutting down").pipe(Effect9.andThen(Effect9.tryPromise({
|
|
735
728
|
try: () => server.shutdown(),
|
|
736
729
|
catch: (error) => toModbusError(error)
|
|
737
|
-
})), Effect9.
|
|
730
|
+
})), Effect9.catch(() => Effect9.void)));
|
|
738
731
|
}));
|
|
739
732
|
// src/TcpModbusServerService.ts
|
|
740
733
|
import { Effect as Effect10, Layer as Layer6 } from "effect";
|
|
741
|
-
var tcpServerLayer = (options, handlers) => Layer6.
|
|
734
|
+
var tcpServerLayer = (options, handlers) => Layer6.effectDiscard(Effect10.gen(function* () {
|
|
742
735
|
const { AsyncTcpModbusServer } = yield* Effect10.promise(() => import("modbus-rs"));
|
|
743
736
|
const server = yield* Effect10.tryPromise({
|
|
744
737
|
try: () => AsyncTcpModbusServer.bind(options, handlers),
|
|
@@ -748,11 +741,11 @@ var tcpServerLayer = (options, handlers) => Layer6.scopedDiscard(Effect10.gen(fu
|
|
|
748
741
|
yield* Effect10.addFinalizer(() => Effect10.logDebug("TCP server shutting down").pipe(Effect10.andThen(Effect10.tryPromise({
|
|
749
742
|
try: () => server.shutdown(),
|
|
750
743
|
catch: (error) => toModbusError(error)
|
|
751
|
-
})), Effect10.
|
|
744
|
+
})), Effect10.catch(() => Effect10.void)));
|
|
752
745
|
}));
|
|
753
746
|
// src/TcpGatewayService.ts
|
|
754
747
|
import { Effect as Effect11, Layer as Layer7 } from "effect";
|
|
755
|
-
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.
|
|
748
|
+
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.effectDiscard(Effect11.gen(function* () {
|
|
756
749
|
const { AsyncTcpGateway } = yield* Effect11.promise(() => import("modbus-rs"));
|
|
757
750
|
const gateway = yield* Effect11.tryPromise({
|
|
758
751
|
try: () => AsyncTcpGateway.bind(options, gatewayConfig),
|
|
@@ -762,50 +755,50 @@ var tcpGatewayLayer = (options, gatewayConfig) => Layer7.scopedDiscard(Effect11.
|
|
|
762
755
|
yield* Effect11.addFinalizer(() => Effect11.logDebug("TCP gateway shutting down").pipe(Effect11.andThen(Effect11.tryPromise({
|
|
763
756
|
try: () => gateway.shutdown(),
|
|
764
757
|
catch: (error) => toModbusError(error)
|
|
765
|
-
})), Effect11.
|
|
758
|
+
})), Effect11.catch(() => Effect11.void)));
|
|
766
759
|
}));
|
|
767
760
|
// src/WasmWsTransportService.ts
|
|
768
|
-
import {
|
|
769
|
-
class WasmWsTransportService extends
|
|
770
|
-
|
|
771
|
-
|
|
761
|
+
import { Context as Context5, Layer as Layer8 } from "effect";
|
|
762
|
+
class WasmWsTransportService extends Context5.Service()("WasmWsTransportService") {
|
|
763
|
+
static makeScoped = makeTransportScoped("WasmWsTransport", (TC, options) => TC.connect(options), "WasmWsTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
764
|
+
static make = (options) => Layer8.effect(WasmWsTransportService, WasmWsTransportService.makeScoped(options));
|
|
772
765
|
static makeMockTransport = (devices) => {
|
|
773
766
|
const factory = makeMockTransport(devices);
|
|
774
|
-
return (options) => Layer8.
|
|
767
|
+
return (options) => Layer8.effect(WasmWsTransportService, factory(options));
|
|
775
768
|
};
|
|
776
769
|
}
|
|
777
770
|
// src/WasmRtuTransportService.ts
|
|
778
|
-
import {
|
|
779
|
-
class WasmRtuTransportService extends
|
|
780
|
-
|
|
781
|
-
|
|
771
|
+
import { Context as Context6, Layer as Layer9 } from "effect";
|
|
772
|
+
class WasmRtuTransportService extends Context6.Service()("WasmRtuTransportService") {
|
|
773
|
+
static makeScoped = makeTransportScoped("WasmRtuTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmRtuTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
774
|
+
static make = (options) => Layer9.effect(WasmRtuTransportService, WasmRtuTransportService.makeScoped(options));
|
|
782
775
|
static makeMockTransport = (devices) => {
|
|
783
776
|
const factory = makeMockTransport(devices);
|
|
784
|
-
return (options) => Layer9.
|
|
777
|
+
return (options) => Layer9.effect(WasmRtuTransportService, factory(options));
|
|
785
778
|
};
|
|
786
779
|
}
|
|
787
780
|
// src/WasmAsciiTransportService.ts
|
|
788
|
-
import {
|
|
789
|
-
class WasmAsciiTransportService extends
|
|
790
|
-
|
|
791
|
-
|
|
781
|
+
import { Context as Context7, Layer as Layer10 } from "effect";
|
|
782
|
+
class WasmAsciiTransportService extends Context7.Service()("WasmAsciiTransportService") {
|
|
783
|
+
static makeScoped = makeTransportScoped("WasmAsciiTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmAsciiTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
784
|
+
static make = (options) => Layer10.effect(WasmAsciiTransportService, WasmAsciiTransportService.makeScoped(options));
|
|
792
785
|
static makeMockTransport = (devices) => {
|
|
793
786
|
const factory = makeMockTransport(devices);
|
|
794
|
-
return (options) => Layer10.
|
|
787
|
+
return (options) => Layer10.effect(WasmAsciiTransportService, factory(options));
|
|
795
788
|
};
|
|
796
789
|
}
|
|
797
790
|
// src/WasmSerialTransportService.ts
|
|
798
|
-
import { Context as
|
|
799
|
-
class WasmSerialTransportService extends
|
|
791
|
+
import { Context as Context8, Layer as Layer11 } from "effect";
|
|
792
|
+
class WasmSerialTransportService extends Context8.Service()("WasmSerialTransportService") {
|
|
800
793
|
static fromAscii(options) {
|
|
801
|
-
return Layer11.
|
|
794
|
+
return Layer11.flatMap(WasmAsciiTransportService.make(options), (context) => Layer11.succeed(WasmSerialTransportService, Context8.get(context, WasmAsciiTransportService)));
|
|
802
795
|
}
|
|
803
796
|
static fromRtu(options) {
|
|
804
|
-
return Layer11.
|
|
797
|
+
return Layer11.flatMap(WasmRtuTransportService.make(options), (context) => Layer11.succeed(WasmSerialTransportService, Context8.get(context, WasmRtuTransportService)));
|
|
805
798
|
}
|
|
806
799
|
static makeMockTransport = (devices) => {
|
|
807
800
|
const factory = makeMockTransport(devices);
|
|
808
|
-
return (options) => Layer11.
|
|
801
|
+
return (options) => Layer11.effect(WasmSerialTransportService, factory(options));
|
|
809
802
|
};
|
|
810
803
|
}
|
|
811
804
|
// src/WasmSerialPort.ts
|
|
@@ -819,7 +812,7 @@ var requestSerialPort = () => Effect15.tryPromise({
|
|
|
819
812
|
});
|
|
820
813
|
// src/WasmTcpServerService.ts
|
|
821
814
|
import { Effect as Effect16, Layer as Layer12 } from "effect";
|
|
822
|
-
var wasmWsServerLayer = (options, handlers) => Layer12.
|
|
815
|
+
var wasmWsServerLayer = (options, handlers) => Layer12.effectDiscard(Effect16.gen(function* () {
|
|
823
816
|
const { WasmWsModbusServer } = yield* Effect16.promise(() => import("modbus-rs/web"));
|
|
824
817
|
const server = yield* Effect16.tryPromise({
|
|
825
818
|
try: () => WasmWsModbusServer.bind(options, handlers),
|
|
@@ -829,15 +822,15 @@ var wasmWsServerLayer = (options, handlers) => Layer12.scopedDiscard(Effect16.ge
|
|
|
829
822
|
yield* Effect16.forkScoped(Effect16.tryPromise({
|
|
830
823
|
try: () => server.serve(),
|
|
831
824
|
catch: (error) => toModbusError(error)
|
|
832
|
-
}).pipe(Effect16.
|
|
825
|
+
}).pipe(Effect16.catch((error) => Effect16.logError("WASM WS server loop ended", error))));
|
|
833
826
|
yield* Effect16.addFinalizer(() => Effect16.logDebug("WASM WS server shutting down").pipe(Effect16.andThen(Effect16.tryPromise({
|
|
834
827
|
try: () => server.shutdown(),
|
|
835
828
|
catch: (error) => toModbusError(error)
|
|
836
|
-
})), Effect16.
|
|
829
|
+
})), Effect16.catch(() => Effect16.void)));
|
|
837
830
|
}));
|
|
838
831
|
// src/WasmSerialModbusServerService.ts
|
|
839
832
|
import { Effect as Effect17, Layer as Layer13 } from "effect";
|
|
840
|
-
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.
|
|
833
|
+
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
841
834
|
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
842
835
|
const server = yield* Effect17.tryPromise({
|
|
843
836
|
try: () => WasmSerialModbusServer.bindRtu(options, handlers),
|
|
@@ -847,13 +840,13 @@ var wasmSerialRtuServerLayer = (options, handlers) => Layer13.scopedDiscard(Effe
|
|
|
847
840
|
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
848
841
|
try: () => server.serve(),
|
|
849
842
|
catch: (error) => toModbusError(error)
|
|
850
|
-
}).pipe(Effect17.
|
|
843
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial RTU server loop ended", error))));
|
|
851
844
|
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial RTU server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
852
845
|
try: () => server.shutdown(),
|
|
853
846
|
catch: (error) => toModbusError(error)
|
|
854
|
-
})), Effect17.
|
|
847
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
855
848
|
}));
|
|
856
|
-
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.
|
|
849
|
+
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
857
850
|
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
858
851
|
const server = yield* Effect17.tryPromise({
|
|
859
852
|
try: () => WasmSerialModbusServer.bindAscii(options, handlers),
|
|
@@ -863,41 +856,41 @@ var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.scopedDiscard(Ef
|
|
|
863
856
|
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
864
857
|
try: () => server.serve(),
|
|
865
858
|
catch: (error) => toModbusError(error)
|
|
866
|
-
}).pipe(Effect17.
|
|
859
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial ASCII server loop ended", error))));
|
|
867
860
|
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial ASCII server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
868
861
|
try: () => server.shutdown(),
|
|
869
862
|
catch: (error) => toModbusError(error)
|
|
870
|
-
})), Effect17.
|
|
863
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
871
864
|
}));
|
|
872
865
|
export {
|
|
873
|
-
|
|
874
|
-
wasmSerialRtuServerLayer,
|
|
875
|
-
wasmSerialAsciiServerLayer,
|
|
876
|
-
toModbusError,
|
|
877
|
-
tcpServerLayer,
|
|
878
|
-
tcpGatewayLayer,
|
|
879
|
-
serialRtuServerLayer,
|
|
880
|
-
serialAsciiServerLayer,
|
|
881
|
-
retryableExceptionCodes,
|
|
882
|
-
retryModbus,
|
|
883
|
-
requestSerialPort,
|
|
884
|
-
makeRetryPolicy,
|
|
885
|
-
WasmWsTransportService,
|
|
886
|
-
WasmSerialTransportService,
|
|
887
|
-
WasmRtuTransportService,
|
|
888
|
-
WasmAsciiTransportService,
|
|
889
|
-
TcpTransportService,
|
|
890
|
-
SerialTransportService,
|
|
891
|
-
RtuTransportService,
|
|
892
|
-
RetryPolicies,
|
|
893
|
-
ModbusTransportError,
|
|
894
|
-
ModbusTimeoutError,
|
|
895
|
-
ModbusNotConnectedError,
|
|
896
|
-
ModbusInvalidArgumentError,
|
|
897
|
-
ModbusInternalError,
|
|
898
|
-
ModbusExceptionError,
|
|
899
|
-
ModbusConnectionClosedError,
|
|
900
|
-
ModbusCircuitOpenError,
|
|
866
|
+
AsciiTransportService,
|
|
901
867
|
ConnectionState,
|
|
902
|
-
|
|
868
|
+
ModbusCircuitOpenError,
|
|
869
|
+
ModbusConnectionClosedError,
|
|
870
|
+
ModbusExceptionError,
|
|
871
|
+
ModbusInternalError,
|
|
872
|
+
ModbusInvalidArgumentError,
|
|
873
|
+
ModbusNotConnectedError,
|
|
874
|
+
ModbusTimeoutError,
|
|
875
|
+
ModbusTransportError,
|
|
876
|
+
RetryPolicies,
|
|
877
|
+
RtuTransportService,
|
|
878
|
+
SerialTransportService,
|
|
879
|
+
TcpTransportService,
|
|
880
|
+
WasmAsciiTransportService,
|
|
881
|
+
WasmRtuTransportService,
|
|
882
|
+
WasmSerialTransportService,
|
|
883
|
+
WasmWsTransportService,
|
|
884
|
+
makeRetryPolicy,
|
|
885
|
+
requestSerialPort,
|
|
886
|
+
retryModbus,
|
|
887
|
+
retryableExceptionCodes,
|
|
888
|
+
serialAsciiServerLayer,
|
|
889
|
+
serialRtuServerLayer,
|
|
890
|
+
tcpGatewayLayer,
|
|
891
|
+
tcpServerLayer,
|
|
892
|
+
toModbusError,
|
|
893
|
+
wasmSerialAsciiServerLayer,
|
|
894
|
+
wasmSerialRtuServerLayer,
|
|
895
|
+
wasmWsServerLayer
|
|
903
896
|
};
|