@gravito/flare 3.0.3 → 4.0.0

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.md CHANGED
@@ -2,15 +2,20 @@
2
2
 
3
3
  > Lightweight, high-performance notifications for Gravito with multi-channel delivery (mail, database, broadcast, Slack, SMS).
4
4
 
5
- **Status**: v0.1.0 - core features complete with multiple channel support.
5
+ **Status**: v3.4.0 - Production ready with advanced features (Retries, Metrics, Batching, Timeout, Rate Limiting, Preference Driver).
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Zero runtime overhead**: Pure type wrappers that delegate to channel drivers
10
- - **Multi-channel delivery**: Mail, database, broadcast, Slack, SMS
11
- - **Modular by design**: Install only the channels you need
12
- - **Queue support**: Works with `@gravito/stream` for async delivery
13
- - **AI-friendly**: Strong typing, clear JSDoc, and predictable APIs
10
+ - **Multi-channel delivery**: Mail, database, broadcast, Slack, SMS (Twilio & AWS SNS)
11
+ - **High Performance**: Parallel channel execution and batch sending capabilities
12
+ - **Reliability**: Built-in retry mechanism with exponential backoff and timeout protection
13
+ - **Observability**: Comprehensive metrics with Prometheus support
14
+ - **Developer Experience**: Strong typing, lifecycle hooks, and template system
15
+ - **Queue support**: Works with `@gravito/stream` for async delivery with Lazy Loading
16
+ - **Rate Limiting**: Channel-level rate limiting with Token Bucket algorithm
17
+ - **Preference Driver**: User notification preferences with automatic channel filtering
18
+ - **Middleware System**: Extensible middleware chain for custom notification processing
14
19
 
15
20
  ## Installation
16
21
 
@@ -20,208 +25,274 @@ bun add @gravito/flare
20
25
 
21
26
  ## Quick Start
22
27
 
23
- ### 1. Create a notification
24
-
25
- ```typescript
26
- import { Notification } from '@gravito/flare'
27
- import type { MailMessage, DatabaseNotification, Notifiable } from '@gravito/flare'
28
-
29
- class InvoicePaid extends Notification {
30
- constructor(private invoice: Invoice) {
31
- super()
32
- }
33
-
34
- via(user: Notifiable): string[] {
35
- return ['mail', 'database']
36
- }
37
-
38
- toMail(user: Notifiable): MailMessage {
39
- return {
40
- subject: 'Invoice Paid',
41
- view: 'emails.invoice-paid',
42
- data: { invoice: this.invoice },
43
- to: user.email,
44
- }
45
- }
46
-
47
- toDatabase(user: Notifiable): DatabaseNotification {
48
- return {
49
- type: 'invoice-paid',
50
- data: {
51
- invoice_id: this.invoice.id,
52
- amount: this.invoice.amount,
53
- },
54
- }
55
- }
56
- }
57
- ```
58
-
59
- ### 2. Configure OrbitFlare
28
+ ### 1. Configure OrbitFlare
60
29
 
61
30
  ```typescript
62
31
  import { PlanetCore } from '@gravito/core'
63
32
  import { OrbitFlare } from '@gravito/flare'
64
- import { OrbitSignal } from '@gravito/signal'
65
- import { OrbitStream } from '@gravito/stream'
66
33
 
67
34
  const core = await PlanetCore.boot({
68
35
  orbits: [
69
- OrbitSignal.configure({ /* ... */ }),
70
- OrbitStream.configure({ /* ... */ }),
71
36
  OrbitFlare.configure({
72
37
  enableMail: true,
73
38
  enableDatabase: true,
74
- enableBroadcast: true,
75
39
  channels: {
76
- slack: {
77
- webhookUrl: 'https://hooks.slack.com/services/...',
78
- },
40
+ slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL },
41
+ sms: {
42
+ provider: 'aws-sns', // or 'twilio'
43
+ region: 'us-east-1'
44
+ }
79
45
  },
46
+ // Optional: Global retry policy
47
+ retry: {
48
+ maxAttempts: 3,
49
+ backoff: 'exponential',
50
+ baseDelay: 1000
51
+ }
80
52
  }),
81
53
  ],
82
54
  })
83
55
  ```
84
56
 
85
- ### 3. Send a notification
86
-
87
- ```typescript
88
- // In a controller
89
- const notifications = c.get('notifications') as NotificationManager
90
-
91
- await notifications.send(user, new InvoicePaid(invoice))
92
- ```
93
-
94
- ## Queueing Notifications
57
+ ### 2. Create a notification
95
58
 
96
59
  ```typescript
97
- import { Notification, ShouldQueue } from '@gravito/flare'
60
+ import { Notification } from '@gravito/flare'
61
+ import type { MailMessage, Notifiable } from '@gravito/flare'
98
62
 
99
- class SendEmailNotification extends Notification implements ShouldQueue {
100
- queue = 'notifications'
101
- delay = 60 // delay by 60 seconds
63
+ class WelcomeNotification extends Notification {
64
+ constructor(private name: string) {
65
+ super()
66
+ }
102
67
 
103
68
  via(user: Notifiable): string[] {
104
- return ['mail']
69
+ return ['mail', 'database']
105
70
  }
106
71
 
107
72
  toMail(user: Notifiable): MailMessage {
108
73
  return {
109
74
  subject: 'Welcome!',
110
- to: user.email,
111
75
  view: 'emails.welcome',
76
+ data: { name: this.name },
77
+ to: user.email,
112
78
  }
113
79
  }
114
- }
115
80
 
116
- await notifications.send(user, new SendEmailNotification())
81
+ // Define per-notification retry logic
82
+ retry = {
83
+ maxAttempts: 5,
84
+ backoff: 'linear' as const
85
+ }
86
+ }
117
87
  ```
118
88
 
119
- ## Channels
89
+ ### 3. Send a notification
120
90
 
121
- ### Mail
91
+ ```typescript
92
+ const notifications = c.get('notifications') as NotificationManager
122
93
 
123
- Requires `@gravito/signal`:
94
+ // Simple send
95
+ const result = await notifications.send(user, new WelcomeNotification('Alice'))
124
96
 
125
- ```typescript
126
- via(user: Notifiable): string[] {
127
- return ['mail']
97
+ if (result.failed.length > 0) {
98
+ console.error('Some channels failed:', result.failed)
128
99
  }
129
100
 
130
- toMail(user: Notifiable): MailMessage {
131
- return {
132
- subject: 'Subject',
133
- view: 'emails.template',
134
- data: { /* ... */ },
135
- to: user.email,
101
+ // Batch send (high performance)
102
+ await notifications.sendBatch(users, new SystemUpdateNotification())
103
+ ```
104
+
105
+ ## Advanced Features
106
+
107
+ ### Retries
108
+ Configure retries globally or per-notification:
109
+
110
+ ```typescript
111
+ // Per-notification
112
+ class CriticalAlert extends Notification {
113
+ shouldRetry(attempt: number, error: Error): boolean {
114
+ return attempt < 5 && isRetryable(error)
136
115
  }
137
116
  }
138
117
  ```
139
118
 
140
- ### Database
119
+ ### Metrics
120
+ Enable metrics to track success rates and latency:
121
+
122
+ ```typescript
123
+ const metrics = new NotificationMetricsCollector()
124
+ notifications.setMetricsCollector(metrics)
125
+
126
+ // Export to Prometheus
127
+ const promData = toPrometheusFormat(metrics.getSummary())
128
+ ```
141
129
 
142
- Requires database support:
130
+ ### Hooks
131
+ Listen to lifecycle events:
143
132
 
144
133
  ```typescript
145
- via(user: Notifiable): string[] {
146
- return ['database']
147
- }
134
+ notifications.on('notification:failed', ({ notification, error }) => {
135
+ logger.error('Notification failed completely', error)
136
+ })
137
+ ```
148
138
 
149
- toDatabase(user: Notifiable): DatabaseNotification {
150
- return {
151
- type: 'notification-type',
152
- data: { /* ... */ },
139
+ ### Templates
140
+ Use `TemplatedNotification` for consistent messaging:
141
+
142
+ ```typescript
143
+ class OrderShipped extends TemplatedNotification {
144
+ constructor(order: Order) {
145
+ super('order-shipped', { orderId: order.id })
153
146
  }
154
147
  }
155
148
  ```
156
149
 
157
- ### Broadcast
158
-
159
- Requires `@gravito/radiance`:
150
+ ### Timeout Protection (v3.4.0)
151
+ All channels support timeout configuration to prevent slow responses from blocking notifications:
160
152
 
161
153
  ```typescript
162
- via(user: Notifiable): string[] {
163
- return ['broadcast']
164
- }
154
+ OrbitFlare.configure({
155
+ channels: {
156
+ slack: {
157
+ webhookUrl: process.env.SLACK_WEBHOOK_URL,
158
+ timeout: 5000, // 5 秒超時
159
+ onTimeout: (channel, notification) => {
160
+ console.error(`Timeout: ${channel}`)
161
+ }
162
+ }
163
+ }
164
+ })
165
+ ```
166
+
167
+ ### Rate Limiting (v3.4.0)
168
+ Protect downstream services with channel-level rate limiting:
165
169
 
166
- toBroadcast(user: Notifiable): BroadcastNotification {
167
- return {
168
- type: 'notification-type',
169
- data: { /* ... */ },
170
+ ```typescript
171
+ import { RateLimitMiddleware } from '@gravito/flare'
172
+
173
+ const rateLimiter = new RateLimitMiddleware({
174
+ email: {
175
+ maxPerSecond: 10,
176
+ maxPerMinute: 100,
177
+ maxPerHour: 1000
178
+ },
179
+ sms: {
180
+ maxPerSecond: 5
170
181
  }
171
- }
182
+ })
183
+
184
+ notifications.use(rateLimiter)
185
+
186
+ // 檢查狀態
187
+ const status = rateLimiter.getStatus('email')
188
+ console.log(`剩餘: ${status.second}/10`)
172
189
  ```
173
190
 
174
- ### Slack
191
+ ### Lazy Loading for Queue (v3.4.0)
192
+ Optimize queue serialization with Lazy Loading pattern:
175
193
 
176
194
  ```typescript
177
- via(user: Notifiable): string[] {
178
- return ['slack']
179
- }
195
+ import { LazyNotification } from '@gravito/flare'
180
196
 
181
- toSlack(user: Notifiable): SlackMessage {
182
- return {
183
- text: 'Notification message',
184
- channel: '#notifications',
197
+ class OrderConfirmation extends LazyNotification<Order> {
198
+ constructor(private orderId: string) {
199
+ super()
200
+ }
201
+
202
+ protected async loadData(notifiable: Notifiable): Promise<Order> {
203
+ return await db.orders.find(this.orderId)
204
+ }
205
+
206
+ via(user: Notifiable): string[] {
207
+ return ['mail', 'database']
208
+ }
209
+
210
+ async toMail(user: Notifiable): Promise<MailMessage> {
211
+ const order = await this.ensureLoaded(user)
212
+ return {
213
+ subject: `訂單確認 #${order.id}`,
214
+ view: 'emails.order-confirmation',
215
+ data: { order },
216
+ to: user.email,
217
+ }
185
218
  }
186
219
  }
187
220
  ```
188
221
 
189
- ### SMS
222
+ ### User Preferences (v3.4.0)
223
+ Respect user notification preferences automatically:
190
224
 
191
225
  ```typescript
192
- via(user: Notifiable): string[] {
193
- return ['sms']
194
- }
226
+ import { PreferenceMiddleware } from '@gravito/flare'
195
227
 
196
- toSms(user: Notifiable): SmsMessage {
197
- return {
198
- to: user.phone,
199
- message: 'Notification message',
228
+ // 方式 1: OrbitFlare 配置中啟用
229
+ OrbitFlare.configure({
230
+ enablePreference: true
231
+ })
232
+
233
+ // 方式 2: 使用自定義偏好提供者
234
+ const preferenceMiddleware = new PreferenceMiddleware({
235
+ async getUserPreferences(notifiable) {
236
+ const prefs = await db.userPreferences.find(notifiable.id)
237
+ return {
238
+ enabledChannels: prefs.enabledChannels,
239
+ disabledChannels: prefs.disabledChannels,
240
+ disabledNotifications: prefs.disabledNotifications
241
+ }
242
+ }
243
+ })
244
+
245
+ notifications.use(preferenceMiddleware)
246
+
247
+ // 方式 3: 在 Notifiable 物件中實作
248
+ class User implements Notifiable {
249
+ async getNotificationPreferences() {
250
+ return {
251
+ enabledChannels: ['email', 'slack'],
252
+ disabledChannels: ['sms']
253
+ }
200
254
  }
201
255
  }
202
256
  ```
203
257
 
204
- ## API Reference
258
+ ### Middleware Chain (v3.4.0)
259
+ Combine multiple middleware for powerful notification processing:
205
260
 
206
- ### Notification
261
+ ```typescript
262
+ import { RateLimitMiddleware, PreferenceMiddleware } from '@gravito/flare'
207
263
 
208
- Every notification should extend `Notification`.
264
+ // 建立中介層
265
+ const rateLimiter = new RateLimitMiddleware({ /* ... */ })
266
+ const preferenceFilter = new PreferenceMiddleware({ /* ... */ })
209
267
 
210
- #### Methods
268
+ // 註冊中介層(執行順序:由內而外)
269
+ notifications
270
+ .use(rateLimiter) // 先限流
271
+ .use(preferenceFilter) // 後過濾
211
272
 
212
- - `via(notifiable: Notifiable): string[]` - Choose delivery channels (required)
213
- - `toMail(notifiable: Notifiable): MailMessage` - Mail payload (optional)
214
- - `toDatabase(notifiable: Notifiable): DatabaseNotification` - Database payload (optional)
215
- - `toBroadcast(notifiable: Notifiable): BroadcastNotification` - Broadcast payload (optional)
216
- - `toSlack(notifiable: Notifiable): SlackMessage` - Slack payload (optional)
217
- - `toSms(notifiable: Notifiable): SmsMessage` - SMS payload (optional)
273
+ // 或在 OrbitFlare 配置中一次註冊
274
+ OrbitFlare.configure({
275
+ middleware: [rateLimiter, preferenceFilter]
276
+ })
277
+ ```
278
+
279
+ ## API Reference
218
280
 
219
281
  ### NotificationManager
220
282
 
221
283
  #### Methods
222
284
 
223
- - `send(notifiable: Notifiable, notification: Notification): Promise<void>` - Send notification
224
- - `channel(name: string, channel: NotificationChannel): void` - Register a custom channel
285
+ - `send(notifiable: Notifiable, notification: Notification, options?: SendOptions): Promise<NotificationResult>`
286
+ - `sendBatch(notifiables: Notifiable[], notification: Notification): Promise<BatchResult>`
287
+ - `sendBatchStream(iterator: AsyncIterator<Notifiable>, notification: Notification): Promise<BatchResult>`
288
+
289
+ ### Notification
290
+
291
+ #### Methods
292
+
293
+ - `via(notifiable: Notifiable): string[]` - Choose delivery channels
294
+ - `toMail`, `toDatabase`, `toBroadcast`, `toSlack`, `toSms` - Channel payloads
295
+ - `shouldRetry(attempt: number, error: Error): boolean` - Custom retry logic
225
296
 
226
297
  ## License
227
298
 
package/README.zh-TW.md CHANGED
@@ -1,35 +1,150 @@
1
- # @gravito/flare
1
+ # @gravito/flare 🌌
2
2
 
3
- > Gravito 的通知模組,支援郵件、資料庫、廣播、Slack、SMS 等多種通道。
3
+ > 輕量化、高效能的 Gravito 通知引擎,支援多通路發送(郵件、資料庫、廣播、Slack、SMS)。
4
4
 
5
- ## 安裝
5
+ `@gravito/flare` 是 Gravito 框架官方提供的通知引擎。它提供了一套簡潔且具表現力的 API,讓開發者能夠輕鬆地透過多種通路發送通知,並原生支援背景隊列處理。
6
+
7
+ **狀態**: v1.0.0 - 生產環境可用。
8
+
9
+ ## 🌟 核心特性
10
+
11
+ - **零執行負擔 (Zero Runtime Overhead)**:純 TypeScript 實作,直接委派給高效的通路驅動程式。
12
+ - **多通路支援**:單一通知可同時透過多個通路(Mail, DB, Slack 等)發送。
13
+ - **背景隊列支援**:與 `@gravito/stream` (OrbitStream) 無縫整合,處理高流量通知發送而不阻塞請求。
14
+ - **類型安全 (Type-Safe)**:為每個通路提供嚴格的訊息結構定義,確保資料完整性。
15
+ - **易於擴充**:可輕鬆註冊自定義的通知通路。
16
+ - **Galaxy 架構相容**:設計為標準的 Gravito Orbit,支援零配置整合。
17
+
18
+ ## 📦 安裝
6
19
 
7
20
  ```bash
8
21
  bun add @gravito/flare
9
22
  ```
10
23
 
11
- ## 快速開始
24
+ ## 🚀 快速上手
25
+
26
+ ### 1. 定義您的通知
27
+
28
+ 建立一個繼承自 `Notification` 的類別。實作 `via` 方法來指定發送通路,以及實作 `to[Channel]` 方法來定義發送內容。
12
29
 
13
30
  ```typescript
14
31
  import { Notification } from '@gravito/flare'
15
- import type { MailMessage, Notifiable } from '@gravito/flare'
32
+ import type { MailMessage, DatabaseNotification, Notifiable } from '@gravito/flare'
16
33
 
17
- class WelcomeUser extends Notification {
34
+ class OrderShipped extends Notification {
35
+ constructor(private order: any) {
36
+ super()
37
+ }
38
+
39
+ // 指定發送通路
18
40
  via(user: Notifiable): string[] {
19
- return ['mail']
41
+ return ['mail', 'database']
20
42
  }
21
43
 
44
+ // 郵件內容
22
45
  toMail(user: Notifiable): MailMessage {
23
46
  return {
24
- subject: 'Welcome!',
25
- view: 'emails.welcome',
47
+ subject: `訂單 #${this.order.id} 已出貨!`,
48
+ view: 'emails.order-shipped',
49
+ data: { order: this.order },
26
50
  to: user.email,
27
51
  }
28
52
  }
53
+
54
+ // 資料庫通知內容
55
+ toDatabase(user: Notifiable): DatabaseNotification {
56
+ return {
57
+ type: 'order_shipped',
58
+ data: {
59
+ order_id: this.order.id,
60
+ tracking_number: this.order.tracking,
61
+ },
62
+ }
63
+ }
29
64
  }
30
65
  ```
31
66
 
67
+ ### 2. 配置 OrbitFlare
68
+
69
+ 在 `PlanetCore` 啟動程序中註冊 `OrbitFlare`。
70
+
71
+ ```typescript
72
+ import { PlanetCore } from '@gravito/core'
73
+ import { OrbitFlare } from '@gravito/flare'
74
+
75
+ const core = await PlanetCore.boot({
76
+ orbits: [
77
+ OrbitFlare.configure({
78
+ enableMail: true,
79
+ enableDatabase: true,
80
+ channels: {
81
+ slack: {
82
+ webhookUrl: process.env.SLACK_WEBHOOK_URL,
83
+ },
84
+ },
85
+ }),
86
+ ],
87
+ })
88
+ ```
89
+
90
+ ### 3. 發送通知
91
+
92
+ 透過核心容器或上下文變數取得 `notifications` 管理器。
93
+
94
+ ```typescript
95
+ // 在業務邏輯或控制器中
96
+ const notifications = core.container.make('notifications')
97
+
98
+ await notifications.send(user, new OrderShipped(order))
99
+ ```
100
+
101
+ ## ⏳ 非同步隊列
102
+
103
+ 若要將通知放入背景發送,只需在通知類別中實作 `ShouldQueue` 介面。
104
+
32
105
  ```typescript
33
- const notifications = c.get('notifications')
34
- await notifications.send(user, new WelcomeUser())
106
+ import { Notification, ShouldQueue } from '@gravito/flare'
107
+
108
+ class WeeklyReport extends Notification implements ShouldQueue {
109
+ queue = 'notifications' // 可選:指定隊列名稱
110
+ delay = 3600 // 可選:延遲秒數
111
+
112
+ via(user: Notifiable): string[] {
113
+ return ['mail']
114
+ }
115
+
116
+ // ... toMail 實作
117
+ }
35
118
  ```
119
+
120
+ ## 🛠️ 支援的通路
121
+
122
+ | 通路 | 依賴模組 | 描述 |
123
+ |---|---|---|
124
+ | **Mail** | `@gravito/signal` | 透過配置的郵件驅動發送電子郵件。 |
125
+ | **Database** | `@gravito/atlas` | 將通知儲存至 `notifications` 資料表。 |
126
+ | **Broadcast**| `@gravito/radiance`| 透過 WebSockets 推送即時更新。 |
127
+ | **Slack** | 無 | 透過 Webhooks 發送訊息至 Slack。 |
128
+ | **SMS** | 供應商配置 | 透過配置的簡訊供應商發送簡訊。 |
129
+
130
+ ## 🧩 API 參考
131
+
132
+ ### `Notification` 基礎類別
133
+ - `via(notifiable)`:回傳通路名稱陣列。
134
+ - `toMail(notifiable)`:回傳 `MailMessage`。
135
+ - `toDatabase(notifiable)`:回傳 `DatabaseNotification`。
136
+ - `toBroadcast(notifiable)`:回傳 `BroadcastNotification`。
137
+ - `toSlack(notifiable)`:回傳 `SlackMessage`。
138
+ - `toSms(notifiable)`:回傳 `SmsMessage`。
139
+
140
+ ### `NotificationManager`
141
+ - `send(notifiable, notification)`:將通知發送至所有指定的通路。
142
+ - `channel(name, implementation)`:註冊自定義的發送通路。
143
+
144
+ ## 🤝 參與貢獻
145
+
146
+ 我們歡迎任何形式的貢獻!詳細資訊請參閱 [貢獻指南](../../CONTRIBUTING.md)。
147
+
148
+ ## 📄 開源授權
149
+
150
+ MIT © Carl Lee