@zyther/tzmail 0.1.4 → 0.5.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.
Files changed (132) hide show
  1. package/README.md +411 -78
  2. package/dist/core/enums/theme.enum.d.ts +11 -1
  3. package/dist/core/enums/theme.enum.d.ts.map +1 -1
  4. package/dist/core/enums/theme.enum.js +10 -0
  5. package/dist/core/interfaces/email.interface.d.ts +95 -5
  6. package/dist/core/interfaces/email.interface.d.ts.map +1 -1
  7. package/dist/core/interfaces/template.interface.d.ts +12 -1
  8. package/dist/core/interfaces/template.interface.d.ts.map +1 -1
  9. package/dist/factories/email-factory.d.ts +32 -6
  10. package/dist/factories/email-factory.d.ts.map +1 -1
  11. package/dist/factories/email-factory.js +110 -6
  12. package/dist/factories/template-factory.d.ts.map +1 -1
  13. package/dist/factories/template-factory.js +170 -60
  14. package/dist/index.d.ts +16 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +23 -1
  17. package/dist/providers/mailgun.transport.d.ts +10 -0
  18. package/dist/providers/mailgun.transport.d.ts.map +1 -0
  19. package/dist/providers/mailgun.transport.js +85 -0
  20. package/dist/providers/nodemailer.transport.d.ts +11 -0
  21. package/dist/providers/nodemailer.transport.d.ts.map +1 -0
  22. package/dist/providers/nodemailer.transport.js +23 -0
  23. package/dist/providers/provider-utils.d.ts +17 -0
  24. package/dist/providers/provider-utils.d.ts.map +1 -0
  25. package/dist/providers/provider-utils.js +89 -0
  26. package/dist/providers/provider.interface.d.ts +30 -0
  27. package/dist/providers/provider.interface.d.ts.map +1 -0
  28. package/dist/providers/provider.interface.js +2 -0
  29. package/dist/providers/sendgrid.transport.d.ts +10 -0
  30. package/dist/providers/sendgrid.transport.d.ts.map +1 -0
  31. package/dist/providers/sendgrid.transport.js +76 -0
  32. package/dist/queues/bullmq-email-queue.d.ts +153 -0
  33. package/dist/queues/bullmq-email-queue.d.ts.map +1 -0
  34. package/dist/queues/bullmq-email-queue.js +292 -0
  35. package/dist/services/attachment.service.d.ts +15 -37
  36. package/dist/services/attachment.service.d.ts.map +1 -1
  37. package/dist/services/attachment.service.js +58 -46
  38. package/dist/services/email.service.d.ts +22 -22
  39. package/dist/services/email.service.d.ts.map +1 -1
  40. package/dist/services/email.service.js +117 -33
  41. package/dist/services/template.service.d.ts +24 -111
  42. package/dist/services/template.service.d.ts.map +1 -1
  43. package/dist/services/template.service.js +273 -362
  44. package/dist/templates/base/color-accessibility.d.ts +4 -0
  45. package/dist/templates/base/color-accessibility.d.ts.map +1 -0
  46. package/dist/templates/base/color-accessibility.js +38 -0
  47. package/dist/templates/base/template-builder.d.ts +4 -1
  48. package/dist/templates/base/template-builder.d.ts.map +1 -1
  49. package/dist/templates/base/template-builder.js +276 -527
  50. package/dist/templates/themes/aurora.theme.d.ts +12 -0
  51. package/dist/templates/themes/aurora.theme.d.ts.map +1 -0
  52. package/dist/templates/themes/aurora.theme.js +88 -0
  53. package/dist/templates/themes/corporate.theme.d.ts +2 -1
  54. package/dist/templates/themes/corporate.theme.d.ts.map +1 -1
  55. package/dist/templates/themes/corporate.theme.js +139 -102
  56. package/dist/templates/themes/editorial.theme.d.ts +12 -0
  57. package/dist/templates/themes/editorial.theme.d.ts.map +1 -0
  58. package/dist/templates/themes/editorial.theme.js +90 -0
  59. package/dist/templates/themes/forest.theme.d.ts +12 -0
  60. package/dist/templates/themes/forest.theme.d.ts.map +1 -0
  61. package/dist/templates/themes/forest.theme.js +89 -0
  62. package/dist/templates/themes/glass.theme.d.ts +12 -0
  63. package/dist/templates/themes/glass.theme.d.ts.map +1 -0
  64. package/dist/templates/themes/glass.theme.js +88 -0
  65. package/dist/templates/themes/lavender.theme.d.ts +12 -0
  66. package/dist/templates/themes/lavender.theme.d.ts.map +1 -0
  67. package/dist/templates/themes/lavender.theme.js +91 -0
  68. package/dist/templates/themes/midnight.theme.d.ts +12 -0
  69. package/dist/templates/themes/midnight.theme.d.ts.map +1 -0
  70. package/dist/templates/themes/midnight.theme.js +91 -0
  71. package/dist/templates/themes/minimal.theme.d.ts +2 -1
  72. package/dist/templates/themes/minimal.theme.d.ts.map +1 -1
  73. package/dist/templates/themes/minimal.theme.js +142 -107
  74. package/dist/templates/themes/modern.theme.d.ts +2 -1
  75. package/dist/templates/themes/modern.theme.d.ts.map +1 -1
  76. package/dist/templates/themes/modern.theme.js +109 -74
  77. package/dist/templates/themes/monokai.theme.d.ts +2 -1
  78. package/dist/templates/themes/monokai.theme.d.ts.map +1 -1
  79. package/dist/templates/themes/monokai.theme.js +102 -66
  80. package/dist/templates/themes/neobrutal.theme.d.ts +12 -0
  81. package/dist/templates/themes/neobrutal.theme.d.ts.map +1 -0
  82. package/dist/templates/themes/neobrutal.theme.js +91 -0
  83. package/dist/templates/themes/ocean.theme.d.ts +12 -0
  84. package/dist/templates/themes/ocean.theme.d.ts.map +1 -0
  85. package/dist/templates/themes/ocean.theme.js +90 -0
  86. package/dist/templates/themes/pastel.theme.d.ts +12 -0
  87. package/dist/templates/themes/pastel.theme.d.ts.map +1 -0
  88. package/dist/templates/themes/pastel.theme.js +89 -0
  89. package/dist/templates/themes/sunset.theme.d.ts +12 -0
  90. package/dist/templates/themes/sunset.theme.d.ts.map +1 -0
  91. package/dist/templates/themes/sunset.theme.js +90 -0
  92. package/dist/templates/themes/system.theme.d.ts +2 -1
  93. package/dist/templates/themes/system.theme.d.ts.map +1 -1
  94. package/dist/templates/themes/system.theme.js +81 -47
  95. package/dist/templates/themes/theme.interface.d.ts +48 -0
  96. package/dist/templates/themes/theme.interface.d.ts.map +1 -1
  97. package/package.json +36 -14
  98. package/src/core/enums/template-part.enum.ts +7 -0
  99. package/src/core/enums/theme.enum.ts +18 -0
  100. package/src/core/interfaces/email.interface.ts +116 -0
  101. package/src/core/interfaces/template.interface.ts +71 -0
  102. package/src/factories/email-factory.ts +208 -0
  103. package/src/factories/template-factory.ts +201 -0
  104. package/src/index.ts +53 -0
  105. package/src/providers/mailgun.transport.ts +80 -0
  106. package/src/providers/nodemailer.transport.ts +20 -0
  107. package/src/providers/provider-utils.ts +91 -0
  108. package/src/providers/provider.interface.ts +26 -0
  109. package/src/providers/sendgrid.transport.ts +80 -0
  110. package/src/queues/bullmq-email-queue.ts +418 -0
  111. package/src/services/attachment.service.ts +98 -0
  112. package/src/services/email.service.ts +151 -0
  113. package/src/services/template.service.ts +431 -0
  114. package/src/templates/base/color-accessibility.ts +31 -0
  115. package/src/templates/base/template-builder.ts +438 -0
  116. package/src/templates/themes/aurora.theme.ts +91 -0
  117. package/src/templates/themes/corporate.theme.ts +156 -0
  118. package/src/templates/themes/editorial.theme.ts +93 -0
  119. package/src/templates/themes/forest.theme.ts +92 -0
  120. package/src/templates/themes/glass.theme.ts +91 -0
  121. package/src/templates/themes/lavender.theme.ts +94 -0
  122. package/src/templates/themes/midnight.theme.ts +94 -0
  123. package/src/templates/themes/minimal.theme.ts +158 -0
  124. package/src/templates/themes/modern.theme.ts +124 -0
  125. package/src/templates/themes/monokai.theme.ts +117 -0
  126. package/src/templates/themes/neobrutal.theme.ts +94 -0
  127. package/src/templates/themes/ocean.theme.ts +93 -0
  128. package/src/templates/themes/pastel.theme.ts +92 -0
  129. package/src/templates/themes/sunset.theme.ts +93 -0
  130. package/src/templates/themes/system.theme.ts +92 -0
  131. package/src/templates/themes/theme.interface.ts +95 -0
  132. package/src/tests/template.service.test.ts +1 -0
@@ -37,67 +37,79 @@ exports.AttachmentService = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  /**
40
- * AttachmentService - Gerencia anexos para emails
41
- *
42
- * Métodos:
43
- * - addFromPath(filePath: string, options?: Partial<IAttachment>): Promise<IAttachment>
44
- * Adiciona um anexo a partir de um caminho de arquivo.
45
- *
46
- * - addFromBuffer(buffer: Buffer, filename: string, options?: Partial<IAttachment>): Promise<IAttachment>
47
- * Adiciona um anexo a partir de um buffer em memória.
48
- *
49
- * - addFromUrl(url: string, filename: string): Promise<IAttachment>
50
- * Adiciona um anexo a partir de uma URL (ainda não implementado).
51
- *
52
- * Exemplo de uso:
53
- *
54
- * const attachmentService = new AttachmentService();
55
- * const attachment = await attachmentService.addFromPath('path/to/file.pdf');
56
- *
57
- * emailFactory.sendEmail({
58
- * to: 'user@example.com',
59
- * subject: 'Assunto do Email',
60
- * template: myTemplate,
61
- * attachments: [{...attachment}]
62
- * });
40
+ * Creates Nodemailer attachment descriptors. Path attachments remain paths so
41
+ * Nodemailer can stream large files instead of loading them into memory.
42
+ * If allowedRoot is omitted, callers are responsible for supplying trusted paths.
63
43
  */
64
44
  class AttachmentService {
65
- /**
66
- * addFromPath - Adiciona um anexo a partir de um caminho de arquivo
67
- * @param filePath - Caminho do arquivo a ser anexado
68
- * @param options - Opções adicionais para o anexo (filename, contentType, cid)
69
- * @returns Promise<IAttachment> - Objeto de anexo formatado para Nodemailer
70
- * @throws Error se o caminho não for um arquivo válido
71
- */
45
+ options;
46
+ maxSizeBytes;
47
+ constructor(options = {}) {
48
+ this.options = options;
49
+ if (options.maxSizeBytes !== undefined &&
50
+ (!Number.isFinite(options.maxSizeBytes) || options.maxSizeBytes < 0)) {
51
+ throw new Error('maxSizeBytes must be a finite non-negative number');
52
+ }
53
+ this.maxSizeBytes = options.maxSizeBytes;
54
+ }
55
+ /** Add an attachment as a file path, preserving streaming behavior. */
72
56
  async addFromPath(filePath, options) {
73
- const stats = await fs.promises.stat(filePath);
57
+ if (typeof filePath !== 'string' || filePath.trim() === '') {
58
+ throw new Error('filePath must be a non-empty string');
59
+ }
60
+ let attachmentPath = filePath;
61
+ let stats;
62
+ if (this.options.allowedRoot !== undefined) {
63
+ const [rootRealPath, fileRealPath] = await Promise.all([
64
+ fs.promises.realpath(this.options.allowedRoot),
65
+ fs.promises.realpath(filePath)
66
+ ]);
67
+ const relativePath = path.relative(rootRealPath, fileRealPath);
68
+ if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
69
+ throw new Error('Attachment path is outside allowedRoot');
70
+ }
71
+ attachmentPath = fileRealPath;
72
+ stats = await fs.promises.stat(fileRealPath);
73
+ }
74
+ else {
75
+ stats = await fs.promises.stat(filePath);
76
+ }
74
77
  if (!stats.isFile()) {
75
- throw new Error(`Path is not a file: ${filePath}`);
78
+ throw new Error('Attachment path is not a file');
76
79
  }
80
+ this.assertSize(stats.size);
77
81
  return {
78
- filename: (options === null || options === void 0 ? void 0 : options.filename) || path.basename(filePath),
79
- path: filePath,
80
- contentType: options === null || options === void 0 ? void 0 : options.contentType,
81
- cid: options === null || options === void 0 ? void 0 : options.cid
82
+ filename: options?.filename || path.basename(filePath),
83
+ path: attachmentPath,
84
+ contentType: options?.contentType,
85
+ cid: options?.cid
82
86
  };
83
87
  }
84
88
  async addFromBuffer(buffer, filename, options) {
89
+ if (!Buffer.isBuffer(buffer)) {
90
+ throw new Error('buffer must be a Buffer');
91
+ }
92
+ if (typeof filename !== 'string' || filename.trim() === '') {
93
+ throw new Error('filename must be a non-empty string');
94
+ }
95
+ this.assertSize(buffer.length);
85
96
  return {
86
97
  filename,
87
98
  content: buffer,
88
- contentType: options === null || options === void 0 ? void 0 : options.contentType,
89
- cid: options === null || options === void 0 ? void 0 : options.cid
99
+ contentType: options?.contentType,
100
+ cid: options?.cid
90
101
  };
91
102
  }
92
- /**
93
- * addFromUrl - Adiciona um anexo a partir de uma URL (a ser implementado)
94
- * @param url - URL do recurso a ser anexado
95
- * @param filename - Nome do arquivo para o anexo
96
- * @returns Promise<IAttachment> - Objeto de anexo formatado para Nodemailer
97
- * @throws Error se o método não for implementado
98
- */
103
+ /** URL attachments are deliberately not fetched by this service. */
99
104
  addFromUrl(url, filename) {
100
- throw new Error('Method not implemented yet');
105
+ void url;
106
+ void filename;
107
+ throw new Error('addFromUrl is not supported; fetch and validate content in the application before adding an attachment');
108
+ }
109
+ assertSize(size) {
110
+ if (this.maxSizeBytes !== undefined && size > this.maxSizeBytes) {
111
+ throw new Error(`Attachment exceeds configured maxSizeBytes (${this.maxSizeBytes})`);
112
+ }
101
113
  }
102
114
  }
103
115
  exports.AttachmentService = AttachmentService;
@@ -1,31 +1,31 @@
1
- import { Transporter } from 'nodemailer';
2
- import { IEmailOptions } from '../core/interfaces/email.interface';
1
+ import { IEmailOptions, IEmailSendResult } from '../core/interfaces/email.interface';
2
+ import { IEmailTransport } from '../providers/provider.interface';
3
3
  /**
4
- * EmailService - Gerencia o envio de emails usando Nodemailer
5
- *
6
- * Métodos:
7
- * - send(options: IEmailOptions): Promise<any>
8
- * Envia um email com as opções especificadas.
9
- *
10
- * Exemplo de uso:
11
- *
12
- * const emailService = new EmailService(transporter, 'from@example.com');
13
- * const result = await emailService.send({
14
- * to: 'to@example.com',
15
- * subject: 'Assunto do Email',
16
- * template: myTemplate
17
- * });
4
+ * EmailService sends messages through a provider-neutral transport.
5
+ * `send` always reports validation, render and transport failures in a typed
6
+ * result; callers should inspect `status` and `rejected` rather than treating
7
+ * any resolved promise as proof that every recipient received the message.
18
8
  */
19
9
  export declare class EmailService {
20
10
  private transporter;
21
11
  private defaultFrom?;
22
- constructor(transporter: Transporter, defaultFrom?: string | undefined);
12
+ private errorStage;
13
+ constructor(transporter: IEmailTransport, defaultFrom?: string | undefined, errorStage?: 'smtp' | 'provider');
23
14
  /**
24
- * send - Envia um email com as opções especificadas
25
- * @param options - Opções para o email (to, cc, bcc, subject, text, html, template, attachments)
26
- * @returns Promise<any> - Resultado do envio do email
27
- * @throws Error se o envio falhar
15
+ * Validates and normalizes basic message fields, then sends the message.
16
+ * Header fields must be non-empty and contain no control characters; deeper
17
+ * address syntax and deliverability policy are delegated to the selected transport/provider.
28
18
  */
29
- send(options: IEmailOptions): Promise<any>;
19
+ send(options: IEmailOptions): Promise<IEmailSendResult>;
20
+ /** Verify SMTP configuration/connectivity without delivering a message. */
21
+ verify(): Promise<true>;
22
+ /** Close pooled connections during graceful application shutdown. */
23
+ close(): void;
24
+ private normalizeRecipients;
25
+ private normalizeOptionalRecipients;
26
+ private normalizeRequiredText;
27
+ private normalizeOptionalText;
28
+ private normalizeBody;
29
+ private recipientList;
30
30
  }
31
31
  //# sourceMappingURL=email.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"email.service.d.ts","sourceRoot":"","sources":["../../src/services/email.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAEnE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW,CAAC;gBADZ,WAAW,EAAE,WAAW,EACxB,WAAW,CAAC,EAAE,MAAM,YAAA;IAG9B;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC;CA0BjD"}
1
+ {"version":3,"file":"email.service.d.ts","sourceRoot":"","sources":["../../src/services/email.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,eAAe,EAAqB,MAAM,iCAAiC,CAAC;AAErF;;;;;GAKG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW,CAAC;IACpB,OAAO,CAAC,UAAU;gBAFV,WAAW,EAAE,eAAe,EAC5B,WAAW,CAAC,EAAE,MAAM,YAAA,EACpB,UAAU,GAAE,MAAM,GAAG,UAAmB;IAGlD;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkE7D,2EAA2E;IACrE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B,qEAAqE;IACrE,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,2BAA2B;IAKnC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;CAGtB"}
@@ -2,57 +2,141 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EmailService = void 0;
4
4
  /**
5
- * EmailService - Gerencia o envio de emails usando Nodemailer
6
- *
7
- * Métodos:
8
- * - send(options: IEmailOptions): Promise<any>
9
- * Envia um email com as opções especificadas.
10
- *
11
- * Exemplo de uso:
12
- *
13
- * const emailService = new EmailService(transporter, 'from@example.com');
14
- * const result = await emailService.send({
15
- * to: 'to@example.com',
16
- * subject: 'Assunto do Email',
17
- * template: myTemplate
18
- * });
5
+ * EmailService sends messages through a provider-neutral transport.
6
+ * `send` always reports validation, render and transport failures in a typed
7
+ * result; callers should inspect `status` and `rejected` rather than treating
8
+ * any resolved promise as proof that every recipient received the message.
19
9
  */
20
10
  class EmailService {
21
- constructor(transporter, defaultFrom) {
11
+ transporter;
12
+ defaultFrom;
13
+ errorStage;
14
+ constructor(transporter, defaultFrom, errorStage = 'smtp') {
22
15
  this.transporter = transporter;
23
16
  this.defaultFrom = defaultFrom;
17
+ this.errorStage = errorStage;
24
18
  }
25
19
  /**
26
- * send - Envia um email com as opções especificadas
27
- * @param options - Opções para o email (to, cc, bcc, subject, text, html, template, attachments)
28
- * @returns Promise<any> - Resultado do envio do email
29
- * @throws Error se o envio falhar
20
+ * Validates and normalizes basic message fields, then sends the message.
21
+ * Header fields must be non-empty and contain no control characters; deeper
22
+ * address syntax and deliverability policy are delegated to the selected transport/provider.
30
23
  */
31
24
  async send(options) {
32
- const mailOptions = {
33
- from: options.from || this.defaultFrom,
34
- to: Array.isArray(options.to) ? options.to.join(',') : options.to,
35
- cc: options.cc,
36
- bcc: options.bcc,
37
- subject: options.subject,
38
- text: options.text,
39
- html: options.html || (options.template ? await options.template.render(options) : undefined),
40
- attachments: options.attachments
41
- };
25
+ let mailOptions;
26
+ try {
27
+ mailOptions = {
28
+ from: this.normalizeOptionalText(options.from ?? this.defaultFrom, 'from'),
29
+ to: this.normalizeRecipients(options.to, 'to'),
30
+ cc: this.normalizeOptionalRecipients(options.cc, 'cc'),
31
+ bcc: this.normalizeOptionalRecipients(options.bcc, 'bcc'),
32
+ subject: this.normalizeRequiredText(options.subject, 'subject'),
33
+ text: this.normalizeBody(options.text, 'text'),
34
+ html: this.normalizeBody(options.html, 'html'),
35
+ attachments: options.attachments
36
+ };
37
+ }
38
+ catch (error) {
39
+ return { success: false, status: 'failure', error, errorStage: 'validation' };
40
+ }
41
+ if (mailOptions.html === undefined && options.template) {
42
+ try {
43
+ mailOptions.html = await options.template.render(options);
44
+ }
45
+ catch (error) {
46
+ return { success: false, status: 'failure', error, errorStage: 'render' };
47
+ }
48
+ }
42
49
  try {
43
50
  const info = await this.transporter.sendMail(mailOptions);
51
+ const accepted = this.recipientList(info?.accepted);
52
+ const rejected = this.recipientList(info?.rejected);
53
+ const status = rejected.length > 0
54
+ ? (accepted.length > 0 ? 'partial' : 'failure')
55
+ : 'success';
44
56
  return {
45
- success: true,
46
- messageId: info.messageId,
47
- response: info.response
57
+ success: status === 'success',
58
+ status,
59
+ messageId: info?.messageId,
60
+ response: info?.response,
61
+ ...(info?.accepted !== undefined ? { accepted } : {}),
62
+ ...(info?.rejected !== undefined ? { rejected } : {})
48
63
  };
49
64
  }
50
65
  catch (error) {
66
+ const details = error;
67
+ const accepted = this.recipientList(details?.accepted);
68
+ const rejected = this.recipientList(details?.rejected);
69
+ const partial = accepted.length > 0 && rejected.length > 0;
51
70
  return {
52
71
  success: false,
53
- error: error
72
+ status: partial ? 'partial' : 'failure',
73
+ error,
74
+ errorStage: this.errorStage,
75
+ ...(details?.messageId !== undefined ? { messageId: details.messageId } : {}),
76
+ ...(details?.response !== undefined ? { response: details.response } : {}),
77
+ ...(details?.accepted !== undefined ? { accepted } : {}),
78
+ ...(details?.rejected !== undefined ? { rejected } : {})
54
79
  };
55
80
  }
56
81
  }
82
+ /** Verify SMTP configuration/connectivity without delivering a message. */
83
+ async verify() {
84
+ if (!this.transporter.verify) {
85
+ throw new Error('This API transport does not expose a non-delivery verification operation.');
86
+ }
87
+ return this.transporter.verify();
88
+ }
89
+ /** Close pooled connections during graceful application shutdown. */
90
+ close() {
91
+ this.transporter.close?.();
92
+ }
93
+ normalizeRecipients(value, field) {
94
+ if (Array.isArray(value)) {
95
+ if (value.length === 0) {
96
+ throw new Error(`${field} must contain at least one recipient`);
97
+ }
98
+ const recipients = value.map((entry) => this.normalizeRequiredText(entry, field));
99
+ return recipients.join(', ');
100
+ }
101
+ return this.normalizeRequiredText(value, field);
102
+ }
103
+ normalizeOptionalRecipients(value, field) {
104
+ if (value === undefined)
105
+ return undefined;
106
+ return this.normalizeRecipients(value, field);
107
+ }
108
+ normalizeRequiredText(value, field) {
109
+ const normalized = this.normalizeOptionalText(value, field);
110
+ if (normalized === undefined) {
111
+ throw new Error(`${field} is required`);
112
+ }
113
+ return normalized;
114
+ }
115
+ normalizeOptionalText(value, field) {
116
+ if (value === undefined)
117
+ return undefined;
118
+ if (typeof value !== 'string') {
119
+ throw new Error(`${field} must be a string`);
120
+ }
121
+ const normalized = value.trim();
122
+ if (!normalized) {
123
+ throw new Error(`${field} must not be empty`);
124
+ }
125
+ if (/[\u0000-\u001f\u007f]/.test(normalized)) {
126
+ throw new Error(`${field} must not contain header control characters`);
127
+ }
128
+ return normalized;
129
+ }
130
+ normalizeBody(value, field) {
131
+ if (value === undefined)
132
+ return undefined;
133
+ if (typeof value !== 'string') {
134
+ throw new Error(`${field} must be a string`);
135
+ }
136
+ return value;
137
+ }
138
+ recipientList(value) {
139
+ return Array.isArray(value) ? value : [];
140
+ }
57
141
  }
58
142
  exports.EmailService = EmailService;
@@ -6,81 +6,41 @@ interface TemplateCache {
6
6
  expiresAt: Date;
7
7
  hits: number;
8
8
  }
9
- interface TemplateOptions {
9
+ export interface ITemplateServiceOptions {
10
+ /** Cache ligado por padrão; pode ser desligado por chamada. */
10
11
  cache?: boolean;
12
+ /** TTL em segundos. Zero desabilita o cache para aquela criação; valores negativos/não finitos são inválidos. */
11
13
  cacheTTL?: number;
12
14
  validateConfig?: boolean;
15
+ /** Mantido por compatibilidade. A transformação destrutiva foi desativada. */
13
16
  minify?: boolean;
14
17
  preview?: boolean;
18
+ /** Limite de templates em cache; padrão finito de 100. */
19
+ maxCacheEntries?: number;
20
+ /** Limite de IDs de template guardados no histórico; padrão finito de 100. */
21
+ maxHistoryEntries?: number;
22
+ /** Máximo de versões por ID de template; padrão 10. */
23
+ maxVersionsPerTemplate?: number;
15
24
  }
25
+ type TemplateOptions = ITemplateServiceOptions;
16
26
  /**
17
- * TemplateService - Gerencia a criação, renderização e gerenciamento de templates de email
18
- *
19
- * Métodos:
20
- * - createTemplate(themeType, variant, config, options): ITemplate
21
- * Cria um novo template com as configurações especificadas.
22
- *
23
- * - cloneTemplate(template, modifications): ITemplate
24
- * Cria um template a partir de um template existente (clone).
25
- *
26
- * - renderTemplate(template, data, options): Promise<string>
27
- * Renderiza um template com os dados fornecidos.
28
- *
29
- * - previewTemplate(themeType, variant, config, data): Promise<string>
30
- * Pré-visualiza um template sem enviar email.
31
- *
32
- * - getThemeInfo(themeType): object
33
- * Obtém informações sobre um tema específico.
34
- *
35
- * - listThemes(): ThemeType[]
36
- * Lista todos os temas disponíveis.
37
- *
38
- * - getTemplateStats(): object
39
- * Obtém estatísticas de uso dos templates.
40
- *
41
- * - clearCache(themeType?): number
42
- * Limpa o cache de templates, opcionalmente por tema.
43
- *
44
- * - getTemplateHistory(templateId): ITemplate[]
45
- * Obtém histórico de versões de um template.
46
- *
47
- * - restoreTemplateVersion(templateId, versionIndex): ITemplate | null
48
- * Restaura uma versão anterior do template.
49
- *
50
- * Exemplo de uso:
51
- *
52
- * const templateService = new TemplateService();
53
- * const myTemplate = templateService.createTemplate(ThemeType.MODERN, 'light', { header: { show: true } });
54
- * const html = await templateService.renderTemplate(myTemplate, { username: 'John' });
55
- * console.log(html);
27
+ * TemplateService - criação, renderização e gestão de templates de email.
28
+ * Cache e histórico mantêm limites finitos e retornam snapshots defensivos.
56
29
  */
57
30
  export declare class TemplateService {
58
- private options;
59
31
  private templateCache;
60
- private defaultTTL;
61
32
  private templateHistory;
33
+ private cacheRequests;
34
+ private cacheHits;
35
+ private readonly options;
36
+ private cleanupTimer;
62
37
  constructor(options?: TemplateOptions);
63
- /**
64
- * Cria um novo template com as configurações especificadas
65
- */
66
38
  createTemplate(themeType: ThemeType, variant: 'light' | 'dark', config: ITemplateConfig, options?: TemplateOptions): ITemplate;
67
- /**
68
- * Cria um template a partir de um template existente (clone)
69
- */
70
39
  cloneTemplate(template: ITemplate, modifications?: Partial<ITemplateConfig>): ITemplate;
71
- /**
72
- * Renderiza um template com os dados fornecidos
73
- */
74
40
  renderTemplate(template: ITemplate, data: any, options?: {
75
41
  preview?: boolean;
76
42
  }): Promise<string>;
77
- /**
78
- * Pré-visualiza um template sem enviar email
79
- */
80
43
  previewTemplate(themeType: ThemeType, variant: 'light' | 'dark', config: ITemplateConfig, data: any): Promise<string>;
81
- /**
82
- * Obtém informações sobre um tema específico
83
- */
84
44
  getThemeInfo(themeType: ThemeType): {
85
45
  name: string;
86
46
  description: string;
@@ -88,87 +48,40 @@ export declare class TemplateService {
88
48
  availableVariants: ('light' | 'dark')[];
89
49
  defaultConfig: Partial<ITemplateConfig>;
90
50
  };
91
- /**
92
- * Lista todos os temas disponíveis
93
- */
94
51
  listThemes(): ThemeType[];
95
- /**
96
- * Obtém estatísticas de uso dos templates
97
- */
98
52
  getTemplateStats(): {
99
53
  totalTemplates: number;
100
54
  cachedTemplates: number;
101
55
  totalHits: number;
56
+ cacheRequests: number;
102
57
  templatesByTheme: Record<ThemeType, number>;
103
58
  cacheHitRate: number;
104
59
  };
105
- /**
106
- * Limpa o cache de templates
107
- */
108
60
  clearCache(themeType?: ThemeType): number;
109
- /**
110
- * Obtém histórico de versões de um template
111
- */
112
61
  getTemplateHistory(templateId: string): ITemplate[];
113
- /**
114
- * Restaura uma versão anterior do template
115
- */
116
62
  restoreTemplateVersion(templateId: string, versionIndex: number): ITemplate | null;
117
- /**
118
- * Valida a configuração do template
119
- */
63
+ /** Encerrar o timer de limpeza quando a instância não for mais necessária. */
64
+ dispose(): void;
120
65
  private validateTemplateConfig;
121
- /**
122
- * Gera um ID único para o template baseado na configuração
123
- */
66
+ private assertThemeAndVariant;
124
67
  private generateTemplateId;
125
- /**
126
- * Adiciona funcionalidades extras ao template
127
- */
128
68
  private enhanceTemplate;
129
- /**
130
- * Adiciona template ao histórico
131
- */
69
+ private copyTemplate;
132
70
  private addToHistory;
133
- /**
134
- * Remove templates expirados do cache
135
- */
71
+ private trimCache;
136
72
  private cleanExpiredCache;
137
- /**
138
- * Minifica o HTML para reduzir tamanho
139
- */
140
73
  private minifyHtml;
141
- /**
142
- * Obtém configuração padrão para um tema
143
- */
74
+ private validateLimit;
144
75
  private getDefaultConfigForTheme;
145
- /**
146
- * Pré-carrega templates no cache
147
- */
148
76
  preloadTemplates(templates: Array<{
149
77
  themeType: ThemeType;
150
78
  variant: 'light' | 'dark';
151
79
  config: ITemplateConfig;
152
80
  }>): Promise<void>;
153
- /**
154
- * Exporta template para formato JSON
155
- */
156
81
  exportTemplate(template: ITemplate): string;
157
- /**
158
- * Importa template de formato JSON
159
- */
160
82
  importTemplate(jsonData: string): ITemplate;
161
- /**
162
- * Gera um template de exemplo para demonstração
163
- */
164
83
  generateExampleTemplate(themeType?: ThemeType, variant?: 'light' | 'dark'): ITemplate;
165
- /**
166
- * Valida se um template é válido
167
- */
168
84
  isValidTemplate(template: any): boolean;
169
- /**
170
- * Obtém estatísticas detalhadas de um template específico
171
- */
172
85
  getTemplateDetails(templateId: string): {
173
86
  template: ITemplate | null;
174
87
  history: ITemplate[];
@@ -1 +1 @@
1
- {"version":3,"file":"template.service.d.ts","sourceRoot":"","sources":["../../src/services/template.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.service.d.ts","sourceRoot":"","sources":["../../src/services/template.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iHAAiH;IACjH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,KAAK,eAAe,GAAG,uBAAuB,CAAC;AA+C/C;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,YAAY,CAAiC;gBAEzC,OAAO,GAAE,eAAoB;IAmBzC,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS;IA6C9H,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS;IAKjF,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBhG,eAAe,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3H,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,iBAAiB,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;KAAE;IAO/K,UAAU,IAAI,SAAS,EAAE;IAEzB,gBAAgB,IAAI;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAkBpL,UAAU,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM;IAWzC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE;IAInD,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAOlF,8EAA8E;IAC9E,OAAO,IAAI,IAAI;IAEf,OAAO,CAAC,sBAAsB;IAkE9B,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,UAAU;IAElB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,wBAAwB;IAe1B,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrI,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,MAAM;IAI3C,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS;IAY3C,uBAAuB,CAAC,SAAS,GAAE,SAA4B,EAAE,OAAO,GAAE,OAAO,GAAG,MAAgB,GAAG,SAAS;IAehH,eAAe,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO;IAQvC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAC;QAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAWlJ"}