@fluojs/discord
Advanced tools
@@ -15,5 +15,7 @@ import type { OnApplicationShutdown, OnModuleInit } from '@fluojs/runtime'; | ||
| private resolvedTransport; | ||
| private shutdownPromise; | ||
| private transportPromise; | ||
| constructor(options: NormalizedDiscordModuleOptions); | ||
| onApplicationShutdown(): Promise<void>; | ||
| private closeOwnedTransport; | ||
| onModuleInit(): Promise<void>; | ||
@@ -20,0 +22,0 @@ /** |
@@ -1,1 +0,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"} | ||
| {"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+CpB;;;;;;;GAOG;AACH,qBACa,cAAe,YAAW,OAAO,EAAE,YAAY,EAAE,qBAAqB;IAMrE,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,cAAc,CAA2C;IACjE,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;YAgB9B,mBAAmB;IAe3B,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCnC;;;;OAIG;IACH,4BAA4B;IAW5B;;;;;;;;;;;;;;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;YAiCf,eAAe;IAuB7B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,2BAA2B;YAkBrB,kBAAkB;CAejC"} |
+36
-2
@@ -25,2 +25,5 @@ let _initClass; | ||
| } | ||
| function isShutdownLifecycleState(state) { | ||
| return state === 'stopping' || state === 'stopped'; | ||
| } | ||
| function normalizeOptionalString(value) { | ||
@@ -50,2 +53,3 @@ const trimmed = value?.trim(); | ||
| resolvedTransport; | ||
| shutdownPromise; | ||
| transportPromise; | ||
@@ -56,6 +60,17 @@ constructor(options) { | ||
| async onApplicationShutdown() { | ||
| if (this.lifecycleState === 'stopped') { | ||
| return; | ||
| } | ||
| if (this.shutdownPromise) { | ||
| return this.shutdownPromise; | ||
| } | ||
| this.lifecycleState = 'stopping'; | ||
| this.shutdownPromise = this.closeOwnedTransport(); | ||
| return this.shutdownPromise; | ||
| } | ||
| async closeOwnedTransport() { | ||
| try { | ||
| if (this.resolvedTransport && this.options.transport.ownsResources && this.resolvedTransport.close) { | ||
| await this.resolvedTransport.close(); | ||
| const transport = this.resolvedTransport ?? (this.transportPromise ? await this.transportPromise : undefined); | ||
| if (transport && this.options.transport.ownsResources && transport.close) { | ||
| await transport.close(); | ||
| } | ||
@@ -71,10 +86,22 @@ this.lifecycleState = 'stopped'; | ||
| async onModuleInit() { | ||
| if (isShutdownLifecycleState(this.lifecycleState)) { | ||
| return; | ||
| } | ||
| this.lifecycleState = 'starting'; | ||
| try { | ||
| const transport = await this.ensureTransport(); | ||
| if (this.lifecycleState !== 'starting') { | ||
| return; | ||
| } | ||
| if (this.options.verifyOnModuleInit && transport.verify) { | ||
| await transport.verify(); | ||
| } | ||
| if (this.lifecycleState !== 'starting') { | ||
| return; | ||
| } | ||
| this.lifecycleState = 'ready'; | ||
| } catch (error) { | ||
| if (isShutdownLifecycleState(this.lifecycleState)) { | ||
| throw error; | ||
| } | ||
| this.lifecycleState = 'failed'; | ||
@@ -124,2 +151,3 @@ throw new Error('Discord transport failed to initialize.', { | ||
| const transport = await this.ensureTransport(); | ||
| this.assertReadyForSend(); | ||
| const normalized = this.normalizeMessage(message); | ||
@@ -228,2 +256,8 @@ assertMessageContent(normalized); | ||
| async ensureTransport() { | ||
| if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') { | ||
| throw createStoppedTransportError(); | ||
| } | ||
| if (this.lifecycleState === 'failed') { | ||
| throw createLifecycleReadinessError(this.lifecycleState); | ||
| } | ||
| if (this.resolvedTransport) { | ||
@@ -230,0 +264,0 @@ return this.resolvedTransport; |
+4
-4
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "1.0.3", | ||
| "version": "1.0.4", | ||
| "private": false, | ||
@@ -41,5 +41,5 @@ "license": "MIT", | ||
| "@fluojs/core": "^1.0.3", | ||
| "@fluojs/notifications": "^1.0.1", | ||
| "@fluojs/runtime": "^1.1.1", | ||
| "@fluojs/di": "^1.0.3" | ||
| "@fluojs/di": "^1.1.0", | ||
| "@fluojs/notifications": "^1.0.2", | ||
| "@fluojs/runtime": "^1.1.8" | ||
| }, | ||
@@ -46,0 +46,0 @@ "devDependencies": { |
+15
-1
@@ -7,2 +7,4 @@ # @fluojs/discord | ||
| 마이그레이션 경계: 이 모듈 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를 조합해야 합니다. | ||
| ## 목차 | ||
@@ -96,9 +98,13 @@ | ||
| `forRootAsync(...)`는 fluo async 형태만 받습니다. 필요한 의존성은 애플리케이션 module graph에 먼저 등록하고, token을 `inject`에 나열한 뒤, `useFactory`에서 최종 `DiscordModuleOptions`를 반환하세요. NestJS `imports`, `useClass`, `useExisting` 변형은 소비하지 않으므로 그런 패턴은 Discord에 option을 넘기기 전에 application-owned provider로 옮겨야 합니다. | ||
| Behavioral contract 메모: | ||
| - `DiscordModule.forRoot(...)`와 `DiscordModule.forRootAsync(...)`는 `DiscordService`, `DiscordChannel`, `DISCORD`, `DISCORD_CHANNEL`을 기본 global로 export합니다. fluo 옵션인 `global?: boolean`을 사용하고, migrated code가 Discord provider를 importing module 안에만 유지해야 할 때만 `global: false`를 설정하세요. NestJS `isGlobal`은 지원하지 않습니다. | ||
| - `DiscordService.send(...)`는 전달 전에 `defaultThreadId`를 해석합니다. | ||
| - `DiscordService.sendMany(...)`는 `DiscordMessage[]`를 직접 순차 전송하는 batch API이며 `continueOnError`를 지원합니다. 이는 multi-recipient `@fluojs/notifications` dispatch shortcut이 아닙니다. | ||
| - 서비스는 모듈 bootstrap 시 transport를 초기화하고, factory가 소유한 리소스만 애플리케이션 shutdown 시 닫습니다. | ||
| - 서비스는 모듈 bootstrap 시 transport를 초기화하고, shutdown 전에 시작된 factory 생성 transport가 아직 완료되지 않았더라도 이를 기다린 뒤 factory가 소유한 리소스를 애플리케이션 shutdown 시 닫습니다. | ||
| - send는 bootstrap이 transport를 `ready`로 표시한 뒤에만 허용됩니다. bootstrap 전, startup 중, bootstrap 실패 후, shutdown 중, shutdown 후 시도는 전달 전에 거부됩니다. | ||
| - 서비스가 shutdown 중이거나 이미 stopped 상태라면 cached transport를 재사용하지 않고 send를 거부합니다. | ||
| - `DiscordService.createPlatformStatusSnapshot()`은 `createDiscordPlatformStatusSnapshot(...)`과 같은 status 계약을 노출합니다. 여기에는 lifecycle/readiness, health, transport kind와 ownership, 기본 thread 구성, bootstrap verification 상태, notifications channel dependency details가 포함되어, 호출자가 내부 옵션에 접근하지 않고도 Discord wiring을 관찰할 수 있습니다. | ||
| - 빈 `defaultThreadId`와 `notifications.channel` 값은 trim 후 무시됩니다. notifications channel은 기본적으로 `discord`입니다. | ||
@@ -183,2 +189,3 @@ - 이 패키지는 절대로 `process.env`를 직접 읽지 않습니다. 모든 설정은 명시적인 옵션 또는 DI를 통해 들어와야 합니다. | ||
| - webhook helper와 export된 transport 계약 이상으로 하나의 provider 전략을 강제하는 것 | ||
| - 애플리케이션 import용 내부 provider helper, normalized option token, 또는 NestJS-style custom provider replacement seam을 노출하는 것 | ||
| - 하나의 dispatch 호출 안에서 multi-thread fan-out을 자동 변환하는 것 | ||
@@ -197,2 +204,6 @@ | ||
| - `DiscordService` | ||
| - `DiscordService.send(message, options)` | ||
| - `DiscordService.sendMany(messages, options)` | ||
| - `DiscordService.sendNotification(notification, options)` | ||
| - `DiscordService.createPlatformStatusSnapshot()` | ||
| - `DiscordChannel` | ||
@@ -204,2 +215,4 @@ - `DISCORD` | ||
| 이 패키지는 `createDiscordProviders(...)`, `DISCORD_OPTIONS`, `NormalizedDiscordModuleOptions`를 public root barrel에 의도적으로 노출하지 않습니다. 기존 migration이 NestJS 내부 provider token이나 custom provider seam을 바꾸고 있었다면 private helper를 import하지 말고 `DiscordModule.forRoot(...)` / `forRootAsync(...)`를 감싸는 app-owned module을 구성하세요. | ||
| ### 계약과 헬퍼 | ||
@@ -235,2 +248,3 @@ | ||
| - `DiscordService.createPlatformStatusSnapshot()` | ||
| - `createDiscordPlatformStatusSnapshot(...)` | ||
@@ -237,0 +251,0 @@ - `DiscordLifecycleState` |
+15
-1
@@ -7,2 +7,4 @@ # @fluojs/discord | ||
| 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. | ||
| ## Table of Contents | ||
@@ -96,9 +98,13 @@ | ||
| `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. | ||
| Behavioral contract notes: | ||
| - `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. | ||
| - `DiscordService.send(...)` resolves `defaultThreadId` before delivery. | ||
| - `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. | ||
| - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown. | ||
| - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown, including any in-flight factory-created transport before shutdown began. | ||
| - 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. | ||
| - Sends attempted while the service is shutting down or already stopped are rejected before reusing the cached transport. | ||
| - `DiscordService.createPlatformStatusSnapshot()` exposes the same status contract as `createDiscordPlatformStatusSnapshot(...)`: lifecycle/readiness, health, transport kind and ownership, default thread configuration, bootstrap verification state, and notifications channel dependency details, so callers can observe Discord wiring without reaching into internal options. | ||
| - Blank `defaultThreadId` and `notifications.channel` values are trimmed and ignored; the notifications channel defaults to `discord`. | ||
@@ -183,2 +189,3 @@ - The package never reads `process.env` directly. All configuration must enter through explicit options or DI. | ||
| - force one provider strategy beyond the webhook-first helper and exported transport contract | ||
| - expose internal provider helpers, normalized option tokens, or NestJS-style custom provider replacement seams for application imports | ||
| - translate one notification into multi-thread fan-out inside a single dispatch call | ||
@@ -197,2 +204,6 @@ | ||
| - `DiscordService` | ||
| - `DiscordService.send(message, options)` | ||
| - `DiscordService.sendMany(messages, options)` | ||
| - `DiscordService.sendNotification(notification, options)` | ||
| - `DiscordService.createPlatformStatusSnapshot()` | ||
| - `DiscordChannel` | ||
@@ -204,2 +215,4 @@ - `DISCORD` | ||
| 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. | ||
| ### Contracts and helpers | ||
@@ -235,2 +248,3 @@ | ||
| - `DiscordService.createPlatformStatusSnapshot()` | ||
| - `createDiscordPlatformStatusSnapshot(...)` | ||
@@ -237,0 +251,0 @@ - `DiscordLifecycleState` |
98720
6.52%1316
2.81%263
5.62%Updated
Updated
Updated