@zerotal/notifications 1.6.3 → 1.7.2
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/api-surface.md +537 -0
- package/package.json +6 -5
- package/src/messages/MailMessage.ts +33 -2
package/api-surface.md
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# @zerotal/notifications — public API surface
|
|
2
|
+
|
|
3
|
+
<!-- AUTO-GENERATED by scripts/api-surface.ts. Do not edit by hand.
|
|
4
|
+
Run `bun run api:surface` to regenerate after an intentional API change. -->
|
|
5
|
+
|
|
6
|
+
## . `(./src/index.ts)`
|
|
7
|
+
|
|
8
|
+
class BroadcastChannel = {
|
|
9
|
+
new (): BroadcastChannel
|
|
10
|
+
channelFor: (notifiable: Notifiable) => string
|
|
11
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class BroadcastMessage = {
|
|
15
|
+
new (data: Record<string, unknown>): BroadcastMessage
|
|
16
|
+
onQueue: (queue: string) => BroadcastMessage
|
|
17
|
+
queue: string | undefined
|
|
18
|
+
readonly data: Record<string, unknown>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class BroadcastNotificationJob = {
|
|
22
|
+
new (_channel?: string, _event?: string, _data?: Record<string, unknown>, queue?: string): BroadcastNotificationJob
|
|
23
|
+
static fromPayload: (data: Record<string, unknown>) => BroadcastNotificationJob
|
|
24
|
+
_chain: SerializedJob[] | undefined
|
|
25
|
+
batchId: string | undefined
|
|
26
|
+
className: string
|
|
27
|
+
debounceKey: () => string
|
|
28
|
+
handle: () => Promise<void>
|
|
29
|
+
payload: () => Record<string, unknown>
|
|
30
|
+
readonly debounce?: number
|
|
31
|
+
readonly maxAttempts: number
|
|
32
|
+
readonly queue: string
|
|
33
|
+
readonly retryDelay: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class DatabaseChannel = {
|
|
37
|
+
new (table: string, _sql: SQLInstance): DatabaseChannel
|
|
38
|
+
_count: () => Promise<number>
|
|
39
|
+
all: (notifiable: Notifiable, query?: InboxQuery) => Promise<NotificationRecord[]>
|
|
40
|
+
clear: (notifiable: Notifiable) => Promise<void>
|
|
41
|
+
delete: (id: string) => Promise<void>
|
|
42
|
+
markAllAsRead: (notifiable: Notifiable) => Promise<void>
|
|
43
|
+
markAsRead: (id: string) => Promise<void>
|
|
44
|
+
markAsUnread: (id: string) => Promise<void>
|
|
45
|
+
prune: (days: number, includeUnread?: boolean) => Promise<number>
|
|
46
|
+
recent: (limit?: number) => Promise<NotificationRecord[]>
|
|
47
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
48
|
+
unread: (notifiable: Notifiable, query?: InboxQuery) => Promise<NotificationRecord[]>
|
|
49
|
+
unreadCount: (notifiable: Notifiable) => Promise<number>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class LogDriver = {
|
|
53
|
+
new (_channel?: 'console' | string): LogDriver
|
|
54
|
+
send: (message: MailPayload) => Promise<void>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class MailChannel = {
|
|
58
|
+
new (config: MailConfigShape): MailChannel
|
|
59
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class MailMessage = {
|
|
63
|
+
new (): MailMessage
|
|
64
|
+
action: (text: string, url: string) => MailMessage
|
|
65
|
+
attach: (attachment: MailAttachment) => MailMessage
|
|
66
|
+
attachFile: (path: string, options?: { filename?: string; contentType?: string;}) => Promise<MailMessage>
|
|
67
|
+
bcc: (address: AddressInput | AddressInput[]) => MailMessage
|
|
68
|
+
cc: (address: AddressInput | AddressInput[]) => MailMessage
|
|
69
|
+
embed: (cid: string, attachment: Omit<MailAttachment, 'cid' | 'inline'>) => MailMessage
|
|
70
|
+
from: (address: AddressInput) => MailMessage
|
|
71
|
+
greeting: { (text: string): MailMessage; (text: string, style: TextStyle): MailMessage; (build: (t: RichLine) => void): MailMessage;}
|
|
72
|
+
html: (content: string) => MailMessage
|
|
73
|
+
line: { (text: string): MailMessage; (text: string, style: TextStyle): MailMessage; (build: (t: RichLine) => void): MailMessage;}
|
|
74
|
+
replyTo: (address: AddressInput) => MailMessage
|
|
75
|
+
salutation: { (text: string): MailMessage; (text: string, style: TextStyle): MailMessage; (build: (t: RichLine) => void): MailMessage;}
|
|
76
|
+
subject: (text: string) => MailMessage
|
|
77
|
+
text: (content: string) => MailMessage
|
|
78
|
+
to: (address: AddressInput | AddressInput[]) => MailMessage
|
|
79
|
+
toPayload: (defaultFrom: MailAddress, fallbackTo: MailAddress[]) => MailPayload
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class MessageFailed = {
|
|
83
|
+
new (className: string, to: string[], subject: string, durationMs: number, error: string): MessageFailed
|
|
84
|
+
readonly className: string
|
|
85
|
+
readonly durationMs: number
|
|
86
|
+
readonly error: string
|
|
87
|
+
readonly subject: string
|
|
88
|
+
readonly to: string[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class MessageQueued = {
|
|
92
|
+
new (className: string, to: string[], subject: string, queue: string): MessageQueued
|
|
93
|
+
readonly className: string
|
|
94
|
+
readonly queue: string
|
|
95
|
+
readonly subject: string
|
|
96
|
+
readonly to: string[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class MessageSent = {
|
|
100
|
+
new (className: string, to: string[], subject: string, html: string, durationMs: number, queued: boolean): MessageSent
|
|
101
|
+
readonly className: string
|
|
102
|
+
readonly durationMs: number
|
|
103
|
+
readonly html: string
|
|
104
|
+
readonly queued: boolean
|
|
105
|
+
readonly subject: string
|
|
106
|
+
readonly to: string[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class Notification = {
|
|
110
|
+
new (): Notification
|
|
111
|
+
broadcastType: () => string
|
|
112
|
+
channels: (notifiable?: Notifiable) => string[]
|
|
113
|
+
payload: () => Record<string, unknown>
|
|
114
|
+
toBroadcast: (_notifiable: Notifiable) => BroadcastMessage | Record<string, unknown> | Promise<BroadcastMessage | Record<string, unknown>>
|
|
115
|
+
toDatabase: (_notifiable: Notifiable) => Record<string, unknown> | Promise<Record<string, unknown>>
|
|
116
|
+
toMail: (_notifiable: Notifiable) => MailMessage | Promise<MailMessage>
|
|
117
|
+
toSlack: (_notifiable: Notifiable) => SlackMessage | Promise<SlackMessage>
|
|
118
|
+
toSms: (_notifiable: Notifiable) => SmsMessage | Promise<SmsMessage>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class NotificationChannelNotConfiguredError = {
|
|
122
|
+
new (channel: string, hint?: string): NotificationChannelNotConfiguredError
|
|
123
|
+
readonly code: string
|
|
124
|
+
readonly context?: Record<string, unknown> | undefined
|
|
125
|
+
readonly status: number
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class NotificationChannelUnavailableError = {
|
|
129
|
+
new (message: string): NotificationChannelUnavailableError
|
|
130
|
+
readonly code: string
|
|
131
|
+
readonly context?: Record<string, unknown> | undefined
|
|
132
|
+
readonly status: number
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class NotificationConfigError = {
|
|
136
|
+
new (message: string, context?: Record<string, unknown>): NotificationConfigError
|
|
137
|
+
readonly code: string
|
|
138
|
+
readonly context?: Record<string, unknown> | undefined
|
|
139
|
+
readonly status: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class NotificationContractError = {
|
|
143
|
+
new (notification: string, method: string, channel: string): NotificationContractError
|
|
144
|
+
readonly code: string
|
|
145
|
+
readonly context?: Record<string, unknown> | undefined
|
|
146
|
+
readonly status: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class NotificationDeliveryError = {
|
|
150
|
+
new (message: string, context?: Record<string, unknown>): NotificationDeliveryError
|
|
151
|
+
readonly code: string
|
|
152
|
+
readonly context?: Record<string, unknown> | undefined
|
|
153
|
+
readonly status: number
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
class NotificationDispatchError = {
|
|
157
|
+
new (notification: string, failures: Array<{ channel: string; error: Error; }>, delivered: string[]): NotificationDispatchError
|
|
158
|
+
readonly code: string
|
|
159
|
+
readonly context?: Record<string, unknown> | undefined
|
|
160
|
+
readonly delivered: string[]
|
|
161
|
+
readonly failures: { channel: string; error: Error;}[]
|
|
162
|
+
readonly notification: string
|
|
163
|
+
readonly status: number
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class NotificationError = {
|
|
167
|
+
new (message: string, code?: string, status?: number, context?: Record<string, unknown>): NotificationError
|
|
168
|
+
readonly code: string
|
|
169
|
+
readonly context?: Record<string, unknown> | undefined
|
|
170
|
+
readonly status: number
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
class NotificationFake = {
|
|
174
|
+
new (app: Application, original: Binding): NotificationFake
|
|
175
|
+
static install: () => NotificationFake
|
|
176
|
+
assertNotSentTo: <T extends Notification>(notifiable: Notifiable, NotificationClass: new (...args: never[]) => T) => void
|
|
177
|
+
assertNothingSent: () => void
|
|
178
|
+
assertQueued: <T extends Notification>(notifiable: Notifiable, NotificationClass: new (...args: never[]) => T) => void
|
|
179
|
+
assertSentCount: (count: number) => void
|
|
180
|
+
assertSentOn: <T extends Notification>(notifiable: Notifiable, NotificationClass: new (...args: never[]) => T, channel: string) => void
|
|
181
|
+
assertSentTimes: <T extends Notification>(NotificationClass: new (...args: never[]) => T, count: number) => void
|
|
182
|
+
assertSentTo: <T extends Notification>(notifiable: Notifiable, NotificationClass: new (...args: never[]) => T, callback?: (notification: T) => boolean) => void
|
|
183
|
+
channels: () => string[]
|
|
184
|
+
database: { all(): Promise<NotificationRecord[]>; unread(): Promise<NotificationRecord[]>; unreadCount(): Promise<number>; markAllAsRead(): Promise<void>; markAsRead(): Promise<void>; markAsUnread(): Promise<void>; delete(): Promise<void>; clear(): Promise<void>;}
|
|
185
|
+
extend: () => NotificationFake
|
|
186
|
+
queue: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
187
|
+
queueMany: (notifiables: Iterable<Notifiable>, notification: Notification) => Promise<void>
|
|
188
|
+
restore: () => void
|
|
189
|
+
route: (routes: OnDemandRoutes) => { notify(notification: Notification): Promise<void>; notifyLater(notification: Notification): Promise<void>;}
|
|
190
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
191
|
+
sendMany: (notifiables: Iterable<Notifiable>, notification: Notification) => Promise<void>
|
|
192
|
+
sent: () => CapturedNotification[]
|
|
193
|
+
sentTo: (notifiable: Notifiable) => CapturedNotification[]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
class NotificationManager = {
|
|
197
|
+
new (config: NotificationConfigShape): NotificationManager
|
|
198
|
+
channels: () => string[]
|
|
199
|
+
database: DatabaseChannel
|
|
200
|
+
extend: (channel: string, factory: ChannelResolver) => NotificationManager
|
|
201
|
+
queue: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
202
|
+
queueMany: (notifiables: Iterable<Notifiable>, notification: Notification) => Promise<void>
|
|
203
|
+
route: (routes: OnDemandRoutes) => { notify(notification: Notification): Promise<void>; notifyLater(notification: Notification): Promise<void>;}
|
|
204
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
205
|
+
sendMany: (notifiables: Iterable<Notifiable>, notification: Notification) => Promise<void>
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
class NotificationProvider = {
|
|
209
|
+
new (app: Application): NotificationProvider
|
|
210
|
+
static dependsOn?: (new (app: Application) => ServiceProvider)[]
|
|
211
|
+
static environments: AppEnvironment[]
|
|
212
|
+
static priority?: number
|
|
213
|
+
static provides: readonly ['notifications']
|
|
214
|
+
devProcesses: () => DevProcessDefinition[]
|
|
215
|
+
doctorChecks: () => DoctorCheck[]
|
|
216
|
+
onBooted: () => Promise<void>
|
|
217
|
+
onBooting: () => Promise<void>
|
|
218
|
+
onRegister: () => void
|
|
219
|
+
onRequestProcessed: (_ctx: HttpContext) => Promise<void>
|
|
220
|
+
onRequestReceived: (_ctx: HttpContext) => Promise<void>
|
|
221
|
+
onResponseSent: (_ctx: HttpContext) => Promise<void>
|
|
222
|
+
onStarted: () => Promise<void>
|
|
223
|
+
onStarting: () => Promise<void>
|
|
224
|
+
onStopped: () => Promise<void>
|
|
225
|
+
onStopping: () => Promise<void>
|
|
226
|
+
replContext: () => Record<string, unknown>
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class NotificationSent = {
|
|
230
|
+
new (className: string, channel: string, notifiable: string, ok: boolean, durationMs: number, error?: string | undefined): NotificationSent
|
|
231
|
+
readonly channel: string
|
|
232
|
+
readonly className: string
|
|
233
|
+
readonly durationMs: number
|
|
234
|
+
readonly error?: string | undefined
|
|
235
|
+
readonly notifiable: string
|
|
236
|
+
readonly ok: boolean
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
class NotificationsPruneCommand = {
|
|
240
|
+
new (): NotificationsPruneCommand
|
|
241
|
+
static args: ArgDef[]
|
|
242
|
+
static commandName: string
|
|
243
|
+
static description: string
|
|
244
|
+
static flags: FlagDef[]
|
|
245
|
+
static needsApp: boolean
|
|
246
|
+
_readLine: () => Promise<string>
|
|
247
|
+
_writer: OutputWriter
|
|
248
|
+
app: unknown
|
|
249
|
+
args: Record<string, string>
|
|
250
|
+
ask: (question: string, defaultValue?: string) => Promise<string>
|
|
251
|
+
choice: (question: string, options: string[]) => Promise<string>
|
|
252
|
+
confirm: (question: string, defaultValue?: boolean) => Promise<boolean>
|
|
253
|
+
dim: (msg: string) => void
|
|
254
|
+
error: (msg: string) => void
|
|
255
|
+
flags: Record<string, string | number | boolean>
|
|
256
|
+
info: (msg: string) => void
|
|
257
|
+
line: (msg: string) => void
|
|
258
|
+
newLine: () => void
|
|
259
|
+
run: () => Promise<void>
|
|
260
|
+
secret: (question: string) => Promise<string>
|
|
261
|
+
section: (title: string) => void
|
|
262
|
+
table: (rows: [string, string][], indent?: number) => void
|
|
263
|
+
warn: (msg: string) => void
|
|
264
|
+
write: (msg: string) => void
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
class NotificationsTestCommand = {
|
|
268
|
+
new (): NotificationsTestCommand
|
|
269
|
+
static args: ArgDef[]
|
|
270
|
+
static commandName: string
|
|
271
|
+
static description: string
|
|
272
|
+
static flags: FlagDef[]
|
|
273
|
+
static needsApp: boolean
|
|
274
|
+
_readLine: () => Promise<string>
|
|
275
|
+
_writer: OutputWriter
|
|
276
|
+
app: unknown
|
|
277
|
+
args: Record<string, string>
|
|
278
|
+
ask: (question: string, defaultValue?: string) => Promise<string>
|
|
279
|
+
choice: (question: string, options: string[]) => Promise<string>
|
|
280
|
+
confirm: (question: string, defaultValue?: boolean) => Promise<boolean>
|
|
281
|
+
dim: (msg: string) => void
|
|
282
|
+
error: (msg: string) => void
|
|
283
|
+
flags: Record<string, string | number | boolean>
|
|
284
|
+
info: (msg: string) => void
|
|
285
|
+
line: (msg: string) => void
|
|
286
|
+
newLine: () => void
|
|
287
|
+
run: () => Promise<void>
|
|
288
|
+
secret: (question: string) => Promise<string>
|
|
289
|
+
section: (title: string) => void
|
|
290
|
+
table: (rows: [string, string][], indent?: number) => void
|
|
291
|
+
warn: (msg: string) => void
|
|
292
|
+
write: (msg: string) => void
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
class OnDemandNotifiable = {
|
|
296
|
+
new (_routes: OnDemandRoutes): OnDemandNotifiable
|
|
297
|
+
readonly email?: string
|
|
298
|
+
readonly id: string
|
|
299
|
+
readonly phone?: string
|
|
300
|
+
receivesBroadcastNotificationsOn: () => string
|
|
301
|
+
routeNotificationFor: (channel: string) => string | undefined
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
class ResendDriver = {
|
|
305
|
+
new (_apiKey: string): ResendDriver
|
|
306
|
+
send: (message: MailPayload) => Promise<void>
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
class RichLine = {
|
|
310
|
+
new (): RichLine
|
|
311
|
+
bold: (content: string) => RichLine
|
|
312
|
+
color: (content: string, color: string) => RichLine
|
|
313
|
+
italic: (content: string) => RichLine
|
|
314
|
+
readonly segments: TextSegment[]
|
|
315
|
+
size: (content: string, px: number) => RichLine
|
|
316
|
+
styled: (content: string, style: TextStyle) => RichLine
|
|
317
|
+
text: (content: string) => RichLine
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class SendNotificationJob = {
|
|
321
|
+
new (_notifiable?: Notifiable | undefined, _notification?: Notification | undefined, serialized?: SerializedNotification): SendNotificationJob
|
|
322
|
+
static fromPayload: (data: Record<string, unknown>) => SendNotificationJob
|
|
323
|
+
_chain: SerializedJob[] | undefined
|
|
324
|
+
batchId: string | undefined
|
|
325
|
+
className: string
|
|
326
|
+
debounceKey: () => string
|
|
327
|
+
handle: () => Promise<void>
|
|
328
|
+
payload: () => Record<string, unknown>
|
|
329
|
+
readonly debounce?: number
|
|
330
|
+
readonly maxAttempts: number
|
|
331
|
+
readonly queue: 'notifications'
|
|
332
|
+
readonly retryDelay: number
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
class SlackChannel = {
|
|
336
|
+
new (config?: { webhook?: string; }): SlackChannel
|
|
337
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
class SmsChannel = {
|
|
341
|
+
new (config: SmsConfigShape): SmsChannel
|
|
342
|
+
send: (notifiable: Notifiable, notification: Notification) => Promise<void>
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
class SmtpConnectionError = {
|
|
346
|
+
new (message: string): SmtpConnectionError
|
|
347
|
+
readonly code: string
|
|
348
|
+
readonly context?: Record<string, unknown> | undefined
|
|
349
|
+
readonly status: number
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
class SmtpDriver = {
|
|
353
|
+
new (_host: string, _port: number, _username: string, _password: string, _secure?: boolean, _options?: { allowInsecureAuth?: boolean; rejectUnauthorized?: boolean; timeoutMs?: number; clientName?: string; }): SmtpDriver
|
|
354
|
+
send: (message: MailPayload) => Promise<void>
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
class SmtpResponseError = {
|
|
358
|
+
new (replyCode: number, replyText: string, expected: number[]): SmtpResponseError
|
|
359
|
+
readonly code: string
|
|
360
|
+
readonly context?: Record<string, unknown> | undefined
|
|
361
|
+
readonly replyCode: number
|
|
362
|
+
readonly replyText: string
|
|
363
|
+
readonly status: number
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
class UnknownNotificationChannelError = {
|
|
367
|
+
new (channel: string, known?: string[]): UnknownNotificationChannelError
|
|
368
|
+
readonly code: string
|
|
369
|
+
readonly context?: Record<string, unknown> | undefined
|
|
370
|
+
readonly status: number
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
class UnknownNotificationTypeError = {
|
|
374
|
+
new (type: string, known: string[]): UnknownNotificationTypeError
|
|
375
|
+
readonly code: string
|
|
376
|
+
readonly context?: Record<string, unknown> | undefined
|
|
377
|
+
readonly status: number
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
class UnknownSmsDriverError = {
|
|
381
|
+
new (driver: string): UnknownSmsDriverError
|
|
382
|
+
readonly code: string
|
|
383
|
+
readonly context?: Record<string, unknown> | undefined
|
|
384
|
+
readonly status: number
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const BROADCAST_NOTIFICATION_EVENT = 'notification'
|
|
388
|
+
|
|
389
|
+
const NotificationRegistry = { _map: Map<string, NotificationClass>; register(NotificationClass: NotificationClass): void; resolve(className: string): NotificationClass | undefined; all(): ReadonlyMap<string, NotificationClass>;}
|
|
390
|
+
|
|
391
|
+
const Notify = NotificationManager
|
|
392
|
+
|
|
393
|
+
function channelStats = () => ChannelStat[]
|
|
394
|
+
|
|
395
|
+
function NotificationConfig = (options?: DeepPartial<NotificationConfigShape>) => NotificationConfigShape
|
|
396
|
+
|
|
397
|
+
function recentDeliveries = () => RecentDelivery[]
|
|
398
|
+
|
|
399
|
+
function validateNotificationConfig = (config: NotificationConfigShape) => void
|
|
400
|
+
|
|
401
|
+
interface ChannelStat = {
|
|
402
|
+
avgMs: number
|
|
403
|
+
channel: string
|
|
404
|
+
failed: number
|
|
405
|
+
sent: number
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
interface InboxQuery = {
|
|
409
|
+
limit?: number
|
|
410
|
+
offset?: number
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
interface MailAddress = {
|
|
414
|
+
address: string
|
|
415
|
+
name?: string
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
interface MailAttachment = {
|
|
419
|
+
cid?: string
|
|
420
|
+
content: string | Uint8Array<ArrayBufferLike>
|
|
421
|
+
contentType?: string
|
|
422
|
+
filename: string
|
|
423
|
+
inline?: boolean
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
interface MailConfigShape = {
|
|
427
|
+
driver: 'log' | 'smtp' | 'resend'
|
|
428
|
+
from: { address: string; name: string;}
|
|
429
|
+
log: { channel: 'console' | string;}
|
|
430
|
+
resend: { apiKey: string;}
|
|
431
|
+
smtp: { host: string; port: number; secure: boolean; username: string; password: string; allowInsecureAuth?: boolean; rejectUnauthorized?: boolean; timeoutMs?: number; clientName?: string;}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
interface MailDriver = {
|
|
435
|
+
send: (message: MailPayload) => Promise<void>
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
interface MailPayload = {
|
|
439
|
+
attachments?: MailAttachment[]
|
|
440
|
+
bcc?: MailAddress[]
|
|
441
|
+
cc?: MailAddress[]
|
|
442
|
+
from: MailAddress
|
|
443
|
+
html?: string
|
|
444
|
+
replyTo?: MailAddress
|
|
445
|
+
subject: string
|
|
446
|
+
text?: string
|
|
447
|
+
to: MailAddress[]
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
interface Notifiable = {
|
|
451
|
+
email?: string
|
|
452
|
+
id: string | number
|
|
453
|
+
name?: string
|
|
454
|
+
phone?: string
|
|
455
|
+
receivesBroadcastNotificationsOn?: () => string
|
|
456
|
+
routeNotificationFor?: (channel: string) => string | undefined
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
interface NotificationChannel = {
|
|
460
|
+
send: (notifiable: Notifiable, notification: NotificationLike) => Promise<void>
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
interface NotificationConfigShape = {
|
|
464
|
+
database: { table: string;}
|
|
465
|
+
mail: MailConfigShape
|
|
466
|
+
slack?: { webhook?: string;}
|
|
467
|
+
sms?: SmsConfigShape
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
interface NotificationRecord = {
|
|
471
|
+
created_at: string
|
|
472
|
+
data: string
|
|
473
|
+
id: string
|
|
474
|
+
notifiable_id: string
|
|
475
|
+
notifiable_type: string
|
|
476
|
+
read_at: string | null
|
|
477
|
+
type: string
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
interface OnDemandRoutes = {
|
|
481
|
+
[key: string]: string | undefined
|
|
482
|
+
broadcast?: string
|
|
483
|
+
mail?: string
|
|
484
|
+
slack?: string
|
|
485
|
+
sms?: string
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
interface RecentDelivery = {
|
|
489
|
+
at: number
|
|
490
|
+
channel: string
|
|
491
|
+
className: string
|
|
492
|
+
durationMs: number
|
|
493
|
+
error: string | undefined
|
|
494
|
+
notifiable: string
|
|
495
|
+
ok: boolean
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
interface SlackMessage = {
|
|
499
|
+
blocks?: unknown[]
|
|
500
|
+
text: string
|
|
501
|
+
webhookUrl?: string
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
interface SmsConfigShape = {
|
|
505
|
+
driver: 'twilio' | 'vonage'
|
|
506
|
+
twilio?: TwilioConfigShape
|
|
507
|
+
vonage?: VonageConfigShape
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
interface SmsMessage = {
|
|
511
|
+
body: string
|
|
512
|
+
from?: string
|
|
513
|
+
to?: string
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
interface TextStyle = {
|
|
517
|
+
bold?: boolean
|
|
518
|
+
color?: string
|
|
519
|
+
italic?: boolean
|
|
520
|
+
size?: number
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
interface TwilioConfigShape = {
|
|
524
|
+
accountSid: string
|
|
525
|
+
authToken: string
|
|
526
|
+
from: string
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
interface VonageConfigShape = {
|
|
530
|
+
apiKey: string
|
|
531
|
+
apiSecret: string
|
|
532
|
+
from: string
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
type AddressInput = string | MailAddress
|
|
536
|
+
|
|
537
|
+
type NotificationClass = (new (...args: never[]) => Notification) & { fromPayload?(data: Record<string, unknown>): Notification | Promise<Notification>;}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/notifications",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"CHANGELOG.md",
|
|
15
|
+
"api-surface.md",
|
|
15
16
|
"src",
|
|
16
17
|
"!src/**/*.test.ts",
|
|
17
18
|
"!src/**/*.test.tsx",
|
|
@@ -29,12 +30,12 @@
|
|
|
29
30
|
"typecheck": "tsc --noEmit"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@zerotal/core": "1.
|
|
33
|
-
"@zerotal/orm": "1.
|
|
34
|
-
"@zerotal/queue": "1.
|
|
33
|
+
"@zerotal/core": "1.7.2",
|
|
34
|
+
"@zerotal/orm": "1.7.2",
|
|
35
|
+
"@zerotal/queue": "1.7.2"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@zerotal/broadcasting": "1.
|
|
38
|
+
"@zerotal/broadcasting": "1.7.2",
|
|
38
39
|
"typescript": "^5.8.0"
|
|
39
40
|
},
|
|
40
41
|
"description": "Multi-channel notifications for Zerotal — mail (SMTP/Resend), database, broadcast, Slack, and SMS.",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { tryCurrentApp } from "@zerotal/core";
|
|
1
2
|
import {
|
|
2
3
|
resolveAddress,
|
|
3
4
|
type AddressInput,
|
|
@@ -6,6 +7,35 @@ import {
|
|
|
6
7
|
type MailPayload,
|
|
7
8
|
} from "../drivers/MailDriver.ts";
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Make an action URL absolute.
|
|
12
|
+
*
|
|
13
|
+
* A browser resolves `/projects/4` against the page it is on. An email client
|
|
14
|
+
* has no page, so a root-relative href in a delivered message is simply dead —
|
|
15
|
+
* and this is the one place the base URL cannot be left to the caller, because
|
|
16
|
+
* the caller is usually a queue job with no request to read an origin from.
|
|
17
|
+
*
|
|
18
|
+
* `config.app.url` is the origin the app already declares for exactly this.
|
|
19
|
+
* Resolution happens at render rather than in `action()` so a message can still
|
|
20
|
+
* be constructed outside an application — a unit test, or a driver being
|
|
21
|
+
* exercised on its own — where it is returned untouched.
|
|
22
|
+
*
|
|
23
|
+
* Anything already absolute is left alone, `mailto:` and `tel:` included.
|
|
24
|
+
*/
|
|
25
|
+
function _absoluteUrl(url: string): string {
|
|
26
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//")) return url;
|
|
27
|
+
|
|
28
|
+
const base = tryCurrentApp()?.container.tryMake("config")?.get<string>("app.url");
|
|
29
|
+
if (!base) return url;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return new URL(url, base).toString();
|
|
33
|
+
} catch {
|
|
34
|
+
// A base that is not a URL is a config error, not this message's problem.
|
|
35
|
+
return url;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
9
39
|
/** Inline text styling applied to a whole line or to a single run within a line. */
|
|
10
40
|
export interface TextStyle {
|
|
11
41
|
bold?: boolean;
|
|
@@ -302,7 +332,7 @@ export class MailMessage {
|
|
|
302
332
|
for (const l of this._introLines) parts.push(this._paragraph(l));
|
|
303
333
|
if (this._actionText && this._actionUrl) {
|
|
304
334
|
parts.push(
|
|
305
|
-
`<p style="margin:24px 0"><a href="${escapeHtml(this._actionUrl)}" ` +
|
|
335
|
+
`<p style="margin:24px 0"><a href="${escapeHtml(_absoluteUrl(this._actionUrl))}" ` +
|
|
306
336
|
`style="display:inline-block;background:#4f46e5;color:#fff;text-decoration:none;` +
|
|
307
337
|
`padding:12px 20px;border-radius:8px;font-size:14px;font-weight:600">${escapeHtml(this._actionText)}</a></p>`,
|
|
308
338
|
);
|
|
@@ -330,7 +360,8 @@ export class MailMessage {
|
|
|
330
360
|
private _renderText(): string {
|
|
331
361
|
const parts: string[] = [this._lineText(this._greeting ?? [{ text: "Hello," }])];
|
|
332
362
|
for (const l of this._introLines) parts.push(this._lineText(l));
|
|
333
|
-
if (this._actionText && this._actionUrl)
|
|
363
|
+
if (this._actionText && this._actionUrl)
|
|
364
|
+
parts.push(`${this._actionText}: ${_absoluteUrl(this._actionUrl)}`);
|
|
334
365
|
for (const l of this._outroLines) parts.push(this._lineText(l));
|
|
335
366
|
parts.push(this._lineText(this._salutation ?? [{ text: "Regards," }]));
|
|
336
367
|
return parts.join("\n\n");
|