@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/dist/index.d.cts CHANGED
@@ -4,6 +4,32 @@ import { PlanetCore, GravitoOrbit } from '@gravito/core';
4
4
  * Notification system type definitions.
5
5
  */
6
6
 
7
+ /**
8
+ * 支援取消的發送選項
9
+ *
10
+ * 用於在發送通知時支援 AbortController 取消功能。
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const controller = new AbortController()
15
+ * setTimeout(() => controller.abort(), 5000)
16
+ *
17
+ * await channel.send(notification, notifiable, {
18
+ * signal: controller.signal
19
+ * })
20
+ * ```
21
+ *
22
+ * @public
23
+ */
24
+ interface AbortableSendOptions {
25
+ /**
26
+ * AbortSignal 用於取消請求
27
+ *
28
+ * 當 signal 被 abort 時,底層的網路請求(如 fetch)會被取消。
29
+ * 這對於實作 timeout 或使用者主動取消特別有用。
30
+ */
31
+ signal?: AbortSignal;
32
+ }
7
33
  /**
8
34
  * Notification channel interface.
9
35
  * @public
@@ -14,8 +40,9 @@ interface NotificationChannel {
14
40
  *
15
41
  * @param notification - The notification instance containing data.
16
42
  * @param notifiable - The recipient of the notification.
43
+ * @param options - Optional abort options (v4.0.0+)
17
44
  */
18
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
45
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
19
46
  }
20
47
  /**
21
48
  * Interface for recipients that can receive notifications.
@@ -34,6 +61,18 @@ interface Notifiable {
34
61
  * Optional list of preferred channels for this specific recipient.
35
62
  */
36
63
  preferredNotificationChannels?(): string[];
64
+ /**
65
+ * Optional notification preferences for this specific recipient.
66
+ * 取得用戶的通知偏好設定,用於控制通知的發送行為。
67
+ */
68
+ getNotificationPreferences?(): Promise<{
69
+ /** 啟用的通道列表(如果設定,則只允許這些通道) */
70
+ enabledChannels?: string[];
71
+ /** 禁用的通道列表(優先於 enabledChannels) */
72
+ disabledChannels?: string[];
73
+ /** 禁用的通知類型列表(Notification class names) */
74
+ disabledNotifications?: string[];
75
+ }>;
37
76
  }
38
77
  /**
39
78
  * Payload for email notifications.
@@ -118,6 +157,153 @@ interface SmsMessage {
118
157
  /** Sender ID or phone number */
119
158
  from?: string;
120
159
  }
160
+ /**
161
+ * Result of a single channel delivery attempt.
162
+ * @public
163
+ */
164
+ interface SendResult {
165
+ success: boolean;
166
+ channel: string;
167
+ error?: Error;
168
+ duration?: number;
169
+ }
170
+ /**
171
+ * Aggregated result of a notification delivery.
172
+ * @public
173
+ */
174
+ interface NotificationResult {
175
+ notification: string;
176
+ notifiable: string | number;
177
+ results: SendResult[];
178
+ allSuccess: boolean;
179
+ timestamp: Date;
180
+ }
181
+ /**
182
+ * Options for sending notifications.
183
+ * @public
184
+ */
185
+ interface SendOptions {
186
+ /** If true, throws an AggregateError when any channel fails */
187
+ throwOnError?: boolean;
188
+ /** Whether to send to channels in parallel (default: true) */
189
+ parallel?: boolean;
190
+ /** Maximum number of concurrent channel sends (default: unlimited) */
191
+ concurrency?: number;
192
+ /** Retry configuration */
193
+ retry?: Partial<RetryConfig> | boolean;
194
+ }
195
+ /**
196
+ * Retry configuration options.
197
+ * @public
198
+ */
199
+ interface RetryConfig {
200
+ /** Maximum number of retry attempts (default: 3) */
201
+ maxAttempts: number;
202
+ /** Base delay in milliseconds (default: 1000) */
203
+ baseDelay: number;
204
+ /** Backoff strategy: 'fixed' | 'linear' | 'exponential' (default: 'exponential') */
205
+ backoff: 'fixed' | 'linear' | 'exponential';
206
+ /** Maximum delay in milliseconds (default: 30000) */
207
+ maxDelay: number;
208
+ /** Function to determine if an error is retryable */
209
+ retryableErrors?: (error: Error) => boolean;
210
+ }
211
+ /**
212
+ * Interface for notifications that should retry on failure.
213
+ * @public
214
+ */
215
+ interface ShouldRetry {
216
+ /** Retry configuration for this notification */
217
+ retry?: Partial<RetryConfig>;
218
+ }
219
+ /**
220
+ * Result of a batch notification delivery.
221
+ * @public
222
+ */
223
+ interface BatchResult {
224
+ total: number;
225
+ success: number;
226
+ failed: number;
227
+ results: NotificationResult[];
228
+ duration: number;
229
+ }
230
+ interface NotificationHookPayload {
231
+ notification: Notification;
232
+ notifiable: Notifiable;
233
+ channels: string[];
234
+ }
235
+ interface ChannelHookPayload {
236
+ notification: Notification;
237
+ notifiable: Notifiable;
238
+ channel: string;
239
+ }
240
+ interface ChannelSuccessPayload extends ChannelHookPayload {
241
+ duration: number;
242
+ }
243
+ interface ChannelFailurePayload extends ChannelHookPayload {
244
+ error: Error;
245
+ duration: number;
246
+ }
247
+ interface NotificationCompletePayload {
248
+ notification: Notification;
249
+ notifiable: Notifiable;
250
+ results: SendResult[];
251
+ allSuccess: boolean;
252
+ totalDuration: number;
253
+ }
254
+ interface NotificationBatchStartPayload {
255
+ notification: Notification;
256
+ count: number;
257
+ }
258
+ interface NotificationBatchCompletePayload {
259
+ notification: Notification;
260
+ total: number;
261
+ success: number;
262
+ failed: number;
263
+ duration: number;
264
+ }
265
+ /**
266
+ * Notification preference provider interface.
267
+ *
268
+ * 用於提供用戶通知偏好的介面。可以從資料庫、快取或其他來源載入用戶的偏好設定。
269
+ *
270
+ * @public
271
+ */
272
+ interface NotificationPreference {
273
+ /**
274
+ * Get user notification preferences.
275
+ *
276
+ * 獲取用戶的通知偏好設定。
277
+ *
278
+ * @param notifiable - 接收通知的用戶
279
+ * @returns 用戶的通知偏好設定
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * class DatabasePreferenceProvider implements NotificationPreference {
284
+ * async getUserPreferences(notifiable: Notifiable) {
285
+ * const prefs = await db.query(
286
+ * 'SELECT * FROM user_preferences WHERE user_id = ?',
287
+ * [notifiable.getNotifiableId()]
288
+ * )
289
+ * return {
290
+ * enabledChannels: prefs.enabled_channels,
291
+ * disabledChannels: prefs.disabled_channels,
292
+ * disabledNotifications: prefs.disabled_notifications
293
+ * }
294
+ * }
295
+ * }
296
+ * ```
297
+ */
298
+ getUserPreferences(notifiable: Notifiable): Promise<{
299
+ /** 啟用的通道列表(如果設定,則只允許這些通道) */
300
+ enabledChannels: string[];
301
+ /** 禁用的通道列表(優先於 enabledChannels) */
302
+ disabledChannels: string[];
303
+ /** 禁用的通知類型列表(Notification class names) */
304
+ disabledNotifications: string[];
305
+ }>;
306
+ }
121
307
 
122
308
  /**
123
309
  * Marker interface for notifications that should be queued.
@@ -155,27 +341,27 @@ declare abstract class Notification {
155
341
  * Get mail message (optional).
156
342
  * Implement this if the notification will be sent via the mail channel.
157
343
  */
158
- toMail?(_notifiable: Notifiable): MailMessage;
344
+ toMail?(notifiable: Notifiable): MailMessage;
159
345
  /**
160
346
  * Get database notification (optional).
161
347
  * Implement this if the notification will be stored via the database channel.
162
348
  */
163
- toDatabase?(_notifiable: Notifiable): DatabaseNotification;
349
+ toDatabase?(notifiable: Notifiable): DatabaseNotification;
164
350
  /**
165
351
  * Get broadcast notification (optional).
166
352
  * Implement this if the notification will be sent via the broadcast channel.
167
353
  */
168
- toBroadcast?(_notifiable: Notifiable): BroadcastNotification;
354
+ toBroadcast?(notifiable: Notifiable): BroadcastNotification;
169
355
  /**
170
356
  * Get Slack message (optional).
171
357
  * Implement this if the notification will be sent via the Slack channel.
172
358
  */
173
- toSlack?(_notifiable: Notifiable): SlackMessage;
359
+ toSlack?(notifiable: Notifiable): SlackMessage;
174
360
  /**
175
361
  * Get SMS message (optional).
176
362
  * Implement this if the notification will be sent via the SMS channel.
177
363
  */
178
- toSms?(_notifiable: Notifiable): SmsMessage;
364
+ toSms?(notifiable: Notifiable): SmsMessage;
179
365
  /**
180
366
  * Check whether this notification should be queued.
181
367
  */
@@ -190,6 +376,19 @@ declare abstract class Notification {
190
376
  };
191
377
  }
192
378
 
379
+ /**
380
+ * Broadcast channel 配置選項。
381
+ */
382
+ interface BroadcastChannelConfig {
383
+ /**
384
+ * Timeout duration in milliseconds. Default: 10000ms (10s).
385
+ */
386
+ timeout?: number;
387
+ /**
388
+ * Callback function triggered when a timeout occurs.
389
+ */
390
+ onTimeout?: (channel: string, notification: Notification) => void;
391
+ }
193
392
  /**
194
393
  * Broadcast channel.
195
394
  *
@@ -197,12 +396,27 @@ declare abstract class Notification {
197
396
  */
198
397
  declare class BroadcastChannel implements NotificationChannel {
199
398
  private broadcastService;
399
+ private config?;
400
+ private timeoutChannel;
200
401
  constructor(broadcastService: {
201
402
  broadcast(channel: string, event: string, data: Record<string, unknown>): Promise<void>;
202
- });
203
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
403
+ }, config?: BroadcastChannelConfig | undefined);
404
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
204
405
  }
205
406
 
407
+ /**
408
+ * Database channel 配置選項。
409
+ */
410
+ interface DatabaseChannelConfig {
411
+ /**
412
+ * Timeout duration in milliseconds. Default: 10000ms (10s).
413
+ */
414
+ timeout?: number;
415
+ /**
416
+ * Callback function triggered when a timeout occurs.
417
+ */
418
+ onTimeout?: (channel: string, notification: Notification) => void;
419
+ }
206
420
  /**
207
421
  * Database channel.
208
422
  *
@@ -210,6 +424,8 @@ declare class BroadcastChannel implements NotificationChannel {
210
424
  */
211
425
  declare class DatabaseChannel implements NotificationChannel {
212
426
  private dbService;
427
+ private config?;
428
+ private timeoutChannel;
213
429
  constructor(dbService: {
214
430
  insertNotification(data: {
215
431
  notifiableId: string | number;
@@ -217,10 +433,23 @@ declare class DatabaseChannel implements NotificationChannel {
217
433
  type: string;
218
434
  data: Record<string, unknown>;
219
435
  }): Promise<void>;
220
- });
221
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
436
+ }, config?: DatabaseChannelConfig | undefined);
437
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
222
438
  }
223
439
 
440
+ /**
441
+ * Mail channel 配置選項。
442
+ */
443
+ interface MailChannelConfig {
444
+ /**
445
+ * Timeout duration in milliseconds. Default: 30000ms (30s).
446
+ */
447
+ timeout?: number;
448
+ /**
449
+ * Callback function triggered when a timeout occurs.
450
+ */
451
+ onTimeout?: (channel: string, notification: Notification) => void;
452
+ }
224
453
  /**
225
454
  * Mail channel.
226
455
  *
@@ -228,10 +457,12 @@ declare class DatabaseChannel implements NotificationChannel {
228
457
  */
229
458
  declare class MailChannel implements NotificationChannel {
230
459
  private mailService;
460
+ private config?;
461
+ private timeoutChannel;
231
462
  constructor(mailService: {
232
463
  send(message: MailMessage): Promise<void>;
233
- });
234
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
464
+ }, config?: MailChannelConfig | undefined);
465
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
235
466
  }
236
467
 
237
468
  /**
@@ -240,6 +471,14 @@ declare class MailChannel implements NotificationChannel {
240
471
  interface SlackChannelConfig {
241
472
  webhookUrl: string;
242
473
  defaultChannel?: string;
474
+ /**
475
+ * Timeout 時間(毫秒),預設 30000ms (30秒)。
476
+ */
477
+ timeout?: number;
478
+ /**
479
+ * Timeout 發生時的回調函數。
480
+ */
481
+ onTimeout?: (channel: string, notification: Notification) => void;
243
482
  }
244
483
  /**
245
484
  * Slack channel.
@@ -248,8 +487,9 @@ interface SlackChannelConfig {
248
487
  */
249
488
  declare class SlackChannel implements NotificationChannel {
250
489
  private config;
490
+ private timeoutChannel;
251
491
  constructor(config: SlackChannelConfig);
252
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
492
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
253
493
  }
254
494
 
255
495
  /**
@@ -260,141 +500,852 @@ interface SmsChannelConfig {
260
500
  apiKey?: string;
261
501
  apiSecret?: string;
262
502
  from?: string;
503
+ region?: string;
504
+ accessKeyId?: string;
505
+ secretAccessKey?: string;
506
+ /**
507
+ * Timeout duration in milliseconds. Default: 30000ms (30s).
508
+ */
509
+ timeout?: number;
510
+ /**
511
+ * Callback function triggered when a timeout occurs.
512
+ */
513
+ onTimeout?: (channel: string, notification: Notification) => void;
263
514
  }
264
515
  /**
265
516
  * SMS channel.
266
517
  *
267
518
  * Sends notifications via an SMS provider.
268
- * Only a basic interface is provided; real implementations should be extended per provider.
269
519
  */
270
520
  declare class SmsChannel implements NotificationChannel {
271
521
  private config;
522
+ private timeoutChannel;
272
523
  constructor(config: SmsChannelConfig);
273
- send(notification: Notification, notifiable: Notifiable): Promise<void>;
524
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
274
525
  /**
275
- * Send SMS via Twilio.
526
+ * Send SMS via Twilio with AbortSignal support.
276
527
  */
277
528
  private sendViaTwilio;
278
529
  /**
279
- * Send SMS via AWS SNS.
530
+ * Send SMS via AWS SNS with AbortSignal support.
280
531
  */
281
532
  private sendViaAwsSns;
282
533
  }
283
534
 
284
535
  /**
285
- * Notification manager.
286
- *
287
- * Responsible for managing notification channels and delivering notifications.
536
+ * Configuration options for TimeoutChannel.
288
537
  */
289
- declare class NotificationManager {
290
- private core;
538
+ interface TimeoutConfig {
291
539
  /**
292
- * Channel registry.
540
+ * Timeout duration in milliseconds.
293
541
  */
294
- private channels;
542
+ timeout: number;
295
543
  /**
296
- * Queue manager (optional, injected by `orbit-queue`).
544
+ * Optional callback function triggered when a timeout occurs.
545
+ *
546
+ * @param channel - The name of the channel that timed out.
547
+ * @param notification - The notification instance that was being sent.
297
548
  */
298
- private queueManager?;
299
- constructor(core: PlanetCore);
549
+ onTimeout?: (channel: string, notification: Notification) => void;
550
+ }
551
+ /**
552
+ * Exception thrown when a notification send operation exceeds the configured timeout.
553
+ */
554
+ declare class TimeoutError extends Error {
555
+ constructor(message: string);
556
+ }
557
+ /**
558
+ * Exception thrown when a request is aborted by an external AbortController signal.
559
+ */
560
+ declare class AbortError extends Error {
561
+ constructor(message: string);
562
+ }
563
+ /**
564
+ * Decorator for notification channels that adds timeout and cancellation support.
565
+ *
566
+ * Implements actual request cancellation using AbortController and Promise.race.
567
+ * Compatible with v4.0.0+ cancellation architecture.
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * const slackChannel = new SlackChannel(config);
572
+ * const timeoutChannel = new TimeoutChannel(slackChannel, {
573
+ * timeout: 5000,
574
+ * onTimeout: (channel, notification) => {
575
+ * console.error(`Channel ${channel} timed out`);
576
+ * }
577
+ * });
578
+ *
579
+ * // Send with timeout
580
+ * await timeoutChannel.send(notification, user);
581
+ *
582
+ * // Support external manual abort
583
+ * const controller = new AbortController();
584
+ * setTimeout(() => controller.abort(), 3000);
585
+ * await timeoutChannel.send(notification, user, { signal: controller.signal });
586
+ * ```
587
+ *
588
+ * @remarks
589
+ * In v4.0.0, this class uses AbortController to allow underlying fetch requests
590
+ * to be physically cancelled, while using Promise.race to ensure the timeout
591
+ * error is thrown immediately without waiting for the underlying request to finish.
592
+ */
593
+ declare class TimeoutChannel implements NotificationChannel {
594
+ private inner;
595
+ private config;
596
+ constructor(inner: NotificationChannel, config: TimeoutConfig);
300
597
  /**
301
- * Register a notification channel.
598
+ * Sends a notification through the inner channel with a timeout guard.
302
599
  *
303
- * @param name - The name of the channel.
304
- * @param channel - The channel instance.
600
+ * @param notification - The notification to send.
601
+ * @param notifiable - The recipient of the notification.
602
+ * @param options - Send options including an optional AbortSignal.
603
+ * @returns A promise that resolves when the notification is sent.
604
+ * @throws {TimeoutError} Thrown if the operation exceeds the configured timeout.
605
+ * @throws {AbortError} Thrown if the operation is aborted via the provided signal.
305
606
  */
306
- channel(name: string, channel: NotificationChannel): void;
607
+ send(notification: Notification, notifiable: Notifiable, options?: AbortableSendOptions): Promise<void>;
608
+ }
609
+
610
+ interface NotificationMetric {
611
+ notification: string;
612
+ channel: string;
613
+ success: boolean;
614
+ duration: number;
615
+ timestamp: Date;
616
+ error?: string;
617
+ retryCount?: number;
618
+ }
619
+ interface MetricsSummary {
620
+ totalSent: number;
621
+ totalSuccess: number;
622
+ totalFailed: number;
623
+ avgDuration: number;
624
+ byChannel: Record<string, {
625
+ sent: number;
626
+ success: number;
627
+ failed: number;
628
+ avgDuration: number;
629
+ }>;
630
+ byNotification: Record<string, {
631
+ sent: number;
632
+ success: number;
633
+ failed: number;
634
+ avgDuration: number;
635
+ }>;
636
+ }
637
+ declare class NotificationMetricsCollector {
638
+ private metrics;
639
+ private readonly maxHistory;
640
+ constructor(maxHistory?: number);
641
+ record(metric: NotificationMetric): void;
642
+ getSummary(since?: Date): MetricsSummary;
643
+ getRecentFailures(limit?: number): NotificationMetric[];
644
+ getSlowNotifications(threshold: number, limit?: number): NotificationMetric[];
645
+ clear(): void;
646
+ }
647
+
648
+ declare function toPrometheusFormat(summary: MetricsSummary): string;
649
+
650
+ /**
651
+ * Middleware type definitions for notification channels.
652
+ *
653
+ * 中介層允許在通知發送前後執行自訂邏輯,例如:
654
+ * - 限流(Rate Limiting)
655
+ * - 日誌記錄
656
+ * - 資料轉換
657
+ * - 權限檢查
658
+ * - 錯誤處理
659
+ *
660
+ * @packageDocumentation
661
+ */
662
+
663
+ /**
664
+ * 中介層優先級常數
665
+ *
666
+ * 提供預定義的優先級值,用於控制中介層的執行順序。
667
+ * 優先級越高(數字越大)越先執行。
668
+ *
669
+ * @example
670
+ * ```typescript
671
+ * const securityMiddleware: ChannelMiddleware = {
672
+ * name: 'security',
673
+ * priority: MiddlewarePriority.SECURITY, // 最高優先級,最先執行
674
+ * async handle(notification, notifiable, channel, next) {
675
+ * // 安全檢查...
676
+ * await next()
677
+ * }
678
+ * }
679
+ * ```
680
+ *
681
+ * @public
682
+ */
683
+ declare const MiddlewarePriority: {
684
+ /** 最高優先級:安全檢查 (100) */
685
+ readonly SECURITY: 100;
686
+ /** 高優先級:限流 (80) */
687
+ readonly RATE_LIMIT: 80;
688
+ /** 中等優先級:驗證 (50) */
689
+ readonly VALIDATION: 50;
690
+ /** 預設優先級 (0) */
691
+ readonly DEFAULT: 0;
692
+ /** 低優先級:日誌記錄 (-50) */
693
+ readonly LOGGING: -50;
694
+ /** 最低優先級:監控 (-100) */
695
+ readonly MONITORING: -100;
696
+ };
697
+ /**
698
+ * 中介層優先級值型別
699
+ *
700
+ * @public
701
+ */
702
+ type MiddlewarePriorityValue = (typeof MiddlewarePriority)[keyof typeof MiddlewarePriority];
703
+ /**
704
+ * Channel middleware interface.
705
+ *
706
+ * 中介層提供一個統一的擴展機制,允許在通知發送到特定通道時執行額外的邏輯。
707
+ * 中介層可以:
708
+ * - 在發送前檢查或修改通知
709
+ * - 決定是否允許發送(透過是否呼叫 next)
710
+ * - 在發送後執行清理或記錄操作
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * // 建立一個記錄中介層
715
+ * const loggingMiddleware: ChannelMiddleware = {
716
+ * name: 'logging',
717
+ * async handle(notification, notifiable, channel, next) {
718
+ * console.log(`Sending ${notification.constructor.name} via ${channel}`)
719
+ * await next()
720
+ * console.log(`Sent successfully`)
721
+ * }
722
+ * }
723
+ *
724
+ * // 註冊到 NotificationManager
725
+ * manager.use(loggingMiddleware)
726
+ * ```
727
+ *
728
+ * @public
729
+ */
730
+ interface ChannelMiddleware {
307
731
  /**
308
- * Register the queue manager (called by `orbit-queue`).
732
+ * Middleware name (for debugging and identification).
309
733
  *
310
- * @param manager - The queue manager implementation.
734
+ * 中介層名稱,用於除錯和識別。
311
735
  */
312
- setQueueManager(manager: NotificationManager['queueManager']): void;
736
+ name: string;
313
737
  /**
314
- * Send a notification.
738
+ * Middleware priority (optional, default: 0).
315
739
  *
316
- * @param notifiable - The recipient of the notification.
317
- * @param notification - The notification instance.
318
- * @returns A promise that resolves when the notification is sent or queued.
740
+ * 中介層優先級,數字越大越先執行。
741
+ * 預設為 0。相同優先級的中介層按照註冊順序執行。
742
+ *
743
+ * @default 0
319
744
  *
320
745
  * @example
321
746
  * ```typescript
322
- * await notificationManager.send(user, new InvoicePaid(invoice))
747
+ * // 安全檢查應該最先執行
748
+ * const securityMiddleware: ChannelMiddleware = {
749
+ * name: 'security',
750
+ * priority: MiddlewarePriority.SECURITY, // 100
751
+ * async handle(notification, notifiable, channel, next) {
752
+ * // 檢查權限...
753
+ * await next()
754
+ * }
755
+ * }
756
+ *
757
+ * // 限流應該在安全檢查之後
758
+ * const rateLimitMiddleware: ChannelMiddleware = {
759
+ * name: 'rate-limit',
760
+ * priority: MiddlewarePriority.RATE_LIMIT, // 80
761
+ * async handle(notification, notifiable, channel, next) {
762
+ * // 檢查限流...
763
+ * await next()
764
+ * }
765
+ * }
766
+ *
767
+ * // 日誌記錄應該最後執行
768
+ * const loggingMiddleware: ChannelMiddleware = {
769
+ * name: 'logging',
770
+ * priority: MiddlewarePriority.LOGGING, // -50
771
+ * async handle(notification, notifiable, channel, next) {
772
+ * console.log('Sending...')
773
+ * await next()
774
+ * console.log('Sent!')
775
+ * }
776
+ * }
323
777
  * ```
324
778
  */
325
- send(notifiable: Notifiable, notification: Notification): Promise<void>;
779
+ priority?: number;
326
780
  /**
327
- * Send immediately (without queue).
781
+ * Handle the notification before it's sent to the channel.
328
782
  *
329
- * @param notifiable - The recipient.
330
- * @param notification - The notification.
331
- * @param channels - The list of channels to send via.
332
- */
333
- private sendNow;
334
- /**
335
- * Serialize notification (for queuing).
783
+ * 處理通知發送請求。此方法會在通知發送到通道之前被呼叫。
336
784
  *
337
- * @param notification - The notification to serialize.
338
- * @returns A plain object representation of the notification.
785
+ * @param notification - The notification instance being sent
786
+ * @param notifiable - The recipient of the notification
787
+ * @param channel - The channel name (e.g., 'mail', 'sms')
788
+ * @param next - Call this function to continue the middleware chain and send the notification
789
+ *
790
+ * @throws {Error} 可以拋出錯誤來阻止通知發送
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * async handle(notification, notifiable, channel, next) {
795
+ * // 發送前的邏輯
796
+ * if (shouldBlock(channel)) {
797
+ * throw new Error('Channel blocked')
798
+ * }
799
+ *
800
+ * // 繼續執行
801
+ * await next()
802
+ *
803
+ * // 發送後的邏輯
804
+ * console.log('Sent successfully')
805
+ * }
806
+ * ```
339
807
  */
340
- private serializeNotification;
808
+ handle(notification: Notification, notifiable: Notifiable, channel: string, next: () => Promise<void>): Promise<void>;
341
809
  }
342
810
 
343
811
  /**
344
- * OrbitFlare options.
345
- */
346
- /**
347
- * Options for configuring OrbitFlare.
348
- * @public
812
+ * User preference middleware for notification channels.
813
+ *
814
+ * This middleware filters notification channels and notification types based on user settings,
815
+ * allowing users to customize how they receive notifications.
816
+ *
817
+ * @packageDocumentation
349
818
  */
350
- interface OrbitFlareOptions {
351
- /** Enable or disable the email notification channel. */
352
- enableMail?: boolean;
353
- /** Enable or disable the database storage notification channel. */
354
- enableDatabase?: boolean;
355
- /** Enable or disable the real-time broadcast notification channel. */
356
- enableBroadcast?: boolean;
357
- /** Enable or disable the Slack notification channel. */
358
- enableSlack?: boolean;
359
- /** Enable or disable the SMS notification channel. */
360
- enableSms?: boolean;
361
- /** Custom channel configuration records for flexible extension. */
362
- channels?: Record<string, unknown>;
363
- }
819
+
364
820
  /**
365
- * OrbitFlare is the official notification orbit for Gravito.
366
- * It provides a unified API for sending notifications across multiple channels
367
- * (Mail, Database, Broadcast, Slack, SMS) and supports background queuing.
821
+ * User preference middleware for filtering notification channels.
822
+ *
823
+ * This middleware filters notifications based on user preferences, supporting:
824
+ * - Enabling specific channels (enabledChannels)
825
+ * - Disabling specific channels (disabledChannels, takes precedence over enabledChannels)
826
+ * - Disabling specific notification types (disabledNotifications)
368
827
  *
369
828
  * @example
370
829
  * ```typescript
371
- * const flare = new OrbitFlare({
372
- * enableSlack: true,
373
- * channels: { slack: { webhookUrl: '...' } }
374
- * });
375
- * core.addOrbit(flare);
830
+ * // Using Notifiable preferences
831
+ * const middleware = new PreferenceMiddleware();
832
+ * manager.use(middleware);
376
833
  *
377
- * // Usage in controller
378
- * await ctx.get('notifications').send(user, new WelcomeNotification());
834
+ * // Using custom preference provider
835
+ * const dbProvider: NotificationPreference = {
836
+ * async getUserPreferences(notifiable) {
837
+ * return await db.getUserPrefs(notifiable.getNotifiableId());
838
+ * }
839
+ * };
840
+ * const middleware = new PreferenceMiddleware(dbProvider);
841
+ * manager.use(middleware);
379
842
  * ```
843
+ *
380
844
  * @public
381
845
  */
382
- declare class OrbitFlare implements GravitoOrbit {
383
- private options;
384
- constructor(options?: OrbitFlareOptions);
846
+ declare class PreferenceMiddleware implements ChannelMiddleware {
847
+ private preferenceProvider?;
848
+ private logger?;
385
849
  /**
386
- * Configure OrbitFlare.
850
+ * Middleware name.
851
+ */
852
+ readonly name = "preference";
853
+ /**
854
+ * Middleware priority (medium priority for validation).
855
+ */
856
+ readonly priority: 50;
857
+ /**
858
+ * Create a new PreferenceMiddleware instance.
387
859
  *
388
- * @param options - The OrbitFlare configuration options.
389
- * @returns A new OrbitFlare instance.
860
+ * @param preferenceProvider - Optional preference provider; uses Notifiable method if not provided.
861
+ * @param logger - Optional logger instance for recording errors.
862
+ *
863
+ * @example
864
+ * ```typescript
865
+ * // Without provider (reads from Notifiable.getNotificationPreferences)
866
+ * const middleware = new PreferenceMiddleware();
867
+ *
868
+ * // Using database provider and logger
869
+ * const middleware = new PreferenceMiddleware(new DatabasePreferenceProvider(), logger);
870
+ * ```
390
871
  */
391
- static configure(options?: OrbitFlareOptions): OrbitFlare;
872
+ constructor(preferenceProvider?: NotificationPreference | undefined, logger?: {
873
+ error: (message: string, ...args: unknown[]) => void;
874
+ } | undefined);
392
875
  /**
393
- * Install OrbitFlare into PlanetCore.
876
+ * Handle the notification and apply user preference filtering.
394
877
  *
395
- * @param core - The PlanetCore instance.
878
+ * Processes the notification and filters based on user preferences:
879
+ * 1. If notification type is in disabledNotifications, it is skipped.
880
+ * 2. If channel is in disabledChannels, it is skipped.
881
+ * 3. If enabledChannels is set, only channels in that list are allowed.
882
+ * 4. If preference loading fails, the notification is allowed as a fallback.
883
+ *
884
+ * @param notification - The notification to send.
885
+ * @param notifiable - The recipient.
886
+ * @param channel - The channel name.
887
+ * @param next - Callback to proceed to the next middleware or send operation.
888
+ * @returns A promise that resolves when processing is complete.
396
889
  */
397
- install(core: PlanetCore): Promise<void>;
890
+ handle(notification: Notification, notifiable: Notifiable, channel: string, next: () => Promise<void>): Promise<void>;
891
+ /**
892
+ * Get user preferences from Notifiable or custom provider.
893
+ *
894
+ * Priority: Notifiable.getNotificationPreferences > preferenceProvider.
895
+ *
896
+ * @param notifiable - The recipient.
897
+ * @returns The user preferences or null if not found.
898
+ */
899
+ private getPreferences;
900
+ /**
901
+ * Check if notification type is disabled by user.
902
+ *
903
+ * @param notification - The notification instance.
904
+ * @param preferences - User preferences.
905
+ * @returns True if the notification is disabled.
906
+ */
907
+ private isNotificationDisabled;
908
+ /**
909
+ * Check if channel is allowed by user preferences.
910
+ *
911
+ * Priority:
912
+ * 1. disabledChannels (if listed, it is denied)
913
+ * 2. enabledChannels (if set, only listed are allowed)
914
+ * 3. Allow all if neither are set.
915
+ *
916
+ * @param channel - The channel name.
917
+ * @param preferences - User preferences.
918
+ * @returns True if the channel is allowed.
919
+ */
920
+ private isChannelAllowed;
921
+ }
922
+
923
+ /**
924
+ * Rate limiting middleware for notification channels.
925
+ *
926
+ * 此中介層使用 Token Bucket 演算法對不同的通知通道進行限流,
927
+ * 防止短時間內發送過多通知,保護下游服務。
928
+ *
929
+ * @packageDocumentation
930
+ */
931
+
932
+ /**
933
+ * Rate limit configuration for a specific channel.
934
+ *
935
+ * 通道限流配置,支援多個時間窗口的限流。
936
+ *
937
+ * @public
938
+ */
939
+ interface ChannelRateLimitConfig {
940
+ /**
941
+ * Maximum requests per second.
942
+ * 每秒最大請求數
943
+ */
944
+ maxPerSecond?: number;
945
+ /**
946
+ * Maximum requests per minute.
947
+ * 每分鐘最大請求數
948
+ */
949
+ maxPerMinute?: number;
950
+ /**
951
+ * Maximum requests per hour.
952
+ * 每小時最大請求數
953
+ */
954
+ maxPerHour?: number;
955
+ }
956
+ /**
957
+ * Rate limit configuration for all channels.
958
+ *
959
+ * 所有通道的限流配置,key 為通道名稱。
960
+ *
961
+ * @public
962
+ */
963
+ interface RateLimitConfig {
964
+ [channel: string]: ChannelRateLimitConfig;
965
+ }
966
+ /**
967
+ * Cache store interface for rate limiting.
968
+ *
969
+ * 快取儲存介面,用於支援分散式限流。
970
+ * 預設使用記憶體儲存,可替換為 Redis 等分散式快取。
971
+ *
972
+ * @public
973
+ */
974
+ interface CacheStore {
975
+ get<T>(key: string): Promise<T | null>;
976
+ put<T>(key: string, value: T, ttl: number): Promise<void>;
977
+ forget(key: string): Promise<void>;
978
+ }
979
+ /**
980
+ * Simple in-memory cache store implementation.
981
+ *
982
+ * 簡單的記憶體快取實作,用於單機環境。
983
+ * 生產環境建議使用 Redis 等分散式快取。
984
+ *
985
+ * @public
986
+ */
987
+ declare class MemoryStore implements CacheStore {
988
+ private cache;
989
+ private cleanupInterval?;
990
+ constructor(cleanupIntervalMs?: number);
991
+ get<T>(key: string): Promise<T | null>;
992
+ put<T>(key: string, value: T, ttl: number): Promise<void>;
993
+ forget(key: string): Promise<void>;
994
+ /**
995
+ * 清理所有資源,停止清理計時器
996
+ */
997
+ destroy(): void;
998
+ }
999
+ /**
1000
+ * Rate limiting middleware using Token Bucket algorithm.
1001
+ *
1002
+ * 此中介層為每個通道維護獨立的 Token Bucket,
1003
+ * 支援每秒、每分鐘、每小時三種時間窗口的限流。
1004
+ *
1005
+ * @example
1006
+ * ```typescript
1007
+ * const rateLimiter = new RateLimitMiddleware({
1008
+ * email: {
1009
+ * maxPerSecond: 10,
1010
+ * maxPerMinute: 100,
1011
+ * maxPerHour: 1000
1012
+ * },
1013
+ * sms: {
1014
+ * maxPerSecond: 5,
1015
+ * maxPerMinute: 50
1016
+ * }
1017
+ * })
1018
+ *
1019
+ * manager.use(rateLimiter)
1020
+ * ```
1021
+ *
1022
+ * @public
1023
+ */
1024
+ declare class RateLimitMiddleware implements ChannelMiddleware {
1025
+ private config;
1026
+ /**
1027
+ * Middleware name.
1028
+ */
1029
+ readonly name = "rate-limit";
1030
+ /**
1031
+ * Middleware priority (high priority, executes early in the chain).
1032
+ * 中介層優先級(高優先級,在鏈中較早執行)
1033
+ */
1034
+ readonly priority: 80;
1035
+ /**
1036
+ * Token buckets for each channel and time window.
1037
+ * Key format: `{channel}:{window}` (e.g., 'email:second', 'sms:minute')
1038
+ */
1039
+ private buckets;
1040
+ /**
1041
+ * Cache store for rate limit state (memory or distributed).
1042
+ */
1043
+ private store;
1044
+ /**
1045
+ * Create a new RateLimitMiddleware instance.
1046
+ *
1047
+ * @param config - Rate limit configuration for each channel
1048
+ * @param store - Optional cache store for distributed rate limiting
1049
+ *
1050
+ * @example
1051
+ * ```typescript
1052
+ * // 使用預設記憶體儲存
1053
+ * const middleware = new RateLimitMiddleware({
1054
+ * email: { maxPerSecond: 10 }
1055
+ * })
1056
+ *
1057
+ * // 使用 Redis 儲存(分散式環境)
1058
+ * const middleware = new RateLimitMiddleware({
1059
+ * email: { maxPerSecond: 10 }
1060
+ * }, redisStore)
1061
+ * ```
1062
+ */
1063
+ constructor(config: RateLimitConfig, store?: CacheStore);
1064
+ /**
1065
+ * Initialize token buckets for all configured channels.
1066
+ *
1067
+ * 為所有配置的通道初始化 Token Bucket。
1068
+ *
1069
+ * @private
1070
+ */
1071
+ private initializeBuckets;
1072
+ /**
1073
+ * Handle the notification and apply rate limiting.
1074
+ *
1075
+ * 處理通知並應用限流規則。如果超過任一時間窗口的限制,
1076
+ * 將拋出錯誤阻止通知發送。
1077
+ *
1078
+ * @param notification - The notification being sent
1079
+ * @param notifiable - The recipient
1080
+ * @param channel - The channel name
1081
+ * @param next - Continue to the next middleware or send the notification
1082
+ *
1083
+ * @throws {Error} 當超過限流時拋出錯誤
1084
+ */
1085
+ handle(_notification: Notification, _notifiable: Notifiable, channel: string, next: () => Promise<void>): Promise<void>;
1086
+ /**
1087
+ * Get current rate limit status for a channel.
1088
+ *
1089
+ * 獲取指定通道的當前限流狀態(用於除錯和監控)。
1090
+ *
1091
+ * @param channel - The channel name
1092
+ * @returns Current token counts for each time window
1093
+ *
1094
+ * @example
1095
+ * ```typescript
1096
+ * const status = middleware.getStatus('email')
1097
+ * console.log(`Email remaining: ${status.second}/${config.email.maxPerSecond}`)
1098
+ * ```
1099
+ */
1100
+ getStatus(channel: string): {
1101
+ second?: number;
1102
+ minute?: number;
1103
+ hour?: number;
1104
+ };
1105
+ /**
1106
+ * Reset rate limit for a specific channel.
1107
+ *
1108
+ * 重置指定通道的限流計數(用於測試或手動重置)。
1109
+ *
1110
+ * @param channel - The channel name to reset
1111
+ *
1112
+ * @example
1113
+ * ```typescript
1114
+ * // 在測試中重置限流
1115
+ * middleware.reset('email')
1116
+ * ```
1117
+ */
1118
+ reset(channel: string): void;
1119
+ }
1120
+
1121
+ /**
1122
+ * Notification manager.
1123
+ *
1124
+ * Responsible for managing notification channels and delivering notifications.
1125
+ */
1126
+ declare class NotificationManager {
1127
+ private core;
1128
+ /**
1129
+ * Channel registry.
1130
+ */
1131
+ private channels;
1132
+ /**
1133
+ * Middleware stack for intercepting channel sends.
1134
+ */
1135
+ private middlewares;
1136
+ /**
1137
+ * Indicates whether the middleware stack needs re-sorting.
1138
+ */
1139
+ private middlewaresDirty;
1140
+ /**
1141
+ * Queue manager (optional, injected by `orbit-queue`).
1142
+ */
1143
+ private queueManager?;
1144
+ /**
1145
+ * Type-safe hook emitter for notification events.
1146
+ */
1147
+ private hookEmitter;
1148
+ constructor(core: PlanetCore);
1149
+ private metrics?;
1150
+ /**
1151
+ * Enable metrics collection.
1152
+ */
1153
+ enableMetrics(maxHistory?: number): void;
1154
+ /**
1155
+ * Get metrics summary.
1156
+ */
1157
+ getMetrics(since?: Date): MetricsSummary | undefined;
1158
+ /**
1159
+ * Get recent failures.
1160
+ */
1161
+ getRecentFailures(limit?: number): NotificationMetric[];
1162
+ /**
1163
+ * Register a notification channel.
1164
+ *
1165
+ * @param name - The name of the channel.
1166
+ * @param channel - The channel instance.
1167
+ */
1168
+ channel(name: string, channel: NotificationChannel): void;
1169
+ /**
1170
+ * Register a middleware for intercepting channel sends.
1171
+ *
1172
+ * Middleware will be executed in the order they are registered.
1173
+ * Each middleware can modify, block, or monitor the notification flow.
1174
+ *
1175
+ * @param middleware - The middleware instance to register.
1176
+ *
1177
+ * @example
1178
+ * ```typescript
1179
+ * import { RateLimitMiddleware } from '@gravito/flare'
1180
+ *
1181
+ * const rateLimiter = new RateLimitMiddleware({
1182
+ * email: { maxPerSecond: 10 },
1183
+ * sms: { maxPerSecond: 5 }
1184
+ * })
1185
+ *
1186
+ * manager.use(rateLimiter)
1187
+ * ```
1188
+ */
1189
+ use(middleware: ChannelMiddleware): void;
1190
+ /**
1191
+ * Register the queue manager (called by `orbit-queue`).
1192
+ *
1193
+ * @param manager - The queue manager implementation.
1194
+ */
1195
+ setQueueManager(manager: NotificationManager['queueManager']): void;
1196
+ /**
1197
+ * Send a notification.
1198
+ *
1199
+ * @param notifiable - The recipient of the notification.
1200
+ * @param notification - The notification instance.
1201
+ * @param options - Options for sending.
1202
+ * @returns A promise that resolves to the notification result.
1203
+ *
1204
+ * @example
1205
+ * ```typescript
1206
+ * const result = await notificationManager.send(user, new InvoicePaid(invoice))
1207
+ * if (!result.allSuccess) { ... }
1208
+ * ```
1209
+ */
1210
+ send(notifiable: Notifiable, notification: Notification, options?: SendOptions): Promise<NotificationResult>;
1211
+ /**
1212
+ * Batch send notification to multiple recipients.
1213
+ *
1214
+ * @param notifiables - List of recipients.
1215
+ * @param notification - The notification instance.
1216
+ * @param options - Options for sending.
1217
+ * @returns A promise that resolves to the batch result.
1218
+ */
1219
+ sendBatch(notifiables: Notifiable[], notification: Notification, options?: SendOptions & {
1220
+ /** Batch concurrency (default: 10) */
1221
+ batchConcurrency?: number;
1222
+ }): Promise<BatchResult>;
1223
+ /**
1224
+ * Batch send notification to multiple recipients (streaming).
1225
+ *
1226
+ * @param notifiables - AsyncIterable or Iterable of recipients.
1227
+ * @param notification - The notification instance.
1228
+ * @param options - Options for sending.
1229
+ * @yields Notification results as they are processed.
1230
+ */
1231
+ sendBatchStream(notifiables: AsyncIterable<Notifiable> | Iterable<Notifiable>, notification: Notification, options?: SendOptions & {
1232
+ batchSize?: number;
1233
+ }): AsyncGenerator<NotificationResult>;
1234
+ /**
1235
+ * Send immediately (without queue).
1236
+ */
1237
+ private sendNow;
1238
+ private sendSequential;
1239
+ private sendParallel;
1240
+ private sendWithConcurrencyLimit;
1241
+ private processWithConcurrency;
1242
+ private sendToChannel;
1243
+ private executeChannelSend;
1244
+ /**
1245
+ * Retrieves the sorted list of middleware (Lazy sorting).
1246
+ *
1247
+ * Uses a lazy evaluation strategy: sorting only happens when needed to avoid
1248
+ * overhead on every `use()` call.
1249
+ * Sorting rules:
1250
+ * 1. Higher priority (larger number) executes first.
1251
+ * 2. Stable sort is maintained for identical priorities (registration order).
1252
+ *
1253
+ * @returns The sorted list of middleware.
1254
+ * @private
1255
+ */
1256
+ private getSortedMiddlewares;
1257
+ /**
1258
+ * Execute middleware chain recursively.
1259
+ *
1260
+ * @param index - Current middleware index
1261
+ * @param notification - The notification being sent
1262
+ * @param notifiable - The recipient
1263
+ * @param channelName - The channel name
1264
+ * @param finalHandler - The final handler to execute (actual channel.send)
1265
+ * @private
1266
+ */
1267
+ private executeMiddlewareChain;
1268
+ private getRetryConfig;
1269
+ /**
1270
+ * Serialize notification (for queuing).
1271
+ *
1272
+ * @param notification - The notification to serialize.
1273
+ * @returns A plain object representation of the notification.
1274
+ */
1275
+ private serializeNotification;
1276
+ }
1277
+
1278
+ /**
1279
+ * Options for configuring OrbitFlare.
1280
+ * @public
1281
+ */
1282
+ interface OrbitFlareOptions {
1283
+ /** Enable or disable the email notification channel. */
1284
+ enableMail?: boolean;
1285
+ /** Enable or disable the database storage notification channel. */
1286
+ enableDatabase?: boolean;
1287
+ /** Enable or disable the real-time broadcast notification channel. */
1288
+ enableBroadcast?: boolean;
1289
+ /** Enable or disable the Slack notification channel. */
1290
+ enableSlack?: boolean;
1291
+ /** Enable or disable the SMS notification channel. */
1292
+ enableSms?: boolean;
1293
+ /** Custom channel configuration records for flexible extension. */
1294
+ channels?: Record<string, unknown>;
1295
+ /** Channel middleware to apply to all notifications. */
1296
+ middleware?: ChannelMiddleware[];
1297
+ /** Custom notification preference provider. */
1298
+ preferenceProvider?: NotificationPreference;
1299
+ /** Enable automatic preference filtering middleware (default: false). */
1300
+ enablePreference?: boolean;
1301
+ }
1302
+ /**
1303
+ * OrbitFlare is the official notification orbit for Gravito.
1304
+ * It provides a unified API for sending notifications across multiple channels
1305
+ * (Mail, Database, Broadcast, Slack, SMS) and supports background queuing.
1306
+ *
1307
+ * @example
1308
+ * ```typescript
1309
+ * const flare = new OrbitFlare({
1310
+ * enableSlack: true,
1311
+ * channels: { slack: { webhookUrl: '...' } }
1312
+ * });
1313
+ * core.addOrbit(flare);
1314
+ *
1315
+ * // Usage in controller
1316
+ * await ctx.get('notifications').send(user, new WelcomeNotification());
1317
+ * ```
1318
+ * @public
1319
+ */
1320
+ declare class OrbitFlare implements GravitoOrbit {
1321
+ private options;
1322
+ constructor(options?: OrbitFlareOptions);
1323
+ /**
1324
+ * Configure OrbitFlare.
1325
+ *
1326
+ * @param options - The OrbitFlare configuration options.
1327
+ * @returns A new OrbitFlare instance.
1328
+ */
1329
+ static configure(options?: OrbitFlareOptions): OrbitFlare;
1330
+ private validateOptions;
1331
+ private isValidUrl;
1332
+ /**
1333
+ * Install OrbitFlare into PlanetCore.
1334
+ *
1335
+ * @param core - The PlanetCore instance.
1336
+ */
1337
+ install(core: PlanetCore): Promise<void>;
1338
+ private setupMailChannel;
1339
+ private setupDatabaseChannel;
1340
+ private setupBroadcastChannel;
1341
+ private setupSlackChannel;
1342
+ private setupSmsChannel;
1343
+ private setupQueueIntegration;
1344
+ private setupMiddleware;
1345
+ private isMailService;
1346
+ private isDatabaseService;
1347
+ private isBroadcastService;
1348
+ private isQueueService;
398
1349
  }
399
1350
  declare module '@gravito/core' {
400
1351
  interface GravitoVariables {
@@ -403,4 +1354,515 @@ declare module '@gravito/core' {
403
1354
  }
404
1355
  }
405
1356
 
406
- export { BroadcastChannel, type BroadcastNotification, DatabaseChannel, type DatabaseNotification, MailChannel, type MailMessage, type Notifiable, Notification, type NotificationChannel, NotificationManager, OrbitFlare, type OrbitFlareOptions, type ShouldQueue, SlackChannel, type SlackChannelConfig, type SlackMessage, SmsChannel, type SmsChannelConfig, type SmsMessage };
1357
+ interface TemplateData {
1358
+ [key: string]: unknown;
1359
+ }
1360
+ interface MailTemplate {
1361
+ subject: string;
1362
+ view?: string;
1363
+ data?: TemplateData;
1364
+ }
1365
+ interface SlackTemplate {
1366
+ text: string;
1367
+ channel?: string;
1368
+ attachments?: Array<{
1369
+ color?: string;
1370
+ title?: string;
1371
+ text?: string;
1372
+ }>;
1373
+ }
1374
+ declare abstract class TemplatedNotification extends Notification {
1375
+ protected data: TemplateData;
1376
+ with(data: TemplateData): this;
1377
+ protected abstract mailTemplate(): MailTemplate;
1378
+ protected slackTemplate?(): SlackTemplate;
1379
+ toMail(notifiable: Notifiable): MailMessage;
1380
+ toSlack(_notifiable: Notifiable): SlackMessage;
1381
+ private interpolate;
1382
+ private getRecipientEmail;
1383
+ }
1384
+
1385
+ /**
1386
+ * FlareHooks 型別定義
1387
+ *
1388
+ * 提供型別安全的 hook 事件系統,包含所有通知相關的 hook 事件和 payload 型別。
1389
+ *
1390
+ * @packageDocumentation
1391
+ */
1392
+
1393
+ /**
1394
+ * FlareHooks 事件名稱列舉
1395
+ *
1396
+ * 包含所有通知系統支援的 hook 事件。
1397
+ *
1398
+ * @public
1399
+ */
1400
+ type FlareHookEvent = 'notification:sending' | 'notification:queued' | 'notification:sent' | 'notification:channel:sending' | 'notification:channel:sent' | 'notification:channel:failed' | 'notification:channel:retry' | 'notification:batch:start' | 'notification:batch:complete';
1401
+ /**
1402
+ * 各 hook 事件的 Payload 型別映射
1403
+ *
1404
+ * 定義每個 hook 事件對應的 payload 結構,提供完整的型別安全。
1405
+ *
1406
+ * @example
1407
+ * ```typescript
1408
+ * // 監聽通知發送事件
1409
+ * hooks.on('notification:sending', (payload: FlareHookPayloads['notification:sending']) => {
1410
+ * console.log(`Sending ${payload.notification.constructor.name} to ${payload.notifiable.getNotifiableId()}`)
1411
+ * })
1412
+ * ```
1413
+ *
1414
+ * @public
1415
+ */
1416
+ interface FlareHookPayloads {
1417
+ /**
1418
+ * 通知開始發送前觸發
1419
+ */
1420
+ 'notification:sending': {
1421
+ /** 要發送的通知實例 */
1422
+ notification: Notification;
1423
+ /** 接收通知的對象 */
1424
+ notifiable: Notifiable;
1425
+ /** 要發送的通道列表 */
1426
+ channels: string[];
1427
+ };
1428
+ /**
1429
+ * 通知被加入佇列時觸發
1430
+ */
1431
+ 'notification:queued': {
1432
+ /** 要發送的通知實例 */
1433
+ notification: Notification;
1434
+ /** 接收通知的對象 */
1435
+ notifiable: Notifiable;
1436
+ /** 要發送的通道列表 */
1437
+ channels: string[];
1438
+ };
1439
+ /**
1440
+ * 通知發送完成後觸發
1441
+ */
1442
+ 'notification:sent': {
1443
+ /** 已發送的通知實例 */
1444
+ notification: Notification;
1445
+ /** 接收通知的對象 */
1446
+ notifiable: Notifiable;
1447
+ /** 各通道的發送結果 */
1448
+ results: SendResult[];
1449
+ /** 是否所有通道都發送成功 */
1450
+ allSuccess: boolean;
1451
+ /** 總執行時間(毫秒) */
1452
+ totalDuration: number;
1453
+ };
1454
+ /**
1455
+ * 通道開始發送前觸發
1456
+ */
1457
+ 'notification:channel:sending': {
1458
+ /** 要發送的通知實例 */
1459
+ notification: Notification;
1460
+ /** 接收通知的對象 */
1461
+ notifiable: Notifiable;
1462
+ /** 通道名稱 */
1463
+ channel: string;
1464
+ };
1465
+ /**
1466
+ * 通道發送成功後觸發
1467
+ */
1468
+ 'notification:channel:sent': {
1469
+ /** 已發送的通知實例 */
1470
+ notification: Notification;
1471
+ /** 接收通知的對象 */
1472
+ notifiable: Notifiable;
1473
+ /** 通道名稱 */
1474
+ channel: string;
1475
+ /** 發送耗時(毫秒) */
1476
+ duration: number;
1477
+ };
1478
+ /**
1479
+ * 通道發送失敗時觸發
1480
+ */
1481
+ 'notification:channel:failed': {
1482
+ /** 發送失敗的通知實例 */
1483
+ notification: Notification;
1484
+ /** 接收通知的對象 */
1485
+ notifiable: Notifiable;
1486
+ /** 通道名稱 */
1487
+ channel: string;
1488
+ /** 錯誤物件 */
1489
+ error: Error;
1490
+ /** 發送耗時(毫秒) */
1491
+ duration: number;
1492
+ };
1493
+ /**
1494
+ * 通道重試時觸發
1495
+ */
1496
+ 'notification:channel:retry': {
1497
+ /** 要重試的通知實例 */
1498
+ notification: Notification;
1499
+ /** 接收通知的對象 */
1500
+ notifiable: Notifiable;
1501
+ /** 通道名稱 */
1502
+ channel: string;
1503
+ /** 錯誤物件 */
1504
+ error: Error;
1505
+ /** 當前重試次數 */
1506
+ attempt: number;
1507
+ /** 下次重試延遲(毫秒) */
1508
+ nextDelay: number;
1509
+ };
1510
+ /**
1511
+ * 批次發送開始時觸發
1512
+ */
1513
+ 'notification:batch:start': {
1514
+ /** 要發送的通知實例 */
1515
+ notification: Notification;
1516
+ /** 接收者數量 */
1517
+ count: number;
1518
+ };
1519
+ /**
1520
+ * 批次發送完成時觸發
1521
+ */
1522
+ 'notification:batch:complete': {
1523
+ /** 已發送的通知實例 */
1524
+ notification: Notification;
1525
+ /** 總接收者數量 */
1526
+ total: number;
1527
+ /** 成功數量 */
1528
+ success: number;
1529
+ /** 失敗數量 */
1530
+ failed: number;
1531
+ /** 總執行時間(毫秒) */
1532
+ duration: number;
1533
+ };
1534
+ }
1535
+ /**
1536
+ * FlareHooks 介面
1537
+ *
1538
+ * 提供型別安全的 hook 系統介面,用於發送和監聽通知事件。
1539
+ *
1540
+ * @remarks
1541
+ * 此介面主要用於型別檢查,實際的 hook 系統由 @gravito/core 提供。
1542
+ *
1543
+ * @example
1544
+ * ```typescript
1545
+ * // 發送型別安全的 hook 事件
1546
+ * const emitter = createHookEmitter(core)
1547
+ * await emitter.emit('notification:sending', {
1548
+ * notification,
1549
+ * notifiable,
1550
+ * channels: ['mail', 'sms']
1551
+ * })
1552
+ * ```
1553
+ *
1554
+ * @public
1555
+ */
1556
+ interface FlareHooks {
1557
+ /**
1558
+ * 發送 hook 事件
1559
+ *
1560
+ * @param event - 事件名稱
1561
+ * @param payload - 事件 payload,型別由事件名稱決定
1562
+ */
1563
+ emit<E extends FlareHookEvent>(event: E, payload: FlareHookPayloads[E]): Promise<void>;
1564
+ /**
1565
+ * 監聽 hook 事件
1566
+ *
1567
+ * @param event - 事件名稱
1568
+ * @param handler - 事件處理函數
1569
+ */
1570
+ on<E extends FlareHookEvent>(event: E, handler: (payload: FlareHookPayloads[E]) => void): void;
1571
+ /**
1572
+ * 移除 hook 事件監聽器
1573
+ *
1574
+ * @param event - 事件名稱
1575
+ * @param handler - 要移除的事件處理函數
1576
+ */
1577
+ off<E extends FlareHookEvent>(event: E, handler: (payload: FlareHookPayloads[E]) => void): void;
1578
+ }
1579
+
1580
+ /**
1581
+ * Hook Emitter 輔助工具
1582
+ *
1583
+ * 提供型別安全的 hook emit 功能,封裝底層 @gravito/core 的 hooks 系統。
1584
+ *
1585
+ * @packageDocumentation
1586
+ */
1587
+
1588
+ /**
1589
+ * 型別安全的 Hook Emitter
1590
+ *
1591
+ * 封裝 PlanetCore 的 hooks.emit,提供完整的型別檢查和 IntelliSense 支援。
1592
+ *
1593
+ * @public
1594
+ */
1595
+ interface HookEmitter {
1596
+ /**
1597
+ * 發送 hook 事件
1598
+ *
1599
+ * @param event - 事件名稱
1600
+ * @param payload - 事件 payload,型別由事件名稱決定
1601
+ * @returns Promise,在所有監聽器執行完成後 resolve
1602
+ *
1603
+ * @example
1604
+ * ```typescript
1605
+ * const emitter = createHookEmitter(core)
1606
+ * await emitter.emit('notification:sending', {
1607
+ * notification,
1608
+ * notifiable,
1609
+ * channels: ['mail', 'sms']
1610
+ * })
1611
+ * ```
1612
+ */
1613
+ emit<E extends FlareHookEvent>(event: E, payload: FlareHookPayloads[E]): Promise<void>;
1614
+ }
1615
+ /**
1616
+ * 建立型別安全的 hook emitter
1617
+ *
1618
+ * 將 PlanetCore 的 hooks 系統包裝成型別安全的 emitter,
1619
+ * 避免在 NotificationManager 中使用 `any` 型別斷言。
1620
+ *
1621
+ * @param core - PlanetCore 實例
1622
+ * @returns 型別安全的 HookEmitter 實例
1623
+ *
1624
+ * @example
1625
+ * ```typescript
1626
+ * export class NotificationManager {
1627
+ * private hookEmitter: HookEmitter
1628
+ *
1629
+ * constructor(private core: PlanetCore) {
1630
+ * this.hookEmitter = createHookEmitter(core)
1631
+ * }
1632
+ *
1633
+ * async send(notifiable: Notifiable, notification: Notification) {
1634
+ * await this.hookEmitter.emit('notification:sending', {
1635
+ * notification,
1636
+ * notifiable,
1637
+ * channels: notification.via(notifiable)
1638
+ * })
1639
+ * // ...
1640
+ * }
1641
+ * }
1642
+ * ```
1643
+ *
1644
+ * @public
1645
+ */
1646
+ declare function createHookEmitter(core: PlanetCore): HookEmitter;
1647
+
1648
+ /**
1649
+ * Lazy Loading 通知基類
1650
+ *
1651
+ * 用於處理包含大量資料或需要資料庫查詢的通知。
1652
+ * 只儲存識別符 (ID),在需要時才載入完整資料。
1653
+ *
1654
+ * @typeParam TData - 載入的資料型別
1655
+ *
1656
+ * @example
1657
+ * ```typescript
1658
+ * class InvoicePaidNotification extends LazyNotification<Invoice> {
1659
+ * constructor(private invoiceId: string) {
1660
+ * super()
1661
+ * }
1662
+ *
1663
+ * protected async loadData(notifiable: Notifiable): Promise<Invoice> {
1664
+ * return await db.invoices.findById(this.invoiceId)
1665
+ * }
1666
+ *
1667
+ * toMail(notifiable: Notifiable): MailMessage {
1668
+ * const invoice = this.getCached()
1669
+ * if (!invoice) {
1670
+ * throw new Error('Invoice data not loaded')
1671
+ * }
1672
+ * return {
1673
+ * subject: `Invoice #${invoice.number} paid`,
1674
+ * text: `Amount: ${invoice.amount}`
1675
+ * }
1676
+ * }
1677
+ * }
1678
+ * ```
1679
+ */
1680
+ declare abstract class LazyNotification<TData> extends Notification {
1681
+ /**
1682
+ * 快取的資料
1683
+ */
1684
+ private _cachedData?;
1685
+ /**
1686
+ * 載入資料的抽象方法(由子類實作)
1687
+ *
1688
+ * @param notifiable - 通知接收者
1689
+ * @returns 載入的資料
1690
+ */
1691
+ protected abstract loadData(notifiable: Notifiable): Promise<TData>;
1692
+ /**
1693
+ * 取得快取的資料
1694
+ *
1695
+ * @returns 快取的資料,如果尚未載入則回傳 undefined
1696
+ */
1697
+ protected getCached(): TData | undefined;
1698
+ /**
1699
+ * 設定快取資料
1700
+ *
1701
+ * @param data - 要快取的資料
1702
+ */
1703
+ protected setCached(data: TData): void;
1704
+ /**
1705
+ * 檢查資料是否已載入
1706
+ *
1707
+ * @returns 如果資料已快取則回傳 true
1708
+ */
1709
+ protected isLoaded(): boolean;
1710
+ /**
1711
+ * 清除快取
1712
+ */
1713
+ protected clearCache(): void;
1714
+ /**
1715
+ * 載入資料並快取(如果尚未載入)
1716
+ *
1717
+ * @param notifiable - 通知接收者
1718
+ * @returns 載入的資料
1719
+ */
1720
+ protected ensureLoaded(notifiable: Notifiable): Promise<TData>;
1721
+ }
1722
+
1723
+ /**
1724
+ * Deep serialize object, handling special types.
1725
+ */
1726
+ declare function deepSerialize(obj: unknown, seen?: WeakSet<WeakKey>): unknown;
1727
+ /**
1728
+ * Deserialize object, restoring special types.
1729
+ */
1730
+ declare function deepDeserialize(obj: unknown): unknown;
1731
+
1732
+ /**
1733
+ * 序列化檢查結果介面
1734
+ */
1735
+ interface SerializationCheckResult {
1736
+ /** 是否可安全序列化 */
1737
+ serializable: boolean;
1738
+ /** 有問題的屬性路徑列表 */
1739
+ problematicPaths: string[];
1740
+ /** 警告訊息列表 */
1741
+ warnings: string[];
1742
+ }
1743
+ /**
1744
+ * 檢查物件是否可安全序列化
1745
+ *
1746
+ * @param obj - 要檢查的物件
1747
+ * @param path - 當前路徑(用於巢狀物件追蹤)
1748
+ * @returns 序列化檢查結果
1749
+ *
1750
+ * @example
1751
+ * ```typescript
1752
+ * const result = checkSerializable({ name: 'test', fn: () => {} })
1753
+ * console.log(result.serializable) // false
1754
+ * console.log(result.problematicPaths) // ['fn']
1755
+ * ```
1756
+ */
1757
+ declare function checkSerializable(obj: unknown, path?: string): SerializationCheckResult;
1758
+ /**
1759
+ * 斷言物件可序列化,不可序列化時拋出錯誤
1760
+ *
1761
+ * @param obj - 要檢查的物件
1762
+ * @throws {Error} 當物件不可序列化時
1763
+ *
1764
+ * @example
1765
+ * ```typescript
1766
+ * assertSerializable({ name: 'test' }) // OK
1767
+ * assertSerializable({ fn: () => {} }) // 拋出錯誤
1768
+ * ```
1769
+ */
1770
+ declare function assertSerializable(obj: unknown): void;
1771
+
1772
+ /**
1773
+ * Simple Token Bucket algorithm for rate limiting.
1774
+ *
1775
+ * Token Bucket 是一種常用的限流演算法,用於控制請求頻率。
1776
+ * 它透過一個固定容量的桶來存儲 tokens,並以固定速率補充。
1777
+ * 每個請求需要消耗一個或多個 tokens,當桶內 tokens 不足時請求被拒絕。
1778
+ *
1779
+ * @example
1780
+ * ```typescript
1781
+ * // 創建一個容量為 10,每秒補充 5 個 tokens 的桶
1782
+ * const bucket = new TokenBucket(10, 5)
1783
+ *
1784
+ * // 嘗試消耗 1 個 token
1785
+ * if (bucket.tryConsume()) {
1786
+ * // 請求被允許
1787
+ * } else {
1788
+ * // 請求被限流
1789
+ * }
1790
+ * ```
1791
+ *
1792
+ * @public
1793
+ */
1794
+ declare class TokenBucket {
1795
+ private capacity;
1796
+ private refillRate;
1797
+ /**
1798
+ * 當前可用的 tokens 數量
1799
+ */
1800
+ private tokens;
1801
+ /**
1802
+ * 上次補充 tokens 的時間戳(毫秒)
1803
+ */
1804
+ private lastRefill;
1805
+ /**
1806
+ * 創建一個新的 TokenBucket 實例
1807
+ *
1808
+ * @param capacity - 桶的最大容量(tokens 上限)
1809
+ * @param refillRate - 每秒補充的 tokens 數量
1810
+ *
1811
+ * @example
1812
+ * ```typescript
1813
+ * // 每秒最多 100 個請求
1814
+ * const bucket = new TokenBucket(100, 100)
1815
+ * ```
1816
+ */
1817
+ constructor(capacity: number, refillRate: number);
1818
+ /**
1819
+ * 嘗試從桶中消耗指定數量的 tokens
1820
+ *
1821
+ * 此方法會先執行 token 補充,然後檢查是否有足夠的 tokens。
1822
+ * 如果有足夠的 tokens,則消耗並返回 true;否則返回 false 且不消耗。
1823
+ *
1824
+ * @param tokens - 要消耗的 tokens 數量(預設為 1)
1825
+ * @returns 如果成功消耗則返回 true,否則返回 false
1826
+ *
1827
+ * @example
1828
+ * ```typescript
1829
+ * const bucket = new TokenBucket(10, 1)
1830
+ *
1831
+ * // 嘗試消耗 1 個 token
1832
+ * if (bucket.tryConsume()) {
1833
+ * console.log('請求被允許')
1834
+ * }
1835
+ *
1836
+ * // 嘗試消耗 3 個 tokens
1837
+ * if (bucket.tryConsume(3)) {
1838
+ * console.log('批次請求被允許')
1839
+ * }
1840
+ * ```
1841
+ */
1842
+ tryConsume(tokens?: number): boolean;
1843
+ /**
1844
+ * 獲取當前可用的 tokens 數量
1845
+ *
1846
+ * 此方法會先執行補充操作,然後返回當前的 tokens 數量。
1847
+ *
1848
+ * @returns 當前可用的 tokens 數量
1849
+ *
1850
+ * @example
1851
+ * ```typescript
1852
+ * const bucket = new TokenBucket(10, 1)
1853
+ * console.log(`剩餘 ${bucket.getTokens()} 個 tokens`)
1854
+ * ```
1855
+ */
1856
+ getTokens(): number;
1857
+ /**
1858
+ * 根據經過的時間補充 tokens
1859
+ *
1860
+ * 此方法計算自上次補充以來經過的時間,並根據 refillRate 補充相應數量的 tokens。
1861
+ * tokens 數量不會超過容量上限。
1862
+ *
1863
+ * @private
1864
+ */
1865
+ private refill;
1866
+ }
1867
+
1868
+ export { AbortError, type BatchResult, BroadcastChannel, type BroadcastNotification, type CacheStore, type ChannelFailurePayload, type ChannelHookPayload, type ChannelMiddleware, type ChannelRateLimitConfig, type ChannelSuccessPayload, DatabaseChannel, type DatabaseNotification, type FlareHookEvent, type FlareHookPayloads, type FlareHooks, type HookEmitter, LazyNotification, MailChannel, type MailChannelConfig, type MailMessage, type MailTemplate, MemoryStore, type MetricsSummary, MiddlewarePriority, type MiddlewarePriorityValue, type Notifiable, Notification, type NotificationBatchCompletePayload, type NotificationBatchStartPayload, type NotificationChannel, type NotificationCompletePayload, type NotificationHookPayload, NotificationManager, type NotificationMetric, NotificationMetricsCollector, type NotificationPreference, type NotificationResult, OrbitFlare, type OrbitFlareOptions, PreferenceMiddleware, type RateLimitConfig, RateLimitMiddleware, type RetryConfig, type SendOptions, type SendResult, type SerializationCheckResult, type ShouldQueue, type ShouldRetry, SlackChannel, type SlackChannelConfig, type SlackMessage, type SlackTemplate, SmsChannel, type SmsChannelConfig, type SmsMessage, type TemplateData, TemplatedNotification, TimeoutChannel, type TimeoutConfig, TimeoutError, TokenBucket, assertSerializable, checkSerializable, createHookEmitter, deepDeserialize, deepSerialize, toPrometheusFormat };