alepha 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +3 -3
  2. package/dist/api/audits/index.browser.js +5 -5
  3. package/dist/api/audits/index.browser.js.map +1 -1
  4. package/dist/api/audits/index.d.ts +784 -784
  5. package/dist/api/audits/index.d.ts.map +1 -1
  6. package/dist/api/audits/index.js +13 -13
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.browser.js +5 -5
  9. package/dist/api/files/index.browser.js.map +1 -1
  10. package/dist/api/files/index.d.ts +57 -57
  11. package/dist/api/files/index.d.ts.map +1 -1
  12. package/dist/api/files/index.js +71 -71
  13. package/dist/api/files/index.js.map +1 -1
  14. package/dist/api/jobs/index.browser.js +5 -5
  15. package/dist/api/jobs/index.browser.js.map +1 -1
  16. package/dist/api/jobs/index.d.ts +165 -165
  17. package/dist/api/jobs/index.d.ts.map +1 -1
  18. package/dist/api/jobs/index.js +10 -10
  19. package/dist/api/jobs/index.js.map +1 -1
  20. package/dist/api/notifications/index.browser.js +10 -10
  21. package/dist/api/notifications/index.browser.js.map +1 -1
  22. package/dist/api/notifications/index.d.ts +583 -171
  23. package/dist/api/notifications/index.d.ts.map +1 -1
  24. package/dist/api/notifications/index.js +12 -12
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/parameters/index.browser.js +163 -10
  27. package/dist/api/parameters/index.browser.js.map +1 -1
  28. package/dist/api/parameters/index.d.ts +281 -276
  29. package/dist/api/parameters/index.d.ts.map +1 -1
  30. package/dist/api/parameters/index.js +196 -91
  31. package/dist/api/parameters/index.js.map +1 -1
  32. package/dist/api/users/index.browser.js +19 -19
  33. package/dist/api/users/index.browser.js.map +1 -1
  34. package/dist/api/users/index.d.ts +778 -764
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +831 -596
  37. package/dist/api/users/index.js.map +1 -1
  38. package/dist/api/verifications/index.browser.js +6 -6
  39. package/dist/api/verifications/index.browser.js.map +1 -1
  40. package/dist/api/verifications/index.d.ts +125 -125
  41. package/dist/api/verifications/index.d.ts.map +1 -1
  42. package/dist/api/verifications/index.js +6 -6
  43. package/dist/api/verifications/index.js.map +1 -1
  44. package/dist/batch/index.js.map +1 -1
  45. package/dist/bin/index.d.ts +1 -2
  46. package/dist/bin/index.js +0 -1
  47. package/dist/bin/index.js.map +1 -1
  48. package/dist/cache/core/index.js.map +1 -1
  49. package/dist/cli/index.d.ts +249 -218
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +951 -821
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/command/index.d.ts +40 -0
  54. package/dist/command/index.d.ts.map +1 -1
  55. package/dist/command/index.js +97 -17
  56. package/dist/command/index.js.map +1 -1
  57. package/dist/core/index.browser.js +14 -18
  58. package/dist/core/index.browser.js.map +1 -1
  59. package/dist/core/index.d.ts +29 -0
  60. package/dist/core/index.d.ts.map +1 -1
  61. package/dist/core/index.js +21 -24
  62. package/dist/core/index.js.map +1 -1
  63. package/dist/core/index.native.js +21 -24
  64. package/dist/core/index.native.js.map +1 -1
  65. package/dist/datetime/index.js.map +1 -1
  66. package/dist/fake/index.js +195 -168
  67. package/dist/fake/index.js.map +1 -1
  68. package/dist/file/index.d.ts +8 -0
  69. package/dist/file/index.d.ts.map +1 -1
  70. package/dist/file/index.js +3 -0
  71. package/dist/file/index.js.map +1 -1
  72. package/dist/lock/redis/index.js.map +1 -1
  73. package/dist/logger/index.js.map +1 -1
  74. package/dist/mcp/index.d.ts.map +1 -1
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/orm/index.browser.js +26 -5
  77. package/dist/orm/index.browser.js.map +1 -1
  78. package/dist/orm/index.d.ts +146 -121
  79. package/dist/orm/index.d.ts.map +1 -1
  80. package/dist/orm/index.js +49 -24
  81. package/dist/orm/index.js.map +1 -1
  82. package/dist/redis/index.js.map +1 -1
  83. package/dist/retry/index.js.map +1 -1
  84. package/dist/router/index.js.map +1 -1
  85. package/dist/scheduler/index.d.ts +6 -6
  86. package/dist/scheduler/index.js.map +1 -1
  87. package/dist/security/index.d.ts +29 -29
  88. package/dist/security/index.d.ts.map +1 -1
  89. package/dist/security/index.js +1 -1
  90. package/dist/security/index.js.map +1 -1
  91. package/dist/server/auth/index.d.ts +171 -155
  92. package/dist/server/auth/index.d.ts.map +1 -1
  93. package/dist/server/auth/index.js +0 -1
  94. package/dist/server/auth/index.js.map +1 -1
  95. package/dist/server/cache/index.js.map +1 -1
  96. package/dist/server/compress/index.d.ts.map +1 -1
  97. package/dist/server/compress/index.js +2 -0
  98. package/dist/server/compress/index.js.map +1 -1
  99. package/dist/server/cookies/index.browser.js.map +1 -1
  100. package/dist/server/cookies/index.js.map +1 -1
  101. package/dist/server/core/index.browser.js.map +1 -1
  102. package/dist/server/core/index.d.ts.map +1 -1
  103. package/dist/server/core/index.js +1 -1
  104. package/dist/server/core/index.js.map +1 -1
  105. package/dist/server/health/index.d.ts +17 -17
  106. package/dist/server/helmet/index.js.map +1 -1
  107. package/dist/server/links/index.browser.js +22 -6
  108. package/dist/server/links/index.browser.js.map +1 -1
  109. package/dist/server/links/index.d.ts +46 -44
  110. package/dist/server/links/index.d.ts.map +1 -1
  111. package/dist/server/links/index.js +24 -41
  112. package/dist/server/links/index.js.map +1 -1
  113. package/dist/server/multipart/index.js.map +1 -1
  114. package/dist/server/rate-limit/index.js.map +1 -1
  115. package/dist/server/security/index.js.map +1 -1
  116. package/dist/server/swagger/index.d.ts +2 -1
  117. package/dist/server/swagger/index.d.ts.map +1 -1
  118. package/dist/server/swagger/index.js +8 -3
  119. package/dist/server/swagger/index.js.map +1 -1
  120. package/dist/thread/index.js.map +1 -1
  121. package/dist/topic/core/index.js.map +1 -1
  122. package/dist/vite/index.d.ts.map +1 -1
  123. package/dist/vite/index.js +12 -4
  124. package/dist/vite/index.js.map +1 -1
  125. package/dist/websocket/index.browser.js.map +1 -1
  126. package/dist/websocket/index.js.map +1 -1
  127. package/package.json +7 -7
  128. package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
  129. package/src/api/audits/entities/audits.ts +5 -5
  130. package/src/api/audits/index.browser.ts +1 -1
  131. package/src/api/audits/index.ts +3 -3
  132. package/src/api/audits/primitives/$audit.spec.ts +276 -0
  133. package/src/api/audits/services/AuditService.spec.ts +495 -0
  134. package/src/api/files/__tests__/$bucket.spec.ts +91 -0
  135. package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
  136. package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
  137. package/src/api/files/controllers/FileController.spec.ts +558 -0
  138. package/src/api/files/controllers/FileController.ts +4 -5
  139. package/src/api/files/entities/files.ts +5 -5
  140. package/src/api/files/index.browser.ts +1 -1
  141. package/src/api/files/index.ts +4 -4
  142. package/src/api/files/jobs/FileJobs.spec.ts +52 -0
  143. package/src/api/files/services/FileService.spec.ts +109 -0
  144. package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
  145. package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
  146. package/src/api/jobs/entities/jobExecutions.ts +5 -5
  147. package/src/api/jobs/index.ts +3 -3
  148. package/src/api/jobs/primitives/$job.spec.ts +476 -0
  149. package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
  150. package/src/api/notifications/entities/notifications.ts +5 -5
  151. package/src/api/notifications/index.browser.ts +1 -1
  152. package/src/api/notifications/index.ts +4 -4
  153. package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
  154. package/src/api/parameters/entities/parameters.ts +7 -17
  155. package/src/api/parameters/index.ts +3 -3
  156. package/src/api/parameters/primitives/$config.spec.ts +356 -0
  157. package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
  158. package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
  159. package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
  160. package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
  161. package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
  162. package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
  163. package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
  164. package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
  165. package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
  166. package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
  167. package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
  168. package/src/api/parameters/schemas/index.ts +15 -0
  169. package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
  170. package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
  171. package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
  172. package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
  173. package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
  174. package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
  175. package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
  176. package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
  177. package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
  178. package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
  179. package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
  180. package/src/api/users/controllers/AdminUserController.ts +116 -0
  181. package/src/api/users/controllers/UserController.ts +4 -107
  182. package/src/api/users/controllers/UserRealmController.ts +3 -0
  183. package/src/api/users/entities/identities.ts +6 -6
  184. package/src/api/users/entities/sessions.ts +6 -6
  185. package/src/api/users/entities/users.ts +9 -9
  186. package/src/api/users/index.ts +13 -6
  187. package/src/api/users/primitives/$userRealm.ts +13 -8
  188. package/src/api/users/services/CredentialService.spec.ts +509 -0
  189. package/src/api/users/services/CredentialService.ts +46 -0
  190. package/src/api/users/services/IdentityService.ts +15 -0
  191. package/src/api/users/services/RegistrationService.spec.ts +630 -0
  192. package/src/api/users/services/RegistrationService.ts +18 -0
  193. package/src/api/users/services/SessionService.spec.ts +301 -0
  194. package/src/api/users/services/SessionService.ts +110 -1
  195. package/src/api/users/services/UserService.ts +67 -2
  196. package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
  197. package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
  198. package/src/api/verifications/entities/verifications.ts +6 -6
  199. package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
  200. package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
  201. package/src/batch/primitives/$batch.spec.ts +766 -0
  202. package/src/batch/providers/BatchProvider.spec.ts +786 -0
  203. package/src/bin/index.ts +0 -1
  204. package/src/bucket/__tests__/shared.ts +194 -0
  205. package/src/bucket/primitives/$bucket.spec.ts +104 -0
  206. package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
  207. package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
  208. package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
  209. package/src/cache/core/__tests__/shared.ts +377 -0
  210. package/src/cache/core/primitives/$cache.spec.ts +111 -0
  211. package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
  212. package/src/cli/apps/AlephaCli.ts +54 -16
  213. package/src/cli/apps/AlephaPackageBuilderCli.ts +2 -1
  214. package/src/cli/assets/appRouterTs.ts +1 -1
  215. package/src/cli/commands/{ViteCommands.ts → build.ts} +2 -105
  216. package/src/cli/commands/clean.ts +14 -0
  217. package/src/cli/commands/{DrizzleCommands.ts → db.ts} +10 -117
  218. package/src/cli/commands/{DeployCommands.ts → deploy.ts} +1 -1
  219. package/src/cli/commands/dev.ts +69 -0
  220. package/src/cli/commands/format.ts +17 -0
  221. package/src/cli/commands/gen/changelog.spec.ts +315 -0
  222. package/src/cli/commands/{ChangelogCommands.ts → gen/changelog.ts} +16 -31
  223. package/src/cli/commands/gen/openapi.ts +71 -0
  224. package/src/cli/commands/gen.ts +18 -0
  225. package/src/cli/commands/{CoreCommands.ts → init.ts} +4 -40
  226. package/src/cli/commands/lint.ts +17 -0
  227. package/src/cli/commands/root.ts +41 -0
  228. package/src/cli/commands/run.ts +24 -0
  229. package/src/cli/commands/test.ts +42 -0
  230. package/src/cli/commands/typecheck.ts +24 -0
  231. package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
  232. package/src/cli/defineConfig.ts +10 -1
  233. package/src/cli/index.ts +17 -7
  234. package/src/cli/services/AlephaCliUtils.ts +71 -32
  235. package/src/cli/services/GitMessageParser.ts +1 -1
  236. package/src/command/helpers/Asker.spec.ts +127 -0
  237. package/src/command/helpers/Runner.spec.ts +126 -0
  238. package/src/command/primitives/$command.spec.ts +1588 -0
  239. package/src/command/providers/CliProvider.ts +74 -24
  240. package/src/core/Alepha.ts +52 -4
  241. package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
  242. package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
  243. package/src/core/__tests__/Alepha-has.spec.ts +41 -0
  244. package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
  245. package/src/core/__tests__/Alepha-register.spec.ts +81 -0
  246. package/src/core/__tests__/Alepha-start.spec.ts +176 -0
  247. package/src/core/__tests__/Alepha-with.spec.ts +14 -0
  248. package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
  249. package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
  250. package/src/core/__tests__/descriptor.spec.ts +34 -0
  251. package/src/core/__tests__/fixtures/A.ts +5 -0
  252. package/src/core/__tests__/pagination.spec.ts +77 -0
  253. package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
  254. package/src/core/primitives/$atom.spec.ts +43 -0
  255. package/src/core/primitives/$hook.spec.ts +130 -0
  256. package/src/core/primitives/$inject.spec.ts +175 -0
  257. package/src/core/primitives/$module.spec.ts +115 -0
  258. package/src/core/providers/CodecManager.spec.ts +740 -0
  259. package/src/core/providers/EventManager.spec.ts +762 -0
  260. package/src/core/providers/EventManager.ts +4 -0
  261. package/src/core/providers/StateManager.spec.ts +365 -0
  262. package/src/core/providers/TypeProvider.spec.ts +1607 -0
  263. package/src/core/providers/TypeProvider.ts +20 -26
  264. package/src/datetime/primitives/$interval.spec.ts +103 -0
  265. package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
  266. package/src/email/primitives/$email.spec.ts +175 -0
  267. package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
  268. package/src/fake/__tests__/keyName.example.ts +40 -0
  269. package/src/fake/__tests__/keyName.spec.ts +152 -0
  270. package/src/fake/__tests__/module.example.ts +32 -0
  271. package/src/fake/providers/FakeProvider.spec.ts +438 -0
  272. package/src/file/providers/FileSystemProvider.ts +8 -0
  273. package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
  274. package/src/file/providers/NodeFileSystemProvider.ts +5 -0
  275. package/src/file/services/FileDetector.spec.ts +591 -0
  276. package/src/lock/core/__tests__/shared.ts +190 -0
  277. package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
  278. package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
  279. package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
  280. package/src/logger/primitives/$logger.spec.ts +108 -0
  281. package/src/logger/services/Logger.spec.ts +295 -0
  282. package/src/mcp/__tests__/errors.spec.ts +175 -0
  283. package/src/mcp/__tests__/integration.spec.ts +450 -0
  284. package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
  285. package/src/mcp/primitives/$prompt.spec.ts +468 -0
  286. package/src/mcp/primitives/$resource.spec.ts +390 -0
  287. package/src/mcp/primitives/$tool.spec.ts +406 -0
  288. package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
  289. package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
  290. package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
  291. package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
  292. package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
  293. package/src/orm/__tests__/$repository-save.spec.ts +37 -0
  294. package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
  295. package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
  296. package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
  297. package/src/orm/__tests__/delete-returning.spec.ts +256 -0
  298. package/src/orm/__tests__/deletedAt.spec.ts +80 -0
  299. package/src/orm/__tests__/enums.spec.ts +315 -0
  300. package/src/orm/__tests__/execute.spec.ts +72 -0
  301. package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
  302. package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
  303. package/src/orm/__tests__/joins.spec.ts +1114 -0
  304. package/src/orm/__tests__/page.spec.ts +287 -0
  305. package/src/orm/__tests__/primaryKey.spec.ts +87 -0
  306. package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
  307. package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
  308. package/src/orm/__tests__/references.spec.ts +102 -0
  309. package/src/orm/__tests__/security.spec.ts +710 -0
  310. package/src/orm/__tests__/sqlite.spec.ts +111 -0
  311. package/src/orm/__tests__/string-operators.spec.ts +429 -0
  312. package/src/orm/__tests__/timestamps.spec.ts +388 -0
  313. package/src/orm/__tests__/validation.spec.ts +183 -0
  314. package/src/orm/__tests__/version.spec.ts +64 -0
  315. package/src/orm/helpers/parseQueryString.spec.ts +196 -0
  316. package/src/orm/index.browser.ts +1 -1
  317. package/src/orm/index.ts +10 -6
  318. package/src/orm/primitives/$repository.spec.ts +137 -0
  319. package/src/orm/primitives/$sequence.spec.ts +29 -0
  320. package/src/orm/primitives/$transaction.spec.ts +82 -0
  321. package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
  322. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
  323. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  324. package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
  325. package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
  326. package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
  327. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  328. package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
  329. package/src/orm/services/ModelBuilder.spec.ts +575 -0
  330. package/src/orm/services/Repository.spec.ts +137 -0
  331. package/src/queue/core/__tests__/shared.ts +143 -0
  332. package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
  333. package/src/queue/core/providers/WorkerProvider.spec.ts +378 -0
  334. package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
  335. package/src/redis/__tests__/redis.spec.ts +58 -0
  336. package/src/retry/primitives/$retry.spec.ts +234 -0
  337. package/src/retry/providers/RetryProvider.spec.ts +438 -0
  338. package/src/router/__tests__/match.spec.ts +252 -0
  339. package/src/router/providers/RouterProvider.spec.ts +197 -0
  340. package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
  341. package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
  342. package/src/scheduler/__tests__/shared.ts +77 -0
  343. package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
  344. package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
  345. package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
  346. package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
  347. package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
  348. package/src/security/primitives/$permission.spec.ts +30 -0
  349. package/src/security/primitives/$permission.ts +2 -2
  350. package/src/security/primitives/$realm.spec.ts +101 -0
  351. package/src/security/primitives/$role.spec.ts +52 -0
  352. package/src/security/primitives/$serviceAccount.spec.ts +61 -0
  353. package/src/security/providers/SecurityProvider.spec.ts +350 -0
  354. package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
  355. package/src/server/cache/providers/ServerCacheProvider.spec.ts +942 -0
  356. package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
  357. package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
  358. package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
  359. package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
  360. package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
  361. package/src/server/core/primitives/$action.spec.ts +191 -0
  362. package/src/server/core/primitives/$route.spec.ts +65 -0
  363. package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
  364. package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
  365. package/src/server/core/providers/ServerProvider.ts +3 -1
  366. package/src/server/core/services/HttpClient.spec.ts +123 -0
  367. package/src/server/core/services/UserAgentParser.spec.ts +111 -0
  368. package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
  369. package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
  370. package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
  371. package/src/server/links/__tests__/$action.spec.ts +238 -0
  372. package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
  373. package/src/server/links/__tests__/requestId.spec.ts +120 -0
  374. package/src/server/links/primitives/$remote.spec.ts +228 -0
  375. package/src/server/links/providers/LinkProvider.spec.ts +54 -0
  376. package/src/server/links/providers/LinkProvider.ts +49 -3
  377. package/src/server/links/providers/ServerLinksProvider.ts +1 -53
  378. package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
  379. package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
  380. package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
  381. package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
  382. package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
  383. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
  384. package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
  385. package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
  386. package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
  387. package/src/server/static/primitives/$serve.spec.ts +193 -0
  388. package/src/server/swagger/__tests__/ui.spec.ts +52 -0
  389. package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
  390. package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
  391. package/src/sms/primitives/$sms.spec.ts +165 -0
  392. package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
  393. package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
  394. package/src/thread/primitives/$thread.spec.ts +186 -0
  395. package/src/topic/core/__tests__/shared.ts +144 -0
  396. package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
  397. package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
  398. package/src/vite/plugins/viteAlephaDev.ts +16 -4
  399. package/src/vite/tasks/runAlepha.ts +7 -1
  400. package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
  401. package/src/websocket/primitives/$channel.spec.ts +30 -0
  402. package/src/cli/commands/BiomeCommands.ts +0 -29
@@ -0,0 +1,388 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Alepha } from "alepha";
3
+ import { $realm } from "alepha/security";
4
+ import { $action, $route, ForbiddenError, ServerProvider } from "alepha/server";
5
+ import { describe, expect, it } from "vitest";
6
+ import { AlephaServerSecurity } from "../index.ts";
7
+
8
+ describe("ServerSecurityProvider - Realm Protection", () => {
9
+ it("should allow access when user belongs to the required realm", async () => {
10
+ class TestApp {
11
+ realmA = $realm({
12
+ secret: "test-realm-a",
13
+ roles: [
14
+ {
15
+ name: "user",
16
+ permissions: [{ name: "*" }],
17
+ },
18
+ ],
19
+ });
20
+
21
+ realmB = $realm({
22
+ secret: "test-realm-b",
23
+ roles: [
24
+ {
25
+ name: "user",
26
+ permissions: [{ name: "*" }],
27
+ },
28
+ ],
29
+ });
30
+
31
+ // Action that requires realmA
32
+ actionA = $action({
33
+ secure: { realm: "realmA" },
34
+ handler: () => "REALM_A",
35
+ });
36
+
37
+ // Route that requires realmB
38
+ routeB = $route({
39
+ method: "GET",
40
+ path: "/realm-b",
41
+ secure: { realm: "realmB" },
42
+ handler: () => "REALM_B",
43
+ });
44
+ }
45
+
46
+ const alepha = Alepha.create().with(AlephaServerSecurity);
47
+ const app = alepha.inject(TestApp);
48
+ await alepha.start();
49
+
50
+ const userA = {
51
+ id: randomUUID(),
52
+ roles: ["user"],
53
+ realm: "realmA",
54
+ name: "Test User A",
55
+ };
56
+
57
+ const userB = {
58
+ id: randomUUID(),
59
+ roles: ["user"],
60
+ realm: "realmB",
61
+ name: "Test User B",
62
+ };
63
+
64
+ // User from realmA should access actionA via .run()
65
+ expect(await app.actionA.run({}, { user: userA })).toBe("REALM_A");
66
+
67
+ // User from realmA should access actionA via .fetch()
68
+ expect(
69
+ await app.actionA.fetch({}, { user: userA }).then((it) => it.data),
70
+ ).toBe("REALM_A");
71
+
72
+ // User from realmB should access routeB via .run()
73
+ const tokenB = await app.realmB.createToken(userB);
74
+ const responseB = await fetch(
75
+ `${alepha.inject(ServerProvider).hostname}/realm-b`,
76
+ {
77
+ headers: {
78
+ authorization: `Bearer ${tokenB.access_token}`,
79
+ },
80
+ },
81
+ );
82
+
83
+ expect(await responseB.text()).toBe("REALM_B");
84
+ });
85
+
86
+ it("should deny access when user does not belong to the required realm", async () => {
87
+ class TestApp {
88
+ realmA = $realm({
89
+ secret: "test-realm-a",
90
+ roles: [
91
+ {
92
+ name: "user",
93
+ permissions: [{ name: "*" }],
94
+ },
95
+ ],
96
+ });
97
+
98
+ realmB = $realm({
99
+ secret: "test-realm-b",
100
+ roles: [
101
+ {
102
+ name: "user",
103
+ permissions: [{ name: "*" }],
104
+ },
105
+ ],
106
+ });
107
+
108
+ // Action that requires realmA
109
+ actionA = $action({
110
+ secure: { realm: "realmA" },
111
+ handler: () => "REALM_A",
112
+ });
113
+
114
+ // Route that requires realmB
115
+ routeB = $route({
116
+ method: "GET",
117
+ path: "/realm-b",
118
+ secure: { realm: "realmB" },
119
+ handler: () => "REALM_B",
120
+ });
121
+ }
122
+
123
+ const alepha = Alepha.create().with(AlephaServerSecurity);
124
+ const app = alepha.inject(TestApp);
125
+ await alepha.start();
126
+
127
+ const userA = {
128
+ id: randomUUID(),
129
+ roles: ["user"],
130
+ realm: "realmA",
131
+ name: "Test User A",
132
+ };
133
+
134
+ const userB = {
135
+ id: randomUUID(),
136
+ roles: ["user"],
137
+ realm: "realmB",
138
+ name: "Test User B",
139
+ };
140
+
141
+ // User from realmB should NOT access actionA via .run()
142
+ await expect(app.actionA.run({}, { user: userB })).rejects.toThrowError(
143
+ ForbiddenError,
144
+ );
145
+
146
+ // User from realmB should NOT access actionA via .fetch()
147
+ await expect(app.actionA.fetch({}, { user: userB })).rejects.toThrowError(
148
+ "User must belong to realm 'realmA' to access this route",
149
+ );
150
+
151
+ // User from realmA should NOT access routeB via HTTP (requires realmB)
152
+ const tokenA = await app.realmA.createToken(userA);
153
+
154
+ const responseB = await fetch(
155
+ `${alepha.inject(ServerProvider).hostname}/realm-b`,
156
+ {
157
+ headers: {
158
+ authorization: `Bearer ${tokenA.access_token}`,
159
+ },
160
+ },
161
+ );
162
+
163
+ expect(responseB.status).toBe(403);
164
+ const errorData = await responseB.json();
165
+ expect(errorData).toEqual({
166
+ error: "ForbiddenError",
167
+ message: "User must belong to realm 'realmB' to access this route",
168
+ status: 403,
169
+ requestId: expect.any(String),
170
+ });
171
+ });
172
+
173
+ it("should work with actions when user has no realm attribute", async () => {
174
+ class TestApp {
175
+ realmA = $realm({
176
+ secret: "test-realm-a",
177
+ roles: [
178
+ {
179
+ name: "user",
180
+ permissions: [{ name: "*" }],
181
+ },
182
+ ],
183
+ });
184
+
185
+ actionA = $action({
186
+ secure: { realm: "realmA" },
187
+ handler: () => "REALM_A",
188
+ });
189
+ }
190
+
191
+ const alepha = Alepha.create().with(AlephaServerSecurity);
192
+ const app = alepha.inject(TestApp);
193
+ await alepha.start();
194
+
195
+ const userWithoutRealm = {
196
+ id: randomUUID(),
197
+ roles: ["user"],
198
+ // no realm attribute
199
+ };
200
+
201
+ // User without realm attribute should be denied via .run()
202
+ await expect(
203
+ app.actionA.run({}, { user: userWithoutRealm }),
204
+ ).rejects.toThrowError(ForbiddenError);
205
+
206
+ // Note: .fetch() in test mode auto-assigns the first realm during token creation,
207
+ // so a user without a realm will succeed if the first realm matches the required realm.
208
+ // This is expected test helper behavior for convenience.
209
+ expect(
210
+ await app.actionA
211
+ .fetch({}, { user: userWithoutRealm })
212
+ .then((it) => it.data),
213
+ ).toBe("REALM_A");
214
+ });
215
+
216
+ it("should combine realm and permission checks", async () => {
217
+ class TestApp {
218
+ realmA = $realm({
219
+ secret: "test-realm-a",
220
+ roles: [
221
+ {
222
+ name: "admin",
223
+ permissions: [{ name: "*" }],
224
+ },
225
+ {
226
+ name: "user",
227
+ permissions: [{ name: "read:*" }],
228
+ },
229
+ ],
230
+ });
231
+
232
+ realmB = $realm({
233
+ secret: "test-realm-b",
234
+ });
235
+
236
+ // Requires both realmA and admin permission
237
+ adminAction = $action({
238
+ group: "admin",
239
+ secure: { realm: "realmA" },
240
+ handler: () => "ADMIN",
241
+ });
242
+
243
+ // Requires both realmA and read permission
244
+ readAction = $action({
245
+ group: "read",
246
+ secure: { realm: "realmA" },
247
+ handler: () => "READ",
248
+ });
249
+ }
250
+
251
+ const alepha = Alepha.create().with(AlephaServerSecurity);
252
+ const app = alepha.inject(TestApp);
253
+ await alepha.start();
254
+
255
+ const adminUserRealmA = {
256
+ id: randomUUID(),
257
+ roles: ["admin"],
258
+ realm: "realmA",
259
+ name: "Admin User",
260
+ };
261
+
262
+ const regularUserRealmA = {
263
+ id: randomUUID(),
264
+ roles: ["user"],
265
+ realm: "realmA",
266
+ name: "Regular User",
267
+ };
268
+
269
+ const adminUserRealmB = {
270
+ id: randomUUID(),
271
+ roles: ["admin"],
272
+ realm: "realmB",
273
+ name: "Admin User B",
274
+ };
275
+
276
+ // Admin from realmA should access both actions via .run()
277
+ expect(await app.adminAction.run({}, { user: adminUserRealmA })).toBe(
278
+ "ADMIN",
279
+ );
280
+ expect(await app.readAction.run({}, { user: adminUserRealmA })).toBe(
281
+ "READ",
282
+ );
283
+
284
+ // Admin from realmA should access both actions via .fetch()
285
+ expect(
286
+ await app.adminAction
287
+ .fetch({}, { user: adminUserRealmA })
288
+ .then((it) => it.data),
289
+ ).toBe("ADMIN");
290
+ expect(
291
+ await app.readAction
292
+ .fetch({}, { user: adminUserRealmA })
293
+ .then((it) => it.data),
294
+ ).toBe("READ");
295
+
296
+ // Regular user from realmA can access both actions via .run() (realm check only, no permission check)
297
+ // When secure: { realm: "..." } is used, permissions are not enforced
298
+ expect(await app.adminAction.run({}, { user: regularUserRealmA })).toBe(
299
+ "ADMIN",
300
+ );
301
+ expect(await app.readAction.run({}, { user: regularUserRealmA })).toBe(
302
+ "READ",
303
+ );
304
+
305
+ // Regular user from realmA can access both actions via .fetch()
306
+ expect(
307
+ await app.adminAction
308
+ .fetch({}, { user: regularUserRealmA })
309
+ .then((it) => it.data),
310
+ ).toBe("ADMIN");
311
+ expect(
312
+ await app.readAction
313
+ .fetch({}, { user: regularUserRealmA })
314
+ .then((it) => it.data),
315
+ ).toBe("READ");
316
+
317
+ // Admin from realmB should NOT access any action via .run() (wrong realm)
318
+ await expect(
319
+ app.adminAction.run({}, { user: adminUserRealmB }),
320
+ ).rejects.toThrowError(ForbiddenError);
321
+ await expect(
322
+ app.readAction.run({}, { user: adminUserRealmB }),
323
+ ).rejects.toThrowError(ForbiddenError);
324
+
325
+ await expect(
326
+ app.adminAction.fetch({}, { user: adminUserRealmB }),
327
+ ).rejects.toThrowError(
328
+ "User must belong to realm 'realmA' to access this route",
329
+ );
330
+ await expect(
331
+ app.readAction.fetch({}, { user: adminUserRealmB }),
332
+ ).rejects.toThrowError(
333
+ "User must belong to realm 'realmA' to access this route",
334
+ );
335
+ });
336
+
337
+ it("should work with fetch requests when realm is valid", async () => {
338
+ class TestApp {
339
+ realmA = $realm({
340
+ secret: "test-realm-a",
341
+ roles: [
342
+ {
343
+ name: "user",
344
+ permissions: [{ name: "*" }],
345
+ },
346
+ ],
347
+ });
348
+
349
+ actionA = $action({
350
+ secure: { realm: "realmA" },
351
+ handler: () => "SUCCESS",
352
+ });
353
+ }
354
+
355
+ const alepha = Alepha.create().with(AlephaServerSecurity);
356
+ const app = alepha.inject(TestApp);
357
+ await alepha.start();
358
+
359
+ const user = {
360
+ id: randomUUID(),
361
+ roles: ["user"],
362
+ realm: "realmA",
363
+ name: "Test User",
364
+ };
365
+
366
+ // Should work via .run()
367
+ expect(await app.actionA.run({}, { user })).toBe("SUCCESS");
368
+
369
+ // Should work via .fetch()
370
+ expect(await app.actionA.fetch({}, { user }).then((it) => it.data)).toBe(
371
+ "SUCCESS",
372
+ );
373
+
374
+ // Should work via HTTP with token
375
+ const token = await app.realmA.createToken(user);
376
+ const response = await fetch(
377
+ `${alepha.inject(ServerProvider).hostname}${app.actionA.route.path}`,
378
+ {
379
+ headers: {
380
+ authorization: `Bearer ${token.access_token}`,
381
+ },
382
+ },
383
+ );
384
+
385
+ expect(response.status).toBe(200);
386
+ expect(await response.text()).toBe("SUCCESS");
387
+ });
388
+ });
@@ -0,0 +1,123 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Alepha } from "alepha";
3
+ import { $realm } from "alepha/security";
4
+ import {
5
+ $action,
6
+ ForbiddenError,
7
+ HttpError,
8
+ ServerProvider,
9
+ UnauthorizedError,
10
+ } from "alepha/server";
11
+ import { describe, expect, it } from "vitest";
12
+ import { AlephaServerSecurity } from "../index.ts";
13
+
14
+ describe("ServerSecurityProvider", () => {
15
+ it("should protect action from unauthorized users", async () => {
16
+ class TestApp {
17
+ ok = $action({
18
+ handler: () => "OK",
19
+ });
20
+ }
21
+
22
+ const alepha = Alepha.create().with(AlephaServerSecurity);
23
+ const app = alepha.inject(TestApp);
24
+ await alepha.start();
25
+
26
+ // in testing environment, .run() a dummy user is created
27
+ expect(await app.ok.run({})).toBe("OK");
28
+
29
+ // but you can force empty user
30
+ await expect(app.ok.run({}, { user: undefined })).rejects.toThrowError(
31
+ UnauthorizedError,
32
+ );
33
+
34
+ // .fetch() will also generates a dummy user in testing environment
35
+ expect(await app.ok.fetch({}).then((it) => it.data)).toBe("OK");
36
+
37
+ // but you can also force empty user
38
+ await expect(app.ok.fetch({}, { user: undefined })).rejects.toThrowError(
39
+ HttpError,
40
+ );
41
+
42
+ // regular fetch does not trigger helpers
43
+ expect(
44
+ await fetch(
45
+ `${alepha.inject(ServerProvider).hostname}${app.ok.route.path}`,
46
+ ).then((it) => it.json()),
47
+ ).toEqual({
48
+ error: "UnauthorizedError",
49
+ message: "Invalid authorization header, maybe token is missing ?",
50
+ status: 401,
51
+ requestId: expect.any(String),
52
+ });
53
+ });
54
+
55
+ it("should guard by permission", async () => {
56
+ class TestApp {
57
+ admin = $action({
58
+ group: "read",
59
+ handler: () => "ADMIN",
60
+ });
61
+ user = $action({
62
+ group: "read",
63
+ handler: () => "USER",
64
+ });
65
+ realm = $realm({
66
+ secret: "test",
67
+ roles: [
68
+ {
69
+ name: "admin",
70
+ permissions: [{ name: "*" }],
71
+ },
72
+ {
73
+ name: "user",
74
+ permissions: [{ name: "read:user" }],
75
+ },
76
+ ],
77
+ });
78
+ }
79
+
80
+ const alepha = Alepha.create().with(AlephaServerSecurity);
81
+ const app = alepha.inject(TestApp);
82
+ await alepha.start();
83
+
84
+ const user = {
85
+ id: randomUUID(),
86
+ roles: ["user"],
87
+ };
88
+ const admin = {
89
+ id: randomUUID(),
90
+ roles: ["admin"],
91
+ };
92
+
93
+ // as user, you can access user action
94
+ expect(await app.user.run({}, { user })).toBe("USER");
95
+ expect(await app.user.fetch({}, { user }).then((it) => it.data)).toBe(
96
+ "USER",
97
+ );
98
+
99
+ // as admin, you can access user action too
100
+ expect(await app.user.run({}, { user: admin })).toBe("USER");
101
+ expect(
102
+ await app.user.fetch({}, { user: admin }).then((it) => it.data),
103
+ ).toBe("USER");
104
+
105
+ // as user, you cannot access admin action
106
+ await expect(app.admin.run({}, { user })).rejects.toThrowError(
107
+ ForbiddenError,
108
+ );
109
+ await expect(app.admin.fetch({}, { user })).rejects.toThrowError(
110
+ new HttpError({
111
+ status: 403,
112
+ message: "User is not allowed to access 'read:admin'",
113
+ requestId: expect.any(String),
114
+ }),
115
+ );
116
+
117
+ // as admin, you can access admin action
118
+ expect(await app.admin.run({}, { user: admin })).toBe("ADMIN");
119
+ expect(
120
+ await app.admin.fetch({}, { user: admin }).then((it) => it.data),
121
+ ).toBe("ADMIN");
122
+ });
123
+ });
@@ -0,0 +1,193 @@
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { brotliCompressSync, gzipSync } from "node:zlib";
5
+ import { Alepha } from "alepha";
6
+ import { AlephaServer, ServerProvider } from "alepha/server";
7
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
8
+ import {
9
+ $serve,
10
+ AlephaServerStatic,
11
+ type ServePrimitiveOptions,
12
+ } from "../index.ts";
13
+
14
+ // --- Test Setup: Create a temporary directory for static files ---
15
+
16
+ const tempTestDir = join(tmpdir(), `alepha-static-test-${Date.now()}`);
17
+ const tempWeirdFileName = "weird file 02020&&&&&éééé";
18
+
19
+ beforeAll(async () => {
20
+ await mkdir(tempTestDir, { recursive: true });
21
+
22
+ // Create some test files
23
+ await writeFile(join(tempTestDir, "index.html"), "<h1>Hello World</h1>");
24
+ await writeFile(join(tempTestDir, "style.css"), "body { color: red; }");
25
+ await writeFile(join(tempTestDir, "script.js"), "console.log('test');");
26
+ await writeFile(join(tempTestDir, ".secret"), "should-not-be-served");
27
+ await writeFile(join(tempTestDir, tempWeirdFileName), "ok");
28
+
29
+ // Create pre-compressed versions
30
+ const cssContent = "body { color: blue; }";
31
+ await writeFile(join(tempTestDir, "compressed.css"), cssContent);
32
+ await writeFile(join(tempTestDir, "compressed.css.gz"), gzipSync(cssContent));
33
+ await writeFile(
34
+ join(tempTestDir, "compressed.css.br"),
35
+ brotliCompressSync(cssContent),
36
+ );
37
+ });
38
+
39
+ afterAll(async () => {
40
+ // Clean up the temporary directory
41
+ await rm(tempTestDir, { recursive: true, force: true });
42
+ });
43
+
44
+ // --- Test Suite ---
45
+
46
+ describe("alepha/server/static", () => {
47
+ const setupServer = async (serveOptions: ServePrimitiveOptions) => {
48
+ class TestApp {
49
+ staticContent = $serve({ root: tempTestDir, ...serveOptions });
50
+ }
51
+
52
+ const alepha = Alepha.create({ env: { LOG_LEVEL: "error" } })
53
+ .with(AlephaServer)
54
+ .with(AlephaServerStatic)
55
+ .with(TestApp);
56
+
57
+ await alepha.start();
58
+ const server = alepha.inject(ServerProvider);
59
+
60
+ return {
61
+ hostname: server.hostname,
62
+ };
63
+ };
64
+
65
+ test("should serve a basic static file with correct content-type", async () => {
66
+ const { hostname } = await setupServer({});
67
+
68
+ const response = await fetch(`${hostname}/style.css`);
69
+
70
+ expect(response.status).toBe(200);
71
+ expect(response.headers.get("content-type")).toBe("text/css");
72
+ expect(await response.text()).toBe("body { color: red; }");
73
+ });
74
+
75
+ test("should serve a file with invalid character", async () => {
76
+ const { hostname } = await setupServer({});
77
+ const r1 = await fetch(`${hostname}/${tempWeirdFileName}`);
78
+ expect(r1.status).toBe(200);
79
+ expect(await r1.text()).toBe("ok");
80
+
81
+ const r2 = await fetch(`${hostname}/${encodeURI(tempWeirdFileName)}`);
82
+ expect(r2.status).toBe(200);
83
+ expect(await r2.text()).toBe("ok");
84
+ });
85
+
86
+ test("should serve index.html for root path", async () => {
87
+ const { hostname } = await setupServer({});
88
+
89
+ const response = await fetch(`${hostname}/`);
90
+
91
+ expect(response.status).toBe(200);
92
+ expect(response.headers.get("content-type")).toBe("text/html");
93
+ expect(await response.text()).toBe("<h1>Hello World</h1>");
94
+ });
95
+
96
+ test("should handle ETag and Last-Modified headers for caching", async () => {
97
+ const { hostname } = await setupServer({});
98
+
99
+ const initialResponse = await fetch(`${hostname}/script.js`);
100
+ const etag = initialResponse.headers.get("etag");
101
+ const lastModified = initialResponse.headers.get("last-modified");
102
+
103
+ expect(etag).toBeDefined();
104
+ expect(lastModified).toBeDefined();
105
+
106
+ // Second request with caching headers
107
+ const cachedResponse = await fetch(`${hostname}/script.js`, {
108
+ headers: {
109
+ "if-none-match": etag!,
110
+ "if-modified-since": lastModified!,
111
+ },
112
+ });
113
+
114
+ expect(cachedResponse.status).toBe(304); // Not Modified
115
+ expect(await cachedResponse.text()).toBe(""); // Body should be empty
116
+ });
117
+
118
+ test("should serve pre-compressed .gz file if accepted", async () => {
119
+ const { hostname } = await setupServer({});
120
+
121
+ const response = await fetch(`${hostname}/compressed.css`, {
122
+ headers: { "Accept-Encoding": "gzip, deflate, br" },
123
+ });
124
+
125
+ expect(response.status).toBe(200);
126
+ expect(response.headers.get("content-encoding")).toBe("br");
127
+ expect(response.headers.get("content-type")).toBe("text/css");
128
+ // The fetched content will be automatically decompressed by fetch
129
+ expect(await response.text()).toBe("body { color: blue; }");
130
+ });
131
+
132
+ test("should serve pre-compressed .br file if accepted and preferred", async () => {
133
+ const { hostname } = await setupServer({});
134
+
135
+ // Brotli is generally preferred by servers if available
136
+ const response = await fetch(`${hostname}/compressed.css`, {
137
+ headers: { "Accept-Encoding": "br, gzip" },
138
+ });
139
+
140
+ expect(response.status).toBe(200);
141
+ expect(response.headers.get("content-encoding")).toBe("br");
142
+ expect(await response.text()).toBe("body { color: blue; }");
143
+ });
144
+
145
+ test("should not serve dotfiles by default", async () => {
146
+ const { hostname } = await setupServer({});
147
+
148
+ const response = await fetch(`${hostname}/.secret`);
149
+ expect(response.status).toBe(404);
150
+ });
151
+
152
+ test("should serve dotfiles if ignoreDotEnvFiles is false", async () => {
153
+ const { hostname } = await setupServer({ ignoreDotEnvFiles: false });
154
+
155
+ const response = await fetch(`${hostname}/.secret`);
156
+ expect(response.status).toBe(200);
157
+ expect(await response.text()).toBe("should-not-be-served");
158
+ });
159
+
160
+ test("should use historyApiFallback for SPA routing", async () => {
161
+ const { hostname } = await setupServer({ historyApiFallback: true });
162
+
163
+ // A path that doesn't correspond to a real file
164
+ const response = await fetch(`${hostname}/some/deep/spa/route`);
165
+
166
+ expect(response.status).toBe(200);
167
+ expect(response.headers.get("content-type")).toBe("text/html");
168
+ expect(await response.text()).toBe("<h1>Hello World</h1>");
169
+
170
+ // Should still not fallback for paths that look like files
171
+ const fileResponse = await fetch(`${hostname}/non-existent/style.css`);
172
+ expect(fileResponse.status).toBe(404);
173
+ });
174
+
175
+ test("should apply Cache-Control headers for configured file types", async () => {
176
+ const { hostname } = await setupServer({
177
+ cacheControl: {
178
+ fileTypes: [".css"],
179
+ maxAge: [1, "day"],
180
+ immutable: true,
181
+ },
182
+ });
183
+
184
+ const cssResponse = await fetch(`${hostname}/style.css`);
185
+ expect(cssResponse.headers.get("cache-control")).toBe(
186
+ "public, max-age=86400, immutable",
187
+ );
188
+
189
+ // JS file should not have the header
190
+ const jsResponse = await fetch(`${hostname}/script.js`);
191
+ expect(jsResponse.headers.get("cache-control")).toBeNull();
192
+ });
193
+ });