alepha 0.14.2 → 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 (361) hide show
  1. package/dist/api/audits/index.browser.js +5 -5
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +784 -784
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +13 -13
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +5 -5
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +57 -57
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +71 -71
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +5 -5
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +165 -165
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +10 -10
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/notifications/index.browser.js +10 -10
  20. package/dist/api/notifications/index.browser.js.map +1 -1
  21. package/dist/api/notifications/index.d.ts +583 -171
  22. package/dist/api/notifications/index.d.ts.map +1 -1
  23. package/dist/api/notifications/index.js +12 -12
  24. package/dist/api/notifications/index.js.map +1 -1
  25. package/dist/api/parameters/index.browser.js +163 -10
  26. package/dist/api/parameters/index.browser.js.map +1 -1
  27. package/dist/api/parameters/index.d.ts +281 -276
  28. package/dist/api/parameters/index.d.ts.map +1 -1
  29. package/dist/api/parameters/index.js +196 -91
  30. package/dist/api/parameters/index.js.map +1 -1
  31. package/dist/api/users/index.browser.js +19 -19
  32. package/dist/api/users/index.browser.js.map +1 -1
  33. package/dist/api/users/index.d.ts +1137 -1123
  34. package/dist/api/users/index.d.ts.map +1 -1
  35. package/dist/api/users/index.js +827 -596
  36. package/dist/api/users/index.js.map +1 -1
  37. package/dist/api/verifications/index.browser.js +6 -6
  38. package/dist/api/verifications/index.browser.js.map +1 -1
  39. package/dist/api/verifications/index.d.ts +13 -13
  40. package/dist/api/verifications/index.d.ts.map +1 -1
  41. package/dist/api/verifications/index.js +6 -6
  42. package/dist/api/verifications/index.js.map +1 -1
  43. package/dist/bin/index.d.ts +1 -2
  44. package/dist/bin/index.js +0 -1
  45. package/dist/bin/index.js.map +1 -1
  46. package/dist/cli/index.d.ts +137 -112
  47. package/dist/cli/index.d.ts.map +1 -1
  48. package/dist/cli/index.js +371 -259
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/command/index.d.ts +45 -5
  51. package/dist/command/index.d.ts.map +1 -1
  52. package/dist/command/index.js +97 -17
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js +14 -18
  55. package/dist/core/index.browser.js.map +1 -1
  56. package/dist/core/index.d.ts +29 -0
  57. package/dist/core/index.d.ts.map +1 -1
  58. package/dist/core/index.js +14 -18
  59. package/dist/core/index.js.map +1 -1
  60. package/dist/core/index.native.js +14 -18
  61. package/dist/core/index.native.js.map +1 -1
  62. package/dist/fake/index.js +195 -168
  63. package/dist/fake/index.js.map +1 -1
  64. package/dist/file/index.d.ts +8 -0
  65. package/dist/file/index.d.ts.map +1 -1
  66. package/dist/file/index.js +3 -0
  67. package/dist/file/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts.map +1 -1
  69. package/dist/orm/index.d.ts +32 -32
  70. package/dist/orm/index.d.ts.map +1 -1
  71. package/dist/orm/index.js +12 -12
  72. package/dist/orm/index.js.map +1 -1
  73. package/dist/security/index.d.ts +1 -1
  74. package/dist/security/index.d.ts.map +1 -1
  75. package/dist/security/index.js +1 -1
  76. package/dist/security/index.js.map +1 -1
  77. package/dist/server/auth/index.d.ts +171 -155
  78. package/dist/server/auth/index.d.ts.map +1 -1
  79. package/dist/server/auth/index.js +0 -1
  80. package/dist/server/auth/index.js.map +1 -1
  81. package/dist/server/compress/index.d.ts.map +1 -1
  82. package/dist/server/compress/index.js +2 -0
  83. package/dist/server/compress/index.js.map +1 -1
  84. package/dist/server/core/index.d.ts.map +1 -1
  85. package/dist/server/core/index.js +1 -1
  86. package/dist/server/core/index.js.map +1 -1
  87. package/dist/server/links/index.browser.js +22 -6
  88. package/dist/server/links/index.browser.js.map +1 -1
  89. package/dist/server/links/index.d.ts +46 -44
  90. package/dist/server/links/index.d.ts.map +1 -1
  91. package/dist/server/links/index.js +24 -41
  92. package/dist/server/links/index.js.map +1 -1
  93. package/dist/server/security/index.d.ts +9 -9
  94. package/dist/server/swagger/index.d.ts +2 -1
  95. package/dist/server/swagger/index.d.ts.map +1 -1
  96. package/dist/server/swagger/index.js +8 -3
  97. package/dist/server/swagger/index.js.map +1 -1
  98. package/dist/vite/index.d.ts.map +1 -1
  99. package/dist/vite/index.js +12 -4
  100. package/dist/vite/index.js.map +1 -1
  101. package/dist/websocket/index.d.ts +7 -7
  102. package/package.json +7 -7
  103. package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
  104. package/src/api/audits/entities/audits.ts +5 -5
  105. package/src/api/audits/index.browser.ts +1 -1
  106. package/src/api/audits/index.ts +3 -3
  107. package/src/api/audits/primitives/$audit.spec.ts +276 -0
  108. package/src/api/audits/services/AuditService.spec.ts +495 -0
  109. package/src/api/files/__tests__/$bucket.spec.ts +91 -0
  110. package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
  111. package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
  112. package/src/api/files/controllers/FileController.spec.ts +558 -0
  113. package/src/api/files/controllers/FileController.ts +4 -5
  114. package/src/api/files/entities/files.ts +5 -5
  115. package/src/api/files/index.browser.ts +1 -1
  116. package/src/api/files/index.ts +4 -4
  117. package/src/api/files/jobs/FileJobs.spec.ts +52 -0
  118. package/src/api/files/services/FileService.spec.ts +109 -0
  119. package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
  120. package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
  121. package/src/api/jobs/entities/jobExecutions.ts +5 -5
  122. package/src/api/jobs/index.ts +3 -3
  123. package/src/api/jobs/primitives/$job.spec.ts +476 -0
  124. package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
  125. package/src/api/notifications/entities/notifications.ts +5 -5
  126. package/src/api/notifications/index.browser.ts +1 -1
  127. package/src/api/notifications/index.ts +4 -4
  128. package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
  129. package/src/api/parameters/entities/parameters.ts +7 -17
  130. package/src/api/parameters/index.ts +3 -3
  131. package/src/api/parameters/primitives/$config.spec.ts +356 -0
  132. package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
  133. package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
  134. package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
  135. package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
  136. package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
  137. package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
  138. package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
  139. package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
  140. package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
  141. package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
  142. package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
  143. package/src/api/parameters/schemas/index.ts +15 -0
  144. package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
  145. package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
  146. package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
  147. package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
  148. package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
  149. package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
  150. package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
  151. package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
  152. package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
  153. package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
  154. package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
  155. package/src/api/users/controllers/AdminUserController.ts +116 -0
  156. package/src/api/users/controllers/UserController.ts +4 -107
  157. package/src/api/users/controllers/UserRealmController.ts +3 -0
  158. package/src/api/users/entities/identities.ts +6 -6
  159. package/src/api/users/entities/sessions.ts +6 -6
  160. package/src/api/users/entities/users.ts +9 -9
  161. package/src/api/users/index.ts +9 -6
  162. package/src/api/users/primitives/$userRealm.ts +13 -8
  163. package/src/api/users/services/CredentialService.spec.ts +509 -0
  164. package/src/api/users/services/CredentialService.ts +46 -0
  165. package/src/api/users/services/IdentityService.ts +15 -0
  166. package/src/api/users/services/RegistrationService.spec.ts +630 -0
  167. package/src/api/users/services/RegistrationService.ts +18 -0
  168. package/src/api/users/services/SessionService.spec.ts +301 -0
  169. package/src/api/users/services/SessionService.ts +110 -1
  170. package/src/api/users/services/UserService.ts +67 -2
  171. package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
  172. package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
  173. package/src/api/verifications/entities/verifications.ts +6 -6
  174. package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
  175. package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
  176. package/src/batch/primitives/$batch.spec.ts +766 -0
  177. package/src/batch/providers/BatchProvider.spec.ts +786 -0
  178. package/src/bin/index.ts +0 -1
  179. package/src/bucket/__tests__/shared.ts +194 -0
  180. package/src/bucket/primitives/$bucket.spec.ts +104 -0
  181. package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
  182. package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
  183. package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
  184. package/src/cache/core/__tests__/shared.ts +377 -0
  185. package/src/cache/core/primitives/$cache.spec.ts +111 -0
  186. package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
  187. package/src/cli/apps/AlephaCli.ts +25 -4
  188. package/src/cli/commands/dev.ts +19 -7
  189. package/src/cli/commands/gen/changelog.spec.ts +315 -0
  190. package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
  191. package/src/cli/commands/gen/openapi.ts +71 -0
  192. package/src/cli/commands/gen.ts +18 -0
  193. package/src/cli/commands/init.ts +2 -0
  194. package/src/cli/commands/root.ts +12 -3
  195. package/src/cli/commands/typecheck.ts +5 -0
  196. package/src/cli/index.ts +2 -1
  197. package/src/cli/services/AlephaCliUtils.ts +71 -32
  198. package/src/cli/services/GitMessageParser.ts +1 -1
  199. package/src/command/helpers/Asker.spec.ts +127 -0
  200. package/src/command/helpers/Runner.spec.ts +126 -0
  201. package/src/command/primitives/$command.spec.ts +1588 -0
  202. package/src/command/providers/CliProvider.ts +74 -24
  203. package/src/core/Alepha.ts +45 -0
  204. package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
  205. package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
  206. package/src/core/__tests__/Alepha-has.spec.ts +41 -0
  207. package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
  208. package/src/core/__tests__/Alepha-register.spec.ts +81 -0
  209. package/src/core/__tests__/Alepha-start.spec.ts +176 -0
  210. package/src/core/__tests__/Alepha-with.spec.ts +14 -0
  211. package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
  212. package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
  213. package/src/core/__tests__/descriptor.spec.ts +34 -0
  214. package/src/core/__tests__/fixtures/A.ts +5 -0
  215. package/src/core/__tests__/pagination.spec.ts +77 -0
  216. package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
  217. package/src/core/primitives/$atom.spec.ts +43 -0
  218. package/src/core/primitives/$hook.spec.ts +130 -0
  219. package/src/core/primitives/$inject.spec.ts +175 -0
  220. package/src/core/primitives/$module.spec.ts +115 -0
  221. package/src/core/providers/CodecManager.spec.ts +740 -0
  222. package/src/core/providers/EventManager.spec.ts +762 -0
  223. package/src/core/providers/EventManager.ts +4 -0
  224. package/src/core/providers/StateManager.spec.ts +365 -0
  225. package/src/core/providers/TypeProvider.spec.ts +1607 -0
  226. package/src/core/providers/TypeProvider.ts +20 -26
  227. package/src/datetime/primitives/$interval.spec.ts +103 -0
  228. package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
  229. package/src/email/primitives/$email.spec.ts +175 -0
  230. package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
  231. package/src/fake/__tests__/keyName.example.ts +40 -0
  232. package/src/fake/__tests__/keyName.spec.ts +152 -0
  233. package/src/fake/__tests__/module.example.ts +32 -0
  234. package/src/fake/providers/FakeProvider.spec.ts +438 -0
  235. package/src/file/providers/FileSystemProvider.ts +8 -0
  236. package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
  237. package/src/file/providers/NodeFileSystemProvider.ts +5 -0
  238. package/src/file/services/FileDetector.spec.ts +591 -0
  239. package/src/lock/core/__tests__/shared.ts +190 -0
  240. package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
  241. package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
  242. package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
  243. package/src/logger/primitives/$logger.spec.ts +108 -0
  244. package/src/logger/services/Logger.spec.ts +295 -0
  245. package/src/mcp/__tests__/errors.spec.ts +175 -0
  246. package/src/mcp/__tests__/integration.spec.ts +450 -0
  247. package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
  248. package/src/mcp/primitives/$prompt.spec.ts +468 -0
  249. package/src/mcp/primitives/$resource.spec.ts +390 -0
  250. package/src/mcp/primitives/$tool.spec.ts +406 -0
  251. package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
  252. package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
  253. package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
  254. package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
  255. package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
  256. package/src/orm/__tests__/$repository-save.spec.ts +37 -0
  257. package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
  258. package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
  259. package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
  260. package/src/orm/__tests__/delete-returning.spec.ts +256 -0
  261. package/src/orm/__tests__/deletedAt.spec.ts +80 -0
  262. package/src/orm/__tests__/enums.spec.ts +315 -0
  263. package/src/orm/__tests__/execute.spec.ts +72 -0
  264. package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
  265. package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
  266. package/src/orm/__tests__/joins.spec.ts +1114 -0
  267. package/src/orm/__tests__/page.spec.ts +287 -0
  268. package/src/orm/__tests__/primaryKey.spec.ts +87 -0
  269. package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
  270. package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
  271. package/src/orm/__tests__/references.spec.ts +102 -0
  272. package/src/orm/__tests__/security.spec.ts +710 -0
  273. package/src/orm/__tests__/sqlite.spec.ts +111 -0
  274. package/src/orm/__tests__/string-operators.spec.ts +429 -0
  275. package/src/orm/__tests__/timestamps.spec.ts +388 -0
  276. package/src/orm/__tests__/validation.spec.ts +183 -0
  277. package/src/orm/__tests__/version.spec.ts +64 -0
  278. package/src/orm/helpers/parseQueryString.spec.ts +196 -0
  279. package/src/orm/primitives/$repository.spec.ts +137 -0
  280. package/src/orm/primitives/$sequence.spec.ts +29 -0
  281. package/src/orm/primitives/$transaction.spec.ts +82 -0
  282. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
  283. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  284. package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
  285. package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
  286. package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
  287. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  288. package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
  289. package/src/orm/services/ModelBuilder.spec.ts +575 -0
  290. package/src/orm/services/Repository.spec.ts +137 -0
  291. package/src/queue/core/__tests__/shared.ts +143 -0
  292. package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
  293. package/src/queue/core/providers/WorkerProvider.spec.ts +378 -0
  294. package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
  295. package/src/redis/__tests__/redis.spec.ts +58 -0
  296. package/src/retry/primitives/$retry.spec.ts +234 -0
  297. package/src/retry/providers/RetryProvider.spec.ts +438 -0
  298. package/src/router/__tests__/match.spec.ts +252 -0
  299. package/src/router/providers/RouterProvider.spec.ts +197 -0
  300. package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
  301. package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
  302. package/src/scheduler/__tests__/shared.ts +77 -0
  303. package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
  304. package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
  305. package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
  306. package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
  307. package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
  308. package/src/security/primitives/$permission.spec.ts +30 -0
  309. package/src/security/primitives/$permission.ts +2 -2
  310. package/src/security/primitives/$realm.spec.ts +101 -0
  311. package/src/security/primitives/$role.spec.ts +52 -0
  312. package/src/security/primitives/$serviceAccount.spec.ts +61 -0
  313. package/src/security/providers/SecurityProvider.spec.ts +350 -0
  314. package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
  315. package/src/server/cache/providers/ServerCacheProvider.spec.ts +942 -0
  316. package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
  317. package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
  318. package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
  319. package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
  320. package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
  321. package/src/server/core/primitives/$action.spec.ts +191 -0
  322. package/src/server/core/primitives/$route.spec.ts +65 -0
  323. package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
  324. package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
  325. package/src/server/core/providers/ServerProvider.ts +3 -1
  326. package/src/server/core/services/HttpClient.spec.ts +123 -0
  327. package/src/server/core/services/UserAgentParser.spec.ts +111 -0
  328. package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
  329. package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
  330. package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
  331. package/src/server/links/__tests__/$action.spec.ts +238 -0
  332. package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
  333. package/src/server/links/__tests__/requestId.spec.ts +120 -0
  334. package/src/server/links/primitives/$remote.spec.ts +228 -0
  335. package/src/server/links/providers/LinkProvider.spec.ts +54 -0
  336. package/src/server/links/providers/LinkProvider.ts +49 -3
  337. package/src/server/links/providers/ServerLinksProvider.ts +1 -53
  338. package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
  339. package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
  340. package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
  341. package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
  342. package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
  343. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
  344. package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
  345. package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
  346. package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
  347. package/src/server/static/primitives/$serve.spec.ts +193 -0
  348. package/src/server/swagger/__tests__/ui.spec.ts +52 -0
  349. package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
  350. package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
  351. package/src/sms/primitives/$sms.spec.ts +165 -0
  352. package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
  353. package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
  354. package/src/thread/primitives/$thread.spec.ts +186 -0
  355. package/src/topic/core/__tests__/shared.ts +144 -0
  356. package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
  357. package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
  358. package/src/vite/plugins/viteAlephaDev.ts +16 -4
  359. package/src/vite/tasks/runAlepha.ts +7 -1
  360. package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
  361. package/src/websocket/primitives/$channel.spec.ts +30 -0
@@ -0,0 +1,942 @@
1
+ import { Alepha } from "alepha";
2
+ import { $action } from "alepha/server";
3
+ import { afterEach, beforeEach, describe, test } from "vitest";
4
+ import { AlephaServerCache, ServerCacheProvider } from "../index.ts";
5
+
6
+ class TestApp {
7
+ counter = 0;
8
+ private resetCounter = 0;
9
+
10
+ cachedAction = $action({
11
+ cache: true,
12
+ handler: () => `cached-${this.counter++}`,
13
+ });
14
+
15
+ uncachedAction = $action({
16
+ cache: false,
17
+ handler: () => `uncached-${this.counter++}`,
18
+ });
19
+
20
+ cachedWithCustomTtl = $action({
21
+ cache: { store: { ttl: 1000 } },
22
+ handler: () => `ttl-cached-${this.counter++}`,
23
+ });
24
+
25
+ asyncCachedAction = $action({
26
+ cache: true,
27
+ handler: async () => {
28
+ await new Promise((resolve) => setTimeout(resolve, 10));
29
+ return `async-cached-${this.counter++}`;
30
+ },
31
+ });
32
+
33
+ etagOnlyAction = $action({
34
+ cache: { etag: true },
35
+ handler: () => `etag-only-${this.counter++}`,
36
+ });
37
+
38
+ etagAndCacheAction = $action({
39
+ cache: true,
40
+ handler: () => `etag-and-cache-${this.counter++}`,
41
+ });
42
+
43
+ dynamicContentAction = $action({
44
+ cache: { etag: true },
45
+ handler: () => `dynamic-${Date.now()}`,
46
+ });
47
+
48
+ cacheWithControlTrue = $action({
49
+ cache: { store: true, control: true },
50
+ handler: () => `control-true-${this.counter++}`,
51
+ });
52
+
53
+ cacheWithControlString = $action({
54
+ cache: { store: true, control: "public, max-age=600, immutable" },
55
+ handler: () => `control-string-${this.counter++}`,
56
+ });
57
+
58
+ cacheWithControlObject = $action({
59
+ cache: {
60
+ store: true,
61
+ control: {
62
+ public: true,
63
+ maxAge: 3600,
64
+ mustRevalidate: true,
65
+ },
66
+ },
67
+ handler: () => `control-object-${this.counter++}`,
68
+ });
69
+
70
+ cacheWithComplexControl = $action({
71
+ cache: {
72
+ store: {
73
+ ttl: [10, "minutes"],
74
+ },
75
+ etag: true,
76
+ control: {
77
+ public: true,
78
+ maxAge: 600,
79
+ sMaxAge: 1200,
80
+ immutable: true,
81
+ },
82
+ },
83
+ handler: () => `complex-control-${this.counter++}`,
84
+ });
85
+
86
+ cacheWithDurationMaxAge = $action({
87
+ cache: {
88
+ store: true,
89
+ control: {
90
+ public: true,
91
+ maxAge: [5, "minutes"],
92
+ sMaxAge: [1, "hour"],
93
+ },
94
+ },
95
+ handler: () => `duration-maxage-${this.counter++}`,
96
+ });
97
+
98
+ cacheWith30Seconds = $action({
99
+ cache: {
100
+ store: true,
101
+ control: {
102
+ public: true,
103
+ maxAge: [30, "seconds"],
104
+ },
105
+ },
106
+ handler: () => "test-30s",
107
+ });
108
+
109
+ cacheWith10Minutes = $action({
110
+ cache: {
111
+ store: true,
112
+ control: {
113
+ public: true,
114
+ maxAge: [10, "minutes"],
115
+ },
116
+ },
117
+ handler: () => "test-10m",
118
+ });
119
+
120
+ cacheWith2Hours = $action({
121
+ cache: {
122
+ store: true,
123
+ control: {
124
+ public: true,
125
+ maxAge: [2, "hours"],
126
+ },
127
+ },
128
+ handler: () => "test-2h",
129
+ });
130
+
131
+ cacheWith1Day = $action({
132
+ cache: {
133
+ store: true,
134
+ control: {
135
+ public: true,
136
+ maxAge: [1, "day"],
137
+ },
138
+ },
139
+ handler: () => "test-1d",
140
+ });
141
+
142
+ cacheWithMixedMaxAge = $action({
143
+ cache: {
144
+ store: true,
145
+ control: {
146
+ public: true,
147
+ maxAge: 600, // number (seconds)
148
+ sMaxAge: [20, "minutes"], // DurationLike
149
+ },
150
+ },
151
+ handler: () => "mixed",
152
+ });
153
+
154
+ errorAction = $action({
155
+ cache: true,
156
+ handler: ({ reply }) => {
157
+ reply.status = 500;
158
+ return `error-${this.counter++}`;
159
+ },
160
+ });
161
+
162
+ notFoundAction = $action({
163
+ cache: true,
164
+ handler: ({ reply }) => {
165
+ reply.status = 404;
166
+ return `not-found-${this.counter++}`;
167
+ },
168
+ });
169
+
170
+ conditionalErrorAction = $action({
171
+ cache: true,
172
+ handler: ({ reply }) => {
173
+ if (this.counter === 0) {
174
+ reply.status = 500;
175
+ this.counter++;
176
+ return "error-0";
177
+ }
178
+ return `success-${this.counter++}`;
179
+ },
180
+ });
181
+
182
+ reset() {
183
+ this.counter = this.resetCounter++;
184
+ }
185
+ }
186
+
187
+ describe("ServerCacheProvider", () => {
188
+ let alepha: Alepha;
189
+ let app: TestApp;
190
+ let cacheProvider: ServerCacheProvider;
191
+
192
+ beforeEach(async () => {
193
+ alepha = Alepha.create().with(AlephaServerCache);
194
+ app = alepha.inject(TestApp);
195
+ cacheProvider = alepha.inject(ServerCacheProvider);
196
+ await alepha.start();
197
+ });
198
+
199
+ afterEach(async () => {
200
+ await alepha?.stop();
201
+ });
202
+
203
+ describe("ETag support", () => {
204
+ test("should return 200 on first request and 304 on subsequent requests with same ETag", async ({
205
+ expect,
206
+ }) => {
207
+ const firstResponse = await app.cachedAction.fetch();
208
+ expect(firstResponse.status).toBe(200);
209
+ expect(firstResponse.data).toBe("cached-0");
210
+
211
+ const secondResponse = await app.cachedAction.fetch();
212
+ expect(secondResponse.status).toBe(304);
213
+ expect(secondResponse.data).toBe("cached-0");
214
+ });
215
+
216
+ test("should return fresh data after cache invalidation", async ({
217
+ expect,
218
+ }) => {
219
+ const firstResponse = await app.cachedAction.fetch();
220
+ expect(firstResponse.data).toBe("cached-0");
221
+
222
+ await cacheProvider.invalidate(app.cachedAction.route);
223
+
224
+ const afterInvalidation = await app.cachedAction.fetch();
225
+ expect(afterInvalidation.status).toBe(200);
226
+ expect(afterInvalidation.data).toBe("cached-1");
227
+ });
228
+
229
+ test("should set etag property on route and handle caching correctly", async ({
230
+ expect,
231
+ }) => {
232
+ // Make first request to trigger caching
233
+ const response = await app.cachedAction.fetch();
234
+ expect(response.status).toBe(200);
235
+ expect(response.data).toBe("cached-0");
236
+
237
+ // Subsequent request should return 304 (cached with etag check)
238
+ const cachedResponse = await app.cachedAction.fetch();
239
+ expect(cachedResponse.status).toBe(304);
240
+
241
+ // Cache invalidation should work
242
+ await cacheProvider.invalidate(app.cachedAction.route);
243
+ const afterInvalidation = await app.cachedAction.fetch();
244
+ expect(afterInvalidation.status).toBe(200);
245
+ expect(afterInvalidation.data).toBe("cached-1");
246
+ });
247
+ });
248
+
249
+ describe("Cache behavior", () => {
250
+ test("should cache responses for actions with cache: true", async ({
251
+ expect,
252
+ }) => {
253
+ for (let i = 0; i < 5; i++) {
254
+ const response1 = await app.cachedAction.fetch();
255
+ const response2 = await app.cachedAction.fetch();
256
+ expect(response1.data).toBe(response2.data);
257
+ app.reset();
258
+ }
259
+ });
260
+
261
+ test("should not cache responses for actions with cache: false", async ({
262
+ expect,
263
+ }) => {
264
+ for (let i = 0; i < 5; i++) {
265
+ const response1 = await app.uncachedAction.fetch();
266
+ const response2 = await app.uncachedAction.fetch();
267
+ expect(response1.data).not.toBe(response2.data);
268
+ }
269
+ });
270
+
271
+ test("should handle async cached actions correctly", async ({ expect }) => {
272
+ const response1 = await app.asyncCachedAction.fetch();
273
+ const response2 = await app.asyncCachedAction.fetch();
274
+
275
+ expect(response1.data).toBe("async-cached-0");
276
+ expect(response2.data).toBe("async-cached-0");
277
+ expect(response1.data).toBe(response2.data);
278
+ });
279
+ });
280
+
281
+ describe("Cache invalidation", () => {
282
+ test("should invalidate cache using ServerCacheProvider.invalidate()", async ({
283
+ expect,
284
+ }) => {
285
+ const initialResponse = await app.cachedAction.fetch();
286
+ const cachedResponse = await app.cachedAction.fetch();
287
+
288
+ expect(initialResponse.data).toBe(cachedResponse.data);
289
+
290
+ await cacheProvider.invalidate(app.cachedAction.route);
291
+
292
+ const afterInvalidation = await app.cachedAction.fetch();
293
+ expect(afterInvalidation.data).not.toBe(initialResponse.data);
294
+ });
295
+
296
+ test("should invalidate cache using action.invalidate() method", async ({
297
+ expect,
298
+ }) => {
299
+ const initialResponse = await app.cachedAction.fetch();
300
+ const cachedResponse = await app.cachedAction.fetch();
301
+
302
+ expect(initialResponse.data).toBe(cachedResponse.data);
303
+
304
+ await app.cachedAction.invalidate();
305
+
306
+ const afterInvalidation = await app.cachedAction.fetch();
307
+ expect(afterInvalidation.data).not.toBe(initialResponse.data);
308
+ });
309
+
310
+ test("should not affect other cached actions when invalidating specific route", async ({
311
+ expect,
312
+ }) => {
313
+ const cachedResponse1 = await app.cachedAction.fetch();
314
+ const ttlCachedResponse1 = await app.cachedWithCustomTtl.fetch();
315
+
316
+ await cacheProvider.invalidate(app.cachedAction.route);
317
+
318
+ const cachedResponse2 = await app.cachedAction.fetch();
319
+ const ttlCachedResponse2 = await app.cachedWithCustomTtl.fetch();
320
+
321
+ expect(cachedResponse1.data).not.toBe(cachedResponse2.data);
322
+ expect(ttlCachedResponse1.data).toBe(ttlCachedResponse2.data);
323
+ });
324
+ });
325
+
326
+ describe("Cache configuration", () => {
327
+ test("should respect custom TTL configuration", async ({ expect }) => {
328
+ const response1 = await app.cachedWithCustomTtl.fetch();
329
+ const response2 = await app.cachedWithCustomTtl.fetch();
330
+
331
+ expect(response1.data).toBe(response2.data);
332
+ expect(response1.data).toBe("ttl-cached-0");
333
+ });
334
+ });
335
+
336
+ describe("Error handling", () => {
337
+ test("should handle invalidation of non-existent route gracefully", async ({
338
+ expect,
339
+ }) => {
340
+ expect(async () => {
341
+ await cacheProvider.invalidate("non-existent-route" as any);
342
+ }).not.toThrow();
343
+ });
344
+
345
+ test("should handle multiple concurrent cache requests correctly", async ({
346
+ expect,
347
+ }) => {
348
+ const promises = Array.from({ length: 10 }, () =>
349
+ app.cachedAction.fetch(),
350
+ );
351
+ const responses = await Promise.all(promises);
352
+
353
+ const firstData = responses[0].data;
354
+ for (const response of responses) {
355
+ expect(response.data).toBe(firstData);
356
+ }
357
+ });
358
+
359
+ test("should handle cache invalidation during concurrent requests", async ({
360
+ expect,
361
+ }) => {
362
+ const initialResponse = await app.cachedAction.fetch();
363
+
364
+ const concurrentPromises = [
365
+ app.cachedAction.fetch(),
366
+ app.cachedAction.fetch(),
367
+ ];
368
+
369
+ const [cachedResponse1, cachedResponse2] =
370
+ await Promise.all(concurrentPromises);
371
+
372
+ expect(cachedResponse1.data).toBe(initialResponse.data);
373
+ expect(cachedResponse2.data).toBe(initialResponse.data);
374
+
375
+ await cacheProvider.invalidate(app.cachedAction.route);
376
+
377
+ const finalResponse = await app.cachedAction.fetch();
378
+ expect(finalResponse.data).not.toBe(initialResponse.data);
379
+ });
380
+ });
381
+
382
+ describe("Provider lifecycle", () => {
383
+ test("should maintain cache state across multiple requests", async ({
384
+ expect,
385
+ }) => {
386
+ const responses = [];
387
+ for (let i = 0; i < 5; i++) {
388
+ responses.push(await app.cachedAction.fetch());
389
+ }
390
+
391
+ const firstData = responses[0].data;
392
+ for (const response of responses) {
393
+ expect(response.data).toBe(firstData);
394
+ }
395
+ });
396
+
397
+ test("should clear cache when application stops and restarts", async ({
398
+ expect,
399
+ }) => {
400
+ await app.cachedAction.fetch();
401
+ await alepha.stop();
402
+
403
+ alepha = Alepha.create().with(AlephaServerCache);
404
+ app = alepha.inject(TestApp);
405
+ await alepha.start();
406
+
407
+ const afterRestartResponse = await app.cachedAction.fetch();
408
+ expect(afterRestartResponse.data).toBe("cached-0");
409
+ });
410
+ });
411
+
412
+ describe("ETag-only support (without cache)", () => {
413
+ test("should generate and return ETag header for etag-only routes", async ({
414
+ expect,
415
+ }) => {
416
+ const response = await app.etagOnlyAction.fetch();
417
+
418
+ expect(response.status).toBe(200);
419
+ expect(response.data).toBe("etag-only-0");
420
+ expect(response.headers.get("etag")).toBeDefined();
421
+ expect(response.headers.get("last-modified")).toBeDefined();
422
+ });
423
+
424
+ test("should return 304 when client sends matching ETag", async ({
425
+ expect,
426
+ }) => {
427
+ const firstResponse = await app.etagOnlyAction.fetch();
428
+ const etag = firstResponse.headers.get("etag");
429
+
430
+ expect(firstResponse.status).toBe(200);
431
+ expect(firstResponse.data).toBe("etag-only-0");
432
+ expect(etag).toBeDefined();
433
+
434
+ // Send request with If-None-Match header
435
+ const secondResponse = await app.etagOnlyAction.fetch({
436
+ headers: { "if-none-match": etag! },
437
+ });
438
+
439
+ expect(secondResponse.status).toBe(200);
440
+ expect(secondResponse.data).toBe("etag-only-1");
441
+ });
442
+
443
+ test("should NOT cache responses for etag-only routes", async ({
444
+ expect,
445
+ }) => {
446
+ const response1 = await app.etagOnlyAction.fetch();
447
+ const response2 = await app.etagOnlyAction.fetch();
448
+
449
+ // Counter should increment because responses are NOT cached
450
+ expect(response1.data).toBe("etag-only-0");
451
+ expect(response2.data).toBe("etag-only-1");
452
+
453
+ // But both should have different ETags
454
+ expect(response1.headers.get("etag")).not.toBe(
455
+ response2.headers.get("etag"),
456
+ );
457
+ });
458
+
459
+ test("should return 200 with new content when ETag doesn't match", async ({
460
+ expect,
461
+ }) => {
462
+ const firstResponse = await app.etagOnlyAction.fetch();
463
+ expect(firstResponse.status).toBe(200);
464
+ expect(firstResponse.data).toBe("etag-only-0");
465
+
466
+ // Send request with wrong ETag
467
+ const secondResponse = await app.etagOnlyAction.fetch({
468
+ headers: { "if-none-match": '"wrong-etag"' },
469
+ });
470
+
471
+ expect(secondResponse.status).toBe(200);
472
+ expect(secondResponse.data).toBe("etag-only-1");
473
+ });
474
+
475
+ test("should handle dynamic content with ETags correctly", async ({
476
+ expect,
477
+ }) => {
478
+ const response1 = await app.dynamicContentAction.fetch();
479
+ const etag1 = response1.headers.get("etag");
480
+
481
+ expect(response1.status).toBe(200);
482
+ expect(etag1).toBeDefined();
483
+
484
+ // Wait a bit to ensure timestamp changes
485
+ await new Promise((resolve) => setTimeout(resolve, 10));
486
+
487
+ const response2 = await app.dynamicContentAction.fetch();
488
+ const etag2 = response2.headers.get("etag");
489
+
490
+ expect(response2.status).toBe(200);
491
+ expect(etag2).toBeDefined();
492
+
493
+ // ETags should be different because content is dynamic
494
+ expect(etag1).not.toBe(etag2);
495
+ });
496
+ });
497
+
498
+ describe("ETag with cache combined", () => {
499
+ test("should support both caching and ETag validation", async ({
500
+ expect,
501
+ }) => {
502
+ const response1 = await app.etagAndCacheAction.fetch();
503
+ const etag1 = response1.headers.get("etag");
504
+
505
+ expect(response1.status).toBe(200);
506
+ expect(response1.data).toBe("etag-and-cache-0");
507
+ expect(etag1).toBeDefined();
508
+
509
+ // Second request should return cached content with 304
510
+ const response2 = await app.etagAndCacheAction.fetch();
511
+
512
+ expect(response2.status).toBe(304);
513
+ expect(response2.data).toBe("etag-and-cache-0");
514
+ });
515
+
516
+ test("should return cached content even without ETag header", async ({
517
+ expect,
518
+ }) => {
519
+ const response1 = await app.etagAndCacheAction.fetch();
520
+ expect(response1.status).toBe(200);
521
+ expect(response1.data).toBe("etag-and-cache-0");
522
+
523
+ // Request without ETag should still get cached response
524
+ const response2 = await app.etagAndCacheAction.fetch({
525
+ headers: {
526
+ "if-none-match": "non-matching-etag",
527
+ },
528
+ });
529
+
530
+ expect(response2.status).toBe(200);
531
+ expect(response2.data).toBe("etag-and-cache-0");
532
+ });
533
+
534
+ test("should invalidate both cache and ETag when cache is cleared", async ({
535
+ expect,
536
+ }) => {
537
+ const response1 = await app.etagAndCacheAction.fetch();
538
+ const etag1 = response1.headers.get("etag");
539
+
540
+ expect(response1.status).toBe(200);
541
+ expect(response1.data).toBe("etag-and-cache-0");
542
+
543
+ // Invalidate cache
544
+ await cacheProvider.invalidate(app.etagAndCacheAction.route);
545
+
546
+ // Next request should generate new content and new ETag
547
+ const response2 = await app.etagAndCacheAction.fetch();
548
+ const etag2 = response2.headers.get("etag");
549
+
550
+ expect(response2.status).toBe(200);
551
+ expect(response2.data).toBe("etag-and-cache-1");
552
+ expect(etag2).toBeDefined();
553
+ expect(etag1).not.toBe(etag2);
554
+ });
555
+ });
556
+
557
+ describe("ETag header validation", () => {
558
+ test("should generate consistent ETags for same content", async ({
559
+ expect,
560
+ }) => {
561
+ const response1 = await app.cachedAction.fetch();
562
+ const etag1 = response1.headers.get("etag");
563
+
564
+ const response2 = await app.cachedAction.fetch();
565
+ const etag2 = response2.headers.get("etag");
566
+
567
+ // Same content should produce same ETag
568
+ expect(etag1).toBe(etag2);
569
+ expect(response1.data).toBe(response2.data);
570
+ });
571
+
572
+ test("should generate different ETags for different content", async ({
573
+ expect,
574
+ }) => {
575
+ const response1 = await app.etagOnlyAction.fetch();
576
+ const etag1 = response1.headers.get("etag");
577
+
578
+ const response2 = await app.etagOnlyAction.fetch();
579
+ const etag2 = response2.headers.get("etag");
580
+
581
+ // Different content should produce different ETags
582
+ expect(response1.data).not.toBe(response2.data);
583
+ expect(etag1).not.toBe(etag2);
584
+ });
585
+
586
+ test("should set Last-Modified header with ETag", async ({ expect }) => {
587
+ const response = await app.etagOnlyAction.fetch();
588
+
589
+ expect(response.headers.get("etag")).toBeDefined();
590
+ expect(response.headers.get("last-modified")).toBeDefined();
591
+
592
+ // Verify it's a valid ISO date string
593
+ const lastModified = response.headers.get("last-modified");
594
+ expect(() => new Date(lastModified!)).not.toThrow();
595
+ });
596
+ });
597
+
598
+ describe("Mixed scenarios", () => {
599
+ test("should handle routes with different cache/etag configurations independently", async ({
600
+ expect,
601
+ }) => {
602
+ // Cached action
603
+ const cached1 = await app.cachedAction.fetch();
604
+ const cached2 = await app.cachedAction.fetch();
605
+ expect(cached1.data).toBe(cached2.data);
606
+
607
+ // Uncached action
608
+ const uncached1 = await app.uncachedAction.fetch();
609
+ const uncached2 = await app.uncachedAction.fetch();
610
+ expect(uncached1.data).not.toBe(uncached2.data);
611
+
612
+ // ETag-only action
613
+ const etag1 = await app.etagOnlyAction.fetch();
614
+ const etag2 = await app.etagOnlyAction.fetch();
615
+ expect(etag1.data).not.toBe(etag2.data);
616
+
617
+ // All should have their own independent state
618
+ expect(cached1.data).toContain("cached");
619
+ expect(uncached1.data).toContain("uncached");
620
+ expect(etag1.data).toContain("etag-only");
621
+ });
622
+
623
+ test("should not interfere with each other's ETags", async ({ expect }) => {
624
+ const response1 = await app.etagOnlyAction.fetch();
625
+ const etag1 = response1.headers.get("etag");
626
+
627
+ const response2 = await app.cachedAction.fetch();
628
+ const etag2 = response2.headers.get("etag");
629
+
630
+ // Different actions should have different ETags
631
+ expect(etag1).not.toBe(etag2);
632
+ });
633
+ });
634
+
635
+ describe("Cache-Control header support", () => {
636
+ test("should set Cache-Control header when control: true", async ({
637
+ expect,
638
+ }) => {
639
+ const response = await app.cacheWithControlTrue.fetch();
640
+
641
+ expect(response.status).toBe(200);
642
+ expect(response.headers.get("cache-control")).toBe("public, max-age=300");
643
+ });
644
+
645
+ test("should set Cache-Control header with custom string value", async ({
646
+ expect,
647
+ }) => {
648
+ const response = await app.cacheWithControlString.fetch();
649
+
650
+ expect(response.status).toBe(200);
651
+ expect(response.headers.get("cache-control")).toBe(
652
+ "public, max-age=600, immutable",
653
+ );
654
+ });
655
+
656
+ test("should build Cache-Control header from object directives", async ({
657
+ expect,
658
+ }) => {
659
+ const response = await app.cacheWithControlObject.fetch();
660
+
661
+ expect(response.status).toBe(200);
662
+ const cacheControl = response.headers.get("cache-control");
663
+ expect(cacheControl).toContain("public");
664
+ expect(cacheControl).toContain("max-age=3600");
665
+ expect(cacheControl).toContain("must-revalidate");
666
+ });
667
+
668
+ test("should support complex Cache-Control with multiple directives", async ({
669
+ expect,
670
+ }) => {
671
+ const response = await app.cacheWithComplexControl.fetch();
672
+
673
+ expect(response.status).toBe(200);
674
+ const cacheControl = response.headers.get("cache-control");
675
+ expect(cacheControl).toContain("public");
676
+ expect(cacheControl).toContain("max-age=600");
677
+ expect(cacheControl).toContain("s-maxage=1200");
678
+ expect(cacheControl).toContain("immutable");
679
+ });
680
+
681
+ test("should not set Cache-Control when cache is true without control option", async ({
682
+ expect,
683
+ }) => {
684
+ const response = await app.cachedAction.fetch();
685
+
686
+ expect(response.status).toBe(200);
687
+ expect(response.headers.get("cache-control")).toBeNull();
688
+ });
689
+
690
+ test("should cache responses with Cache-Control headers", async ({
691
+ expect,
692
+ }) => {
693
+ const response1 = await app.cacheWithControlObject.fetch();
694
+ const response2 = await app.cacheWithControlObject.fetch();
695
+
696
+ expect(response1.data).toBe(response2.data);
697
+ expect(response1.data).toBe("control-object-0");
698
+ });
699
+
700
+ test("should support private cache directive", async ({ expect }) => {
701
+ const alepha = Alepha.create().with(AlephaServerCache);
702
+ const privateAction = alepha.inject(
703
+ class TestPrivateCache {
704
+ action = $action({
705
+ cache: {
706
+ store: true,
707
+ control: {
708
+ private: true,
709
+ maxAge: 300,
710
+ },
711
+ },
712
+ handler: () => "private-cache",
713
+ });
714
+ },
715
+ ).action;
716
+ await alepha.start();
717
+
718
+ const response = await privateAction.fetch();
719
+ const cacheControl = response.headers.get("cache-control");
720
+
721
+ expect(cacheControl).toContain("private");
722
+ expect(cacheControl).toContain("max-age=300");
723
+ expect(cacheControl).not.toContain("public");
724
+ });
725
+
726
+ test("should support no-cache and no-store directives", async ({
727
+ expect,
728
+ }) => {
729
+ const alepha = Alepha.create().with(AlephaServerCache);
730
+ const noCacheAction = alepha.inject(
731
+ class TestNoCache {
732
+ action = $action({
733
+ cache: {
734
+ store: true,
735
+ control: {
736
+ noCache: true,
737
+ noStore: true,
738
+ },
739
+ },
740
+ handler: () => "no-cache",
741
+ });
742
+ },
743
+ ).action;
744
+ await alepha.start();
745
+
746
+ const response = await noCacheAction.fetch();
747
+ const cacheControl = response.headers.get("cache-control");
748
+
749
+ expect(cacheControl).toContain("no-cache");
750
+ expect(cacheControl).toContain("no-store");
751
+ });
752
+
753
+ test("should support proxy-revalidate directive", async ({ expect }) => {
754
+ const alepha = Alepha.create().with(AlephaServerCache);
755
+ const proxyAction = alepha.inject(
756
+ class TestProxyRevalidate {
757
+ action = $action({
758
+ cache: {
759
+ store: true,
760
+ control: {
761
+ public: true,
762
+ proxyRevalidate: true,
763
+ maxAge: 600,
764
+ },
765
+ },
766
+ handler: () => "proxy-revalidate",
767
+ });
768
+ },
769
+ ).action;
770
+ await alepha.start();
771
+
772
+ const response = await proxyAction.fetch();
773
+ const cacheControl = response.headers.get("cache-control");
774
+
775
+ expect(cacheControl).toContain("public");
776
+ expect(cacheControl).toContain("proxy-revalidate");
777
+ expect(cacheControl).toContain("max-age=600");
778
+ });
779
+
780
+ test("should support s-maxage for shared cache control", async ({
781
+ expect,
782
+ }) => {
783
+ const alepha = Alepha.create().with(AlephaServerCache);
784
+ const sharedCacheAction = alepha.inject(
785
+ class TestSharedCache {
786
+ action = $action({
787
+ cache: {
788
+ store: true,
789
+ control: {
790
+ public: true,
791
+ maxAge: 300,
792
+ sMaxAge: 600,
793
+ },
794
+ },
795
+ handler: () => "shared-cache",
796
+ });
797
+ },
798
+ ).action;
799
+ await alepha.start();
800
+
801
+ const response = await sharedCacheAction.fetch();
802
+ const cacheControl = response.headers.get("cache-control");
803
+
804
+ expect(cacheControl).toContain("public");
805
+ expect(cacheControl).toContain("max-age=300");
806
+ expect(cacheControl).toContain("s-maxage=600");
807
+ });
808
+
809
+ test("should support DurationLike for maxAge and sMaxAge", async ({
810
+ expect,
811
+ }) => {
812
+ const response = await app.cacheWithDurationMaxAge.fetch();
813
+
814
+ expect(response.status).toBe(200);
815
+ const cacheControl = response.headers.get("cache-control");
816
+
817
+ // 5 minutes = 300 seconds, 1 hour = 3600 seconds
818
+ expect(cacheControl).toContain("public");
819
+ expect(cacheControl).toContain("max-age=300");
820
+ expect(cacheControl).toContain("s-maxage=3600");
821
+ });
822
+
823
+ test("should handle various DurationLike formats for maxAge", ({
824
+ expect,
825
+ }) => {
826
+ // Test directly via buildCacheControlHeader method
827
+ expect(
828
+ cacheProvider.buildCacheControlHeader({
829
+ control: { public: true, maxAge: [30, "seconds"] },
830
+ }),
831
+ ).toContain("max-age=30");
832
+
833
+ expect(
834
+ cacheProvider.buildCacheControlHeader({
835
+ control: { public: true, maxAge: [10, "minutes"] },
836
+ }),
837
+ ).toContain("max-age=600");
838
+
839
+ expect(
840
+ cacheProvider.buildCacheControlHeader({
841
+ control: { public: true, maxAge: [2, "hours"] },
842
+ }),
843
+ ).toContain("max-age=7200");
844
+
845
+ expect(
846
+ cacheProvider.buildCacheControlHeader({
847
+ control: { public: true, maxAge: [1, "day"] },
848
+ }),
849
+ ).toContain("max-age=86400");
850
+ });
851
+
852
+ test("should mix number and DurationLike for maxAge and sMaxAge", ({
853
+ expect,
854
+ }) => {
855
+ const cacheControl = cacheProvider.buildCacheControlHeader({
856
+ control: {
857
+ public: true,
858
+ maxAge: 600, // number (seconds)
859
+ sMaxAge: [20, "minutes"], // DurationLike
860
+ },
861
+ });
862
+
863
+ expect(cacheControl).toContain("max-age=600");
864
+ expect(cacheControl).toContain("s-maxage=1200"); // 20 minutes = 1200 seconds
865
+ });
866
+ });
867
+
868
+ describe("Error response caching", () => {
869
+ test("should NOT cache 500 error responses", async ({ expect }) => {
870
+ const response1 = await app.errorAction.fetch();
871
+ expect(response1.status).toBe(500);
872
+ expect(response1.data).toBe("error-0");
873
+
874
+ // Second request should execute handler again (not cached)
875
+ const response2 = await app.errorAction.fetch();
876
+ expect(response2.status).toBe(500);
877
+ expect(response2.data).toBe("error-1");
878
+
879
+ // Verify counter incremented (handler was called)
880
+ expect(response1.data).not.toBe(response2.data);
881
+ });
882
+
883
+ test("should NOT cache 404 error responses", async ({ expect }) => {
884
+ const response1 = await app.notFoundAction.fetch();
885
+ expect(response1.status).toBe(404);
886
+ expect(response1.data).toBe("not-found-0");
887
+
888
+ // Second request should execute handler again (not cached)
889
+ const response2 = await app.notFoundAction.fetch();
890
+ expect(response2.status).toBe(404);
891
+ expect(response2.data).toBe("not-found-1");
892
+
893
+ // Verify counter incremented (handler was called)
894
+ expect(response1.data).not.toBe(response2.data);
895
+ });
896
+
897
+ test("should cache successful responses after error responses", async ({
898
+ expect,
899
+ }) => {
900
+ // First request returns 500 error
901
+ const errorResponse = await app.conditionalErrorAction.fetch();
902
+ expect(errorResponse.status).toBe(500);
903
+ expect(errorResponse.data).toBe("error-0");
904
+
905
+ // Second request returns success and should be cached
906
+ const successResponse1 = await app.conditionalErrorAction.fetch();
907
+ expect(successResponse1.status).toBe(200);
908
+ expect(successResponse1.data).toBe("success-1");
909
+
910
+ // Third request should return cached response
911
+ const successResponse2 = await app.conditionalErrorAction.fetch();
912
+ expect(successResponse2.status).toBe(304);
913
+ expect(successResponse2.data).toBe("success-1");
914
+ });
915
+
916
+ test("should NOT cache 4xx client errors", async ({ expect }) => {
917
+ // Test with 400 Bad Request
918
+ const alepha = Alepha.create();
919
+ const badRequestAction = alepha.inject(
920
+ class TestBadRequest {
921
+ action = $action({
922
+ cache: true,
923
+ handler: ({ reply }) => {
924
+ reply.status = 400;
925
+ return `bad-request-${app.counter++}`;
926
+ },
927
+ });
928
+ },
929
+ ).action;
930
+ await alepha.start();
931
+
932
+ const response1 = await badRequestAction.fetch();
933
+ expect(response1.status).toBe(400);
934
+
935
+ const response2 = await badRequestAction.fetch();
936
+ expect(response2.status).toBe(400);
937
+
938
+ // Verify responses are different (not cached)
939
+ expect(response1.data).not.toBe(response2.data);
940
+ });
941
+ });
942
+ });