@fluojs/notifications 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 +21 -0
- package/README.ko.md +192 -0
- package/README.md +192 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/module.d.ts +38 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +115 -0
- package/dist/service.d.ts +62 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +260 -0
- package/dist/status.d.ts +25 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +76 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +6 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +53 -0
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,192 @@
|
|
|
1
|
+
# @fluojs/notifications
|
|
2
|
+
|
|
3
|
+
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
|
+
|
|
5
|
+
fluo를 위한 채널 중립(notification channel-agnostic) 알림 오케스트레이션 패키지입니다. 알림 채널의 공통 계약을 고정하고, Nest-like 모듈 API를 제공하며, 선택적인 큐 기반 전달 심(seam)과 라이프사이클 이벤트 발행 심을 노출합니다.
|
|
6
|
+
|
|
7
|
+
## 목차
|
|
8
|
+
|
|
9
|
+
- [설치](#설치)
|
|
10
|
+
- [사용 시점](#사용-시점)
|
|
11
|
+
- [빠른 시작](#빠른-시작)
|
|
12
|
+
- [일반적인 패턴](#일반적인-패턴)
|
|
13
|
+
- [큐 기반 대량 전달](#큐-기반-대량-전달)
|
|
14
|
+
- [이벤트 발행자를 통한 라이프사이클 발행](#이벤트-발행자를-통한-라이프사이클-발행)
|
|
15
|
+
- [의도적인 제한 사항](#의도적인-제한-사항)
|
|
16
|
+
- [공개 API 개요](#공개-api-개요)
|
|
17
|
+
- [관련 패키지](#관련-패키지)
|
|
18
|
+
- [예제 소스](#예제-소스)
|
|
19
|
+
|
|
20
|
+
## 설치
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @fluojs/notifications
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 사용 시점
|
|
27
|
+
|
|
28
|
+
- 여러 알림 채널에 대해 하나의 공통 dispatch 계약을 두고, sibling 패키지끼리 직접 결합되지 않게 하고 싶을 때.
|
|
29
|
+
- 애플리케이션 코드가 provider SDK나 전송 구현 세부사항 대신 `NotificationsService`에 의존해야 할 때.
|
|
30
|
+
- 대량 전달은 큐로 넘길 수 있어야 하지만, 기본 경로는 여전히 인프로세스 직접 전달이어야 할 때.
|
|
31
|
+
- 알림 라이프사이클 이벤트(requested, queued, delivered, failed)를 별도의 이벤트 발행 심으로 관찰하고 싶을 때.
|
|
32
|
+
|
|
33
|
+
## 빠른 시작
|
|
34
|
+
|
|
35
|
+
### 1. foundation 모듈 등록
|
|
36
|
+
|
|
37
|
+
알림 모듈 등록은 `NotificationsModule.forRoot(...)` 또는 `NotificationsModule.forRootAsync(...)`로 구성합니다.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Module } from '@fluojs/core';
|
|
41
|
+
import {
|
|
42
|
+
NotificationsModule,
|
|
43
|
+
type NotificationChannel,
|
|
44
|
+
} from '@fluojs/notifications';
|
|
45
|
+
|
|
46
|
+
const emailChannel: NotificationChannel = {
|
|
47
|
+
channel: 'email',
|
|
48
|
+
async send(notification) {
|
|
49
|
+
console.log('email 전송', notification.subject, notification.payload);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
externalId: 'email-123',
|
|
53
|
+
metadata: { provider: 'demo-email' },
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
@Module({
|
|
59
|
+
imports: [
|
|
60
|
+
NotificationsModule.forRoot({
|
|
61
|
+
channels: [emailChannel],
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
export class AppModule {}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. `NotificationsService` 주입
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Inject } from '@fluojs/core';
|
|
72
|
+
import { NotificationsService } from '@fluojs/notifications';
|
|
73
|
+
|
|
74
|
+
export class WelcomeService {
|
|
75
|
+
constructor(@Inject(NotificationsService) private readonly notifications: NotificationsService) {}
|
|
76
|
+
|
|
77
|
+
async sendWelcomeEmail(userId: string, email: string) {
|
|
78
|
+
await this.notifications.dispatch({
|
|
79
|
+
channel: 'email',
|
|
80
|
+
recipients: [email],
|
|
81
|
+
subject: 'fluo에 오신 것을 환영합니다',
|
|
82
|
+
payload: {
|
|
83
|
+
template: 'welcome-email',
|
|
84
|
+
userId,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 일반적인 패턴
|
|
92
|
+
|
|
93
|
+
### 큐 기반 대량 전달
|
|
94
|
+
|
|
95
|
+
많은 알림을 백그라운드 워커로 넘기고 싶다면 선택적인 queue seam을 사용합니다.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
NotificationsModule.forRoot({
|
|
99
|
+
channels: [emailChannel],
|
|
100
|
+
queue: {
|
|
101
|
+
adapter: {
|
|
102
|
+
async enqueue(job) {
|
|
103
|
+
return queue.enqueue(job);
|
|
104
|
+
},
|
|
105
|
+
async enqueueMany(jobs) {
|
|
106
|
+
return Promise.all(jobs.map((job) => queue.enqueue(job)));
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
bulkThreshold: 50,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Behavioral contract 메모:
|
|
115
|
+
|
|
116
|
+
- 알림 개수가 `bulkThreshold` 이상이면 대량 큐 위임이 시작됩니다.
|
|
117
|
+
- `dispatch()`는 queue adapter가 구성되어 있어도 기본적으로 직접 전달을 유지합니다. 단건 알림을 큐로 보내려면 `dispatch(..., { queue: true })`를 사용합니다.
|
|
118
|
+
- 큐 기반 전달은 단건 dispatch에서는 opt-in이고, `dispatchMany(...)`에서는 threshold 기반으로 동작합니다.
|
|
119
|
+
- queue enqueue가 실패하면 서비스는 enqueue 에러를 다시 던지기 전에 결정적인 `notification.dispatch.failed` 라이프사이클 이벤트를 발행합니다.
|
|
120
|
+
- foundation 패키지는 특정 큐 구현을 가정하거나 import하지 않습니다.
|
|
121
|
+
|
|
122
|
+
### 이벤트 발행자를 통한 라이프사이클 발행
|
|
123
|
+
|
|
124
|
+
foundation 패키지를 `@fluojs/event-bus` 구현에 직접 결합하지 않고도 caller-visible 라이프사이클 이벤트를 발행할 수 있습니다.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
NotificationsModule.forRoot({
|
|
128
|
+
channels: [emailChannel],
|
|
129
|
+
events: {
|
|
130
|
+
publishLifecycleEvents: true,
|
|
131
|
+
publisher: {
|
|
132
|
+
async publish(event) {
|
|
133
|
+
await eventBus.publish(event);
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
발행되는 이벤트 이름:
|
|
141
|
+
|
|
142
|
+
- `notification.dispatch.requested`
|
|
143
|
+
- `notification.dispatch.queued`
|
|
144
|
+
- `notification.dispatch.delivered`
|
|
145
|
+
- `notification.dispatch.failed`
|
|
146
|
+
|
|
147
|
+
### 의도적인 제한 사항
|
|
148
|
+
|
|
149
|
+
foundation 패키지는 의도적으로 다음을 **포함하지 않습니다**:
|
|
150
|
+
|
|
151
|
+
- 내장 email, Slack, Discord 구현
|
|
152
|
+
- 직접적인 `process.env` 접근
|
|
153
|
+
- `@fluojs/queue` 또는 `@fluojs/event-bus`의 concrete runtime 타입 의존성
|
|
154
|
+
- provider별 payload 의미를 공유 계약에 인코딩하는 것
|
|
155
|
+
|
|
156
|
+
이 제한 사항은 leaf 패키지가 하나의 안정적인 오케스트레이션 계층 위에서 독립적으로 진화할 수 있도록 하는 package contract의 일부입니다.
|
|
157
|
+
|
|
158
|
+
## 공개 API 개요
|
|
159
|
+
|
|
160
|
+
### 핵심
|
|
161
|
+
|
|
162
|
+
- `NotificationsModule.forRoot(options)` / `NotificationsModule.forRootAsync(options)`
|
|
163
|
+
- `NotificationsService`
|
|
164
|
+
- `NOTIFICATIONS`
|
|
165
|
+
- `NOTIFICATION_CHANNELS`
|
|
166
|
+
|
|
167
|
+
### 계약(Contracts)
|
|
168
|
+
|
|
169
|
+
- `NotificationDispatchRequest`
|
|
170
|
+
- `NotificationChannel`
|
|
171
|
+
- `NotificationsQueueAdapter`
|
|
172
|
+
- `NotificationsEventPublisher`
|
|
173
|
+
- `NotificationLifecycleEvent`
|
|
174
|
+
|
|
175
|
+
### 상태 및 에러
|
|
176
|
+
|
|
177
|
+
- `createNotificationsPlatformStatusSnapshot(...)`
|
|
178
|
+
- `NotificationsConfigurationError`
|
|
179
|
+
- `NotificationChannelNotFoundError`
|
|
180
|
+
- `NotificationQueueNotConfiguredError`
|
|
181
|
+
|
|
182
|
+
## 관련 패키지
|
|
183
|
+
|
|
184
|
+
- `@fluojs/queue`: 대량 알림 전달을 백그라운드에서 처리하려는 경우 권장됩니다.
|
|
185
|
+
- `@fluojs/event-bus`: 알림 라이프사이클 이벤트를 애플리케이션 전반에 발행하려는 경우 권장됩니다.
|
|
186
|
+
- `@fluojs/config`: 환경 직접 접근 없이 `forRootAsync()`로 provider 설정을 전달하려는 경우 권장됩니다.
|
|
187
|
+
|
|
188
|
+
## 예제 소스
|
|
189
|
+
|
|
190
|
+
- `packages/notifications/src/module.test.ts`: 모듈 등록, async wiring, queue seam, tolerant bulk dispatch 예제.
|
|
191
|
+
- `packages/notifications/src/public-surface.test.ts`: 루트 export와 TypeScript-only 타입에 대한 공개 계약 검증 예제.
|
|
192
|
+
- `packages/notifications/src/status.test.ts`: health/readiness 계약 예제.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @fluojs/notifications
|
|
2
|
+
|
|
3
|
+
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
|
+
|
|
5
|
+
Channel-agnostic notification orchestration for fluo. It freezes the shared contract for notification channels, provides a Nest-like module API, and exposes optional queue-backed delivery and lifecycle event publication seams.
|
|
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
|
+
- [Queue-backed bulk delivery](#queue-backed-bulk-delivery)
|
|
14
|
+
- [Lifecycle publication through an event publisher](#lifecycle-publication-through-an-event-publisher)
|
|
15
|
+
- [Intentional limitations](#intentional-limitations)
|
|
16
|
+
- [Public API Overview](#public-api-overview)
|
|
17
|
+
- [Related Packages](#related-packages)
|
|
18
|
+
- [Example Sources](#example-sources)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @fluojs/notifications
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## When to Use
|
|
27
|
+
|
|
28
|
+
- When you want one shared dispatch contract for multiple notification channels without coupling sibling packages to each other.
|
|
29
|
+
- When application code should depend on `NotificationsService` instead of provider-specific SDKs or transport details.
|
|
30
|
+
- When bulk delivery may need to be offloaded to a queue, but direct in-process dispatch should still remain available.
|
|
31
|
+
- When notification lifecycle events (requested, queued, delivered, failed) should be observable through an event publication seam.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Register the foundation module
|
|
36
|
+
|
|
37
|
+
Register notifications with `NotificationsModule.forRoot(...)` or `NotificationsModule.forRootAsync(...)`.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Module } from '@fluojs/core';
|
|
41
|
+
import {
|
|
42
|
+
NotificationsModule,
|
|
43
|
+
type NotificationChannel,
|
|
44
|
+
} from '@fluojs/notifications';
|
|
45
|
+
|
|
46
|
+
const emailChannel: NotificationChannel = {
|
|
47
|
+
channel: 'email',
|
|
48
|
+
async send(notification) {
|
|
49
|
+
console.log('sending email', notification.subject, notification.payload);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
externalId: 'email-123',
|
|
53
|
+
metadata: { provider: 'demo-email' },
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
@Module({
|
|
59
|
+
imports: [
|
|
60
|
+
NotificationsModule.forRoot({
|
|
61
|
+
channels: [emailChannel],
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
export class AppModule {}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Inject `NotificationsService`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Inject } from '@fluojs/core';
|
|
72
|
+
import { NotificationsService } from '@fluojs/notifications';
|
|
73
|
+
|
|
74
|
+
export class WelcomeService {
|
|
75
|
+
constructor(@Inject(NotificationsService) private readonly notifications: NotificationsService) {}
|
|
76
|
+
|
|
77
|
+
async sendWelcomeEmail(userId: string, email: string) {
|
|
78
|
+
await this.notifications.dispatch({
|
|
79
|
+
channel: 'email',
|
|
80
|
+
recipients: [email],
|
|
81
|
+
subject: 'Welcome to fluo',
|
|
82
|
+
payload: {
|
|
83
|
+
template: 'welcome-email',
|
|
84
|
+
userId,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Common Patterns
|
|
92
|
+
|
|
93
|
+
### Queue-backed bulk delivery
|
|
94
|
+
|
|
95
|
+
Use the optional queue seam when many notifications should be deferred to background workers.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
NotificationsModule.forRoot({
|
|
99
|
+
channels: [emailChannel],
|
|
100
|
+
queue: {
|
|
101
|
+
adapter: {
|
|
102
|
+
async enqueue(job) {
|
|
103
|
+
return queue.enqueue(job);
|
|
104
|
+
},
|
|
105
|
+
async enqueueMany(jobs) {
|
|
106
|
+
return Promise.all(jobs.map((job) => queue.enqueue(job)));
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
bulkThreshold: 50,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Behavioral contract notes:
|
|
115
|
+
|
|
116
|
+
- Bulk queue delegation starts when the notification count reaches `bulkThreshold`.
|
|
117
|
+
- `dispatch()` stays direct by default even when a queue adapter is configured. Use `dispatch(..., { queue: true })` to opt one single notification into queue-backed delivery.
|
|
118
|
+
- Queue-backed delivery is opt-in for single dispatch and threshold-driven for `dispatchMany(...)`.
|
|
119
|
+
- When queue enqueue fails, the service emits deterministic `notification.dispatch.failed` lifecycle events before rethrowing the enqueue error to the caller.
|
|
120
|
+
- The foundation package does not assume or import a concrete queue implementation.
|
|
121
|
+
|
|
122
|
+
### Lifecycle publication through an event publisher
|
|
123
|
+
|
|
124
|
+
Publish caller-visible lifecycle events without coupling the foundation package to `@fluojs/event-bus` directly.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
NotificationsModule.forRoot({
|
|
128
|
+
channels: [emailChannel],
|
|
129
|
+
events: {
|
|
130
|
+
publishLifecycleEvents: true,
|
|
131
|
+
publisher: {
|
|
132
|
+
async publish(event) {
|
|
133
|
+
await eventBus.publish(event);
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Published event names:
|
|
141
|
+
|
|
142
|
+
- `notification.dispatch.requested`
|
|
143
|
+
- `notification.dispatch.queued`
|
|
144
|
+
- `notification.dispatch.delivered`
|
|
145
|
+
- `notification.dispatch.failed`
|
|
146
|
+
|
|
147
|
+
### Intentional limitations
|
|
148
|
+
|
|
149
|
+
The foundation package intentionally does **not**:
|
|
150
|
+
|
|
151
|
+
- ship built-in email, Slack, or Discord implementations
|
|
152
|
+
- inspect `process.env` directly
|
|
153
|
+
- depend on `@fluojs/queue` or `@fluojs/event-bus` concrete runtime types
|
|
154
|
+
- encode provider-specific payload semantics into the shared contract
|
|
155
|
+
|
|
156
|
+
These limitations are part of the package contract so leaf packages can evolve independently while sharing one stable orchestration layer.
|
|
157
|
+
|
|
158
|
+
## Public API Overview
|
|
159
|
+
|
|
160
|
+
### Core
|
|
161
|
+
|
|
162
|
+
- `NotificationsModule.forRoot(options)` / `NotificationsModule.forRootAsync(options)`
|
|
163
|
+
- `NotificationsService`
|
|
164
|
+
- `NOTIFICATIONS`
|
|
165
|
+
- `NOTIFICATION_CHANNELS`
|
|
166
|
+
|
|
167
|
+
### Contracts
|
|
168
|
+
|
|
169
|
+
- `NotificationDispatchRequest`
|
|
170
|
+
- `NotificationChannel`
|
|
171
|
+
- `NotificationsQueueAdapter`
|
|
172
|
+
- `NotificationsEventPublisher`
|
|
173
|
+
- `NotificationLifecycleEvent`
|
|
174
|
+
|
|
175
|
+
### Status and errors
|
|
176
|
+
|
|
177
|
+
- `createNotificationsPlatformStatusSnapshot(...)`
|
|
178
|
+
- `NotificationsConfigurationError`
|
|
179
|
+
- `NotificationChannelNotFoundError`
|
|
180
|
+
- `NotificationQueueNotConfiguredError`
|
|
181
|
+
|
|
182
|
+
## Related Packages
|
|
183
|
+
|
|
184
|
+
- `@fluojs/queue`: Recommended when bulk notification delivery should run in the background.
|
|
185
|
+
- `@fluojs/event-bus`: Recommended when notification lifecycle events should be published to the wider app.
|
|
186
|
+
- `@fluojs/config`: Recommended for passing provider configuration into `forRootAsync()` without direct environment access.
|
|
187
|
+
|
|
188
|
+
## Example Sources
|
|
189
|
+
|
|
190
|
+
- `packages/notifications/src/module.test.ts`: Module registration, async wiring, queue seam, and tolerant bulk dispatch examples.
|
|
191
|
+
- `packages/notifications/src/public-surface.test.ts`: Public contract verification for root exports and TypeScript-only types.
|
|
192
|
+
- `packages/notifications/src/status.test.ts`: Health/readiness contract examples.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error type for caller-visible notification module configuration failures.
|
|
3
|
+
*/
|
|
4
|
+
export declare class NotificationsConfigurationError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Thrown when a notification references a channel that is not registered.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NotificationChannelNotFoundError extends Error {
|
|
11
|
+
readonly channel: string;
|
|
12
|
+
constructor(channel: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when queue-backed delivery is requested without a configured queue adapter.
|
|
16
|
+
*/
|
|
17
|
+
export declare class NotificationQueueNotConfiguredError extends Error {
|
|
18
|
+
constructor();
|
|
19
|
+
}
|
|
20
|
+
//# 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,+BAAgC,SAAQ,KAAK;gBAC5C,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAIrC;AAED;;GAEG;AACH,qBAAa,mCAAoC,SAAQ,KAAK;;CAK7D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error type for caller-visible notification module configuration failures.
|
|
3
|
+
*/
|
|
4
|
+
export class NotificationsConfigurationError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'NotificationsConfigurationError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Thrown when a notification references a channel that is not registered.
|
|
13
|
+
*/
|
|
14
|
+
export class NotificationChannelNotFoundError extends Error {
|
|
15
|
+
constructor(channel) {
|
|
16
|
+
super(`No notification channel is registered for "${channel}".`);
|
|
17
|
+
this.channel = channel;
|
|
18
|
+
this.name = 'NotificationChannelNotFoundError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when queue-backed delivery is requested without a configured queue adapter.
|
|
24
|
+
*/
|
|
25
|
+
export class NotificationQueueNotConfiguredError extends Error {
|
|
26
|
+
constructor() {
|
|
27
|
+
super('Queue-backed notification delivery requires a configured queue adapter.');
|
|
28
|
+
this.name = 'NotificationQueueNotConfiguredError';
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NotificationChannelNotFoundError, NotificationQueueNotConfiguredError, NotificationsConfigurationError, } from './errors.js';
|
|
2
|
+
export { NotificationsModule } from './module.js';
|
|
3
|
+
export { NotificationsService } from './service.js';
|
|
4
|
+
export { createNotificationsPlatformStatusSnapshot } from './status.js';
|
|
5
|
+
export type { NotificationsPlatformStatusSnapshot, NotificationsStatusAdapterInput } from './status.js';
|
|
6
|
+
export { NOTIFICATION_CHANNELS, NOTIFICATIONS } from './tokens.js';
|
|
7
|
+
export type { NotificationChannel, NotificationChannelContext, NotificationChannelDelivery, NotificationDispatchBatchResult, NotificationDispatchFailure, NotificationDispatchManyOptions, NotificationDispatchOptions, NotificationDispatchRequest, NotificationDispatchResult, NotificationDispatchStatus, NotificationLifecycleEvent, NotificationLifecycleEventName, NotificationPayload, Notifications, NotificationsAsyncModuleOptions, NotificationsEventPublisher, NotificationsEventsOptions, NotificationsModuleOptions, NotificationsQueueAdapter, NotificationsQueueJob, NotificationsQueueOptions, } from './types.js';
|
|
8
|
+
//# 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,gCAAgC,EAChC,mCAAmC,EACnC,+BAA+B,GAChC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,yCAAyC,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,mCAAmC,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AACxG,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,+BAA+B,EAC/B,2BAA2B,EAC3B,+BAA+B,EAC/B,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,8BAA8B,EAC9B,mBAAmB,EACnB,aAAa,EACb,+BAA+B,EAC/B,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { NotificationChannelNotFoundError, NotificationQueueNotConfiguredError, NotificationsConfigurationError } from './errors.js';
|
|
2
|
+
export { NotificationsModule } from './module.js';
|
|
3
|
+
export { NotificationsService } from './service.js';
|
|
4
|
+
export { createNotificationsPlatformStatusSnapshot } from './status.js';
|
|
5
|
+
export { NOTIFICATION_CHANNELS, NOTIFICATIONS } from './tokens.js';
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AsyncModuleOptions } from '@fluojs/core';
|
|
2
|
+
import { type ModuleType } from '@fluojs/runtime';
|
|
3
|
+
import type { NotificationsModuleOptions } from './types.js';
|
|
4
|
+
/** Runtime module entrypoint for notification orchestration. */
|
|
5
|
+
export declare class NotificationsModule {
|
|
6
|
+
/**
|
|
7
|
+
* Registers notifications providers using static options.
|
|
8
|
+
*
|
|
9
|
+
* @param options Static notifications module options including channels and optional queue/event integrations.
|
|
10
|
+
* @returns A global module definition that exports {@link NotificationsService}, `NOTIFICATIONS`, and `NOTIFICATION_CHANNELS`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* NotificationsModule.forRoot({
|
|
15
|
+
* channels: [emailChannel],
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
static forRoot(options?: NotificationsModuleOptions): ModuleType;
|
|
20
|
+
/**
|
|
21
|
+
* Registers notifications providers from an async DI factory.
|
|
22
|
+
*
|
|
23
|
+
* @param options Async module options that resolve channels and optional integration seams.
|
|
24
|
+
* @returns A global module definition that memoizes async options resolution per module instance.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* NotificationsModule.forRootAsync({
|
|
29
|
+
* inject: [ConfigService],
|
|
30
|
+
* useFactory: (config) => ({
|
|
31
|
+
* channels: [createEmailChannel(config)],
|
|
32
|
+
* }),
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
static forRootAsync(options: AsyncModuleOptions<NotificationsModuleOptions>): ModuleType;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,cAAc,CAAC;AAErE,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EAMV,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AA2GpB,gEAAgE;AAChE,qBAAa,mBAAmB;IAC9B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,0BAA+B,GAAG,UAAU;IAIpE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,0BAA0B,CAAC,GAAG,UAAU;CAGzF"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { defineModule } from '@fluojs/runtime';
|
|
2
|
+
import { NotificationsConfigurationError } from './errors.js';
|
|
3
|
+
import { NotificationsService } from './service.js';
|
|
4
|
+
import { NOTIFICATIONS, NOTIFICATION_CHANNELS, NOTIFICATIONS_OPTIONS } from './tokens.js';
|
|
5
|
+
const DEFAULT_BULK_QUEUE_THRESHOLD = 10;
|
|
6
|
+
function normalizeNotificationsModuleOptions(options = {}) {
|
|
7
|
+
const channels = [...(options.channels ?? [])];
|
|
8
|
+
const seenChannelNames = new Set();
|
|
9
|
+
for (const channel of channels) {
|
|
10
|
+
if (seenChannelNames.has(channel.channel)) {
|
|
11
|
+
throw new NotificationsConfigurationError(`Duplicate notification channel registration detected for "${channel.channel}".`);
|
|
12
|
+
}
|
|
13
|
+
seenChannelNames.add(channel.channel);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
channels: Object.freeze(channels),
|
|
17
|
+
events: options.events ? {
|
|
18
|
+
publishLifecycleEvents: options.events.publishLifecycleEvents ?? true,
|
|
19
|
+
publisher: options.events.publisher
|
|
20
|
+
} : undefined,
|
|
21
|
+
queue: options.queue ? {
|
|
22
|
+
adapter: options.queue.adapter,
|
|
23
|
+
bulkThreshold: Math.max(1, options.queue.bulkThreshold ?? DEFAULT_BULK_QUEUE_THRESHOLD)
|
|
24
|
+
} : undefined
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createNotificationsRuntimeProviders(optionsProvider) {
|
|
28
|
+
return [optionsProvider, {
|
|
29
|
+
inject: [NOTIFICATIONS_OPTIONS],
|
|
30
|
+
provide: NOTIFICATION_CHANNELS,
|
|
31
|
+
useFactory: options => options.channels
|
|
32
|
+
}, NotificationsService, {
|
|
33
|
+
inject: [NotificationsService],
|
|
34
|
+
provide: NOTIFICATIONS,
|
|
35
|
+
useFactory: service => ({
|
|
36
|
+
dispatch: (notification, options) => service.dispatch(notification, options),
|
|
37
|
+
dispatchMany: (notifications, options) => service.dispatchMany(notifications, options)
|
|
38
|
+
})
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
function buildNotificationsProviders(options = {}) {
|
|
42
|
+
return createNotificationsRuntimeProviders({
|
|
43
|
+
provide: NOTIFICATIONS_OPTIONS,
|
|
44
|
+
useValue: normalizeNotificationsModuleOptions(options)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function buildNotificationsModule(options) {
|
|
48
|
+
class NotificationsRootModuleDefinition {}
|
|
49
|
+
return defineModule(NotificationsRootModuleDefinition, {
|
|
50
|
+
exports: [NotificationsService, NOTIFICATIONS, NOTIFICATION_CHANNELS],
|
|
51
|
+
global: true,
|
|
52
|
+
providers: buildNotificationsProviders(options)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function buildNotificationsModuleAsync(options) {
|
|
56
|
+
class NotificationsAsyncModuleDefinition {}
|
|
57
|
+
const factory = options.useFactory;
|
|
58
|
+
let cachedResult;
|
|
59
|
+
const memoizedFactory = (...deps) => {
|
|
60
|
+
if (!cachedResult) {
|
|
61
|
+
cachedResult = Promise.resolve(factory(...deps)).then(resolved => normalizeNotificationsModuleOptions(resolved));
|
|
62
|
+
}
|
|
63
|
+
return cachedResult;
|
|
64
|
+
};
|
|
65
|
+
return defineModule(NotificationsAsyncModuleDefinition, {
|
|
66
|
+
exports: [NotificationsService, NOTIFICATIONS, NOTIFICATION_CHANNELS],
|
|
67
|
+
global: true,
|
|
68
|
+
providers: createNotificationsRuntimeProviders({
|
|
69
|
+
inject: options.inject,
|
|
70
|
+
provide: NOTIFICATIONS_OPTIONS,
|
|
71
|
+
scope: 'singleton',
|
|
72
|
+
useFactory: (...deps) => memoizedFactory(...deps)
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Runtime module entrypoint for notification orchestration. */
|
|
78
|
+
export class NotificationsModule {
|
|
79
|
+
/**
|
|
80
|
+
* Registers notifications providers using static options.
|
|
81
|
+
*
|
|
82
|
+
* @param options Static notifications module options including channels and optional queue/event integrations.
|
|
83
|
+
* @returns A global module definition that exports {@link NotificationsService}, `NOTIFICATIONS`, and `NOTIFICATION_CHANNELS`.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* NotificationsModule.forRoot({
|
|
88
|
+
* channels: [emailChannel],
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
static forRoot(options = {}) {
|
|
93
|
+
return buildNotificationsModule(options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Registers notifications providers from an async DI factory.
|
|
98
|
+
*
|
|
99
|
+
* @param options Async module options that resolve channels and optional integration seams.
|
|
100
|
+
* @returns A global module definition that memoizes async options resolution per module instance.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* NotificationsModule.forRootAsync({
|
|
105
|
+
* inject: [ConfigService],
|
|
106
|
+
* useFactory: (config) => ({
|
|
107
|
+
* channels: [createEmailChannel(config)],
|
|
108
|
+
* }),
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
static forRootAsync(options) {
|
|
113
|
+
return buildNotificationsModuleAsync(options);
|
|
114
|
+
}
|
|
115
|
+
}
|