alepha 0.13.6 → 0.13.8

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 (487) hide show
  1. package/README.md +5 -2
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui-standalone-preset.js +1 -1
  4. package/assets/swagger-ui/swagger-ui.css +1 -1
  5. package/dist/api/audits/index.browser.js +116 -0
  6. package/dist/api/audits/index.browser.js.map +1 -0
  7. package/dist/api/audits/index.d.ts +1194 -0
  8. package/dist/api/audits/index.js +674 -0
  9. package/dist/api/audits/index.js.map +1 -0
  10. package/dist/{api-files → api/files}/index.browser.js +5 -5
  11. package/dist/api/files/index.browser.js.map +1 -0
  12. package/dist/{api-files → api/files}/index.d.ts +16 -9
  13. package/dist/{api-files → api/files}/index.js +10 -10
  14. package/dist/api/files/index.js.map +1 -0
  15. package/dist/{api-jobs → api/jobs}/index.browser.js +5 -5
  16. package/dist/api/jobs/index.browser.js.map +1 -0
  17. package/dist/{api-jobs → api/jobs}/index.d.ts +35 -35
  18. package/dist/{api-jobs → api/jobs}/index.js +9 -9
  19. package/dist/api/jobs/index.js.map +1 -0
  20. package/dist/{api-notifications → api/notifications}/index.browser.js +11 -11
  21. package/dist/api/notifications/index.browser.js.map +1 -0
  22. package/dist/api/notifications/index.d.ts +327 -0
  23. package/dist/{api-notifications → api/notifications}/index.js +11 -11
  24. package/dist/api/notifications/index.js.map +1 -0
  25. package/dist/api/parameters/index.browser.js +60 -0
  26. package/dist/api/parameters/index.browser.js.map +1 -0
  27. package/dist/api/parameters/index.d.ts +761 -0
  28. package/dist/api/parameters/index.js +877 -0
  29. package/dist/api/parameters/index.js.map +1 -0
  30. package/dist/{api-users → api/users}/index.browser.js +6 -6
  31. package/dist/api/users/index.browser.js.map +1 -0
  32. package/dist/{api-users → api/users}/index.d.ts +259 -247
  33. package/dist/{api-users → api/users}/index.js +125 -112
  34. package/dist/api/users/index.js.map +1 -0
  35. package/dist/{api-verifications → api/verifications}/index.browser.js +5 -5
  36. package/dist/api/verifications/index.browser.js.map +1 -0
  37. package/dist/api/verifications/index.d.ts +248 -0
  38. package/dist/{api-verifications → api/verifications}/index.js +13 -12
  39. package/dist/api/verifications/index.js.map +1 -0
  40. package/dist/bin/index.js +1 -0
  41. package/dist/bin/index.js.map +1 -1
  42. package/dist/cache/{index.d.ts → core/index.d.ts} +4 -4
  43. package/dist/cache/{index.js → core/index.js} +5 -5
  44. package/dist/cache/core/index.js.map +1 -0
  45. package/dist/{cache-redis → cache/redis}/index.d.ts +2 -2
  46. package/dist/{cache-redis → cache/redis}/index.js +2 -2
  47. package/dist/cache/redis/index.js.map +1 -0
  48. package/dist/cli/index.d.ts +71 -9
  49. package/dist/cli/index.js +280 -79
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/command/index.d.ts +63 -2
  52. package/dist/command/index.js +30 -3
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js +241 -61
  55. package/dist/core/index.browser.js.map +1 -1
  56. package/dist/core/index.d.ts +170 -90
  57. package/dist/core/index.js +264 -67
  58. package/dist/core/index.js.map +1 -1
  59. package/dist/core/index.native.js +248 -65
  60. package/dist/core/index.native.js.map +1 -1
  61. package/dist/email/index.js +15 -10554
  62. package/dist/email/index.js.map +1 -1
  63. package/dist/lock/{index.d.ts → core/index.d.ts} +5 -5
  64. package/dist/lock/{index.js → core/index.js} +5 -5
  65. package/dist/lock/core/index.js.map +1 -0
  66. package/dist/{lock-redis → lock/redis}/index.d.ts +2 -2
  67. package/dist/{lock-redis → lock/redis}/index.js +2 -2
  68. package/dist/lock/redis/index.js.map +1 -0
  69. package/dist/logger/index.d.ts +4 -4
  70. package/dist/logger/index.js +77 -72
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/orm/index.d.ts +5 -1
  73. package/dist/orm/index.js +24 -7
  74. package/dist/orm/index.js.map +1 -1
  75. package/dist/queue/core/index.d.ts +548 -0
  76. package/dist/queue/core/index.js +391 -0
  77. package/dist/queue/core/index.js.map +1 -0
  78. package/dist/queue/redis/index.d.ts +28 -0
  79. package/dist/queue/redis/index.js +43 -0
  80. package/dist/queue/redis/index.js.map +1 -0
  81. package/dist/scheduler/index.d.ts +7 -7
  82. package/dist/scheduler/index.js +1 -393
  83. package/dist/scheduler/index.js.map +1 -1
  84. package/dist/security/index.d.ts +1 -1
  85. package/dist/security/index.js +2 -1413
  86. package/dist/security/index.js.map +1 -1
  87. package/dist/{server-auth → server/auth}/index.browser.js +6 -6
  88. package/dist/server/auth/index.browser.js.map +1 -0
  89. package/dist/{server-auth → server/auth}/index.d.ts +175 -164
  90. package/dist/server/auth/index.js +742 -0
  91. package/dist/server/auth/index.js.map +1 -0
  92. package/dist/{server-cache → server/cache}/index.d.ts +2 -2
  93. package/dist/{server-cache → server/cache}/index.js +2 -2
  94. package/dist/server/cache/index.js.map +1 -0
  95. package/dist/{server-compress → server/compress}/index.d.ts +2 -2
  96. package/dist/{server-compress → server/compress}/index.js +2 -2
  97. package/dist/server/compress/index.js.map +1 -0
  98. package/dist/{server-cookies → server/cookies}/index.browser.js +3 -3
  99. package/dist/server/cookies/index.browser.js.map +1 -0
  100. package/dist/{server-cookies → server/cookies}/index.d.ts +4 -4
  101. package/dist/{server-cookies → server/cookies}/index.js +9 -5
  102. package/dist/server/cookies/index.js.map +1 -0
  103. package/dist/server/{index.browser.js → core/index.browser.js} +14 -14
  104. package/dist/server/core/index.browser.js.map +1 -0
  105. package/dist/server/{index.d.ts → core/index.d.ts} +46 -37
  106. package/dist/server/{index.js → core/index.js} +47 -33
  107. package/dist/server/core/index.js.map +1 -0
  108. package/dist/{server-cors → server/cors}/index.d.ts +3 -3
  109. package/dist/{server-cors → server/cors}/index.js +3 -3
  110. package/dist/server/cors/index.js.map +1 -0
  111. package/dist/{server-health → server/health}/index.d.ts +3 -3
  112. package/dist/{server-health → server/health}/index.js +3 -3
  113. package/dist/server/health/index.js.map +1 -0
  114. package/dist/{server-helmet → server/helmet}/index.d.ts +2 -2
  115. package/dist/{server-helmet → server/helmet}/index.js +2 -2
  116. package/dist/server/helmet/index.js.map +1 -0
  117. package/dist/{server-links → server/links}/index.browser.js +5 -5
  118. package/dist/server/links/index.browser.js.map +1 -0
  119. package/dist/{server-links → server/links}/index.d.ts +40 -40
  120. package/dist/{server-links → server/links}/index.js +7 -7
  121. package/dist/server/links/index.js.map +1 -0
  122. package/dist/{server-metrics → server/metrics}/index.d.ts +2 -2
  123. package/dist/server/metrics/index.js +74 -0
  124. package/dist/server/metrics/index.js.map +1 -0
  125. package/dist/{server-multipart → server/multipart}/index.d.ts +2 -2
  126. package/dist/{server-multipart → server/multipart}/index.js +2 -2
  127. package/dist/server/multipart/index.js.map +1 -0
  128. package/dist/{server-proxy → server/proxy}/index.d.ts +3 -3
  129. package/dist/{server-proxy → server/proxy}/index.js +3 -3
  130. package/dist/server/proxy/index.js.map +1 -0
  131. package/dist/{server-rate-limit → server/rate-limit}/index.d.ts +4 -4
  132. package/dist/{server-rate-limit → server/rate-limit}/index.js +4 -4
  133. package/dist/server/rate-limit/index.js.map +1 -0
  134. package/dist/{server-security → server/security}/index.browser.js +1 -1
  135. package/dist/server/security/index.browser.js.map +1 -0
  136. package/dist/{server-security → server/security}/index.d.ts +4 -4
  137. package/dist/{server-security → server/security}/index.js +4 -4
  138. package/dist/server/security/index.js.map +1 -0
  139. package/dist/{server-static → server/static}/index.d.ts +3 -3
  140. package/dist/{server-static → server/static}/index.js +3 -3
  141. package/dist/server/static/index.js.map +1 -0
  142. package/dist/{server-swagger → server/swagger}/index.d.ts +3 -3
  143. package/dist/{server-swagger → server/swagger}/index.js +4 -4
  144. package/dist/server/swagger/index.js.map +1 -0
  145. package/dist/thread/index.js +2 -2
  146. package/dist/thread/index.js.map +1 -1
  147. package/dist/topic/{index.d.ts → core/index.d.ts} +6 -6
  148. package/dist/topic/{index.js → core/index.js} +6 -6
  149. package/dist/topic/core/index.js.map +1 -0
  150. package/dist/{topic-redis → topic/redis}/index.d.ts +2 -2
  151. package/dist/{topic-redis → topic/redis}/index.js +2 -2
  152. package/dist/topic/redis/index.js.map +1 -0
  153. package/dist/vite/index.d.ts +13 -2
  154. package/dist/vite/index.js +114 -50
  155. package/dist/vite/index.js.map +1 -1
  156. package/dist/websocket/index.browser.js +3 -3
  157. package/dist/websocket/index.browser.js.map +1 -1
  158. package/dist/websocket/index.js +4 -4
  159. package/dist/websocket/index.js.map +1 -1
  160. package/package.json +160 -156
  161. package/src/api/audits/controllers/AuditController.ts +186 -0
  162. package/src/api/audits/entities/audits.ts +132 -0
  163. package/src/api/audits/index.browser.ts +18 -0
  164. package/src/api/audits/index.ts +58 -0
  165. package/src/api/audits/primitives/$audit.ts +159 -0
  166. package/src/api/audits/schemas/auditQuerySchema.ts +23 -0
  167. package/src/api/audits/schemas/auditResourceSchema.ts +9 -0
  168. package/src/api/audits/schemas/createAuditSchema.ts +27 -0
  169. package/src/api/audits/services/AuditService.ts +412 -0
  170. package/src/{api-files → api/files}/index.ts +1 -0
  171. package/src/api/parameters/controllers/ConfigController.ts +324 -0
  172. package/src/api/parameters/entities/parameters.ts +113 -0
  173. package/src/api/parameters/index.ts +60 -0
  174. package/src/api/parameters/primitives/$config.ts +351 -0
  175. package/src/api/parameters/schedulers/ConfigActivationScheduler.ts +30 -0
  176. package/src/api/parameters/services/ConfigStore.ts +491 -0
  177. package/src/{api-users → api/users}/atoms/realmAuthSettingsAtom.ts +19 -0
  178. package/src/{api-users → api/users}/controllers/UserRealmController.ts +0 -2
  179. package/src/{api-users → api/users}/index.ts +2 -0
  180. package/src/{api-users → api/users}/primitives/$userRealm.ts +18 -3
  181. package/src/{api-users → api/users}/providers/UserRealmProvider.ts +12 -10
  182. package/src/{api-users → api/users}/services/RegistrationService.ts +2 -1
  183. package/src/{api-users → api/users}/services/SessionService.ts +4 -0
  184. package/src/{api-users → api/users}/services/UserService.ts +3 -0
  185. package/src/{api-verifications → api/verifications}/index.ts +9 -1
  186. package/src/bin/index.ts +1 -0
  187. package/src/cli/apps/AlephaPackageBuilderCli.ts +73 -48
  188. package/src/cli/assets/appRouterTs.ts +1 -1
  189. package/src/cli/assets/biomeJson.ts +2 -2
  190. package/src/cli/assets/dummySpecTs.ts +7 -0
  191. package/src/cli/assets/editorconfig.ts +13 -0
  192. package/src/cli/assets/indexHtml.ts +1 -1
  193. package/src/cli/assets/mainBrowserTs.ts +1 -1
  194. package/src/cli/assets/mainTs.ts +14 -0
  195. package/src/cli/assets/viteConfigTs.ts +1 -1
  196. package/src/cli/commands/BiomeCommands.ts +2 -0
  197. package/src/cli/commands/CoreCommands.ts +38 -15
  198. package/src/cli/commands/VerifyCommands.ts +6 -2
  199. package/src/cli/commands/ViteCommands.ts +28 -18
  200. package/src/cli/services/AlephaCliUtils.ts +243 -37
  201. package/src/command/helpers/Asker.ts +0 -1
  202. package/src/command/primitives/$command.ts +67 -0
  203. package/src/command/providers/CliProvider.ts +39 -8
  204. package/src/core/Alepha.ts +40 -30
  205. package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
  206. package/src/core/index.shared.ts +1 -0
  207. package/src/core/index.ts +30 -3
  208. package/src/core/providers/EventManager.ts +1 -1
  209. package/src/core/providers/SchemaValidator.ts +1 -1
  210. package/src/core/providers/StateManager.ts +23 -12
  211. package/src/core/providers/TypeProvider.ts +26 -34
  212. package/src/logger/index.ts +8 -6
  213. package/src/logger/primitives/$logger.ts +1 -1
  214. package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
  215. package/src/orm/index.ts +6 -0
  216. package/src/orm/services/PgRelationManager.ts +2 -2
  217. package/src/orm/services/PostgresModelBuilder.ts +11 -7
  218. package/src/orm/services/Repository.ts +16 -7
  219. package/src/orm/services/SqliteModelBuilder.ts +10 -0
  220. package/src/queue/{index.ts → core/index.ts} +2 -3
  221. package/src/queue/{primitives → core/primitives}/$queue.ts +17 -162
  222. package/src/queue/core/providers/MemoryQueueProvider.ts +19 -0
  223. package/src/queue/core/providers/QueueProvider.ts +23 -0
  224. package/src/queue/core/providers/WorkerProvider.ts +244 -0
  225. package/src/queue/redis/providers/RedisQueueProvider.ts +31 -0
  226. package/src/{server-auth → server/auth}/primitives/$auth.ts +7 -0
  227. package/src/{server-auth → server/auth}/providers/ServerAuthProvider.ts +51 -8
  228. package/src/{server-cookies → server/cookies}/index.ts +2 -1
  229. package/src/server/{index.ts → core/index.ts} +7 -0
  230. package/src/server/{primitives → core/primitives}/$action.ts +10 -1
  231. package/src/server/{providers → core/providers}/ServerBodyParserProvider.ts +11 -5
  232. package/src/server/{providers → core/providers}/ServerRouterProvider.ts +13 -7
  233. package/src/{server-rate-limit → server/rate-limit}/index.ts +1 -1
  234. package/src/{server-swagger → server/swagger}/providers/ServerSwaggerProvider.ts +1 -0
  235. package/src/thread/primitives/$thread.ts +2 -2
  236. package/src/vite/index.ts +0 -2
  237. package/src/vite/tasks/buildServer.ts +3 -4
  238. package/src/vite/tasks/copyAssets.ts +32 -8
  239. package/src/vite/tasks/generateCloudflare.ts +35 -19
  240. package/src/vite/tasks/generateDocker.ts +18 -4
  241. package/src/vite/tasks/generateSitemap.ts +5 -7
  242. package/src/vite/tasks/generateVercel.ts +76 -41
  243. package/src/vite/tasks/runAlepha.ts +16 -1
  244. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
  245. package/src/websocket/services/WebSocketClient.ts +3 -3
  246. package/dist/api-files/index.browser.js.map +0 -1
  247. package/dist/api-files/index.js.map +0 -1
  248. package/dist/api-jobs/index.browser.js.map +0 -1
  249. package/dist/api-jobs/index.js.map +0 -1
  250. package/dist/api-notifications/index.browser.js.map +0 -1
  251. package/dist/api-notifications/index.d.ts +0 -327
  252. package/dist/api-notifications/index.js.map +0 -1
  253. package/dist/api-parameters/index.browser.js +0 -29
  254. package/dist/api-parameters/index.browser.js.map +0 -1
  255. package/dist/api-parameters/index.d.ts +0 -83
  256. package/dist/api-parameters/index.js +0 -63
  257. package/dist/api-parameters/index.js.map +0 -1
  258. package/dist/api-users/index.browser.js.map +0 -1
  259. package/dist/api-users/index.js.map +0 -1
  260. package/dist/api-verifications/index.browser.js.map +0 -1
  261. package/dist/api-verifications/index.d.ts +0 -229
  262. package/dist/api-verifications/index.js.map +0 -1
  263. package/dist/cache/index.js.map +0 -1
  264. package/dist/cache-redis/index.js.map +0 -1
  265. package/dist/cli/dist-BlfFtOk2.js +0 -2770
  266. package/dist/cli/dist-BlfFtOk2.js.map +0 -1
  267. package/dist/lock/index.js.map +0 -1
  268. package/dist/lock-redis/index.js.map +0 -1
  269. package/dist/queue/index.d.ts +0 -1265
  270. package/dist/queue/index.js +0 -1037
  271. package/dist/queue/index.js.map +0 -1
  272. package/dist/queue-redis/index.d.ts +0 -82
  273. package/dist/queue-redis/index.js +0 -872
  274. package/dist/queue-redis/index.js.map +0 -1
  275. package/dist/server/index.browser.js.map +0 -1
  276. package/dist/server/index.js.map +0 -1
  277. package/dist/server-auth/index.browser.js.map +0 -1
  278. package/dist/server-auth/index.js +0 -1943
  279. package/dist/server-auth/index.js.map +0 -1
  280. package/dist/server-cache/index.js.map +0 -1
  281. package/dist/server-compress/index.js.map +0 -1
  282. package/dist/server-cookies/index.browser.js.map +0 -1
  283. package/dist/server-cookies/index.js.map +0 -1
  284. package/dist/server-cors/index.js.map +0 -1
  285. package/dist/server-health/index.js.map +0 -1
  286. package/dist/server-helmet/index.js.map +0 -1
  287. package/dist/server-links/index.browser.js.map +0 -1
  288. package/dist/server-links/index.js.map +0 -1
  289. package/dist/server-metrics/index.js +0 -4532
  290. package/dist/server-metrics/index.js.map +0 -1
  291. package/dist/server-multipart/index.js.map +0 -1
  292. package/dist/server-proxy/index.js.map +0 -1
  293. package/dist/server-rate-limit/index.js.map +0 -1
  294. package/dist/server-security/index.browser.js.map +0 -1
  295. package/dist/server-security/index.js.map +0 -1
  296. package/dist/server-static/index.js.map +0 -1
  297. package/dist/server-swagger/index.js.map +0 -1
  298. package/dist/topic/index.js.map +0 -1
  299. package/dist/topic-redis/index.js.map +0 -1
  300. package/src/api-parameters/controllers/ParameterController.ts +0 -45
  301. package/src/api-parameters/entities/parameters.ts +0 -30
  302. package/src/api-parameters/index.ts +0 -21
  303. package/src/api-parameters/primitives/$config.ts +0 -79
  304. package/src/api-parameters/services/ParameterStore.ts +0 -23
  305. package/src/queue/interfaces/QueueJob.ts +0 -459
  306. package/src/queue/providers/MemoryQueueProvider.ts +0 -850
  307. package/src/queue/providers/QueueProvider.ts +0 -319
  308. package/src/queue/providers/WorkerProvider.ts +0 -344
  309. package/src/queue-redis/providers/RedisQueueProvider.ts +0 -1209
  310. /package/src/{api-files → api/files}/controllers/FileController.ts +0 -0
  311. /package/src/{api-files → api/files}/controllers/StorageStatsController.ts +0 -0
  312. /package/src/{api-files → api/files}/entities/files.ts +0 -0
  313. /package/src/{api-files → api/files}/index.browser.ts +0 -0
  314. /package/src/{api-files → api/files}/jobs/FileJobs.ts +0 -0
  315. /package/src/{api-files → api/files}/schemas/fileQuerySchema.ts +0 -0
  316. /package/src/{api-files → api/files}/schemas/fileResourceSchema.ts +0 -0
  317. /package/src/{api-files → api/files}/schemas/storageStatsSchema.ts +0 -0
  318. /package/src/{api-files → api/files}/services/FileService.ts +0 -0
  319. /package/src/{api-jobs → api/jobs}/controllers/JobController.ts +0 -0
  320. /package/src/{api-jobs → api/jobs}/entities/jobExecutions.ts +0 -0
  321. /package/src/{api-jobs → api/jobs}/index.browser.ts +0 -0
  322. /package/src/{api-jobs → api/jobs}/index.ts +0 -0
  323. /package/src/{api-jobs → api/jobs}/primitives/$job.ts +0 -0
  324. /package/src/{api-jobs → api/jobs}/providers/JobProvider.ts +0 -0
  325. /package/src/{api-jobs → api/jobs}/schemas/jobExecutionQuerySchema.ts +0 -0
  326. /package/src/{api-jobs → api/jobs}/schemas/jobExecutionResourceSchema.ts +0 -0
  327. /package/src/{api-jobs → api/jobs}/schemas/triggerJobSchema.ts +0 -0
  328. /package/src/{api-jobs → api/jobs}/services/JobService.ts +0 -0
  329. /package/src/{api-notifications → api/notifications}/controllers/NotificationController.ts +0 -0
  330. /package/src/{api-notifications → api/notifications}/entities/notifications.ts +0 -0
  331. /package/src/{api-notifications → api/notifications}/index.browser.ts +0 -0
  332. /package/src/{api-notifications → api/notifications}/index.ts +0 -0
  333. /package/src/{api-notifications → api/notifications}/jobs/NotificationJobs.ts +0 -0
  334. /package/src/{api-notifications → api/notifications}/primitives/$notification.ts +0 -0
  335. /package/src/{api-notifications → api/notifications}/queues/NotificationQueues.ts +0 -0
  336. /package/src/{api-notifications → api/notifications}/schemas/notificationContactPreferencesSchema.ts +0 -0
  337. /package/src/{api-notifications → api/notifications}/schemas/notificationContactSchema.ts +0 -0
  338. /package/src/{api-notifications → api/notifications}/schemas/notificationCreateSchema.ts +0 -0
  339. /package/src/{api-notifications → api/notifications}/schemas/notificationQuerySchema.ts +0 -0
  340. /package/src/{api-notifications → api/notifications}/services/NotificationSenderService.ts +0 -0
  341. /package/src/{api-notifications → api/notifications}/services/NotificationService.ts +0 -0
  342. /package/src/{api-parameters → api/parameters}/index.browser.ts +0 -0
  343. /package/src/{api-users → api/users}/controllers/IdentityController.ts +0 -0
  344. /package/src/{api-users → api/users}/controllers/SessionController.ts +0 -0
  345. /package/src/{api-users → api/users}/controllers/UserController.ts +0 -0
  346. /package/src/{api-users → api/users}/entities/identities.ts +0 -0
  347. /package/src/{api-users → api/users}/entities/sessions.ts +0 -0
  348. /package/src/{api-users → api/users}/entities/users.ts +0 -0
  349. /package/src/{api-users → api/users}/index.browser.ts +0 -0
  350. /package/src/{api-users → api/users}/notifications/UserNotifications.ts +0 -0
  351. /package/src/{api-users → api/users}/schemas/completePasswordResetRequestSchema.ts +0 -0
  352. /package/src/{api-users → api/users}/schemas/completeRegistrationRequestSchema.ts +0 -0
  353. /package/src/{api-users → api/users}/schemas/createUserSchema.ts +0 -0
  354. /package/src/{api-users → api/users}/schemas/identityQuerySchema.ts +0 -0
  355. /package/src/{api-users → api/users}/schemas/identityResourceSchema.ts +0 -0
  356. /package/src/{api-users → api/users}/schemas/loginSchema.ts +0 -0
  357. /package/src/{api-users → api/users}/schemas/passwordResetIntentResponseSchema.ts +0 -0
  358. /package/src/{api-users → api/users}/schemas/registerQuerySchema.ts +0 -0
  359. /package/src/{api-users → api/users}/schemas/registerRequestSchema.ts +0 -0
  360. /package/src/{api-users → api/users}/schemas/registerResponseSchema.ts +0 -0
  361. /package/src/{api-users → api/users}/schemas/registerSchema.ts +0 -0
  362. /package/src/{api-users → api/users}/schemas/registrationIntentResponseSchema.ts +0 -0
  363. /package/src/{api-users → api/users}/schemas/resetPasswordSchema.ts +0 -0
  364. /package/src/{api-users → api/users}/schemas/sessionQuerySchema.ts +0 -0
  365. /package/src/{api-users → api/users}/schemas/sessionResourceSchema.ts +0 -0
  366. /package/src/{api-users → api/users}/schemas/updateUserSchema.ts +0 -0
  367. /package/src/{api-users → api/users}/schemas/userQuerySchema.ts +0 -0
  368. /package/src/{api-users → api/users}/schemas/userRealmConfigSchema.ts +0 -0
  369. /package/src/{api-users → api/users}/schemas/userResourceSchema.ts +0 -0
  370. /package/src/{api-users → api/users}/services/CredentialService.ts +0 -0
  371. /package/src/{api-users → api/users}/services/IdentityService.ts +0 -0
  372. /package/src/{api-users → api/users}/services/SessionCrudService.ts +0 -0
  373. /package/src/{api-verifications → api/verifications}/controllers/VerificationController.ts +0 -0
  374. /package/src/{api-verifications → api/verifications}/entities/verifications.ts +0 -0
  375. /package/src/{api-verifications → api/verifications}/index.browser.ts +0 -0
  376. /package/src/{api-verifications → api/verifications}/jobs/VerificationJobs.ts +0 -0
  377. /package/src/{api-verifications → api/verifications}/parameters/VerificationParameters.ts +0 -0
  378. /package/src/{api-verifications → api/verifications}/schemas/requestVerificationCodeResponseSchema.ts +0 -0
  379. /package/src/{api-verifications → api/verifications}/schemas/validateVerificationCodeResponseSchema.ts +0 -0
  380. /package/src/{api-verifications → api/verifications}/schemas/verificationSettingsSchema.ts +0 -0
  381. /package/src/{api-verifications → api/verifications}/schemas/verificationTypeEnumSchema.ts +0 -0
  382. /package/src/{api-verifications → api/verifications}/services/VerificationService.ts +0 -0
  383. /package/src/cache/{errors → core/errors}/CacheError.ts +0 -0
  384. /package/src/cache/{index.ts → core/index.ts} +0 -0
  385. /package/src/cache/{primitives → core/primitives}/$cache.ts +0 -0
  386. /package/src/cache/{providers → core/providers}/CacheProvider.ts +0 -0
  387. /package/src/cache/{providers → core/providers}/MemoryCacheProvider.ts +0 -0
  388. /package/src/{cache-redis → cache/redis}/index.ts +0 -0
  389. /package/src/{cache-redis → cache/redis}/providers/RedisCacheProvider.ts +0 -0
  390. /package/src/lock/{index.ts → core/index.ts} +0 -0
  391. /package/src/lock/{primitives → core/primitives}/$lock.ts +0 -0
  392. /package/src/lock/{providers → core/providers}/LockProvider.ts +0 -0
  393. /package/src/lock/{providers → core/providers}/LockTopicProvider.ts +0 -0
  394. /package/src/lock/{providers → core/providers}/MemoryLockProvider.ts +0 -0
  395. /package/src/{lock-redis → lock/redis}/index.ts +0 -0
  396. /package/src/{lock-redis → lock/redis}/providers/RedisLockProvider.ts +0 -0
  397. /package/src/queue/{primitives → core/primitives}/$consumer.ts +0 -0
  398. /package/src/{queue-redis → queue/redis}/index.ts +0 -0
  399. /package/src/{server-auth → server/auth}/constants/routes.ts +0 -0
  400. /package/src/{server-auth → server/auth}/index.browser.ts +0 -0
  401. /package/src/{server-auth → server/auth}/index.shared.ts +0 -0
  402. /package/src/{server-auth → server/auth}/index.ts +0 -0
  403. /package/src/{server-auth → server/auth}/primitives/$authApple.ts +0 -0
  404. /package/src/{server-auth → server/auth}/primitives/$authCredentials.ts +0 -0
  405. /package/src/{server-auth → server/auth}/primitives/$authGithub.ts +0 -0
  406. /package/src/{server-auth → server/auth}/primitives/$authGoogle.ts +0 -0
  407. /package/src/{server-auth → server/auth}/schemas/authenticationProviderSchema.ts +0 -0
  408. /package/src/{server-auth → server/auth}/schemas/tokenResponseSchema.ts +0 -0
  409. /package/src/{server-auth → server/auth}/schemas/tokensSchema.ts +0 -0
  410. /package/src/{server-auth → server/auth}/schemas/userinfoResponseSchema.ts +0 -0
  411. /package/src/{server-cache → server/cache}/index.ts +0 -0
  412. /package/src/{server-cache → server/cache}/providers/ServerCacheProvider.ts +0 -0
  413. /package/src/{server-compress → server/compress}/index.ts +0 -0
  414. /package/src/{server-compress → server/compress}/providers/ServerCompressProvider.ts +0 -0
  415. /package/src/{server-cookies → server/cookies}/index.browser.ts +0 -0
  416. /package/src/{server-cookies → server/cookies}/primitives/$cookie.browser.ts +0 -0
  417. /package/src/{server-cookies → server/cookies}/primitives/$cookie.ts +0 -0
  418. /package/src/{server-cookies → server/cookies}/providers/ServerCookiesProvider.ts +0 -0
  419. /package/src/{server-cookies → server/cookies}/services/CookieParser.ts +0 -0
  420. /package/src/server/{constants → core/constants}/routeMethods.ts +0 -0
  421. /package/src/server/{errors → core/errors}/BadRequestError.ts +0 -0
  422. /package/src/server/{errors → core/errors}/ConflictError.ts +0 -0
  423. /package/src/server/{errors → core/errors}/ForbiddenError.ts +0 -0
  424. /package/src/server/{errors → core/errors}/HttpError.ts +0 -0
  425. /package/src/server/{errors → core/errors}/NotFoundError.ts +0 -0
  426. /package/src/server/{errors → core/errors}/UnauthorizedError.ts +0 -0
  427. /package/src/server/{errors → core/errors}/ValidationError.ts +0 -0
  428. /package/src/server/{helpers → core/helpers}/ServerReply.ts +0 -0
  429. /package/src/server/{helpers → core/helpers}/isMultipart.ts +0 -0
  430. /package/src/server/{index.browser.ts → core/index.browser.ts} +0 -0
  431. /package/src/server/{index.shared.ts → core/index.shared.ts} +0 -0
  432. /package/src/server/{interfaces → core/interfaces}/ServerRequest.ts +0 -0
  433. /package/src/server/{primitives → core/primitives}/$route.ts +0 -0
  434. /package/src/server/{providers → core/providers}/BunHttpServerProvider.ts +0 -0
  435. /package/src/server/{providers → core/providers}/NodeHttpServerProvider.ts +0 -0
  436. /package/src/server/{providers → core/providers}/ServerLoggerProvider.ts +0 -0
  437. /package/src/server/{providers → core/providers}/ServerNotReadyProvider.ts +0 -0
  438. /package/src/server/{providers → core/providers}/ServerProvider.ts +0 -0
  439. /package/src/server/{providers → core/providers}/ServerTimingProvider.ts +0 -0
  440. /package/src/server/{schemas → core/schemas}/errorSchema.ts +0 -0
  441. /package/src/server/{schemas → core/schemas}/okSchema.ts +0 -0
  442. /package/src/server/{services → core/services}/HttpClient.ts +0 -0
  443. /package/src/server/{services → core/services}/ServerRequestParser.ts +0 -0
  444. /package/src/server/{services → core/services}/UserAgentParser.ts +0 -0
  445. /package/src/{server-cors → server/cors}/index.ts +0 -0
  446. /package/src/{server-cors → server/cors}/primitives/$cors.ts +0 -0
  447. /package/src/{server-cors → server/cors}/providers/ServerCorsProvider.ts +0 -0
  448. /package/src/{server-health → server/health}/index.ts +0 -0
  449. /package/src/{server-health → server/health}/providers/ServerHealthProvider.ts +0 -0
  450. /package/src/{server-health → server/health}/schemas/healthSchema.ts +0 -0
  451. /package/src/{server-helmet → server/helmet}/index.ts +0 -0
  452. /package/src/{server-helmet → server/helmet}/providers/ServerHelmetProvider.ts +0 -0
  453. /package/src/{server-links → server/links}/index.browser.ts +0 -0
  454. /package/src/{server-links → server/links}/index.ts +0 -0
  455. /package/src/{server-links → server/links}/primitives/$client.ts +0 -0
  456. /package/src/{server-links → server/links}/primitives/$remote.ts +0 -0
  457. /package/src/{server-links → server/links}/providers/LinkProvider.ts +0 -0
  458. /package/src/{server-links → server/links}/providers/RemotePrimitiveProvider.ts +0 -0
  459. /package/src/{server-links → server/links}/providers/ServerLinksProvider.ts +0 -0
  460. /package/src/{server-links → server/links}/schemas/apiLinksResponseSchema.ts +0 -0
  461. /package/src/{server-metrics → server/metrics}/index.ts +0 -0
  462. /package/src/{server-metrics → server/metrics}/providers/ServerMetricsProvider.ts +0 -0
  463. /package/src/{server-multipart → server/multipart}/index.ts +0 -0
  464. /package/src/{server-multipart → server/multipart}/providers/ServerMultipartProvider.ts +0 -0
  465. /package/src/{server-proxy → server/proxy}/index.ts +0 -0
  466. /package/src/{server-proxy → server/proxy}/primitives/$proxy.ts +0 -0
  467. /package/src/{server-proxy → server/proxy}/providers/ServerProxyProvider.ts +0 -0
  468. /package/src/{server-rate-limit → server/rate-limit}/primitives/$rateLimit.ts +0 -0
  469. /package/src/{server-rate-limit → server/rate-limit}/providers/ServerRateLimitProvider.ts +0 -0
  470. /package/src/{server-security → server/security}/index.browser.ts +0 -0
  471. /package/src/{server-security → server/security}/index.ts +0 -0
  472. /package/src/{server-security → server/security}/primitives/$basicAuth.ts +0 -0
  473. /package/src/{server-security → server/security}/providers/ServerBasicAuthProvider.ts +0 -0
  474. /package/src/{server-security → server/security}/providers/ServerSecurityProvider.ts +0 -0
  475. /package/src/{server-static → server/static}/index.ts +0 -0
  476. /package/src/{server-static → server/static}/primitives/$serve.ts +0 -0
  477. /package/src/{server-static → server/static}/providers/ServerStaticProvider.ts +0 -0
  478. /package/src/{server-swagger → server/swagger}/index.ts +0 -0
  479. /package/src/{server-swagger → server/swagger}/primitives/$swagger.ts +0 -0
  480. /package/src/topic/{errors → core/errors}/TopicTimeoutError.ts +0 -0
  481. /package/src/topic/{index.ts → core/index.ts} +0 -0
  482. /package/src/topic/{primitives → core/primitives}/$subscriber.ts +0 -0
  483. /package/src/topic/{primitives → core/primitives}/$topic.ts +0 -0
  484. /package/src/topic/{providers → core/providers}/MemoryTopicProvider.ts +0 -0
  485. /package/src/topic/{providers → core/providers}/TopicProvider.ts +0 -0
  486. /package/src/{topic-redis → topic/redis}/index.ts +0 -0
  487. /package/src/{topic-redis → topic/redis}/providers/RedisTopicProvider.ts +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["webRequest: Request | undefined","WebStream","formData: FormData","body: Record<string, any>","tempFiles: HybridFile[]"],"sources":["../../src/server-multipart/providers/ServerMultipartProvider.ts","../../src/server-multipart/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { readFile, unlink, writeFile } from \"node:fs/promises\";\nimport * as os from \"node:os\";\nimport { ReadableStream as WebStream } from \"node:stream/web\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n type FileLike,\n isTypeFile,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { HttpError, isMultipart, type ServerRoute } from \"alepha/server\";\n\nconst envSchema = t.object({\n SERVER_MULTIPART_LIMIT: t.integer({\n default: 10_000_000, // 10MB total\n min: 0,\n description: \"Maximum total size of multipart request body in bytes.\",\n }),\n SERVER_MULTIPART_FILE_LIMIT: t.integer({\n default: 5_000_000, // 5MB per file\n min: 0,\n description: \"Maximum size of a single file in bytes.\",\n }),\n SERVER_MULTIPART_FILE_COUNT: t.integer({\n default: 10,\n min: 1,\n description: \"Maximum number of files allowed in a single request.\",\n }),\n});\n\nexport class ServerMultipartProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n // already parsed (e.g. by body parser)\n if (request.body) {\n return;\n }\n\n // we do not parse body if no schema\n if (!route.schema?.body) {\n return;\n }\n\n let webRequest: Request | undefined;\n\n if (request.raw.web?.req) {\n webRequest = request.raw.web.req;\n } else if (request.raw.node?.req) {\n webRequest = new Request(request.url, {\n method: request.method,\n headers: request.headers,\n body: WebStream.from(request.raw.node.req) as ReadableStream,\n duplex: \"half\",\n } as RequestInit & { duplex: \"half\" });\n }\n\n if (!webRequest) {\n return;\n }\n\n const contentType = request.headers[\"content-type\"];\n\n // Check content-length before processing to fail fast on oversized requests\n const contentLength = request.headers[\"content-length\"];\n if (contentLength) {\n const size = Number.parseInt(contentLength, 10);\n if (!Number.isNaN(size) && size > this.env.SERVER_MULTIPART_LIMIT) {\n this.log.error(\n `Multipart request size limit exceeded: ${size} > ${this.env.SERVER_MULTIPART_LIMIT}`,\n );\n throw new HttpError({\n status: 413,\n message: `Request body size limit exceeded. Maximum allowed: ${this.env.SERVER_MULTIPART_LIMIT} bytes`,\n });\n }\n }\n\n if (!contentType?.startsWith(\"multipart/form-data\")) {\n if (!isMultipart(route)) {\n return;\n }\n\n // route expects multipart but content-type is not correct! reject with 415\n throw new HttpError({\n status: 415,\n message: `Invalid content-type: ${contentType} - only \"multipart/form-data\" is accepted`,\n });\n }\n\n const { body, cleanup } = await this.handleMultipartBodyFromWeb(\n route,\n webRequest,\n );\n\n request.body = body;\n request.metadata.multipart = { cleanup };\n },\n });\n\n public readonly onResponse = $hook({\n on: \"server:onResponse\",\n handler: async ({ request }) => {\n const cleanup = request.metadata.multipart?.cleanup;\n if (typeof cleanup === \"function\") {\n await cleanup();\n }\n },\n });\n\n public async handleMultipartBodyFromWeb(\n route: ServerRoute,\n request: Request,\n ): Promise<{\n body: Record<string, unknown>;\n cleanup: () => Promise<void>;\n }> {\n let formData: FormData;\n\n try {\n // Parse the FormData from the request\n formData = await request.formData();\n } catch (error) {\n throw new HttpError(\n {\n status: 400,\n message: \"Malformed multipart/form-data\",\n },\n error,\n );\n }\n\n const body: Record<string, any> = {};\n const tempFiles: HybridFile[] = [];\n\n // Helper to clean up temp files on error\n const cleanupOnError = async () => {\n for (const file of tempFiles) {\n try {\n await file.cleanup();\n } catch {\n // Ignore cleanup errors during error handling\n }\n }\n };\n\n try {\n let fileCount = 0;\n let totalSize = 0;\n\n if (route.schema?.body && t.schema.isObject(route.schema.body)) {\n for (const [key, value] of Object.entries(\n route.schema.body.properties,\n )) {\n if (t.schema.isSchema(value)) {\n if (isTypeFile(value)) {\n const file = formData.get(key);\n // Check if file is a Blob (File extends Blob in Web APIs)\n if (file && typeof file === \"object\" && \"arrayBuffer\" in file) {\n const blob = file as Blob;\n\n // Validate file count\n fileCount++;\n if (fileCount > this.env.SERVER_MULTIPART_FILE_COUNT) {\n this.log.error(\n `Too many files in multipart request: ${fileCount} > ${this.env.SERVER_MULTIPART_FILE_COUNT}`,\n );\n throw new HttpError({\n status: 413,\n message: `Too many files. Maximum allowed: ${this.env.SERVER_MULTIPART_FILE_COUNT}`,\n });\n }\n\n // Validate individual file size\n if (blob.size > this.env.SERVER_MULTIPART_FILE_LIMIT) {\n this.log.error(\n `File \"${key}\" exceeds size limit: ${blob.size} > ${this.env.SERVER_MULTIPART_FILE_LIMIT}`,\n );\n throw new HttpError({\n status: 413,\n message: `File \"${key}\" exceeds size limit. Maximum allowed: ${this.env.SERVER_MULTIPART_FILE_LIMIT} bytes`,\n });\n }\n\n // Validate total size\n totalSize += blob.size;\n if (totalSize > this.env.SERVER_MULTIPART_LIMIT) {\n this.log.error(\n `Total multipart size exceeds limit: ${totalSize} > ${this.env.SERVER_MULTIPART_LIMIT}`,\n );\n throw new HttpError({\n status: 413,\n message: `Total request size exceeds limit. Maximum allowed: ${this.env.SERVER_MULTIPART_LIMIT} bytes`,\n });\n }\n\n const hybridFile = await this.createHybridFile(blob, key);\n body[key] = hybridFile;\n tempFiles.push(hybridFile);\n }\n } else {\n const fieldValue = formData.get(key);\n if (fieldValue !== null) {\n // FormData values are either string or File/Blob\n const stringValue =\n typeof fieldValue === \"string\" ? fieldValue : \"\";\n body[key] = this.alepha.codec.decode(value, stringValue);\n }\n }\n }\n }\n }\n\n return {\n body,\n cleanup: async () => {\n for (const file of tempFiles) {\n await file.cleanup();\n }\n },\n };\n } catch (error) {\n // Clean up any temp files that were created before the error\n await cleanupOnError();\n throw error;\n }\n }\n\n /**\n * This is a legacy code, previously we used \"busboy\" to parse multipart in Node.js environment.\n * Now we rely on Web Request's formData() method, which is supported in modern Node.js versions.\n * However, we still need to create temporary files for uploaded files to provide a consistent File-like interface.\n *\n * TODO: In future, we might want to refactor this to avoid using temporary files if not necessary?\n */\n protected async createHybridFile(\n file: Blob,\n fieldName: string,\n ): Promise<HybridFile> {\n const tmpPath = `${os.tmpdir()}/${randomUUID()}`;\n\n // Get file data\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Write to temp file\n await writeFile(tmpPath, buffer);\n\n // Get file name - check if it has name property (File type)\n const fileName = (file as any).name || `${fieldName}_${Date.now()}`;\n\n const hybridFile: HybridFile = {\n _state: {\n cleanup: false,\n size: file.size,\n tmpPath,\n },\n name: fileName,\n type: file.type || \"application/octet-stream\",\n lastModified: (file as any).lastModified || Date.now(),\n filepath: tmpPath,\n get size() {\n return this._state.size;\n },\n stream() {\n return createReadStream(tmpPath);\n },\n async arrayBuffer() {\n const content = await readFile(tmpPath);\n return content.buffer.slice(\n content.byteOffset,\n content.byteOffset + content.byteLength,\n ) as ArrayBuffer;\n },\n text: async () => {\n return await readFile(tmpPath, \"utf-8\");\n },\n async cleanup() {\n if (this._state.cleanup) {\n return;\n }\n\n await unlink(tmpPath); // clean up the temp file\n this._state.cleanup = true;\n },\n };\n\n return hybridFile;\n }\n}\n\ninterface HybridFile extends FileLike {\n cleanup(): Promise<void>;\n _state: {\n cleanup: boolean;\n size: number;\n tmpPath: string;\n };\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { ServerMultipartProvider } from \"./providers/ServerMultipartProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/ServerMultipartProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * This module provides support for handling multipart/form-data requests.\n * It allows to parse body data containing t.file().\n *\n * @see {@link ServerMultipartProvider}\n * @module alepha.server.multipart\n */\nexport const AlephaServerMultipart = $module({\n name: \"alepha.server.multipart\",\n services: [AlephaServer, ServerMultipartProvider],\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["WebStream"],"sources":["../../src/server-proxy/primitives/$proxy.ts","../../src/server-proxy/providers/ServerProxyProvider.ts","../../src/server-proxy/index.ts"],"sourcesContent":["import { type Async, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\n\n/**\n * Creates a proxy primitive to forward requests to another server.\n *\n * This primitive enables you to create reverse proxy functionality, allowing your Alepha server\n * to forward requests to other services while maintaining a unified API surface. It's particularly\n * useful for microservice architectures, API gateways, or when you need to aggregate multiple\n * services behind a single endpoint.\n *\n * **Key Features**\n *\n * - **Path-based routing**: Match specific paths or patterns to proxy\n * - **Dynamic targets**: Support both static and dynamic target resolution\n * - **Request/Response hooks**: Modify requests before forwarding and responses after receiving\n * - **URL rewriting**: Transform URLs before forwarding to the target\n * - **Conditional proxying**: Enable/disable proxies based on environment or conditions\n *\n * @example\n * **Basic proxy setup:**\n * ```ts\n * import { $proxy } from \"alepha/server/proxy\";\n *\n * class ApiGateway {\n * // Forward all /api/* requests to external service\n * api = $proxy({\n * path: \"/api/*\",\n * target: \"https://api.example.com\"\n * });\n * }\n * ```\n *\n * @example\n * **Dynamic target with environment-based routing:**\n * ```ts\n * class ApiGateway {\n * // Route to different environments based on configuration\n * api = $proxy({\n * path: \"/api/*\",\n * target: () => process.env.NODE_ENV === \"production\"\n * ? \"https://api.prod.example.com\"\n * : \"https://api.dev.example.com\"\n * });\n * }\n * ```\n *\n * @example\n * **Advanced proxy with request/response modification:**\n * ```ts\n * class SecureProxy {\n * secure = $proxy({\n * path: \"/secure/*\",\n * target: \"https://secure-api.example.com\",\n * beforeRequest: async (request, proxyRequest) => {\n * // Add authentication headers\n * proxyRequest.headers = {\n * ...proxyRequest.headers,\n * 'Authorization': `Bearer ${await getServiceToken()}`,\n * 'X-Forwarded-For': request.headers['x-forwarded-for'] || request.ip\n * };\n * },\n * afterResponse: async (request, proxyResponse) => {\n * // Log response for monitoring\n * console.log(`Proxied ${request.url} -> ${proxyResponse.status}`);\n * },\n * rewrite: (url) => {\n * // Remove /secure prefix when forwarding\n * url.pathname = url.pathname.replace('/secure', '');\n * }\n * });\n * }\n * ```\n *\n * @example\n * **Conditional proxy based on feature flags:**\n * ```ts\n * class FeatureProxy {\n * newApi = $proxy({\n * path: \"/v2/*\",\n * target: \"https://new-api.example.com\",\n * disabled: !process.env.ENABLE_V2_API // Disable if feature flag is off\n * });\n * }\n * ```\n */\nexport const $proxy = (options: ProxyPrimitiveOptions): ProxyPrimitive => {\n return createPrimitive(ProxyPrimitive, options);\n};\n\nexport type ProxyPrimitiveOptions = {\n /**\n * Path pattern to match for proxying requests.\n *\n * Supports wildcards and path parameters:\n * - `/api/*` - Matches all paths starting with `/api/`\n * - `/api/v1/*` - Matches all paths starting with `/api/v1/`\n * - `/users/:id` - Matches `/users/123`, `/users/abc`, etc.\n *\n * @example \"/api/*\"\n * @example \"/secure/admin/*\"\n * @example \"/users/:id/posts\"\n */\n path: string;\n\n /**\n * Target URL to which matching requests should be forwarded.\n *\n * Can be either:\n * - **Static string**: A fixed URL like `\"https://api.example.com\"`\n * - **Dynamic function**: A function that returns the URL, enabling runtime target resolution\n *\n * The target URL will be combined with the remaining path from the original request.\n *\n * @example \"https://api.example.com\"\n * @example () => process.env.API_URL || \"http://localhost:3001\"\n */\n target: string | (() => string);\n\n /**\n * Whether this proxy is disabled.\n *\n * When `true`, requests matching the path will not be proxied and will be handled\n * by other routes or return 404. Useful for feature toggles or conditional proxying.\n *\n * @default false\n * @example !process.env.ENABLE_PROXY\n */\n disabled?: boolean;\n\n /**\n * Hook called before forwarding the request to the target server.\n *\n * Use this to:\n * - Add authentication headers\n * - Modify request headers or body\n * - Add request tracking/logging\n * - Transform the request before forwarding\n *\n * @param request - The original incoming server request\n * @param proxyRequest - The request that will be sent to the target (modifiable)\n *\n * @example\n * ```ts\n * beforeRequest: async (request, proxyRequest) => {\n * proxyRequest.headers = {\n * ...proxyRequest.headers,\n * 'Authorization': `Bearer ${await getToken()}`,\n * 'X-Request-ID': generateRequestId()\n * };\n * }\n * ```\n */\n beforeRequest?: (\n request: ServerRequest,\n proxyRequest: RequestInit,\n ) => Async<void>;\n\n /**\n * Hook called after receiving the response from the target server.\n *\n * Use this to:\n * - Log response details for monitoring\n * - Add custom headers to the response\n * - Transform response data\n * - Handle error responses\n *\n * @param request - The original incoming server request\n * @param proxyResponse - The response received from the target server\n *\n * @example\n * ```ts\n * afterResponse: async (request, proxyResponse) => {\n * console.log(`Proxy ${request.method} ${request.url} -> ${proxyResponse.status}`);\n *\n * if (!proxyResponse.ok) {\n * await logError(`Proxy error: ${proxyResponse.status}`, { request, response: proxyResponse });\n * }\n * }\n * ```\n */\n afterResponse?: (\n request: ServerRequest,\n proxyResponse: Response,\n ) => Async<void>;\n\n /**\n * Function to rewrite the URL before sending to the target server.\n *\n * Use this to:\n * - Remove or add path prefixes\n * - Transform path parameters\n * - Modify query parameters\n * - Change the URL structure entirely\n *\n * The function receives a mutable URL object and should modify it in-place.\n *\n * @param url - The URL object to modify (mutable)\n *\n * @example\n * ```ts\n * // Remove /api prefix when forwarding\n * rewrite: (url) => {\n * url.pathname = url.pathname.replace('/api', '');\n * }\n * ```\n *\n * @example\n * ```ts\n * // Add version prefix\n * rewrite: (url) => {\n * url.pathname = `/v2${url.pathname}`;\n * }\n * ```\n */\n rewrite?: (url: URL) => void;\n\n // TODO: Add retry functionality\n // retry?: RetryOptions;\n};\n\nexport class ProxyPrimitive extends Primitive<ProxyPrimitiveOptions> {}\n\n$proxy[KIND] = ProxyPrimitive;\n","import { ReadableStream as WebStream } from \"node:stream/web\";\nimport { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n routeMethods,\n type ServerHandler,\n type ServerRequest,\n ServerRouterProvider,\n} from \"alepha/server\";\nimport { $proxy, type ProxyPrimitiveOptions } from \"../primitives/$proxy.ts\";\n\nexport class ServerProxyProvider {\n protected readonly log = $logger();\n protected readonly routerProvider = $inject(ServerRouterProvider);\n protected readonly alepha = $inject(Alepha);\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: () => {\n for (const proxy of this.alepha.primitives($proxy)) {\n this.createProxy(proxy.options);\n }\n },\n });\n\n public createProxy(options: ProxyPrimitiveOptions): void {\n if (options.disabled) {\n return;\n }\n\n const path = options.path;\n const target =\n typeof options.target === \"function\" ? options.target() : options.target;\n\n if (!path.endsWith(\"/*\")) {\n throw new AlephaError(\"Proxy path should end with '/*'\");\n }\n\n // Extract base path without /*\n const handler = this.createProxyHandler(target, options);\n\n for (const method of routeMethods) {\n this.routerProvider.createRoute({\n method,\n path,\n handler,\n });\n }\n\n this.log.info(\"Proxying\", { path, target });\n }\n\n public createProxyHandler(\n target: string,\n options: Omit<ProxyPrimitiveOptions, \"path\">,\n ): ServerHandler {\n return async (request) => {\n const url = new URL(target + request.url.pathname);\n if (request.url.search) {\n url.search = request.url.search;\n }\n\n options.rewrite?.(url);\n\n const requestInit = {\n url: url.toString(),\n method: request.method,\n headers: {\n ...request.headers,\n \"accept-encoding\": \"identity\", // ignore compression\n },\n body: this.getRawRequestBody(request),\n };\n\n if (requestInit.body) {\n (requestInit as any).duplex = \"half\";\n }\n\n if (options.beforeRequest) {\n await options.beforeRequest(request, requestInit);\n }\n\n this.log.debug(\"Proxying request\", {\n url: url.toString(),\n method: request.method,\n headers: request.headers,\n });\n\n const response = await fetch(requestInit.url, requestInit);\n\n request.reply.status = response.status;\n request.reply.headers = Object.fromEntries(response.headers.entries());\n request.reply.body = response.body;\n\n this.log.debug(\"Received response\", {\n status: request.reply.status,\n headers: request.reply.headers,\n });\n\n if (options.afterResponse) {\n await options.afterResponse(request, response);\n }\n };\n }\n\n private getRawRequestBody(req: ServerRequest): ReadableStream | undefined {\n const { method } = req;\n\n if (method === \"GET\" || method === \"HEAD\" || method === \"OPTIONS\") {\n return;\n }\n\n if (req.raw?.web?.req) {\n return req.raw.web.req.body as ReadableStream;\n }\n\n if (req.raw?.node?.req) {\n const nodeReq = req.raw.node.req;\n return WebStream.from(nodeReq) as ReadableStream;\n }\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $proxy } from \"./primitives/$proxy.ts\";\nimport { ServerProxyProvider } from \"./providers/ServerProxyProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$proxy.ts\";\nexport * from \"./providers/ServerProxyProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha that provides a proxy server functionality.\n *\n * @see {@link $proxy}\n * @module alepha.server.proxy\n */\nexport const AlephaServerProxy = $module({\n name: \"alepha.server.proxy\",\n primitives: [$proxy],\n services: [AlephaServer, ServerProxyProvider],\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,MAAa,UAAU,YAAmD;AACxE,QAAO,gBAAgB,gBAAgB,QAAQ;;AAsIjD,IAAa,iBAAb,cAAoC,UAAiC;AAErE,OAAO,QAAQ;;;;ACpNf,IAAa,sBAAb,MAAiC;CAC/B,AAAmB,MAAM,SAAS;CAClC,AAAmB,iBAAiB,QAAQ,qBAAqB;CACjE,AAAmB,SAAS,QAAQ,OAAO;CAE3C,AAAmB,YAAY,MAAM;EACnC,IAAI;EACJ,eAAe;AACb,QAAK,MAAM,SAAS,KAAK,OAAO,WAAW,OAAO,CAChD,MAAK,YAAY,MAAM,QAAQ;;EAGpC,CAAC;CAEF,AAAO,YAAY,SAAsC;AACvD,MAAI,QAAQ,SACV;EAGF,MAAM,OAAO,QAAQ;EACrB,MAAM,SACJ,OAAO,QAAQ,WAAW,aAAa,QAAQ,QAAQ,GAAG,QAAQ;AAEpE,MAAI,CAAC,KAAK,SAAS,KAAK,CACtB,OAAM,IAAI,YAAY,kCAAkC;EAI1D,MAAM,UAAU,KAAK,mBAAmB,QAAQ,QAAQ;AAExD,OAAK,MAAM,UAAU,aACnB,MAAK,eAAe,YAAY;GAC9B;GACA;GACA;GACD,CAAC;AAGJ,OAAK,IAAI,KAAK,YAAY;GAAE;GAAM;GAAQ,CAAC;;CAG7C,AAAO,mBACL,QACA,SACe;AACf,SAAO,OAAO,YAAY;GACxB,MAAM,MAAM,IAAI,IAAI,SAAS,QAAQ,IAAI,SAAS;AAClD,OAAI,QAAQ,IAAI,OACd,KAAI,SAAS,QAAQ,IAAI;AAG3B,WAAQ,UAAU,IAAI;GAEtB,MAAM,cAAc;IAClB,KAAK,IAAI,UAAU;IACnB,QAAQ,QAAQ;IAChB,SAAS;KACP,GAAG,QAAQ;KACX,mBAAmB;KACpB;IACD,MAAM,KAAK,kBAAkB,QAAQ;IACtC;AAED,OAAI,YAAY,KACd,CAAC,YAAoB,SAAS;AAGhC,OAAI,QAAQ,cACV,OAAM,QAAQ,cAAc,SAAS,YAAY;AAGnD,QAAK,IAAI,MAAM,oBAAoB;IACjC,KAAK,IAAI,UAAU;IACnB,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IAClB,CAAC;GAEF,MAAM,WAAW,MAAM,MAAM,YAAY,KAAK,YAAY;AAE1D,WAAQ,MAAM,SAAS,SAAS;AAChC,WAAQ,MAAM,UAAU,OAAO,YAAY,SAAS,QAAQ,SAAS,CAAC;AACtE,WAAQ,MAAM,OAAO,SAAS;AAE9B,QAAK,IAAI,MAAM,qBAAqB;IAClC,QAAQ,QAAQ,MAAM;IACtB,SAAS,QAAQ,MAAM;IACxB,CAAC;AAEF,OAAI,QAAQ,cACV,OAAM,QAAQ,cAAc,SAAS,SAAS;;;CAKpD,AAAQ,kBAAkB,KAAgD;EACxE,MAAM,EAAE,WAAW;AAEnB,MAAI,WAAW,SAAS,WAAW,UAAU,WAAW,UACtD;AAGF,MAAI,IAAI,KAAK,KAAK,IAChB,QAAO,IAAI,IAAI,IAAI,IAAI;AAGzB,MAAI,IAAI,KAAK,MAAM,KAAK;GACtB,MAAM,UAAU,IAAI,IAAI,KAAK;AAC7B,UAAOA,eAAU,KAAK,QAAQ;;;;;;;;;;;;;ACpGpC,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,YAAY,CAAC,OAAO;CACpB,UAAU,CAAC,cAAc,oBAAoB;CAC9C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["result: RateLimitResult"],"sources":["../../src/server-rate-limit/providers/ServerRateLimitProvider.ts","../../src/server-rate-limit/primitives/$rateLimit.ts","../../src/server-rate-limit/index.ts"],"sourcesContent":["import { $atom, $env, $hook, $inject, $use, type Static, t } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { $logger } from \"alepha/logger\";\nimport {\n HttpError,\n type ServerRequest,\n ServerRouterProvider,\n} from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport type { RateLimitPrimitiveOptions } from \"../primitives/$rateLimit.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitResult {\n allowed: boolean;\n limit: number;\n remaining: number;\n resetTime: number;\n retryAfter?: number;\n}\n\n/**\n * Rate limit configuration atom (global defaults)\n */\nexport const rateLimitOptions = $atom({\n name: \"alepha.server.rate-limit.options\",\n schema: t.object({\n windowMs: t.optional(\n t.number({\n description: \"Window duration in milliseconds\",\n }),\n ),\n max: t.optional(\n t.number({\n description: \"Maximum number of requests per window\",\n }),\n ),\n skipFailedRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for failed requests\",\n }),\n ),\n skipSuccessfulRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for successful requests\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type RateLimitAtomOptions = Static<typeof rateLimitOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [rateLimitOptions.key]: RateLimitAtomOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n RATE_LIMIT_WINDOW_MS: t.number({\n default: 15 * 60 * 1000, // 15 minutes\n description: \"Rate limit window in milliseconds\",\n }),\n RATE_LIMIT_MAX_REQUESTS: t.number({\n default: 100,\n description: \"Maximum requests per window\",\n }),\n});\n\nexport class ServerRateLimitProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly env = $env(envSchema);\n\n protected readonly cache = $cache<RateLimitData>({\n name: \"server-rate-limit\",\n ttl: [this.env.RATE_LIMIT_WINDOW_MS, \"milliseconds\"],\n });\n\n protected readonly globalOptions = $use(rateLimitOptions);\n\n /**\n * Registered rate limit configurations with their path patterns\n */\n public readonly registeredConfigs: RateLimitPrimitiveOptions[] = [];\n\n /**\n * Register a rate limit configuration (called by primitives)\n */\n public registerRateLimit(config: RateLimitPrimitiveOptions): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific rate limit configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.rateLimit = this.buildRateLimitOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered rate-limit configurations.`,\n );\n }\n },\n });\n\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n // Use route-specific rate limit if defined, otherwise use global options\n const rateLimitConfig = route.rateLimit ?? this.globalOptions;\n\n // Skip if no rate limiting configured\n if (!rateLimitConfig.max && !rateLimitConfig.windowMs) {\n return;\n }\n\n const result = await this.checkLimit(request, rateLimitConfig);\n this.setRateLimitHeaders(request, result);\n\n if (!result.allowed) {\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n },\n });\n\n public readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request }) => {\n // Check if this action has rate limiting enabled\n const rateLimit = action.options?.rateLimit;\n if (!rateLimit) {\n return; // No rate limiting for this action\n }\n\n const result = await this.checkLimit(request, rateLimit);\n\n if (!result.allowed) {\n // Actions are internal - don't set HTTP headers\n // Only throw error to prevent action execution\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n\n // Action allowed - no headers to set since actions are internal\n },\n });\n\n /**\n * Build complete rate limit options by merging with global defaults\n */\n protected buildRateLimitOptions(\n config: RateLimitPrimitiveOptions,\n ): RateLimitOptions {\n return {\n max: config.max ?? this.globalOptions.max,\n windowMs: config.windowMs ?? this.globalOptions.windowMs,\n keyGenerator: config.keyGenerator,\n skipFailedRequests:\n config.skipFailedRequests ?? this.globalOptions.skipFailedRequests,\n skipSuccessfulRequests:\n config.skipSuccessfulRequests ??\n this.globalOptions.skipSuccessfulRequests,\n };\n }\n\n /**\n * Set rate limit headers on the response\n */\n protected setRateLimitHeaders(\n request: ServerRequest,\n result: RateLimitResult,\n ): void {\n request.reply.setHeader(\"X-RateLimit-Limit\", result.limit.toString());\n request.reply.setHeader(\n \"X-RateLimit-Remaining\",\n result.remaining.toString(),\n );\n request.reply.setHeader(\n \"X-RateLimit-Reset\",\n Math.ceil(result.resetTime / 1000).toString(),\n );\n\n if (!result.allowed && result.retryAfter) {\n request.reply.setHeader(\"Retry-After\", result.retryAfter.toString());\n }\n }\n\n public async checkLimit(\n req: ServerRequest,\n options: RateLimitOptions = {},\n ): Promise<RateLimitResult> {\n const windowMs = options.windowMs ?? this.env.RATE_LIMIT_WINDOW_MS;\n const max = options.max ?? this.env.RATE_LIMIT_MAX_REQUESTS;\n const key = this.generateKey(req);\n\n const now = Date.now();\n const windowStart = now - windowMs;\n\n // Get current rate limit data\n const currentData = (await this.cache.get(key)) || {\n count: 0,\n windowStart: now,\n hits: [],\n };\n\n // Clean old hits outside the current window\n const validHits = currentData.hits.filter(\n (hit: number) => hit >= windowStart,\n );\n\n // Check if limit exceeded\n const allowed = validHits.length < max;\n const remaining = Math.max(0, max - validHits.length);\n const resetTime = Math.max(...validHits, windowStart) + windowMs;\n\n // If allowed, record this request\n if (allowed) {\n validHits.push(now);\n await this.cache.set(key, {\n count: validHits.length,\n windowStart: Math.min(currentData.windowStart, windowStart),\n hits: validHits,\n });\n }\n\n const result: RateLimitResult = {\n allowed,\n limit: max,\n remaining: allowed ? remaining - 1 : remaining,\n resetTime,\n };\n\n if (!allowed) {\n result.retryAfter = Math.ceil((resetTime - now) / 1000);\n }\n\n return result;\n }\n\n protected generateKey(req: ServerRequest): string {\n // Default to IP-based rate limiting\n const ip = this.getClientIP(req);\n return `ip:${ip}`;\n }\n\n protected getClientIP(req: ServerRequest): string {\n // Check x-forwarded-for header first (for proxies/load balancers)\n const forwarded = req.headers?.[\"x-forwarded-for\"];\n if (forwarded) {\n // x-forwarded-for can contain multiple IPs, get the first one (original client)\n const firstIp = forwarded.split(\",\")[0].trim();\n if (firstIp) return firstIp;\n }\n\n return req.ip || \"unknown\";\n }\n}\n\ninterface RateLimitData {\n count: number;\n windowStart: number;\n hits: number[];\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport {\n type RateLimitResult,\n ServerRateLimitProvider,\n} from \"../providers/ServerRateLimitProvider.ts\";\n\n/**\n * Declares rate limiting for server routes or custom usage.\n * This primitive provides methods to check rate limits and configure behavior\n * within the server request/response cycle.\n *\n * @example\n * ```ts\n * class ApiService {\n * // Apply rate limiting to specific paths\n * apiRateLimit = $rateLimit({\n * paths: [\"/api/*\"],\n * max: 100,\n * windowMs: 15 * 60 * 1000, // 15 minutes\n * });\n *\n * // Or use check() method for manual rate limiting\n * customAction = $action({\n * handler: async (req) => {\n * const result = await this.apiRateLimit.check(req);\n * if (!result.allowed) throw new Error(\"Rate limited\");\n * return \"ok\";\n * },\n * });\n * }\n * ```\n */\nexport const $rateLimit = (\n options: RateLimitPrimitiveOptions = {},\n): AbstractRateLimitPrimitive => {\n return createPrimitive(RateLimitPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitPrimitiveOptions extends RateLimitOptions {\n /** Name identifier for this rate limit (default: property key) */\n name?: string;\n /** Path patterns to match (supports wildcards like /api/*) */\n paths?: string[];\n}\n\nexport interface AbstractRateLimitPrimitive {\n readonly name: string;\n readonly options: RateLimitPrimitiveOptions;\n check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult>;\n}\n\nexport class RateLimitPrimitive\n extends Primitive<RateLimitPrimitiveOptions>\n implements AbstractRateLimitPrimitive\n{\n protected readonly serverRateLimitProvider = $inject(ServerRateLimitProvider);\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n protected onInit() {\n // Register this rate limit configuration with the provider\n this.serverRateLimitProvider.registerRateLimit(this.options);\n }\n\n /**\n * Checks rate limit for the given request using this primitive's configuration.\n */\n public async check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult> {\n const mergedOptions = { ...this.options, ...options };\n return this.serverRateLimitProvider.checkLimit(request, mergedOptions);\n }\n}\n\n$rateLimit[KIND] = RateLimitPrimitive;\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $rateLimit } from \"./primitives/$rateLimit.ts\";\nimport { ServerRateLimitProvider } from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$rateLimit.ts\";\nexport * from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ActionPrimitiveOptions<TConfig> {\n /**\n * Rate limiting configuration for this action.\n * When specified, the action will be rate limited according to these settings.\n */\n rateLimit?: RateLimitOptions;\n }\n\n interface ServerRoute {\n /**\n * Route-specific rate limit configuration.\n * If set, overrides the global rate limit options for this route.\n */\n rateLimit?: RateLimitOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitOptions {\n /** Maximum number of requests per window (default: 100) */\n max?: number;\n /** Window duration in milliseconds (default: 15 minutes) */\n windowMs?: number;\n /** Custom key generator function */\n keyGenerator?: (req: any) => string;\n /** Skip rate limiting for failed requests */\n skipFailedRequests?: boolean;\n /** Skip rate limiting for successful requests */\n skipSuccessfulRequests?: boolean;\n}\n\n/**\n * Provides rate limiting capabilities for server routes and actions with configurable limits and windows.\n *\n * The server-rate-limit module enables per-route and per-action rate limiting using either:\n * - The `$rateLimit` primitive with `paths` option for path-based rate limiting\n * - The `rateLimit` option in action primitives for action-specific limiting\n *\n * It offers sliding window rate limiting, custom key generation, and seamless integration with server routes.\n *\n * @example\n * ```ts\n * import { $rateLimit, AlephaServerRateLimit } from \"alepha/server-rate-limit\";\n *\n * class ApiService {\n * // Path-specific rate limiting\n * apiRateLimit = $rateLimit({\n * paths: [\"/api/*\"],\n * max: 100,\n * windowMs: 15 * 60 * 1000, // 15 minutes\n * });\n * }\n * ```\n *\n * @see {@link $rateLimit}\n * @module alepha.server.rate-limit\n */\nexport const AlephaServerRateLimit = $module({\n name: \"alepha.server.rate-limit\",\n primitives: [$rateLimit],\n services: [AlephaServer, ServerRateLimitProvider],\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.browser.js","names":[],"sources":["../../src/server-security/index.browser.ts"],"sourcesContent":["import { $module } from \"alepha\";\nimport { AlephaSecurity } from \"alepha/security\";\nimport { AlephaServer } from \"alepha/server\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaServerSecurity = $module({\n name: \"alepha.server.security\",\n services: [AlephaServer, AlephaSecurity],\n});\n"],"mappings":";;;;;AAMA,MAAa,uBAAuB,QAAQ;CAC1C,MAAM;CACN,UAAU,CAAC,cAAc,eAAe;CACzC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["user: UserAccountToken | undefined","ownership: boolean | string | undefined"],"sources":["../../src/server-security/providers/ServerBasicAuthProvider.ts","../../src/server-security/primitives/$basicAuth.ts","../../src/server-security/providers/ServerSecurityProvider.ts","../../src/server-security/index.ts"],"sourcesContent":["import { timingSafeEqual } from \"node:crypto\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n HttpError,\n type ServerRequest,\n ServerRouterProvider,\n} from \"alepha/server\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BasicAuthOptions {\n username: string;\n password: string;\n}\n\nexport interface BasicAuthPrimitiveConfig extends BasicAuthOptions {\n /** Name identifier for this basic auth (default: property key) */\n name?: string;\n /** Path patterns to match (supports wildcards like /devtools/*) */\n paths?: string[];\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerBasicAuthProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly routerProvider = $inject(ServerRouterProvider);\n protected readonly realm = \"Secure Area\";\n\n /**\n * Registered basic auth primitives with their configurations\n */\n public readonly registeredAuths: BasicAuthPrimitiveConfig[] = [];\n\n /**\n * Register a basic auth configuration (called by primitives)\n */\n public registerAuth(config: BasicAuthPrimitiveConfig): void {\n this.registeredAuths.push(config);\n }\n\n public readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n for (const auth of this.registeredAuths) {\n if (auth.paths) {\n for (const pattern of auth.paths) {\n const matchedRoutes = this.routerProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.secure = {\n basic: {\n username: auth.username,\n password: auth.password,\n },\n };\n }\n }\n }\n }\n\n if (this.registeredAuths.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredAuths.length} registered basic-auth configurations.`,\n );\n }\n },\n });\n\n /**\n * Hook into server:onRequest to check basic auth\n */\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n const routeAuth = route.secure;\n if (\n typeof routeAuth === \"object\" &&\n \"basic\" in routeAuth &&\n routeAuth.basic\n ) {\n this.checkAuth(request, routeAuth.basic);\n }\n },\n });\n\n /**\n * Hook into action:onRequest to check basic auth for actions\n */\n public readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request }) => {\n const routeAuth = action.route.secure;\n if (isBasicAuth(routeAuth)) {\n this.checkAuth(request, routeAuth.basic);\n }\n },\n });\n\n /**\n * Check basic authentication\n */\n public checkAuth(request: ServerRequest, options: BasicAuthOptions): void {\n const authHeader = request.headers?.authorization;\n\n if (!authHeader || !authHeader.startsWith(\"Basic \")) {\n this.sendAuthRequired(request);\n throw new HttpError({\n status: 401,\n message: \"Authentication required\",\n });\n }\n\n // decode base64 credentials\n const base64Credentials = authHeader.slice(6); // Remove \"Basic \"\n const credentials = Buffer.from(base64Credentials, \"base64\").toString(\n \"utf-8\",\n );\n\n // split only on the first colon to handle passwords with colons\n const colonIndex = credentials.indexOf(\":\");\n const username =\n colonIndex !== -1 ? credentials.slice(0, colonIndex) : credentials;\n const password = colonIndex !== -1 ? credentials.slice(colonIndex + 1) : \"\";\n\n // verify credentials using timing-safe comparison to prevent timing attacks\n const isValid = this.timingSafeCredentialCheck(\n username,\n password,\n options.username,\n options.password,\n );\n\n if (!isValid) {\n this.sendAuthRequired(request);\n this.log.warn(`Failed basic auth attempt for user`, {\n username,\n });\n throw new HttpError({\n status: 401,\n message: \"Invalid credentials\",\n });\n }\n }\n\n /**\n * Performs a timing-safe comparison of credentials to prevent timing attacks.\n * Always compares both username and password to avoid leaking which one is wrong.\n */\n protected timingSafeCredentialCheck(\n inputUsername: string,\n inputPassword: string,\n expectedUsername: string,\n expectedPassword: string,\n ): boolean {\n // Convert to buffers for timing-safe comparison\n const inputUserBuf = Buffer.from(inputUsername, \"utf-8\");\n const expectedUserBuf = Buffer.from(expectedUsername, \"utf-8\");\n const inputPassBuf = Buffer.from(inputPassword, \"utf-8\");\n const expectedPassBuf = Buffer.from(expectedPassword, \"utf-8\");\n\n // timingSafeEqual requires same-length buffers\n // When lengths differ, we compare against a dummy buffer to maintain constant time\n const userMatch = this.safeCompare(inputUserBuf, expectedUserBuf);\n const passMatch = this.safeCompare(inputPassBuf, expectedPassBuf);\n\n // Both must match - bitwise AND avoids short-circuit evaluation\n // eslint-disable-next-line no-bitwise\n return (userMatch & passMatch) === 1;\n }\n\n /**\n * Compares two buffers in constant time, handling different lengths safely.\n * Returns 1 if equal, 0 if not equal.\n */\n protected safeCompare(input: Buffer, expected: Buffer): number {\n // If lengths differ, compare input against itself to maintain timing\n // but return 0 (not equal)\n if (input.length !== expected.length) {\n // Still perform a comparison to keep timing consistent\n timingSafeEqual(input, input);\n return 0;\n }\n\n return timingSafeEqual(input, expected) ? 1 : 0;\n }\n\n /**\n * Send WWW-Authenticate header\n */\n protected sendAuthRequired(request: ServerRequest): void {\n request.reply.setHeader(\"WWW-Authenticate\", `Basic realm=\"${this.realm}\"`);\n }\n}\n\nexport const isBasicAuth = (\n value: unknown,\n): value is { basic: BasicAuthOptions } => {\n return (\n typeof value === \"object\" && !!value && \"basic\" in value && !!value.basic\n );\n};\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type {\n BasicAuthOptions,\n BasicAuthPrimitiveConfig,\n} from \"../providers/ServerBasicAuthProvider.ts\";\nimport { ServerBasicAuthProvider } from \"../providers/ServerBasicAuthProvider.ts\";\n\n/**\n * Declares HTTP Basic Authentication for server routes.\n * This primitive provides methods to protect routes with username/password authentication.\n */\nexport const $basicAuth = (\n options: BasicAuthPrimitiveConfig,\n): AbstractBasicAuthPrimitive => {\n return createPrimitive(BasicAuthPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface AbstractBasicAuthPrimitive {\n readonly name: string;\n readonly options: BasicAuthPrimitiveConfig;\n check(request: ServerRequest, options?: BasicAuthOptions): void;\n}\n\nexport class BasicAuthPrimitive\n extends Primitive<BasicAuthPrimitiveConfig>\n implements AbstractBasicAuthPrimitive\n{\n protected readonly serverBasicAuthProvider = $inject(ServerBasicAuthProvider);\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n protected onInit() {\n // Register this auth configuration with the provider\n this.serverBasicAuthProvider.registerAuth(this.options);\n }\n\n /**\n * Checks basic auth for the given request using this primitive's configuration.\n */\n public check(request: ServerRequest, options?: BasicAuthOptions): void {\n const mergedOptions = { ...this.options, ...options };\n this.serverBasicAuthProvider.checkAuth(request, mergedOptions);\n }\n}\n\n$basicAuth[KIND] = BasicAuthPrimitive;\n","import { randomUUID } from \"node:crypto\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n JwtProvider,\n type Permission,\n SecurityProvider,\n type UserAccountToken,\n userAccountInfoSchema,\n} from \"alepha/security\";\nimport {\n $action,\n ForbiddenError,\n type ServerRequest,\n UnauthorizedError,\n} from \"alepha/server\";\nimport {\n type BasicAuthOptions,\n isBasicAuth,\n} from \"./ServerBasicAuthProvider.ts\";\n\nexport class ServerSecurityProvider {\n protected readonly log = $logger();\n protected readonly securityProvider = $inject(SecurityProvider);\n protected readonly jwtProvider = $inject(JwtProvider);\n protected readonly alepha = $inject(Alepha);\n\n protected readonly onConfigure = $hook({\n on: \"configure\",\n handler: async () => {\n for (const action of this.alepha.primitives($action)) {\n // -------------------------------------------------------------------------------------------------------------\n // if the action is disabled or not secure, we do NOT create a permission for it\n // -------------------------------------------------------------------------------------------------------------\n if (\n action.options.disabled ||\n action.options.secure === false ||\n this.securityProvider.getRealms().length === 0\n ) {\n continue;\n }\n\n const secure = action.options.secure;\n if (typeof secure !== \"object\") {\n this.securityProvider.createPermission({\n name: action.name,\n group: action.group,\n method: action.route.method,\n path: action.route.path,\n });\n }\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request, options }) => {\n // if you set explicitly secure: false, we assume you don't want any security check\n // but only if no user is provided in options\n if (action.options.secure === false && !options.user) {\n this.log.trace(\"Skipping security check for route\");\n return;\n }\n\n if (isBasicAuth(action.route.secure)) {\n return;\n }\n\n const permission = this.securityProvider\n .getPermissions()\n .find(\n (it) =>\n it.path === action.route.path && it.method === action.route.method,\n );\n\n try {\n request.user = this.createUserFromLocalFunctionContext(\n options,\n permission,\n );\n\n const route = action.route;\n if (typeof route.secure === \"object\") {\n this.check(request.user, route.secure);\n }\n\n this.alepha.store.set(\n \"alepha.server.request.user\",\n this.alepha.codec.decode(userAccountInfoSchema, request.user),\n );\n } catch (error) {\n if (action.options.secure || permission) {\n throw error;\n }\n // else, we skip the security check\n this.log.trace(\"Skipping security check for action\");\n }\n },\n });\n\n protected readonly onRequest = $hook({\n on: \"server:onRequest\",\n priority: \"last\",\n handler: async ({ request, route }) => {\n // if you set explicitly secure: false, we assume you don't want any security check\n if (route.secure === false) {\n this.log.trace(\n \"Skipping security check for route - explicitly disabled\",\n );\n return;\n }\n\n if (isBasicAuth(route.secure)) {\n return;\n }\n\n const permission = this.securityProvider\n .getPermissions()\n .find((it) => it.path === route.path && it.method === route.method);\n\n if (!request.headers.authorization && !route.secure && !permission) {\n this.log.trace(\n \"Skipping security check for route - no authorization header and not secure\",\n );\n return;\n }\n\n try {\n // set user to request\n request.user = await this.securityProvider.createUserFromToken(\n request.headers.authorization,\n { permission },\n );\n\n if (typeof route.secure === \"object\") {\n this.check(request.user, route.secure);\n }\n\n this.alepha.store.set(\n \"alepha.server.request.user\",\n // remove sensitive info\n this.alepha.codec.decode(userAccountInfoSchema, request.user),\n );\n\n this.log.trace(\"User set from request token\", {\n user: request.user,\n permission,\n });\n } catch (error) {\n if (route.secure || permission) {\n throw error;\n }\n\n // else, we skip the security check\n this.log.trace(\n \"Skipping security check for route - error occurred\",\n error,\n );\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected check(user: UserAccountToken, secure: ServerRouteSecure) {\n if (secure.realm) {\n if (user.realm !== secure.realm) {\n throw new ForbiddenError(\n `User must belong to realm '${secure.realm}' to access this route`,\n );\n }\n }\n }\n\n /**\n * Get the user account token for a local action call.\n * There are three possible sources for the user:\n * - `options.user`: the user passed in the options\n * - `\"system\"`: the system user from the state (you MUST set state `server.security.system.user`)\n * - `\"context\"`: the user from the request context (you MUST be in an HTTP request context)\n *\n * Priority order: `options.user` > `\"system\"` > `\"context\"`.\n *\n * In testing environment, if no user is provided, a test user is created based on the SecurityProvider's roles.\n */\n protected createUserFromLocalFunctionContext(\n options: { user?: UserAccountToken | \"system\" | \"context\" },\n permission?: Permission,\n ): UserAccountToken {\n const fromOptions =\n typeof options.user === \"object\" ? options.user : undefined;\n\n const type = typeof options.user === \"string\" ? options.user : undefined;\n\n let user: UserAccountToken | undefined;\n\n const fromContext = this.alepha.context.get<ServerRequest>(\"request\")?.user;\n const fromSystem = this.alepha.store.get(\n \"alepha.server.security.system.user\",\n );\n\n if (type === \"system\") {\n user = fromSystem;\n } else if (type === \"context\") {\n user = fromContext;\n } else {\n user = fromOptions ?? fromContext ?? fromSystem;\n }\n\n if (!user) {\n // in testing mode, we create a test user\n if (this.alepha.isTest() && !(\"user\" in options)) {\n return this.createTestUser();\n }\n\n throw new UnauthorizedError(\"User is required for calling this action\");\n }\n\n const roles =\n user.roles ??\n (this.alepha.isTest()\n ? this.securityProvider.getRoles().map((role) => role.name)\n : []);\n let ownership: boolean | string | undefined;\n\n if (permission) {\n const result = this.securityProvider.checkPermission(\n permission,\n ...roles,\n );\n if (!result.isAuthorized) {\n throw new ForbiddenError(\n `Permission '${this.securityProvider.permissionToString(permission)}' is required for this route`,\n );\n }\n ownership = result.ownership;\n }\n\n // create a new user object with ownership if needed\n return {\n ...user,\n ownership,\n };\n }\n\n // ---------------------------------------------------------------------------------------------------------------\n // TESTING ONLY\n // ---------------------------------------------------------------------------------------------------------------\n\n protected createTestUser(): UserAccountToken {\n return {\n id: randomUUID(),\n name: \"Test\",\n roles: this.securityProvider.getRoles().map((role) => role.name),\n };\n }\n\n protected readonly onClientRequest = $hook({\n on: \"client:onRequest\",\n handler: async ({ request, options }) => {\n if (!this.alepha.isTest()) {\n return;\n }\n\n // skip helper if user is explicitly set to undefined\n if (\"user\" in options && options.user === undefined) {\n return;\n }\n\n request.headers = new Headers(request.headers);\n\n if (!request.headers.has(\"authorization\")) {\n const test = this.createTestUser();\n const user =\n typeof options?.user === \"object\" ? options.user : undefined;\n const sub = user?.id ?? test.id;\n const roles = user?.roles ?? test.roles;\n\n const token = await this.jwtProvider.create(\n {\n sub,\n roles,\n },\n user?.realm ?? this.securityProvider.getRealms()[0]?.name,\n );\n\n request.headers.set(\"authorization\", `Bearer ${token}`);\n }\n },\n });\n}\n\nexport type ServerRouteSecure = {\n realm?: string;\n basic?: BasicAuthOptions;\n};\n","import { $module } from \"alepha\";\nimport {\n $permission,\n $realm,\n $role,\n AlephaSecurity,\n type UserAccount,\n type UserAccountToken,\n} from \"alepha/security\";\nimport { AlephaServer, type FetchOptions } from \"alepha/server\";\nimport { $basicAuth } from \"./primitives/$basicAuth.ts\";\nimport { ServerBasicAuthProvider } from \"./providers/ServerBasicAuthProvider.ts\";\nimport {\n type ServerRouteSecure,\n ServerSecurityProvider,\n} from \"./providers/ServerSecurityProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$basicAuth.ts\";\nexport * from \"./providers/ServerBasicAuthProvider.ts\";\nexport * from \"./providers/ServerSecurityProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface State {\n /**\n * Real (or fake) user account, used for internal actions.\n *\n * If you define this, you assume that all actions are executed by this user by default.\n * > To force a different user, you need to pass it explicitly in the options.\n */\n\n \"alepha.server.security.system.user\"?: UserAccountToken;\n\n /**\n * The authenticated user account attached to the server request state.\n *\n * @internal\n */\n \"alepha.server.request.user\"?: UserAccount;\n }\n}\n\ndeclare module \"alepha/server\" {\n interface ServerRequest<TConfig> {\n user?: UserAccountToken; // for all routes, user is maybe present\n }\n\n interface ServerActionRequest<TConfig> {\n user: UserAccountToken; // for actions, user is always present\n }\n\n interface ServerRoute {\n /**\n * If true, the route will be protected by the security provider.\n * All actions are secure by default, but you can disable it for specific actions.\n */\n secure?: boolean | ServerRouteSecure;\n }\n\n interface ClientRequestOptions extends FetchOptions {\n /**\n * Forward user from the previous request.\n * If \"system\", use system user. @see {ServerSecurityProvider.localSystemUser}\n * If \"context\", use the user from the current context (e.g. request).\n *\n * @default \"system\" if provided, else \"context\" if available.\n */\n user?: UserAccountToken | \"system\" | \"context\";\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Server that provides security features. Based on the Alepha Security module.\n *\n * By default, all $action will be guarded by a permission check.\n *\n * @see {@link ServerSecurityProvider}\n * @module alepha.server.security\n */\nexport const AlephaServerSecurity = $module({\n name: \"alepha.server.security\",\n primitives: [$realm, $role, $permission, $basicAuth],\n services: [\n AlephaServer,\n AlephaSecurity,\n ServerSecurityProvider,\n ServerBasicAuthProvider,\n ],\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/server-static/primitives/$serve.ts","../../src/server-static/providers/ServerStaticProvider.ts","../../src/server-static/index.ts"],"sourcesContent":["import { createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { DurationLike } from \"alepha/datetime\";\n\n/**\n * Create a new static file handler.\n */\nexport const $serve = (options: ServePrimitiveOptions = {}): ServePrimitive => {\n return createPrimitive(ServePrimitive, options);\n};\n\nexport interface ServePrimitiveOptions {\n /**\n * Prefix for the served path.\n *\n * @default \"/\"\n */\n path?: string;\n\n /**\n * Path to the directory to serve.\n *\n * @default process.cwd()\n */\n root?: string;\n\n /**\n * If true, primitive will be ignored.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether to keep dot files (e.g. `.gitignore`, `.env`) in the served directory.\n *\n * @default true\n */\n ignoreDotEnvFiles?: boolean;\n\n /**\n * Whether to use the index.html file when the path is a directory.\n *\n * @default true\n */\n indexFallback?: boolean;\n\n /**\n * Force all requests \"not found\" to be served with the index.html file.\n * This is useful for single-page applications (SPAs) that use client-side only routing.\n */\n historyApiFallback?: boolean;\n\n /**\n * Optional name of the primitive.\n * This is used for logging and debugging purposes.\n *\n * @default Key name.\n */\n name?: string;\n\n /**\n * Whether to use cache control headers.\n *\n * @default {}\n */\n cacheControl?: Partial<CacheControlOptions> | false;\n}\n\nexport interface CacheControlOptions {\n /**\n * Whether to use cache control headers.\n *\n * @default [.js, .css]\n */\n fileTypes: string[];\n\n /**\n * The maximum age of the cache in seconds.\n *\n * @default 60 * 60 * 24 * 2 // 2 days\n */\n maxAge: DurationLike;\n\n /**\n * Whether to use immutable cache control headers.\n *\n * @default true\n */\n immutable: boolean;\n}\n\nexport class ServePrimitive extends Primitive<ServePrimitiveOptions> {}\n\n$serve[KIND] = ServePrimitive;\n","import { createReadStream } from \"node:fs\";\nimport { access, readdir, stat } from \"node:fs/promises\";\nimport { basename, isAbsolute, join } from \"node:path\";\nimport type { Readable as NodeStream } from \"node:stream\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { FileDetector } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\nimport { type ServerHandler, ServerRouterProvider } from \"alepha/server\";\nimport { $serve, type ServePrimitiveOptions } from \"../primitives/$serve.ts\";\n\nexport class ServerStaticProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly routerProvider = $inject(ServerRouterProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly log = $logger();\n protected readonly directories: ServeDirectory[] = [];\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n await Promise.all(\n this.alepha\n .primitives($serve)\n .map((it) => this.createStaticServer(it.options)),\n );\n },\n });\n\n public async createStaticServer(\n options: ServePrimitiveOptions,\n ): Promise<void> {\n const prefix = options.path ?? \"/\";\n\n let root = options.root ?? process.cwd();\n if (!isAbsolute(root)) {\n root = join(process.cwd(), root);\n }\n\n this.log.debug(\"Serve static files\", { prefix, root });\n\n await stat(root);\n\n // 1. get all files in the root directory (recursively)\n const files = await this.getAllFiles(root, options.ignoreDotEnvFiles);\n\n // 2. create a $route for each file (yes, this could be a lot of routes)\n const routes = await Promise.all(\n files.map(async (file) => {\n const path = file.replace(root, \"\").replace(/\\\\/g, \"/\");\n this.log.trace(`Mount ${join(prefix, path)} -> ${join(root, path)}`);\n return {\n path: join(prefix, encodeURI(path)),\n handler: await this.createFileHandler(join(root, path), options),\n };\n }),\n );\n\n for (const route of routes) {\n this.routerProvider.createRoute(route);\n\n // if route is for index.html, also create a route without it\n // e.g. /my/path/index.html -> /my/path/\n if (\n options.indexFallback !== false &&\n route.path.endsWith(\"index.html\")\n ) {\n this.routerProvider.createRoute({\n path: route.path.replace(/index\\.html$/, \"\"),\n handler: route.handler,\n });\n }\n }\n\n // 3. store the directory info for reference\n this.directories.push({\n options,\n files: files.map((file) => file.replace(root, \"\").replace(/\\\\/g, \"/\")),\n });\n\n // bonus! for SPAs, handle history API fallback\n if (options.historyApiFallback) {\n // meaning all unmatched routes should serve index.html\n this.routerProvider.createRoute({\n path: join(prefix, \"*\").replace(/\\\\/g, \"/\"),\n handler: async (request) => {\n const { reply } = request;\n\n if (request.url.pathname.includes(\".\")) {\n // If the request is for a file (e.g., /style.css), do not fall back\n reply.headers[\"content-type\"] = \"text/plain\";\n reply.body = \"Not Found\";\n reply.status = 404;\n return;\n }\n\n reply.headers[\"content-type\"] = \"text/html\";\n reply.status = 200;\n\n return new Promise<any>((resolve, reject) => {\n const stream = createReadStream(join(root, \"index.html\"));\n stream.on(\"open\", () => {\n resolve(stream);\n });\n stream.on(\"error\", (err) => {\n reject(err);\n });\n });\n },\n });\n }\n }\n\n public async createFileHandler(\n filepath: string,\n options: ServePrimitiveOptions,\n ): Promise<ServerHandler> {\n const filename = basename(filepath);\n\n const hasGzip = await access(`${filepath}.gz`)\n .then(() => true)\n .catch(() => false);\n\n const hasBr = await access(`${filepath}.br`)\n .then(() => true)\n .catch(() => false);\n\n const fileStat = await stat(filepath);\n const lastModified = fileStat.mtime.toUTCString();\n const etag = `\"${fileStat.size}-${fileStat.mtime.getTime()}\"`;\n const contentType = this.fileDetector.getContentType(filename);\n const cacheControl = this.getCacheControl(filename, options);\n\n return async (request): Promise<NodeStream | undefined> => {\n const { headers, reply } = request;\n let path = filepath;\n\n const encoding = headers[\"accept-encoding\"];\n if (encoding) {\n if (hasBr && encoding.includes(\"br\")) {\n reply.headers[\"content-encoding\"] = \"br\";\n path += \".br\";\n } else if (hasGzip && encoding.includes(\"gzip\")) {\n reply.headers[\"content-encoding\"] = \"gzip\";\n path += \".gz\";\n }\n }\n\n reply.headers[\"content-type\"] = contentType;\n reply.headers[\"accept-ranges\"] = \"bytes\";\n reply.headers[\"last-modified\"] = lastModified;\n\n if (cacheControl) {\n reply.headers[\"cache-control\"] =\n `public, max-age=${cacheControl.maxAge}`;\n if (cacheControl.immutable) {\n reply.headers[\"cache-control\"] += \", immutable\";\n }\n }\n\n reply.headers.etag = etag;\n if (\n headers[\"if-none-match\"] === etag ||\n headers[\"if-modified-since\"] === lastModified\n ) {\n reply.status = 304;\n return;\n }\n\n return new Promise<any>((resolve, reject) => {\n const stream = createReadStream(path);\n stream.on(\"open\", () => {\n resolve(stream);\n });\n stream.on(\"error\", (err) => {\n reject(err);\n });\n });\n };\n }\n\n protected getCacheFileTypes(): string[] {\n return [\n \".js\",\n \".css\",\n \".woff\",\n \".woff2\",\n \".ttf\",\n \".eot\",\n \".otf\",\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".svg\",\n \".gif\",\n ];\n }\n\n protected getCacheControl(\n filename: string,\n options: ServePrimitiveOptions,\n ): { maxAge: number; immutable: boolean } | undefined {\n if (!options.cacheControl) {\n return;\n }\n\n const fileTypes =\n options.cacheControl.fileTypes ?? this.getCacheFileTypes();\n\n for (const type of fileTypes) {\n if (filename.endsWith(type)) {\n return {\n immutable: options.cacheControl.immutable ?? true,\n maxAge: this.dateTimeProvider\n .duration(options.cacheControl.maxAge ?? [30, \"days\"])\n .as(\"seconds\"),\n };\n }\n }\n }\n\n public async getAllFiles(\n dir: string,\n ignoreDotEnvFiles = true,\n ): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n const files = await Promise.all(\n entries.map((dirent) => {\n // skip .env & other dot files\n if (ignoreDotEnvFiles && dirent.name.startsWith(\".\")) {\n return [];\n }\n\n const fullPath = join(dir, dirent.name);\n return dirent.isDirectory() ? this.getAllFiles(fullPath) : fullPath;\n }),\n );\n\n return files.flat();\n }\n}\n\nexport interface ServeDirectory {\n options: ServePrimitiveOptions;\n files: string[];\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $serve } from \"./primitives/$serve.ts\";\nimport { ServerStaticProvider } from \"./providers/ServerStaticProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$serve.ts\";\nexport * from \"./providers/ServerStaticProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Create static file server with `$static()`.\n *\n * @see {@link ServerStaticProvider}\n * @module alepha.server.static\n */\nexport const AlephaServerStatic = $module({\n name: \"alepha.server.static\",\n primitives: [$serve],\n services: [AlephaServer, ServerStaticProvider],\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["openApi: OpenApiDocument","schemas: Record<string, any>","operation: OpenApiOperation","schema: any"],"sources":["../../src/server-swagger/primitives/$swagger.ts","../../src/server-swagger/providers/ServerSwaggerProvider.ts","../../src/server-swagger/index.ts"],"sourcesContent":["import { createPrimitive, KIND, Primitive } from \"alepha\";\n\n/**\n * Creates an OpenAPI/Swagger documentation primitive with interactive UI.\n *\n * Automatically generates API documentation from your $action primitives and serves\n * an interactive Swagger UI for testing endpoints. Supports customization, tag filtering,\n * and OAuth configuration.\n *\n * @example\n * ```ts\n * class App {\n * docs = $swagger({\n * prefix: \"/api-docs\",\n * info: {\n * title: \"My API\",\n * version: \"1.0.0\",\n * description: \"REST API documentation\"\n * },\n * excludeTags: [\"internal\"],\n * ui: { root: \"/swagger\" }\n * });\n * }\n * ```\n */\nexport const $swagger = (\n options: SwaggerPrimitiveOptions = {},\n): SwaggerPrimitive => {\n return createPrimitive(SwaggerPrimitive, options);\n};\n\nexport interface SwaggerPrimitiveOptions {\n info?: OpenApiDocument[\"info\"];\n\n /**\n * @default: \"/docs\"\n */\n prefix?: string;\n\n /**\n * If true, docs will be disabled.\n */\n disabled?: boolean;\n\n /**\n * Tags to exclude from the documentation.\n */\n excludeTags?: string[];\n\n /**\n * Enable Swagger UI.\n *\n * @default true\n */\n ui?: boolean | SwaggerUiOptions;\n\n /**\n * Function to rewrite the OpenAPI document before serving it.\n */\n rewrite?: (doc: OpenApiDocument) => void;\n}\n\nexport interface SwaggerUiOptions {\n root?: string;\n\n initOAuth?: {\n /**\n * Default clientId.\n */\n clientId?: string;\n\n /**\n * realm query parameter (for oauth1) added to authorizationUrl and tokenUrl.\n */\n realm?: string;\n\n /**\n * application name, displayed in authorization popup.\n */\n appName?: string;\n\n /**\n * scope separator for passing scopes, encoded before calling, default\n * value is a space (encoded value %20).\n *\n * @default ' '\n */\n scopeSeparator?: string;\n\n /**\n * string array or scope separator (i.e. space) separated string of\n * initially selected oauth scopes\n *\n * @default []\n */\n scopes?: string | string[];\n\n /**\n * Additional query parameters added to authorizationUrl and tokenUrl.\n * MUST be an object\n */\n additionalQueryStringParams?: { [key: string]: any };\n\n /**\n * Only activated for the accessCode flow. During the authorization_code\n * request to the tokenUrl, pass the Client Password using the HTTP Basic\n * Authentication scheme (Authorization header with Basic\n * base64encode(client_id + client_secret)).\n *\n * @default false\n */\n useBasicAuthenticationWithAccessCodeGrant?: boolean;\n\n /**\n * Only applies to Authorization Code flows. Proof Key for Code Exchange\n * brings enhanced security for OAuth public clients.\n *\n * @default false\n */\n usePkceWithAuthorizationCodeGrant?: boolean;\n };\n}\n\nexport class SwaggerPrimitive extends Primitive<SwaggerPrimitiveOptions> {}\n\n$swagger[KIND] = SwaggerPrimitive;\n\nexport interface OpenApiDocument {\n openapi: string;\n info: {\n title: string;\n version: string;\n description?: string;\n };\n paths: Record<string, any>;\n components?: {\n schemas?: Record<string, any>;\n securitySchemes?: Record<string, any>;\n };\n}\n\nexport interface OpenApiOperation {\n tags?: string[];\n summary?: string;\n description?: string;\n operationId?: string;\n parameters?: Array<{\n name: string;\n in: \"query\" | \"header\" | \"path\" | \"cookie\";\n description?: string;\n required?: boolean;\n schema: any;\n }>;\n requestBody?: {\n description?: string;\n content: Record<\n string,\n {\n schema: any;\n }\n >;\n required?: boolean;\n };\n responses: Record<\n string,\n {\n description: string;\n content?: Record<\n string,\n {\n schema: any;\n }\n >;\n }\n >;\n security?: Array<Record<string, any[]>>;\n}\n","import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n $atom,\n $hook,\n $inject,\n $use,\n Alepha,\n isTypeFile,\n type Static,\n type TObject,\n type TSchema,\n t,\n} from \"alepha\";\nimport { FileSystemProvider } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\nimport { AlephaSecurity } from \"alepha/security\";\nimport {\n $action,\n type ActionPrimitive,\n type RequestConfigSchema,\n ServerProvider,\n ServerRouterProvider,\n} from \"alepha/server\";\nimport { ServerStaticProvider } from \"alepha/server/static\";\nimport {\n $swagger,\n type OpenApiDocument,\n type OpenApiOperation,\n type SwaggerPrimitiveOptions,\n} from \"../primitives/$swagger.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Swagger provider configuration atom\n */\nexport const swaggerOptions = $atom({\n name: \"alepha.server.swagger.options\",\n schema: t.object({\n excludeKeys: t.optional(\n t.array(t.string(), {\n description: \"Keys to exclude from swagger schema\",\n }),\n ),\n }),\n default: {\n excludeKeys: [],\n },\n});\n\nexport type ServerSwaggerProviderOptions = Static<typeof swaggerOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [swaggerOptions.key]: ServerSwaggerProviderOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerSwaggerProvider {\n protected readonly serverStaticProvider = $inject(ServerStaticProvider);\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly serverProvider = $inject(ServerProvider);\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly options = $use(swaggerOptions);\n protected readonly fs = $inject(FileSystemProvider);\n\n public json?: OpenApiDocument;\n\n protected readonly configure = $hook({\n on: \"configure\",\n priority: \"last\", // wait for all configurations, sometimes some actions are registered late!\n handler: async (alepha) => {\n const options = alepha.primitives($swagger)?.[0]?.options;\n if (!options) {\n return;\n }\n\n this.json = await this.createSwagger(options);\n },\n });\n\n public async createSwagger(\n options: SwaggerPrimitiveOptions,\n ): Promise<OpenApiDocument | undefined> {\n if (options.disabled) {\n return;\n }\n\n const json = this.configureOpenApi(\n this.alepha.primitives($action),\n options,\n );\n\n if (options.rewrite) {\n options.rewrite(json);\n }\n\n const prefix = options.prefix ?? \"/docs\";\n\n this.configureSwaggerApi(prefix, json);\n\n if (options.ui !== false) {\n await this.configureSwaggerUi(prefix, options);\n }\n\n return json;\n }\n\n protected configureOpenApi(\n actions: ActionPrimitive<RequestConfigSchema>[],\n doc: SwaggerPrimitiveOptions,\n ): OpenApiDocument {\n const openApi: OpenApiDocument = {\n openapi: \"3.0.0\",\n info: doc.info ?? {\n title: \"API Documentation\",\n version: \"1.0.0\",\n },\n paths: {},\n components: {},\n };\n\n const hasSecurity = this.alepha.has(AlephaSecurity);\n if (hasSecurity && openApi.components) {\n openApi.components.securitySchemes = {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n },\n };\n }\n\n const excludeTags = doc.excludeTags ?? [];\n const schemas: Record<string, any> = {};\n\n const schema = (source: TSchema) => {\n if (\"title\" in source && typeof source.title === \"string\") {\n schemas[source.title] = copy(source);\n return { $ref: `#/components/schemas/${source.title}` };\n }\n return copy(source);\n };\n\n const copy = (obj: any) => {\n const newValue = JSON.parse(JSON.stringify(obj));\n this.removePrivateFields(newValue, [\n ...(this.options.excludeKeys || []),\n \"~options\",\n ]);\n return newValue;\n };\n\n for (const route of actions) {\n if (!route.options.schema) {\n continue;\n }\n\n const response = this.getResponseSchema(route);\n if (!response) {\n continue;\n }\n\n if (excludeTags.includes(route.group)) {\n continue;\n }\n\n if (route.options.hide) {\n continue;\n }\n\n const operation: OpenApiOperation = {\n operationId: route.name,\n summary: route.options.summary,\n description: route.options.description,\n tags: [route.group.replaceAll(\":\", \" / \")],\n responses: {\n [response.status]: {\n description: \"\",\n content: response.type\n ? {\n [response.type]: {\n schema: schema(response.schema),\n },\n }\n : undefined,\n },\n },\n };\n\n if (route.options.secure !== false && hasSecurity) {\n operation.security = [{ bearerAuth: [] }];\n }\n\n const g = t.raw;\n\n if (\n g.IsObject(route.options.schema.body) ||\n g.IsArray(route.options.schema.body)\n ) {\n if (\n g.IsObject(route.options.schema.body) &&\n this.isBodyMultipart(route.options.schema.body)\n ) {\n operation.requestBody = {\n required: true,\n content: {\n \"multipart/form-data\": {\n schema: schema(route.options.schema.body),\n },\n },\n };\n } else {\n operation.requestBody = {\n required: true,\n content: {\n \"application/json\": {\n schema: schema(route.options.schema.body),\n },\n },\n };\n }\n }\n\n if (g.IsObject(route.options.schema.query)) {\n operation.parameters ??= [];\n for (const [key, value] of Object.entries(\n route.options.schema.query.properties,\n )) {\n operation.parameters.push({\n name: key,\n in: \"query\",\n required: false,\n schema: schema(value),\n });\n }\n }\n\n if (g.IsObject(route.options.schema.params)) {\n operation.parameters ??= [];\n for (const [key, value] of Object.entries(\n route.options.schema.params.properties,\n )) {\n const description =\n \"description\" in value && typeof value.description === \"string\"\n ? value.description\n : undefined;\n const ref = schema(value);\n delete ref.description;\n operation.parameters.push({\n name: key,\n in: \"path\",\n required: true,\n description,\n schema: ref,\n });\n }\n }\n\n const url = route.prefix + this.replacePathParams(route.path);\n\n openApi.paths[url] = {\n ...openApi.paths[url],\n [route.method.toLowerCase()]: operation,\n };\n }\n\n if (openApi.components) openApi.components.schemas = schemas;\n\n return JSON.parse(JSON.stringify(openApi));\n }\n\n public isBodyMultipart(schema: TObject): boolean {\n for (const key in schema.properties) {\n if (isTypeFile(schema.properties[key])) {\n return true;\n }\n }\n return false;\n }\n\n public replacePathParams(url: string): string {\n return url.replace(/:\\w+/g, (match) => {\n const paramName = match.slice(1);\n return `{${paramName}}`;\n });\n }\n\n public getResponseSchema(route: ActionPrimitive<RequestConfigSchema>):\n | {\n type?: string;\n schema?: any;\n status: number;\n }\n | undefined {\n const schema: any = route.options.schema?.response;\n if (!schema) {\n return {\n status: 204,\n };\n }\n\n if (t.schema.isObject(schema) || t.schema.isArray(schema)) {\n return {\n schema,\n status: 200,\n type: \"application/json\",\n };\n }\n\n if (t.schema.isString(schema)) {\n return {\n schema,\n status: 200,\n type: \"text/plain\",\n };\n }\n\n if (isTypeFile(schema)) {\n return {\n schema,\n status: 200,\n type: \"application/octet-stream\",\n };\n }\n\n const status = Object.keys(schema)[0];\n if (t.schema.isObject(schema[status]) || isTypeFile(schema[status])) {\n return {\n schema,\n type: t.schema.isObject(schema[status])\n ? \"application/json\"\n : \"application/octet-stream\",\n status: Number(status),\n };\n }\n }\n\n protected configureSwaggerApi(prefix: string, json: OpenApiDocument): void {\n this.serverRouterProvider.createRoute({\n method: \"GET\",\n path: `${prefix}/json`,\n cache: {\n etag: true,\n },\n schema: {\n response: t.json(),\n },\n handler: () => json,\n });\n this.log.info(`Swagger API available at ${prefix}/json`);\n }\n\n protected async configureSwaggerUi(\n prefix: string,\n options: SwaggerPrimitiveOptions,\n ): Promise<void> {\n const ui = typeof options.ui === \"object\" ? options.ui : {};\n const initializer = `\nwindow.onload = function() {\n\twindow.ui = SwaggerUIBundle({\n\t\turl: \"/docs/json\",\n\t\tdom_id: '#swagger-ui',\n\t\tdeepLinking: true,\n\t\tpresets: [\n\t\t\tSwaggerUIBundle.presets.apis,\n\t\t\tSwaggerUIStandalonePreset\n\t\t],\n\t\tplugins: [\n\t\t\tSwaggerUIBundle.plugins.DownloadUrl\n\t\t],\n\t\tlayout: \"BaseLayout\"\n\t});\n\n document.body.style.backgroundColor = \"#f2f2f2\";\n\n\tconst options = ${JSON.stringify(ui)};\n\n\tif (options.initOAuth) {\n\t\tui.initOAuth(options.initOAuth);\n\t}\n};\n\t\t`.trim();\n\n const dirname = fileURLToPath(import.meta.url);\n\n const root = await this.getAssetPath(\n ui.root,\n join(dirname, \"../../assets/swagger-ui\"),\n join(dirname, \"../../../assets/swagger-ui\"),\n join(dirname, \"../../../../assets/swagger-ui\"),\n );\n\n if (!root) {\n this.log.warn(`Failed to locate Swagger UI assets for path ${prefix}`);\n return;\n }\n\n await this.serverStaticProvider.createStaticServer({\n path: prefix,\n root,\n });\n\n this.serverRouterProvider.createRoute({\n method: \"GET\",\n path: `${prefix}/swagger-initializer.js`,\n cache: {\n etag: true,\n },\n handler: ({ reply }) => {\n reply.headers[\"content-type\"] = \"application/javascript; charset=utf-8\";\n return initializer;\n },\n });\n\n this.log.info(\n `Swagger UI available at ${this.serverProvider.hostname}${prefix}/`,\n );\n }\n\n protected async getAssetPath(\n ...paths: (string | undefined)[]\n ): Promise<string | undefined> {\n for (const path of paths) {\n if (!path) continue;\n const exists = await this.fs.exists(path);\n if (exists) {\n return path;\n }\n }\n }\n\n public removePrivateFields<T extends Record<string, any>>(\n obj: T,\n excludeList: string[],\n ): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n\n const visited = new WeakSet();\n\n const traverse = (o: any): void => {\n if (visited.has(o)) return;\n visited.add(o);\n\n if (Array.isArray(o)) {\n for (let i = 0; i < o.length; i++) {\n const item = o[i];\n if (item !== null && typeof item === \"object\") {\n traverse(item);\n }\n }\n } else {\n for (const excludeKey of excludeList) {\n if (excludeKey in o) {\n delete o[excludeKey];\n }\n }\n for (const key in o) {\n const item = o[key];\n if (item !== null && typeof item === \"object\") {\n traverse(item);\n }\n }\n }\n };\n\n traverse(obj);\n return obj;\n }\n}\n","import \"alepha/server/security\";\nimport { $module } from \"alepha\";\nimport { AlephaServer, type RequestConfigSchema } from \"alepha/server\";\nimport { AlephaServerCache } from \"alepha/server/cache\";\nimport { AlephaServerStatic } from \"alepha/server/static\";\nimport { $swagger } from \"./primitives/$swagger.ts\";\nimport { ServerSwaggerProvider } from \"./providers/ServerSwaggerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$swagger.ts\";\nexport * from \"./providers/ServerSwaggerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ActionPrimitiveOptions<TConfig extends RequestConfigSchema> {\n /**\n * Short description of the route.\n */\n summary?: string;\n\n /**\n * Don't include this action in the Swagger documentation.\n */\n hide?: boolean;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Server that provides Swagger documentation capabilities.\n * It generates OpenAPI v3 documentation for the server's endpoints ($action).\n * It also provides a Swagger UI for interactive API documentation.\n *\n * @see {@link ServerSwaggerProvider}\n * @module alepha.server.swagger\n */\nexport const AlephaServerSwagger = $module({\n name: \"alepha.server.swagger\",\n primitives: [$swagger],\n services: [ServerSwaggerProvider],\n register: (alepha) => {\n alepha.with(AlephaServer);\n alepha.with(AlephaServerCache);\n alepha.with(AlephaServerStatic);\n alepha.with(ServerSwaggerProvider);\n alepha.store.push(\"alepha.build.assets\", \"alepha\");\n },\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["handlers: Array<() => Promise<unknown>>","ref: { timeout?: Timeout }"],"sources":["../../src/topic/primitives/$subscriber.ts","../../src/topic/errors/TopicTimeoutError.ts","../../src/topic/providers/TopicProvider.ts","../../src/topic/providers/MemoryTopicProvider.ts","../../src/topic/primitives/$topic.ts","../../src/topic/index.ts"],"sourcesContent":["import { createPrimitive, KIND, Primitive } from \"alepha\";\nimport type {\n TopicHandler,\n TopicMessageSchema,\n TopicPrimitive,\n} from \"./$topic.ts\";\n\n/**\n * Creates a subscriber primitive to listen for messages from a specific topic.\n *\n * Provides a dedicated message subscriber that connects to a topic and processes messages\n * with custom handler logic, enabling scalable pub/sub architectures where multiple\n * subscribers can react to the same events independently.\n *\n * **Key Features**\n * - Seamless integration with any $topic primitive\n * - Full type safety inherited from topic schema\n * - Real-time message delivery when events are published\n * - Error isolation between subscribers\n * - Support for multiple independent subscribers per topic\n *\n * **Common Use Cases**\n * - Notification services and audit logging\n * - Analytics and metrics collection\n * - Data synchronization and real-time UI updates\n *\n * @example\n * ```ts\n * class UserActivityService {\n * userEvents = $topic({\n * name: \"user-activity\",\n * schema: {\n * payload: t.object({\n * userId: t.text(),\n * action: t.enum([\"login\", \"logout\", \"purchase\"]),\n * timestamp: t.number()\n * })\n * }\n * });\n *\n * activityLogger = $subscriber({\n * topic: this.userEvents,\n * handler: async (message) => {\n * const { userId, action, timestamp } = message.payload;\n * await this.auditLogger.log({\n * userId,\n * action,\n * timestamp\n * });\n * }\n * });\n *\n * async trackUserLogin(userId: string) {\n * await this.userEvents.publish({\n * userId,\n * action: \"login\",\n * timestamp: Date.now()\n * });\n * }\n * }\n * ```\n */\nexport const $subscriber = <T extends TopicMessageSchema>(\n options: SubscriberPrimitiveOptions<T>,\n): SubscriberPrimitive<T> => {\n return createPrimitive(SubscriberPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface SubscriberPrimitiveOptions<T extends TopicMessageSchema> {\n /**\n * The topic primitive that this subscriber will listen to for messages.\n *\n * This establishes the connection between the subscriber and its source topic:\n * - The subscriber inherits the topic's message schema for type safety\n * - Messages published to the topic will be automatically delivered to this subscriber\n * - Multiple subscribers can listen to the same topic independently\n * - The subscriber will use the topic's provider and configuration settings\n *\n * **Topic Integration Benefits**:\n * - Type safety: Subscriber handler gets fully typed message payloads\n * - Schema validation: Messages are validated before reaching the subscriber\n * - Real-time delivery: Messages are delivered immediately upon publication\n * - Error isolation: Subscriber errors don't affect the topic or other subscribers\n * - Monitoring: Topic metrics include subscriber processing statistics\n *\n * @example\n * ```ts\n * // First, define a topic\n * userEvents = $topic({\n * name: \"user-activity\",\n * schema: {\n * payload: t.object({ userId: t.text(), action: t.text() })\n * }\n * });\n *\n * // Then, create a subscriber for that topic\n * activitySubscriber = $subscriber({\n * topic: this.userEvents, // Reference the topic primitive\n * handler: async (message) => { } // Process messages here\n * });\n * ```\n */\n topic: TopicPrimitive<T>;\n\n /**\n * Message handler function that processes individual messages from the topic.\n *\n * This function:\n * - Receives fully typed and validated message payloads from the connected topic\n * - Executes immediately when messages are published to the topic\n * - Should implement the core business logic for reacting to these events\n * - Runs independently of other subscribers to the same topic\n * - Should handle errors gracefully to avoid affecting other subscribers\n * - Has access to the full Alepha dependency injection container\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Consider performance impact for high-frequency topics\n * - Make handlers idempotent when possible for reliability\n * - Validate business rules within the handler logic\n * - Log important processing steps for debugging and monitoring\n *\n * **Error Handling Strategy**:\n * - Log errors but don't re-throw to avoid affecting other subscribers\n * - Use try-catch blocks for external service calls\n * - Implement circuit breakers for resilience with external systems\n * - Monitor error rates and patterns for system health\n * - Consider implementing retry logic for temporary failures\n *\n * **Performance Considerations**:\n * - Keep handler execution time minimal for high-throughput topics\n * - Use background queues for heavy processing triggered by events\n * - Implement batching for efficiency when processing many similar events\n * - Consider async processing patterns for non-critical operations\n *\n * @param message - The topic message with validated payload and optional headers\n * @param message.payload - The typed message data based on the topic's schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, eventType, timestamp } = message.payload;\n *\n * try {\n * // Log event receipt\n * this.logger.info(`Processing ${eventType} event for user ${userId}`, {\n * timestamp,\n * userId,\n * eventType\n * });\n *\n * // Perform event-specific processing\n * switch (eventType) {\n * case 'user.login':\n * await this.updateLastLogin(userId, timestamp);\n * await this.sendWelcomeNotification(userId);\n * break;\n * case 'user.logout':\n * await this.updateSessionDuration(userId, timestamp);\n * break;\n * case 'user.purchase':\n * await this.updateRewardsPoints(userId, message.payload.purchaseAmount);\n * await this.triggerRecommendations(userId);\n * break;\n * default:\n * this.logger.warn(`Unknown event type: ${eventType}`);\n * }\n *\n * // Update analytics\n * await this.analytics.track(eventType, {\n * userId,\n * timestamp,\n * source: 'topic-subscriber'\n * });\n *\n * this.logger.info(`Successfully processed ${eventType} for user ${userId}`);\n *\n * } catch (error) {\n * // Log error but don't re-throw to avoid affecting other subscribers\n * this.logger.error(`Failed to process ${eventType} for user ${userId}`, {\n * error: error.message,\n * stack: error.stack,\n * userId,\n * eventType,\n * timestamp\n * });\n *\n * // Optionally send to error tracking service\n * await this.errorTracker.captureException(error, {\n * context: { userId, eventType, timestamp },\n * tags: { component: 'topic-subscriber' }\n * });\n * }\n * }\n * ```\n */\n handler: TopicHandler<T>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class SubscriberPrimitive<\n T extends TopicMessageSchema,\n> extends Primitive<SubscriberPrimitiveOptions<T>> {}\n\n$subscriber[KIND] = SubscriberPrimitive;\n","export class TopicTimeoutError extends Error {\n public readonly topic: string;\n public readonly timeout: number;\n\n constructor(topic: string, timeout: number) {\n super(`Timeout of ${timeout}ms exceeded for topic ${topic}`);\n this.timeout = timeout;\n this.topic = topic;\n }\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $subscriber } from \"../primitives/$subscriber.ts\";\nimport { $topic } from \"../primitives/$topic.ts\";\n\n/**\n * Base class for topic providers.\n */\nexport abstract class TopicProvider {\n protected readonly alepha = $inject(Alepha);\n\n /**\n * Publish a message to a topic.\n *\n * @param topic - The topic to publish to.\n * @param message - The message to publish.\n */\n public abstract publish(topic: string, message: string): Promise<void>;\n\n /**\n * Subscribe to a topic.\n *\n * @param topic - The topic to subscribe to.\n * @param callback - The callback to call when a message is received.\n */\n public abstract subscribe(\n topic: string,\n callback: SubscribeCallback,\n ): Promise<UnSubscribeFn>;\n\n /**\n * Unsubscribe from a topic.\n *\n * @param topic - The topic to unsubscribe from.\n */\n public abstract unsubscribe(topic: string): Promise<void>;\n\n /**\n * Returns the list of $subscribers for this provider.\n */\n protected subscribers(): Array<() => Promise<unknown>> {\n const handlers: Array<() => Promise<unknown>> = [];\n\n const topics = this.alepha.primitives($topic);\n\n for (const topic of topics) {\n if (topic.provider !== this) {\n continue;\n }\n\n const handler = topic.options.handler;\n if (handler && topic.provider === this) {\n handlers.push(() => topic.subscribe(handler));\n }\n }\n\n const subscribers = this.alepha.primitives($subscriber);\n for (const subscriber of subscribers) {\n if (subscriber.options.topic.provider !== this) {\n continue;\n }\n\n handlers.push(() =>\n subscriber.options.topic.subscribe(subscriber.options.handler),\n );\n }\n\n return handlers;\n }\n}\n\nexport type SubscribeCallback = (message: string) => Promise<void> | void;\n\nexport type UnSubscribeFn = () => Promise<void>;\n","import { $hook } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport {\n type SubscribeCallback,\n TopicProvider,\n type UnSubscribeFn,\n} from \"./TopicProvider.ts\";\n\nexport class MemoryTopicProvider extends TopicProvider {\n protected readonly log = $logger();\n protected readonly subscriptions: Record<string, SubscribeCallback[]> = {};\n\n protected readonly start = $hook({\n on: \"start\",\n handler: async () => {\n const subscribers = this.subscribers();\n if (subscribers.length) {\n await Promise.all(subscribers.map((fn) => fn()));\n for (const subscriber of subscribers) {\n this.log.debug(`Subscribed to topic '${subscriber.name}'`);\n }\n }\n },\n });\n\n /**\n * Publish a message to a topic.\n *\n * @param topic\n * @param message\n */\n public async publish(topic: string, message: string): Promise<void> {\n if (!this.subscriptions[topic]) {\n return;\n }\n\n for (const callback of this.subscriptions[topic]) {\n await callback(message);\n }\n }\n\n /**\n * Subscribe to a topic.\n *\n * @param topic - The topic to subscribe to.\n * @param callback\n */\n\n public async subscribe(\n topic: string,\n callback: SubscribeCallback,\n ): Promise<UnSubscribeFn> {\n if (!this.subscriptions[topic]) {\n this.subscriptions[topic] = [];\n }\n\n this.subscriptions[topic].push(callback);\n\n return async () => {\n const callbacks = this.subscriptions[topic];\n if (!callbacks) {\n return;\n }\n\n this.subscriptions[topic] = callbacks.filter((cb) => cb !== callback);\n if (this.subscriptions[topic].length === 0) {\n delete this.subscriptions[topic];\n }\n };\n }\n\n /**\n * Unsubscribe from a topic.\n *\n * @param topic - The topic to unsubscribe from.\n */\n public async unsubscribe(topic: string): Promise<void> {\n delete this.subscriptions[topic];\n }\n}\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Service,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n type Timeout,\n} from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { TopicTimeoutError } from \"../errors/TopicTimeoutError.ts\";\nimport { MemoryTopicProvider } from \"../providers/MemoryTopicProvider.ts\";\nimport {\n TopicProvider,\n type UnSubscribeFn,\n} from \"../providers/TopicProvider.ts\";\n\n/**\n * Creates a topic primitive for publish/subscribe messaging and event-driven architecture.\n *\n * Enables decoupled communication through a pub/sub pattern where publishers send messages\n * and multiple subscribers receive them. Supports type-safe messages, real-time delivery,\n * event filtering, and pluggable backends (memory, Redis, custom providers).\n *\n * **Use Cases**: User notifications, real-time chat, event broadcasting, microservice communication\n *\n * @example\n * ```ts\n * class NotificationService {\n * userActivity = $topic({\n * name: \"user-activity\",\n * schema: {\n * payload: t.object({\n * userId: t.text(),\n * action: t.enum([\"login\", \"logout\", \"purchase\"]),\n * timestamp: t.number()\n * })\n * },\n * handler: async (message) => {\n * console.log(`User ${message.payload.userId}: ${message.payload.action}`);\n * }\n * });\n *\n * async trackLogin(userId: string) {\n * await this.userActivity.publish({ userId, action: \"login\", timestamp: Date.now() });\n * }\n *\n * async subscribeToEvents() {\n * await this.userActivity.subscribe(async (message) => {\n * // Additional subscriber logic\n * });\n * }\n * }\n * ```\n */\nexport const $topic = <T extends TopicMessageSchema>(\n options: TopicPrimitiveOptions<T>,\n): TopicPrimitive<T> => {\n return createPrimitive(TopicPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface TopicPrimitiveOptions<T extends TopicMessageSchema> {\n /**\n * Unique name identifier for the topic.\n *\n * This name is used for:\n * - Topic identification across the pub/sub system\n * - Message routing between publishers and subscribers\n * - Logging and debugging topic-related operations\n * - Provider-specific topic management (channels, keys, etc.)\n *\n * If not provided, defaults to the property key where the topic is declared.\n *\n * **Naming Conventions**:\n * - Use descriptive, hierarchical names: \"user.activity\", \"order.events\"\n * - Avoid spaces and special characters\n * - Consider using dot notation for categorization\n * - Keep names concise but meaningful\n *\n * @example \"user-activity\"\n * @example \"chat.messages\"\n * @example \"system.health.checks\"\n * @example \"payment.webhooks\"\n */\n name?: string;\n\n /**\n * Human-readable description of the topic's purpose and usage.\n *\n * Used for:\n * - Documentation generation and API references\n * - Developer onboarding and understanding\n * - Monitoring dashboards and admin interfaces\n * - Team communication about system architecture\n *\n * **Description Best Practices**:\n * - Explain what events/messages this topic handles\n * - Mention key use cases and subscribers\n * - Include any important timing or ordering guarantees\n * - Note any special processing requirements\n *\n * @example \"Real-time user activity events for analytics and notifications\"\n * @example \"Order lifecycle events from creation to delivery\"\n * @example \"Chat messages broadcast to all room participants\"\n * @example \"System health checks and service status updates\"\n */\n description?: string;\n\n /**\n * Topic provider configuration for message storage and delivery.\n *\n * Options:\n * - **\"memory\"**: In-memory provider (default for development, lost on restart)\n * - **Service<TopicProvider>**: Custom provider class (e.g., RedisTopicProvider)\n * - **undefined**: Uses the default topic provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - **Development**: Use \"memory\" for fast, simple testing without external dependencies\n * - **Production**: Use Redis or message brokers for persistence and scalability\n * - **Distributed systems**: Use Redis/RabbitMQ for cross-service communication\n * - **High-throughput**: Use specialized providers with connection pooling\n * - **Real-time**: Ensure provider supports low-latency message delivery\n *\n * **Provider Capabilities**:\n * - Message persistence and durability\n * - Subscriber management and connection handling\n * - Message ordering and delivery guarantees\n * - Horizontal scaling and load distribution\n *\n * @default Uses injected TopicProvider\n * @example \"memory\"\n * @example RedisTopicProvider\n * @example RabbitMQTopicProvider\n */\n provider?: \"memory\" | Service<TopicProvider>;\n\n /**\n * TypeBox schema defining the structure of messages published to this topic.\n *\n * The schema must include:\n * - **payload**: Required schema for the main message data\n * - **headers**: Optional schema for message metadata\n *\n * This schema:\n * - Validates all messages published to the topic\n * - Provides full TypeScript type inference for subscribers\n * - Ensures type safety between publishers and subscribers\n * - Enables automatic serialization/deserialization\n *\n * **Schema Design Best Practices**:\n * - Keep payload schemas focused and cohesive\n * - Use optional fields for data that might not always be present\n * - Include timestamp fields for event ordering\n * - Consider versioning for schema evolution\n * - Use union types for different event types in the same topic\n *\n * @example\n * ```ts\n * {\n * payload: t.object({\n * eventId: t.text(),\n * eventType: t.enum([\"created\", \"updated\"]),\n * data: t.record(t.text(), t.any()),\n * timestamp: t.number(),\n * userId: t.optional(t.text())\n * }),\n * headers: t.optional(t.object({\n * source: t.text(),\n * correlationId: t.text()\n * }))\n * }\n * ```\n */\n schema: T;\n\n /**\n * Default subscriber handler function that processes messages published to this topic.\n *\n * This handler:\n * - Automatically subscribes when the topic is initialized\n * - Receives all messages published to the topic\n * - Runs for every message without additional subscription setup\n * - Can be supplemented with additional subscribers via `subscribe()` method\n * - Should handle errors gracefully to avoid breaking other subscribers\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Consider performance impact for high-frequency topics\n * - Make handlers idempotent when possible\n * - Validate business rules within the handler logic\n * - Log important processing steps for debugging\n *\n * **Error Handling Strategy**:\n * - Log errors but don't re-throw to avoid affecting other subscribers\n * - Use try-catch blocks for external service calls\n * - Consider implementing circuit breakers for resilience\n * - Monitor error rates and patterns for system health\n *\n * @param message - The topic message with validated payload and headers\n * @param message.payload - The typed message data based on the schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { eventType, data, timestamp } = message.payload;\n *\n * try {\n * // Log message receipt\n * this.logger.info(`Processing ${eventType} event`, { timestamp, data });\n *\n * // Process based on event type\n * switch (eventType) {\n * case \"created\":\n * await this.handleCreation(data);\n * break;\n * case \"updated\":\n * await this.handleUpdate(data);\n * break;\n * default:\n * this.logger.warn(`Unknown event type: ${eventType}`);\n * }\n *\n * this.logger.info(`Successfully processed ${eventType} event`);\n *\n * } catch (error) {\n * // Log error but don't re-throw to avoid affecting other subscribers\n * this.logger.error(`Failed to process ${eventType} event`, {\n * error: error.message,\n * eventType,\n * timestamp,\n * data\n * });\n * }\n * }\n * ```\n */\n handler?: TopicHandler<T>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class TopicPrimitive<T extends TopicMessageSchema> extends Primitive<\n TopicPrimitiveOptions<T>\n> {\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n public readonly provider = this.$provider();\n\n public get name(): string {\n return this.options.name || this.config.propertyKey;\n }\n\n public async publish(payload: TopicMessage<T>[\"payload\"]): Promise<void> {\n await this.provider.publish(\n this.name,\n JSON.stringify({\n payload: this.alepha.codec.encode(this.options.schema.payload, payload),\n }),\n );\n }\n\n public async subscribe(handler: TopicHandler<T>): Promise<UnSubscribeFn> {\n return this.provider.subscribe(this.name, async (message) => {\n try {\n await handler(this.parseMessage(message));\n } catch (error) {\n this.log.error(\"Message processing has failed\", error);\n }\n });\n }\n\n public async wait(\n options: TopicWaitOptions<T> = {},\n ): Promise<TopicMessage<T>> {\n const filter = options.filter ?? (() => true);\n\n return new Promise((resolve, reject) => {\n const ref: { timeout?: Timeout } = {};\n\n (async () => {\n const clear = await this.provider.subscribe(this.name, (raw) => {\n const message = this.parseMessage(raw);\n if (!filter(message)) {\n return;\n }\n\n ref.timeout?.clear();\n clear();\n resolve(message);\n });\n\n const timeoutDuration = options.timeout ?? [10, \"seconds\"];\n\n ref.timeout = this.dateTimeProvider.createTimeout(() => {\n clear();\n reject(\n new TopicTimeoutError(\n this.name,\n this.dateTimeProvider.duration(timeoutDuration).asMilliseconds(),\n ),\n );\n }, timeoutDuration);\n })();\n });\n }\n\n protected $provider(): TopicProvider {\n if (!this.options.provider) {\n return this.alepha.inject(TopicProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryTopicProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n\n protected parseMessage(message: string): TopicMessage<T> {\n const { payload } = JSON.parse(message);\n return {\n payload: this.alepha.codec.decode(\n this.options.schema.payload,\n payload,\n ) as TopicMessage<T>[\"payload\"],\n };\n }\n}\n\n$topic[KIND] = TopicPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface TopicMessage<T extends TopicMessageSchema> {\n payload: Static<T[\"payload\"]>;\n}\n\nexport interface TopicWaitOptions<T extends TopicMessageSchema> {\n timeout?: DurationLike;\n filter?: (message: { payload: Static<T[\"payload\"]> }) => boolean;\n}\n\nexport interface TopicMessageSchema {\n payload: TSchema;\n}\n\nexport type TopicHandler<T extends TopicMessageSchema = TopicMessageSchema> = (\n message: TopicMessage<T>,\n) => unknown;\n","import { $module, type Alepha } from \"alepha\";\nimport { $subscriber } from \"./primitives/$subscriber.ts\";\nimport { $topic } from \"./primitives/$topic.ts\";\nimport { MemoryTopicProvider } from \"./providers/MemoryTopicProvider.ts\";\nimport { TopicProvider } from \"./providers/TopicProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/TopicTimeoutError.ts\";\nexport * from \"./primitives/$subscriber.ts\";\nexport * from \"./primitives/$topic.ts\";\nexport * from \"./providers/MemoryTopicProvider.ts\";\nexport * from \"./providers/TopicProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Generic interface for pub/sub messaging.\n * Gives you the ability to create topics and subscribers.\n * This module provides only a memory implementation of the topic provider.\n *\n * @see {@link $topic}\n * @see {@link $subscriber}\n * @module alepha.topic\n */\nexport const AlephaTopic = $module({\n name: \"alepha.topic\",\n primitives: [$topic, $subscriber],\n services: [TopicProvider, MemoryTopicProvider],\n register: (alepha: Alepha) =>\n alepha.with({\n optional: true,\n provide: TopicProvider,\n use: MemoryTopicProvider,\n }),\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/topic-redis/providers/RedisTopicProvider.ts","../../src/topic-redis/index.ts"],"sourcesContent":["import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider, RedisSubscriberProvider } from \"alepha/redis\";\nimport {\n type SubscribeCallback,\n TopicProvider,\n type UnSubscribeFn,\n} from \"alepha/topic\";\n\nconst envSchema = t.object({\n REDIS_TOPIC_PREFIX: t.text({\n default: \"topic\",\n }),\n});\n\nexport class RedisTopicProvider extends TopicProvider {\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly redisProvider = $inject(RedisProvider);\n protected readonly redisSubscriberProvider = $inject(RedisSubscriberProvider);\n\n protected readonly log = $logger();\n\n protected readonly start = $hook({\n on: \"start\",\n handler: async () => {\n const subscribers = this.subscribers();\n if (subscribers.length) {\n await Promise.all(subscribers.map((fn) => fn()));\n for (const subscriber of subscribers) {\n this.log.debug(`Subscribed to topic '${subscriber.name}'`);\n }\n }\n },\n });\n\n public prefix(queue: string): string {\n return `${this.env.REDIS_TOPIC_PREFIX}:${queue}`;\n }\n\n /**\n * Publish a message to a topic.\n */\n public async publish(topic: string, message: string): Promise<void> {\n await this.redisProvider.publisher.publish(this.prefix(topic), message);\n }\n\n /**\n * Subscribe to a topic.\n */\n public async subscribe(\n name: string,\n callback: SubscribeCallback,\n ): Promise<UnSubscribeFn> {\n const topic = this.prefix(name);\n await this.redisSubscriberProvider.subscriber.subscribe(topic, callback);\n\n return () => this.unsubscribe(name, callback);\n }\n\n /**\n * Unsubscribe from a topic.\n */\n public async unsubscribe(\n name: string,\n callback?: SubscribeCallback,\n ): Promise<void> {\n const topic = this.prefix(name);\n\n await this.redisSubscriberProvider.subscriber.unsubscribe(topic, callback);\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaTopic, TopicProvider } from \"alepha/topic\";\nimport { RedisTopicProvider } from \"./providers/RedisTopicProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/RedisTopicProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Topic that provides Redis pub/sub capabilities.\n *\n * @see {@link RedisTopicProvider}\n * @module alepha.topic.redis\n */\nexport const AlephaTopicRedis = $module({\n name: \"alepha.topic.redis\",\n services: [RedisTopicProvider],\n register: (alepha: Alepha): Alepha =>\n alepha\n .with({\n optional: true,\n provide: TopicProvider,\n use: RedisTopicProvider,\n })\n .with(AlephaTopic),\n});\n"],"mappings":";;;;;;AASA,MAAM,YAAY,EAAE,OAAO,EACzB,oBAAoB,EAAE,KAAK,EACzB,SAAS,SACV,CAAC,EACH,CAAC;AAEF,IAAa,qBAAb,cAAwC,cAAc;CACpD,AAAmB,MAAM,KAAK,UAAU;CACxC,AAAmB,SAAS,QAAQ,OAAO;CAC3C,AAAmB,gBAAgB,QAAQ,cAAc;CACzD,AAAmB,0BAA0B,QAAQ,wBAAwB;CAE7E,AAAmB,MAAM,SAAS;CAElC,AAAmB,QAAQ,MAAM;EAC/B,IAAI;EACJ,SAAS,YAAY;GACnB,MAAM,cAAc,KAAK,aAAa;AACtC,OAAI,YAAY,QAAQ;AACtB,UAAM,QAAQ,IAAI,YAAY,KAAK,OAAO,IAAI,CAAC,CAAC;AAChD,SAAK,MAAM,cAAc,YACvB,MAAK,IAAI,MAAM,wBAAwB,WAAW,KAAK,GAAG;;;EAIjE,CAAC;CAEF,AAAO,OAAO,OAAuB;AACnC,SAAO,GAAG,KAAK,IAAI,mBAAmB,GAAG;;;;;CAM3C,MAAa,QAAQ,OAAe,SAAgC;AAClE,QAAM,KAAK,cAAc,UAAU,QAAQ,KAAK,OAAO,MAAM,EAAE,QAAQ;;;;;CAMzE,MAAa,UACX,MACA,UACwB;EACxB,MAAM,QAAQ,KAAK,OAAO,KAAK;AAC/B,QAAM,KAAK,wBAAwB,WAAW,UAAU,OAAO,SAAS;AAExE,eAAa,KAAK,YAAY,MAAM,SAAS;;;;;CAM/C,MAAa,YACX,MACA,UACe;EACf,MAAM,QAAQ,KAAK,OAAO,KAAK;AAE/B,QAAM,KAAK,wBAAwB,WAAW,YAAY,OAAO,SAAS;;;;;;;;;;;;ACrD9E,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,UAAU,CAAC,mBAAmB;CAC9B,WAAW,WACT,OACG,KAAK;EACJ,UAAU;EACV,SAAS;EACT,KAAK;EACN,CAAC,CACD,KAAK,YAAY;CACvB,CAAC"}
@@ -1,45 +0,0 @@
1
- import { $action } from "alepha/server";
2
-
3
- export class ParameterController {
4
- /**
5
- - List all configs
6
- - List all parameter versions of one config
7
- - List current active parameters of one config
8
- - Create new parameter version for one config, with future date or immediate
9
- - Rollback to previous parameter version (creates new version with previous content)
10
- - Activate future parameter version immediately (creates new version with future content)
11
- */
12
-
13
- getAllConfigs = $action({
14
- path: "/configs",
15
- method: "GET",
16
- handler: async () => {
17
- // Implementation goes here
18
- },
19
- });
20
-
21
- getAllParameters = $action({
22
- path: "/parameters",
23
- schema: {
24
- // query: name
25
- },
26
- method: "GET",
27
- handler: async ({ params }) => {
28
- const { configName } = params;
29
- // Implementation goes here
30
- },
31
- });
32
-
33
- getParametersByName = $action({
34
- description: "Get current active parameters for a specific configuration.",
35
- path: "/parameters/:name",
36
- method: "GET",
37
- schema: {
38
- // response: { current: ..., next?: ... }
39
- },
40
- handler: async ({ params }) => {
41
- const { configName } = params;
42
- // Implementation goes here
43
- },
44
- });
45
- }
@@ -1,30 +0,0 @@
1
- import { type Static, t } from "alepha";
2
- import { $entity, pg } from "alepha/orm";
3
-
4
- export const parameters = $entity({
5
- name: "parameters",
6
- schema: t.object({
7
- id: pg.primaryKey(t.uuid()),
8
-
9
- createdAt: pg.createdAt(),
10
-
11
- updatedAt: pg.updatedAt(),
12
-
13
- name: t.string(),
14
-
15
- content: t.json(),
16
-
17
- tags: t.optional(t.array(t.string())),
18
-
19
- creatorId: t.optional(t.uuid()),
20
-
21
- creatorName: t.optional(t.string()),
22
-
23
- activationDate: t.datetime({
24
- description:
25
- "Optional activation date. Default to now. Must be now or later.",
26
- }),
27
- }),
28
- });
29
-
30
- export type ParameterEntity = Static<typeof parameters.schema>;
@@ -1,21 +0,0 @@
1
- import { $module } from "alepha";
2
-
3
- // ---------------------------------------------------------------------------------------------------------------------
4
-
5
- export * from "./entities/parameters.ts";
6
- export * from "./primitives/$config.ts";
7
-
8
- // ---------------------------------------------------------------------------------------------------------------------
9
-
10
- /**
11
- * Provides parameter management API endpoints for Alepha applications.
12
- *
13
- * This module includes configuration parameter storage, retrieval,
14
- * and dynamic application settings management.
15
- *
16
- * @module alepha.api.parameters
17
- */
18
- export const AlephaApiParameters = $module({
19
- name: "alepha.api.parameters",
20
- services: [],
21
- });
@@ -1,79 +0,0 @@
1
- import { createPrimitive, Primitive, type Static, type TObject } from "alepha";
2
- import type { UserAccount } from "alepha/security";
3
-
4
- /**
5
- * Creates a configuration parameter primitive for managing application settings.
6
- *
7
- * Provides type-safe, versioned configuration with schema validation, default values,
8
- * and scheduled activation. Useful for feature flags, system parameters, and runtime settings.
9
- *
10
- * @example
11
- * ```ts
12
- * class AppConfig {
13
- * features = $config({
14
- * name: "feature-flags",
15
- * schema: t.object({
16
- * enableBeta: t.boolean(),
17
- * maxUploadSize: t.number()
18
- * }),
19
- * default: { enableBeta: false, maxUploadSize: 10485760 }
20
- * });
21
- *
22
- * async updateFeatures() {
23
- * await this.features.set(
24
- * { enableBeta: true, maxUploadSize: 20971520 },
25
- * { user: currentUser, activationDate: tomorrow }
26
- * );
27
- * }
28
- * }
29
- * ```
30
- */
31
- export interface ConfigPrimitiveOptions<T extends TObject> {
32
- name?: string;
33
- description?: string;
34
- schema: T;
35
- default: Static<T>;
36
- }
37
-
38
- export class ConfigPrimitive<T extends TObject> extends Primitive<
39
- ConfigPrimitiveOptions<T>
40
- > {
41
- public get name() {
42
- return this.options.name || this.config.propertyKey;
43
- }
44
-
45
- public get schema(): T {
46
- return this.options.schema;
47
- }
48
-
49
- public get current(): Static<T> {
50
- return this.options.default;
51
- }
52
-
53
- public get next(): Static<T> | undefined {
54
- return undefined;
55
- }
56
-
57
- public get<Key extends keyof Static<T>>(key: Key): Static<T>[Key] {
58
- return this.current[key];
59
- }
60
-
61
- /**
62
- * Apply a new configuration object.
63
- */
64
- public async set(
65
- value: Static<T>,
66
- options: {
67
- user?: UserAccount;
68
- activationDate?: Date; // default to now
69
- },
70
- ): Promise<void> {}
71
-
72
- public sub(fn: (curr: Static<T>) => void): void {}
73
- }
74
-
75
- export const $config = <T extends TObject>(
76
- options: ConfigPrimitiveOptions<T>,
77
- ) => {
78
- return createPrimitive(ConfigPrimitive<T>, options);
79
- };
@@ -1,23 +0,0 @@
1
- import { t } from "alepha";
2
- import { $topic } from "alepha/topic";
3
-
4
- export class ParameterStore {
5
- protected readonly parameters = new Map<
6
- string,
7
- {
8
- current: any;
9
- next?: any;
10
- }
11
- >();
12
-
13
- public readonly onNewParameter = $topic({
14
- schema: {
15
- payload: t.object({
16
- name: t.text(),
17
- }),
18
- },
19
- handler: async ({ payload }) => {
20
- // this.synchronize( payload.name );
21
- },
22
- });
23
- }