@venizia/ignis 0.4.7-0 → 0.4.7-2

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 (177) hide show
  1. package/LICENSE.md +1 -4
  2. package/README.md +2 -2
  3. package/dist/esm/base/applications/base.d.ts.map +1 -1
  4. package/dist/esm/base/applications/base.js +25 -18
  5. package/dist/esm/base/applications/base.js.map +1 -1
  6. package/dist/esm/base/applications/common/index.d.ts +1 -0
  7. package/dist/esm/base/applications/common/index.d.ts.map +1 -1
  8. package/dist/esm/base/applications/common/index.js +1 -0
  9. package/dist/esm/base/applications/common/index.js.map +1 -1
  10. package/dist/esm/base/applications/common/logger-startup.d.ts +11 -0
  11. package/dist/esm/base/applications/common/logger-startup.d.ts.map +1 -0
  12. package/dist/esm/base/applications/common/logger-startup.js +29 -0
  13. package/dist/esm/base/applications/common/logger-startup.js.map +1 -0
  14. package/dist/esm/base/applications/server.d.ts +14 -9
  15. package/dist/esm/base/applications/server.d.ts.map +1 -1
  16. package/dist/esm/base/applications/server.js +36 -29
  17. package/dist/esm/base/applications/server.js.map +1 -1
  18. package/dist/esm/common/constants.d.ts.map +1 -1
  19. package/dist/esm/common/constants.js +2 -2
  20. package/dist/esm/common/constants.js.map +1 -1
  21. package/dist/esm/components/auth/authenticate/component.js +2 -2
  22. package/dist/esm/components/auth/authenticate/component.js.map +1 -1
  23. package/dist/esm/components/auth/authenticate/controllers/factory.d.ts +139 -136
  24. package/dist/esm/components/auth/authenticate/controllers/factory.d.ts.map +1 -1
  25. package/dist/esm/components/auth/authenticate/controllers/factory.js +175 -172
  26. package/dist/esm/components/auth/authenticate/controllers/factory.js.map +1 -1
  27. package/dist/esm/components/auth/authenticate/controllers/jwks/definitions.js +2 -2
  28. package/dist/esm/components/auth/authenticate/controllers/jwks/definitions.js.map +1 -1
  29. package/dist/esm/components/auth/authenticate/controllers/service-certs/definitions.js +2 -2
  30. package/dist/esm/components/auth/authenticate/controllers/service-certs/definitions.js.map +1 -1
  31. package/dist/esm/components/auth/authorize/adapters/base-filtered.js +2 -2
  32. package/dist/esm/components/auth/authorize/adapters/base-filtered.js.map +1 -1
  33. package/dist/esm/components/auth/authorize/adapters/custom-grant-expander.d.ts.map +1 -1
  34. package/dist/esm/components/auth/authorize/adapters/custom-grant-expander.js +2 -2
  35. package/dist/esm/components/auth/authorize/adapters/custom-grant-expander.js.map +1 -1
  36. package/dist/esm/components/auth/models/entities/columns.d.ts +99 -0
  37. package/dist/esm/components/auth/models/entities/columns.d.ts.map +1 -0
  38. package/dist/esm/components/auth/models/entities/columns.js +133 -0
  39. package/dist/esm/components/auth/models/entities/columns.js.map +1 -0
  40. package/dist/esm/components/auth/models/entities/index.d.ts +1 -2
  41. package/dist/esm/components/auth/models/entities/index.d.ts.map +1 -1
  42. package/dist/esm/components/auth/models/entities/index.js +1 -2
  43. package/dist/esm/components/auth/models/entities/index.js.map +1 -1
  44. package/dist/esm/components/auth/models/entities/permission.model.d.ts +1 -12
  45. package/dist/esm/components/auth/models/entities/permission.model.d.ts.map +1 -1
  46. package/dist/esm/components/auth/models/entities/permission.model.js +1 -36
  47. package/dist/esm/components/auth/models/entities/permission.model.js.map +1 -1
  48. package/dist/esm/components/auth/models/entities/policy-definition.model.d.ts +1 -49
  49. package/dist/esm/components/auth/models/entities/policy-definition.model.d.ts.map +1 -1
  50. package/dist/esm/components/auth/models/entities/policy-definition.model.js +1 -69
  51. package/dist/esm/components/auth/models/entities/policy-definition.model.js.map +1 -1
  52. package/dist/esm/components/controller/grpc/grpc.component.js +2 -2
  53. package/dist/esm/components/controller/grpc/grpc.component.js.map +1 -1
  54. package/dist/esm/components/health-check/controller.js +5 -5
  55. package/dist/esm/components/health-check/controller.js.map +1 -1
  56. package/dist/esm/components/index.d.ts +0 -1
  57. package/dist/esm/components/index.d.ts.map +1 -1
  58. package/dist/esm/components/index.js +0 -1
  59. package/dist/esm/components/index.js.map +1 -1
  60. package/dist/esm/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts.map +1 -1
  61. package/dist/esm/components/mail/helpers/transporters/amazon-ses-transporter.helper.js +13 -9
  62. package/dist/esm/components/mail/helpers/transporters/amazon-ses-transporter.helper.js.map +1 -1
  63. package/dist/esm/components/mail/helpers/transporters/mailgun-transporter.helper.d.ts.map +1 -1
  64. package/dist/esm/components/mail/helpers/transporters/mailgun-transporter.helper.js +5 -3
  65. package/dist/esm/components/mail/helpers/transporters/mailgun-transporter.helper.js.map +1 -1
  66. package/dist/esm/components/mail/helpers/transporters/nodemail-transporter.helper.d.ts.map +1 -1
  67. package/dist/esm/components/mail/helpers/transporters/nodemail-transporter.helper.js +5 -5
  68. package/dist/esm/components/mail/helpers/transporters/nodemail-transporter.helper.js.map +1 -1
  69. package/dist/esm/components/mail/services/generators/verification-data.d.ts.map +1 -1
  70. package/dist/esm/components/mail/services/generators/verification-data.js +7 -3
  71. package/dist/esm/components/mail/services/generators/verification-data.js.map +1 -1
  72. package/dist/esm/components/mail/services/mail.service.js +7 -7
  73. package/dist/esm/components/mail/services/mail.service.js.map +1 -1
  74. package/dist/esm/components/mail/utilities/address.utility.d.ts +12 -8
  75. package/dist/esm/components/mail/utilities/address.utility.d.ts.map +1 -1
  76. package/dist/esm/components/mail/utilities/address.utility.js +30 -27
  77. package/dist/esm/components/mail/utilities/address.utility.js.map +1 -1
  78. package/dist/esm/components/mail/utilities/attachment.utility.d.ts +31 -29
  79. package/dist/esm/components/mail/utilities/attachment.utility.d.ts.map +1 -1
  80. package/dist/esm/components/mail/utilities/attachment.utility.js +97 -86
  81. package/dist/esm/components/mail/utilities/attachment.utility.js.map +1 -1
  82. package/dist/esm/components/mail/utilities/body.utility.d.ts +6 -4
  83. package/dist/esm/components/mail/utilities/body.utility.d.ts.map +1 -1
  84. package/dist/esm/components/mail/utilities/body.utility.js +14 -12
  85. package/dist/esm/components/mail/utilities/body.utility.js.map +1 -1
  86. package/dist/esm/components/mail/utilities/mime.utility.d.ts +23 -17
  87. package/dist/esm/components/mail/utilities/mime.utility.d.ts.map +1 -1
  88. package/dist/esm/components/mail/utilities/mime.utility.js +109 -102
  89. package/dist/esm/components/mail/utilities/mime.utility.js.map +1 -1
  90. package/dist/esm/components/mail/utilities/verification.utility.d.ts +8 -2
  91. package/dist/esm/components/mail/utilities/verification.utility.d.ts.map +1 -1
  92. package/dist/esm/components/mail/utilities/verification.utility.js +7 -5
  93. package/dist/esm/components/mail/utilities/verification.utility.js.map +1 -1
  94. package/dist/esm/components/socket-io/component.js +3 -3
  95. package/dist/esm/components/socket-io/component.js.map +1 -1
  96. package/dist/esm/components/socket-io/handlers/bun.handler.d.ts +13 -11
  97. package/dist/esm/components/socket-io/handlers/bun.handler.d.ts.map +1 -1
  98. package/dist/esm/components/socket-io/handlers/bun.handler.js +32 -30
  99. package/dist/esm/components/socket-io/handlers/bun.handler.js.map +1 -1
  100. package/dist/esm/components/static-asset/common/commit-token.d.ts +21 -16
  101. package/dist/esm/components/static-asset/common/commit-token.d.ts.map +1 -1
  102. package/dist/esm/components/static-asset/common/commit-token.js +35 -28
  103. package/dist/esm/components/static-asset/common/commit-token.js.map +1 -1
  104. package/dist/esm/components/static-asset/common/constants.d.ts +0 -2
  105. package/dist/esm/components/static-asset/common/constants.d.ts.map +1 -1
  106. package/dist/esm/components/static-asset/common/constants.js +1 -3
  107. package/dist/esm/components/static-asset/common/constants.js.map +1 -1
  108. package/dist/esm/components/static-asset/common/types.d.ts +0 -4
  109. package/dist/esm/components/static-asset/common/types.d.ts.map +1 -1
  110. package/dist/esm/components/static-asset/common/types.js.map +1 -1
  111. package/dist/esm/components/static-asset/component.js +2 -2
  112. package/dist/esm/components/static-asset/component.js.map +1 -1
  113. package/dist/esm/components/static-asset/controller/base.definition.d.ts +6 -6
  114. package/dist/esm/components/static-asset/controller/base.definition.d.ts.map +1 -1
  115. package/dist/esm/components/static-asset/controller/base.definition.js +224 -224
  116. package/dist/esm/components/static-asset/controller/base.definition.js.map +1 -1
  117. package/dist/esm/components/static-asset/controller/factory.d.ts +23 -23
  118. package/dist/esm/components/static-asset/controller/factory.d.ts.map +1 -1
  119. package/dist/esm/components/static-asset/controller/factory.js +105 -76
  120. package/dist/esm/components/static-asset/controller/factory.js.map +1 -1
  121. package/dist/esm/components/static-asset/controller/key-scope.d.ts +28 -26
  122. package/dist/esm/components/static-asset/controller/key-scope.d.ts.map +1 -1
  123. package/dist/esm/components/static-asset/controller/key-scope.js +34 -30
  124. package/dist/esm/components/static-asset/controller/key-scope.js.map +1 -1
  125. package/dist/esm/components/static-asset/controller/meta-link.d.ts +37 -33
  126. package/dist/esm/components/static-asset/controller/meta-link.d.ts.map +1 -1
  127. package/dist/esm/components/static-asset/controller/meta-link.js +86 -84
  128. package/dist/esm/components/static-asset/controller/meta-link.js.map +1 -1
  129. package/dist/esm/components/static-asset/controller/route-options.d.ts +18 -16
  130. package/dist/esm/components/static-asset/controller/route-options.d.ts.map +1 -1
  131. package/dist/esm/components/static-asset/controller/route-options.js +28 -26
  132. package/dist/esm/components/static-asset/controller/route-options.js.map +1 -1
  133. package/dist/esm/components/static-asset/models/base.model.js +3 -3
  134. package/dist/esm/components/static-asset/models/base.model.js.map +1 -1
  135. package/dist/esm/components/websocket/component.js +2 -2
  136. package/dist/esm/components/websocket/component.js.map +1 -1
  137. package/dist/esm/components/websocket/handlers/bun.handler.d.ts +6 -4
  138. package/dist/esm/components/websocket/handlers/bun.handler.d.ts.map +1 -1
  139. package/dist/esm/components/websocket/handlers/bun.handler.js +21 -19
  140. package/dist/esm/components/websocket/handlers/bun.handler.js.map +1 -1
  141. package/dist/esm/testing/assert-application-boots.d.ts +6 -1
  142. package/dist/esm/testing/assert-application-boots.d.ts.map +1 -1
  143. package/dist/esm/testing/assert-application-boots.js +139 -136
  144. package/dist/esm/testing/assert-application-boots.js.map +1 -1
  145. package/dist/esm/utilities/drizzle-result.utility.d.ts +6 -8
  146. package/dist/esm/utilities/drizzle-result.utility.d.ts.map +1 -1
  147. package/dist/esm/utilities/drizzle-result.utility.js +23 -49
  148. package/dist/esm/utilities/drizzle-result.utility.js.map +1 -1
  149. package/dist/esm/utilities/network.utility.d.ts.map +1 -1
  150. package/dist/esm/utilities/network.utility.js +2 -2
  151. package/dist/esm/utilities/network.utility.js.map +1 -1
  152. package/dist/presets/app/files/src/boot.test.ts +2 -2
  153. package/dist/presets/app/files/src/controllers/hello.controller.ts +2 -2
  154. package/dist/presets/app/preset.json +4 -4
  155. package/dist/presets/lib/preset.json +4 -4
  156. package/dist/presets/shared/files/.configs/formatters/.ignore +4 -0
  157. package/dist/presets/shared/files/.configs/lints/.ignore +4 -0
  158. package/dist/presets/shared/files/.configs/lints/eslint.config.mjs +4 -0
  159. package/dist/presets/shared/files/.configs/lints/oxlint.config.mts +4 -0
  160. package/dist/presets/shared/files/README.md +15 -0
  161. package/dist/presets/shared/files/src/index.test.ts +8 -0
  162. package/dist/presets/shared/files/src/index.ts +8 -0
  163. package/dist/presets/shared/files/tsconfig.json +20 -0
  164. package/dist/presets/shared/preset.json +35 -0
  165. package/package.json +11 -11
  166. package/dist/esm/components/auth/models/entities/role.model.d.ts +0 -10
  167. package/dist/esm/components/auth/models/entities/role.model.d.ts.map +0 -1
  168. package/dist/esm/components/auth/models/entities/role.model.js +0 -15
  169. package/dist/esm/components/auth/models/entities/role.model.js.map +0 -1
  170. package/dist/esm/components/auth/models/entities/user.model.d.ts +0 -28
  171. package/dist/esm/components/auth/models/entities/user.model.d.ts.map +0 -1
  172. package/dist/esm/components/auth/models/entities/user.model.js +0 -17
  173. package/dist/esm/components/auth/models/entities/user.model.js.map +0 -1
  174. package/dist/esm/components/controller/index.d.ts +0 -2
  175. package/dist/esm/components/controller/index.d.ts.map +0 -1
  176. package/dist/esm/components/controller/index.js +0 -7
  177. package/dist/esm/components/controller/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"mail.service.js","sourceRoot":"","sources":["../../../../../src/components/mail/services/mail.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { HTTP } from '@venizia/ignis-helpers/common';\nimport { BaseService } from '@venizia/ignis-kernel';\nimport type {\n IMailMessage,\n IMailSendResult,\n IMailService,\n IMailTemplateEngine,\n IMailTransport,\n TMailOptions,\n} from '../common';\nimport { MailDefaults, MailErrorCodes, MailErrors, MailKeys } from '../common';\nimport { inject } from '@/base/metadata';\nimport { getError, isApplicationError } from '@venizia/ignis-helpers/core';\nimport { type AnyType } from '@venizia/ignis-helpers/common';\nimport { executePromiseWithLimit } from '@venizia/ignis-helpers';\nimport { releaseAttachmentStreams, resolveMailAttachments } from '../utilities/attachment.utility';\nimport { assertMailBodyIsText } from '../utilities/body.utility';\n\nexport class MailService extends BaseService implements IMailService {\n constructor(\n @inject({ key: MailKeys.MAIL_OPTIONS })\n protected options: TMailOptions,\n @inject({ key: MailKeys.MAIL_TRANSPORT_INSTANCE })\n protected transport: IMailTransport,\n @inject({ key: MailKeys.MAIL_TEMPLATE_ENGINE, isOptional: true })\n protected templateEngine?: IMailTemplateEngine,\n ) {\n super({ scope: MailService.name });\n this.logger\n .for(this.constructor.name)\n .info('Mail service initialized with provider: %s', this.options.provider);\n }\n\n async send(message: IMailMessage): Promise<IMailSendResult> {\n try {\n this.validateMessage(message);\n\n const emailMessage: IMailMessage = {\n ...message,\n from: message.from ?? this.getDefaultFrom(),\n };\n\n // The one place the attachment policy applies: every transport receives bytes, never a path.\n if (message.attachments) {\n const result2 = await resolveMailAttachments({\n attachments: message.attachments,\n attachmentRoot: this.options.attachmentRoot,\n maxBytes: this.options.maxAttachmentBytes ?? MailDefaults.MAX_ATTACHMENT_BYTES,\n });\n emailMessage.attachments = result2;\n }\n\n this.logger.for(this.send.name).debug('Sending email to: %s', emailMessage.to);\n const result = await this.transport.send(emailMessage);\n\n if (result.success) {\n this.logger\n .for(this.send.name)\n .debug('Email sent successfully. MessageId: %s', result.messageId);\n } else {\n this.logger.for(this.send.name).debug('Email send failed: %s', result.error);\n }\n\n return result;\n } catch (error) {\n this.logger.for(this.send.name).error('Error sending email: %s', error);\n releaseAttachmentStreams({ attachments: message.attachments });\n\n // An error the framework already shaped carries its own status and message code; re-wrapping as 500 would report a caller mistake as a server fault.\n if (isApplicationError(error)) {\n throw error;\n }\n\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.SEND_FAILED,\n // The raw text can carry a file path or a provider detail; it rides in `cause`, which only development responses show.\n message: 'Failed to send email',\n cause: error,\n });\n }\n }\n\n async sendBatch(\n messages: IMailMessage[],\n options?: { concurrency?: number },\n ): Promise<IMailSendResult[]> {\n try {\n const concurrency = options?.concurrency ?? MailDefaults.BATCH_CONCURRENCY;\n this.logger\n .for(this.sendBatch.name)\n .info('Sending batch of %d emails with concurrency: %d', messages.length, concurrency);\n\n const tasks = messages.map((message) => async () => {\n try {\n const result = await this.send(message);\n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n });\n\n const results = await executePromiseWithLimit<IMailSendResult>({\n tasks,\n limit: concurrency,\n });\n\n const successCount = results.filter((r) => r.success).length;\n this.logger\n .for(this.sendBatch.name)\n .info(\n 'Batch send completed. Success: %d, Failed: %d',\n successCount,\n results.length - successCount,\n );\n\n return results;\n } catch (error) {\n this.logger.for(this.sendBatch.name).error('Error sending batch emails: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.BATCH_SEND_FAILED,\n message: 'Failed to send batch emails',\n cause: error,\n });\n }\n }\n\n async sendTemplate(opts: {\n templateName: string;\n data: Record<string, AnyType>;\n recipients: string | string[];\n options?: Partial<IMailMessage>;\n }): Promise<IMailSendResult> {\n const { templateName, data, recipients, options } = opts;\n\n try {\n if (!this.templateEngine) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.INVALID_CONFIGURATION,\n message: 'Template engine not configured',\n });\n }\n\n this.logger.for(this.sendTemplate.name).debug('Rendering template: %s', templateName);\n const html = this.templateEngine.render({\n templateName,\n data,\n requireValidate: options?.requireValidate,\n });\n\n const templateData = this.templateEngine.getTemplate(templateName);\n\n const message: IMailMessage = {\n to: recipients,\n subject:\n options?.subject ??\n (templateData?.subject !== undefined\n ? this.templateEngine.render({\n templateData: templateData.subject,\n data,\n requireValidate: options?.requireValidate,\n })\n : 'No Subject'),\n html,\n ...options,\n };\n\n const result = await this.send(message);\n return result;\n } catch (error) {\n this.logger.for(this.sendTemplate.name).error('Error sending template email: %s', error);\n throw error;\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n this.logger.for(this.verify.name).debug('Verifying mail transport connection');\n const isValid = await this.transport.verify();\n this.logger.for(this.verify.name).debug('Verification result: %s', isValid);\n return isValid;\n } catch (error) {\n this.logger.for(this.verify.name).error('Verification failed: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.VERIFICATION_FAILED,\n message: 'Mail transport verification failed',\n cause: error,\n });\n }\n }\n\n protected validateMessage(message: IMailMessage): void {\n if (!message.to || (Array.isArray(message.to) && message.to.length === 0)) {\n throw getError({\n error: MailErrors.INVALID_RECIPIENT,\n message: 'Recipient email address is required',\n });\n }\n\n if (!message.subject) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email subject is required',\n });\n }\n\n if (!message.text && !message.html) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email must have either text or html content',\n });\n }\n\n assertMailBodyIsText({ message });\n }\n\n protected getDefaultFrom(): string {\n const { from, fromName } = this.options;\n\n // A display name is only renderable around an address: `\"Name\" <undefined>` is not a sender.\n if (!from) {\n return MailDefaults.FALLBACK_FROM;\n }\n\n if (!fromName) {\n return from;\n }\n\n return `\"${fromName}\" <${from}>`;\n }\n}\n"]}
1
+ {"version":3,"file":"mail.service.js","sourceRoot":"","sources":["../../../../../src/components/mail/services/mail.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { HTTP } from '@venizia/ignis-helpers/common';\nimport { BaseService } from '@venizia/ignis-kernel';\nimport type {\n IMailMessage,\n IMailSendResult,\n IMailService,\n IMailTemplateEngine,\n IMailTransport,\n TMailOptions,\n} from '../common';\nimport { MailDefaults, MailErrorCodes, MailErrors, MailKeys } from '../common';\nimport { inject } from '@/base/metadata';\nimport { getError, isApplicationError } from '@venizia/ignis-helpers/core';\nimport { type AnyType } from '@venizia/ignis-helpers/common';\nimport { PromiseUtility } from '@venizia/ignis-helpers';\nimport { MailAttachmentUtility } from '../utilities/attachment.utility';\nimport { MailBodyUtility } from '../utilities/body.utility';\n\nexport class MailService extends BaseService implements IMailService {\n constructor(\n @inject({ key: MailKeys.MAIL_OPTIONS })\n protected options: TMailOptions,\n @inject({ key: MailKeys.MAIL_TRANSPORT_INSTANCE })\n protected transport: IMailTransport,\n @inject({ key: MailKeys.MAIL_TEMPLATE_ENGINE, isOptional: true })\n protected templateEngine?: IMailTemplateEngine,\n ) {\n super({ scope: MailService.name });\n this.logger\n .for(this.constructor.name)\n .info('Mail service initialized with provider: %s', this.options.provider);\n }\n\n async send(message: IMailMessage): Promise<IMailSendResult> {\n try {\n this.validateMessage(message);\n\n const emailMessage: IMailMessage = {\n ...message,\n from: message.from ?? this.getDefaultFrom(),\n };\n\n // The one place the attachment policy applies: every transport receives bytes, never a path.\n if (message.attachments) {\n const result2 = await MailAttachmentUtility.resolve({\n attachments: message.attachments,\n attachmentRoot: this.options.attachmentRoot,\n maxBytes: this.options.maxAttachmentBytes ?? MailDefaults.MAX_ATTACHMENT_BYTES,\n });\n emailMessage.attachments = result2;\n }\n\n this.logger.for(this.send.name).debug('Sending email to: %s', emailMessage.to);\n const result = await this.transport.send(emailMessage);\n\n if (result.success) {\n this.logger\n .for(this.send.name)\n .debug('Email sent successfully. MessageId: %s', result.messageId);\n } else {\n this.logger.for(this.send.name).debug('Email send failed: %s', result.error);\n }\n\n return result;\n } catch (error) {\n this.logger.for(this.send.name).error('Error sending email: %s', error);\n MailAttachmentUtility.release({ attachments: message.attachments });\n\n // An error the framework already shaped carries its own status and message code; re-wrapping as 500 would report a caller mistake as a server fault.\n if (isApplicationError(error)) {\n throw error;\n }\n\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.SEND_FAILED,\n // The raw text can carry a file path or a provider detail; it rides in `cause`, which only development responses show.\n message: 'Failed to send email',\n cause: error,\n });\n }\n }\n\n async sendBatch(\n messages: IMailMessage[],\n options?: { concurrency?: number },\n ): Promise<IMailSendResult[]> {\n try {\n const concurrency = options?.concurrency ?? MailDefaults.BATCH_CONCURRENCY;\n this.logger\n .for(this.sendBatch.name)\n .info('Sending batch of %d emails with concurrency: %d', messages.length, concurrency);\n\n const tasks = messages.map((message) => async () => {\n try {\n const result = await this.send(message);\n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n });\n\n const results = await PromiseUtility.executeWithLimit<IMailSendResult>({\n tasks,\n limit: concurrency,\n });\n\n const successCount = results.filter((r) => r.success).length;\n this.logger\n .for(this.sendBatch.name)\n .info(\n 'Batch send completed. Success: %d, Failed: %d',\n successCount,\n results.length - successCount,\n );\n\n return results;\n } catch (error) {\n this.logger.for(this.sendBatch.name).error('Error sending batch emails: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.BATCH_SEND_FAILED,\n message: 'Failed to send batch emails',\n cause: error,\n });\n }\n }\n\n async sendTemplate(opts: {\n templateName: string;\n data: Record<string, AnyType>;\n recipients: string | string[];\n options?: Partial<IMailMessage>;\n }): Promise<IMailSendResult> {\n const { templateName, data, recipients, options } = opts;\n\n try {\n if (!this.templateEngine) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.INVALID_CONFIGURATION,\n message: 'Template engine not configured',\n });\n }\n\n this.logger.for(this.sendTemplate.name).debug('Rendering template: %s', templateName);\n const html = this.templateEngine.render({\n templateName,\n data,\n requireValidate: options?.requireValidate,\n });\n\n const templateData = this.templateEngine.getTemplate(templateName);\n\n const message: IMailMessage = {\n to: recipients,\n subject:\n options?.subject ??\n (templateData?.subject !== undefined\n ? this.templateEngine.render({\n templateData: templateData.subject,\n data,\n requireValidate: options?.requireValidate,\n })\n : 'No Subject'),\n html,\n ...options,\n };\n\n const result = await this.send(message);\n return result;\n } catch (error) {\n this.logger.for(this.sendTemplate.name).error('Error sending template email: %s', error);\n throw error;\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n this.logger.for(this.verify.name).debug('Verifying mail transport connection');\n const isValid = await this.transport.verify();\n this.logger.for(this.verify.name).debug('Verification result: %s', isValid);\n return isValid;\n } catch (error) {\n this.logger.for(this.verify.name).error('Verification failed: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.VERIFICATION_FAILED,\n message: 'Mail transport verification failed',\n cause: error,\n });\n }\n }\n\n protected validateMessage(message: IMailMessage): void {\n if (!message.to || (Array.isArray(message.to) && message.to.length === 0)) {\n throw getError({\n error: MailErrors.INVALID_RECIPIENT,\n message: 'Recipient email address is required',\n });\n }\n\n if (!message.subject) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email subject is required',\n });\n }\n\n if (!message.text && !message.html) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email must have either text or html content',\n });\n }\n\n MailBodyUtility.assertIsText({ message });\n }\n\n protected getDefaultFrom(): string {\n const { from, fromName } = this.options;\n\n // A display name is only renderable around an address: `\"Name\" <undefined>` is not a sender.\n if (!from) {\n return MailDefaults.FALLBACK_FROM;\n }\n\n if (!fromName) {\n return from;\n }\n\n return `\"${fromName}\" <${from}>`;\n }\n}\n"]}
@@ -1,9 +1,13 @@
1
- /**
2
- * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`
3
- * is frequently a single comma-separated string (`"a@x.com, b@y.com"`), not an array - treating
4
- * it as one opaque address turns it into a single malformed recipient. Splitting only at
5
- * top-level commas (outside a quoted display name) keeps `"Doe, Jane" <jane@x.com>` intact while
6
- * still separating `"Doe, Jane" <jane@x.com>, bob@y.com` into its two mailboxes.
7
- */
8
- export declare function splitAddressList(value: string | string[]): string[];
1
+ export declare class MailAddressUtility {
2
+ /**
3
+ * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`
4
+ * is frequently a single comma-separated string (`"a@x.com, b@y.com"`), not an array - treating
5
+ * it as one opaque address turns it into a single malformed recipient. Splitting only at
6
+ * top-level commas (outside a quoted display name) keeps `"Doe, Jane" <jane@x.com>` intact while
7
+ * still separating `"Doe, Jane" <jane@x.com>, bob@y.com` into its two mailboxes.
8
+ */
9
+ static splitList(opts: {
10
+ value: string | string[];
11
+ }): string[];
12
+ }
9
13
  //# sourceMappingURL=address.utility.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"address.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/address.utility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CA8BnE"}
1
+ {"version":3,"file":"address.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/address.utility.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAkB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,GAAG,MAAM,EAAE;CAgC/D"}
@@ -1,33 +1,36 @@
1
- /**
2
- * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`
3
- * is frequently a single comma-separated string (`"a@x.com, b@y.com"`), not an array - treating
4
- * it as one opaque address turns it into a single malformed recipient. Splitting only at
5
- * top-level commas (outside a quoted display name) keeps `"Doe, Jane" <jane@x.com>` intact while
6
- * still separating `"Doe, Jane" <jane@x.com>, bob@y.com` into its two mailboxes.
7
- */
8
- export function splitAddressList(value) {
9
- const items = Array.isArray(value) ? value : [value];
10
- const addresses = [];
11
- for (const item of items) {
12
- let current = '';
13
- let inQuotes = false;
14
- for (const char of item) {
15
- if (char === '"') {
16
- inQuotes = !inQuotes;
17
- }
18
- if (char === ',' && !inQuotes) {
19
- if (current.trim()) {
20
- addresses.push(current.trim());
1
+ export class MailAddressUtility {
2
+ /**
3
+ * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`
4
+ * is frequently a single comma-separated string (`"a@x.com, b@y.com"`), not an array - treating
5
+ * it as one opaque address turns it into a single malformed recipient. Splitting only at
6
+ * top-level commas (outside a quoted display name) keeps `"Doe, Jane" <jane@x.com>` intact while
7
+ * still separating `"Doe, Jane" <jane@x.com>, bob@y.com` into its two mailboxes.
8
+ */
9
+ static splitList(opts) {
10
+ const { value } = opts;
11
+ const items = Array.isArray(value) ? value : [value];
12
+ const addresses = [];
13
+ for (const item of items) {
14
+ let current = '';
15
+ let inQuotes = false;
16
+ for (const char of item) {
17
+ if (char === '"') {
18
+ inQuotes = !inQuotes;
19
+ }
20
+ if (char === ',' && !inQuotes) {
21
+ if (current.trim()) {
22
+ addresses.push(current.trim());
23
+ }
24
+ current = '';
25
+ continue;
21
26
  }
22
- current = '';
23
- continue;
27
+ current += char;
28
+ }
29
+ if (current.trim()) {
30
+ addresses.push(current.trim());
24
31
  }
25
- current += char;
26
- }
27
- if (current.trim()) {
28
- addresses.push(current.trim());
29
32
  }
33
+ return addresses;
30
34
  }
31
- return addresses;
32
35
  }
33
36
  //# sourceMappingURL=address.utility.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"address.utility.js","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/address.utility.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,QAAQ,GAAG,CAAC,QAAQ,CAAC;YACvB,CAAC;YAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC9B,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;oBACnB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBACjC,CAAC;gBACD,OAAO,GAAG,EAAE,CAAC;gBACb,SAAS;YACX,CAAC;YAED,OAAO,IAAI,IAAI,CAAC;QAClB,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["/**\n * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`\n * is frequently a single comma-separated string (`\"a@x.com, b@y.com\"`), not an array - treating\n * it as one opaque address turns it into a single malformed recipient. Splitting only at\n * top-level commas (outside a quoted display name) keeps `\"Doe, Jane\" <jane@x.com>` intact while\n * still separating `\"Doe, Jane\" <jane@x.com>, bob@y.com` into its two mailboxes.\n */\nexport function splitAddressList(value: string | string[]): string[] {\n const items = Array.isArray(value) ? value : [value];\n const addresses: string[] = [];\n\n for (const item of items) {\n let current = '';\n let inQuotes = false;\n\n for (const char of item) {\n if (char === '\"') {\n inQuotes = !inQuotes;\n }\n\n if (char === ',' && !inQuotes) {\n if (current.trim()) {\n addresses.push(current.trim());\n }\n current = '';\n continue;\n }\n\n current += char;\n }\n\n if (current.trim()) {\n addresses.push(current.trim());\n }\n }\n\n return addresses;\n}\n"]}
1
+ {"version":3,"file":"address.utility.js","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/address.utility.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,kBAAkB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CAAC,IAAkC;QACjD,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QACvB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,OAAO,GAAG,EAAE,CAAC;YACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;YAErB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;gBACxB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;oBACjB,QAAQ,GAAG,CAAC,QAAQ,CAAC;gBACvB,CAAC;gBAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAC9B,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;wBACnB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;oBACjC,CAAC;oBACD,OAAO,GAAG,EAAE,CAAC;oBACb,SAAS;gBACX,CAAC;gBAED,OAAO,IAAI,IAAI,CAAC;YAClB,CAAC;YAED,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;CACF","sourcesContent":["export class MailAddressUtility {\n /**\n * Splits one or more RFC 5322 mailbox strings into individual addresses. A caller-supplied `to`\n * is frequently a single comma-separated string (`\"a@x.com, b@y.com\"`), not an array - treating\n * it as one opaque address turns it into a single malformed recipient. Splitting only at\n * top-level commas (outside a quoted display name) keeps `\"Doe, Jane\" <jane@x.com>` intact while\n * still separating `\"Doe, Jane\" <jane@x.com>, bob@y.com` into its two mailboxes.\n */\n static splitList(opts: { value: string | string[] }): string[] {\n const { value } = opts;\n const items = Array.isArray(value) ? value : [value];\n const addresses: string[] = [];\n\n for (const item of items) {\n let current = '';\n let inQuotes = false;\n\n for (const char of item) {\n if (char === '\"') {\n inQuotes = !inQuotes;\n }\n\n if (char === ',' && !inQuotes) {\n if (current.trim()) {\n addresses.push(current.trim());\n }\n current = '';\n continue;\n }\n\n current += char;\n }\n\n if (current.trim()) {\n addresses.push(current.trim());\n }\n }\n\n return addresses;\n }\n}\n"]}
@@ -1,31 +1,33 @@
1
1
  import { type IMailAttachment } from '../common/index.js';
2
- /**
3
- * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise
4
- * (nodemailer's convention); `null` counts as absent. A `path` is read only inside
5
- * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what
6
- * earlier attachments already took.
7
- */
8
- export declare const readAttachmentContent: (opts: {
9
- attachment: IMailAttachment;
10
- attachmentRoot?: string;
11
- maxBytes?: number;
12
- usedBytes?: number;
13
- }) => Promise<Buffer>;
14
- /**
15
- * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a
16
- * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An
17
- * attachment read from `path` keeps the file's name and, when none is given, its content type.
18
- */
19
- export declare const resolveMailAttachments: (opts: {
20
- attachments: IMailAttachment[];
21
- attachmentRoot?: string;
22
- maxBytes?: number;
23
- }) => Promise<IMailAttachment[]>;
24
- /**
25
- * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor
26
- * open. A drained stream is already closed; releasing it again is harmless.
27
- */
28
- export declare const releaseAttachmentStreams: (opts: {
29
- attachments?: IMailAttachment[];
30
- }) => void;
2
+ export declare class MailAttachmentUtility {
3
+ /**
4
+ * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise
5
+ * (nodemailer's convention); `null` counts as absent. A `path` is read only inside
6
+ * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what
7
+ * earlier attachments already took.
8
+ */
9
+ static readContent(opts: {
10
+ attachment: IMailAttachment;
11
+ attachmentRoot?: string;
12
+ maxBytes?: number;
13
+ usedBytes?: number;
14
+ }): Promise<Buffer>;
15
+ /**
16
+ * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a
17
+ * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An
18
+ * attachment read from `path` keeps the file's name and, when none is given, its content type.
19
+ */
20
+ static resolve(opts: {
21
+ attachments: IMailAttachment[];
22
+ attachmentRoot?: string;
23
+ maxBytes?: number;
24
+ }): Promise<IMailAttachment[]>;
25
+ /**
26
+ * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor
27
+ * open. A drained stream is already closed; releasing it again is harmless.
28
+ */
29
+ static release(opts: {
30
+ attachments?: IMailAttachment[];
31
+ }): void;
32
+ }
31
33
  //# sourceMappingURL=attachment.utility.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AA2J7D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM;IAChD,UAAU,EAAE,eAAe,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,KAAG,OAAO,CAAC,MAAM,CAgCjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAU,MAAM;IACjD,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,KAAG,OAAO,CAAC,eAAe,EAAE,CAwC5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM;IAAE,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAAE,KAAG,IA0BpF,CAAC"}
1
+ {"version":3,"file":"attachment.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AA+J7D,qBAAa,qBAAqB;IAChC;;;;;OAKG;WACU,WAAW,CAAC,IAAI,EAAE;QAC7B,UAAU,EAAE,eAAe,CAAC;QAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,MAAM,CAAC;IAsCnB;;;;OAIG;WACU,OAAO,CAAC,IAAI,EAAE;QACzB,WAAW,EAAE,eAAe,EAAE,CAAC;QAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA4C9B;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;KAAE,GAAG,IAAI;CA2BhE"}
@@ -91,9 +91,14 @@ const readConfinedFile = async (opts) => {
91
91
  });
92
92
  }
93
93
  const filePath = await resolveConfinedPath({ path, attachmentRoot });
94
- const handle = await open(filePath, OPEN_FLAGS).catch((error) => {
94
+ let handle;
95
+ try {
96
+ const opened = await open(filePath, OPEN_FLAGS);
97
+ handle = opened;
98
+ }
99
+ catch (error) {
95
100
  throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });
96
- });
101
+ }
97
102
  try {
98
103
  const stats = await handle.stat();
99
104
  if (!stats.isFile()) {
@@ -114,97 +119,103 @@ const readConfinedFile = async (opts) => {
114
119
  await handle.close();
115
120
  }
116
121
  };
117
- /**
118
- * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise
119
- * (nodemailer's convention); `null` counts as absent. A `path` is read only inside
120
- * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what
121
- * earlier attachments already took.
122
- */
123
- export const readAttachmentContent = async (opts) => {
124
- const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;
125
- const { content } = attachment;
126
- if (content !== undefined && content !== null) {
127
- if (typeof content === 'string') {
128
- const encoding = attachment.encoding ?? 'utf-8';
129
- assertWithinLimit({ byteLength: Buffer.byteLength(content, encoding), usedBytes, maxBytes });
130
- return Buffer.from(content, encoding);
131
- }
132
- if (content instanceof Uint8Array) {
133
- assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });
134
- return toBuffer(content);
122
+ export class MailAttachmentUtility {
123
+ /**
124
+ * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise
125
+ * (nodemailer's convention); `null` counts as absent. A `path` is read only inside
126
+ * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what
127
+ * earlier attachments already took.
128
+ */
129
+ static async readContent(opts) {
130
+ const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;
131
+ const { content } = attachment;
132
+ if (content !== undefined && content !== null) {
133
+ if (typeof content === 'string') {
134
+ const encoding = attachment.encoding ?? 'utf-8';
135
+ assertWithinLimit({
136
+ byteLength: Buffer.byteLength(content, encoding),
137
+ usedBytes,
138
+ maxBytes,
139
+ });
140
+ return Buffer.from(content, encoding);
141
+ }
142
+ if (content instanceof Uint8Array) {
143
+ assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });
144
+ return toBuffer(content);
145
+ }
146
+ if (!isAttachmentStream(content)) {
147
+ throw getError({
148
+ error: MailErrors.INVALID_CONFIGURATION,
149
+ message: 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',
150
+ });
151
+ }
152
+ return drainStream({ stream: content, usedBytes, maxBytes });
135
153
  }
136
- if (!isAttachmentStream(content)) {
137
- throw getError({
138
- error: MailErrors.INVALID_CONFIGURATION,
139
- message: 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',
140
- });
154
+ if (attachment.path !== undefined && attachment.path !== null) {
155
+ return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });
141
156
  }
142
- return drainStream({ stream: content, usedBytes, maxBytes });
143
- }
144
- if (attachment.path !== undefined && attachment.path !== null) {
145
- return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });
157
+ return Buffer.alloc(0);
146
158
  }
147
- return Buffer.alloc(0);
148
- };
149
- /**
150
- * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a
151
- * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An
152
- * attachment read from `path` keeps the file's name and, when none is given, its content type.
153
- */
154
- export const resolveMailAttachments = async (opts) => {
155
- const { attachments, attachmentRoot, maxBytes } = opts;
156
- const resolved = [];
157
- let usedBytes = 0;
158
- // Sequential on purpose: each read spends the budget the previous ones left.
159
- for (const attachment of attachments) {
160
- const { path, href, raw, content, encoding, ...rest } = attachment;
161
- // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.
162
- if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {
163
- throw buildPathRefusedError({
164
- reason: 'attachment.href and attachment.raw are not supported | pass the bytes as content',
159
+ /**
160
+ * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a
161
+ * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An
162
+ * attachment read from `path` keeps the file's name and, when none is given, its content type.
163
+ */
164
+ static async resolve(opts) {
165
+ const { attachments, attachmentRoot, maxBytes } = opts;
166
+ const resolved = [];
167
+ let usedBytes = 0;
168
+ // Sequential on purpose: each read spends the budget the previous ones left.
169
+ for (const attachment of attachments) {
170
+ const { path, href, raw, content, encoding, ...rest } = attachment;
171
+ // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.
172
+ if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {
173
+ throw buildPathRefusedError({
174
+ reason: 'attachment.href and attachment.raw are not supported | pass the bytes as content',
175
+ });
176
+ }
177
+ const bytes = await MailAttachmentUtility.readContent({
178
+ attachment: { content, encoding, path },
179
+ attachmentRoot,
180
+ maxBytes,
181
+ usedBytes,
182
+ });
183
+ usedBytes += bytes.byteLength;
184
+ const isReadFromPath = (content === undefined || content === null) && typeof path === 'string';
185
+ const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);
186
+ const contentType = rest.contentType ??
187
+ (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);
188
+ resolved.push({
189
+ ...rest,
190
+ ...(filename === undefined ? {} : { filename }),
191
+ ...(contentType === undefined ? {} : { contentType }),
192
+ content: bytes,
165
193
  });
166
194
  }
167
- const bytes = await readAttachmentContent({
168
- attachment: { content, encoding, path },
169
- attachmentRoot,
170
- maxBytes,
171
- usedBytes,
172
- });
173
- usedBytes += bytes.byteLength;
174
- const isReadFromPath = (content === undefined || content === null) && typeof path === 'string';
175
- const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);
176
- const contentType = rest.contentType ??
177
- (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);
178
- resolved.push({
179
- ...rest,
180
- ...(filename === undefined ? {} : { filename }),
181
- ...(contentType === undefined ? {} : { contentType }),
182
- content: bytes,
183
- });
195
+ return resolved;
184
196
  }
185
- return resolved;
186
- };
187
- /**
188
- * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor
189
- * open. A drained stream is already closed; releasing it again is harmless.
190
- */
191
- export const releaseAttachmentStreams = (opts) => {
192
- for (const attachment of opts.attachments ?? []) {
193
- const { content } = attachment;
194
- if (content instanceof ReadableStream) {
195
- if (!content.locked) {
196
- content.cancel().catch((error) => {
197
- logger.warn('[releaseAttachmentStreams] Cancelling a web stream failed | error: %s', error);
198
- });
197
+ /**
198
+ * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor
199
+ * open. A drained stream is already closed; releasing it again is harmless.
200
+ */
201
+ static release(opts) {
202
+ for (const attachment of opts.attachments ?? []) {
203
+ const { content } = attachment;
204
+ if (content instanceof ReadableStream) {
205
+ if (!content.locked) {
206
+ content.cancel().catch((error) => {
207
+ logger.warn('[MailAttachmentUtility.release] Cancelling a web stream failed | error: %s', error);
208
+ });
209
+ }
210
+ continue;
211
+ }
212
+ if (typeof content === 'object' &&
213
+ content !== null &&
214
+ 'destroy' in content &&
215
+ typeof content.destroy === 'function') {
216
+ content.destroy();
199
217
  }
200
- continue;
201
- }
202
- if (typeof content === 'object' &&
203
- content !== null &&
204
- 'destroy' in content &&
205
- typeof content.destroy === 'function') {
206
- content.destroy();
207
218
  }
208
219
  }
209
- };
220
+ }
210
221
  //# sourceMappingURL=attachment.utility.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment.utility.js","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAwB,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAE5D,0FAA0F;AAC1F,+EAA+E;AAC/E,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;AAElG,yFAAyF;AACzF,MAAM,mBAAmB,GACvB,qGAAqG,CAAC;AAIxG,MAAM,qBAAqB,GAAG,CAAC,IAAyC,EAAE,EAAE;IAC1E,OAAO,QAAQ,CAAC;QACd,KAAK,EAAE,UAAU,CAAC,uBAAuB;QACzC,OAAO,EAAE,kCAAkC,IAAI,CAAC,MAAM,EAAE;QACxD,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,IAA0B,EAAE,EAAE;IACxD,OAAO,QAAQ,CAAC;QACd,KAAK,EAAE,UAAU,CAAC,oBAAoB;QACtC,OAAO,EAAE,iDAAiD,IAAI,CAAC,QAAQ,iGAAiG;KACzK,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,IAAkE,EAAE,EAAE;IAC/F,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAEjD,IAAI,QAAQ,KAAK,SAAS,IAAI,SAAS,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC;QACjE,OAAO;IACT,CAAC;IAED,MAAM,kBAAkB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAA8B,EAAE;IACxE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,aAAa,IAAI,KAAK,CAAC;AACtF,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAA0B,EAAU,EAAE;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC3B,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,6FAA6F;AAC7F,MAAM,WAAW,GAAG,KAAK,EAAE,IAI1B,EAAmB,EAAE;IACpB,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,WAAW,IAAI,MAAM,CAAC,UAAU,CAAC;QAEjC,mFAAmF;QACnF,iBAAiB,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,KAAK,EAAE,IAGlC,EAAmB,EAAE;IACpB,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEtC,IAAI,QAAgB,CAAC;IACrB,IAAI,QAAgB,CAAC;IAErB,mGAAmG;IACnG,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACjC,QAAQ,CAAC,cAAc,CAAC;YACxB,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;SACxC,CAAC,CAAC;QACH,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,SAAS,GACb,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;IAElG,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,EAAE,IAK/B,EAAmB,EAAE;IACpB,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE3D,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,qBAAqB,CAAC;YAC1B,MAAM,EACJ,4IAA4I;SAC/I,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9D,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACpB,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,qFAAqF;QACrF,iBAAiB,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEnE,2FAA2F;QAC3F,qBAAqB;QACrB,MAAM,MAAM,GAAG,MAAM,CAAC,gBAAgB,CAAC;YACrC,SAAS,EAAE,KAAK;YAChB,GAAG,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,SAAS;SAC9D,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClE,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAAE,IAK3C,EAAmB,EAAE;IACpB,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;IACrE,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAE/B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,OAAO,CAAC;YAChD,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC7F,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,OAAO,YAAY,UAAU,EAAE,CAAC;YAClC,iBAAiB,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC3E,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YACjC,MAAM,QAAQ,CAAC;gBACb,KAAK,EAAE,UAAU,CAAC,qBAAqB;gBACvC,OAAO,EACL,mGAAmG;aACtG,CAAC,CAAC;QACL,CAAC;QAED,OAAO,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9D,OAAO,gBAAgB,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAAE,IAI5C,EAA8B,EAAE;IAC/B,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvD,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,6EAA6E;IAC7E,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU,CAAC;QAEnE,qFAAqF;QACrF,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC;YACjF,MAAM,qBAAqB,CAAC;gBAC1B,MAAM,EAAE,kFAAkF;aAC3F,CAAC,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC;YACxC,UAAU,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;YACvC,cAAc;YACd,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;QAEH,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC;QAE9B,MAAM,cAAc,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC;QAC/F,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,WAAW,GACf,IAAI,CAAC,WAAW;YAChB,CAAC,cAAc,IAAI,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAEpF,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,IAAI;YACP,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;YAC/C,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;YACrD,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAyC,EAAQ,EAAE;IAC1F,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;QAE/B,IAAI,OAAO,YAAY,cAAc,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACpB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC/B,MAAM,CAAC,IAAI,CACT,uEAAuE,EACvE,KAAK,CACN,CAAC;gBACJ,CAAC,CAAC,CAAC;YACL,CAAC;YAED,SAAS;QACX,CAAC;QAED,IACE,OAAO,OAAO,KAAK,QAAQ;YAC3B,OAAO,KAAK,IAAI;YAChB,SAAS,IAAI,OAAO;YACpB,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU,EACrC,CAAC;YACD,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { constants } from 'node:fs';\nimport { open, realpath } from 'node:fs/promises';\nimport { basename, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { ContentTypeTable } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { LoggerFactory } from '@venizia/ignis-helpers';\nimport { MailErrors, type IMailAttachment } from '../common';\n\nconst logger = LoggerFactory.getLogger(['MailAttachments']);\n\n// O_NONBLOCK: opening a FIFO must not wait for a writer; the fstat check then refuses it.\n// Windows has neither flag; there the realpath containment check stands alone.\nconst OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);\n\n// One reason for missing, directory, unreadable and escaping paths: no existence oracle.\nconst PATH_OUTSIDE_REASON =\n 'attachment.path must name a readable file inside attachmentRoot | pass the bytes as content instead';\n\ntype TAttachmentStream = AsyncIterable<Uint8Array | string>;\n\nconst buildPathRefusedError = (opts: { reason: string; cause?: unknown }) => {\n return getError({\n error: MailErrors.ATTACHMENT_PATH_REFUSED,\n message: `Mail attachment path refused | ${opts.reason}`,\n cause: opts.cause,\n });\n};\n\nconst buildTooLargeError = (opts: { maxBytes: number }) => {\n return getError({\n error: MailErrors.ATTACHMENT_TOO_LARGE,\n message: `Mail attachments are over maxAttachmentBytes (${opts.maxBytes} bytes per message) | raise maxAttachmentBytes on the mail options, or send smaller attachments`,\n });\n};\n\nconst assertWithinLimit = (opts: { byteLength: number; usedBytes: number; maxBytes?: number }) => {\n const { byteLength, usedBytes, maxBytes } = opts;\n\n if (maxBytes === undefined || usedBytes + byteLength <= maxBytes) {\n return;\n }\n\n throw buildTooLargeError({ maxBytes });\n};\n\nconst isAttachmentStream = (value: unknown): value is TAttachmentStream => {\n return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;\n};\n\nconst toBuffer = (chunk: Uint8Array | string): Buffer => {\n if (typeof chunk === 'string') {\n return Buffer.from(chunk);\n }\n\n return Buffer.isBuffer(chunk)\n ? chunk\n : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n};\n\n/** Throwing out of `for await` destroys a Node Readable and cancels a web ReadableStream. */\nconst drainStream = async (opts: {\n stream: TAttachmentStream;\n usedBytes: number;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { stream, usedBytes, maxBytes } = opts;\n const chunks: Buffer[] = [];\n let streamBytes = 0;\n\n for await (const chunk of stream) {\n const buffer = toBuffer(chunk);\n streamBytes += buffer.byteLength;\n\n // Per chunk: a stream has no length up front, and the rest must never be buffered.\n assertWithinLimit({ byteLength: streamBytes, usedBytes, maxBytes });\n chunks.push(buffer);\n }\n\n return Buffer.concat(chunks, streamBytes);\n};\n\n/**\n * Both sides go through realpath, so neither `..` nor a symlink can leave the root. Not closed: a\n * directory inside the root swapped for a symlink between this check and `open` - that takes write\n * access inside the root, so keep the root writable only by what the application trusts.\n */\nconst resolveConfinedPath = async (opts: {\n path: string;\n attachmentRoot: string;\n}): Promise<string> => {\n const { path, attachmentRoot } = opts;\n\n let rootPath: string;\n let filePath: string;\n\n // `resolve` throws synchronously on a non-string `path` from an untyped caller; it lands here too.\n try {\n const resolved = await Promise.all([\n realpath(attachmentRoot),\n realpath(resolve(attachmentRoot, path)),\n ]);\n [rootPath, filePath] = resolved;\n } catch (error) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n }\n\n const relation = relative(rootPath, filePath);\n const isOutside =\n relation === '' || relation === '..' || relation.startsWith(`..${sep}`) || isAbsolute(relation);\n\n if (isOutside) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n return filePath;\n};\n\nconst readConfinedFile = async (opts: {\n path: string;\n usedBytes: number;\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { path, usedBytes, attachmentRoot, maxBytes } = opts;\n\n if (!attachmentRoot) {\n throw buildPathRefusedError({\n reason:\n 'attachment.path is read only under attachmentRoot, which is not set | set attachmentRoot on the mail options, or pass the bytes as content',\n });\n }\n\n const filePath = await resolveConfinedPath({ path, attachmentRoot });\n const handle = await open(filePath, OPEN_FLAGS).catch((error) => {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n });\n\n try {\n const stats = await handle.stat();\n\n if (!stats.isFile()) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n // Sized before reading, so a file known to be oversized is never opened for reading.\n assertWithinLimit({ byteLength: stats.size, usedBytes, maxBytes });\n\n // Counted while read, and read no further than one byte past the budget: the file may have\n // grown since fstat.\n const stream = handle.createReadStream({\n autoClose: false,\n end: maxBytes === undefined ? Infinity : maxBytes - usedBytes,\n });\n\n const result = await drainStream({ stream, usedBytes, maxBytes });\n return result;\n } finally {\n await handle.close();\n }\n};\n\n/**\n * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise\n * (nodemailer's convention); `null` counts as absent. A `path` is read only inside\n * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what\n * earlier attachments already took.\n */\nexport const readAttachmentContent = async (opts: {\n attachment: IMailAttachment;\n attachmentRoot?: string;\n maxBytes?: number;\n usedBytes?: number;\n}): Promise<Buffer> => {\n const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;\n const { content } = attachment;\n\n if (content !== undefined && content !== null) {\n if (typeof content === 'string') {\n const encoding = attachment.encoding ?? 'utf-8';\n assertWithinLimit({ byteLength: Buffer.byteLength(content, encoding), usedBytes, maxBytes });\n return Buffer.from(content, encoding);\n }\n\n if (content instanceof Uint8Array) {\n assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });\n return toBuffer(content);\n }\n\n if (!isAttachmentStream(content)) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message:\n 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',\n });\n }\n\n return drainStream({ stream: content, usedBytes, maxBytes });\n }\n\n if (attachment.path !== undefined && attachment.path !== null) {\n return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });\n }\n\n return Buffer.alloc(0);\n};\n\n/**\n * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a\n * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An\n * attachment read from `path` keeps the file's name and, when none is given, its content type.\n */\nexport const resolveMailAttachments = async (opts: {\n attachments: IMailAttachment[];\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<IMailAttachment[]> => {\n const { attachments, attachmentRoot, maxBytes } = opts;\n const resolved: IMailAttachment[] = [];\n let usedBytes = 0;\n\n // Sequential on purpose: each read spends the budget the previous ones left.\n for (const attachment of attachments) {\n const { path, href, raw, content, encoding, ...rest } = attachment;\n\n // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.\n if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {\n throw buildPathRefusedError({\n reason: 'attachment.href and attachment.raw are not supported | pass the bytes as content',\n });\n }\n\n const bytes = await readAttachmentContent({\n attachment: { content, encoding, path },\n attachmentRoot,\n maxBytes,\n usedBytes,\n });\n\n usedBytes += bytes.byteLength;\n\n const isReadFromPath = (content === undefined || content === null) && typeof path === 'string';\n const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);\n const contentType =\n rest.contentType ??\n (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);\n\n resolved.push({\n ...rest,\n ...(filename === undefined ? {} : { filename }),\n ...(contentType === undefined ? {} : { contentType }),\n content: bytes,\n });\n }\n\n return resolved;\n};\n\n/**\n * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor\n * open. A drained stream is already closed; releasing it again is harmless.\n */\nexport const releaseAttachmentStreams = (opts: { attachments?: IMailAttachment[] }): void => {\n for (const attachment of opts.attachments ?? []) {\n const { content } = attachment;\n\n if (content instanceof ReadableStream) {\n if (!content.locked) {\n content.cancel().catch((error) => {\n logger.warn(\n '[releaseAttachmentStreams] Cancelling a web stream failed | error: %s',\n error,\n );\n });\n }\n\n continue;\n }\n\n if (\n typeof content === 'object' &&\n content !== null &&\n 'destroy' in content &&\n typeof content.destroy === 'function'\n ) {\n content.destroy();\n }\n }\n};\n"]}
1
+ {"version":3,"file":"attachment.utility.js","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from 'node:fs';\nimport { open, realpath, type FileHandle } from 'node:fs/promises';\nimport { basename, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { ContentTypeTable } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { LoggerFactory } from '@venizia/ignis-helpers';\nimport { MailErrors, type IMailAttachment } from '../common';\n\nconst logger = LoggerFactory.getLogger(['MailAttachments']);\n\n// O_NONBLOCK: opening a FIFO must not wait for a writer; the fstat check then refuses it.\n// Windows has neither flag; there the realpath containment check stands alone.\nconst OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);\n\n// One reason for missing, directory, unreadable and escaping paths: no existence oracle.\nconst PATH_OUTSIDE_REASON =\n 'attachment.path must name a readable file inside attachmentRoot | pass the bytes as content instead';\n\ntype TAttachmentStream = AsyncIterable<Uint8Array | string>;\n\nconst buildPathRefusedError = (opts: { reason: string; cause?: unknown }) => {\n return getError({\n error: MailErrors.ATTACHMENT_PATH_REFUSED,\n message: `Mail attachment path refused | ${opts.reason}`,\n cause: opts.cause,\n });\n};\n\nconst buildTooLargeError = (opts: { maxBytes: number }) => {\n return getError({\n error: MailErrors.ATTACHMENT_TOO_LARGE,\n message: `Mail attachments are over maxAttachmentBytes (${opts.maxBytes} bytes per message) | raise maxAttachmentBytes on the mail options, or send smaller attachments`,\n });\n};\n\nconst assertWithinLimit = (opts: { byteLength: number; usedBytes: number; maxBytes?: number }) => {\n const { byteLength, usedBytes, maxBytes } = opts;\n\n if (maxBytes === undefined || usedBytes + byteLength <= maxBytes) {\n return;\n }\n\n throw buildTooLargeError({ maxBytes });\n};\n\nconst isAttachmentStream = (value: unknown): value is TAttachmentStream => {\n return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;\n};\n\nconst toBuffer = (chunk: Uint8Array | string): Buffer => {\n if (typeof chunk === 'string') {\n return Buffer.from(chunk);\n }\n\n return Buffer.isBuffer(chunk)\n ? chunk\n : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n};\n\n/** Throwing out of `for await` destroys a Node Readable and cancels a web ReadableStream. */\nconst drainStream = async (opts: {\n stream: TAttachmentStream;\n usedBytes: number;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { stream, usedBytes, maxBytes } = opts;\n const chunks: Buffer[] = [];\n let streamBytes = 0;\n\n for await (const chunk of stream) {\n const buffer = toBuffer(chunk);\n streamBytes += buffer.byteLength;\n\n // Per chunk: a stream has no length up front, and the rest must never be buffered.\n assertWithinLimit({ byteLength: streamBytes, usedBytes, maxBytes });\n chunks.push(buffer);\n }\n\n return Buffer.concat(chunks, streamBytes);\n};\n\n/**\n * Both sides go through realpath, so neither `..` nor a symlink can leave the root. Not closed: a\n * directory inside the root swapped for a symlink between this check and `open` - that takes write\n * access inside the root, so keep the root writable only by what the application trusts.\n */\nconst resolveConfinedPath = async (opts: {\n path: string;\n attachmentRoot: string;\n}): Promise<string> => {\n const { path, attachmentRoot } = opts;\n\n let rootPath: string;\n let filePath: string;\n\n // `resolve` throws synchronously on a non-string `path` from an untyped caller; it lands here too.\n try {\n const resolved = await Promise.all([\n realpath(attachmentRoot),\n realpath(resolve(attachmentRoot, path)),\n ]);\n [rootPath, filePath] = resolved;\n } catch (error) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n }\n\n const relation = relative(rootPath, filePath);\n const isOutside =\n relation === '' || relation === '..' || relation.startsWith(`..${sep}`) || isAbsolute(relation);\n\n if (isOutside) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n return filePath;\n};\n\nconst readConfinedFile = async (opts: {\n path: string;\n usedBytes: number;\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { path, usedBytes, attachmentRoot, maxBytes } = opts;\n\n if (!attachmentRoot) {\n throw buildPathRefusedError({\n reason:\n 'attachment.path is read only under attachmentRoot, which is not set | set attachmentRoot on the mail options, or pass the bytes as content',\n });\n }\n\n const filePath = await resolveConfinedPath({ path, attachmentRoot });\n let handle: FileHandle;\n try {\n const opened = await open(filePath, OPEN_FLAGS);\n handle = opened;\n } catch (error) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n }\n\n try {\n const stats = await handle.stat();\n\n if (!stats.isFile()) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n // Sized before reading, so a file known to be oversized is never opened for reading.\n assertWithinLimit({ byteLength: stats.size, usedBytes, maxBytes });\n\n // Counted while read, and read no further than one byte past the budget: the file may have\n // grown since fstat.\n const stream = handle.createReadStream({\n autoClose: false,\n end: maxBytes === undefined ? Infinity : maxBytes - usedBytes,\n });\n\n const result = await drainStream({ stream, usedBytes, maxBytes });\n return result;\n } finally {\n await handle.close();\n }\n};\n\nexport class MailAttachmentUtility {\n /**\n * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise\n * (nodemailer's convention); `null` counts as absent. A `path` is read only inside\n * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what\n * earlier attachments already took.\n */\n static async readContent(opts: {\n attachment: IMailAttachment;\n attachmentRoot?: string;\n maxBytes?: number;\n usedBytes?: number;\n }): Promise<Buffer> {\n const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;\n const { content } = attachment;\n\n if (content !== undefined && content !== null) {\n if (typeof content === 'string') {\n const encoding = attachment.encoding ?? 'utf-8';\n assertWithinLimit({\n byteLength: Buffer.byteLength(content, encoding),\n usedBytes,\n maxBytes,\n });\n return Buffer.from(content, encoding);\n }\n\n if (content instanceof Uint8Array) {\n assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });\n return toBuffer(content);\n }\n\n if (!isAttachmentStream(content)) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message:\n 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',\n });\n }\n\n return drainStream({ stream: content, usedBytes, maxBytes });\n }\n\n if (attachment.path !== undefined && attachment.path !== null) {\n return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });\n }\n\n return Buffer.alloc(0);\n }\n\n /**\n * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a\n * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An\n * attachment read from `path` keeps the file's name and, when none is given, its content type.\n */\n static async resolve(opts: {\n attachments: IMailAttachment[];\n attachmentRoot?: string;\n maxBytes?: number;\n }): Promise<IMailAttachment[]> {\n const { attachments, attachmentRoot, maxBytes } = opts;\n const resolved: IMailAttachment[] = [];\n let usedBytes = 0;\n\n // Sequential on purpose: each read spends the budget the previous ones left.\n for (const attachment of attachments) {\n const { path, href, raw, content, encoding, ...rest } = attachment;\n\n // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.\n if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {\n throw buildPathRefusedError({\n reason:\n 'attachment.href and attachment.raw are not supported | pass the bytes as content',\n });\n }\n\n const bytes = await MailAttachmentUtility.readContent({\n attachment: { content, encoding, path },\n attachmentRoot,\n maxBytes,\n usedBytes,\n });\n\n usedBytes += bytes.byteLength;\n\n const isReadFromPath =\n (content === undefined || content === null) && typeof path === 'string';\n const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);\n const contentType =\n rest.contentType ??\n (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);\n\n resolved.push({\n ...rest,\n ...(filename === undefined ? {} : { filename }),\n ...(contentType === undefined ? {} : { contentType }),\n content: bytes,\n });\n }\n\n return resolved;\n }\n\n /**\n * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor\n * open. A drained stream is already closed; releasing it again is harmless.\n */\n static release(opts: { attachments?: IMailAttachment[] }): void {\n for (const attachment of opts.attachments ?? []) {\n const { content } = attachment;\n\n if (content instanceof ReadableStream) {\n if (!content.locked) {\n content.cancel().catch((error) => {\n logger.warn(\n '[MailAttachmentUtility.release] Cancelling a web stream failed | error: %s',\n error,\n );\n });\n }\n\n continue;\n }\n\n if (\n typeof content === 'object' &&\n content !== null &&\n 'destroy' in content &&\n typeof content.destroy === 'function'\n ) {\n content.destroy();\n }\n }\n }\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  import { type IMailMessage } from '../common/index.js';
2
- /** nodemailer reads a `text`/`html` given as `{ path }` or `{ href }`, so only a string may pass; `null` counts as absent. */
3
- export declare const assertMailBodyIsText: (opts: {
4
- message: IMailMessage;
5
- }) => void;
2
+ export declare class MailBodyUtility {
3
+ /** nodemailer reads a `text`/`html` given as `{ path }` or `{ href }`, so only a string may pass; `null` counts as absent. */
4
+ static assertIsText(opts: {
5
+ message: IMailMessage;
6
+ }): void;
7
+ }
6
8
  //# sourceMappingURL=body.utility.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"body.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/body.utility.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE1D,8HAA8H;AAC9H,eAAO,MAAM,oBAAoB,GAAI,MAAM;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,KAAG,IActE,CAAC"}
1
+ {"version":3,"file":"body.utility.d.ts","sourceRoot":"","sources":["../../../../../src/components/mail/utilities/body.utility.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE1D,qBAAa,eAAe;IAC1B,8HAA8H;IAC9H,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI;CAe3D"}
@@ -1,16 +1,18 @@
1
1
  import { getError } from '@venizia/ignis-helpers/core';
2
2
  import { MailErrors } from '../common/index.js';
3
- /** nodemailer reads a `text`/`html` given as `{ path }` or `{ href }`, so only a string may pass; `null` counts as absent. */
4
- export const assertMailBodyIsText = (opts) => {
5
- const { text, html } = opts.message;
6
- const isTextValid = text === undefined || text === null || typeof text === 'string';
7
- const isHtmlValid = html === undefined || html === null || typeof html === 'string';
8
- if (isTextValid && isHtmlValid) {
9
- return;
3
+ export class MailBodyUtility {
4
+ /** nodemailer reads a `text`/`html` given as `{ path }` or `{ href }`, so only a string may pass; `null` counts as absent. */
5
+ static assertIsText(opts) {
6
+ const { text, html } = opts.message;
7
+ const isTextValid = text === undefined || text === null || typeof text === 'string';
8
+ const isHtmlValid = html === undefined || html === null || typeof html === 'string';
9
+ if (isTextValid && isHtmlValid) {
10
+ return;
11
+ }
12
+ throw getError({
13
+ error: MailErrors.BODY_SOURCE_REFUSED,
14
+ message: 'Mail body refused | text and html must be strings; pass the content itself, not a { path } or { href } for the transport to read',
15
+ });
10
16
  }
11
- throw getError({
12
- error: MailErrors.BODY_SOURCE_REFUSED,
13
- message: 'Mail body refused | text and html must be strings; pass the content itself, not a { path } or { href } for the transport to read',
14
- });
15
- };
17
+ }
16
18
  //# sourceMappingURL=body.utility.js.map