alepha 0.14.4 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +44 -102
  2. package/dist/api/audits/index.d.ts +331 -443
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +2 -2
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +0 -113
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +2 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +151 -262
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/notifications/index.browser.js +4 -4
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +164 -276
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/notifications/index.js +4 -4
  17. package/dist/api/notifications/index.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +265 -377
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/users/index.browser.js +1 -2
  21. package/dist/api/users/index.browser.js.map +1 -1
  22. package/dist/api/users/index.d.ts +195 -301
  23. package/dist/api/users/index.d.ts.map +1 -1
  24. package/dist/api/users/index.js +203 -184
  25. package/dist/api/users/index.js.map +1 -1
  26. package/dist/api/verifications/index.d.ts.map +1 -1
  27. package/dist/batch/index.d.ts.map +1 -1
  28. package/dist/batch/index.js +1 -2
  29. package/dist/batch/index.js.map +1 -1
  30. package/dist/bucket/index.d.ts.map +1 -1
  31. package/dist/cache/core/index.d.ts.map +1 -1
  32. package/dist/cache/redis/index.d.ts.map +1 -1
  33. package/dist/cache/redis/index.js +2 -2
  34. package/dist/cache/redis/index.js.map +1 -1
  35. package/dist/cli/index.d.ts +5900 -165
  36. package/dist/cli/index.d.ts.map +1 -1
  37. package/dist/cli/index.js +1481 -639
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/command/index.d.ts +8 -4
  40. package/dist/command/index.d.ts.map +1 -1
  41. package/dist/command/index.js +29 -25
  42. package/dist/command/index.js.map +1 -1
  43. package/dist/core/index.browser.js +563 -54
  44. package/dist/core/index.browser.js.map +1 -1
  45. package/dist/core/index.d.ts +175 -8
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js +564 -54
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/index.native.js +563 -54
  50. package/dist/core/index.native.js.map +1 -1
  51. package/dist/datetime/index.d.ts.map +1 -1
  52. package/dist/datetime/index.js +4 -4
  53. package/dist/datetime/index.js.map +1 -1
  54. package/dist/email/index.d.ts +89 -42
  55. package/dist/email/index.d.ts.map +1 -1
  56. package/dist/email/index.js +129 -33
  57. package/dist/email/index.js.map +1 -1
  58. package/dist/fake/index.d.ts +7969 -2
  59. package/dist/fake/index.d.ts.map +1 -1
  60. package/dist/fake/index.js +22 -22
  61. package/dist/fake/index.js.map +1 -1
  62. package/dist/file/index.d.ts +134 -1
  63. package/dist/file/index.d.ts.map +1 -1
  64. package/dist/file/index.js +253 -1
  65. package/dist/file/index.js.map +1 -1
  66. package/dist/lock/core/index.d.ts.map +1 -1
  67. package/dist/lock/redis/index.d.ts.map +1 -1
  68. package/dist/logger/index.d.ts +1 -2
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +1 -5
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/mcp/index.d.ts +19 -1
  73. package/dist/mcp/index.d.ts.map +1 -1
  74. package/dist/mcp/index.js +28 -4
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/orm/chunk-DH6iiROE.js +38 -0
  77. package/dist/orm/index.browser.js +9 -9
  78. package/dist/orm/index.browser.js.map +1 -1
  79. package/dist/orm/index.bun.js +2821 -0
  80. package/dist/orm/index.bun.js.map +1 -0
  81. package/dist/orm/index.d.ts +318 -169
  82. package/dist/orm/index.d.ts.map +1 -1
  83. package/dist/orm/index.js +2086 -1776
  84. package/dist/orm/index.js.map +1 -1
  85. package/dist/queue/core/index.d.ts +4 -4
  86. package/dist/queue/core/index.d.ts.map +1 -1
  87. package/dist/queue/redis/index.d.ts.map +1 -1
  88. package/dist/redis/index.bun.js +285 -0
  89. package/dist/redis/index.bun.js.map +1 -0
  90. package/dist/redis/index.d.ts +13 -31
  91. package/dist/redis/index.d.ts.map +1 -1
  92. package/dist/redis/index.js +18 -38
  93. package/dist/redis/index.js.map +1 -1
  94. package/dist/retry/index.d.ts.map +1 -1
  95. package/dist/router/index.d.ts.map +1 -1
  96. package/dist/scheduler/index.d.ts +83 -1
  97. package/dist/scheduler/index.d.ts.map +1 -1
  98. package/dist/scheduler/index.js +393 -1
  99. package/dist/scheduler/index.js.map +1 -1
  100. package/dist/security/index.browser.js +5 -1
  101. package/dist/security/index.browser.js.map +1 -1
  102. package/dist/security/index.d.ts +598 -112
  103. package/dist/security/index.d.ts.map +1 -1
  104. package/dist/security/index.js +1808 -97
  105. package/dist/security/index.js.map +1 -1
  106. package/dist/server/auth/index.d.ts +1200 -175
  107. package/dist/server/auth/index.d.ts.map +1 -1
  108. package/dist/server/auth/index.js +1268 -37
  109. package/dist/server/auth/index.js.map +1 -1
  110. package/dist/server/cache/index.d.ts +6 -3
  111. package/dist/server/cache/index.d.ts.map +1 -1
  112. package/dist/server/cache/index.js +1 -1
  113. package/dist/server/cache/index.js.map +1 -1
  114. package/dist/server/compress/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.d.ts.map +1 -1
  116. package/dist/server/cookies/index.js +3 -3
  117. package/dist/server/cookies/index.js.map +1 -1
  118. package/dist/server/core/index.d.ts +115 -13
  119. package/dist/server/core/index.d.ts.map +1 -1
  120. package/dist/server/core/index.js +321 -139
  121. package/dist/server/core/index.js.map +1 -1
  122. package/dist/server/cors/index.d.ts +0 -1
  123. package/dist/server/cors/index.d.ts.map +1 -1
  124. package/dist/server/health/index.d.ts +0 -1
  125. package/dist/server/health/index.d.ts.map +1 -1
  126. package/dist/server/helmet/index.d.ts.map +1 -1
  127. package/dist/server/links/index.browser.js +9 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.d.ts +1 -2
  130. package/dist/server/links/index.d.ts.map +1 -1
  131. package/dist/server/links/index.js +14 -7
  132. package/dist/server/links/index.js.map +1 -1
  133. package/dist/server/metrics/index.d.ts +514 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/metrics/index.js +4462 -4
  136. package/dist/server/metrics/index.js.map +1 -1
  137. package/dist/server/multipart/index.d.ts.map +1 -1
  138. package/dist/server/proxy/index.d.ts +0 -1
  139. package/dist/server/proxy/index.d.ts.map +1 -1
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts.map +1 -1
  142. package/dist/server/swagger/index.d.ts +1 -2
  143. package/dist/server/swagger/index.d.ts.map +1 -1
  144. package/dist/server/swagger/index.js +1 -2
  145. package/dist/server/swagger/index.js.map +1 -1
  146. package/dist/sms/index.d.ts +3 -1
  147. package/dist/sms/index.d.ts.map +1 -1
  148. package/dist/sms/index.js +10 -10
  149. package/dist/sms/index.js.map +1 -1
  150. package/dist/thread/index.d.ts +0 -1
  151. package/dist/thread/index.d.ts.map +1 -1
  152. package/dist/thread/index.js +2 -2
  153. package/dist/thread/index.js.map +1 -1
  154. package/dist/topic/core/index.d.ts.map +1 -1
  155. package/dist/topic/redis/index.d.ts.map +1 -1
  156. package/dist/vite/index.d.ts +6315 -149
  157. package/dist/vite/index.d.ts.map +1 -1
  158. package/dist/vite/index.js +140 -469
  159. package/dist/vite/index.js.map +1 -1
  160. package/dist/websocket/index.browser.js +9 -9
  161. package/dist/websocket/index.browser.js.map +1 -1
  162. package/dist/websocket/index.d.ts +28 -28
  163. package/dist/websocket/index.d.ts.map +1 -1
  164. package/dist/websocket/index.js +9 -9
  165. package/dist/websocket/index.js.map +1 -1
  166. package/package.json +13 -18
  167. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  168. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  169. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  170. package/src/api/users/entities/users.ts +1 -1
  171. package/src/api/users/index.ts +8 -8
  172. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  173. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  174. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  175. package/src/api/users/services/CredentialService.ts +7 -7
  176. package/src/api/users/services/IdentityService.ts +4 -4
  177. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  178. package/src/api/users/services/RegistrationService.ts +38 -27
  179. package/src/api/users/services/SessionCrudService.ts +3 -3
  180. package/src/api/users/services/SessionService.spec.ts +3 -3
  181. package/src/api/users/services/SessionService.ts +27 -18
  182. package/src/api/users/services/UserService.ts +7 -7
  183. package/src/batch/providers/BatchProvider.ts +1 -2
  184. package/src/cli/apps/AlephaCli.ts +2 -2
  185. package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
  186. package/src/cli/assets/apiHelloControllerTs.ts +19 -0
  187. package/src/cli/assets/apiIndexTs.ts +16 -0
  188. package/src/cli/assets/biomeJson.ts +2 -1
  189. package/src/cli/assets/claudeMd.ts +308 -0
  190. package/src/cli/assets/dummySpecTs.ts +2 -1
  191. package/src/cli/assets/editorconfig.ts +2 -1
  192. package/src/cli/assets/mainBrowserTs.ts +4 -3
  193. package/src/cli/assets/mainCss.ts +24 -0
  194. package/src/cli/assets/mainServerTs.ts +24 -0
  195. package/src/cli/assets/tsconfigJson.ts +2 -1
  196. package/src/cli/assets/webAppRouterTs.ts +16 -0
  197. package/src/cli/assets/webHelloComponentTsx.ts +20 -0
  198. package/src/cli/assets/webIndexTs.ts +16 -0
  199. package/src/cli/atoms/appEntryOptions.ts +13 -0
  200. package/src/cli/atoms/buildOptions.ts +1 -1
  201. package/src/cli/atoms/changelogOptions.ts +1 -1
  202. package/src/cli/commands/build.ts +97 -61
  203. package/src/cli/commands/db.ts +21 -18
  204. package/src/cli/commands/deploy.ts +17 -5
  205. package/src/cli/commands/dev.ts +26 -47
  206. package/src/cli/commands/gen/env.ts +1 -1
  207. package/src/cli/commands/init.ts +79 -25
  208. package/src/cli/commands/lint.ts +9 -3
  209. package/src/cli/commands/test.ts +8 -2
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +4 -2
  212. package/src/cli/defineConfig.ts +9 -0
  213. package/src/cli/index.ts +2 -1
  214. package/src/cli/providers/AppEntryProvider.ts +131 -0
  215. package/src/cli/providers/ViteBuildProvider.ts +82 -0
  216. package/src/cli/providers/ViteDevServerProvider.ts +350 -0
  217. package/src/cli/providers/ViteTemplateProvider.ts +27 -0
  218. package/src/cli/services/AlephaCliUtils.ts +72 -602
  219. package/src/cli/services/PackageManagerUtils.ts +308 -0
  220. package/src/cli/services/ProjectScaffolder.ts +329 -0
  221. package/src/command/helpers/Runner.ts +15 -3
  222. package/src/core/Alepha.ts +2 -8
  223. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  224. package/src/core/index.shared.ts +1 -0
  225. package/src/core/index.ts +2 -0
  226. package/src/core/primitives/$hook.ts +6 -2
  227. package/src/core/primitives/$module.spec.ts +4 -0
  228. package/src/core/primitives/$module.ts +12 -0
  229. package/src/core/providers/AlsProvider.ts +1 -1
  230. package/src/core/providers/CodecManager.spec.ts +12 -6
  231. package/src/core/providers/CodecManager.ts +26 -6
  232. package/src/core/providers/EventManager.ts +169 -13
  233. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
  234. package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
  235. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  236. package/src/core/providers/StateManager.spec.ts +27 -16
  237. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  238. package/src/email/providers/LocalEmailProvider.ts +52 -15
  239. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  240. package/src/file/errors/FileError.ts +7 -0
  241. package/src/file/index.ts +9 -1
  242. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  243. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  244. package/src/mcp/errors/McpError.ts +30 -0
  245. package/src/mcp/index.ts +3 -0
  246. package/src/mcp/transports/SseMcpTransport.ts +16 -6
  247. package/src/orm/index.browser.ts +1 -19
  248. package/src/orm/index.bun.ts +77 -0
  249. package/src/orm/index.shared-server.ts +22 -0
  250. package/src/orm/index.shared.ts +15 -0
  251. package/src/orm/index.ts +19 -39
  252. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  253. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  254. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  255. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  256. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  257. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  258. package/src/orm/services/Repository.ts +19 -0
  259. package/src/redis/index.bun.ts +35 -0
  260. package/src/redis/providers/BunRedisProvider.ts +12 -43
  261. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  262. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  263. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  264. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  265. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  266. package/src/security/index.browser.ts +5 -0
  267. package/src/security/index.ts +90 -7
  268. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  269. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  270. package/src/security/primitives/$role.ts +5 -5
  271. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  272. package/src/security/primitives/$serviceAccount.ts +3 -3
  273. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  274. package/src/server/auth/primitives/$auth.ts +10 -10
  275. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  276. package/src/server/auth/primitives/$authGithub.ts +3 -3
  277. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  278. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  279. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  280. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  281. package/src/server/core/index.ts +1 -1
  282. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  283. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  284. package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
  285. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  286. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  287. package/src/server/core/providers/ServerProvider.ts +144 -24
  288. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  289. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  290. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  291. package/src/server/links/index.browser.ts +2 -0
  292. package/src/server/links/index.ts +3 -1
  293. package/src/server/links/providers/LinkProvider.ts +1 -1
  294. package/src/server/swagger/index.ts +1 -1
  295. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  296. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  297. package/src/vite/index.ts +3 -2
  298. package/src/vite/tasks/buildClient.ts +0 -1
  299. package/src/vite/tasks/buildServer.ts +80 -22
  300. package/src/vite/tasks/copyAssets.ts +5 -4
  301. package/src/vite/tasks/generateCloudflare.ts +7 -0
  302. package/src/vite/tasks/generateSitemap.ts +64 -23
  303. package/src/vite/tasks/index.ts +0 -2
  304. package/src/vite/tasks/prerenderPages.ts +49 -24
  305. package/dist/server/security/index.browser.js +0 -13
  306. package/dist/server/security/index.browser.js.map +0 -1
  307. package/dist/server/security/index.d.ts +0 -173
  308. package/dist/server/security/index.d.ts.map +0 -1
  309. package/dist/server/security/index.js +0 -311
  310. package/dist/server/security/index.js.map +0 -1
  311. package/src/cli/assets/appRouterTs.ts +0 -9
  312. package/src/cli/assets/indexHtml.ts +0 -15
  313. package/src/cli/assets/mainTs.ts +0 -13
  314. package/src/cli/commands/format.ts +0 -17
  315. package/src/server/security/index.browser.ts +0 -10
  316. package/src/server/security/index.ts +0 -94
  317. package/src/vite/helpers/boot.ts +0 -106
  318. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  319. package/src/vite/tasks/devServer.ts +0 -69
  320. package/src/vite/tasks/runAlepha.ts +0 -270
  321. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  322. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  import { AlephaError } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import {
4
4
  $auth,
5
5
  type CredentialsFn,
@@ -13,7 +13,7 @@ import {
13
13
  * Uses username and password to authenticate users.
14
14
  */
15
15
  export const $authCredentials = (
16
- realm: RealmPrimitive & WithLoginFn,
16
+ realm: IssuerPrimitive & WithLoginFn,
17
17
  options: Partial<CredentialsOptions> = {},
18
18
  ) => {
19
19
  const name = "credentials";
@@ -29,7 +29,7 @@ export const $authCredentials = (
29
29
  }
30
30
 
31
31
  return $auth({
32
- realm,
32
+ issuer: realm,
33
33
  name,
34
34
  credentials: {
35
35
  account,
@@ -1,5 +1,5 @@
1
1
  import { $context, AlephaError, t } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import type { OAuth2Profile } from "../providers/ServerAuthProvider.ts";
4
4
  import {
5
5
  $auth,
@@ -19,7 +19,7 @@ import {
19
19
  * - `GITHUB_CLIENT_SECRET`: The client secret obtained from the GitHub Developer Settings.
20
20
  */
21
21
  export const $authGithub = (
22
- realm: RealmPrimitive & WithLinkFn,
22
+ realm: IssuerPrimitive & WithLinkFn,
23
23
  options: Partial<OidcOptions> = {},
24
24
  ) => {
25
25
  const { alepha } = $context();
@@ -45,7 +45,7 @@ export const $authGithub = (
45
45
  }
46
46
 
47
47
  return $auth({
48
- realm,
48
+ issuer: realm,
49
49
  name,
50
50
  oauth: {
51
51
  clientId: env.GITHUB_CLIENT_ID!,
@@ -1,5 +1,5 @@
1
1
  import { $context, AlephaError, t } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import {
4
4
  $auth,
5
5
  type LinkAccountFn,
@@ -18,7 +18,7 @@ import {
18
18
  * - `GOOGLE_CLIENT_SECRET`: The client secret obtained from the Google Developer Console.
19
19
  */
20
20
  export const $authGoogle = (
21
- realm: RealmPrimitive & WithLinkFn,
21
+ realm: IssuerPrimitive & WithLinkFn,
22
22
  options: Partial<OidcOptions> = {},
23
23
  ) => {
24
24
  const { alepha } = $context();
@@ -44,7 +44,7 @@ export const $authGoogle = (
44
44
  }
45
45
 
46
46
  return $auth({
47
- realm,
47
+ issuer: realm,
48
48
  name,
49
49
  oidc: {
50
50
  issuer: "https://accounts.google.com",
@@ -71,8 +71,8 @@ export class ServerAuthProvider {
71
71
 
72
72
  for (const identity of this.identities) {
73
73
  if (filters.realmName) {
74
- const realm = "realm" in identity.options && identity.options.realm;
75
- if (!realm || realm.name !== filters.realmName) {
74
+ const issuer = identity.issuer;
75
+ if (!issuer || issuer.name !== filters.realmName) {
76
76
  continue;
77
77
  }
78
78
  }
@@ -145,7 +145,7 @@ export class ServerAuthProvider {
145
145
  // [feature] support for auth providers with fallback
146
146
  if (!request.headers.authorization) {
147
147
  for (const provider of this.identities) {
148
- if (!("realm" in provider.options) && !!provider.options.fallback) {
148
+ if ("fallback" in provider.options && !!provider.options.fallback) {
149
149
  const token = await provider.options.fallback();
150
150
  if (token) {
151
151
  request.headers.authorization = `Bearer ${token}`;
@@ -340,8 +340,8 @@ export class ServerAuthProvider {
340
340
  realm: query.realm,
341
341
  });
342
342
 
343
- const realm = "realm" in provider.options && provider.options.realm;
344
- if (!realm) {
343
+ const issuer = provider.issuer;
344
+ if (!issuer) {
345
345
  throw new SecurityError(
346
346
  `Auth provider '${query.provider}' does not support password grant`,
347
347
  );
@@ -373,7 +373,7 @@ export class ServerAuthProvider {
373
373
 
374
374
  const tokens = {
375
375
  provider: query.provider,
376
- ...(await realm.createToken(user)),
376
+ ...(await issuer.createToken(user)),
377
377
  };
378
378
 
379
379
  // for web applications, we store tokens in cookies
@@ -519,10 +519,10 @@ export class ServerAuthProvider {
519
519
 
520
520
  this.authorizationCode.del({ cookies });
521
521
 
522
- const realm = "realm" in provider.options && provider.options.realm;
522
+ const issuer = provider.issuer;
523
523
 
524
524
  // external, full OIDC System (e.g. Keycloak, Auth0)
525
- if (!realm) {
525
+ if (!issuer) {
526
526
  this.setTokens(externalTokens, cookies);
527
527
  reply.redirect(redirectUri);
528
528
  return;
@@ -531,7 +531,7 @@ export class ServerAuthProvider {
531
531
  // internal, we need to create our own tokens
532
532
 
533
533
  const user = await provider.user(externalTokens);
534
- const tokens = await realm.createToken(user);
534
+ const tokens = await issuer.createToken(user);
535
535
 
536
536
  this.setTokens(
537
537
  {
@@ -570,9 +570,9 @@ export class ServerAuthProvider {
570
570
  this.tokens.del({ cookies });
571
571
 
572
572
  // for internal providers, we can delete the session - if available
573
- if ("realm" in provider.options && tokens.refresh_token) {
573
+ if (provider.issuer && tokens.refresh_token) {
574
574
  const onDeleteSession =
575
- provider.options.realm.options.settings?.onDeleteSession;
575
+ provider.issuer.options.settings?.onDeleteSession;
576
576
  if (onDeleteSession) {
577
577
  try {
578
578
  await onDeleteSession(tokens.refresh_token);
@@ -635,8 +635,8 @@ export class ServerAuthProvider {
635
635
  return false;
636
636
  }
637
637
 
638
- // If realm filter is specified, match against provider's realm
639
- if (realmName && identity.realm?.name !== realmName) {
638
+ // If realm filter is specified, match against provider's issuer
639
+ if (realmName && identity.issuer?.name !== realmName) {
640
640
  return false;
641
641
  }
642
642
 
@@ -204,7 +204,7 @@ export class ServerCacheProvider {
204
204
 
205
205
  protected readonly onSend = $hook({
206
206
  on: "server:onSend",
207
- handler: async ({ route, request }) => {
207
+ handler: ({ route, request }) => {
208
208
  // before sending the response, check if the ETag matches
209
209
  // and if so, return a 304 Not Modified response
210
210
  // -> this is only relevant for etag-only routes, not cached routes <-
@@ -48,7 +48,7 @@ export class ServerCookiesProvider {
48
48
 
49
49
  public readonly onRequest = $hook({
50
50
  on: "server:onRequest",
51
- handler: async ({ request }) => {
51
+ handler: ({ request }) => {
52
52
  request.cookies = {
53
53
  req: this.cookieParser.parseRequestCookies(
54
54
  request.headers.cookie ?? "",
@@ -60,7 +60,7 @@ export class ServerCookiesProvider {
60
60
 
61
61
  public readonly onAction = $hook({
62
62
  on: "action:onRequest",
63
- handler: async ({ request }) => {
63
+ handler: ({ request }) => {
64
64
  request.cookies = {
65
65
  req: this.cookieParser.parseRequestCookies(
66
66
  request.headers.cookie ?? "",
@@ -72,7 +72,7 @@ export class ServerCookiesProvider {
72
72
 
73
73
  public readonly onSend = $hook({
74
74
  on: "server:onSend",
75
- handler: async ({ request }) => {
75
+ handler: ({ request }) => {
76
76
  if (request.cookies && Object.keys(request.cookies.res).length > 0) {
77
77
  const setCookieHeaders = this.cookieParser.serializeResponseCookies(
78
78
  request.cookies.res,
@@ -141,7 +141,7 @@ export const AlephaServer = $module({
141
141
  ServerRouterProvider,
142
142
  ],
143
143
  register: (alepha: Alepha) => {
144
- if (!alepha.isServerless() && !alepha.isViteDev()) {
144
+ if (!alepha.isServerless()) {
145
145
  if (alepha.isBun()) {
146
146
  alepha.with({
147
147
  optional: true,
@@ -107,7 +107,7 @@ export class BunHttpServerProvider extends ServerProvider {
107
107
  },
108
108
  });
109
109
 
110
- this.log.info(`Server listening on ${this.hostname}`);
110
+ this.log.info(`Server listening on ${this.hostname}/`);
111
111
  } catch (err) {
112
112
  this.log.error("Failed to start Bun server", err);
113
113
  throw err;
@@ -0,0 +1,125 @@
1
+ import { Alepha } from "alepha";
2
+ import { afterEach, describe, expect, test } from "vitest";
3
+ import { NodeHttpServerProvider } from "./NodeHttpServerProvider.ts";
4
+
5
+ describe("NodeHttpServerProvider", () => {
6
+ describe("graceful shutdown", () => {
7
+ let alepha: Alepha;
8
+ let server: NodeHttpServerProvider;
9
+
10
+ afterEach(async () => {
11
+ await alepha?.stop().catch(() => {});
12
+ });
13
+
14
+ test("dev mode: destroys connections immediately on close", async () => {
15
+ alepha = Alepha.create({ env: { NODE_ENV: "development" } });
16
+ alepha.with(NodeHttpServerProvider);
17
+
18
+ await alepha.start();
19
+ server = alepha.inject(NodeHttpServerProvider);
20
+
21
+ // Make a request to establish connection
22
+ await fetch(`${server.hostname}/`);
23
+
24
+ const startTime = Date.now();
25
+ await alepha.stop();
26
+ const elapsed = Date.now() - startTime;
27
+
28
+ // Should close instantly (under 100ms)
29
+ expect(elapsed).toBeLessThan(100);
30
+ expect(server.getConnectionsCount()).toBe(0);
31
+ });
32
+
33
+ test("production mode: waits for connections then closes", async () => {
34
+ alepha = Alepha.create({ env: { NODE_ENV: "production" } });
35
+ alepha.with(NodeHttpServerProvider);
36
+
37
+ await alepha.start();
38
+ server = alepha.inject(NodeHttpServerProvider);
39
+ server.options.shutdownTimeout = 500;
40
+
41
+ // Make a request to establish keep-alive connection
42
+ await fetch(`${server.hostname}/`);
43
+
44
+ const startTime = Date.now();
45
+ await alepha.stop();
46
+ const elapsed = Date.now() - startTime;
47
+
48
+ // In production, should not be instant (waits for graceful close or timeout)
49
+ // But should complete within timeout
50
+ expect(elapsed).toBeLessThan(server.options.shutdownTimeout + 100);
51
+ expect(server.getConnectionsCount()).toBe(0);
52
+ });
53
+
54
+ test("production mode: forces close after timeout", async () => {
55
+ alepha = Alepha.create({ env: { NODE_ENV: "production" } });
56
+ alepha.with(NodeHttpServerProvider);
57
+
58
+ await alepha.start();
59
+ server = alepha.inject(NodeHttpServerProvider);
60
+ server.options.shutdownTimeout = 50;
61
+
62
+ // Make a request to establish connection
63
+ await fetch(`${server.hostname}/`);
64
+
65
+ const startTime = Date.now();
66
+ await alepha.stop();
67
+ const elapsed = Date.now() - startTime;
68
+
69
+ // Should close around timeout
70
+ expect(elapsed).toBeLessThan(200);
71
+ expect(server.getConnectionsCount()).toBe(0);
72
+ });
73
+
74
+ test("connections are tracked and cleared", async () => {
75
+ alepha = Alepha.create({ env: { NODE_ENV: "development" } });
76
+ alepha.with(NodeHttpServerProvider);
77
+
78
+ await alepha.start();
79
+ server = alepha.inject(NodeHttpServerProvider);
80
+
81
+ // Make multiple requests
82
+ await fetch(`${server.hostname}/`);
83
+ await fetch(`${server.hostname}/`);
84
+
85
+ await alepha.stop();
86
+
87
+ // All connections cleared after stop
88
+ expect(server.getConnectionsCount()).toBe(0);
89
+ });
90
+
91
+ test("rejects new requests during shutdown", async () => {
92
+ alepha = Alepha.create({ env: { NODE_ENV: "production" } });
93
+ alepha.with(NodeHttpServerProvider);
94
+
95
+ await alepha.start();
96
+ server = alepha.inject(NodeHttpServerProvider);
97
+ server.options.shutdownTimeout = 500;
98
+
99
+ // Establish a connection to keep server busy
100
+ await fetch(`${server.hostname}/`);
101
+
102
+ // Start shutdown (don't await yet)
103
+ const stopPromise = alepha.stop();
104
+
105
+ // Give server.close() time to be called
106
+ await new Promise((r) => setTimeout(r, 10));
107
+
108
+ // New request should fail (server no longer accepting connections)
109
+ let error: Error | null = null;
110
+ try {
111
+ await fetch(`${server.hostname}/`, {
112
+ signal: AbortSignal.timeout(100),
113
+ });
114
+ } catch (e) {
115
+ error = e as Error;
116
+ }
117
+
118
+ // Should get a connection error (ECONNREFUSED or similar)
119
+ expect(error).not.toBeNull();
120
+
121
+ // Wait for shutdown to complete
122
+ await stopPromise;
123
+ });
124
+ });
125
+ });
@@ -4,6 +4,7 @@ import {
4
4
  type Server,
5
5
  type ServerResponse,
6
6
  } from "node:http";
7
+ import type { Socket } from "node:net";
7
8
  import { $env, $hook, $inject, Alepha, type Static, t } from "alepha";
8
9
  import { DateTimeProvider } from "alepha/datetime";
9
10
  import { $logger } from "alepha/logger";
@@ -34,6 +35,24 @@ export class NodeHttpServerProvider extends ServerProvider {
34
35
  protected readonly env = $env(envSchema);
35
36
  protected readonly router = $inject(ServerRouterProvider);
36
37
 
38
+ /** Track active connections for fast shutdown */
39
+ protected readonly connections = new Set<Socket>();
40
+
41
+ /** Get number of active connections */
42
+ public getConnectionsCount(): number {
43
+ return this.connections.size;
44
+ }
45
+
46
+ /** Server options */
47
+ public readonly options = {
48
+ /**
49
+ * Graceful shutdown timeout in ms.
50
+ * After this, remaining connections are forcefully closed.
51
+ * @default 30000
52
+ */
53
+ shutdownTimeout: 10000,
54
+ };
55
+
37
56
  public get hostname(): string {
38
57
  if (this.server.listening) {
39
58
  const address = this.server.address();
@@ -44,13 +63,29 @@ export class NodeHttpServerProvider extends ServerProvider {
44
63
  return `http://${this.env.SERVER_HOST}:${this.env.SERVER_PORT}`;
45
64
  }
46
65
 
66
+ // Pre-bound error handler to avoid function allocation per request
67
+ protected readonly handleRequestError = (res: ServerResponse, err: Error) => {
68
+ this.log.error("Error handling request", err);
69
+ res.statusCode = 500;
70
+ res.end("Internal Server Error");
71
+ };
72
+
73
+ // P3: Pre-allocated event object to avoid { req, res } allocation per request
74
+ // Safe because handleNodeRequest completes before the next request reuses this object
75
+ protected readonly nodeRequestEvent = {
76
+ req: null as unknown as IncomingMessage,
77
+ res: null as unknown as ServerResponse,
78
+ };
79
+
47
80
  public readonly server = this.createHttpServer((req, res) => {
48
- this.log.trace(`Incoming Node.js message -> ${req.url}`);
49
- this.handleNodeRequest({ req, res }).catch((err) => {
50
- this.log.error("Error handling request", err);
51
- res.statusCode = 500;
52
- res.end("Internal Server Error");
53
- });
81
+ // Reuse pre-allocated event object instead of creating { req, res } per request
82
+ const ev = this.nodeRequestEvent;
83
+ ev.req = req;
84
+ ev.res = res;
85
+ // Note: handleNodeRequest returns a promise that resolves after response is sent
86
+ this.handleNodeRequest(ev).catch((err) =>
87
+ this.handleRequestError(res, err),
88
+ );
54
89
  });
55
90
 
56
91
  public readonly start = $hook({
@@ -64,25 +99,27 @@ export class NodeHttpServerProvider extends ServerProvider {
64
99
  protected createHttpServer(
65
100
  func: (req: IncomingMessage, res: ServerResponse) => void,
66
101
  ): Server {
67
- return createServer(
102
+ const server = createServer(
68
103
  {
69
104
  // nov 25 - keep connections alive for better performance, cuz we http/1.1 by default
70
105
  keepAlive: this.alepha.isProduction(),
71
106
  },
72
107
  func,
73
108
  );
109
+
110
+ // Track connections for fast shutdown
111
+ server.on("connection", (socket) => {
112
+ this.connections.add(socket);
113
+ socket.on("close", () => this.connections.delete(socket));
114
+ });
115
+
116
+ return server;
74
117
  }
75
118
 
76
119
  protected readonly stop = $hook({
77
120
  on: "stop",
78
121
  handler: async () => {
79
- if (this.alepha.isProduction()) {
80
- await this.close();
81
- return;
82
- }
83
-
84
- // do not await in development & test
85
- this.close().catch(() => {});
122
+ await this.close();
86
123
  },
87
124
  });
88
125
 
@@ -96,7 +133,7 @@ export class NodeHttpServerProvider extends ServerProvider {
96
133
 
97
134
  await new Promise<void>((resolve, reject) => {
98
135
  this.server?.listen(port, this.env.SERVER_HOST, () => {
99
- this.log.info(`Server listening on ${this.hostname}`);
136
+ this.log.info(`Server listening on ${this.hostname}/`);
100
137
  resolve();
101
138
  });
102
139
 
@@ -107,18 +144,49 @@ export class NodeHttpServerProvider extends ServerProvider {
107
144
  }
108
145
 
109
146
  protected async close() {
110
- const promise = new Promise<void>((resolve, reject) => {
111
- this.server?.close((err) => {
112
- if (err) {
113
- reject(err);
114
- } else {
115
- resolve();
116
- }
117
- });
147
+ // Dev/Test: instant shutdown (destroy connections immediately)
148
+ // Production: graceful shutdown (wait for requests to complete, then close)
149
+ if (!this.alepha.isProduction()) {
150
+ this.destroyAllConnections();
151
+ }
152
+
153
+ // Stop accepting new connections
154
+ const closePromise = new Promise<void>((resolve, reject) => {
155
+ this.server?.close((err) => (err ? reject(err) : resolve()));
118
156
  });
119
157
 
120
- await Promise.race([this.dateTimeProvider.wait(2000), promise]);
158
+ if (this.alepha.isProduction() && this.connections.size > 0) {
159
+ // In production, wait for connections with timeout
160
+ const timeout = this.options.shutdownTimeout;
161
+
162
+ // Set up timeout to force-close connections
163
+ const timeoutId = setTimeout(() => {
164
+ if (this.connections.size > 0) {
165
+ this.log.warn(
166
+ `Shutdown timeout (${timeout}ms) reached, forcing ${this.connections.size} connections to close`,
167
+ );
168
+ // Destroy sockets - this triggers 'close' events which eventually resolves closePromise
169
+ for (const socket of this.connections) {
170
+ socket.destroy();
171
+ }
172
+ }
173
+ }, timeout);
174
+
175
+ // Wait for server to fully close (all connections closed)
176
+ await closePromise;
177
+ clearTimeout(timeoutId);
178
+ this.connections.clear();
179
+ } else {
180
+ await closePromise;
181
+ }
121
182
 
122
183
  this.log.info("Server closed");
123
184
  }
185
+
186
+ protected destroyAllConnections() {
187
+ for (const socket of this.connections) {
188
+ socket.destroy();
189
+ }
190
+ this.connections.clear();
191
+ }
124
192
  }
@@ -24,7 +24,7 @@ export class ServerBodyParserProvider {
24
24
 
25
25
  public readonly onRequest = $hook({
26
26
  on: "server:onRequest",
27
- handler: async ({ route, request }) => {
27
+ handler: ({ route, request }) => {
28
28
  if (request.body) {
29
29
  return; // already parsed
30
30
  }
@@ -46,28 +46,24 @@ export class ServerBodyParserProvider {
46
46
  }
47
47
 
48
48
  if (route.schema?.body) {
49
- try {
50
- const body = await this.parse(
51
- stream,
52
- request.headers,
53
- route.schema.body,
54
- );
55
- if (body) {
56
- request.body = body;
57
- }
58
- } catch (error) {
59
- if (error instanceof HttpError) {
60
- throw error;
61
- }
62
-
63
- throw new HttpError(
64
- {
65
- status: 400,
66
- message: "Failed to parse request body",
67
- },
68
- error,
69
- );
70
- }
49
+ return this.parse(stream, request.headers, route.schema.body)
50
+ .then((body) => {
51
+ if (body) {
52
+ request.body = body;
53
+ }
54
+ })
55
+ .catch((error) => {
56
+ if (error instanceof HttpError) {
57
+ throw error;
58
+ }
59
+ throw new HttpError(
60
+ {
61
+ status: 400,
62
+ message: "Failed to parse request body",
63
+ },
64
+ error,
65
+ );
66
+ });
71
67
  }
72
68
  },
73
69
  });
@@ -9,24 +9,26 @@ export class ServerLoggerProvider {
9
9
  on: "server:onRequest",
10
10
  priority: "first",
11
11
  handler: ({ route, request }) => {
12
- if (!route.silent) {
13
- request.metadata.now = Date.now();
14
-
15
- const data: Record<string, string> = {
16
- method: request.method,
17
- path: request.url.pathname,
18
- };
19
-
20
- if (this.alepha.isProduction()) {
21
- data.agent = request.headers["user-agent"];
22
- const ip = request.ip;
23
- if (ip) {
24
- data.ip = ip;
25
- }
26
- }
12
+ if (route.silent || request.metadata.vite) {
13
+ return;
14
+ }
15
+
16
+ request.metadata.now = Date.now();
17
+
18
+ const data: Record<string, string> = {
19
+ method: request.method,
20
+ path: request.url.pathname,
21
+ };
27
22
 
28
- this.log.info("Incoming request", data);
23
+ if (this.alepha.isProduction()) {
24
+ data.agent = request.headers["user-agent"];
25
+ const ip = request.ip;
26
+ if (ip) {
27
+ data.ip = ip;
28
+ }
29
29
  }
30
+
31
+ this.log.info("Incoming request", data);
30
32
  },
31
33
  });
32
34
 
@@ -42,10 +44,12 @@ export class ServerLoggerProvider {
42
44
  on: "server:onResponse",
43
45
  priority: "last",
44
46
  handler: ({ route, request, response }) => {
45
- if (!route.silent) {
46
- const ms = Date.now() - request.metadata.now;
47
- this.log.info("Request completed", { status: response.status, ms });
47
+ if (route.silent || request.metadata.vite) {
48
+ return;
48
49
  }
50
+
51
+ const ms = Date.now() - request.metadata.now;
52
+ this.log.info("Request completed", { status: response.status, ms });
49
53
  },
50
54
  });
51
55
  }