@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 +199 -128
- package/README.zh-TW.md +126 -11
- package/dist/index.cjs +1700 -160
- package/dist/index.d.cts +1553 -91
- package/dist/index.d.ts +1553 -91
- package/dist/index.js +1690 -159
- package/package.json +9 -6
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?(
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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?(
|
|
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
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* Responsible for managing notification channels and delivering notifications.
|
|
536
|
+
* Configuration options for TimeoutChannel.
|
|
288
537
|
*/
|
|
289
|
-
|
|
290
|
-
private core;
|
|
538
|
+
interface TimeoutConfig {
|
|
291
539
|
/**
|
|
292
|
-
*
|
|
540
|
+
* Timeout duration in milliseconds.
|
|
293
541
|
*/
|
|
294
|
-
|
|
542
|
+
timeout: number;
|
|
295
543
|
/**
|
|
296
|
-
*
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
*
|
|
598
|
+
* Sends a notification through the inner channel with a timeout guard.
|
|
302
599
|
*
|
|
303
|
-
* @param
|
|
304
|
-
* @param
|
|
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
|
-
|
|
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
|
-
*
|
|
732
|
+
* Middleware name (for debugging and identification).
|
|
309
733
|
*
|
|
310
|
-
*
|
|
734
|
+
* 中介層名稱,用於除錯和識別。
|
|
311
735
|
*/
|
|
312
|
-
|
|
736
|
+
name: string;
|
|
313
737
|
/**
|
|
314
|
-
*
|
|
738
|
+
* Middleware priority (optional, default: 0).
|
|
315
739
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
740
|
+
* 中介層優先級,數字越大越先執行。
|
|
741
|
+
* 預設為 0。相同優先級的中介層按照註冊順序執行。
|
|
742
|
+
*
|
|
743
|
+
* @default 0
|
|
319
744
|
*
|
|
320
745
|
* @example
|
|
321
746
|
* ```typescript
|
|
322
|
-
*
|
|
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
|
-
|
|
779
|
+
priority?: number;
|
|
326
780
|
/**
|
|
327
|
-
*
|
|
781
|
+
* Handle the notification before it's sent to the channel.
|
|
328
782
|
*
|
|
329
|
-
*
|
|
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
|
|
338
|
-
* @
|
|
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
|
-
|
|
808
|
+
handle(notification: Notification, notifiable: Notifiable, channel: string, next: () => Promise<void>): Promise<void>;
|
|
341
809
|
}
|
|
342
810
|
|
|
343
811
|
/**
|
|
344
|
-
*
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
*
|
|
348
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
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
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* });
|
|
375
|
-
* core.addOrbit(flare);
|
|
830
|
+
* // Using Notifiable preferences
|
|
831
|
+
* const middleware = new PreferenceMiddleware();
|
|
832
|
+
* manager.use(middleware);
|
|
376
833
|
*
|
|
377
|
-
* //
|
|
378
|
-
*
|
|
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
|
|
383
|
-
private
|
|
384
|
-
|
|
846
|
+
declare class PreferenceMiddleware implements ChannelMiddleware {
|
|
847
|
+
private preferenceProvider?;
|
|
848
|
+
private logger?;
|
|
385
849
|
/**
|
|
386
|
-
*
|
|
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
|
|
389
|
-
* @
|
|
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
|
-
|
|
872
|
+
constructor(preferenceProvider?: NotificationPreference | undefined, logger?: {
|
|
873
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
874
|
+
} | undefined);
|
|
392
875
|
/**
|
|
393
|
-
*
|
|
876
|
+
* Handle the notification and apply user preference filtering.
|
|
394
877
|
*
|
|
395
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|