alepha 0.15.1 → 0.15.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 (523) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +10 -33
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +10 -33
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +10 -3
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +10 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +162 -155
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.d.ts +10 -4
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +10 -4
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +43 -50
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +30 -37
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +1081 -760
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2539 -218
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts +138 -132
  31. package/dist/api/verifications/index.d.ts.map +1 -1
  32. package/dist/api/verifications/index.js +12 -4
  33. package/dist/api/verifications/index.js.map +1 -1
  34. package/dist/batch/index.d.ts +20 -40
  35. package/dist/batch/index.d.ts.map +1 -1
  36. package/dist/batch/index.js +31 -44
  37. package/dist/batch/index.js.map +1 -1
  38. package/dist/bucket/index.d.ts +440 -8
  39. package/dist/bucket/index.d.ts.map +1 -1
  40. package/dist/bucket/index.js +1861 -12
  41. package/dist/bucket/index.js.map +1 -1
  42. package/dist/cache/core/index.d.ts +179 -7
  43. package/dist/cache/core/index.d.ts.map +1 -1
  44. package/dist/cache/core/index.js +213 -7
  45. package/dist/cache/core/index.js.map +1 -1
  46. package/dist/cache/redis/index.d.ts +1 -0
  47. package/dist/cache/redis/index.d.ts.map +1 -1
  48. package/dist/cache/redis/index.js +4 -0
  49. package/dist/cache/redis/index.js.map +1 -1
  50. package/dist/cli/index.d.ts +638 -5645
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +2550 -368
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/command/index.d.ts +203 -45
  55. package/dist/command/index.d.ts.map +1 -1
  56. package/dist/command/index.js +2060 -71
  57. package/dist/command/index.js.map +1 -1
  58. package/dist/core/index.browser.js +70 -40
  59. package/dist/core/index.browser.js.map +1 -1
  60. package/dist/core/index.d.ts +34 -13
  61. package/dist/core/index.d.ts.map +1 -1
  62. package/dist/core/index.js +90 -40
  63. package/dist/core/index.js.map +1 -1
  64. package/dist/core/index.native.js +70 -40
  65. package/dist/core/index.native.js.map +1 -1
  66. package/dist/datetime/index.d.ts +15 -0
  67. package/dist/datetime/index.d.ts.map +1 -1
  68. package/dist/datetime/index.js +15 -0
  69. package/dist/datetime/index.js.map +1 -1
  70. package/dist/email/index.d.ts +323 -20
  71. package/dist/email/index.d.ts.map +1 -1
  72. package/dist/email/index.js +1857 -7
  73. package/dist/email/index.js.map +1 -1
  74. package/dist/fake/index.d.ts +90 -8
  75. package/dist/fake/index.d.ts.map +1 -1
  76. package/dist/fake/index.js +91 -20
  77. package/dist/fake/index.js.map +1 -1
  78. package/dist/lock/core/index.d.ts +11 -4
  79. package/dist/lock/core/index.d.ts.map +1 -1
  80. package/dist/lock/core/index.js +11 -4
  81. package/dist/lock/core/index.js.map +1 -1
  82. package/dist/logger/index.d.ts +17 -66
  83. package/dist/logger/index.d.ts.map +1 -1
  84. package/dist/logger/index.js +14 -63
  85. package/dist/logger/index.js.map +1 -1
  86. package/dist/mcp/index.d.ts +10 -30
  87. package/dist/mcp/index.d.ts.map +1 -1
  88. package/dist/mcp/index.js +12 -35
  89. package/dist/mcp/index.js.map +1 -1
  90. package/dist/orm/index.browser.js +3 -3
  91. package/dist/orm/index.browser.js.map +1 -1
  92. package/dist/orm/index.bun.js +39 -20
  93. package/dist/orm/index.bun.js.map +1 -1
  94. package/dist/orm/index.d.ts +517 -540
  95. package/dist/orm/index.d.ts.map +1 -1
  96. package/dist/orm/index.js +58 -71
  97. package/dist/orm/index.js.map +1 -1
  98. package/dist/queue/core/index.d.ts +18 -10
  99. package/dist/queue/core/index.d.ts.map +1 -1
  100. package/dist/queue/core/index.js +14 -6
  101. package/dist/queue/core/index.js.map +1 -1
  102. package/dist/react/auth/index.browser.js +108 -0
  103. package/dist/react/auth/index.browser.js.map +1 -0
  104. package/dist/react/auth/index.d.ts +100 -0
  105. package/dist/react/auth/index.d.ts.map +1 -0
  106. package/dist/react/auth/index.js +145 -0
  107. package/dist/react/auth/index.js.map +1 -0
  108. package/dist/react/core/index.d.ts +469 -0
  109. package/dist/react/core/index.d.ts.map +1 -0
  110. package/dist/react/core/index.js +464 -0
  111. package/dist/react/core/index.js.map +1 -0
  112. package/dist/react/form/index.d.ts +232 -0
  113. package/dist/react/form/index.d.ts.map +1 -0
  114. package/dist/react/form/index.js +432 -0
  115. package/dist/react/form/index.js.map +1 -0
  116. package/dist/react/head/index.browser.js +423 -0
  117. package/dist/react/head/index.browser.js.map +1 -0
  118. package/dist/react/head/index.d.ts +288 -0
  119. package/dist/react/head/index.d.ts.map +1 -0
  120. package/dist/react/head/index.js +465 -0
  121. package/dist/react/head/index.js.map +1 -0
  122. package/dist/react/i18n/index.d.ts +175 -0
  123. package/dist/react/i18n/index.d.ts.map +1 -0
  124. package/dist/react/i18n/index.js +224 -0
  125. package/dist/react/i18n/index.js.map +1 -0
  126. package/dist/react/router/index.browser.js +1974 -0
  127. package/dist/react/router/index.browser.js.map +1 -0
  128. package/dist/react/router/index.d.ts +1956 -0
  129. package/dist/react/router/index.d.ts.map +1 -0
  130. package/dist/react/router/index.js +4722 -0
  131. package/dist/react/router/index.js.map +1 -0
  132. package/dist/react/websocket/index.d.ts +117 -0
  133. package/dist/react/websocket/index.d.ts.map +1 -0
  134. package/dist/react/websocket/index.js +107 -0
  135. package/dist/react/websocket/index.js.map +1 -0
  136. package/dist/redis/index.bun.js +4 -0
  137. package/dist/redis/index.bun.js.map +1 -1
  138. package/dist/redis/index.d.ts +41 -44
  139. package/dist/redis/index.d.ts.map +1 -1
  140. package/dist/redis/index.js +16 -25
  141. package/dist/redis/index.js.map +1 -1
  142. package/dist/retry/index.d.ts +11 -2
  143. package/dist/retry/index.d.ts.map +1 -1
  144. package/dist/retry/index.js +11 -2
  145. package/dist/retry/index.js.map +1 -1
  146. package/dist/scheduler/index.d.ts +11 -2
  147. package/dist/scheduler/index.d.ts.map +1 -1
  148. package/dist/scheduler/index.js +11 -2
  149. package/dist/scheduler/index.js.map +1 -1
  150. package/dist/security/index.d.ts +140 -49
  151. package/dist/security/index.d.ts.map +1 -1
  152. package/dist/security/index.js +164 -32
  153. package/dist/security/index.js.map +1 -1
  154. package/dist/server/auth/index.d.ts +12 -7
  155. package/dist/server/auth/index.d.ts.map +1 -1
  156. package/dist/server/auth/index.js +12 -7
  157. package/dist/server/auth/index.js.map +1 -1
  158. package/dist/server/cache/index.d.ts +7 -22
  159. package/dist/server/cache/index.d.ts.map +1 -1
  160. package/dist/server/cache/index.js +7 -22
  161. package/dist/server/cache/index.js.map +1 -1
  162. package/dist/server/compress/index.d.ts +10 -2
  163. package/dist/server/compress/index.d.ts.map +1 -1
  164. package/dist/server/compress/index.js +10 -2
  165. package/dist/server/compress/index.js.map +1 -1
  166. package/dist/server/cookies/index.d.ts +40 -16
  167. package/dist/server/cookies/index.d.ts.map +1 -1
  168. package/dist/server/cookies/index.js +7 -5
  169. package/dist/server/cookies/index.js.map +1 -1
  170. package/dist/server/core/index.d.ts +124 -23
  171. package/dist/server/core/index.d.ts.map +1 -1
  172. package/dist/server/core/index.js +231 -14
  173. package/dist/server/core/index.js.map +1 -1
  174. package/dist/server/cors/index.d.ts +13 -23
  175. package/dist/server/cors/index.d.ts.map +1 -1
  176. package/dist/server/cors/index.js +7 -21
  177. package/dist/server/cors/index.js.map +1 -1
  178. package/dist/server/health/index.d.ts +8 -2
  179. package/dist/server/health/index.d.ts.map +1 -1
  180. package/dist/server/health/index.js +8 -2
  181. package/dist/server/health/index.js.map +1 -1
  182. package/dist/server/helmet/index.d.ts +11 -3
  183. package/dist/server/helmet/index.d.ts.map +1 -1
  184. package/dist/server/helmet/index.js +11 -3
  185. package/dist/server/helmet/index.js.map +1 -1
  186. package/dist/server/links/index.d.ts +11 -6
  187. package/dist/server/links/index.d.ts.map +1 -1
  188. package/dist/server/links/index.js +11 -6
  189. package/dist/server/links/index.js.map +1 -1
  190. package/dist/server/metrics/index.d.ts +10 -3
  191. package/dist/server/metrics/index.d.ts.map +1 -1
  192. package/dist/server/metrics/index.js +10 -3
  193. package/dist/server/metrics/index.js.map +1 -1
  194. package/dist/server/multipart/index.d.ts +9 -3
  195. package/dist/server/multipart/index.d.ts.map +1 -1
  196. package/dist/server/multipart/index.js +9 -3
  197. package/dist/server/multipart/index.js.map +1 -1
  198. package/dist/server/proxy/index.d.ts +8 -2
  199. package/dist/server/proxy/index.d.ts.map +1 -1
  200. package/dist/server/proxy/index.js +8 -2
  201. package/dist/server/proxy/index.js.map +1 -1
  202. package/dist/server/rate-limit/index.d.ts +30 -35
  203. package/dist/server/rate-limit/index.d.ts.map +1 -1
  204. package/dist/server/rate-limit/index.js +18 -55
  205. package/dist/server/rate-limit/index.js.map +1 -1
  206. package/dist/server/static/index.d.ts +137 -4
  207. package/dist/server/static/index.d.ts.map +1 -1
  208. package/dist/server/static/index.js +1853 -5
  209. package/dist/server/static/index.js.map +1 -1
  210. package/dist/server/swagger/index.d.ts +309 -6
  211. package/dist/server/swagger/index.d.ts.map +1 -1
  212. package/dist/server/swagger/index.js +1854 -6
  213. package/dist/server/swagger/index.js.map +1 -1
  214. package/dist/sms/index.d.ts +309 -7
  215. package/dist/sms/index.d.ts.map +1 -1
  216. package/dist/sms/index.js +1856 -7
  217. package/dist/sms/index.js.map +1 -1
  218. package/dist/system/index.browser.js +1218 -0
  219. package/dist/system/index.browser.js.map +1 -0
  220. package/dist/{file → system}/index.d.ts +343 -16
  221. package/dist/system/index.d.ts.map +1 -0
  222. package/dist/{file → system}/index.js +419 -22
  223. package/dist/system/index.js.map +1 -0
  224. package/dist/thread/index.d.ts +11 -2
  225. package/dist/thread/index.d.ts.map +1 -1
  226. package/dist/thread/index.js +11 -2
  227. package/dist/thread/index.js.map +1 -1
  228. package/dist/topic/core/index.d.ts +12 -5
  229. package/dist/topic/core/index.d.ts.map +1 -1
  230. package/dist/topic/core/index.js +12 -5
  231. package/dist/topic/core/index.js.map +1 -1
  232. package/dist/vite/index.d.ts +5 -6272
  233. package/dist/vite/index.d.ts.map +1 -1
  234. package/dist/vite/index.js +23 -10
  235. package/dist/vite/index.js.map +1 -1
  236. package/dist/websocket/index.d.ts +12 -8
  237. package/dist/websocket/index.d.ts.map +1 -1
  238. package/dist/websocket/index.js +12 -8
  239. package/dist/websocket/index.js.map +1 -1
  240. package/package.json +82 -11
  241. package/src/api/audits/index.ts +10 -33
  242. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  243. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  244. package/src/api/files/controllers/FileController.spec.ts +1 -1
  245. package/src/api/files/index.ts +10 -3
  246. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  247. package/src/api/files/services/FileService.spec.ts +1 -1
  248. package/src/api/jobs/index.ts +10 -3
  249. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  250. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  251. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  252. package/src/api/keys/index.ts +49 -0
  253. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  254. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  255. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  256. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  257. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  258. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  259. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  260. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  261. package/src/api/keys/services/ApiKeyService.ts +306 -0
  262. package/src/api/logs/TODO.md +55 -0
  263. package/src/api/notifications/index.ts +10 -4
  264. package/src/api/parameters/index.ts +9 -30
  265. package/src/api/parameters/primitives/$config.ts +12 -4
  266. package/src/api/parameters/services/ConfigStore.ts +9 -3
  267. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  268. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  269. package/src/api/users/index.ts +14 -3
  270. package/src/api/users/primitives/$realm.ts +33 -5
  271. package/src/api/users/providers/RealmProvider.ts +1 -12
  272. package/src/api/users/services/SessionService.ts +1 -1
  273. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  274. package/src/api/verifications/index.ts +10 -4
  275. package/src/batch/index.ts +9 -36
  276. package/src/batch/primitives/$batch.ts +0 -8
  277. package/src/batch/providers/BatchProvider.ts +29 -2
  278. package/src/bucket/__tests__/shared.ts +1 -1
  279. package/src/bucket/index.ts +13 -6
  280. package/src/bucket/primitives/$bucket.ts +1 -1
  281. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  282. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  283. package/src/cache/core/__tests__/shared.ts +30 -0
  284. package/src/cache/core/index.ts +11 -6
  285. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  286. package/src/cache/core/providers/CacheProvider.ts +17 -0
  287. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  288. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  289. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  290. package/src/cli/apps/AlephaCli.ts +1 -14
  291. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
  292. package/src/cli/atoms/buildOptions.ts +99 -9
  293. package/src/cli/commands/build.ts +150 -37
  294. package/src/cli/commands/db.ts +22 -18
  295. package/src/cli/commands/deploy.ts +1 -1
  296. package/src/cli/commands/dev.ts +1 -20
  297. package/src/cli/commands/gen/env.ts +5 -2
  298. package/src/cli/commands/gen/openapi.ts +5 -2
  299. package/src/cli/commands/init.spec.ts +588 -0
  300. package/src/cli/commands/init.ts +115 -58
  301. package/src/cli/commands/lint.ts +7 -1
  302. package/src/cli/commands/typecheck.ts +11 -0
  303. package/src/cli/providers/AppEntryProvider.ts +1 -1
  304. package/src/cli/providers/ViteBuildProvider.ts +8 -50
  305. package/src/cli/providers/ViteDevServerProvider.ts +35 -16
  306. package/src/cli/services/AlephaCliUtils.ts +52 -121
  307. package/src/cli/services/PackageManagerUtils.ts +129 -11
  308. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  309. package/src/cli/services/ProjectScaffolder.ts +148 -81
  310. package/src/cli/services/ViteUtils.ts +82 -0
  311. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
  312. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  313. package/src/cli/templates/apiIndexTs.ts +30 -0
  314. package/src/cli/templates/gitignore.ts +39 -0
  315. package/src/cli/{assets → templates}/mainCss.ts +11 -2
  316. package/src/cli/templates/mainServerTs.ts +33 -0
  317. package/src/cli/templates/webAppRouterTs.ts +74 -0
  318. package/src/cli/templates/webHelloComponentTsx.ts +30 -0
  319. package/src/command/helpers/Runner.spec.ts +139 -0
  320. package/src/command/helpers/Runner.ts +7 -22
  321. package/src/command/index.ts +12 -4
  322. package/src/command/providers/CliProvider.spec.ts +1392 -0
  323. package/src/command/providers/CliProvider.ts +320 -47
  324. package/src/core/Alepha.ts +34 -27
  325. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  326. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  327. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  328. package/src/core/index.shared.ts +1 -0
  329. package/src/core/index.ts +20 -0
  330. package/src/core/providers/EventManager.spec.ts +0 -71
  331. package/src/core/providers/EventManager.ts +3 -15
  332. package/src/core/providers/Json.ts +2 -14
  333. package/src/datetime/index.ts +15 -0
  334. package/src/email/index.ts +10 -5
  335. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  336. package/src/email/providers/LocalEmailProvider.ts +1 -1
  337. package/src/fake/__tests__/keyName.example.ts +1 -1
  338. package/src/fake/__tests__/keyName.spec.ts +5 -5
  339. package/src/fake/index.ts +9 -6
  340. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  341. package/src/fake/providers/FakeProvider.ts +133 -19
  342. package/src/lock/core/index.ts +11 -4
  343. package/src/logger/index.ts +17 -66
  344. package/src/mcp/index.ts +10 -27
  345. package/src/mcp/transports/SseMcpTransport.ts +0 -11
  346. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  347. package/src/orm/index.browser.ts +2 -2
  348. package/src/orm/index.bun.ts +5 -3
  349. package/src/orm/index.ts +23 -53
  350. package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
  351. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  352. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  353. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  354. package/src/orm/services/Repository.ts +7 -3
  355. package/src/queue/core/index.ts +14 -6
  356. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  357. package/src/react/auth/hooks/useAuth.ts +32 -0
  358. package/src/react/auth/index.browser.ts +13 -0
  359. package/src/react/auth/index.shared.ts +2 -0
  360. package/src/react/auth/index.ts +48 -0
  361. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  362. package/src/react/auth/services/ReactAuth.ts +135 -0
  363. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  364. package/src/react/core/components/ClientOnly.tsx +49 -0
  365. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  366. package/src/react/core/contexts/AlephaContext.ts +7 -0
  367. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  368. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  369. package/src/react/core/hooks/useAction.ts +480 -0
  370. package/src/react/core/hooks/useAlepha.ts +26 -0
  371. package/src/react/core/hooks/useClient.ts +17 -0
  372. package/src/react/core/hooks/useEvents.ts +51 -0
  373. package/src/react/core/hooks/useInject.ts +12 -0
  374. package/src/react/core/hooks/useStore.ts +52 -0
  375. package/src/react/core/index.ts +90 -0
  376. package/src/react/form/components/FormState.tsx +17 -0
  377. package/src/react/form/errors/FormValidationError.ts +18 -0
  378. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  379. package/src/react/form/hooks/useForm.ts +47 -0
  380. package/src/react/form/hooks/useFormState.ts +130 -0
  381. package/src/react/form/index.ts +44 -0
  382. package/src/react/form/services/FormModel.ts +614 -0
  383. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  384. package/src/react/head/helpers/SeoExpander.ts +142 -0
  385. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  386. package/src/react/head/hooks/useHead.ts +62 -0
  387. package/src/react/head/index.browser.ts +26 -0
  388. package/src/react/head/index.ts +44 -0
  389. package/src/react/head/interfaces/Head.ts +105 -0
  390. package/src/react/head/primitives/$head.ts +25 -0
  391. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  392. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  393. package/src/react/head/providers/HeadProvider.ts +168 -0
  394. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  395. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  396. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  397. package/src/react/i18n/components/Localize.tsx +35 -0
  398. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  399. package/src/react/i18n/hooks/useI18n.ts +18 -0
  400. package/src/react/i18n/index.ts +41 -0
  401. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  402. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  403. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  404. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  405. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  406. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  407. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  408. package/src/react/router/components/ErrorViewer.tsx +872 -0
  409. package/src/react/router/components/Link.tsx +23 -0
  410. package/src/react/router/components/NestedView.tsx +223 -0
  411. package/src/react/router/components/NotFound.tsx +30 -0
  412. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  413. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  414. package/src/react/router/errors/Redirection.ts +28 -0
  415. package/src/react/router/hooks/useActive.ts +52 -0
  416. package/src/react/router/hooks/useQueryParams.ts +63 -0
  417. package/src/react/router/hooks/useRouter.ts +20 -0
  418. package/src/react/router/hooks/useRouterState.ts +11 -0
  419. package/src/react/router/index.browser.ts +45 -0
  420. package/src/react/router/index.shared.ts +19 -0
  421. package/src/react/router/index.ts +146 -0
  422. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  423. package/src/react/router/primitives/$page.spec.tsx +676 -0
  424. package/src/react/router/primitives/$page.ts +489 -0
  425. package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
  426. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  427. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  428. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  429. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  430. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  431. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  432. package/src/react/router/providers/ReactServerProvider.ts +487 -0
  433. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  434. package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
  435. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  436. package/src/react/router/services/ReactPageServerService.ts +48 -0
  437. package/src/react/router/services/ReactPageService.ts +27 -0
  438. package/src/react/router/services/ReactRouter.ts +262 -0
  439. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  440. package/src/react/websocket/index.ts +7 -0
  441. package/src/redis/__tests__/redis.spec.ts +13 -0
  442. package/src/redis/index.ts +9 -25
  443. package/src/redis/providers/BunRedisProvider.ts +9 -0
  444. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  445. package/src/redis/providers/RedisProvider.ts +16 -0
  446. package/src/retry/index.ts +11 -2
  447. package/src/router/index.ts +15 -0
  448. package/src/scheduler/index.ts +11 -2
  449. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  450. package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
  451. package/src/security/index.ts +15 -10
  452. package/src/security/interfaces/IssuerResolver.ts +27 -0
  453. package/src/security/primitives/$issuer.ts +55 -0
  454. package/src/security/providers/SecurityProvider.ts +179 -0
  455. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  456. package/src/security/providers/ServerSecurityProvider.ts +63 -41
  457. package/src/server/auth/index.ts +12 -7
  458. package/src/server/cache/index.ts +7 -22
  459. package/src/server/compress/index.ts +10 -2
  460. package/src/server/cookies/index.ts +7 -5
  461. package/src/server/cookies/primitives/$cookie.ts +33 -11
  462. package/src/server/core/index.ts +16 -6
  463. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  464. package/src/server/core/primitives/$action.spec.ts +1 -1
  465. package/src/server/core/primitives/$action.ts +8 -3
  466. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  467. package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
  468. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  469. package/src/server/core/services/ServerRequestParser.ts +306 -13
  470. package/src/server/cors/index.ts +7 -21
  471. package/src/server/cors/primitives/$cors.ts +6 -2
  472. package/src/server/health/index.ts +8 -2
  473. package/src/server/helmet/index.ts +11 -3
  474. package/src/server/links/index.ts +11 -6
  475. package/src/server/metrics/index.ts +10 -3
  476. package/src/server/multipart/index.ts +9 -3
  477. package/src/server/proxy/index.ts +8 -2
  478. package/src/server/rate-limit/index.ts +21 -25
  479. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  480. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  481. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  482. package/src/server/static/index.ts +8 -2
  483. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  484. package/src/server/swagger/index.ts +9 -4
  485. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  486. package/src/sms/index.ts +9 -5
  487. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  488. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  489. package/src/system/index.browser.ts +36 -0
  490. package/src/system/index.ts +62 -0
  491. package/src/system/index.workerd.ts +1 -0
  492. package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
  493. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  494. package/src/system/providers/MemoryShellProvider.ts +164 -0
  495. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  496. package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
  497. package/src/system/providers/NodeShellProvider.ts +184 -0
  498. package/src/system/providers/ShellProvider.ts +74 -0
  499. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  500. package/src/thread/index.ts +11 -2
  501. package/src/topic/core/index.ts +12 -5
  502. package/src/vite/tasks/buildClient.ts +2 -7
  503. package/src/vite/tasks/buildServer.ts +19 -13
  504. package/src/vite/tasks/generateCloudflare.ts +10 -7
  505. package/src/vite/tasks/generateDocker.ts +4 -0
  506. package/src/websocket/index.ts +12 -8
  507. package/dist/file/index.d.ts.map +0 -1
  508. package/dist/file/index.js.map +0 -1
  509. package/src/cli/assets/apiIndexTs.ts +0 -16
  510. package/src/cli/assets/mainServerTs.ts +0 -24
  511. package/src/cli/assets/webAppRouterTs.ts +0 -16
  512. package/src/cli/assets/webHelloComponentTsx.ts +0 -20
  513. package/src/cli/providers/ViteTemplateProvider.ts +0 -27
  514. package/src/file/index.ts +0 -43
  515. /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
  516. /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
  517. /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
  518. /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
  519. /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
  520. /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
  521. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  522. /package/src/{file → system}/errors/FileError.ts +0 -0
  523. /package/src/{file → system}/services/FileDetector.ts +0 -0
@@ -0,0 +1,487 @@
1
+ import { join } from "node:path";
2
+ import {
3
+ $atom,
4
+ $env,
5
+ $hook,
6
+ $inject,
7
+ $use,
8
+ Alepha,
9
+ type Static,
10
+ t,
11
+ } from "alepha";
12
+ import { $logger } from "alepha/logger";
13
+ import { ServerHeadProvider } from "alepha/react/head";
14
+ import { type ServerHandler, ServerRouterProvider } from "alepha/server";
15
+ import { ServerLinksProvider } from "alepha/server/links";
16
+ import { ServerStaticProvider } from "alepha/server/static";
17
+ import { FileSystemProvider } from "alepha/system";
18
+ import { renderToReadableStream } from "react-dom/server";
19
+ import { Redirection } from "../errors/Redirection.ts";
20
+ import {
21
+ $page,
22
+ type PagePrimitiveRenderOptions,
23
+ type PagePrimitiveRenderResult,
24
+ } from "../primitives/$page.ts";
25
+ import {
26
+ type PageRoute,
27
+ ReactPageProvider,
28
+ type ReactRouterState,
29
+ } from "./ReactPageProvider.ts";
30
+ import { ReactServerTemplateProvider } from "./ReactServerTemplateProvider.ts";
31
+ import { SSRManifestProvider } from "./SSRManifestProvider.ts";
32
+
33
+ /**
34
+ * React server provider responsible for SSR and static file serving.
35
+ *
36
+ * Coordinates between:
37
+ * - ReactPageProvider: Page routing and layer resolution
38
+ * - ReactServerTemplateProvider: HTML template parsing and streaming
39
+ * - ServerHeadProvider: Head content management
40
+ * - SSRManifestProvider: Module preload link collection
41
+ *
42
+ * Uses `react-dom/server` under the hood.
43
+ */
44
+ export class ReactServerProvider {
45
+ /**
46
+ * SSR response headers - pre-allocated to avoid object creation per request.
47
+ */
48
+ protected readonly SSR_HEADERS = {
49
+ "content-type": "text/html",
50
+ "cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
51
+ pragma: "no-cache",
52
+ expires: "0",
53
+ } as const;
54
+
55
+ protected readonly fs = $inject(FileSystemProvider);
56
+ protected readonly log = $logger();
57
+ protected readonly alepha = $inject(Alepha);
58
+ protected readonly env = $env(envSchema);
59
+ protected readonly pageApi = $inject(ReactPageProvider);
60
+ protected readonly templateProvider = $inject(ReactServerTemplateProvider);
61
+ protected readonly serverHeadProvider = $inject(ServerHeadProvider);
62
+ protected readonly serverStaticProvider = $inject(ServerStaticProvider);
63
+ protected readonly serverRouterProvider = $inject(ServerRouterProvider);
64
+ protected readonly ssrManifestProvider = $inject(SSRManifestProvider);
65
+
66
+ /**
67
+ * Cached check for ServerLinksProvider - avoids has() lookup per request.
68
+ */
69
+ protected hasServerLinksProvider = false;
70
+
71
+ protected readonly options = $use(reactServerOptions);
72
+
73
+ /**
74
+ * Configure the React server provider.
75
+ */
76
+ public readonly onConfigure = $hook({
77
+ on: "configure",
78
+ handler: async () => {
79
+ const pages = this.alepha.primitives($page);
80
+
81
+ const ssrEnabled =
82
+ pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
83
+
84
+ this.alepha.store.set("alepha.react.server.ssr", ssrEnabled);
85
+
86
+ // production mode
87
+ let root = "";
88
+
89
+ // non-serverless mode only -> serve static files
90
+ if (!this.alepha.isServerless() && !this.alepha.isViteDev()) {
91
+ root = await this.getPublicDirectory();
92
+ if (!root) {
93
+ this.log.warn(
94
+ "Missing static files, static file server will be disabled",
95
+ );
96
+ } else {
97
+ this.log.debug(`Using static files from: ${root}`);
98
+ await this.configureStaticServer(root);
99
+ }
100
+ }
101
+
102
+ if (ssrEnabled) {
103
+ this.registerPages();
104
+ this.log.info("SSR OK");
105
+ return;
106
+ }
107
+
108
+ // no SSR enabled, serve a minimal fallback
109
+ this.log.info("SSR is disabled");
110
+ },
111
+ });
112
+
113
+ /**
114
+ * Register all pages as server routes.
115
+ */
116
+ protected registerPages(): void {
117
+ // Set up early head content (entry assets)
118
+ this.setupEarlyHeadContent();
119
+
120
+ // Cache ServerLinksProvider check at startup
121
+ this.hasServerLinksProvider = this.alepha.has(ServerLinksProvider);
122
+
123
+ for (const page of this.pageApi.getPages()) {
124
+ if (page.component || page.lazy) {
125
+ this.log.debug(`+ ${page.match} -> ${page.name}`);
126
+
127
+ this.serverRouterProvider.createRoute({
128
+ ...page,
129
+ schema: undefined, // schema is handled by the page primitive provider
130
+ method: "GET",
131
+ path: page.match,
132
+ handler: this.createHandler(page),
133
+ });
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Set up early head content with entry assets.
140
+ *
141
+ * This content is sent immediately when streaming starts, before page loaders run,
142
+ * allowing the browser to start downloading entry.js and CSS files early.
143
+ */
144
+ protected setupEarlyHeadContent(): void {
145
+ const assets = this.ssrManifestProvider.getEntryAssets();
146
+ const globalHead = this.serverHeadProvider.resolveGlobalHead();
147
+
148
+ const parts: string[] = [];
149
+
150
+ if (assets) {
151
+ for (const css of assets.css) {
152
+ parts.push(`<link rel="stylesheet" href="${css}" crossorigin="">`);
153
+ }
154
+ if (assets.js) {
155
+ parts.push(
156
+ `<script type="module" crossorigin="" src="${assets.js}"></script>`,
157
+ );
158
+ }
159
+ }
160
+
161
+ this.templateProvider.setEarlyHeadContent(
162
+ parts.length > 0 ? `${parts.join("\n")}\n` : "",
163
+ globalHead,
164
+ );
165
+
166
+ this.log.debug("Early head content set", {
167
+ css: assets?.css.length ?? 0,
168
+ js: assets?.js ? 1 : 0,
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Get the public directory path where static files are located.
174
+ */
175
+ protected async getPublicDirectory(): Promise<string> {
176
+ const maybe = [
177
+ join(process.cwd(), `dist/${this.options.publicDir}`),
178
+ join(process.cwd(), this.options.publicDir),
179
+ ];
180
+
181
+ for (const it of maybe) {
182
+ if (await this.fs.exists(it)) {
183
+ return it;
184
+ }
185
+ }
186
+
187
+ return "";
188
+ }
189
+
190
+ /**
191
+ * Configure the static file server to serve files from the given root directory.
192
+ */
193
+ protected async configureStaticServer(root: string) {
194
+ await this.serverStaticProvider.createStaticServer({
195
+ root,
196
+ cacheControl: {
197
+ maxAge: 3600,
198
+ immutable: true,
199
+ },
200
+ ...this.options.staticServer,
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Create the request handler for a page route.
206
+ */
207
+ protected createHandler(route: PageRoute): ServerHandler {
208
+ return async (serverRequest) => {
209
+ const { url, reply, query, params } = serverRequest;
210
+
211
+ this.log.trace("Rendering page", { name: route.name });
212
+
213
+ // Initialize router state
214
+ const state: ReactRouterState = {
215
+ url,
216
+ params,
217
+ query,
218
+ name: route.name,
219
+ onError: () => null,
220
+ layers: [],
221
+ meta: {},
222
+ head: {},
223
+ };
224
+
225
+ // Set up API links if available
226
+ if (this.hasServerLinksProvider) {
227
+ this.alepha.store.set(
228
+ "alepha.server.request.apiLinks",
229
+ await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
230
+ user: (serverRequest as any).user, // TODO: fix type
231
+ authorization: serverRequest.headers.authorization,
232
+ }),
233
+ );
234
+ }
235
+
236
+ // Check access permissions
237
+ let target: PageRoute | undefined = route;
238
+ while (target) {
239
+ if (route.can && !route.can()) {
240
+ this.log.warn(
241
+ `Access to page '${route.name}' is forbidden by can() check`,
242
+ );
243
+ reply.status = 403;
244
+ reply.headers["content-type"] = "text/plain";
245
+ return "Forbidden";
246
+ }
247
+ target = target.parent;
248
+ }
249
+
250
+ await this.alepha.events.emit("react:server:render:begin", {
251
+ request: serverRequest,
252
+ state,
253
+ });
254
+
255
+ // Apply SSR headers early
256
+ Object.assign(reply.headers, this.SSR_HEADERS);
257
+
258
+ // Resolve global head for early streaming (htmlAttributes only)
259
+ const globalHead = this.serverHeadProvider.resolveGlobalHead();
260
+
261
+ // Create optimized HTML stream with early head
262
+ const htmlStream = this.templateProvider.createEarlyHtmlStream(
263
+ globalHead,
264
+ async () => {
265
+ // === ASYNC WORK (runs while early head is being sent) ===
266
+ const result = await this.renderPage(route, state);
267
+
268
+ if (result.redirect) {
269
+ // Return redirect URL - template provider will inject meta refresh
270
+ // since HTTP headers have already been sent
271
+ return { redirect: result.redirect };
272
+ }
273
+
274
+ return { state, reactStream: result.reactStream! };
275
+ },
276
+ {
277
+ hydration: true,
278
+ onError: (error) => {
279
+ if (error instanceof Redirection) {
280
+ this.log.debug("Streaming resulted in redirection", {
281
+ redirect: error.redirect,
282
+ });
283
+ // Can't do redirect after streaming started - already handled above
284
+ } else {
285
+ // disable logging here, it's noisy and duplicate
286
+ // this.log.error("HTML stream error", error);
287
+ }
288
+ },
289
+ },
290
+ );
291
+
292
+ this.log.trace("Page streaming started (early head optimization)");
293
+ route.onServerResponse?.(serverRequest);
294
+ reply.body = htmlStream;
295
+ };
296
+ }
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // Core rendering logic - shared between SSR handler and static prerendering
300
+ // ---------------------------------------------------------------------------
301
+
302
+ /**
303
+ * Core page rendering logic shared between SSR handler and static prerendering.
304
+ *
305
+ * Handles:
306
+ * - Layer resolution (loaders)
307
+ * - Redirect detection
308
+ * - Head content filling
309
+ * - Preload link collection
310
+ * - React stream rendering
311
+ *
312
+ * @param route - The page route to render
313
+ * @param state - The router state
314
+ * @returns Render result with redirect or React stream
315
+ */
316
+ protected async renderPage(
317
+ route: PageRoute,
318
+ state: ReactRouterState,
319
+ ): Promise<{ redirect?: string; reactStream?: ReadableStream<Uint8Array> }> {
320
+ // Resolve page layers (loaders)
321
+ const { redirect } = await this.pageApi.createLayers(route, state);
322
+ if (redirect) {
323
+ this.log.debug("Resolver resulted in redirection", { redirect });
324
+ return { redirect };
325
+ }
326
+
327
+ // Fill head from route config
328
+ this.serverHeadProvider.fillHead(state);
329
+
330
+ // Collect and inject modulepreload links for page-specific chunks
331
+ const preloadLinks = this.ssrManifestProvider.collectPreloadLinks(route);
332
+ if (preloadLinks.length > 0) {
333
+ state.head ??= {};
334
+ state.head.link = [...(state.head.link ?? []), ...preloadLinks];
335
+ }
336
+
337
+ // Render React to stream
338
+
339
+ const element = this.pageApi.root(state);
340
+ this.alepha.store.set("alepha.react.router.state", state);
341
+
342
+ const reactStream = await renderToReadableStream(element, {
343
+ onError: (error: unknown) => {
344
+ if (error instanceof Redirection) {
345
+ this.log.warn("Redirect during streaming ignored", {
346
+ redirect: error.redirect,
347
+ });
348
+ } else {
349
+ // disable logging here, it's noisy and duplicate
350
+ // this.log.error("Streaming render error", error);
351
+ }
352
+ },
353
+ });
354
+
355
+ return { reactStream };
356
+ }
357
+
358
+ // ---------------------------------------------------------------------------
359
+ // Testing utilities - kept for backwards compatibility with tests
360
+ // ---------------------------------------------------------------------------
361
+
362
+ /**
363
+ * For testing purposes, renders a page to HTML string.
364
+ * Uses the same streaming code path as production, then collects to string.
365
+ *
366
+ * @param name - Page name to render
367
+ * @param options - Render options (params, query, html, hydration)
368
+ */
369
+ public async render(
370
+ name: string,
371
+ options: PagePrimitiveRenderOptions = {},
372
+ ): Promise<PagePrimitiveRenderResult> {
373
+ const page = this.pageApi.page(name);
374
+ const url = new URL(this.pageApi.url(name, options));
375
+ const state: ReactRouterState = {
376
+ url,
377
+ params: options.params ?? {},
378
+ query: options.query ?? {},
379
+ onError: () => null,
380
+ layers: [],
381
+ meta: {},
382
+ head: {},
383
+ };
384
+
385
+ this.log.trace("Rendering", { url });
386
+
387
+ await this.alepha.events.emit("react:server:render:begin", { state });
388
+
389
+ // Use shared rendering logic
390
+ const result = await this.renderPage(page, state);
391
+
392
+ if (result.redirect) {
393
+ return { state, html: "", redirect: result.redirect };
394
+ }
395
+
396
+ const reactStream = result.reactStream!;
397
+
398
+ // If full HTML page not requested, collect just the React content
399
+ if (!options.html) {
400
+ const html = await this.streamToString(reactStream);
401
+ return { state, html };
402
+ }
403
+
404
+ // Create full HTML stream and collect to string
405
+ const htmlStream = this.templateProvider.createHtmlStream(
406
+ reactStream,
407
+ state,
408
+ { hydration: options.hydration ?? true },
409
+ );
410
+
411
+ const html = await this.streamToString(htmlStream);
412
+
413
+ await this.alepha.events.emit("react:server:render:end", { state, html });
414
+
415
+ return { state, html };
416
+ }
417
+
418
+ /**
419
+ * Collect a ReadableStream into a string.
420
+ */
421
+ protected async streamToString(
422
+ stream: ReadableStream<Uint8Array>,
423
+ ): Promise<string> {
424
+ const reader = stream.getReader();
425
+ const decoder = new TextDecoder();
426
+ const chunks: string[] = [];
427
+
428
+ try {
429
+ while (true) {
430
+ const { done, value } = await reader.read();
431
+ if (done) break;
432
+ chunks.push(decoder.decode(value, { stream: true }));
433
+ }
434
+ chunks.push(decoder.decode()); // Flush remaining
435
+ } finally {
436
+ reader.releaseLock();
437
+ }
438
+
439
+ return chunks.join("");
440
+ }
441
+ }
442
+
443
+ // ---------------------------------------------------------------------------------------------------------------------
444
+
445
+ const envSchema = t.object({
446
+ REACT_SSR_ENABLED: t.optional(t.boolean()),
447
+ });
448
+
449
+ declare module "alepha" {
450
+ interface Env extends Partial<Static<typeof envSchema>> {}
451
+ interface State {
452
+ "alepha.react.server.ssr"?: boolean;
453
+ }
454
+ }
455
+
456
+ /**
457
+ * React server provider configuration atom
458
+ */
459
+ export const reactServerOptions = $atom({
460
+ name: "alepha.react.server.options",
461
+ schema: t.object({
462
+ publicDir: t.string(),
463
+ staticServer: t.object({
464
+ disabled: t.boolean(),
465
+ path: t.string({
466
+ description: "URL path where static files will be served.",
467
+ }),
468
+ }),
469
+ }),
470
+ default: {
471
+ publicDir: "public",
472
+ staticServer: {
473
+ disabled: false,
474
+ path: "/",
475
+ },
476
+ },
477
+ });
478
+
479
+ export type ReactServerProviderOptions = Static<
480
+ typeof reactServerOptions.schema
481
+ >;
482
+
483
+ declare module "alepha" {
484
+ interface State {
485
+ [reactServerOptions.key]: ReactServerProviderOptions;
486
+ }
487
+ }
@@ -0,0 +1,210 @@
1
+ import { Alepha } from "alepha";
2
+ import { $head } from "alepha/react/head";
3
+ import { HttpClient, ServerProvider } from "alepha/server";
4
+ import { describe, it } from "vitest";
5
+ import { ssrManifestAtom } from "../atoms/ssrManifestAtom.ts";
6
+ import { $page } from "../index.ts";
7
+
8
+ describe("ReactServerTemplateProvider", () => {
9
+ describe("streaming", () => {
10
+ class App {
11
+ head = $head({
12
+ htmlAttributes: { lang: "en" },
13
+ });
14
+
15
+ home = $page({
16
+ path: "/",
17
+ head: {
18
+ title: "Test Page",
19
+ meta: [{ name: "description", content: "Test description" }],
20
+ },
21
+ component: () => "Hello World",
22
+ });
23
+
24
+ withLoader = $page({
25
+ path: "/with-loader",
26
+ head: { title: "Loader Page" },
27
+ loader: async () => ({ data: "loaded" }),
28
+ component: ({ data }) => `Data: ${data}`,
29
+ });
30
+ }
31
+
32
+ it("should stream complete HTML document with correct structure", async ({
33
+ expect,
34
+ }) => {
35
+ const alepha = Alepha.create({
36
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
37
+ }).with(App);
38
+
39
+ await alepha.start();
40
+
41
+ const server = alepha.inject(ServerProvider);
42
+ const http = alepha.inject(HttpClient);
43
+
44
+ const response = await http.fetch(`${server.hostname}/`);
45
+
46
+ // Verify HTML structure
47
+ expect(response.data).toContain("<!DOCTYPE html>");
48
+ expect(response.data).toContain('<html lang="en">');
49
+ expect(response.data).toContain("<head>");
50
+ expect(response.data).toContain('<meta charset="UTF-8">');
51
+ expect(response.data).toContain('<meta name="viewport"');
52
+ expect(response.data).toContain("<title>Test Page</title>");
53
+ expect(response.data).toContain(
54
+ '<meta name="description" content="Test description">',
55
+ );
56
+ expect(response.data).toContain("</head>");
57
+ expect(response.data).toContain("<body>");
58
+ expect(response.data).toContain('<div id="root">');
59
+ expect(response.data).toContain("Hello World");
60
+ expect(response.data).toContain("</div>");
61
+ expect(response.data).toContain("</body>");
62
+ expect(response.data).toContain("</html>");
63
+
64
+ await alepha.stop();
65
+ });
66
+
67
+ it("should include hydration data when enabled", async ({ expect }) => {
68
+ const alepha = Alepha.create({
69
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
70
+ }).with(App);
71
+
72
+ await alepha.start();
73
+
74
+ const server = alepha.inject(ServerProvider);
75
+ const http = alepha.inject(HttpClient);
76
+
77
+ const response = await http.fetch(`${server.hostname}/with-loader`);
78
+
79
+ // Verify hydration script is present
80
+ expect(response.data).toContain("<script>window.__ssr=");
81
+ expect(response.data).toContain("</script>");
82
+
83
+ // Verify hydration data structure
84
+ expect(response.data).toMatch(/window\.__ssr=\{.*"layers".*\}/);
85
+
86
+ await alepha.stop();
87
+ });
88
+
89
+ it("should include entry assets in head when manifest is available", async ({
90
+ expect,
91
+ }) => {
92
+ const alepha = Alepha.create({
93
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
94
+ }).with(App);
95
+
96
+ // Set up mock SSR manifest
97
+ alepha.store.set(ssrManifestAtom, {
98
+ client: {
99
+ "src/entry.tsx": {
100
+ file: "assets/entry.abc123.js",
101
+ isEntry: true,
102
+ css: ["assets/style.def456.css"],
103
+ },
104
+ },
105
+ });
106
+
107
+ await alepha.start();
108
+
109
+ const server = alepha.inject(ServerProvider);
110
+ const http = alepha.inject(HttpClient);
111
+
112
+ const response = await http.fetch(`${server.hostname}/`);
113
+
114
+ // Verify entry assets are in the head
115
+ expect(response.data).toContain(
116
+ '<link rel="stylesheet" href="/assets/style.def456.css" crossorigin="">',
117
+ );
118
+ expect(response.data).toContain(
119
+ '<script type="module" crossorigin="" src="/assets/entry.abc123.js"></script>',
120
+ );
121
+
122
+ await alepha.stop();
123
+ });
124
+
125
+ it("should handle pages with loaders correctly", async ({ expect }) => {
126
+ const alepha = Alepha.create({
127
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
128
+ }).with(App);
129
+
130
+ await alepha.start();
131
+
132
+ const server = alepha.inject(ServerProvider);
133
+ const http = alepha.inject(HttpClient);
134
+
135
+ const response = await http.fetch(`${server.hostname}/with-loader`);
136
+
137
+ // Verify loader data is rendered
138
+ expect(response.data).toContain("Data: loaded");
139
+
140
+ await alepha.stop();
141
+ });
142
+
143
+ it("should set correct content-type header", async ({ expect }) => {
144
+ const alepha = Alepha.create({
145
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
146
+ }).with(App);
147
+
148
+ await alepha.start();
149
+
150
+ const server = alepha.inject(ServerProvider);
151
+ const http = alepha.inject(HttpClient);
152
+
153
+ const response = await http.fetch(`${server.hostname}/`);
154
+
155
+ expect(response.headers.get("content-type")).toBe("text/html");
156
+
157
+ await alepha.stop();
158
+ });
159
+
160
+ it("should set cache-control headers for SSR responses", async ({
161
+ expect,
162
+ }) => {
163
+ const alepha = Alepha.create({
164
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
165
+ }).with(App);
166
+
167
+ await alepha.start();
168
+
169
+ const server = alepha.inject(ServerProvider);
170
+ const http = alepha.inject(HttpClient);
171
+
172
+ const response = await http.fetch(`${server.hostname}/`);
173
+
174
+ expect(response.headers.get("cache-control")).toContain("no-store");
175
+
176
+ await alepha.stop();
177
+ });
178
+ });
179
+
180
+ describe("error handling", () => {
181
+ class ErrorApp {
182
+ errorPage = $page({
183
+ path: "/error",
184
+ loader: async () => {
185
+ throw new Error("Loader error");
186
+ },
187
+ component: () => "Should not render",
188
+ });
189
+ }
190
+
191
+ it("should render error when loader throws", async ({ expect }) => {
192
+ const alepha = Alepha.create({
193
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
194
+ }).with(ErrorApp);
195
+
196
+ await alepha.start();
197
+
198
+ const server = alepha.inject(ServerProvider);
199
+ const http = alepha.inject(HttpClient);
200
+
201
+ const response = await http.fetch(`${server.hostname}/error`);
202
+
203
+ // Should still return a valid HTML response with error
204
+ expect(response.data).toContain("<!DOCTYPE html>");
205
+ expect(response.data).toContain("Loader error");
206
+
207
+ await alepha.stop();
208
+ });
209
+ });
210
+ });