@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
|
@@ -2,12 +2,40 @@ import { SESClient, SendRawEmailCommand } from '@aws-sdk/client-ses'
|
|
|
2
2
|
import nodemailer from 'nodemailer'
|
|
3
3
|
import type { Address, Message, Transport } from '../types'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for AWS SES email transport.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
* @since 3.0.0
|
|
10
|
+
*/
|
|
5
11
|
export interface SesConfig {
|
|
12
|
+
/** AWS region (e.g., 'us-east-1') */
|
|
6
13
|
region: string
|
|
14
|
+
/** AWS access key ID (optional, uses default credentials if not provided) */
|
|
7
15
|
accessKeyId?: string
|
|
16
|
+
/** AWS secret access key (optional, uses default credentials if not provided) */
|
|
8
17
|
secretAccessKey?: string
|
|
9
18
|
}
|
|
10
19
|
|
|
20
|
+
/**
|
|
21
|
+
* AWS SES (Simple Email Service) transport.
|
|
22
|
+
*
|
|
23
|
+
* Sends emails using Amazon SES with support for attachments,
|
|
24
|
+
* HTML/text content, and all standard email features.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const transport = new SesTransport({
|
|
29
|
+
* region: 'us-east-1',
|
|
30
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
31
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
32
|
+
* })
|
|
33
|
+
* await transport.send(message)
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @since 3.0.0
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
11
39
|
export class SesTransport implements Transport {
|
|
12
40
|
private transporter: nodemailer.Transporter
|
|
13
41
|
|
|
@@ -1,20 +1,58 @@
|
|
|
1
1
|
import nodemailer from 'nodemailer'
|
|
2
2
|
import type { Address, Message, Transport } from '../types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for SMTP email transport.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @since 3.0.0
|
|
9
|
+
*/
|
|
4
10
|
export interface SmtpConfig {
|
|
11
|
+
/** SMTP server hostname */
|
|
5
12
|
host: string
|
|
13
|
+
/** SMTP server port (typically 25, 465, or 587) */
|
|
6
14
|
port: number
|
|
15
|
+
/** Use TLS/SSL connection (true for port 465) */
|
|
7
16
|
secure?: boolean
|
|
17
|
+
/** Authentication credentials */
|
|
8
18
|
auth?: {
|
|
19
|
+
/** SMTP username */
|
|
9
20
|
user: string
|
|
21
|
+
/** SMTP password */
|
|
10
22
|
pass: string
|
|
11
23
|
}
|
|
24
|
+
/** TLS options */
|
|
12
25
|
tls?: {
|
|
26
|
+
/** Reject unauthorized certificates */
|
|
13
27
|
rejectUnauthorized?: boolean
|
|
28
|
+
/** Cipher suite */
|
|
14
29
|
ciphers?: string
|
|
15
30
|
}
|
|
16
31
|
}
|
|
17
32
|
|
|
33
|
+
/**
|
|
34
|
+
* SMTP email transport.
|
|
35
|
+
*
|
|
36
|
+
* Sends emails using standard SMTP protocol with support for
|
|
37
|
+
* authentication, TLS/SSL, attachments, and all email features.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const transport = new SmtpTransport({
|
|
42
|
+
* host: 'smtp.gmail.com',
|
|
43
|
+
* port: 587,
|
|
44
|
+
* secure: false,
|
|
45
|
+
* auth: {
|
|
46
|
+
* user: 'user@gmail.com',
|
|
47
|
+
* pass: 'app-password'
|
|
48
|
+
* }
|
|
49
|
+
* })
|
|
50
|
+
* await transport.send(message)
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @since 3.0.0
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
18
56
|
export class SmtpTransport implements Transport {
|
|
19
57
|
private transporter: nodemailer.Transporter
|
|
20
58
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { Message } from '../types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Interface for email transport mechanisms (SMTP, SES, etc.).
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
* @since 3.0.0
|
|
8
|
+
*/
|
|
3
9
|
export interface Transport {
|
|
4
10
|
/**
|
|
5
|
-
* Send the given message
|
|
11
|
+
* Send the given message using the underlying transport.
|
|
12
|
+
*
|
|
13
|
+
* @param message - The finalized message to send.
|
|
6
14
|
*/
|
|
7
15
|
send(message: Message): Promise<void>
|
|
8
16
|
}
|
package/src/types.ts
CHANGED
|
@@ -2,79 +2,137 @@ import type { GravitoContext } from '@gravito/core'
|
|
|
2
2
|
import type { Transport } from './transports/Transport'
|
|
3
3
|
export type { Transport }
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Representation of an email address with optional display name.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
* @since 3.0.0
|
|
10
|
+
*/
|
|
5
11
|
export interface Address {
|
|
12
|
+
/** The display name of the recipient/sender, e.g., "John Doe". */
|
|
6
13
|
name?: string
|
|
14
|
+
/** The actual email address string. */
|
|
7
15
|
address: string
|
|
8
16
|
}
|
|
9
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for an email attachment.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
* @since 3.0.0
|
|
23
|
+
*/
|
|
10
24
|
export interface Attachment {
|
|
25
|
+
/** The filename of the attachment. */
|
|
11
26
|
filename: string
|
|
27
|
+
/** The content of the attachment as a string or Buffer. */
|
|
12
28
|
content: string | Buffer // Buffer for Node.js
|
|
29
|
+
/** Optional MIME type of the content. */
|
|
13
30
|
contentType?: string
|
|
31
|
+
/** Optional Content-ID for referencing within HTML content (inline images). */
|
|
14
32
|
cid?: string // Content-ID for inline images
|
|
33
|
+
/** Optional content encoding. */
|
|
15
34
|
encoding?: string
|
|
16
35
|
}
|
|
17
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The envelope containing metadata for an email message.
|
|
39
|
+
*
|
|
40
|
+
* Used during the construction phase of a Mailable.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
* @since 3.0.0
|
|
44
|
+
*/
|
|
18
45
|
export interface Envelope {
|
|
46
|
+
/** The sender's address. */
|
|
19
47
|
from?: Address | undefined
|
|
48
|
+
/** Primary recipients. */
|
|
20
49
|
to?: Address[] | undefined
|
|
50
|
+
/** Carbon copy recipients. */
|
|
21
51
|
cc?: Address[] | undefined
|
|
52
|
+
/** Blind carbon copy recipients. */
|
|
22
53
|
bcc?: Address[] | undefined
|
|
54
|
+
/** Reply-to address. */
|
|
23
55
|
replyTo?: Address | undefined
|
|
56
|
+
/** Email subject line. */
|
|
24
57
|
subject?: string | undefined
|
|
58
|
+
/** Importance level of the email. */
|
|
25
59
|
priority?: 'high' | 'normal' | 'low' | undefined
|
|
60
|
+
/** List of file attachments. */
|
|
26
61
|
attachments?: Attachment[] | undefined
|
|
27
62
|
}
|
|
28
63
|
|
|
64
|
+
/**
|
|
65
|
+
* A fully finalized email message ready to be sent by a transport.
|
|
66
|
+
*
|
|
67
|
+
* Requires mandatory fields that were optional in the Envelope.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
* @since 3.0.0
|
|
71
|
+
*/
|
|
29
72
|
export interface Message extends Envelope {
|
|
30
|
-
|
|
31
|
-
|
|
73
|
+
/** The mandatory sender's address. */
|
|
74
|
+
from: Address
|
|
75
|
+
/** At least one recipient is required. */
|
|
76
|
+
to: Address[]
|
|
77
|
+
/** Mandatory subject. */
|
|
32
78
|
subject: string
|
|
33
|
-
|
|
34
|
-
|
|
79
|
+
/** The rendered HTML body content. */
|
|
80
|
+
html: string
|
|
81
|
+
/** Optional rendered plain text body content. */
|
|
82
|
+
text?: string
|
|
83
|
+
/** Custom SMTP headers. */
|
|
35
84
|
headers?: Record<string, string>
|
|
36
85
|
}
|
|
37
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Global configuration options for OrbitSignal and Mailable instances.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
* @since 3.0.0
|
|
92
|
+
*/
|
|
38
93
|
export interface MailConfig {
|
|
39
94
|
/**
|
|
40
|
-
* Default sender address
|
|
95
|
+
* Default sender address used if not specified in the Mailable.
|
|
41
96
|
*/
|
|
42
97
|
from?: Address
|
|
43
98
|
|
|
44
99
|
/**
|
|
45
|
-
* The transport mechanism used to send emails
|
|
100
|
+
* The transport mechanism used to send emails (e.g., SMTP, SES, Log).
|
|
46
101
|
*/
|
|
47
102
|
transport?: Transport
|
|
48
103
|
|
|
49
104
|
/**
|
|
50
|
-
* Enable development mode
|
|
105
|
+
* Enable development mode.
|
|
106
|
+
* When true, emails are intercepted by the DevMailbox instead of being sent.
|
|
51
107
|
*/
|
|
52
108
|
devMode?: boolean | undefined
|
|
53
109
|
|
|
54
110
|
/**
|
|
55
|
-
* Directory where email templates are located
|
|
111
|
+
* Directory where email templates are located for use with OrbitPrism.
|
|
56
112
|
* Default: src/emails
|
|
57
113
|
*/
|
|
58
114
|
viewsDir?: string | undefined
|
|
59
115
|
|
|
60
116
|
/**
|
|
61
|
-
* URL prefix for Dev UI
|
|
117
|
+
* URL prefix for the Mail Dev UI.
|
|
62
118
|
* Default: /__mail
|
|
63
119
|
*/
|
|
64
120
|
devUiPrefix?: string | undefined
|
|
65
121
|
|
|
66
122
|
/**
|
|
67
|
-
*
|
|
123
|
+
* Whether to allow access to the Mail Dev UI in production environments.
|
|
124
|
+
* @default false
|
|
68
125
|
*/
|
|
69
126
|
devUiAllowInProduction?: boolean | undefined
|
|
70
127
|
|
|
71
128
|
/**
|
|
72
|
-
*
|
|
129
|
+
* Authorization gate for the Mail Dev UI.
|
|
130
|
+
* Should return true to allow access to the UI.
|
|
73
131
|
*/
|
|
74
132
|
devUiGate?: ((ctx: GravitoContext) => boolean | Promise<boolean>) | undefined
|
|
75
133
|
|
|
76
134
|
/**
|
|
77
|
-
* Translation function for
|
|
135
|
+
* Translation function for internationalization within emails.
|
|
78
136
|
*/
|
|
79
137
|
translator?:
|
|
80
138
|
| ((key: string, replace?: Record<string, unknown>, locale?: string) => string)
|
|
@@ -1,61 +1,68 @@
|
|
|
1
|
-
import { describe, expect, it
|
|
2
|
-
import { DevMailbox } from '../src/dev/DevMailbox'
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
3
2
|
import { Mailable } from '../src/Mailable'
|
|
4
|
-
import { OrbitSignal } from '../src/OrbitSignal'
|
|
5
|
-
import { MemoryTransport } from '../src/transports/MemoryTransport'
|
|
6
3
|
|
|
7
|
-
class
|
|
4
|
+
class TestMailable extends Mailable {
|
|
8
5
|
build() {
|
|
9
|
-
return this
|
|
10
|
-
.to('to@example.com')
|
|
11
|
-
.cc('cc@example.com')
|
|
12
|
-
.bcc('bcc@example.com')
|
|
13
|
-
.replyTo('reply@example.com')
|
|
14
|
-
.subject('Hello')
|
|
15
|
-
.emailPriority('high')
|
|
16
|
-
.html('<p>Hello World</p>')
|
|
6
|
+
return this
|
|
17
7
|
}
|
|
18
8
|
}
|
|
19
9
|
|
|
20
|
-
describe('Mailable', () => {
|
|
21
|
-
it('
|
|
22
|
-
const mail = new
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
expect(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
10
|
+
describe('Mailable Extras', () => {
|
|
11
|
+
it('should handle attachments', () => {
|
|
12
|
+
const mail = new TestMailable()
|
|
13
|
+
mail.attach({ filename: 'test.txt', content: 'hello' })
|
|
14
|
+
expect((mail as any).envelope.attachments).toHaveLength(1)
|
|
15
|
+
expect((mail as any).envelope.attachments[0].filename).toBe('test.txt')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should set view logic', () => {
|
|
19
|
+
const mail = new TestMailable()
|
|
20
|
+
mail.view('emails/welcome', { user: 'Carl' })
|
|
21
|
+
expect((mail as any).renderer).toBeDefined()
|
|
22
|
+
expect((mail as any).renderData.user).toBe('Carl')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should handle queue options', () => {
|
|
26
|
+
const mail = new TestMailable()
|
|
27
|
+
mail.onQueue('low').onConnection('redis').delay(60).withPriority('high')
|
|
28
|
+
|
|
29
|
+
expect(mail.queueName).toBe('low')
|
|
30
|
+
expect(mail.connectionName).toBe('redis')
|
|
31
|
+
expect(mail.delaySeconds).toBe(60)
|
|
32
|
+
expect(mail.priority).toBe('high')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should handle locale', () => {
|
|
36
|
+
const mail = new TestMailable()
|
|
37
|
+
mail.locale('zh-TW')
|
|
38
|
+
expect((mail as any).currentLocale).toBe('zh-TW')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should support i18n helper', () => {
|
|
42
|
+
const mail = new TestMailable()
|
|
43
|
+
mail.setTranslator((key) => key.toUpperCase())
|
|
44
|
+
|
|
45
|
+
expect(mail.t('hello')).toBe('HELLO')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should safely fail queue() if app not available', async () => {
|
|
49
|
+
const mail = new TestMailable()
|
|
50
|
+
// We are not mocking @gravito/core, so it should hit the catch block and log warning
|
|
51
|
+
// We just ensure it doesn't throw
|
|
52
|
+
await expect(mail.queue()).resolves.toBeUndefined()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should support React renderer builder', async () => {
|
|
56
|
+
const mail = new TestMailable()
|
|
57
|
+
// Mock import mechanics? Hard to test dynamic import in this unit test without mocking module system.
|
|
58
|
+
// Instead we test the builder method sets the resolver.
|
|
59
|
+
mail.react('MyComponent', { prop: 1 })
|
|
60
|
+
expect((mail as any).rendererResolver).toBeDefined()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should support Vue renderer builder', async () => {
|
|
64
|
+
const mail = new TestMailable()
|
|
65
|
+
mail.vue('MyComponent', { prop: 1 })
|
|
66
|
+
expect((mail as any).rendererResolver).toBeDefined()
|
|
60
67
|
})
|
|
61
68
|
})
|
package/src/augmentation.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export {}
|
|
2
|
-
|
|
3
|
-
// Module augmentation for GravitoVariables (new abstraction)
|
|
4
|
-
declare module '@gravito/core' {
|
|
5
|
-
interface GravitoVariables {
|
|
6
|
-
/** Mail service for sending emails */
|
|
7
|
-
mail?: {
|
|
8
|
-
// Use any for params to break circularity in dts generation if any
|
|
9
|
-
send: (mailable: any) => Promise<void>
|
|
10
|
-
queue: (mailable: any) => Promise<void>
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|