alepha 0.15.0 → 0.15.2

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 (551) hide show
  1. package/README.md +43 -98
  2. package/dist/api/audits/index.d.ts +630 -653
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +12 -35
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +365 -358
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +12 -5
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +255 -248
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.browser.js +4 -4
  19. package/dist/api/notifications/index.browser.js.map +1 -1
  20. package/dist/api/notifications/index.d.ts +84 -78
  21. package/dist/api/notifications/index.d.ts.map +1 -1
  22. package/dist/api/notifications/index.js +14 -8
  23. package/dist/api/notifications/index.js.map +1 -1
  24. package/dist/api/parameters/index.d.ts +528 -535
  25. package/dist/api/parameters/index.d.ts.map +1 -1
  26. package/dist/api/parameters/index.js +30 -37
  27. package/dist/api/parameters/index.js.map +1 -1
  28. package/dist/api/users/index.d.ts +1221 -910
  29. package/dist/api/users/index.d.ts.map +1 -1
  30. package/dist/api/users/index.js +2556 -248
  31. package/dist/api/users/index.js.map +1 -1
  32. package/dist/api/verifications/index.d.ts +142 -136
  33. package/dist/api/verifications/index.d.ts.map +1 -1
  34. package/dist/api/verifications/index.js +12 -4
  35. package/dist/api/verifications/index.js.map +1 -1
  36. package/dist/batch/index.d.ts +142 -162
  37. package/dist/batch/index.d.ts.map +1 -1
  38. package/dist/batch/index.js +31 -44
  39. package/dist/batch/index.js.map +1 -1
  40. package/dist/bucket/index.d.ts +595 -171
  41. package/dist/bucket/index.d.ts.map +1 -1
  42. package/dist/bucket/index.js +1856 -12
  43. package/dist/bucket/index.js.map +1 -1
  44. package/dist/cache/core/index.d.ts +225 -53
  45. package/dist/cache/core/index.d.ts.map +1 -1
  46. package/dist/cache/core/index.js +213 -7
  47. package/dist/cache/core/index.js.map +1 -1
  48. package/dist/cache/redis/index.d.ts +1 -0
  49. package/dist/cache/redis/index.d.ts.map +1 -1
  50. package/dist/cache/redis/index.js +6 -2
  51. package/dist/cache/redis/index.js.map +1 -1
  52. package/dist/cli/index.d.ts +834 -226
  53. package/dist/cli/index.d.ts.map +1 -1
  54. package/dist/cli/index.js +2872 -417
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/command/index.d.ts +458 -310
  57. package/dist/command/index.d.ts.map +1 -1
  58. package/dist/command/index.js +2011 -76
  59. package/dist/command/index.js.map +1 -1
  60. package/dist/core/index.browser.js +309 -97
  61. package/dist/core/index.browser.js.map +1 -1
  62. package/dist/core/index.d.ts +796 -701
  63. package/dist/core/index.d.ts.map +1 -1
  64. package/dist/core/index.js +329 -97
  65. package/dist/core/index.js.map +1 -1
  66. package/dist/core/index.native.js +309 -97
  67. package/dist/core/index.native.js.map +1 -1
  68. package/dist/datetime/index.d.ts +59 -44
  69. package/dist/datetime/index.d.ts.map +1 -1
  70. package/dist/datetime/index.js +15 -0
  71. package/dist/datetime/index.js.map +1 -1
  72. package/dist/email/index.d.ts +314 -19
  73. package/dist/email/index.d.ts.map +1 -1
  74. package/dist/email/index.js +1852 -7
  75. package/dist/email/index.js.map +1 -1
  76. package/dist/fake/index.d.ts +5500 -5418
  77. package/dist/fake/index.d.ts.map +1 -1
  78. package/dist/fake/index.js +113 -42
  79. package/dist/fake/index.js.map +1 -1
  80. package/dist/lock/core/index.d.ts +219 -212
  81. package/dist/lock/core/index.d.ts.map +1 -1
  82. package/dist/lock/core/index.js +11 -4
  83. package/dist/lock/core/index.js.map +1 -1
  84. package/dist/lock/redis/index.d.ts.map +1 -1
  85. package/dist/logger/index.d.ts +41 -90
  86. package/dist/logger/index.d.ts.map +1 -1
  87. package/dist/logger/index.js +15 -68
  88. package/dist/logger/index.js.map +1 -1
  89. package/dist/mcp/index.d.ts +228 -230
  90. package/dist/mcp/index.d.ts.map +1 -1
  91. package/dist/mcp/index.js +32 -31
  92. package/dist/mcp/index.js.map +1 -1
  93. package/dist/orm/index.browser.js +12 -12
  94. package/dist/orm/index.browser.js.map +1 -1
  95. package/dist/orm/index.bun.js +90 -80
  96. package/dist/orm/index.bun.js.map +1 -1
  97. package/dist/orm/index.d.ts +1434 -1459
  98. package/dist/orm/index.d.ts.map +1 -1
  99. package/dist/orm/index.js +112 -130
  100. package/dist/orm/index.js.map +1 -1
  101. package/dist/queue/core/index.d.ts +262 -254
  102. package/dist/queue/core/index.d.ts.map +1 -1
  103. package/dist/queue/core/index.js +14 -6
  104. package/dist/queue/core/index.js.map +1 -1
  105. package/dist/queue/redis/index.d.ts.map +1 -1
  106. package/dist/react/auth/index.browser.js +108 -0
  107. package/dist/react/auth/index.browser.js.map +1 -0
  108. package/dist/react/auth/index.d.ts +100 -0
  109. package/dist/react/auth/index.d.ts.map +1 -0
  110. package/dist/react/auth/index.js +145 -0
  111. package/dist/react/auth/index.js.map +1 -0
  112. package/dist/react/core/index.d.ts +469 -0
  113. package/dist/react/core/index.d.ts.map +1 -0
  114. package/dist/react/core/index.js +464 -0
  115. package/dist/react/core/index.js.map +1 -0
  116. package/dist/react/form/index.d.ts +232 -0
  117. package/dist/react/form/index.d.ts.map +1 -0
  118. package/dist/react/form/index.js +432 -0
  119. package/dist/react/form/index.js.map +1 -0
  120. package/dist/react/head/index.browser.js +423 -0
  121. package/dist/react/head/index.browser.js.map +1 -0
  122. package/dist/react/head/index.d.ts +288 -0
  123. package/dist/react/head/index.d.ts.map +1 -0
  124. package/dist/react/head/index.js +465 -0
  125. package/dist/react/head/index.js.map +1 -0
  126. package/dist/react/i18n/index.d.ts +175 -0
  127. package/dist/react/i18n/index.d.ts.map +1 -0
  128. package/dist/react/i18n/index.js +224 -0
  129. package/dist/react/i18n/index.js.map +1 -0
  130. package/dist/react/router/index.browser.js +1980 -0
  131. package/dist/react/router/index.browser.js.map +1 -0
  132. package/dist/react/router/index.d.ts +2068 -0
  133. package/dist/react/router/index.d.ts.map +1 -0
  134. package/dist/react/router/index.js +4932 -0
  135. package/dist/react/router/index.js.map +1 -0
  136. package/dist/react/websocket/index.d.ts +117 -0
  137. package/dist/react/websocket/index.d.ts.map +1 -0
  138. package/dist/react/websocket/index.js +107 -0
  139. package/dist/react/websocket/index.js.map +1 -0
  140. package/dist/redis/index.bun.js +4 -0
  141. package/dist/redis/index.bun.js.map +1 -1
  142. package/dist/redis/index.d.ts +127 -130
  143. package/dist/redis/index.d.ts.map +1 -1
  144. package/dist/redis/index.js +16 -25
  145. package/dist/redis/index.js.map +1 -1
  146. package/dist/retry/index.d.ts +80 -71
  147. package/dist/retry/index.d.ts.map +1 -1
  148. package/dist/retry/index.js +11 -2
  149. package/dist/retry/index.js.map +1 -1
  150. package/dist/router/index.d.ts +6 -6
  151. package/dist/router/index.d.ts.map +1 -1
  152. package/dist/scheduler/index.d.ts +119 -28
  153. package/dist/scheduler/index.d.ts.map +1 -1
  154. package/dist/scheduler/index.js +404 -3
  155. package/dist/scheduler/index.js.map +1 -1
  156. package/dist/security/index.d.ts +642 -228
  157. package/dist/security/index.d.ts.map +1 -1
  158. package/dist/security/index.js +1579 -37
  159. package/dist/security/index.js.map +1 -1
  160. package/dist/server/auth/index.d.ts +1141 -111
  161. package/dist/server/auth/index.d.ts.map +1 -1
  162. package/dist/server/auth/index.js +1261 -25
  163. package/dist/server/auth/index.js.map +1 -1
  164. package/dist/server/cache/index.d.ts +63 -78
  165. package/dist/server/cache/index.d.ts.map +1 -1
  166. package/dist/server/cache/index.js +7 -22
  167. package/dist/server/cache/index.js.map +1 -1
  168. package/dist/server/compress/index.d.ts +13 -5
  169. package/dist/server/compress/index.d.ts.map +1 -1
  170. package/dist/server/compress/index.js +10 -2
  171. package/dist/server/compress/index.js.map +1 -1
  172. package/dist/server/cookies/index.d.ts +46 -22
  173. package/dist/server/cookies/index.d.ts.map +1 -1
  174. package/dist/server/cookies/index.js +7 -5
  175. package/dist/server/cookies/index.js.map +1 -1
  176. package/dist/server/core/index.d.ts +307 -196
  177. package/dist/server/core/index.d.ts.map +1 -1
  178. package/dist/server/core/index.js +271 -38
  179. package/dist/server/core/index.js.map +1 -1
  180. package/dist/server/cors/index.d.ts +24 -34
  181. package/dist/server/cors/index.d.ts.map +1 -1
  182. package/dist/server/cors/index.js +7 -21
  183. package/dist/server/cors/index.js.map +1 -1
  184. package/dist/server/health/index.d.ts +25 -19
  185. package/dist/server/health/index.d.ts.map +1 -1
  186. package/dist/server/health/index.js +8 -2
  187. package/dist/server/health/index.js.map +1 -1
  188. package/dist/server/helmet/index.d.ts +13 -5
  189. package/dist/server/helmet/index.d.ts.map +1 -1
  190. package/dist/server/helmet/index.js +11 -3
  191. package/dist/server/helmet/index.js.map +1 -1
  192. package/dist/server/links/index.browser.js +9 -1
  193. package/dist/server/links/index.browser.js.map +1 -1
  194. package/dist/server/links/index.d.ts +133 -128
  195. package/dist/server/links/index.d.ts.map +1 -1
  196. package/dist/server/links/index.js +24 -11
  197. package/dist/server/links/index.js.map +1 -1
  198. package/dist/server/metrics/index.d.ts +524 -4
  199. package/dist/server/metrics/index.d.ts.map +1 -1
  200. package/dist/server/metrics/index.js +4472 -7
  201. package/dist/server/metrics/index.js.map +1 -1
  202. package/dist/server/multipart/index.d.ts +15 -9
  203. package/dist/server/multipart/index.d.ts.map +1 -1
  204. package/dist/server/multipart/index.js +9 -3
  205. package/dist/server/multipart/index.js.map +1 -1
  206. package/dist/server/proxy/index.d.ts +110 -104
  207. package/dist/server/proxy/index.d.ts.map +1 -1
  208. package/dist/server/proxy/index.js +8 -2
  209. package/dist/server/proxy/index.js.map +1 -1
  210. package/dist/server/rate-limit/index.d.ts +46 -51
  211. package/dist/server/rate-limit/index.d.ts.map +1 -1
  212. package/dist/server/rate-limit/index.js +18 -55
  213. package/dist/server/rate-limit/index.js.map +1 -1
  214. package/dist/server/static/index.d.ts +181 -48
  215. package/dist/server/static/index.d.ts.map +1 -1
  216. package/dist/server/static/index.js +1848 -5
  217. package/dist/server/static/index.js.map +1 -1
  218. package/dist/server/swagger/index.d.ts +348 -53
  219. package/dist/server/swagger/index.d.ts.map +1 -1
  220. package/dist/server/swagger/index.js +1849 -6
  221. package/dist/server/swagger/index.js.map +1 -1
  222. package/dist/sms/index.d.ts +312 -18
  223. package/dist/sms/index.d.ts.map +1 -1
  224. package/dist/sms/index.js +1854 -10
  225. package/dist/sms/index.js.map +1 -1
  226. package/dist/system/index.browser.js +496 -0
  227. package/dist/system/index.browser.js.map +1 -0
  228. package/dist/system/index.d.ts +1158 -0
  229. package/dist/system/index.d.ts.map +1 -0
  230. package/dist/{file → system}/index.js +412 -20
  231. package/dist/system/index.js.map +1 -0
  232. package/dist/thread/index.d.ts +82 -73
  233. package/dist/thread/index.d.ts.map +1 -1
  234. package/dist/thread/index.js +13 -4
  235. package/dist/thread/index.js.map +1 -1
  236. package/dist/topic/core/index.d.ts +330 -323
  237. package/dist/topic/core/index.d.ts.map +1 -1
  238. package/dist/topic/core/index.js +12 -5
  239. package/dist/topic/core/index.js.map +1 -1
  240. package/dist/topic/redis/index.d.ts +6 -6
  241. package/dist/topic/redis/index.d.ts.map +1 -1
  242. package/dist/vite/index.d.ts +163 -5825
  243. package/dist/vite/index.d.ts.map +1 -1
  244. package/dist/vite/index.js +130 -477
  245. package/dist/vite/index.js.map +1 -1
  246. package/dist/websocket/index.browser.js +3 -3
  247. package/dist/websocket/index.browser.js.map +1 -1
  248. package/dist/websocket/index.d.ts +287 -283
  249. package/dist/websocket/index.d.ts.map +1 -1
  250. package/dist/websocket/index.js +15 -11
  251. package/dist/websocket/index.js.map +1 -1
  252. package/package.json +86 -17
  253. package/src/api/audits/index.ts +10 -33
  254. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  255. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  256. package/src/api/files/controllers/FileController.spec.ts +1 -1
  257. package/src/api/files/index.ts +10 -3
  258. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  259. package/src/api/files/services/FileService.spec.ts +1 -1
  260. package/src/api/jobs/index.ts +10 -3
  261. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  262. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  263. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  264. package/src/api/keys/index.ts +49 -0
  265. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  266. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  267. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  268. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  269. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  270. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  271. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  272. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  273. package/src/api/keys/services/ApiKeyService.ts +306 -0
  274. package/src/api/logs/TODO.md +52 -0
  275. package/src/api/notifications/index.ts +10 -4
  276. package/src/api/parameters/index.ts +9 -30
  277. package/src/api/parameters/primitives/$config.ts +12 -4
  278. package/src/api/parameters/services/ConfigStore.ts +9 -3
  279. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  280. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  281. package/src/api/users/index.ts +14 -3
  282. package/src/api/users/primitives/$realm.ts +33 -5
  283. package/src/api/users/providers/RealmProvider.ts +1 -12
  284. package/src/api/users/services/SessionService.ts +1 -11
  285. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  286. package/src/api/verifications/index.ts +10 -4
  287. package/src/batch/index.ts +9 -36
  288. package/src/batch/primitives/$batch.ts +0 -8
  289. package/src/batch/providers/BatchProvider.ts +29 -2
  290. package/src/bucket/__tests__/shared.ts +1 -1
  291. package/src/bucket/index.ts +13 -6
  292. package/src/bucket/primitives/$bucket.ts +1 -1
  293. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  294. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  295. package/src/cache/core/__tests__/shared.ts +30 -0
  296. package/src/cache/core/index.ts +11 -6
  297. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  298. package/src/cache/core/providers/CacheProvider.ts +17 -0
  299. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  300. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  301. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  302. package/src/cli/apps/AlephaCli.ts +3 -16
  303. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -2
  304. package/src/cli/atoms/appEntryOptions.ts +13 -0
  305. package/src/cli/atoms/buildOptions.ts +1 -1
  306. package/src/cli/atoms/changelogOptions.ts +1 -1
  307. package/src/cli/commands/build.ts +64 -52
  308. package/src/cli/commands/db.ts +17 -11
  309. package/src/cli/commands/deploy.ts +1 -1
  310. package/src/cli/commands/dev.ts +13 -49
  311. package/src/cli/commands/gen/env.ts +6 -3
  312. package/src/cli/commands/gen/openapi.ts +5 -2
  313. package/src/cli/commands/init.spec.ts +544 -0
  314. package/src/cli/commands/init.ts +101 -58
  315. package/src/cli/commands/lint.ts +8 -2
  316. package/src/cli/commands/typecheck.ts +11 -0
  317. package/src/cli/defineConfig.ts +9 -0
  318. package/src/cli/index.ts +2 -1
  319. package/src/cli/providers/AppEntryProvider.ts +131 -0
  320. package/src/cli/providers/ViteBuildProvider.ts +40 -0
  321. package/src/cli/providers/ViteDevServerProvider.ts +378 -0
  322. package/src/cli/services/AlephaCliUtils.ts +39 -93
  323. package/src/cli/services/PackageManagerUtils.ts +140 -17
  324. package/src/cli/services/ProjectScaffolder.ts +169 -101
  325. package/src/cli/services/ViteUtils.ts +82 -0
  326. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +41 -28
  327. package/src/cli/{assets → templates}/apiHelloControllerTs.ts +2 -1
  328. package/src/cli/{assets → templates}/biomeJson.ts +2 -1
  329. package/src/cli/{assets → templates}/dummySpecTs.ts +2 -1
  330. package/src/cli/{assets → templates}/editorconfig.ts +2 -1
  331. package/src/cli/templates/gitignore.ts +39 -0
  332. package/src/cli/{assets → templates}/mainBrowserTs.ts +2 -1
  333. package/src/cli/templates/mainCss.ts +33 -0
  334. package/src/cli/templates/mainServerTs.ts +33 -0
  335. package/src/cli/{assets → templates}/tsconfigJson.ts +2 -1
  336. package/src/cli/templates/webAppRouterTs.ts +50 -0
  337. package/src/cli/templates/webHelloComponentTsx.ts +20 -0
  338. package/src/command/helpers/Runner.spec.ts +4 -0
  339. package/src/command/helpers/Runner.ts +3 -21
  340. package/src/command/index.ts +12 -4
  341. package/src/command/providers/CliProvider.spec.ts +1067 -0
  342. package/src/command/providers/CliProvider.ts +203 -40
  343. package/src/core/Alepha.ts +3 -9
  344. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  345. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  346. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  347. package/src/core/index.shared.ts +1 -0
  348. package/src/core/index.ts +20 -0
  349. package/src/core/primitives/$module.ts +12 -0
  350. package/src/core/providers/EventManager.spec.ts +0 -71
  351. package/src/core/providers/EventManager.ts +3 -15
  352. package/src/core/providers/Json.ts +2 -14
  353. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +257 -0
  354. package/src/core/providers/KeylessJsonSchemaCodec.ts +396 -14
  355. package/src/core/providers/SchemaValidator.spec.ts +236 -0
  356. package/src/datetime/index.ts +15 -0
  357. package/src/email/index.ts +10 -5
  358. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  359. package/src/email/providers/LocalEmailProvider.ts +1 -1
  360. package/src/fake/__tests__/keyName.example.ts +1 -1
  361. package/src/fake/__tests__/keyName.spec.ts +5 -5
  362. package/src/fake/index.ts +9 -6
  363. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  364. package/src/fake/providers/FakeProvider.ts +133 -19
  365. package/src/lock/core/index.ts +11 -4
  366. package/src/logger/index.ts +17 -66
  367. package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
  368. package/src/mcp/errors/McpError.ts +30 -0
  369. package/src/mcp/index.ts +13 -27
  370. package/src/mcp/transports/SseMcpTransport.ts +6 -7
  371. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  372. package/src/orm/index.browser.ts +2 -2
  373. package/src/orm/index.bun.ts +4 -2
  374. package/src/orm/index.ts +21 -47
  375. package/src/orm/providers/DrizzleKitProvider.ts +3 -5
  376. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -0
  377. package/src/orm/services/Repository.ts +18 -3
  378. package/src/queue/core/index.ts +14 -6
  379. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  380. package/src/react/auth/hooks/useAuth.ts +32 -0
  381. package/src/react/auth/index.browser.ts +13 -0
  382. package/src/react/auth/index.shared.ts +2 -0
  383. package/src/react/auth/index.ts +48 -0
  384. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  385. package/src/react/auth/services/ReactAuth.ts +135 -0
  386. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  387. package/src/react/core/components/ClientOnly.tsx +49 -0
  388. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  389. package/src/react/core/contexts/AlephaContext.ts +7 -0
  390. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  391. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  392. package/src/react/core/hooks/useAction.ts +480 -0
  393. package/src/react/core/hooks/useAlepha.ts +26 -0
  394. package/src/react/core/hooks/useClient.ts +17 -0
  395. package/src/react/core/hooks/useEvents.ts +51 -0
  396. package/src/react/core/hooks/useInject.ts +12 -0
  397. package/src/react/core/hooks/useStore.ts +52 -0
  398. package/src/react/core/index.ts +90 -0
  399. package/src/react/form/components/FormState.tsx +17 -0
  400. package/src/react/form/errors/FormValidationError.ts +18 -0
  401. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  402. package/src/react/form/hooks/useForm.ts +47 -0
  403. package/src/react/form/hooks/useFormState.ts +130 -0
  404. package/src/react/form/index.ts +44 -0
  405. package/src/react/form/services/FormModel.ts +614 -0
  406. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  407. package/src/react/head/helpers/SeoExpander.ts +142 -0
  408. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  409. package/src/react/head/hooks/useHead.ts +62 -0
  410. package/src/react/head/index.browser.ts +26 -0
  411. package/src/react/head/index.ts +44 -0
  412. package/src/react/head/interfaces/Head.ts +105 -0
  413. package/src/react/head/primitives/$head.ts +25 -0
  414. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  415. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  416. package/src/react/head/providers/HeadProvider.ts +168 -0
  417. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  418. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  419. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  420. package/src/react/i18n/components/Localize.tsx +35 -0
  421. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  422. package/src/react/i18n/hooks/useI18n.ts +18 -0
  423. package/src/react/i18n/index.ts +41 -0
  424. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  425. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  426. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  427. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  428. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  429. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  430. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  431. package/src/react/router/components/ErrorViewer.tsx +872 -0
  432. package/src/react/router/components/Link.tsx +23 -0
  433. package/src/react/router/components/NestedView.tsx +223 -0
  434. package/src/react/router/components/NotFound.tsx +30 -0
  435. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  436. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  437. package/src/react/router/errors/Redirection.ts +28 -0
  438. package/src/react/router/hooks/useActive.ts +52 -0
  439. package/src/react/router/hooks/useQueryParams.ts +63 -0
  440. package/src/react/router/hooks/useRouter.ts +20 -0
  441. package/src/react/router/hooks/useRouterState.ts +11 -0
  442. package/src/react/router/index.browser.ts +45 -0
  443. package/src/react/router/index.shared.ts +19 -0
  444. package/src/react/router/index.ts +142 -0
  445. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  446. package/src/react/router/primitives/$page.spec.tsx +708 -0
  447. package/src/react/router/primitives/$page.ts +497 -0
  448. package/src/react/router/providers/ReactBrowserProvider.ts +309 -0
  449. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  450. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  451. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  452. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  453. package/src/react/router/providers/ReactServerProvider.ts +558 -0
  454. package/src/react/router/providers/ReactServerTemplateProvider.ts +979 -0
  455. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  456. package/src/react/router/services/ReactPageServerService.ts +48 -0
  457. package/src/react/router/services/ReactPageService.ts +27 -0
  458. package/src/react/router/services/ReactRouter.ts +262 -0
  459. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  460. package/src/react/websocket/index.ts +7 -0
  461. package/src/redis/__tests__/redis.spec.ts +13 -0
  462. package/src/redis/index.ts +9 -25
  463. package/src/redis/providers/BunRedisProvider.ts +9 -0
  464. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  465. package/src/redis/providers/RedisProvider.ts +16 -0
  466. package/src/retry/index.ts +11 -2
  467. package/src/router/index.ts +15 -0
  468. package/src/scheduler/index.ts +11 -2
  469. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  470. package/src/security/__tests__/ServerSecurityProvider.spec.ts +13 -5
  471. package/src/security/index.ts +15 -10
  472. package/src/security/interfaces/IssuerResolver.ts +27 -0
  473. package/src/security/primitives/$issuer.ts +55 -0
  474. package/src/security/providers/SecurityProvider.ts +179 -0
  475. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  476. package/src/security/providers/ServerSecurityProvider.ts +36 -22
  477. package/src/server/auth/index.ts +12 -7
  478. package/src/server/cache/index.ts +7 -22
  479. package/src/server/compress/index.ts +10 -2
  480. package/src/server/cookies/index.ts +7 -5
  481. package/src/server/cookies/primitives/$cookie.ts +33 -11
  482. package/src/server/core/index.ts +17 -7
  483. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  484. package/src/server/core/primitives/$action.spec.ts +1 -1
  485. package/src/server/core/primitives/$action.ts +8 -3
  486. package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
  487. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
  488. package/src/server/core/providers/NodeHttpServerProvider.ts +77 -22
  489. package/src/server/core/providers/ServerLoggerProvider.ts +2 -2
  490. package/src/server/core/providers/ServerProvider.ts +9 -12
  491. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  492. package/src/server/core/services/ServerRequestParser.ts +306 -13
  493. package/src/server/cors/index.ts +7 -21
  494. package/src/server/cors/primitives/$cors.ts +6 -2
  495. package/src/server/health/index.ts +8 -2
  496. package/src/server/helmet/index.ts +11 -3
  497. package/src/server/links/atoms/apiLinksAtom.ts +7 -0
  498. package/src/server/links/index.browser.ts +2 -0
  499. package/src/server/links/index.ts +13 -6
  500. package/src/server/metrics/index.ts +10 -3
  501. package/src/server/multipart/index.ts +9 -3
  502. package/src/server/proxy/index.ts +8 -2
  503. package/src/server/rate-limit/index.ts +21 -25
  504. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  505. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  506. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  507. package/src/server/static/index.ts +8 -2
  508. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  509. package/src/server/swagger/index.ts +9 -4
  510. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  511. package/src/sms/index.ts +9 -5
  512. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  513. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  514. package/src/system/index.browser.ts +11 -0
  515. package/src/system/index.ts +62 -0
  516. package/src/{file → system}/providers/FileSystemProvider.ts +16 -0
  517. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  518. package/src/system/providers/MemoryShellProvider.ts +164 -0
  519. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  520. package/src/{file → system}/providers/NodeFileSystemProvider.ts +36 -0
  521. package/src/system/providers/NodeShellProvider.ts +184 -0
  522. package/src/system/providers/ShellProvider.ts +74 -0
  523. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  524. package/src/thread/index.ts +11 -2
  525. package/src/topic/core/index.ts +12 -5
  526. package/src/vite/index.ts +3 -2
  527. package/src/vite/tasks/buildClient.ts +2 -8
  528. package/src/vite/tasks/buildServer.ts +84 -21
  529. package/src/vite/tasks/copyAssets.ts +5 -4
  530. package/src/vite/tasks/generateSitemap.ts +64 -23
  531. package/src/vite/tasks/index.ts +0 -2
  532. package/src/vite/tasks/prerenderPages.ts +49 -24
  533. package/src/websocket/index.ts +12 -8
  534. package/dist/file/index.d.ts +0 -839
  535. package/dist/file/index.d.ts.map +0 -1
  536. package/dist/file/index.js.map +0 -1
  537. package/src/cli/assets/indexHtml.ts +0 -15
  538. package/src/cli/assets/mainServerTs.ts +0 -24
  539. package/src/cli/assets/webAppRouterTs.ts +0 -15
  540. package/src/cli/assets/webHelloComponentTsx.ts +0 -16
  541. package/src/cli/commands/format.ts +0 -23
  542. package/src/file/index.ts +0 -43
  543. package/src/vite/helpers/boot.ts +0 -117
  544. package/src/vite/plugins/viteAlephaDev.ts +0 -177
  545. package/src/vite/tasks/devServer.ts +0 -71
  546. package/src/vite/tasks/runAlepha.ts +0 -270
  547. /package/dist/orm/{chunk-DtkW-qnP.js → chunk-DH6iiROE.js} +0 -0
  548. /package/src/cli/{assets → templates}/apiIndexTs.ts +0 -0
  549. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  550. /package/src/{file → system}/errors/FileError.ts +0 -0
  551. /package/src/{file → system}/services/FileDetector.ts +0 -0
@@ -19,12 +19,12 @@ declare class ServerMultipartProvider {
19
19
  cleanup: () => Promise<void>;
20
20
  }>;
21
21
  /**
22
- * This is a legacy code, previously we used "busboy" to parse multipart in Node.js environment.
23
- * Now we rely on Web Request's formData() method, which is supported in modern Node.js versions.
24
- * However, we still need to create temporary files for uploaded files to provide a consistent File-like interface.
25
- *
26
- * TODO: In future, we might want to refactor this to avoid using temporary files if not necessary?
27
- */
22
+ * This is a legacy code, previously we used "busboy" to parse multipart in Node.js environment.
23
+ * Now we rely on Web Request's formData() method, which is supported in modern Node.js versions.
24
+ * However, we still need to create temporary files for uploaded files to provide a consistent File-like interface.
25
+ *
26
+ * TODO: In future, we might want to refactor this to avoid using temporary files if not necessary?
27
+ */
28
28
  protected createHybridFile(file: Blob, fieldName: string): Promise<HybridFile>;
29
29
  }
30
30
  interface HybridFile extends FileLike {
@@ -38,10 +38,16 @@ interface HybridFile extends FileLike {
38
38
  //#endregion
39
39
  //#region ../../src/server/multipart/index.d.ts
40
40
  /**
41
- * This module provides support for handling multipart/form-data requests.
42
- * It allows to parse body data containing t.file().
41
+ * | type | quality | stability |
42
+ * |------|---------|-----------|
43
+ * | backend | standard | stable |
44
+ *
45
+ * Multipart form data handling for file uploads.
46
+ *
47
+ * **Features:**
48
+ * - File upload parsing
49
+ * - Form field extraction
43
50
  *
44
- * @see {@link ServerMultipartProvider}
45
51
  * @module alepha.server.multipart
46
52
  */
47
53
  declare const AlephaServerMultipart: alepha1.Service<alepha1.Module>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/multipart/providers/ServerMultipartProvider.ts","../../../src/server/multipart/index.ts"],"mappings":";;;;;;cAmCa,uBAAA;EAAA,mBAAA,MAAA,EACc,MAAA;EAAA,mBAAA,GAAA;IAAA,sBAAA;IAAA,2BAAA;IAAA,2BAAA;EAAA;EAAA,mBAAA,GAAA,EAAA,cAAA,CAEH,MAAA;EAAA,SAAA,SAAA,EAAA,OAAA,CAEG,aAAA;EAAA,SAAA,UAAA,EAAA,OAAA,CAuEC,aAAA;EAAA,2BAAA,KAAA,EAWjB,WAAA,EAAA,OAAA,EACE,OAAA,GACR,OAAA;IAAA,IAAA,EACK,MAAA;IAAA,OAAA,QACS,OAAA;EAAA;EAAA;;;;;;AA8KlB;EA9KkB,UAAA,iBAAA,IAAA,EAyHT,IAAA,EAAA,SAAA,WAEL,OAAA,CAAQ,UAAA;AAAA;AAAA,UAqDH,UAAA,SAAmB,QAAA;EAAA,OAAA,IAChB,OAAA;EAAA,MAAA;IAAA,OAAA;IAAA,IAAA;IAAA,OAAA;EAAA;AAAA;;;;AC9Rb;;;;;;cAAa,qBAAA,EAAqB,OAAA,CAAA,OAAA,CAGhC,OAAA,CAHgC,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/multipart/providers/ServerMultipartProvider.ts","../../../src/server/multipart/index.ts"],"mappings":";;;;;;cAmCa,uBAAA;EAAA,mBACQ,MAAA,EAAM,MAAA;EAAA,mBACN,GAAA;;;;;qBACA,GAAA,EAFM,cAAA,CAEH,MAAA;EAAA,SAEN,SAAA,EAFM,OAAA,CAEG,aAAA;EAAA,SAuET,UAAA,EAvES,OAAA,CAuEC,aAAA;EAUb,0BAAA,CACX,KAAA,EAAO,WAAA,EACP,OAAA,EAAS,OAAA,GACR,OAAA;IACD,IAAA,EAAM,MAAA;IACN,OAAA,QAAe,OAAA;EAAA;EAAA;;;;;;;EAAA,UAwHD,gBAAA,CACd,IAAA,EAAM,IAAA,EACN,SAAA,WACC,OAAA,CAAQ,UAAA;AAAA;AAAA,UAqDH,UAAA,SAAmB,QAAA;EAC3B,OAAA,IAAW,OAAA;EACX,MAAA;IACE,OAAA;IACA,IAAA;IACA,OAAA;EAAA;AAAA;;;;;;;;AAhRJ;;;;;;;;cCZa,qBAAA,EAAqB,OAAA,CAAA,OAAA,CAGhC,OAAA,CAHgC,MAAA"}
@@ -192,10 +192,16 @@ var ServerMultipartProvider = class {
192
192
  //#endregion
193
193
  //#region ../../src/server/multipart/index.ts
194
194
  /**
195
- * This module provides support for handling multipart/form-data requests.
196
- * It allows to parse body data containing t.file().
195
+ * | type | quality | stability |
196
+ * |------|---------|-----------|
197
+ * | backend | standard | stable |
198
+ *
199
+ * Multipart form data handling for file uploads.
200
+ *
201
+ * **Features:**
202
+ * - File upload parsing
203
+ * - Form field extraction
197
204
  *
198
- * @see {@link ServerMultipartProvider}
199
205
  * @module alepha.server.multipart
200
206
  */
201
207
  const AlephaServerMultipart = $module({
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["WebStream"],"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(\n request.raw.node.req,\n ) as unknown 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
+ {"version":3,"file":"index.js","names":["WebStream"],"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(\n request.raw.node.req,\n ) as unknown 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 * | type | quality | stability |\n * |------|---------|-----------|\n * | backend | standard | stable |\n *\n * Multipart form data handling for file uploads.\n *\n * **Features:**\n * - File upload parsing\n * - Form field extraction\n *\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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"}
@@ -93,118 +93,118 @@ declare const $proxy: {
93
93
  };
94
94
  type ProxyPrimitiveOptions = {
95
95
  /**
96
- * Path pattern to match for proxying requests.
97
- *
98
- * Supports wildcards and path parameters:
99
- * - `/api/*` - Matches all paths starting with `/api/`
100
- * - `/api/v1/*` - Matches all paths starting with `/api/v1/`
101
- * - `/users/:id` - Matches `/users/123`, `/users/abc`, etc.
102
- *
103
- * @example "/api/*"
104
- * @example "/secure/admin/*"
105
- * @example "/users/:id/posts"
106
- */
96
+ * Path pattern to match for proxying requests.
97
+ *
98
+ * Supports wildcards and path parameters:
99
+ * - `/api/*` - Matches all paths starting with `/api/`
100
+ * - `/api/v1/*` - Matches all paths starting with `/api/v1/`
101
+ * - `/users/:id` - Matches `/users/123`, `/users/abc`, etc.
102
+ *
103
+ * @example "/api/*"
104
+ * @example "/secure/admin/*"
105
+ * @example "/users/:id/posts"
106
+ */
107
107
  path: string;
108
108
  /**
109
- * Target URL to which matching requests should be forwarded.
110
- *
111
- * Can be either:
112
- * - **Static string**: A fixed URL like `"https://api.example.com"`
113
- * - **Dynamic function**: A function that returns the URL, enabling runtime target resolution
114
- *
115
- * The target URL will be combined with the remaining path from the original request.
116
- *
117
- * @example "https://api.example.com"
118
- * @example () => process.env.API_URL || "http://localhost:3001"
119
- */
109
+ * Target URL to which matching requests should be forwarded.
110
+ *
111
+ * Can be either:
112
+ * - **Static string**: A fixed URL like `"https://api.example.com"`
113
+ * - **Dynamic function**: A function that returns the URL, enabling runtime target resolution
114
+ *
115
+ * The target URL will be combined with the remaining path from the original request.
116
+ *
117
+ * @example "https://api.example.com"
118
+ * @example () => process.env.API_URL || "http://localhost:3001"
119
+ */
120
120
  target: string | (() => string);
121
121
  /**
122
- * Whether this proxy is disabled.
123
- *
124
- * When `true`, requests matching the path will not be proxied and will be handled
125
- * by other routes or return 404. Useful for feature toggles or conditional proxying.
126
- *
127
- * @default false
128
- * @example !process.env.ENABLE_PROXY
129
- */
122
+ * Whether this proxy is disabled.
123
+ *
124
+ * When `true`, requests matching the path will not be proxied and will be handled
125
+ * by other routes or return 404. Useful for feature toggles or conditional proxying.
126
+ *
127
+ * @default false
128
+ * @example !process.env.ENABLE_PROXY
129
+ */
130
130
  disabled?: boolean;
131
131
  /**
132
- * Hook called before forwarding the request to the target server.
133
- *
134
- * Use this to:
135
- * - Add authentication headers
136
- * - Modify request headers or body
137
- * - Add request tracking/logging
138
- * - Transform the request before forwarding
139
- *
140
- * @param request - The original incoming server request
141
- * @param proxyRequest - The request that will be sent to the target (modifiable)
142
- *
143
- * @example
144
- * ```ts
145
- * beforeRequest: async (request, proxyRequest) => {
146
- * proxyRequest.headers = {
147
- * ...proxyRequest.headers,
148
- * 'Authorization': `Bearer ${await getToken()}`,
149
- * 'X-Request-ID': generateRequestId()
150
- * };
151
- * }
152
- * ```
153
- */
132
+ * Hook called before forwarding the request to the target server.
133
+ *
134
+ * Use this to:
135
+ * - Add authentication headers
136
+ * - Modify request headers or body
137
+ * - Add request tracking/logging
138
+ * - Transform the request before forwarding
139
+ *
140
+ * @param request - The original incoming server request
141
+ * @param proxyRequest - The request that will be sent to the target (modifiable)
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * beforeRequest: async (request, proxyRequest) => {
146
+ * proxyRequest.headers = {
147
+ * ...proxyRequest.headers,
148
+ * 'Authorization': `Bearer ${await getToken()}`,
149
+ * 'X-Request-ID': generateRequestId()
150
+ * };
151
+ * }
152
+ * ```
153
+ */
154
154
  beforeRequest?: (request: ServerRequest, proxyRequest: RequestInit) => Async<void>;
155
155
  /**
156
- * Hook called after receiving the response from the target server.
157
- *
158
- * Use this to:
159
- * - Log response details for monitoring
160
- * - Add custom headers to the response
161
- * - Transform response data
162
- * - Handle error responses
163
- *
164
- * @param request - The original incoming server request
165
- * @param proxyResponse - The response received from the target server
166
- *
167
- * @example
168
- * ```ts
169
- * afterResponse: async (request, proxyResponse) => {
170
- * console.log(`Proxy ${request.method} ${request.url} -> ${proxyResponse.status}`);
171
- *
172
- * if (!proxyResponse.ok) {
173
- * await logError(`Proxy error: ${proxyResponse.status}`, { request, response: proxyResponse });
174
- * }
175
- * }
176
- * ```
177
- */
156
+ * Hook called after receiving the response from the target server.
157
+ *
158
+ * Use this to:
159
+ * - Log response details for monitoring
160
+ * - Add custom headers to the response
161
+ * - Transform response data
162
+ * - Handle error responses
163
+ *
164
+ * @param request - The original incoming server request
165
+ * @param proxyResponse - The response received from the target server
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * afterResponse: async (request, proxyResponse) => {
170
+ * console.log(`Proxy ${request.method} ${request.url} -> ${proxyResponse.status}`);
171
+ *
172
+ * if (!proxyResponse.ok) {
173
+ * await logError(`Proxy error: ${proxyResponse.status}`, { request, response: proxyResponse });
174
+ * }
175
+ * }
176
+ * ```
177
+ */
178
178
  afterResponse?: (request: ServerRequest, proxyResponse: Response) => Async<void>;
179
179
  /**
180
- * Function to rewrite the URL before sending to the target server.
181
- *
182
- * Use this to:
183
- * - Remove or add path prefixes
184
- * - Transform path parameters
185
- * - Modify query parameters
186
- * - Change the URL structure entirely
187
- *
188
- * The function receives a mutable URL object and should modify it in-place.
189
- *
190
- * @param url - The URL object to modify (mutable)
191
- *
192
- * @example
193
- * ```ts
194
- * // Remove /api prefix when forwarding
195
- * rewrite: (url) => {
196
- * url.pathname = url.pathname.replace('/api', '');
197
- * }
198
- * ```
199
- *
200
- * @example
201
- * ```ts
202
- * // Add version prefix
203
- * rewrite: (url) => {
204
- * url.pathname = `/v2${url.pathname}`;
205
- * }
206
- * ```
207
- */
180
+ * Function to rewrite the URL before sending to the target server.
181
+ *
182
+ * Use this to:
183
+ * - Remove or add path prefixes
184
+ * - Transform path parameters
185
+ * - Modify query parameters
186
+ * - Change the URL structure entirely
187
+ *
188
+ * The function receives a mutable URL object and should modify it in-place.
189
+ *
190
+ * @param url - The URL object to modify (mutable)
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * // Remove /api prefix when forwarding
195
+ * rewrite: (url) => {
196
+ * url.pathname = url.pathname.replace('/api', '');
197
+ * }
198
+ * ```
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * // Add version prefix
203
+ * rewrite: (url) => {
204
+ * url.pathname = `/v2${url.pathname}`;
205
+ * }
206
+ * ```
207
+ */
208
208
  rewrite?: (url: URL) => void;
209
209
  };
210
210
  declare class ProxyPrimitive extends Primitive<ProxyPrimitiveOptions> {}
@@ -222,9 +222,15 @@ declare class ServerProxyProvider {
222
222
  //#endregion
223
223
  //#region ../../src/server/proxy/index.d.ts
224
224
  /**
225
- * Plugin for Alepha that provides a proxy server functionality.
225
+ * | type | quality | stability |
226
+ * |------|---------|-----------|
227
+ * | backend | standard | stable |
228
+ *
229
+ * Reverse proxy routing.
230
+ *
231
+ * **Features:**
232
+ * - Proxy configuration and routing
226
233
  *
227
- * @see {@link $proxy}
228
234
  * @module alepha.server.proxy
229
235
  */
230
236
  declare const AlephaServerProxy: alepha1.Service<alepha1.Module>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/proxy/primitives/$proxy.ts","../../../src/server/proxy/providers/ServerProxyProvider.ts","../../../src/server/proxy/index.ts"],"mappings":";;;;;;;AAsFA;;;;;;AAIA;;;;;;;;;AAmIA;;;;AClNA;;;;;;;;;;;;;ACOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cFoEa,MAAA;EAAA,CAAA,OAAA,EAAmB,qBAAA,GAAwB,cAAA;EAAA;;KAI5C,qBAAA;EAAA;;;;;;;;;AAmIZ;;;EAnIY,IAAA;EAAA;;;;;;;;;AAmIZ;;;EAnIY,MAAA;EAAA;;;;;;;;;EAAA,QAAA;EAAA;;;;;;;;;AAmIZ;;;;AClNA;;;;;;;;;;ED+EY,aAAA,IAAA,OAAA,EAgEC,aAAA,EAAA,YAAA,EACK,WAAA,KACX,KAAA;EAAA;;;;;;AAiEP;;;;AClNA;;;;;;;;;;;;;EDiJO,aAAA,IAAA,OAAA,EA0BM,aAAA,EAAA,aAAA,EACM,QAAA,KACZ,KAAA;EAAA;;;AAqCP;;;;AClNA;;;;;;;;;;;;;ACOA;;;;;;;;;EFsKO,OAAA,IAAA,GAAA,EA+BW,GAAA;AAAA;AAAA,cAML,cAAA,SAAuB,SAAA,CAAU,qBAAA;;;cClNjC,mBAAA;EAAA,mBAAA,GAAA,EAAmB,cAAA,CACR,MAAA;EAAA,mBAAA,cAAA,EACW,oBAAA;EAAA,mBAAA,MAAA,EACR,MAAA;EAAA,mBAAA,SAAA,EAAA,OAAA,CAEG,aAAA;EAAA,YAAA,OAAA,EASA,qBAAA;EAAA,mBAAA,MAAA,UAAA,OAAA,EA6BjB,IAAA,CAAK,qBAAA,YACb,aAAA;EAAA,QAAA,iBAAA;AAAA;;;;ACrCL;;;;;cAAa,iBAAA,EAAiB,OAAA,CAAA,OAAA,CAI5B,OAAA,CAJ4B,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/proxy/primitives/$proxy.ts","../../../src/server/proxy/providers/ServerProxyProvider.ts","../../../src/server/proxy/index.ts"],"mappings":";;;;;;;;;;AAsFA;;;;;;;;;;;;;;;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmIA;;;;;;;;AClNA;;;;;;;;;;;;;;;;;;;;;;;cD2Ea,MAAA;EAAA,UAAmB,qBAAA,GAAwB,cAAA;EAAA;;KAI5C,qBAAA;ECpCR;;;;;;;;AC9BJ;;;;EF+EE,IAAA;;;;;;;;;;;;;EAcA,MAAA;;;;;;;;;;EAWA,QAAA;;;;;;;;;;;;;;;;;;;;;;;;EAyBA,aAAA,IACE,OAAA,EAAS,aAAA,EACT,YAAA,EAAc,WAAA,KACX,KAAA;;;;;;;;;;;;;;;;;;;;;;;;EAyBL,aAAA,IACE,OAAA,EAAS,aAAA,EACT,aAAA,EAAe,QAAA,KACZ,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BL,OAAA,IAAW,GAAA,EAAK,GAAA;AAAA;AAAA,cAML,cAAA,SAAuB,SAAA,CAAU,qBAAA;;;cClNjC,mBAAA;EAAA,mBACQ,GAAA,EADW,cAAA,CACR,MAAA;EAAA,mBACH,cAAA,EAAc,oBAAA;EAAA,mBACd,MAAA,EAAM,MAAA;EAAA,mBAEN,SAAA,EAFM,OAAA,CAEG,aAAA;EASrB,WAAA,CAAY,OAAA,EAAS,qBAAA;EA2BrB,kBAAA,CACL,MAAA,UACA,OAAA,EAAS,IAAA,CAAK,qBAAA,YACb,aAAA;EAAA,QAkDK,iBAAA;AAAA;;;;;;;ADnBV;;;;;;;;cE9Da,iBAAA,EAAiB,OAAA,CAAA,OAAA,CAI5B,OAAA,CAJ4B,MAAA"}
@@ -167,9 +167,15 @@ var ServerProxyProvider = class {
167
167
  //#endregion
168
168
  //#region ../../src/server/proxy/index.ts
169
169
  /**
170
- * Plugin for Alepha that provides a proxy server functionality.
170
+ * | type | quality | stability |
171
+ * |------|---------|-----------|
172
+ * | backend | standard | stable |
173
+ *
174
+ * Reverse proxy routing.
175
+ *
176
+ * **Features:**
177
+ * - Proxy configuration and routing
171
178
  *
172
- * @see {@link $proxy}
173
179
  * @module alepha.server.proxy
174
180
  */
175
181
  const AlephaServerProxy = $module({
@@ -1 +1 @@
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 unknown 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
+ {"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 unknown 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 * | type | quality | stability |\n * |------|---------|-----------|\n * | backend | standard | stable |\n *\n * Reverse proxy routing.\n *\n * **Features:**\n * - Proxy configuration and routing\n *\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;;;;;;;;;;;;;;;;;;;AC9FpC,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,YAAY,CAAC,OAAO;CACpB,UAAU,CAAC,cAAc,oBAAoB;CAC9C,CAAC"}