@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/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
|
|
11
|
-
// import './augmentation'
|
|
12
|
-
|
|
13
|
-
export type { Queueable } from '@gravito/stream'
|
|
14
|
-
export { DevMailbox, type MailboxEntry } from './dev/DevMailbox'
|
|
15
|
-
export { MailErrorCode, MailTransportError } from './errors'
|
|
16
|
-
export type { MailEvent, MailEventHandler, MailEventType } from './events'
|
|
17
|
-
export { Mailable } from './Mailable'
|
|
18
|
-
export { OrbitSignal } from './OrbitSignal'
|
|
19
|
-
export { HtmlRenderer } from './renderers/HtmlRenderer'
|
|
20
|
-
export { MjmlRenderer } from './renderers/MjmlRenderer'
|
|
21
|
-
export * from './renderers/mjml-templates'
|
|
22
|
-
export { ReactMjmlRenderer } from './renderers/ReactMjmlRenderer'
|
|
23
|
-
export type { Renderer, RenderResult } from './renderers/Renderer'
|
|
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'
|
|
28
|
-
export { LogTransport } from './transports/LogTransport'
|
|
29
|
-
export { MemoryTransport } from './transports/MemoryTransport'
|
|
30
|
-
export { SesTransport } from './transports/SesTransport'
|
|
31
|
-
export { SmtpTransport } from './transports/SmtpTransport'
|
|
32
|
-
export type { Transport } from './transports/Transport'
|
|
33
|
-
export type {
|
|
34
|
-
Address,
|
|
35
|
-
Attachment,
|
|
36
|
-
Envelope,
|
|
37
|
-
MailConfig,
|
|
38
|
-
Message,
|
|
39
|
-
} from './types'
|
|
40
|
-
export { type SendGridWebhookConfig, SendGridWebhookDriver } from './webhooks/SendGridWebhookDriver'
|
|
41
|
-
export { SesWebhookDriver } from './webhooks/SesWebhookDriver'
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for plain HTML content.
|
|
6
|
-
*
|
|
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.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* const renderer = new HtmlRenderer('<h1>Hello</h1>');
|
|
14
|
-
* const result = await renderer.render();
|
|
15
|
-
* // result.html: '<h1>Hello</h1>'
|
|
16
|
-
* // result.text: 'Hello'
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @public
|
|
20
|
-
* @since 3.0.0
|
|
21
|
-
*/
|
|
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
|
-
*/
|
|
28
|
-
constructor(private content: string) {}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns the original HTML and a stripped plain text version.
|
|
32
|
-
*
|
|
33
|
-
* @returns A promise resolving to the rendered content.
|
|
34
|
-
*/
|
|
35
|
-
async render(): Promise<RenderResult> {
|
|
36
|
-
return {
|
|
37
|
-
html: this.content,
|
|
38
|
-
text: stripHtml(this.content),
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for MJML-based emails.
|
|
6
|
-
*
|
|
7
|
-
* MJML is a markup language designed to reduce the pain of coding a responsive email.
|
|
8
|
-
* This renderer lazily loads the `mjml` package to keep the core lightweight.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* const renderer = new MjmlRenderer('<mjml><mj-body>...</mj-body></mjml>');
|
|
13
|
-
* const result = await renderer.render();
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @public
|
|
17
|
-
* @since 1.1.0
|
|
18
|
-
*/
|
|
19
|
-
export class MjmlRenderer implements Renderer {
|
|
20
|
-
/**
|
|
21
|
-
* Creates an instance of MjmlRenderer.
|
|
22
|
-
*
|
|
23
|
-
* @param content - The MJML markup string to be rendered.
|
|
24
|
-
* @param options - Optional MJML transformation options.
|
|
25
|
-
* @param deps - Optional dependency injection for testing.
|
|
26
|
-
*/
|
|
27
|
-
constructor(
|
|
28
|
-
private content: string,
|
|
29
|
-
private options: Record<string, any> = {},
|
|
30
|
-
private deps: {
|
|
31
|
-
mjml2html?: (mjml: string, options?: any) => any
|
|
32
|
-
} = {}
|
|
33
|
-
) {}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Renders the MJML content to static HTML.
|
|
37
|
-
*
|
|
38
|
-
* This method performs a dynamic import of `mjml` to ensure it's only
|
|
39
|
-
* loaded if this renderer is actually used.
|
|
40
|
-
*
|
|
41
|
-
* @returns A promise resolving to the rendered content.
|
|
42
|
-
* @throws {Error} If MJML dependencies cannot be loaded or rendering fails.
|
|
43
|
-
*/
|
|
44
|
-
async render(): Promise<RenderResult> {
|
|
45
|
-
let mjml2html = this.deps.mjml2html
|
|
46
|
-
|
|
47
|
-
if (!mjml2html) {
|
|
48
|
-
try {
|
|
49
|
-
mjml2html = (await import('mjml')).default
|
|
50
|
-
} catch (_e) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
'[OrbitSignal] The "mjml" package is required for MjmlRenderer. Please install it using "bun add mjml".'
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const { html, errors } = mjml2html(this.content, {
|
|
58
|
-
validationLevel: 'soft',
|
|
59
|
-
...this.options,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (errors && errors.length > 0 && this.options.validationLevel === 'strict') {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`MJML rendering failed: ${errors.map((e: any) => e.formattedMessage).join(', ')}`
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
html,
|
|
70
|
-
text: stripHtml(html),
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for React component-based MJML emails.
|
|
6
|
-
*
|
|
7
|
-
* Renders React components to MJML string using SSR, then converts
|
|
8
|
-
* the MJML to responsive HTML.
|
|
9
|
-
*
|
|
10
|
-
* @typeParam P - Props type for the React component.
|
|
11
|
-
* @public
|
|
12
|
-
* @since 1.1.0
|
|
13
|
-
*/
|
|
14
|
-
export class ReactMjmlRenderer<P extends object = object> implements Renderer {
|
|
15
|
-
/**
|
|
16
|
-
* Creates an instance of ReactMjmlRenderer.
|
|
17
|
-
*
|
|
18
|
-
* @param component - The React component to render.
|
|
19
|
-
* @param props - Initial props for the component.
|
|
20
|
-
* @param options - Optional MJML transformation options.
|
|
21
|
-
* @param deps - Optional dependency injection for testing.
|
|
22
|
-
*/
|
|
23
|
-
constructor(
|
|
24
|
-
private component: any,
|
|
25
|
-
private props?: P,
|
|
26
|
-
private options: Record<string, any> = {},
|
|
27
|
-
private deps: {
|
|
28
|
-
createElement?: (...args: any[]) => any
|
|
29
|
-
renderToStaticMarkup?: (element: any) => string
|
|
30
|
-
mjml2html?: (mjml: string, options?: any) => any
|
|
31
|
-
} = {}
|
|
32
|
-
) {}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Renders the React component to a static HTML string via MJML.
|
|
36
|
-
*
|
|
37
|
-
* @param data - Runtime data to be merged with initial props.
|
|
38
|
-
* @returns A promise resolving to the rendered content.
|
|
39
|
-
* @throws {Error} If MJML rendering fails.
|
|
40
|
-
*/
|
|
41
|
-
async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
42
|
-
let { createElement, renderToStaticMarkup, mjml2html } = this.deps
|
|
43
|
-
|
|
44
|
-
if (!createElement || !renderToStaticMarkup) {
|
|
45
|
-
try {
|
|
46
|
-
const react = await import('react')
|
|
47
|
-
const reactDomServer = await import('react-dom/server')
|
|
48
|
-
createElement ??= react.createElement
|
|
49
|
-
renderToStaticMarkup ??= reactDomServer.renderToStaticMarkup
|
|
50
|
-
} catch (_e) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
'[OrbitSignal] The "react" and "react-dom" packages are required for ReactMjmlRenderer. Please install them using "bun add react react-dom".'
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!mjml2html) {
|
|
58
|
-
try {
|
|
59
|
-
mjml2html = (await import('mjml')).default
|
|
60
|
-
} catch (_e) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
'[OrbitSignal] The "mjml" package is required for ReactMjmlRenderer. Please install it using "bun add mjml".'
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const mergedProps = { ...this.props, ...data } as P
|
|
68
|
-
const element = createElement?.(this.component, mergedProps)
|
|
69
|
-
const mjml = renderToStaticMarkup?.(element)
|
|
70
|
-
if (!mjml) {
|
|
71
|
-
throw new Error('Failed to render MJML template')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = mjml2html?.(mjml, {
|
|
75
|
-
validationLevel: 'soft',
|
|
76
|
-
...this.options,
|
|
77
|
-
})
|
|
78
|
-
if (!result) {
|
|
79
|
-
throw new Error('Failed to convert MJML to HTML')
|
|
80
|
-
}
|
|
81
|
-
const { html, errors } = result
|
|
82
|
-
|
|
83
|
-
if (errors && errors.length > 0 && this.options.validationLevel === 'strict') {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`MJML rendering failed: ${errors.map((e: any) => e.formattedMessage).join(', ')}`
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
html,
|
|
91
|
-
text: stripHtml(html),
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for React component-based emails.
|
|
6
|
-
*
|
|
7
|
-
* Renders React components to static HTML using server-side rendering (SSR).
|
|
8
|
-
* It lazily loads React and ReactDOM dependencies only when needed, preventing
|
|
9
|
-
* unnecessary bundle bloat for users who do not use React renderers.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* const renderer = new ReactRenderer(MyComponent, { title: 'Welcome' });
|
|
14
|
-
* const result = await renderer.render({ name: 'John' });
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @typeParam P - Props type for the React component.
|
|
18
|
-
* @public
|
|
19
|
-
* @since 3.0.0
|
|
20
|
-
*/
|
|
21
|
-
export class ReactRenderer<P extends object = object> implements Renderer {
|
|
22
|
-
/**
|
|
23
|
-
* Creates an instance of ReactRenderer.
|
|
24
|
-
*
|
|
25
|
-
* @param component - The React component to render.
|
|
26
|
-
* @param props - Initial props for the component.
|
|
27
|
-
* @param deps - Optional dependency injection for testing.
|
|
28
|
-
*/
|
|
29
|
-
constructor(
|
|
30
|
-
private component: any, // Use any to avoid hard React dependency in types
|
|
31
|
-
private props?: P,
|
|
32
|
-
private deps: {
|
|
33
|
-
createElement?: (...args: any[]) => any
|
|
34
|
-
renderToStaticMarkup?: (element: any) => string
|
|
35
|
-
} = {}
|
|
36
|
-
) {}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Renders the React component to a static HTML string.
|
|
40
|
-
*
|
|
41
|
-
* This method performs dynamic imports of `react` and `react-dom/server`
|
|
42
|
-
* to ensure they are only loaded if this renderer is actually used.
|
|
43
|
-
*
|
|
44
|
-
* @param data - Runtime data to be merged with initial props.
|
|
45
|
-
* @returns A promise resolving to the rendered content.
|
|
46
|
-
* @throws {Error} If React dependencies cannot be loaded or rendering fails.
|
|
47
|
-
*/
|
|
48
|
-
async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
49
|
-
// Dynamic imports to avoid hard dependencies on react/react-dom
|
|
50
|
-
const createElement = this.deps.createElement ?? (await import('react')).createElement
|
|
51
|
-
const renderToStaticMarkup =
|
|
52
|
-
this.deps.renderToStaticMarkup ?? (await import('react-dom/server')).renderToStaticMarkup
|
|
53
|
-
|
|
54
|
-
const mergedProps = { ...this.props, ...data } as P
|
|
55
|
-
|
|
56
|
-
const element = createElement(this.component, mergedProps)
|
|
57
|
-
const html = renderToStaticMarkup(element)
|
|
58
|
-
|
|
59
|
-
const fullHtml = html.startsWith('<!DOCTYPE') ? html : `<!DOCTYPE html>${html}`
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
html: fullHtml,
|
|
63
|
-
text: stripHtml(html),
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Result of a content rendering operation.
|
|
3
|
-
*
|
|
4
|
-
* This interface defines the structure of the output produced by any renderer.
|
|
5
|
-
* It ensures consistency across different rendering strategies (HTML, React, Vue, etc.),
|
|
6
|
-
* providing both the final HTML for the email body and an optional plain text version
|
|
7
|
-
* for clients that do not support HTML.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* const result: RenderResult = {
|
|
12
|
-
* html: '<html><body><h1>Hello</h1></body></html>',
|
|
13
|
-
* text: 'Hello'
|
|
14
|
-
* };
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @public
|
|
18
|
-
* @since 3.0.0
|
|
19
|
-
*/
|
|
20
|
-
export interface RenderResult {
|
|
21
|
-
/**
|
|
22
|
-
* The rendered HTML string.
|
|
23
|
-
*
|
|
24
|
-
* This is the primary content used for the email body.
|
|
25
|
-
*/
|
|
26
|
-
html: string
|
|
27
|
-
/**
|
|
28
|
-
* Optional rendered plain text string.
|
|
29
|
-
*
|
|
30
|
-
* Used as a fallback for email clients that cannot display HTML or for accessibility.
|
|
31
|
-
*/
|
|
32
|
-
text?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Interface for email content renderers.
|
|
37
|
-
*
|
|
38
|
-
* Renderers are responsible for transforming various input formats (raw HTML,
|
|
39
|
-
* templates, or UI components) into a standardized {@link RenderResult}.
|
|
40
|
-
* This abstraction allows the mail system to support multiple view engines
|
|
41
|
-
* and frameworks interchangeably.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* class MyRenderer implements Renderer {
|
|
46
|
-
* async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
47
|
-
* return { html: `<div>${data.name}</div>`, text: String(data.name) };
|
|
48
|
-
* }
|
|
49
|
-
* }
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @public
|
|
53
|
-
* @since 3.0.0
|
|
54
|
-
*/
|
|
55
|
-
export interface Renderer {
|
|
56
|
-
/**
|
|
57
|
-
* Render the content into HTML and optionally plain text.
|
|
58
|
-
*
|
|
59
|
-
* This method performs the actual transformation of the source content
|
|
60
|
-
* using the provided data context.
|
|
61
|
-
*
|
|
62
|
-
* @param data - The data context for rendering.
|
|
63
|
-
* @returns A promise resolving to the rendered content.
|
|
64
|
-
* @throws {Error} If rendering fails due to syntax errors or missing dependencies.
|
|
65
|
-
*/
|
|
66
|
-
render(data: Record<string, unknown>): Promise<RenderResult>
|
|
67
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for template-based emails using Gravito Prism.
|
|
6
|
-
*
|
|
7
|
-
* Renders email templates from the filesystem using the Prism template engine.
|
|
8
|
-
* It uses a static cache for the template engine to avoid redundant initialization
|
|
9
|
-
* costs when rendering multiple emails from the same directory.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* const renderer = new TemplateRenderer('welcome', './src/emails');
|
|
14
|
-
* const result = await renderer.render({ name: 'John' });
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @public
|
|
18
|
-
* @since 3.0.0
|
|
19
|
-
*/
|
|
20
|
-
export class TemplateRenderer implements Renderer {
|
|
21
|
-
private template: string
|
|
22
|
-
private viewsDir: string
|
|
23
|
-
private static engineCache = new Map<string, any>()
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates an instance of TemplateRenderer.
|
|
27
|
-
*
|
|
28
|
-
* @param templateName - The name of the template file (without extension).
|
|
29
|
-
* @param viewsDir - The directory containing template files. Defaults to `src/emails`.
|
|
30
|
-
*/
|
|
31
|
-
constructor(templateName: string, viewsDir?: string) {
|
|
32
|
-
this.template = templateName
|
|
33
|
-
// Default to src/emails if not provided, falling back to process cwd
|
|
34
|
-
this.viewsDir = viewsDir || `${process.cwd()}/src/emails`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Renders the template with the provided data.
|
|
39
|
-
*
|
|
40
|
-
* This method lazily loads `@gravito/prism` to ensure the core package
|
|
41
|
-
* remains lightweight for users who don't need template rendering.
|
|
42
|
-
*
|
|
43
|
-
* @param data - The data context for template interpolation.
|
|
44
|
-
* @returns A promise resolving to the rendered content.
|
|
45
|
-
* @throws {Error} If the template engine fails to load or rendering fails.
|
|
46
|
-
*/
|
|
47
|
-
async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
48
|
-
// Dynamic import to avoid hard dependency on @gravito/prism
|
|
49
|
-
const { TemplateEngine } = await import('@gravito/prism')
|
|
50
|
-
|
|
51
|
-
const cached = TemplateRenderer.engineCache.get(this.viewsDir)
|
|
52
|
-
const engine = cached || new TemplateEngine(this.viewsDir)
|
|
53
|
-
|
|
54
|
-
if (!cached) {
|
|
55
|
-
TemplateRenderer.engineCache.set(this.viewsDir, engine)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Disable automatic layout by default for emails, unless explicitly handled in template
|
|
59
|
-
const html = engine.render(this.template, data, {})
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
html,
|
|
63
|
-
text: stripHtml(html),
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Clear template engine cache.
|
|
69
|
-
*
|
|
70
|
-
* Useful in development environments to force recompilation of templates
|
|
71
|
-
* after they have been modified on disk.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* TemplateRenderer.clearCache();
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @public
|
|
79
|
-
* @since 3.1.0
|
|
80
|
-
*/
|
|
81
|
-
static clearCache(): void {
|
|
82
|
-
TemplateRenderer.engineCache.clear()
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for Vue component-based MJML emails.
|
|
6
|
-
*
|
|
7
|
-
* Renders Vue 3 components to MJML string using SSR, then converts
|
|
8
|
-
* the MJML to responsive HTML.
|
|
9
|
-
*
|
|
10
|
-
* @typeParam P - Props type for the Vue component.
|
|
11
|
-
* @public
|
|
12
|
-
* @since 1.1.0
|
|
13
|
-
*/
|
|
14
|
-
export class VueMjmlRenderer<P extends object = object> implements Renderer {
|
|
15
|
-
/**
|
|
16
|
-
* Creates an instance of VueMjmlRenderer.
|
|
17
|
-
*
|
|
18
|
-
* @param component - The Vue component to render.
|
|
19
|
-
* @param props - Initial props for the component.
|
|
20
|
-
* @param options - Optional MJML transformation options.
|
|
21
|
-
* @param deps - Optional dependency injection for testing.
|
|
22
|
-
*/
|
|
23
|
-
constructor(
|
|
24
|
-
private component: any,
|
|
25
|
-
private props?: P,
|
|
26
|
-
private options: Record<string, any> = {},
|
|
27
|
-
private deps: {
|
|
28
|
-
createSSRApp?: (...args: any[]) => any
|
|
29
|
-
h?: (...args: any[]) => any
|
|
30
|
-
renderToString?: (app: any) => Promise<string>
|
|
31
|
-
mjml2html?: (mjml: string, options?: any) => any
|
|
32
|
-
} = {}
|
|
33
|
-
) {}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Renders the Vue component to a static HTML string via MJML.
|
|
37
|
-
*
|
|
38
|
-
* @param data - Runtime data to be merged with initial props.
|
|
39
|
-
* @returns A promise resolving to the rendered content.
|
|
40
|
-
* @throws {Error} If MJML rendering fails.
|
|
41
|
-
*/
|
|
42
|
-
async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
43
|
-
let { createSSRApp, h, renderToString, mjml2html } = this.deps
|
|
44
|
-
|
|
45
|
-
if (!createSSRApp || !h || !renderToString) {
|
|
46
|
-
try {
|
|
47
|
-
const vue = await import('vue')
|
|
48
|
-
const vueServerRenderer = await import('@vue/server-renderer')
|
|
49
|
-
createSSRApp ??= vue.createSSRApp
|
|
50
|
-
h ??= vue.h
|
|
51
|
-
renderToString ??= vueServerRenderer.renderToString
|
|
52
|
-
} catch (_e) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
'[OrbitSignal] The "vue" and "@vue/server-renderer" packages are required for VueMjmlRenderer. Please install them using "bun add vue @vue/server-renderer".'
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!mjml2html) {
|
|
60
|
-
try {
|
|
61
|
-
mjml2html = (await import('mjml')).default
|
|
62
|
-
} catch (_e) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
'[OrbitSignal] The "mjml" package is required for VueMjmlRenderer. Please install it using "bun add mjml".'
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const mergedProps = { ...this.props, ...data }
|
|
70
|
-
const app = createSSRApp?.({
|
|
71
|
-
render: () => h?.(this.component, mergedProps),
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const mjml = await renderToString?.(app)
|
|
75
|
-
if (!mjml) {
|
|
76
|
-
throw new Error('Failed to render MJML template')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const result = mjml2html?.(mjml, {
|
|
80
|
-
validationLevel: 'soft',
|
|
81
|
-
...this.options,
|
|
82
|
-
})
|
|
83
|
-
if (!result) {
|
|
84
|
-
throw new Error('Failed to convert MJML to HTML')
|
|
85
|
-
}
|
|
86
|
-
const { html, errors } = result
|
|
87
|
-
|
|
88
|
-
if (errors && errors.length > 0 && this.options.validationLevel === 'strict') {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`MJML rendering failed: ${errors.map((e: any) => e.formattedMessage).join(', ')}`
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
html,
|
|
96
|
-
text: stripHtml(html),
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { stripHtml } from '../utils/html'
|
|
2
|
-
import type { Renderer, RenderResult } from './Renderer'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renderer for Vue component-based emails.
|
|
6
|
-
*
|
|
7
|
-
* Renders Vue 3 components to static HTML using server-side rendering (SSR).
|
|
8
|
-
* It lazily loads Vue and `@vue/server-renderer` dependencies only when needed,
|
|
9
|
-
* preventing unnecessary bundle bloat for users who do not use Vue renderers.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* const renderer = new VueRenderer(MyComponent, { title: 'Welcome' });
|
|
14
|
-
* const result = await renderer.render({ name: 'John' });
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @typeParam P - Props type for the Vue component.
|
|
18
|
-
* @public
|
|
19
|
-
* @since 3.0.0
|
|
20
|
-
*/
|
|
21
|
-
export class VueRenderer<P extends object = object> implements Renderer {
|
|
22
|
-
/**
|
|
23
|
-
* Creates an instance of VueRenderer.
|
|
24
|
-
*
|
|
25
|
-
* @param component - The Vue component to render.
|
|
26
|
-
* @param props - Initial props for the component.
|
|
27
|
-
* @param deps - Optional dependency injection for testing.
|
|
28
|
-
*/
|
|
29
|
-
constructor(
|
|
30
|
-
private component: any, // Use any to avoid hard Vue dependency in types
|
|
31
|
-
private props?: P,
|
|
32
|
-
private deps: {
|
|
33
|
-
createSSRApp?: (...args: any[]) => any
|
|
34
|
-
h?: (...args: any[]) => any
|
|
35
|
-
renderToString?: (app: any) => Promise<string>
|
|
36
|
-
} = {}
|
|
37
|
-
) {}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Renders the Vue component to a static HTML string.
|
|
41
|
-
*
|
|
42
|
-
* This method performs dynamic imports of `vue` and `@vue/server-renderer`
|
|
43
|
-
* to ensure they are only loaded if this renderer is actually used.
|
|
44
|
-
*
|
|
45
|
-
* @param data - Runtime data to be merged with initial props.
|
|
46
|
-
* @returns A promise resolving to the rendered content.
|
|
47
|
-
* @throws {Error} If Vue dependencies cannot be loaded or rendering fails.
|
|
48
|
-
*/
|
|
49
|
-
async render(data: Record<string, unknown>): Promise<RenderResult> {
|
|
50
|
-
// Dynamic imports to avoid hard dependencies on vue/@vue/server-renderer
|
|
51
|
-
const createSSRApp = this.deps.createSSRApp ?? (await import('vue')).createSSRApp
|
|
52
|
-
const h = this.deps.h ?? (await import('vue')).h
|
|
53
|
-
const renderToString =
|
|
54
|
-
this.deps.renderToString ?? (await import('@vue/server-renderer')).renderToString
|
|
55
|
-
|
|
56
|
-
const mergedProps = { ...this.props, ...data }
|
|
57
|
-
|
|
58
|
-
const app = createSSRApp({
|
|
59
|
-
render: () => h(this.component, mergedProps),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const html = await renderToString(app)
|
|
63
|
-
|
|
64
|
-
const fullHtml = html.startsWith('<!DOCTYPE') ? html : `<!DOCTYPE html>${html}`
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
html: fullHtml,
|
|
68
|
-
text: stripHtml(html),
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base layout for MJML emails.
|
|
3
|
-
* Includes common head styles and responsive settings.
|
|
4
|
-
*
|
|
5
|
-
* Placeholder: {{content}}
|
|
6
|
-
*/
|
|
7
|
-
export const baseLayout = `
|
|
8
|
-
<mjml>
|
|
9
|
-
<mj-head>
|
|
10
|
-
<mj-attributes>
|
|
11
|
-
<mj-all font-family="Arial, Helvetica, sans-serif" />
|
|
12
|
-
<mj-text font-size="16px" color="#333333" line-height="1.5" />
|
|
13
|
-
<mj-section padding="20px" />
|
|
14
|
-
</mj-attributes>
|
|
15
|
-
<mj-style>
|
|
16
|
-
.link-white { color: #ffffff !important; text-decoration: none; }
|
|
17
|
-
.footer-text { font-size: 12px; color: #999999; }
|
|
18
|
-
</mj-style>
|
|
19
|
-
</mj-head>
|
|
20
|
-
<mj-body background-color="#f4f4f4">
|
|
21
|
-
<mj-section background-color="#ffffff" padding-bottom="0px">
|
|
22
|
-
<mj-column>
|
|
23
|
-
<mj-image width="150px" src="https://gravito.dev/logo.png" alt="Gravito" />
|
|
24
|
-
</mj-column>
|
|
25
|
-
</mj-section>
|
|
26
|
-
|
|
27
|
-
{{content}}
|
|
28
|
-
|
|
29
|
-
<mj-section>
|
|
30
|
-
<mj-column>
|
|
31
|
-
<mj-divider border-width="1px" border-color="#dddddd" />
|
|
32
|
-
<mj-text align="center" css-class="footer-text">
|
|
33
|
-
© ${new Date().getFullYear()} Gravito Framework. All rights reserved.
|
|
34
|
-
</mj-text>
|
|
35
|
-
</mj-column>
|
|
36
|
-
</mj-section>
|
|
37
|
-
</mj-body>
|
|
38
|
-
</mjml>
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A simple transactional component layout.
|
|
43
|
-
*/
|
|
44
|
-
export const transactionLayout = `
|
|
45
|
-
<mj-section background-color="#ffffff" padding-top="0px">
|
|
46
|
-
<mj-column>
|
|
47
|
-
{{content}}
|
|
48
|
-
</mj-column>
|
|
49
|
-
</mj-section>
|
|
50
|
-
`
|