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
@@ -64,7 +64,7 @@ export class CloudflareD1Provider extends DatabaseProvider {
64
64
  protected readonly env = $env(
65
65
  t.object({
66
66
  DATABASE_URL: t.string({
67
- description: "Expect to be 'cloudflare-d1://name:id'",
67
+ description: "Expect to be 'd1://name:id'",
68
68
  }),
69
69
  }),
70
70
  );
@@ -104,41 +104,68 @@ export class CloudflareD1Provider extends DatabaseProvider {
104
104
  protected readonly onStart = $hook({
105
105
  on: "start",
106
106
  handler: async () => {
107
- const [bindingName] = this.env.DATABASE_URL.replace(
108
- "cloudflare-d1://",
109
- "",
110
- ).split(":");
111
- const cloudflareEnv = this.alepha.store.get("cloudflare.env" as any);
112
- if (!cloudflareEnv) {
113
- throw new AlephaError(
114
- "Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.",
107
+ try {
108
+ const [bindingName] = this.env.DATABASE_URL.replace("d1://", "").split(
109
+ ":",
115
110
  );
111
+ const cloudflareEnv = this.alepha.store.get("cloudflare.env" as any);
112
+ if (!cloudflareEnv) {
113
+ throw new AlephaError(
114
+ "Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.",
115
+ );
116
+ }
117
+
118
+ const binding = cloudflareEnv[bindingName] as D1Database;
119
+ if (!binding) {
120
+ throw new AlephaError(
121
+ `D1 binding '${bindingName}' not found in Cloudflare Workers environment.`,
122
+ );
123
+ }
124
+
125
+ this.d1 = binding;
126
+
127
+ // Dynamic import to avoid crashes when not on Cloudflare
128
+ const { drizzle } = await import("drizzle-orm/d1");
129
+
130
+ this.drizzleDb = drizzle(this.d1) as DrizzleD1Database;
131
+
132
+ // Never migrate in serverless mode - D1 migrations must be applied
133
+ // via `wrangler d1 migrations apply` before deployment
134
+ if (!this.alepha.isServerless()) {
135
+ await this.migrate();
136
+ }
137
+
138
+ this.log.info("Using Cloudflare D1 database");
139
+ } catch (error) {
140
+ // Log the full error for debugging since Cloudflare Workers
141
+ // doesn't properly display error causes
142
+ const errorMessage =
143
+ error instanceof Error
144
+ ? `${error.message}${error.stack ? `\n${error.stack}` : ""}`
145
+ : String(error);
146
+ this.log.error(`D1 initialization failed: ${errorMessage}`);
147
+ throw error;
116
148
  }
117
-
118
- const binding = cloudflareEnv[bindingName] as D1Database;
119
- if (!binding) {
120
- throw new AlephaError(
121
- `D1 binding '${bindingName}' not found in Cloudflare Workers environment.`,
122
- );
123
- }
124
-
125
- this.d1 = binding;
126
-
127
- // Dynamic import to avoid crashes when not on Cloudflare
128
- const { drizzle } = await import("drizzle-orm/d1");
129
-
130
- this.drizzleDb = drizzle(this.d1) as DrizzleD1Database;
131
-
132
- await this.migrate();
133
-
134
- this.log.info("Using Cloudflare D1 database");
135
149
  },
136
150
  });
137
151
 
138
152
  protected async executeMigrations(migrationsFolder: string): Promise<void> {
139
- // Dynamic import for D1 migrator
140
- const { migrate } = await import("drizzle-orm/d1/migrator");
141
- await migrate(this.db as any, { migrationsFolder });
153
+ this.log.debug(`Running D1 migrations from '${migrationsFolder}'...`);
154
+ try {
155
+ // Dynamic import for D1 migrator
156
+ const { migrate } = await import("drizzle-orm/d1/migrator");
157
+ await migrate(this.db as any, { migrationsFolder });
158
+ this.log.debug("D1 migrations completed successfully");
159
+ } catch (error) {
160
+ const errorMessage =
161
+ error instanceof Error
162
+ ? `${error.name}: ${error.message}`
163
+ : String(error);
164
+ throw new AlephaError(
165
+ `D1 migration failed from '${migrationsFolder}': ${errorMessage}`,
166
+ { cause: error },
167
+ );
168
+ }
142
169
  }
143
170
 
144
171
  /**
@@ -97,9 +97,17 @@ export abstract class DatabaseProvider {
97
97
  }
98
98
 
99
99
  /**
100
- * Base migration orchestration - handles environment logic
100
+ * Base migration orchestration - handles environment logic.
101
+ *
102
+ * Never runs in serverless mode - migrations should be applied during
103
+ * deployment, not at runtime (to avoid race conditions and timeouts).
101
104
  */
102
105
  public async migrate(): Promise<void> {
106
+ // Never migrate in serverless mode - migrations should be applied during deployment
107
+ if (this.alepha.isServerless()) {
108
+ return;
109
+ }
110
+
103
111
  const migrationsFolder = this.getMigrationsFolder();
104
112
 
105
113
  // Handle different environments
@@ -157,7 +157,10 @@ export class NodeSqliteProvider extends DatabaseProvider {
157
157
 
158
158
  this.sqlite = new DatabaseSync(filepath);
159
159
 
160
- await this.migrate();
160
+ // Never migrate in serverless mode - migrations should be applied during deployment
161
+ if (!this.alepha.isServerless()) {
162
+ await this.migrate();
163
+ }
161
164
 
162
165
  this.log.info(`Using SQLite database at ${filepath}`);
163
166
  },
@@ -616,7 +616,7 @@ export abstract class Repository<T extends TObject> {
616
616
  */
617
617
  public async updateOne(
618
618
  where: PgQueryWhereOrSQL<T>,
619
- data: Partial<Static<TObjectUpdate<T>>>,
619
+ data: WithSQL<Static<TObjectUpdate<T>>>,
620
620
  opts: StatementOptions = {},
621
621
  ): Promise<Static<T>> {
622
622
  await this.alepha.events.emit("repository:update:before", {
@@ -767,7 +767,7 @@ export abstract class Repository<T extends TObject> {
767
767
  */
768
768
  public async updateById(
769
769
  id: string | number,
770
- data: Partial<Static<TObjectUpdate<T>>>,
770
+ data: WithSQL<Static<TObjectUpdate<T>>>,
771
771
  opts: StatementOptions = {},
772
772
  ): Promise<Static<T>> {
773
773
  return await this.updateOne(this.getWhereId(id), data, opts);
@@ -778,7 +778,7 @@ export abstract class Repository<T extends TObject> {
778
778
  */
779
779
  public async updateMany(
780
780
  where: PgQueryWhereOrSQL<T>,
781
- data: Partial<Static<TObjectUpdate<T>>>,
781
+ data: WithSQL<Static<TObjectUpdate<T>>>,
782
782
  opts: StatementOptions = {},
783
783
  ): Promise<Array<number | string>> {
784
784
  await this.alepha.events.emit("repository:update:before", {
@@ -1177,3 +1177,7 @@ export interface StatementOptions {
1177
1177
  */
1178
1178
  now?: DateTime | string;
1179
1179
  }
1180
+
1181
+ type WithSQL<T> = {
1182
+ [P in keyof T]?: T[P] | SQL;
1183
+ };
@@ -16,14 +16,22 @@ export * from "./providers/WorkerProvider.ts";
16
16
  // ---------------------------------------------------------------------------------------------------------------------
17
17
 
18
18
  /**
19
- * Provides asynchronous message queuing and processing capabilities through declarative queue descriptors.
19
+ * | type | quality | stability |
20
+ * |------|---------|-----------|
21
+ * | backend | epic | stable |
20
22
  *
21
- * The queue module enables reliable background job processing and message passing using the `$queue` descriptor
22
- * on class properties. It supports schema validation, automatic retries, and multiple queue backends for
23
- * building scalable, decoupled applications with robust error handling.
23
+ * Asynchronous message processing with automatic worker management.
24
+ *
25
+ * **Features:**
26
+ * - Background job queues with type-safe payloads
27
+ * - Queue consumer handlers
28
+ * - Automatic worker threads for non-blocking processing
29
+ * - Retry mechanisms with exponential backoff
30
+ * - Dead letter queues for failed messages
31
+ * - Batch processing support
32
+ * - Configurable concurrency and worker pools
33
+ * - Providers: Memory (dev), Redis (production)
24
34
  *
25
- * @see {@link $queue}
26
- * @see {@link $consumer}
27
35
  * @module alepha.queue
28
36
  */
29
37
  export const AlephaQueue = $module({
@@ -0,0 +1,202 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Alepha } from "alepha";
3
+ import { DateTimeProvider } from "alepha/datetime";
4
+ import { $issuer, AlephaSecurity } from "alepha/security";
5
+ import { AlephaServer, HttpClient, ServerProvider } from "alepha/server";
6
+ import {
7
+ $auth,
8
+ alephaServerAuthRoutes,
9
+ type TokenResponse,
10
+ tokenResponseSchema,
11
+ tokensSchema,
12
+ } from "alepha/server/auth";
13
+ import { $client } from "alepha/server/links";
14
+ import { describe, test } from "vitest";
15
+ import { ReactAuth, type ReactAuthProvider } from "../index.ts";
16
+
17
+ describe("$auth", () => {
18
+ const user = {
19
+ id: randomUUID(),
20
+ name: "John Doe",
21
+ username: "john",
22
+ password: "***",
23
+ roles: ["admin"],
24
+ };
25
+
26
+ class App {
27
+ issuer = $issuer({
28
+ secret: "my-secret-key",
29
+ roles: [
30
+ {
31
+ name: "admin",
32
+ permissions: [{ name: "*" }],
33
+ },
34
+ ],
35
+ });
36
+
37
+ auth = $auth({
38
+ issuer: this.issuer,
39
+ credentials: {
40
+ account: () => user,
41
+ },
42
+ });
43
+
44
+ api = $client<ReactAuthProvider>();
45
+ }
46
+
47
+ const userinfo = (alepha: Alepha, token?: string) =>
48
+ alepha
49
+ .inject(HttpClient)
50
+ .fetch(
51
+ `${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.userinfo}`,
52
+ {
53
+ method: "GET",
54
+ headers: {
55
+ authorization: `Bearer ${token}`,
56
+ },
57
+ },
58
+ )
59
+ .then((it) => it.data);
60
+
61
+ const login = (alepha: Alepha) =>
62
+ alepha
63
+ .inject(HttpClient)
64
+ .fetch(
65
+ `${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.token}?provider=auth`,
66
+ {
67
+ method: "POST",
68
+ body: JSON.stringify({
69
+ username: user.username,
70
+ password: user.password,
71
+ }),
72
+ schema: {
73
+ response: tokenResponseSchema,
74
+ },
75
+ },
76
+ );
77
+
78
+ const refresh = (alepha: Alepha, tokens: TokenResponse) =>
79
+ alepha
80
+ .inject(HttpClient)
81
+ .fetch(
82
+ `${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.refresh}?provider=auth`,
83
+ {
84
+ method: "POST",
85
+ body: JSON.stringify({
86
+ refresh_token: tokens.refresh_token!,
87
+ access_token: tokens.access_token,
88
+ }),
89
+ schema: {
90
+ response: tokensSchema,
91
+ },
92
+ },
93
+ );
94
+
95
+ test("should login with credentials", async ({ expect }) => {
96
+ const alepha = Alepha.create()
97
+ .with(AlephaServer)
98
+ .with(AlephaSecurity)
99
+ .with(App);
100
+ const auth = alepha.inject(ReactAuth);
101
+ await alepha.start();
102
+
103
+ expect(auth.user).toBeUndefined();
104
+ await auth.login("auth", {
105
+ username: user.username,
106
+ password: user.password,
107
+ hostname: alepha.inject(ServerProvider).hostname,
108
+ });
109
+ expect(auth.user).toEqual({
110
+ id: user.id,
111
+ name: user.name,
112
+ roles: user.roles,
113
+ username: user.username,
114
+ });
115
+ });
116
+
117
+ test("should get userinfo", async ({ expect }) => {
118
+ const alepha = Alepha.create()
119
+ .with(AlephaServer)
120
+ .with(AlephaSecurity)
121
+ .with(App);
122
+ await alepha.start();
123
+
124
+ const { data: tokens } = await login(alepha);
125
+
126
+ expect(await userinfo(alepha, tokens.access_token)).toEqual({
127
+ user: {
128
+ id: user.id,
129
+ name: user.name,
130
+ roles: user.roles,
131
+ username: user.username,
132
+ sessionId: expect.any(String),
133
+ },
134
+ api: {
135
+ prefix: "/api",
136
+ links: [],
137
+ },
138
+ });
139
+ });
140
+
141
+ test("should reject expired token", async ({ expect }) => {
142
+ const alepha = Alepha.create()
143
+ .with(AlephaServer)
144
+ .with(AlephaSecurity)
145
+ .with(App);
146
+ await alepha.start();
147
+
148
+ const { data: tokens } = await login(alepha);
149
+
150
+ await alepha.inject(DateTimeProvider).travel(1, "hour");
151
+
152
+ expect(await userinfo(alepha, tokens.access_token)).toEqual({
153
+ api: {
154
+ prefix: "/api",
155
+ links: [],
156
+ },
157
+ });
158
+ });
159
+
160
+ test("should refresh expired token", async ({ expect }) => {
161
+ const alepha = Alepha.create()
162
+ .with(AlephaServer)
163
+ .with(AlephaSecurity)
164
+ .with(App);
165
+ await alepha.start();
166
+
167
+ const { data: tokens } = await login(alepha);
168
+
169
+ await alepha.inject(DateTimeProvider).travel(1, "hour");
170
+
171
+ const { data: tokens2 } = await refresh(alepha, tokens);
172
+
173
+ expect(await userinfo(alepha, tokens2.access_token)).toEqual({
174
+ user: {
175
+ id: user.id,
176
+ name: user.name,
177
+ roles: user.roles,
178
+ username: user.username,
179
+ },
180
+ api: {
181
+ prefix: "/api",
182
+ links: [],
183
+ },
184
+ });
185
+ });
186
+
187
+ test("should reject expired refresh token", async ({ expect }) => {
188
+ const alepha = Alepha.create()
189
+ .with(AlephaServer)
190
+ .with(AlephaSecurity)
191
+ .with(App);
192
+ await alepha.start();
193
+
194
+ const { data: tokens } = await login(alepha);
195
+
196
+ await alepha.inject(DateTimeProvider).travel(40, "days");
197
+
198
+ await expect(refresh(alepha, tokens)).rejects.toThrowError(
199
+ "Failed to refresh access token using the refresh token (issuer)",
200
+ );
201
+ });
202
+ });
@@ -0,0 +1,32 @@
1
+ import { useAlepha, useStore } from "alepha/react";
2
+ import { type HttpVirtualClient, LinkProvider } from "alepha/server/links";
3
+ import { ReactAuth } from "../services/ReactAuth.ts";
4
+
5
+ export const useAuth = <T extends object = any>() => {
6
+ const alepha = useAlepha();
7
+ const [user] = useStore("alepha.server.request.user");
8
+
9
+ return {
10
+ user,
11
+ logout: () => {
12
+ alepha.inject(ReactAuth).logout();
13
+ },
14
+ login: async (
15
+ provider: keyof T,
16
+ options: {
17
+ username?: string;
18
+ password?: string;
19
+ redirect?: string;
20
+ realm?: string;
21
+ [extra: string]: any;
22
+ } = {},
23
+ ) => {
24
+ await alepha.inject(ReactAuth).login(provider as string, options);
25
+ },
26
+ can: <Api extends object = any>(
27
+ name: keyof HttpVirtualClient<Api>,
28
+ ): boolean => {
29
+ return alepha.inject(LinkProvider).can(name as string);
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,13 @@
1
+ import { $module } from "alepha";
2
+ import { ReactAuth } from "./services/ReactAuth.ts";
3
+
4
+ // ---------------------------------------------------------------------------------------------------------------------
5
+
6
+ export * from "./index.shared.ts";
7
+
8
+ // ---------------------------------------------------------------------------------------------------------------------
9
+
10
+ export const AlephaReactAuth = $module({
11
+ name: "alepha.react.auth",
12
+ services: [ReactAuth],
13
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./hooks/useAuth.ts";
2
+ export * from "./services/ReactAuth.ts";
@@ -0,0 +1,48 @@
1
+ import { $module } from "alepha";
2
+ import { AlephaReact } from "alepha/react";
3
+ import type { UserAccount } from "alepha/security";
4
+ import { $auth, AlephaServerAuth } from "alepha/server/auth";
5
+ import { AlephaServerLinks } from "alepha/server/links";
6
+ import { ReactAuthProvider } from "./providers/ReactAuthProvider.ts";
7
+ import { ReactAuth } from "./services/ReactAuth.ts";
8
+
9
+ // ---------------------------------------------------------------------------------------------------------------------
10
+
11
+ export * from "./index.shared.ts";
12
+ export * from "./providers/ReactAuthProvider.ts";
13
+
14
+ // ---------------------------------------------------------------------------------------------------------------------
15
+
16
+ declare module "alepha/react/router" {
17
+ interface ReactRouterState {
18
+ user?: UserAccount;
19
+ }
20
+ }
21
+
22
+ // ---------------------------------------------------------------------------------------------------------------------
23
+
24
+ /**
25
+ * | type | quality | stability |
26
+ * |------|---------|-----------|
27
+ * | frontend | rare | stable |
28
+ *
29
+ * Auth-related React components and hooks.
30
+ *
31
+ * **Features:**
32
+ * - Login/logout components
33
+ * - Protected route wrappers
34
+ * - Auth state hooks
35
+ *
36
+ * @module alepha.react.auth
37
+ */
38
+ export const AlephaReactAuth = $module({
39
+ name: "alepha.react.auth",
40
+ primitives: [$auth],
41
+ services: [
42
+ AlephaReact,
43
+ AlephaServerLinks,
44
+ AlephaServerAuth,
45
+ ReactAuthProvider,
46
+ ReactAuth,
47
+ ],
48
+ });
@@ -0,0 +1,16 @@
1
+ import { $hook, $inject, Alepha } from "alepha";
2
+
3
+ export class ReactAuthProvider {
4
+ protected readonly alepha = $inject(Alepha);
5
+
6
+ public readonly onRender = $hook({
7
+ on: "react:server:render:begin",
8
+ handler: async ({ request, state }) => {
9
+ if (request?.user) {
10
+ const { token, realm, ...user } = request.user; // do not send token and realm to the client
11
+ this.alepha.store.set("alepha.server.request.user", user); // for hydration, browser, etc...
12
+ state.user = user;
13
+ }
14
+ },
15
+ });
16
+ }
@@ -0,0 +1,135 @@
1
+ import { $hook, $inject, Alepha } from "alepha";
2
+ import { $logger } from "alepha/logger";
3
+ import { ReactBrowserProvider, Redirection } from "alepha/react/router";
4
+ import type { UserAccountToken } from "alepha/security";
5
+ import { HttpClient } from "alepha/server";
6
+ import {
7
+ alephaServerAuthRoutes,
8
+ type Tokens,
9
+ tokenResponseSchema,
10
+ userinfoResponseSchema,
11
+ } from "alepha/server/auth";
12
+ import { LinkProvider } from "alepha/server/links";
13
+
14
+ /**
15
+ * Browser, SSR friendly, service to handle authentication.
16
+ */
17
+ export class ReactAuth {
18
+ protected readonly log = $logger();
19
+ protected readonly alepha = $inject(Alepha);
20
+ protected readonly httpClient = $inject(HttpClient);
21
+ protected readonly linkProvider = $inject(LinkProvider);
22
+
23
+ protected readonly onBeginTransition = $hook({
24
+ on: "react:transition:begin",
25
+ handler: async (event) => {
26
+ if (this.alepha.isBrowser()) {
27
+ Object.defineProperty(event.state, "user", {
28
+ get: () => this.user,
29
+ });
30
+ }
31
+ },
32
+ });
33
+
34
+ protected readonly onFetchRequest = $hook({
35
+ on: "client:onRequest",
36
+ handler: async ({ request }) => {
37
+ if (this.alepha.isBrowser() && this.user) {
38
+ // ensure cookies are sent with requests and refresh-able
39
+ request.credentials ??= "include";
40
+ }
41
+ },
42
+ });
43
+
44
+ /**
45
+ * Get the current authenticated user.
46
+ *
47
+ * Alias for `alepha.state.get("user")`
48
+ */
49
+ public get user(): UserAccountToken | undefined {
50
+ return this.alepha.store.get("alepha.server.request.user");
51
+ }
52
+
53
+ public async ping() {
54
+ const { data } = await this.httpClient.fetch(
55
+ alephaServerAuthRoutes.userinfo,
56
+ {
57
+ schema: { response: userinfoResponseSchema },
58
+ },
59
+ );
60
+
61
+ this.alepha.store.set("alepha.server.request.apiLinks", data.api);
62
+ this.alepha.store.set("alepha.server.request.user", data.user);
63
+
64
+ return data.user;
65
+ }
66
+
67
+ public can(action: string): boolean {
68
+ if (!this.user) {
69
+ return false;
70
+ }
71
+
72
+ return this.linkProvider.can(action);
73
+ }
74
+
75
+ public async login(
76
+ provider: string,
77
+ options: {
78
+ hostname?: string;
79
+ username?: string;
80
+ password?: string;
81
+ redirect?: string;
82
+ realm?: string;
83
+ [extra: string]: any;
84
+ },
85
+ ): Promise<Tokens> {
86
+ const realmParam = options.realm
87
+ ? `&realm=${encodeURIComponent(options.realm)}`
88
+ : "";
89
+
90
+ if (options.username || options.password) {
91
+ const { data } = await this.httpClient.fetch(
92
+ `${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,
93
+ {
94
+ method: "POST",
95
+ body: JSON.stringify({
96
+ username: options.username,
97
+ password: options.password,
98
+ }),
99
+ schema: { response: tokenResponseSchema },
100
+ },
101
+ );
102
+
103
+ this.alepha.store.set("alepha.server.request.apiLinks", data.api);
104
+ this.alepha.store.set("alepha.server.request.user", data.user);
105
+
106
+ return data;
107
+ }
108
+
109
+ if (this.alepha.isBrowser()) {
110
+ const browser = this.alepha.inject(ReactBrowserProvider);
111
+ const redirect =
112
+ options.redirect ||
113
+ (browser.transitioning
114
+ ? window.location.origin + browser.transitioning.to
115
+ : window.location.href);
116
+
117
+ const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;
118
+
119
+ if (browser.transitioning) {
120
+ throw new Redirection(href);
121
+ } else {
122
+ window.location.href = href;
123
+ return {} as Tokens;
124
+ }
125
+ }
126
+
127
+ throw new Redirection(
128
+ `${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`,
129
+ );
130
+ }
131
+
132
+ public logout() {
133
+ window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
134
+ }
135
+ }