@gravito/signal 3.0.0 ā 3.0.3
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/CHANGELOG.md +23 -0
- package/dist/ReactRenderer-KYNA4WKE.mjs +28 -0
- package/dist/VueRenderer-IIR5SYTM.mjs +31 -0
- package/dist/index.d.mts +387 -27
- package/dist/index.d.ts +387 -27
- package/dist/index.js +84 -11
- package/dist/index.mjs +84 -11
- package/package.json +1 -1
- package/src/Mailable.ts +102 -3
- package/src/OrbitSignal.ts +42 -7
- package/src/dev/DevMailbox.ts +26 -0
- package/src/dev/DevServer.ts +25 -0
- package/src/dev/ui/mailbox.ts +9 -0
- package/src/dev/ui/preview.ts +9 -0
- package/src/dev/ui/shared.ts +13 -0
- package/src/index.ts +1 -1
- package/src/renderers/HtmlRenderer.ts +19 -2
- package/src/renderers/ReactRenderer.ts +22 -2
- package/src/renderers/Renderer.ts +17 -1
- package/src/renderers/TemplateRenderer.ts +15 -0
- package/src/renderers/VueRenderer.ts +22 -2
- package/src/transports/LogTransport.ts +16 -0
- package/src/transports/MemoryTransport.ts +16 -0
- package/src/transports/SesTransport.ts +28 -0
- package/src/transports/SmtpTransport.ts +38 -0
- package/src/transports/Transport.ts +9 -1
- package/src/types.ts +70 -12
- package/tests/mailable-extra.test.ts +60 -53
- package/src/augmentation.ts +0 -13
package/src/Mailable.ts
CHANGED
|
@@ -8,6 +8,27 @@ import type { Address, Attachment, Envelope, MailConfig } from './types'
|
|
|
8
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
9
|
type ComponentType = any
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Base class for all mailable messages.
|
|
13
|
+
*
|
|
14
|
+
* Provides a fluent API for building email envelopes and rendering content
|
|
15
|
+
* using various engines (raw HTML, Prism templates, React, or Vue).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* class WelcomeMail extends Mailable {
|
|
20
|
+
* build() {
|
|
21
|
+
* return this.subject('Welcome!')
|
|
22
|
+
* .view('emails.welcome', { name: 'John' });
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* await new WelcomeMail().to('john@example.com').send();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
* @since 3.0.0
|
|
31
|
+
*/
|
|
11
32
|
export abstract class Mailable implements Queueable {
|
|
12
33
|
protected envelope: Partial<Envelope> = {}
|
|
13
34
|
protected renderer?: Renderer
|
|
@@ -17,41 +38,81 @@ export abstract class Mailable implements Queueable {
|
|
|
17
38
|
|
|
18
39
|
// ===== Fluent API (Envelope Construction) =====
|
|
19
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Set the sender address for the email.
|
|
43
|
+
*
|
|
44
|
+
* @param address - Email address or Address object.
|
|
45
|
+
*/
|
|
20
46
|
from(address: string | Address): this {
|
|
21
47
|
this.envelope.from = typeof address === 'string' ? { address } : address
|
|
22
48
|
return this
|
|
23
49
|
}
|
|
24
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Set the primary recipient(s) for the email.
|
|
53
|
+
*
|
|
54
|
+
* @param address - Single address, address object, or array of either.
|
|
55
|
+
*/
|
|
25
56
|
to(address: string | Address | (string | Address)[]): this {
|
|
26
57
|
this.envelope.to = this.normalizeAddressArray(address)
|
|
27
58
|
return this
|
|
28
59
|
}
|
|
29
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Set the carbon copy (CC) recipient(s).
|
|
63
|
+
*
|
|
64
|
+
* @param address - Single address, address object, or array of either.
|
|
65
|
+
*/
|
|
30
66
|
cc(address: string | Address | (string | Address)[]): this {
|
|
31
67
|
this.envelope.cc = this.normalizeAddressArray(address)
|
|
32
68
|
return this
|
|
33
69
|
}
|
|
34
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Set the blind carbon copy (BCC) recipient(s).
|
|
73
|
+
*
|
|
74
|
+
* @param address - Single address, address object, or array of either.
|
|
75
|
+
*/
|
|
35
76
|
bcc(address: string | Address | (string | Address)[]): this {
|
|
36
77
|
this.envelope.bcc = this.normalizeAddressArray(address)
|
|
37
78
|
return this
|
|
38
79
|
}
|
|
39
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Set the reply-to address.
|
|
83
|
+
*
|
|
84
|
+
* @param address - Email address or Address object.
|
|
85
|
+
*/
|
|
40
86
|
replyTo(address: string | Address): this {
|
|
41
87
|
this.envelope.replyTo = typeof address === 'string' ? { address } : address
|
|
42
88
|
return this
|
|
43
89
|
}
|
|
44
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Set the subject line for the email.
|
|
93
|
+
*
|
|
94
|
+
* @param subject - The email subject.
|
|
95
|
+
*/
|
|
45
96
|
subject(subject: string): this {
|
|
46
97
|
this.envelope.subject = subject
|
|
47
98
|
return this
|
|
48
99
|
}
|
|
49
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Set the email priority.
|
|
103
|
+
*
|
|
104
|
+
* @param level - Priority level ('high', 'normal', or 'low').
|
|
105
|
+
*/
|
|
50
106
|
emailPriority(level: 'high' | 'normal' | 'low'): this {
|
|
51
107
|
this.envelope.priority = level
|
|
52
108
|
return this
|
|
53
109
|
}
|
|
54
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Attach a file to the email.
|
|
113
|
+
*
|
|
114
|
+
* @param attachment - Attachment configuration object.
|
|
115
|
+
*/
|
|
55
116
|
attach(attachment: Attachment): this {
|
|
56
117
|
this.envelope.attachments = this.envelope.attachments || []
|
|
57
118
|
this.envelope.attachments.push(attachment)
|
|
@@ -62,6 +123,8 @@ export abstract class Mailable implements Queueable {
|
|
|
62
123
|
|
|
63
124
|
/**
|
|
64
125
|
* Set the content using raw HTML string.
|
|
126
|
+
*
|
|
127
|
+
* @param content - The HTML content string.
|
|
65
128
|
*/
|
|
66
129
|
html(content: string): this {
|
|
67
130
|
this.renderer = new HtmlRenderer(content)
|
|
@@ -70,8 +133,9 @@ export abstract class Mailable implements Queueable {
|
|
|
70
133
|
|
|
71
134
|
/**
|
|
72
135
|
* Set the content using an OrbitPrism template.
|
|
73
|
-
*
|
|
74
|
-
* @param
|
|
136
|
+
*
|
|
137
|
+
* @param template - Template name (relative to viewsDir/emails).
|
|
138
|
+
* @param data - Optional data to pass to the template.
|
|
75
139
|
*/
|
|
76
140
|
view(template: string, data?: Record<string, unknown>): this {
|
|
77
141
|
this.renderer = new TemplateRenderer(template, undefined) // Dir will be injected later if possible, or use default
|
|
@@ -82,6 +146,10 @@ export abstract class Mailable implements Queueable {
|
|
|
82
146
|
/**
|
|
83
147
|
* Set the content using a React component.
|
|
84
148
|
* Dynamically imports ReactRenderer to avoid hard dependency errors if React is not installed.
|
|
149
|
+
*
|
|
150
|
+
* @param component - The React component to render.
|
|
151
|
+
* @param props - Optional props for the component.
|
|
152
|
+
* @param deps - Optional dependencies (React, ReactDOMServer) if not globally available.
|
|
85
153
|
*/
|
|
86
154
|
react<P extends object>(
|
|
87
155
|
component: ComponentType,
|
|
@@ -101,6 +169,10 @@ export abstract class Mailable implements Queueable {
|
|
|
101
169
|
/**
|
|
102
170
|
* Set the content using a Vue component.
|
|
103
171
|
* Dynamically imports VueRenderer to avoid hard dependency errors if Vue is not installed.
|
|
172
|
+
*
|
|
173
|
+
* @param component - The Vue component to render.
|
|
174
|
+
* @param props - Optional props for the component.
|
|
175
|
+
* @param deps - Optional dependencies (Vue, VueServerRenderer) if not globally available.
|
|
104
176
|
*/
|
|
105
177
|
vue<P extends object>(
|
|
106
178
|
component: ComponentType,
|
|
@@ -122,31 +194,48 @@ export abstract class Mailable implements Queueable {
|
|
|
122
194
|
|
|
123
195
|
/**
|
|
124
196
|
* Setup the mailable. This is where you call from(), to(), view(), etc.
|
|
197
|
+
* This method must be implemented by concrete classes.
|
|
125
198
|
*/
|
|
126
199
|
abstract build(): this
|
|
127
200
|
|
|
128
201
|
// ===== Queueable Implementation =====
|
|
129
202
|
|
|
203
|
+
/** The name of the queue to push this mailable to. */
|
|
130
204
|
queueName?: string
|
|
205
|
+
/** The connection name for the queue. */
|
|
131
206
|
connectionName?: string
|
|
207
|
+
/** Delay in seconds before the message is sent. */
|
|
132
208
|
delaySeconds?: number
|
|
209
|
+
/** Priority of the message in the queue. */
|
|
133
210
|
priority?: number | string
|
|
134
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Set the queue name for this mailable.
|
|
214
|
+
*/
|
|
135
215
|
onQueue(queue: string): this {
|
|
136
216
|
this.queueName = queue
|
|
137
217
|
return this
|
|
138
218
|
}
|
|
139
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Set the connection name for this mailable.
|
|
222
|
+
*/
|
|
140
223
|
onConnection(connection: string): this {
|
|
141
224
|
this.connectionName = connection
|
|
142
225
|
return this
|
|
143
226
|
}
|
|
144
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Set a delay for the queued mailable.
|
|
230
|
+
*/
|
|
145
231
|
delay(seconds: number): this {
|
|
146
232
|
this.delaySeconds = seconds
|
|
147
233
|
return this
|
|
148
234
|
}
|
|
149
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Set the priority for the queued mailable.
|
|
238
|
+
*/
|
|
150
239
|
withPriority(priority: string | number): this {
|
|
151
240
|
this.priority = priority
|
|
152
241
|
return this
|
|
@@ -154,6 +243,7 @@ export abstract class Mailable implements Queueable {
|
|
|
154
243
|
|
|
155
244
|
/**
|
|
156
245
|
* Queue the mailable for sending.
|
|
246
|
+
* Attempts to resolve the mail service from the PlanetCore container.
|
|
157
247
|
*/
|
|
158
248
|
async queue(): Promise<void> {
|
|
159
249
|
// We should ideally use the container to get the mail service
|
|
@@ -179,6 +269,8 @@ export abstract class Mailable implements Queueable {
|
|
|
179
269
|
|
|
180
270
|
/**
|
|
181
271
|
* Set the locale for the message.
|
|
272
|
+
*
|
|
273
|
+
* @param locale - Valid locale string (e.g., 'en', 'zh-TW').
|
|
182
274
|
*/
|
|
183
275
|
locale(locale: string): this {
|
|
184
276
|
this.currentLocale = locale
|
|
@@ -187,6 +279,7 @@ export abstract class Mailable implements Queueable {
|
|
|
187
279
|
|
|
188
280
|
/**
|
|
189
281
|
* Internal: Set the translator function (called by OrbitSignal)
|
|
282
|
+
* @internal
|
|
190
283
|
*/
|
|
191
284
|
setTranslator(
|
|
192
285
|
translator: (key: string, replace?: Record<string, unknown>, locale?: string) => string
|
|
@@ -196,6 +289,10 @@ export abstract class Mailable implements Queueable {
|
|
|
196
289
|
|
|
197
290
|
/**
|
|
198
291
|
* Translate a string using the configured translator.
|
|
292
|
+
*
|
|
293
|
+
* @param key - Translation key.
|
|
294
|
+
* @param replace - Interpolation variables.
|
|
295
|
+
* @returns Translated string or the key if translator is missing.
|
|
199
296
|
*/
|
|
200
297
|
t(key: string, replace?: Record<string, unknown>): string {
|
|
201
298
|
if (this.translator) {
|
|
@@ -208,6 +305,7 @@ export abstract class Mailable implements Queueable {
|
|
|
208
305
|
|
|
209
306
|
/**
|
|
210
307
|
* Compile the envelope based on config defaults and mailable settings.
|
|
308
|
+
* Called by OrbitSignal before rendering/sending.
|
|
211
309
|
*/
|
|
212
310
|
async buildEnvelope(configPromise: MailConfig | Promise<MailConfig>): Promise<Envelope> {
|
|
213
311
|
const config = await Promise.resolve(configPromise)
|
|
@@ -250,7 +348,8 @@ export abstract class Mailable implements Queueable {
|
|
|
250
348
|
}
|
|
251
349
|
|
|
252
350
|
/**
|
|
253
|
-
*
|
|
351
|
+
* Execute the renderer and produce HTML/Text content.
|
|
352
|
+
* @returns Object containing rendered html and optional text content.
|
|
254
353
|
*/
|
|
255
354
|
async renderContent(): Promise<{ html: string; text?: string }> {
|
|
256
355
|
// Resolve lazy renderer if needed
|
package/src/OrbitSignal.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GravitoOrbit, PlanetCore } from '@gravito/core'
|
|
1
|
+
import type { GravitoContext, GravitoNext, GravitoOrbit, PlanetCore } from '@gravito/core'
|
|
2
2
|
import { DevMailbox } from './dev/DevMailbox'
|
|
3
3
|
import { DevServer } from './dev/DevServer'
|
|
4
4
|
import type { Mailable } from './Mailable'
|
|
@@ -6,6 +6,37 @@ import { LogTransport } from './transports/LogTransport'
|
|
|
6
6
|
import { MemoryTransport } from './transports/MemoryTransport'
|
|
7
7
|
import type { MailConfig, Message } from './types'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* OrbitSignal - Mail service orbit for Gravito framework.
|
|
11
|
+
*
|
|
12
|
+
* Provides email sending capabilities with support for multiple transports,
|
|
13
|
+
* development mode with email preview UI, and queue integration.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { OrbitSignal } from '@gravito/signal'
|
|
18
|
+
* import { SmtpTransport } from '@gravito/signal'
|
|
19
|
+
*
|
|
20
|
+
* const app = new Application({
|
|
21
|
+
* orbits: [
|
|
22
|
+
* new OrbitSignal({
|
|
23
|
+
* transport: new SmtpTransport({
|
|
24
|
+
* host: 'smtp.example.com',
|
|
25
|
+
* port: 587,
|
|
26
|
+
* auth: { user: 'user', pass: 'pass' }
|
|
27
|
+
* }),
|
|
28
|
+
* from: { name: 'App', email: 'noreply@example.com' }
|
|
29
|
+
* })
|
|
30
|
+
* ]
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // In route handler
|
|
34
|
+
* await c.get('mail').send(new WelcomeEmail(user))
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @since 3.0.0
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
9
40
|
export class OrbitSignal implements GravitoOrbit {
|
|
10
41
|
private config: MailConfig
|
|
11
42
|
private devMailbox?: DevMailbox
|
|
@@ -43,10 +74,10 @@ export class OrbitSignal implements GravitoOrbit {
|
|
|
43
74
|
}
|
|
44
75
|
|
|
45
76
|
// 3. Register in container
|
|
46
|
-
core.container.
|
|
77
|
+
core.container.instance('mail', this)
|
|
47
78
|
|
|
48
79
|
// 4. Inject mail service into context for easy access in routes
|
|
49
|
-
core.adapter.use('*', async (c:
|
|
80
|
+
core.adapter.use('*', async (c: GravitoContext, next: GravitoNext) => {
|
|
50
81
|
c.set('mail', this)
|
|
51
82
|
return await next()
|
|
52
83
|
})
|
|
@@ -54,10 +85,6 @@ export class OrbitSignal implements GravitoOrbit {
|
|
|
54
85
|
|
|
55
86
|
/**
|
|
56
87
|
* Send a mailable instance
|
|
57
|
-
*
|
|
58
|
-
* @param mailable - The mailable object to send.
|
|
59
|
-
* @returns A promise that resolves when the email is sent.
|
|
60
|
-
* @throws {Error} If the message is missing "from" or "to" addresses, or if no transport is configured.
|
|
61
88
|
*/
|
|
62
89
|
async send(mailable: Mailable): Promise<void> {
|
|
63
90
|
// 1. Build envelope and get configuration
|
|
@@ -114,3 +141,11 @@ export class OrbitSignal implements GravitoOrbit {
|
|
|
114
141
|
await this.send(mailable)
|
|
115
142
|
}
|
|
116
143
|
}
|
|
144
|
+
|
|
145
|
+
// Module augmentation for GravitoVariables
|
|
146
|
+
declare module '@gravito/core' {
|
|
147
|
+
interface GravitoVariables {
|
|
148
|
+
/** Mail service for sending emails */
|
|
149
|
+
mail?: OrbitSignal
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/dev/DevMailbox.ts
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import type { Envelope, Message } from '../types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Represents a captured email in the development mailbox.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
4
9
|
export interface MailboxEntry {
|
|
10
|
+
/** Unique identifier for the email */
|
|
5
11
|
id: string
|
|
12
|
+
/** Email envelope information (from, to, subject, etc.) */
|
|
6
13
|
envelope: Envelope
|
|
14
|
+
/** HTML content of the email */
|
|
7
15
|
html: string
|
|
16
|
+
/** Plain text content of the email (optional) */
|
|
8
17
|
text?: string
|
|
18
|
+
/** Timestamp when the email was captured */
|
|
9
19
|
sentAt: Date
|
|
10
20
|
}
|
|
11
21
|
|
|
22
|
+
/**
|
|
23
|
+
* In-memory mailbox for capturing emails during development.
|
|
24
|
+
*
|
|
25
|
+
* Stores up to 50 recent emails and provides methods to list,
|
|
26
|
+
* retrieve, and delete captured messages.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const mailbox = new DevMailbox()
|
|
31
|
+
* mailbox.add(message)
|
|
32
|
+
* const emails = mailbox.list()
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @since 3.0.0
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
12
38
|
export class DevMailbox {
|
|
13
39
|
private entries: MailboxEntry[] = []
|
|
14
40
|
private maxEntries = 50
|
package/src/dev/DevServer.ts
CHANGED
|
@@ -3,11 +3,36 @@ import type { DevMailbox } from './DevMailbox'
|
|
|
3
3
|
import { getMailboxHtml } from './ui/mailbox'
|
|
4
4
|
import { getPreviewHtml } from './ui/preview'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for the development mail server.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
6
11
|
export type DevServerOptions = {
|
|
12
|
+
/** Allow access in production environment (use with caution) */
|
|
7
13
|
allowInProduction?: boolean
|
|
14
|
+
/** Custom gate function to control access to the dev UI */
|
|
8
15
|
gate?: (c: GravitoContext) => boolean | Promise<boolean>
|
|
9
16
|
}
|
|
10
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Development mail server for previewing captured emails.
|
|
20
|
+
*
|
|
21
|
+
* Provides a web UI at the configured base path (default: `/__mail`)
|
|
22
|
+
* to view, preview, and manage emails captured during development.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const mailbox = new DevMailbox()
|
|
27
|
+
* const devServer = new DevServer(mailbox, '/__mail', {
|
|
28
|
+
* gate: (c) => c.get('user')?.isAdmin
|
|
29
|
+
* })
|
|
30
|
+
* devServer.register(core)
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @since 3.0.0
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
11
36
|
export class DevServer {
|
|
12
37
|
constructor(
|
|
13
38
|
private mailbox: DevMailbox,
|
package/src/dev/ui/mailbox.ts
CHANGED
|
@@ -21,6 +21,15 @@ function timeAgo(date: Date): string {
|
|
|
21
21
|
return date.toLocaleDateString()
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Generates the HTML for the mailbox list view.
|
|
26
|
+
*
|
|
27
|
+
* @param entries - Array of mailbox entries to display
|
|
28
|
+
* @param prefix - Base URL prefix for the dev server
|
|
29
|
+
* @returns HTML string for the mailbox UI
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
24
33
|
export function getMailboxHtml(entries: MailboxEntry[], prefix: string): string {
|
|
25
34
|
const list =
|
|
26
35
|
entries.length === 0
|
package/src/dev/ui/preview.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { MailboxEntry } from '../DevMailbox'
|
|
2
2
|
import { layout } from './shared'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Generates the HTML for the email preview page.
|
|
6
|
+
*
|
|
7
|
+
* @param entry - Mailbox entry to preview
|
|
8
|
+
* @param prefix - Base URL prefix for the dev server
|
|
9
|
+
* @returns HTML string for the preview UI
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
4
13
|
export function getPreviewHtml(entry: MailboxEntry, prefix: string): string {
|
|
5
14
|
const from = entry.envelope.from
|
|
6
15
|
? `${entry.envelope.from.name || ''} <${entry.envelope.from.address}>`
|
package/src/dev/ui/shared.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS styles for the development mail UI.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
1
5
|
export const styles = `
|
|
2
6
|
:root {
|
|
3
7
|
--primary: #6366f1;
|
|
@@ -30,6 +34,15 @@ body { background: var(--bg-dark); color: var(--text); font-family: -apple-syste
|
|
|
30
34
|
.empty { padding: 40px; text-align: center; color: var(--text-muted); }
|
|
31
35
|
`
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* HTML layout wrapper for dev mail UI pages.
|
|
39
|
+
*
|
|
40
|
+
* @param title - Page title
|
|
41
|
+
* @param content - HTML content to inject
|
|
42
|
+
* @returns Complete HTML document
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
33
46
|
export const layout = (title: string, content: string) => `
|
|
34
47
|
<!DOCTYPE html>
|
|
35
48
|
<html>
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { Renderer, RenderResult } from './Renderer'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Renderer for plain HTML content.
|
|
5
|
+
*
|
|
6
|
+
* Renders static HTML strings and automatically generates
|
|
7
|
+
* a plain text version by stripping HTML tags.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const renderer = new HtmlRenderer('<h1>Hello</h1><p>World</p>')
|
|
12
|
+
* const { html, text } = await renderer.render()
|
|
13
|
+
* // html: '<h1>Hello</h1><p>World</p>'
|
|
14
|
+
* // text: 'Hello World'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @since 3.0.0
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
3
20
|
export class HtmlRenderer implements Renderer {
|
|
4
21
|
constructor(private content: string) {}
|
|
5
22
|
|
|
@@ -12,8 +29,8 @@ export class HtmlRenderer implements Renderer {
|
|
|
12
29
|
|
|
13
30
|
private stripHtml(html: string): string {
|
|
14
31
|
return html
|
|
15
|
-
.replace(/<style[^>]
|
|
16
|
-
.replace(/<script[^>]
|
|
32
|
+
.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, '') // Remove styles
|
|
33
|
+
.replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, '') // Remove scripts
|
|
17
34
|
.replace(/<[^>]+>/g, '') // Remove tags
|
|
18
35
|
.replace(/ /g, ' ') // Replace non-breaking space
|
|
19
36
|
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import type { Renderer, RenderResult } from './Renderer'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Renderer for React component-based emails.
|
|
5
|
+
*
|
|
6
|
+
* Renders React components to static HTML for email delivery.
|
|
7
|
+
* Supports server-side rendering with optional dependency injection
|
|
8
|
+
* for testing.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { WelcomeEmail } from './emails/WelcomeEmail'
|
|
13
|
+
*
|
|
14
|
+
* const renderer = new ReactRenderer(WelcomeEmail, { name: 'John' })
|
|
15
|
+
* const { html, text } = await renderer.render({ date: new Date() })
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @typeParam P - Props type for the React component
|
|
19
|
+
*
|
|
20
|
+
* @since 3.0.0
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
3
23
|
export class ReactRenderer<P extends object = object> implements Renderer {
|
|
4
24
|
constructor(
|
|
5
25
|
private component: any, // Use any to avoid hard React dependency in types
|
|
@@ -31,8 +51,8 @@ export class ReactRenderer<P extends object = object> implements Renderer {
|
|
|
31
51
|
|
|
32
52
|
private stripHtml(html: string): string {
|
|
33
53
|
return html
|
|
34
|
-
.replace(/<style[^>]
|
|
35
|
-
.replace(/<script[^>]
|
|
54
|
+
.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, '')
|
|
55
|
+
.replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, '')
|
|
36
56
|
.replace(/<[^>]+>/g, '')
|
|
37
57
|
.replace(/ /g, ' ')
|
|
38
58
|
.replace(/\s+/g, ' ')
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a content rendering operation.
|
|
3
|
+
*
|
|
4
|
+
* @public
|
|
5
|
+
* @since 3.0.0
|
|
6
|
+
*/
|
|
1
7
|
export interface RenderResult {
|
|
8
|
+
/** Rendered HTML string. */
|
|
2
9
|
html: string
|
|
10
|
+
/** Optional rendered plain text string. */
|
|
3
11
|
text?: string
|
|
4
12
|
}
|
|
5
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Interface for email content renderers (HTML, Template, React, Vue).
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
* @since 3.0.0
|
|
19
|
+
*/
|
|
6
20
|
export interface Renderer {
|
|
7
21
|
/**
|
|
8
|
-
* Render the content into HTML and optionally plain text
|
|
22
|
+
* Render the content into HTML and optionally plain text.
|
|
23
|
+
*
|
|
24
|
+
* @param data - The data context for rendering.
|
|
9
25
|
*/
|
|
10
26
|
render(data: Record<string, unknown>): Promise<RenderResult>
|
|
11
27
|
}
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { Renderer, RenderResult } from './Renderer'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Renderer for template-based emails using Gravito Prism.
|
|
5
|
+
*
|
|
6
|
+
* Renders email templates from the filesystem using the Prism
|
|
7
|
+
* template engine with support for data binding and layouts.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const renderer = new TemplateRenderer('welcome', './src/emails')
|
|
12
|
+
* const { html, text } = await renderer.render({ name: 'John' })
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @since 3.0.0
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
3
18
|
export class TemplateRenderer implements Renderer {
|
|
4
19
|
private template: string
|
|
5
20
|
private viewsDir: string
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import type { Renderer, RenderResult } from './Renderer'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Renderer for Vue component-based emails.
|
|
5
|
+
*
|
|
6
|
+
* Renders Vue 3 components to static HTML for email delivery
|
|
7
|
+
* using server-side rendering. Supports optional dependency
|
|
8
|
+
* injection for testing.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { WelcomeEmail } from './emails/WelcomeEmail.vue'
|
|
13
|
+
*
|
|
14
|
+
* const renderer = new VueRenderer(WelcomeEmail, { name: 'John' })
|
|
15
|
+
* const { html, text } = await renderer.render({ date: new Date() })
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @typeParam P - Props type for the Vue component
|
|
19
|
+
*
|
|
20
|
+
* @since 3.0.0
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
3
23
|
export class VueRenderer<P extends object = object> implements Renderer {
|
|
4
24
|
constructor(
|
|
5
25
|
private component: any, // Use any to avoid hard Vue dependency in types
|
|
@@ -36,8 +56,8 @@ export class VueRenderer<P extends object = object> implements Renderer {
|
|
|
36
56
|
|
|
37
57
|
private stripHtml(html: string): string {
|
|
38
58
|
return html
|
|
39
|
-
.replace(/<style[^>]
|
|
40
|
-
.replace(/<script[^>]
|
|
59
|
+
.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, '')
|
|
60
|
+
.replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, '')
|
|
41
61
|
.replace(/<[^>]+>/g, '')
|
|
42
62
|
.replace(/ /g, ' ')
|
|
43
63
|
.replace(/\s+/g, ' ')
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { Message, Transport } from '../types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Log transport for development and testing.
|
|
5
|
+
*
|
|
6
|
+
* Logs email details to the console instead of sending them.
|
|
7
|
+
* Useful for debugging and local development.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const transport = new LogTransport()
|
|
12
|
+
* await transport.send(message)
|
|
13
|
+
* // Outputs email details to console
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @since 3.0.0
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
3
19
|
export class LogTransport implements Transport {
|
|
4
20
|
async send(message: Message): Promise<void> {
|
|
5
21
|
console.log('\nš§ [OrbitSignal] Email Sent (Simulated):')
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { DevMailbox } from '../dev/DevMailbox'
|
|
2
2
|
import type { Message, Transport } from '../types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Memory transport for development mode.
|
|
6
|
+
*
|
|
7
|
+
* Captures emails to an in-memory mailbox instead of sending them.
|
|
8
|
+
* Used automatically when `devMode` is enabled in OrbitSignal.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const mailbox = new DevMailbox()
|
|
13
|
+
* const transport = new MemoryTransport(mailbox)
|
|
14
|
+
* await transport.send(message)
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @since 3.0.0
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
4
20
|
export class MemoryTransport implements Transport {
|
|
5
21
|
constructor(private mailbox: DevMailbox) {}
|
|
6
22
|
|