alepha 0.14.4 → 0.15.1

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 (322) hide show
  1. package/README.md +44 -102
  2. package/dist/api/audits/index.d.ts +331 -443
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +2 -2
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +0 -113
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +2 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +151 -262
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/notifications/index.browser.js +4 -4
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +164 -276
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/notifications/index.js +4 -4
  17. package/dist/api/notifications/index.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +265 -377
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/users/index.browser.js +1 -2
  21. package/dist/api/users/index.browser.js.map +1 -1
  22. package/dist/api/users/index.d.ts +195 -301
  23. package/dist/api/users/index.d.ts.map +1 -1
  24. package/dist/api/users/index.js +203 -184
  25. package/dist/api/users/index.js.map +1 -1
  26. package/dist/api/verifications/index.d.ts.map +1 -1
  27. package/dist/batch/index.d.ts.map +1 -1
  28. package/dist/batch/index.js +1 -2
  29. package/dist/batch/index.js.map +1 -1
  30. package/dist/bucket/index.d.ts.map +1 -1
  31. package/dist/cache/core/index.d.ts.map +1 -1
  32. package/dist/cache/redis/index.d.ts.map +1 -1
  33. package/dist/cache/redis/index.js +2 -2
  34. package/dist/cache/redis/index.js.map +1 -1
  35. package/dist/cli/index.d.ts +5900 -165
  36. package/dist/cli/index.d.ts.map +1 -1
  37. package/dist/cli/index.js +1481 -639
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/command/index.d.ts +8 -4
  40. package/dist/command/index.d.ts.map +1 -1
  41. package/dist/command/index.js +29 -25
  42. package/dist/command/index.js.map +1 -1
  43. package/dist/core/index.browser.js +563 -54
  44. package/dist/core/index.browser.js.map +1 -1
  45. package/dist/core/index.d.ts +175 -8
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js +564 -54
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/index.native.js +563 -54
  50. package/dist/core/index.native.js.map +1 -1
  51. package/dist/datetime/index.d.ts.map +1 -1
  52. package/dist/datetime/index.js +4 -4
  53. package/dist/datetime/index.js.map +1 -1
  54. package/dist/email/index.d.ts +89 -42
  55. package/dist/email/index.d.ts.map +1 -1
  56. package/dist/email/index.js +129 -33
  57. package/dist/email/index.js.map +1 -1
  58. package/dist/fake/index.d.ts +7969 -2
  59. package/dist/fake/index.d.ts.map +1 -1
  60. package/dist/fake/index.js +22 -22
  61. package/dist/fake/index.js.map +1 -1
  62. package/dist/file/index.d.ts +134 -1
  63. package/dist/file/index.d.ts.map +1 -1
  64. package/dist/file/index.js +253 -1
  65. package/dist/file/index.js.map +1 -1
  66. package/dist/lock/core/index.d.ts.map +1 -1
  67. package/dist/lock/redis/index.d.ts.map +1 -1
  68. package/dist/logger/index.d.ts +1 -2
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +1 -5
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/mcp/index.d.ts +19 -1
  73. package/dist/mcp/index.d.ts.map +1 -1
  74. package/dist/mcp/index.js +28 -4
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/orm/chunk-DH6iiROE.js +38 -0
  77. package/dist/orm/index.browser.js +9 -9
  78. package/dist/orm/index.browser.js.map +1 -1
  79. package/dist/orm/index.bun.js +2821 -0
  80. package/dist/orm/index.bun.js.map +1 -0
  81. package/dist/orm/index.d.ts +318 -169
  82. package/dist/orm/index.d.ts.map +1 -1
  83. package/dist/orm/index.js +2086 -1776
  84. package/dist/orm/index.js.map +1 -1
  85. package/dist/queue/core/index.d.ts +4 -4
  86. package/dist/queue/core/index.d.ts.map +1 -1
  87. package/dist/queue/redis/index.d.ts.map +1 -1
  88. package/dist/redis/index.bun.js +285 -0
  89. package/dist/redis/index.bun.js.map +1 -0
  90. package/dist/redis/index.d.ts +13 -31
  91. package/dist/redis/index.d.ts.map +1 -1
  92. package/dist/redis/index.js +18 -38
  93. package/dist/redis/index.js.map +1 -1
  94. package/dist/retry/index.d.ts.map +1 -1
  95. package/dist/router/index.d.ts.map +1 -1
  96. package/dist/scheduler/index.d.ts +83 -1
  97. package/dist/scheduler/index.d.ts.map +1 -1
  98. package/dist/scheduler/index.js +393 -1
  99. package/dist/scheduler/index.js.map +1 -1
  100. package/dist/security/index.browser.js +5 -1
  101. package/dist/security/index.browser.js.map +1 -1
  102. package/dist/security/index.d.ts +598 -112
  103. package/dist/security/index.d.ts.map +1 -1
  104. package/dist/security/index.js +1808 -97
  105. package/dist/security/index.js.map +1 -1
  106. package/dist/server/auth/index.d.ts +1200 -175
  107. package/dist/server/auth/index.d.ts.map +1 -1
  108. package/dist/server/auth/index.js +1268 -37
  109. package/dist/server/auth/index.js.map +1 -1
  110. package/dist/server/cache/index.d.ts +6 -3
  111. package/dist/server/cache/index.d.ts.map +1 -1
  112. package/dist/server/cache/index.js +1 -1
  113. package/dist/server/cache/index.js.map +1 -1
  114. package/dist/server/compress/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.d.ts.map +1 -1
  116. package/dist/server/cookies/index.js +3 -3
  117. package/dist/server/cookies/index.js.map +1 -1
  118. package/dist/server/core/index.d.ts +115 -13
  119. package/dist/server/core/index.d.ts.map +1 -1
  120. package/dist/server/core/index.js +321 -139
  121. package/dist/server/core/index.js.map +1 -1
  122. package/dist/server/cors/index.d.ts +0 -1
  123. package/dist/server/cors/index.d.ts.map +1 -1
  124. package/dist/server/health/index.d.ts +0 -1
  125. package/dist/server/health/index.d.ts.map +1 -1
  126. package/dist/server/helmet/index.d.ts.map +1 -1
  127. package/dist/server/links/index.browser.js +9 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.d.ts +1 -2
  130. package/dist/server/links/index.d.ts.map +1 -1
  131. package/dist/server/links/index.js +14 -7
  132. package/dist/server/links/index.js.map +1 -1
  133. package/dist/server/metrics/index.d.ts +514 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/metrics/index.js +4462 -4
  136. package/dist/server/metrics/index.js.map +1 -1
  137. package/dist/server/multipart/index.d.ts.map +1 -1
  138. package/dist/server/proxy/index.d.ts +0 -1
  139. package/dist/server/proxy/index.d.ts.map +1 -1
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts.map +1 -1
  142. package/dist/server/swagger/index.d.ts +1 -2
  143. package/dist/server/swagger/index.d.ts.map +1 -1
  144. package/dist/server/swagger/index.js +1 -2
  145. package/dist/server/swagger/index.js.map +1 -1
  146. package/dist/sms/index.d.ts +3 -1
  147. package/dist/sms/index.d.ts.map +1 -1
  148. package/dist/sms/index.js +10 -10
  149. package/dist/sms/index.js.map +1 -1
  150. package/dist/thread/index.d.ts +0 -1
  151. package/dist/thread/index.d.ts.map +1 -1
  152. package/dist/thread/index.js +2 -2
  153. package/dist/thread/index.js.map +1 -1
  154. package/dist/topic/core/index.d.ts.map +1 -1
  155. package/dist/topic/redis/index.d.ts.map +1 -1
  156. package/dist/vite/index.d.ts +6315 -149
  157. package/dist/vite/index.d.ts.map +1 -1
  158. package/dist/vite/index.js +140 -469
  159. package/dist/vite/index.js.map +1 -1
  160. package/dist/websocket/index.browser.js +9 -9
  161. package/dist/websocket/index.browser.js.map +1 -1
  162. package/dist/websocket/index.d.ts +28 -28
  163. package/dist/websocket/index.d.ts.map +1 -1
  164. package/dist/websocket/index.js +9 -9
  165. package/dist/websocket/index.js.map +1 -1
  166. package/package.json +13 -18
  167. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  168. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  169. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  170. package/src/api/users/entities/users.ts +1 -1
  171. package/src/api/users/index.ts +8 -8
  172. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  173. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  174. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  175. package/src/api/users/services/CredentialService.ts +7 -7
  176. package/src/api/users/services/IdentityService.ts +4 -4
  177. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  178. package/src/api/users/services/RegistrationService.ts +38 -27
  179. package/src/api/users/services/SessionCrudService.ts +3 -3
  180. package/src/api/users/services/SessionService.spec.ts +3 -3
  181. package/src/api/users/services/SessionService.ts +27 -18
  182. package/src/api/users/services/UserService.ts +7 -7
  183. package/src/batch/providers/BatchProvider.ts +1 -2
  184. package/src/cli/apps/AlephaCli.ts +2 -2
  185. package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
  186. package/src/cli/assets/apiHelloControllerTs.ts +19 -0
  187. package/src/cli/assets/apiIndexTs.ts +16 -0
  188. package/src/cli/assets/biomeJson.ts +2 -1
  189. package/src/cli/assets/claudeMd.ts +308 -0
  190. package/src/cli/assets/dummySpecTs.ts +2 -1
  191. package/src/cli/assets/editorconfig.ts +2 -1
  192. package/src/cli/assets/mainBrowserTs.ts +4 -3
  193. package/src/cli/assets/mainCss.ts +24 -0
  194. package/src/cli/assets/mainServerTs.ts +24 -0
  195. package/src/cli/assets/tsconfigJson.ts +2 -1
  196. package/src/cli/assets/webAppRouterTs.ts +16 -0
  197. package/src/cli/assets/webHelloComponentTsx.ts +20 -0
  198. package/src/cli/assets/webIndexTs.ts +16 -0
  199. package/src/cli/atoms/appEntryOptions.ts +13 -0
  200. package/src/cli/atoms/buildOptions.ts +1 -1
  201. package/src/cli/atoms/changelogOptions.ts +1 -1
  202. package/src/cli/commands/build.ts +97 -61
  203. package/src/cli/commands/db.ts +21 -18
  204. package/src/cli/commands/deploy.ts +17 -5
  205. package/src/cli/commands/dev.ts +26 -47
  206. package/src/cli/commands/gen/env.ts +1 -1
  207. package/src/cli/commands/init.ts +79 -25
  208. package/src/cli/commands/lint.ts +9 -3
  209. package/src/cli/commands/test.ts +8 -2
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +4 -2
  212. package/src/cli/defineConfig.ts +9 -0
  213. package/src/cli/index.ts +2 -1
  214. package/src/cli/providers/AppEntryProvider.ts +131 -0
  215. package/src/cli/providers/ViteBuildProvider.ts +82 -0
  216. package/src/cli/providers/ViteDevServerProvider.ts +350 -0
  217. package/src/cli/providers/ViteTemplateProvider.ts +27 -0
  218. package/src/cli/services/AlephaCliUtils.ts +72 -602
  219. package/src/cli/services/PackageManagerUtils.ts +308 -0
  220. package/src/cli/services/ProjectScaffolder.ts +329 -0
  221. package/src/command/helpers/Runner.ts +15 -3
  222. package/src/core/Alepha.ts +2 -8
  223. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  224. package/src/core/index.shared.ts +1 -0
  225. package/src/core/index.ts +2 -0
  226. package/src/core/primitives/$hook.ts +6 -2
  227. package/src/core/primitives/$module.spec.ts +4 -0
  228. package/src/core/primitives/$module.ts +12 -0
  229. package/src/core/providers/AlsProvider.ts +1 -1
  230. package/src/core/providers/CodecManager.spec.ts +12 -6
  231. package/src/core/providers/CodecManager.ts +26 -6
  232. package/src/core/providers/EventManager.ts +169 -13
  233. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
  234. package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
  235. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  236. package/src/core/providers/StateManager.spec.ts +27 -16
  237. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  238. package/src/email/providers/LocalEmailProvider.ts +52 -15
  239. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  240. package/src/file/errors/FileError.ts +7 -0
  241. package/src/file/index.ts +9 -1
  242. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  243. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  244. package/src/mcp/errors/McpError.ts +30 -0
  245. package/src/mcp/index.ts +3 -0
  246. package/src/mcp/transports/SseMcpTransport.ts +16 -6
  247. package/src/orm/index.browser.ts +1 -19
  248. package/src/orm/index.bun.ts +77 -0
  249. package/src/orm/index.shared-server.ts +22 -0
  250. package/src/orm/index.shared.ts +15 -0
  251. package/src/orm/index.ts +19 -39
  252. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  253. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  254. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  255. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  256. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  257. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  258. package/src/orm/services/Repository.ts +19 -0
  259. package/src/redis/index.bun.ts +35 -0
  260. package/src/redis/providers/BunRedisProvider.ts +12 -43
  261. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  262. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  263. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  264. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  265. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  266. package/src/security/index.browser.ts +5 -0
  267. package/src/security/index.ts +90 -7
  268. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  269. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  270. package/src/security/primitives/$role.ts +5 -5
  271. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  272. package/src/security/primitives/$serviceAccount.ts +3 -3
  273. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  274. package/src/server/auth/primitives/$auth.ts +10 -10
  275. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  276. package/src/server/auth/primitives/$authGithub.ts +3 -3
  277. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  278. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  279. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  280. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  281. package/src/server/core/index.ts +1 -1
  282. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  283. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  284. package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
  285. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  286. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  287. package/src/server/core/providers/ServerProvider.ts +144 -24
  288. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  289. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  290. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  291. package/src/server/links/index.browser.ts +2 -0
  292. package/src/server/links/index.ts +3 -1
  293. package/src/server/links/providers/LinkProvider.ts +1 -1
  294. package/src/server/swagger/index.ts +1 -1
  295. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  296. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  297. package/src/vite/index.ts +3 -2
  298. package/src/vite/tasks/buildClient.ts +0 -1
  299. package/src/vite/tasks/buildServer.ts +80 -22
  300. package/src/vite/tasks/copyAssets.ts +5 -4
  301. package/src/vite/tasks/generateCloudflare.ts +7 -0
  302. package/src/vite/tasks/generateSitemap.ts +64 -23
  303. package/src/vite/tasks/index.ts +0 -2
  304. package/src/vite/tasks/prerenderPages.ts +49 -24
  305. package/dist/server/security/index.browser.js +0 -13
  306. package/dist/server/security/index.browser.js.map +0 -1
  307. package/dist/server/security/index.d.ts +0 -173
  308. package/dist/server/security/index.d.ts.map +0 -1
  309. package/dist/server/security/index.js +0 -311
  310. package/dist/server/security/index.js.map +0 -1
  311. package/src/cli/assets/appRouterTs.ts +0 -9
  312. package/src/cli/assets/indexHtml.ts +0 -15
  313. package/src/cli/assets/mainTs.ts +0 -13
  314. package/src/cli/commands/format.ts +0 -17
  315. package/src/server/security/index.browser.ts +0 -10
  316. package/src/server/security/index.ts +0 -94
  317. package/src/vite/helpers/boot.ts +0 -106
  318. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  319. package/src/vite/tasks/devServer.ts +0 -69
  320. package/src/vite/tasks/runAlepha.ts +0 -270
  321. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  322. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -1,24 +1,64 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
1
+ import { $atom, $hook, $inject, $use, type Static, t } from "alepha";
2
+ import { FileSystemProvider } from "alepha/file";
3
3
  import { $logger } from "alepha/logger";
4
4
  import { EmailError } from "../errors/EmailError.ts";
5
5
  import type { EmailProvider, EmailSendOptions } from "./EmailProvider.ts";
6
6
 
7
- export interface LocalEmailProviderOptions {
8
- /**
9
- * Directory to save email files.
10
- */
11
- directory?: string;
7
+ // ---------------------------------------------------------------------------------------------------------------------
8
+
9
+ /**
10
+ * Local email provider configuration atom
11
+ */
12
+ export const localEmailOptions = $atom({
13
+ name: "alepha.email.local.options",
14
+ schema: t.object({
15
+ directory: t.string({
16
+ description: "Directory path where email files will be stored",
17
+ }),
18
+ }),
19
+ default: {
20
+ directory: "node_modules/.alepha/emails",
21
+ },
22
+ });
23
+
24
+ export type LocalEmailProviderOptions = Static<typeof localEmailOptions.schema>;
25
+
26
+ declare module "alepha" {
27
+ interface State {
28
+ [localEmailOptions.key]: LocalEmailProviderOptions;
29
+ }
12
30
  }
13
31
 
32
+ // ---------------------------------------------------------------------------------------------------------------------
33
+
14
34
  export class LocalEmailProvider implements EmailProvider {
15
35
  protected readonly log = $logger();
16
- protected readonly directory: string;
36
+ protected readonly fs = $inject(FileSystemProvider);
37
+ protected readonly options = $use(localEmailOptions);
17
38
 
18
- constructor(options: LocalEmailProviderOptions = {}) {
19
- this.directory = options.directory ?? "node_modules/.alepha/emails";
39
+ protected get directory(): string {
40
+ return this.options.directory;
20
41
  }
21
42
 
43
+ protected onStart = $hook({
44
+ on: "start",
45
+ handler: async () => {
46
+ try {
47
+ await this.fs.mkdir(this.directory, { recursive: true });
48
+ this.log.info("Email directory OK", {
49
+ directory: this.directory,
50
+ });
51
+ } catch (error) {
52
+ const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;
53
+ this.log.error(message, { directory: this.directory });
54
+ throw new EmailError(
55
+ message,
56
+ error instanceof Error ? error : undefined,
57
+ );
58
+ }
59
+ },
60
+ });
61
+
22
62
  public async send(options: EmailSendOptions): Promise<void> {
23
63
  const { to, subject, body } = options;
24
64
 
@@ -29,15 +69,12 @@ export class LocalEmailProvider implements EmailProvider {
29
69
  });
30
70
 
31
71
  try {
32
- // Ensure directory exists
33
- await fs.mkdir(this.directory, { recursive: true });
34
-
35
72
  // Create filename: emailcontact+date
36
73
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
37
74
  for (const recipient of Array.isArray(to) ? to : [to]) {
38
75
  const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, "_");
39
76
  const filename = `${sanitizedEmail}+${timestamp}.html`;
40
- const filepath = path.join(this.directory, filename);
77
+ const filepath = this.fs.join(this.directory, filename);
41
78
 
42
79
  // Create HTML content
43
80
  const htmlContent = this.createEmailHtml({
@@ -47,7 +84,7 @@ export class LocalEmailProvider implements EmailProvider {
47
84
  });
48
85
 
49
86
  // Write to file
50
- await fs.writeFile(filepath, htmlContent, "utf8");
87
+ await this.fs.writeFile(filepath, htmlContent);
51
88
 
52
89
  this.log.info("Email saved to local file", { filepath, to, subject });
53
90
  }
@@ -1,69 +1,173 @@
1
- import { $env, $hook, t } from "alepha";
1
+ import { $atom, $env, $hook, $use, type Static, t } from "alepha";
2
2
  import { $logger } from "alepha/logger";
3
3
  import type { Transporter } from "nodemailer";
4
4
  import nodemailer from "nodemailer";
5
5
  import { EmailError } from "../errors/EmailError.ts";
6
6
  import type { EmailProvider, EmailSendOptions } from "./EmailProvider.ts";
7
7
 
8
+ // ---------------------------------------------------------------------------------------------------------------------
9
+
10
+ /**
11
+ * Environment variables for nodemailer configuration
12
+ */
8
13
  const envSchema = t.object({
9
- EMAIL_HOST: t.text({
10
- description: "SMTP server host",
11
- }),
14
+ EMAIL_HOST: t.optional(
15
+ t.text({
16
+ description: "SMTP server host",
17
+ }),
18
+ ),
12
19
  EMAIL_PORT: t.number({
13
20
  default: 587,
14
21
  description: "SMTP server port",
15
22
  }),
16
- EMAIL_USER: t.text({
17
- description: "SMTP authentication username",
18
- }),
19
- EMAIL_PASS: t.text({
20
- description: "SMTP authentication password",
21
- }),
22
- EMAIL_FROM: t.text({
23
- description: "Default from email address",
24
- }),
23
+ EMAIL_USER: t.optional(
24
+ t.text({
25
+ description: "SMTP authentication username",
26
+ }),
27
+ ),
28
+ EMAIL_PASS: t.optional(
29
+ t.text({
30
+ description: "SMTP authentication password",
31
+ }),
32
+ ),
33
+ EMAIL_FROM: t.optional(
34
+ t.text({
35
+ description: "Default from email address",
36
+ }),
37
+ ),
25
38
  EMAIL_SECURE: t.boolean({
26
39
  default: false,
27
40
  description: "Use secure connection (TLS)",
28
41
  }),
29
42
  });
30
43
 
31
- export interface NodemailerEmailProviderOptions {
32
- /**
33
- * Custom transporter configuration.
34
- * If provided, will override environment variables.
35
- */
36
- transporter?: Transporter;
44
+ // ---------------------------------------------------------------------------------------------------------------------
37
45
 
38
- /**
39
- * Custom from email address.
40
- * If not provided, will use EMAIL_FROM from environment.
41
- */
42
- from?: string;
46
+ /**
47
+ * Nodemailer connection pooling and rate limiting options
48
+ */
49
+ export const nodemailerEmailOptions = $atom({
50
+ name: "alepha.email.nodemailer.options",
51
+ schema: t.object({
52
+ pool: t.optional(
53
+ t.boolean({
54
+ description: "Enable connection pooling",
55
+ }),
56
+ ),
57
+ maxConnections: t.optional(
58
+ t.number({
59
+ description: "Maximum number of connections in pool",
60
+ }),
61
+ ),
62
+ maxMessages: t.optional(
63
+ t.number({
64
+ description: "Maximum messages per connection",
65
+ }),
66
+ ),
67
+ rateDelta: t.optional(
68
+ t.number({
69
+ description: "Time in milliseconds between message sends",
70
+ }),
71
+ ),
72
+ rateLimit: t.optional(
73
+ t.number({
74
+ description: "Maximum number of messages per rateDelta",
75
+ }),
76
+ ),
77
+ }),
78
+ default: {},
79
+ });
43
80
 
44
- /**
45
- * Additional nodemailer options.
46
- */
47
- options?: {
48
- pool?: boolean;
49
- maxConnections?: number;
50
- maxMessages?: number;
51
- rateDelta?: number;
52
- rateLimit?: number;
53
- };
81
+ export type NodemailerEmailProviderOptions = Static<
82
+ typeof nodemailerEmailOptions.schema
83
+ >;
84
+
85
+ declare module "alepha" {
86
+ interface State {
87
+ [nodemailerEmailOptions.key]: NodemailerEmailProviderOptions;
88
+ }
54
89
  }
55
90
 
91
+ // ---------------------------------------------------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Email provider using Nodemailer for SMTP transport.
95
+ *
96
+ * Configuration is provided via environment variables:
97
+ * - EMAIL_HOST: SMTP server host
98
+ * - EMAIL_PORT: SMTP server port (default: 587)
99
+ * - EMAIL_USER: SMTP authentication username
100
+ * - EMAIL_PASS: SMTP authentication password
101
+ * - EMAIL_FROM: Default from email address
102
+ * - EMAIL_SECURE: Use secure connection (default: false)
103
+ *
104
+ * Advanced pooling/rate limiting options can be configured via atom:
105
+ * @see {@link nodemailerEmailOptions}
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * // Configure via environment variables
110
+ * // EMAIL_HOST=smtp.example.com
111
+ * // EMAIL_PORT=587
112
+ * // EMAIL_USER=user@example.com
113
+ * // EMAIL_PASS=secret
114
+ * // EMAIL_FROM=noreply@example.com
115
+ *
116
+ * // Optionally configure pooling via atom
117
+ * alepha.state.set(nodemailerEmailOptions.key, {
118
+ * pool: true,
119
+ * maxConnections: 5,
120
+ * rateLimit: 10,
121
+ * });
122
+ * ```
123
+ */
56
124
  export class NodemailerEmailProvider implements EmailProvider {
57
125
  protected readonly env = $env(envSchema);
58
126
  protected readonly log = $logger();
59
- protected transporter: Transporter;
60
- protected fromAddress: string;
127
+ protected readonly options = $use(nodemailerEmailOptions);
128
+ protected transporter: Transporter | null = null;
61
129
 
62
- public readonly options: NodemailerEmailProviderOptions = {};
130
+ protected get host(): string {
131
+ const host = this.env.EMAIL_HOST;
132
+ if (!host) {
133
+ throw new EmailError(
134
+ "Email host not configured. Set EMAIL_HOST env var.",
135
+ );
136
+ }
137
+ return host;
138
+ }
139
+
140
+ protected get port(): number {
141
+ return this.env.EMAIL_PORT;
142
+ }
143
+
144
+ protected get secure(): boolean {
145
+ return this.env.EMAIL_SECURE;
146
+ }
147
+
148
+ protected get user(): string | undefined {
149
+ return this.env.EMAIL_USER;
150
+ }
151
+
152
+ protected get pass(): string | undefined {
153
+ return this.env.EMAIL_PASS;
154
+ }
155
+
156
+ protected get fromAddress(): string {
157
+ const from = this.env.EMAIL_FROM;
158
+ if (!from) {
159
+ throw new EmailError(
160
+ "Email from address not configured. Set EMAIL_FROM env var.",
161
+ );
162
+ }
163
+ return from;
164
+ }
63
165
 
64
- constructor() {
65
- this.fromAddress = this.options.from ?? this.env.EMAIL_FROM;
66
- this.transporter = this.createTransporter();
166
+ protected getTransporter(): Transporter {
167
+ if (!this.transporter) {
168
+ this.transporter = this.createTransporter();
169
+ }
170
+ return this.transporter;
67
171
  }
68
172
 
69
173
  public async send(options: EmailSendOptions): Promise<void> {
@@ -71,7 +175,7 @@ export class NodemailerEmailProvider implements EmailProvider {
71
175
  this.log.debug("Sending email via Nodemailer", { to, subject });
72
176
 
73
177
  try {
74
- const result = await this.transporter.sendMail({
178
+ const result = await this.getTransporter().sendMail({
75
179
  from: this.fromAddress,
76
180
  to,
77
181
  subject,
@@ -92,26 +196,30 @@ export class NodemailerEmailProvider implements EmailProvider {
92
196
  }
93
197
 
94
198
  protected createTransporter(): Transporter {
95
- if (this.options.transporter) {
96
- return this.options.transporter;
97
- }
98
-
99
199
  const transporterConfig = {
100
- host: this.env.EMAIL_HOST,
101
- port: this.env.EMAIL_PORT,
102
- secure: this.env.EMAIL_SECURE,
103
- auth: {
104
- user: this.env.EMAIL_USER,
105
- pass: this.env.EMAIL_PASS,
106
- },
107
- ...this.options.options,
200
+ host: this.host,
201
+ port: this.port,
202
+ secure: this.secure,
203
+ auth:
204
+ this.user && this.pass
205
+ ? {
206
+ user: this.user,
207
+ pass: this.pass,
208
+ }
209
+ : undefined,
210
+ pool: this.options.pool,
211
+ maxConnections: this.options.maxConnections,
212
+ maxMessages: this.options.maxMessages,
213
+ rateDelta: this.options.rateDelta,
214
+ rateLimit: this.options.rateLimit,
108
215
  };
109
216
 
110
217
  this.log.debug("Creating Nodemailer transporter", {
111
218
  host: transporterConfig.host,
112
219
  port: transporterConfig.port,
113
220
  secure: transporterConfig.secure,
114
- user: transporterConfig.auth.user,
221
+ user: transporterConfig.auth?.user,
222
+ pool: transporterConfig.pool,
115
223
  });
116
224
 
117
225
  return nodemailer.createTransport(transporterConfig);
@@ -122,7 +230,7 @@ export class NodemailerEmailProvider implements EmailProvider {
122
230
  */
123
231
  public async verify(): Promise<boolean> {
124
232
  try {
125
- await this.transporter.verify();
233
+ await this.getTransporter().verify();
126
234
  this.log.info("Email server connection verified");
127
235
  return true;
128
236
  } catch (error) {
@@ -135,7 +243,10 @@ export class NodemailerEmailProvider implements EmailProvider {
135
243
  * Close the transporter connection.
136
244
  */
137
245
  public close(): void {
138
- this.transporter.close();
246
+ if (this.transporter) {
247
+ this.transporter.close();
248
+ this.transporter = null;
249
+ }
139
250
  }
140
251
 
141
252
  protected readonly onStart = $hook({
@@ -0,0 +1,7 @@
1
+ export class FileError extends Error {
2
+ constructor(message: string, cause?: Error) {
3
+ super(message);
4
+ this.name = "FileError";
5
+ this.cause = cause;
6
+ }
7
+ }
package/src/file/index.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import { $module } from "alepha";
2
2
  import { FileSystemProvider } from "./providers/FileSystemProvider.ts";
3
+ import { MemoryFileSystemProvider } from "./providers/MemoryFileSystemProvider.ts";
3
4
  import { NodeFileSystemProvider } from "./providers/NodeFileSystemProvider.ts";
4
5
  import { FileDetector } from "./services/FileDetector.ts";
5
6
 
6
7
  // ---------------------------------------------------------------------------------------------------------------------
7
8
 
9
+ export * from "./errors/FileError.ts";
8
10
  export * from "./providers/FileSystemProvider.ts";
11
+ export * from "./providers/MemoryFileSystemProvider.ts";
9
12
  export * from "./providers/NodeFileSystemProvider.ts";
10
13
  export * from "./services/FileDetector.ts";
11
14
 
@@ -25,7 +28,12 @@ export * from "./services/FileDetector.ts";
25
28
  export const AlephaFile = $module({
26
29
  name: "alepha.file",
27
30
  primitives: [],
28
- services: [FileDetector, FileSystemProvider, NodeFileSystemProvider],
31
+ services: [
32
+ FileDetector,
33
+ FileSystemProvider,
34
+ MemoryFileSystemProvider,
35
+ NodeFileSystemProvider,
36
+ ],
29
37
  register: (alepha) =>
30
38
  alepha.with({
31
39
  optional: true,