alepha 0.13.0 → 0.13.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 (461) hide show
  1. package/README.md +1 -1
  2. package/dist/api-files/index.d.ts +28 -91
  3. package/dist/api-files/index.js +10 -755
  4. package/dist/api-files/index.js.map +1 -1
  5. package/dist/api-jobs/index.d.ts +67 -67
  6. package/dist/api-jobs/index.js +13 -13
  7. package/dist/api-jobs/index.js.map +1 -1
  8. package/dist/api-notifications/index.d.ts +129 -146
  9. package/dist/api-notifications/index.js +17 -39
  10. package/dist/api-notifications/index.js.map +1 -1
  11. package/dist/api-parameters/index.d.ts +21 -22
  12. package/dist/api-parameters/index.js +22 -22
  13. package/dist/api-parameters/index.js.map +1 -1
  14. package/dist/api-users/index.d.ts +224 -2001
  15. package/dist/api-users/index.js +914 -4787
  16. package/dist/api-users/index.js.map +1 -1
  17. package/dist/api-verifications/index.d.ts +96 -96
  18. package/dist/batch/index.d.ts +13 -13
  19. package/dist/batch/index.js +8 -8
  20. package/dist/batch/index.js.map +1 -1
  21. package/dist/bucket/index.d.ts +14 -14
  22. package/dist/bucket/index.js +12 -12
  23. package/dist/bucket/index.js.map +1 -1
  24. package/dist/cache/index.d.ts +11 -11
  25. package/dist/cache/index.js +9 -9
  26. package/dist/cache/index.js.map +1 -1
  27. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  28. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  29. package/dist/cli/index.d.ts +31 -37
  30. package/dist/cli/index.js +152 -83
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/command/index.d.ts +19 -19
  33. package/dist/command/index.js +25 -25
  34. package/dist/command/index.js.map +1 -1
  35. package/dist/core/index.browser.js +218 -218
  36. package/dist/core/index.browser.js.map +1 -1
  37. package/dist/core/index.d.ts +232 -232
  38. package/dist/core/index.js +218 -218
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/{index.cjs → index.native.js} +304 -455
  41. package/dist/core/index.native.js.map +1 -0
  42. package/dist/datetime/index.d.ts +9 -9
  43. package/dist/datetime/index.js +7 -7
  44. package/dist/datetime/index.js.map +1 -1
  45. package/dist/email/index.d.ts +16 -16
  46. package/dist/email/index.js +80 -82
  47. package/dist/email/index.js.map +1 -1
  48. package/dist/file/index.js +1 -1
  49. package/dist/file/index.js.map +1 -1
  50. package/dist/lock/index.d.ts +9 -9
  51. package/dist/lock/index.js +8 -8
  52. package/dist/lock/index.js.map +1 -1
  53. package/dist/lock-redis/index.js +3 -66
  54. package/dist/lock-redis/index.js.map +1 -1
  55. package/dist/logger/index.d.ts +5 -5
  56. package/dist/logger/index.js +8 -8
  57. package/dist/logger/index.js.map +1 -1
  58. package/dist/orm/index.browser.js +114 -114
  59. package/dist/orm/index.browser.js.map +1 -1
  60. package/dist/orm/index.d.ts +219 -219
  61. package/dist/orm/index.js +46 -46
  62. package/dist/orm/index.js.map +1 -1
  63. package/dist/queue/index.d.ts +25 -25
  64. package/dist/queue/index.js +20 -20
  65. package/dist/queue/index.js.map +1 -1
  66. package/dist/queue-redis/index.d.ts +2 -2
  67. package/dist/redis/index.d.ts +10 -10
  68. package/dist/retry/index.d.ts +20 -20
  69. package/dist/retry/index.js +9 -9
  70. package/dist/retry/index.js.map +1 -1
  71. package/dist/scheduler/index.d.ts +12 -12
  72. package/dist/scheduler/index.js +9 -9
  73. package/dist/scheduler/index.js.map +1 -1
  74. package/dist/security/index.d.ts +53 -53
  75. package/dist/security/index.js +32 -32
  76. package/dist/security/index.js.map +1 -1
  77. package/dist/server/index.browser.js +1 -1
  78. package/dist/server/index.browser.js.map +1 -1
  79. package/dist/server/index.d.ts +101 -101
  80. package/dist/server/index.js +17 -17
  81. package/dist/server/index.js.map +1 -1
  82. package/dist/server-auth/index.browser.js +4 -982
  83. package/dist/server-auth/index.browser.js.map +1 -1
  84. package/dist/server-auth/index.d.ts +204 -785
  85. package/dist/server-auth/index.js +47 -1239
  86. package/dist/server-auth/index.js.map +1 -1
  87. package/dist/server-cache/index.d.ts +10 -10
  88. package/dist/server-cache/index.js +2 -2
  89. package/dist/server-cache/index.js.map +1 -1
  90. package/dist/server-compress/index.d.ts +4 -4
  91. package/dist/server-compress/index.js +1 -1
  92. package/dist/server-compress/index.js.map +1 -1
  93. package/dist/server-cookies/index.browser.js +8 -8
  94. package/dist/server-cookies/index.browser.js.map +1 -1
  95. package/dist/server-cookies/index.d.ts +17 -17
  96. package/dist/server-cookies/index.js +10 -10
  97. package/dist/server-cookies/index.js.map +1 -1
  98. package/dist/server-cors/index.d.ts +17 -17
  99. package/dist/server-cors/index.js +9 -9
  100. package/dist/server-cors/index.js.map +1 -1
  101. package/dist/server-health/index.d.ts +2 -2
  102. package/dist/server-helmet/index.d.ts +1 -1
  103. package/dist/server-links/index.browser.js +12 -12
  104. package/dist/server-links/index.browser.js.map +1 -1
  105. package/dist/server-links/index.d.ts +59 -251
  106. package/dist/server-links/index.js +23 -502
  107. package/dist/server-links/index.js.map +1 -1
  108. package/dist/server-metrics/index.d.ts +4 -4
  109. package/dist/server-metrics/index.js +170 -174
  110. package/dist/server-metrics/index.js.map +1 -1
  111. package/dist/server-multipart/index.d.ts +2 -2
  112. package/dist/server-proxy/index.d.ts +12 -12
  113. package/dist/server-proxy/index.js +10 -10
  114. package/dist/server-proxy/index.js.map +1 -1
  115. package/dist/server-rate-limit/index.d.ts +22 -22
  116. package/dist/server-rate-limit/index.js +12 -12
  117. package/dist/server-rate-limit/index.js.map +1 -1
  118. package/dist/server-security/index.d.ts +24 -24
  119. package/dist/server-security/index.js +15 -15
  120. package/dist/server-security/index.js.map +1 -1
  121. package/dist/server-static/index.d.ts +14 -14
  122. package/dist/server-static/index.js +8 -8
  123. package/dist/server-static/index.js.map +1 -1
  124. package/dist/server-swagger/index.d.ts +25 -184
  125. package/dist/server-swagger/index.js +21 -724
  126. package/dist/server-swagger/index.js.map +1 -1
  127. package/dist/sms/index.d.ts +14 -14
  128. package/dist/sms/index.js +9 -9
  129. package/dist/sms/index.js.map +1 -1
  130. package/dist/thread/index.d.ts +11 -11
  131. package/dist/thread/index.js +17 -17
  132. package/dist/thread/index.js.map +1 -1
  133. package/dist/topic/index.d.ts +26 -26
  134. package/dist/topic/index.js +16 -16
  135. package/dist/topic/index.js.map +1 -1
  136. package/dist/topic-redis/index.d.ts +1 -1
  137. package/dist/vite/index.d.ts +3 -3
  138. package/dist/vite/index.js +12 -13
  139. package/dist/vite/index.js.map +1 -1
  140. package/dist/websocket/index.browser.js +11 -11
  141. package/dist/websocket/index.browser.js.map +1 -1
  142. package/dist/websocket/index.d.ts +51 -51
  143. package/dist/websocket/index.js +13 -13
  144. package/dist/websocket/index.js.map +1 -1
  145. package/package.json +62 -52
  146. package/src/api-files/services/FileService.ts +5 -7
  147. package/src/api-jobs/index.ts +1 -1
  148. package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
  149. package/src/api-jobs/providers/JobProvider.ts +9 -9
  150. package/src/api-jobs/services/JobService.ts +5 -5
  151. package/src/api-notifications/index.ts +5 -15
  152. package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
  153. package/src/api-notifications/services/NotificationSenderService.ts +3 -3
  154. package/src/api-parameters/index.ts +1 -1
  155. package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
  156. package/src/api-users/index.ts +1 -1
  157. package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
  158. package/src/api-users/providers/UserRealmProvider.ts +1 -1
  159. package/src/batch/index.ts +3 -3
  160. package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
  161. package/src/bucket/index.ts +8 -8
  162. package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
  163. package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
  164. package/src/cache/index.ts +4 -4
  165. package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
  166. package/src/cli/apps/AlephaPackageBuilderCli.ts +30 -3
  167. package/src/cli/assets/appRouterTs.ts +9 -0
  168. package/src/cli/assets/indexHtml.ts +2 -1
  169. package/src/cli/assets/mainBrowserTs.ts +10 -0
  170. package/src/cli/commands/CoreCommands.ts +6 -5
  171. package/src/cli/commands/DrizzleCommands.ts +69 -61
  172. package/src/cli/commands/VerifyCommands.ts +2 -2
  173. package/src/cli/commands/ViteCommands.ts +6 -1
  174. package/src/cli/services/ProjectUtils.ts +78 -41
  175. package/src/command/index.ts +5 -5
  176. package/src/command/{descriptors → primitives}/$command.ts +9 -12
  177. package/src/command/providers/CliProvider.ts +10 -10
  178. package/src/core/Alepha.ts +30 -33
  179. package/src/core/constants/KIND.ts +1 -1
  180. package/src/core/constants/OPTIONS.ts +1 -1
  181. package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
  182. package/src/core/helpers/ref.ts +1 -1
  183. package/src/core/index.shared.ts +8 -8
  184. package/src/core/{descriptors → primitives}/$context.ts +5 -5
  185. package/src/core/{descriptors → primitives}/$hook.ts +4 -4
  186. package/src/core/{descriptors → primitives}/$inject.ts +2 -2
  187. package/src/core/{descriptors → primitives}/$module.ts +9 -9
  188. package/src/core/{descriptors → primitives}/$use.ts +2 -2
  189. package/src/core/providers/CodecManager.ts +1 -1
  190. package/src/core/providers/JsonSchemaCodec.ts +1 -1
  191. package/src/core/providers/StateManager.ts +2 -2
  192. package/src/datetime/index.ts +3 -3
  193. package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
  194. package/src/email/index.ts +4 -4
  195. package/src/email/{descriptors → primitives}/$email.ts +8 -8
  196. package/src/file/index.ts +1 -1
  197. package/src/lock/index.ts +3 -3
  198. package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
  199. package/src/logger/index.ts +8 -8
  200. package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
  201. package/src/logger/services/Logger.ts +1 -1
  202. package/src/orm/constants/PG_SYMBOLS.ts +2 -2
  203. package/src/orm/index.browser.ts +2 -2
  204. package/src/orm/index.ts +8 -8
  205. package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
  206. package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
  207. package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
  208. package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
  209. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  210. package/src/orm/providers/PostgresTypeProvider.ts +3 -3
  211. package/src/orm/providers/RepositoryProvider.ts +4 -4
  212. package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
  213. package/src/orm/services/ModelBuilder.ts +9 -9
  214. package/src/orm/services/PgRelationManager.ts +2 -2
  215. package/src/orm/services/PostgresModelBuilder.ts +5 -5
  216. package/src/orm/services/Repository.ts +7 -7
  217. package/src/orm/services/SqliteModelBuilder.ts +5 -5
  218. package/src/queue/index.ts +7 -7
  219. package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
  220. package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
  221. package/src/queue/providers/WorkerProvider.ts +7 -7
  222. package/src/retry/index.ts +3 -3
  223. package/src/retry/{descriptors → primitives}/$retry.ts +19 -17
  224. package/src/scheduler/index.ts +3 -3
  225. package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
  226. package/src/scheduler/providers/CronProvider.ts +1 -1
  227. package/src/security/index.ts +9 -9
  228. package/src/security/{descriptors → primitives}/$permission.ts +7 -7
  229. package/src/security/{descriptors → primitives}/$realm.ts +6 -12
  230. package/src/security/{descriptors → primitives}/$role.ts +12 -12
  231. package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
  232. package/src/server/index.browser.ts +1 -1
  233. package/src/server/index.ts +14 -14
  234. package/src/server/{descriptors → primitives}/$action.ts +13 -13
  235. package/src/server/{descriptors → primitives}/$route.ts +9 -9
  236. package/src/server/providers/NodeHttpServerProvider.ts +2 -2
  237. package/src/server/services/HttpClient.ts +1 -1
  238. package/src/server-auth/index.browser.ts +1 -1
  239. package/src/server-auth/index.ts +6 -6
  240. package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
  241. package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
  242. package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
  243. package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
  244. package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
  245. package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
  246. package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
  247. package/src/server-cookies/index.browser.ts +2 -2
  248. package/src/server-cookies/index.ts +5 -5
  249. package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
  250. package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
  251. package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
  252. package/src/server-cookies/services/CookieParser.ts +1 -1
  253. package/src/server-cors/index.ts +3 -3
  254. package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
  255. package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
  256. package/src/server-links/index.browser.ts +5 -5
  257. package/src/server-links/index.ts +9 -9
  258. package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
  259. package/src/server-links/providers/LinkProvider.ts +7 -7
  260. package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
  261. package/src/server-links/providers/ServerLinksProvider.ts +3 -3
  262. package/src/server-proxy/index.ts +3 -3
  263. package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
  264. package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
  265. package/src/server-rate-limit/index.ts +6 -6
  266. package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
  267. package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
  268. package/src/server-security/index.ts +3 -3
  269. package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
  270. package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
  271. package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
  272. package/src/server-static/index.ts +3 -3
  273. package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
  274. package/src/server-static/providers/ServerStaticProvider.ts +6 -6
  275. package/src/server-swagger/index.ts +5 -5
  276. package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
  277. package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
  278. package/src/sms/index.ts +4 -4
  279. package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
  280. package/src/thread/index.ts +3 -3
  281. package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
  282. package/src/thread/providers/ThreadProvider.ts +7 -9
  283. package/src/topic/index.ts +5 -5
  284. package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
  285. package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
  286. package/src/topic/providers/TopicProvider.ts +4 -4
  287. package/src/vite/helpers/boot.ts +3 -3
  288. package/src/vite/tasks/copyAssets.ts +1 -1
  289. package/src/vite/tasks/generateSitemap.ts +3 -3
  290. package/src/vite/tasks/prerenderPages.ts +2 -2
  291. package/src/vite/tasks/runAlepha.ts +2 -2
  292. package/src/websocket/index.browser.ts +3 -3
  293. package/src/websocket/index.shared.ts +2 -2
  294. package/src/websocket/index.ts +4 -4
  295. package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
  296. package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
  297. package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
  298. package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
  299. package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
  300. package/src/websocket/services/WebSocketClient.ts +5 -5
  301. package/dist/api-files/index.cjs +0 -1293
  302. package/dist/api-files/index.cjs.map +0 -1
  303. package/dist/api-files/index.d.cts +0 -829
  304. package/dist/api-jobs/index.cjs +0 -274
  305. package/dist/api-jobs/index.cjs.map +0 -1
  306. package/dist/api-jobs/index.d.cts +0 -654
  307. package/dist/api-notifications/index.cjs +0 -380
  308. package/dist/api-notifications/index.cjs.map +0 -1
  309. package/dist/api-notifications/index.d.cts +0 -289
  310. package/dist/api-parameters/index.cjs +0 -66
  311. package/dist/api-parameters/index.cjs.map +0 -1
  312. package/dist/api-parameters/index.d.cts +0 -84
  313. package/dist/api-users/index.cjs +0 -6009
  314. package/dist/api-users/index.cjs.map +0 -1
  315. package/dist/api-users/index.d.cts +0 -4740
  316. package/dist/api-verifications/index.cjs +0 -407
  317. package/dist/api-verifications/index.cjs.map +0 -1
  318. package/dist/api-verifications/index.d.cts +0 -207
  319. package/dist/batch/index.cjs +0 -408
  320. package/dist/batch/index.cjs.map +0 -1
  321. package/dist/batch/index.d.cts +0 -330
  322. package/dist/bin/index.cjs +0 -17
  323. package/dist/bin/index.cjs.map +0 -1
  324. package/dist/bin/index.d.cts +0 -1
  325. package/dist/bucket/index.cjs +0 -303
  326. package/dist/bucket/index.cjs.map +0 -1
  327. package/dist/bucket/index.d.cts +0 -355
  328. package/dist/cache/index.cjs +0 -241
  329. package/dist/cache/index.cjs.map +0 -1
  330. package/dist/cache/index.d.cts +0 -202
  331. package/dist/cache-redis/index.cjs +0 -84
  332. package/dist/cache-redis/index.cjs.map +0 -1
  333. package/dist/cache-redis/index.d.cts +0 -40
  334. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  335. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  336. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  337. package/dist/cli/index.cjs +0 -1241
  338. package/dist/cli/index.cjs.map +0 -1
  339. package/dist/cli/index.d.cts +0 -422
  340. package/dist/command/index.cjs +0 -693
  341. package/dist/command/index.cjs.map +0 -1
  342. package/dist/command/index.d.cts +0 -340
  343. package/dist/core/index.cjs.map +0 -1
  344. package/dist/core/index.d.cts +0 -1927
  345. package/dist/datetime/index.cjs +0 -318
  346. package/dist/datetime/index.cjs.map +0 -1
  347. package/dist/datetime/index.d.cts +0 -145
  348. package/dist/email/index.cjs +0 -10874
  349. package/dist/email/index.cjs.map +0 -1
  350. package/dist/email/index.d.cts +0 -186
  351. package/dist/fake/index.cjs +0 -34641
  352. package/dist/fake/index.cjs.map +0 -1
  353. package/dist/fake/index.d.cts +0 -74
  354. package/dist/file/index.cjs +0 -1212
  355. package/dist/file/index.cjs.map +0 -1
  356. package/dist/file/index.d.cts +0 -698
  357. package/dist/lock/index.cjs +0 -226
  358. package/dist/lock/index.cjs.map +0 -1
  359. package/dist/lock/index.d.cts +0 -361
  360. package/dist/lock-redis/index.cjs +0 -113
  361. package/dist/lock-redis/index.cjs.map +0 -1
  362. package/dist/lock-redis/index.d.cts +0 -24
  363. package/dist/logger/index.cjs +0 -521
  364. package/dist/logger/index.cjs.map +0 -1
  365. package/dist/logger/index.d.cts +0 -281
  366. package/dist/orm/index.cjs +0 -2986
  367. package/dist/orm/index.cjs.map +0 -1
  368. package/dist/orm/index.d.cts +0 -2213
  369. package/dist/queue/index.cjs +0 -1044
  370. package/dist/queue/index.cjs.map +0 -1
  371. package/dist/queue/index.d.cts +0 -1265
  372. package/dist/queue-redis/index.cjs +0 -873
  373. package/dist/queue-redis/index.cjs.map +0 -1
  374. package/dist/queue-redis/index.d.cts +0 -82
  375. package/dist/redis/index.cjs +0 -153
  376. package/dist/redis/index.cjs.map +0 -1
  377. package/dist/redis/index.d.cts +0 -82
  378. package/dist/retry/index.cjs +0 -146
  379. package/dist/retry/index.cjs.map +0 -1
  380. package/dist/retry/index.d.cts +0 -172
  381. package/dist/router/index.cjs +0 -111
  382. package/dist/router/index.cjs.map +0 -1
  383. package/dist/router/index.d.cts +0 -46
  384. package/dist/scheduler/index.cjs +0 -576
  385. package/dist/scheduler/index.cjs.map +0 -1
  386. package/dist/scheduler/index.d.cts +0 -145
  387. package/dist/security/index.cjs +0 -2402
  388. package/dist/security/index.cjs.map +0 -1
  389. package/dist/security/index.d.cts +0 -598
  390. package/dist/server/index.cjs +0 -1680
  391. package/dist/server/index.cjs.map +0 -1
  392. package/dist/server/index.d.cts +0 -810
  393. package/dist/server-auth/index.cjs +0 -3146
  394. package/dist/server-auth/index.cjs.map +0 -1
  395. package/dist/server-auth/index.d.cts +0 -1164
  396. package/dist/server-cache/index.cjs +0 -252
  397. package/dist/server-cache/index.cjs.map +0 -1
  398. package/dist/server-cache/index.d.cts +0 -164
  399. package/dist/server-compress/index.cjs +0 -141
  400. package/dist/server-compress/index.cjs.map +0 -1
  401. package/dist/server-compress/index.d.cts +0 -38
  402. package/dist/server-cookies/index.cjs +0 -234
  403. package/dist/server-cookies/index.cjs.map +0 -1
  404. package/dist/server-cookies/index.d.cts +0 -144
  405. package/dist/server-cors/index.cjs +0 -201
  406. package/dist/server-cors/index.cjs.map +0 -1
  407. package/dist/server-cors/index.d.cts +0 -140
  408. package/dist/server-health/index.cjs +0 -62
  409. package/dist/server-health/index.cjs.map +0 -1
  410. package/dist/server-health/index.d.cts +0 -58
  411. package/dist/server-helmet/index.cjs +0 -131
  412. package/dist/server-helmet/index.cjs.map +0 -1
  413. package/dist/server-helmet/index.d.cts +0 -97
  414. package/dist/server-links/index.cjs +0 -992
  415. package/dist/server-links/index.cjs.map +0 -1
  416. package/dist/server-links/index.d.cts +0 -513
  417. package/dist/server-metrics/index.cjs +0 -4535
  418. package/dist/server-metrics/index.cjs.map +0 -1
  419. package/dist/server-metrics/index.d.cts +0 -35
  420. package/dist/server-multipart/index.cjs +0 -237
  421. package/dist/server-multipart/index.cjs.map +0 -1
  422. package/dist/server-multipart/index.d.cts +0 -50
  423. package/dist/server-proxy/index.cjs +0 -186
  424. package/dist/server-proxy/index.cjs.map +0 -1
  425. package/dist/server-proxy/index.d.cts +0 -234
  426. package/dist/server-rate-limit/index.cjs +0 -241
  427. package/dist/server-rate-limit/index.cjs.map +0 -1
  428. package/dist/server-rate-limit/index.d.cts +0 -183
  429. package/dist/server-security/index.cjs +0 -316
  430. package/dist/server-security/index.cjs.map +0 -1
  431. package/dist/server-security/index.d.cts +0 -173
  432. package/dist/server-static/index.cjs +0 -170
  433. package/dist/server-static/index.cjs.map +0 -1
  434. package/dist/server-static/index.d.cts +0 -121
  435. package/dist/server-swagger/index.cjs +0 -1021
  436. package/dist/server-swagger/index.cjs.map +0 -1
  437. package/dist/server-swagger/index.d.cts +0 -382
  438. package/dist/sms/index.cjs +0 -221
  439. package/dist/sms/index.cjs.map +0 -1
  440. package/dist/sms/index.d.cts +0 -130
  441. package/dist/thread/index.cjs +0 -350
  442. package/dist/thread/index.cjs.map +0 -1
  443. package/dist/thread/index.d.cts +0 -260
  444. package/dist/topic/index.cjs +0 -282
  445. package/dist/topic/index.cjs.map +0 -1
  446. package/dist/topic/index.d.cts +0 -523
  447. package/dist/topic-redis/index.cjs +0 -71
  448. package/dist/topic-redis/index.cjs.map +0 -1
  449. package/dist/topic-redis/index.d.cts +0 -42
  450. package/dist/vite/index.cjs +0 -1077
  451. package/dist/vite/index.cjs.map +0 -1
  452. package/dist/vite/index.d.cts +0 -542
  453. package/dist/websocket/index.cjs +0 -1117
  454. package/dist/websocket/index.cjs.map +0 -1
  455. package/dist/websocket/index.d.cts +0 -861
  456. package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
  457. package/src/api-notifications/providers/SmsProvider.ts +0 -8
  458. /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
  459. /package/src/core/{descriptors → primitives}/$env.ts +0 -0
  460. /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
  461. /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
@@ -1,4 +1,4 @@
1
- import { $hook, $inject, $module, Alepha, Descriptor, KIND, createDescriptor } from "alepha";
1
+ import { $hook, $inject, $module, Alepha, KIND, Primitive, createPrimitive } from "alepha";
2
2
  import { $permission, $realm, $role, AlephaSecurity, JwtProvider, SecurityProvider, userAccountInfoSchema } from "alepha/security";
3
3
  import { $action, AlephaServer, ForbiddenError, HttpError, ServerRouterProvider, UnauthorizedError } from "alepha/server";
4
4
  import { randomUUID, timingSafeEqual } from "node:crypto";
@@ -11,11 +11,11 @@ var ServerBasicAuthProvider = class {
11
11
  routerProvider = $inject(ServerRouterProvider);
12
12
  realm = "Secure Area";
13
13
  /**
14
- * Registered basic auth descriptors with their configurations
14
+ * Registered basic auth primitives with their configurations
15
15
  */
16
16
  registeredAuths = [];
17
17
  /**
18
- * Register a basic auth configuration (called by descriptors)
18
+ * Register a basic auth configuration (called by primitives)
19
19
  */
20
20
  registerAuth(config) {
21
21
  this.registeredAuths.push(config);
@@ -113,15 +113,15 @@ const isBasicAuth = (value) => {
113
113
  };
114
114
 
115
115
  //#endregion
116
- //#region src/server-security/descriptors/$basicAuth.ts
116
+ //#region src/server-security/primitives/$basicAuth.ts
117
117
  /**
118
118
  * Declares HTTP Basic Authentication for server routes.
119
- * This descriptor provides methods to protect routes with username/password authentication.
119
+ * This primitive provides methods to protect routes with username/password authentication.
120
120
  */
121
121
  const $basicAuth = (options) => {
122
- return createDescriptor(BasicAuthDescriptor, options);
122
+ return createPrimitive(BasicAuthPrimitive, options);
123
123
  };
124
- var BasicAuthDescriptor = class extends Descriptor {
124
+ var BasicAuthPrimitive = class extends Primitive {
125
125
  serverBasicAuthProvider = $inject(ServerBasicAuthProvider);
126
126
  get name() {
127
127
  return this.options.name ?? `${this.config.propertyKey}`;
@@ -130,7 +130,7 @@ var BasicAuthDescriptor = class extends Descriptor {
130
130
  this.serverBasicAuthProvider.registerAuth(this.options);
131
131
  }
132
132
  /**
133
- * Checks basic auth for the given request using this descriptor's configuration.
133
+ * Checks basic auth for the given request using this primitive's configuration.
134
134
  */
135
135
  check(request, options) {
136
136
  const mergedOptions = {
@@ -140,7 +140,7 @@ var BasicAuthDescriptor = class extends Descriptor {
140
140
  this.serverBasicAuthProvider.checkAuth(request, mergedOptions);
141
141
  }
142
142
  };
143
- $basicAuth[KIND] = BasicAuthDescriptor;
143
+ $basicAuth[KIND] = BasicAuthPrimitive;
144
144
 
145
145
  //#endregion
146
146
  //#region src/server-security/providers/ServerSecurityProvider.ts
@@ -152,7 +152,7 @@ var ServerSecurityProvider = class {
152
152
  onConfigure = $hook({
153
153
  on: "configure",
154
154
  handler: async () => {
155
- for (const action of this.alepha.descriptors($action)) {
155
+ for (const action of this.alepha.primitives($action)) {
156
156
  if (action.options.disabled || action.options.secure === false || this.securityProvider.getRealms().length === 0) continue;
157
157
  if (typeof action.options.secure !== "object") this.securityProvider.createPermission({
158
158
  name: action.name,
@@ -176,7 +176,7 @@ var ServerSecurityProvider = class {
176
176
  request.user = this.createUserFromLocalFunctionContext(options, permission);
177
177
  const route = action.route;
178
178
  if (typeof route.secure === "object") this.check(request.user, route.secure);
179
- this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(userAccountInfoSchema, request.user));
179
+ this.alepha.store.set("alepha.server.request.user", this.alepha.codec.decode(userAccountInfoSchema, request.user));
180
180
  } catch (error) {
181
181
  if (action.options.secure || permission) throw error;
182
182
  this.log.trace("Skipping security check for action");
@@ -200,7 +200,7 @@ var ServerSecurityProvider = class {
200
200
  try {
201
201
  request.user = await this.securityProvider.createUserFromToken(request.headers.authorization, { permission });
202
202
  if (typeof route.secure === "object") this.check(request.user, route.secure);
203
- this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(userAccountInfoSchema, request.user));
203
+ this.alepha.store.set("alepha.server.request.user", this.alepha.codec.decode(userAccountInfoSchema, request.user));
204
204
  this.log.trace("User set from request token", {
205
205
  user: request.user,
206
206
  permission
@@ -232,7 +232,7 @@ var ServerSecurityProvider = class {
232
232
  const type = typeof options.user === "string" ? options.user : void 0;
233
233
  let user;
234
234
  const fromContext = this.alepha.context.get("request")?.user;
235
- const fromSystem = this.alepha.state.get("alepha.server.security.system.user");
235
+ const fromSystem = this.alepha.store.get("alepha.server.security.system.user");
236
236
  if (type === "system") user = fromSystem;
237
237
  else if (type === "context") user = fromContext;
238
238
  else user = fromOptions ?? fromContext ?? fromSystem;
@@ -292,7 +292,7 @@ var ServerSecurityProvider = class {
292
292
  */
293
293
  const AlephaServerSecurity = $module({
294
294
  name: "alepha.server.security",
295
- descriptors: [
295
+ primitives: [
296
296
  $realm,
297
297
  $role,
298
298
  $permission,
@@ -307,5 +307,5 @@ const AlephaServerSecurity = $module({
307
307
  });
308
308
 
309
309
  //#endregion
310
- export { $basicAuth, AlephaServerSecurity, BasicAuthDescriptor, ServerBasicAuthProvider, ServerSecurityProvider, isBasicAuth };
310
+ export { $basicAuth, AlephaServerSecurity, BasicAuthPrimitive, ServerBasicAuthProvider, ServerSecurityProvider, isBasicAuth };
311
311
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["user: UserAccountToken | undefined","ownership: boolean | string | undefined"],"sources":["../../src/server-security/providers/ServerBasicAuthProvider.ts","../../src/server-security/descriptors/$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 BasicAuthDescriptorConfig 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 descriptors with their configurations\n */\n public readonly registeredAuths: BasicAuthDescriptorConfig[] = [];\n\n /**\n * Register a basic auth configuration (called by descriptors)\n */\n public registerAuth(config: BasicAuthDescriptorConfig): 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, createDescriptor, Descriptor, KIND } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type {\n BasicAuthDescriptorConfig,\n BasicAuthOptions,\n} from \"../providers/ServerBasicAuthProvider.ts\";\nimport { ServerBasicAuthProvider } from \"../providers/ServerBasicAuthProvider.ts\";\n\n/**\n * Declares HTTP Basic Authentication for server routes.\n * This descriptor provides methods to protect routes with username/password authentication.\n */\nexport const $basicAuth = (\n options: BasicAuthDescriptorConfig,\n): AbstractBasicAuthDescriptor => {\n return createDescriptor(BasicAuthDescriptor, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface AbstractBasicAuthDescriptor {\n readonly name: string;\n readonly options: BasicAuthDescriptorConfig;\n check(request: ServerRequest, options?: BasicAuthOptions): void;\n}\n\nexport class BasicAuthDescriptor\n extends Descriptor<BasicAuthDescriptorConfig>\n implements AbstractBasicAuthDescriptor\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 descriptor'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] = BasicAuthDescriptor;\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.descriptors($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.state.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.state.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.state.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 \"./descriptors/$basicAuth.ts\";\nimport { ServerBasicAuthProvider } from \"./providers/ServerBasicAuthProvider.ts\";\nimport {\n type ServerRouteSecure,\n ServerSecurityProvider,\n} from \"./providers/ServerSecurityProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$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 descriptors: [$realm, $role, $permission, $basicAuth],\n services: [\n AlephaServer,\n AlephaSecurity,\n ServerSecurityProvider,\n ServerBasicAuthProvider,\n ],\n});\n"],"mappings":"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"}
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,19 +1,19 @@
1
1
  import * as alepha1 from "alepha";
2
- import { Alepha, Descriptor, KIND } from "alepha";
2
+ import { Alepha, KIND, Primitive } from "alepha";
3
3
  import { ServerHandler, ServerRouterProvider } from "alepha/server";
4
4
  import { DateTimeProvider, DurationLike } from "alepha/datetime";
5
5
  import { FileDetector } from "alepha/file";
6
6
  import * as alepha_logger0 from "alepha/logger";
7
7
 
8
- //#region src/server-static/descriptors/$serve.d.ts
8
+ //#region src/server-static/primitives/$serve.d.ts
9
9
  /**
10
10
  * Create a new static file handler.
11
11
  */
12
12
  declare const $serve: {
13
- (options?: ServeDescriptorOptions): ServeDescriptor;
14
- [KIND]: typeof ServeDescriptor;
13
+ (options?: ServePrimitiveOptions): ServePrimitive;
14
+ [KIND]: typeof ServePrimitive;
15
15
  };
16
- interface ServeDescriptorOptions {
16
+ interface ServePrimitiveOptions {
17
17
  /**
18
18
  * Prefix for the served path.
19
19
  *
@@ -27,7 +27,7 @@ interface ServeDescriptorOptions {
27
27
  */
28
28
  root?: string;
29
29
  /**
30
- * If true, descriptor will be ignored.
30
+ * If true, primitive will be ignored.
31
31
  *
32
32
  * @default false
33
33
  */
@@ -50,7 +50,7 @@ interface ServeDescriptorOptions {
50
50
  */
51
51
  historyApiFallback?: boolean;
52
52
  /**
53
- * Optional name of the descriptor.
53
+ * Optional name of the primitive.
54
54
  * This is used for logging and debugging purposes.
55
55
  *
56
56
  * @default Key name.
@@ -83,7 +83,7 @@ interface CacheControlOptions {
83
83
  */
84
84
  immutable: boolean;
85
85
  }
86
- declare class ServeDescriptor extends Descriptor<ServeDescriptorOptions> {}
86
+ declare class ServePrimitive extends Primitive<ServePrimitiveOptions> {}
87
87
  //#endregion
88
88
  //#region src/server-static/providers/ServerStaticProvider.d.ts
89
89
  declare class ServerStaticProvider {
@@ -93,18 +93,18 @@ declare class ServerStaticProvider {
93
93
  protected readonly fileDetector: FileDetector;
94
94
  protected readonly log: alepha_logger0.Logger;
95
95
  protected readonly directories: ServeDirectory[];
96
- protected readonly configure: alepha1.HookDescriptor<"configure">;
97
- createStaticServer(options: ServeDescriptorOptions): Promise<void>;
98
- createFileHandler(filepath: string, options: ServeDescriptorOptions): Promise<ServerHandler>;
96
+ protected readonly configure: alepha1.HookPrimitive<"configure">;
97
+ createStaticServer(options: ServePrimitiveOptions): Promise<void>;
98
+ createFileHandler(filepath: string, options: ServePrimitiveOptions): Promise<ServerHandler>;
99
99
  protected getCacheFileTypes(): string[];
100
- protected getCacheControl(filename: string, options: ServeDescriptorOptions): {
100
+ protected getCacheControl(filename: string, options: ServePrimitiveOptions): {
101
101
  maxAge: number;
102
102
  immutable: boolean;
103
103
  } | undefined;
104
104
  getAllFiles(dir: string, ignoreDotEnvFiles?: boolean): Promise<string[]>;
105
105
  }
106
106
  interface ServeDirectory {
107
- options: ServeDescriptorOptions;
107
+ options: ServePrimitiveOptions;
108
108
  files: string[];
109
109
  }
110
110
  //#endregion
@@ -117,5 +117,5 @@ interface ServeDirectory {
117
117
  */
118
118
  declare const AlephaServerStatic: alepha1.Service<alepha1.Module>;
119
119
  //#endregion
120
- export { $serve, AlephaServerStatic, CacheControlOptions, ServeDescriptor, ServeDescriptorOptions, ServeDirectory, ServerStaticProvider };
120
+ export { $serve, AlephaServerStatic, CacheControlOptions, ServeDirectory, ServePrimitive, ServePrimitiveOptions, ServerStaticProvider };
121
121
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import { $hook, $inject, $module, Alepha, Descriptor, KIND, createDescriptor } from "alepha";
1
+ import { $hook, $inject, $module, Alepha, KIND, Primitive, createPrimitive } from "alepha";
2
2
  import { AlephaServer, ServerRouterProvider } from "alepha/server";
3
3
  import { createReadStream } from "node:fs";
4
4
  import { access, readdir, stat } from "node:fs/promises";
@@ -7,15 +7,15 @@ import { DateTimeProvider } from "alepha/datetime";
7
7
  import { FileDetector } from "alepha/file";
8
8
  import { $logger } from "alepha/logger";
9
9
 
10
- //#region src/server-static/descriptors/$serve.ts
10
+ //#region src/server-static/primitives/$serve.ts
11
11
  /**
12
12
  * Create a new static file handler.
13
13
  */
14
14
  const $serve = (options = {}) => {
15
- return createDescriptor(ServeDescriptor, options);
15
+ return createPrimitive(ServePrimitive, options);
16
16
  };
17
- var ServeDescriptor = class extends Descriptor {};
18
- $serve[KIND] = ServeDescriptor;
17
+ var ServePrimitive = class extends Primitive {};
18
+ $serve[KIND] = ServePrimitive;
19
19
 
20
20
  //#endregion
21
21
  //#region src/server-static/providers/ServerStaticProvider.ts
@@ -29,7 +29,7 @@ var ServerStaticProvider = class {
29
29
  configure = $hook({
30
30
  on: "configure",
31
31
  handler: async () => {
32
- await Promise.all(this.alepha.descriptors($serve).map((it) => this.createStaticServer(it.options)));
32
+ await Promise.all(this.alepha.primitives($serve).map((it) => this.createStaticServer(it.options)));
33
33
  }
34
34
  });
35
35
  async createStaticServer(options) {
@@ -158,10 +158,10 @@ var ServerStaticProvider = class {
158
158
  */
159
159
  const AlephaServerStatic = $module({
160
160
  name: "alepha.server.static",
161
- descriptors: [$serve],
161
+ primitives: [$serve],
162
162
  services: [AlephaServer, ServerStaticProvider]
163
163
  });
164
164
 
165
165
  //#endregion
166
- export { $serve, AlephaServerStatic, ServeDescriptor, ServerStaticProvider };
166
+ export { $serve, AlephaServerStatic, ServePrimitive, ServerStaticProvider };
167
167
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/server-static/descriptors/$serve.ts","../../src/server-static/providers/ServerStaticProvider.ts","../../src/server-static/index.ts"],"sourcesContent":["import { createDescriptor, Descriptor, KIND } from \"alepha\";\nimport type { DurationLike } from \"alepha/datetime\";\n\n/**\n * Create a new static file handler.\n */\nexport const $serve = (\n options: ServeDescriptorOptions = {},\n): ServeDescriptor => {\n return createDescriptor(ServeDescriptor, options);\n};\n\nexport interface ServeDescriptorOptions {\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, descriptor 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 descriptor.\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 ServeDescriptor extends Descriptor<ServeDescriptorOptions> {}\n\n$serve[KIND] = ServeDescriptor;\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 ServeDescriptorOptions } from \"../descriptors/$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 .descriptors($serve)\n .map((it) => this.createStaticServer(it.options)),\n );\n },\n });\n\n public async createStaticServer(\n options: ServeDescriptorOptions,\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 // Serve index.html for all unmatched routes\n return createReadStream(join(root, \"index.html\"));\n },\n });\n }\n }\n\n public async createFileHandler(\n filepath: string,\n options: ServeDescriptorOptions,\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 createReadStream(path);\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: ServeDescriptorOptions,\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: ServeDescriptorOptions;\n files: string[];\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $serve } from \"./descriptors/$serve.ts\";\nimport { ServerStaticProvider } from \"./providers/ServerStaticProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$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 descriptors: [$serve],\n services: [AlephaServer, ServerStaticProvider],\n});\n"],"mappings":"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"}
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 // Serve index.html for all unmatched routes\n return createReadStream(join(root, \"index.html\"));\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 createReadStream(path);\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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"}