@fluojs/discord 1.0.3 → 1.0.5

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
@@ -4,14 +4,19 @@
4
4
 
5
5
  fluo를 위한 webhook-first, transport-agnostic Discord 전달 코어 패키지입니다. Nest-like 모듈 API, standalone 사용을 위한 주입 가능한 `DiscordService`, 그리고 Node 전용 Discord SDK를 가정하지 않는 `@fluojs/notifications` 연동용 1st-party `DiscordChannel`을 제공합니다.
6
6
 
7
+ 마이그레이션 경계: 이 모듈 API는 의도적으로 Nest-like이지만 NestJS dynamic-module clone은 아닙니다. `DiscordModule`은 `global: options.global ?? true`로 기본 global이며, `forRootAsync(...)`는 `inject`와 `useFactory`만 지원하고, 내부 provider helper/token은 private으로 유지되어 애플리케이션은 module facade와 export된 service/channel token으로 Discord를 조합해야 합니다.
8
+
7
9
  ## 목차
8
10
 
9
11
  - [설치](#설치)
10
12
  - [사용 시점](#사용-시점)
11
13
  - [빠른 시작](#빠른-시작)
12
14
  - [일반적인 패턴](#일반적인-패턴)
15
+ - [모듈 visibility와 migration 경계](#모듈-visibility와-migration-경계)
13
16
  - [`DiscordService`를 이용한 standalone 전달](#discordservice를-이용한-standalone-전달)
17
+ - [`verifyOnModuleInit` bootstrap verification](#verifyonmoduleinit-bootstrap-verification)
14
18
  - [`@fluojs/notifications`와의 통합](#fluojs-notifications와의-통합)
19
+ - [payload override를 사용하는 template rendering](#payload-override를-사용하는-template-rendering)
15
20
  - [명시적 fetch 주입을 사용하는 webhook-first 전달](#명시적-fetch-주입을-사용하는-webhook-first-전달)
16
21
  - [의도적인 제한 사항](#의도적인-제한-사항)
17
22
  - [공개 API 개요](#공개-api-개요)
@@ -75,6 +80,12 @@ export class DeployNotifier {
75
80
 
76
81
  ## 일반적인 패턴
77
82
 
83
+ ### 모듈 visibility와 migration 경계
84
+
85
+ `DiscordModule.forRoot(...)`와 `DiscordModule.forRootAsync(...)`는 기본적으로 global module을 반환합니다. 이 모듈은 `DiscordService`, `DiscordChannel`, `DISCORD`, `DISCORD_CHANNEL`을 export합니다. 반환된 모듈을 명시적으로 import한 모듈에서만 이 provider들을 보이게 해야 하는 migrated code가 있을 때만 `global: false`를 전달하세요. 이 옵션은 NestJS `isGlobal`이 아니라 `global?: boolean`입니다.
86
+
87
+ 패키지 수준 registration surface는 의도적으로 singleton 중심입니다. `DISCORD`와 `DISCORD_CHANNEL`은 하나의 구성된 Discord service와 notifications channel을 위한 compatibility token입니다. 여러 Discord client가 필요한 애플리케이션은 private provider helper를 import하지 말고 서로 다른 `DiscordTransport` 인스턴스를 감싼 app-owned module/provider 또는 app-owned facade를 구성해야 합니다.
88
+
78
89
  ### `DiscordService`를 이용한 standalone 전달
79
90
 
80
91
  notifications foundation을 거치지 않고 직접 Discord 전달을 하고 싶다면 `DiscordService`를 사용합니다.
@@ -92,16 +103,39 @@ DiscordModule.forRootAsync({
92
103
  });
93
104
  ```
94
105
 
106
+ `forRootAsync(...)`는 fluo async 형태만 받습니다. 필요한 의존성은 애플리케이션 module graph에 먼저 등록하고, token을 `inject`에 나열한 뒤, `useFactory`에서 최종 `DiscordModuleOptions`를 반환하세요. NestJS `imports`, `useClass`, `useExisting` 변형은 소비하지 않으므로 그런 패턴은 Discord에 option을 넘기기 전에 application-owned provider로 옮겨야 합니다.
107
+
95
108
  Behavioral contract 메모:
96
109
 
110
+ - `DiscordModule.forRoot(...)`와 `DiscordModule.forRootAsync(...)`는 `DiscordService`, `DiscordChannel`, `DISCORD`, `DISCORD_CHANNEL`을 기본 global로 export합니다. fluo 옵션인 `global?: boolean`을 사용하고, migrated code가 Discord provider를 importing module 안에만 유지해야 할 때만 `global: false`를 설정하세요. NestJS `isGlobal`은 지원하지 않습니다.
97
111
  - `DiscordService.send(...)`는 전달 전에 `defaultThreadId`를 해석합니다.
98
112
  - `DiscordService.sendMany(...)`는 `DiscordMessage[]`를 직접 순차 전송하는 batch API이며 `continueOnError`를 지원합니다. 이는 multi-recipient `@fluojs/notifications` dispatch shortcut이 아닙니다.
99
- - 서비스는 모듈 bootstrap 시 transport를 초기화하고, factory가 소유한 리소스만 애플리케이션 shutdown 닫습니다.
113
+ - 서비스는 모듈 bootstrap 시 transport를 초기화하고, bootstrap verification 실패와 애플리케이션 shutdown 전반에서 factory-owned 리소스를 정확히 한 번 닫습니다. shutdown 전에 시작된 factory 생성 transport아직 완료되지 않았더라도 이를 기다리며, reject된 factory creation은 shutdown cleanup failure로 재분류되지 않고 initialization failure로 유지됩니다.
100
114
  - send는 bootstrap이 transport를 `ready`로 표시한 뒤에만 허용됩니다. bootstrap 전, startup 중, bootstrap 실패 후, shutdown 중, shutdown 후 시도는 전달 전에 거부됩니다.
101
115
  - 서비스가 shutdown 중이거나 이미 stopped 상태라면 cached transport를 재사용하지 않고 send를 거부합니다.
116
+ - `DiscordService.sendNotification(...)`은 구성된 renderer를 호출하기 전에 lifecycle readiness를 확인하고, 호출자의 `AbortSignal`을 `DiscordTemplateRenderInput.signal`과 transport delivery 양쪽에 전달합니다.
117
+ - `DiscordService.createPlatformStatusSnapshot()`은 `createDiscordPlatformStatusSnapshot(...)`과 같은 status 계약을 노출합니다. 여기에는 lifecycle/readiness, health, transport kind와 ownership, 기본 thread 구성, bootstrap verification 상태, bootstrap initialization 실패와 shutdown cleanup 실패를 구분하는 diagnostics, notifications channel dependency details가 포함되어, 호출자가 내부 옵션에 접근하지 않고도 Discord wiring을 관찰할 수 있습니다.
102
118
  - 빈 `defaultThreadId`와 `notifications.channel` 값은 trim 후 무시됩니다. notifications channel은 기본적으로 `discord`입니다.
103
119
  - 이 패키지는 절대로 `process.env`를 직접 읽지 않습니다. 모든 설정은 명시적인 옵션 또는 DI를 통해 들어와야 합니다.
104
120
 
121
+ ### `verifyOnModuleInit` bootstrap verification
122
+
123
+ 선택한 transport가 애플리케이션 bootstrap 중 자체 readiness를 검증할 수 있다면 `DiscordModuleOptions.verifyOnModuleInit?: boolean`을 `true`로 설정하세요. `DiscordService.onModuleInit()`은 항상 구성된 transport를 먼저 해석합니다. `verifyOnModuleInit`이 켜져 있고 해석된 transport가 optional `verify()` 메서드를 노출하면, 서비스는 Discord provider를 ready로 표시하기 전에 `transport.verify()`를 await합니다. `verify`를 구현하지 않은 transport도 유효하며 verification 단계를 건너뜁니다.
124
+
125
+ ```typescript
126
+ DiscordModule.forRoot({
127
+ transport: customDiscordTransport,
128
+ verifyOnModuleInit: true,
129
+ });
130
+ ```
131
+
132
+ Behavioral contract 메모:
133
+
134
+ - `verifyOnModuleInit`은 optional이며 기본값은 `false`입니다.
135
+ - Verification은 capability 기반입니다. `verify()`를 노출한 transport만 호출하므로 webhook-only 또는 app-owned transport가 no-op verifier를 추가할 필요는 없습니다.
136
+ - `transport.verify()`가 reject하면 bootstrap은 initialization failure로 실패하고, service lifecycle은 `failed`로 이동하며, readiness/status snapshot은 provider를 not ready로 보고합니다. Factory-owned transport는 정확히 한 번 닫고, 직접 전달된 app-owned transport는 보존합니다.
137
+ - `DiscordService.createPlatformStatusSnapshot()`은 `verifiedOnModuleInit`과 bootstrap verification 상태를 포함하므로 health/readiness tooling이 내부 옵션에 접근하지 않고도 bootstrap verification 요청 여부를 확인할 수 있습니다.
138
+
105
139
  ### `@fluojs/notifications`와의 통합
106
140
 
107
141
  `DISCORD_CHANNEL`을 `NotificationsModule.forRootAsync(...)`에 주입하여, Discord 전용 payload 필드와 recipient-to-thread 해석 규칙이 모두 `@fluojs/discord` 안에만 남도록 구성합니다.
@@ -144,9 +178,54 @@ Behavioral contract 메모:
144
178
 
145
179
  - 하나의 notification dispatch는 정확히 하나의 Discord thread 경로로 매핑됩니다. `payload.threadId` 또는 `recipients`의 단일 항목을 사용해야 합니다.
146
180
  - `payload.threadId`가 없으면 `DiscordService.sendNotification(...)`는 첫 번째 `recipients` 항목을 사용하고, 그것도 없으면 `defaultThreadId`로 폴백합니다.
147
- - notification metadata는 payload metadata, dispatch metadata, template/subject marker를 합쳐 구성됩니다. `template`은 renderer가 구성된 경우에만 렌더링됩니다.
181
+ - notification metadata는 payload metadata, dispatch metadata, template/subject marker를 합쳐 구성됩니다. 중복 key에서는 dispatch metadata가 payload metadata를 덮어쓰고, 최종 `subject` / `template` marker가 둘 모두를 덮어씁니다. `template`은 renderer가 구성된 경우에만 렌더링됩니다.
182
+ - Template rendering은 서비스가 ready일 때만 시작합니다. Render input에는 `signal`이 포함되므로 renderer는 transport delivery 전에 caller-cancelled 작업을 중단할 수 있습니다.
148
183
  - 여러 Discord thread로 fan-out이 필요한 notification workflow라면 thread별 concrete Discord message를 만들어 `DiscordService.sendMany(...)`로 보내거나 별도 notification dispatch를 실행해야 합니다. 하나의 notification dispatch는 multi-recipient fan-out을 암묵적으로 확장하지 않습니다.
149
184
 
185
+ ### payload override를 사용하는 template rendering
186
+
187
+ Notification template에서 재사용 가능한 Discord content, embed, component를 생성하려면 `DiscordTemplateRenderer`를 등록합니다. 같은 module registration에 transport를 설정하고 `@fluojs/notifications`를 통해 `template` key를 dispatch하세요.
188
+
189
+ ```typescript
190
+ import type { DiscordTemplateRenderer } from '@fluojs/discord';
191
+
192
+ const renderer: DiscordTemplateRenderer = {
193
+ render(input) {
194
+ return {
195
+ content: `Order ${String(input.payload.orderId)} was received.`,
196
+ embeds: [
197
+ {
198
+ description: input.subject,
199
+ title: 'New order',
200
+ },
201
+ ],
202
+ };
203
+ },
204
+ };
205
+
206
+ DiscordModule.forRoot({
207
+ renderer,
208
+ transport: createDiscordWebhookTransport({
209
+ fetch: runtime.fetch,
210
+ webhookUrl: config.discordWebhookUrl,
211
+ }),
212
+ });
213
+
214
+ await notifications.dispatch({
215
+ channel: 'discord',
216
+ locale: 'en',
217
+ metadata: { source: 'orders' },
218
+ payload: {
219
+ content: 'Order #123 is ready for review.',
220
+ orderId: '123',
221
+ },
222
+ subject: 'New order received',
223
+ template: 'orders.received',
224
+ });
225
+ ```
226
+
227
+ `DiscordService.sendNotification(...)`은 `template`과 `renderer`가 모두 있을 때만 renderer를 호출합니다. Renderer는 `{ template, payload, subject, locale, metadata, signal }`을 받습니다. 명시적인 `payload.content`, `payload.embeds`, `payload.components` 값은 대응하는 rendered 값보다 우선합니다. `payload.content`가 `undefined`이면 rendered content, `subject` 순서로 fallback합니다. 따라서 renderer나 transport 설정을 교체하지 않고도 호출자가 template 결과의 일부를 override할 수 있습니다.
228
+
150
229
  ### 명시적 fetch 주입을 사용하는 webhook-first 전달
151
230
 
152
231
  런타임에 독립적인 1st-party transport가 필요하다면 fetch-compatible HTTP 경계만 의존하는 `createDiscordWebhookTransport(...)`를 사용합니다.
@@ -168,6 +247,7 @@ bot 기반 REST 전달처럼 더 풍부한 API 연동이 필요하다면 export
168
247
  Behavioral contract 메모:
169
248
 
170
249
  - 내장 webhook transport는 `408`, `429`, `5xx` 같은 일시적 응답뿐 아니라 transport-level exception도 bounded exponential backoff로 재시도한 뒤 호출자에게 에러를 노출합니다. 영구적인 upstream 응답은 재시도하지 않습니다.
250
+ - Retry backoff는 `DiscordSendOptions.signal`을 관찰합니다. 이미 abort된 signal은 다음 backoff timer를 기다리지 않고 즉시 reject됩니다.
171
251
  - 성공한 webhook 응답은 `DiscordSendResult.response`로 노출됩니다. rate-limit 재시도가 끝내 실패한 경우를 포함해, 호출자에게 보이는 `DiscordTransportError` 메시지는 기본적으로 raw upstream response body를 포함하지 않습니다.
172
252
  - 잘못되었거나 절대 URL이 아닌 `webhookUrl` 값은 전달 실패로 재시도하지 않고 즉시 `DiscordConfigurationError`로 거부됩니다.
173
253
 
@@ -178,6 +258,7 @@ Discord 패키지는 의도적으로 다음을 **포함하지 않습니다**:
178
258
  - 자격 증명이나 webhook URL을 `process.env`에서 직접 읽는 동작
179
259
  - 공유 루트 패키지 경계에 Node 전용 Discord SDK를 내장하는 것
180
260
  - webhook helper와 export된 transport 계약 이상으로 하나의 provider 전략을 강제하는 것
261
+ - 애플리케이션 import용 내부 provider helper, normalized option token, 또는 NestJS-style custom provider replacement seam을 노출하는 것
181
262
  - 하나의 dispatch 호출 안에서 multi-thread fan-out을 자동 변환하는 것
182
263
 
183
264
  이 제한 사항은 런타임 선택, provider capability, rollout 전략이 애플리케이션 경계에서 명시적으로 결정되도록 하기 위한 package contract의 일부입니다.
@@ -191,12 +272,18 @@ Discord 패키지는 의도적으로 다음을 **포함하지 않습니다**:
191
272
  - `DiscordModuleOptions`
192
273
  - `DiscordAsyncModuleOptions`
193
274
  - `DiscordService`
275
+ - `DiscordService.send(message, options)`
276
+ - `DiscordService.sendMany(messages, options)`
277
+ - `DiscordService.sendNotification(notification, options)`
278
+ - `DiscordService.createPlatformStatusSnapshot()`
194
279
  - `DiscordChannel`
195
280
  - `DISCORD`
196
281
  - `DISCORD_CHANNEL`
197
282
 
198
283
  애플리케이션 구성은 `DiscordModule`로, notifications 연동은 `DISCORD_CHANNEL`과 export된 transport 계약으로 조합합니다.
199
284
 
285
+ 이 패키지는 `createDiscordProviders(...)`, `DISCORD_OPTIONS`, `NormalizedDiscordModuleOptions`를 public root barrel에 의도적으로 노출하지 않습니다. 기존 migration이 NestJS 내부 provider token이나 custom provider seam을 바꾸고 있었다면 private helper를 import하지 말고 `DiscordModule.forRoot(...)` / `forRootAsync(...)`를 감싸는 app-owned module을 구성하세요.
286
+
200
287
  ### 계약과 헬퍼
201
288
 
202
289
  - `DiscordMessage`
@@ -227,6 +314,7 @@ Discord 패키지는 의도적으로 다음을 **포함하지 않습니다**:
227
314
 
228
315
  ### 상태 및 에러
229
316
 
317
+ - `DiscordService.createPlatformStatusSnapshot()`
230
318
  - `createDiscordPlatformStatusSnapshot(...)`
231
319
  - `DiscordLifecycleState`
232
320
  - `DiscordPlatformStatusSnapshot`
package/README.md CHANGED
@@ -4,14 +4,19 @@
4
4
 
5
5
  Webhook-first, transport-agnostic Discord delivery core for fluo. It provides a Nest-like module API, an injectable `DiscordService` for standalone usage, and a first-party `DiscordChannel` for `@fluojs/notifications` integration without assuming a Node-only Discord SDK.
6
6
 
7
+ Migration boundary: the module API is intentionally Nest-like but not a NestJS dynamic-module clone. `DiscordModule` is global by default through `global: options.global ?? true`, `forRootAsync(...)` supports only `inject` plus `useFactory`, and internal provider helpers/tokens stay private so applications compose Discord through the module facade and exported service/channel tokens.
8
+
7
9
  ## Table of Contents
8
10
 
9
11
  - [Installation](#installation)
10
12
  - [When to Use](#when-to-use)
11
13
  - [Quick Start](#quick-start)
12
14
  - [Common Patterns](#common-patterns)
15
+ - [Module visibility and migration boundaries](#module-visibility-and-migration-boundaries)
13
16
  - [Standalone delivery with `DiscordService`](#standalone-delivery-with-discordservice)
17
+ - [Bootstrap verification with `verifyOnModuleInit`](#bootstrap-verification-with-verifyonmoduleinit)
14
18
  - [Integration with `@fluojs/notifications`](#integration-with-fluojs-notifications)
19
+ - [Template rendering with payload overrides](#template-rendering-with-payload-overrides)
15
20
  - [Webhook-first delivery with explicit fetch injection](#webhook-first-delivery-with-explicit-fetch-injection)
16
21
  - [Intentional limitations](#intentional-limitations)
17
22
  - [Public API Overview](#public-api-overview)
@@ -75,6 +80,12 @@ export class DeployNotifier {
75
80
 
76
81
  ## Common Patterns
77
82
 
83
+ ### Module visibility and migration boundaries
84
+
85
+ `DiscordModule.forRoot(...)` and `DiscordModule.forRootAsync(...)` return a global module by default. The module exports `DiscordService`, `DiscordChannel`, `DISCORD`, and `DISCORD_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`.
86
+
87
+ The package-level registration surface is intentionally singleton-oriented. `DISCORD` and `DISCORD_CHANNEL` are compatibility tokens for the one configured Discord service and notifications channel. Applications that need multiple Discord clients should compose app-owned modules/providers around distinct `DiscordTransport` instances or expose app-owned facades instead of importing private provider helpers.
88
+
78
89
  ### Standalone delivery with `DiscordService`
79
90
 
80
91
  Use `DiscordService` when your application wants direct Discord delivery without routing through the notifications foundation.
@@ -92,16 +103,39 @@ DiscordModule.forRootAsync({
92
103
  });
93
104
  ```
94
105
 
106
+ `forRootAsync(...)` accepts the fluo async shape only: register dependencies elsewhere in the application graph, list their tokens in `inject`, and return final `DiscordModuleOptions` from `useFactory`. It does not consume NestJS `imports`, `useClass`, or `useExisting` variants, so migrate those patterns to application-owned providers before passing resolved options to Discord.
107
+
95
108
  Behavioral contract notes:
96
109
 
110
+ - `DiscordModule.forRoot(...)` and `DiscordModule.forRootAsync(...)` export `DiscordService`, `DiscordChannel`, `DISCORD`, and `DISCORD_CHANNEL` globally by default. Use the fluo `global?: boolean` option and set `global: false` only when migrated code must keep Discord providers local to importing modules; NestJS `isGlobal` is not supported.
97
111
  - `DiscordService.send(...)` resolves `defaultThreadId` before delivery.
98
112
  - `DiscordService.sendMany(...)` is a direct `DiscordMessage[]` batch API that sends messages sequentially and supports `continueOnError`; it is not a multi-recipient `@fluojs/notifications` dispatch shortcut.
99
- - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown.
113
+ - The service initializes the configured transport during module bootstrap and closes factory-owned resources exactly once across bootstrap verification failure and application shutdown, including any in-flight factory-created transport before shutdown began. A rejected factory creation remains an initialization failure instead of being reclassified as a shutdown cleanup failure.
100
114
  - Sends are accepted only after bootstrap marks the transport `ready`; attempts before bootstrap, during startup, after failed bootstrap, while shutting down, or after shutdown are rejected before delivery.
101
115
  - Sends attempted while the service is shutting down or already stopped are rejected before reusing the cached transport.
116
+ - `DiscordService.sendNotification(...)` checks lifecycle readiness before invoking a configured renderer and passes the caller's `AbortSignal` to both `DiscordTemplateRenderInput.signal` and transport delivery.
117
+ - `DiscordService.createPlatformStatusSnapshot()` exposes the same status contract as `createDiscordPlatformStatusSnapshot(...)`: lifecycle/readiness, health, transport kind and ownership, default thread configuration, bootstrap verification state, distinct bootstrap initialization versus shutdown cleanup failure diagnostics, and notifications channel dependency details, so callers can observe Discord wiring without reaching into internal options.
102
118
  - Blank `defaultThreadId` and `notifications.channel` values are trimmed and ignored; the notifications channel defaults to `discord`.
103
119
  - The package never reads `process.env` directly. All configuration must enter through explicit options or DI.
104
120
 
121
+ ### Bootstrap verification with `verifyOnModuleInit`
122
+
123
+ Set `DiscordModuleOptions.verifyOnModuleInit?: boolean` to `true` when the selected transport can verify its own readiness during application bootstrap. `DiscordService.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 Discord provider ready. Transports that do not implement `verify` are still valid and simply skip the verification step.
124
+
125
+ ```typescript
126
+ DiscordModule.forRoot({
127
+ transport: customDiscordTransport,
128
+ verifyOnModuleInit: true,
129
+ });
130
+ ```
131
+
132
+ Behavioral contract notes:
133
+
134
+ - `verifyOnModuleInit` is optional and defaults to `false`.
135
+ - 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.
136
+ - If `transport.verify()` rejects, bootstrap fails with the initialization failure, the service lifecycle moves to `failed`, and readiness/status snapshots report the provider as not ready. Factory-owned transports are closed exactly once; directly supplied app-owned transports are preserved.
137
+ - `DiscordService.createPlatformStatusSnapshot()` includes `verifiedOnModuleInit` and bootstrap verification state so health/readiness tooling can tell whether bootstrap verification was requested without reaching into internal options.
138
+
105
139
  ### Integration with `@fluojs/notifications`
106
140
 
107
141
  Inject `DISCORD_CHANNEL` into `NotificationsModule.forRootAsync(...)` so the Discord package remains the only place that understands Discord-specific payload fields and recipient-to-thread translation.
@@ -144,9 +178,54 @@ Behavioral contract notes:
144
178
 
145
179
  - One notification dispatch maps to exactly one Discord thread route. Use `payload.threadId` or a single entry in `recipients`.
146
180
  - If `payload.threadId` is omitted, `DiscordService.sendNotification(...)` uses the first `recipients` entry or falls back to `defaultThreadId`.
147
- - Notification metadata is merged from payload metadata, dispatch metadata, and template/subject markers. `template` is rendered only when a renderer is configured.
181
+ - Notification metadata is merged from payload metadata, dispatch metadata, and template/subject markers. On duplicate keys, dispatch metadata overrides payload metadata, and final `subject` / `template` markers override both. `template` is rendered only when a renderer is configured.
182
+ - Template rendering starts only while the service is ready. The render input includes `signal`, so renderers can stop caller-cancelled work before transport delivery.
148
183
  - If a notification workflow needs fan-out across multiple Discord threads, create one concrete Discord message per thread with `DiscordService.sendMany(...)` or issue separate notification dispatches; a single notification dispatch never expands multi-recipient fan-out implicitly.
149
184
 
185
+ ### Template rendering with payload overrides
186
+
187
+ Register a `DiscordTemplateRenderer` when notification templates should produce reusable Discord content, embeds, or components. Keep transport setup in the same module registration and dispatch a `template` key through `@fluojs/notifications`.
188
+
189
+ ```typescript
190
+ import type { DiscordTemplateRenderer } from '@fluojs/discord';
191
+
192
+ const renderer: DiscordTemplateRenderer = {
193
+ render(input) {
194
+ return {
195
+ content: `Order ${String(input.payload.orderId)} was received.`,
196
+ embeds: [
197
+ {
198
+ description: input.subject,
199
+ title: 'New order',
200
+ },
201
+ ],
202
+ };
203
+ },
204
+ };
205
+
206
+ DiscordModule.forRoot({
207
+ renderer,
208
+ transport: createDiscordWebhookTransport({
209
+ fetch: runtime.fetch,
210
+ webhookUrl: config.discordWebhookUrl,
211
+ }),
212
+ });
213
+
214
+ await notifications.dispatch({
215
+ channel: 'discord',
216
+ locale: 'en',
217
+ metadata: { source: 'orders' },
218
+ payload: {
219
+ content: 'Order #123 is ready for review.',
220
+ orderId: '123',
221
+ },
222
+ subject: 'New order received',
223
+ template: 'orders.received',
224
+ });
225
+ ```
226
+
227
+ `DiscordService.sendNotification(...)` calls the renderer only when both `template` and `renderer` are present. The renderer receives `{ template, payload, subject, locale, metadata, signal }`. Explicit `payload.content`, `payload.embeds`, and `payload.components` values take precedence over the corresponding rendered values; when `payload.content` is `undefined`, content falls back to rendered content and then to `subject`. This lets callers override one template result without replacing the renderer or transport configuration.
228
+
150
229
  ### Webhook-first delivery with explicit fetch injection
151
230
 
152
231
  Use `createDiscordWebhookTransport(...)` when you want a portable first-party transport that only depends on a fetch-compatible HTTP boundary.
@@ -168,6 +247,7 @@ For richer API integrations such as bot-backed REST delivery, implement the expo
168
247
  Behavioral contract notes:
169
248
 
170
249
  - The built-in webhook transport retries transient `408`, `429`, and `5xx` responses, and also retries transport-level exceptions, using bounded exponential backoff before surfacing an error. Permanent upstream responses are not retried.
250
+ - Retry backoff observes `DiscordSendOptions.signal`; an already-aborted signal rejects immediately instead of waiting for the next backoff timer.
171
251
  - Successful webhook responses are exposed through `DiscordSendResult.response`; caller-visible `DiscordTransportError` messages still omit raw upstream response bodies by default, including after rate-limit retries fail.
172
252
  - Malformed or non-absolute `webhookUrl` values are rejected immediately as `DiscordConfigurationError` instead of being retried as delivery failures.
173
253
 
@@ -178,6 +258,7 @@ The Discord package intentionally does **not**:
178
258
  - read credentials or webhook URLs from `process.env`
179
259
  - ship a Node-only Discord SDK inside the shared root package boundary
180
260
  - force one provider strategy beyond the webhook-first helper and exported transport contract
261
+ - expose internal provider helpers, normalized option tokens, or NestJS-style custom provider replacement seams for application imports
181
262
  - translate one notification into multi-thread fan-out inside a single dispatch call
182
263
 
183
264
  These limitations are part of the package contract so runtime choice, provider capability, and rollout strategy stay explicit at the application boundary.
@@ -191,12 +272,18 @@ These limitations are part of the package contract so runtime choice, provider c
191
272
  - `DiscordModuleOptions`
192
273
  - `DiscordAsyncModuleOptions`
193
274
  - `DiscordService`
275
+ - `DiscordService.send(message, options)`
276
+ - `DiscordService.sendMany(messages, options)`
277
+ - `DiscordService.sendNotification(notification, options)`
278
+ - `DiscordService.createPlatformStatusSnapshot()`
194
279
  - `DiscordChannel`
195
280
  - `DISCORD`
196
281
  - `DISCORD_CHANNEL`
197
282
 
198
283
  Compose applications through `DiscordModule` and integrate notifications through `DISCORD_CHANNEL` plus the exported transport contracts.
199
284
 
285
+ The package intentionally keeps `createDiscordProviders(...)`, `DISCORD_OPTIONS`, and `NormalizedDiscordModuleOptions` out of the public root barrel. If a migration previously customized NestJS internals or provider tokens, wrap `DiscordModule.forRoot(...)` / `forRootAsync(...)` in an app-owned module instead of importing private helpers.
286
+
200
287
  ### Contracts and helpers
201
288
 
202
289
  - `DiscordMessage`
@@ -227,6 +314,7 @@ Compose applications through `DiscordModule` and integrate notifications through
227
314
 
228
315
  ### Status and errors
229
316
 
317
+ - `DiscordService.createPlatformStatusSnapshot()`
230
318
  - `createDiscordPlatformStatusSnapshot(...)`
231
319
  - `DiscordLifecycleState`
232
320
  - `DiscordPlatformStatusSnapshot`
package/dist/module.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare class DiscordModule {
6
6
  * Registers Discord providers using static options.
7
7
  *
8
8
  * @param options Static Discord module options including transport wiring and optional template rendering behavior.
9
- * @returns A global module definition that exports {@link DiscordService}, {@link DiscordChannel}, and compatibility tokens.
9
+ * @returns A module definition that exports {@link DiscordService}, {@link DiscordChannel}, and compatibility tokens, globally by default unless `global` is `false`.
10
10
  *
11
11
  * @example
12
12
  * ```ts
@@ -20,7 +20,7 @@ export declare class DiscordModule {
20
20
  * Registers Discord providers from an async DI factory.
21
21
  *
22
22
  * @param options Async module options that resolve Discord transport and renderer configuration through DI.
23
- * @returns A global module definition that memoizes async option resolution per module instance.
23
+ * @returns A module definition that memoizes async option resolution per module instance and is global by default unless `global` is `false`.
24
24
  *
25
25
  * @example
26
26
  * ```ts
package/dist/module.js CHANGED
@@ -94,7 +94,7 @@ export class DiscordModule {
94
94
  * Registers Discord providers using static options.
95
95
  *
96
96
  * @param options Static Discord module options including transport wiring and optional template rendering behavior.
97
- * @returns A global module definition that exports {@link DiscordService}, {@link DiscordChannel}, and compatibility tokens.
97
+ * @returns A module definition that exports {@link DiscordService}, {@link DiscordChannel}, and compatibility tokens, globally by default unless `global` is `false`.
98
98
  *
99
99
  * @example
100
100
  * ```ts
@@ -111,7 +111,7 @@ export class DiscordModule {
111
111
  * Registers Discord providers from an async DI factory.
112
112
  *
113
113
  * @param options Async module options that resolve Discord transport and renderer configuration through DI.
114
- * @returns A global module definition that memoizes async option resolution per module instance.
114
+ * @returns A module definition that memoizes async option resolution per module instance and is global by default unless `global` is `false`.
115
115
  *
116
116
  * @example
117
117
  * ```ts
package/dist/service.d.ts CHANGED
@@ -10,11 +10,18 @@ import type { Discord, DiscordMessage, DiscordNotificationDispatchRequest, Disco
10
10
  */
11
11
  export declare class DiscordService implements Discord, OnModuleInit, OnApplicationShutdown {
12
12
  private readonly options;
13
+ private lifecycleFailurePhase;
13
14
  private lifecycleState;
15
+ private ownedTransportCleanupPromise;
14
16
  private resolvedTransport;
17
+ private shutdownPromise;
15
18
  private transportPromise;
16
19
  constructor(options: NormalizedDiscordModuleOptions);
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 Discord transport wiring.
@@ -71,6 +78,8 @@ export declare class DiscordService implements Discord, OnModuleInit, OnApplicat
71
78
  */
72
79
  sendNotification(notification: DiscordNotificationDispatchRequest, options?: DiscordSendOptions): Promise<DiscordSendResult>;
73
80
  private ensureTransport;
81
+ private clearResolvedTransport;
82
+ private handleTransportInitializationFailure;
74
83
  private assertReadyForSend;
75
84
  private normalizeMessage;
76
85
  private resolveNotificationThreadId;
@@ -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,EACV,OAAO,EACP,cAAc,EACd,kCAAkC,EAClC,sBAAsB,EAEtB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EAIjB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AAyCpB;;;;;;;GAOG;AACH,qBACa,cAAe,YAAW,OAAO,EAAE,YAAY,EAAE,qBAAqB;IAKrE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,gBAAgB,CAAwC;gBAEnC,OAAO,EAAE,8BAA8B;IAE9D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAetC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBnC;;;;OAIG;IACH,4BAA4B;IAW5B;;;;;;;;;;;;;;OAcG;IACG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyBjG;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA6B1H;;;;;;;;;;;;;;;;OAgBG;IACG,gBAAgB,CACpB,YAAY,EAAE,kCAAkC,EAChD,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAiCf,eAAe;IAe7B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,2BAA2B;YAkBrB,kBAAkB;CAejC"}
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,EACV,OAAO,EACP,cAAc,EACd,kCAAkC,EAClC,sBAAsB,EAEtB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EAIjB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AA+DpB;;;;;;;GAOG;AACH,qBACa,cAAe,YAAW,OAAO,EAAE,YAAY,EAAE,qBAAqB;IAQrE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,qBAAqB,CAAkD;IAC/E,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,4BAA4B,CAA4B;IAChE,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,gBAAgB,CAAwC;gBAEnC,OAAO,EAAE,8BAA8B;IAE9D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;YAiB9B,mBAAmB;IAajC,OAAO,CAAC,4BAA4B;YAQtB,gCAAgC;YAYhC,0BAA0B;IAelC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BnC;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;;;;;;;;OAcG;IACG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0BjG;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA6B1H;;;;;;;;;;;;;;;;OAgBG;IACG,gBAAgB,CACpB,YAAY,EAAE,kCAAkC,EAChD,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAmCf,eAAe;IAuB7B,OAAO,CAAC,sBAAsB;YAKhB,oCAAoC;IAwBlD,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,2BAA2B;YAkBrB,kBAAkB;CAiBjC"}
package/dist/service.js CHANGED
@@ -16,12 +16,21 @@ function createAbortError() {
16
16
  function createStoppedTransportError() {
17
17
  return new DiscordTransportError('Discord transport is shutting down or already stopped.');
18
18
  }
19
- function createLifecycleReadinessError(lifecycleState) {
19
+ function createLifecycleReadinessError(lifecycleState, lifecycleFailurePhase) {
20
20
  if (lifecycleState === 'failed') {
21
+ if (lifecycleFailurePhase === 'shutdown-cleanup') {
22
+ return new DiscordTransportError('Discord transport failed during shutdown cleanup.');
23
+ }
21
24
  return new DiscordTransportError('Discord transport failed to initialize.');
22
25
  }
23
26
  return new DiscordTransportError('Discord transport is not ready for delivery.');
24
27
  }
28
+ function createCleanupFailureCause(originalError, cleanupError) {
29
+ return new AggregateError([originalError, cleanupError], 'Discord transport initialization failed and the owned transport failed to close.');
30
+ }
31
+ function isShutdownLifecycleState(state) {
32
+ return state === 'stopping' || state === 'stopped';
33
+ }
25
34
  function normalizeOptionalString(value) {
26
35
  const trimmed = value?.trim();
27
36
  return trimmed && trimmed.length > 0 ? trimmed : undefined;
@@ -44,39 +53,86 @@ class DiscordService {
44
53
  static {
45
54
  [_DiscordService, _initClass] = _applyDecs(this, [Inject(DISCORD_OPTIONS)], []).c;
46
55
  }
56
+ lifecycleFailurePhase;
47
57
  lifecycleState = 'created';
58
+ ownedTransportCleanupPromise;
48
59
  resolvedTransport;
60
+ shutdownPromise;
49
61
  transportPromise;
50
62
  constructor(options) {
51
63
  this.options = options;
52
64
  }
53
65
  async onApplicationShutdown() {
66
+ if (this.lifecycleState === 'stopped') {
67
+ return;
68
+ }
69
+ if (this.shutdownPromise) {
70
+ return this.shutdownPromise;
71
+ }
54
72
  this.lifecycleState = 'stopping';
73
+ this.lifecycleFailurePhase = undefined;
74
+ this.shutdownPromise = this.closeOwnedTransport();
75
+ return this.shutdownPromise;
76
+ }
77
+ async closeOwnedTransport() {
55
78
  try {
56
- if (this.resolvedTransport && this.options.transport.ownsResources && this.resolvedTransport.close) {
57
- await this.resolvedTransport.close();
58
- }
79
+ await this.closeOwnedTransportResources();
59
80
  this.lifecycleState = 'stopped';
81
+ this.lifecycleFailurePhase = undefined;
60
82
  } catch (error) {
61
83
  this.lifecycleState = 'failed';
84
+ this.lifecycleFailurePhase = 'shutdown-cleanup';
62
85
  throw new Error('Discord transport failed to close cleanly.', {
63
86
  cause: error
64
87
  });
65
88
  }
66
89
  }
90
+ closeOwnedTransportResources() {
91
+ if (!this.ownedTransportCleanupPromise) {
92
+ this.ownedTransportCleanupPromise = this.closeOwnedTransportResourcesOnce();
93
+ }
94
+ return this.ownedTransportCleanupPromise;
95
+ }
96
+ async closeOwnedTransportResourcesOnce() {
97
+ try {
98
+ const transport = await this.resolveTransportForCleanup();
99
+ if (transport && this.options.transport.ownsResources && transport.close) {
100
+ await transport.close();
101
+ }
102
+ } finally {
103
+ this.clearResolvedTransport();
104
+ }
105
+ }
106
+ async resolveTransportForCleanup() {
107
+ if (this.resolvedTransport) {
108
+ return this.resolvedTransport;
109
+ }
110
+ if (!this.transportPromise) {
111
+ return undefined;
112
+ }
113
+ return this.transportPromise.then(transport => transport, () => undefined);
114
+ }
67
115
  async onModuleInit() {
116
+ if (isShutdownLifecycleState(this.lifecycleState)) {
117
+ return;
118
+ }
68
119
  this.lifecycleState = 'starting';
120
+ this.lifecycleFailurePhase = undefined;
69
121
  try {
70
122
  const transport = await this.ensureTransport();
123
+ if (this.lifecycleState !== 'starting') {
124
+ return;
125
+ }
71
126
  if (this.options.verifyOnModuleInit && transport.verify) {
72
127
  await transport.verify();
73
128
  }
129
+ if (this.lifecycleState !== 'starting') {
130
+ return;
131
+ }
74
132
  this.lifecycleState = 'ready';
133
+ this.lifecycleFailurePhase = undefined;
75
134
  } catch (error) {
76
- this.lifecycleState = 'failed';
77
- throw new Error('Discord transport failed to initialize.', {
78
- cause: error
79
- });
135
+ await this.handleTransportInitializationFailure(error);
80
136
  }
81
137
  }
82
138
 
@@ -89,6 +145,7 @@ class DiscordService {
89
145
  return createDiscordPlatformStatusSnapshot({
90
146
  channelName: this.options.notifications.channel,
91
147
  defaultThreadConfigured: this.options.defaultThreadId !== undefined,
148
+ lifecycleFailurePhase: this.lifecycleFailurePhase,
92
149
  lifecycleState: this.lifecycleState,
93
150
  ownsTransportResources: this.options.transport.ownsResources,
94
151
  transportKind: this.options.transport.kind,
@@ -117,6 +174,7 @@ class DiscordService {
117
174
  }
118
175
  this.assertReadyForSend();
119
176
  const transport = await this.ensureTransport();
177
+ this.assertReadyForSend();
120
178
  const normalized = this.normalizeMessage(message);
121
179
  assertMessageContent(normalized);
122
180
  const result = await transport.send(normalized, options);
@@ -192,8 +250,9 @@ class DiscordService {
192
250
  if (options.signal?.aborted) {
193
251
  throw createAbortError();
194
252
  }
253
+ this.assertReadyForSend();
195
254
  const payload = notification.payload;
196
- const rendered = await this.renderNotification(notification);
255
+ const rendered = await this.renderNotification(notification, options.signal);
197
256
  return this.send({
198
257
  allowedMentions: payload.allowedMentions,
199
258
  attachments: payload.attachments ?? [],
@@ -220,6 +279,12 @@ class DiscordService {
220
279
  }, options);
221
280
  }
222
281
  async ensureTransport() {
282
+ if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
283
+ throw createStoppedTransportError();
284
+ }
285
+ if (this.lifecycleState === 'failed') {
286
+ throw createLifecycleReadinessError(this.lifecycleState, this.lifecycleFailurePhase);
287
+ }
223
288
  if (this.resolvedTransport) {
224
289
  return this.resolvedTransport;
225
290
  }
@@ -231,12 +296,36 @@ class DiscordService {
231
296
  }
232
297
  return this.transportPromise;
233
298
  }
299
+ clearResolvedTransport() {
300
+ this.resolvedTransport = undefined;
301
+ this.transportPromise = undefined;
302
+ }
303
+ async handleTransportInitializationFailure(error) {
304
+ const interruptedByShutdown = isShutdownLifecycleState(this.lifecycleState);
305
+ if (!interruptedByShutdown) {
306
+ this.lifecycleState = 'failed';
307
+ this.lifecycleFailurePhase = 'initialization';
308
+ }
309
+ let cause = error;
310
+ try {
311
+ await this.closeOwnedTransportResources();
312
+ } catch (cleanupError) {
313
+ cause = createCleanupFailureCause(error, cleanupError);
314
+ }
315
+ if (!interruptedByShutdown && !isShutdownLifecycleState(this.lifecycleState)) {
316
+ this.lifecycleState = 'failed';
317
+ this.lifecycleFailurePhase = 'initialization';
318
+ }
319
+ throw new Error('Discord transport failed to initialize.', {
320
+ cause
321
+ });
322
+ }
234
323
  assertReadyForSend() {
235
324
  if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
236
325
  throw createStoppedTransportError();
237
326
  }
238
327
  if (this.lifecycleState !== 'ready') {
239
- throw createLifecycleReadinessError(this.lifecycleState);
328
+ throw createLifecycleReadinessError(this.lifecycleState, this.lifecycleFailurePhase);
240
329
  }
241
330
  }
242
331
  normalizeMessage(message) {
@@ -267,7 +356,7 @@ class DiscordService {
267
356
  }
268
357
  return recipients[0] ?? this.options.defaultThreadId;
269
358
  }
270
- async renderNotification(notification) {
359
+ async renderNotification(notification, signal) {
271
360
  if (!notification.template || !this.options.renderer) {
272
361
  return undefined;
273
362
  }
@@ -275,6 +364,7 @@ class DiscordService {
275
364
  locale: notification.locale,
276
365
  metadata: notification.metadata,
277
366
  payload: notification.payload,
367
+ signal,
278
368
  subject: notification.subject,
279
369
  template: notification.template
280
370
  });
package/dist/status.d.ts CHANGED
@@ -5,6 +5,7 @@ export type DiscordLifecycleState = 'created' | 'starting' | 'ready' | 'stopping
5
5
  export interface DiscordStatusAdapterInput {
6
6
  channelName: string;
7
7
  defaultThreadConfigured: boolean;
8
+ lifecycleFailurePhase?: 'initialization' | 'shutdown-cleanup';
8
9
  lifecycleState: DiscordLifecycleState;
9
10
  ownsTransportResources: boolean;
10
11
  transportKind: string;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzG,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,OAAO,CAAC;IACjC,cAAc,EAAE,qBAAqB,CAAC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,mFAAmF;AACnF,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AA6DD;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,yBAAyB,GAAG,6BAA6B,CAiBnH"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzG,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,gBAAgB,GAAG,kBAAkB,CAAC;IAC9D,cAAc,EAAE,qBAAqB,CAAC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,mFAAmF;AACnF,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAmED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,yBAAyB,GAAG,6BAA6B,CAkBnH"}
package/dist/status.js CHANGED
@@ -28,7 +28,7 @@ function createReadiness(input) {
28
28
  if (input.lifecycleState === 'failed') {
29
29
  return {
30
30
  critical: true,
31
- reason: 'Discord transport failed to initialize.',
31
+ reason: input.lifecycleFailurePhase === 'shutdown-cleanup' ? 'Discord transport failed during shutdown cleanup.' : 'Discord transport failed to initialize.',
32
32
  status: 'not-ready'
33
33
  };
34
34
  }
@@ -41,7 +41,7 @@ function createReadiness(input) {
41
41
  function createHealth(input) {
42
42
  if (input.lifecycleState === 'failed' || input.lifecycleState === 'stopped') {
43
43
  return {
44
- reason: 'Discord transport is unavailable.',
44
+ reason: input.lifecycleState === 'failed' && input.lifecycleFailurePhase === 'shutdown-cleanup' ? 'Discord transport failed during shutdown cleanup.' : 'Discord transport is unavailable.',
45
45
  status: 'unhealthy'
46
46
  };
47
47
  }
@@ -68,6 +68,9 @@ export function createDiscordPlatformStatusSnapshot(input) {
68
68
  channelName: input.channelName,
69
69
  defaultThreadConfigured: input.defaultThreadConfigured,
70
70
  dependencies: ['notifications.channel', 'discord.transport'],
71
+ ...(input.lifecycleFailurePhase ? {
72
+ lifecycleFailurePhase: input.lifecycleFailurePhase
73
+ } : {}),
71
74
  lifecycleState: input.lifecycleState,
72
75
  transportKind: input.transportKind,
73
76
  verifiedOnModuleInit: input.verifiedOnModuleInit
package/dist/types.d.ts CHANGED
@@ -132,6 +132,8 @@ export interface DiscordTemplateRenderInput<TPayload extends DiscordNotification
132
132
  locale?: string;
133
133
  metadata?: Record<string, unknown>;
134
134
  payload: TPayload;
135
+ /** Caller cancellation signal shared with rendering and transport delivery. */
136
+ signal?: AbortSignal;
135
137
  subject?: string;
136
138
  template: string;
137
139
  }
@@ -147,7 +149,7 @@ export interface DiscordTemplateRenderer {
147
149
  * Renders one notification template into Discord content and/or embed fragments.
148
150
  *
149
151
  * @typeParam TPayload Payload shape carried by the notification request.
150
- * @param input Template render input including the template key and opaque payload.
152
+ * @param input Template render input including the template key, opaque payload, and caller cancellation signal.
151
153
  * @returns Rendered content or embed fragments that are merged with explicit payload overrides.
152
154
  */
153
155
  render<TPayload extends DiscordNotificationPayload = DiscordNotificationPayload>(input: DiscordTemplateRenderInput<TPayload>): MaybePromise<DiscordTemplateRenderResult>;
@@ -1 +1 @@
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,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjE,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAElE,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE5D,wFAAwF;AACxF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvE,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,WAAW,wBAAwB;IACvC,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,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,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAE5G;;;;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,uBAAuB;IACtC;;;;OAIG;IACH,MAAM,IAAI,YAAY,CAAC,gBAAgB,CAAC,CAAC;IAEzC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gGAAgG;AAChG,MAAM,WAAW,oBAAoB;IACnC,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,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IACpD,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,oBAAoB,CAAC,CAAC;AAEzC,iEAAiE;AACjE,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,0BAA0B,CAAC,QAAQ,SAAS,0BAA0B,GAAG,0BAA0B;IAClH,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,uEAAuE;AACvE,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAClC;AAED,2FAA2F;AAC3F,MAAM,WAAW,uBAAuB;IACtC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,SAAS,0BAA0B,GAAG,0BAA0B,EAC7E,KAAK,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC1C,YAAY,CAAC,2BAA2B,CAAC,CAAC;CAC9C;AAED,6GAA6G;AAC7G,MAAM,WAAW,0BAA2B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,kCAAmC,SAAQ,2BAA2B,CAAC,0BAA0B,CAAC;IACjH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAkB,SAAQ,uBAAuB;IAChE,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,+DAA+D;AAC/D,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,SAAS,EAAE,gBAAgB,GAAG,uBAAuB,CAAC;IACtD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;AAExI,0EAA0E;AAC1E,MAAM,WAAW,8BAA8B;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,8EAA8E;AAC9E,MAAM,WAAW,OAAO;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAExF;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEjH;;;;;;OAMG;IACH,gBAAgB,CACd,YAAY,EAAE,kCAAkC,EAChD,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B"}
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,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjE,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAElE,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE5D,wFAAwF;AACxF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvE,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,WAAW,wBAAwB;IACvC,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,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,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAE5G;;;;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,uBAAuB;IACtC;;;;OAIG;IACH,MAAM,IAAI,YAAY,CAAC,gBAAgB,CAAC,CAAC;IAEzC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gGAAgG;AAChG,MAAM,WAAW,oBAAoB;IACnC,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,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IACpD,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,oBAAoB,CAAC,CAAC;AAEzC,iEAAiE;AACjE,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,0BAA0B,CAAC,QAAQ,SAAS,0BAA0B,GAAG,0BAA0B;IAClH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,QAAQ,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,uEAAuE;AACvE,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAClC;AAED,2FAA2F;AAC3F,MAAM,WAAW,uBAAuB;IACtC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,SAAS,0BAA0B,GAAG,0BAA0B,EAC7E,KAAK,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC1C,YAAY,CAAC,2BAA2B,CAAC,CAAC;CAC9C;AAED,6GAA6G;AAC7G,MAAM,WAAW,0BAA2B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,kCAAmC,SAAQ,2BAA2B,CAAC,0BAA0B,CAAC;IACjH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAkB,SAAQ,uBAAuB;IAChE,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,+DAA+D;AAC/D,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,SAAS,EAAE,gBAAgB,GAAG,uBAAuB,CAAC;IACtD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;AAExI,0EAA0E;AAC1E,MAAM,WAAW,8BAA8B;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,8EAA8E;AAC9E,MAAM,WAAW,OAAO;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAExF;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEjH;;;;;;OAMG;IACH,gBAAgB,CACd,YAAY,EAAE,kCAAkC,EAChD,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,gBAAgB,EAEhB,8BAA8B,EAE/B,MAAM,YAAY,CAAC;AAiIpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,gBAAgB,CAyEvG"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,gBAAgB,EAEhB,8BAA8B,EAE/B,MAAM,YAAY,CAAC;AAqIpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,gBAAgB,CAyEvG"}
package/dist/webhook.js CHANGED
@@ -93,6 +93,9 @@ async function waitForRetry(delayMs, signal) {
93
93
  if (delayMs <= 0) {
94
94
  return;
95
95
  }
96
+ if (signal?.aborted) {
97
+ throw signal.reason ?? new DOMException('The operation was aborted.', 'AbortError');
98
+ }
96
99
  await new Promise((resolve, reject) => {
97
100
  const timer = setTimeout(() => {
98
101
  signal?.removeEventListener('abort', onAbort);
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "portable",
10
10
  "fetch"
11
11
  ],
12
- "version": "1.0.3",
12
+ "version": "1.0.5",
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/notifications": "^1.0.1",
41
- "@fluojs/runtime": "^1.1.1",
42
- "@fluojs/di": "^1.0.3"
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"