@fluojs/slack 1.1.3 → 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 CHANGED
@@ -10,9 +10,12 @@ fluo를 위한 webhook-first, transport-agnostic Slack 전달 코어 패키지
10
10
  - [사용 시점](#사용-시점)
11
11
  - [빠른 시작](#빠른-시작)
12
12
  - [일반적인 패턴](#일반적인-패턴)
13
+ - [모듈 visibility와 migration boundary](#모듈-visibility와-migration-boundary)
13
14
  - [`createSlackProviders`를 이용한 수동 provider 조합](#createslackproviders를-이용한-수동-provider-조합)
14
15
  - [`SlackService`를 이용한 standalone 전달](#slackservice를-이용한-standalone-전달)
16
+ - [`verifyOnModuleInit`을 이용한 bootstrap 검증](#verifyonmoduleinit을-이용한-bootstrap-검증)
15
17
  - [`@fluojs/notifications`와의 통합](#fluojs-notifications와의-통합)
18
+ - [Template rendering과 merge precedence](#template-rendering과-merge-precedence)
16
19
  - [명시적 fetch 주입을 사용하는 webhook-first 전달](#명시적-fetch-주입을-사용하는-webhook-first-전달)
17
20
  - [의도적인 제한 사항](#의도적인-제한-사항)
18
21
  - [공개 API 개요](#공개-api-개요)
@@ -25,7 +28,7 @@ fluo를 위한 webhook-first, transport-agnostic Slack 전달 코어 패키지
25
28
  npm install @fluojs/slack @fluojs/notifications
26
29
  ```
27
30
 
28
- 이 패키지는 published package metadata에 반영된 저장소 전반의 Node.js 20+ 설치 baseline을 따르지만, 런타임 전달 계약 자체는 명시적인 fetch-compatible 경계를 통해 계속 transport-agnostic하게 유지됩니다.
31
+ 이 패키지는 패키지 자체의 지원 계약에 따라 Node.js `>=24.0.0 <27`을 요구합니다. Node 20 Node 22 host를 Node.js `>=24.0.0 <27`로 올리세요. Node 24 미만과 Node 27 이상은 지원하지 않으므로 process를 지원되는 line으로 옮겨야 합니다. 런타임 전달 계약 자체는 명시적인 fetch-compatible 경계를 통해 계속 transport-agnostic하게 유지됩니다.
29
32
 
30
33
  ## 사용 시점
31
34
 
@@ -76,9 +79,17 @@ export class DeployNotifier {
76
79
 
77
80
  ## 일반적인 패턴
78
81
 
82
+ ### 모듈 visibility와 migration boundary
83
+
84
+ `SlackModule.forRoot(...)`와 `SlackModule.forRootAsync(...)`는 기본적으로 global module을 반환합니다. 이 module은 `SlackService`, `SlackChannel`, `SLACK`, `SLACK_CHANNEL`을 export하며, migrated code에서 해당 provider를 반환된 module을 명시적으로 import한 module에만 보이게 해야 할 때만 `global: false`를 전달합니다. 옵션 이름은 NestJS `isGlobal`이 아니라 `global?: boolean`입니다.
85
+
86
+ Async registration은 injected factory 형태인 `SlackModule.forRootAsync({ inject, useFactory, global? })`만 지원합니다. `inject`와 `useFactory`만 소비하며 NestJS `imports`, `useClass`, `useExisting`은 소비하지 않습니다. 필요한 의존성은 application module graph에 먼저 등록하고 token을 `inject`에 나열한 뒤, `useFactory`에서 최종 Slack option을 반환하세요.
87
+
88
+ 패키지 수준 registration surface는 의도적으로 singleton 중심입니다. `SLACK`과 `SLACK_CHANNEL`은 하나의 설정된 Slack service와 notifications channel을 위한 compatibility token이며, `createSlackProviders(...)`는 수동 module composition에서도 같은 singleton wiring을 재사용합니다. 여러 Slack client가 필요한 애플리케이션은 package-level multi-client registry를 기대하지 말고, 서로 다른 `SlackTransport` 인스턴스를 감싸는 자체 module/provider를 조합하거나 app-owned facade를 노출해야 합니다.
89
+
79
90
  ### `createSlackProviders`를 이용한 수동 provider 조합
80
91
 
81
- `createSlackProviders(...)`는 애플리케이션이 `SlackModule.forRoot(...)` 밖에서 동일한 provider 정규화 구성을 재사용해야 할 때 지원되는 manual-composition helper입니다.
92
+ `createSlackProviders(...)`는 애플리케이션이 `SlackModule.forRoot(...)` 밖에서 동일한 singleton provider 정규화 구성을 재사용해야 할 때 지원되는 manual-composition helper입니다.
82
93
 
83
94
  ```typescript
84
95
  import { Module } from '@fluojs/core';
@@ -126,13 +137,81 @@ SlackModule.forRootAsync({
126
137
  Behavioral contract 메모:
127
138
 
128
139
  - `SlackService.send(...)`는 전달 전에 `defaultChannel`을 해석합니다.
129
- - `SlackService.sendMany(...)`는 메시지를 순차적으로 보내며, fail-fast 대신 batch result가 필요한 호출자를 위해 `continueOnError`를 지원합니다.
130
- - `SlackService.send(...)`, `SlackService.sendMany(...)`, `SlackService.sendNotification(...)`은 provider handoff 전에 이미 abort된 signal을 존중하며, 같은 signal을 transport 호출로 전달합니다.
140
+ - `SlackService.sendMany(...)`는 메시지를 순차적으로 보내며, fail-fast 대신 batch result가 필요한 호출자를 위해 `continueOnError`를 지원합니다. 다만 일반 provider 실패만 수집하며 caller 취소는 항상 batch를 reject합니다.
141
+ - `SlackService.send(...)`, `SlackService.sendMany(...)`, `SlackService.sendNotification(...)`은 provider handoff 전에 이미 abort된 signal을 존중합니다. Abort된 signal 또는 transport의 `AbortError`는 `continueOnError`보다 우선하며, 같은 signal은 notification channel거쳐 transport 호출까지 전달됩니다.
131
142
  - 서비스는 모듈 bootstrap 시 transport를 초기화하고, factory가 소유한 리소스만 애플리케이션 shutdown 시 닫습니다.
132
- - shutdown이 시작된 뒤에는 `SlackService.send(...)`와 `SlackService.sendNotification(...)`이 transport를 재사용하거나 lazy 생성하지 않고 `SlackLifecycleError`로 실패합니다. 진행 중인 factory 소유 transport 생성은 shutdown이 기다린 뒤 닫습니다.
143
+ - 직접 전달과 notifications 기반 전달은 lifecycle이 `ready`일 때만 허용됩니다. `onModuleInit()`이 끝나기 전, 초기화 실패 뒤, 또는 shutdown 호출하면 transport를 lazy 생성하거나 재사용하지 않고 `SlackLifecycleError`로 실패합니다.
144
+ - shutdown은 새 전달을 거부하고, 진행 중인 factory 소유 transport 생성과 `verify()` 호출을 포함하는 공유 bootstrap initialization promise가 settle되기를 기다린 뒤 활성 전달을 drain하고, 소유한 transport를 닫고 완료됩니다.
145
+ - factory 소유 transport cleanup은 bootstrap 실패 cleanup과 application shutdown 사이에서 직렬화되므로, 두 경로가 경합해도 같은 owned transport는 최대 한 번만 닫힙니다.
133
146
  - `SlackService.createPlatformStatusSnapshot()`은 호출자가 내부 옵션에 접근하지 않아도 lifecycle, readiness, transport 소유권을 보고합니다.
134
147
  - 이 패키지는 절대로 `process.env`를 직접 읽지 않습니다. 모든 설정은 명시적인 옵션 또는 DI를 통해 들어와야 합니다.
135
148
 
149
+ ### `verifyOnModuleInit`을 이용한 bootstrap 검증
150
+
151
+ 선택한 transport가 bootstrap 중 자신의 readiness를 검증할 수 있다면 `SlackModuleOptions.verifyOnModuleInit?: boolean`을 `true`로 설정합니다. `SlackService.onModuleInit()`은 항상 설정된 transport를 먼저 해석하며, `verifyOnModuleInit`이 켜져 있고 해석된 transport가 optional `verify()` 메서드를 노출할 때에만 `transport.verify()`를 기다린 뒤 Slack provider를 ready로 표시합니다. `verify`를 구현하지 않는 transport도 유효하며, 이 경우 검증 단계만 건너뜁니다.
152
+
153
+ ```typescript
154
+ import { Module } from '@fluojs/core';
155
+ import {
156
+ SlackModule,
157
+ type SlackTemplateRenderer,
158
+ type SlackTransport,
159
+ } from '@fluojs/slack';
160
+
161
+ const renderer: SlackTemplateRenderer = {
162
+ render({ template, payload, subject }) {
163
+ if (template === 'deploy.finished') {
164
+ return {
165
+ blocks: [
166
+ {
167
+ text: { text: `*${String(payload.version)}* is live`, type: 'mrkdwn' },
168
+ type: 'section',
169
+ },
170
+ ],
171
+ text: subject,
172
+ };
173
+ }
174
+
175
+ return { text: subject };
176
+ },
177
+ };
178
+
179
+ const slackApiTransport: SlackTransport = {
180
+ async verify() {
181
+ await slackApi.authTest();
182
+ },
183
+ async send(message, { signal }) {
184
+ const receipt = await slackApi.postMessage(message, { signal });
185
+
186
+ return {
187
+ channel: receipt.channel,
188
+ messageTs: receipt.ts,
189
+ ok: receipt.ok,
190
+ };
191
+ },
192
+ };
193
+
194
+ @Module({
195
+ imports: [
196
+ SlackModule.forRoot({
197
+ defaultChannel: '#ops',
198
+ renderer,
199
+ transport: slackApiTransport,
200
+ verifyOnModuleInit: true,
201
+ }),
202
+ ],
203
+ })
204
+ export class AppModule {}
205
+ ```
206
+
207
+ Behavioral contract 메모:
208
+
209
+ - `verifyOnModuleInit`은 선택 사항이며 기본값은 `false`입니다.
210
+ - 검증은 capability 기반입니다. `verify()`를 노출하는 transport만 호출하므로 webhook-only 또는 애플리케이션이 소유한 transport가 no-op verifier를 추가할 필요는 없습니다.
211
+ - Bootstrap initialization과 verification은 shutdown과 하나의 promise를 공유하므로, factory 소유 transport는 진행 중인 `verify()`가 settle될 때까지 열린 상태를 유지하고 이후 최대 한 번만 닫힙니다.
212
+ - `transport.verify()`가 reject되면 bootstrap은 초기화 실패를 감싼 `SlackLifecycleError`로 실패하고, service lifecycle은 `failed`로 이동하며, readiness/status snapshot은 provider를 not ready로 보고하고, 이미 해석된 factory 소유 transport는 오류를 다시 던지기 전에 닫습니다. 그 cleanup이 진행 중일 때 shutdown이 시작되면 두 호출자는 같은 close 작업을 기다립니다.
213
+ - `SlackService.createPlatformStatusSnapshot()`은 bootstrap 검증 요청 여부를 health/readiness tooling이 확인할 수 있도록 `verifiedOnModuleInit`을 포함합니다.
214
+
136
215
  ### `@fluojs/notifications`와의 통합
137
216
 
138
217
  `SLACK_CHANNEL`을 `NotificationsModule.forRootAsync(...)`에 주입하여, Slack 전용 payload 필드와 recipient-to-channel 해석 규칙이 모두 `@fluojs/slack` 안에만 남도록 구성합니다.
@@ -179,6 +258,51 @@ Behavioral contract 메모:
179
258
  - notification metadata는 전달 전에 payload metadata, dispatch metadata, subject/template marker를 합쳐 구성됩니다.
180
259
  - 여러 Slack 대상지로 fan-out이 필요하다면 하나의 multi-recipient dispatch 대신 `sendMany(...)`를 사용해야 합니다.
181
260
 
261
+ ### Template rendering과 merge precedence
262
+
263
+ Notification template을 공유 `@fluojs/notifications` envelope에서 Slack 전용 text, blocks, attachments로 변환해야 한다면 `SlackTemplateRenderer`를 제공합니다. `SlackService.sendNotification(...)`은 `notification.template`과 `SlackModuleOptions.renderer`가 모두 있을 때에만 `renderer.render(input)`을 호출합니다. Render input에는 `template`, `payload`, optional `subject`, optional `locale`, optional dispatch `metadata`가 들어갑니다.
264
+
265
+ ```typescript
266
+ import type { SlackTemplateRenderer } from '@fluojs/slack';
267
+
268
+ const renderer: SlackTemplateRenderer = {
269
+ async render(input) {
270
+ return {
271
+ blocks: [
272
+ {
273
+ text: {
274
+ text: `*${String(input.payload.releaseId)}* deployed for ${input.locale ?? 'default'}`,
275
+ type: 'mrkdwn',
276
+ },
277
+ type: 'section',
278
+ },
279
+ ],
280
+ text: input.subject,
281
+ };
282
+ },
283
+ };
284
+
285
+ await notifications.dispatch({
286
+ channel: 'slack',
287
+ locale: 'en',
288
+ metadata: { source: 'ci' },
289
+ payload: {
290
+ metadata: { releaseId: 'rel-42' },
291
+ text: 'Deploy rel-42 finished.',
292
+ },
293
+ recipients: ['#ops'],
294
+ subject: 'Deploy finished',
295
+ template: 'deploy.finished',
296
+ });
297
+ ```
298
+
299
+ Merge precedence는 결정적입니다.
300
+
301
+ - `payload.attachments`, `payload.blocks`가 정의되어 있으면 rendered `attachments`, `blocks`보다 우선합니다. 비공백 `payload.text`는 rendered `text`보다 우선합니다.
302
+ - Text는 비공백 `payload.text`에서 비공백 rendered `text`, 그리고 비공백 `notification.subject` 순서로 fallback합니다. 빈 문자열 또는 공백 전용 text 값은 미지정으로 취급합니다.
303
+ - Metadata는 payload metadata, dispatch metadata, subject marker, template marker 순서로 merge됩니다. 중복 키는 뒤쪽 값이 이기므로, 최종 메시지는 존재하는 dispatch `subject`와 `template` marker를 기록합니다.
304
+ - `template`이 없거나 renderer가 등록되지 않았다면 template rendering은 실행되지 않습니다. 이 경우 notification은 payload와 subject만으로 Slack 메시지로 변환됩니다.
305
+
182
306
  ### 명시적 fetch 주입을 사용하는 webhook-first 전달
183
307
 
184
308
  런타임에 독립적인 1st-party transport가 필요하다면 fetch-compatible HTTP 경계만 의존하는 `createSlackWebhookTransport(...)`를 사용합니다.
@@ -200,7 +324,7 @@ await slack.send({
200
324
  Behavioral contract 메모:
201
325
 
202
326
  - `fetch`를 명시적으로 전달하는 방식이 portable path이며 모든 지원 런타임에서 권장됩니다. 하위 호환성을 위해 `fetch`를 생략하면 ambient runtime API인 `globalThis.fetch`가 있을 때 이를 폴백으로 사용합니다. `globalThis.fetch`가 없는 런타임에서는 `SlackConfigurationError`로 빠르게 실패합니다.
203
- - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 실패를 호출자에게 에러를 노출하기 전에 bounded exponential backoff로 재시도합니다.
327
+ - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 실패 응답의 본문을 소비한 bounded exponential backoff로 재시도하고, 재시도가 모두 소진된 뒤에만 호출자에게 에러를 노출합니다.
204
328
  - Abort signal은 주입된 `fetch` 경계로 전달되며, retry backoff를 취소할 때 `AbortError`를 `SlackTransportError`로 감싸지 않습니다.
205
329
  - 호출자에게 보이는 `SlackTransportError` 메시지는 기본적으로 raw upstream response body를 포함하지 않습니다.
206
330
 
@@ -211,6 +335,7 @@ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
211
335
  - 자격 증명이나 webhook URL을 `process.env`에서 직접 읽는 동작
212
336
  - 공유 루트 패키지 경계에 Node 전용 Slack SDK를 내장하는 것
213
337
  - webhook helper와 export된 transport 계약 이상으로 하나의 provider 전략을 강제하는 것
338
+ - singleton module/helper surface를 넘어서는 package-level multi-client registry를 제공하는 것
214
339
  - 하나의 dispatch 호출 안에서 multi-channel fan-out을 자동 변환하는 것
215
340
 
216
341
  이 제한 사항은 런타임 선택, provider capability, rollout 전략이 애플리케이션 경계에서 명시적으로 결정되도록 하기 위한 package contract의 일부입니다.
@@ -268,8 +393,8 @@ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
268
393
  - `SlackLifecycleState`
269
394
  - `SlackStatusAdapterInput`
270
395
  - `SlackConfigurationError`
271
- - `SlackLifecycleError`: lifecycle로 차단된 전달, transport 초기화, 소유 리소스 shutdown 실패에서 발생합니다. 애플리케이션 teardown과 전송이 경합할 수 있다면 이 에러를 catch하세요.
272
- - `SlackMessageValidationError`
396
+ - `SlackLifecycleError`: readiness 전, 초기화 실패 뒤, 또는 shutdown 중 lifecycle로 차단된 전달과 transport factory, verification, 소유 리소스 cleanup 실패에서 발생합니다. bootstrap 또는 애플리케이션 teardown과 전송이 경합할 수 있다면 이 에러를 catch하세요.
397
+ - `SlackMessageValidationError`: 직접 메시지에 Slack에서 보이는 `text`, `blocks`, `attachments`가 없거나, 하나의 notification dispatch가 여러 Slack recipient로 해석되어 `sendMany(...)`로 분리해야 할 때 발생합니다.
273
398
  - `SlackTransportError`
274
399
 
275
400
  ## 관련 패키지
@@ -281,5 +406,6 @@ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
281
406
  ## 예제 소스
282
407
 
283
408
  - `packages/slack/src/module.test.ts`: 모듈 등록, `createSlackProviders(...)` helper coverage, async wiring, webhook transport, notifications integration 예제.
409
+ - `packages/slack/src/lifecycle-regression.test.ts`: Bootstrap verification과 shutdown 순서의 regression coverage.
284
410
  - `packages/slack/src/public-surface.test.ts`: 공개 export와 TypeScript 계약 검증 예제.
285
411
  - `packages/slack/src/status.test.ts`: health/readiness 계약 예제.
package/README.md CHANGED
@@ -10,9 +10,12 @@ Webhook-first, transport-agnostic Slack delivery core for fluo. It provides a Ne
10
10
  - [When to Use](#when-to-use)
11
11
  - [Quick Start](#quick-start)
12
12
  - [Common Patterns](#common-patterns)
13
+ - [Module visibility and migration boundaries](#module-visibility-and-migration-boundaries)
13
14
  - [Manual provider composition with `createSlackProviders`](#manual-provider-composition-with-createslackproviders)
14
15
  - [Standalone delivery with `SlackService`](#standalone-delivery-with-slackservice)
16
+ - [Bootstrap verification with `verifyOnModuleInit`](#bootstrap-verification-with-verifyonmoduleinit)
15
17
  - [Integration with `@fluojs/notifications`](#integration-with-fluojs-notifications)
18
+ - [Template rendering and merge precedence](#template-rendering-and-merge-precedence)
16
19
  - [Webhook-first delivery with explicit fetch injection](#webhook-first-delivery-with-explicit-fetch-injection)
17
20
  - [Intentional limitations](#intentional-limitations)
18
21
  - [Public API Overview](#public-api-overview)
@@ -25,7 +28,7 @@ Webhook-first, transport-agnostic Slack delivery core for fluo. It provides a Ne
25
28
  npm install @fluojs/slack @fluojs/notifications
26
29
  ```
27
30
 
28
- This package follows the repo-wide Node.js 20+ install baseline reflected in published package metadata, while keeping its delivery contract transport-agnostic at runtime through explicit fetch-compatible boundaries.
31
+ This package requires Node.js `>=24.0.0 <27` as its package-owned support contract. Upgrade Node 20 and Node 22 hosts to Node.js `>=24.0.0 <27`; Node versions below 24 and Node 27+ are unsupported, so move the process to a supported line. Its delivery contract remains transport-agnostic at runtime through explicit fetch-compatible boundaries.
29
32
 
30
33
  ## When to Use
31
34
 
@@ -76,9 +79,17 @@ export class DeployNotifier {
76
79
 
77
80
  ## Common Patterns
78
81
 
82
+ ### Module visibility and migration boundaries
83
+
84
+ `SlackModule.forRoot(...)` and `SlackModule.forRootAsync(...)` return a global module by default. The module exports `SlackService`, `SlackChannel`, `SLACK`, and `SLACK_CHANNEL`; pass `global: false` only when migrated code needs those providers to remain visible only to modules that explicitly import the returned module. The option is `global?: boolean`, not NestJS `isGlobal`.
85
+
86
+ Async registration supports the injected factory shape only: `SlackModule.forRootAsync({ inject, useFactory, global? })`. It consumes `inject` and `useFactory`, not NestJS `imports`, `useClass`, or `useExisting`. Register dependencies in the application module graph first, list their tokens in `inject`, then return the final Slack options from `useFactory`.
87
+
88
+ The package-level registration surface is intentionally singleton-oriented. `SLACK` and `SLACK_CHANNEL` are compatibility tokens for the one configured Slack service and notifications channel, and `createSlackProviders(...)` mirrors that same singleton wiring for manual module composition. Applications that need multiple Slack clients should compose their own modules/providers around distinct `SlackTransport` instances or expose app-owned facades instead of expecting a package-level multi-client registry.
89
+
79
90
  ### Manual provider composition with `createSlackProviders`
80
91
 
81
- `createSlackProviders(...)` is the supported manual-composition helper when applications need the same provider normalization outside `SlackModule.forRoot(...)`.
92
+ `createSlackProviders(...)` is the supported manual-composition helper when applications need the same singleton provider normalization outside `SlackModule.forRoot(...)`.
82
93
 
83
94
  ```typescript
84
95
  import { Module } from '@fluojs/core';
@@ -126,13 +137,81 @@ SlackModule.forRootAsync({
126
137
  Behavioral contract notes:
127
138
 
128
139
  - `SlackService.send(...)` resolves `defaultChannel` before delivery.
129
- - `SlackService.sendMany(...)` sends messages sequentially and supports `continueOnError` when callers need a batch result instead of fail-fast behavior.
130
- - `SlackService.send(...)`, `SlackService.sendMany(...)`, and `SlackService.sendNotification(...)` honor already-aborted signals before provider handoff, and the same signal is propagated to transport calls.
140
+ - `SlackService.sendMany(...)` sends messages sequentially and supports `continueOnError` when callers need a batch result instead of fail-fast behavior; it collects ordinary provider failures only, while caller cancellation always rejects the batch.
141
+ - `SlackService.send(...)`, `SlackService.sendMany(...)`, and `SlackService.sendNotification(...)` honor already-aborted signals before provider handoff. An aborted signal or a transport `AbortError` takes precedence over `continueOnError`, and the same signal is propagated through notification channels to transport calls.
131
142
  - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown.
132
- - Once shutdown starts, `SlackService.send(...)` and `SlackService.sendNotification(...)` fail with `SlackLifecycleError` instead of reusing or lazily creating transports; any in-flight factory-owned transport creation is awaited and closed by shutdown.
143
+ - Direct and notification-backed delivery require the lifecycle to be `ready`; calls before `onModuleInit()` finishes, after initialization failure, or during shutdown fail with `SlackLifecycleError` instead of lazily creating or reusing transports.
144
+ - Shutdown rejects new deliveries, waits for the shared bootstrap initialization promise—including any in-flight factory-owned transport creation and `verify()` call—to settle, drains active deliveries, and then closes the owned transport before completion.
145
+ - Factory-owned transport cleanup is serialized across bootstrap-failure cleanup and application shutdown, so the same owned transport is closed at most once even when those paths race.
133
146
  - `SlackService.createPlatformStatusSnapshot()` reports lifecycle, readiness, and transport ownership without requiring callers to reach into internal options.
134
147
  - The package never reads `process.env` directly. All configuration must enter through explicit options or DI.
135
148
 
149
+ ### Bootstrap verification with `verifyOnModuleInit`
150
+
151
+ Set `SlackModuleOptions.verifyOnModuleInit?: boolean` to `true` when the selected transport can verify its own readiness during application bootstrap. `SlackService.onModuleInit()` always resolves the configured transport first; if `verifyOnModuleInit` is enabled **and** the resolved transport exposes an optional `verify()` method, the service awaits `transport.verify()` before marking the Slack provider ready. Transports that do not implement `verify` are still valid and simply skip the verification step.
152
+
153
+ ```typescript
154
+ import { Module } from '@fluojs/core';
155
+ import {
156
+ SlackModule,
157
+ type SlackTemplateRenderer,
158
+ type SlackTransport,
159
+ } from '@fluojs/slack';
160
+
161
+ const renderer: SlackTemplateRenderer = {
162
+ render({ template, payload, subject }) {
163
+ if (template === 'deploy.finished') {
164
+ return {
165
+ blocks: [
166
+ {
167
+ text: { text: `*${String(payload.version)}* is live`, type: 'mrkdwn' },
168
+ type: 'section',
169
+ },
170
+ ],
171
+ text: subject,
172
+ };
173
+ }
174
+
175
+ return { text: subject };
176
+ },
177
+ };
178
+
179
+ const slackApiTransport: SlackTransport = {
180
+ async verify() {
181
+ await slackApi.authTest();
182
+ },
183
+ async send(message, { signal }) {
184
+ const receipt = await slackApi.postMessage(message, { signal });
185
+
186
+ return {
187
+ channel: receipt.channel,
188
+ messageTs: receipt.ts,
189
+ ok: receipt.ok,
190
+ };
191
+ },
192
+ };
193
+
194
+ @Module({
195
+ imports: [
196
+ SlackModule.forRoot({
197
+ defaultChannel: '#ops',
198
+ renderer,
199
+ transport: slackApiTransport,
200
+ verifyOnModuleInit: true,
201
+ }),
202
+ ],
203
+ })
204
+ export class AppModule {}
205
+ ```
206
+
207
+ Behavioral contract notes:
208
+
209
+ - `verifyOnModuleInit` is optional and defaults to `false`.
210
+ - Verification is capability-based: only transports that expose `verify()` are called, so webhook-only or app-owned transports do not have to add a no-op verifier.
211
+ - Bootstrap initialization and verification share one promise with shutdown, so a factory-owned transport remains open until an in-flight `verify()` call settles and is then closed at most once.
212
+ - If `transport.verify()` rejects, bootstrap fails with `SlackLifecycleError` wrapping the initialization failure, the service lifecycle moves to `failed`, readiness/status snapshots report the provider as not ready, and factory-owned transports that were already resolved are closed before the error is rethrown. If shutdown starts while that cleanup is still in progress, both callers await the same close work.
213
+ - `SlackService.createPlatformStatusSnapshot()` includes `verifiedOnModuleInit` so health/readiness tooling can tell whether bootstrap verification was requested.
214
+
136
215
  ### Integration with `@fluojs/notifications`
137
216
 
138
217
  Inject `SLACK_CHANNEL` into `NotificationsModule.forRootAsync(...)` so the Slack package remains the only place that understands Slack-specific payload fields and recipient-to-channel translation.
@@ -179,6 +258,51 @@ Behavioral contract notes:
179
258
  - Notification metadata is merged from payload metadata, dispatch metadata, and subject/template markers before delivery.
180
259
  - If a notification needs fan-out across multiple Slack destinations, call `sendMany(...)` instead of one multi-recipient dispatch.
181
260
 
261
+ ### Template rendering and merge precedence
262
+
263
+ Provide a `SlackTemplateRenderer` when notification templates should turn shared `@fluojs/notifications` envelopes into Slack-specific text, blocks, or attachments. `SlackService.sendNotification(...)` calls `renderer.render(input)` only when both `notification.template` and `SlackModuleOptions.renderer` are present. The render input includes `template`, `payload`, optional `subject`, optional `locale`, and optional dispatch `metadata`.
264
+
265
+ ```typescript
266
+ import type { SlackTemplateRenderer } from '@fluojs/slack';
267
+
268
+ const renderer: SlackTemplateRenderer = {
269
+ async render(input) {
270
+ return {
271
+ blocks: [
272
+ {
273
+ text: {
274
+ text: `*${String(input.payload.releaseId)}* deployed for ${input.locale ?? 'default'}`,
275
+ type: 'mrkdwn',
276
+ },
277
+ type: 'section',
278
+ },
279
+ ],
280
+ text: input.subject,
281
+ };
282
+ },
283
+ };
284
+
285
+ await notifications.dispatch({
286
+ channel: 'slack',
287
+ locale: 'en',
288
+ metadata: { source: 'ci' },
289
+ payload: {
290
+ metadata: { releaseId: 'rel-42' },
291
+ text: 'Deploy rel-42 finished.',
292
+ },
293
+ recipients: ['#ops'],
294
+ subject: 'Deploy finished',
295
+ template: 'deploy.finished',
296
+ });
297
+ ```
298
+
299
+ Merge precedence is deterministic:
300
+
301
+ - `payload.attachments` and `payload.blocks` win over rendered `attachments` and `blocks` when those payload fields are defined. Non-blank `payload.text` wins over rendered `text`.
302
+ - Text falls back from non-blank `payload.text` to non-blank rendered `text`, then to non-blank `notification.subject`. Empty or whitespace-only text values are treated as unspecified.
303
+ - Metadata is merged as payload metadata, dispatch metadata, subject marker, then template marker. Later entries win on duplicate keys, so the final message records the dispatch `subject` and `template` markers when present.
304
+ - If no `template` is set or no renderer is registered, no template rendering occurs; the notification is adapted from its payload and subject only.
305
+
182
306
  ### Webhook-first delivery with explicit fetch injection
183
307
 
184
308
  Use `createSlackWebhookTransport(...)` when you want a portable first-party transport that only depends on a fetch-compatible HTTP boundary.
@@ -200,7 +324,7 @@ For richer API integrations such as `chat.postMessage`, implement the exported `
200
324
  Behavioral contract notes:
201
325
 
202
326
  - Passing `fetch` explicitly is the portable path and is recommended for all supported runtimes. For backward compatibility, omitting `fetch` falls back to `globalThis.fetch` when that ambient runtime API exists; runtimes without `globalThis.fetch` fail fast with `SlackConfigurationError`.
203
- - The built-in webhook transport retries transient `408`, `429`, and `5xx` failures with bounded exponential backoff before surfacing an error.
327
+ - The built-in webhook transport consumes transient `408`, `429`, and `5xx` response bodies before retrying with bounded exponential backoff, then surfaces an error only after retries are exhausted.
204
328
  - Abort signals are passed to the injected `fetch` boundary and cancel retry backoff without wrapping `AbortError` values as `SlackTransportError`.
205
329
  - Caller-visible `SlackTransportError` messages omit raw upstream response bodies by default.
206
330
 
@@ -211,6 +335,7 @@ The Slack package intentionally does **not**:
211
335
  - read credentials or webhook URLs from `process.env`
212
336
  - ship a Node-only Slack SDK inside the shared root package boundary
213
337
  - force one provider strategy beyond the webhook-first helper and exported transport contract
338
+ - provide a package-level multi-client registry beyond the singleton module/helper surface
214
339
  - translate one notification into multi-channel fan-out inside a single dispatch call
215
340
 
216
341
  These limitations are part of the package contract so runtime choice, provider capability, and rollout strategy stay explicit at the application boundary.
@@ -268,8 +393,8 @@ These limitations are part of the package contract so runtime choice, provider c
268
393
  - `SlackLifecycleState`
269
394
  - `SlackStatusAdapterInput`
270
395
  - `SlackConfigurationError`
271
- - `SlackLifecycleError`: thrown by lifecycle-gated delivery, transport initialization, and owned-resource shutdown failures. Catch this error when sends can race with application teardown.
272
- - `SlackMessageValidationError`
396
+ - `SlackLifecycleError`: thrown by lifecycle-gated delivery before readiness, after initialization failure, or during shutdown, plus transport factory, verification, and owned-resource cleanup failures. Catch this error when sends can race with bootstrap or application teardown.
397
+ - `SlackMessageValidationError`: thrown when a direct message has no Slack-visible `text`, `blocks`, or `attachments`, or when one notification dispatch resolves multiple Slack recipients and should be split with `sendMany(...)`.
273
398
  - `SlackTransportError`
274
399
 
275
400
  ## Related Packages
@@ -281,5 +406,6 @@ These limitations are part of the package contract so runtime choice, provider c
281
406
  ## Example Sources
282
407
 
283
408
  - `packages/slack/src/module.test.ts`: Module registration, `createSlackProviders(...)` helper coverage, async wiring, webhook transport, and notifications integration examples.
409
+ - `packages/slack/src/lifecycle-regression.test.ts`: Bootstrap verification and shutdown ordering regression coverage.
284
410
  - `packages/slack/src/public-surface.test.ts`: Public export and TypeScript contract verification.
285
411
  - `packages/slack/src/status.test.ts`: Health/readiness contract examples.
package/dist/errors.d.ts CHANGED
@@ -5,13 +5,24 @@ export declare class SlackConfigurationError extends Error {
5
5
  constructor(message: string);
6
6
  }
7
7
  /**
8
- * Thrown when a Slack message or notification payload is missing one required contract field.
8
+ * Thrown when caller-supplied Slack message data violates the public delivery contract.
9
+ *
10
+ * @remarks
11
+ * Direct messages surface this error when normalized content has no Slack-visible `text`, `blocks`, or `attachments`.
12
+ * Notification-backed delivery also surfaces it when one dispatch resolves to more than one Slack destination and callers
13
+ * should use `sendMany(...)` for fan-out instead.
9
14
  */
10
15
  export declare class SlackMessageValidationError extends Error {
11
16
  constructor(message: string);
12
17
  }
13
18
  /**
14
- * Thrown when Slack delivery is requested after the service lifecycle has started shutting down.
19
+ * Thrown when Slack delivery, transport initialization, verification, or owned-resource cleanup is blocked by lifecycle state.
20
+ *
21
+ * @remarks
22
+ * Direct and notification-backed delivery require a ready service. Calls made before module initialization finishes,
23
+ * after initialization fails, or after shutdown starts surface this error instead of lazily creating or reusing a transport.
24
+ * Bootstrap also wraps transport factory or `verify()` failures in this type, and application shutdown wraps failures from
25
+ * factory-owned transport cleanup so callers can distinguish lifecycle failures from provider delivery failures.
15
26
  */
16
27
  export declare class SlackLifecycleError extends Error {
17
28
  constructor(message: string, options?: ErrorOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js CHANGED
@@ -9,7 +9,12 @@ export class SlackConfigurationError extends Error {
9
9
  }
10
10
 
11
11
  /**
12
- * Thrown when a Slack message or notification payload is missing one required contract field.
12
+ * Thrown when caller-supplied Slack message data violates the public delivery contract.
13
+ *
14
+ * @remarks
15
+ * Direct messages surface this error when normalized content has no Slack-visible `text`, `blocks`, or `attachments`.
16
+ * Notification-backed delivery also surfaces it when one dispatch resolves to more than one Slack destination and callers
17
+ * should use `sendMany(...)` for fan-out instead.
13
18
  */
14
19
  export class SlackMessageValidationError extends Error {
15
20
  constructor(message) {
@@ -19,7 +24,13 @@ export class SlackMessageValidationError extends Error {
19
24
  }
20
25
 
21
26
  /**
22
- * Thrown when Slack delivery is requested after the service lifecycle has started shutting down.
27
+ * Thrown when Slack delivery, transport initialization, verification, or owned-resource cleanup is blocked by lifecycle state.
28
+ *
29
+ * @remarks
30
+ * Direct and notification-backed delivery require a ready service. Calls made before module initialization finishes,
31
+ * after initialization fails, or after shutdown starts surface this error instead of lazily creating or reusing a transport.
32
+ * Bootstrap also wraps transport factory or `verify()` failures in this type, and application shutdown wraps failures from
33
+ * factory-owned transport cleanup so callers can distinguish lifecycle failures from provider delivery failures.
23
34
  */
24
35
  export class SlackLifecycleError extends Error {
25
36
  constructor(message, options) {
package/dist/module.d.ts CHANGED
@@ -2,10 +2,10 @@ import type { Provider } from '@fluojs/di';
2
2
  import { type ModuleType } from '@fluojs/runtime';
3
3
  import type { SlackAsyncModuleOptions, SlackModuleOptions } from './types.js';
4
4
  /**
5
- * Creates Slack providers for manual module composition.
5
+ * Creates the singleton Slack providers for manual module composition.
6
6
  *
7
- * @param options Static Slack module options including explicit transport wiring.
8
- * @returns Provider definitions equivalent to {@link SlackModule.forRoot} wiring.
7
+ * @param options Static singleton Slack module options including explicit transport wiring.
8
+ * @returns Provider definitions equivalent to {@link SlackModule.forRoot} wiring for `SlackService`, `SlackChannel`, `SLACK`, and `SLACK_CHANNEL`.
9
9
  */
10
10
  export declare function createSlackProviders(options: SlackModuleOptions): Provider[];
11
11
  /** Runtime module entrypoint for Slack delivery and notifications integration. */
@@ -14,7 +14,7 @@ export declare class SlackModule {
14
14
  * Registers Slack providers using static options.
15
15
  *
16
16
  * @param options Static Slack module options including transport wiring and optional template rendering behavior.
17
- * @returns A global module definition that exports {@link SlackService}, {@link SlackChannel}, and compatibility tokens.
17
+ * @returns A module definition that exports {@link SlackService}, {@link SlackChannel}, and compatibility tokens.
18
18
  *
19
19
  * @example
20
20
  * ```ts
@@ -31,7 +31,7 @@ export declare class SlackModule {
31
31
  * Registers Slack providers from an async DI factory.
32
32
  *
33
33
  * @param options Async module options that resolve Slack transport and renderer configuration through DI.
34
- * @returns A global module definition that memoizes async option resolution per module instance.
34
+ * @returns A module definition that resolves async options through each active application container.
35
35
  *
36
36
  * @example
37
37
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EAGV,uBAAuB,EACvB,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AA0DpB;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,QAAQ,EAAE,CAK5E;AAsCD,kFAAkF;AAClF,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAIvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU;CAGlE"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EAGV,uBAAuB,EACvB,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AA0DpB;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,QAAQ,EAAE,CAK5E;AA8BD,kFAAkF;AAClF,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAIvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU;CAGlE"}
package/dist/module.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineModule } from '@fluojs/runtime';
2
- import { SlackConfigurationError } from './errors.js';
3
2
  import { SlackChannel } from './channel.js';
3
+ import { SlackConfigurationError } from './errors.js';
4
4
  import { SlackService } from './service.js';
5
5
  import { SLACK, SLACK_CHANNEL, SLACK_OPTIONS } from './tokens.js';
6
6
  function normalizeOptionalString(value) {
@@ -47,10 +47,10 @@ function createSlackRuntimeProviders(optionsProvider) {
47
47
  }
48
48
 
49
49
  /**
50
- * Creates Slack providers for manual module composition.
50
+ * Creates the singleton Slack providers for manual module composition.
51
51
  *
52
- * @param options Static Slack module options including explicit transport wiring.
53
- * @returns Provider definitions equivalent to {@link SlackModule.forRoot} wiring.
52
+ * @param options Static singleton Slack module options including explicit transport wiring.
53
+ * @returns Provider definitions equivalent to {@link SlackModule.forRoot} wiring for `SlackService`, `SlackChannel`, `SLACK`, and `SLACK_CHANNEL`.
54
54
  */
55
55
  export function createSlackProviders(options) {
56
56
  return createSlackRuntimeProviders({
@@ -69,13 +69,6 @@ function buildSlackModule(options) {
69
69
  function buildSlackModuleAsync(options) {
70
70
  class SlackAsyncModuleDefinition {}
71
71
  const factory = options.useFactory;
72
- let cachedResult;
73
- const memoizedFactory = (...deps) => {
74
- if (!cachedResult) {
75
- cachedResult = Promise.resolve(factory(...deps)).then(resolved => normalizeSlackModuleOptions(resolved));
76
- }
77
- return cachedResult;
78
- };
79
72
  return defineModule(SlackAsyncModuleDefinition, {
80
73
  exports: [SlackService, SlackChannel, SLACK, SLACK_CHANNEL],
81
74
  global: options.global ?? true,
@@ -83,7 +76,7 @@ function buildSlackModuleAsync(options) {
83
76
  inject: options.inject,
84
77
  provide: SLACK_OPTIONS,
85
78
  scope: 'singleton',
86
- useFactory: (...deps) => memoizedFactory(...deps)
79
+ useFactory: (...deps) => Promise.resolve(factory(...deps)).then(resolved => normalizeSlackModuleOptions(resolved))
87
80
  })
88
81
  });
89
82
  }
@@ -94,7 +87,7 @@ export class SlackModule {
94
87
  * Registers Slack providers using static options.
95
88
  *
96
89
  * @param options Static Slack module options including transport wiring and optional template rendering behavior.
97
- * @returns A global module definition that exports {@link SlackService}, {@link SlackChannel}, and compatibility tokens.
90
+ * @returns A module definition that exports {@link SlackService}, {@link SlackChannel}, and compatibility tokens.
98
91
  *
99
92
  * @example
100
93
  * ```ts
@@ -114,7 +107,7 @@ export class SlackModule {
114
107
  * Registers Slack providers from an async DI factory.
115
108
  *
116
109
  * @param options Async module options that resolve Slack transport and renderer configuration through DI.
117
- * @returns A global module definition that memoizes async option resolution per module instance.
110
+ * @returns A module definition that resolves async options through each active application container.
118
111
  *
119
112
  * @example
120
113
  * ```ts
package/dist/service.d.ts CHANGED
@@ -10,14 +10,21 @@ import type { NormalizedSlackModuleOptions, Slack, SlackMessage, SlackNotificati
10
10
  */
11
11
  export declare class SlackService implements Slack, OnModuleInit, OnApplicationShutdown {
12
12
  private readonly options;
13
+ private readonly inFlightDeliveries;
14
+ private initializationPromise;
13
15
  private lifecycleState;
16
+ private ownedTransportCleanupPromise;
14
17
  private resolvedTransport;
15
18
  private shutdownPromise;
16
19
  private transportPromise;
17
20
  constructor(options: NormalizedSlackModuleOptions);
18
21
  onApplicationShutdown(): Promise<void>;
19
22
  private closeOwnedTransport;
23
+ private closeOwnedTransportResources;
24
+ private closeOwnedTransportResourcesOnce;
25
+ private resolveTransportForCleanup;
20
26
  onModuleInit(): Promise<void>;
27
+ private initializeTransport;
21
28
  /**
22
29
  * Creates a platform status snapshot for the active Slack transport wiring.
23
30
  *
@@ -30,7 +37,8 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
30
37
  * @param message Caller-supplied Slack message with text and/or block content.
31
38
  * @param options Optional abort signal propagated to the transport.
32
39
  * @returns A normalized delivery receipt describing the transport response.
33
- * @throws {SlackMessageValidationError} When the resolved message does not include Slack-visible content.
40
+ * @throws {SlackMessageValidationError} When the resolved message has no Slack-visible `text`, `blocks`, or `attachments`.
41
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
34
42
  *
35
43
  * @example
36
44
  * ```ts
@@ -47,6 +55,7 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
47
55
  * @param messages Ordered message list to deliver through the configured transport.
48
56
  * @param options Optional tolerant batch controls such as `continueOnError`.
49
57
  * @returns A batch summary containing successes and any captured failures.
58
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
50
59
  *
51
60
  * @example
52
61
  * ```ts
@@ -61,7 +70,8 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
61
70
  * @param notification Shared notification envelope interpreted by the Slack package.
62
71
  * @param options Optional abort signal propagated to rendering and transport work.
63
72
  * @returns A normalized delivery receipt for the resulting Slack message.
64
- * @throws {SlackMessageValidationError} When the notification cannot resolve one target channel or any Slack-visible content.
73
+ * @throws {SlackMessageValidationError} When the notification resolves multiple Slack recipients or no Slack-visible content.
74
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
65
75
  *
66
76
  * @example
67
77
  * ```ts
@@ -74,6 +84,10 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
74
84
  */
75
85
  sendNotification(notification: SlackNotificationDispatchRequest, options?: SlackSendOptions): Promise<SlackSendResult>;
76
86
  private ensureTransport;
87
+ private clearResolvedTransport;
88
+ private handleTransportInitializationFailure;
89
+ private drainInFlightDeliveries;
90
+ private trackInFlightDelivery;
77
91
  private assertCanCreateOrUseTransport;
78
92
  private assertCanDeliver;
79
93
  private normalizeMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EAEV,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,gCAAgC,EAChC,oBAAoB,EAEpB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EAGhB,MAAM,YAAY,CAAC;AA2CpB;;;;;;;GAOG;AACH,qBACa,YAAa,YAAW,KAAK,EAAE,YAAY,EAAE,qBAAqB;IAMjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,gBAAgB,CAAsC;gBAEjC,OAAO,EAAE,4BAA4B;IAE5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;YAgB9B,mBAAmB;IAe3B,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCnC;;;;OAIG;IACH,4BAA4B;IAW5B;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsB3F;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BpH;;;;;;;;;;;;;;;;OAgBG;IACG,gBAAgB,CACpB,YAAY,EAAE,gCAAgC,EAC9C,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;YAkCb,eAAe;IAiB7B,OAAO,CAAC,6BAA6B;IAMrC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;YAkBpB,kBAAkB;CAkBjC"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EAEV,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,gCAAgC,EAChC,oBAAoB,EAEpB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EAGhB,MAAM,YAAY,CAAC;AAsDpB;;;;;;;GAOG;AACH,qBACa,YAAa,YAAW,KAAK,EAAE,YAAY,EAAE,qBAAqB;IASjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IARpC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA+B;IAClE,OAAO,CAAC,qBAAqB,CAA4B;IACzD,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,4BAA4B,CAA4B;IAChE,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,gBAAgB,CAAsC;gBAEjC,OAAO,EAAE,4BAA4B;IAE5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;YAgB9B,mBAAmB;IAoBjC,OAAO,CAAC,4BAA4B;YAQtB,gCAAgC;YAYhC,0BAA0B;IAexC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAoBf,mBAAmB;IAsBjC;;;;OAIG;IACH,4BAA4B;IAW5B;;;;;;;;;;;;;;;;OAgBG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAyB3F;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAgCpH;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,YAAY,EAAE,gCAAgC,EAC9C,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;YAkCb,eAAe;IAiB7B,OAAO,CAAC,sBAAsB;YAKhB,oCAAoC;YAsBpC,uBAAuB;YAMvB,qBAAqB;IAWnC,OAAO,CAAC,6BAA6B;IAMrC,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;YAkBpB,kBAAkB;CAkBjC"}
package/dist/service.js CHANGED
@@ -18,11 +18,17 @@ function assertNotAborted(signal) {
18
18
  throw createAbortError();
19
19
  }
20
20
  }
21
+ function isAbortError(error) {
22
+ return error.name === 'AbortError';
23
+ }
21
24
  function createLifecycleError(message, cause) {
22
25
  return new SlackLifecycleError(message, {
23
26
  cause
24
27
  });
25
28
  }
29
+ function createCleanupFailureCause(originalError, cleanupError) {
30
+ return new AggregateError([originalError, cleanupError], 'Slack transport verification failed and the owned transport failed to close.');
31
+ }
26
32
  function createDeliveryLifecycleError(state) {
27
33
  return new SlackLifecycleError(`Slack delivery cannot start while the service lifecycle is ${state}.`);
28
34
  }
@@ -52,7 +58,10 @@ class SlackService {
52
58
  static {
53
59
  [_SlackService, _initClass] = _applyDecs(this, [Inject(SLACK_OPTIONS)], []).c;
54
60
  }
61
+ inFlightDeliveries = new Set();
62
+ initializationPromise;
55
63
  lifecycleState = 'created';
64
+ ownedTransportCleanupPromise;
56
65
  resolvedTransport;
57
66
  shutdownPromise;
58
67
  transportPromise;
@@ -72,21 +81,60 @@ class SlackService {
72
81
  }
73
82
  async closeOwnedTransport() {
74
83
  try {
75
- const transport = this.resolvedTransport ?? (this.transportPromise ? await this.transportPromise : undefined);
76
- if (transport && this.options.transport.ownsResources && transport.close) {
77
- await transport.close();
84
+ const initializationPromise = this.initializationPromise;
85
+ if (initializationPromise) {
86
+ await initializationPromise.then(() => undefined, () => undefined);
78
87
  }
88
+ await this.drainInFlightDeliveries();
89
+ await this.closeOwnedTransportResources();
79
90
  this.lifecycleState = 'stopped';
80
91
  } catch (error) {
81
92
  this.lifecycleState = 'failed';
82
93
  throw createLifecycleError('Slack transport failed to close cleanly.', error);
83
94
  }
84
95
  }
85
- async onModuleInit() {
96
+ closeOwnedTransportResources() {
97
+ if (!this.ownedTransportCleanupPromise) {
98
+ this.ownedTransportCleanupPromise = this.closeOwnedTransportResourcesOnce();
99
+ }
100
+ return this.ownedTransportCleanupPromise;
101
+ }
102
+ async closeOwnedTransportResourcesOnce() {
103
+ try {
104
+ const transport = await this.resolveTransportForCleanup();
105
+ if (transport && this.options.transport.ownsResources && transport.close) {
106
+ await transport.close();
107
+ }
108
+ } finally {
109
+ this.clearResolvedTransport();
110
+ }
111
+ }
112
+ async resolveTransportForCleanup() {
113
+ if (this.resolvedTransport) {
114
+ return this.resolvedTransport;
115
+ }
116
+ if (!this.transportPromise) {
117
+ return undefined;
118
+ }
119
+ return this.transportPromise.then(transport => transport, () => undefined);
120
+ }
121
+ onModuleInit() {
86
122
  if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
87
- return;
123
+ return Promise.resolve();
88
124
  }
89
- this.lifecycleState = 'starting';
125
+ if (!this.initializationPromise) {
126
+ let resolveInitialization = () => undefined;
127
+ let rejectInitialization = _reason => undefined;
128
+ this.lifecycleState = 'starting';
129
+ this.initializationPromise = new Promise((resolve, reject) => {
130
+ resolveInitialization = resolve;
131
+ rejectInitialization = reject;
132
+ });
133
+ void this.initializeTransport().then(resolveInitialization, rejectInitialization);
134
+ }
135
+ return this.initializationPromise;
136
+ }
137
+ async initializeTransport() {
90
138
  try {
91
139
  const transport = await this.ensureTransport();
92
140
  if (this.lifecycleState !== 'starting') {
@@ -100,11 +148,7 @@ class SlackService {
100
148
  }
101
149
  this.lifecycleState = 'ready';
102
150
  } catch (error) {
103
- if (isShutdownLifecycleState(this.lifecycleState)) {
104
- throw error;
105
- }
106
- this.lifecycleState = 'failed';
107
- throw createLifecycleError('Slack transport failed to initialize.', error);
151
+ await this.handleTransportInitializationFailure(error);
108
152
  }
109
153
  }
110
154
 
@@ -130,7 +174,8 @@ class SlackService {
130
174
  * @param message Caller-supplied Slack message with text and/or block content.
131
175
  * @param options Optional abort signal propagated to the transport.
132
176
  * @returns A normalized delivery receipt describing the transport response.
133
- * @throws {SlackMessageValidationError} When the resolved message does not include Slack-visible content.
177
+ * @throws {SlackMessageValidationError} When the resolved message has no Slack-visible `text`, `blocks`, or `attachments`.
178
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
134
179
  *
135
180
  * @example
136
181
  * ```ts
@@ -148,7 +193,10 @@ class SlackService {
148
193
  assertMessageContent(normalized);
149
194
  assertNotAborted(options.signal);
150
195
  this.assertCanDeliver();
151
- const result = await transport.send(normalized, options);
196
+ const result = await this.trackInFlightDelivery(() => {
197
+ assertNotAborted(options.signal);
198
+ return transport.send(normalized, options);
199
+ });
152
200
  return {
153
201
  channel: result.channel ?? normalized.channel,
154
202
  messageTs: result.messageTs,
@@ -166,6 +214,7 @@ class SlackService {
166
214
  * @param messages Ordered message list to deliver through the configured transport.
167
215
  * @param options Optional tolerant batch controls such as `continueOnError`.
168
216
  * @returns A batch summary containing successes and any captured failures.
217
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
169
218
  *
170
219
  * @example
171
220
  * ```ts
@@ -175,6 +224,7 @@ class SlackService {
175
224
  */
176
225
  async sendMany(messages, options = {}) {
177
226
  assertNotAborted(options.signal);
227
+ this.assertCanDeliver();
178
228
  const results = [];
179
229
  const failures = [];
180
230
  for (const message of messages) {
@@ -185,7 +235,7 @@ class SlackService {
185
235
  error: error instanceof Error ? error : new Error('Slack delivery failed.'),
186
236
  message
187
237
  };
188
- if (!(options.continueOnError ?? false)) {
238
+ if (options.signal?.aborted || isAbortError(failure.error) || !(options.continueOnError ?? false)) {
189
239
  throw failure.error;
190
240
  }
191
241
  failures.push(failure);
@@ -205,7 +255,8 @@ class SlackService {
205
255
  * @param notification Shared notification envelope interpreted by the Slack package.
206
256
  * @param options Optional abort signal propagated to rendering and transport work.
207
257
  * @returns A normalized delivery receipt for the resulting Slack message.
208
- * @throws {SlackMessageValidationError} When the notification cannot resolve one target channel or any Slack-visible content.
258
+ * @throws {SlackMessageValidationError} When the notification resolves multiple Slack recipients or no Slack-visible content.
259
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
209
260
  *
210
261
  * @example
211
262
  * ```ts
@@ -240,7 +291,7 @@ class SlackService {
240
291
  },
241
292
  mrkdwn: payload.mrkdwn,
242
293
  replyBroadcast: payload.replyBroadcast,
243
- text: payload.text ?? rendered?.text ?? notification.subject,
294
+ text: normalizeOptionalString(payload.text) ?? normalizeOptionalString(rendered?.text) ?? normalizeOptionalString(notification.subject),
244
295
  threadTs: payload.threadTs,
245
296
  unfurlLinks: payload.unfurlLinks,
246
297
  unfurlMedia: payload.unfurlMedia,
@@ -260,13 +311,49 @@ class SlackService {
260
311
  }
261
312
  return this.transportPromise;
262
313
  }
314
+ clearResolvedTransport() {
315
+ this.resolvedTransport = undefined;
316
+ this.transportPromise = undefined;
317
+ }
318
+ async handleTransportInitializationFailure(error) {
319
+ const interruptedByShutdown = isShutdownLifecycleState(this.lifecycleState);
320
+ if (!interruptedByShutdown) {
321
+ this.lifecycleState = 'failed';
322
+ }
323
+ let cause = error;
324
+ try {
325
+ await this.closeOwnedTransportResources();
326
+ } catch (cleanupError) {
327
+ cause = createCleanupFailureCause(error, cleanupError);
328
+ }
329
+ if (!interruptedByShutdown && !isShutdownLifecycleState(this.lifecycleState)) {
330
+ this.lifecycleState = 'failed';
331
+ }
332
+ throw createLifecycleError('Slack transport failed to initialize.', cause);
333
+ }
334
+ async drainInFlightDeliveries() {
335
+ while (this.inFlightDeliveries.size > 0) {
336
+ await Promise.allSettled(Array.from(this.inFlightDeliveries));
337
+ }
338
+ }
339
+ async trackInFlightDelivery(start) {
340
+ const delivery = Promise.resolve().then(start);
341
+ this.inFlightDeliveries.add(delivery);
342
+ try {
343
+ return await delivery;
344
+ } finally {
345
+ this.inFlightDeliveries.delete(delivery);
346
+ }
347
+ }
263
348
  assertCanCreateOrUseTransport() {
264
349
  if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped' || this.lifecycleState === 'failed') {
265
350
  throw createDeliveryLifecycleError(this.lifecycleState);
266
351
  }
267
352
  }
268
353
  assertCanDeliver() {
269
- this.assertCanCreateOrUseTransport();
354
+ if (this.lifecycleState !== 'ready') {
355
+ throw createDeliveryLifecycleError(this.lifecycleState);
356
+ }
270
357
  }
271
358
  normalizeMessage(message) {
272
359
  return {
@@ -286,14 +373,14 @@ class SlackService {
286
373
  };
287
374
  }
288
375
  resolveNotificationChannel(notification) {
289
- const payloadChannel = normalizeOptionalString(notification.payload.channel);
290
- if (payloadChannel) {
291
- return payloadChannel;
292
- }
293
376
  const recipients = notification.recipients?.map(entry => entry.trim()).filter(entry => entry.length > 0) ?? [];
294
377
  if (recipients.length > 1) {
295
378
  throw new SlackMessageValidationError('Slack notifications accept exactly one target channel per dispatch. Use `sendMany(...)` for fan-out delivery.');
296
379
  }
380
+ const payloadChannel = normalizeOptionalString(notification.payload.channel);
381
+ if (payloadChannel) {
382
+ return payloadChannel;
383
+ }
297
384
  return recipients[0] ?? this.options.defaultChannel;
298
385
  }
299
386
  async renderNotification(notification, signal) {
package/dist/types.d.ts CHANGED
@@ -107,14 +107,12 @@ export interface SlackFetchResponse {
107
107
  text(): MaybePromise<string>;
108
108
  }
109
109
  /** Minimal fetch-compatible function signature used by the built-in webhook transport helper. */
110
- export interface SlackFetchLike {
111
- (input: string, init?: {
112
- body?: string;
113
- headers?: Readonly<Record<string, string>>;
114
- method?: string;
115
- signal?: AbortSignal;
116
- }): MaybePromise<SlackFetchResponse>;
117
- }
110
+ export type SlackFetchLike = (input: string, init?: {
111
+ body?: string;
112
+ headers?: Readonly<Record<string, string>>;
113
+ method?: string;
114
+ signal?: AbortSignal;
115
+ }) => MaybePromise<SlackFetchResponse>;
118
116
  /** Options accepted by {@link createSlackWebhookTransport}. */
119
117
  export interface SlackWebhookTransportOptions {
120
118
  fetch?: SlackFetchLike;
@@ -193,13 +191,23 @@ export interface SlackSendManyOptions extends SlackSendOptions {
193
191
  /** Module options accepted by {@link SlackModule.forRoot} and `forRootAsync`. */
194
192
  export interface SlackModuleOptions {
195
193
  defaultChannel?: string;
196
- /** Whether Slack providers should be visible globally. Defaults to `true`. */
194
+ /**
195
+ * Controls whether Slack providers are visible through the whole module graph.
196
+ *
197
+ * @remarks
198
+ * Defaults to `true` for Nest-like root registration. Set `false` when migrated code needs `SlackService`,
199
+ * `SlackChannel`, `SLACK`, and `SLACK_CHANNEL` to stay visible only to modules that explicitly import the returned
200
+ * module definition.
201
+ */
197
202
  global?: boolean;
198
203
  notifications?: {
199
204
  channel?: string;
200
205
  };
201
206
  renderer?: SlackTemplateRenderer;
202
207
  transport: SlackTransport | SlackTransportFactory;
208
+ /**
209
+ * Verifies the configured transport during `onModuleInit()` before the service becomes ready for delivery.
210
+ */
203
211
  verifyOnModuleInit?: boolean;
204
212
  }
205
213
  /** Async registration options for Slack modules that derive config through DI. */
@@ -226,6 +234,8 @@ export interface Slack {
226
234
  * @param message Caller-supplied Slack message with text and/or block content.
227
235
  * @param options Optional abort signal propagated to the transport.
228
236
  * @returns A normalized delivery receipt describing the transport response.
237
+ * @throws {SlackMessageValidationError} When the resolved message has no Slack-visible `text`, `blocks`, or `attachments`.
238
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
229
239
  */
230
240
  send(message: SlackMessage, options?: SlackSendOptions): Promise<SlackSendResult>;
231
241
  /**
@@ -234,6 +244,7 @@ export interface Slack {
234
244
  * @param messages Ordered message list to deliver through the configured transport.
235
245
  * @param options Optional tolerant batch controls such as `continueOnError`.
236
246
  * @returns A batch summary containing successes and any captured failures.
247
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
237
248
  */
238
249
  sendMany(messages: readonly SlackMessage[], options?: SlackSendManyOptions): Promise<SlackSendBatchResult>;
239
250
  /**
@@ -242,6 +253,8 @@ export interface Slack {
242
253
  * @param notification Shared notification envelope interpreted by the Slack package.
243
254
  * @param options Optional abort signal propagated to rendering and transport work.
244
255
  * @returns A normalized delivery receipt for the resulting Slack message.
256
+ * @throws {SlackMessageValidationError} When the notification resolves multiple Slack recipients or no Slack-visible content.
257
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
245
258
  */
246
259
  sendNotification(notification: SlackNotificationDispatchRequest, options?: SlackSendOptions): Promise<SlackSendResult>;
247
260
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,cAAc,EAEd,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AA4FpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,cAAc,CA8DjG"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,cAAc,EAEd,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AA4FpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,cAAc,CA4DjG"}
package/dist/webhook.js CHANGED
@@ -134,15 +134,14 @@ export function createSlackWebhookTransport(options) {
134
134
  method: 'POST',
135
135
  signal: context.signal
136
136
  });
137
+ const body = await readResponseBody(response);
137
138
  if (!response.ok) {
138
139
  if (attempt < DEFAULT_RETRY_ATTEMPTS && isTransientStatus(response.status)) {
139
140
  await waitForRetry(DEFAULT_RETRY_DELAY_MS * 2 ** (attempt - 1), context.signal);
140
141
  continue;
141
142
  }
142
- await readResponseBody(response);
143
143
  throw new SlackTransportError(createStatusFailureMessage(response, attempt));
144
144
  }
145
- const body = await readResponseBody(response);
146
145
  return {
147
146
  channel: message.channel,
148
147
  ok: true,
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "portable",
10
10
  "fetch"
11
11
  ],
12
- "version": "1.1.3",
12
+ "version": "2.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "directory": "packages/slack"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20.0.0"
21
+ "node": ">=24.0.0 <27"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
@@ -36,13 +36,13 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.3",
40
- "@fluojs/di": "^1.1.0",
41
- "@fluojs/notifications": "^1.0.1",
42
- "@fluojs/runtime": "^1.1.6"
39
+ "@fluojs/core": "^2.0.0",
40
+ "@fluojs/di": "^3.0.0",
41
+ "@fluojs/notifications": "^2.0.0",
42
+ "@fluojs/runtime": "^3.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "vitest": "^3.2.4"
45
+ "vitest": "^4.1.11"
46
46
  },
47
47
  "scripts": {
48
48
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",