@flux-control/effect-modbus-rs 0.3.0 → 0.4.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.
- package/README.md +74 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +114 -118
- package/dist/src/AsciiTransportService.d.ts +31 -22
- package/dist/src/RtuTransportService.d.ts +31 -22
- package/dist/src/SerialTransportService.d.ts +17 -7
- 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 +20 -10
- package/dist/src/WasmWsTransportService.d.ts +30 -21
- package/dist/src/connection.d.ts +9 -9
- package/dist/src/mock-options.test.d.ts +1 -0
- package/dist/src/mocks.d.ts +41 -57
- package/dist/src/retry.d.ts +11 -8
- package/package.json +5 -5
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,13 +185,13 @@ 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)
|
|
@@ -195,7 +199,7 @@ var superviseReconnect = (reconnect, state, resolved) => Effect2.gen(function* (
|
|
|
195
199
|
}
|
|
196
200
|
});
|
|
197
201
|
// src/AsciiTransportService.ts
|
|
198
|
-
import {
|
|
202
|
+
import { Context, Layer } from "effect";
|
|
199
203
|
|
|
200
204
|
// src/mocks.ts
|
|
201
205
|
import { Effect as Effect4, Schema, SubscriptionRef as SubscriptionRef2 } from "effect";
|
|
@@ -228,7 +232,7 @@ var makeEffectModbusClient = (client) => ({
|
|
|
228
232
|
});
|
|
229
233
|
var withResilience = (operations, resilience) => {
|
|
230
234
|
const run = (operation) => {
|
|
231
|
-
const attempt = Effect3.
|
|
235
|
+
const attempt = Effect3.andThen(resilience.guard, Effect3.suspend(operation)).pipe(Effect3.tapError((error) => resilience.report(error)));
|
|
232
236
|
return resilience.policy ? retryModbus(resilience.policy)(attempt) : attempt;
|
|
233
237
|
};
|
|
234
238
|
return {
|
|
@@ -266,18 +270,10 @@ var RegisterDefinition = Schema.Struct({
|
|
|
266
270
|
});
|
|
267
271
|
var SlaveDeviceDefinition = Schema.Struct({
|
|
268
272
|
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
|
-
})
|
|
273
|
+
coils: Schema.Array(CoilDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
274
|
+
discreteInputs: Schema.Array(DiscreteInputDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
275
|
+
holdingRegisters: Schema.Array(RegisterDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([]))),
|
|
276
|
+
inputRegisters: Schema.Array(RegisterDefinition).pipe(Schema.withDecodingDefaultType(Effect4.succeed([])))
|
|
281
277
|
});
|
|
282
278
|
var SlaveDeviceDefinitions = Schema.Array(SlaveDeviceDefinition);
|
|
283
279
|
var buildCoils = (defs) => {
|
|
@@ -458,7 +454,7 @@ var makeMockTransport = (devices) => {
|
|
|
458
454
|
const connectionState = yield* SubscriptionRef2.make(ConnectionState.Connected());
|
|
459
455
|
const supervised = options.reconnect ? resolveReconnect(options.reconnect) : null;
|
|
460
456
|
const serviceScope = yield* Effect4.scope;
|
|
461
|
-
const reconnectOnce = Effect4.
|
|
457
|
+
const reconnectOnce = Effect4.andThen(Effect4.logDebug("Mock: reconnecting"), Effect4.suspend(() => {
|
|
462
458
|
const injected = options.reconnectFault?.();
|
|
463
459
|
return injected ? Effect4.fail(injected) : Effect4.void;
|
|
464
460
|
}));
|
|
@@ -472,7 +468,7 @@ var makeMockTransport = (devices) => {
|
|
|
472
468
|
yield* Effect4.forkIn(superviseReconnect(reconnectOnce, connectionState, supervised), serviceScope);
|
|
473
469
|
});
|
|
474
470
|
};
|
|
475
|
-
const guard = Effect4.
|
|
471
|
+
const guard = Effect4.andThen(supervised ? guardCircuit(connectionState) : Effect4.void, Effect4.suspend(() => {
|
|
476
472
|
const injected = options.fault?.();
|
|
477
473
|
return injected ? Effect4.fail(injected) : Effect4.void;
|
|
478
474
|
}));
|
|
@@ -494,8 +490,8 @@ var makeMockTransport = (devices) => {
|
|
|
494
490
|
}),
|
|
495
491
|
setRequestTimeout: (_timeoutMs) => Effect4.void,
|
|
496
492
|
clearRequestTimeout: () => Effect4.void,
|
|
497
|
-
reconnect: () => Effect4.
|
|
498
|
-
close: () => Effect4.
|
|
493
|
+
reconnect: () => Effect4.andThen(reconnectOnce, SubscriptionRef2.set(connectionState, ConnectionState.Connected())),
|
|
494
|
+
close: () => Effect4.andThen(Effect4.logDebug("Mock: closing transport"), SubscriptionRef2.set(connectionState, ConnectionState.Disconnected())),
|
|
499
495
|
hasPendingRequests: () => false
|
|
500
496
|
};
|
|
501
497
|
});
|
|
@@ -511,7 +507,7 @@ var singleFlight = (inFlight, work) => Effect5.uninterruptibleMask((restore) =>
|
|
|
511
507
|
onNone: () => [[fresh, true], Option.some(fresh)]
|
|
512
508
|
}));
|
|
513
509
|
if (isLeader) {
|
|
514
|
-
yield* Effect5.
|
|
510
|
+
yield* Effect5.forkDetach(Effect5.interruptible(work).pipe(Effect5.onExit((exit) => Effect5.andThen(Ref.set(inFlight, Option.none()), Deferred.done(deferred, exit)))));
|
|
515
511
|
}
|
|
516
512
|
return yield* restore(Deferred.await(deferred));
|
|
517
513
|
}));
|
|
@@ -533,11 +529,11 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
533
529
|
cause: new Error("Transport has been closed"),
|
|
534
530
|
message: "Transport has been closed"
|
|
535
531
|
});
|
|
536
|
-
const closeOrphan = (t) => Effect5.
|
|
532
|
+
const closeOrphan = (t) => Effect5.forkDetach(Effect5.ignore(Effect5.tryPromise(() => t.close())));
|
|
537
533
|
const openTransport = Effect5.tryPromise({
|
|
538
534
|
try: () => openMethod(TC, openOptions),
|
|
539
535
|
catch: (error) => toModbusError(error)
|
|
540
|
-
}).pipe(Effect5.tap((t) => closed ? closeOrphan(t) : Effect5.
|
|
536
|
+
}).pipe(Effect5.tap((t) => closed ? closeOrphan(t) : Effect5.andThen(Effect5.sync(() => {
|
|
541
537
|
transport = t;
|
|
542
538
|
}), SubscriptionRef3.set(connectionState, ConnectionState.Connected()))));
|
|
543
539
|
const ensureOpen = Effect5.fnUntraced(function* () {
|
|
@@ -557,7 +553,7 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
557
553
|
const t = transport;
|
|
558
554
|
if (!t)
|
|
559
555
|
return SubscriptionRef3.set(connectionState, ConnectionState.Disconnected());
|
|
560
|
-
return Effect5.andThen(Effect5.logDebug(`Closing ${serviceName}`), Effect5.
|
|
556
|
+
return Effect5.andThen(Effect5.logDebug(`Closing ${serviceName}`), Effect5.andThen(Effect5.promise(() => t.close()), SubscriptionRef3.set(connectionState, ConnectionState.Disconnected())));
|
|
561
557
|
});
|
|
562
558
|
const notConnectedMsg = "Transport is not connected. Call withClient() first.";
|
|
563
559
|
const reconnectOnce = Effect5.suspend(() => {
|
|
@@ -665,54 +661,54 @@ function makeTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
665
661
|
}
|
|
666
662
|
|
|
667
663
|
// src/AsciiTransportService.ts
|
|
668
|
-
class AsciiTransportService extends
|
|
669
|
-
|
|
670
|
-
|
|
664
|
+
class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
665
|
+
static makeScoped = makeTransportScoped("AsyncAsciiTransport", (TC, options) => TC.open(options), "AsciiTransportService");
|
|
666
|
+
static make = (options) => Layer.effect(AsciiTransportService, AsciiTransportService.makeScoped(options));
|
|
671
667
|
static makeMockTransport = (devices) => {
|
|
672
668
|
const factory = makeMockTransport(devices);
|
|
673
|
-
return (options) => Layer.
|
|
669
|
+
return (options) => Layer.effect(AsciiTransportService, factory(options));
|
|
674
670
|
};
|
|
675
671
|
}
|
|
676
672
|
// src/SerialTransportService.ts
|
|
677
|
-
import { Context, Layer as Layer3 } from "effect";
|
|
673
|
+
import { Context as Context3, Layer as Layer3 } from "effect";
|
|
678
674
|
|
|
679
675
|
// src/RtuTransportService.ts
|
|
680
|
-
import {
|
|
681
|
-
class RtuTransportService extends
|
|
682
|
-
|
|
683
|
-
|
|
676
|
+
import { Context as Context2, Layer as Layer2 } from "effect";
|
|
677
|
+
class RtuTransportService extends Context2.Service()("RtuTransportService") {
|
|
678
|
+
static makeScoped = makeTransportScoped("AsyncRtuTransport", (TC, options) => TC.open(options), "RtuTransportService");
|
|
679
|
+
static make = (options) => Layer2.effect(RtuTransportService, RtuTransportService.makeScoped(options));
|
|
684
680
|
static makeMockTransport = (devices) => {
|
|
685
681
|
const factory = makeMockTransport(devices);
|
|
686
|
-
return (options) => Layer2.
|
|
682
|
+
return (options) => Layer2.effect(RtuTransportService, factory(options));
|
|
687
683
|
};
|
|
688
684
|
}
|
|
689
685
|
|
|
690
686
|
// src/SerialTransportService.ts
|
|
691
|
-
class SerialTransportService extends
|
|
687
|
+
class SerialTransportService extends Context3.Service()("SerialTransportService") {
|
|
692
688
|
static fromAscii(options) {
|
|
693
|
-
return Layer3.
|
|
689
|
+
return Layer3.flatMap(AsciiTransportService.make(options), (context) => Layer3.succeed(SerialTransportService, Context3.get(context, AsciiTransportService)));
|
|
694
690
|
}
|
|
695
691
|
static fromRtu(options) {
|
|
696
|
-
return Layer3.
|
|
692
|
+
return Layer3.flatMap(RtuTransportService.make(options), (context) => Layer3.succeed(SerialTransportService, Context3.get(context, RtuTransportService)));
|
|
697
693
|
}
|
|
698
694
|
static makeMockTransport = (devices) => {
|
|
699
695
|
const factory = makeMockTransport(devices);
|
|
700
|
-
return (options) => Layer3.
|
|
696
|
+
return (options) => Layer3.effect(SerialTransportService, factory(options));
|
|
701
697
|
};
|
|
702
698
|
}
|
|
703
699
|
// src/TcpTransportService.ts
|
|
704
|
-
import {
|
|
705
|
-
class TcpTransportService extends
|
|
706
|
-
|
|
707
|
-
|
|
700
|
+
import { Context as Context4, Layer as Layer4 } from "effect";
|
|
701
|
+
class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
702
|
+
static makeScoped = makeTransportScoped("AsyncTcpTransport", (TC, options) => TC.connect(options), "TcpTransportService");
|
|
703
|
+
static make = (options) => Layer4.effect(TcpTransportService, TcpTransportService.makeScoped(options));
|
|
708
704
|
static makeMockTransport = (devices) => {
|
|
709
705
|
const factory = makeMockTransport(devices);
|
|
710
|
-
return (options) => Layer4.
|
|
706
|
+
return (options) => Layer4.effect(TcpTransportService, factory(options));
|
|
711
707
|
};
|
|
712
708
|
}
|
|
713
709
|
// src/SerialModbusServerService.ts
|
|
714
710
|
import { Effect as Effect9, Layer as Layer5 } from "effect";
|
|
715
|
-
var serialRtuServerLayer = (options, handlers) => Layer5.
|
|
711
|
+
var serialRtuServerLayer = (options, handlers) => Layer5.effectDiscard(Effect9.gen(function* () {
|
|
716
712
|
const { AsyncSerialModbusServer } = yield* Effect9.promise(() => import("modbus-rs"));
|
|
717
713
|
const server = yield* Effect9.tryPromise({
|
|
718
714
|
try: () => AsyncSerialModbusServer.bindRtu(options, handlers),
|
|
@@ -722,9 +718,9 @@ var serialRtuServerLayer = (options, handlers) => Layer5.scopedDiscard(Effect9.g
|
|
|
722
718
|
yield* Effect9.addFinalizer(() => Effect9.logDebug("Serial RTU server shutting down").pipe(Effect9.andThen(Effect9.tryPromise({
|
|
723
719
|
try: () => server.shutdown(),
|
|
724
720
|
catch: (error) => toModbusError(error)
|
|
725
|
-
})), Effect9.
|
|
721
|
+
})), Effect9.catch(() => Effect9.void)));
|
|
726
722
|
}));
|
|
727
|
-
var serialAsciiServerLayer = (options, handlers) => Layer5.
|
|
723
|
+
var serialAsciiServerLayer = (options, handlers) => Layer5.effectDiscard(Effect9.gen(function* () {
|
|
728
724
|
const { AsyncSerialModbusServer } = yield* Effect9.promise(() => import("modbus-rs"));
|
|
729
725
|
const server = yield* Effect9.tryPromise({
|
|
730
726
|
try: () => AsyncSerialModbusServer.bindAscii(options, handlers),
|
|
@@ -734,11 +730,11 @@ var serialAsciiServerLayer = (options, handlers) => Layer5.scopedDiscard(Effect9
|
|
|
734
730
|
yield* Effect9.addFinalizer(() => Effect9.logDebug("Serial ASCII server shutting down").pipe(Effect9.andThen(Effect9.tryPromise({
|
|
735
731
|
try: () => server.shutdown(),
|
|
736
732
|
catch: (error) => toModbusError(error)
|
|
737
|
-
})), Effect9.
|
|
733
|
+
})), Effect9.catch(() => Effect9.void)));
|
|
738
734
|
}));
|
|
739
735
|
// src/TcpModbusServerService.ts
|
|
740
736
|
import { Effect as Effect10, Layer as Layer6 } from "effect";
|
|
741
|
-
var tcpServerLayer = (options, handlers) => Layer6.
|
|
737
|
+
var tcpServerLayer = (options, handlers) => Layer6.effectDiscard(Effect10.gen(function* () {
|
|
742
738
|
const { AsyncTcpModbusServer } = yield* Effect10.promise(() => import("modbus-rs"));
|
|
743
739
|
const server = yield* Effect10.tryPromise({
|
|
744
740
|
try: () => AsyncTcpModbusServer.bind(options, handlers),
|
|
@@ -748,11 +744,11 @@ var tcpServerLayer = (options, handlers) => Layer6.scopedDiscard(Effect10.gen(fu
|
|
|
748
744
|
yield* Effect10.addFinalizer(() => Effect10.logDebug("TCP server shutting down").pipe(Effect10.andThen(Effect10.tryPromise({
|
|
749
745
|
try: () => server.shutdown(),
|
|
750
746
|
catch: (error) => toModbusError(error)
|
|
751
|
-
})), Effect10.
|
|
747
|
+
})), Effect10.catch(() => Effect10.void)));
|
|
752
748
|
}));
|
|
753
749
|
// src/TcpGatewayService.ts
|
|
754
750
|
import { Effect as Effect11, Layer as Layer7 } from "effect";
|
|
755
|
-
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.
|
|
751
|
+
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.effectDiscard(Effect11.gen(function* () {
|
|
756
752
|
const { AsyncTcpGateway } = yield* Effect11.promise(() => import("modbus-rs"));
|
|
757
753
|
const gateway = yield* Effect11.tryPromise({
|
|
758
754
|
try: () => AsyncTcpGateway.bind(options, gatewayConfig),
|
|
@@ -762,50 +758,50 @@ var tcpGatewayLayer = (options, gatewayConfig) => Layer7.scopedDiscard(Effect11.
|
|
|
762
758
|
yield* Effect11.addFinalizer(() => Effect11.logDebug("TCP gateway shutting down").pipe(Effect11.andThen(Effect11.tryPromise({
|
|
763
759
|
try: () => gateway.shutdown(),
|
|
764
760
|
catch: (error) => toModbusError(error)
|
|
765
|
-
})), Effect11.
|
|
761
|
+
})), Effect11.catch(() => Effect11.void)));
|
|
766
762
|
}));
|
|
767
763
|
// src/WasmWsTransportService.ts
|
|
768
|
-
import {
|
|
769
|
-
class WasmWsTransportService extends
|
|
770
|
-
|
|
771
|
-
|
|
764
|
+
import { Context as Context5, Layer as Layer8 } from "effect";
|
|
765
|
+
class WasmWsTransportService extends Context5.Service()("WasmWsTransportService") {
|
|
766
|
+
static makeScoped = makeTransportScoped("WasmWsTransport", (TC, options) => TC.connect(options), "WasmWsTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
767
|
+
static make = (options) => Layer8.effect(WasmWsTransportService, WasmWsTransportService.makeScoped(options));
|
|
772
768
|
static makeMockTransport = (devices) => {
|
|
773
769
|
const factory = makeMockTransport(devices);
|
|
774
|
-
return (options) => Layer8.
|
|
770
|
+
return (options) => Layer8.effect(WasmWsTransportService, factory(options));
|
|
775
771
|
};
|
|
776
772
|
}
|
|
777
773
|
// src/WasmRtuTransportService.ts
|
|
778
|
-
import {
|
|
779
|
-
class WasmRtuTransportService extends
|
|
780
|
-
|
|
781
|
-
|
|
774
|
+
import { Context as Context6, Layer as Layer9 } from "effect";
|
|
775
|
+
class WasmRtuTransportService extends Context6.Service()("WasmRtuTransportService") {
|
|
776
|
+
static makeScoped = makeTransportScoped("WasmRtuTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmRtuTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
777
|
+
static make = (options) => Layer9.effect(WasmRtuTransportService, WasmRtuTransportService.makeScoped(options));
|
|
782
778
|
static makeMockTransport = (devices) => {
|
|
783
779
|
const factory = makeMockTransport(devices);
|
|
784
|
-
return (options) => Layer9.
|
|
780
|
+
return (options) => Layer9.effect(WasmRtuTransportService, factory(options));
|
|
785
781
|
};
|
|
786
782
|
}
|
|
787
783
|
// src/WasmAsciiTransportService.ts
|
|
788
|
-
import {
|
|
789
|
-
class WasmAsciiTransportService extends
|
|
790
|
-
|
|
791
|
-
|
|
784
|
+
import { Context as Context7, Layer as Layer10 } from "effect";
|
|
785
|
+
class WasmAsciiTransportService extends Context7.Service()("WasmAsciiTransportService") {
|
|
786
|
+
static makeScoped = makeTransportScoped("WasmAsciiTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmAsciiTransportService", { moduleSpecifier: "modbus-rs/web" });
|
|
787
|
+
static make = (options) => Layer10.effect(WasmAsciiTransportService, WasmAsciiTransportService.makeScoped(options));
|
|
792
788
|
static makeMockTransport = (devices) => {
|
|
793
789
|
const factory = makeMockTransport(devices);
|
|
794
|
-
return (options) => Layer10.
|
|
790
|
+
return (options) => Layer10.effect(WasmAsciiTransportService, factory(options));
|
|
795
791
|
};
|
|
796
792
|
}
|
|
797
793
|
// src/WasmSerialTransportService.ts
|
|
798
|
-
import { Context as
|
|
799
|
-
class WasmSerialTransportService extends
|
|
794
|
+
import { Context as Context8, Layer as Layer11 } from "effect";
|
|
795
|
+
class WasmSerialTransportService extends Context8.Service()("WasmSerialTransportService") {
|
|
800
796
|
static fromAscii(options) {
|
|
801
|
-
return Layer11.
|
|
797
|
+
return Layer11.flatMap(WasmAsciiTransportService.make(options), (context) => Layer11.succeed(WasmSerialTransportService, Context8.get(context, WasmAsciiTransportService)));
|
|
802
798
|
}
|
|
803
799
|
static fromRtu(options) {
|
|
804
|
-
return Layer11.
|
|
800
|
+
return Layer11.flatMap(WasmRtuTransportService.make(options), (context) => Layer11.succeed(WasmSerialTransportService, Context8.get(context, WasmRtuTransportService)));
|
|
805
801
|
}
|
|
806
802
|
static makeMockTransport = (devices) => {
|
|
807
803
|
const factory = makeMockTransport(devices);
|
|
808
|
-
return (options) => Layer11.
|
|
804
|
+
return (options) => Layer11.effect(WasmSerialTransportService, factory(options));
|
|
809
805
|
};
|
|
810
806
|
}
|
|
811
807
|
// src/WasmSerialPort.ts
|
|
@@ -819,7 +815,7 @@ var requestSerialPort = () => Effect15.tryPromise({
|
|
|
819
815
|
});
|
|
820
816
|
// src/WasmTcpServerService.ts
|
|
821
817
|
import { Effect as Effect16, Layer as Layer12 } from "effect";
|
|
822
|
-
var wasmWsServerLayer = (options, handlers) => Layer12.
|
|
818
|
+
var wasmWsServerLayer = (options, handlers) => Layer12.effectDiscard(Effect16.gen(function* () {
|
|
823
819
|
const { WasmWsModbusServer } = yield* Effect16.promise(() => import("modbus-rs/web"));
|
|
824
820
|
const server = yield* Effect16.tryPromise({
|
|
825
821
|
try: () => WasmWsModbusServer.bind(options, handlers),
|
|
@@ -829,15 +825,15 @@ var wasmWsServerLayer = (options, handlers) => Layer12.scopedDiscard(Effect16.ge
|
|
|
829
825
|
yield* Effect16.forkScoped(Effect16.tryPromise({
|
|
830
826
|
try: () => server.serve(),
|
|
831
827
|
catch: (error) => toModbusError(error)
|
|
832
|
-
}).pipe(Effect16.
|
|
828
|
+
}).pipe(Effect16.catch((error) => Effect16.logError("WASM WS server loop ended", error))));
|
|
833
829
|
yield* Effect16.addFinalizer(() => Effect16.logDebug("WASM WS server shutting down").pipe(Effect16.andThen(Effect16.tryPromise({
|
|
834
830
|
try: () => server.shutdown(),
|
|
835
831
|
catch: (error) => toModbusError(error)
|
|
836
|
-
})), Effect16.
|
|
832
|
+
})), Effect16.catch(() => Effect16.void)));
|
|
837
833
|
}));
|
|
838
834
|
// src/WasmSerialModbusServerService.ts
|
|
839
835
|
import { Effect as Effect17, Layer as Layer13 } from "effect";
|
|
840
|
-
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.
|
|
836
|
+
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
841
837
|
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
842
838
|
const server = yield* Effect17.tryPromise({
|
|
843
839
|
try: () => WasmSerialModbusServer.bindRtu(options, handlers),
|
|
@@ -847,13 +843,13 @@ var wasmSerialRtuServerLayer = (options, handlers) => Layer13.scopedDiscard(Effe
|
|
|
847
843
|
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
848
844
|
try: () => server.serve(),
|
|
849
845
|
catch: (error) => toModbusError(error)
|
|
850
|
-
}).pipe(Effect17.
|
|
846
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial RTU server loop ended", error))));
|
|
851
847
|
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial RTU server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
852
848
|
try: () => server.shutdown(),
|
|
853
849
|
catch: (error) => toModbusError(error)
|
|
854
|
-
})), Effect17.
|
|
850
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
855
851
|
}));
|
|
856
|
-
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.
|
|
852
|
+
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
857
853
|
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
858
854
|
const server = yield* Effect17.tryPromise({
|
|
859
855
|
try: () => WasmSerialModbusServer.bindAscii(options, handlers),
|
|
@@ -863,41 +859,41 @@ var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.scopedDiscard(Ef
|
|
|
863
859
|
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
864
860
|
try: () => server.serve(),
|
|
865
861
|
catch: (error) => toModbusError(error)
|
|
866
|
-
}).pipe(Effect17.
|
|
862
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial ASCII server loop ended", error))));
|
|
867
863
|
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial ASCII server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
868
864
|
try: () => server.shutdown(),
|
|
869
865
|
catch: (error) => toModbusError(error)
|
|
870
|
-
})), Effect17.
|
|
866
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
871
867
|
}));
|
|
872
868
|
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,
|
|
869
|
+
AsciiTransportService,
|
|
901
870
|
ConnectionState,
|
|
902
|
-
|
|
871
|
+
ModbusCircuitOpenError,
|
|
872
|
+
ModbusConnectionClosedError,
|
|
873
|
+
ModbusExceptionError,
|
|
874
|
+
ModbusInternalError,
|
|
875
|
+
ModbusInvalidArgumentError,
|
|
876
|
+
ModbusNotConnectedError,
|
|
877
|
+
ModbusTimeoutError,
|
|
878
|
+
ModbusTransportError,
|
|
879
|
+
RetryPolicies,
|
|
880
|
+
RtuTransportService,
|
|
881
|
+
SerialTransportService,
|
|
882
|
+
TcpTransportService,
|
|
883
|
+
WasmAsciiTransportService,
|
|
884
|
+
WasmRtuTransportService,
|
|
885
|
+
WasmSerialTransportService,
|
|
886
|
+
WasmWsTransportService,
|
|
887
|
+
makeRetryPolicy,
|
|
888
|
+
requestSerialPort,
|
|
889
|
+
retryModbus,
|
|
890
|
+
retryableExceptionCodes,
|
|
891
|
+
serialAsciiServerLayer,
|
|
892
|
+
serialRtuServerLayer,
|
|
893
|
+
tcpGatewayLayer,
|
|
894
|
+
tcpServerLayer,
|
|
895
|
+
toModbusError,
|
|
896
|
+
wasmSerialAsciiServerLayer,
|
|
897
|
+
wasmSerialRtuServerLayer,
|
|
898
|
+
wasmWsServerLayer
|
|
903
899
|
};
|
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
import { Effect, Layer } from 'effect';
|
|
1
|
+
import { Context, Effect, Layer } from 'effect';
|
|
2
2
|
import type { AsciiTransportOptions } from 'modbus-rs';
|
|
3
3
|
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
4
|
import type { MockFaultOptions } from './mocks';
|
|
5
|
-
import type { TransportResilienceOptions, WithoutUpstreamRetry } from './shared-transport';
|
|
5
|
+
import type { TransportResilienceOptions, WithoutUpstreamRetry, TransportServiceApi } from './shared-transport';
|
|
6
6
|
/**
|
|
7
7
|
* {@link AsciiTransportOptions} minus the upstream retry knobs.
|
|
8
8
|
*
|
|
9
9
|
* @see WithoutUpstreamRetry — Why they are withheld.
|
|
10
10
|
*/
|
|
11
11
|
export type AsciiTransportOpenOptions = WithoutUpstreamRetry<AsciiTransportOptions>;
|
|
12
|
-
declare const AsciiTransportService_base:
|
|
13
|
-
|
|
12
|
+
declare const AsciiTransportService_base: Context.ServiceClass<AsciiTransportService, "AsciiTransportService", TransportServiceApi>;
|
|
13
|
+
/**
|
|
14
|
+
* Scoped Effect service wrapping the `modbus-rs` {@link AsyncAsciiTransport}
|
|
15
|
+
* for ASCII (serial) Modbus communication.
|
|
16
|
+
*
|
|
17
|
+
* The transport connection is opened lazily on the first call to
|
|
18
|
+
* `withClient(unitId)` and automatically closed when the consuming
|
|
19
|
+
* {@link Effect.Scope | Scope} finalizes.
|
|
20
|
+
*
|
|
21
|
+
* Clients are created per `unitId` via
|
|
22
|
+
* {@link AsyncAsciiTransport.createClient} and cached, so repeated
|
|
23
|
+
* requests for the same unit ID reuse the same client.
|
|
24
|
+
*
|
|
25
|
+
* @see AsyncAsciiTransport — Upstream `modbus-rs` ASCII transport.
|
|
26
|
+
* @see AsciiTransportOpenOptions — Configuration for the ASCII serial port.
|
|
27
|
+
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
28
|
+
*/
|
|
29
|
+
export declare class AsciiTransportService extends AsciiTransportService_base {
|
|
30
|
+
/**
|
|
31
|
+
* Scoped constructor effect for the service. v4 does not auto-generate a
|
|
32
|
+
* layer from this, so {@link AsciiTransportService.make} builds one explicitly.
|
|
33
|
+
*/
|
|
34
|
+
static readonly makeScoped: (options: AsciiTransportOpenOptions & TransportResilienceOptions) => Effect.Effect<{
|
|
14
35
|
connectionState: import("effect/SubscriptionRef").SubscriptionRef<{
|
|
15
36
|
readonly _tag: "Connected";
|
|
16
37
|
} | {
|
|
@@ -31,24 +52,12 @@ declare const AsciiTransportService_base: Effect.Service.Class<AsciiTransportSer
|
|
|
31
52
|
close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
|
|
32
53
|
hasPendingRequests: () => boolean;
|
|
33
54
|
}, never, import("effect/Scope").Scope>;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* `withClient(unitId)` and automatically closed when the consuming
|
|
41
|
-
* {@link Effect.Scope | Scope} finalizes.
|
|
42
|
-
*
|
|
43
|
-
* Clients are created per `unitId` via
|
|
44
|
-
* {@link AsyncAsciiTransport.createClient} and cached, so repeated
|
|
45
|
-
* requests for the same unit ID reuse the same client.
|
|
46
|
-
*
|
|
47
|
-
* @see AsyncAsciiTransport — Upstream `modbus-rs` ASCII transport.
|
|
48
|
-
* @see AsciiTransportOpenOptions — Configuration for the ASCII serial port.
|
|
49
|
-
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
50
|
-
*/
|
|
51
|
-
export declare class AsciiTransportService extends AsciiTransportService_base {
|
|
55
|
+
/**
|
|
56
|
+
* Creates a {@link Layer} providing a live {@link AsciiTransportService}.
|
|
57
|
+
*
|
|
58
|
+
* @param options - Connection and resilience options for the transport.
|
|
59
|
+
*/
|
|
60
|
+
static readonly make: (options: AsciiTransportOpenOptions & TransportResilienceOptions) => Layer.Layer<AsciiTransportService>;
|
|
52
61
|
/**
|
|
53
62
|
* Creates a {@link Layer} providing an in-memory mock
|
|
54
63
|
* {@link AsciiTransportService} for testing or development.
|
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
import { Effect, Layer } from 'effect';
|
|
1
|
+
import { Context, Effect, Layer } from 'effect';
|
|
2
2
|
import type { RtuTransportOptions } from 'modbus-rs';
|
|
3
3
|
import type { MockFaultOptions } from './mocks';
|
|
4
4
|
import type { SlaveDeviceDefinitions } from './mocks';
|
|
5
|
-
import type { TransportResilienceOptions, WithoutUpstreamRetry } from './shared-transport';
|
|
5
|
+
import type { TransportResilienceOptions, WithoutUpstreamRetry, TransportServiceApi } from './shared-transport';
|
|
6
6
|
/**
|
|
7
7
|
* {@link RtuTransportOptions} minus the upstream retry knobs.
|
|
8
8
|
*
|
|
9
9
|
* @see WithoutUpstreamRetry — Why they are withheld.
|
|
10
10
|
*/
|
|
11
11
|
export type RtuTransportOpenOptions = WithoutUpstreamRetry<RtuTransportOptions>;
|
|
12
|
-
declare const RtuTransportService_base:
|
|
13
|
-
|
|
12
|
+
declare const RtuTransportService_base: Context.ServiceClass<RtuTransportService, "RtuTransportService", TransportServiceApi>;
|
|
13
|
+
/**
|
|
14
|
+
* Scoped Effect service wrapping the `modbus-rs` {@link AsyncRtuTransport}
|
|
15
|
+
* for RTU (serial) Modbus communication.
|
|
16
|
+
*
|
|
17
|
+
* The transport connection is opened lazily on the first call to
|
|
18
|
+
* `withClient(unitId)` and automatically closed when the consuming
|
|
19
|
+
* {@link Effect.Scope | Scope} finalizes.
|
|
20
|
+
*
|
|
21
|
+
* Clients are created per `unitId` via
|
|
22
|
+
* {@link AsyncRtuTransport.createClient} and cached, so repeated
|
|
23
|
+
* requests for the same unit ID reuse the same client.
|
|
24
|
+
*
|
|
25
|
+
* @see AsyncRtuTransport — Upstream `modbus-rs` RTU transport.
|
|
26
|
+
* @see RtuTransportOpenOptions — Configuration for the RTU serial port.
|
|
27
|
+
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
28
|
+
*/
|
|
29
|
+
export declare class RtuTransportService extends RtuTransportService_base {
|
|
30
|
+
/**
|
|
31
|
+
* Scoped constructor effect for the service. v4 does not auto-generate a
|
|
32
|
+
* layer from this, so {@link RtuTransportService.make} builds one explicitly.
|
|
33
|
+
*/
|
|
34
|
+
static readonly makeScoped: (options: RtuTransportOpenOptions & TransportResilienceOptions) => Effect.Effect<{
|
|
14
35
|
connectionState: import("effect/SubscriptionRef").SubscriptionRef<{
|
|
15
36
|
readonly _tag: "Connected";
|
|
16
37
|
} | {
|
|
@@ -31,24 +52,12 @@ declare const RtuTransportService_base: Effect.Service.Class<RtuTransportService
|
|
|
31
52
|
close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
|
|
32
53
|
hasPendingRequests: () => boolean;
|
|
33
54
|
}, never, import("effect/Scope").Scope>;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* `withClient(unitId)` and automatically closed when the consuming
|
|
41
|
-
* {@link Effect.Scope | Scope} finalizes.
|
|
42
|
-
*
|
|
43
|
-
* Clients are created per `unitId` via
|
|
44
|
-
* {@link AsyncRtuTransport.createClient} and cached, so repeated
|
|
45
|
-
* requests for the same unit ID reuse the same client.
|
|
46
|
-
*
|
|
47
|
-
* @see AsyncRtuTransport — Upstream `modbus-rs` RTU transport.
|
|
48
|
-
* @see RtuTransportOpenOptions — Configuration for the RTU serial port.
|
|
49
|
-
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
50
|
-
*/
|
|
51
|
-
export declare class RtuTransportService extends RtuTransportService_base {
|
|
55
|
+
/**
|
|
56
|
+
* Creates a {@link Layer} providing a live {@link RtuTransportService}.
|
|
57
|
+
*
|
|
58
|
+
* @param options - Connection and resilience options for the transport.
|
|
59
|
+
*/
|
|
60
|
+
static readonly make: (options: RtuTransportOpenOptions & TransportResilienceOptions) => Layer.Layer<RtuTransportService>;
|
|
52
61
|
/**
|
|
53
62
|
* Creates a {@link Layer} providing an in-memory mock
|
|
54
63
|
* {@link RtuTransportService} for testing or development.
|