alepha 0.20.2 → 0.20.3

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 (208) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts.map +1 -1
  7. package/dist/api/audits/index.js +49 -0
  8. package/dist/api/audits/index.js.map +1 -1
  9. package/dist/api/files/index.d.ts.map +1 -1
  10. package/dist/api/files/index.js.map +1 -1
  11. package/dist/api/jobs/index.d.ts +16 -75
  12. package/dist/api/jobs/index.d.ts.map +1 -1
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.js.map +1 -1
  15. package/dist/api/notifications/index.d.ts +1 -10
  16. package/dist/api/notifications/index.d.ts.map +1 -1
  17. package/dist/api/organizations/index.d.ts.map +1 -1
  18. package/dist/api/parameters/index.browser.js +37 -0
  19. package/dist/api/parameters/index.browser.js.map +1 -1
  20. package/dist/api/parameters/index.d.ts +4 -65
  21. package/dist/api/parameters/index.d.ts.map +1 -1
  22. package/dist/api/parameters/index.js +37 -0
  23. package/dist/api/parameters/index.js.map +1 -1
  24. package/dist/api/payments/index.d.ts.map +1 -1
  25. package/dist/api/payments/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +207 -5184
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2 -4
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts.map +1 -1
  31. package/dist/api/verifications/index.js +2 -1
  32. package/dist/api/verifications/index.js.map +1 -1
  33. package/dist/bucket/index.js +5 -1
  34. package/dist/bucket/index.js.map +1 -1
  35. package/dist/bucket/index.workerd.js +5 -1
  36. package/dist/bucket/index.workerd.js.map +1 -1
  37. package/dist/cache/core/index.js.map +1 -1
  38. package/dist/cache/core/index.workerd.js.map +1 -1
  39. package/dist/captcha/index.js.map +1 -1
  40. package/dist/cli/core/index.d.ts +217 -11647
  41. package/dist/cli/core/index.d.ts.map +1 -1
  42. package/dist/cli/core/index.js +706 -42
  43. package/dist/cli/core/index.js.map +1 -1
  44. package/dist/cli/devtools/index.js +7 -1
  45. package/dist/cli/devtools/index.js.map +1 -1
  46. package/dist/cli/platform/index.d.ts +41 -64
  47. package/dist/cli/platform/index.d.ts.map +1 -1
  48. package/dist/cli/platform/index.js +47 -0
  49. package/dist/cli/platform/index.js.map +1 -1
  50. package/dist/cli/vendor/index.js +15 -0
  51. package/dist/cli/vendor/index.js.map +1 -1
  52. package/dist/command/index.js +1 -1
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js.map +1 -1
  55. package/dist/core/index.d.ts +2 -8
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js.map +1 -1
  58. package/dist/core/index.native.js.map +1 -1
  59. package/dist/core/index.workerd.js.map +1 -1
  60. package/dist/crypto/index.js.map +1 -1
  61. package/dist/datetime/index.js.map +1 -1
  62. package/dist/email/core/index.js.map +1 -1
  63. package/dist/email/smtp/index.js +2 -10522
  64. package/dist/email/smtp/index.js.map +1 -1
  65. package/dist/fake/index.d.ts +4 -8085
  66. package/dist/fake/index.d.ts.map +1 -1
  67. package/dist/fake/index.js +3 -33554
  68. package/dist/fake/index.js.map +1 -1
  69. package/dist/lock/core/index.js.map +1 -1
  70. package/dist/lock/redis/index.js.map +1 -1
  71. package/dist/logger/index.js +32 -1
  72. package/dist/logger/index.js.map +1 -1
  73. package/dist/mcp/index.js +5 -1
  74. package/dist/mcp/index.js.map +1 -1
  75. package/dist/orm/core/index.browser.js +1 -361
  76. package/dist/orm/core/index.browser.js.map +1 -1
  77. package/dist/orm/core/index.bun.js +14 -406
  78. package/dist/orm/core/index.bun.js.map +1 -1
  79. package/dist/orm/core/index.d.ts +96 -5117
  80. package/dist/orm/core/index.d.ts.map +1 -1
  81. package/dist/orm/core/index.js +23 -419
  82. package/dist/orm/core/index.js.map +1 -1
  83. package/dist/orm/postgres/index.bun.js +17 -20
  84. package/dist/orm/postgres/index.bun.js.map +1 -1
  85. package/dist/orm/postgres/index.d.ts +2 -613
  86. package/dist/orm/postgres/index.d.ts.map +1 -1
  87. package/dist/orm/postgres/index.js +17 -20
  88. package/dist/orm/postgres/index.js.map +1 -1
  89. package/dist/react/core/index.js.map +1 -1
  90. package/dist/react/i18n/index.js.map +1 -1
  91. package/dist/react/intro/index.js +22 -17
  92. package/dist/react/intro/index.js.map +1 -1
  93. package/dist/react/router/index.browser.js +78 -2
  94. package/dist/react/router/index.browser.js.map +1 -1
  95. package/dist/react/router/index.d.ts +22 -1
  96. package/dist/react/router/index.d.ts.map +1 -1
  97. package/dist/react/router/index.js +102 -4
  98. package/dist/react/router/index.js.map +1 -1
  99. package/dist/react/testing/index.d.ts +1 -411
  100. package/dist/react/testing/index.d.ts.map +1 -1
  101. package/dist/react/testing/index.js +13 -12293
  102. package/dist/react/testing/index.js.map +1 -1
  103. package/dist/react/ui/index.js +3 -0
  104. package/dist/react/ui/index.js.map +1 -1
  105. package/dist/react/websocket/index.js.map +1 -1
  106. package/dist/redis/index.js.map +1 -1
  107. package/dist/scheduler/index.d.ts +1 -83
  108. package/dist/scheduler/index.d.ts.map +1 -1
  109. package/dist/scheduler/index.js +2 -391
  110. package/dist/scheduler/index.js.map +1 -1
  111. package/dist/scheduler/index.workerd.js +2 -391
  112. package/dist/scheduler/index.workerd.js.map +1 -1
  113. package/dist/security/index.browser.js.map +1 -1
  114. package/dist/security/index.d.ts +2 -325
  115. package/dist/security/index.d.ts.map +1 -1
  116. package/dist/security/index.js +3 -1362
  117. package/dist/security/index.js.map +1 -1
  118. package/dist/server/auth/index.d.ts +1 -1054
  119. package/dist/server/auth/index.d.ts.map +1 -1
  120. package/dist/server/auth/index.js +16 -1224
  121. package/dist/server/auth/index.js.map +1 -1
  122. package/dist/server/cookies/index.js.map +1 -1
  123. package/dist/server/core/index.browser.js.map +1 -1
  124. package/dist/server/core/index.d.ts +1 -4
  125. package/dist/server/core/index.d.ts.map +1 -1
  126. package/dist/server/core/index.js +19 -4
  127. package/dist/server/core/index.js.map +1 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.js.map +1 -1
  130. package/dist/server/metrics/index.d.ts +1 -514
  131. package/dist/server/metrics/index.d.ts.map +1 -1
  132. package/dist/server/metrics/index.js +4 -4356
  133. package/dist/server/metrics/index.js.map +1 -1
  134. package/dist/server/rate-limit/index.js.map +1 -1
  135. package/dist/server/static/index.js.map +1 -1
  136. package/dist/server/swagger/index.js +1 -1
  137. package/dist/server/swagger/index.js.map +1 -1
  138. package/dist/sms/index.js.map +1 -1
  139. package/dist/system/index.browser.js.map +1 -1
  140. package/dist/system/index.js.map +1 -1
  141. package/dist/system/index.workerd.js.map +1 -1
  142. package/dist/topic/core/index.js.map +1 -1
  143. package/dist/websocket/index.browser.js +21 -0
  144. package/dist/websocket/index.browser.js.map +1 -1
  145. package/dist/websocket/index.js +21 -0
  146. package/dist/websocket/index.js.map +1 -1
  147. package/package.json +18 -15
  148. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  149. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  150. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  151. package/src/api/users/services/UserService.ts +1 -5
  152. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  153. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  154. package/src/api/verifications/services/VerificationService.ts +1 -0
  155. package/src/cli/core/__tests__/init.spec.ts +208 -0
  156. package/src/cli/core/commands/init.ts +12 -0
  157. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  158. package/src/cli/core/services/ProjectScaffolder.ts +298 -20
  159. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  160. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  161. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  162. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  163. package/src/cli/core/templates/mainCss.ts +1 -0
  164. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  165. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  166. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  167. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  168. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  169. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  170. package/src/cli/core/templates/webIndexTs.ts +23 -1
  171. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  172. package/src/command/providers/CliProvider.ts +1 -1
  173. package/src/core/interfaces/Service.ts +3 -1
  174. package/src/core/providers/TypeProvider.ts +1 -1
  175. package/src/logger/services/Logger.ts +1 -1
  176. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  177. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  178. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  179. package/src/orm/__tests__/$repository-tests.ts +1 -0
  180. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  181. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  182. package/src/orm/core/index.shared.ts +0 -2
  183. package/src/orm/core/index.ts +1 -2
  184. package/src/orm/core/primitives/$repository.ts +3 -6
  185. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  186. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  187. package/src/orm/core/services/ModelBuilder.ts +1 -13
  188. package/src/orm/core/services/Repository.ts +1 -42
  189. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  190. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  191. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  192. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  193. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  194. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  195. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  196. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  197. package/src/scheduler/providers/CronProvider.ts +1 -1
  198. package/src/security/primitives/$basicAuth.ts +1 -1
  199. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  200. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  201. package/src/server/core/providers/ServerProvider.ts +1 -1
  202. package/src/server/core/providers/ServerRouterProvider.ts +2 -2
  203. package/src/server/core/services/HttpClient.ts +1 -1
  204. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  205. package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
  206. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  207. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  208. package/src/orm/core/primitives/$view.ts +0 -88
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n}\n","import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n // It's not ideal from a security pov but it's the best for convenience,\n // it can be changed to a hard error in a future release.\n if (\n this.secretKey === DEFAULT_SECRET_KEY_VALUE &&\n this.alepha.isProduction()\n ) {\n this.log.warn(\n \"Using default secret key. Please set a secure APP_SECRET environment variable.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n}\n","import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n // It's not ideal from a security pov but it's the best for convenience,\n // it can be changed to a hard error in a future release.\n if (\n this.secretKey === DEFAULT_SECRET_KEY_VALUE &&\n this.alepha.isProduction()\n ) {\n this.log.warn(\n \"Using default secret key. Please set a secure APP_SECRET environment variable.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type PluginFunc,\n} from \"dayjs\";\nimport dayjsDuration from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type DateTime = DayjsApi.Dayjs;\nexport type Duration = dayjsDuration.Duration;\nexport type DurationLike =\n | number\n | dayjsDuration.Duration\n | [number, ManipulateType];\n\nexport const dayjs = DayjsApi;\nexport const isDateTime = (value: unknown): value is DateTime => {\n return dayjs.isDayjs(value);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n dayjs.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n dayjs.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return dayjs.isDayjs(value);\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(\n date: string | number | Date | Dayjs | null | undefined,\n ): DateTime {\n return dayjs.utc(date);\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: string | number | Date | Dayjs | null | undefined): DateTime {\n return dayjs(date);\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: Date | string | DateTime = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return dayjs();\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (Array.isArray(duration)) {\n return dayjs.duration(duration[0], duration[1]);\n }\n\n if (typeof duration === \"number\") {\n return dayjs.duration(duration, unit || \"milliseconds\");\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return dayjs.isDuration(this.duration(value as DurationLike));\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next < this.now()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed). Use `$rateLimit` for distributed rate limiting.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type PluginFunc,\n} from \"dayjs\";\nimport dayjsDuration from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type DateTime = DayjsApi.Dayjs;\nexport type Duration = dayjsDuration.Duration;\nexport type DurationLike =\n | number\n | dayjsDuration.Duration\n | [number, ManipulateType];\n\nexport const dayjs = DayjsApi;\nexport const isDateTime = (value: unknown): value is DateTime => {\n return dayjs.isDayjs(value);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n dayjs.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n dayjs.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return dayjs.isDayjs(value);\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(\n date: string | number | Date | Dayjs | null | undefined,\n ): DateTime {\n return dayjs.utc(date);\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: string | number | Date | Dayjs | null | undefined): DateTime {\n return dayjs(date);\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: Date | string | DateTime = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return dayjs();\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (Array.isArray(duration)) {\n return dayjs.duration(duration[0], duration[1]);\n }\n\n if (typeof duration === \"number\") {\n return dayjs.duration(duration, unit || \"milliseconds\");\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return dayjs.isDuration(this.duration(value as DurationLike));\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next < this.now()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed). Use `$rateLimit` for distributed rate limiting.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/email/core/providers/EmailProvider.ts","../../../src/email/core/providers/MemoryEmailProvider.ts","../../../src/email/core/primitives/$email.ts","../../../src/email/core/errors/EmailError.ts","../../../src/email/core/providers/LocalEmailProvider.ts","../../../src/email/core/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 AlephaError,\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 AlephaError(\"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","import { AlephaError } from \"alepha\";\n\nexport class EmailError extends AlephaError {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileSystemProvider } from \"alepha/system\";\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 = $state(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 at: 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, { at: 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 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}.eml.json`;\n const filepath = this.fs.join(this.directory, filename);\n\n const content = this.createEmailJson({ to: recipient, subject, body });\n await this.fs.writeFile(filepath, JSON.stringify(content, null, 2));\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 createEmailJson(options: {\n to: string;\n subject: string;\n body: string;\n }): { to: string; subject: string; body: string; sentAt: string } {\n return {\n to: options.to,\n subject: options.subject,\n body: options.body,\n sentAt: new Date().toISOString(),\n };\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\";\n\n// Exports\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\";\n\n// Hook declarations\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 * Email delivery with template support.\n *\n * **Features:**\n * - Send emails with templates\n * - Multiple recipients\n * - Local file provider for development\n * - Memory provider for testing\n *\n * For SMTP support, use `AlephaEmailSmtp` from `alepha/email/smtp`.\n * For Brevo support, use `AlephaEmailBrevo` from `alepha/email/brevo`.\n *\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [EmailProvider],\n variants: [MemoryEmailProvider, LocalEmailProvider],\n register: (alepha) => {\n if (alepha.isTest()) {\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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/email/core/providers/EmailProvider.ts","../../../src/email/core/providers/MemoryEmailProvider.ts","../../../src/email/core/primitives/$email.ts","../../../src/email/core/errors/EmailError.ts","../../../src/email/core/providers/LocalEmailProvider.ts","../../../src/email/core/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 AlephaError,\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 AlephaError(\"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","import { AlephaError } from \"alepha\";\n\nexport class EmailError extends AlephaError {\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = \"EmailError\";\n this.cause = cause;\n }\n}\n","import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileSystemProvider } from \"alepha/system\";\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 = $state(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 at: 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, { at: 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 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}.eml.json`;\n const filepath = this.fs.join(this.directory, filename);\n\n const content = this.createEmailJson({ to: recipient, subject, body });\n await this.fs.writeFile(filepath, JSON.stringify(content, null, 2));\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 createEmailJson(options: {\n to: string;\n subject: string;\n body: string;\n }): { to: string; subject: string; body: string; sentAt: string } {\n return {\n to: options.to,\n subject: options.subject,\n body: options.body,\n sentAt: new Date().toISOString(),\n };\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\";\n\n// Exports\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\";\n\n// Hook declarations\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 * Email delivery with template support.\n *\n * **Features:**\n * - Send emails with templates\n * - Multiple recipients\n * - Local file provider for development\n * - Memory provider for testing\n *\n * For SMTP support, use `AlephaEmailSmtp` from `alepha/email/smtp`.\n * For Brevo support, use `AlephaEmailBrevo` from `alepha/email/brevo`.\n *\n * @module alepha.email\n */\nexport const AlephaEmail = $module({\n name: \"alepha.email\",\n primitives: [$email],\n services: [EmailProvider],\n variants: [MemoryEmailProvider, LocalEmailProvider],\n register: (alepha) => {\n if (alepha.isTest()) {\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"],"mappings":"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"}