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.
- package/README.md +44 -102
- package/dist/api/audits/index.d.ts +331 -443
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +2 -2
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +0 -113
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +2 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +151 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +164 -276
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +4 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +265 -377
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +1 -2
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +195 -301
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +203 -184
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +1 -2
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +5900 -165
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1481 -639
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +8 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +29 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +563 -54
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +175 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +564 -54
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +563 -54
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +4 -4
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +89 -42
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +129 -33
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +7969 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +22 -22
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +134 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +253 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +1 -2
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -5
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +19 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +28 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/chunk-DH6iiROE.js +38 -0
- package/dist/orm/index.browser.js +9 -9
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2821 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +318 -169
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2086 -1776
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/redis/index.bun.js +285 -0
- package/dist/redis/index.bun.js.map +1 -0
- package/dist/redis/index.d.ts +13 -31
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +18 -38
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +83 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +393 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.browser.js +5 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +598 -112
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1808 -97
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1200 -175
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1268 -37
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +6 -3
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +115 -13
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +321 -139
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +0 -1
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +0 -1
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/links/index.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +1 -2
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +14 -7
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4462 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +0 -1
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +1 -2
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -1
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +10 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +0 -1
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +6315 -149
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +140 -469
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +9 -9
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +28 -28
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +9 -9
- package/dist/websocket/index.js.map +1 -1
- package/package.json +13 -18
- package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
- package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
- package/src/api/users/entities/users.ts +1 -1
- package/src/api/users/index.ts +8 -8
- package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
- package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
- package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
- package/src/api/users/services/CredentialService.ts +7 -7
- package/src/api/users/services/IdentityService.ts +4 -4
- package/src/api/users/services/RegistrationService.spec.ts +25 -27
- package/src/api/users/services/RegistrationService.ts +38 -27
- package/src/api/users/services/SessionCrudService.ts +3 -3
- package/src/api/users/services/SessionService.spec.ts +3 -3
- package/src/api/users/services/SessionService.ts +27 -18
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaCli.ts +2 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
- package/src/cli/assets/apiHelloControllerTs.ts +19 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/biomeJson.ts +2 -1
- package/src/cli/assets/claudeMd.ts +308 -0
- package/src/cli/assets/dummySpecTs.ts +2 -1
- package/src/cli/assets/editorconfig.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +4 -3
- package/src/cli/assets/mainCss.ts +24 -0
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/tsconfigJson.ts +2 -1
- package/src/cli/assets/webAppRouterTs.ts +16 -0
- package/src/cli/assets/webHelloComponentTsx.ts +20 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +97 -61
- package/src/cli/commands/db.ts +21 -18
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +26 -47
- package/src/cli/commands/gen/env.ts +1 -1
- package/src/cli/commands/init.ts +79 -25
- package/src/cli/commands/lint.ts +9 -3
- package/src/cli/commands/test.ts +8 -2
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +4 -2
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +82 -0
- package/src/cli/providers/ViteDevServerProvider.ts +350 -0
- package/src/cli/providers/ViteTemplateProvider.ts +27 -0
- package/src/cli/services/AlephaCliUtils.ts +72 -602
- package/src/cli/services/PackageManagerUtils.ts +308 -0
- package/src/cli/services/ProjectScaffolder.ts +329 -0
- package/src/command/helpers/Runner.ts +15 -3
- package/src/core/Alepha.ts +2 -8
- package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/primitives/$hook.ts +6 -2
- package/src/core/primitives/$module.spec.ts +4 -0
- package/src/core/primitives/$module.ts +12 -0
- package/src/core/providers/AlsProvider.ts +1 -1
- package/src/core/providers/CodecManager.spec.ts +12 -6
- package/src/core/providers/CodecManager.ts +26 -6
- package/src/core/providers/EventManager.ts +169 -13
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
- package/src/core/providers/SchemaValidator.spec.ts +236 -0
- package/src/core/providers/StateManager.spec.ts +27 -16
- package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
- package/src/email/providers/LocalEmailProvider.ts +52 -15
- package/src/email/providers/NodemailerEmailProvider.ts +167 -56
- package/src/file/errors/FileError.ts +7 -0
- package/src/file/index.ts +9 -1
- package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
- package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/transports/SseMcpTransport.ts +16 -6
- package/src/orm/index.browser.ts +1 -19
- package/src/orm/index.bun.ts +77 -0
- package/src/orm/index.shared-server.ts +22 -0
- package/src/orm/index.shared.ts +15 -0
- package/src/orm/index.ts +19 -39
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
- package/src/orm/services/Repository.ts +19 -0
- package/src/redis/index.bun.ts +35 -0
- package/src/redis/providers/BunRedisProvider.ts +12 -43
- package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
- package/src/redis/providers/NodeRedisProvider.ts +16 -34
- package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
- package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
- package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
- package/src/security/index.browser.ts +5 -0
- package/src/security/index.ts +90 -7
- package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
- package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
- package/src/security/primitives/$role.ts +5 -5
- package/src/security/primitives/$serviceAccount.spec.ts +5 -5
- package/src/security/primitives/$serviceAccount.ts +3 -3
- package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
- package/src/server/auth/primitives/$auth.ts +10 -10
- package/src/server/auth/primitives/$authCredentials.ts +3 -3
- package/src/server/auth/primitives/$authGithub.ts +3 -3
- package/src/server/auth/primitives/$authGoogle.ts +3 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
- package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/index.ts +1 -1
- package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
- package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
- package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
- package/src/server/core/providers/ServerProvider.ts +144 -24
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +3 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/swagger/index.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +0 -1
- package/src/vite/tasks/buildServer.ts +80 -22
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- package/dist/server/security/index.browser.js +0 -13
- package/dist/server/security/index.browser.js.map +0 -1
- package/dist/server/security/index.d.ts +0 -173
- package/dist/server/security/index.d.ts.map +0 -1
- package/dist/server/security/index.js +0 -311
- package/dist/server/security/index.js.map +0 -1
- package/src/cli/assets/appRouterTs.ts +0 -9
- package/src/cli/assets/indexHtml.ts +0 -15
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/cli/commands/format.ts +0 -17
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/helpers/boot.ts +0 -106
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -69
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
package/dist/email/index.js.map
CHANGED
|
@@ -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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\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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"}
|