@igniter-js/mail 0.1.13 → 0.1.14

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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { StandardSchemaV1, JobLimiter, IgniterLogger, IgniterJobQueueAdapter, IgniterError } from '@igniter-js/core';
1
+ import { StandardSchemaV1, IgniterLogger, IgniterError } from '@igniter-js/common';
2
+ import { JobLimiter, IgniterJobQueueAdapter } from '@igniter-js/core';
2
3
  import { I as IgniterMailAdapter } from './index-CbiH0sth.js';
3
4
  export { b as IgniterMailAdapterCredentials, a as IgniterMailAdapterSendParams, M as MockMailAdapter, P as PostmarkMailAdapter, R as ResendMailAdapter, S as SendGridMailAdapter, c as SmtpMailAdapter } from './index-CbiH0sth.js';
4
5
  import { ReactElement } from 'react';
@@ -10,11 +11,44 @@ import { IgniterTelemetryManager } from '@igniter-js/telemetry';
10
11
  interface IgniterMailTemplateBuilt<TSchema extends StandardSchemaV1> {
11
12
  /** Default subject for the template (can be overridden per-send). */
12
13
  subject: string;
14
+ /** Optional display name for Studio and tooling. */
15
+ name?: string;
16
+ /** Optional description for Studio and tooling. */
17
+ description?: string;
18
+ /** Optional path hint for Studio and tooling. */
19
+ path?: string;
20
+ /** Optional created timestamp (ISO string). */
21
+ createdAt?: string;
22
+ /** Optional updated timestamp (ISO string). */
23
+ updatedAt?: string;
24
+ /** Optional list of variable names for Studio and tooling. */
25
+ variables?: string[];
13
26
  /** Schema used to validate and infer template payload. */
14
27
  schema: TSchema;
15
28
  /** React Email component renderer. */
16
29
  render: (data: StandardSchemaV1.InferInput<TSchema>) => ReactElement;
17
30
  }
31
+ /**
32
+ * Template metadata exposed by {@link IIgniterMail}.
33
+ */
34
+ interface IgniterMailTemplateMeta {
35
+ /** Template identifier (registry key). */
36
+ id: string;
37
+ /** Display name. */
38
+ name: string;
39
+ /** Description for UI listings. */
40
+ description: string;
41
+ /** Path hint for routing. */
42
+ path: string;
43
+ /** Subject line for the template. */
44
+ subject: string;
45
+ /** Creation timestamp (ISO string). */
46
+ createdAt: string;
47
+ /** Last update timestamp (ISO string). */
48
+ updatedAt: string;
49
+ /** Optional list of variables for the template. */
50
+ variables?: string[];
51
+ }
18
52
  /**
19
53
  * Extracts the valid template keys from a template map.
20
54
  */
@@ -107,6 +141,31 @@ interface IgniterMailHooks<TTemplates extends object> {
107
141
  /** Invoked after a successful send. */
108
142
  onSendSuccess?: (params: IgniterMailSendParams<TTemplates, any>) => Promise<void>;
109
143
  }
144
+ /**
145
+ * Template registry API exposed by {@link IIgniterMail}.
146
+ */
147
+ interface IgniterMailTemplatesAPI<TTemplates extends object> {
148
+ /**
149
+ * Lists all registered templates with metadata.
150
+ */
151
+ list: () => Promise<IgniterMailTemplateMeta[]>;
152
+ /**
153
+ * Resolves a template metadata entry by id.
154
+ *
155
+ * @param id - Template key.
156
+ */
157
+ get: <TSelectedTemplate extends IgniterMailTemplateKey<TTemplates>>(id: TSelectedTemplate) => Promise<IgniterMailTemplateMeta | null>;
158
+ /**
159
+ * Renders a template to HTML + text.
160
+ *
161
+ * @param id - Template key.
162
+ * @param variables - Template input payload.
163
+ */
164
+ render: <TSelectedTemplate extends IgniterMailTemplateKey<TTemplates>>(id: TSelectedTemplate, variables?: IgniterMailTemplatePayload<TTemplates[TSelectedTemplate]>) => Promise<{
165
+ html: string;
166
+ text: string;
167
+ }>;
168
+ }
110
169
  /**
111
170
  * Options used to initialize {@link IgniterMail}.
112
171
  */
@@ -133,6 +192,10 @@ interface IIgniterMail<TTemplates extends object> {
133
192
  * Access via `typeof mail.$Infer` (type-level only).
134
193
  */
135
194
  readonly $Infer: IgniterMailInfer<TTemplates>;
195
+ /**
196
+ * Template registry helpers.
197
+ */
198
+ readonly templates: IgniterMailTemplatesAPI<TTemplates>;
136
199
  /** Sends an email immediately. */
137
200
  send: <TSelectedTemplate extends IgniterMailTemplateKey<TTemplates>>(params: IgniterMailSendParams<TTemplates, TSelectedTemplate>) => Promise<void>;
138
201
  /** Schedules an email for a future date (requires queue adapter). */
@@ -223,7 +286,7 @@ declare const IgniterMailTemplate: typeof IgniterMailTemplateBuilder;
223
286
  */
224
287
  declare class IgniterMailManagerCore<TTemplates extends object> implements IIgniterMail<TTemplates> {
225
288
  private readonly adapter;
226
- private readonly templates;
289
+ private readonly templateRegistry;
227
290
  private readonly logger?;
228
291
  private readonly telemetry?;
229
292
  private readonly queue?;
@@ -235,7 +298,15 @@ declare class IgniterMailManagerCore<TTemplates extends object> implements IIgni
235
298
  * Access via `typeof mail.$Infer` (type-level only).
236
299
  */
237
300
  readonly $Infer: IgniterMailInfer<TTemplates>;
301
+ /**
302
+ * Template registry helpers.
303
+ */
304
+ readonly templates: IgniterMailTemplatesAPI<TTemplates>;
238
305
  constructor(options: IgniterMailOptions<TTemplates>);
306
+ private buildTemplateMeta;
307
+ private listTemplates;
308
+ private getTemplate;
309
+ private renderTemplate;
239
310
  private ensureQueueJobRegistered;
240
311
  private validateTemplateData;
241
312
  /**
@@ -256,7 +327,7 @@ declare class IgniterMailManagerCore<TTemplates extends object> implements IIgni
256
327
  /**
257
328
  * Known error codes thrown by `@igniter-js/mail` runtime.
258
329
  */
259
- type IgniterMailErrorCode = 'MAIL_PROVIDER_FROM_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_NOT_FOUND' | 'MAIL_PROVIDER_TEMPLATES_REQUIRED' | 'MAIL_PROVIDER_TEMPLATE_NOT_FOUND' | 'MAIL_PROVIDER_TEMPLATE_DATA_INVALID' | 'MAIL_PROVIDER_SCHEDULE_DATE_INVALID' | 'MAIL_PROVIDER_SEND_FAILED' | 'MAIL_PROVIDER_SCHEDULE_FAILED' | 'MAIL_ADAPTER_CONFIGURATION_INVALID' | 'MAIL_TEMPLATE_CONFIGURATION_INVALID' | 'MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED';
330
+ type IgniterMailErrorCode = 'MAIL_PROVIDER_FROM_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED' | 'MAIL_PROVIDER_ADAPTER_NOT_FOUND' | 'MAIL_PROVIDER_TEMPLATES_REQUIRED' | 'MAIL_PROVIDER_TEMPLATE_NOT_FOUND' | 'MAIL_PROVIDER_TEMPLATE_DATA_INVALID' | 'MAIL_PROVIDER_TEMPLATE_LIST_FAILED' | 'MAIL_PROVIDER_TEMPLATE_GET_FAILED' | 'MAIL_PROVIDER_TEMPLATE_RENDER_FAILED' | 'MAIL_PROVIDER_SCHEDULE_DATE_INVALID' | 'MAIL_PROVIDER_SEND_FAILED' | 'MAIL_PROVIDER_SCHEDULE_FAILED' | 'MAIL_ADAPTER_CONFIGURATION_INVALID' | 'MAIL_TEMPLATE_CONFIGURATION_INVALID' | 'MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED';
260
331
  /**
261
332
  * Payload used to create an {@link IgniterMailError}.
262
333
  */
@@ -306,4 +377,4 @@ declare class IgniterMailSchema {
306
377
  static createPassthroughSchema(): StandardSchemaV1;
307
378
  }
308
379
 
309
- export { type IIgniterMail, IgniterMail, IgniterMailAdapter, IgniterMailBuilder, IgniterMailError, type IgniterMailErrorCode, type IgniterMailErrorPayload, type IgniterMailHooks, type IgniterMailInfer, IgniterMailManagerCore, type IgniterMailOptions, type IgniterMailQueueConfig, type IgniterMailQueueOptions, IgniterMailSchema, type IgniterMailSendParams, IgniterMailTemplate, IgniterMailTemplateBuilder, type IgniterMailTemplateBuilt, type IgniterMailTemplateKey, type IgniterMailTemplatePayload };
380
+ export { type IIgniterMail, IgniterMail, IgniterMailAdapter, IgniterMailBuilder, IgniterMailError, type IgniterMailErrorCode, type IgniterMailErrorPayload, type IgniterMailHooks, type IgniterMailInfer, IgniterMailManagerCore, type IgniterMailOptions, type IgniterMailQueueConfig, type IgniterMailQueueOptions, IgniterMailSchema, type IgniterMailSendParams, IgniterMailTemplate, IgniterMailTemplateBuilder, type IgniterMailTemplateBuilt, type IgniterMailTemplateKey, type IgniterMailTemplateMeta, type IgniterMailTemplatePayload, type IgniterMailTemplatesAPI };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@igniter-js/core');
3
+ var common = require('@igniter-js/common');
4
4
  var resend = require('resend');
5
5
  var nodemailer = require('nodemailer');
6
6
  var React = require('react');
@@ -12,7 +12,7 @@ var nodemailer__default = /*#__PURE__*/_interopDefault(nodemailer);
12
12
  var React__default = /*#__PURE__*/_interopDefault(React);
13
13
 
14
14
  // src/errors/mail.error.ts
15
- var IgniterMailError = class _IgniterMailError extends core.IgniterError {
15
+ var IgniterMailError = class _IgniterMailError extends common.IgniterError {
16
16
  constructor(payload) {
17
17
  super({
18
18
  code: payload.code,
@@ -402,11 +402,193 @@ var IgniterMailManagerCore = class {
402
402
  });
403
403
  }
404
404
  this.adapter = adapter;
405
- this.templates = templates;
405
+ this.templateRegistry = templates;
406
406
  this.logger = logger;
407
407
  this.telemetry = telemetry;
408
408
  this.queue = queue;
409
409
  this.options = rest;
410
+ this.templates = {
411
+ list: async () => this.listTemplates(),
412
+ get: async (id) => this.getTemplate(id),
413
+ render: async (id, variables) => this.renderTemplate(id, variables)
414
+ };
415
+ }
416
+ buildTemplateMeta(key, template) {
417
+ const now = (/* @__PURE__ */ new Date()).toISOString();
418
+ return {
419
+ id: key,
420
+ name: template.name ?? key,
421
+ description: template.description ?? `Template ${key}`,
422
+ path: template.path ?? `mail/${key}`,
423
+ subject: template.subject,
424
+ createdAt: template.createdAt ?? now,
425
+ updatedAt: template.updatedAt ?? now,
426
+ variables: template.variables
427
+ };
428
+ }
429
+ async listTemplates() {
430
+ const startTime = Date.now();
431
+ this.logger?.debug("IgniterMail.templates.list started");
432
+ this.telemetry?.emit("igniter.mail.templates.list.started", {
433
+ level: "debug",
434
+ attributes: {}
435
+ });
436
+ try {
437
+ const results = Object.entries(this.templateRegistry).map(
438
+ ([key, template]) => this.buildTemplateMeta(
439
+ key,
440
+ template
441
+ )
442
+ );
443
+ this.telemetry?.emit("igniter.mail.templates.list.success", {
444
+ level: "info",
445
+ attributes: {
446
+ "ctx.mail.template.count": results.length,
447
+ "ctx.mail.duration_ms": Date.now() - startTime
448
+ }
449
+ });
450
+ this.logger?.info("IgniterMail.templates.list success", {
451
+ count: results.length,
452
+ durationMs: Date.now() - startTime
453
+ });
454
+ return results;
455
+ } catch (error) {
456
+ const normalizedError = IgniterMailError.is(error) ? error : new IgniterMailError({
457
+ code: "MAIL_PROVIDER_TEMPLATE_LIST_FAILED",
458
+ message: "MAIL_PROVIDER_TEMPLATE_LIST_FAILED",
459
+ cause: error,
460
+ logger: this.logger
461
+ });
462
+ this.telemetry?.emit("igniter.mail.templates.list.error", {
463
+ level: "error",
464
+ attributes: {
465
+ "ctx.mail.error.code": normalizedError.code,
466
+ "ctx.mail.error.message": normalizedError.message,
467
+ "ctx.mail.duration_ms": Date.now() - startTime
468
+ }
469
+ });
470
+ this.logger?.error("IgniterMail.templates.list failed", normalizedError);
471
+ throw normalizedError;
472
+ }
473
+ }
474
+ async getTemplate(id) {
475
+ const startTime = Date.now();
476
+ this.logger?.debug("IgniterMail.templates.get started", {
477
+ template: String(id)
478
+ });
479
+ this.telemetry?.emit("igniter.mail.templates.get.started", {
480
+ level: "debug",
481
+ attributes: {
482
+ "ctx.mail.template_id": String(id)
483
+ }
484
+ });
485
+ try {
486
+ const template = this.templateRegistry[id];
487
+ if (!template) {
488
+ this.telemetry?.emit("igniter.mail.templates.get.success", {
489
+ level: "info",
490
+ attributes: {
491
+ "ctx.mail.template_id": String(id),
492
+ "ctx.mail.duration_ms": Date.now() - startTime
493
+ }
494
+ });
495
+ return null;
496
+ }
497
+ const result = this.buildTemplateMeta(String(id), template);
498
+ this.telemetry?.emit("igniter.mail.templates.get.success", {
499
+ level: "info",
500
+ attributes: {
501
+ "ctx.mail.template_id": String(id),
502
+ "ctx.mail.duration_ms": Date.now() - startTime
503
+ }
504
+ });
505
+ return result;
506
+ } catch (error) {
507
+ const normalizedError = IgniterMailError.is(error) ? error : new IgniterMailError({
508
+ code: "MAIL_PROVIDER_TEMPLATE_GET_FAILED",
509
+ message: "MAIL_PROVIDER_TEMPLATE_GET_FAILED",
510
+ cause: error,
511
+ logger: this.logger,
512
+ metadata: { template: String(id) }
513
+ });
514
+ this.telemetry?.emit("igniter.mail.templates.get.error", {
515
+ level: "error",
516
+ attributes: {
517
+ "ctx.mail.template_id": String(id),
518
+ "ctx.mail.error.code": normalizedError.code,
519
+ "ctx.mail.error.message": normalizedError.message,
520
+ "ctx.mail.duration_ms": Date.now() - startTime
521
+ }
522
+ });
523
+ this.logger?.error("IgniterMail.templates.get failed", normalizedError);
524
+ throw normalizedError;
525
+ }
526
+ }
527
+ async renderTemplate(id, variables) {
528
+ const startTime = Date.now();
529
+ this.logger?.debug("IgniterMail.templates.render started", {
530
+ template: String(id)
531
+ });
532
+ this.telemetry?.emit("igniter.mail.templates.render.started", {
533
+ level: "debug",
534
+ attributes: {
535
+ "ctx.mail.template_id": String(id)
536
+ }
537
+ });
538
+ try {
539
+ const template = this.templateRegistry[id];
540
+ if (!template) {
541
+ throw new IgniterMailError({
542
+ code: "MAIL_PROVIDER_TEMPLATE_NOT_FOUND",
543
+ message: "MAIL_PROVIDER_TEMPLATE_NOT_FOUND",
544
+ logger: this.logger,
545
+ metadata: { template: String(id) }
546
+ });
547
+ }
548
+ const validatedData = await this.validateTemplateData(
549
+ template,
550
+ variables ?? {}
551
+ );
552
+ const MailTemplate = template.render;
553
+ const html = await components.render(
554
+ /* @__PURE__ */ React__default.default.createElement(MailTemplate, { ...validatedData })
555
+ );
556
+ const text = await components.render(
557
+ /* @__PURE__ */ React__default.default.createElement(MailTemplate, { ...validatedData }),
558
+ { plainText: true }
559
+ );
560
+ this.telemetry?.emit("igniter.mail.templates.render.success", {
561
+ level: "info",
562
+ attributes: {
563
+ "ctx.mail.template_id": String(id),
564
+ "ctx.mail.duration_ms": Date.now() - startTime
565
+ }
566
+ });
567
+ this.logger?.info("IgniterMail.templates.render success", {
568
+ template: String(id),
569
+ durationMs: Date.now() - startTime
570
+ });
571
+ return { html, text };
572
+ } catch (error) {
573
+ const normalizedError = IgniterMailError.is(error) ? error : new IgniterMailError({
574
+ code: "MAIL_PROVIDER_TEMPLATE_RENDER_FAILED",
575
+ message: "MAIL_PROVIDER_TEMPLATE_RENDER_FAILED",
576
+ cause: error,
577
+ logger: this.logger,
578
+ metadata: { template: String(id) }
579
+ });
580
+ this.telemetry?.emit("igniter.mail.templates.render.error", {
581
+ level: "error",
582
+ attributes: {
583
+ "ctx.mail.template_id": String(id),
584
+ "ctx.mail.error.code": normalizedError.code,
585
+ "ctx.mail.error.message": normalizedError.message,
586
+ "ctx.mail.duration_ms": Date.now() - startTime
587
+ }
588
+ });
589
+ this.logger?.error("IgniterMail.templates.render failed", normalizedError);
590
+ throw normalizedError;
591
+ }
410
592
  }
411
593
  async ensureQueueJobRegistered() {
412
594
  const queue = this.queue;
@@ -477,7 +659,7 @@ var IgniterMailManagerCore = class {
477
659
  }
478
660
  });
479
661
  await this.onSendStarted(params);
480
- const template = this.templates[params.template];
662
+ const template = this.templateRegistry[params.template];
481
663
  if (!template) {
482
664
  throw new IgniterMailError({
483
665
  code: "MAIL_PROVIDER_TEMPLATE_NOT_FOUND",