@breadstone/archipel-platform-mailing 0.0.32 → 0.0.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breadstone/archipel-platform-mailing",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "description": "Email delivery with strategy-based transport and template engines for NestJS applications.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -99,10 +99,10 @@
99
99
  "README.md"
100
100
  ],
101
101
  "dependencies": {
102
- "@breadstone/archipel-platform-blob-storage": "0.0.32",
103
- "@breadstone/archipel-platform-configuration": "0.0.32",
104
- "@breadstone/archipel-platform-core": "0.0.32",
105
- "@breadstone/archipel-platform-resources": "0.0.32",
102
+ "@breadstone/archipel-platform-blob-storage": "0.0.33",
103
+ "@breadstone/archipel-platform-configuration": "0.0.33",
104
+ "@breadstone/archipel-platform-core": "0.0.33",
105
+ "@breadstone/archipel-platform-resources": "0.0.33",
106
106
  "tslib": "2.8.1"
107
107
  },
108
108
  "peerDependencies": {
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export declare const MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN: unique symbol;
7
7
  export declare const MAIL_DELIVERY_STRATEGY_TOKEN: unique symbol;
8
+ export declare const MAIL_TEMPLATE_REPOSITORY_TOKEN: unique symbol;
8
9
  /**
9
10
  * Union type of all supported template formats.
10
11
  */
package/src/MailTokens.js CHANGED
@@ -7,7 +7,8 @@
7
7
  */
8
8
  // #endregion
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MAIL_DELIVERY_STRATEGY_TOKEN = exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = void 0;
10
+ exports.MAIL_TEMPLATE_REPOSITORY_TOKEN = exports.MAIL_DELIVERY_STRATEGY_TOKEN = exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = void 0;
11
11
  exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = Symbol('MailTemplateFetchStrategy');
12
12
  exports.MAIL_DELIVERY_STRATEGY_TOKEN = Symbol('MailDeliveryStrategy');
13
+ exports.MAIL_TEMPLATE_REPOSITORY_TOKEN = Symbol('MailTemplateRepository');
13
14
  //# sourceMappingURL=MailTokens.js.map
package/src/env.js CHANGED
@@ -65,7 +65,7 @@ exports.MAIL_PORT = (0, archipel_platform_configuration_1.createConfigKey)('MAIL
65
65
  /** All configuration entries required by `platform-mailing`. */
66
66
  exports.PLATFORM_MAILING_CONFIG_ENTRIES = [
67
67
  // Strategy
68
- { key: exports.MAIL_TEMPLATE_STRATEGY, required: true, description: 'Template-fetch strategy (file, blob).' },
68
+ { key: exports.MAIL_TEMPLATE_STRATEGY, required: true, description: 'Template-fetch strategy (file, blob, database).' },
69
69
  {
70
70
  key: exports.MAIL_DELIVERY_STRATEGY,
71
71
  required: true,
@@ -1,5 +1,5 @@
1
- import { OnModuleDestroy } from '@nestjs/common';
2
1
  import { EventHub } from '@breadstone/archipel-platform-core';
2
+ import { OnModuleDestroy } from '@nestjs/common';
3
3
  import { MailService } from '../../services/MailService';
4
4
  /**
5
5
  * Bridges verification email events to the {@link MailService} delivery workflow.
@@ -9,6 +9,7 @@ import { MailService } from '../../services/MailService';
9
9
  export declare class MailEventSubscriber implements OnModuleDestroy {
10
10
  private readonly _mailService;
11
11
  private readonly _subscription;
12
+ private readonly _logger;
12
13
  /**
13
14
  * Initializes a new instance of the {@link MailEventSubscriber} class.
14
15
  *
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  // #region Imports
3
+ var MailEventSubscriber_1;
3
4
  Object.defineProperty(exports, "__esModule", { value: true });
4
5
  exports.MailEventSubscriber = void 0;
5
6
  const tslib_1 = require("tslib");
6
- const common_1 = require("@nestjs/common");
7
7
  const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
8
+ const common_1 = require("@nestjs/common");
8
9
  const MailService_1 = require("../../services/MailService");
9
10
  const MailEventKeys_1 = require("../MailEventKeys");
10
11
  // #endregion
@@ -13,7 +14,7 @@ const MailEventKeys_1 = require("../MailEventKeys");
13
14
  *
14
15
  * @public
15
16
  */
16
- let MailEventSubscriber = class MailEventSubscriber {
17
+ let MailEventSubscriber = MailEventSubscriber_1 = class MailEventSubscriber {
17
18
  // #endregion
18
19
  // #region Ctor
19
20
  /**
@@ -24,11 +25,16 @@ let MailEventSubscriber = class MailEventSubscriber {
24
25
  * @param eventHub The shared event hub that emits verification email requests.
25
26
  */
26
27
  constructor(mailService, eventHub) {
28
+ this._logger = new common_1.Logger(MailEventSubscriber_1.name);
27
29
  this._mailService = mailService;
28
30
  this._subscription = eventHub.observe(MailEventKeys_1.SEND_MAIL_EVENT_KEY).subscribe({
29
31
  next: (payload) => {
32
+ this._logger.debug(`Received mail event for recipient ${(0, archipel_platform_core_1.maskSensitive)(payload.recipientEmail)} with subject "${(0, archipel_platform_core_1.maskSensitive)(payload.subject)}"`);
30
33
  void this.handle(payload);
31
34
  },
35
+ error: (err) => {
36
+ this._logger.error('Error handling mail event', err);
37
+ },
32
38
  });
33
39
  }
34
40
  // #endregion
@@ -60,7 +66,7 @@ let MailEventSubscriber = class MailEventSubscriber {
60
66
  }
61
67
  };
62
68
  exports.MailEventSubscriber = MailEventSubscriber;
63
- exports.MailEventSubscriber = MailEventSubscriber = tslib_1.__decorate([
69
+ exports.MailEventSubscriber = MailEventSubscriber = MailEventSubscriber_1 = tslib_1.__decorate([
64
70
  (0, common_1.Injectable)(),
65
71
  tslib_1.__metadata("design:paramtypes", [MailService_1.MailService, archipel_platform_core_1.EventHub])
66
72
  ], MailEventSubscriber);
package/src/index.d.ts CHANGED
@@ -3,11 +3,14 @@ export * from './errors/MailDeliveryError';
3
3
  export * from './events';
4
4
  export * from './MailTokens';
5
5
  export * from './models/IMailAttachment';
6
+ export * from './models/IMailTemplate';
7
+ export * from './ports/IMailTemplateRepository';
6
8
  export * from './services/MailService';
7
9
  export * from './services/MailVerificationService';
8
10
  export * from './services/SmtpConnectionVerifier';
9
11
  export * from './services/strategies/abstracts/DeliveryStrategyBase';
10
12
  export * from './templating/MailTemplateEngine';
11
13
  export * from './templating/strategies/abstracts/TemplateFetchStrategyBase';
14
+ export * from './templating/strategies/DatabaseTemplateFetchStrategy';
12
15
  export * from './tokens/MailModule';
13
16
  export type { IMailModuleOptions } from './tokens/MailModule';
package/src/index.js CHANGED
@@ -6,11 +6,14 @@ tslib_1.__exportStar(require("./errors/MailDeliveryError"), exports);
6
6
  tslib_1.__exportStar(require("./events"), exports);
7
7
  tslib_1.__exportStar(require("./MailTokens"), exports);
8
8
  tslib_1.__exportStar(require("./models/IMailAttachment"), exports);
9
+ tslib_1.__exportStar(require("./models/IMailTemplate"), exports);
10
+ tslib_1.__exportStar(require("./ports/IMailTemplateRepository"), exports);
9
11
  tslib_1.__exportStar(require("./services/MailService"), exports);
10
12
  tslib_1.__exportStar(require("./services/MailVerificationService"), exports);
11
13
  tslib_1.__exportStar(require("./services/SmtpConnectionVerifier"), exports);
12
14
  tslib_1.__exportStar(require("./services/strategies/abstracts/DeliveryStrategyBase"), exports);
13
15
  tslib_1.__exportStar(require("./templating/MailTemplateEngine"), exports);
14
16
  tslib_1.__exportStar(require("./templating/strategies/abstracts/TemplateFetchStrategyBase"), exports);
17
+ tslib_1.__exportStar(require("./templating/strategies/DatabaseTemplateFetchStrategy"), exports);
15
18
  tslib_1.__exportStar(require("./tokens/MailModule"), exports);
16
19
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Represents a single mail template record stored in a persistent store.
3
+ *
4
+ * @public
5
+ */
6
+ export interface IMailTemplate {
7
+ /**
8
+ * Unique template key used to identify this template (e.g. `AuthRegister`, `AppointmentInvitation`).
9
+ */
10
+ readonly key: string;
11
+ /**
12
+ * Locale identifier (e.g. `de`, `en`). Used for multi-language support.
13
+ * When `undefined`, it is the default/fallback template.
14
+ */
15
+ readonly locale?: string;
16
+ /**
17
+ * Default subject line for this template.
18
+ * Can contain placeholders (e.g. `{{appName}}`).
19
+ */
20
+ readonly subject: string;
21
+ /**
22
+ * HTML body of the template. Contains Handlebars-style placeholders.
23
+ */
24
+ readonly htmlBody?: string;
25
+ /**
26
+ * Plain-text body of the template. Contains Handlebars-style placeholders.
27
+ */
28
+ readonly txtBody?: string;
29
+ /**
30
+ * Human-readable description of this template's purpose.
31
+ */
32
+ readonly description?: string;
33
+ /**
34
+ * List of placeholder names expected by this template (for documentation/validation).
35
+ */
36
+ readonly placeholders?: ReadonlyArray<string>;
37
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=IMailTemplate.js.map
@@ -0,0 +1,39 @@
1
+ import type { IMailTemplate } from '../models/IMailTemplate';
2
+ /**
3
+ * Port interface for mail template persistence.
4
+ *
5
+ * Applications must implement this interface and register the implementation
6
+ * via the `MAIL_TEMPLATE_REPOSITORY_TOKEN` when using the `database` template strategy.
7
+ *
8
+ * @public
9
+ */
10
+ export interface IMailTemplateRepository {
11
+ /**
12
+ * Finds a template by its unique key and optional locale.
13
+ * When no locale is provided, returns the default (locale-agnostic) template.
14
+ *
15
+ * @param key - The template key (e.g. `AuthRegister`).
16
+ * @param locale - Optional locale identifier (e.g. `de`, `en`).
17
+ * @returns The matching template, or `undefined` if not found.
18
+ */
19
+ findByKey(key: string, locale?: string): Promise<IMailTemplate | undefined>;
20
+ /**
21
+ * Returns all templates available in the store.
22
+ */
23
+ findAll(): Promise<Array<IMailTemplate>>;
24
+ /**
25
+ * Creates or updates a template. Upsert semantics: if a template with the
26
+ * same `key` and `locale` exists, it is overwritten.
27
+ *
28
+ * @param template - The template to persist.
29
+ * @returns The persisted template.
30
+ */
31
+ upsert(template: IMailTemplate): Promise<IMailTemplate>;
32
+ /**
33
+ * Deletes a template by key and locale.
34
+ *
35
+ * @param key - The template key.
36
+ * @param locale - Optional locale. When omitted, deletes the default template.
37
+ */
38
+ delete(key: string, locale?: string): Promise<void>;
39
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=IMailTemplateRepository.js.map
@@ -0,0 +1,26 @@
1
+ import type { IMailTemplateRepository } from '../../ports/IMailTemplateRepository';
2
+ import { TemplateFetchStrategyBase } from './abstracts/TemplateFetchStrategyBase';
3
+ import type { IMailTemplateVariants } from '../../MailTokens';
4
+ /**
5
+ * Template fetch strategy that loads templates from a database via the
6
+ * {@link IMailTemplateRepository} port.
7
+ *
8
+ * Applications provide the concrete repository implementation at module
9
+ * registration time. This strategy loads all templates on module init
10
+ * and caches them in-memory.
11
+ *
12
+ * @public
13
+ */
14
+ export declare class DatabaseTemplateFetchStrategy extends TemplateFetchStrategyBase {
15
+ private readonly _repository;
16
+ private readonly _logger;
17
+ constructor(repository: IMailTemplateRepository);
18
+ /**
19
+ * Loads all templates from the database and returns them grouped by key.
20
+ * Only the default locale (no locale or first match) is loaded into the
21
+ * in-memory cache used by `MailTemplateEngine`.
22
+ *
23
+ * @public
24
+ */
25
+ load(): Promise<Record<string, IMailTemplateVariants>>;
26
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // #region Imports
3
+ var DatabaseTemplateFetchStrategy_1;
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.DatabaseTemplateFetchStrategy = void 0;
6
+ const tslib_1 = require("tslib");
7
+ const common_1 = require("@nestjs/common");
8
+ const MailTokens_1 = require("../../MailTokens");
9
+ const TemplateFetchStrategyBase_1 = require("./abstracts/TemplateFetchStrategyBase");
10
+ // #endregion
11
+ /**
12
+ * Template fetch strategy that loads templates from a database via the
13
+ * {@link IMailTemplateRepository} port.
14
+ *
15
+ * Applications provide the concrete repository implementation at module
16
+ * registration time. This strategy loads all templates on module init
17
+ * and caches them in-memory.
18
+ *
19
+ * @public
20
+ */
21
+ let DatabaseTemplateFetchStrategy = DatabaseTemplateFetchStrategy_1 = class DatabaseTemplateFetchStrategy extends TemplateFetchStrategyBase_1.TemplateFetchStrategyBase {
22
+ // #endregion
23
+ // #region Ctor
24
+ constructor(repository) {
25
+ super();
26
+ this._logger = new common_1.Logger(DatabaseTemplateFetchStrategy_1.name);
27
+ this._repository = repository;
28
+ }
29
+ // #endregion
30
+ // #region Methods
31
+ /**
32
+ * Loads all templates from the database and returns them grouped by key.
33
+ * Only the default locale (no locale or first match) is loaded into the
34
+ * in-memory cache used by `MailTemplateEngine`.
35
+ *
36
+ * @public
37
+ */
38
+ async load() {
39
+ const templates = {};
40
+ const allTemplates = await this._repository.findAll();
41
+ for (const template of allTemplates) {
42
+ const key = template.key;
43
+ if (!templates[key]) {
44
+ templates[key] = {};
45
+ }
46
+ // Only set if not already filled (first match wins — default locale preferred)
47
+ if (template.htmlBody && !templates[key].html) {
48
+ templates[key].html = template.htmlBody;
49
+ }
50
+ if (template.txtBody && !templates[key].txt) {
51
+ templates[key].txt = template.txtBody;
52
+ }
53
+ }
54
+ const templateCount = Object.keys(templates).length;
55
+ this._logger.log(`${templateCount} template(s) loaded from database`);
56
+ return templates;
57
+ }
58
+ };
59
+ exports.DatabaseTemplateFetchStrategy = DatabaseTemplateFetchStrategy;
60
+ exports.DatabaseTemplateFetchStrategy = DatabaseTemplateFetchStrategy = DatabaseTemplateFetchStrategy_1 = tslib_1.__decorate([
61
+ (0, common_1.Injectable)(),
62
+ tslib_1.__param(0, (0, common_1.Inject)(MailTokens_1.MAIL_TEMPLATE_REPOSITORY_TOKEN)),
63
+ tslib_1.__metadata("design:paramtypes", [Object])
64
+ ], DatabaseTemplateFetchStrategy);
65
+ //# sourceMappingURL=DatabaseTemplateFetchStrategy.js.map
@@ -1,5 +1,6 @@
1
1
  import type { IConfigRegistryEntry } from '@breadstone/archipel-platform-configuration';
2
- import { type DynamicModule } from '@nestjs/common';
2
+ import { type DynamicModule, type Provider } from '@nestjs/common';
3
+ import type { IMailTemplateRepository } from '../ports/IMailTemplateRepository';
3
4
  /**
4
5
  * Options for configuring the {@link MailModule}.
5
6
  *
@@ -16,6 +17,21 @@ export interface IMailModuleOptions {
16
17
  * Merged with the platform default entries.
17
18
  */
18
19
  readonly configEntries?: ReadonlyArray<Omit<IConfigRegistryEntry, 'module'>>;
20
+ /**
21
+ * Provider for the `IMailTemplateRepository` port.
22
+ * Required when `MAIL_TEMPLATE_STRATEGY` is set to `'database'`.
23
+ *
24
+ * Example:
25
+ * ```typescript
26
+ * MailModule.register({
27
+ * templateRepositoryProvider: {
28
+ * provide: MAIL_TEMPLATE_REPOSITORY_TOKEN,
29
+ * useClass: PrismaMailTemplateRepository,
30
+ * }
31
+ * })
32
+ * ```
33
+ */
34
+ readonly templateRepositoryProvider?: Provider<IMailTemplateRepository>;
19
35
  }
20
36
  /**
21
37
  * Represents the mail module.
@@ -36,14 +36,19 @@ let MailModule = MailModule_1 = class MailModule {
36
36
  static register(options) {
37
37
  const isGlobal = options?.isGlobal ?? true;
38
38
  const configEntries = options?.configEntries ?? env_1.PLATFORM_MAILING_CONFIG_ENTRIES;
39
+ const additionalProviders = [];
40
+ if (options?.templateRepositoryProvider) {
41
+ additionalProviders.push(options.templateRepositoryProvider);
42
+ }
39
43
  return {
40
44
  module: MailModule_1,
41
45
  global: isGlobal,
42
46
  imports: [archipel_platform_configuration_1.ConfigModule.register('platform-mailing', configEntries), archipel_platform_blob_storage_1.BlobModule, archipel_platform_core_1.EventModule],
43
47
  providers: [
48
+ ...additionalProviders,
44
49
  {
45
50
  provide: MailTokens_1.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN,
46
- useFactory: async (configService, blobService, resourceManager) => {
51
+ useFactory: async (configService, blobService, resourceManager, ...extra) => {
47
52
  const templateStrategy = configService.get(env_1.MAIL_TEMPLATE_STRATEGY.key);
48
53
  if (templateStrategy === 'file') {
49
54
  const { FileTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('../templating/strategies/FileTemplateFetchStrategy')));
@@ -53,9 +58,22 @@ let MailModule = MailModule_1 = class MailModule {
53
58
  const { BlobTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('../templating/strategies/BlobTemplateFetchStrategy')));
54
59
  return new BlobTemplateFetchStrategy(blobService);
55
60
  }
61
+ if (templateStrategy === 'database') {
62
+ const repository = extra[0];
63
+ if (!repository) {
64
+ throw new Error("Template strategy 'database' requires a templateRepositoryProvider in MailModule.register() options.");
65
+ }
66
+ const { DatabaseTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('../templating/strategies/DatabaseTemplateFetchStrategy')));
67
+ return new DatabaseTemplateFetchStrategy(repository);
68
+ }
56
69
  throw new Error(`Unknown template strategy: ${templateStrategy}`);
57
70
  },
58
- inject: [archipel_platform_configuration_1.ConfigService, archipel_platform_blob_storage_1.BlobService, archipel_platform_resources_1.ResourceManager],
71
+ inject: [
72
+ archipel_platform_configuration_1.ConfigService,
73
+ archipel_platform_blob_storage_1.BlobService,
74
+ archipel_platform_resources_1.ResourceManager,
75
+ ...(options?.templateRepositoryProvider ? [MailTokens_1.MAIL_TEMPLATE_REPOSITORY_TOKEN] : []),
76
+ ],
59
77
  },
60
78
  {
61
79
  provide: MailTokens_1.MAIL_DELIVERY_STRATEGY_TOKEN,