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
@@ -0,0 +1,350 @@
1
+ import { $inject, type Alepha, AlephaError } from "alepha";
2
+ import { FileSystemProvider } from "alepha/file";
3
+ import { $logger } from "alepha/logger";
4
+ import { importVite, importViteReact, viteAlephaSsrPreload } from "alepha/vite";
5
+ import type { InlineConfig, Plugin, ViteDevServer } from "vite";
6
+ import type { AppEntry } from "./AppEntryProvider.ts";
7
+ import { ViteTemplateProvider } from "./ViteTemplateProvider.ts";
8
+
9
+ export interface ViteDevServerOptions {
10
+ /**
11
+ * Root directory of the project.
12
+ */
13
+ root: string;
14
+
15
+ /**
16
+ * Path to the server entry file.
17
+ */
18
+ entry: AppEntry;
19
+
20
+ /**
21
+ * Port to run the dev server on.
22
+ */
23
+ port?: number;
24
+
25
+ /**
26
+ * Host to bind the dev server to.
27
+ */
28
+ host?: string | boolean;
29
+ }
30
+
31
+ /**
32
+ * Vite development server with Alepha integration.
33
+ *
34
+ * Architecture:
35
+ * - Vite runs in middleware mode (no HTTP server)
36
+ * - Alepha is the HTTP server via server:onRequest event
37
+ * - Request flow: Page requests → Alepha SSR, Assets → Vite middleware
38
+ *
39
+ * HMR Strategy:
40
+ * - Browser-only changes (CSS, client components) → Vite HMR (React Fast Refresh)
41
+ * - Server-only changes → Restart Alepha → Full browser reload
42
+ * - Shared changes → Restart Alepha → Let Vite HMR propagate
43
+ *
44
+ * Features:
45
+ * - Automatic .env reload detection
46
+ * - Error recovery on next file change
47
+ * - Optimized module invalidation (only changed files + importers)
48
+ */
49
+ export class ViteDevServerProvider {
50
+ protected readonly log = $logger();
51
+ protected readonly fs = $inject(FileSystemProvider);
52
+ protected readonly templateProvider = $inject(ViteTemplateProvider);
53
+ protected server!: ViteDevServer;
54
+ protected options!: ViteDevServerOptions;
55
+ protected alepha: Alepha | null = null;
56
+ protected hasError = false;
57
+ protected changedFiles = new Set<string>();
58
+
59
+ public async init(options: ViteDevServerOptions): Promise<Alepha> {
60
+ this.options = options;
61
+ await this.createViteServer();
62
+ return await this.loadAlepha(true);
63
+ }
64
+
65
+ public async start(): Promise<void> {
66
+ await this.alepha?.start();
67
+ }
68
+
69
+ /**
70
+ * Create the Vite server in middleware mode.
71
+ */
72
+ protected async createViteServer(): Promise<void> {
73
+ const { createServer } = await importVite();
74
+ const viteReact = await importViteReact();
75
+
76
+ const plugins: Plugin[] = [];
77
+ if (viteReact) plugins.push(viteReact());
78
+ plugins.push(viteAlephaSsrPreload());
79
+ plugins.push(this.createHmrPlugin());
80
+
81
+ this.server = await createServer({
82
+ root: this.options.root,
83
+ plugins,
84
+ server: { middlewareMode: true },
85
+ appType: "custom",
86
+ customLogger: {
87
+ info: () => {},
88
+ warn: this.log.warn.bind(this.log),
89
+ error: this.log.error.bind(this.log),
90
+ warnOnce: this.log.warn.bind(this.log),
91
+ clearScreen: () => {},
92
+ hasWarned: false,
93
+ hasErrorLogged: () => false,
94
+ },
95
+ } satisfies InlineConfig);
96
+
97
+ // Intercept .env changes (Vite calls restart() for .env files)
98
+ this.server.restart = async () => {
99
+ const startTime = Date.now();
100
+ try {
101
+ this.hasError = true; // Force full invalidation for env changes
102
+ await this.loadAlepha(false);
103
+ await this.alepha?.start();
104
+ this.log.debug(`Env reloaded in ${Date.now() - startTime}ms`);
105
+ this.sendBrowserReload();
106
+ } catch (err) {
107
+ this.hasError = true;
108
+ this.log.error("Reload failed", err);
109
+ this.log.warn("Waiting for file changes to retry...");
110
+ this.alepha = null;
111
+ }
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Vite plugin to handle HMR for Alepha.
117
+ */
118
+ protected createHmrPlugin(): Plugin {
119
+ return {
120
+ name: "alepha-hmr",
121
+ handleHotUpdate: async (ctx) => {
122
+ if (ctx.file.includes("/.idea/")) return [];
123
+
124
+ const firstModule = ctx.modules[0] as any;
125
+ const isBrowserOnly = firstModule && !firstModule._ssrModule;
126
+ const isServerOnly = firstModule && !firstModule._clientModule;
127
+
128
+ // Browser-only: let Vite HMR handle it (React Fast Refresh)
129
+ if (isBrowserOnly) return;
130
+
131
+ // Server or shared change: restart Alepha
132
+ const startTime = Date.now();
133
+
134
+ try {
135
+ this.changedFiles.add(ctx.file);
136
+ await this.loadAlepha(false);
137
+ await this.alepha?.start();
138
+ this.log.debug(`Reloaded in ${Date.now() - startTime}ms`);
139
+
140
+ // Server-only: full browser reload
141
+ if (isServerOnly) {
142
+ this.sendBrowserReload();
143
+ return [];
144
+ }
145
+
146
+ // Shared: let HMR propagate to browser
147
+ return;
148
+ } catch (err) {
149
+ this.hasError = true;
150
+ this.log.error("Reload failed", err);
151
+ this.log.warn("Waiting for file changes to retry...");
152
+ this.alepha = null;
153
+ return [];
154
+ }
155
+ },
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Send browser reload signal via custom event.
161
+ * Browser listens for 'alepha:reload' and does window.location.reload()
162
+ */
163
+ protected sendBrowserReload(): void {
164
+ this.server.ws.send({
165
+ type: "custom",
166
+ event: "alepha:reload",
167
+ data: {},
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Setup environment variables for dev mode.
173
+ */
174
+ protected async setupEnvironment(): Promise<void> {
175
+ const { loadEnv } = await importVite();
176
+ const mode = process.env.NODE_ENV || "development";
177
+ const env = loadEnv(mode, this.options.root, "");
178
+
179
+ process.env.NODE_ENV ??= "development";
180
+ process.env.VITE_ALEPHA_DEV = "true";
181
+ process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
182
+ process.env.SERVER_PORT ??= String(
183
+ this.options.port ??
184
+ (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3000),
185
+ );
186
+
187
+ // Merge into process.env (only set if not already defined)
188
+ for (const [key, value] of Object.entries(env)) {
189
+ process.env[key] ??= value;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Load or reload the Alepha instance.
195
+ */
196
+ protected async loadAlepha(isInitialLoad = false): Promise<Alepha> {
197
+ if (this.alepha) {
198
+ await this.alepha
199
+ .stop()
200
+ .catch((err) => this.log.warn("Error stopping Alepha", err));
201
+ this.alepha = null;
202
+ }
203
+
204
+ if (isInitialLoad || this.hasError) {
205
+ this.server.moduleGraph.invalidateAll();
206
+ } else {
207
+ this.invalidateModulesWithImporters();
208
+ }
209
+ this.changedFiles.clear();
210
+
211
+ // Snapshot and restore process.env to isolate each reload
212
+ const envSnapshot = { ...process.env };
213
+ await this.setupEnvironment();
214
+
215
+ await this.server.ssrLoadModule(this.options.entry.server);
216
+
217
+ const alepha: Alepha = (globalThis as any).__alepha;
218
+ if (!alepha) {
219
+ throw new AlephaError(
220
+ "Alepha instance not found after loading entry module",
221
+ );
222
+ }
223
+
224
+ this.alepha = alepha;
225
+ await this.setupAlepha();
226
+
227
+ this.hasError = false;
228
+ process.env = envSnapshot;
229
+
230
+ return alepha;
231
+ }
232
+
233
+ public hasReact(): boolean {
234
+ try {
235
+ this.alepha?.inject("ReactServerProvider");
236
+ return true;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Setup Alepha instance with Vite middleware and template.
244
+ */
245
+ protected async setupAlepha(): Promise<void> {
246
+ if (!this.alepha || !this.hasReact()) {
247
+ return;
248
+ }
249
+
250
+ const template = await this.server.transformIndexHtml(
251
+ "/",
252
+ this.templateProvider.generateIndexHtml(this.options.entry),
253
+ );
254
+
255
+ this.alepha.store.set("alepha.react.server.template", template);
256
+
257
+ this.alepha.events.on("server:onRequest", {
258
+ priority: "first",
259
+ callback: async ({ request }) => {
260
+ const node = request.raw.node;
261
+ if (!node || this.isPageRequest(node.req)) return;
262
+
263
+ const handled = await this.runViteMiddleware(
264
+ node.req,
265
+ node.res,
266
+ request,
267
+ );
268
+ if (handled) {
269
+ request.reply.status = node.res.statusCode || 200;
270
+ request.reply.body = null;
271
+ }
272
+ },
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Check if request is for an HTML page (not an asset).
278
+ */
279
+ protected isPageRequest(req: any): boolean {
280
+ const url = req.url || "/";
281
+
282
+ // Root and index.html are page requests
283
+ if (url === "/" || url === "/index.html") return true;
284
+
285
+ // Vite internal routes
286
+ if (url.startsWith("/@") || url.startsWith("/__vite")) return false;
287
+
288
+ // Files with extensions are assets
289
+ if (/\.\w+$/.test(url.split("?")[0])) return false;
290
+
291
+ return true;
292
+ }
293
+
294
+ /**
295
+ * Run Vite middleware and detect if it handled the request.
296
+ */
297
+ protected async runViteMiddleware(
298
+ req: any,
299
+ res: any,
300
+ ctx: { metadata: any },
301
+ ): Promise<boolean> {
302
+ return new Promise((resolve) => {
303
+ let resolved = false;
304
+
305
+ const done = (handled: boolean) => {
306
+ if (resolved) return;
307
+ resolved = true;
308
+ if (handled) ctx.metadata.vite = true;
309
+ resolve(handled);
310
+ };
311
+
312
+ res.on("finish", () => done(true));
313
+ res.on("close", () => res.headersSent && done(true));
314
+
315
+ this.server.middlewares(req, res, () => done(false));
316
+
317
+ // Check after microtask if Vite started writing (for async handlers)
318
+ setImmediate(() => {
319
+ if (res.headersSent || res.writableEnded) {
320
+ done(true);
321
+ }
322
+ });
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Invalidate modules and all their importers.
328
+ */
329
+ protected invalidateModulesWithImporters(): void {
330
+ const invalidated = new Set<string>();
331
+ const queue: string[] = [...this.changedFiles];
332
+
333
+ while (queue.length > 0) {
334
+ const file = queue.pop()!;
335
+ if (invalidated.has(file)) continue;
336
+
337
+ const mod = this.server.moduleGraph.getModuleById(file);
338
+ if (!mod) continue;
339
+
340
+ this.server.moduleGraph.invalidateModule(mod);
341
+ invalidated.add(file);
342
+
343
+ for (const importer of mod.importers) {
344
+ if (importer.id && !invalidated.has(importer.id)) {
345
+ queue.push(importer.id);
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
@@ -0,0 +1,27 @@
1
+ import { $inject } from "alepha";
2
+ import { FileSystemProvider } from "alepha/file";
3
+ import type { AppEntry } from "./AppEntryProvider.ts";
4
+
5
+ export class ViteTemplateProvider {
6
+ protected readonly fs = $inject(FileSystemProvider);
7
+
8
+ public generateIndexHtml(entry: AppEntry): string {
9
+ const style = entry.style;
10
+ const browser = entry.browser ?? entry.server;
11
+ return `
12
+ <!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8" />
16
+ <title>App</title>
17
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
18
+ ${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
19
+ </head>
20
+ <body>
21
+ <div id="root"></div>
22
+ <script type="module" src="/${browser}"></script>
23
+ </body>
24
+ </html>
25
+ `.trim();
26
+ }
27
+ }