@gravito/signal 3.1.0 → 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/dist/OrbitSignal.d.ts +6 -0
- package/dist/errors/codes.d.ts +19 -0
- package/dist/errors.d.ts +18 -11
- package/dist/index.cjs +55148 -81886
- package/dist/index.d.ts +514 -1
- package/dist/index.js +61553 -0
- package/dist/index.mjs +55158 -81907
- package/dist/transports/BaseTransport.d.ts +15 -23
- package/package.json +16 -4
- package/CHANGELOG.md +0 -74
- package/build.ts +0 -133
- package/dist/index.cjs.map +0 -712
- package/dist/index.mjs.map +0 -710
- package/doc/ADVANCED_RENDERING.md +0 -71
- package/doc/DISTRIBUTED_MESSAGING.md +0 -79
- package/doc/OPTIMIZATION_PLAN.md +0 -496
- package/package.json.bak +0 -75
- package/scripts/check-coverage.ts +0 -64
- package/src/Mailable.ts +0 -674
- package/src/OrbitSignal.ts +0 -451
- package/src/Queueable.ts +0 -9
- package/src/TypedMailable.ts +0 -96
- package/src/dev/DevMailbox.ts +0 -146
- package/src/dev/DevServer.ts +0 -192
- package/src/dev/storage/FileMailboxStorage.ts +0 -66
- package/src/dev/storage/MailboxStorage.ts +0 -15
- package/src/dev/storage/MemoryMailboxStorage.ts +0 -36
- package/src/dev/ui/mailbox.ts +0 -77
- package/src/dev/ui/preview.ts +0 -103
- package/src/dev/ui/shared.ts +0 -60
- package/src/errors.ts +0 -69
- package/src/events.ts +0 -72
- package/src/index.ts +0 -41
- package/src/renderers/HtmlRenderer.ts +0 -41
- package/src/renderers/MjmlRenderer.ts +0 -73
- package/src/renderers/ReactMjmlRenderer.ts +0 -94
- package/src/renderers/ReactRenderer.ts +0 -66
- package/src/renderers/Renderer.ts +0 -67
- package/src/renderers/TemplateRenderer.ts +0 -84
- package/src/renderers/VueMjmlRenderer.ts +0 -99
- package/src/renderers/VueRenderer.ts +0 -71
- package/src/renderers/mjml-templates.ts +0 -50
- package/src/transports/BaseTransport.ts +0 -148
- package/src/transports/LogTransport.ts +0 -55
- package/src/transports/MemoryTransport.ts +0 -55
- package/src/transports/SesTransport.ts +0 -129
- package/src/transports/SmtpTransport.ts +0 -184
- package/src/transports/Transport.ts +0 -45
- package/src/types.ts +0 -309
- package/src/utils/html.ts +0 -43
- package/src/webhooks/SendGridWebhookDriver.ts +0 -80
- package/src/webhooks/SesWebhookDriver.ts +0 -44
- package/tests/DevMailbox.test.ts +0 -54
- package/tests/FileMailboxStorage.test.ts +0 -56
- package/tests/MjmlLayout.test.ts +0 -28
- package/tests/MjmlRenderer.test.ts +0 -53
- package/tests/OrbitSignalWebhook.test.ts +0 -56
- package/tests/ReactMjmlRenderer.test.ts +0 -33
- package/tests/SendGridWebhookDriver.test.ts +0 -69
- package/tests/SesWebhookDriver.test.ts +0 -46
- package/tests/VueMjmlRenderer.test.ts +0 -35
- package/tests/dev-server.test.ts +0 -66
- package/tests/log-transport.test.ts +0 -21
- package/tests/mailable-extra.test.ts +0 -68
- package/tests/mailable.test.ts +0 -77
- package/tests/orbit-signal.test.ts +0 -43
- package/tests/renderers.test.ts +0 -58
- package/tests/template-renderer.test.ts +0 -24
- package/tests/transports.test.ts +0 -52
- package/tests/ui.test.ts +0 -37
- package/tsconfig.build.json +0 -24
- package/tsconfig.json +0 -9
package/src/Mailable.ts
DELETED
|
@@ -1,674 +0,0 @@
|
|
|
1
|
-
import type { Queueable } from '@gravito/stream' // Import Queueable from orbit-queue
|
|
2
|
-
import { HtmlRenderer } from './renderers/HtmlRenderer'
|
|
3
|
-
import type { Renderer } from './renderers/Renderer'
|
|
4
|
-
import { TemplateRenderer } from './renderers/TemplateRenderer'
|
|
5
|
-
import type { Address, Attachment, Envelope, MailConfig } from './types'
|
|
6
|
-
|
|
7
|
-
// Type placeholders for React/Vue components to avoid hard dependencies in core
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
-
type ComponentType = any
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Base class for all mailable messages.
|
|
13
|
-
*
|
|
14
|
-
* @description
|
|
15
|
-
* Mailable provides a fluent API to build email envelopes and render content
|
|
16
|
-
* using multiple engines: HTML, Prism templates, React, and Vue components.
|
|
17
|
-
*
|
|
18
|
-
* @architecture
|
|
19
|
-
* ```
|
|
20
|
-
* Mailable
|
|
21
|
-
* ├── Envelope (from, to, subject, cc, bcc, attachments)
|
|
22
|
-
* ├── Renderer (HtmlRenderer | TemplateRenderer | ReactRenderer | VueRenderer)
|
|
23
|
-
* └── Queueable (queue support interface)
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @lifecycle
|
|
27
|
-
* 1. Create Mailable subclass
|
|
28
|
-
* 2. Implement build() method to configure envelope and content
|
|
29
|
-
* 3. Call send() to send immediately, or queue() for background processing
|
|
30
|
-
* 4. OrbitSignal calls buildEnvelope() → renderContent() → transport.send()
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```typescript
|
|
34
|
-
* import { Mailable } from '@gravito/signal'
|
|
35
|
-
*
|
|
36
|
-
* class WelcomeEmail extends Mailable {
|
|
37
|
-
* constructor(private user: User) {
|
|
38
|
-
* super()
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* build() {
|
|
42
|
-
* return this
|
|
43
|
-
* .to(this.user.email)
|
|
44
|
-
* .subject('Welcome!')
|
|
45
|
-
* .view('emails/welcome', { name: this.user.name })
|
|
46
|
-
* }
|
|
47
|
-
* }
|
|
48
|
-
*
|
|
49
|
-
* // Send immediately
|
|
50
|
-
* await mail.send(new WelcomeEmail(user))
|
|
51
|
-
*
|
|
52
|
-
* // Queue for background processing
|
|
53
|
-
* await new WelcomeEmail(user).onQueue('emails').queue()
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* @see {@link OrbitSignal} Mail service orchestrator
|
|
57
|
-
* @see {@link Renderer} Content rendering interface
|
|
58
|
-
* @see {@link Envelope} Email metadata structure
|
|
59
|
-
* @see {@link Queueable} Queue integration interface
|
|
60
|
-
*
|
|
61
|
-
* @public
|
|
62
|
-
* @since 3.0.0
|
|
63
|
-
*/
|
|
64
|
-
export abstract class Mailable implements Queueable {
|
|
65
|
-
protected envelope: Partial<Envelope> = {}
|
|
66
|
-
protected renderer?: Renderer
|
|
67
|
-
private rendererResolver?: () => Promise<Renderer>
|
|
68
|
-
protected renderData: Record<string, unknown> = {}
|
|
69
|
-
protected config?: MailConfig
|
|
70
|
-
|
|
71
|
-
// ===== Fluent API (Envelope Construction) =====
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Set the sender address for the email.
|
|
75
|
-
*
|
|
76
|
-
* This defines the "From" field in the email envelope. If not called, the default
|
|
77
|
-
* sender from the mail configuration will be used.
|
|
78
|
-
*
|
|
79
|
-
* @param address - The email address or address object containing name and address.
|
|
80
|
-
* @returns The current mailable instance for chaining.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* mailable.from('admin@example.com')
|
|
85
|
-
* mailable.from({ name: 'Support', address: 'support@example.com' })
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
from(address: string | Address): this {
|
|
89
|
-
this.envelope.from = typeof address === 'string' ? { address } : address
|
|
90
|
-
return this
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Set the primary recipient(s) for the email.
|
|
95
|
-
*
|
|
96
|
-
* Configures the "To" field. Supports single or multiple recipients in various formats.
|
|
97
|
-
*
|
|
98
|
-
* @param address - A single email string, an address object, or an array of either.
|
|
99
|
-
* @returns The current mailable instance for chaining.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```typescript
|
|
103
|
-
* mailable.to('user@example.com')
|
|
104
|
-
* mailable.to(['a@example.com', 'b@example.com'])
|
|
105
|
-
* mailable.to({ name: 'John', address: 'john@example.com' })
|
|
106
|
-
* ```
|
|
107
|
-
*/
|
|
108
|
-
to(address: string | Address | (string | Address)[]): this {
|
|
109
|
-
this.envelope.to = this.normalizeAddressArray(address)
|
|
110
|
-
return this
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Set the carbon copy (CC) recipient(s).
|
|
115
|
-
*
|
|
116
|
-
* Adds recipients to the "Cc" field of the email.
|
|
117
|
-
*
|
|
118
|
-
* @param address - A single email string, an address object, or an array of either.
|
|
119
|
-
* @returns The current mailable instance for chaining.
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```typescript
|
|
123
|
-
* mailable.cc('manager@example.com')
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
cc(address: string | Address | (string | Address)[]): this {
|
|
127
|
-
this.envelope.cc = this.normalizeAddressArray(address)
|
|
128
|
-
return this
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Set the blind carbon copy (BCC) recipient(s).
|
|
133
|
-
*
|
|
134
|
-
* Adds recipients to the "Bcc" field. These recipients are hidden from others.
|
|
135
|
-
*
|
|
136
|
-
* @param address - A single email string, an address object, or an array of either.
|
|
137
|
-
* @returns The current mailable instance for chaining.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```typescript
|
|
141
|
-
* mailable.bcc('audit@example.com')
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
bcc(address: string | Address | (string | Address)[]): this {
|
|
145
|
-
this.envelope.bcc = this.normalizeAddressArray(address)
|
|
146
|
-
return this
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Set the reply-to address.
|
|
151
|
-
*
|
|
152
|
-
* Specifies where replies to this email should be directed.
|
|
153
|
-
*
|
|
154
|
-
* @param address - The email address or address object for replies.
|
|
155
|
-
* @returns The current mailable instance for chaining.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```typescript
|
|
159
|
-
* mailable.replyTo('no-reply@example.com')
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
replyTo(address: string | Address): this {
|
|
163
|
-
this.envelope.replyTo = typeof address === 'string' ? { address } : address
|
|
164
|
-
return this
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Set the subject line for the email.
|
|
169
|
-
*
|
|
170
|
-
* Defines the text that appears in the recipient's inbox subject field.
|
|
171
|
-
*
|
|
172
|
-
* @param subject - The subject text.
|
|
173
|
-
* @returns The current mailable instance for chaining.
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```typescript
|
|
177
|
-
* mailable.subject('Your Order Confirmation')
|
|
178
|
-
* ```
|
|
179
|
-
*/
|
|
180
|
-
subject(subject: string): this {
|
|
181
|
-
this.envelope.subject = subject
|
|
182
|
-
return this
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Set the email priority.
|
|
187
|
-
*
|
|
188
|
-
* Hints to the email client how urgent this message is.
|
|
189
|
-
*
|
|
190
|
-
* @param level - The priority level: 'high', 'normal', or 'low'.
|
|
191
|
-
* @returns The current mailable instance for chaining.
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```typescript
|
|
195
|
-
* mailable.emailPriority('high')
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
|
-
emailPriority(level: 'high' | 'normal' | 'low'): this {
|
|
199
|
-
this.envelope.priority = level
|
|
200
|
-
return this
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Attach a file to the email.
|
|
205
|
-
*
|
|
206
|
-
* Adds a file attachment to the message. Can be called multiple times for multiple files.
|
|
207
|
-
*
|
|
208
|
-
* @param attachment - The attachment configuration including path, content, or filename.
|
|
209
|
-
* @returns The current mailable instance for chaining.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* mailable.attach({
|
|
214
|
-
* filename: 'invoice.pdf',
|
|
215
|
-
* path: './storage/invoices/123.pdf'
|
|
216
|
-
* })
|
|
217
|
-
* ```
|
|
218
|
-
*/
|
|
219
|
-
attach(attachment: Attachment): this {
|
|
220
|
-
this.envelope.attachments = this.envelope.attachments || []
|
|
221
|
-
this.envelope.attachments.push(attachment)
|
|
222
|
-
return this
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ===== Content Methods (Renderer Selection) =====
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Set the content using a raw HTML string.
|
|
229
|
-
*
|
|
230
|
-
* Use this for simple emails where a full template engine is not required.
|
|
231
|
-
*
|
|
232
|
-
* @param content - The raw HTML content.
|
|
233
|
-
* @returns The current mailable instance for chaining.
|
|
234
|
-
*
|
|
235
|
-
* @example
|
|
236
|
-
* ```typescript
|
|
237
|
-
* mailable.html('<h1>Hello</h1><p>Welcome to our platform.</p>')
|
|
238
|
-
* ```
|
|
239
|
-
*/
|
|
240
|
-
html(content: string): this {
|
|
241
|
-
this.renderer = new HtmlRenderer(content)
|
|
242
|
-
return this
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Set the content using an OrbitPrism template.
|
|
247
|
-
*
|
|
248
|
-
* Renders a template file with the provided data. This is the recommended way
|
|
249
|
-
* to build complex, data-driven emails.
|
|
250
|
-
*
|
|
251
|
-
* @param template - The template name or path relative to the configured views directory.
|
|
252
|
-
* @param data - The data object to be injected into the template.
|
|
253
|
-
* @returns The current mailable instance for chaining.
|
|
254
|
-
*
|
|
255
|
-
* @example
|
|
256
|
-
* ```typescript
|
|
257
|
-
* mailable.view('emails.welcome', { name: 'Alice' })
|
|
258
|
-
* ```
|
|
259
|
-
*/
|
|
260
|
-
view(template: string, data?: Record<string, unknown>): this {
|
|
261
|
-
this.renderer = new TemplateRenderer(template, undefined) // Dir will be injected later if possible, or use default
|
|
262
|
-
this.renderData = data || {}
|
|
263
|
-
return this
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Set the content using a React component.
|
|
268
|
-
*
|
|
269
|
-
* Leverages React's component model for email design. The renderer is loaded
|
|
270
|
-
* dynamically to keep the core package lightweight.
|
|
271
|
-
*
|
|
272
|
-
* @param component - The React component class or function.
|
|
273
|
-
* @param props - The properties to pass to the component.
|
|
274
|
-
* @param deps - Optional React/ReactDOMServer overrides for custom environments.
|
|
275
|
-
* @returns The current mailable instance for chaining.
|
|
276
|
-
*
|
|
277
|
-
* @example
|
|
278
|
-
* ```typescript
|
|
279
|
-
* mailable.react(WelcomeEmailComponent, { name: 'Alice' })
|
|
280
|
-
* ```
|
|
281
|
-
*/
|
|
282
|
-
react<P extends object>(
|
|
283
|
-
component: ComponentType,
|
|
284
|
-
props?: P,
|
|
285
|
-
deps?: {
|
|
286
|
-
createElement?: (...args: any[]) => any
|
|
287
|
-
renderToStaticMarkup?: (element: any) => string
|
|
288
|
-
}
|
|
289
|
-
): this {
|
|
290
|
-
this.rendererResolver = async () => {
|
|
291
|
-
const { ReactRenderer } = await import('./renderers/ReactRenderer')
|
|
292
|
-
return new ReactRenderer(component, props, deps)
|
|
293
|
-
}
|
|
294
|
-
return this
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Set the content using a Vue component.
|
|
299
|
-
*
|
|
300
|
-
* Leverages Vue's component model for email design. The renderer is loaded
|
|
301
|
-
* dynamically to keep the core package lightweight.
|
|
302
|
-
*
|
|
303
|
-
* @param component - The Vue component object.
|
|
304
|
-
* @param props - The properties to pass to the component.
|
|
305
|
-
* @param deps - Optional Vue/VueServerRenderer overrides for custom environments.
|
|
306
|
-
* @returns The current mailable instance for chaining.
|
|
307
|
-
*
|
|
308
|
-
* @example
|
|
309
|
-
* ```typescript
|
|
310
|
-
* mailable.vue(WelcomeEmailComponent, { name: 'Alice' })
|
|
311
|
-
* ```
|
|
312
|
-
*/
|
|
313
|
-
vue<P extends object>(
|
|
314
|
-
component: ComponentType,
|
|
315
|
-
props?: P,
|
|
316
|
-
deps?: {
|
|
317
|
-
createSSRApp?: (...args: any[]) => any
|
|
318
|
-
h?: (...args: any[]) => any
|
|
319
|
-
renderToString?: (app: any) => Promise<string>
|
|
320
|
-
}
|
|
321
|
-
): this {
|
|
322
|
-
this.rendererResolver = async () => {
|
|
323
|
-
const { VueRenderer } = await import('./renderers/VueRenderer')
|
|
324
|
-
return new VueRenderer(component, props as any, deps)
|
|
325
|
-
}
|
|
326
|
-
return this
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Set the content using an MJML markup string.
|
|
331
|
-
*
|
|
332
|
-
* MJML ensures responsive email compatibility across various clients.
|
|
333
|
-
*
|
|
334
|
-
* @param content - The MJML markup string or inner content.
|
|
335
|
-
* @param options - MJML transformation options.
|
|
336
|
-
* @param options.layout - Optional full MJML layout string. Use '{{content}}' as placeholder.
|
|
337
|
-
* @returns The current mailable instance for chaining.
|
|
338
|
-
*
|
|
339
|
-
* @example
|
|
340
|
-
* ```typescript
|
|
341
|
-
* mailable.mjml('<mj-text>Hello</mj-text>', {
|
|
342
|
-
* layout: '<mjml><mj-body>{{content}}</mj-body></mjml>'
|
|
343
|
-
* })
|
|
344
|
-
* ```
|
|
345
|
-
*/
|
|
346
|
-
mjml(content: string, options?: Record<string, any> & { layout?: string }): this {
|
|
347
|
-
const finalContent = options?.layout?.includes('{{content}}')
|
|
348
|
-
? options.layout.replace('{{content}}', content)
|
|
349
|
-
: content
|
|
350
|
-
|
|
351
|
-
this.rendererResolver = async () => {
|
|
352
|
-
const { MjmlRenderer } = await import('./renderers/MjmlRenderer')
|
|
353
|
-
return new MjmlRenderer(finalContent, options)
|
|
354
|
-
}
|
|
355
|
-
return this
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Set the content using a React component that outputs MJML.
|
|
360
|
-
*
|
|
361
|
-
* @param component - The React component.
|
|
362
|
-
* @param props - Component properties.
|
|
363
|
-
* @param options - MJML options.
|
|
364
|
-
* @returns The current mailable instance for chaining.
|
|
365
|
-
*/
|
|
366
|
-
mjmlReact<P extends object>(
|
|
367
|
-
component: ComponentType,
|
|
368
|
-
props?: P,
|
|
369
|
-
options?: Record<string, any>
|
|
370
|
-
): this {
|
|
371
|
-
this.rendererResolver = async () => {
|
|
372
|
-
const { ReactMjmlRenderer } = await import('./renderers/ReactMjmlRenderer')
|
|
373
|
-
return new ReactMjmlRenderer(component, props, options)
|
|
374
|
-
}
|
|
375
|
-
return this
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Set the content using a Vue component that outputs MJML.
|
|
380
|
-
*
|
|
381
|
-
* @param component - The Vue component.
|
|
382
|
-
* @param props - Component properties.
|
|
383
|
-
* @param options - MJML options.
|
|
384
|
-
* @returns The current mailable instance for chaining.
|
|
385
|
-
*/
|
|
386
|
-
mjmlVue<P extends object>(
|
|
387
|
-
component: ComponentType,
|
|
388
|
-
props?: P,
|
|
389
|
-
options?: Record<string, any>
|
|
390
|
-
): this {
|
|
391
|
-
this.rendererResolver = async () => {
|
|
392
|
-
const { VueMjmlRenderer } = await import('./renderers/VueMjmlRenderer')
|
|
393
|
-
return new VueMjmlRenderer(component, props, options)
|
|
394
|
-
}
|
|
395
|
-
return this
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// ===== Life Cycle =====
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Configure the mailable's envelope and content.
|
|
402
|
-
*
|
|
403
|
-
* This abstract method must be implemented by subclasses to define the email's
|
|
404
|
-
* recipients, subject, and body using the fluent API.
|
|
405
|
-
*
|
|
406
|
-
* @returns The current mailable instance.
|
|
407
|
-
*
|
|
408
|
-
* @example
|
|
409
|
-
* ```typescript
|
|
410
|
-
* build() {
|
|
411
|
-
* return this.to('user@example.com').subject('Hi').html('...')
|
|
412
|
-
* }
|
|
413
|
-
* ```
|
|
414
|
-
*/
|
|
415
|
-
abstract build(): this
|
|
416
|
-
|
|
417
|
-
// ===== Queueable Implementation =====
|
|
418
|
-
|
|
419
|
-
/** The name of the queue to push this mailable to. */
|
|
420
|
-
queueName?: string
|
|
421
|
-
/** The connection name for the queue. */
|
|
422
|
-
connectionName?: string
|
|
423
|
-
/** Delay in seconds before the message is sent. */
|
|
424
|
-
delaySeconds?: number
|
|
425
|
-
/** Priority of the message in the queue. */
|
|
426
|
-
priority?: number | string
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Set the target queue for background processing.
|
|
430
|
-
*
|
|
431
|
-
* @param queue - The name of the queue.
|
|
432
|
-
* @returns The current mailable instance for chaining.
|
|
433
|
-
*
|
|
434
|
-
* @example
|
|
435
|
-
* ```typescript
|
|
436
|
-
* mailable.onQueue('notifications')
|
|
437
|
-
* ```
|
|
438
|
-
*/
|
|
439
|
-
onQueue(queue: string): this {
|
|
440
|
-
this.queueName = queue
|
|
441
|
-
return this
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Set the queue connection to be used.
|
|
446
|
-
*
|
|
447
|
-
* @param connection - The name of the connection (e.g., 'redis', 'sqs').
|
|
448
|
-
* @returns The current mailable instance for chaining.
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* ```typescript
|
|
452
|
-
* mailable.onConnection('redis')
|
|
453
|
-
* ```
|
|
454
|
-
*/
|
|
455
|
-
onConnection(connection: string): this {
|
|
456
|
-
this.connectionName = connection
|
|
457
|
-
return this
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Set a delay for the queued message.
|
|
462
|
-
*
|
|
463
|
-
* The message will remain in the queue and only be processed after the delay.
|
|
464
|
-
*
|
|
465
|
-
* @param seconds - The delay in seconds.
|
|
466
|
-
* @returns The current mailable instance for chaining.
|
|
467
|
-
*
|
|
468
|
-
* @example
|
|
469
|
-
* ```typescript
|
|
470
|
-
* mailable.delay(3600) // Delay for 1 hour
|
|
471
|
-
* ```
|
|
472
|
-
*/
|
|
473
|
-
delay(seconds: number): this {
|
|
474
|
-
this.delaySeconds = seconds
|
|
475
|
-
return this
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Set the priority for the queued message.
|
|
480
|
-
*
|
|
481
|
-
* Higher priority messages are typically processed before lower priority ones.
|
|
482
|
-
*
|
|
483
|
-
* @param priority - The priority value (numeric or string).
|
|
484
|
-
* @returns The current mailable instance for chaining.
|
|
485
|
-
*
|
|
486
|
-
* @example
|
|
487
|
-
* ```typescript
|
|
488
|
-
* mailable.withPriority(10)
|
|
489
|
-
* ```
|
|
490
|
-
*/
|
|
491
|
-
withPriority(priority: string | number): this {
|
|
492
|
-
this.priority = priority
|
|
493
|
-
return this
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Push the mailable onto the configured queue.
|
|
498
|
-
*
|
|
499
|
-
* Automatically resolves the mail service from the Gravito container and
|
|
500
|
-
* dispatches this mailable for background processing.
|
|
501
|
-
*
|
|
502
|
-
* @returns A promise that resolves when the mailable is queued.
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```typescript
|
|
506
|
-
* await new WelcomeEmail(user).queue()
|
|
507
|
-
* ```
|
|
508
|
-
*/
|
|
509
|
-
async queue(): Promise<void> {
|
|
510
|
-
// We should ideally use the container to get the mail service
|
|
511
|
-
// But since Mailable might be used outside a core context, we'll try a safe approach.
|
|
512
|
-
try {
|
|
513
|
-
// biome-ignore lint/suspicious/noTsIgnore: Global access to app() helper from core
|
|
514
|
-
// @ts-ignore
|
|
515
|
-
const { app } = await import('@gravito/core')
|
|
516
|
-
const mail = app().container.make<any>('mail')
|
|
517
|
-
if (mail) {
|
|
518
|
-
return mail.queue(this)
|
|
519
|
-
}
|
|
520
|
-
} catch (_e) {
|
|
521
|
-
// Fallback if core is not available
|
|
522
|
-
console.warn('[Mailable] Could not auto-resolve mail service for queuing.')
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// ===== I18n Support =====
|
|
527
|
-
|
|
528
|
-
protected currentLocale?: string
|
|
529
|
-
protected translator?: (key: string, replace?: Record<string, unknown>, locale?: string) => string
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Set the locale for the email content.
|
|
533
|
-
*
|
|
534
|
-
* Used by the translator to resolve localized strings in templates or components.
|
|
535
|
-
*
|
|
536
|
-
* @param locale - The locale identifier (e.g., 'en-US', 'fr').
|
|
537
|
-
* @returns The current mailable instance for chaining.
|
|
538
|
-
*
|
|
539
|
-
* @example
|
|
540
|
-
* ```typescript
|
|
541
|
-
* mailable.locale('es')
|
|
542
|
-
* ```
|
|
543
|
-
*/
|
|
544
|
-
locale(locale: string): this {
|
|
545
|
-
this.currentLocale = locale
|
|
546
|
-
return this
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Internal: Set the translator function (called by OrbitSignal)
|
|
551
|
-
* @internal
|
|
552
|
-
*/
|
|
553
|
-
setTranslator(
|
|
554
|
-
translator: (key: string, replace?: Record<string, unknown>, locale?: string) => string
|
|
555
|
-
): void {
|
|
556
|
-
this.translator = translator
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Translate a key into a localized string.
|
|
561
|
-
*
|
|
562
|
-
* Uses the configured translator and current locale to resolve the key.
|
|
563
|
-
*
|
|
564
|
-
* @param key - The translation key.
|
|
565
|
-
* @param replace - Key-value pairs for string interpolation.
|
|
566
|
-
* @returns The translated string, or the key itself if no translator is available.
|
|
567
|
-
*
|
|
568
|
-
* @example
|
|
569
|
-
* ```typescript
|
|
570
|
-
* const text = mailable.t('messages.welcome', { name: 'Alice' })
|
|
571
|
-
* ```
|
|
572
|
-
*/
|
|
573
|
-
t(key: string, replace?: Record<string, unknown>): string {
|
|
574
|
-
if (this.translator) {
|
|
575
|
-
return this.translator(key, replace, this.currentLocale)
|
|
576
|
-
}
|
|
577
|
-
return key // Fallback: just return the key if no translator
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// ===== Internal Systems =====
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Compile the final email envelope.
|
|
584
|
-
*
|
|
585
|
-
* Merges mailable-specific settings with global configuration defaults.
|
|
586
|
-
* This is called internally by the mail service before sending.
|
|
587
|
-
*
|
|
588
|
-
* @param configPromise - The mail configuration or a promise resolving to it.
|
|
589
|
-
* @returns The fully constructed envelope.
|
|
590
|
-
*
|
|
591
|
-
* @example
|
|
592
|
-
* ```typescript
|
|
593
|
-
* const envelope = await mailable.buildEnvelope(config)
|
|
594
|
-
* ```
|
|
595
|
-
*/
|
|
596
|
-
async buildEnvelope(configPromise: MailConfig | Promise<MailConfig>): Promise<Envelope> {
|
|
597
|
-
const config = await Promise.resolve(configPromise)
|
|
598
|
-
this.config = config
|
|
599
|
-
|
|
600
|
-
// Inject translator from config if available
|
|
601
|
-
if (config.translator) {
|
|
602
|
-
this.setTranslator(config.translator)
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
this.build() // User logic executes here
|
|
606
|
-
|
|
607
|
-
// Ensure Renderer is initialized if using TemplateRenderer with config path
|
|
608
|
-
if (this.renderer instanceof TemplateRenderer && config.viewsDir) {
|
|
609
|
-
// Re-initialize or update TemplateRenderer with the correct directory
|
|
610
|
-
this.renderer = new TemplateRenderer((this.renderer as any).template, config.viewsDir)
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const envelope: Envelope = {
|
|
614
|
-
from: this.envelope.from || config.from,
|
|
615
|
-
to: this.envelope.to || [],
|
|
616
|
-
subject: this.envelope.subject || '(No Subject)',
|
|
617
|
-
priority: this.envelope.priority || 'normal',
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (this.envelope.cc) {
|
|
621
|
-
envelope.cc = this.envelope.cc
|
|
622
|
-
}
|
|
623
|
-
if (this.envelope.bcc) {
|
|
624
|
-
envelope.bcc = this.envelope.bcc
|
|
625
|
-
}
|
|
626
|
-
if (this.envelope.replyTo) {
|
|
627
|
-
envelope.replyTo = this.envelope.replyTo
|
|
628
|
-
}
|
|
629
|
-
if (this.envelope.attachments) {
|
|
630
|
-
envelope.attachments = this.envelope.attachments
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
return envelope
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Render the email content to HTML and plain text.
|
|
638
|
-
*
|
|
639
|
-
* Executes the chosen renderer (HTML, Template, React, or Vue) with the
|
|
640
|
-
* provided data and i18n helpers.
|
|
641
|
-
*
|
|
642
|
-
* @returns The rendered HTML and optional plain text content.
|
|
643
|
-
* @throws {Error} If no renderer has been specified.
|
|
644
|
-
*
|
|
645
|
-
* @example
|
|
646
|
-
* ```typescript
|
|
647
|
-
* const { html } = await mailable.renderContent()
|
|
648
|
-
* ```
|
|
649
|
-
*/
|
|
650
|
-
async renderContent(): Promise<{ html: string; text?: string }> {
|
|
651
|
-
// Resolve lazy renderer if needed
|
|
652
|
-
if (!this.renderer && this.rendererResolver) {
|
|
653
|
-
this.renderer = await this.rendererResolver()
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (!this.renderer) {
|
|
657
|
-
throw new Error('No content renderer specified. Use html(), view(), react(), or vue().')
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Inject i18n helpers into renderData
|
|
661
|
-
this.renderData = {
|
|
662
|
-
...this.renderData,
|
|
663
|
-
locale: this.currentLocale,
|
|
664
|
-
t: (key: string, replace?: Record<string, unknown>) => this.t(key, replace),
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return this.renderer.render(this.renderData)
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
private normalizeAddressArray(input: string | Address | (string | Address)[]): Address[] {
|
|
671
|
-
const arr = Array.isArray(input) ? input : [input]
|
|
672
|
-
return arr.map((item) => (typeof item === 'string' ? { address: item } : item))
|
|
673
|
-
}
|
|
674
|
-
}
|