@fluojs/slack 1.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,246 @@
1
+ # @fluojs/slack
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ fluo를 위한 webhook-first, transport-agnostic Slack 전달 코어 패키지입니다. Nest-like 모듈 API, standalone 사용을 위한 주입 가능한 `SlackService`, 그리고 Node 전용 SDK를 가정하지 않는 `@fluojs/notifications` 연동용 1st-party `SlackChannel`을 제공합니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [사용 시점](#사용-시점)
11
+ - [빠른 시작](#빠른-시작)
12
+ - [일반적인 패턴](#일반적인-패턴)
13
+ - [`createSlackProviders`를 이용한 수동 provider 조합](#createslackproviders를-이용한-수동-provider-조합)
14
+ - [`SlackService`를 이용한 standalone 전달](#slackservice를-이용한-standalone-전달)
15
+ - [`@fluojs/notifications`와의 통합](#fluojs-notifications와의-통합)
16
+ - [명시적 fetch 주입을 사용하는 webhook-first 전달](#명시적-fetch-주입을-사용하는-webhook-first-전달)
17
+ - [의도적인 제한 사항](#의도적인-제한-사항)
18
+ - [공개 API 개요](#공개-api-개요)
19
+ - [관련 패키지](#관련-패키지)
20
+ - [예제 소스](#예제-소스)
21
+
22
+ ## 설치
23
+
24
+ ```bash
25
+ npm install @fluojs/slack @fluojs/notifications
26
+ ```
27
+
28
+ 이 패키지는 published package metadata에 반영된 저장소 전반의 Node.js 20+ 설치 baseline을 따르지만, 런타임 전달 계약 자체는 명시적인 fetch-compatible 경계를 통해 계속 transport-agnostic하게 유지됩니다.
29
+
30
+ ## 사용 시점
31
+
32
+ - Slack 메시지를 직접 보내는 기능과 `@fluojs/notifications` 채널 연동을 한 패키지에서 처리하고 싶을 때.
33
+ - transport 선택을 Node, Bun, Deno, Cloudflare 호환 애플리케이션 경계 전반에서 명시적이고 이식 가능하게 유지해야 할 때.
34
+ - incoming webhook을 기본 경로로 선호하되, 더 풍부한 API 연동은 커스텀 transport 계약으로 열어 두고 싶을 때.
35
+ - 설정을 패키지 내부 `process.env` 접근이 아니라 DI 또는 명시적인 옵션으로 주입하고 싶을 때.
36
+
37
+ ## 빠른 시작
38
+
39
+ ### 모듈 등록
40
+
41
+ ```typescript
42
+ import { Module } from '@fluojs/core';
43
+ import { SlackModule, createSlackWebhookTransport } from '@fluojs/slack';
44
+
45
+ @Module({
46
+ imports: [
47
+ SlackModule.forRoot({
48
+ defaultChannel: '#ops',
49
+ transport: createSlackWebhookTransport({
50
+ fetch: globalThis.fetch.bind(globalThis),
51
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
52
+ }),
53
+ }),
54
+ ],
55
+ })
56
+ export class AppModule {}
57
+ ```
58
+
59
+ ### 직접 Slack 메시지 보내기
60
+
61
+ ```typescript
62
+ import { Inject } from '@fluojs/core';
63
+ import { SlackService } from '@fluojs/slack';
64
+
65
+ export class DeployNotifier {
66
+ constructor(@Inject(SlackService) private readonly slack: SlackService) {}
67
+
68
+ async announce(version: string) {
69
+ await this.slack.send({
70
+ text: `Deploy ${version} finished successfully.`,
71
+ });
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## 일반적인 패턴
77
+
78
+ ### `createSlackProviders`를 이용한 수동 provider 조합
79
+
80
+ `createSlackProviders(...)`는 애플리케이션이 `SlackModule.forRoot(...)` 밖에서 동일한 provider 정규화 구성을 재사용해야 할 때 지원되는 manual-composition helper입니다.
81
+
82
+ ```typescript
83
+ import { Module } from '@fluojs/core';
84
+ import { createSlackProviders, createSlackWebhookTransport } from '@fluojs/slack';
85
+
86
+ @Module({
87
+ providers: [
88
+ ...createSlackProviders({
89
+ defaultChannel: '#ops',
90
+ notifications: { channel: 'alerts' },
91
+ transport: createSlackWebhookTransport({
92
+ fetch: globalThis.fetch.bind(globalThis),
93
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
94
+ }),
95
+ }),
96
+ ],
97
+ exports: [],
98
+ })
99
+ export class SlackProvidersModule {}
100
+ ```
101
+
102
+ Behavioral contract 메모:
103
+
104
+ - 이 helper는 `SlackModule.forRoot(...)`가 구성하는 `SLACK`, `SLACK_CHANNEL`, `SlackService` wiring을 동일하게 유지합니다.
105
+ - `createSlackProviders(...)`는 trim된 기본 채널, notification 채널 fallback, transport 소유권 기본값을 포함해 `SlackModule.forRoot(...)`와 동일한 옵션 정규화를 적용합니다.
106
+ - 이 helper도 여전히 명시적인 `transport`를 요구하며, 패키지의 runtime-portable·no-implicit-env 계약을 약화시키지 않습니다.
107
+
108
+ ### `SlackService`를 이용한 standalone 전달
109
+
110
+ notifications foundation을 거치지 않고 직접 Slack 전달을 하고 싶다면 `SlackService`를 사용합니다.
111
+
112
+ ```typescript
113
+ SlackModule.forRootAsync({
114
+ inject: [ConfigService],
115
+ useFactory: (config) => ({
116
+ defaultChannel: config.slack.defaultChannel,
117
+ transport: createSlackWebhookTransport({
118
+ fetch: config.runtime.fetch,
119
+ webhookUrl: config.slack.webhookUrl,
120
+ }),
121
+ }),
122
+ });
123
+ ```
124
+
125
+ Behavioral contract 메모:
126
+
127
+ - `SlackService.send(...)`는 전달 전에 `defaultChannel`을 해석합니다.
128
+ - 서비스는 모듈 bootstrap 시 transport를 초기화하고, factory가 소유한 리소스만 애플리케이션 shutdown 시 닫습니다.
129
+ - 이 패키지는 절대로 `process.env`를 직접 읽지 않습니다. 모든 설정은 명시적인 옵션 또는 DI를 통해 들어와야 합니다.
130
+
131
+ ### `@fluojs/notifications`와의 통합
132
+
133
+ `SLACK_CHANNEL`을 `NotificationsModule.forRootAsync(...)`에 주입하여, Slack 전용 payload 필드와 recipient-to-channel 해석 규칙이 모두 `@fluojs/slack` 안에만 남도록 구성합니다.
134
+
135
+ ```typescript
136
+ import { Module } from '@fluojs/core';
137
+ import { NotificationsModule } from '@fluojs/notifications';
138
+ import {
139
+ SLACK_CHANNEL,
140
+ SlackModule,
141
+ createSlackWebhookTransport,
142
+ } from '@fluojs/slack';
143
+
144
+ @Module({
145
+ imports: [
146
+ SlackModule.forRoot({
147
+ transport: createSlackWebhookTransport({
148
+ fetch: globalThis.fetch.bind(globalThis),
149
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
150
+ }),
151
+ }),
152
+ NotificationsModule.forRootAsync({
153
+ inject: [SLACK_CHANNEL],
154
+ useFactory: (channel) => ({
155
+ channels: [channel],
156
+ }),
157
+ }),
158
+ ],
159
+ })
160
+ export class AppModule {}
161
+ ```
162
+
163
+ 지원하는 notification payload 필드:
164
+
165
+ - `text`, `blocks`, `attachments`
166
+ - `channel`, `threadTs`, `replyBroadcast`
167
+ - `username`, `iconEmoji`, `iconUrl`
168
+ - `mrkdwn`, `unfurlLinks`, `unfurlMedia`, `metadata`
169
+
170
+ Behavioral contract 메모:
171
+
172
+ - 하나의 notification dispatch는 정확히 하나의 Slack 대상지로 매핑됩니다. `payload.channel` 또는 `recipients`의 단일 항목을 사용해야 합니다.
173
+ - `payload.channel`이 없으면 `SlackService.sendNotification(...)`는 첫 번째 `recipients` 항목을 사용하고, 그것도 없으면 `defaultChannel`로 폴백합니다.
174
+ - 여러 Slack 대상지로 fan-out이 필요하다면 하나의 multi-recipient dispatch 대신 `sendMany(...)`를 사용해야 합니다.
175
+
176
+ ### 명시적 fetch 주입을 사용하는 webhook-first 전달
177
+
178
+ 런타임에 독립적인 1st-party transport가 필요하다면 fetch-compatible HTTP 경계만 의존하는 `createSlackWebhookTransport(...)`를 사용합니다.
179
+
180
+ ```typescript
181
+ const transport = createSlackWebhookTransport({
182
+ fetch: runtime.fetch,
183
+ webhookUrl: slackWebhookUrl,
184
+ });
185
+
186
+ await slack.send({
187
+ blocks: [{ type: 'section', text: { type: 'mrkdwn', text: '*Deploy finished*' } }],
188
+ text: 'Deploy finished',
189
+ });
190
+ ```
191
+
192
+ `chat.postMessage` 같은 더 풍부한 API 연동이 필요하다면 export된 `SlackTransport` 계약을 구현해 `SlackModule.forRoot(...)` 또는 `forRootAsync(...)`에 주입하면 됩니다.
193
+
194
+ Behavioral contract 메모:
195
+
196
+ - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 실패를 호출자에게 에러를 노출하기 전에 bounded exponential backoff로 재시도합니다.
197
+ - 호출자에게 보이는 `SlackTransportError` 메시지는 기본적으로 raw upstream response body를 포함하지 않습니다.
198
+
199
+ ### 의도적인 제한 사항
200
+
201
+ Slack 패키지는 의도적으로 다음을 **포함하지 않습니다**:
202
+
203
+ - 자격 증명이나 webhook URL을 `process.env`에서 직접 읽는 동작
204
+ - 공유 루트 패키지 경계에 Node 전용 Slack SDK를 내장하는 것
205
+ - webhook helper와 export된 transport 계약 이상으로 하나의 provider 전략을 강제하는 것
206
+ - 하나의 dispatch 호출 안에서 multi-channel fan-out을 자동 변환하는 것
207
+
208
+ 이 제한 사항은 런타임 선택, provider capability, rollout 전략이 애플리케이션 경계에서 명시적으로 결정되도록 하기 위한 package contract의 일부입니다.
209
+
210
+ ## 공개 API 개요
211
+
212
+ ### 핵심
213
+
214
+ - `SlackModule.forRoot(options)` / `SlackModule.forRootAsync(options)`
215
+ - `createSlackProviders(options)`
216
+ - `SlackService`
217
+ - `SlackChannel`
218
+ - `SLACK`
219
+ - `SLACK_CHANNEL`
220
+
221
+ ### 계약과 헬퍼
222
+
223
+ - `SlackMessage`
224
+ - `SlackTransport`
225
+ - `SlackTransportFactory`
226
+ - `SlackTemplateRenderer`
227
+ - `createSlackWebhookTransport(options)`
228
+
229
+ ### 상태 및 에러
230
+
231
+ - `createSlackPlatformStatusSnapshot(...)`
232
+ - `SlackConfigurationError`
233
+ - `SlackMessageValidationError`
234
+ - `SlackTransportError`
235
+
236
+ ## 관련 패키지
237
+
238
+ - `@fluojs/notifications`: `SLACK_CHANNEL`을 소비하는 공통 오케스트레이션 계층입니다.
239
+ - `@fluojs/config`: 환경 직접 접근 없이 webhook URL이나 토큰을 해석하려는 경우 권장됩니다.
240
+ - `@fluojs/event-bus`: Slack 알림이 여러 이벤트 기반 부작용 중 하나일 때 유용합니다.
241
+
242
+ ## 예제 소스
243
+
244
+ - `packages/slack/src/module.test.ts`: 모듈 등록, `createSlackProviders(...)` helper coverage, async wiring, webhook transport, notifications integration 예제.
245
+ - `packages/slack/src/public-surface.test.ts`: 공개 export와 TypeScript 계약 검증 예제.
246
+ - `packages/slack/src/status.test.ts`: health/readiness 계약 예제.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # @fluojs/slack
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ Webhook-first, transport-agnostic Slack delivery core for fluo. It provides a Nest-like module API, an injectable `SlackService` for standalone usage, and a first-party `SlackChannel` for `@fluojs/notifications` integration without assuming a Node-only SDK.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [When to Use](#when-to-use)
11
+ - [Quick Start](#quick-start)
12
+ - [Common Patterns](#common-patterns)
13
+ - [Manual provider composition with `createSlackProviders`](#manual-provider-composition-with-createslackproviders)
14
+ - [Standalone delivery with `SlackService`](#standalone-delivery-with-slackservice)
15
+ - [Integration with `@fluojs/notifications`](#integration-with-fluojs-notifications)
16
+ - [Webhook-first delivery with explicit fetch injection](#webhook-first-delivery-with-explicit-fetch-injection)
17
+ - [Intentional limitations](#intentional-limitations)
18
+ - [Public API Overview](#public-api-overview)
19
+ - [Related Packages](#related-packages)
20
+ - [Example Sources](#example-sources)
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @fluojs/slack @fluojs/notifications
26
+ ```
27
+
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.
29
+
30
+ ## When to Use
31
+
32
+ - When you want one package that can send Slack messages directly and also plug into `@fluojs/notifications`.
33
+ - When transport choice must stay explicit and portable across Node, Bun, Deno, and Cloudflare-compatible application boundaries.
34
+ - When Slack delivery should prefer incoming webhooks while still allowing richer API integrations through a custom transport contract.
35
+ - When configuration must enter through DI or explicit options instead of `process.env` reads inside the package.
36
+
37
+ ## Quick Start
38
+
39
+ ### Register the module
40
+
41
+ ```typescript
42
+ import { Module } from '@fluojs/core';
43
+ import { SlackModule, createSlackWebhookTransport } from '@fluojs/slack';
44
+
45
+ @Module({
46
+ imports: [
47
+ SlackModule.forRoot({
48
+ defaultChannel: '#ops',
49
+ transport: createSlackWebhookTransport({
50
+ fetch: globalThis.fetch.bind(globalThis),
51
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
52
+ }),
53
+ }),
54
+ ],
55
+ })
56
+ export class AppModule {}
57
+ ```
58
+
59
+ ### Send Slack messages directly
60
+
61
+ ```typescript
62
+ import { Inject } from '@fluojs/core';
63
+ import { SlackService } from '@fluojs/slack';
64
+
65
+ export class DeployNotifier {
66
+ constructor(@Inject(SlackService) private readonly slack: SlackService) {}
67
+
68
+ async announce(version: string) {
69
+ await this.slack.send({
70
+ text: `Deploy ${version} finished successfully.`,
71
+ });
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Common Patterns
77
+
78
+ ### Manual provider composition with `createSlackProviders`
79
+
80
+ `createSlackProviders(...)` is the supported manual-composition helper when applications need the same provider normalization outside `SlackModule.forRoot(...)`.
81
+
82
+ ```typescript
83
+ import { Module } from '@fluojs/core';
84
+ import { createSlackProviders, createSlackWebhookTransport } from '@fluojs/slack';
85
+
86
+ @Module({
87
+ providers: [
88
+ ...createSlackProviders({
89
+ defaultChannel: '#ops',
90
+ notifications: { channel: 'alerts' },
91
+ transport: createSlackWebhookTransport({
92
+ fetch: globalThis.fetch.bind(globalThis),
93
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
94
+ }),
95
+ }),
96
+ ],
97
+ exports: [],
98
+ })
99
+ export class SlackProvidersModule {}
100
+ ```
101
+
102
+ Behavioral contract notes:
103
+
104
+ - The helper preserves the same `SLACK`, `SLACK_CHANNEL`, and `SlackService` wiring that `SlackModule.forRoot(...)` installs.
105
+ - `createSlackProviders(...)` applies the same option normalization as `SlackModule.forRoot(...)`, including trimmed default channels, notification channel fallback, and transport ownership defaults.
106
+ - The helper still requires an explicit `transport`; it does not weaken the package's runtime-portable, no-implicit-env contract.
107
+
108
+ ### Standalone delivery with `SlackService`
109
+
110
+ Use `SlackService` when your application wants direct Slack delivery without routing through the notifications foundation.
111
+
112
+ ```typescript
113
+ SlackModule.forRootAsync({
114
+ inject: [ConfigService],
115
+ useFactory: (config) => ({
116
+ defaultChannel: config.slack.defaultChannel,
117
+ transport: createSlackWebhookTransport({
118
+ fetch: config.runtime.fetch,
119
+ webhookUrl: config.slack.webhookUrl,
120
+ }),
121
+ }),
122
+ });
123
+ ```
124
+
125
+ Behavioral contract notes:
126
+
127
+ - `SlackService.send(...)` resolves `defaultChannel` before delivery.
128
+ - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown.
129
+ - The package never reads `process.env` directly. All configuration must enter through explicit options or DI.
130
+
131
+ ### Integration with `@fluojs/notifications`
132
+
133
+ 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.
134
+
135
+ ```typescript
136
+ import { Module } from '@fluojs/core';
137
+ import { NotificationsModule } from '@fluojs/notifications';
138
+ import {
139
+ SLACK_CHANNEL,
140
+ SlackModule,
141
+ createSlackWebhookTransport,
142
+ } from '@fluojs/slack';
143
+
144
+ @Module({
145
+ imports: [
146
+ SlackModule.forRoot({
147
+ transport: createSlackWebhookTransport({
148
+ fetch: globalThis.fetch.bind(globalThis),
149
+ webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
150
+ }),
151
+ }),
152
+ NotificationsModule.forRootAsync({
153
+ inject: [SLACK_CHANNEL],
154
+ useFactory: (channel) => ({
155
+ channels: [channel],
156
+ }),
157
+ }),
158
+ ],
159
+ })
160
+ export class AppModule {}
161
+ ```
162
+
163
+ Supported notification payload fields:
164
+
165
+ - `text`, `blocks`, `attachments`
166
+ - `channel`, `threadTs`, `replyBroadcast`
167
+ - `username`, `iconEmoji`, `iconUrl`
168
+ - `mrkdwn`, `unfurlLinks`, `unfurlMedia`, `metadata`
169
+
170
+ Behavioral contract notes:
171
+
172
+ - One notification dispatch maps to exactly one Slack destination. Use `payload.channel` or a single entry in `recipients`.
173
+ - If `payload.channel` is omitted, `SlackService.sendNotification(...)` uses the first `recipients` entry or falls back to `defaultChannel`.
174
+ - If a notification needs fan-out across multiple Slack destinations, call `sendMany(...)` instead of one multi-recipient dispatch.
175
+
176
+ ### Webhook-first delivery with explicit fetch injection
177
+
178
+ Use `createSlackWebhookTransport(...)` when you want a portable first-party transport that only depends on a fetch-compatible HTTP boundary.
179
+
180
+ ```typescript
181
+ const transport = createSlackWebhookTransport({
182
+ fetch: runtime.fetch,
183
+ webhookUrl: slackWebhookUrl,
184
+ });
185
+
186
+ await slack.send({
187
+ blocks: [{ type: 'section', text: { type: 'mrkdwn', text: '*Deploy finished*' } }],
188
+ text: 'Deploy finished',
189
+ });
190
+ ```
191
+
192
+ For richer API integrations such as `chat.postMessage`, implement the exported `SlackTransport` contract and inject it through `SlackModule.forRoot(...)` or `forRootAsync(...)`.
193
+
194
+ Behavioral contract notes:
195
+
196
+ - The built-in webhook transport retries transient `408`, `429`, and `5xx` failures with bounded exponential backoff before surfacing an error.
197
+ - Caller-visible `SlackTransportError` messages omit raw upstream response bodies by default.
198
+
199
+ ### Intentional limitations
200
+
201
+ The Slack package intentionally does **not**:
202
+
203
+ - read credentials or webhook URLs from `process.env`
204
+ - ship a Node-only Slack SDK inside the shared root package boundary
205
+ - force one provider strategy beyond the webhook-first helper and exported transport contract
206
+ - translate one notification into multi-channel fan-out inside a single dispatch call
207
+
208
+ These limitations are part of the package contract so runtime choice, provider capability, and rollout strategy stay explicit at the application boundary.
209
+
210
+ ## Public API Overview
211
+
212
+ ### Core
213
+
214
+ - `SlackModule.forRoot(options)` / `SlackModule.forRootAsync(options)`
215
+ - `createSlackProviders(options)`
216
+ - `SlackService`
217
+ - `SlackChannel`
218
+ - `SLACK`
219
+ - `SLACK_CHANNEL`
220
+
221
+ ### Contracts and helpers
222
+
223
+ - `SlackMessage`
224
+ - `SlackTransport`
225
+ - `SlackTransportFactory`
226
+ - `SlackTemplateRenderer`
227
+ - `createSlackWebhookTransport(options)`
228
+
229
+ ### Status and errors
230
+
231
+ - `createSlackPlatformStatusSnapshot(...)`
232
+ - `SlackConfigurationError`
233
+ - `SlackMessageValidationError`
234
+ - `SlackTransportError`
235
+
236
+ ## Related Packages
237
+
238
+ - `@fluojs/notifications`: Shared orchestration layer that consumes `SLACK_CHANNEL`.
239
+ - `@fluojs/config`: Recommended for resolving webhook URLs or tokens without direct environment access.
240
+ - `@fluojs/event-bus`: Useful when Slack notifications are one side effect among several event-driven workflows.
241
+
242
+ ## Example Sources
243
+
244
+ - `packages/slack/src/module.test.ts`: Module registration, `createSlackProviders(...)` helper coverage, async wiring, webhook transport, and notifications integration examples.
245
+ - `packages/slack/src/public-surface.test.ts`: Public export and TypeScript contract verification.
246
+ - `packages/slack/src/status.test.ts`: Health/readiness contract examples.
@@ -0,0 +1,24 @@
1
+ import type { NotificationChannel, NotificationChannelContext, NotificationChannelDelivery } from '@fluojs/notifications';
2
+ import { SlackService } from './service.js';
3
+ import type { NormalizedSlackModuleOptions, SlackNotificationDispatchRequest, SlackSendResult } from './types.js';
4
+ /**
5
+ * Notification channel implementation that bridges `@fluojs/notifications` to {@link SlackService}.
6
+ *
7
+ * @remarks
8
+ * This class keeps the foundation package channel-agnostic while allowing `@fluojs/slack`
9
+ * to interpret Slack-specific payload fields, webhook delivery, and transport behavior.
10
+ */
11
+ export declare class SlackChannel implements NotificationChannel<SlackNotificationDispatchRequest, SlackSendResult> {
12
+ private readonly slack;
13
+ readonly channel: string;
14
+ constructor(slack: SlackService, options: NormalizedSlackModuleOptions);
15
+ /**
16
+ * Sends one notifications foundation request through the configured Slack transport.
17
+ *
18
+ * @param notification Shared notification envelope understood by the Slack package.
19
+ * @param context Optional abort context propagated from the notifications service.
20
+ * @returns A normalized channel delivery result with the provider message timestamp exposed as `externalId` when available.
21
+ */
22
+ send(notification: SlackNotificationDispatchRequest, context: NotificationChannelContext): Promise<NotificationChannelDelivery<SlackSendResult>>;
23
+ }
24
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAG1H,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,4BAA4B,EAAE,gCAAgC,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElH;;;;;;GAMG;AACH,qBACa,YAAa,YAAW,mBAAmB,CAAC,gCAAgC,EAAE,eAAe,CAAC;IAIvG,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGN,KAAK,EAAE,YAAY,EACpC,OAAO,EAAE,4BAA4B;IAKvC;;;;;;OAMG;IACG,IAAI,CACR,YAAY,EAAE,gCAAgC,EAC9C,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,2BAA2B,CAAC,eAAe,CAAC,CAAC;CAmBzD"}
@@ -0,0 +1,59 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { Inject } from '@fluojs/core';
8
+ import { SlackTransportError } from './errors.js';
9
+ import { SlackService } from './service.js';
10
+ import { SLACK_OPTIONS } from './tokens.js';
11
+ let _SlackChannel;
12
+ /**
13
+ * Notification channel implementation that bridges `@fluojs/notifications` to {@link SlackService}.
14
+ *
15
+ * @remarks
16
+ * This class keeps the foundation package channel-agnostic while allowing `@fluojs/slack`
17
+ * to interpret Slack-specific payload fields, webhook delivery, and transport behavior.
18
+ */
19
+ class SlackChannel {
20
+ static {
21
+ [_SlackChannel, _initClass] = _applyDecs(this, [Inject(SlackService, SLACK_OPTIONS)], []).c;
22
+ }
23
+ channel;
24
+ constructor(slack, options) {
25
+ this.slack = slack;
26
+ this.channel = options.notifications.channel;
27
+ }
28
+
29
+ /**
30
+ * Sends one notifications foundation request through the configured Slack transport.
31
+ *
32
+ * @param notification Shared notification envelope understood by the Slack package.
33
+ * @param context Optional abort context propagated from the notifications service.
34
+ * @returns A normalized channel delivery result with the provider message timestamp exposed as `externalId` when available.
35
+ */
36
+ async send(notification, context) {
37
+ const receipt = await this.slack.sendNotification(notification, {
38
+ signal: context.signal
39
+ });
40
+ if (receipt.ok === false) {
41
+ throw new SlackTransportError('Slack transport reported an unsuccessful delivery.');
42
+ }
43
+ return {
44
+ externalId: receipt.messageTs,
45
+ metadata: {
46
+ channel: receipt.channel,
47
+ response: receipt.response,
48
+ statusCode: receipt.statusCode,
49
+ warnings: receipt.warnings
50
+ },
51
+ receipt,
52
+ status: 'delivered'
53
+ };
54
+ }
55
+ static {
56
+ _initClass();
57
+ }
58
+ }
59
+ export { _SlackChannel as SlackChannel };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Base error type for caller-visible Slack module configuration failures.
3
+ */
4
+ export declare class SlackConfigurationError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ /**
8
+ * Thrown when a Slack message or notification payload is missing one required contract field.
9
+ */
10
+ export declare class SlackMessageValidationError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ /**
14
+ * Thrown when one concrete Slack transport reports a caller-visible delivery failure.
15
+ */
16
+ export declare class SlackTransportError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +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;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Base error type for caller-visible Slack module configuration failures.
3
+ */
4
+ export class SlackConfigurationError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'SlackConfigurationError';
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Thrown when a Slack message or notification payload is missing one required contract field.
13
+ */
14
+ export class SlackMessageValidationError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'SlackMessageValidationError';
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Thrown when one concrete Slack transport reports a caller-visible delivery failure.
23
+ */
24
+ export class SlackTransportError extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = 'SlackTransportError';
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ export { SlackConfigurationError, SlackMessageValidationError, SlackTransportError, } from './errors.js';
2
+ export { SlackChannel } from './channel.js';
3
+ export { SlackModule, createSlackProviders } from './module.js';
4
+ export { SlackService } from './service.js';
5
+ export { createSlackPlatformStatusSnapshot } from './status.js';
6
+ export type { SlackLifecycleState, SlackPlatformStatusSnapshot, SlackStatusAdapterInput } from './status.js';
7
+ export { SLACK, SLACK_CHANNEL } from './tokens.js';
8
+ export type { NormalizedSlackMessage, Slack, SlackAsyncModuleOptions, SlackAttachment, SlackBlock, SlackFetchLike, SlackFetchResponse, SlackMessage, SlackModuleOptions, SlackNotificationDispatchRequest, SlackNotificationPayload, SlackSendBatchResult, SlackSendFailure, SlackSendManyOptions, SlackSendOptions, SlackSendResult, SlackTemplateRenderInput, SlackTemplateRenderer, SlackTemplateRenderResult, SlackTransport, SlackTransportContext, SlackTransportFactory, SlackTransportReceipt, SlackWebhookTransportOptions, } from './types.js';
9
+ export { createSlackWebhookTransport } from './webhook.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iCAAiC,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,sBAAsB,EACtB,KAAK,EACL,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,gCAAgC,EAChC,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC"}