@fluojs/microservices 1.0.5 → 2.0.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.ko.md +20 -6
- package/README.md +20 -6
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +1 -0
- package/dist/service.d.ts +12 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +92 -36
- package/dist/status.d.ts +3 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +10 -2
- package/dist/transports/grpc-transport.d.ts +4 -2
- package/dist/transports/grpc-transport.d.ts.map +1 -1
- package/dist/transports/grpc-transport.js +77 -12
- package/dist/transports/kafka-transport.d.ts +9 -1
- package/dist/transports/kafka-transport.d.ts.map +1 -1
- package/dist/transports/kafka-transport.js +29 -5
- package/dist/transports/nats-transport.d.ts +2 -0
- package/dist/transports/nats-transport.d.ts.map +1 -1
- package/dist/transports/nats-transport.js +50 -27
- package/dist/transports/rabbitmq-transport.d.ts +9 -1
- package/dist/transports/rabbitmq-transport.d.ts.map +1 -1
- package/dist/transports/rabbitmq-transport.js +38 -13
- package/dist/transports/redis-streams-transport.d.ts +18 -0
- package/dist/transports/redis-streams-transport.d.ts.map +1 -1
- package/dist/transports/redis-streams-transport.js +31 -1
- package/dist/transports/redis-transport.d.ts +1 -1
- package/dist/transports/redis-transport.d.ts.map +1 -1
- package/dist/transports/redis-transport.js +32 -16
- package/dist/transports/tcp-transport.d.ts +1 -0
- package/dist/transports/tcp-transport.d.ts.map +1 -1
- package/dist/transports/tcp-transport.js +45 -44
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -7
package/README.ko.md
CHANGED
|
@@ -7,6 +7,7 @@ fluo용 트랜스포트 기반 마이크로서비스 패키지입니다. TCP, Re
|
|
|
7
7
|
## 목차
|
|
8
8
|
|
|
9
9
|
- [설치](#설치)
|
|
10
|
+
- [요구 사항](#요구-사항)
|
|
10
11
|
- [사용 시점](#사용-시점)
|
|
11
12
|
- [빠른 시작](#빠른-시작)
|
|
12
13
|
- [주요 기능](#주요-기능)
|
|
@@ -26,6 +27,12 @@ pnpm add @fluojs/microservices
|
|
|
26
27
|
- `@fluojs/microservices`가 직접 로드하는 선택적 peer: `@grpc/grpc-js`, `@grpc/proto-loader`, `ioredis`, `mqtt`
|
|
27
28
|
- 애플리케이션이 transport에 명시적으로 넘겨야 하는 caller-owned broker client: `nats`, `kafkajs`, `amqplib`
|
|
28
29
|
|
|
30
|
+
gRPC transport는 `@grpc/grpc-js@^1.14.4`와 `@grpc/proto-loader@^0.8.0`을 요구합니다. 더 오래된 `@grpc/grpc-js` release를 사용하던 consumer는 이 major `@fluojs/microservices` release를 적용하기 전에 peer를 업그레이드하고 lockfile을 갱신해야 합니다. 갱신된 install은 proto-loader chain을 `protobufjs@7.6.5` 이상으로 resolve해 수정된 UTF-8 helper를 포함해야 합니다. fluo transport API는 그대로입니다.
|
|
31
|
+
|
|
32
|
+
## 요구 사항
|
|
33
|
+
|
|
34
|
+
`@fluojs/microservices`는 패키지 자체의 지원 계약인 Node.js `>=24.0.0 <27`를 요구합니다. Node 24 미만과 Node 27 이상은 지원하지 않습니다.
|
|
35
|
+
|
|
29
36
|
## 사용 시점
|
|
30
37
|
|
|
31
38
|
- 서비스 간 통신을 메시지나 이벤트 중심으로 분리하고 싶을 때
|
|
@@ -114,27 +121,33 @@ Redis Pub/Sub은 동일한 등록 경로를 사용하기 위해 공통 transport
|
|
|
114
121
|
| --- | --- |
|
|
115
122
|
| `await microservice.send(...)` | transport가 상관관계가 유지된 원격 응답을 반환할 때 settle하며, 원격 오류, abort, timeout, shutdown 시 reject합니다. |
|
|
116
123
|
| `await microservice.emit(...)` | transport의 publish 연산이 outbound event를 accept/complete할 때 settle합니다. 원격 event handler를 기다리거나 collaborator의 publish 계약을 넘어선 delivery/redelivery 보장을 추가하지는 않습니다. |
|
|
117
|
-
| `await microservice.close()` | transport-owned listener/subscription teardown과 pending-request cleanup을
|
|
124
|
+
| `await microservice.close()` | 반복 호출은 하나의 shutdown 결과를 공유합니다. 이미 수락된 inbound handler가 settle할 때까지 기다린 뒤 transport-owned listener/subscription teardown과 pending-request cleanup을 수행합니다. Caller-owned NATS, Kafka, RabbitMQ collaborator의 경우 전달받은 broker resource를 close/disconnect하지 않습니다. |
|
|
118
125
|
|
|
119
126
|
Kafka와 RabbitMQ는 일치한 handler와 request response publication이 settle할 때까지 각 inbound consumer callback을 pending 상태로 유지합니다. 이 consumer-side completion boundary를 통해 broker adapter가 delivery를 acknowledge할지 retry할지 결정할 수 있지만, producer-side `emit()` promise가 end-to-end handler completion signal로 바뀌는 것은 아닙니다. 애플리케이션 shutdown에서는 먼저 `Microservice` facade를 닫아 transport callback을 detach한 다음, caller-owned client, producer, consumer, publisher, channel, connection을 application bootstrap layer에서 close 또는 drain하세요.
|
|
120
127
|
|
|
121
128
|
### 전달 안전 기본값
|
|
122
129
|
|
|
123
|
-
- TCP 프레임은 기본적으로 newline-delimited 메시지당 1 MiB로 제한되며, 한도를 넘는 프레임은 요청 버퍼를 무한히 키우는 대신 소켓을 종료합니다.
|
|
130
|
+
- TCP 프레임은 raw byte로 버퍼링하고 구분한 뒤 완성된 각 프레임을 UTF-8로 한 번만 디코딩합니다. 따라서 여러 socket chunk에 걸쳐 분할된 multibyte code point도 그대로 유지됩니다. 프레임은 기본적으로 newline-delimited 메시지당 1 MiB로 제한되며, 한도를 넘는 프레임은 요청 버퍼를 무한히 키우는 대신 소켓을 종료합니다.
|
|
131
|
+
- Redis Pub/Sub은 runtime dispatch 전에 inbound JSON frame을 검증합니다. Invalid JSON, non-object payload, unknown kind, string `pattern`이 없는 event frame은 callback boundary에서 discard하고 configured transport logger를 통해 보고합니다. logger가 없거나 logger가 throw해도 caller-owned subscriber를 닫거나 `console.error` fallback으로 기록하지 않습니다.
|
|
124
132
|
- Redis Streams는 요청/이벤트 엔트리를 핸들러 처리가 끝난 뒤에만 ACK합니다. 실패한 이벤트는 조기 ACK로 유실하지 않고 broker 복구/재전달 경로에 남겨 둡니다.
|
|
125
|
-
- Kafka와 RabbitMQ는 inbound event/request 처리와 response publish가 끝날 때까지 consumer delivery completion을 pending 상태로 유지합니다. Event-handler와 response-publish 실패는 consumer callback을 reject해 broker adapter가 ACK를 보류하거나 재시도할 수 있게 하며, request-handler 오류는 error response를 publish할 수 있으면 기존처럼 호출자에게 전달합니다.
|
|
133
|
+
- Kafka와 RabbitMQ는 inbound event/request 처리와 response publish가 끝날 때까지 consumer delivery completion을 pending 상태로 유지합니다. Event-handler와 response-publish 실패는 consumer callback을 reject해 broker adapter가 ACK를 보류하거나 재시도할 수 있게 하며, request-handler 오류는 error response를 publish할 수 있으면 기존처럼 호출자에게 전달합니다. Inbound event-handler 실패는 rethrow 전에 설정된 transport logger를 통해 추가로 보고합니다. logger가 없거나 logger가 throw해도 해당 실패를 가리지 않으며 `console.error` fallback으로 기록하지 않습니다.
|
|
126
134
|
- Redis Streams는 기본적으로 live request/event stream에 publish-time trimming을 적용하지 않으므로, pending 엔트리가 `xack` 또는 consumer-group 복구 경로가 끝나기 전에 잘리지 않습니다. ACK가 끝난 request/reply 엔트리는 정리되고, 인스턴스별 response stream은 기본적으로 bounded retention(`responseRetentionMaxLen: 1_000`)을 유지한 뒤 `close()` 중 삭제됩니다.
|
|
135
|
+
- `readerClient.xautoclaim`을 제공하면 Redis Streams는 `pendingReclaimIdleMs` 동안 유휴 상태인 공유 request consumer group의 pending request 엔트리를 reclaim합니다(기본값: `60_000`). 여기에는 crash된 consumer가 남긴 엔트리도 포함됩니다. 또한 같은 listener의 instance-scoped event group에서 실패한 event 엔트리를 reclaim합니다. broadcast delivery를 보존하기 위해 event group은 UUID별로 분리되므로 replacement listener는 crash된 listener의 event PEL을 reclaim할 수 없습니다. 이 옵션을 0 또는 음수로 설정하면 reclaim을 끌 수 있으며, adapter는 다음 `close()`까지 consumer group별 `XAUTOCLAIM` cursor를 유지합니다.
|
|
127
136
|
- Redis Streams는 `close()` 중 인스턴스별 response stream은 항상 삭제하지만, 활성 fleet 전체에서 ownership를 증명할 수 없으면 공유 request consumer group은 보수적으로 유지합니다. lease-capable listener는 coordination metadata만 정리하고, mixed/fallback fleet에서는 살아 있는 다른 listener가 여전히 필요로 할 수 있으므로 공유 request group을 제거하지 않습니다.
|
|
128
137
|
- `messageRetentionMaxLen`과 `eventRetentionMaxLen`은 고급 opt-in 설정으로 남아 있습니다. 이를 켜면 Redis가 ACK 전 pending live-stream 엔트리를 먼저 trim할 수 있으므로 broker-managed recovery 보장을 일부 포기하는 운영 판단이 됩니다.
|
|
129
138
|
- RabbitMQ 요청-응답은 기본적으로 인스턴스별 response queue를 사용합니다. 공유 reply topology를 의도적으로 운영할 때만 `responseQueue`를 명시적으로 지정하세요.
|
|
130
139
|
- caller-owned broker collaborator는 shutdown 중에도 caller-owned로 유지됩니다. NATS, Kafka, RabbitMQ transport는 subscription/consumer를 분리하고 in-flight 요청을 reject하지만, 애플리케이션이 넘긴 client, producer, consumer, publisher, 외부 connection 객체를 close/disconnect하지 않습니다.
|
|
131
140
|
- NATS subscription setup이 `listen()` 중 실패하면 transport는 해당 시도에서 이미 생성한 subscription을 setup의 역순으로 unsubscribe하고 caller-owned NATS client는 열어 둡니다.
|
|
132
|
-
-
|
|
141
|
+
- NATS shutdown 중에는 하나의 unsubscribe가 실패해도 모든 subscription cleanup을 시도하고, 실패한 subscription reference를 이후 `close()` 재시도를 위해 유지합니다. 단일 실패는 그대로 보고하고 여러 실패는 `AggregateError`로 보고하며, 이미 성공한 subscription cleanup은 반복하지 않습니다. 유지된 cleanup이 성공하기 전에는 `listen()`을 다시 시작할 수 없습니다.
|
|
142
|
+
- RabbitMQ shutdown 중에는 하나의 consumer cancel이 실패해도 모든 consumer cancellation을 시도하고, 실패한 queue reference를 이후 `close()` 재시도를 위해 유지하며 이미 성공한 cancellation은 반복하지 않습니다. 유지된 cleanup이 성공하기 전에는 `listen()`을 다시 시작할 수 없습니다.
|
|
143
|
+
- NATS request subscription callback은 malformed request frame, response encoding 실패, 예외를 던지는 `respond()` callback을 async callback boundary 안에서 격리합니다. 이러한 실패는 caller-owned NATS client를 닫지 않고 설정된 transport logger를 통해 보고됩니다. Transport logger가 설정되지 않았으면 fluo는 raw `console.error` fallback을 추가하지 않습니다. Encoding 및 publish가 가능한 request-handler 오류는 계속 error response로 왕복합니다.
|
|
144
|
+
- `AbortSignal`을 받는 요청-응답 transport는 이미 abort된 send를 publish 전에 reject하고, deferred broker/RPC dispatch 직전 cancellation을 다시 확인하며, 나중에 abort된 in-flight send도 reject합니다. `close()`가 시작되면 programmatic `Microservice` facade의 terminal ingress gate가 `listen()`이 아직 pending 상태여도 transport handoff 전에 새 `send()`, `emit()`, `serverStream()`, `clientStream()`, `bidiStream()` 호출을 reject하고, runtime shell은 같은 terminal gate를 `send()`와 `emit()`에 적용합니다. Transport adapter는 자체 shutdown guard를 계속 유지하고, 동시 `listen()` 호출은 아직 진행 중인 shutdown 상태를 reset할 수 없으며, 동시에 또는 반복해서 호출된 TCP `close()`는 첫 shutdown promise를 공유해 listener와 socket cleanup을 한 번만 수행합니다.
|
|
133
145
|
- Programmatic `Microservice` facade는 런타임 shutdown hook이 종료를 시작한 signal을 전달할 수 있도록 `close(signal?: string)`을 받습니다. `MicroserviceLifecycleService.close(signal)`은 이 lifecycle-compatible facade 계약을 유지하면서도 현재 설정된 transport에는 기존 `close(): Promise<void>` 계약으로 호출합니다. 각 transport는 자체 문서가 signal-aware shutdown을 명시하지 않는 한 계속 인자를 받지 않는 shutdown adapter입니다.
|
|
134
146
|
- 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을 시도합니다.
|
|
135
147
|
- TCP는 테스트와 ephemeral listener를 위해 `port: 0`을 허용하고, listen 중에는 OS가 할당한 포트로 outbound `send()`/`emit()`을 라우팅합니다.
|
|
136
|
-
- Platform status snapshot은 transport resource ownership을 보고합니다. TCP와 internally-created gRPC server는 framework-owned listener/client resource로 보고하고, MQTT는 client를 직접 생성한 경우에만 framework ownership을 보고하며, caller-
|
|
148
|
+
- Platform status snapshot은 mixed transport resource ownership을 하나의 owner로 축약하지 않고 보고합니다. TCP와 internally-created gRPC server는 framework-owned listener/client resource로 보고하고, MQTT는 client를 직접 생성한 경우에만 framework ownership을 보고하며, caller-owned broker collaborator transport는 externally managed로 남습니다. 전달받은 server를 쓰는 gRPC에서는 `ownership.externallyManaged`와 `ownership.ownsResources`가 모두 `true`이고, `details.transportResourceOwnership`이 caller-supplied gRPC server와 framework-owned cached outbound client를 각각 구분합니다.
|
|
137
149
|
- gRPC shutdown은 transport가 server를 직접 생성한 경우 server-level `tryShutdown()`을 사용하고, graceful shutdown을 제공하지 않는 런타임에서만 `forceShutdown()`으로 fallback합니다. Caller-supplied `GrpcMicroserviceTransportOptions.server` 인스턴스는 `close()` 중에도 caller-owned로 유지되며, fluo는 cached outbound client만 닫고 해당 server는 shutdown하지 않습니다. Active unary/streaming call의 AbortSignal 취소는 call-level `cancel()` 또는 stream end 경로를 사용합니다. fluo는 unary call이 settle한 뒤, streaming call이 reader iteration 시작 전에 terminal event를 낸 경우를 포함해 end/error로 끝난 뒤, 또는 reader가 early return한 뒤 각 `AbortSignal` abort listener를 제거합니다. Terminal, cancellation, iterator-return 경로가 겹쳐도 cleanup은 한 번만 수행됩니다.
|
|
150
|
+
- Outbound gRPC `clientStream()`과 `bidiStream()` writer는 `writer.error(err)`를 clean end로 처리하지 않고 그대로 전파합니다. fluo는 call-level `destroy(err)` 경로로 outbound call을 abort하며, 이를 제공하지 않는 런타임에서는 `cancel()`, 마지막으로 `end()` 순으로 fallback합니다. 따라서 remote peer는 성공적인 end-of-stream이 아니라 실패한 RPC를 관측합니다. `clientStream()` result promise를 reject하고 `bidiStream()` reader에 노출되는 것은 abort 뒤에 뒤따르는 transport-level cancellation status가 아니라 caller가 전달한 원본 error입니다. `writer.error()`를 반복 호출하거나 그 뒤에 `end()`를 호출해도 무시되므로, call은 한 번만 abort되고 처음 보고된 원인이 유지됩니다.
|
|
138
151
|
- MQTT는 `listen()` 중 subscription setup이 실패하거나 `close()`가 실패한 in-flight listen 시도를 unwinding할 때 internally-created client를 닫고, 호출자에게는 원래 startup error를 보존해 전달합니다. Caller-supplied MQTT client는 계속 caller-owned로 남습니다.
|
|
139
152
|
- transport logger를 통해 이벤트 핸들러 실패를 기록하는 경로(`RedisPubSubMicroserviceTransport`, `RedisStreamsMicroserviceTransport`, `NatsMicroserviceTransport`, `MqttMicroserviceTransport`, gRPC event emit)는 끝까지 logger-driven observability를 유지합니다. transport logger를 주입하지 않으면 fluo는 해당 실패를 raw `console.error` fallback으로 복제하지 않습니다.
|
|
140
153
|
|
|
@@ -214,13 +227,14 @@ Payload는 dispatch 전에 clone되고, 동시 `listen()` 호출은 dedupe되며
|
|
|
214
227
|
|
|
215
228
|
- `@fluojs/microservices/tcp`
|
|
216
229
|
- `@fluojs/microservices/redis` (Redis Pub/Sub 트랜스포트)
|
|
230
|
+
- `@fluojs/microservices/redis-streams`
|
|
217
231
|
- `@fluojs/microservices/nats`
|
|
218
232
|
- `@fluojs/microservices/kafka`
|
|
219
233
|
- `@fluojs/microservices/rabbitmq`
|
|
220
234
|
- `@fluojs/microservices/grpc`
|
|
221
235
|
- `@fluojs/microservices/mqtt`
|
|
222
236
|
|
|
223
|
-
`RedisStreamsMicroserviceTransport`는
|
|
237
|
+
`RedisStreamsMicroserviceTransport`, `RedisStreamsMicroserviceTransportOptions`, `RedisStreamClientLike`는 루트 배럴과 전용 `@fluojs/microservices/redis-streams` 서브패스에서 모두 사용할 수 있습니다.
|
|
224
238
|
|
|
225
239
|
정식 transport 학습 자료는 [TCP](../../book/intermediate/ch02-tcp.ko.md), [RabbitMQ](../../book/intermediate/ch04-rabbitmq.ko.md), [gRPC](../../book/intermediate/ch08-grpc.ko.md) 책 장에 있으며, 이 README는 패키지 수준 동작 계약 기준으로 남습니다.
|
|
226
240
|
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Transport-driven microservices for fluo. Build scalable, message-driven architec
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
9
9
|
- [Installation](#installation)
|
|
10
|
+
- [Requirements](#requirements)
|
|
10
11
|
- [When to Use](#when-to-use)
|
|
11
12
|
- [Quick Start](#quick-start)
|
|
12
13
|
- [Core Capabilities](#core-capabilities)
|
|
@@ -26,6 +27,12 @@ Optional transport-specific dependencies:
|
|
|
26
27
|
- Package-managed optional peers loaded by `@fluojs/microservices`: `@grpc/grpc-js`, `@grpc/proto-loader`, `ioredis`, `mqtt`
|
|
27
28
|
- Caller-owned broker clients passed explicitly to transports: `nats`, `kafkajs`, `amqplib`
|
|
28
29
|
|
|
30
|
+
The gRPC transport requires `@grpc/grpc-js@^1.14.4` and `@grpc/proto-loader@^0.8.0`. Consumers using an older `@grpc/grpc-js` release must upgrade the peer and refresh their lockfile before adopting this major `@fluojs/microservices` release. A refreshed install must resolve the proto-loader chain to `protobufjs@7.6.5` or newer so its patched UTF-8 helper is included; the fluo transport API is unchanged.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
`@fluojs/microservices` requires Node.js `>=24.0.0 <27` as its package-owned support contract. Node versions below 24 and Node 27+ are excluded.
|
|
35
|
+
|
|
29
36
|
## When to Use
|
|
30
37
|
|
|
31
38
|
- When building a **Distributed System** where services communicate via messages or events.
|
|
@@ -112,26 +119,32 @@ Keep application registration and programmatic calls on the root facade: registe
|
|
|
112
119
|
| --- | --- |
|
|
113
120
|
| `await microservice.send(...)` | Settles when the transport returns the correlated remote response, or rejects for a remote error, abort, timeout, or shutdown. |
|
|
114
121
|
| `await microservice.emit(...)` | Settles when the transport's publish operation accepts/completes the outbound event. It does not wait for remote event handlers or add delivery/redelivery guarantees beyond the collaborator's publish contract. |
|
|
115
|
-
| `await microservice.close()` |
|
|
122
|
+
| `await microservice.close()` | Repeated calls share one shutdown result. It waits for already-admitted inbound handlers to settle before transport-owned listener/subscription teardown and pending-request cleanup. For caller-owned NATS, Kafka, and RabbitMQ collaborators, it does not close or disconnect the supplied broker resources. |
|
|
116
123
|
|
|
117
124
|
Kafka and RabbitMQ keep each inbound consumer callback pending until the matched handler and any request response publication settle. That consumer-side completion boundary lets a broker adapter decide whether to acknowledge or retry delivery, but it does not turn the producer-side `emit()` promise into an end-to-end handler completion signal. During application shutdown, close the `Microservice` facade first so it can detach transport callbacks, then close or drain caller-owned clients, producers, consumers, publishers, channels, and connections from the application bootstrap layer.
|
|
118
125
|
|
|
119
126
|
### Delivery Safety Defaults
|
|
120
|
-
- TCP frames are bounded to 1 MiB per newline-delimited message by default; oversized frames close the socket instead of growing the request buffer without limit.
|
|
127
|
+
- TCP frames are buffered and delimited as raw bytes, then each complete frame is decoded once as UTF-8. A multibyte code point split across socket chunks therefore remains intact. Frames are bounded to 1 MiB per newline-delimited message by default; oversized frames close the socket instead of growing the request buffer without limit.
|
|
128
|
+
- Redis Pub/Sub validates inbound JSON frames before runtime dispatch. Invalid JSON, non-object payloads, unknown kinds, and event frames without a string `pattern` are discarded at the callback boundary and reported through the configured transport logger; no logger or a throwing logger does not close the caller-owned subscriber or fall back to `console.error`.
|
|
121
129
|
- Redis Streams acknowledges request/event entries only after handler-side processing finishes. Failed events stay pending for broker-managed recovery instead of being acknowledged early.
|
|
122
|
-
- Kafka and RabbitMQ keep consumer delivery completion pending until inbound event/request handling and response publication settle. Event-handler and response-publish failures reject the consumer callback so broker adapters can withhold acknowledgement or retry; request-handler errors still round-trip as error responses when that response can be published.
|
|
130
|
+
- Kafka and RabbitMQ keep consumer delivery completion pending until inbound event/request handling and response publication settle. Event-handler and response-publish failures reject the consumer callback so broker adapters can withhold acknowledgement or retry; request-handler errors still round-trip as error responses when that response can be published. Inbound event-handler failures are additionally reported through the configured transport logger before being rethrown; no logger or a throwing logger neither masks the failure nor falls back to `console.error`.
|
|
123
131
|
- Redis Streams does not apply publish-time trimming to live request/event streams by default, so pending entries remain recoverable until `xack` or consumer-group recovery completes. Acked request/reply entries are cleaned up, each per-consumer response stream keeps bounded retention by default (`responseRetentionMaxLen: 1_000`), and each response stream is deleted during `close()`.
|
|
132
|
+
- When `readerClient.xautoclaim` is available, Redis Streams reclaims pending request entries from the shared request consumer group after `pendingReclaimIdleMs` of inactivity (default: `60_000`), including entries abandoned by a crashed consumer. It also reclaims failed event entries from the same listener's instance-scoped event group. A replacement listener cannot reclaim a crashed listener's event PEL because event groups are UUID-scoped to preserve broadcast delivery. Set that option to zero or a negative value to disable reclaiming; the adapter retains the `XAUTOCLAIM` cursor per consumer group until the next `close()`.
|
|
124
133
|
- Redis Streams always deletes each per-consumer response stream during `close()`, but it retains the shared request consumer group conservatively once ownership cannot be proven across the active fleet. Lease-capable listeners clean up only their coordination metadata, and mixed or fallback listener fleets keep the shared request group in place so one peer cannot destroy a group that another live listener still needs.
|
|
125
134
|
- `messageRetentionMaxLen` and `eventRetentionMaxLen` remain available as advanced opt-in knobs. Enabling them can trade away broker-managed recovery guarantees because Redis may trim pending live-stream entries before they are acknowledged.
|
|
126
135
|
- 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.
|
|
127
136
|
- 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.
|
|
128
137
|
- If NATS subscription setup fails during `listen()`, the transport unsubscribes subscriptions created by that attempt in reverse setup order while leaving the caller-owned NATS client open.
|
|
129
|
-
-
|
|
138
|
+
- During NATS shutdown, the transport attempts every subscription cleanup even when one unsubscribe fails, keeps failed subscription references for a later `close()` retry, and reports one failure directly or multiple failures through `AggregateError`. Successful subscription cleanup is not repeated, and `listen()` cannot resume until the retained cleanup succeeds.
|
|
139
|
+
- During RabbitMQ shutdown, the transport attempts every consumer cancellation even when one fails, keeps failed queue references for a later `close()` retry, and does not repeat successful cancellations. `listen()` cannot resume until the retained cleanup succeeds.
|
|
140
|
+
- NATS request subscription callbacks contain malformed request frames, response encoding failures, and throwing `respond()` callbacks at the async callback boundary. These failures are reported through the configured transport logger without closing the caller-owned NATS client. If no transport logger is configured, fluo does not add a raw `console.error` fallback. Request-handler errors that can be encoded and published continue to round-trip as error responses.
|
|
141
|
+
- Request-response transports that accept `AbortSignal` reject already-aborted sends before publishing, re-check cancellation immediately before deferred broker/RPC dispatch, and reject in-flight sends on later abort. Once `close()` starts, a terminal ingress gate on the programmatic `Microservice` facade rejects new `send()`, `emit()`, `serverStream()`, `clientStream()`, and `bidiStream()` calls before transport handoff, including while `listen()` is still pending; the runtime shell applies the same terminal gate to `send()` and `emit()`. Transport adapters retain their own shutdown guards, concurrent `listen()` calls cannot reset a shutdown that is still in progress, and concurrent or repeated TCP `close()` calls share the first shutdown promise so listener and socket cleanup runs once.
|
|
130
142
|
- The programmatic `Microservice` facade accepts `close(signal?: string)` so runtime shutdown hooks can report the signal that initiated shutdown. `MicroserviceLifecycleService.close(signal)` preserves that lifecycle-compatible facade contract while continuing to call the configured transport's current `close(): Promise<void>` contract; individual transports remain no-argument shutdown adapters unless their own documentation explicitly says they consume a shutdown signal.
|
|
131
143
|
- 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.
|
|
132
144
|
- 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.
|
|
133
|
-
- Platform status snapshots report transport resource ownership
|
|
145
|
+
- Platform status snapshots report mixed transport resource ownership without collapsing it to one owner. TCP and internally-created gRPC servers report framework-owned listener/client resources, MQTT reports framework ownership only when it creates the client, and caller-owned broker collaborator transports remain externally managed. For gRPC with a supplied server, `ownership.externallyManaged` and `ownership.ownsResources` are both `true`, while `details.transportResourceOwnership` reports the caller-supplied gRPC server and framework-owned cached outbound clients separately.
|
|
134
146
|
- gRPC shutdown uses server-level `tryShutdown()` when the transport created the server, and falls back to `forceShutdown()` only for runtimes without graceful shutdown support. Caller-supplied `GrpcMicroserviceTransportOptions.server` instances remain caller-owned during `close()`; fluo closes cached outbound clients but does not shut down that server. AbortSignal cancellation for active unary or streaming calls uses the call-level `cancel()`/stream end path. fluo removes each `AbortSignal` abort listener after a unary call settles and when a streaming call ends or errors, including terminal events before reader iteration starts, or when its reader returns early. Cleanup runs only once when terminal, cancellation, and iterator-return paths overlap.
|
|
147
|
+
- Outbound gRPC `clientStream()` and `bidiStream()` writers propagate `writer.error(err)` instead of ending the call cleanly. fluo aborts the outbound call through the call-level `destroy(err)` path, falling back to `cancel()` and finally `end()` for runtimes that expose neither, so the remote peer observes a failed RPC rather than a successful end-of-stream. The caller's original error — not the transport-level cancellation status that follows the abort — rejects the `clientStream()` result promise and surfaces on the `bidiStream()` reader. Repeated `writer.error()` calls, and an `end()` that follows one, are ignored so the call is aborted once and the first reported cause wins.
|
|
135
148
|
- MQTT closes internally-created clients when subscription setup fails during `listen()` or when `close()` unwinds a failed in-flight listen attempt, while preserving the original startup error for callers. Caller-supplied MQTT clients remain caller-owned.
|
|
136
149
|
- 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.
|
|
137
150
|
|
|
@@ -211,13 +224,14 @@ Payloads are cloned before dispatch, concurrent `listen()` calls are deduped, re
|
|
|
211
224
|
|
|
212
225
|
- `@fluojs/microservices/tcp`
|
|
213
226
|
- `@fluojs/microservices/redis` (Redis Pub/Sub transport)
|
|
227
|
+
- `@fluojs/microservices/redis-streams`
|
|
214
228
|
- `@fluojs/microservices/nats`
|
|
215
229
|
- `@fluojs/microservices/kafka`
|
|
216
230
|
- `@fluojs/microservices/rabbitmq`
|
|
217
231
|
- `@fluojs/microservices/grpc`
|
|
218
232
|
- `@fluojs/microservices/mqtt`
|
|
219
233
|
|
|
220
|
-
`RedisStreamsMicroserviceTransport`
|
|
234
|
+
`RedisStreamsMicroserviceTransport`, `RedisStreamsMicroserviceTransportOptions`, and `RedisStreamClientLike` are available from the root barrel and the dedicated `@fluojs/microservices/redis-streams` subpath.
|
|
221
235
|
|
|
222
236
|
Canonical transport learning material lives in the book chapters for [TCP](../../book/intermediate/ch02-tcp.md), [RabbitMQ](../../book/intermediate/ch04-rabbitmq.md), and [gRPC](../../book/intermediate/ch08-grpc.md), while this README remains the package-level behavioral contract reference.
|
|
223
237
|
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIhE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,yBAAyB,GAAG,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIhE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,yBAAyB,GAAG,QAAQ,EAAE,CA4B3F;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU;CAgB/D"}
|
package/dist/module.js
CHANGED
|
@@ -22,6 +22,7 @@ export function createMicroservicesProviders(options) {
|
|
|
22
22
|
close: signal => runtime.close(signal),
|
|
23
23
|
emit: (pattern, payload) => runtime.emit(pattern, payload),
|
|
24
24
|
listen: () => runtime.listen(),
|
|
25
|
+
markShutdownStarted: () => runtime.markShutdownStarted(),
|
|
25
26
|
send: (pattern, payload, signal) => runtime.send(pattern, payload, signal),
|
|
26
27
|
serverStream: runtime.serverStream ? (pattern, payload, signal) => runtime.serverStream(pattern, payload, signal) : undefined
|
|
27
28
|
};
|
package/dist/service.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
14
14
|
private readonly moduleOptions;
|
|
15
15
|
private readonly descriptors;
|
|
16
16
|
private readonly handlerInstances;
|
|
17
|
+
private closeStarted;
|
|
18
|
+
private closePromise;
|
|
19
|
+
private readonly inboundWork;
|
|
17
20
|
private lifecycleState;
|
|
18
21
|
private lastListenError;
|
|
19
22
|
private listening;
|
|
@@ -25,6 +28,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
25
28
|
* @returns A promise that resolves once the configured transport is ready to accept traffic.
|
|
26
29
|
*/
|
|
27
30
|
listen(): Promise<void>;
|
|
31
|
+
private startListening;
|
|
28
32
|
/**
|
|
29
33
|
* Closes the configured transport and stops accepting microservice traffic.
|
|
30
34
|
*
|
|
@@ -32,6 +36,10 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
32
36
|
* @returns A promise that resolves once shutdown completes.
|
|
33
37
|
*/
|
|
34
38
|
close(signal?: string): Promise<void>;
|
|
39
|
+
private closeTransport;
|
|
40
|
+
private drainInboundWork;
|
|
41
|
+
private trackInboundWork;
|
|
42
|
+
markShutdownStarted(): void;
|
|
35
43
|
onApplicationShutdown(signal?: string): Promise<void>;
|
|
36
44
|
/**
|
|
37
45
|
* Creates a platform status snapshot for health checks and diagnostics.
|
|
@@ -56,6 +64,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
56
64
|
* @returns A promise that resolves once the transport accepts the event.
|
|
57
65
|
*/
|
|
58
66
|
emit(pattern: string, payload: unknown): Promise<void>;
|
|
67
|
+
private assertTransportIngressOpen;
|
|
59
68
|
/**
|
|
60
69
|
* Opens a server-streaming request through the configured transport.
|
|
61
70
|
*
|
|
@@ -64,7 +73,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
64
73
|
* @param signal Optional abort signal passed to the transport.
|
|
65
74
|
* @returns An async iterable of stream messages.
|
|
66
75
|
*
|
|
67
|
-
* @throws {Error} When the configured transport does not implement `serverStream()`.
|
|
76
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `serverStream()`.
|
|
68
77
|
*/
|
|
69
78
|
serverStream(pattern: string, payload: unknown, signal?: AbortSignal): AsyncIterable<unknown>;
|
|
70
79
|
/**
|
|
@@ -74,7 +83,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
74
83
|
* @param signal Optional abort signal passed to the transport.
|
|
75
84
|
* @returns A writer for request chunks plus a promise for the final response.
|
|
76
85
|
*
|
|
77
|
-
* @throws {Error} When the configured transport does not implement `clientStream()`.
|
|
86
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `clientStream()`.
|
|
78
87
|
*/
|
|
79
88
|
clientStream(pattern: string, signal?: AbortSignal): {
|
|
80
89
|
writer: ServerStreamWriter;
|
|
@@ -87,7 +96,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
|
|
|
87
96
|
* @param signal Optional abort signal passed to the transport.
|
|
88
97
|
* @returns A reader for response chunks and a writer for outbound chunks.
|
|
89
98
|
*
|
|
90
|
-
* @throws {Error} When the configured transport does not implement `bidiStream()`.
|
|
99
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `bidiStream()`.
|
|
91
100
|
*/
|
|
92
101
|
bidiStream(pattern: string, signal?: AbortSignal): {
|
|
93
102
|
reader: AsyncIterable<unknown>;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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,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;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/service.js
CHANGED
|
@@ -4,7 +4,7 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
4
4
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
|
-
import { Inject } from '@fluojs/core';
|
|
7
|
+
import { Inject, InvariantError } from '@fluojs/core';
|
|
8
8
|
import { cloneWithFallback, getClassDiMetadata } from '@fluojs/core/internal';
|
|
9
9
|
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
10
|
import { getHandlerMetadataEntries } from './metadata.js';
|
|
@@ -39,6 +39,9 @@ class MicroserviceLifecycleService {
|
|
|
39
39
|
}
|
|
40
40
|
descriptors = [];
|
|
41
41
|
handlerInstances = new Map();
|
|
42
|
+
closeStarted = false;
|
|
43
|
+
closePromise;
|
|
44
|
+
inboundWork = new Set();
|
|
42
45
|
lifecycleState = 'created';
|
|
43
46
|
lastListenError;
|
|
44
47
|
listening = false;
|
|
@@ -56,6 +59,7 @@ class MicroserviceLifecycleService {
|
|
|
56
59
|
* @returns A promise that resolves once the configured transport is ready to accept traffic.
|
|
57
60
|
*/
|
|
58
61
|
async listen() {
|
|
62
|
+
this.assertTransportIngressOpen('listen');
|
|
59
63
|
if (this.listening) {
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
@@ -63,32 +67,7 @@ class MicroserviceLifecycleService {
|
|
|
63
67
|
await this.listenPromise;
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
this.listenPromise = (
|
|
67
|
-
this.lifecycleState = 'starting';
|
|
68
|
-
this.lastListenError = undefined;
|
|
69
|
-
this.descriptors.length = 0;
|
|
70
|
-
this.descriptors.push(...this.discoverHandlerDescriptors());
|
|
71
|
-
const transport = this.moduleOptions.transport;
|
|
72
|
-
transport.setLogger?.(this.logger);
|
|
73
|
-
if (transport.listenServerStreaming) {
|
|
74
|
-
transport.listenServerStreaming(async (pattern, payload, writer) => {
|
|
75
|
-
await this.dispatchServerStream(pattern, cloneWithFallback(payload), writer);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
if (transport.listenClientStreaming) {
|
|
79
|
-
transport.listenClientStreaming(async (pattern, reader) => {
|
|
80
|
-
return await this.dispatchClientStream(pattern, reader);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
if (transport.listenBidiStreaming) {
|
|
84
|
-
transport.listenBidiStreaming(async (pattern, reader, writer) => {
|
|
85
|
-
await this.dispatchBidiStream(pattern, reader, writer);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
await transport.listen(async packet => this.dispatchPacket(packet));
|
|
89
|
-
this.listening = true;
|
|
90
|
-
this.lifecycleState = 'ready';
|
|
91
|
-
})();
|
|
70
|
+
this.listenPromise = this.startListening();
|
|
92
71
|
try {
|
|
93
72
|
await this.listenPromise;
|
|
94
73
|
} catch (error) {
|
|
@@ -99,6 +78,33 @@ class MicroserviceLifecycleService {
|
|
|
99
78
|
this.listenPromise = undefined;
|
|
100
79
|
}
|
|
101
80
|
}
|
|
81
|
+
async startListening() {
|
|
82
|
+
this.assertTransportIngressOpen('listen');
|
|
83
|
+
this.lifecycleState = 'starting';
|
|
84
|
+
this.lastListenError = undefined;
|
|
85
|
+
this.descriptors.length = 0;
|
|
86
|
+
this.descriptors.push(...this.discoverHandlerDescriptors());
|
|
87
|
+
const transport = this.moduleOptions.transport;
|
|
88
|
+
transport.setLogger?.(this.logger);
|
|
89
|
+
if (transport.listenServerStreaming) {
|
|
90
|
+
transport.listenServerStreaming(async (pattern, payload, writer) => {
|
|
91
|
+
await this.trackInboundWork(() => this.dispatchServerStream(pattern, cloneWithFallback(payload), writer));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (transport.listenClientStreaming) {
|
|
95
|
+
transport.listenClientStreaming(async (pattern, reader) => {
|
|
96
|
+
return await this.trackInboundWork(() => this.dispatchClientStream(pattern, reader));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (transport.listenBidiStreaming) {
|
|
100
|
+
transport.listenBidiStreaming(async (pattern, reader, writer) => {
|
|
101
|
+
await this.trackInboundWork(() => this.dispatchBidiStream(pattern, reader, writer));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
await transport.listen(async packet => await this.trackInboundWork(() => this.dispatchPacket(packet)));
|
|
105
|
+
this.listening = true;
|
|
106
|
+
this.lifecycleState = 'ready';
|
|
107
|
+
}
|
|
102
108
|
|
|
103
109
|
/**
|
|
104
110
|
* Closes the configured transport and stops accepting microservice traffic.
|
|
@@ -106,8 +112,22 @@ class MicroserviceLifecycleService {
|
|
|
106
112
|
* @param signal Optional shutdown signal reported by the runtime lifecycle hook.
|
|
107
113
|
* @returns A promise that resolves once shutdown completes.
|
|
108
114
|
*/
|
|
109
|
-
|
|
115
|
+
close(signal) {
|
|
110
116
|
void signal;
|
|
117
|
+
this.closeStarted = true;
|
|
118
|
+
if (this.closePromise) {
|
|
119
|
+
return this.closePromise;
|
|
120
|
+
}
|
|
121
|
+
let resolveClose;
|
|
122
|
+
let rejectClose;
|
|
123
|
+
this.closePromise = new Promise((resolve, reject) => {
|
|
124
|
+
resolveClose = resolve;
|
|
125
|
+
rejectClose = reject;
|
|
126
|
+
});
|
|
127
|
+
void this.closeTransport().then(resolveClose, rejectClose);
|
|
128
|
+
return this.closePromise;
|
|
129
|
+
}
|
|
130
|
+
async closeTransport() {
|
|
111
131
|
let listenError;
|
|
112
132
|
if (this.listenPromise) {
|
|
113
133
|
try {
|
|
@@ -118,6 +138,9 @@ class MicroserviceLifecycleService {
|
|
|
118
138
|
}
|
|
119
139
|
this.lifecycleState = 'stopping';
|
|
120
140
|
try {
|
|
141
|
+
if (this.inboundWork.size > 0) {
|
|
142
|
+
await this.drainInboundWork();
|
|
143
|
+
}
|
|
121
144
|
await this.moduleOptions.transport.close();
|
|
122
145
|
this.listening = false;
|
|
123
146
|
if (listenError) {
|
|
@@ -132,6 +155,23 @@ class MicroserviceLifecycleService {
|
|
|
132
155
|
throw error;
|
|
133
156
|
}
|
|
134
157
|
}
|
|
158
|
+
async drainInboundWork() {
|
|
159
|
+
while (this.inboundWork.size > 0) {
|
|
160
|
+
await Promise.allSettled(this.inboundWork);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
trackInboundWork(operation) {
|
|
164
|
+
if (this.closeStarted) {
|
|
165
|
+
return Promise.reject(new InvariantError('Microservice cannot accept inbound work after shutdown has started.'));
|
|
166
|
+
}
|
|
167
|
+
const work = Promise.resolve().then(operation);
|
|
168
|
+
this.inboundWork.add(work);
|
|
169
|
+
void work.then(() => this.inboundWork.delete(work), () => this.inboundWork.delete(work));
|
|
170
|
+
return work;
|
|
171
|
+
}
|
|
172
|
+
markShutdownStarted() {
|
|
173
|
+
this.closeStarted = true;
|
|
174
|
+
}
|
|
135
175
|
async onApplicationShutdown(signal) {
|
|
136
176
|
await this.close(signal);
|
|
137
177
|
}
|
|
@@ -159,7 +199,8 @@ class MicroserviceLifecycleService {
|
|
|
159
199
|
send: typeof this.moduleOptions.transport.send === 'function',
|
|
160
200
|
serverStream: typeof this.moduleOptions.transport.serverStream === 'function'
|
|
161
201
|
},
|
|
162
|
-
transportOwnsResources: this.moduleOptions.transport.ownsResources
|
|
202
|
+
transportOwnsResources: this.moduleOptions.transport.ownsResources,
|
|
203
|
+
transportResourceOwnership: this.moduleOptions.transport.resourceOwnership
|
|
163
204
|
});
|
|
164
205
|
}
|
|
165
206
|
|
|
@@ -172,6 +213,7 @@ class MicroserviceLifecycleService {
|
|
|
172
213
|
* @returns The transport response payload.
|
|
173
214
|
*/
|
|
174
215
|
async send(pattern, payload, signal) {
|
|
216
|
+
this.assertTransportIngressOpen('send');
|
|
175
217
|
return this.moduleOptions.transport.send(pattern, cloneWithFallback(payload), signal);
|
|
176
218
|
}
|
|
177
219
|
|
|
@@ -183,8 +225,14 @@ class MicroserviceLifecycleService {
|
|
|
183
225
|
* @returns A promise that resolves once the transport accepts the event.
|
|
184
226
|
*/
|
|
185
227
|
async emit(pattern, payload) {
|
|
228
|
+
this.assertTransportIngressOpen('emit');
|
|
186
229
|
await this.moduleOptions.transport.emit(pattern, cloneWithFallback(payload));
|
|
187
230
|
}
|
|
231
|
+
assertTransportIngressOpen(operation) {
|
|
232
|
+
if (this.closeStarted) {
|
|
233
|
+
throw new InvariantError(`Microservice cannot ${operation} after shutdown has started.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
188
236
|
|
|
189
237
|
/**
|
|
190
238
|
* Opens a server-streaming request through the configured transport.
|
|
@@ -194,9 +242,10 @@ class MicroserviceLifecycleService {
|
|
|
194
242
|
* @param signal Optional abort signal passed to the transport.
|
|
195
243
|
* @returns An async iterable of stream messages.
|
|
196
244
|
*
|
|
197
|
-
* @throws {Error} When the configured transport does not implement `serverStream()`.
|
|
245
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `serverStream()`.
|
|
198
246
|
*/
|
|
199
247
|
serverStream(pattern, payload, signal) {
|
|
248
|
+
this.assertTransportIngressOpen('serverStream');
|
|
200
249
|
const transport = this.moduleOptions.transport;
|
|
201
250
|
if (!transport.serverStream) {
|
|
202
251
|
throw new Error('The configured transport does not support server streaming. Use a transport that implements serverStream().');
|
|
@@ -211,9 +260,10 @@ class MicroserviceLifecycleService {
|
|
|
211
260
|
* @param signal Optional abort signal passed to the transport.
|
|
212
261
|
* @returns A writer for request chunks plus a promise for the final response.
|
|
213
262
|
*
|
|
214
|
-
* @throws {Error} When the configured transport does not implement `clientStream()`.
|
|
263
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `clientStream()`.
|
|
215
264
|
*/
|
|
216
265
|
clientStream(pattern, signal) {
|
|
266
|
+
this.assertTransportIngressOpen('clientStream');
|
|
217
267
|
const transport = this.moduleOptions.transport;
|
|
218
268
|
if (!transport.clientStream) {
|
|
219
269
|
throw new Error('The configured transport does not support client streaming. Use a transport that implements clientStream().');
|
|
@@ -228,9 +278,10 @@ class MicroserviceLifecycleService {
|
|
|
228
278
|
* @param signal Optional abort signal passed to the transport.
|
|
229
279
|
* @returns A reader for response chunks and a writer for outbound chunks.
|
|
230
280
|
*
|
|
231
|
-
* @throws {Error} When the configured transport does not implement `bidiStream()`.
|
|
281
|
+
* @throws {Error} When shutdown has started or the configured transport does not implement `bidiStream()`.
|
|
232
282
|
*/
|
|
233
283
|
bidiStream(pattern, signal) {
|
|
284
|
+
this.assertTransportIngressOpen('bidiStream');
|
|
234
285
|
const transport = this.moduleOptions.transport;
|
|
235
286
|
if (!transport.bidiStream) {
|
|
236
287
|
throw new Error('The configured transport does not support bidirectional streaming. Use a transport that implements bidiStream().');
|
|
@@ -436,7 +487,7 @@ class MicroserviceLifecycleService {
|
|
|
436
487
|
const entries = getHandlerMetadataEntries(candidate.targetType.prototype);
|
|
437
488
|
for (const entry of entries) {
|
|
438
489
|
const dedupeKey = this.dedupeKey(entry.metadata.kind, entry.metadata.pattern);
|
|
439
|
-
if (this.isDuplicate(seen, candidate.targetType, entry.propertyKey, dedupeKey)) {
|
|
490
|
+
if (this.isDuplicate(seen, candidate.targetType, candidate.token, entry.propertyKey, dedupeKey)) {
|
|
440
491
|
this.logger.warn(`Duplicate microservice handler registration for ${dedupeKey} on ${candidate.targetType.name}.${methodKeyToName(entry.propertyKey)} was ignored.`, 'MicroserviceLifecycleService');
|
|
441
492
|
continue;
|
|
442
493
|
}
|
|
@@ -460,11 +511,16 @@ class MicroserviceLifecycleService {
|
|
|
460
511
|
}
|
|
461
512
|
return `${kind}:${pattern}`;
|
|
462
513
|
}
|
|
463
|
-
isDuplicate(seen, targetType, methodKey, dedupeKey) {
|
|
464
|
-
let
|
|
514
|
+
isDuplicate(seen, targetType, token, methodKey, dedupeKey) {
|
|
515
|
+
let methodsByToken = seen.get(targetType);
|
|
516
|
+
if (!methodsByToken) {
|
|
517
|
+
methodsByToken = new Map();
|
|
518
|
+
seen.set(targetType, methodsByToken);
|
|
519
|
+
}
|
|
520
|
+
let methodsByKey = methodsByToken.get(token);
|
|
465
521
|
if (!methodsByKey) {
|
|
466
522
|
methodsByKey = new Map();
|
|
467
|
-
|
|
523
|
+
methodsByToken.set(token, methodsByKey);
|
|
468
524
|
}
|
|
469
525
|
let seenPatterns = methodsByKey.get(methodKey);
|
|
470
526
|
if (!seenPatterns) {
|
package/dist/status.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
|
|
2
|
+
import type { MicroserviceTransport } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Defines the microservice lifecycle state type.
|
|
4
5
|
*/
|
|
@@ -33,6 +34,8 @@ export interface MicroserviceStatusAdapterInput {
|
|
|
33
34
|
transportCapabilities: MicroserviceTransportCapabilities;
|
|
34
35
|
/** Whether the configured transport owns listener/client resources it must close. */
|
|
35
36
|
transportOwnsResources?: boolean;
|
|
37
|
+
/** Per-resource ownership details for transports that mix caller and framework ownership. */
|
|
38
|
+
transportResourceOwnership?: NonNullable<MicroserviceTransport['resourceOwnership']>;
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
38
41
|
* Describes the microservice platform status snapshot contract.
|
package/dist/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9G;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,aAAa,EAAE,yBAAyB,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,0BAA0B,CAAC;IAC3C,qBAAqB,EAAE,iCAAiC,CAAC;IACzD,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9G;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,aAAa,EAAE,yBAAyB,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,0BAA0B,CAAC;IAC3C,qBAAqB,EAAE,iCAAiC,CAAC;IACzD,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,6FAA6F;IAC7F,0BAA0B,CAAC,EAAE,WAAW,CAAC,qBAAqB,CAAC,mBAAmB,CAAC,CAAC,CAAC;CACtF;AAED;;GAEG;AACH,MAAM,WAAW,kCAAkC;IACjD,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAqED;;;;;GAKG;AACH,wBAAgB,wCAAwC,CACtD,KAAK,EAAE,8BAA8B,GACpC,kCAAkC,CA6BpC"}
|
package/dist/status.js
CHANGED
|
@@ -84,7 +84,10 @@ function createHealth(input) {
|
|
|
84
84
|
* @returns The create microservice platform status snapshot result.
|
|
85
85
|
*/
|
|
86
86
|
export function createMicroservicePlatformStatusSnapshot(input) {
|
|
87
|
-
const
|
|
87
|
+
const transportResourceOwnership = input.transportResourceOwnership;
|
|
88
|
+
const resourceOwners = transportResourceOwnership ? Object.values(transportResourceOwnership) : [];
|
|
89
|
+
const transportOwnsResources = input.transportOwnsResources ?? resourceOwners.includes('framework');
|
|
90
|
+
const hasExternallyManagedResources = resourceOwners.length > 0 ? resourceOwners.includes('caller') : !transportOwnsResources;
|
|
88
91
|
return {
|
|
89
92
|
details: {
|
|
90
93
|
dependencies: ['transport.external'],
|
|
@@ -96,11 +99,16 @@ export function createMicroservicePlatformStatusSnapshot(input) {
|
|
|
96
99
|
transportCapabilities: {
|
|
97
100
|
...input.transportCapabilities
|
|
98
101
|
},
|
|
102
|
+
...(transportResourceOwnership ? {
|
|
103
|
+
transportResourceOwnership: {
|
|
104
|
+
...transportResourceOwnership
|
|
105
|
+
}
|
|
106
|
+
} : {}),
|
|
99
107
|
transportOwnsResources
|
|
100
108
|
},
|
|
101
109
|
health: createHealth(input),
|
|
102
110
|
ownership: {
|
|
103
|
-
externallyManaged:
|
|
111
|
+
externallyManaged: hasExternallyManagedResources,
|
|
104
112
|
ownsResources: transportOwnsResources
|
|
105
113
|
},
|
|
106
114
|
readiness: createReadiness(input)
|
|
@@ -61,8 +61,10 @@ export interface GrpcMicroserviceTransportOptions {
|
|
|
61
61
|
*/
|
|
62
62
|
export declare class GrpcMicroserviceTransport implements MicroserviceTransport {
|
|
63
63
|
private readonly options;
|
|
64
|
-
/** Indicates
|
|
65
|
-
readonly ownsResources
|
|
64
|
+
/** Indicates that gRPC closes the cached outbound clients it creates. */
|
|
65
|
+
readonly ownsResources = true;
|
|
66
|
+
/** Reports server and cached outbound client ownership independently. */
|
|
67
|
+
readonly resourceOwnership: NonNullable<MicroserviceTransport['resourceOwnership']>;
|
|
66
68
|
private bidiStreamHandler;
|
|
67
69
|
private clientStreamHandler;
|
|
68
70
|
private closePromise;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grpc-transport.d.ts","sourceRoot":"","sources":["../../src/transports/grpc-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAGpN,KAAK,aAAa,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7D,UAAU,gBAAgB;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,UAAU,gCAAgC;IACxC,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,UAAU,cAAc;IACtB,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtF,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9G,aAAa,CAAC,IAAI,IAAI,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;CACvD;
|
|
1
|
+
{"version":3,"file":"grpc-transport.d.ts","sourceRoot":"","sources":["../../src/transports/grpc-transport.ts"],"names":[],"mappings":"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"}
|