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,1035 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Alepha, t } from "alepha";
3
+ import { AdminApiKeyController, ApiKeyController } from "alepha/api/keys";
4
+ import { DateTimeProvider } from "alepha/datetime";
5
+ import { AlephaEmail } from "alepha/email";
6
+ import { AlephaFake, FakeProvider } from "alepha/fake";
7
+ import { AlephaSecurity } from "alepha/security";
8
+ import { $action, AlephaServer } from "alepha/server";
9
+ import { describe, it } from "vitest";
10
+ import { AdminUserController } from "../controllers/AdminUserController.ts";
11
+ import { $realm, AlephaApiUsers } from "../index.ts";
12
+
13
+ // Admin context for admin controller calls
14
+ const adminUser = { id: randomUUID(), roles: ["admin"] };
15
+
16
+ // Schema for generating fake user data
17
+ const userDataSchema = t.object({
18
+ username: t.string(),
19
+ email: t.email(),
20
+ });
21
+
22
+ const setup = async () => {
23
+ const alepha = Alepha.create({
24
+ env: { LOG_LEVEL: "error" },
25
+ });
26
+
27
+ alepha.with(AlephaServer);
28
+ alepha.with(AlephaSecurity);
29
+ alepha.with(AlephaEmail);
30
+ alepha.with(AlephaApiUsers);
31
+ alepha.with(AlephaFake);
32
+
33
+ // Create a realm with API keys enabled
34
+ class TestApp {
35
+ realm = $realm({
36
+ apiKeys: true,
37
+ });
38
+
39
+ // A protected action to test API key authentication (any authenticated user)
40
+ getProfile = $action({
41
+ path: "/profile",
42
+ group: "profile",
43
+ secure: true,
44
+ schema: {
45
+ response: t.object({
46
+ userId: t.string(),
47
+ roles: t.array(t.string()),
48
+ }),
49
+ },
50
+ handler: (request) => ({
51
+ userId: request.user.id,
52
+ roles: request.user.roles ?? [],
53
+ }),
54
+ });
55
+
56
+ // Admin-only action (group: "admin:*" is excluded from user role)
57
+ adminStats = $action({
58
+ path: "/admin/stats",
59
+ method: "GET",
60
+ group: "admin:stats",
61
+ secure: true,
62
+ schema: {
63
+ response: t.object({
64
+ message: t.string(),
65
+ adminId: t.string(),
66
+ roles: t.array(t.string()),
67
+ }),
68
+ },
69
+ handler: (request) => ({
70
+ message: "Admin stats retrieved",
71
+ adminId: request.user.id,
72
+ roles: request.user.roles ?? [],
73
+ }),
74
+ });
75
+ }
76
+
77
+ const app = alepha.inject(TestApp);
78
+ await alepha.start();
79
+
80
+ const adminUserController = alepha.inject(AdminUserController);
81
+ const apiKeyController = alepha.inject(ApiKeyController);
82
+ const adminApiKeyController = alepha.inject(AdminApiKeyController);
83
+ const dateTimeProvider = alepha.inject(DateTimeProvider);
84
+ const fakeProvider = alepha.inject(FakeProvider);
85
+
86
+ return {
87
+ alepha,
88
+ app,
89
+ adminUserController,
90
+ apiKeyController,
91
+ adminApiKeyController,
92
+ dateTimeProvider,
93
+ fakeProvider,
94
+ };
95
+ };
96
+
97
+ describe("alepha/api/users - API Keys Integration (Controllers)", () => {
98
+ // -------------------------------------------------------------------------
99
+ // User API Key Management (CRUD via ApiKeyController)
100
+ // -------------------------------------------------------------------------
101
+
102
+ it("should create an API key via controller", async ({ expect }) => {
103
+ const { adminUserController, apiKeyController, fakeProvider } =
104
+ await setup();
105
+ const fakeUser = fakeProvider.generate(userDataSchema);
106
+
107
+ // Create a user via admin controller
108
+ const userResponse = await adminUserController.createUser.fetch(
109
+ {
110
+ body: {
111
+ ...fakeUser,
112
+ roles: ["user"],
113
+ },
114
+ },
115
+ { user: adminUser },
116
+ );
117
+ const user = userResponse.data;
118
+
119
+ // Create API key via controller (authenticated as the user)
120
+ const response = await apiKeyController.createApiKey.fetch(
121
+ {
122
+ body: {
123
+ name: "My API Key",
124
+ description: "Test key for CI/CD",
125
+ },
126
+ },
127
+ { user: { id: user.id, roles: user.roles } },
128
+ );
129
+
130
+ expect(response.status).toBe(200);
131
+ expect(response.data.name).toBe("My API Key");
132
+ expect(response.data.token).toMatch(/^ak_/);
133
+ expect(response.data.roles).toEqual(["user"]);
134
+ });
135
+
136
+ it("should list user's own API keys via controller", async ({ expect }) => {
137
+ const { adminUserController, apiKeyController, fakeProvider } =
138
+ await setup();
139
+ const fakeUser = fakeProvider.generate(userDataSchema);
140
+
141
+ // Create a user
142
+ const userResponse = await adminUserController.createUser.fetch(
143
+ {
144
+ body: {
145
+ ...fakeUser,
146
+ roles: ["user"],
147
+ },
148
+ },
149
+ { user: adminUser },
150
+ );
151
+ const user = userResponse.data;
152
+
153
+ // Create multiple API keys
154
+ await apiKeyController.createApiKey.fetch(
155
+ { body: { name: "Key 1" } },
156
+ { user: { id: user.id, roles: user.roles } },
157
+ );
158
+ await apiKeyController.createApiKey.fetch(
159
+ { body: { name: "Key 2" } },
160
+ { user: { id: user.id, roles: user.roles } },
161
+ );
162
+
163
+ // List keys via controller
164
+ const response = await apiKeyController.listApiKeys.fetch(
165
+ {},
166
+ { user: { id: user.id, roles: user.roles } },
167
+ );
168
+
169
+ expect(response.status).toBe(200);
170
+ expect(response.data).toHaveLength(2);
171
+ expect(response.data.map((k) => k.name).sort()).toEqual(["Key 1", "Key 2"]);
172
+ });
173
+
174
+ it("should revoke own API key via controller", async ({ expect }) => {
175
+ const { adminUserController, apiKeyController, app, fakeProvider } =
176
+ await setup();
177
+ const fakeUser = fakeProvider.generate(userDataSchema);
178
+
179
+ // Create a user
180
+ const userResponse = await adminUserController.createUser.fetch(
181
+ {
182
+ body: {
183
+ ...fakeUser,
184
+ roles: ["user"],
185
+ },
186
+ },
187
+ { user: adminUser },
188
+ );
189
+ const user = userResponse.data;
190
+
191
+ // Create an API key
192
+ const createResponse = await apiKeyController.createApiKey.fetch(
193
+ { body: { name: "My Key" } },
194
+ { user: { id: user.id, roles: user.roles } },
195
+ );
196
+ const { id, token } = createResponse.data;
197
+
198
+ // Verify key works (access protected endpoint via query param)
199
+ const beforeRevoke = await app.getProfile.fetch({
200
+ query: { api_key: token },
201
+ });
202
+ expect(beforeRevoke.status).toBe(200);
203
+ expect(beforeRevoke.data.userId).toBe(user.id);
204
+
205
+ // Revoke the key
206
+ const revokeResponse = await apiKeyController.revokeApiKey.fetch(
207
+ { params: { id } },
208
+ { user: { id: user.id, roles: user.roles } },
209
+ );
210
+
211
+ expect(revokeResponse.status).toBe(200);
212
+ expect(revokeResponse.data.ok).toBe(true);
213
+
214
+ // Verify key no longer works
215
+ await expect(
216
+ app.getProfile.fetch({ query: { api_key: token } }),
217
+ ).rejects.toThrow();
218
+ });
219
+
220
+ it("should not allow revoking another user's API key", async ({ expect }) => {
221
+ const { adminUserController, apiKeyController, fakeProvider } =
222
+ await setup();
223
+
224
+ // Create two users
225
+ const user1Response = await adminUserController.createUser.fetch(
226
+ {
227
+ body: {
228
+ ...fakeProvider.generate(userDataSchema),
229
+ roles: ["user"],
230
+ },
231
+ },
232
+ { user: adminUser },
233
+ );
234
+ const user1 = user1Response.data;
235
+
236
+ const user2Response = await adminUserController.createUser.fetch(
237
+ {
238
+ body: {
239
+ ...fakeProvider.generate(userDataSchema),
240
+ roles: ["user"],
241
+ },
242
+ },
243
+ { user: adminUser },
244
+ );
245
+ const user2 = user2Response.data;
246
+
247
+ // User1 creates an API key
248
+ const createResponse = await apiKeyController.createApiKey.fetch(
249
+ { body: { name: "User1 Key" } },
250
+ { user: { id: user1.id, roles: user1.roles } },
251
+ );
252
+ const { id } = createResponse.data;
253
+
254
+ // User2 tries to revoke User1's key - should fail with 403
255
+ await expect(
256
+ apiKeyController.revokeApiKey.fetch(
257
+ { params: { id } },
258
+ { user: { id: user2.id, roles: user2.roles } },
259
+ ),
260
+ ).rejects.toThrow("Not your API key");
261
+ });
262
+
263
+ it("should create API key with expiration", async ({ expect }) => {
264
+ const {
265
+ adminUserController,
266
+ apiKeyController,
267
+ app,
268
+ dateTimeProvider,
269
+ fakeProvider,
270
+ } = await setup();
271
+ const fakeUser = fakeProvider.generate(userDataSchema);
272
+
273
+ // Create a user
274
+ const userResponse = await adminUserController.createUser.fetch(
275
+ {
276
+ body: {
277
+ ...fakeUser,
278
+ roles: ["user"],
279
+ },
280
+ },
281
+ { user: adminUser },
282
+ );
283
+ const user = userResponse.data;
284
+
285
+ // Create API key with 1 hour expiration
286
+ const expiresAt = dateTimeProvider.now().add(1, "hour").toISOString();
287
+ const createResponse = await apiKeyController.createApiKey.fetch(
288
+ { body: { name: "Expiring Key", expiresAt } },
289
+ { user: { id: user.id, roles: user.roles } },
290
+ );
291
+
292
+ expect(createResponse.status).toBe(200);
293
+ expect(createResponse.data.expiresAt).toBe(expiresAt);
294
+
295
+ const { token } = createResponse.data;
296
+
297
+ // Verify key works before expiry
298
+ const beforeExpiry = await app.getProfile.fetch({
299
+ query: { api_key: token },
300
+ });
301
+ expect(beforeExpiry.status).toBe(200);
302
+
303
+ // Travel past expiration
304
+ dateTimeProvider.travel(2, "hours");
305
+
306
+ // Verify key no longer works
307
+ await expect(
308
+ app.getProfile.fetch({ query: { api_key: token } }),
309
+ ).rejects.toThrow();
310
+ });
311
+
312
+ // -------------------------------------------------------------------------
313
+ // Admin API Key Management (via AdminApiKeyController)
314
+ // -------------------------------------------------------------------------
315
+
316
+ it("should list all API keys via admin controller", async ({ expect }) => {
317
+ const {
318
+ adminUserController,
319
+ apiKeyController,
320
+ adminApiKeyController,
321
+ fakeProvider,
322
+ } = await setup();
323
+
324
+ // Create two users with API keys
325
+ const user1Response = await adminUserController.createUser.fetch(
326
+ {
327
+ body: {
328
+ ...fakeProvider.generate(userDataSchema),
329
+ roles: ["user"],
330
+ },
331
+ },
332
+ { user: adminUser },
333
+ );
334
+ const user1 = user1Response.data;
335
+
336
+ const user2Response = await adminUserController.createUser.fetch(
337
+ {
338
+ body: {
339
+ ...fakeProvider.generate(userDataSchema),
340
+ roles: ["user"],
341
+ },
342
+ },
343
+ { user: adminUser },
344
+ );
345
+ const user2 = user2Response.data;
346
+
347
+ // Each user creates an API key
348
+ await apiKeyController.createApiKey.fetch(
349
+ { body: { name: "User1 Key" } },
350
+ { user: { id: user1.id, roles: user1.roles } },
351
+ );
352
+ await apiKeyController.createApiKey.fetch(
353
+ { body: { name: "User2 Key" } },
354
+ { user: { id: user2.id, roles: user2.roles } },
355
+ );
356
+
357
+ // Admin lists all API keys
358
+ const response = await adminApiKeyController.findApiKeys.fetch(
359
+ {},
360
+ { user: adminUser },
361
+ );
362
+
363
+ expect(response.status).toBe(200);
364
+ expect(response.data.content).toHaveLength(2);
365
+ });
366
+
367
+ it("should filter API keys by userId via admin controller", async ({
368
+ expect,
369
+ }) => {
370
+ const {
371
+ adminUserController,
372
+ apiKeyController,
373
+ adminApiKeyController,
374
+ fakeProvider,
375
+ } = await setup();
376
+
377
+ // Create two users
378
+ const user1Response = await adminUserController.createUser.fetch(
379
+ {
380
+ body: {
381
+ ...fakeProvider.generate(userDataSchema),
382
+ roles: ["user"],
383
+ },
384
+ },
385
+ { user: adminUser },
386
+ );
387
+ const user1 = user1Response.data;
388
+
389
+ const user2Response = await adminUserController.createUser.fetch(
390
+ {
391
+ body: {
392
+ ...fakeProvider.generate(userDataSchema),
393
+ roles: ["user"],
394
+ },
395
+ },
396
+ { user: adminUser },
397
+ );
398
+ const user2 = user2Response.data;
399
+
400
+ // Create keys for both users
401
+ await apiKeyController.createApiKey.fetch(
402
+ { body: { name: "User1 Key" } },
403
+ { user: { id: user1.id, roles: user1.roles } },
404
+ );
405
+ await apiKeyController.createApiKey.fetch(
406
+ { body: { name: "User2 Key" } },
407
+ { user: { id: user2.id, roles: user2.roles } },
408
+ );
409
+
410
+ // Admin filters by user1
411
+ const response = await adminApiKeyController.findApiKeys.fetch(
412
+ { query: { userId: user1.id } },
413
+ { user: adminUser },
414
+ );
415
+
416
+ expect(response.status).toBe(200);
417
+ expect(response.data.content).toHaveLength(1);
418
+ expect(response.data.content[0].name).toBe("User1 Key");
419
+ });
420
+
421
+ it("should get API key by ID via admin controller", async ({ expect }) => {
422
+ const {
423
+ adminUserController,
424
+ apiKeyController,
425
+ adminApiKeyController,
426
+ fakeProvider,
427
+ } = await setup();
428
+ const fakeUser = fakeProvider.generate(userDataSchema);
429
+
430
+ // Create a user with an API key
431
+ const userResponse = await adminUserController.createUser.fetch(
432
+ {
433
+ body: {
434
+ ...fakeUser,
435
+ roles: ["user"],
436
+ },
437
+ },
438
+ { user: adminUser },
439
+ );
440
+ const user = userResponse.data;
441
+
442
+ const createResponse = await apiKeyController.createApiKey.fetch(
443
+ { body: { name: "My Key", description: "Test description" } },
444
+ { user: { id: user.id, roles: user.roles } },
445
+ );
446
+ const { id } = createResponse.data;
447
+
448
+ // Admin gets the key by ID
449
+ const response = await adminApiKeyController.getApiKey.fetch(
450
+ { params: { id } },
451
+ { user: adminUser },
452
+ );
453
+
454
+ expect(response.status).toBe(200);
455
+ expect(response.data.id).toBe(id);
456
+ expect(response.data.name).toBe("My Key");
457
+ expect(response.data.description).toBe("Test description");
458
+ expect(response.data.userId).toBe(user.id);
459
+ });
460
+
461
+ it("should revoke any API key via admin controller", async ({ expect }) => {
462
+ const {
463
+ adminUserController,
464
+ apiKeyController,
465
+ adminApiKeyController,
466
+ app,
467
+ fakeProvider,
468
+ } = await setup();
469
+ const fakeUser = fakeProvider.generate(userDataSchema);
470
+
471
+ // Create a user with an API key
472
+ const userResponse = await adminUserController.createUser.fetch(
473
+ {
474
+ body: {
475
+ ...fakeUser,
476
+ roles: ["user"],
477
+ },
478
+ },
479
+ { user: adminUser },
480
+ );
481
+ const user = userResponse.data;
482
+
483
+ const createResponse = await apiKeyController.createApiKey.fetch(
484
+ { body: { name: "My Key" } },
485
+ { user: { id: user.id, roles: user.roles } },
486
+ );
487
+ const { id, token } = createResponse.data;
488
+
489
+ // Verify key works
490
+ const beforeRevoke = await app.getProfile.fetch({
491
+ query: { api_key: token },
492
+ });
493
+ expect(beforeRevoke.status).toBe(200);
494
+
495
+ // Admin revokes the key
496
+ const response = await adminApiKeyController.revokeApiKey.fetch(
497
+ { params: { id } },
498
+ { user: adminUser },
499
+ );
500
+
501
+ expect(response.status).toBe(200);
502
+ expect(response.data.ok).toBe(true);
503
+
504
+ // Verify key no longer works
505
+ await expect(
506
+ app.getProfile.fetch({ query: { api_key: token } }),
507
+ ).rejects.toThrow();
508
+ });
509
+
510
+ it("should include revoked keys when requested via admin controller", async ({
511
+ expect,
512
+ }) => {
513
+ const {
514
+ adminUserController,
515
+ apiKeyController,
516
+ adminApiKeyController,
517
+ fakeProvider,
518
+ } = await setup();
519
+ const fakeUser = fakeProvider.generate(userDataSchema);
520
+
521
+ // Create a user
522
+ const userResponse = await adminUserController.createUser.fetch(
523
+ {
524
+ body: {
525
+ ...fakeUser,
526
+ roles: ["user"],
527
+ },
528
+ },
529
+ { user: adminUser },
530
+ );
531
+ const user = userResponse.data;
532
+
533
+ // Create and revoke a key
534
+ const createResponse = await apiKeyController.createApiKey.fetch(
535
+ { body: { name: "Revoked Key" } },
536
+ { user: { id: user.id, roles: user.roles } },
537
+ );
538
+ await apiKeyController.revokeApiKey.fetch(
539
+ { params: { id: createResponse.data.id } },
540
+ { user: { id: user.id, roles: user.roles } },
541
+ );
542
+
543
+ // Create an active key
544
+ await apiKeyController.createApiKey.fetch(
545
+ { body: { name: "Active Key" } },
546
+ { user: { id: user.id, roles: user.roles } },
547
+ );
548
+
549
+ // List without revoked (default)
550
+ const activeOnly = await adminApiKeyController.findApiKeys.fetch(
551
+ {},
552
+ { user: adminUser },
553
+ );
554
+ expect(activeOnly.data.content).toHaveLength(1);
555
+ expect(activeOnly.data.content[0].name).toBe("Active Key");
556
+
557
+ // List with revoked
558
+ const withRevoked = await adminApiKeyController.findApiKeys.fetch(
559
+ { query: { includeRevoked: true } },
560
+ { user: adminUser },
561
+ );
562
+ expect(withRevoked.data.content).toHaveLength(2);
563
+ });
564
+
565
+ it("should handle pagination via admin controller", async ({ expect }) => {
566
+ const {
567
+ adminUserController,
568
+ apiKeyController,
569
+ adminApiKeyController,
570
+ fakeProvider,
571
+ } = await setup();
572
+ const fakeUser = fakeProvider.generate(userDataSchema);
573
+
574
+ // Create a user
575
+ const userResponse = await adminUserController.createUser.fetch(
576
+ {
577
+ body: {
578
+ ...fakeUser,
579
+ roles: ["user"],
580
+ },
581
+ },
582
+ { user: adminUser },
583
+ );
584
+ const user = userResponse.data;
585
+
586
+ // Create 5 API keys
587
+ for (let i = 1; i <= 5; i++) {
588
+ await apiKeyController.createApiKey.fetch(
589
+ { body: { name: `Key ${i}` } },
590
+ { user: { id: user.id, roles: user.roles } },
591
+ );
592
+ }
593
+
594
+ // Get first page
595
+ const page1 = await adminApiKeyController.findApiKeys.fetch(
596
+ { query: { size: 2, page: 0 } },
597
+ { user: adminUser },
598
+ );
599
+ expect(page1.data.content).toHaveLength(2);
600
+ expect(page1.data.page.totalElements).toBe(5);
601
+ expect(page1.data.page.totalPages).toBe(3);
602
+
603
+ // Get second page
604
+ const page2 = await adminApiKeyController.findApiKeys.fetch(
605
+ { query: { size: 2, page: 1 } },
606
+ { user: adminUser },
607
+ );
608
+ expect(page2.data.content).toHaveLength(2);
609
+ });
610
+
611
+ // -------------------------------------------------------------------------
612
+ // API Key Authentication (query param and Bearer header)
613
+ // -------------------------------------------------------------------------
614
+
615
+ it("should authenticate via api_key query parameter", async ({ expect }) => {
616
+ const { adminUserController, apiKeyController, app, fakeProvider } =
617
+ await setup();
618
+ const fakeUser = fakeProvider.generate(userDataSchema);
619
+
620
+ // Create a user with an API key
621
+ const userResponse = await adminUserController.createUser.fetch(
622
+ {
623
+ body: {
624
+ ...fakeUser,
625
+ roles: ["user"],
626
+ },
627
+ },
628
+ { user: adminUser },
629
+ );
630
+ const user = userResponse.data;
631
+
632
+ const createResponse = await apiKeyController.createApiKey.fetch(
633
+ { body: { name: "My Key" } },
634
+ { user: { id: user.id, roles: user.roles } },
635
+ );
636
+ const { token } = createResponse.data;
637
+
638
+ // Access protected endpoint via query param
639
+ const response = await app.getProfile.fetch({
640
+ query: { api_key: token },
641
+ });
642
+
643
+ expect(response.status).toBe(200);
644
+ expect(response.data.userId).toBe(user.id);
645
+ expect(response.data.roles).toEqual(["user"]);
646
+ });
647
+
648
+ it("should authenticate via Bearer header", async ({ expect }) => {
649
+ const { adminUserController, apiKeyController, app, fakeProvider } =
650
+ await setup();
651
+ const fakeUser = fakeProvider.generate(userDataSchema);
652
+
653
+ // Create a user with an API key
654
+ const userResponse = await adminUserController.createUser.fetch(
655
+ {
656
+ body: {
657
+ ...fakeUser,
658
+ roles: ["user"],
659
+ },
660
+ },
661
+ { user: adminUser },
662
+ );
663
+ const user = userResponse.data;
664
+
665
+ const createResponse = await apiKeyController.createApiKey.fetch(
666
+ { body: { name: "My Key" } },
667
+ { user: { id: user.id, roles: user.roles } },
668
+ );
669
+ const { token } = createResponse.data;
670
+
671
+ // Access protected endpoint via Bearer header
672
+ const response = await app.getProfile.fetch({
673
+ headers: { authorization: `Bearer ${token}` },
674
+ });
675
+
676
+ expect(response.status).toBe(200);
677
+ expect(response.data.userId).toBe(user.id);
678
+ expect(response.data.roles).toEqual(["user"]);
679
+ });
680
+
681
+ it("should reject request with invalid API key", async ({ expect }) => {
682
+ const { app } = await setup();
683
+
684
+ // Try to access with invalid key
685
+ await expect(
686
+ app.getProfile.fetch({ query: { api_key: "ak_invalid_token_12345" } }),
687
+ ).rejects.toThrow();
688
+ });
689
+
690
+ it("should reject request with revoked API key", async ({ expect }) => {
691
+ const { adminUserController, apiKeyController, app, fakeProvider } =
692
+ await setup();
693
+ const fakeUser = fakeProvider.generate(userDataSchema);
694
+
695
+ // Create a user with an API key
696
+ const userResponse = await adminUserController.createUser.fetch(
697
+ {
698
+ body: {
699
+ ...fakeUser,
700
+ roles: ["user"],
701
+ },
702
+ },
703
+ { user: adminUser },
704
+ );
705
+ const user = userResponse.data;
706
+
707
+ const createResponse = await apiKeyController.createApiKey.fetch(
708
+ { body: { name: "My Key" } },
709
+ { user: { id: user.id, roles: user.roles } },
710
+ );
711
+ const { id, token } = createResponse.data;
712
+
713
+ // Revoke the key
714
+ await apiKeyController.revokeApiKey.fetch(
715
+ { params: { id } },
716
+ { user: { id: user.id, roles: user.roles } },
717
+ );
718
+
719
+ // Try to access with revoked key
720
+ await expect(
721
+ app.getProfile.fetch({ query: { api_key: token } }),
722
+ ).rejects.toThrow();
723
+ });
724
+
725
+ // -------------------------------------------------------------------------
726
+ // Role-based Access with API Keys
727
+ // -------------------------------------------------------------------------
728
+
729
+ it("should allow admin API key to access admin-only endpoint", async ({
730
+ expect,
731
+ }) => {
732
+ const { adminUserController, apiKeyController, app, fakeProvider } =
733
+ await setup();
734
+ const fakeUser = fakeProvider.generate(userDataSchema);
735
+
736
+ // Create an admin user
737
+ const userResponse = await adminUserController.createUser.fetch(
738
+ {
739
+ body: {
740
+ ...fakeUser,
741
+ roles: ["admin"],
742
+ },
743
+ },
744
+ { user: adminUser },
745
+ );
746
+ const user = userResponse.data;
747
+
748
+ // Create API key with admin role
749
+ const createResponse = await apiKeyController.createApiKey.fetch(
750
+ { body: { name: "Admin Key" } },
751
+ { user: { id: user.id, roles: user.roles } },
752
+ );
753
+ const { token } = createResponse.data;
754
+
755
+ // Access admin endpoint
756
+ const response = await app.adminStats.fetch({
757
+ query: { api_key: token },
758
+ });
759
+
760
+ expect(response.status).toBe(200);
761
+ expect(response.data.message).toBe("Admin stats retrieved");
762
+ expect(response.data.adminId).toBe(user.id);
763
+ expect(response.data.roles).toContain("admin");
764
+ });
765
+
766
+ it("should reject user API key from admin-only endpoint", async ({
767
+ expect,
768
+ }) => {
769
+ const { adminUserController, apiKeyController, app, fakeProvider } =
770
+ await setup();
771
+ const fakeUser = fakeProvider.generate(userDataSchema);
772
+
773
+ // Create a regular user
774
+ const userResponse = await adminUserController.createUser.fetch(
775
+ {
776
+ body: {
777
+ ...fakeUser,
778
+ roles: ["user"],
779
+ },
780
+ },
781
+ { user: adminUser },
782
+ );
783
+ const user = userResponse.data;
784
+
785
+ // Create API key with user role
786
+ const createResponse = await apiKeyController.createApiKey.fetch(
787
+ { body: { name: "User Key" } },
788
+ { user: { id: user.id, roles: user.roles } },
789
+ );
790
+ const { token } = createResponse.data;
791
+
792
+ // Try to access admin endpoint - should fail
793
+ await expect(
794
+ app.adminStats.fetch({ query: { api_key: token } }),
795
+ ).rejects.toThrow();
796
+ });
797
+
798
+ it("should allow user API key to access user endpoint", async ({
799
+ expect,
800
+ }) => {
801
+ const { adminUserController, apiKeyController, app, fakeProvider } =
802
+ await setup();
803
+ const fakeUser = fakeProvider.generate(userDataSchema);
804
+
805
+ // Create a regular user
806
+ const userResponse = await adminUserController.createUser.fetch(
807
+ {
808
+ body: {
809
+ ...fakeUser,
810
+ roles: ["user"],
811
+ },
812
+ },
813
+ { user: adminUser },
814
+ );
815
+ const user = userResponse.data;
816
+
817
+ // Create API key
818
+ const createResponse = await apiKeyController.createApiKey.fetch(
819
+ { body: { name: "User Key" } },
820
+ { user: { id: user.id, roles: user.roles } },
821
+ );
822
+ const { token } = createResponse.data;
823
+
824
+ // Access user endpoint via Bearer header
825
+ const response = await app.getProfile.fetch({
826
+ headers: { authorization: `Bearer ${token}` },
827
+ });
828
+
829
+ expect(response.status).toBe(200);
830
+ expect(response.data.userId).toBe(user.id);
831
+ });
832
+
833
+ it("should snapshot roles at API key creation time", async ({ expect }) => {
834
+ const { adminUserController, apiKeyController, app, fakeProvider } =
835
+ await setup();
836
+ const fakeUser = fakeProvider.generate(userDataSchema);
837
+
838
+ // Create a user with user role
839
+ const userResponse = await adminUserController.createUser.fetch(
840
+ {
841
+ body: {
842
+ ...fakeUser,
843
+ roles: ["user"],
844
+ },
845
+ },
846
+ { user: adminUser },
847
+ );
848
+ const user = userResponse.data;
849
+
850
+ // Create API key (snapshots current roles: ["user"])
851
+ const createResponse = await apiKeyController.createApiKey.fetch(
852
+ { body: { name: "My Key" } },
853
+ { user: { id: user.id, roles: user.roles } },
854
+ );
855
+ const { token } = createResponse.data;
856
+
857
+ // Update user to have admin role
858
+ await adminUserController.updateUser.fetch(
859
+ {
860
+ params: { id: user.id },
861
+ body: { roles: ["user", "admin"] },
862
+ },
863
+ { user: adminUser },
864
+ );
865
+
866
+ // API key should still only have user role (snapshot)
867
+ // So accessing admin endpoint should fail
868
+ await expect(
869
+ app.adminStats.fetch({ query: { api_key: token } }),
870
+ ).rejects.toThrow();
871
+
872
+ // But user endpoint should still work
873
+ const response = await app.getProfile.fetch({
874
+ query: { api_key: token },
875
+ });
876
+ expect(response.status).toBe(200);
877
+ expect(response.data.roles).toEqual(["user"]); // Original roles
878
+ });
879
+
880
+ it("should create API key with multiple roles", async ({ expect }) => {
881
+ const { adminUserController, apiKeyController, app, fakeProvider } =
882
+ await setup();
883
+ const fakeUser = fakeProvider.generate(userDataSchema);
884
+
885
+ // Create a user with multiple roles
886
+ const userResponse = await adminUserController.createUser.fetch(
887
+ {
888
+ body: {
889
+ ...fakeUser,
890
+ roles: ["user", "admin"],
891
+ },
892
+ },
893
+ { user: adminUser },
894
+ );
895
+ const user = userResponse.data;
896
+
897
+ // Create API key
898
+ const createResponse = await apiKeyController.createApiKey.fetch(
899
+ { body: { name: "Power Key" } },
900
+ { user: { id: user.id, roles: user.roles } },
901
+ );
902
+
903
+ expect(createResponse.data.roles).toContain("user");
904
+ expect(createResponse.data.roles).toContain("admin");
905
+
906
+ const { token } = createResponse.data;
907
+
908
+ // Should access both user and admin endpoints
909
+ const profileResponse = await app.getProfile.fetch({
910
+ query: { api_key: token },
911
+ });
912
+ expect(profileResponse.status).toBe(200);
913
+
914
+ const adminResponse = await app.adminStats.fetch({
915
+ query: { api_key: token },
916
+ });
917
+ expect(adminResponse.status).toBe(200);
918
+ });
919
+
920
+ // -------------------------------------------------------------------------
921
+ // Edge Cases and Error Handling
922
+ // -------------------------------------------------------------------------
923
+
924
+ it("should return 404 when revoking non-existent API key", async ({
925
+ expect,
926
+ }) => {
927
+ const { adminUserController, apiKeyController, fakeProvider } =
928
+ await setup();
929
+ const fakeUser = fakeProvider.generate(userDataSchema);
930
+
931
+ // Create a user
932
+ const userResponse = await adminUserController.createUser.fetch(
933
+ {
934
+ body: {
935
+ ...fakeUser,
936
+ roles: ["user"],
937
+ },
938
+ },
939
+ { user: adminUser },
940
+ );
941
+ const user = userResponse.data;
942
+
943
+ // Try to revoke non-existent key
944
+ await expect(
945
+ apiKeyController.revokeApiKey.fetch(
946
+ { params: { id: "00000000-0000-0000-0000-000000000000" } },
947
+ { user: { id: user.id, roles: user.roles } },
948
+ ),
949
+ ).rejects.toThrow("API key not found");
950
+ });
951
+
952
+ it("should return 404 when getting non-existent API key via admin", async ({
953
+ expect,
954
+ }) => {
955
+ const { adminApiKeyController } = await setup();
956
+
957
+ // Try to get non-existent key
958
+ await expect(
959
+ adminApiKeyController.getApiKey.fetch(
960
+ { params: { id: "00000000-0000-0000-0000-000000000000" } },
961
+ { user: adminUser },
962
+ ),
963
+ ).rejects.toThrow("API key not found");
964
+ });
965
+
966
+ it("should list empty when user has no API keys", async ({ expect }) => {
967
+ const { adminUserController, apiKeyController, fakeProvider } =
968
+ await setup();
969
+ const fakeUser = fakeProvider.generate(userDataSchema);
970
+
971
+ // Create a user without any API keys
972
+ const userResponse = await adminUserController.createUser.fetch(
973
+ {
974
+ body: {
975
+ ...fakeUser,
976
+ roles: ["user"],
977
+ },
978
+ },
979
+ { user: adminUser },
980
+ );
981
+ const user = userResponse.data;
982
+
983
+ // List should return empty array
984
+ const response = await apiKeyController.listApiKeys.fetch(
985
+ {},
986
+ { user: { id: user.id, roles: user.roles } },
987
+ );
988
+
989
+ expect(response.status).toBe(200);
990
+ expect(response.data).toEqual([]);
991
+ });
992
+
993
+ it("should not return revoked keys in user's list", async ({ expect }) => {
994
+ const { adminUserController, apiKeyController, fakeProvider } =
995
+ await setup();
996
+ const fakeUser = fakeProvider.generate(userDataSchema);
997
+
998
+ // Create a user
999
+ const userResponse = await adminUserController.createUser.fetch(
1000
+ {
1001
+ body: {
1002
+ ...fakeUser,
1003
+ roles: ["user"],
1004
+ },
1005
+ },
1006
+ { user: adminUser },
1007
+ );
1008
+ const user = userResponse.data;
1009
+
1010
+ // Create and revoke a key
1011
+ const createResponse = await apiKeyController.createApiKey.fetch(
1012
+ { body: { name: "Revoked Key" } },
1013
+ { user: { id: user.id, roles: user.roles } },
1014
+ );
1015
+ await apiKeyController.revokeApiKey.fetch(
1016
+ { params: { id: createResponse.data.id } },
1017
+ { user: { id: user.id, roles: user.roles } },
1018
+ );
1019
+
1020
+ // Create an active key
1021
+ await apiKeyController.createApiKey.fetch(
1022
+ { body: { name: "Active Key" } },
1023
+ { user: { id: user.id, roles: user.roles } },
1024
+ );
1025
+
1026
+ // List should only show active key
1027
+ const response = await apiKeyController.listApiKeys.fetch(
1028
+ {},
1029
+ { user: { id: user.id, roles: user.roles } },
1030
+ );
1031
+
1032
+ expect(response.data).toHaveLength(1);
1033
+ expect(response.data[0].name).toBe("Active Key");
1034
+ });
1035
+ });