@strav/signal 0.2.7 → 0.2.8
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 +366 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# @strav/signal
|
|
2
|
+
|
|
3
|
+
Communication layer for the Strav framework — mail, notifications, and real-time broadcasting.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @strav/signal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Mail**: Send emails via multiple transports (SMTP, Resend, SendGrid, Mailgun, Alibaba Cloud)
|
|
14
|
+
- **Notifications**: Multi-channel notifications (email, database, webhook, Discord)
|
|
15
|
+
- **Broadcasting**: Real-time WebSocket broadcasting with channel authorization
|
|
16
|
+
- **Queue Integration**: Async mail and notification sending via @strav/queue
|
|
17
|
+
- **Template Support**: Uses @strav/view for email templates with CSS inlining
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Mail
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { mail } from '@strav/signal'
|
|
25
|
+
|
|
26
|
+
// Fluent API
|
|
27
|
+
await mail
|
|
28
|
+
.to('user@example.com')
|
|
29
|
+
.subject('Welcome!')
|
|
30
|
+
.template('welcome', { name: 'Alice' })
|
|
31
|
+
.send()
|
|
32
|
+
|
|
33
|
+
// Convenience API
|
|
34
|
+
await mail.send({
|
|
35
|
+
to: 'user@example.com',
|
|
36
|
+
subject: 'Welcome!',
|
|
37
|
+
template: 'welcome',
|
|
38
|
+
data: { name: 'Alice' }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Queue mail for async sending
|
|
42
|
+
await mail
|
|
43
|
+
.to('user@example.com')
|
|
44
|
+
.subject('Newsletter')
|
|
45
|
+
.template('newsletter', data)
|
|
46
|
+
.queue()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Notifications
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { notify, BaseNotification } from '@strav/signal'
|
|
53
|
+
|
|
54
|
+
// Define a notification
|
|
55
|
+
class WelcomeNotification extends BaseNotification {
|
|
56
|
+
via() {
|
|
57
|
+
return ['email', 'database']
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toMail(notifiable) {
|
|
61
|
+
return {
|
|
62
|
+
subject: 'Welcome!',
|
|
63
|
+
template: 'welcome',
|
|
64
|
+
data: { name: notifiable.name }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toDatabase(notifiable) {
|
|
69
|
+
return {
|
|
70
|
+
type: 'welcome',
|
|
71
|
+
message: `Welcome, ${notifiable.name}!`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Send notification
|
|
77
|
+
await notify(user, new WelcomeNotification())
|
|
78
|
+
|
|
79
|
+
// Send to multiple users
|
|
80
|
+
await notify([user1, user2], new WelcomeNotification())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Broadcasting
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { broadcast } from '@strav/signal'
|
|
87
|
+
|
|
88
|
+
// Setup (in your app bootstrap)
|
|
89
|
+
broadcast.boot(router, {
|
|
90
|
+
middleware: [session()]
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Define channels with authorization
|
|
94
|
+
broadcast.channel('notifications', async (ctx) => {
|
|
95
|
+
return !!ctx.get('user')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
broadcast.channel('chat/:id', async (ctx, { id }) => {
|
|
99
|
+
const user = ctx.get('user')
|
|
100
|
+
return user && await user.canAccessChat(id)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Broadcast from server
|
|
104
|
+
broadcast.to('notifications').send('alert', { text: 'Hello' })
|
|
105
|
+
broadcast.to(`chat/${chatId}`).except(userId).send('message', data)
|
|
106
|
+
|
|
107
|
+
// Client-side (in browser)
|
|
108
|
+
import { Broadcast } from '@strav/signal/broadcast'
|
|
109
|
+
|
|
110
|
+
const broadcast = new Broadcast('/broadcast')
|
|
111
|
+
const subscription = broadcast.subscribe('notifications')
|
|
112
|
+
subscription.on('alert', (data) => console.log(data))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
### Mail Configuration
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// config/mail.ts
|
|
121
|
+
export default {
|
|
122
|
+
mail: {
|
|
123
|
+
default: 'smtp',
|
|
124
|
+
from: 'noreply@example.com',
|
|
125
|
+
templatePrefix: 'mail',
|
|
126
|
+
inlineCss: true,
|
|
127
|
+
tailwind: false,
|
|
128
|
+
|
|
129
|
+
transports: {
|
|
130
|
+
smtp: {
|
|
131
|
+
host: 'smtp.example.com',
|
|
132
|
+
port: 587,
|
|
133
|
+
secure: false,
|
|
134
|
+
auth: {
|
|
135
|
+
user: 'username',
|
|
136
|
+
pass: 'password'
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
resend: {
|
|
140
|
+
apiKey: 'your-api-key',
|
|
141
|
+
from: 'onboarding@resend.dev'
|
|
142
|
+
},
|
|
143
|
+
sendgrid: {
|
|
144
|
+
apiKey: 'your-api-key'
|
|
145
|
+
},
|
|
146
|
+
mailgun: {
|
|
147
|
+
apiKey: 'your-api-key',
|
|
148
|
+
domain: 'mg.example.com',
|
|
149
|
+
region: 'us' // or 'eu'
|
|
150
|
+
},
|
|
151
|
+
alibaba: {
|
|
152
|
+
accessKeyId: 'your-access-key',
|
|
153
|
+
accessKeySecret: 'your-secret',
|
|
154
|
+
accountName: 'noreply@example.com',
|
|
155
|
+
region: 'cn-hangzhou'
|
|
156
|
+
},
|
|
157
|
+
log: {
|
|
158
|
+
level: 'info'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Notification Configuration
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// config/notification.ts
|
|
169
|
+
export default {
|
|
170
|
+
notification: {
|
|
171
|
+
channels: {
|
|
172
|
+
email: {
|
|
173
|
+
from: 'notifications@example.com'
|
|
174
|
+
},
|
|
175
|
+
database: {
|
|
176
|
+
table: 'notifications',
|
|
177
|
+
markAsReadOnGet: true
|
|
178
|
+
},
|
|
179
|
+
webhook: {
|
|
180
|
+
timeout: 5000,
|
|
181
|
+
headers: {
|
|
182
|
+
'X-Service': 'MyApp'
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
discord: {
|
|
186
|
+
username: 'MyApp Bot',
|
|
187
|
+
avatarUrl: 'https://example.com/avatar.png'
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Service Providers
|
|
195
|
+
|
|
196
|
+
Register providers in your app:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { MailProvider, NotificationProvider, BroadcastProvider } from '@strav/signal'
|
|
200
|
+
|
|
201
|
+
app.register([
|
|
202
|
+
MailProvider,
|
|
203
|
+
NotificationProvider,
|
|
204
|
+
BroadcastProvider
|
|
205
|
+
])
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## API Reference
|
|
209
|
+
|
|
210
|
+
### Mail
|
|
211
|
+
|
|
212
|
+
#### `mail.to(address: string | string[]): PendingMail`
|
|
213
|
+
Start building an email with fluent API.
|
|
214
|
+
|
|
215
|
+
#### `PendingMail` methods:
|
|
216
|
+
- `from(address: string)`: Set sender
|
|
217
|
+
- `cc(address: string | string[])`: Add CC recipients
|
|
218
|
+
- `bcc(address: string | string[])`: Add BCC recipients
|
|
219
|
+
- `replyTo(address: string)`: Set reply-to address
|
|
220
|
+
- `subject(value: string)`: Set subject
|
|
221
|
+
- `template(name: string, data?: Record<string, any>)`: Use template
|
|
222
|
+
- `html(value: string)`: Set raw HTML content
|
|
223
|
+
- `text(value: string)`: Set plain text content
|
|
224
|
+
- `attach(attachment: MailAttachment)`: Add attachment
|
|
225
|
+
- `send()`: Send immediately
|
|
226
|
+
- `queue(options?)`: Queue for async sending
|
|
227
|
+
|
|
228
|
+
#### `mail.send(options)`
|
|
229
|
+
Convenience method for sending with template.
|
|
230
|
+
|
|
231
|
+
#### `mail.raw(options)`
|
|
232
|
+
Send raw HTML/text without template.
|
|
233
|
+
|
|
234
|
+
#### `mail.registerQueueHandler()`
|
|
235
|
+
Register queue handler for async mail sending.
|
|
236
|
+
|
|
237
|
+
### Notifications
|
|
238
|
+
|
|
239
|
+
#### `notify(notifiable: Notifiable | Notifiable[], notification: BaseNotification)`
|
|
240
|
+
Send notification to one or more recipients.
|
|
241
|
+
|
|
242
|
+
#### `BaseNotification` abstract class
|
|
243
|
+
Override methods:
|
|
244
|
+
- `via(): string[]`: Channels to use
|
|
245
|
+
- `toMail(notifiable)`: Mail envelope
|
|
246
|
+
- `toDatabase(notifiable)`: Database payload
|
|
247
|
+
- `toWebhook(notifiable)`: Webhook envelope
|
|
248
|
+
- `toDiscord(notifiable)`: Discord envelope
|
|
249
|
+
|
|
250
|
+
#### `notifications` helper
|
|
251
|
+
- `markAsRead(userId, notificationIds)`: Mark as read
|
|
252
|
+
- `markAllAsRead(userId)`: Mark all as read
|
|
253
|
+
- `unread(userId, limit?)`: Get unread notifications
|
|
254
|
+
- `all(userId, limit?)`: Get all notifications
|
|
255
|
+
- `delete(userId, notificationIds)`: Delete notifications
|
|
256
|
+
|
|
257
|
+
### Broadcasting
|
|
258
|
+
|
|
259
|
+
#### `broadcast.boot(router: Router, options?: BootOptions)`
|
|
260
|
+
Initialize WebSocket endpoint.
|
|
261
|
+
|
|
262
|
+
#### `broadcast.channel(pattern: string, authorize?: AuthorizeCallback | ChannelConfig)`
|
|
263
|
+
Register channel with optional authorization.
|
|
264
|
+
|
|
265
|
+
#### `broadcast.to(channel: string): PendingBroadcast`
|
|
266
|
+
Start broadcasting to a channel.
|
|
267
|
+
|
|
268
|
+
#### `PendingBroadcast` methods:
|
|
269
|
+
- `except(clientId: string | string[])`: Exclude specific clients
|
|
270
|
+
- `send(event: string, data: any)`: Send to all matching clients
|
|
271
|
+
|
|
272
|
+
#### Client-side `Broadcast` class
|
|
273
|
+
- `constructor(url: string, options?: BroadcastOptions)`
|
|
274
|
+
- `subscribe(channel: string): Subscription`
|
|
275
|
+
- `unsubscribe(channel: string)`
|
|
276
|
+
- `disconnect()`
|
|
277
|
+
|
|
278
|
+
#### `Subscription` class
|
|
279
|
+
- `on(event: string, handler: Function)`: Listen for events
|
|
280
|
+
- `off(event: string, handler?: Function)`: Remove listener
|
|
281
|
+
- `send(event: string, data: any)`: Send to channel
|
|
282
|
+
|
|
283
|
+
## Mail Transports
|
|
284
|
+
|
|
285
|
+
### SMTP Transport
|
|
286
|
+
Standard SMTP configuration using nodemailer.
|
|
287
|
+
|
|
288
|
+
### Resend Transport
|
|
289
|
+
Integration with [Resend](https://resend.com) email API.
|
|
290
|
+
|
|
291
|
+
### SendGrid Transport
|
|
292
|
+
Integration with [SendGrid](https://sendgrid.com) email API.
|
|
293
|
+
|
|
294
|
+
### Mailgun Transport
|
|
295
|
+
Integration with [Mailgun](https://mailgun.com) email API.
|
|
296
|
+
|
|
297
|
+
### Alibaba Cloud Transport
|
|
298
|
+
Integration with Alibaba Cloud DirectMail service.
|
|
299
|
+
|
|
300
|
+
### Log Transport
|
|
301
|
+
Logs emails to console/file for development.
|
|
302
|
+
|
|
303
|
+
## Notification Channels
|
|
304
|
+
|
|
305
|
+
### Email Channel
|
|
306
|
+
Sends notifications via configured mail transport.
|
|
307
|
+
|
|
308
|
+
### Database Channel
|
|
309
|
+
Stores notifications in database for in-app display.
|
|
310
|
+
|
|
311
|
+
### Webhook Channel
|
|
312
|
+
Sends notifications to external webhook URLs.
|
|
313
|
+
|
|
314
|
+
### Discord Channel
|
|
315
|
+
Sends rich embed notifications to Discord webhooks.
|
|
316
|
+
|
|
317
|
+
## Advanced Features
|
|
318
|
+
|
|
319
|
+
### CSS Inlining
|
|
320
|
+
Automatically inline CSS for better email client compatibility:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { inlineCss } from '@strav/signal'
|
|
324
|
+
|
|
325
|
+
const inlined = await inlineCss(html, {
|
|
326
|
+
enabled: true,
|
|
327
|
+
tailwind: true // Load Tailwind styles
|
|
328
|
+
})
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Custom Transports
|
|
332
|
+
Create custom mail transports:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import type { MailTransport } from '@strav/signal'
|
|
336
|
+
|
|
337
|
+
class CustomTransport implements MailTransport {
|
|
338
|
+
async send(message: MailMessage): Promise<MailResult> {
|
|
339
|
+
// Your implementation
|
|
340
|
+
return { success: true, messageId: '...' }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Custom Notification Channels
|
|
346
|
+
Create custom notification channels:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import type { NotificationChannel } from '@strav/signal'
|
|
350
|
+
|
|
351
|
+
class CustomChannel implements NotificationChannel {
|
|
352
|
+
async send(notifiable: Notifiable, notification: BaseNotification): Promise<void> {
|
|
353
|
+
// Your implementation
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Testing
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
bun test
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
MIT
|