@fluojs/microservices 1.0.4 → 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.
Files changed (37) hide show
  1. package/README.ko.md +64 -6
  2. package/README.md +64 -6
  3. package/dist/module.d.ts.map +1 -1
  4. package/dist/module.js +2 -1
  5. package/dist/service.d.ts +15 -5
  6. package/dist/service.d.ts.map +1 -1
  7. package/dist/service.js +96 -38
  8. package/dist/status.d.ts +3 -0
  9. package/dist/status.d.ts.map +1 -1
  10. package/dist/status.js +10 -2
  11. package/dist/transports/grpc-transport.d.ts +4 -2
  12. package/dist/transports/grpc-transport.d.ts.map +1 -1
  13. package/dist/transports/grpc-transport.js +241 -241
  14. package/dist/transports/kafka-transport.d.ts +9 -1
  15. package/dist/transports/kafka-transport.d.ts.map +1 -1
  16. package/dist/transports/kafka-transport.js +41 -21
  17. package/dist/transports/mqtt-transport.d.ts +1 -0
  18. package/dist/transports/mqtt-transport.d.ts.map +1 -1
  19. package/dist/transports/mqtt-transport.js +22 -1
  20. package/dist/transports/nats-transport.d.ts +3 -0
  21. package/dist/transports/nats-transport.d.ts.map +1 -1
  22. package/dist/transports/nats-transport.js +83 -30
  23. package/dist/transports/rabbitmq-transport.d.ts +10 -1
  24. package/dist/transports/rabbitmq-transport.d.ts.map +1 -1
  25. package/dist/transports/rabbitmq-transport.js +80 -30
  26. package/dist/transports/redis-streams-transport.d.ts +18 -0
  27. package/dist/transports/redis-streams-transport.d.ts.map +1 -1
  28. package/dist/transports/redis-streams-transport.js +46 -3
  29. package/dist/transports/redis-transport.d.ts +1 -1
  30. package/dist/transports/redis-transport.d.ts.map +1 -1
  31. package/dist/transports/redis-transport.js +32 -16
  32. package/dist/transports/tcp-transport.d.ts +1 -0
  33. package/dist/transports/tcp-transport.d.ts.map +1 -1
  34. package/dist/transports/tcp-transport.js +45 -44
  35. package/dist/types.d.ts +7 -0
  36. package/dist/types.d.ts.map +1 -1
  37. 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
  - 서비스 간 통신을 메시지나 이벤트 중심으로 분리하고 싶을 때
@@ -69,10 +76,35 @@ await microservice.listen();
69
76
 
70
77
  비즈니스 핸들러는 그대로 두고 TCP, Redis Pub/Sub, Redis Streams, NATS, Kafka, RabbitMQ, MQTT, gRPC 같은 트랜스포트만 바꿔 배치할 수 있습니다.
71
78
 
79
+ ### 트랜스포트 기능 매트릭스
80
+
81
+ 스타터를 고르기 전에 동작부터 선택하세요. 아래 표는 fluo adapter 자체가 노출하는 보장을 설명합니다. 각 행에서 별도로 명시하지 않는 한 broker retention, acknowledgement, retry, topology는 caller 설정에 속합니다. `Streaming`은 이름에 “stream”이 들어간 broker 자료구조가 아니라 `serverStream()`, `clientStream()`, `bidiStream()` API를 뜻합니다.
82
+
83
+ | 트랜스포트 | `send()` | `emit()` | Streaming | Durability | 리소스 소유권 | 상세 문서 |
84
+ | --- | --- | --- | --- | --- | --- | --- |
85
+ | TCP | 지원 — 상관관계가 유지된 응답 | 지원 — frame write | 미지원 | 없음 — broker 저장이나 replay 없음 | fluo가 listener와 active socket을 소유 | [TCP 장](../../book/intermediate/ch02-tcp.ko.md) |
86
+ | Redis Pub/Sub | **미지원 — 항상 reject하며 event-only** | 지원 — Redis publication | 미지원 | 없음 — 현재 연결된 subscriber만 수신하며 ACK/replay 없음 | Caller가 publish/subscribe client를 소유하고 adapter는 unsubscribe만 수행 | [Redis 장](../../book/intermediate/ch03-redis-transport.ko.md) |
87
+ | Redis Streams | 지원 — 상관관계가 유지된 response stream | 지원 — `XADD` 완료 | 미지원 — Redis Streams는 RPC streaming이 아님 | 내장 — consumer group, late `XACK`, recoverable pending entry를 사용하며 opt-in trimming은 복구 보장을 약화할 수 있음 | Caller가 reader/writer client를 소유하고 adapter가 자신이 만든 consumer-group/response-stream artifact를 관리하며 소유권이 불확실한 공유 request group은 보수적으로 유지 | [Redis 장](../../book/intermediate/ch03-redis-transport.ko.md) |
88
+ | NATS | 지원 — NATS request/reply | 지원 — client publish | 미지원 | 이 adapter에는 없음 — JetStream persistence/replay 계약 없음 | Caller가 client와 codec을 소유하고 adapter는 unsubscribe만 수행 | [NATS 장](../../book/intermediate/ch06-nats.ko.md) |
89
+ | Kafka | 지원 — 상관관계가 유지된 response topic | 지원 — producer publish | 미지원 | Broker/config에 의존 — topic retention, producer ACK, consumer offset, retry는 caller-owned collaborator 책임 | Caller가 producer와 consumer를 소유하고 adapter는 unsubscribe만 수행 | [Kafka 장](../../book/intermediate/ch05-kafka.ko.md) |
90
+ | RabbitMQ | 지원 — 상관관계가 유지된 response queue | 지원 — publisher publish | 미지원 | Topology/collaborator에 의존 — durable queue, confirm, ACK/retry, DLX 정책은 application-owned | Caller가 publisher, consumer, channel, connection resource를 소유하고 adapter는 자신의 consumer를 cancel | [RabbitMQ 장](../../book/intermediate/ch04-rabbitmq.ko.md) |
91
+ | MQTT | 지원 — 상관관계가 유지된 reply topic | 지원 — client publish callback | 미지원 | QoS/retain에 의존 — retained message는 history가 아니라 last-known value이며 fluo는 handler-completion 보장을 추가하지 않음 | URL로 생성한 client는 fluo가 닫고, 전달받은 client는 caller-owned로 유지 | [MQTT 장](../../book/intermediate/ch07-mqtt.ko.md) |
92
+ | gRPC | 지원 — unary response | 지원 — 원격 unary acknowledgement | Server, client, bidirectional | 없음 — broker persistence/replay가 없는 point-to-point RPC | fluo가 cached outbound client와 직접 생성한 server를 닫고, 전달받은 server는 caller-owned로 유지 | [gRPC 장](../../book/intermediate/ch08-grpc.ko.md) |
93
+
94
+ Redis Pub/Sub은 동일한 등록 경로를 사용하기 위해 공통 transport shape를 구현하지만 `send()`는 의도적으로 예외를 던집니다. 생성 가능한 starter는 [package chooser](../../docs/reference/package-chooser.ko.md#마이크로서비스-스타터-만들기)에서 확인하고, 학습 경로에 따른 선택은 [책의 transport chooser](../../book/intermediate/ch01-microservices-intro.ko.md#123-transport-capability-chooser)를 참고하세요.
95
+
96
+ 위 기능 주장은 공개 [transport type](./src/types.ts)과 [TCP](./src/transports/tcp-transport.ts), [Redis Pub/Sub](./src/transports/redis-transport.ts), [Redis Streams](./src/transports/redis-streams-transport.ts), [NATS](./src/transports/nats-transport.ts), [Kafka](./src/transports/kafka-transport.ts), [RabbitMQ](./src/transports/rabbitmq-transport.ts), [MQTT](./src/transports/mqtt-transport.ts), [gRPC](./src/transports/grpc-transport.ts) 구현을 근거로 합니다.
97
+
72
98
  ### 패턴 기반 라우팅
73
99
 
74
100
  `@MessagePattern`은 요청-응답 흐름에, `@EventPattern`은 fire-and-forget 이벤트에 사용합니다. 문자열과 정규식 패턴 모두 지원합니다.
75
101
 
102
+ ### 핸들러 탐색과 데코레이터 모델
103
+
104
+ `@MessagePattern`, `@EventPattern`, 스트리밍 패턴 데코레이터는 TC39 표준 메서드 데코레이터입니다. 표준 데코레이터 context를 통해 라우팅 데이터를 기록하며 `reflect-metadata`, `experimentalDecorators`, `emitDecoratorMetadata` 출력은 사용하지 않습니다.
105
+
106
+ 데코레이터가 붙은 메서드는 소유 클래스를 컴파일된 모듈의 `providers` 또는 `controllers`에 명시적으로 나열해야만 탐색 대상이 됩니다. 탐색 과정은 등록된 token을 인스턴스로 resolve한 뒤 데코레이터가 붙은 public instance method를 호출합니다. Private 또는 static target은 거부되며, 모듈 등록 없이 클래스를 import하거나 decorate하는 것만으로는 핸들러가 등록되지 않습니다.
107
+
76
108
  ### gRPC 스트리밍
77
109
 
78
110
  `@ServerStreamPattern`, `@ClientStreamPattern`, `@BidiStreamPattern`으로 unary 외의 스트리밍 패턴도 다룰 수 있습니다.
@@ -81,20 +113,42 @@ await microservice.listen();
81
113
 
82
114
  마이크로서비스 핸들러도 fluo의 request/transient scope 모델을 그대로 따르므로, 메시지 또는 이벤트 단위로 격리된 상태를 안전하게 사용할 수 있습니다.
83
115
 
116
+ ### 완료 및 소유권 경계
117
+
118
+ 애플리케이션 등록과 programmatic 호출은 root facade에 유지하세요. `MicroservicesModule.forRoot({ transport })`로 adapter를 등록하고 `MICROSERVICE`를 `Microservice`로 주입합니다. `MICROSERVICE`는 raw transport가 아닙니다. `@fluojs/microservices/nats`, `@fluojs/microservices/kafka`, `@fluojs/microservices/rabbitmq` 같은 transport-specific import는 adapter를 노출하지만 module과 facade 소유권은 root package에 남깁니다.
119
+
120
+ | 연산 | 완료 경계 |
121
+ | --- | --- |
122
+ | `await microservice.send(...)` | transport가 상관관계가 유지된 원격 응답을 반환할 때 settle하며, 원격 오류, abort, timeout, shutdown 시 reject합니다. |
123
+ | `await microservice.emit(...)` | transport의 publish 연산이 outbound event를 accept/complete할 때 settle합니다. 원격 event handler를 기다리거나 collaborator의 publish 계약을 넘어선 delivery/redelivery 보장을 추가하지는 않습니다. |
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하지 않습니다. |
125
+
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하세요.
127
+
84
128
  ### 전달 안전 기본값
85
129
 
86
- - 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으로 기록하지 않습니다.
87
132
  - Redis Streams는 요청/이벤트 엔트리를 핸들러 처리가 끝난 뒤에만 ACK합니다. 실패한 이벤트는 조기 ACK로 유실하지 않고 broker 복구/재전달 경로에 남겨 둡니다.
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으로 기록하지 않습니다.
88
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를 유지합니다.
89
136
  - Redis Streams는 `close()` 중 인스턴스별 response stream은 항상 삭제하지만, 활성 fleet 전체에서 ownership를 증명할 수 없으면 공유 request consumer group은 보수적으로 유지합니다. lease-capable listener는 coordination metadata만 정리하고, mixed/fallback fleet에서는 살아 있는 다른 listener가 여전히 필요로 할 수 있으므로 공유 request group을 제거하지 않습니다.
90
137
  - `messageRetentionMaxLen`과 `eventRetentionMaxLen`은 고급 opt-in 설정으로 남아 있습니다. 이를 켜면 Redis가 ACK 전 pending live-stream 엔트리를 먼저 trim할 수 있으므로 broker-managed recovery 보장을 일부 포기하는 운영 판단이 됩니다.
91
138
  - RabbitMQ 요청-응답은 기본적으로 인스턴스별 response queue를 사용합니다. 공유 reply topology를 의도적으로 운영할 때만 `responseQueue`를 명시적으로 지정하세요.
92
139
  - caller-owned broker collaborator는 shutdown 중에도 caller-owned로 유지됩니다. NATS, Kafka, RabbitMQ transport는 subscription/consumer를 분리하고 in-flight 요청을 reject하지만, 애플리케이션이 넘긴 client, producer, consumer, publisher, 외부 connection 객체를 close/disconnect하지 않습니다.
93
- - `AbortSignal`을 받는 요청-응답 transport는 이미 abort된 send를 publish 전에 reject하고, deferred broker/RPC dispatch 직전 cancellation을 다시 확인하며, 나중에 abort된 in-flight send도 reject합니다. `close()`가 시작된 뒤에는 shutdown 중인 lifecycle에 작업을 publish하지 않고 `send()`/`emit()`을 reject하며, 동시 `listen()` 호출은 아직 진행 중인 shutdown 상태를 reset할 수 없습니다.
140
+ - NATS subscription setup이 `listen()` 실패하면 transport는 해당 시도에서 이미 생성한 subscription을 setup의 역순으로 unsubscribe하고 caller-owned NATS client는 열어 둡니다.
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을 한 번만 수행합니다.
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입니다.
94
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을 시도합니다.
95
147
  - TCP는 테스트와 ephemeral listener를 위해 `port: 0`을 허용하고, listen 중에는 OS가 할당한 포트로 outbound `send()`/`emit()`을 라우팅합니다.
96
- - Platform status snapshot은 transport resource ownership을 보고합니다. TCP와 internally-created gRPC server는 framework-owned listener/client resource로 보고하고, MQTT는 client를 직접 생성한 경우에만 framework ownership을 보고하며, caller-supplied gRPC server와 caller-owned broker collaborator transport는 externally managed로 남습니다.
97
- - 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 경로를 사용하며, stream이 end/error/early return으로 끝나면 abort listener를 제거합니다.
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를 각각 구분합니다.
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 callsettle한 뒤, 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되고 처음 보고된 원인이 유지됩니다.
151
+ - MQTT는 `listen()` 중 subscription setup이 실패하거나 `close()`가 실패한 in-flight listen 시도를 unwinding할 때 internally-created client를 닫고, 호출자에게는 원래 startup error를 보존해 전달합니다. Caller-supplied MQTT client는 계속 caller-owned로 남습니다.
98
152
  - transport logger를 통해 이벤트 핸들러 실패를 기록하는 경로(`RedisPubSubMicroserviceTransport`, `RedisStreamsMicroserviceTransport`, `NatsMicroserviceTransport`, `MqttMicroserviceTransport`, gRPC event emit)는 끝까지 logger-driven observability를 유지합니다. transport logger를 주입하지 않으면 fluo는 해당 실패를 raw `console.error` fallback으로 복제하지 않습니다.
99
153
 
100
154
  ## 공통 패턴
@@ -159,7 +213,7 @@ class ManualMicroserviceProvidersModule {}
159
213
 
160
214
  ### Programmatic runtime
161
215
 
162
- `MicroserviceLifecycleService`는 programmatic runtime access를 위해 `listen()`, `close()`, `send()`, `emit()`, `serverStream()`, `clientStream()`, `bidiStream()`, `createPlatformStatusSnapshot()`을 제공합니다.
216
+ `MicroserviceLifecycleService`는 programmatic runtime access를 위해 `listen()`, `close(signal?: string)`, `send()`, `emit()`, `serverStream()`, `clientStream()`, `bidiStream()`, `createPlatformStatusSnapshot()`을 제공합니다. `MICROSERVICE` 토큰은 raw transport instance가 아니라 같은 programmatic `Microservice` facade로 resolve됩니다.
163
217
 
164
218
  ### Type export
165
219
 
@@ -173,17 +227,21 @@ Payload는 dispatch 전에 clone되고, 동시 `listen()` 호출은 dedupe되며
173
227
 
174
228
  - `@fluojs/microservices/tcp`
175
229
  - `@fluojs/microservices/redis` (Redis Pub/Sub 트랜스포트)
230
+ - `@fluojs/microservices/redis-streams`
176
231
  - `@fluojs/microservices/nats`
177
232
  - `@fluojs/microservices/kafka`
178
233
  - `@fluojs/microservices/rabbitmq`
179
234
  - `@fluojs/microservices/grpc`
180
235
  - `@fluojs/microservices/mqtt`
181
236
 
182
- `RedisStreamsMicroserviceTransport`는 현재 루트 배럴에서만 지원하며, `@fluojs/microservices/redis-streams` 전용 export는 없습니다.
237
+ `RedisStreamsMicroserviceTransport`, `RedisStreamsMicroserviceTransportOptions`, `RedisStreamClientLike`는 루트 배럴과 전용 `@fluojs/microservices/redis-streams` 서브패스에서 모두 사용할 수 있습니다.
238
+
239
+ 정식 transport 학습 자료는 [TCP](../../book/intermediate/ch02-tcp.ko.md), [RabbitMQ](../../book/intermediate/ch04-rabbitmq.ko.md), [gRPC](../../book/intermediate/ch08-grpc.ko.md) 책 장에 있으며, 이 README는 패키지 수준 동작 계약 기준으로 남습니다.
183
240
 
184
241
  ## 관련 패키지
185
242
 
186
243
  - `@fluojs/core`: 모듈과 DI 메타데이터의 기반 패키지입니다.
244
+ - `@fluojs/core/internal`: 이 패키지가 데코레이터 메타데이터와 clone helper를 위해 사용하는 first-party package-integration seam입니다. 애플리케이션-facing import surface가 아닙니다.
187
245
  - `@fluojs/runtime`: 마이크로서비스 부트스트랩과 팩토리 API를 제공합니다.
188
246
  - `@fluojs/di`: 핸들러와 provider를 resolve하는 DI 엔진입니다.
189
247
 
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.
@@ -70,28 +77,75 @@ await microservice.listen();
70
77
  ### Multi-Transport Support
71
78
  Write your business logic once and deploy it across various transports. Supports TCP, Redis (Pub/Sub and Streams), NATS, Kafka, RabbitMQ, MQTT, and gRPC.
72
79
 
80
+ ### Transport Capability Matrix
81
+
82
+ Choose by behavior before choosing a starter. The table describes the guarantees that the fluo adapter itself exposes; broker retention, acknowledgements, retries, and topology remain caller configuration unless a row states otherwise. `Streaming` means the `serverStream()`, `clientStream()`, and `bidiStream()` APIs, not a broker data structure named “stream.”
83
+
84
+ | Transport | `send()` | `emit()` | Streaming | Durability | Resource ownership | Detail |
85
+ | --- | --- | --- | --- | --- | --- | --- |
86
+ | TCP | Yes — correlated response | Yes — frame write | No | None — no broker storage or replay | fluo owns the listener and active sockets | [TCP chapter](../../book/intermediate/ch02-tcp.md) |
87
+ | Redis Pub/Sub | **No — always rejects; event-only** | Yes — Redis publication | No | None — live subscribers only; no ACK or replay | Caller owns the publish/subscribe clients; the adapter only unsubscribes | [Redis chapter](../../book/intermediate/ch03-redis-transport.md) |
88
+ | Redis Streams | Yes — correlated response stream | Yes — `XADD` completion | No — Redis Streams is not RPC streaming | Built in — consumer groups, late `XACK`, and recoverable pending entries; opt-in trimming can weaken recovery | Caller owns reader/writer clients; the adapter manages its consumer-group and response-stream artifacts and conservatively retains a shared request group when ownership is uncertain | [Redis chapter](../../book/intermediate/ch03-redis-transport.md) |
89
+ | NATS | Yes — NATS request/reply | Yes — client publish | No | None in this adapter — no JetStream persistence or replay contract | Caller owns the client and codec; the adapter unsubscribes only | [NATS chapter](../../book/intermediate/ch06-nats.md) |
90
+ | Kafka | Yes — correlated response topic | Yes — producer publish | No | Broker/config dependent — topic retention, producer ACKs, consumer offsets, and retries belong to the caller-owned collaborators | Caller owns the producer and consumer; the adapter unsubscribes only | [Kafka chapter](../../book/intermediate/ch05-kafka.md) |
91
+ | RabbitMQ | Yes — correlated response queue | Yes — publisher publish | No | Topology/collaborator dependent — durable queues, confirms, ACK/retry, and DLX policy remain application-owned | Caller owns publisher, consumer, channel, and connection resources; the adapter cancels its consumers | [RabbitMQ chapter](../../book/intermediate/ch04-rabbitmq.md) |
92
+ | MQTT | Yes — correlated reply topic | Yes — client publish callback | No | QoS/retain dependent — retained messages are last-known values, not history, and fluo adds no handler-completion guarantee | fluo closes a URL-created client; a supplied client stays caller-owned | [MQTT chapter](../../book/intermediate/ch07-mqtt.md) |
93
+ | gRPC | Yes — unary response | Yes — remote unary acknowledgement | Server, client, and bidirectional | None — point-to-point RPC without broker persistence or replay | fluo closes cached outbound clients and a server it creates; a supplied server stays caller-owned | [gRPC chapter](../../book/intermediate/ch08-grpc.md) |
94
+
95
+ Redis Pub/Sub implements the common transport shape so it can be registered uniformly, but its `send()` method intentionally throws. Use [the package chooser](../../docs/reference/package-chooser.md#build-a-microservice-starter) for generated starter availability or [the book transport chooser](../../book/intermediate/ch01-microservices-intro.md#123-transport-capability-chooser) for a learning-path decision.
96
+
97
+ The capability claims above are grounded in the public [transport type](./src/types.ts) and the implementations for [TCP](./src/transports/tcp-transport.ts), [Redis Pub/Sub](./src/transports/redis-transport.ts), [Redis Streams](./src/transports/redis-streams-transport.ts), [NATS](./src/transports/nats-transport.ts), [Kafka](./src/transports/kafka-transport.ts), [RabbitMQ](./src/transports/rabbitmq-transport.ts), [MQTT](./src/transports/mqtt-transport.ts), and [gRPC](./src/transports/grpc-transport.ts).
98
+
73
99
  ### Pattern-Based Routing
74
100
  Use `@MessagePattern` for request-response flows and `@EventPattern` for fire-and-forget event broadcasting. Patterns support string matching and regular expressions.
75
101
 
102
+ ### Handler Discovery and Decorator Model
103
+
104
+ `@MessagePattern`, `@EventPattern`, and the streaming pattern decorators are TC39 standard method decorators. They write routing data through the standard decorator context; they do not consume `reflect-metadata`, `experimentalDecorators`, or `emitDecoratorMetadata` output.
105
+
106
+ A decorated method becomes discoverable only when its owning class is explicitly listed in a compiled module's `providers` or `controllers`. Discovery resolves that registered token as an instance and invokes the decorated public instance method. Private and static targets are rejected, while importing or decorating a class without module registration does not register a handler.
107
+
76
108
  ### Advanced gRPC Streaming
77
109
  First-party support for all gRPC streaming modes: Server-side, Client-side, and Bidirectional streaming using `@ServerStreamPattern`, `@ClientStreamPattern`, and `@BidiStreamPattern`.
78
110
 
79
111
  ### Request-Scoped DI
80
112
  Microservice handlers fully support fluo's DI scopes. Request-scoped providers are isolated per message or per event, ensuring safe state management in concurrent processing.
81
113
 
114
+ ### Completion and Ownership Boundaries
115
+
116
+ Keep application registration and programmatic calls on the root facade: register the adapter with `MicroservicesModule.forRoot({ transport })`, then inject `MICROSERVICE` as a `Microservice`. `MICROSERVICE` is not the raw transport. Transport-specific imports such as `@fluojs/microservices/nats`, `@fluojs/microservices/kafka`, and `@fluojs/microservices/rabbitmq` expose the adapters while leaving module and facade ownership on the root package.
117
+
118
+ | Operation | Completion boundary |
119
+ | --- | --- |
120
+ | `await microservice.send(...)` | Settles when the transport returns the correlated remote response, or rejects for a remote error, abort, timeout, or shutdown. |
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. |
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. |
123
+
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.
125
+
82
126
  ### Delivery Safety Defaults
83
- - 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`.
84
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.
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`.
85
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()`.
86
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.
87
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.
88
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.
89
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.
90
- - 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, transports reject new `send()`/`emit()` calls instead of publishing work into a shutting-down lifecycle, and concurrent `listen()` calls cannot reset a shutdown that is still in progress.
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.
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.
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.
91
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.
92
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.
93
- - Platform status snapshots report transport resource ownership: TCP and internally-created gRPC servers report framework-owned listener/client resources, MQTT reports framework ownership only when it creates the client, caller-supplied gRPC servers report caller ownership, and caller-owned broker collaborator transports remain externally managed.
94
- - 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 and removes abort listeners when streams end, error, or are returned early.
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.
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.
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.
95
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.
96
150
 
97
151
  ## Common Patterns
@@ -156,7 +210,7 @@ class ManualMicroserviceProvidersModule {}
156
210
 
157
211
  ### Programmatic runtime
158
212
 
159
- `MicroserviceLifecycleService` exposes `listen()`, `close()`, `send()`, `emit()`, `serverStream()`, `clientStream()`, `bidiStream()`, and `createPlatformStatusSnapshot()` for programmatic runtime access.
213
+ `MicroserviceLifecycleService` exposes `listen()`, `close(signal?: string)`, `send()`, `emit()`, `serverStream()`, `clientStream()`, `bidiStream()`, and `createPlatformStatusSnapshot()` for programmatic runtime access. The `MICROSERVICE` token resolves to the same programmatic `Microservice` facade rather than the raw transport instance.
160
214
 
161
215
  ### Type exports
162
216
 
@@ -170,17 +224,21 @@ Payloads are cloned before dispatch, concurrent `listen()` calls are deduped, re
170
224
 
171
225
  - `@fluojs/microservices/tcp`
172
226
  - `@fluojs/microservices/redis` (Redis Pub/Sub transport)
227
+ - `@fluojs/microservices/redis-streams`
173
228
  - `@fluojs/microservices/nats`
174
229
  - `@fluojs/microservices/kafka`
175
230
  - `@fluojs/microservices/rabbitmq`
176
231
  - `@fluojs/microservices/grpc`
177
232
  - `@fluojs/microservices/mqtt`
178
233
 
179
- `RedisStreamsMicroserviceTransport` is currently supported from the root barrel only; there is no dedicated `@fluojs/microservices/redis-streams` export.
234
+ `RedisStreamsMicroserviceTransport`, `RedisStreamsMicroserviceTransportOptions`, and `RedisStreamClientLike` are available from the root barrel and the dedicated `@fluojs/microservices/redis-streams` subpath.
235
+
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.
180
237
 
181
238
  ## Related Packages
182
239
 
183
240
  - `@fluojs/core`: Core DI and module system.
241
+ - `@fluojs/core/internal`: First-party package-integration seam used by this package for decorator metadata and clone helpers; it is not an application-facing import surface.
184
242
  - `@fluojs/runtime`: Microservice bootstrap and factory.
185
243
  - `@fluojs/di`: Underlying dependency injection engine.
186
244
 
@@ -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,CA2B3F;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU;CAgB/D"}
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
@@ -19,9 +19,10 @@ export function createMicroservicesProviders(options) {
19
19
  return {
20
20
  bidiStream: runtime.bidiStream ? (pattern, signal) => runtime.bidiStream(pattern, signal) : undefined,
21
21
  clientStream: runtime.clientStream ? (pattern, signal) => runtime.clientStream(pattern, signal) : undefined,
22
- close: _signal => runtime.close(),
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,13 +28,19 @@ 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
  *
35
+ * @param signal Optional shutdown signal reported by the runtime lifecycle hook.
31
36
  * @returns A promise that resolves once shutdown completes.
32
37
  */
33
- close(): Promise<void>;
34
- onApplicationShutdown(): Promise<void>;
38
+ close(signal?: string): Promise<void>;
39
+ private closeTransport;
40
+ private drainInboundWork;
41
+ private trackInboundWork;
42
+ markShutdownStarted(): void;
43
+ onApplicationShutdown(signal?: string): Promise<void>;
35
44
  /**
36
45
  * Creates a platform status snapshot for health checks and diagnostics.
37
46
  *
@@ -55,6 +64,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
55
64
  * @returns A promise that resolves once the transport accepts the event.
56
65
  */
57
66
  emit(pattern: string, payload: unknown): Promise<void>;
67
+ private assertTransportIngressOpen;
58
68
  /**
59
69
  * Opens a server-streaming request through the configured transport.
60
70
  *
@@ -63,7 +73,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
63
73
  * @param signal Optional abort signal passed to the transport.
64
74
  * @returns An async iterable of stream messages.
65
75
  *
66
- * @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()`.
67
77
  */
68
78
  serverStream(pattern: string, payload: unknown, signal?: AbortSignal): AsyncIterable<unknown>;
69
79
  /**
@@ -73,7 +83,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
73
83
  * @param signal Optional abort signal passed to the transport.
74
84
  * @returns A writer for request chunks plus a promise for the final response.
75
85
  *
76
- * @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()`.
77
87
  */
78
88
  clientStream(pattern: string, signal?: AbortSignal): {
79
89
  writer: ServerStreamWriter;
@@ -86,7 +96,7 @@ export declare class MicroserviceLifecycleService implements Microservice, Micro
86
96
  * @param signal Optional abort signal passed to the transport.
87
97
  * @returns A reader for response chunks and a writer for outbound chunks.
88
98
  *
89
- * @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()`.
90
100
  */
91
101
  bidiStream(pattern: string, signal?: AbortSignal): {
92
102
  reader: AsyncIterable<unknown>;
@@ -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;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;IAsB5B;;;;;;;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;IAYzG,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAdhC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsC;IACvE,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,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;YAyBf,cAAc;IAuC5B;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAmBvB,cAAc;YAmCd,gBAAgB;IAM9B,OAAO,CAAC,gBAAgB;IAexB,mBAAmB,IAAI,IAAI;IAIrB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;;;OAIG;IACH,4BAA4B;IAuB5B;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAMrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5D,OAAO,CAAC,0BAA0B;IAQlC;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAW7F;;;;;;;;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;IAW7G;;;;;;;;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;YAWnG,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;IAoCnB,OAAO,CAAC,mBAAmB;YAsCb,aAAa;YAuBb,qBAAqB;YA2BrB,+BAA+B;CAsB9C"}