@fluojs/microservices 1.0.1 → 1.0.3

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.ko.md CHANGED
@@ -91,6 +91,7 @@ await microservice.listen();
91
91
  - RabbitMQ 요청-응답은 기본적으로 인스턴스별 response queue를 사용합니다. 공유 reply topology를 의도적으로 운영할 때만 `responseQueue`를 명시적으로 지정하세요.
92
92
  - caller-owned broker collaborator는 shutdown 중에도 caller-owned로 유지됩니다. NATS, Kafka, RabbitMQ transport는 subscription/consumer를 분리하고 in-flight 요청을 reject하지만, 애플리케이션이 넘긴 client, producer, consumer, publisher, 외부 connection 객체를 close/disconnect하지 않습니다.
93
93
  - `AbortSignal`을 받는 요청-응답 transport는 이미 abort된 send를 publish 전에 reject하고, 나중에 abort된 in-flight send도 reject합니다. `close()`가 시작된 뒤에는 shutdown 중인 lifecycle에 새 작업을 publish하지 않고 `send()`/`emit()`을 reject합니다.
94
+ - Root `@fluojs/microservices` barrel import와 `TcpMicroserviceTransport` 생성은 `node:net`을 load하지 않습니다. TCP는 `listen()`이 server를 시작하거나 outbound `send()`/`emit()`이 socket을 생성하는 경로에서만 Node networking을 lazy-load합니다. `close()`가 in-flight listen 시도를 기다리는 중 startup이 실패해도 microservice shutdown은 캡처한 listen error를 다시 surface하기 전에 transport cleanup을 시도합니다.
94
95
  - TCP는 테스트와 ephemeral listener를 위해 `port: 0`을 허용하고, listen 중에는 OS가 할당한 포트로 outbound `send()`/`emit()`을 라우팅합니다.
95
96
  - gRPC shutdown은 가능하면 server-level `tryShutdown()`을 사용하고, graceful shutdown을 제공하지 않는 런타임에서만 `forceShutdown()`으로 fallback합니다. Active unary/streaming call의 AbortSignal 취소는 call-level `cancel()` 또는 stream end 경로를 사용하며, stream이 end/error/early return으로 끝나면 abort listener를 제거합니다.
96
97
  - transport logger를 통해 이벤트 핸들러 실패를 기록하는 경로(`RedisPubSubMicroserviceTransport`, `RedisStreamsMicroserviceTransport`, `NatsMicroserviceTransport`, `MqttMicroserviceTransport`, gRPC event emit)는 끝까지 logger-driven observability를 유지합니다. transport logger를 주입하지 않으면 fluo는 해당 실패를 raw `console.error` fallback으로 복제하지 않습니다.
@@ -160,7 +161,7 @@ class ManualMicroserviceProvidersModule {}
160
161
 
161
162
  ### Type export
162
163
 
163
- Root barrel은 `Microservice`, `MicroserviceModuleOptions`, `MicroserviceModuleRegistrationOptions`, `MicroserviceTransport`, `Pattern`, `ServerStreamWriter`와 `GrpcMicroserviceTransportOptions`, `KafkaMicroserviceTransportOptions`, `MqttMicroserviceTransportOptions`, `NatsMicroserviceTransportOptions`, `RabbitMqMicroserviceTransportOptions`, `RedisPubSubMicroserviceTransportOptions`, `RedisStreamsMicroserviceTransportOptions`, `RedisStreamClientLike` 같은 transport option type을 export합니다.
164
+ Root barrel은 `Microservice`, `MicroserviceLifecycleState`, `MicroserviceHandlerCounts`, `MicroserviceModuleOptions`, `MicroserviceModuleRegistrationOptions`, `MicroservicePlatformStatusSnapshot`, `MicroserviceStatusAdapterInput`, `MicroserviceTransport`, `MicroserviceTransportCapabilities`, `Pattern`, `ServerStreamWriter`와 `GrpcMicroserviceTransportOptions`, `KafkaMicroserviceTransportOptions`, `MqttMicroserviceTransportOptions`, `NatsMicroserviceTransportOptions`, `RabbitMqMicroserviceTransportOptions`, `RedisPubSubMicroserviceTransportOptions`, `RedisStreamsMicroserviceTransportOptions`, `RedisStreamClientLike` 같은 transport option type을 export합니다.
164
165
 
165
166
  ### 동작 계약
166
167
 
package/README.md CHANGED
@@ -88,6 +88,7 @@ Microservice handlers fully support fluo's DI scopes. Request-scoped providers a
88
88
  - RabbitMQ request/reply uses an instance-scoped response queue by default. Pass `responseQueue` explicitly only when you intentionally own and coordinate a shared reply topology.
89
89
  - Caller-owned broker collaborators stay caller-owned during shutdown. NATS, Kafka, and RabbitMQ transports detach their subscriptions/consumers and reject in-flight requests, but they do not close or disconnect the client, producer, consumer, publisher, or external connection objects supplied by the application.
90
90
  - Request-response transports that accept `AbortSignal` reject already-aborted sends before publishing and reject in-flight sends on later abort. Once `close()` starts, transports reject new `send()`/`emit()` calls instead of publishing work into a shutting-down lifecycle.
91
+ - Importing the root `@fluojs/microservices` barrel and constructing `TcpMicroserviceTransport` do not load `node:net`; TCP loads Node networking only when `listen()` starts a server or an outbound `send()`/`emit()` constructs a socket. If startup fails while `close()` is waiting on an in-flight listen attempt, microservice shutdown still attempts transport cleanup before surfacing the captured listen error.
91
92
  - TCP accepts `port: 0` for tests and ephemeral listeners, then routes outbound `send()`/`emit()` calls through the OS-assigned port while the transport is listening.
92
93
  - gRPC shutdown uses server-level `tryShutdown()` when available and falls back to `forceShutdown()` only for runtimes without graceful shutdown support. AbortSignal cancellation for active unary or streaming calls uses the call-level `cancel()`/stream end path and removes abort listeners when streams end, error, or are returned early.
93
94
  - Event-handler failures that flow through the transport logger (`RedisPubSubMicroserviceTransport`, `RedisStreamsMicroserviceTransport`, `NatsMicroserviceTransport`, `MqttMicroserviceTransport`, and gRPC event emits) remain logger-driven. If you do not inject a transport logger, fluo does not mirror those failures through a raw `console.error` fallback.
@@ -157,7 +158,7 @@ class ManualMicroserviceProvidersModule {}
157
158
 
158
159
  ### Type exports
159
160
 
160
- The root barrel exports `Microservice`, `MicroserviceModuleOptions`, `MicroserviceModuleRegistrationOptions`, `MicroserviceTransport`, `Pattern`, `ServerStreamWriter`, and transport option types such as `GrpcMicroserviceTransportOptions`, `KafkaMicroserviceTransportOptions`, `MqttMicroserviceTransportOptions`, `NatsMicroserviceTransportOptions`, `RabbitMqMicroserviceTransportOptions`, `RedisPubSubMicroserviceTransportOptions`, `RedisStreamsMicroserviceTransportOptions`, and `RedisStreamClientLike`.
161
+ The root barrel exports `Microservice`, `MicroserviceLifecycleState`, `MicroserviceHandlerCounts`, `MicroserviceModuleOptions`, `MicroserviceModuleRegistrationOptions`, `MicroservicePlatformStatusSnapshot`, `MicroserviceStatusAdapterInput`, `MicroserviceTransport`, `MicroserviceTransportCapabilities`, `Pattern`, `ServerStreamWriter`, and transport option types such as `GrpcMicroserviceTransportOptions`, `KafkaMicroserviceTransportOptions`, `MqttMicroserviceTransportOptions`, `NatsMicroserviceTransportOptions`, `RabbitMqMicroserviceTransportOptions`, `RedisPubSubMicroserviceTransportOptions`, `RedisStreamsMicroserviceTransportOptions`, and `RedisStreamClientLike`.
161
162
 
162
163
  ### Behavioral contracts
163
164
 
package/dist/service.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Container } from '@fluojs/di';
2
- import { type ApplicationLogger, type CompiledModule, type MicroserviceRuntime, type OnApplicationShutdown } from '@fluojs/runtime';
2
+ import type { ApplicationLogger, CompiledModule, MicroserviceRuntime, OnApplicationShutdown } from '@fluojs/runtime';
3
3
  import type { Microservice, MicroserviceModuleOptions, ServerStreamWriter } from './types.js';
4
4
  /**
5
5
  * Lifecycle-managed microservice runtime that discovers pattern handlers and binds them to a transport.
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAMzB,OAAO,KAAK,EAGV,YAAY,EACZ,yBAAyB,EAEzB,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA6BpB;;;;;GAKG;AACH,qBACa,4BAA6B,YAAW,YAAY,EAAE,mBAAmB,EAAE,qBAAqB;IASzG,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAXhC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,cAAc,CAAmF;IACzG,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;gBAG9B,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,yBAAyB;IAG3D;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D7B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;OAIG;IACH,4BAA4B;IAqB5B;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAU7F;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG;QAAE,MAAM,EAAE,kBAAkB,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE;IAU7G;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG;QAAE,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,kBAAkB,CAAA;KAAE;YAUnG,cAAc;YAyBd,oBAAoB;YA+BpB,yBAAyB;YAiCzB,iCAAiC;YAuBjC,oBAAoB;YA4BpB,yBAAyB;YA+BzB,iCAAiC;YAsBjC,kBAAkB;YA+BlB,uBAAuB;YAiCvB,+BAA+B;YAuB/B,qBAAqB;YA8DrB,4BAA4B;IAK1C,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,0BAA0B;IAkClC,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,mBAAmB;YAsCb,aAAa;YAuBb,qBAAqB;YA2BrB,+BAA+B;CAsB9C"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAMrH,OAAO,KAAK,EAGV,YAAY,EACZ,yBAAyB,EAEzB,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA6BpB;;;;;GAKG;AACH,qBACa,4BAA6B,YAAW,YAAY,EAAE,mBAAmB,EAAE,qBAAqB;IASzG,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAXhC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,cAAc,CAAmF;IACzG,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;gBAG9B,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,yBAAyB;IAG3D;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D7B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BtB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;OAIG;IACH,4BAA4B;IAqB5B;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAU7F;;;;;;;;OAQG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG;QAAE,MAAM,EAAE,kBAAkB,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE;IAU7G;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG;QAAE,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,kBAAkB,CAAA;KAAE;YAUnG,cAAc;YAyBd,oBAAoB;YA+BpB,yBAAyB;YAiCzB,iCAAiC;YAuBjC,oBAAoB;YA4BpB,yBAAyB;YA+BzB,iCAAiC;YAsBjC,kBAAkB;YA+BlB,uBAAuB;YAiCvB,+BAA+B;YAuB/B,qBAAqB;YA8DrB,4BAA4B;IAK1C,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,0BAA0B;IAkClC,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,mBAAmB;YAsCb,aAAa;YAuBb,qBAAqB;YA2BrB,+BAA+B;CAsB9C"}
package/dist/service.js CHANGED
@@ -106,13 +106,23 @@ class MicroserviceLifecycleService {
106
106
  * @returns A promise that resolves once shutdown completes.
107
107
  */
108
108
  async close() {
109
+ let listenError;
109
110
  if (this.listenPromise) {
110
- await this.listenPromise;
111
+ try {
112
+ await this.listenPromise;
113
+ } catch (error) {
114
+ listenError = error;
115
+ }
111
116
  }
112
117
  this.lifecycleState = 'stopping';
113
118
  try {
114
119
  await this.moduleOptions.transport.close();
115
120
  this.listening = false;
121
+ if (listenError) {
122
+ this.lifecycleState = 'failed';
123
+ this.lastListenError = listenError instanceof Error ? listenError.message : String(listenError);
124
+ throw listenError;
125
+ }
116
126
  this.lifecycleState = 'stopped';
117
127
  } catch (error) {
118
128
  this.lifecycleState = 'failed';
@@ -17,11 +17,12 @@ export declare class TcpMicroserviceTransport implements MicroserviceTransport {
17
17
  private closing;
18
18
  private handler;
19
19
  private listenPromise;
20
+ private server;
21
+ private serverPromise;
20
22
  private readonly sockets;
21
23
  private readonly host;
22
24
  private readonly maxFrameBytes;
23
25
  private readonly requestTimeoutMs;
24
- private readonly server;
25
26
  /**
26
27
  * Creates a TCP transport bound to one host/port pair.
27
28
  *
@@ -35,6 +36,7 @@ export declare class TcpMicroserviceTransport implements MicroserviceTransport {
35
36
  * @returns A promise that resolves once the TCP server is listening.
36
37
  */
37
38
  listen(handler: TransportHandler): Promise<void>;
39
+ private startListening;
38
40
  /**
39
41
  * Emits one fire-and-forget event over the TCP transport.
40
42
  *
@@ -63,6 +65,7 @@ export declare class TcpMicroserviceTransport implements MicroserviceTransport {
63
65
  private assertAcceptingOutbound;
64
66
  private resolveConnectPort;
65
67
  private resolveBoundPort;
68
+ private ensureServer;
66
69
  private bindSocketParser;
67
70
  private writeLine;
68
71
  private serializeFrame;
@@ -1 +1 @@
1
- {"version":3,"file":"tcp-transport.d.ts","sourceRoot":"","sources":["../../src/transports/tcp-transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAmB,MAAM,aAAa,CAAC;AAQ5F,UAAU,+BAA+B;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IAoBxD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAnBpC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAIpB;IAEH;;;;OAIG;gBAC0B,OAAO,EAAE,+BAA+B;IAMrE;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgCtD;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5D;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAMrF;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAgCd,mBAAmB;YA8BnB,cAAc;IA0G5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,cAAc;CASvB"}
1
+ {"version":3,"file":"tcp-transport.d.ts","sourceRoot":"","sources":["../../src/transports/tcp-transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAmB,MAAM,aAAa,CAAC;AAQ5F,UAAU,+BAA+B;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IAiBxD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhBpC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C;;;;OAIG;gBAC0B,OAAO,EAAE,+BAA+B;IAMrE;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAqBxC,cAAc;IAyB5B;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5D;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAMrF;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Cd,mBAAmB;YA8BnB,cAAc;IA2G5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB;YAUV,YAAY;IAyB1B,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,cAAc;CASvB"}
@@ -1,4 +1,3 @@
1
- import { createServer, Socket } from 'node:net';
2
1
  const DEFAULT_MAX_FRAME_BYTES = 1_048_576;
3
2
 
4
3
  /**
@@ -12,15 +11,12 @@ export class TcpMicroserviceTransport {
12
11
  closing = false;
13
12
  handler;
14
13
  listenPromise;
14
+ server;
15
+ serverPromise;
15
16
  sockets = new Set();
16
17
  host;
17
18
  maxFrameBytes;
18
19
  requestTimeoutMs;
19
- server = createServer(socket => {
20
- this.sockets.add(socket);
21
- this.bindSocketParser(socket, async packet => this.handleInboundPacket(socket, packet));
22
- socket.once('close', () => this.sockets.delete(socket));
23
- });
24
20
 
25
21
  /**
26
22
  * Creates a TCP transport bound to one host/port pair.
@@ -45,25 +41,35 @@ export class TcpMicroserviceTransport {
45
41
  throw new Error('TcpMicroserviceTransport is closing. Wait for close() to complete before listen().');
46
42
  }
47
43
  this.handler = handler;
48
- if (this.server.listening) {
44
+ if (this.listenPromise) {
45
+ await this.listenPromise;
49
46
  return;
50
47
  }
51
- if (this.listenPromise) {
48
+ this.listenPromise = this.startListening();
49
+ try {
52
50
  await this.listenPromise;
51
+ } finally {
52
+ this.listenPromise = undefined;
53
+ }
54
+ }
55
+ async startListening() {
56
+ const server = await this.ensureServer();
57
+ if (this.closing) {
58
+ throw new Error('TcpMicroserviceTransport is closing. Wait for close() to complete before listen().');
59
+ }
60
+ if (server.listening) {
53
61
  return;
54
62
  }
55
- this.listenPromise = new Promise((resolve, reject) => {
56
- this.server.once('error', reject);
57
- this.server.listen(this.options.port, this.host, () => {
58
- this.server.off('error', reject);
63
+ await new Promise((resolve, reject) => {
64
+ server.once('error', reject);
65
+ server.listen(this.options.port, this.host, () => {
66
+ server.off('error', reject);
59
67
  this.boundPort = this.resolveBoundPort();
60
68
  resolve();
61
69
  });
62
70
  });
63
- try {
64
- await this.listenPromise;
65
- } finally {
66
- this.listenPromise = undefined;
71
+ if (this.closing) {
72
+ throw new Error('TcpMicroserviceTransport is closing. Wait for close() to complete before listen().');
67
73
  }
68
74
  }
69
75
 
@@ -109,19 +115,27 @@ export class TcpMicroserviceTransport {
109
115
  */
110
116
  async close() {
111
117
  this.closing = true;
118
+ let listenError;
112
119
  if (this.listenPromise) {
113
- await this.listenPromise;
120
+ try {
121
+ await this.listenPromise;
122
+ } catch (error) {
123
+ listenError = error;
124
+ }
114
125
  }
115
126
  for (const socket of this.sockets) {
116
127
  socket.destroy();
117
128
  }
118
129
  this.sockets.clear();
119
- if (!this.server.listening) {
130
+ if (!this.server?.listening) {
120
131
  this.boundPort = undefined;
132
+ if (listenError) {
133
+ throw listenError;
134
+ }
121
135
  return;
122
136
  }
123
137
  await new Promise((resolve, reject) => {
124
- this.server.close(error => {
138
+ this.server?.close(error => {
125
139
  if (error) {
126
140
  reject(error);
127
141
  return;
@@ -130,6 +144,9 @@ export class TcpMicroserviceTransport {
130
144
  });
131
145
  });
132
146
  this.boundPort = undefined;
147
+ if (listenError) {
148
+ throw listenError;
149
+ }
133
150
  }
134
151
  async handleInboundPacket(socket, packet) {
135
152
  if (!this.handler) {
@@ -163,6 +180,9 @@ export class TcpMicroserviceTransport {
163
180
  }
164
181
  async sendWirePacket(packet, signal) {
165
182
  const serializedPacket = this.serializeFrame(packet);
183
+ const {
184
+ Socket
185
+ } = await import('node:net');
166
186
  return await new Promise((resolve, reject) => {
167
187
  const socket = new Socket();
168
188
  let settled = false;
@@ -255,7 +275,7 @@ export class TcpMicroserviceTransport {
255
275
  if (this.closing) {
256
276
  throw new Error(`TcpMicroserviceTransport is closing. Wait for close() to complete before ${operation}().`);
257
277
  }
258
- if (!this.server.listening || typeof this.boundPort !== 'number') {
278
+ if (!this.server?.listening || typeof this.boundPort !== 'number') {
259
279
  throw new Error(`TcpMicroserviceTransport is not listening. Call listen() before ${operation}().`);
260
280
  }
261
281
  }
@@ -266,12 +286,35 @@ export class TcpMicroserviceTransport {
266
286
  return this.options.port;
267
287
  }
268
288
  resolveBoundPort() {
269
- const address = this.server.address();
289
+ const address = this.server?.address();
270
290
  if (address && typeof address === 'object') {
271
291
  return address.port;
272
292
  }
273
293
  return this.options.port;
274
294
  }
295
+ async ensureServer() {
296
+ if (this.server) {
297
+ return this.server;
298
+ }
299
+ this.serverPromise ??= (async () => {
300
+ const {
301
+ createServer
302
+ } = await import('node:net');
303
+ const server = createServer(socket => {
304
+ this.sockets.add(socket);
305
+ this.bindSocketParser(socket, async packet => this.handleInboundPacket(socket, packet));
306
+ socket.once('close', () => this.sockets.delete(socket));
307
+ });
308
+ this.server = server;
309
+ return server;
310
+ })();
311
+ try {
312
+ return await this.serverPromise;
313
+ } catch (error) {
314
+ this.serverPromise = undefined;
315
+ throw error;
316
+ }
317
+ }
275
318
  bindSocketParser(socket, onPacket) {
276
319
  let buffer = '';
277
320
  let bufferBytes = 0;
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "grpc",
14
14
  "mqtt"
15
15
  ],
16
- "version": "1.0.1",
16
+ "version": "1.0.3",
17
17
  "private": false,
18
18
  "license": "MIT",
19
19
  "repository": {
@@ -68,9 +68,9 @@
68
68
  "dist"
69
69
  ],
70
70
  "dependencies": {
71
- "@fluojs/di": "^1.0.1",
72
- "@fluojs/core": "^1.0.1",
73
- "@fluojs/runtime": "^1.0.1"
71
+ "@fluojs/core": "^1.0.3",
72
+ "@fluojs/di": "^1.1.0",
73
+ "@fluojs/runtime": "^1.1.7"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@grpc/grpc-js": "^1.0.0",