alepha 0.14.2 → 0.14.4

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 (405) hide show
  1. package/README.md +1 -1
  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 +706 -785
  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 +58 -137
  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 +29 -108
  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 +504 -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 +277 -351
  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 +787 -852
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +827 -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 +128 -128
  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/bin/index.d.ts +1 -2
  45. package/dist/bin/index.js +0 -1
  46. package/dist/bin/index.js.map +1 -1
  47. package/dist/cli/index.d.ts +252 -131
  48. package/dist/cli/index.d.ts.map +1 -1
  49. package/dist/cli/index.js +595 -395
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/command/index.d.ts +46 -11
  52. package/dist/command/index.d.ts.map +1 -1
  53. package/dist/command/index.js +99 -19
  54. package/dist/command/index.js.map +1 -1
  55. package/dist/core/index.browser.js +40 -22
  56. package/dist/core/index.browser.js.map +1 -1
  57. package/dist/core/index.d.ts +45 -1
  58. package/dist/core/index.d.ts.map +1 -1
  59. package/dist/core/index.js +40 -22
  60. package/dist/core/index.js.map +1 -1
  61. package/dist/core/index.native.js +40 -22
  62. package/dist/core/index.native.js.map +1 -1
  63. package/dist/fake/index.js +195 -168
  64. package/dist/fake/index.js.map +1 -1
  65. package/dist/file/index.d.ts +8 -0
  66. package/dist/file/index.d.ts.map +1 -1
  67. package/dist/file/index.js +3 -0
  68. package/dist/file/index.js.map +1 -1
  69. package/dist/logger/index.d.ts +1 -1
  70. package/dist/logger/index.d.ts.map +1 -1
  71. package/dist/logger/index.js +12 -2
  72. package/dist/logger/index.js.map +1 -1
  73. package/dist/mcp/index.js +1 -1
  74. package/dist/mcp/index.js.map +1 -1
  75. package/dist/orm/index.d.ts +59 -195
  76. package/dist/orm/index.d.ts.map +1 -1
  77. package/dist/orm/index.js +201 -430
  78. package/dist/orm/index.js.map +1 -1
  79. package/dist/security/index.d.ts +1 -1
  80. package/dist/security/index.d.ts.map +1 -1
  81. package/dist/security/index.js +1 -1
  82. package/dist/security/index.js.map +1 -1
  83. package/dist/server/auth/index.d.ts +171 -155
  84. package/dist/server/auth/index.d.ts.map +1 -1
  85. package/dist/server/auth/index.js +0 -1
  86. package/dist/server/auth/index.js.map +1 -1
  87. package/dist/server/cache/index.d.ts +12 -0
  88. package/dist/server/cache/index.d.ts.map +1 -1
  89. package/dist/server/cache/index.js +55 -2
  90. package/dist/server/cache/index.js.map +1 -1
  91. package/dist/server/compress/index.d.ts +6 -0
  92. package/dist/server/compress/index.d.ts.map +1 -1
  93. package/dist/server/compress/index.js +38 -1
  94. package/dist/server/compress/index.js.map +1 -1
  95. package/dist/server/core/index.browser.js +2 -2
  96. package/dist/server/core/index.browser.js.map +1 -1
  97. package/dist/server/core/index.d.ts +10 -10
  98. package/dist/server/core/index.d.ts.map +1 -1
  99. package/dist/server/core/index.js +7 -4
  100. package/dist/server/core/index.js.map +1 -1
  101. package/dist/server/links/index.browser.js +22 -6
  102. package/dist/server/links/index.browser.js.map +1 -1
  103. package/dist/server/links/index.d.ts +46 -44
  104. package/dist/server/links/index.d.ts.map +1 -1
  105. package/dist/server/links/index.js +24 -41
  106. package/dist/server/links/index.js.map +1 -1
  107. package/dist/server/static/index.d.ts.map +1 -1
  108. package/dist/server/static/index.js +4 -0
  109. package/dist/server/static/index.js.map +1 -1
  110. package/dist/server/swagger/index.d.ts +2 -1
  111. package/dist/server/swagger/index.d.ts.map +1 -1
  112. package/dist/server/swagger/index.js +9 -5
  113. package/dist/server/swagger/index.js.map +1 -1
  114. package/dist/vite/index.d.ts +101 -106
  115. package/dist/vite/index.d.ts.map +1 -1
  116. package/dist/vite/index.js +574 -503
  117. package/dist/vite/index.js.map +1 -1
  118. package/dist/websocket/index.d.ts +7 -7
  119. package/package.json +7 -7
  120. package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
  121. package/src/api/audits/entities/audits.ts +5 -5
  122. package/src/api/audits/index.browser.ts +1 -1
  123. package/src/api/audits/index.ts +3 -3
  124. package/src/api/audits/primitives/$audit.spec.ts +276 -0
  125. package/src/api/audits/services/AuditService.spec.ts +495 -0
  126. package/src/api/files/__tests__/$bucket.spec.ts +91 -0
  127. package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
  128. package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
  129. package/src/api/files/controllers/FileController.spec.ts +558 -0
  130. package/src/api/files/controllers/FileController.ts +4 -5
  131. package/src/api/files/entities/files.ts +5 -5
  132. package/src/api/files/index.browser.ts +1 -1
  133. package/src/api/files/index.ts +4 -4
  134. package/src/api/files/jobs/FileJobs.spec.ts +52 -0
  135. package/src/api/files/services/FileService.spec.ts +109 -0
  136. package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
  137. package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
  138. package/src/api/jobs/entities/jobExecutions.ts +5 -5
  139. package/src/api/jobs/index.ts +3 -3
  140. package/src/api/jobs/primitives/$job.spec.ts +476 -0
  141. package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
  142. package/src/api/notifications/entities/notifications.ts +5 -5
  143. package/src/api/notifications/index.browser.ts +1 -1
  144. package/src/api/notifications/index.ts +4 -4
  145. package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
  146. package/src/api/parameters/entities/parameters.ts +7 -17
  147. package/src/api/parameters/index.ts +3 -3
  148. package/src/api/parameters/primitives/$config.spec.ts +356 -0
  149. package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
  150. package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
  151. package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
  152. package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
  153. package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
  154. package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
  155. package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
  156. package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
  157. package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
  158. package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
  159. package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
  160. package/src/api/parameters/schemas/index.ts +15 -0
  161. package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
  162. package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
  163. package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
  164. package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
  165. package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
  166. package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
  167. package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
  168. package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
  169. package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
  170. package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
  171. package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
  172. package/src/api/users/controllers/AdminUserController.ts +116 -0
  173. package/src/api/users/controllers/UserController.ts +4 -107
  174. package/src/api/users/controllers/UserRealmController.ts +3 -0
  175. package/src/api/users/entities/identities.ts +6 -6
  176. package/src/api/users/entities/sessions.ts +6 -6
  177. package/src/api/users/entities/users.ts +9 -9
  178. package/src/api/users/index.ts +9 -6
  179. package/src/api/users/primitives/$userRealm.ts +13 -8
  180. package/src/api/users/services/CredentialService.spec.ts +509 -0
  181. package/src/api/users/services/CredentialService.ts +46 -0
  182. package/src/api/users/services/IdentityService.ts +15 -0
  183. package/src/api/users/services/RegistrationService.spec.ts +630 -0
  184. package/src/api/users/services/RegistrationService.ts +18 -0
  185. package/src/api/users/services/SessionService.spec.ts +301 -0
  186. package/src/api/users/services/SessionService.ts +110 -1
  187. package/src/api/users/services/UserService.ts +67 -2
  188. package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
  189. package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
  190. package/src/api/verifications/entities/verifications.ts +6 -6
  191. package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
  192. package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
  193. package/src/batch/primitives/$batch.spec.ts +766 -0
  194. package/src/batch/providers/BatchProvider.spec.ts +786 -0
  195. package/src/bin/index.ts +0 -1
  196. package/src/bucket/__tests__/shared.ts +194 -0
  197. package/src/bucket/primitives/$bucket.spec.ts +104 -0
  198. package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
  199. package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
  200. package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
  201. package/src/cache/core/__tests__/shared.ts +377 -0
  202. package/src/cache/core/primitives/$cache.spec.ts +111 -0
  203. package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
  204. package/src/cli/apps/AlephaCli.ts +25 -6
  205. package/src/cli/atoms/buildOptions.ts +88 -0
  206. package/src/cli/commands/build.ts +32 -69
  207. package/src/cli/commands/db.ts +0 -4
  208. package/src/cli/commands/dev.ts +34 -10
  209. package/src/cli/commands/gen/changelog.spec.ts +315 -0
  210. package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
  211. package/src/cli/commands/gen/env.ts +53 -0
  212. package/src/cli/commands/gen/openapi.ts +71 -0
  213. package/src/cli/commands/gen/resource.ts +15 -0
  214. package/src/cli/commands/gen.ts +24 -0
  215. package/src/cli/commands/init.ts +2 -1
  216. package/src/cli/commands/root.ts +12 -3
  217. package/src/cli/commands/test.ts +0 -1
  218. package/src/cli/commands/typecheck.ts +5 -0
  219. package/src/cli/commands/verify.ts +1 -1
  220. package/src/cli/defineConfig.ts +49 -7
  221. package/src/cli/index.ts +2 -2
  222. package/src/cli/services/AlephaCliUtils.ts +105 -55
  223. package/src/cli/services/GitMessageParser.ts +1 -1
  224. package/src/command/helpers/Asker.spec.ts +127 -0
  225. package/src/command/helpers/Runner.spec.ts +126 -0
  226. package/src/command/helpers/Runner.ts +1 -1
  227. package/src/command/primitives/$command.spec.ts +1588 -0
  228. package/src/command/primitives/$command.ts +0 -6
  229. package/src/command/providers/CliProvider.ts +75 -27
  230. package/src/core/Alepha.ts +87 -0
  231. package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
  232. package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
  233. package/src/core/__tests__/Alepha-has.spec.ts +41 -0
  234. package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
  235. package/src/core/__tests__/Alepha-register.spec.ts +81 -0
  236. package/src/core/__tests__/Alepha-start.spec.ts +176 -0
  237. package/src/core/__tests__/Alepha-with.spec.ts +14 -0
  238. package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
  239. package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
  240. package/src/core/__tests__/descriptor.spec.ts +34 -0
  241. package/src/core/__tests__/fixtures/A.ts +5 -0
  242. package/src/core/__tests__/pagination.spec.ts +77 -0
  243. package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
  244. package/src/core/primitives/$atom.spec.ts +43 -0
  245. package/src/core/primitives/$hook.spec.ts +130 -0
  246. package/src/core/primitives/$inject.spec.ts +175 -0
  247. package/src/core/primitives/$module.spec.ts +115 -0
  248. package/src/core/providers/CodecManager.spec.ts +740 -0
  249. package/src/core/providers/EventManager.spec.ts +762 -0
  250. package/src/core/providers/EventManager.ts +4 -0
  251. package/src/core/providers/StateManager.spec.ts +365 -0
  252. package/src/core/providers/TypeProvider.spec.ts +1607 -0
  253. package/src/core/providers/TypeProvider.ts +20 -26
  254. package/src/datetime/primitives/$interval.spec.ts +103 -0
  255. package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
  256. package/src/email/primitives/$email.spec.ts +175 -0
  257. package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
  258. package/src/fake/__tests__/keyName.example.ts +40 -0
  259. package/src/fake/__tests__/keyName.spec.ts +152 -0
  260. package/src/fake/__tests__/module.example.ts +32 -0
  261. package/src/fake/providers/FakeProvider.spec.ts +438 -0
  262. package/src/file/providers/FileSystemProvider.ts +8 -0
  263. package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
  264. package/src/file/providers/NodeFileSystemProvider.ts +5 -0
  265. package/src/file/services/FileDetector.spec.ts +591 -0
  266. package/src/lock/core/__tests__/shared.ts +190 -0
  267. package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
  268. package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
  269. package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
  270. package/src/logger/index.ts +15 -3
  271. package/src/logger/primitives/$logger.spec.ts +108 -0
  272. package/src/logger/services/Logger.spec.ts +295 -0
  273. package/src/mcp/__tests__/errors.spec.ts +175 -0
  274. package/src/mcp/__tests__/integration.spec.ts +450 -0
  275. package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
  276. package/src/mcp/primitives/$prompt.spec.ts +468 -0
  277. package/src/mcp/primitives/$resource.spec.ts +390 -0
  278. package/src/mcp/primitives/$tool.spec.ts +406 -0
  279. package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
  280. package/src/mcp/transports/StdioMcpTransport.ts +1 -1
  281. package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
  282. package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
  283. package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
  284. package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
  285. package/src/orm/__tests__/$repository-save.spec.ts +37 -0
  286. package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
  287. package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
  288. package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
  289. package/src/orm/__tests__/delete-returning.spec.ts +256 -0
  290. package/src/orm/__tests__/deletedAt.spec.ts +80 -0
  291. package/src/orm/__tests__/enums.spec.ts +315 -0
  292. package/src/orm/__tests__/execute.spec.ts +72 -0
  293. package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
  294. package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
  295. package/src/orm/__tests__/joins.spec.ts +1114 -0
  296. package/src/orm/__tests__/page.spec.ts +287 -0
  297. package/src/orm/__tests__/primaryKey.spec.ts +87 -0
  298. package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
  299. package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
  300. package/src/orm/__tests__/references.spec.ts +102 -0
  301. package/src/orm/__tests__/security.spec.ts +710 -0
  302. package/src/orm/__tests__/sqlite.spec.ts +111 -0
  303. package/src/orm/__tests__/string-operators.spec.ts +429 -0
  304. package/src/orm/__tests__/timestamps.spec.ts +388 -0
  305. package/src/orm/__tests__/validation.spec.ts +183 -0
  306. package/src/orm/__tests__/version.spec.ts +64 -0
  307. package/src/orm/helpers/parseQueryString.spec.ts +196 -0
  308. package/src/orm/index.ts +2 -8
  309. package/src/orm/primitives/$repository.spec.ts +137 -0
  310. package/src/orm/primitives/$sequence.spec.ts +29 -0
  311. package/src/orm/primitives/$transaction.spec.ts +82 -0
  312. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
  313. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  314. package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
  315. package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
  316. package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
  317. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  318. package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
  319. package/src/orm/services/ModelBuilder.spec.ts +575 -0
  320. package/src/orm/services/Repository.spec.ts +137 -0
  321. package/src/queue/core/__tests__/shared.ts +143 -0
  322. package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
  323. package/src/queue/core/providers/WorkerProvider.spec.ts +394 -0
  324. package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
  325. package/src/redis/__tests__/redis.spec.ts +58 -0
  326. package/src/retry/primitives/$retry.spec.ts +234 -0
  327. package/src/retry/providers/RetryProvider.spec.ts +438 -0
  328. package/src/router/__tests__/match.spec.ts +252 -0
  329. package/src/router/providers/RouterProvider.spec.ts +197 -0
  330. package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
  331. package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
  332. package/src/scheduler/__tests__/shared.ts +77 -0
  333. package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
  334. package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
  335. package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
  336. package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
  337. package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
  338. package/src/security/primitives/$permission.spec.ts +30 -0
  339. package/src/security/primitives/$permission.ts +2 -2
  340. package/src/security/primitives/$realm.spec.ts +101 -0
  341. package/src/security/primitives/$role.spec.ts +52 -0
  342. package/src/security/primitives/$serviceAccount.spec.ts +61 -0
  343. package/src/security/providers/SecurityProvider.spec.ts +350 -0
  344. package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
  345. package/src/server/cache/providers/ServerCacheProvider.spec.ts +1125 -0
  346. package/src/server/cache/providers/ServerCacheProvider.ts +94 -9
  347. package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
  348. package/src/server/compress/providers/ServerCompressProvider.ts +63 -2
  349. package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
  350. package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
  351. package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
  352. package/src/server/core/helpers/ServerReply.ts +2 -2
  353. package/src/server/core/primitives/$action.spec.ts +191 -0
  354. package/src/server/core/primitives/$route.spec.ts +65 -0
  355. package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
  356. package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
  357. package/src/server/core/providers/ServerProvider.ts +14 -2
  358. package/src/server/core/services/HttpClient.spec.ts +123 -0
  359. package/src/server/core/services/UserAgentParser.spec.ts +111 -0
  360. package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
  361. package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
  362. package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
  363. package/src/server/links/__tests__/$action.spec.ts +238 -0
  364. package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
  365. package/src/server/links/__tests__/requestId.spec.ts +120 -0
  366. package/src/server/links/primitives/$remote.spec.ts +228 -0
  367. package/src/server/links/providers/LinkProvider.spec.ts +54 -0
  368. package/src/server/links/providers/LinkProvider.ts +49 -3
  369. package/src/server/links/providers/ServerLinksProvider.ts +1 -53
  370. package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
  371. package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
  372. package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
  373. package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
  374. package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
  375. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
  376. package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
  377. package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
  378. package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
  379. package/src/server/static/primitives/$serve.spec.ts +193 -0
  380. package/src/server/static/providers/ServerStaticProvider.ts +10 -0
  381. package/src/server/swagger/__tests__/ui.spec.ts +52 -0
  382. package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
  383. package/src/server/swagger/providers/ServerSwaggerProvider.ts +19 -12
  384. package/src/sms/primitives/$sms.spec.ts +165 -0
  385. package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
  386. package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
  387. package/src/thread/primitives/$thread.spec.ts +186 -0
  388. package/src/topic/core/__tests__/shared.ts +144 -0
  389. package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
  390. package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
  391. package/src/vite/helpers/importViteReact.ts +13 -0
  392. package/src/vite/index.ts +1 -21
  393. package/src/vite/plugins/viteAlephaDev.ts +32 -5
  394. package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
  395. package/src/vite/tasks/buildClient.ts +11 -0
  396. package/src/vite/tasks/buildServer.ts +47 -3
  397. package/src/vite/tasks/devServer.ts +69 -0
  398. package/src/vite/tasks/index.ts +2 -1
  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/assets/viteConfigTs.ts +0 -14
  403. package/src/cli/commands/run.ts +0 -24
  404. package/src/vite/plugins/viteAlepha.ts +0 -37
  405. package/src/vite/plugins/viteAlephaBuild.ts +0 -281
@@ -0,0 +1,234 @@
1
+ import { Alepha } from "alepha";
2
+ import { DateTimeProvider } from "alepha/datetime";
3
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
+ import { $retry } from "../index.ts";
5
+
6
+ describe("$retry", () => {
7
+ let alepha: Alepha;
8
+ let time: DateTimeProvider;
9
+
10
+ beforeEach(() => {
11
+ alepha = Alepha.create();
12
+ time = alepha.inject(DateTimeProvider);
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await alepha.stop();
17
+ });
18
+
19
+ test("should retry handler up to max retries", async () => {
20
+ class Dummy {
21
+ inc = 0;
22
+ workRetry = $retry({
23
+ max: 3,
24
+ handler: (n: number, end: number) => {
25
+ this.inc += n;
26
+ if (this.inc < end) {
27
+ throw new Error("Retry");
28
+ }
29
+ return this.inc;
30
+ },
31
+ });
32
+
33
+ work = async (n: number, end: number) => {
34
+ this.inc = 0;
35
+ return await this.workRetry(n, end);
36
+ };
37
+ }
38
+
39
+ const basic = alepha.inject(Dummy);
40
+
41
+ expect(await basic.work(1, 2)).toBe(2);
42
+ expect(await basic.work(1, 3)).toBe(3);
43
+ await expect(() => basic.work(1, 4)).rejects.toThrow(Error);
44
+ });
45
+
46
+ test("should only retry when condition matches", async () => {
47
+ class Dummy {
48
+ inc = 0;
49
+ workRetry = $retry({
50
+ max: 10,
51
+ when: (err: Error) => err.message === "Retry1",
52
+ handler: (n: number, end: number) => {
53
+ this.inc += n;
54
+ if (this.inc < end) {
55
+ throw new Error(`Retry${this.inc}`);
56
+ }
57
+ return this.inc;
58
+ },
59
+ });
60
+
61
+ async work(n: number, end: number) {
62
+ this.inc = 0;
63
+ return await this.workRetry(n, end);
64
+ }
65
+ }
66
+
67
+ const basic = alepha.inject(Dummy);
68
+
69
+ expect(await basic.work(1, 2)).toBe(2);
70
+ await expect(() => basic.work(1, 3)).rejects.toThrow(Error);
71
+ });
72
+
73
+ test("should succeed on the first attempt", async () => {
74
+ const handler = vi.fn().mockResolvedValue("success");
75
+ const retryFunc = alepha.inject(
76
+ class {
77
+ retry = $retry({ handler });
78
+ },
79
+ ).retry;
80
+
81
+ await expect(retryFunc()).resolves.toBe("success");
82
+ expect(handler).toHaveBeenCalledTimes(1);
83
+ });
84
+
85
+ test("should retry up to max attempts and then fail", async () => {
86
+ const handler = vi.fn().mockRejectedValue(new Error("Failed"));
87
+ const onError = vi.fn();
88
+
89
+ const retryFunc = alepha.inject(
90
+ class {
91
+ retry = $retry({ handler, max: 3, backoff: 0, onError });
92
+ },
93
+ ).retry;
94
+
95
+ await expect(retryFunc()).rejects.toThrow("Failed");
96
+ expect(handler).toHaveBeenCalledTimes(3);
97
+ expect(onError).toHaveBeenCalledTimes(3); // onError is called for ALL failed attempts, including the last one
98
+ expect(onError).toHaveBeenCalledWith(expect.any(Error), 1);
99
+ expect(onError).toHaveBeenCalledWith(expect.any(Error), 2);
100
+ expect(onError).toHaveBeenCalledWith(expect.any(Error), 3);
101
+ });
102
+
103
+ test("should succeed after a few failed attempts", async () => {
104
+ let attempt = 0;
105
+ const handler = vi.fn(() => {
106
+ attempt++;
107
+ if (attempt < 3) {
108
+ return Promise.reject(new Error("Try again"));
109
+ }
110
+ return Promise.resolve("success");
111
+ });
112
+
113
+ const retryFunc = alepha.inject(
114
+ class {
115
+ retry = $retry({ handler, max: 4, backoff: 0 });
116
+ },
117
+ ).retry;
118
+
119
+ await expect(retryFunc()).resolves.toBe("success");
120
+ expect(handler).toHaveBeenCalledTimes(3);
121
+ });
122
+
123
+ test("should respect maxDuration and time out", async () => {
124
+ const handler = vi.fn(async () => {
125
+ await time.wait(200);
126
+ throw new Error("Failed");
127
+ });
128
+
129
+ const retryFunc = alepha.inject(
130
+ class {
131
+ retry = $retry({
132
+ handler,
133
+ max: 5,
134
+ maxDuration: [300, "ms"],
135
+ backoff: 0,
136
+ });
137
+ },
138
+ ).retry;
139
+
140
+ await expect(retryFunc()).rejects.toThrow(
141
+ "Retry operation timed out after 300ms.",
142
+ );
143
+ expect(handler).toHaveBeenCalledTimes(2);
144
+ });
145
+
146
+ test("should be cancellable with an AbortSignal", async () => {
147
+ const handler = vi.fn(async () => {
148
+ await time.wait(500); // Long delay
149
+ throw new Error("Failed");
150
+ });
151
+
152
+ const abortController = new AbortController();
153
+ const retryFunc = alepha.inject(
154
+ class {
155
+ retry = $retry({
156
+ handler,
157
+ max: 5,
158
+ backoff: 100,
159
+ signal: abortController.signal,
160
+ });
161
+ },
162
+ ).retry;
163
+
164
+ const promise = retryFunc();
165
+
166
+ // Let the first attempt start
167
+ await time.travel(100);
168
+
169
+ // Abort during the first handler execution
170
+ abortController.abort();
171
+
172
+ await expect(promise).rejects.toThrow("Retry operation was cancelled.");
173
+
174
+ // Handler was called once but never completed
175
+ expect(handler).toHaveBeenCalledTimes(1);
176
+ });
177
+
178
+ test("should be cancellable by application shutdown", async () => {
179
+ // Create a fresh alepha instance for this test
180
+ const testAlepha = Alepha.create();
181
+ const testTime = testAlepha.inject(DateTimeProvider);
182
+
183
+ const handler = vi.fn(async () => {
184
+ // Throw immediately to trigger retry
185
+ throw new Error("Failed");
186
+ });
187
+
188
+ const retryFunc = testAlepha.inject(
189
+ class {
190
+ retry = $retry({ handler, max: 10, backoff: 100 });
191
+ },
192
+ ).retry;
193
+
194
+ await testAlepha.start();
195
+
196
+ // Start the retry operation
197
+ const promise = retryFunc();
198
+
199
+ // Give it a moment to start
200
+ await new Promise((resolve) => setTimeout(resolve, 10));
201
+
202
+ // Stop the application which should abort all retries
203
+ await testAlepha.stop();
204
+
205
+ // The promise should reject with cancellation error
206
+ await expect(promise).rejects.toThrow("Retry operation was cancelled.");
207
+
208
+ // Handler should have been called at least once but not 10 times
209
+ expect(handler).toHaveBeenCalled();
210
+ expect(handler.mock.calls.length).toBeLessThan(10);
211
+ });
212
+
213
+ test("should not retry if `when` condition returns false", async () => {
214
+ class CustomError extends Error {}
215
+ const handler = vi
216
+ .fn()
217
+ .mockRejectedValueOnce(new CustomError("Do not retry me"))
218
+ .mockRejectedValue(new Error("Retry me"));
219
+
220
+ const retryFunc = alepha.inject(
221
+ class {
222
+ retry = $retry({
223
+ handler,
224
+ max: 3,
225
+ backoff: 0,
226
+ when: (error) => !(error instanceof CustomError),
227
+ });
228
+ },
229
+ ).retry;
230
+
231
+ await expect(retryFunc()).rejects.toThrow(CustomError);
232
+ expect(handler).toHaveBeenCalledTimes(1);
233
+ });
234
+ });
@@ -0,0 +1,438 @@
1
+ import { Alepha } from "alepha";
2
+ import { beforeEach, describe, expect, test, vi } from "vitest";
3
+ import { RetryCancelError } from "../errors/RetryCancelError.ts";
4
+ import { RetryTimeoutError } from "../errors/RetryTimeoutError.ts";
5
+ import { RetryProvider } from "../providers/RetryProvider.ts";
6
+
7
+ describe("RetryProvider", () => {
8
+ let alepha: Alepha;
9
+ let retryProvider: RetryProvider;
10
+
11
+ beforeEach(async () => {
12
+ alepha = Alepha.create();
13
+ retryProvider = alepha.inject(RetryProvider);
14
+ await alepha.start();
15
+ });
16
+
17
+ test("should retry handler up to max retries", async () => {
18
+ let attempts = 0;
19
+ const handler = vi.fn(() => {
20
+ attempts++;
21
+ if (attempts < 3) {
22
+ throw new Error("Retry");
23
+ }
24
+ return "success";
25
+ });
26
+
27
+ const result = await retryProvider.retry({ handler, max: 3 });
28
+
29
+ expect(result).toBe("success");
30
+ expect(handler).toHaveBeenCalledTimes(3);
31
+ });
32
+
33
+ test("should throw error after max retries exceeded", async () => {
34
+ const handler = vi.fn(() => {
35
+ throw new Error("Always fails");
36
+ });
37
+
38
+ await expect(retryProvider.retry({ handler, max: 3 })).rejects.toThrowError(
39
+ "Always fails",
40
+ );
41
+
42
+ expect(handler).toHaveBeenCalledTimes(3);
43
+ });
44
+
45
+ test("should only retry when condition matches", async () => {
46
+ let attempts = 0;
47
+ const handler = vi.fn(() => {
48
+ attempts++;
49
+ throw new Error(`Error${attempts}`);
50
+ });
51
+
52
+ const when = (error: Error) => error.message === "Error1";
53
+
54
+ await expect(
55
+ retryProvider.retry({ handler, max: 10, when }),
56
+ ).rejects.toThrowError("Error2");
57
+
58
+ // Should fail on second attempt because when() returns false
59
+ expect(handler).toHaveBeenCalledTimes(2);
60
+ });
61
+
62
+ test("should use fixed backoff delay", async () => {
63
+ const handler = vi.fn(() => {
64
+ throw new Error("Retry");
65
+ });
66
+
67
+ const startTime = Date.now();
68
+
69
+ await expect(
70
+ retryProvider.retry({ handler, max: 3, backoff: 100 }),
71
+ ).rejects.toThrowError("Retry");
72
+
73
+ const duration = Date.now() - startTime;
74
+
75
+ // Should have waited ~200ms total (2 retries × 100ms)
76
+ expect(duration).toBeGreaterThanOrEqual(190);
77
+ expect(duration).toBeLessThan(300);
78
+ });
79
+
80
+ test("should use exponential backoff with jitter", async () => {
81
+ const handler = vi.fn(() => {
82
+ throw new Error("Retry");
83
+ });
84
+
85
+ const startTime = Date.now();
86
+
87
+ await expect(
88
+ retryProvider.retry({
89
+ handler,
90
+ max: 3,
91
+ backoff: { initial: 100, factor: 2, jitter: true },
92
+ }),
93
+ ).rejects.toThrowError("Retry");
94
+
95
+ const duration = Date.now() - startTime;
96
+
97
+ // First retry: ~100ms, Second retry: ~200ms
98
+ // With jitter, expect at least base delays
99
+ expect(duration).toBeGreaterThanOrEqual(280);
100
+ });
101
+
102
+ test("should respect maxDuration timeout", { retry: 3 }, async () => {
103
+ const handler = vi.fn(() => {
104
+ throw new Error("Retry");
105
+ });
106
+
107
+ await expect(
108
+ retryProvider.retry({
109
+ handler,
110
+ max: 10,
111
+ maxDuration: [100, "milliseconds"],
112
+ backoff: 50,
113
+ }),
114
+ ).rejects.toThrowError(RetryTimeoutError);
115
+
116
+ // Should not reach max retries due to timeout
117
+ expect(handler).toHaveBeenCalledTimes(2);
118
+ });
119
+
120
+ test("should respect AbortSignal cancellation", async () => {
121
+ const handler = vi.fn(() => {
122
+ throw new Error("Retry");
123
+ });
124
+
125
+ const abortController = new AbortController();
126
+
127
+ // Abort after first attempt
128
+ setTimeout(() => abortController.abort(), 50);
129
+
130
+ await expect(
131
+ retryProvider.retry({
132
+ handler,
133
+ max: 10,
134
+ backoff: 100,
135
+ signal: abortController.signal,
136
+ }),
137
+ ).rejects.toThrowError(RetryCancelError);
138
+
139
+ expect(handler).toHaveBeenCalledTimes(1);
140
+ });
141
+
142
+ test("should call onError callback on each failure", async () => {
143
+ let attempts = 0;
144
+ const handler = vi.fn(() => {
145
+ attempts++;
146
+ if (attempts < 3) {
147
+ throw new Error(`Attempt ${attempts}`);
148
+ }
149
+ return "success";
150
+ });
151
+
152
+ const onError = vi.fn();
153
+
154
+ await retryProvider.retry({ handler, max: 3, onError });
155
+
156
+ expect(onError).toHaveBeenCalledTimes(2);
157
+ expect(onError).toHaveBeenNthCalledWith(
158
+ 1,
159
+ expect.objectContaining({ message: "Attempt 1" }),
160
+ 1,
161
+ );
162
+ expect(onError).toHaveBeenNthCalledWith(
163
+ 2,
164
+ expect.objectContaining({ message: "Attempt 2" }),
165
+ 2,
166
+ );
167
+ });
168
+
169
+ test("should pass arguments to handler", async () => {
170
+ const handler = vi.fn((a: number, b: string) => {
171
+ return `${a}-${b}`;
172
+ });
173
+
174
+ const result = await retryProvider.retry({ handler, max: 1 }, 42, "test");
175
+
176
+ expect(result).toBe("42-test");
177
+ expect(handler).toHaveBeenCalledWith(42, "test");
178
+ });
179
+
180
+ test("should log warnings on retry failures", async () => {
181
+ const handler = vi.fn(() => {
182
+ throw new Error("Test error");
183
+ });
184
+
185
+ // Spy on the log method
186
+ // @ts-expect-error - accessing protected property for testing
187
+ const logSpy = vi.spyOn(retryProvider.log, "warn");
188
+
189
+ await expect(retryProvider.retry({ handler, max: 2 })).rejects.toThrowError(
190
+ "Test error",
191
+ );
192
+
193
+ expect(logSpy).toHaveBeenCalledTimes(2);
194
+ expect(logSpy).toHaveBeenCalledWith(
195
+ "Retry attempt failed",
196
+ expect.objectContaining({
197
+ attempt: 1,
198
+ maxAttempts: 2,
199
+ remainingAttempts: 1,
200
+ error: "Test error",
201
+ errorName: "Error",
202
+ }),
203
+ );
204
+ });
205
+
206
+ test("should support additionalSignal for combined cancellation", async () => {
207
+ const handler = vi.fn(() => {
208
+ throw new Error("Retry");
209
+ });
210
+
211
+ const additionalController = new AbortController();
212
+
213
+ // Abort the additional signal
214
+ setTimeout(() => additionalController.abort(), 50);
215
+
216
+ await expect(
217
+ retryProvider.retry({
218
+ handler,
219
+ max: 10,
220
+ backoff: 100,
221
+ additionalSignal: additionalController.signal,
222
+ }),
223
+ ).rejects.toThrowError(RetryCancelError);
224
+
225
+ expect(handler).toHaveBeenCalledTimes(1);
226
+ });
227
+
228
+ test("should not retry on non-Error throws", async () => {
229
+ const handler = vi.fn(() => {
230
+ throw "string error";
231
+ });
232
+
233
+ await expect(retryProvider.retry({ handler, max: 3 })).rejects.toBe(
234
+ "string error",
235
+ );
236
+
237
+ expect(handler).toHaveBeenCalledTimes(1);
238
+ });
239
+
240
+ test("should succeed on first attempt without retries", async () => {
241
+ const handler = vi.fn(() => "immediate success");
242
+
243
+ const result = await retryProvider.retry({ handler, max: 3 });
244
+
245
+ expect(result).toBe("immediate success");
246
+ expect(handler).toHaveBeenCalledTimes(1);
247
+ });
248
+
249
+ test("should use default max of 3 when not specified", async () => {
250
+ const handler = vi.fn(() => {
251
+ throw new Error("Retry");
252
+ });
253
+
254
+ await expect(retryProvider.retry({ handler })).rejects.toThrowError(
255
+ "Retry",
256
+ );
257
+
258
+ expect(handler).toHaveBeenCalledTimes(3);
259
+ });
260
+
261
+ // Bug #1 (Red/Critical): Signal precedence issue
262
+ // When both signal and additionalSignal are provided, ONLY signal is respected during backoff
263
+ // This means additionalSignal (app lifecycle) cannot cancel during backoff if user signal exists
264
+ test("should respect both user signal and additionalSignal during backoff waits", async () => {
265
+ const handler = vi.fn(() => {
266
+ throw new Error("Retry");
267
+ });
268
+
269
+ const userController = new AbortController();
270
+ const additionalController = new AbortController();
271
+
272
+ // Start retry with both signals provided
273
+ const promise = retryProvider.retry({
274
+ handler,
275
+ max: 10,
276
+ backoff: 200, // Long backoff to ensure we have time to abort
277
+ signal: userController.signal,
278
+ additionalSignal: additionalController.signal,
279
+ });
280
+
281
+ // Wait for first attempt to fail and backoff to start
282
+ await new Promise((resolve) => setTimeout(resolve, 10));
283
+
284
+ // Abort additionalSignal during backoff (user signal still active)
285
+ additionalController.abort();
286
+
287
+ // BUG: This currently does NOT throw RetryCancelError because line 169 only uses signal
288
+ // The additionalSignal abort is ignored during wait
289
+ // EXPECTED: Should throw RetryCancelError
290
+ // ACTUAL: Continues retrying until user signal aborts
291
+ const startTime = Date.now();
292
+
293
+ try {
294
+ await promise;
295
+ // If we reach here, the bug exists - additionalSignal was ignored
296
+ throw new Error("Should have thrown RetryCancelError");
297
+ } catch (error) {
298
+ const duration = Date.now() - startTime;
299
+
300
+ // If bug exists: continues for ~200ms+ (full backoff)
301
+ // If fixed: cancels immediately (~10-30ms)
302
+ if (error instanceof RetryCancelError) {
303
+ // Fixed: should cancel quickly when additionalSignal aborts
304
+ expect(duration).toBeLessThan(100);
305
+ } else {
306
+ // Bug exists: keeps waiting for full backoff despite additionalSignal abort
307
+ expect(duration).toBeGreaterThanOrEqual(180);
308
+ }
309
+ }
310
+ });
311
+
312
+ // Bug #2 (Yellow/High): Abort race condition
313
+ test("should throw RetryCancelError when aborted after handler error", async () => {
314
+ let attempts = 0;
315
+ const handler = vi.fn(() => {
316
+ attempts++;
317
+ if (attempts === 1) {
318
+ throw new Error("Handler failed");
319
+ }
320
+ throw new Error("Should not reach here");
321
+ });
322
+
323
+ const abortController = new AbortController();
324
+
325
+ // Abort immediately after first error (before backoff wait)
326
+ setTimeout(() => abortController.abort(), 10);
327
+
328
+ await expect(
329
+ retryProvider.retry({
330
+ handler,
331
+ max: 10,
332
+ backoff: 100,
333
+ signal: abortController.signal,
334
+ }),
335
+ ).rejects.toThrowError(RetryCancelError);
336
+
337
+ // Should have attempted once, then aborted
338
+ expect(handler).toHaveBeenCalledTimes(1);
339
+ });
340
+
341
+ // Bug #3 (Yellow/High): Timeout check timing
342
+ test("should not allow handler to complete after maxDuration", async () => {
343
+ const handler = vi.fn(async () => {
344
+ // Handler takes 150ms
345
+ await new Promise((resolve) => setTimeout(resolve, 150));
346
+ throw new Error("Retry");
347
+ });
348
+
349
+ const startTime = Date.now();
350
+
351
+ await expect(
352
+ retryProvider.retry({
353
+ handler,
354
+ max: 10,
355
+ maxDuration: [100, "milliseconds"],
356
+ backoff: 10,
357
+ }),
358
+ ).rejects.toThrowError(RetryTimeoutError);
359
+
360
+ const duration = Date.now() - startTime;
361
+
362
+ // Should timeout around 100ms, not wait for handler to complete (150ms)
363
+ // With current bug, this will fail because handler completes at ~150ms
364
+ expect(duration).toBeLessThan(200);
365
+ expect(handler).toHaveBeenCalledTimes(1);
366
+ });
367
+
368
+ // Bug #7 (Medium): onError not called on final attempt
369
+ test("should call onError on the final failed attempt", async () => {
370
+ const handler = vi.fn(() => {
371
+ throw new Error("Always fails");
372
+ });
373
+
374
+ const onError = vi.fn();
375
+
376
+ await expect(
377
+ retryProvider.retry({
378
+ handler,
379
+ max: 3,
380
+ backoff: 0,
381
+ onError,
382
+ }),
383
+ ).rejects.toThrowError("Always fails");
384
+
385
+ // Should call onError for ALL attempts, including the final one
386
+ // BUG: Currently only called 2 times (attempts 1-2), not 3 times
387
+ expect(onError).toHaveBeenCalledTimes(3);
388
+ expect(onError).toHaveBeenNthCalledWith(
389
+ 1,
390
+ expect.objectContaining({ message: "Always fails" }),
391
+ 1,
392
+ );
393
+ expect(onError).toHaveBeenNthCalledWith(
394
+ 2,
395
+ expect.objectContaining({ message: "Always fails" }),
396
+ 2,
397
+ );
398
+ expect(onError).toHaveBeenNthCalledWith(
399
+ 3,
400
+ expect.objectContaining({ message: "Always fails" }),
401
+ 3,
402
+ );
403
+ });
404
+
405
+ // Bug #8 (Critical): AbortSignal.any() memory leak
406
+ test("should not create multiple AbortSignal instances during retries", async () => {
407
+ const handler = vi.fn(() => {
408
+ throw new Error("Retry");
409
+ });
410
+
411
+ const userController = new AbortController();
412
+ const additionalController = new AbortController();
413
+
414
+ // Track how many times AbortSignal.any is called
415
+ const originalAny = AbortSignal.any;
416
+ const anySpy = vi.fn(originalAny);
417
+ AbortSignal.any = anySpy as any;
418
+
419
+ try {
420
+ await expect(
421
+ retryProvider.retry({
422
+ handler,
423
+ max: 5,
424
+ backoff: 10,
425
+ signal: userController.signal,
426
+ additionalSignal: additionalController.signal,
427
+ }),
428
+ ).rejects.toThrowError("Retry");
429
+
430
+ // BUG: Currently creates a NEW signal for each backoff (4 times for 5 attempts)
431
+ // EXPECTED: Should only create ONE combined signal at the start
432
+ expect(anySpy).toHaveBeenCalledTimes(1);
433
+ } finally {
434
+ // Restore original
435
+ AbortSignal.any = originalAny;
436
+ }
437
+ });
438
+ });