@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/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.decode(input));
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.intersect(Schedule.identity()), Schedule.modifyDelay(([retryIndex, error]) => delayFor(error, retryIndex)), Schedule.whileInput(isRetryable));
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 : Schedule.jitteredWith(base, jitter === true ? {} : jitter);
118
- const schedule = options.maxElapsed === undefined ? jittered : Schedule.upTo(jittered, options.maxElapsed);
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, Either, SubscriptionRef } from "effect";
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.decode(options.resetAfter ?? "30 seconds"),
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.either(attempt);
185
- if (Either.isRight(result)) {
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.left.message}`);
190
- yield* SubscriptionRef.set(state, ConnectionState.Down({ cause: result.left }));
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 { Effect as Effect6, Layer } from "effect";
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.zipRight(resilience.guard, Effect3.suspend(operation)).pipe(Effect3.tapError((error) => resilience.report(error)));
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.optionalWith(Schema.Array(CoilDefinition), {
270
- default: () => []
271
- }),
272
- discreteInputs: Schema.optionalWith(Schema.Array(DiscreteInputDefinition), {
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.zipRight(Effect4.logDebug("Mock: reconnecting"), Effect4.suspend(() => {
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.zipRight(supervised ? guardCircuit(connectionState) : Effect4.void, Effect4.suspend(() => {
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.zipRight(reconnectOnce, SubscriptionRef2.set(connectionState, ConnectionState.Connected())),
498
- close: () => Effect4.zipRight(Effect4.logDebug("Mock: closing transport"), SubscriptionRef2.set(connectionState, ConnectionState.Disconnected())),
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.forkDaemon(Effect5.interruptible(work).pipe(Effect5.onExit((exit) => Effect5.zipRight(Ref.set(inFlight, Option.none()), Deferred.done(deferred, exit)))));
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.forkDaemon(Effect5.ignore(Effect5.tryPromise(() => t.close())));
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.zipRight(Effect5.sync(() => {
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.zipRight(Effect5.promise(() => t.close()), SubscriptionRef3.set(connectionState, ConnectionState.Disconnected())));
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 Effect6.Service()("AsciiTransportService", {
669
- scoped: makeTransportScoped("AsyncAsciiTransport", (TC, options) => TC.open(options), "AsciiTransportService")
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.scoped(AsciiTransportService, factory(options));
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 { Effect as Effect7, Layer as Layer2 } from "effect";
681
- class RtuTransportService extends Effect7.Service()("RtuTransportService", {
682
- scoped: makeTransportScoped("AsyncRtuTransport", (TC, options) => TC.open(options), "RtuTransportService")
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.scoped(RtuTransportService, factory(options));
682
+ return (options) => Layer2.effect(RtuTransportService, factory(options));
687
683
  };
688
684
  }
689
685
 
690
686
  // src/SerialTransportService.ts
691
- class SerialTransportService extends Context.Tag("SerialTransportService")() {
687
+ class SerialTransportService extends Context3.Service()("SerialTransportService") {
692
688
  static fromAscii(options) {
693
- return Layer3.project(AsciiTransportService, SerialTransportService, (ascii) => ascii)(AsciiTransportService.Default(options));
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.project(RtuTransportService, SerialTransportService, (rtu) => rtu)(RtuTransportService.Default(options));
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.scoped(SerialTransportService, factory(options));
696
+ return (options) => Layer3.effect(SerialTransportService, factory(options));
701
697
  };
702
698
  }
703
699
  // src/TcpTransportService.ts
704
- import { Effect as Effect8, Layer as Layer4 } from "effect";
705
- class TcpTransportService extends Effect8.Service()("TcpTransportService", {
706
- scoped: makeTransportScoped("AsyncTcpTransport", (TC, options) => TC.connect(options), "TcpTransportService")
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.scoped(TcpTransportService, factory(options));
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.scopedDiscard(Effect9.gen(function* () {
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.catchAll(() => Effect9.void)));
721
+ })), Effect9.catch(() => Effect9.void)));
726
722
  }));
727
- var serialAsciiServerLayer = (options, handlers) => Layer5.scopedDiscard(Effect9.gen(function* () {
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.catchAll(() => Effect9.void)));
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.scopedDiscard(Effect10.gen(function* () {
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.catchAll(() => Effect10.void)));
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.scopedDiscard(Effect11.gen(function* () {
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.catchAll(() => Effect11.void)));
761
+ })), Effect11.catch(() => Effect11.void)));
766
762
  }));
767
763
  // src/WasmWsTransportService.ts
768
- import { Effect as Effect12, Layer as Layer8 } from "effect";
769
- class WasmWsTransportService extends Effect12.Service()("WasmWsTransportService", {
770
- scoped: makeTransportScoped("WasmWsTransport", (TC, options) => TC.connect(options), "WasmWsTransportService", { moduleSpecifier: "modbus-rs/web" })
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.scoped(WasmWsTransportService, factory(options));
770
+ return (options) => Layer8.effect(WasmWsTransportService, factory(options));
775
771
  };
776
772
  }
777
773
  // src/WasmRtuTransportService.ts
778
- import { Effect as Effect13, Layer as Layer9 } from "effect";
779
- class WasmRtuTransportService extends Effect13.Service()("WasmRtuTransportService", {
780
- scoped: makeTransportScoped("WasmRtuTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmRtuTransportService", { moduleSpecifier: "modbus-rs/web" })
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.scoped(WasmRtuTransportService, factory(options));
780
+ return (options) => Layer9.effect(WasmRtuTransportService, factory(options));
785
781
  };
786
782
  }
787
783
  // src/WasmAsciiTransportService.ts
788
- import { Effect as Effect14, Layer as Layer10 } from "effect";
789
- class WasmAsciiTransportService extends Effect14.Service()("WasmAsciiTransportService", {
790
- scoped: makeTransportScoped("WasmAsciiTransport", (TC, { port, ...rest }) => TC.open(port, rest), "WasmAsciiTransportService", { moduleSpecifier: "modbus-rs/web" })
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.scoped(WasmAsciiTransportService, factory(options));
790
+ return (options) => Layer10.effect(WasmAsciiTransportService, factory(options));
795
791
  };
796
792
  }
797
793
  // src/WasmSerialTransportService.ts
798
- import { Context as Context2, Layer as Layer11 } from "effect";
799
- class WasmSerialTransportService extends Context2.Tag("WasmSerialTransportService")() {
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.project(WasmAsciiTransportService, WasmSerialTransportService, (ascii) => ascii)(WasmAsciiTransportService.Default(options));
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.project(WasmRtuTransportService, WasmSerialTransportService, (rtu) => rtu)(WasmRtuTransportService.Default(options));
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.scoped(WasmSerialTransportService, factory(options));
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.scopedDiscard(Effect16.gen(function* () {
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.catchAll((error) => Effect16.logError("WASM WS server loop ended", error))));
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.catchAll(() => Effect16.void)));
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.scopedDiscard(Effect17.gen(function* () {
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.catchAll((error) => Effect17.logError("WASM serial RTU server loop ended", error))));
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.catchAll(() => Effect17.void)));
850
+ })), Effect17.catch(() => Effect17.void)));
855
851
  }));
856
- var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.scopedDiscard(Effect17.gen(function* () {
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.catchAll((error) => Effect17.logError("WASM serial ASCII server loop ended", error))));
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.catchAll(() => Effect17.void)));
866
+ })), Effect17.catch(() => Effect17.void)));
871
867
  }));
872
868
  export {
873
- wasmWsServerLayer,
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
- AsciiTransportService
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: Effect.Service.Class<AsciiTransportService, "AsciiTransportService", {
13
- readonly scoped: (options: AsciiTransportOpenOptions & TransportResilienceOptions) => Effect.Effect<{
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
- * Scoped Effect service wrapping the `modbus-rs` {@link AsyncAsciiTransport}
37
- * for ASCII (serial) Modbus communication.
38
- *
39
- * The transport connection is opened lazily on the first call to
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: Effect.Service.Class<RtuTransportService, "RtuTransportService", {
13
- readonly scoped: (options: RtuTransportOpenOptions & TransportResilienceOptions) => Effect.Effect<{
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
- * Scoped Effect service wrapping the `modbus-rs` {@link AsyncRtuTransport}
37
- * for RTU (serial) Modbus communication.
38
- *
39
- * The transport connection is opened lazily on the first call to
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.