@strav/signal 0.2.7 → 0.2.9

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.
Files changed (2) hide show
  1. package/README.md +366 -0
  2. package/package.json +6 -6
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strav/signal",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "description": "Communication layer for the Strav framework — mail, notifications, and broadcasting",
6
6
  "license": "MIT",
@@ -30,11 +30,11 @@
30
30
  "./providers/*": "./src/providers/*.ts"
31
31
  },
32
32
  "peerDependencies": {
33
- "@strav/kernel": "0.2.6",
34
- "@strav/http": "0.2.6",
35
- "@strav/view": "0.2.6",
36
- "@strav/database": "0.2.6",
37
- "@strav/queue": "0.2.6"
33
+ "@strav/kernel": "0.2.8",
34
+ "@strav/http": "0.2.8",
35
+ "@strav/view": "0.2.8",
36
+ "@strav/database": "0.2.8",
37
+ "@strav/queue": "0.2.8"
38
38
  },
39
39
  "dependencies": {
40
40
  "nodemailer": "^6.10.0",