@fluojs/email
Advanced tools
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAM3E,OAAO,KAAK,EACV,KAAK,EAGL,YAAY,EACZ,gCAAgC,EAC9B,oBAAoB,EAEpB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EAKf,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AA2CtB;;;;;;;GAOG;AACH,qBACa,YAAa,YAAW,KAAK,EAAE,YAAY,EAAE,qBAAqB;IAKjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,cAAc,CAAmF;IACzG,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,gBAAgB,CAAsC;gBAEjC,OAAO,EAAE,4BAA4B;IAE5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAetC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBnC;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;;;;;;;;;;;OAiBG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAoB3F;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA6BpH;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,YAAY,EAAE,gCAAgC,EAC9C,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;YA0Bb,eAAe;IAe7B,OAAO,CAAC,gBAAgB;YAuBV,kBAAkB;CAejC"} | ||
| {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAM3E,OAAO,KAAK,EACV,KAAK,EAGL,YAAY,EACZ,gCAAgC,EAC9B,oBAAoB,EAEpB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EAKf,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAyDtB;;;;;;;GAOG;AACH,qBACa,YAAa,YAAW,KAAK,EAAE,YAAY,EAAE,qBAAqB;IAKjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,cAAc,CAAmF;IACzG,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,gBAAgB,CAAsC;gBAEjC,OAAO,EAAE,4BAA4B;IAE5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAetC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBnC;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;;;;;;;;;;;OAiBG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAmB3F;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA6BpH;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,YAAY,EAAE,gCAAgC,EAC9C,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;YA4Bb,eAAe;IAe7B,OAAO,CAAC,gBAAgB;YAuBV,kBAAkB;CAkBjC"} |
+23
-9
@@ -38,2 +38,12 @@ let _initClass; | ||
| } | ||
| function assertNotAborted(signal) { | ||
| if (signal?.aborted) { | ||
| throw createAbortError(); | ||
| } | ||
| } | ||
| function createLifecycleError(message, cause) { | ||
| return new Error(message, { | ||
| cause | ||
| }); | ||
| } | ||
| function assertMessageContent(message) { | ||
@@ -43,2 +53,5 @@ if (message.to.length === 0) { | ||
| } | ||
| if (message.to.some(entry => entry.address.length === 0)) { | ||
| throw new EmailMessageValidationError('Email messages require non-empty recipients in `to`.'); | ||
| } | ||
| if (!message.from.address) { | ||
@@ -78,5 +91,5 @@ throw new EmailMessageValidationError('Email messages require a resolved `from` address.'); | ||
| this.lifecycleState = 'stopped'; | ||
| } catch { | ||
| } catch (error) { | ||
| this.lifecycleState = 'failed'; | ||
| throw new Error('Email transport failed to close cleanly.'); | ||
| throw createLifecycleError('Email transport failed to close cleanly.', error); | ||
| } | ||
@@ -92,5 +105,5 @@ } | ||
| this.lifecycleState = 'ready'; | ||
| } catch { | ||
| } catch (error) { | ||
| this.lifecycleState = 'failed'; | ||
| throw new Error('Email transport failed to initialize.'); | ||
| throw createLifecycleError('Email transport failed to initialize.', error); | ||
| } | ||
@@ -135,8 +148,7 @@ } | ||
| async send(message, options = {}) { | ||
| if (options.signal?.aborted) { | ||
| throw createAbortError(); | ||
| } | ||
| assertNotAborted(options.signal); | ||
| const transport = await this.ensureTransport(); | ||
| const normalized = this.normalizeMessage(message); | ||
| assertMessageContent(normalized); | ||
| assertNotAborted(options.signal); | ||
| const result = await transport.send(normalized, options); | ||
@@ -211,3 +223,4 @@ return { | ||
| const payload = notification.payload; | ||
| const rendered = await this.renderNotification(notification); | ||
| const rendered = await this.renderNotification(notification, options.signal); | ||
| assertNotAborted(options.signal); | ||
| return this.send({ | ||
@@ -265,6 +278,7 @@ attachments: payload.attachments, | ||
| } | ||
| async renderNotification(notification) { | ||
| async renderNotification(notification, signal) { | ||
| if (!notification.template || !this.options.renderer) { | ||
| return undefined; | ||
| } | ||
| assertNotAborted(signal); | ||
| return this.options.renderer.render({ | ||
@@ -271,0 +285,0 @@ locale: notification.locale, |
+7
-7
@@ -13,3 +13,3 @@ { | ||
| ], | ||
| "version": "1.0.0-beta.3", | ||
| "version": "1.0.0-beta.4", | ||
| "private": false, | ||
@@ -56,10 +56,10 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fluojs/core": "^1.0.0-beta.4", | ||
| "@fluojs/di": "^1.0.0-beta.6", | ||
| "@fluojs/notifications": "^1.0.0-beta.3", | ||
| "@fluojs/runtime": "^1.0.0-beta.11" | ||
| "@fluojs/core": "^1.0.0-beta.5", | ||
| "@fluojs/di": "^1.0.0-beta.7", | ||
| "@fluojs/notifications": "^1.0.0-beta.4", | ||
| "@fluojs/runtime": "^1.0.0-beta.12" | ||
| }, | ||
| "peerDependencies": { | ||
| "nodemailer": "^6.10.1", | ||
| "@fluojs/queue": "^1.0.0-beta.4" | ||
| "@fluojs/queue": "^1.0.0-beta.5" | ||
| }, | ||
@@ -77,3 +77,3 @@ "peerDependenciesMeta": { | ||
| "vitest": "^3.2.4", | ||
| "@fluojs/queue": "^1.0.0-beta.4" | ||
| "@fluojs/queue": "^1.0.0-beta.5" | ||
| }, | ||
@@ -80,0 +80,0 @@ "scripts": { |
+5
-1
@@ -89,4 +89,5 @@ # @fluojs/email | ||
| @Inject(EmailService) | ||
| export class WelcomeService { | ||
| constructor(@Inject(EmailService) private readonly email: EmailService) {} | ||
| constructor(private readonly email: EmailService) {} | ||
@@ -166,2 +167,4 @@ async sendWelcome(address: string) { | ||
| - `EmailService.send(...)`는 전달 전에 `defaultFrom`과 `defaultReplyTo`를 해석합니다. | ||
| - `EmailService.send(...)`는 빈 `to` 수신자를 transport handoff 전에 거부하므로 transport가 빈 전달 대상을 받지 않습니다. | ||
| - `EmailService.send(...)`와 `EmailService.sendNotification(...)`은 이미 abort된 `AbortSignal`을 템플릿 렌더링 또는 transport handoff 전에 반영합니다. | ||
| - `EmailService.send(...)`는 `accepted`, `pending`, `rejected` 수신자를 분리해 보존하므로 provider의 부분 실패가 호출자에게 그대로 보입니다. | ||
@@ -171,2 +174,3 @@ - `EmailService.sendMany(...)`는 기본적으로 fail-fast입니다. 실패를 batch result에 수집하려면 `continueOnError: true`를 전달합니다. | ||
| - 서비스는 모듈 bootstrap 시 transport를 초기화하고, factory가 소유한 리소스만 애플리케이션 shutdown 시 닫습니다. | ||
| - transport `verify()`와 `close()`에서 발생한 provider error는 diagnostics를 위해 lifecycle failure의 `cause`로 보존됩니다. | ||
| - 모듈 옵션은 provider wiring 전에 trim 및 normalize됩니다. 여기에는 sender 기본값, notification channel 이름, transport factory 소유권이 포함됩니다. | ||
@@ -173,0 +177,0 @@ - 이 패키지는 절대로 `process.env`를 직접 읽지 않습니다. 모든 설정은 명시적인 옵션 또는 DI를 통해 들어와야 합니다. |
+5
-1
@@ -89,4 +89,5 @@ # @fluojs/email | ||
| @Inject(EmailService) | ||
| export class WelcomeService { | ||
| constructor(@Inject(EmailService) private readonly email: EmailService) {} | ||
| constructor(private readonly email: EmailService) {} | ||
@@ -166,2 +167,4 @@ async sendWelcome(address: string) { | ||
| - `EmailService.send(...)` resolves `defaultFrom` and `defaultReplyTo` before delivery. | ||
| - `EmailService.send(...)` rejects blank `to` recipients before handoff so transports never receive an empty delivery target. | ||
| - `EmailService.send(...)` and `EmailService.sendNotification(...)` honor an already-aborted `AbortSignal` before template rendering or transport handoff. | ||
| - `EmailService.send(...)` preserves `accepted`, `pending`, and `rejected` recipients separately so partial provider failures stay caller-visible. | ||
@@ -171,2 +174,3 @@ - `EmailService.sendMany(...)` is fail-fast by default; pass `continueOnError: true` to collect failures in a batch result. | ||
| - The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown. | ||
| - Transport `verify()` and `close()` provider errors are preserved as the `cause` of lifecycle failures for diagnostics. | ||
| - Module options are trimmed and normalized before provider wiring, including sender defaults, notification channel names, and transport factory ownership. | ||
@@ -173,0 +177,0 @@ - The package never reads `process.env` directly. All configuration must enter through explicit options or DI. |
114485
1.21%1489
0.95%347
1.17%