@fluojs/slack 1.1.2 → 1.1.4

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-개요)
@@ -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,80 @@ 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 생성을 모두 settle한 뒤 소유한 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
+ - `transport.verify()`가 reject되면 bootstrap은 초기화 실패를 감싼 `SlackLifecycleError`로 실패하고, service lifecycle은 `failed`로 이동하며, readiness/status snapshot은 provider를 not ready로 보고하고, 이미 해석된 factory 소유 transport는 오류를 다시 던지기 전에 닫습니다. 그 cleanup이 진행 중일 때 shutdown이 시작되면 두 호출자는 같은 close 작업을 기다립니다.
212
+ - `SlackService.createPlatformStatusSnapshot()`은 bootstrap 검증 요청 여부를 health/readiness tooling이 확인할 수 있도록 `verifiedOnModuleInit`을 포함합니다.
213
+
136
214
  ### `@fluojs/notifications`와의 통합
137
215
 
138
216
  `SLACK_CHANNEL`을 `NotificationsModule.forRootAsync(...)`에 주입하여, Slack 전용 payload 필드와 recipient-to-channel 해석 규칙이 모두 `@fluojs/slack` 안에만 남도록 구성합니다.
@@ -179,6 +257,51 @@ Behavioral contract 메모:
179
257
  - notification metadata는 전달 전에 payload metadata, dispatch metadata, subject/template marker를 합쳐 구성됩니다.
180
258
  - 여러 Slack 대상지로 fan-out이 필요하다면 하나의 multi-recipient dispatch 대신 `sendMany(...)`를 사용해야 합니다.
181
259
 
260
+ ### Template rendering과 merge precedence
261
+
262
+ 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`가 들어갑니다.
263
+
264
+ ```typescript
265
+ import type { SlackTemplateRenderer } from '@fluojs/slack';
266
+
267
+ const renderer: SlackTemplateRenderer = {
268
+ async render(input) {
269
+ return {
270
+ blocks: [
271
+ {
272
+ text: {
273
+ text: `*${String(input.payload.releaseId)}* deployed for ${input.locale ?? 'default'}`,
274
+ type: 'mrkdwn',
275
+ },
276
+ type: 'section',
277
+ },
278
+ ],
279
+ text: input.subject,
280
+ };
281
+ },
282
+ };
283
+
284
+ await notifications.dispatch({
285
+ channel: 'slack',
286
+ locale: 'en',
287
+ metadata: { source: 'ci' },
288
+ payload: {
289
+ metadata: { releaseId: 'rel-42' },
290
+ text: 'Deploy rel-42 finished.',
291
+ },
292
+ recipients: ['#ops'],
293
+ subject: 'Deploy finished',
294
+ template: 'deploy.finished',
295
+ });
296
+ ```
297
+
298
+ Merge precedence는 결정적입니다.
299
+
300
+ - `payload.attachments`, `payload.blocks`가 정의되어 있으면 rendered `attachments`, `blocks`보다 우선합니다. 비공백 `payload.text`는 rendered `text`보다 우선합니다.
301
+ - Text는 비공백 `payload.text`에서 비공백 rendered `text`, 그리고 비공백 `notification.subject` 순서로 fallback합니다. 빈 문자열 또는 공백 전용 text 값은 미지정으로 취급합니다.
302
+ - Metadata는 payload metadata, dispatch metadata, subject marker, template marker 순서로 merge됩니다. 중복 키는 뒤쪽 값이 이기므로, 최종 메시지는 존재하는 dispatch `subject`와 `template` marker를 기록합니다.
303
+ - `template`이 없거나 renderer가 등록되지 않았다면 template rendering은 실행되지 않습니다. 이 경우 notification은 payload와 subject만으로 Slack 메시지로 변환됩니다.
304
+
182
305
  ### 명시적 fetch 주입을 사용하는 webhook-first 전달
183
306
 
184
307
  런타임에 독립적인 1st-party transport가 필요하다면 fetch-compatible HTTP 경계만 의존하는 `createSlackWebhookTransport(...)`를 사용합니다.
@@ -200,7 +323,7 @@ await slack.send({
200
323
  Behavioral contract 메모:
201
324
 
202
325
  - `fetch`를 명시적으로 전달하는 방식이 portable path이며 모든 지원 런타임에서 권장됩니다. 하위 호환성을 위해 `fetch`를 생략하면 ambient runtime API인 `globalThis.fetch`가 있을 때 이를 폴백으로 사용합니다. `globalThis.fetch`가 없는 런타임에서는 `SlackConfigurationError`로 빠르게 실패합니다.
203
- - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 실패를 호출자에게 에러를 노출하기 전에 bounded exponential backoff로 재시도합니다.
326
+ - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 실패 응답의 본문을 소비한 bounded exponential backoff로 재시도하고, 재시도가 모두 소진된 뒤에만 호출자에게 에러를 노출합니다.
204
327
  - Abort signal은 주입된 `fetch` 경계로 전달되며, retry backoff를 취소할 때 `AbortError`를 `SlackTransportError`로 감싸지 않습니다.
205
328
  - 호출자에게 보이는 `SlackTransportError` 메시지는 기본적으로 raw upstream response body를 포함하지 않습니다.
206
329
 
@@ -211,6 +334,7 @@ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
211
334
  - 자격 증명이나 webhook URL을 `process.env`에서 직접 읽는 동작
212
335
  - 공유 루트 패키지 경계에 Node 전용 Slack SDK를 내장하는 것
213
336
  - webhook helper와 export된 transport 계약 이상으로 하나의 provider 전략을 강제하는 것
337
+ - singleton module/helper surface를 넘어서는 package-level multi-client registry를 제공하는 것
214
338
  - 하나의 dispatch 호출 안에서 multi-channel fan-out을 자동 변환하는 것
215
339
 
216
340
  이 제한 사항은 런타임 선택, provider capability, rollout 전략이 애플리케이션 경계에서 명시적으로 결정되도록 하기 위한 package contract의 일부입니다.
@@ -268,8 +392,8 @@ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
268
392
  - `SlackLifecycleState`
269
393
  - `SlackStatusAdapterInput`
270
394
  - `SlackConfigurationError`
271
- - `SlackLifecycleError`: lifecycle로 차단된 전달, transport 초기화, 소유 리소스 shutdown 실패에서 발생합니다. 애플리케이션 teardown과 전송이 경합할 수 있다면 이 에러를 catch하세요.
272
- - `SlackMessageValidationError`
395
+ - `SlackLifecycleError`: readiness 전, 초기화 실패 뒤, 또는 shutdown 중 lifecycle로 차단된 전달과 transport factory, verification, 소유 리소스 cleanup 실패에서 발생합니다. bootstrap 또는 애플리케이션 teardown과 전송이 경합할 수 있다면 이 에러를 catch하세요.
396
+ - `SlackMessageValidationError`: 직접 메시지에 Slack에서 보이는 `text`, `blocks`, `attachments`가 없거나, 하나의 notification dispatch가 여러 Slack recipient로 해석되어 `sendMany(...)`로 분리해야 할 때 발생합니다.
273
397
  - `SlackTransportError`
274
398
 
275
399
  ## 관련 패키지
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)
@@ -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,80 @@ 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 active deliveries and any in-flight factory-owned transport creation to settle, 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
+ - 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.
212
+ - `SlackService.createPlatformStatusSnapshot()` includes `verifiedOnModuleInit` so health/readiness tooling can tell whether bootstrap verification was requested.
213
+
136
214
  ### Integration with `@fluojs/notifications`
137
215
 
138
216
  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 +257,51 @@ Behavioral contract notes:
179
257
  - Notification metadata is merged from payload metadata, dispatch metadata, and subject/template markers before delivery.
180
258
  - If a notification needs fan-out across multiple Slack destinations, call `sendMany(...)` instead of one multi-recipient dispatch.
181
259
 
260
+ ### Template rendering and merge precedence
261
+
262
+ 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`.
263
+
264
+ ```typescript
265
+ import type { SlackTemplateRenderer } from '@fluojs/slack';
266
+
267
+ const renderer: SlackTemplateRenderer = {
268
+ async render(input) {
269
+ return {
270
+ blocks: [
271
+ {
272
+ text: {
273
+ text: `*${String(input.payload.releaseId)}* deployed for ${input.locale ?? 'default'}`,
274
+ type: 'mrkdwn',
275
+ },
276
+ type: 'section',
277
+ },
278
+ ],
279
+ text: input.subject,
280
+ };
281
+ },
282
+ };
283
+
284
+ await notifications.dispatch({
285
+ channel: 'slack',
286
+ locale: 'en',
287
+ metadata: { source: 'ci' },
288
+ payload: {
289
+ metadata: { releaseId: 'rel-42' },
290
+ text: 'Deploy rel-42 finished.',
291
+ },
292
+ recipients: ['#ops'],
293
+ subject: 'Deploy finished',
294
+ template: 'deploy.finished',
295
+ });
296
+ ```
297
+
298
+ Merge precedence is deterministic:
299
+
300
+ - `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`.
301
+ - 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.
302
+ - 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.
303
+ - If no `template` is set or no renderer is registered, no template rendering occurs; the notification is adapted from its payload and subject only.
304
+
182
305
  ### Webhook-first delivery with explicit fetch injection
183
306
 
184
307
  Use `createSlackWebhookTransport(...)` when you want a portable first-party transport that only depends on a fetch-compatible HTTP boundary.
@@ -200,7 +323,7 @@ For richer API integrations such as `chat.postMessage`, implement the exported `
200
323
  Behavioral contract notes:
201
324
 
202
325
  - 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.
326
+ - 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
327
  - Abort signals are passed to the injected `fetch` boundary and cancel retry backoff without wrapping `AbortError` values as `SlackTransportError`.
205
328
  - Caller-visible `SlackTransportError` messages omit raw upstream response bodies by default.
206
329
 
@@ -211,6 +334,7 @@ The Slack package intentionally does **not**:
211
334
  - read credentials or webhook URLs from `process.env`
212
335
  - ship a Node-only Slack SDK inside the shared root package boundary
213
336
  - force one provider strategy beyond the webhook-first helper and exported transport contract
337
+ - provide a package-level multi-client registry beyond the singleton module/helper surface
214
338
  - translate one notification into multi-channel fan-out inside a single dispatch call
215
339
 
216
340
  These limitations are part of the package contract so runtime choice, provider capability, and rollout strategy stay explicit at the application boundary.
@@ -268,8 +392,8 @@ These limitations are part of the package contract so runtime choice, provider c
268
392
  - `SlackLifecycleState`
269
393
  - `SlackStatusAdapterInput`
270
394
  - `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`
395
+ - `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.
396
+ - `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
397
  - `SlackTransportError`
274
398
 
275
399
  ## Related Packages
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,11 +10,18 @@ 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;
13
14
  private lifecycleState;
15
+ private ownedTransportCleanupPromise;
14
16
  private resolvedTransport;
17
+ private shutdownPromise;
15
18
  private transportPromise;
16
19
  constructor(options: NormalizedSlackModuleOptions);
17
20
  onApplicationShutdown(): Promise<void>;
21
+ private closeOwnedTransport;
22
+ private closeOwnedTransportResources;
23
+ private closeOwnedTransportResourcesOnce;
24
+ private resolveTransportForCleanup;
18
25
  onModuleInit(): Promise<void>;
19
26
  /**
20
27
  * Creates a platform status snapshot for the active Slack transport wiring.
@@ -28,7 +35,8 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
28
35
  * @param message Caller-supplied Slack message with text and/or block content.
29
36
  * @param options Optional abort signal propagated to the transport.
30
37
  * @returns A normalized delivery receipt describing the transport response.
31
- * @throws {SlackMessageValidationError} When the resolved message does not include Slack-visible content.
38
+ * @throws {SlackMessageValidationError} When the resolved message has no Slack-visible `text`, `blocks`, or `attachments`.
39
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
32
40
  *
33
41
  * @example
34
42
  * ```ts
@@ -45,6 +53,7 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
45
53
  * @param messages Ordered message list to deliver through the configured transport.
46
54
  * @param options Optional tolerant batch controls such as `continueOnError`.
47
55
  * @returns A batch summary containing successes and any captured failures.
56
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
48
57
  *
49
58
  * @example
50
59
  * ```ts
@@ -59,7 +68,8 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
59
68
  * @param notification Shared notification envelope interpreted by the Slack package.
60
69
  * @param options Optional abort signal propagated to rendering and transport work.
61
70
  * @returns A normalized delivery receipt for the resulting Slack message.
62
- * @throws {SlackMessageValidationError} When the notification cannot resolve one target channel or any Slack-visible content.
71
+ * @throws {SlackMessageValidationError} When the notification resolves multiple Slack recipients or no Slack-visible content.
72
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
63
73
  *
64
74
  * @example
65
75
  * ```ts
@@ -72,6 +82,10 @@ export declare class SlackService implements Slack, OnModuleInit, OnApplicationS
72
82
  */
73
83
  sendNotification(notification: SlackNotificationDispatchRequest, options?: SlackSendOptions): Promise<SlackSendResult>;
74
84
  private ensureTransport;
85
+ private clearResolvedTransport;
86
+ private handleTransportInitializationFailure;
87
+ private drainInFlightDeliveries;
88
+ private trackInFlightDelivery;
75
89
  private assertCanCreateOrUseTransport;
76
90
  private assertCanDeliver;
77
91
  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;IAKjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,gBAAgB,CAAsC;gBAEjC,OAAO,EAAE,4BAA4B;IAE5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtC,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;IA6BpH;;;;;;;;;;;;;;;;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;IAQjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA+B;IAClE,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;IAWjC,OAAO,CAAC,4BAA4B;YAQtB,gCAAgC;YAYhC,0BAA0B;IAelC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BnC;;;;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,25 +58,61 @@ class SlackService {
52
58
  static {
53
59
  [_SlackService, _initClass] = _applyDecs(this, [Inject(SLACK_OPTIONS)], []).c;
54
60
  }
61
+ inFlightDeliveries = new Set();
55
62
  lifecycleState = 'created';
63
+ ownedTransportCleanupPromise;
56
64
  resolvedTransport;
65
+ shutdownPromise;
57
66
  transportPromise;
58
67
  constructor(options) {
59
68
  this.options = options;
60
69
  }
61
70
  async onApplicationShutdown() {
71
+ if (this.lifecycleState === 'stopped') {
72
+ return;
73
+ }
74
+ if (this.shutdownPromise) {
75
+ return this.shutdownPromise;
76
+ }
62
77
  this.lifecycleState = 'stopping';
78
+ this.shutdownPromise = this.closeOwnedTransport();
79
+ return this.shutdownPromise;
80
+ }
81
+ async closeOwnedTransport() {
63
82
  try {
64
- const transport = this.resolvedTransport ?? (this.transportPromise ? await this.transportPromise : undefined);
65
- if (transport && this.options.transport.ownsResources && transport.close) {
66
- await transport.close();
67
- }
83
+ await this.drainInFlightDeliveries();
84
+ await this.closeOwnedTransportResources();
68
85
  this.lifecycleState = 'stopped';
69
86
  } catch (error) {
70
87
  this.lifecycleState = 'failed';
71
88
  throw createLifecycleError('Slack transport failed to close cleanly.', error);
72
89
  }
73
90
  }
91
+ closeOwnedTransportResources() {
92
+ if (!this.ownedTransportCleanupPromise) {
93
+ this.ownedTransportCleanupPromise = this.closeOwnedTransportResourcesOnce();
94
+ }
95
+ return this.ownedTransportCleanupPromise;
96
+ }
97
+ async closeOwnedTransportResourcesOnce() {
98
+ try {
99
+ const transport = await this.resolveTransportForCleanup();
100
+ if (transport && this.options.transport.ownsResources && transport.close) {
101
+ await transport.close();
102
+ }
103
+ } finally {
104
+ this.clearResolvedTransport();
105
+ }
106
+ }
107
+ async resolveTransportForCleanup() {
108
+ if (this.resolvedTransport) {
109
+ return this.resolvedTransport;
110
+ }
111
+ if (!this.transportPromise) {
112
+ return undefined;
113
+ }
114
+ return this.transportPromise.then(transport => transport, () => undefined);
115
+ }
74
116
  async onModuleInit() {
75
117
  if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
76
118
  return;
@@ -89,11 +131,7 @@ class SlackService {
89
131
  }
90
132
  this.lifecycleState = 'ready';
91
133
  } catch (error) {
92
- if (isShutdownLifecycleState(this.lifecycleState)) {
93
- throw error;
94
- }
95
- this.lifecycleState = 'failed';
96
- throw createLifecycleError('Slack transport failed to initialize.', error);
134
+ await this.handleTransportInitializationFailure(error);
97
135
  }
98
136
  }
99
137
 
@@ -119,7 +157,8 @@ class SlackService {
119
157
  * @param message Caller-supplied Slack message with text and/or block content.
120
158
  * @param options Optional abort signal propagated to the transport.
121
159
  * @returns A normalized delivery receipt describing the transport response.
122
- * @throws {SlackMessageValidationError} When the resolved message does not include Slack-visible content.
160
+ * @throws {SlackMessageValidationError} When the resolved message has no Slack-visible `text`, `blocks`, or `attachments`.
161
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
123
162
  *
124
163
  * @example
125
164
  * ```ts
@@ -137,7 +176,10 @@ class SlackService {
137
176
  assertMessageContent(normalized);
138
177
  assertNotAborted(options.signal);
139
178
  this.assertCanDeliver();
140
- const result = await transport.send(normalized, options);
179
+ const result = await this.trackInFlightDelivery(() => {
180
+ assertNotAborted(options.signal);
181
+ return transport.send(normalized, options);
182
+ });
141
183
  return {
142
184
  channel: result.channel ?? normalized.channel,
143
185
  messageTs: result.messageTs,
@@ -155,6 +197,7 @@ class SlackService {
155
197
  * @param messages Ordered message list to deliver through the configured transport.
156
198
  * @param options Optional tolerant batch controls such as `continueOnError`.
157
199
  * @returns A batch summary containing successes and any captured failures.
200
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
158
201
  *
159
202
  * @example
160
203
  * ```ts
@@ -163,6 +206,8 @@ class SlackService {
163
206
  * ```
164
207
  */
165
208
  async sendMany(messages, options = {}) {
209
+ assertNotAborted(options.signal);
210
+ this.assertCanDeliver();
166
211
  const results = [];
167
212
  const failures = [];
168
213
  for (const message of messages) {
@@ -173,7 +218,7 @@ class SlackService {
173
218
  error: error instanceof Error ? error : new Error('Slack delivery failed.'),
174
219
  message
175
220
  };
176
- if (!(options.continueOnError ?? false)) {
221
+ if (options.signal?.aborted || isAbortError(failure.error) || !(options.continueOnError ?? false)) {
177
222
  throw failure.error;
178
223
  }
179
224
  failures.push(failure);
@@ -193,7 +238,8 @@ class SlackService {
193
238
  * @param notification Shared notification envelope interpreted by the Slack package.
194
239
  * @param options Optional abort signal propagated to rendering and transport work.
195
240
  * @returns A normalized delivery receipt for the resulting Slack message.
196
- * @throws {SlackMessageValidationError} When the notification cannot resolve one target channel or any Slack-visible content.
241
+ * @throws {SlackMessageValidationError} When the notification resolves multiple Slack recipients or no Slack-visible content.
242
+ * @throws {SlackLifecycleError} When delivery is requested before readiness, after initialization failure, or during shutdown.
197
243
  *
198
244
  * @example
199
245
  * ```ts
@@ -228,7 +274,7 @@ class SlackService {
228
274
  },
229
275
  mrkdwn: payload.mrkdwn,
230
276
  replyBroadcast: payload.replyBroadcast,
231
- text: payload.text ?? rendered?.text ?? notification.subject,
277
+ text: normalizeOptionalString(payload.text) ?? normalizeOptionalString(rendered?.text) ?? normalizeOptionalString(notification.subject),
232
278
  threadTs: payload.threadTs,
233
279
  unfurlLinks: payload.unfurlLinks,
234
280
  unfurlMedia: payload.unfurlMedia,
@@ -248,13 +294,49 @@ class SlackService {
248
294
  }
249
295
  return this.transportPromise;
250
296
  }
297
+ clearResolvedTransport() {
298
+ this.resolvedTransport = undefined;
299
+ this.transportPromise = undefined;
300
+ }
301
+ async handleTransportInitializationFailure(error) {
302
+ const interruptedByShutdown = isShutdownLifecycleState(this.lifecycleState);
303
+ if (!interruptedByShutdown) {
304
+ this.lifecycleState = 'failed';
305
+ }
306
+ let cause = error;
307
+ try {
308
+ await this.closeOwnedTransportResources();
309
+ } catch (cleanupError) {
310
+ cause = createCleanupFailureCause(error, cleanupError);
311
+ }
312
+ if (!interruptedByShutdown && !isShutdownLifecycleState(this.lifecycleState)) {
313
+ this.lifecycleState = 'failed';
314
+ }
315
+ throw createLifecycleError('Slack transport failed to initialize.', cause);
316
+ }
317
+ async drainInFlightDeliveries() {
318
+ while (this.inFlightDeliveries.size > 0) {
319
+ await Promise.allSettled(Array.from(this.inFlightDeliveries));
320
+ }
321
+ }
322
+ async trackInFlightDelivery(start) {
323
+ const delivery = Promise.resolve().then(start);
324
+ this.inFlightDeliveries.add(delivery);
325
+ try {
326
+ return await delivery;
327
+ } finally {
328
+ this.inFlightDeliveries.delete(delivery);
329
+ }
330
+ }
251
331
  assertCanCreateOrUseTransport() {
252
332
  if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped' || this.lifecycleState === 'failed') {
253
333
  throw createDeliveryLifecycleError(this.lifecycleState);
254
334
  }
255
335
  }
256
336
  assertCanDeliver() {
257
- this.assertCanCreateOrUseTransport();
337
+ if (this.lifecycleState !== 'ready') {
338
+ throw createDeliveryLifecycleError(this.lifecycleState);
339
+ }
258
340
  }
259
341
  normalizeMessage(message) {
260
342
  return {
@@ -274,14 +356,14 @@ class SlackService {
274
356
  };
275
357
  }
276
358
  resolveNotificationChannel(notification) {
277
- const payloadChannel = normalizeOptionalString(notification.payload.channel);
278
- if (payloadChannel) {
279
- return payloadChannel;
280
- }
281
359
  const recipients = notification.recipients?.map(entry => entry.trim()).filter(entry => entry.length > 0) ?? [];
282
360
  if (recipients.length > 1) {
283
361
  throw new SlackMessageValidationError('Slack notifications accept exactly one target channel per dispatch. Use `sendMany(...)` for fan-out delivery.');
284
362
  }
363
+ const payloadChannel = normalizeOptionalString(notification.payload.channel);
364
+ if (payloadChannel) {
365
+ return payloadChannel;
366
+ }
285
367
  return recipients[0] ?? this.options.defaultChannel;
286
368
  }
287
369
  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":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEzE,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3D,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACzC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;IACxC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAEtG;;;;OAIG;IACH,MAAM,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IAE9B;;;;OAIG;IACH,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,MAAM,IAAI,YAAY,CAAC,cAAc,CAAC,CAAC;IAEvC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gGAAgG;AAChG,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,YAAY,CAAC,kBAAkB,CAAC,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,wBAAwB,CAAC,QAAQ,SAAS,wBAAwB,GAAG,wBAAwB;IAC5G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qEAAqE;AACrE,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACzC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,SAAS,wBAAwB,GAAG,wBAAwB,EACzE,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GACxC,YAAY,CAAC,yBAAyB,CAAC,CAAC;CAC5C;AAED,yGAAyG;AACzG,MAAM,WAAW,wBAAyB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,WAAW,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACzC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,gCAAiC,SAAQ,2BAA2B,CAAC,wBAAwB,CAAC;IAC7G,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2FAA2F;AAC3F,MAAM,WAAW,eAAgB,SAAQ,qBAAqB;IAC5D,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,+DAA+D;AAC/D,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,EAAE,cAAc,GAAG,qBAAqB,CAAC;IAClD;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,CAAC,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAElI,0EAA0E;AAC1E,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;QACtC,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,4EAA4E;AAC5E,MAAM,WAAW,KAAK;IACpB;;;;;;;;OAQG;IACH,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAElF;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAE3G;;;;;;;;OAQG;IACH,gBAAgB,CACd,YAAY,EAAE,gCAAgC,EAC9C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B"}
@@ -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,CA6DjG"}
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/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "portable",
10
10
  "fetch"
11
11
  ],
12
- "version": "1.1.2",
12
+ "version": "1.1.4",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.3",
40
- "@fluojs/di": "^1.0.3",
41
- "@fluojs/notifications": "^1.0.1",
42
- "@fluojs/runtime": "^1.1.2"
39
+ "@fluojs/core": "^1.1.0",
40
+ "@fluojs/di": "^2.0.0",
41
+ "@fluojs/notifications": "^1.0.3",
42
+ "@fluojs/runtime": "^2.0.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^3.2.4"