@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.
@@ -1,8 +1,8 @@
1
1
  import { ApplicationBuilder } from "@veap/kernel";
2
- import { CommunicationServiceProvider } from "./provider.js";
2
+ import { MailServiceProvider } from "./provider.js";
3
3
  ApplicationBuilder.prototype.withMail = function () {
4
- return this.use(CommunicationServiceProvider);
4
+ return this.use(MailServiceProvider);
5
5
  };
6
6
  ApplicationBuilder.prototype.withCommunication = function () {
7
- return this.use(CommunicationServiceProvider);
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 communication use cases and their consumers
4
- * (facades, application services). The same pattern as
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 (`CommunicationServiceProvider`) selects the mail
6
+ * The composition root (`MailServiceProvider`) selects the mail
8
7
  * transport once at boot and binds it here.
9
8
  */
10
- export interface CommunicationContext {
9
+ export interface MailContext {
11
10
  mailer: IMailer;
12
11
  }
13
12
  /**
14
- * Binds the resolved communication services. Called once by the composition root.
13
+ * @deprecated Use `MailContext` instead.
15
14
  */
16
- export declare function bindCommunicationContext(context: CommunicationContext): void;
15
+ export type CommunicationContext = MailContext;
17
16
  /**
18
- * Returns the bound communication services.
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 communication service provider has not booted yet.
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 function communicationContext(): CommunicationContext;
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 globalForCommunicationContext = globalThis;
2
+ const globalForMailContext = globalThis;
3
3
  /**
4
- * Binds the resolved communication services. Called once by the composition root.
4
+ * Binds the resolved mail services. Called once by the composition root.
5
5
  */
6
- export function bindCommunicationContext(context) {
7
- globalForCommunicationContext.__VEAP_COMMUNICATION_CONTEXT__ = context;
6
+ export function bindMailContext(context) {
7
+ globalForMailContext.__VEAP_MAIL_CONTEXT__ = context;
8
+ globalForMailContext.__VEAP_COMMUNICATION_CONTEXT__ = context;
8
9
  }
9
10
  /**
10
- * Returns the bound communication services.
11
+ * @deprecated Use `bindMailContext` instead.
12
+ */
13
+ export const bindCommunicationContext = bindMailContext;
14
+ /**
15
+ * Returns the bound mail services.
11
16
  *
12
- * @throws If the communication service provider has not booted yet.
17
+ * @throws If the mail service provider has not booted yet.
13
18
  */
14
- export function communicationContext() {
15
- const context = globalForCommunicationContext.__VEAP_COMMUNICATION_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("[Communication] Context is not bound. CommunicationServiceProvider must boot before the communication facades are used.");
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
@@ -8,4 +8,3 @@ export * from "./provider.js";
8
8
  export * from "./providers/console.provider.js";
9
9
  export * from "./providers/nodemailer.provider.js";
10
10
  export { NodemailerMailService as MailService } from "./providers/nodemailer.provider.js";
11
- export * from "./translator.js";
package/dist/index.js CHANGED
@@ -7,4 +7,3 @@ export * from "./provider.js";
7
7
  export * from "./providers/console.provider.js";
8
8
  export * from "./providers/nodemailer.provider.js";
9
9
  export { NodemailerMailService as MailService } from "./providers/nodemailer.provider.js";
10
- export * from "./translator.js";
package/dist/mail.d.ts CHANGED
@@ -1,19 +1,9 @@
1
1
  import type { MailMessage } from "./mail-message.js";
2
2
  /**
3
- * Mailables - the domain vocabulary of email.
3
+ * Sends an email message through the configured transport.
4
4
  *
5
- * Transports deliver; mailables decide *what* is sent. Each helper builds a
6
- * `MailMessage` (localized via the injected translator) and hands it to the
7
- * bound `IMailer` port. This is the Laravel split: `Mailable` defines the
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 { communicationContext } from "./context.js";
2
- import { getMailTranslator } from "./translator.js";
1
+ import { mailContext } from "./context.js";
3
2
  /**
4
- * Mailables - the domain vocabulary of email.
3
+ * Sends an email message through the configured transport.
5
4
  *
6
- * Transports deliver; mailables decide *what* is sent. Each helper builds a
7
- * `MailMessage` (localized via the injected translator) and hands it to the
8
- * bound `IMailer` port. This is the Laravel split: `Mailable` defines the
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 communicationContext().mailer.sendMail(message);
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
  }
@@ -1,9 +1,9 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
2
  /**
3
- * Composition root of the communication context.
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 `CommunicationContext`, so facades and services never
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 CommunicationServiceProvider extends ServiceProvider {
14
+ export declare class MailServiceProvider extends ServiceProvider {
15
15
  register(): void;
16
16
  boot(): Promise<void>;
17
17
  private selectTransport;
18
18
  }
19
- export declare const MailServiceProvider: typeof CommunicationServiceProvider;
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 { bindCommunicationContext } from "./context.js";
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 communication context.
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 `CommunicationContext`, so facades and services never
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 CommunicationServiceProvider extends ServiceProvider {
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
- bindCommunicationContext({ mailer });
75
- logger.debug("veap:communication", `Mail transport bound: ${mailer.constructor.name}`);
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("[Communication] MAIL_TRANSPORT=custom but no provider is registered under the CUSTOM_MAILER token (register an IMailer in a ServiceProvider).");
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:communication", "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).");
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
- export const MailServiceProvider = CommunicationServiceProvider;
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.7",
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.0",
31
+ "@veap/kernel": "0.2.1",
32
32
  "nodemailer": "^10.0.1"
33
33
  },
34
34
  "devDependencies": {
@@ -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>;
@@ -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
- }