alepha 0.14.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/README.md +2 -5
  2. package/dist/api/audits/index.d.ts +620 -811
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -377
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -435
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -429
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +236 -427
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1010 -1196
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +17 -17
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +384 -285
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +1113 -623
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +299 -300
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +13 -9
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +445 -103
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +733 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +446 -103
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +445 -103
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/logger/index.js +12 -2
  67. package/dist/logger/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts +197 -197
  69. package/dist/mcp/index.d.ts.map +1 -1
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/mcp/index.js.map +1 -1
  72. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  73. package/dist/orm/index.browser.js.map +1 -1
  74. package/dist/orm/index.bun.js +2814 -0
  75. package/dist/orm/index.bun.js.map +1 -0
  76. package/dist/orm/index.d.ts +1228 -1216
  77. package/dist/orm/index.d.ts.map +1 -1
  78. package/dist/orm/index.js +2041 -1967
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +248 -248
  81. package/dist/queue/core/index.d.ts.map +1 -1
  82. package/dist/queue/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.bun.js +285 -0
  84. package/dist/redis/index.bun.js.map +1 -0
  85. package/dist/redis/index.d.ts +118 -136
  86. package/dist/redis/index.d.ts.map +1 -1
  87. package/dist/redis/index.js +18 -38
  88. package/dist/redis/index.js.map +1 -1
  89. package/dist/retry/index.d.ts +69 -69
  90. package/dist/retry/index.d.ts.map +1 -1
  91. package/dist/router/index.d.ts +6 -6
  92. package/dist/router/index.d.ts.map +1 -1
  93. package/dist/scheduler/index.d.ts +25 -25
  94. package/dist/scheduler/index.d.ts.map +1 -1
  95. package/dist/security/index.browser.js +5 -1
  96. package/dist/security/index.browser.js.map +1 -1
  97. package/dist/security/index.d.ts +417 -254
  98. package/dist/security/index.d.ts.map +1 -1
  99. package/dist/security/index.js +386 -86
  100. package/dist/security/index.js.map +1 -1
  101. package/dist/server/auth/index.d.ts +110 -110
  102. package/dist/server/auth/index.d.ts.map +1 -1
  103. package/dist/server/auth/index.js +20 -20
  104. package/dist/server/auth/index.js.map +1 -1
  105. package/dist/server/cache/index.d.ts +62 -47
  106. package/dist/server/cache/index.d.ts.map +1 -1
  107. package/dist/server/cache/index.js +56 -3
  108. package/dist/server/cache/index.js.map +1 -1
  109. package/dist/server/compress/index.d.ts +6 -0
  110. package/dist/server/compress/index.d.ts.map +1 -1
  111. package/dist/server/compress/index.js +36 -1
  112. package/dist/server/compress/index.js.map +1 -1
  113. package/dist/server/cookies/index.d.ts +6 -6
  114. package/dist/server/cookies/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.js +3 -3
  116. package/dist/server/cookies/index.js.map +1 -1
  117. package/dist/server/core/index.browser.js +2 -2
  118. package/dist/server/core/index.browser.js.map +1 -1
  119. package/dist/server/core/index.d.ts +242 -150
  120. package/dist/server/core/index.d.ts.map +1 -1
  121. package/dist/server/core/index.js +294 -125
  122. package/dist/server/core/index.js.map +1 -1
  123. package/dist/server/cors/index.d.ts +11 -12
  124. package/dist/server/cors/index.d.ts.map +1 -1
  125. package/dist/server/health/index.d.ts +0 -1
  126. package/dist/server/health/index.d.ts.map +1 -1
  127. package/dist/server/helmet/index.d.ts +2 -2
  128. package/dist/server/helmet/index.d.ts.map +1 -1
  129. package/dist/server/links/index.browser.js.map +1 -1
  130. package/dist/server/links/index.d.ts +123 -124
  131. package/dist/server/links/index.d.ts.map +1 -1
  132. package/dist/server/links/index.js +1 -2
  133. package/dist/server/links/index.js.map +1 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/multipart/index.d.ts +6 -6
  136. package/dist/server/multipart/index.d.ts.map +1 -1
  137. package/dist/server/proxy/index.d.ts +102 -103
  138. package/dist/server/proxy/index.d.ts.map +1 -1
  139. package/dist/server/rate-limit/index.d.ts +16 -16
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts +44 -44
  142. package/dist/server/static/index.d.ts.map +1 -1
  143. package/dist/server/static/index.js +4 -0
  144. package/dist/server/static/index.js.map +1 -1
  145. package/dist/server/swagger/index.d.ts +48 -49
  146. package/dist/server/swagger/index.d.ts.map +1 -1
  147. package/dist/server/swagger/index.js +3 -5
  148. package/dist/server/swagger/index.js.map +1 -1
  149. package/dist/sms/index.d.ts +13 -11
  150. package/dist/sms/index.d.ts.map +1 -1
  151. package/dist/sms/index.js +7 -7
  152. package/dist/sms/index.js.map +1 -1
  153. package/dist/thread/index.d.ts +71 -72
  154. package/dist/thread/index.d.ts.map +1 -1
  155. package/dist/topic/core/index.d.ts +318 -318
  156. package/dist/topic/core/index.d.ts.map +1 -1
  157. package/dist/topic/redis/index.d.ts +6 -6
  158. package/dist/topic/redis/index.d.ts.map +1 -1
  159. package/dist/vite/index.d.ts +5805 -249
  160. package/dist/vite/index.d.ts.map +1 -1
  161. package/dist/vite/index.js +599 -513
  162. package/dist/vite/index.js.map +1 -1
  163. package/dist/websocket/index.browser.js +6 -6
  164. package/dist/websocket/index.browser.js.map +1 -1
  165. package/dist/websocket/index.d.ts +247 -247
  166. package/dist/websocket/index.d.ts.map +1 -1
  167. package/dist/websocket/index.js +6 -6
  168. package/dist/websocket/index.js.map +1 -1
  169. package/package.json +9 -14
  170. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  171. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  172. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  173. package/src/api/users/entities/users.ts +1 -1
  174. package/src/api/users/index.ts +8 -8
  175. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  176. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  177. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  178. package/src/api/users/services/CredentialService.ts +7 -7
  179. package/src/api/users/services/IdentityService.ts +4 -4
  180. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  181. package/src/api/users/services/RegistrationService.ts +38 -27
  182. package/src/api/users/services/SessionCrudService.ts +3 -3
  183. package/src/api/users/services/SessionService.spec.ts +3 -3
  184. package/src/api/users/services/SessionService.ts +28 -9
  185. package/src/api/users/services/UserService.ts +7 -7
  186. package/src/batch/providers/BatchProvider.ts +1 -2
  187. package/src/cli/apps/AlephaCli.ts +0 -2
  188. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  189. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  190. package/src/cli/assets/apiIndexTs.ts +16 -0
  191. package/src/cli/assets/claudeMd.ts +303 -0
  192. package/src/cli/assets/mainBrowserTs.ts +2 -2
  193. package/src/cli/assets/mainServerTs.ts +24 -0
  194. package/src/cli/assets/webAppRouterTs.ts +15 -0
  195. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  196. package/src/cli/assets/webIndexTs.ts +16 -0
  197. package/src/cli/atoms/buildOptions.ts +88 -0
  198. package/src/cli/commands/build.ts +70 -87
  199. package/src/cli/commands/db.ts +21 -22
  200. package/src/cli/commands/deploy.ts +17 -5
  201. package/src/cli/commands/dev.ts +22 -14
  202. package/src/cli/commands/format.ts +8 -2
  203. package/src/cli/commands/gen/env.ts +53 -0
  204. package/src/cli/commands/gen/openapi.ts +1 -1
  205. package/src/cli/commands/gen/resource.ts +15 -0
  206. package/src/cli/commands/gen.ts +7 -1
  207. package/src/cli/commands/init.ts +74 -30
  208. package/src/cli/commands/lint.ts +8 -2
  209. package/src/cli/commands/test.ts +8 -3
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +5 -3
  212. package/src/cli/defineConfig.ts +49 -7
  213. package/src/cli/index.ts +0 -1
  214. package/src/cli/services/AlephaCliUtils.ts +39 -589
  215. package/src/cli/services/PackageManagerUtils.ts +301 -0
  216. package/src/cli/services/ProjectScaffolder.ts +306 -0
  217. package/src/command/helpers/Runner.spec.ts +2 -2
  218. package/src/command/helpers/Runner.ts +16 -4
  219. package/src/command/primitives/$command.ts +0 -6
  220. package/src/command/providers/CliProvider.ts +1 -3
  221. package/src/core/Alepha.ts +42 -0
  222. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  223. package/src/core/index.shared.ts +1 -0
  224. package/src/core/index.ts +2 -0
  225. package/src/core/primitives/$hook.ts +6 -2
  226. package/src/core/primitives/$module.spec.ts +4 -0
  227. package/src/core/providers/AlsProvider.ts +1 -1
  228. package/src/core/providers/CodecManager.spec.ts +12 -6
  229. package/src/core/providers/CodecManager.ts +26 -6
  230. package/src/core/providers/EventManager.ts +169 -13
  231. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  232. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  233. package/src/core/providers/StateManager.spec.ts +27 -16
  234. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  235. package/src/email/providers/LocalEmailProvider.ts +52 -15
  236. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  237. package/src/file/errors/FileError.ts +7 -0
  238. package/src/file/index.ts +9 -1
  239. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  240. package/src/logger/index.ts +15 -3
  241. package/src/mcp/transports/StdioMcpTransport.ts +1 -1
  242. package/src/orm/index.browser.ts +1 -19
  243. package/src/orm/index.bun.ts +77 -0
  244. package/src/orm/index.shared-server.ts +22 -0
  245. package/src/orm/index.shared.ts +15 -0
  246. package/src/orm/index.ts +13 -39
  247. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  248. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  249. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  250. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  251. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  252. package/src/orm/services/Repository.ts +8 -0
  253. package/src/queue/core/providers/WorkerProvider.spec.ts +48 -32
  254. package/src/redis/index.bun.ts +35 -0
  255. package/src/redis/providers/BunRedisProvider.ts +12 -43
  256. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  257. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  258. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  259. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  260. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  261. package/src/security/index.browser.ts +5 -0
  262. package/src/security/index.ts +90 -7
  263. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  264. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  265. package/src/security/primitives/$role.ts +5 -5
  266. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  267. package/src/security/primitives/$serviceAccount.ts +3 -3
  268. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  269. package/src/server/auth/primitives/$auth.ts +10 -10
  270. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  271. package/src/server/auth/primitives/$authGithub.ts +3 -3
  272. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  273. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  274. package/src/server/cache/providers/ServerCacheProvider.spec.ts +183 -0
  275. package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
  276. package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
  277. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  278. package/src/server/core/helpers/ServerReply.ts +2 -2
  279. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  280. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  281. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  282. package/src/server/core/providers/ServerProvider.ts +155 -22
  283. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  284. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  285. package/src/server/links/index.ts +1 -1
  286. package/src/server/links/providers/LinkProvider.ts +1 -1
  287. package/src/server/static/providers/ServerStaticProvider.ts +10 -0
  288. package/src/server/swagger/index.ts +1 -1
  289. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
  290. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  291. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  292. package/src/vite/helpers/boot.ts +28 -17
  293. package/src/vite/helpers/importViteReact.ts +13 -0
  294. package/src/vite/index.ts +1 -21
  295. package/src/vite/plugins/viteAlephaDev.ts +16 -1
  296. package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
  297. package/src/vite/tasks/buildClient.ts +11 -0
  298. package/src/vite/tasks/buildServer.ts +59 -4
  299. package/src/vite/tasks/devServer.ts +71 -0
  300. package/src/vite/tasks/generateCloudflare.ts +7 -0
  301. package/src/vite/tasks/index.ts +2 -1
  302. package/dist/server/security/index.browser.js +0 -13
  303. package/dist/server/security/index.browser.js.map +0 -1
  304. package/dist/server/security/index.d.ts +0 -173
  305. package/dist/server/security/index.d.ts.map +0 -1
  306. package/dist/server/security/index.js +0 -311
  307. package/dist/server/security/index.js.map +0 -1
  308. package/src/cli/assets/appRouterTs.ts +0 -9
  309. package/src/cli/assets/mainTs.ts +0 -13
  310. package/src/cli/assets/viteConfigTs.ts +0 -14
  311. package/src/cli/commands/run.ts +0 -24
  312. package/src/server/security/index.browser.ts +0 -10
  313. package/src/server/security/index.ts +0 -94
  314. package/src/vite/plugins/viteAlepha.ts +0 -37
  315. package/src/vite/plugins/viteAlephaBuild.ts +0 -281
  316. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  317. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -1,7 +1,6 @@
1
- import { $env, $hook, $module, KIND, Primitive, createPrimitive, t } from "alepha";
1
+ import { $atom, $env, $hook, $inject, $module, $use, KIND, Primitive, createPrimitive, t } from "alepha";
2
2
  import { $logger } from "alepha/logger";
3
- import * as fs from "node:fs/promises";
4
- import * as path from "node:path";
3
+ import { FileSystemProvider } from "alepha/file";
5
4
  import nodemailer from "nodemailer";
6
5
 
7
6
  //#region ../../src/email/providers/EmailProvider.ts
@@ -104,12 +103,34 @@ var EmailError = class extends Error {
104
103
 
105
104
  //#endregion
106
105
  //#region ../../src/email/providers/LocalEmailProvider.ts
106
+ /**
107
+ * Local email provider configuration atom
108
+ */
109
+ const localEmailOptions = $atom({
110
+ name: "alepha.email.local.options",
111
+ schema: t.object({ directory: t.string({ description: "Directory path where email files will be stored" }) }),
112
+ default: { directory: "node_modules/.alepha/emails" }
113
+ });
107
114
  var LocalEmailProvider = class {
108
115
  log = $logger();
109
- directory;
110
- constructor(options = {}) {
111
- this.directory = options.directory ?? "node_modules/.alepha/emails";
116
+ fs = $inject(FileSystemProvider);
117
+ options = $use(localEmailOptions);
118
+ get directory() {
119
+ return this.options.directory;
112
120
  }
121
+ onStart = $hook({
122
+ on: "start",
123
+ handler: async () => {
124
+ try {
125
+ await this.fs.mkdir(this.directory, { recursive: true });
126
+ this.log.info("Email directory OK", { directory: this.directory });
127
+ } catch (error) {
128
+ const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;
129
+ this.log.error(message, { directory: this.directory });
130
+ throw new EmailError(message, error instanceof Error ? error : void 0);
131
+ }
132
+ }
133
+ });
113
134
  async send(options) {
114
135
  const { to, subject, body } = options;
115
136
  this.log.debug("Sending email to local file", {
@@ -118,17 +139,16 @@ var LocalEmailProvider = class {
118
139
  directory: this.directory
119
140
  });
120
141
  try {
121
- await fs.mkdir(this.directory, { recursive: true });
122
142
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
123
143
  for (const recipient of Array.isArray(to) ? to : [to]) {
124
144
  const filename = `${recipient.replace(/[^a-zA-Z0-9@.-]/g, "_")}+${timestamp}.html`;
125
- const filepath = path.join(this.directory, filename);
145
+ const filepath = this.fs.join(this.directory, filename);
126
146
  const htmlContent = this.createEmailHtml({
127
147
  to: recipient,
128
148
  subject,
129
149
  body
130
150
  });
131
- await fs.writeFile(filepath, htmlContent, "utf8");
151
+ await this.fs.writeFile(filepath, htmlContent);
132
152
  this.log.info("Email saved to local file", {
133
153
  filepath,
134
154
  to,
@@ -180,29 +200,98 @@ var LocalEmailProvider = class {
180
200
 
181
201
  //#endregion
182
202
  //#region ../../src/email/providers/NodemailerEmailProvider.ts
203
+ /**
204
+ * Environment variables for nodemailer configuration
205
+ */
183
206
  const envSchema = t.object({
184
- EMAIL_HOST: t.text({ description: "SMTP server host" }),
207
+ EMAIL_HOST: t.optional(t.text({ description: "SMTP server host" })),
185
208
  EMAIL_PORT: t.number({
186
209
  default: 587,
187
210
  description: "SMTP server port"
188
211
  }),
189
- EMAIL_USER: t.text({ description: "SMTP authentication username" }),
190
- EMAIL_PASS: t.text({ description: "SMTP authentication password" }),
191
- EMAIL_FROM: t.text({ description: "Default from email address" }),
212
+ EMAIL_USER: t.optional(t.text({ description: "SMTP authentication username" })),
213
+ EMAIL_PASS: t.optional(t.text({ description: "SMTP authentication password" })),
214
+ EMAIL_FROM: t.optional(t.text({ description: "Default from email address" })),
192
215
  EMAIL_SECURE: t.boolean({
193
216
  default: false,
194
217
  description: "Use secure connection (TLS)"
195
218
  })
196
219
  });
220
+ /**
221
+ * Nodemailer connection pooling and rate limiting options
222
+ */
223
+ const nodemailerEmailOptions = $atom({
224
+ name: "alepha.email.nodemailer.options",
225
+ schema: t.object({
226
+ pool: t.optional(t.boolean({ description: "Enable connection pooling" })),
227
+ maxConnections: t.optional(t.number({ description: "Maximum number of connections in pool" })),
228
+ maxMessages: t.optional(t.number({ description: "Maximum messages per connection" })),
229
+ rateDelta: t.optional(t.number({ description: "Time in milliseconds between message sends" })),
230
+ rateLimit: t.optional(t.number({ description: "Maximum number of messages per rateDelta" }))
231
+ }),
232
+ default: {}
233
+ });
234
+ /**
235
+ * Email provider using Nodemailer for SMTP transport.
236
+ *
237
+ * Configuration is provided via environment variables:
238
+ * - EMAIL_HOST: SMTP server host
239
+ * - EMAIL_PORT: SMTP server port (default: 587)
240
+ * - EMAIL_USER: SMTP authentication username
241
+ * - EMAIL_PASS: SMTP authentication password
242
+ * - EMAIL_FROM: Default from email address
243
+ * - EMAIL_SECURE: Use secure connection (default: false)
244
+ *
245
+ * Advanced pooling/rate limiting options can be configured via atom:
246
+ * @see {@link nodemailerEmailOptions}
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * // Configure via environment variables
251
+ * // EMAIL_HOST=smtp.example.com
252
+ * // EMAIL_PORT=587
253
+ * // EMAIL_USER=user@example.com
254
+ * // EMAIL_PASS=secret
255
+ * // EMAIL_FROM=noreply@example.com
256
+ *
257
+ * // Optionally configure pooling via atom
258
+ * alepha.state.set(nodemailerEmailOptions.key, {
259
+ * pool: true,
260
+ * maxConnections: 5,
261
+ * rateLimit: 10,
262
+ * });
263
+ * ```
264
+ */
197
265
  var NodemailerEmailProvider = class {
198
266
  env = $env(envSchema);
199
267
  log = $logger();
200
- transporter;
201
- fromAddress;
202
- options = {};
203
- constructor() {
204
- this.fromAddress = this.options.from ?? this.env.EMAIL_FROM;
205
- this.transporter = this.createTransporter();
268
+ options = $use(nodemailerEmailOptions);
269
+ transporter = null;
270
+ get host() {
271
+ const host = this.env.EMAIL_HOST;
272
+ if (!host) throw new EmailError("Email host not configured. Set EMAIL_HOST env var.");
273
+ return host;
274
+ }
275
+ get port() {
276
+ return this.env.EMAIL_PORT;
277
+ }
278
+ get secure() {
279
+ return this.env.EMAIL_SECURE;
280
+ }
281
+ get user() {
282
+ return this.env.EMAIL_USER;
283
+ }
284
+ get pass() {
285
+ return this.env.EMAIL_PASS;
286
+ }
287
+ get fromAddress() {
288
+ const from = this.env.EMAIL_FROM;
289
+ if (!from) throw new EmailError("Email from address not configured. Set EMAIL_FROM env var.");
290
+ return from;
291
+ }
292
+ getTransporter() {
293
+ if (!this.transporter) this.transporter = this.createTransporter();
294
+ return this.transporter;
206
295
  }
207
296
  async send(options) {
208
297
  const { to, subject, body } = options;
@@ -211,7 +300,7 @@ var NodemailerEmailProvider = class {
211
300
  subject
212
301
  });
213
302
  try {
214
- const result = await this.transporter.sendMail({
303
+ const result = await this.getTransporter().sendMail({
215
304
  from: this.fromAddress,
216
305
  to,
217
306
  subject,
@@ -233,22 +322,26 @@ var NodemailerEmailProvider = class {
233
322
  }
234
323
  }
235
324
  createTransporter() {
236
- if (this.options.transporter) return this.options.transporter;
237
325
  const transporterConfig = {
238
- host: this.env.EMAIL_HOST,
239
- port: this.env.EMAIL_PORT,
240
- secure: this.env.EMAIL_SECURE,
241
- auth: {
242
- user: this.env.EMAIL_USER,
243
- pass: this.env.EMAIL_PASS
244
- },
245
- ...this.options.options
326
+ host: this.host,
327
+ port: this.port,
328
+ secure: this.secure,
329
+ auth: this.user && this.pass ? {
330
+ user: this.user,
331
+ pass: this.pass
332
+ } : void 0,
333
+ pool: this.options.pool,
334
+ maxConnections: this.options.maxConnections,
335
+ maxMessages: this.options.maxMessages,
336
+ rateDelta: this.options.rateDelta,
337
+ rateLimit: this.options.rateLimit
246
338
  };
247
339
  this.log.debug("Creating Nodemailer transporter", {
248
340
  host: transporterConfig.host,
249
341
  port: transporterConfig.port,
250
342
  secure: transporterConfig.secure,
251
- user: transporterConfig.auth.user
343
+ user: transporterConfig.auth?.user,
344
+ pool: transporterConfig.pool
252
345
  });
253
346
  return nodemailer.createTransport(transporterConfig);
254
347
  }
@@ -257,7 +350,7 @@ var NodemailerEmailProvider = class {
257
350
  */
258
351
  async verify() {
259
352
  try {
260
- await this.transporter.verify();
353
+ await this.getTransporter().verify();
261
354
  this.log.info("Email server connection verified");
262
355
  return true;
263
356
  } catch (error) {
@@ -269,7 +362,10 @@ var NodemailerEmailProvider = class {
269
362
  * Close the transporter connection.
270
363
  */
271
364
  close() {
272
- this.transporter.close();
365
+ if (this.transporter) {
366
+ this.transporter.close();
367
+ this.transporter = null;
368
+ }
273
369
  }
274
370
  onStart = $hook({
275
371
  on: "start",
@@ -327,5 +423,5 @@ const AlephaEmail = $module({
327
423
  });
328
424
 
329
425
  //#endregion
330
- export { $email, AlephaEmail, EmailError, EmailPrimitive, EmailProvider, LocalEmailProvider, MemoryEmailProvider, NodemailerEmailProvider };
426
+ export { $email, AlephaEmail, EmailError, EmailPrimitive, EmailProvider, LocalEmailProvider, MemoryEmailProvider, NodemailerEmailProvider, localEmailOptions, nodemailerEmailOptions };
331
427
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/email/providers/EmailProvider.ts","../../src/email/providers/MemoryEmailProvider.ts","../../src/email/primitives/$email.ts","../../src/email/errors/EmailError.ts","../../src/email/providers/LocalEmailProvider.ts","../../src/email/providers/NodemailerEmailProvider.ts","../../src/email/index.ts"],"sourcesContent":["/**\n * Email provider interface.\n *\n * All methods are asynchronous and return promises.\n */\nexport abstract class EmailProvider {\n /**\n * Send an email.\n *\n * @return Promise that resolves when the email is sent.\n */\n public abstract send(options: EmailSendOptions): Promise<void>;\n}\n\nexport type EmailSendOptions = {\n to: string | string[];\n subject: string;\n body: string;\n};\n","import { $logger } from \"alepha/logger\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nexport interface EmailRecord {\n to: string;\n subject: string;\n body: string;\n sentAt: Date;\n}\n\nexport class MemoryEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n public records: EmailRecord[] = [];\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email to memory store\", { to, subject });\n\n for (const recipient of Array.isArray(to) ? to : [to]) {\n this.records.push({\n to: recipient,\n subject,\n body,\n sentAt: new Date(),\n });\n }\n }\n\n /**\n * Get the last email sent (for testing purposes).\n */\n public get last(): EmailRecord | undefined {\n return this.records[this.records.length - 1];\n }\n}\n","import {\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n} from \"alepha\";\nimport type { EmailSendOptions } from \"../providers/EmailProvider.ts\";\nimport { EmailProvider } from \"../providers/EmailProvider.ts\";\nimport { MemoryEmailProvider } from \"../providers/MemoryEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const $email = (options: EmailPrimitiveOptions = {}) =>\n createPrimitive(EmailPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface EmailPrimitiveOptions {\n name?: string;\n provider?: InstantiableClass<EmailProvider> | \"memory\";\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email primitive for sending emails through various providers.\n *\n * Usage:\n * ```typescript\n * class MyService {\n * private readonly welcomeEmail = $email({ name: \"welcome\" });\n *\n * async sendWelcome(userEmail: string, userName: string) {\n * await this.welcomeEmail.send({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: `<p>Hello ${userName}!</p>`\n * });\n * }\n * }\n * ```\n */\nexport class EmailPrimitive extends Primitive<EmailPrimitiveOptions> {\n protected readonly provider = this.$provider();\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Send an email using the configured provider.\n */\n public async send(options: EmailSendOptions): Promise<void> {\n await this.alepha.events.emit(\"email:sending\", {\n to: options.to,\n template: this.name,\n variables: {},\n provider: this.provider,\n abort: () => {\n throw new Error(\"Email sending aborted by hook\");\n },\n });\n\n await this.provider.send(options);\n\n await this.alepha.events.emit(\"email:sent\", {\n to: options.to,\n template: this.name,\n provider: this.provider,\n });\n }\n\n protected $provider(): EmailProvider {\n if (!this.options.provider) {\n return this.alepha.inject(EmailProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryEmailProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n$email[KIND] = EmailPrimitive;\n","export class EmailError extends Error {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { $logger } from \"alepha/logger\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nexport interface LocalEmailProviderOptions {\n /**\n * Directory to save email files.\n */\n directory?: string;\n}\n\nexport class LocalEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n protected readonly directory: string;\n\n constructor(options: LocalEmailProviderOptions = {}) {\n this.directory = options.directory ?? \"node_modules/.alepha/emails\";\n }\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n\n this.log.debug(\"Sending email to local file\", {\n to,\n subject,\n directory: this.directory,\n });\n\n try {\n // Ensure directory exists\n await fs.mkdir(this.directory, { recursive: true });\n\n // Create filename: emailcontact+date\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n for (const recipient of Array.isArray(to) ? to : [to]) {\n const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, \"_\");\n const filename = `${sanitizedEmail}+${timestamp}.html`;\n const filepath = path.join(this.directory, filename);\n\n // Create HTML content\n const htmlContent = this.createEmailHtml({\n to: recipient,\n subject,\n body,\n });\n\n // Write to file\n await fs.writeFile(filepath, htmlContent, \"utf8\");\n\n this.log.info(\"Email saved to local file\", { filepath, to, subject });\n }\n } catch (error) {\n const message = `Failed to save email to local file: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject, directory: this.directory });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n public createEmailHtml(options: {\n to: string;\n subject: string;\n body: string;\n }): string {\n const { to, subject, body } = options;\n const timestamp = new Date().toISOString();\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${this.escapeHtml(subject)}</title>\n <style>\n body { font-family: Arial, sans-serif; margin: 20px; }\n .email-header { background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }\n .email-body { background-color: #ffffff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }\n .meta { font-size: 12px; color: #666; margin-bottom: 10px; }\n </style>\n</head>\n<body>\n <div class=\"email-header\">\n <div class=\"meta\">Sent: ${timestamp}</div>\n <div class=\"meta\">To: ${this.escapeHtml(to)}</div>\n <h1>${this.escapeHtml(subject)}</h1>\n </div>\n <div class=\"email-body\">\n ${body}\n </div>\n</body>\n</html>`;\n }\n\n public escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n }\n}\n","import { $env, $hook, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Transporter } from \"nodemailer\";\nimport nodemailer from \"nodemailer\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nconst envSchema = t.object({\n EMAIL_HOST: t.text({\n description: \"SMTP server host\",\n }),\n EMAIL_PORT: t.number({\n default: 587,\n description: \"SMTP server port\",\n }),\n EMAIL_USER: t.text({\n description: \"SMTP authentication username\",\n }),\n EMAIL_PASS: t.text({\n description: \"SMTP authentication password\",\n }),\n EMAIL_FROM: t.text({\n description: \"Default from email address\",\n }),\n EMAIL_SECURE: t.boolean({\n default: false,\n description: \"Use secure connection (TLS)\",\n }),\n});\n\nexport interface NodemailerEmailProviderOptions {\n /**\n * Custom transporter configuration.\n * If provided, will override environment variables.\n */\n transporter?: Transporter;\n\n /**\n * Custom from email address.\n * If not provided, will use EMAIL_FROM from environment.\n */\n from?: string;\n\n /**\n * Additional nodemailer options.\n */\n options?: {\n pool?: boolean;\n maxConnections?: number;\n maxMessages?: number;\n rateDelta?: number;\n rateLimit?: number;\n };\n}\n\nexport class NodemailerEmailProvider implements EmailProvider {\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n protected transporter: Transporter;\n protected fromAddress: string;\n\n public readonly options: NodemailerEmailProviderOptions = {};\n\n constructor() {\n this.fromAddress = this.options.from ?? this.env.EMAIL_FROM;\n this.transporter = this.createTransporter();\n }\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email via Nodemailer\", { to, subject });\n\n try {\n const result = await this.transporter.sendMail({\n from: this.fromAddress,\n to,\n subject,\n html: body,\n });\n\n this.log.info(\"Email sent successfully\", {\n to,\n subject,\n messageId: result.messageId,\n response: result.response,\n });\n } catch (error) {\n const message = `Failed to send email via Nodemailer: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n protected createTransporter(): Transporter {\n if (this.options.transporter) {\n return this.options.transporter;\n }\n\n const transporterConfig = {\n host: this.env.EMAIL_HOST,\n port: this.env.EMAIL_PORT,\n secure: this.env.EMAIL_SECURE,\n auth: {\n user: this.env.EMAIL_USER,\n pass: this.env.EMAIL_PASS,\n },\n ...this.options.options,\n };\n\n this.log.debug(\"Creating Nodemailer transporter\", {\n host: transporterConfig.host,\n port: transporterConfig.port,\n secure: transporterConfig.secure,\n user: transporterConfig.auth.user,\n });\n\n return nodemailer.createTransport(transporterConfig);\n }\n\n /**\n * Verify the connection to the email server.\n */\n public async verify(): Promise<boolean> {\n try {\n await this.transporter.verify();\n this.log.info(\"Email server connection verified\");\n return true;\n } catch (error) {\n this.log.error(\"Email server connection failed\", { error });\n return false;\n }\n }\n\n /**\n * Close the transporter connection.\n */\n public close(): void {\n this.transporter.close();\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => this.verify(),\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n}\n","import { $module } from \"alepha\";\nimport { $email } from \"./primitives/$email.ts\";\nimport { EmailProvider } from \"./providers/EmailProvider.ts\";\nimport { LocalEmailProvider } from \"./providers/LocalEmailProvider.ts\";\nimport { MemoryEmailProvider } from \"./providers/MemoryEmailProvider.ts\";\nimport { NodemailerEmailProvider } from \"./providers/NodemailerEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/EmailError.ts\";\nexport * from \"./primitives/$email.ts\";\nexport * from \"./providers/EmailProvider.ts\";\nexport * from \"./providers/LocalEmailProvider.ts\";\nexport * from \"./providers/MemoryEmailProvider.ts\";\nexport * from \"./providers/NodemailerEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"email:sending\": {\n to: string | string[];\n template: string;\n variables: Record<string, unknown>;\n provider: EmailProvider;\n abort(): void;\n };\n \"email:sent\": {\n to: string | string[];\n template: string;\n provider: EmailProvider;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides email sending capabilities for Alepha applications with multiple provider backends.\n *\n * The email module enables declarative email sending through the `$email` primitive, allowing you to send\n * emails through different providers: memory (for testing), local file system, or SMTP via Nodemailer.\n * It supports HTML email content and automatic provider selection based on environment configuration.\n *\n * @see {@link EmailProvider}\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [\n EmailProvider,\n MemoryEmailProvider,\n LocalEmailProvider,\n NodemailerEmailProvider,\n ],\n register: (alepha) => {\n if (alepha.isTest()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else if (alepha.env.EMAIL_HOST) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: NodemailerEmailProvider,\n });\n } else {\n if (alepha.isServerless()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: LocalEmailProvider,\n });\n }\n }\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/email/providers/EmailProvider.ts","../../src/email/providers/MemoryEmailProvider.ts","../../src/email/primitives/$email.ts","../../src/email/errors/EmailError.ts","../../src/email/providers/LocalEmailProvider.ts","../../src/email/providers/NodemailerEmailProvider.ts","../../src/email/index.ts"],"sourcesContent":["/**\n * Email provider interface.\n *\n * All methods are asynchronous and return promises.\n */\nexport abstract class EmailProvider {\n /**\n * Send an email.\n *\n * @return Promise that resolves when the email is sent.\n */\n public abstract send(options: EmailSendOptions): Promise<void>;\n}\n\nexport type EmailSendOptions = {\n to: string | string[];\n subject: string;\n body: string;\n};\n","import { $logger } from \"alepha/logger\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\nexport interface EmailRecord {\n to: string;\n subject: string;\n body: string;\n sentAt: Date;\n}\n\nexport class MemoryEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n public records: EmailRecord[] = [];\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email to memory store\", { to, subject });\n\n for (const recipient of Array.isArray(to) ? to : [to]) {\n this.records.push({\n to: recipient,\n subject,\n body,\n sentAt: new Date(),\n });\n }\n }\n\n /**\n * Get the last email sent (for testing purposes).\n */\n public get last(): EmailRecord | undefined {\n return this.records[this.records.length - 1];\n }\n}\n","import {\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n} from \"alepha\";\nimport type { EmailSendOptions } from \"../providers/EmailProvider.ts\";\nimport { EmailProvider } from \"../providers/EmailProvider.ts\";\nimport { MemoryEmailProvider } from \"../providers/MemoryEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const $email = (options: EmailPrimitiveOptions = {}) =>\n createPrimitive(EmailPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface EmailPrimitiveOptions {\n name?: string;\n provider?: InstantiableClass<EmailProvider> | \"memory\";\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email primitive for sending emails through various providers.\n *\n * Usage:\n * ```typescript\n * class MyService {\n * private readonly welcomeEmail = $email({ name: \"welcome\" });\n *\n * async sendWelcome(userEmail: string, userName: string) {\n * await this.welcomeEmail.send({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: `<p>Hello ${userName}!</p>`\n * });\n * }\n * }\n * ```\n */\nexport class EmailPrimitive extends Primitive<EmailPrimitiveOptions> {\n protected readonly provider = this.$provider();\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Send an email using the configured provider.\n */\n public async send(options: EmailSendOptions): Promise<void> {\n await this.alepha.events.emit(\"email:sending\", {\n to: options.to,\n template: this.name,\n variables: {},\n provider: this.provider,\n abort: () => {\n throw new Error(\"Email sending aborted by hook\");\n },\n });\n\n await this.provider.send(options);\n\n await this.alepha.events.emit(\"email:sent\", {\n to: options.to,\n template: this.name,\n provider: this.provider,\n });\n }\n\n protected $provider(): EmailProvider {\n if (!this.options.provider) {\n return this.alepha.inject(EmailProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryEmailProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n$email[KIND] = EmailPrimitive;\n","export class EmailError extends Error {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import { $atom, $hook, $inject, $use, type Static, t } from \"alepha\";\nimport { FileSystemProvider } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Local email provider configuration atom\n */\nexport const localEmailOptions = $atom({\n name: \"alepha.email.local.options\",\n schema: t.object({\n directory: t.string({\n description: \"Directory path where email files will be stored\",\n }),\n }),\n default: {\n directory: \"node_modules/.alepha/emails\",\n },\n});\n\nexport type LocalEmailProviderOptions = Static<typeof localEmailOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [localEmailOptions.key]: LocalEmailProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class LocalEmailProvider implements EmailProvider {\n protected readonly log = $logger();\n protected readonly fs = $inject(FileSystemProvider);\n protected readonly options = $use(localEmailOptions);\n\n protected get directory(): string {\n return this.options.directory;\n }\n\n protected onStart = $hook({\n on: \"start\",\n handler: async () => {\n try {\n await this.fs.mkdir(this.directory, { recursive: true });\n this.log.info(\"Email directory OK\", {\n directory: this.directory,\n });\n } catch (error) {\n const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { directory: this.directory });\n throw new EmailError(\n message,\n error instanceof Error ? error : undefined,\n );\n }\n },\n });\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n\n this.log.debug(\"Sending email to local file\", {\n to,\n subject,\n directory: this.directory,\n });\n\n try {\n // Create filename: emailcontact+date\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n for (const recipient of Array.isArray(to) ? to : [to]) {\n const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, \"_\");\n const filename = `${sanitizedEmail}+${timestamp}.html`;\n const filepath = this.fs.join(this.directory, filename);\n\n // Create HTML content\n const htmlContent = this.createEmailHtml({\n to: recipient,\n subject,\n body,\n });\n\n // Write to file\n await this.fs.writeFile(filepath, htmlContent);\n\n this.log.info(\"Email saved to local file\", { filepath, to, subject });\n }\n } catch (error) {\n const message = `Failed to save email to local file: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject, directory: this.directory });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n public createEmailHtml(options: {\n to: string;\n subject: string;\n body: string;\n }): string {\n const { to, subject, body } = options;\n const timestamp = new Date().toISOString();\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${this.escapeHtml(subject)}</title>\n <style>\n body { font-family: Arial, sans-serif; margin: 20px; }\n .email-header { background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }\n .email-body { background-color: #ffffff; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }\n .meta { font-size: 12px; color: #666; margin-bottom: 10px; }\n </style>\n</head>\n<body>\n <div class=\"email-header\">\n <div class=\"meta\">Sent: ${timestamp}</div>\n <div class=\"meta\">To: ${this.escapeHtml(to)}</div>\n <h1>${this.escapeHtml(subject)}</h1>\n </div>\n <div class=\"email-body\">\n ${body}\n </div>\n</body>\n</html>`;\n }\n\n public escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n }\n}\n","import { $atom, $env, $hook, $use, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Transporter } from \"nodemailer\";\nimport nodemailer from \"nodemailer\";\nimport { EmailError } from \"../errors/EmailError.ts\";\nimport type { EmailProvider, EmailSendOptions } from \"./EmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Environment variables for nodemailer configuration\n */\nconst envSchema = t.object({\n EMAIL_HOST: t.optional(\n t.text({\n description: \"SMTP server host\",\n }),\n ),\n EMAIL_PORT: t.number({\n default: 587,\n description: \"SMTP server port\",\n }),\n EMAIL_USER: t.optional(\n t.text({\n description: \"SMTP authentication username\",\n }),\n ),\n EMAIL_PASS: t.optional(\n t.text({\n description: \"SMTP authentication password\",\n }),\n ),\n EMAIL_FROM: t.optional(\n t.text({\n description: \"Default from email address\",\n }),\n ),\n EMAIL_SECURE: t.boolean({\n default: false,\n description: \"Use secure connection (TLS)\",\n }),\n});\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Nodemailer connection pooling and rate limiting options\n */\nexport const nodemailerEmailOptions = $atom({\n name: \"alepha.email.nodemailer.options\",\n schema: t.object({\n pool: t.optional(\n t.boolean({\n description: \"Enable connection pooling\",\n }),\n ),\n maxConnections: t.optional(\n t.number({\n description: \"Maximum number of connections in pool\",\n }),\n ),\n maxMessages: t.optional(\n t.number({\n description: \"Maximum messages per connection\",\n }),\n ),\n rateDelta: t.optional(\n t.number({\n description: \"Time in milliseconds between message sends\",\n }),\n ),\n rateLimit: t.optional(\n t.number({\n description: \"Maximum number of messages per rateDelta\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type NodemailerEmailProviderOptions = Static<\n typeof nodemailerEmailOptions.schema\n>;\n\ndeclare module \"alepha\" {\n interface State {\n [nodemailerEmailOptions.key]: NodemailerEmailProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Email provider using Nodemailer for SMTP transport.\n *\n * Configuration is provided via environment variables:\n * - EMAIL_HOST: SMTP server host\n * - EMAIL_PORT: SMTP server port (default: 587)\n * - EMAIL_USER: SMTP authentication username\n * - EMAIL_PASS: SMTP authentication password\n * - EMAIL_FROM: Default from email address\n * - EMAIL_SECURE: Use secure connection (default: false)\n *\n * Advanced pooling/rate limiting options can be configured via atom:\n * @see {@link nodemailerEmailOptions}\n *\n * @example\n * ```typescript\n * // Configure via environment variables\n * // EMAIL_HOST=smtp.example.com\n * // EMAIL_PORT=587\n * // EMAIL_USER=user@example.com\n * // EMAIL_PASS=secret\n * // EMAIL_FROM=noreply@example.com\n *\n * // Optionally configure pooling via atom\n * alepha.state.set(nodemailerEmailOptions.key, {\n * pool: true,\n * maxConnections: 5,\n * rateLimit: 10,\n * });\n * ```\n */\nexport class NodemailerEmailProvider implements EmailProvider {\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n protected readonly options = $use(nodemailerEmailOptions);\n protected transporter: Transporter | null = null;\n\n protected get host(): string {\n const host = this.env.EMAIL_HOST;\n if (!host) {\n throw new EmailError(\n \"Email host not configured. Set EMAIL_HOST env var.\",\n );\n }\n return host;\n }\n\n protected get port(): number {\n return this.env.EMAIL_PORT;\n }\n\n protected get secure(): boolean {\n return this.env.EMAIL_SECURE;\n }\n\n protected get user(): string | undefined {\n return this.env.EMAIL_USER;\n }\n\n protected get pass(): string | undefined {\n return this.env.EMAIL_PASS;\n }\n\n protected get fromAddress(): string {\n const from = this.env.EMAIL_FROM;\n if (!from) {\n throw new EmailError(\n \"Email from address not configured. Set EMAIL_FROM env var.\",\n );\n }\n return from;\n }\n\n protected getTransporter(): Transporter {\n if (!this.transporter) {\n this.transporter = this.createTransporter();\n }\n return this.transporter;\n }\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.debug(\"Sending email via Nodemailer\", { to, subject });\n\n try {\n const result = await this.getTransporter().sendMail({\n from: this.fromAddress,\n to,\n subject,\n html: body,\n });\n\n this.log.info(\"Email sent successfully\", {\n to,\n subject,\n messageId: result.messageId,\n response: result.response,\n });\n } catch (error) {\n const message = `Failed to send email via Nodemailer: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n protected createTransporter(): Transporter {\n const transporterConfig = {\n host: this.host,\n port: this.port,\n secure: this.secure,\n auth:\n this.user && this.pass\n ? {\n user: this.user,\n pass: this.pass,\n }\n : undefined,\n pool: this.options.pool,\n maxConnections: this.options.maxConnections,\n maxMessages: this.options.maxMessages,\n rateDelta: this.options.rateDelta,\n rateLimit: this.options.rateLimit,\n };\n\n this.log.debug(\"Creating Nodemailer transporter\", {\n host: transporterConfig.host,\n port: transporterConfig.port,\n secure: transporterConfig.secure,\n user: transporterConfig.auth?.user,\n pool: transporterConfig.pool,\n });\n\n return nodemailer.createTransport(transporterConfig);\n }\n\n /**\n * Verify the connection to the email server.\n */\n public async verify(): Promise<boolean> {\n try {\n await this.getTransporter().verify();\n this.log.info(\"Email server connection verified\");\n return true;\n } catch (error) {\n this.log.error(\"Email server connection failed\", { error });\n return false;\n }\n }\n\n /**\n * Close the transporter connection.\n */\n public close(): void {\n if (this.transporter) {\n this.transporter.close();\n this.transporter = null;\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => this.verify(),\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => this.close(),\n });\n}\n","import { $module } from \"alepha\";\nimport { $email } from \"./primitives/$email.ts\";\nimport { EmailProvider } from \"./providers/EmailProvider.ts\";\nimport { LocalEmailProvider } from \"./providers/LocalEmailProvider.ts\";\nimport { MemoryEmailProvider } from \"./providers/MemoryEmailProvider.ts\";\nimport { NodemailerEmailProvider } from \"./providers/NodemailerEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/EmailError.ts\";\nexport * from \"./primitives/$email.ts\";\nexport * from \"./providers/EmailProvider.ts\";\nexport * from \"./providers/LocalEmailProvider.ts\";\nexport * from \"./providers/MemoryEmailProvider.ts\";\nexport * from \"./providers/NodemailerEmailProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"email:sending\": {\n to: string | string[];\n template: string;\n variables: Record<string, unknown>;\n provider: EmailProvider;\n abort(): void;\n };\n \"email:sent\": {\n to: string | string[];\n template: string;\n provider: EmailProvider;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides email sending capabilities for Alepha applications with multiple provider backends.\n *\n * The email module enables declarative email sending through the `$email` primitive, allowing you to send\n * emails through different providers: memory (for testing), local file system, or SMTP via Nodemailer.\n * It supports HTML email content and automatic provider selection based on environment configuration.\n *\n * @see {@link EmailProvider}\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [\n EmailProvider,\n MemoryEmailProvider,\n LocalEmailProvider,\n NodemailerEmailProvider,\n ],\n register: (alepha) => {\n if (alepha.isTest()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else if (alepha.env.EMAIL_HOST) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: NodemailerEmailProvider,\n });\n } else {\n if (alepha.isServerless()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: MemoryEmailProvider,\n });\n } else {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: LocalEmailProvider,\n });\n }\n }\n },\n});\n"],"mappings":"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"}