@gravito/signal 3.0.3 → 3.0.4
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 +16 -0
- package/README.md +89 -60
- package/README.zh-TW.md +140 -9
- package/dist/MjmlRenderer-IUH663FT.mjs +8 -0
- package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +8 -0
- package/dist/ReactRenderer-2JFLRVST.mjs +45 -0
- package/dist/{ReactRenderer-L5INVYKT.mjs → ReactRenderer-LYEOSYFS.mjs} +9 -8
- package/dist/ReactRenderer-V54CUUEI.mjs +45 -0
- package/dist/VueMjmlRenderer-4F4CXHDB.mjs +8 -0
- package/dist/VueMjmlRenderer-5WZR4CQG.mjs +8 -0
- package/dist/VueMjmlRenderer-U5YMWI44.mjs +8 -0
- package/dist/VueRenderer-3YBRQXME.mjs +48 -0
- package/dist/VueRenderer-46JGXTJ2.mjs +48 -0
- package/dist/VueRenderer-5KWD4R3C.mjs +48 -0
- package/dist/VueRenderer-C23U4O5E.mjs +48 -0
- package/dist/VueRenderer-LEVDFLHP.mjs +31 -0
- package/dist/VueRenderer-RNHSCCRI.mjs +48 -0
- package/dist/chunk-3WOR3XSL.mjs +82 -0
- package/dist/chunk-DBFIVHHG.mjs +79 -0
- package/dist/{chunk-6DZX6EAA.mjs → chunk-HEBXNMVQ.mjs} +12 -1
- package/dist/chunk-KB7IDDBT.mjs +82 -0
- package/dist/chunk-LZL5UUPC.mjs +82 -0
- package/dist/chunk-W6LXIJKK.mjs +57 -0
- package/dist/chunk-XBIVBJS2.mjs +8 -0
- package/dist/index.d.mts +1680 -209
- package/dist/index.d.ts +1680 -209
- package/dist/index.js +69405 -542
- package/dist/index.mjs +993 -110
- package/dist/lib-HJTRWKU5.mjs +67788 -0
- package/dist/{VueRenderer-Z5PRVBNH.mjs → server-renderer-4IM3P5XZ.mjs} +308 -423
- package/dist/server-renderer-7KWFSTPV.mjs +37193 -0
- package/dist/{VueRenderer-S65ZARRI.mjs → server-renderer-S5FPSTJ2.mjs} +931 -877
- package/dist/server-renderer-X5LUFVWT.mjs +37193 -0
- package/doc/OPTIMIZATION_PLAN.md +496 -0
- package/package.json +14 -12
- package/scripts/check-coverage.ts +64 -0
- package/src/Mailable.ts +340 -44
- package/src/OrbitSignal.ts +350 -50
- package/src/TypedMailable.ts +96 -0
- package/src/dev/DevMailbox.ts +89 -33
- package/src/dev/DevServer.ts +14 -14
- package/src/dev/storage/FileMailboxStorage.ts +66 -0
- package/src/dev/storage/MailboxStorage.ts +15 -0
- package/src/dev/storage/MemoryMailboxStorage.ts +36 -0
- package/src/dev/ui/mailbox.ts +1 -1
- package/src/dev/ui/preview.ts +4 -4
- package/src/errors.ts +69 -0
- package/src/events.ts +72 -0
- package/src/index.ts +20 -1
- package/src/renderers/HtmlRenderer.ts +20 -18
- package/src/renderers/MjmlRenderer.ts +73 -0
- package/src/renderers/ReactMjmlRenderer.ts +94 -0
- package/src/renderers/ReactRenderer.ts +26 -21
- package/src/renderers/Renderer.ts +43 -3
- package/src/renderers/TemplateRenderer.ts +48 -15
- package/src/renderers/VueMjmlRenderer.ts +99 -0
- package/src/renderers/VueRenderer.ts +26 -21
- package/src/renderers/mjml-templates.ts +50 -0
- package/src/transports/BaseTransport.ts +148 -0
- package/src/transports/LogTransport.ts +28 -6
- package/src/transports/MemoryTransport.ts +34 -6
- package/src/transports/SesTransport.ts +62 -17
- package/src/transports/SmtpTransport.ts +123 -27
- package/src/transports/Transport.ts +33 -4
- package/src/types.ts +172 -3
- package/src/utils/html.ts +43 -0
- package/src/webhooks/SendGridWebhookDriver.ts +80 -0
- package/src/webhooks/SesWebhookDriver.ts +44 -0
- package/tests/DevMailbox.test.ts +54 -0
- package/tests/FileMailboxStorage.test.ts +56 -0
- package/tests/MjmlLayout.test.ts +28 -0
- package/tests/MjmlRenderer.test.ts +53 -0
- package/tests/OrbitSignalWebhook.test.ts +56 -0
- package/tests/ReactMjmlRenderer.test.ts +33 -0
- package/tests/SendGridWebhookDriver.test.ts +69 -0
- package/tests/SesWebhookDriver.test.ts +46 -0
- package/tests/VueMjmlRenderer.test.ts +35 -0
- package/tests/dev-server.test.ts +1 -1
- package/tests/transports.test.ts +3 -3
- package/tsconfig.json +12 -24
- package/dist/OrbitMail-2Z7ZTKYA.mjs +0 -7
- package/dist/OrbitMail-BGV32HWN.mjs +0 -7
- package/dist/OrbitMail-FUYZQSAV.mjs +0 -7
- package/dist/OrbitMail-NAPCRK7B.mjs +0 -7
- package/dist/OrbitMail-REGJ276B.mjs +0 -7
- package/dist/OrbitMail-TCFBJWDT.mjs +0 -7
- package/dist/OrbitMail-XZZW6U4N.mjs +0 -7
- package/dist/OrbitSignal-IPSA2CDO.mjs +0 -7
- package/dist/OrbitSignal-MABW4DDW.mjs +0 -7
- package/dist/OrbitSignal-QSW5VQ5M.mjs +0 -7
- package/dist/OrbitSignal-R22QHWAA.mjs +0 -7
- package/dist/OrbitSignal-ZKKMEC27.mjs +0 -7
- package/dist/chunk-3U2CYJO5.mjs +0 -367
- package/dist/chunk-3XFC4T6M.mjs +0 -392
- package/dist/chunk-456QRYFW.mjs +0 -401
- package/dist/chunk-DT3R2TNV.mjs +0 -367
- package/dist/chunk-F6MVTUCT.mjs +0 -421
- package/dist/chunk-GADWIVC4.mjs +0 -400
- package/dist/chunk-HHKFAMSE.mjs +0 -380
- package/dist/chunk-NEQCQSZI.mjs +0 -406
- package/dist/chunk-OKRNL6PN.mjs +0 -400
- package/dist/chunk-ULN3GMY2.mjs +0 -367
- package/dist/chunk-XAWO7RSP.mjs +0 -398
- package/dist/chunk-YLVDJSED.mjs +0 -431
package/src/dev/DevMailbox.ts
CHANGED
|
@@ -1,45 +1,66 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Message } from '../types'
|
|
3
|
+
import type { MailboxStorage } from './storage/MailboxStorage'
|
|
4
|
+
import { MemoryMailboxStorage } from './storage/MemoryMailboxStorage'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @public
|
|
7
|
+
* Entry structure for messages stored in DevMailbox.
|
|
8
8
|
*/
|
|
9
9
|
export interface MailboxEntry {
|
|
10
|
-
/** Unique identifier for the
|
|
10
|
+
/** Unique identifier for the entry. */
|
|
11
11
|
id: string
|
|
12
|
-
/**
|
|
13
|
-
envelope:
|
|
14
|
-
/** HTML content
|
|
12
|
+
/** The email envelope metadata. */
|
|
13
|
+
envelope: any
|
|
14
|
+
/** The rendered HTML content. */
|
|
15
15
|
html: string
|
|
16
|
-
/**
|
|
16
|
+
/** Optional plain text content. */
|
|
17
17
|
text?: string
|
|
18
|
-
/** Timestamp when the
|
|
18
|
+
/** Timestamp when the message was captured. */
|
|
19
19
|
sentAt: Date
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Capture and store emails during development for preview and testing.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Supports different storage engines (Memory, FileSystem) and implements
|
|
26
|
+
* capacity limits via a Ring Buffer strategy.
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* ```typescript
|
|
30
|
+
* // Default memory mailbox
|
|
30
31
|
* const mailbox = new DevMailbox()
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
*
|
|
33
|
+
* // Persistent file mailbox
|
|
34
|
+
* const storage = new FileMailboxStorage('./storage/mail')
|
|
35
|
+
* const persistentMailbox = new DevMailbox(100, storage)
|
|
33
36
|
* ```
|
|
34
37
|
*
|
|
35
38
|
* @since 3.0.0
|
|
36
39
|
* @public
|
|
37
40
|
*/
|
|
38
41
|
export class DevMailbox {
|
|
39
|
-
private
|
|
40
|
-
private
|
|
42
|
+
private storage: MailboxStorage
|
|
43
|
+
private _maxEntries = 50
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Creates an instance of DevMailbox.
|
|
47
|
+
*
|
|
48
|
+
* @param maxEntries - Maximum number of emails to store (default: 50)
|
|
49
|
+
* @param storage - Optional custom storage engine (defaults to Memory)
|
|
50
|
+
*/
|
|
51
|
+
constructor(maxEntries?: number, storage?: MailboxStorage) {
|
|
52
|
+
if (maxEntries !== undefined) {
|
|
53
|
+
this._maxEntries = maxEntries
|
|
54
|
+
}
|
|
55
|
+
this.storage = storage ?? new MemoryMailboxStorage()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds a new message to the mailbox.
|
|
60
|
+
*
|
|
61
|
+
* If the mailbox exceeds the maximum capacity, the oldest messages are removed.
|
|
62
|
+
*/
|
|
63
|
+
async add(message: Message): Promise<MailboxEntry> {
|
|
43
64
|
const entry: MailboxEntry = {
|
|
44
65
|
id: randomUUID(),
|
|
45
66
|
envelope: {
|
|
@@ -57,34 +78,69 @@ export class DevMailbox {
|
|
|
57
78
|
sentAt: new Date(),
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
// Limit size
|
|
63
|
-
if (this.entries.length > this.maxEntries) {
|
|
64
|
-
this.entries = this.entries.slice(0, this.maxEntries)
|
|
65
|
-
}
|
|
81
|
+
await this.storage.push(entry)
|
|
82
|
+
await this.storage.trim(this._maxEntries)
|
|
66
83
|
|
|
67
84
|
return entry
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Sets the maximum number of emails to store.
|
|
89
|
+
*
|
|
90
|
+
* If the current mailbox exceeds the new capacity, the oldest messages are removed.
|
|
91
|
+
*/
|
|
92
|
+
async setMaxEntries(count: number): Promise<void> {
|
|
93
|
+
this._maxEntries = count
|
|
94
|
+
await this.storage.trim(this._maxEntries)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Returns the maximum capacity of the mailbox.
|
|
99
|
+
*/
|
|
100
|
+
get maxEntries(): number {
|
|
101
|
+
return this._maxEntries
|
|
72
102
|
}
|
|
73
103
|
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Lists all messages in the mailbox.
|
|
106
|
+
*/
|
|
107
|
+
async list(): Promise<MailboxEntry[]> {
|
|
108
|
+
return await this.storage.all()
|
|
76
109
|
}
|
|
77
110
|
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Retrieves a specific message by ID.
|
|
113
|
+
*/
|
|
114
|
+
async get(id: string): Promise<MailboxEntry | undefined> {
|
|
115
|
+
const entries = await this.storage.all()
|
|
116
|
+
return entries.find((e) => e.id === id)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Deletes a specific message by ID.
|
|
121
|
+
*/
|
|
122
|
+
async delete(id: string): Promise<boolean> {
|
|
123
|
+
if (this.storage.delete) {
|
|
124
|
+
return await this.storage.delete(id)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const entries = await this.storage.all()
|
|
128
|
+
const index = entries.findIndex((e) => e.id === id)
|
|
80
129
|
if (index !== -1) {
|
|
81
|
-
this.
|
|
130
|
+
await this.clear()
|
|
131
|
+
entries.splice(index, 1)
|
|
132
|
+
for (const entry of entries.reverse()) {
|
|
133
|
+
await this.storage.push(entry)
|
|
134
|
+
}
|
|
82
135
|
return true
|
|
83
136
|
}
|
|
84
137
|
return false
|
|
85
138
|
}
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Clears all messages from the mailbox.
|
|
142
|
+
*/
|
|
143
|
+
async clear(): Promise<void> {
|
|
144
|
+
await this.storage.clear()
|
|
89
145
|
}
|
|
90
146
|
}
|
package/src/dev/DevServer.ts
CHANGED
|
@@ -78,8 +78,8 @@ export class DevServer {
|
|
|
78
78
|
// 1. Mailbox List
|
|
79
79
|
router.get(
|
|
80
80
|
prefix,
|
|
81
|
-
wrap((ctx) => {
|
|
82
|
-
const entries = this.mailbox.list()
|
|
81
|
+
wrap(async (ctx) => {
|
|
82
|
+
const entries = await this.mailbox.list()
|
|
83
83
|
ctx.header('Content-Type', 'text/html; charset=utf-8')
|
|
84
84
|
return ctx.html(getMailboxHtml(entries, prefix))
|
|
85
85
|
})
|
|
@@ -88,13 +88,13 @@ export class DevServer {
|
|
|
88
88
|
// 2. Single Email Preview
|
|
89
89
|
router.get(
|
|
90
90
|
`${prefix}/:id`,
|
|
91
|
-
wrap((ctx) => {
|
|
91
|
+
wrap(async (ctx) => {
|
|
92
92
|
const id = ctx.req.param('id')
|
|
93
93
|
if (!id) {
|
|
94
94
|
return ctx.text('Bad Request', 400)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const entry = this.mailbox.get(id)
|
|
97
|
+
const entry = await this.mailbox.get(id)
|
|
98
98
|
if (!entry) {
|
|
99
99
|
return ctx.text('Email not found', 404)
|
|
100
100
|
}
|
|
@@ -106,13 +106,13 @@ export class DevServer {
|
|
|
106
106
|
// 3. Iframe Content: HTML
|
|
107
107
|
router.get(
|
|
108
108
|
`${prefix}/:id/html`,
|
|
109
|
-
wrap((ctx) => {
|
|
109
|
+
wrap(async (ctx) => {
|
|
110
110
|
const id = ctx.req.param('id')
|
|
111
111
|
if (!id) {
|
|
112
112
|
return ctx.text('Bad Request', 400)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const entry = this.mailbox.get(id)
|
|
115
|
+
const entry = await this.mailbox.get(id)
|
|
116
116
|
if (!entry) {
|
|
117
117
|
return ctx.text('Not found', 404)
|
|
118
118
|
}
|
|
@@ -124,13 +124,13 @@ export class DevServer {
|
|
|
124
124
|
// 4. Iframe Content: Text
|
|
125
125
|
router.get(
|
|
126
126
|
`${prefix}/:id/text`,
|
|
127
|
-
wrap((ctx) => {
|
|
127
|
+
wrap(async (ctx) => {
|
|
128
128
|
const id = ctx.req.param('id')
|
|
129
129
|
if (!id) {
|
|
130
130
|
return ctx.text('Bad Request', 400)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
const entry = this.mailbox.get(id)
|
|
133
|
+
const entry = await this.mailbox.get(id)
|
|
134
134
|
if (!entry) {
|
|
135
135
|
return ctx.text('Not found', 404)
|
|
136
136
|
}
|
|
@@ -143,13 +143,13 @@ export class DevServer {
|
|
|
143
143
|
// 5. Raw JSON
|
|
144
144
|
router.get(
|
|
145
145
|
`${prefix}/:id/raw`,
|
|
146
|
-
wrap((ctx) => {
|
|
146
|
+
wrap(async (ctx) => {
|
|
147
147
|
const id = ctx.req.param('id')
|
|
148
148
|
if (!id) {
|
|
149
149
|
return ctx.json({ error: 'Bad Request' }, 400)
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
const entry = this.mailbox.get(id)
|
|
152
|
+
const entry = await this.mailbox.get(id)
|
|
153
153
|
if (!entry) {
|
|
154
154
|
return ctx.json({ error: 'Not found' }, 404)
|
|
155
155
|
}
|
|
@@ -167,13 +167,13 @@ export class DevServer {
|
|
|
167
167
|
|
|
168
168
|
router.delete(
|
|
169
169
|
`${prefix}/:id`,
|
|
170
|
-
wrap((ctx) => {
|
|
170
|
+
wrap(async (ctx) => {
|
|
171
171
|
const id = ctx.req.param('id')
|
|
172
172
|
if (!id) {
|
|
173
173
|
return ctx.json({ success: false, error: 'Bad Request' }, 400)
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
const success = this.mailbox.delete(id)
|
|
176
|
+
const success = await this.mailbox.delete(id)
|
|
177
177
|
return ctx.json({ success })
|
|
178
178
|
})
|
|
179
179
|
)
|
|
@@ -181,8 +181,8 @@ export class DevServer {
|
|
|
181
181
|
// 7. API: Clear All
|
|
182
182
|
router.delete(
|
|
183
183
|
prefix,
|
|
184
|
-
wrap((ctx) => {
|
|
185
|
-
this.mailbox.clear()
|
|
184
|
+
wrap(async (ctx) => {
|
|
185
|
+
await this.mailbox.clear()
|
|
186
186
|
return ctx.json({ success: true })
|
|
187
187
|
})
|
|
188
188
|
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { MailboxEntry } from '../DevMailbox'
|
|
4
|
+
import type { MailboxStorage } from './MailboxStorage'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File-based storage for DevMailbox (Persistence).
|
|
8
|
+
* Stores emails as JSON in a specified directory.
|
|
9
|
+
*/
|
|
10
|
+
export class FileMailboxStorage implements MailboxStorage {
|
|
11
|
+
private filePath: string
|
|
12
|
+
|
|
13
|
+
constructor(storageDir: string) {
|
|
14
|
+
this.filePath = join(storageDir, 'mailbox.json')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async all(): Promise<MailboxEntry[]> {
|
|
18
|
+
try {
|
|
19
|
+
const content = await readFile(this.filePath, 'utf-8')
|
|
20
|
+
return JSON.parse(content)
|
|
21
|
+
} catch (_e) {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async push(entry: MailboxEntry): Promise<void> {
|
|
27
|
+
const entries = await this.all()
|
|
28
|
+
entries.unshift(entry)
|
|
29
|
+
await this.save(entries)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async trim(max: number): Promise<void> {
|
|
33
|
+
const entries = await this.all()
|
|
34
|
+
if (entries.length > max) {
|
|
35
|
+
await this.save(entries.slice(0, max))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async clear(): Promise<void> {
|
|
40
|
+
try {
|
|
41
|
+
await unlink(this.filePath)
|
|
42
|
+
} catch (_e) {
|
|
43
|
+
// Ignore if file doesn't exist
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async delete(id: string): Promise<boolean> {
|
|
48
|
+
const entries = await this.all()
|
|
49
|
+
const index = entries.findIndex((e) => e.id === id)
|
|
50
|
+
if (index !== -1) {
|
|
51
|
+
entries.splice(index, 1)
|
|
52
|
+
await this.save(entries)
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async save(entries: MailboxEntry[]): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
await mkdir(join(this.filePath, '..'), { recursive: true })
|
|
61
|
+
await writeFile(this.filePath, JSON.stringify(entries, null, 2), 'utf-8')
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('[FileMailboxStorage] Failed to save mailbox:', error)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for DevMailbox storage engines.
|
|
5
|
+
*/
|
|
6
|
+
export interface MailboxStorage {
|
|
7
|
+
/** Retrieve all entries. */
|
|
8
|
+
all(): Promise<MailboxEntry[]>
|
|
9
|
+
/** Add a single entry. */
|
|
10
|
+
push(entry: MailboxEntry): Promise<void>
|
|
11
|
+
/** Trim entries to a specific count. */
|
|
12
|
+
trim(max: number): Promise<void>
|
|
13
|
+
clear(): Promise<void>
|
|
14
|
+
delete?(id: string): Promise<boolean>
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
+
import type { MailboxStorage } from './MailboxStorage'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Memory-based storage for DevMailbox (Default).
|
|
6
|
+
*/
|
|
7
|
+
export class MemoryMailboxStorage implements MailboxStorage {
|
|
8
|
+
private entries: MailboxEntry[] = []
|
|
9
|
+
|
|
10
|
+
async all(): Promise<MailboxEntry[]> {
|
|
11
|
+
return [...this.entries]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async push(entry: MailboxEntry): Promise<void> {
|
|
15
|
+
this.entries.unshift(entry)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async trim(max: number): Promise<void> {
|
|
19
|
+
if (this.entries.length > max) {
|
|
20
|
+
this.entries = this.entries.slice(0, max)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async clear(): Promise<void> {
|
|
25
|
+
this.entries = []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async delete(id: string): Promise<boolean> {
|
|
29
|
+
const index = this.entries.findIndex((e) => e.id === id)
|
|
30
|
+
if (index !== -1) {
|
|
31
|
+
this.entries.splice(index, 1)
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/dev/ui/mailbox.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function getMailboxHtml(entries: MailboxEntry[], prefix: string): string
|
|
|
47
47
|
${entry.envelope.subject || '(No Subject)'}
|
|
48
48
|
</div>
|
|
49
49
|
<div class="meta" style="margin-top: 4px;">
|
|
50
|
-
To: ${entry.envelope.to?.map((t) => t.address).join(', ')}
|
|
50
|
+
To: ${entry.envelope.to?.map((t: any) => t.address).join(', ')}
|
|
51
51
|
</div>
|
|
52
52
|
</a>
|
|
53
53
|
`
|
package/src/dev/ui/preview.ts
CHANGED
|
@@ -14,7 +14,7 @@ export function getPreviewHtml(entry: MailboxEntry, prefix: string): string {
|
|
|
14
14
|
const from = entry.envelope.from
|
|
15
15
|
? `${entry.envelope.from.name || ''} <${entry.envelope.from.address}>`
|
|
16
16
|
: 'Unknown'
|
|
17
|
-
const to = entry.envelope.to?.map((t) => t.address).join(', ') || 'Unknown'
|
|
17
|
+
const to = entry.envelope.to?.map((t: any) => t.address).join(', ') || 'Unknown'
|
|
18
18
|
|
|
19
19
|
const content = `
|
|
20
20
|
<div class="header">
|
|
@@ -31,12 +31,12 @@ export function getPreviewHtml(entry: MailboxEntry, prefix: string): string {
|
|
|
31
31
|
<div class="meta" style="margin-bottom: 5px;">To: ${to}</div>
|
|
32
32
|
${
|
|
33
33
|
entry.envelope.cc
|
|
34
|
-
? `<div class="meta" style="margin-bottom: 5px;">CC: ${entry.envelope.cc.map((t) => t.address).join(', ')}</div>`
|
|
34
|
+
? `<div class="meta" style="margin-bottom: 5px;">CC: ${entry.envelope.cc.map((t: any) => t.address).join(', ')}</div>`
|
|
35
35
|
: ''
|
|
36
36
|
}
|
|
37
37
|
${
|
|
38
38
|
entry.envelope.bcc
|
|
39
|
-
? `<div class="meta" style="margin-bottom: 5px;">BCC: ${entry.envelope.bcc.map((t) => t.address).join(', ')}</div>`
|
|
39
|
+
? `<div class="meta" style="margin-bottom: 5px;">BCC: ${entry.envelope.bcc.map((t: any) => t.address).join(', ')}</div>`
|
|
40
40
|
: ''
|
|
41
41
|
}
|
|
42
42
|
${entry.envelope.priority ? `<div class="meta" style="margin-bottom: 5px;">Priority: ${entry.envelope.priority}</div>` : ''}
|
|
@@ -49,7 +49,7 @@ export function getPreviewHtml(entry: MailboxEntry, prefix: string): string {
|
|
|
49
49
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
50
50
|
${entry.envelope.attachments
|
|
51
51
|
.map(
|
|
52
|
-
(att) => `
|
|
52
|
+
(att: any) => `
|
|
53
53
|
<div style="background: var(--bg-dark); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; gap: 8px;">
|
|
54
54
|
<span style="font-size: 20px;">📎</span>
|
|
55
55
|
<div>
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mail transport error codes.
|
|
3
|
+
*
|
|
4
|
+
* Categorizes common failure modes in the mail delivery process to allow
|
|
5
|
+
* for programmatic handling (e.g., retries on rate limits).
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @since 3.1.0
|
|
9
|
+
*/
|
|
10
|
+
export enum MailErrorCode {
|
|
11
|
+
/** Connection to mail server failed */
|
|
12
|
+
CONNECTION_FAILED = 'CONNECTION_FAILED',
|
|
13
|
+
/** Authentication with mail server failed */
|
|
14
|
+
AUTH_FAILED = 'AUTH_FAILED',
|
|
15
|
+
/** One or more recipients were rejected by the server */
|
|
16
|
+
RECIPIENT_REJECTED = 'RECIPIENT_REJECTED',
|
|
17
|
+
/** The message was rejected by the server */
|
|
18
|
+
MESSAGE_REJECTED = 'MESSAGE_REJECTED',
|
|
19
|
+
/** Rate limit exceeded */
|
|
20
|
+
RATE_LIMIT = 'RATE_LIMIT',
|
|
21
|
+
/** Unknown or unclassified error */
|
|
22
|
+
UNKNOWN = 'UNKNOWN',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Error class for mail transport failures.
|
|
27
|
+
*
|
|
28
|
+
* Provides structured error information for mail sending failures,
|
|
29
|
+
* including error codes and original cause tracking for debugging.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* throw new MailTransportError(
|
|
34
|
+
* 'Failed to connect to SMTP server',
|
|
35
|
+
* MailErrorCode.CONNECTION_FAILED,
|
|
36
|
+
* originalError
|
|
37
|
+
* )
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
* @since 3.1.0
|
|
42
|
+
*/
|
|
43
|
+
export class MailTransportError extends Error {
|
|
44
|
+
/**
|
|
45
|
+
* Create a new mail transport error.
|
|
46
|
+
*
|
|
47
|
+
* @param message - Human-readable error message
|
|
48
|
+
* @param code - Categorized error code
|
|
49
|
+
* @param cause - Original error that caused this failure
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const error = new MailTransportError('Auth failed', MailErrorCode.AUTH_FAILED);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
constructor(
|
|
57
|
+
message: string,
|
|
58
|
+
public readonly code: MailErrorCode,
|
|
59
|
+
public readonly cause?: Error
|
|
60
|
+
) {
|
|
61
|
+
super(message)
|
|
62
|
+
this.name = 'MailTransportError'
|
|
63
|
+
|
|
64
|
+
// Maintain proper stack trace in V8 environments
|
|
65
|
+
if (Error.captureStackTrace) {
|
|
66
|
+
Error.captureStackTrace(this, MailTransportError)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Mailable } from './Mailable'
|
|
2
|
+
import type { Message } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mail event types.
|
|
6
|
+
*
|
|
7
|
+
* Defines the available lifecycle hooks for the mail service.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
* @since 3.1.0
|
|
11
|
+
*/
|
|
12
|
+
export type MailEventType =
|
|
13
|
+
| 'beforeSend'
|
|
14
|
+
| 'afterSend'
|
|
15
|
+
| 'sendFailed'
|
|
16
|
+
| 'beforeRender'
|
|
17
|
+
| 'afterRender'
|
|
18
|
+
| 'webhookReceived'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mail lifecycle event.
|
|
22
|
+
*
|
|
23
|
+
* Emitted at key points during email sending lifecycle.
|
|
24
|
+
* Used for logging, analytics, or custom processing.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* mail.on('afterSend', async (event) => {
|
|
29
|
+
* console.log('Email sent to:', event.message?.to)
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
* @since 3.1.0
|
|
35
|
+
*/
|
|
36
|
+
export interface MailEvent {
|
|
37
|
+
/** Type of event */
|
|
38
|
+
type: MailEventType
|
|
39
|
+
/** Mailable instance that triggered the event */
|
|
40
|
+
mailable: Mailable
|
|
41
|
+
/** Finalized message (available for send events) */
|
|
42
|
+
message?: Message
|
|
43
|
+
/** Error that occurred (available for sendFailed event) */
|
|
44
|
+
error?: Error
|
|
45
|
+
/** Timestamp when event occurred */
|
|
46
|
+
timestamp: Date
|
|
47
|
+
/** Webhook payload (available for webhookReceived event) */
|
|
48
|
+
webhook?: {
|
|
49
|
+
driver: string
|
|
50
|
+
event: string
|
|
51
|
+
payload: any
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mail event handler function.
|
|
57
|
+
*
|
|
58
|
+
* Defines the signature for functions that subscribe to mail events.
|
|
59
|
+
*
|
|
60
|
+
* @param event - The mail event object
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const handler: MailEventHandler = (event) => {
|
|
65
|
+
* console.log(`Event ${event.type} triggered`);
|
|
66
|
+
* };
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
* @since 3.1.0
|
|
71
|
+
*/
|
|
72
|
+
export type MailEventHandler = (event: MailEvent) => void | Promise<void>
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OrbitSignal - The central Event Bus and Mail Service for Gravito.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core infrastructure for sending emails and managing
|
|
5
|
+
* cross-module signals within the Gravito ecosystem. It supports multiple
|
|
6
|
+
* transport drivers, template rendering, and a robust development environment.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
// import './augmentation'
|
|
2
12
|
|
|
3
13
|
export type { Queueable } from '@gravito/stream'
|
|
4
14
|
export { DevMailbox, type MailboxEntry } from './dev/DevMailbox'
|
|
15
|
+
export { MailErrorCode, MailTransportError } from './errors'
|
|
16
|
+
export type { MailEvent, MailEventHandler, MailEventType } from './events'
|
|
5
17
|
export { Mailable } from './Mailable'
|
|
6
18
|
export { OrbitSignal } from './OrbitSignal'
|
|
7
19
|
export { HtmlRenderer } from './renderers/HtmlRenderer'
|
|
20
|
+
export { MjmlRenderer } from './renderers/MjmlRenderer'
|
|
21
|
+
export * from './renderers/mjml-templates'
|
|
22
|
+
export { ReactMjmlRenderer } from './renderers/ReactMjmlRenderer'
|
|
8
23
|
export type { Renderer, RenderResult } from './renderers/Renderer'
|
|
9
24
|
export { TemplateRenderer } from './renderers/TemplateRenderer'
|
|
25
|
+
export { VueMjmlRenderer } from './renderers/VueMjmlRenderer'
|
|
26
|
+
export { TypedMailable } from './TypedMailable'
|
|
27
|
+
export { BaseTransport, type TransportOptions } from './transports/BaseTransport'
|
|
10
28
|
export { LogTransport } from './transports/LogTransport'
|
|
11
29
|
export { MemoryTransport } from './transports/MemoryTransport'
|
|
12
30
|
export { SesTransport } from './transports/SesTransport'
|
|
13
31
|
export { SmtpTransport } from './transports/SmtpTransport'
|
|
14
|
-
|
|
15
32
|
export type { Transport } from './transports/Transport'
|
|
16
33
|
export type {
|
|
17
34
|
Address,
|
|
@@ -20,3 +37,5 @@ export type {
|
|
|
20
37
|
MailConfig,
|
|
21
38
|
Message,
|
|
22
39
|
} from './types'
|
|
40
|
+
export { type SendGridWebhookConfig, SendGridWebhookDriver } from './webhooks/SendGridWebhookDriver'
|
|
41
|
+
export { SesWebhookDriver } from './webhooks/SesWebhookDriver'
|
|
@@ -1,39 +1,41 @@
|
|
|
1
|
+
import { stripHtml } from '../utils/html'
|
|
1
2
|
import type { Renderer, RenderResult } from './Renderer'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Renderer for plain HTML content.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* a plain text version by stripping HTML tags.
|
|
7
|
+
* The simplest renderer - accepts raw HTML strings and automatically generates
|
|
8
|
+
* a plain text version by stripping HTML tags. Ideal for pre-rendered HTML or
|
|
9
|
+
* when using external HTML generation libraries.
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* ```typescript
|
|
11
|
-
* const renderer = new HtmlRenderer('<h1>Hello</h1
|
|
12
|
-
* const
|
|
13
|
-
* // html: '<h1>Hello</h1
|
|
14
|
-
* // text: 'Hello
|
|
13
|
+
* const renderer = new HtmlRenderer('<h1>Hello</h1>');
|
|
14
|
+
* const result = await renderer.render();
|
|
15
|
+
* // result.html: '<h1>Hello</h1>'
|
|
16
|
+
* // result.text: 'Hello'
|
|
15
17
|
* ```
|
|
16
18
|
*
|
|
17
|
-
* @since 3.0.0
|
|
18
19
|
* @public
|
|
20
|
+
* @since 3.0.0
|
|
19
21
|
*/
|
|
20
22
|
export class HtmlRenderer implements Renderer {
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of HtmlRenderer.
|
|
25
|
+
*
|
|
26
|
+
* @param content - The raw HTML string to be rendered.
|
|
27
|
+
*/
|
|
21
28
|
constructor(private content: string) {}
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Returns the original HTML and a stripped plain text version.
|
|
32
|
+
*
|
|
33
|
+
* @returns A promise resolving to the rendered content.
|
|
34
|
+
*/
|
|
23
35
|
async render(): Promise<RenderResult> {
|
|
24
36
|
return {
|
|
25
37
|
html: this.content,
|
|
26
|
-
text:
|
|
38
|
+
text: stripHtml(this.content),
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
|
-
|
|
30
|
-
private stripHtml(html: string): string {
|
|
31
|
-
return html
|
|
32
|
-
.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, '') // Remove styles
|
|
33
|
-
.replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, '') // Remove scripts
|
|
34
|
-
.replace(/<[^>]+>/g, '') // Remove tags
|
|
35
|
-
.replace(/ /g, ' ') // Replace non-breaking space
|
|
36
|
-
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
37
|
-
.trim()
|
|
38
|
-
}
|
|
39
41
|
}
|