@veap/mail 0.1.7 → 0.1.8
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/composition.js +3 -3
- package/dist/context.d.ts +21 -10
- package/dist/context.js +19 -9
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mail.d.ts +4 -14
- package/dist/mail.js +6 -47
- package/dist/provider.d.ts +8 -4
- package/dist/provider.js +12 -17
- package/package.json +2 -2
- package/dist/translator.d.ts +0 -24
- package/dist/translator.js +0 -31
package/dist/composition.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApplicationBuilder } from "@veap/kernel";
|
|
2
|
-
import {
|
|
2
|
+
import { MailServiceProvider } from "./provider.js";
|
|
3
3
|
ApplicationBuilder.prototype.withMail = function () {
|
|
4
|
-
return this.use(
|
|
4
|
+
return this.use(MailServiceProvider);
|
|
5
5
|
};
|
|
6
6
|
ApplicationBuilder.prototype.withCommunication = function () {
|
|
7
|
-
return this.use(
|
|
7
|
+
return this.use(MailServiceProvider);
|
|
8
8
|
};
|
package/dist/context.d.ts
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import type { IMailer } from "./mailer.js";
|
|
2
2
|
/**
|
|
3
|
-
* Typed boundary between the
|
|
4
|
-
* (facades, application services).
|
|
5
|
-
* `application/auth/context.ts` and `application/plugins/context.ts`.
|
|
3
|
+
* Typed boundary between the mail use cases and their consumers
|
|
4
|
+
* (facades, application services).
|
|
6
5
|
*
|
|
7
|
-
* The composition root (`
|
|
6
|
+
* The composition root (`MailServiceProvider`) selects the mail
|
|
8
7
|
* transport once at boot and binds it here.
|
|
9
8
|
*/
|
|
10
|
-
export interface
|
|
9
|
+
export interface MailContext {
|
|
11
10
|
mailer: IMailer;
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* @deprecated Use `MailContext` instead.
|
|
15
14
|
*/
|
|
16
|
-
export
|
|
15
|
+
export type CommunicationContext = MailContext;
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
17
|
+
* Binds the resolved mail services. Called once by the composition root.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bindMailContext(context: MailContext): void;
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Use `bindMailContext` instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare const bindCommunicationContext: typeof bindMailContext;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the bound mail services.
|
|
19
26
|
*
|
|
20
|
-
* @throws If the
|
|
27
|
+
* @throws If the mail service provider has not booted yet.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mailContext(): MailContext;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `mailContext` instead.
|
|
21
32
|
*/
|
|
22
|
-
export declare
|
|
33
|
+
export declare const communicationContext: typeof mailContext;
|
package/dist/context.js
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { AppError } from "@veap/kernel";
|
|
2
|
-
const
|
|
2
|
+
const globalForMailContext = globalThis;
|
|
3
3
|
/**
|
|
4
|
-
* Binds the resolved
|
|
4
|
+
* Binds the resolved mail services. Called once by the composition root.
|
|
5
5
|
*/
|
|
6
|
-
export function
|
|
7
|
-
|
|
6
|
+
export function bindMailContext(context) {
|
|
7
|
+
globalForMailContext.__VEAP_MAIL_CONTEXT__ = context;
|
|
8
|
+
globalForMailContext.__VEAP_COMMUNICATION_CONTEXT__ = context;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* @deprecated Use `bindMailContext` instead.
|
|
12
|
+
*/
|
|
13
|
+
export const bindCommunicationContext = bindMailContext;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the bound mail services.
|
|
11
16
|
*
|
|
12
|
-
* @throws If the
|
|
17
|
+
* @throws If the mail service provider has not booted yet.
|
|
13
18
|
*/
|
|
14
|
-
export function
|
|
15
|
-
const context =
|
|
19
|
+
export function mailContext() {
|
|
20
|
+
const context = globalForMailContext.__VEAP_MAIL_CONTEXT__ ??
|
|
21
|
+
globalForMailContext.__VEAP_COMMUNICATION_CONTEXT__;
|
|
16
22
|
if (!context) {
|
|
17
|
-
throw AppError.Internal("[
|
|
23
|
+
throw AppError.Internal("[Mail] Context is not bound. MailServiceProvider must boot before the mail facades are used.");
|
|
18
24
|
}
|
|
19
25
|
return context;
|
|
20
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Use `mailContext` instead.
|
|
29
|
+
*/
|
|
30
|
+
export const communicationContext = mailContext;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/mail.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import type { MailMessage } from "./mail-message.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Sends an email message through the configured transport.
|
|
4
4
|
*
|
|
5
|
-
* Transports deliver;
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* content, `Transport` only delivers it - a transport never knows whether it
|
|
9
|
-
* is carrying a verification code or a newsletter.
|
|
10
|
-
*
|
|
11
|
-
* Application services (auth flows) and plugins call these helpers; swapping
|
|
12
|
-
* the transport never touches them, and adding a new mail kind never touches
|
|
13
|
-
* a transport.
|
|
5
|
+
* Transports deliver; application services, plugins, and domains decide
|
|
6
|
+
* what is sent (subject, body, recipients, templates). Swapping the transport
|
|
7
|
+
* never touches the caller.
|
|
14
8
|
*/
|
|
15
9
|
export declare function sendMail(message: MailMessage): Promise<void>;
|
|
16
|
-
export declare function sendVerifyEmail(email: string, code: string): Promise<void>;
|
|
17
|
-
export declare function sendResetPassword(email: string, code: string): Promise<void>;
|
|
18
|
-
export declare function sendRecoveryCode(email: string, recoveryCode: string): Promise<void>;
|
|
19
|
-
export declare function send2FACode(email: string, code: string): Promise<void>;
|
package/dist/mail.js
CHANGED
|
@@ -1,52 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getMailTranslator } from "./translator.js";
|
|
1
|
+
import { mailContext } from "./context.js";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
3
|
+
* Sends an email message through the configured transport.
|
|
5
4
|
*
|
|
6
|
-
* Transports deliver;
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* content, `Transport` only delivers it - a transport never knows whether it
|
|
10
|
-
* is carrying a verification code or a newsletter.
|
|
11
|
-
*
|
|
12
|
-
* Application services (auth flows) and plugins call these helpers; swapping
|
|
13
|
-
* the transport never touches them, and adding a new mail kind never touches
|
|
14
|
-
* a transport.
|
|
5
|
+
* Transports deliver; application services, plugins, and domains decide
|
|
6
|
+
* what is sent (subject, body, recipients, templates). Swapping the transport
|
|
7
|
+
* never touches the caller.
|
|
15
8
|
*/
|
|
16
9
|
export async function sendMail(message) {
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
export async function sendVerifyEmail(email, code) {
|
|
20
|
-
const t = await getMailTranslator();
|
|
21
|
-
return sendMail({
|
|
22
|
-
to: email,
|
|
23
|
-
subject: t("Verify Email"),
|
|
24
|
-
text: t("Verify your email address with your code: {code}", { code }),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
export async function sendResetPassword(email, code) {
|
|
28
|
-
const t = await getMailTranslator();
|
|
29
|
-
return sendMail({
|
|
30
|
-
to: email,
|
|
31
|
-
subject: t("Password Reset"),
|
|
32
|
-
text: t("Verify password reset with code: {code}", { code }),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
export async function sendRecoveryCode(email, recoveryCode) {
|
|
36
|
-
const t = await getMailTranslator();
|
|
37
|
-
return sendMail({
|
|
38
|
-
to: email,
|
|
39
|
-
subject: t("Recovery Code"),
|
|
40
|
-
text: t("Your recovery code is: {recoveryCode}", { recoveryCode }),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
export async function send2FACode(email, code) {
|
|
44
|
-
const t = await getMailTranslator();
|
|
45
|
-
return sendMail({
|
|
46
|
-
to: email,
|
|
47
|
-
subject: t("Your 2FA Code"),
|
|
48
|
-
text: t("Your verification code is: {code}. It will expire in 10 minutes.", {
|
|
49
|
-
code,
|
|
50
|
-
}),
|
|
51
|
-
});
|
|
10
|
+
return mailContext().mailer.sendMail(message);
|
|
52
11
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ServiceProvider } from "@veap/kernel";
|
|
2
2
|
/**
|
|
3
|
-
* Composition root of the
|
|
3
|
+
* Composition root of the mail context.
|
|
4
4
|
*
|
|
5
5
|
* Selects the mail transport exactly once (in `boot()`) from `MAIL_TRANSPORT`
|
|
6
|
-
* and binds it into `
|
|
6
|
+
* and binds it into `MailContext`, so facades and services never
|
|
7
7
|
* look transports up themselves:
|
|
8
8
|
*
|
|
9
9
|
* - `smtp` (default) - Nodemailer (`MAIL_SERVICE`, `MAIL_HOST`, `MAIL_PORT`,
|
|
@@ -11,9 +11,13 @@ import { ServiceProvider } from "@veap/kernel";
|
|
|
11
11
|
* - `console` - logs the message; dev/test with zero mail configuration
|
|
12
12
|
* - `custom` - resolves `CUSTOM_MAILER`; bring your own SES/Postmark/Resend…
|
|
13
13
|
*/
|
|
14
|
-
export declare class
|
|
14
|
+
export declare class MailServiceProvider extends ServiceProvider {
|
|
15
15
|
register(): void;
|
|
16
16
|
boot(): Promise<void>;
|
|
17
17
|
private selectTransport;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use `MailServiceProvider` instead.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CommunicationServiceProvider: typeof MailServiceProvider;
|
|
23
|
+
export type CommunicationServiceProvider = MailServiceProvider;
|
package/dist/provider.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { ConfigService, logger, ServiceProvider } from "@veap/kernel";
|
|
2
|
-
import {
|
|
2
|
+
import { bindMailContext } from "./context.js";
|
|
3
3
|
import { CUSTOM_MAILER, MAILER } from "./mailer.js";
|
|
4
4
|
import { ConsoleMailService } from "./providers/console.provider.js";
|
|
5
5
|
import { NodemailerMailService } from "./providers/nodemailer.provider.js";
|
|
6
|
-
import { setMailTranslatorFactory } from "./translator.js";
|
|
7
6
|
/**
|
|
8
|
-
* Composition root of the
|
|
7
|
+
* Composition root of the mail context.
|
|
9
8
|
*
|
|
10
9
|
* Selects the mail transport exactly once (in `boot()`) from `MAIL_TRANSPORT`
|
|
11
|
-
* and binds it into `
|
|
10
|
+
* and binds it into `MailContext`, so facades and services never
|
|
12
11
|
* look transports up themselves:
|
|
13
12
|
*
|
|
14
13
|
* - `smtp` (default) - Nodemailer (`MAIL_SERVICE`, `MAIL_HOST`, `MAIL_PORT`,
|
|
@@ -16,7 +15,7 @@ import { setMailTranslatorFactory } from "./translator.js";
|
|
|
16
15
|
* - `console` - logs the message; dev/test with zero mail configuration
|
|
17
16
|
* - `custom` - resolves `CUSTOM_MAILER`; bring your own SES/Postmark/Resend…
|
|
18
17
|
*/
|
|
19
|
-
export class
|
|
18
|
+
export class MailServiceProvider extends ServiceProvider {
|
|
20
19
|
register() {
|
|
21
20
|
// Default registration: the Nodemailer transport under the port token,
|
|
22
21
|
// with config getters wired here at the composition root (the adapter
|
|
@@ -53,13 +52,6 @@ export class CommunicationServiceProvider extends ServiceProvider {
|
|
|
53
52
|
});
|
|
54
53
|
}
|
|
55
54
|
async boot() {
|
|
56
|
-
// Bind the default translation factory that supports `{placeholder}`
|
|
57
|
-
// interpolation into the message text.
|
|
58
|
-
setMailTranslatorFactory(async () => (key, params) => {
|
|
59
|
-
if (!params)
|
|
60
|
-
return key;
|
|
61
|
-
return Object.entries(params).reduce((acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v)), key);
|
|
62
|
-
});
|
|
63
55
|
const config = this.container.has(ConfigService)
|
|
64
56
|
? await this.container.resolve(ConfigService)
|
|
65
57
|
: undefined;
|
|
@@ -71,8 +63,8 @@ export class CommunicationServiceProvider extends ServiceProvider {
|
|
|
71
63
|
useValue: mailer,
|
|
72
64
|
singleton: true,
|
|
73
65
|
});
|
|
74
|
-
|
|
75
|
-
logger.debug("veap:
|
|
66
|
+
bindMailContext({ mailer });
|
|
67
|
+
logger.debug("veap:mail", `Mail transport bound: ${mailer.constructor.name}`);
|
|
76
68
|
}
|
|
77
69
|
async selectTransport(config) {
|
|
78
70
|
const getVal = (key) => {
|
|
@@ -82,7 +74,7 @@ export class CommunicationServiceProvider extends ServiceProvider {
|
|
|
82
74
|
const transport = getVal("MAIL_TRANSPORT")?.trim().toLowerCase();
|
|
83
75
|
if (transport === "custom") {
|
|
84
76
|
if (!this.container.has(CUSTOM_MAILER)) {
|
|
85
|
-
throw new Error("[
|
|
77
|
+
throw new Error("[Mail] MAIL_TRANSPORT=custom but no provider is registered under the CUSTOM_MAILER token (register an IMailer in a ServiceProvider).");
|
|
86
78
|
}
|
|
87
79
|
return this.container.resolve(CUSTOM_MAILER);
|
|
88
80
|
}
|
|
@@ -93,11 +85,14 @@ export class CommunicationServiceProvider extends ServiceProvider {
|
|
|
93
85
|
Boolean(getVal("GOOGLE_SMTP_APP_PASSWORD") || getVal("MAIL_PASSWORD"));
|
|
94
86
|
const hasSmtpHostOrService = Boolean(getVal("MAIL_HOST") || getVal("MAIL_SERVICE"));
|
|
95
87
|
if (!transport && !hasSmtpCredentials && !hasSmtpHostOrService) {
|
|
96
|
-
logger.warn("veap:
|
|
88
|
+
logger.warn("veap:mail", "MAIL_TRANSPORT is not set and no SMTP credentials (MAIL_USERNAME/MAIL_PASSWORD) or host (MAIL_HOST) are configured. Defaulting to ConsoleMailService (emails will be logged to server console).");
|
|
97
89
|
return new ConsoleMailService((line) => logger.info("veap:mail", line));
|
|
98
90
|
}
|
|
99
91
|
// Default ("smtp" / unset with credentials/host): whatever register() wired - Nodemailer.
|
|
100
92
|
return this.container.resolve(MAILER);
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
|
-
|
|
95
|
+
/**
|
|
96
|
+
* @deprecated Use `MailServiceProvider` instead.
|
|
97
|
+
*/
|
|
98
|
+
export const CommunicationServiceProvider = MailServiceProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veap/mail",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Mail and communication transport abstraction for Veap",
|
|
5
5
|
"bugs": "https://github.com/veapjs/veap-mail/issues",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test": "vitest run"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@veap/kernel": "0.2.
|
|
31
|
+
"@veap/kernel": "0.2.1",
|
|
32
32
|
"nodemailer": "^10.0.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
package/dist/translator.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation hook for mailables.
|
|
3
|
-
*
|
|
4
|
-
* Lives in the application layer because building localized messages is a
|
|
5
|
-
* use-case concern; the intl implementation (infrastructure) is injected here
|
|
6
|
-
* once by `CommunicationServiceProvider` - so mailables never import intl
|
|
7
|
-
* directly and the package never imports its own entry points (ADR-006).
|
|
8
|
-
*/
|
|
9
|
-
export type Translator = (key: string, params?: Record<string, unknown>) => string;
|
|
10
|
-
/**
|
|
11
|
-
* Helper to interpolate template placeholders like `{name}` or `{code}`.
|
|
12
|
-
*/
|
|
13
|
-
export declare function interpolateMailText(text: string, params?: Record<string, unknown>): string;
|
|
14
|
-
/**
|
|
15
|
-
* Binds the translation factory. Invoked per message build, so the locale
|
|
16
|
-
* always reflects the current request.
|
|
17
|
-
*/
|
|
18
|
-
export declare function setMailTranslatorFactory(factory: () => Promise<Translator>): void;
|
|
19
|
-
/**
|
|
20
|
-
* Returns the bound translator, falling back to simple `{param}`
|
|
21
|
-
* interpolation into the raw key when intl has not bound one yet
|
|
22
|
-
* (unit tests, early boot).
|
|
23
|
-
*/
|
|
24
|
-
export declare function getMailTranslator(): Promise<Translator>;
|
package/dist/translator.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
let translatorFactory = null;
|
|
2
|
-
/**
|
|
3
|
-
* Helper to interpolate template placeholders like `{name}` or `{code}`.
|
|
4
|
-
*/
|
|
5
|
-
export function interpolateMailText(text, params) {
|
|
6
|
-
if (!params)
|
|
7
|
-
return text;
|
|
8
|
-
return Object.entries(params).reduce((acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v)), text);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Binds the translation factory. Invoked per message build, so the locale
|
|
12
|
-
* always reflects the current request.
|
|
13
|
-
*/
|
|
14
|
-
export function setMailTranslatorFactory(factory) {
|
|
15
|
-
translatorFactory = factory;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Returns the bound translator, falling back to simple `{param}`
|
|
19
|
-
* interpolation into the raw key when intl has not bound one yet
|
|
20
|
-
* (unit tests, early boot).
|
|
21
|
-
*/
|
|
22
|
-
export async function getMailTranslator() {
|
|
23
|
-
if (translatorFactory) {
|
|
24
|
-
const fn = await translatorFactory();
|
|
25
|
-
return (key, params) => {
|
|
26
|
-
const translated = fn(key, params);
|
|
27
|
-
return interpolateMailText(translated, params);
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
return (key, params) => interpolateMailText(key, params);
|
|
31
|
-
}
|