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,766 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { DateTimeProvider } from "alepha/datetime";
3
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
+ import { $batch, AlephaBatch } from "../index.ts";
5
+
6
+ // Mock handler to track calls and received items
7
+ const createMockHandler = () => {
8
+ return vi.fn(async (items: any[]) => {
9
+ // Default successful handler
10
+ });
11
+ };
12
+
13
+ describe("$batch primitive", () => {
14
+ let alepha: Alepha;
15
+ let time: DateTimeProvider;
16
+
17
+ beforeEach(() => {
18
+ alepha = Alepha.create().with(AlephaBatch);
19
+ time = alepha.inject(DateTimeProvider);
20
+ });
21
+
22
+ afterEach(async () => {
23
+ await alepha.stop();
24
+ });
25
+
26
+ test("should batch items and flush when maxSize is reached", async () => {
27
+ const mockHandler = createMockHandler();
28
+ class TestApp {
29
+ batcher = $batch({
30
+ schema: t.text(),
31
+ maxSize: 3,
32
+ handler: mockHandler,
33
+ });
34
+ }
35
+
36
+ const app = alepha.inject(TestApp);
37
+ await alepha.start();
38
+
39
+ const id1 = await app.batcher.push("A");
40
+ const id2 = await app.batcher.push("B");
41
+ expect(mockHandler).not.toHaveBeenCalled();
42
+
43
+ const id3 = await app.batcher.push("C"); // This should trigger the flush
44
+
45
+ await vi.waitFor(() => {
46
+ expect(mockHandler).toHaveBeenCalledTimes(1);
47
+ });
48
+ expect(mockHandler).toHaveBeenCalledWith(["A", "B", "C"]);
49
+
50
+ // Verify IDs are returned
51
+ expect(typeof id1).toBe("string");
52
+ expect(typeof id2).toBe("string");
53
+ expect(typeof id3).toBe("string");
54
+ });
55
+
56
+ test("should flush remaining items when maxDuration is reached", async () => {
57
+ const mockHandler = createMockHandler();
58
+ class TestApp {
59
+ batcher = $batch({
60
+ schema: t.text(),
61
+ maxSize: 10,
62
+ maxDuration: [5, "seconds"],
63
+ handler: mockHandler,
64
+ });
65
+ }
66
+
67
+ const app = alepha.inject(TestApp);
68
+ await alepha.start();
69
+
70
+ app.batcher.push("A");
71
+ app.batcher.push("B");
72
+ expect(mockHandler).not.toHaveBeenCalled();
73
+
74
+ await time.travel([6, "seconds"]); // Exceed maxDuration
75
+
76
+ await vi.waitFor(() => {
77
+ expect(mockHandler).toHaveBeenCalledTimes(1);
78
+ });
79
+ expect(mockHandler).toHaveBeenCalledWith(["A", "B"]);
80
+ });
81
+
82
+ test("should handle partitioning correctly", async () => {
83
+ const mockHandler = createMockHandler();
84
+ class TestApp {
85
+ batcher = $batch({
86
+ schema: t.object({ id: t.number(), value: t.text() }),
87
+ maxSize: 2,
88
+ partitionBy: (item) => `partition-${item.id}`,
89
+ handler: mockHandler,
90
+ });
91
+ }
92
+
93
+ const app = alepha.inject(TestApp);
94
+ await alepha.start();
95
+
96
+ app.batcher.push({ id: 1, value: "A" });
97
+ app.batcher.push({ id: 2, value: "B" });
98
+ app.batcher.push({ id: 1, value: "C" }); // Flushes partition 1
99
+
100
+ await vi.waitFor(
101
+ () => {
102
+ expect(mockHandler).toHaveBeenCalledTimes(1);
103
+ },
104
+ { timeout: 1000 },
105
+ );
106
+ expect(mockHandler).toHaveBeenCalledWith([
107
+ { id: 1, value: "A" },
108
+ { id: 1, value: "C" },
109
+ ]);
110
+
111
+ app.batcher.push({ id: 2, value: "D" }); // Flushes partition 2
112
+
113
+ await vi.waitFor(
114
+ () => {
115
+ expect(mockHandler).toHaveBeenCalledTimes(2);
116
+ },
117
+ { timeout: 2000 },
118
+ );
119
+ expect(mockHandler).toHaveBeenCalledWith([
120
+ { id: 2, value: "B" },
121
+ { id: 2, value: "D" },
122
+ ]);
123
+ });
124
+
125
+ test("should flush all pending items on application stop", async () => {
126
+ const mockHandler = createMockHandler();
127
+ class TestApp {
128
+ batcher = $batch({
129
+ schema: t.text(),
130
+ maxSize: 10,
131
+ handler: mockHandler,
132
+ });
133
+ }
134
+
135
+ const app = alepha.inject(TestApp);
136
+ await alepha.start();
137
+
138
+ app.batcher.push("A");
139
+ app.batcher.push("B");
140
+
141
+ await alepha.stop(); // Graceful shutdown should trigger flush
142
+
143
+ expect(mockHandler).toHaveBeenCalledTimes(1);
144
+ expect(mockHandler).toHaveBeenCalledWith(["A", "B"]);
145
+ });
146
+
147
+ test("should reject wait promise if handler fails after retries", async () => {
148
+ const failingHandler = vi.fn(async (items: any[]) => {
149
+ throw new Error("Handler failed");
150
+ });
151
+
152
+ class TestApp {
153
+ batcher = $batch({
154
+ schema: t.text(),
155
+ maxSize: 1,
156
+ handler: failingHandler,
157
+ retry: { max: 2 }, // Try a total of 2 times
158
+ });
159
+ }
160
+
161
+ const app = alepha.inject(TestApp);
162
+ await alepha.start();
163
+
164
+ const id = await app.batcher.push("A");
165
+ expect(typeof id).toBe("string");
166
+
167
+ const waitPromise = app.batcher.wait(id);
168
+ await expect(waitPromise).rejects.toThrow("Handler failed");
169
+
170
+ await vi.waitFor(() => {
171
+ expect(failingHandler).toHaveBeenCalledTimes(2);
172
+ });
173
+
174
+ // Verify status is failed
175
+ const status = app.batcher.status(id);
176
+ expect(status?.status).toBe("failed");
177
+ if (status?.status === "failed") {
178
+ expect(status.error.message).toBe("Handler failed");
179
+ }
180
+ });
181
+
182
+ test("should resolve wait promise on successful processing", async () => {
183
+ const mockHandler = vi.fn(async (items: any[]) => {
184
+ return "success";
185
+ });
186
+ class TestApp {
187
+ batcher = $batch({
188
+ schema: t.text(),
189
+ maxSize: 1,
190
+ handler: mockHandler,
191
+ });
192
+ }
193
+
194
+ const app = alepha.inject(TestApp);
195
+ await alepha.start();
196
+
197
+ const id = await app.batcher.push("A");
198
+ expect(typeof id).toBe("string");
199
+
200
+ // Status should be pending or processing
201
+ let status = app.batcher.status(id);
202
+ expect(status?.status).toMatch(/pending|processing/);
203
+
204
+ const result = await app.batcher.wait(id);
205
+ expect(result).toBe("success");
206
+
207
+ // Status should now be completed
208
+ status = app.batcher.status(id);
209
+ expect(status?.status).toBe("completed");
210
+ if (status?.status === "completed") {
211
+ expect(status.result).toBe("success");
212
+ }
213
+ });
214
+
215
+ test("should respect concurrency option", async () => {
216
+ let activeHandlers = 0;
217
+ let maxActiveHandlers = 0;
218
+
219
+ const slowHandler = vi.fn(async (items: any[]) => {
220
+ activeHandlers++;
221
+ maxActiveHandlers = Math.max(maxActiveHandlers, activeHandlers);
222
+ await time.wait(100); // Simulate work
223
+ activeHandlers--;
224
+ });
225
+
226
+ class TestApp {
227
+ batcher = $batch({
228
+ schema: t.text(),
229
+ maxSize: 1,
230
+ concurrency: 2,
231
+ handler: slowHandler,
232
+ });
233
+ }
234
+
235
+ const app = alepha.inject(TestApp);
236
+ await alepha.start();
237
+
238
+ // Push 4 items to trigger 4 batches
239
+ const ids = await Promise.all([
240
+ app.batcher.push("A"),
241
+ app.batcher.push("B"),
242
+ app.batcher.push("C"),
243
+ app.batcher.push("D"),
244
+ ]);
245
+
246
+ // Wait for all to complete
247
+ await Promise.all(ids.map((id) => app.batcher.wait(id)));
248
+
249
+ expect(slowHandler).toHaveBeenCalledTimes(4);
250
+ expect(maxActiveHandlers).toBe(2);
251
+ });
252
+
253
+ test("should flush manually a specific partition", async () => {
254
+ const mockHandler = createMockHandler();
255
+ class TestApp {
256
+ batcher = $batch({
257
+ schema: t.object({ id: t.number(), value: t.text() }),
258
+ maxSize: 5,
259
+ partitionBy: (item) => `p-${item.id}`,
260
+ handler: mockHandler,
261
+ });
262
+ }
263
+
264
+ const app = alepha.inject(TestApp);
265
+ await alepha.start();
266
+
267
+ app.batcher.push({ id: 1, value: "A" });
268
+ app.batcher.push({ id: 2, value: "B" });
269
+ app.batcher.push({ id: 1, value: "C" });
270
+
271
+ expect(mockHandler).not.toHaveBeenCalled();
272
+
273
+ await app.batcher.flush("p-1");
274
+
275
+ await vi.waitFor(() => {
276
+ expect(mockHandler).toHaveBeenCalledTimes(1);
277
+ });
278
+ expect(mockHandler).toHaveBeenCalledWith([
279
+ { id: 1, value: "A" },
280
+ { id: 1, value: "C" },
281
+ ]);
282
+
283
+ // The other partition should remain
284
+ await app.batcher.flush("p-2");
285
+
286
+ await vi.waitFor(() => {
287
+ expect(mockHandler).toHaveBeenCalledTimes(2);
288
+ });
289
+ expect(mockHandler).toHaveBeenCalledWith([{ id: 2, value: "B" }]);
290
+ });
291
+
292
+ test("should flush all partitions manually", async () => {
293
+ const mockHandler = createMockHandler();
294
+ class TestApp {
295
+ batcher = $batch({
296
+ schema: t.object({ id: t.number(), value: t.text() }),
297
+ maxSize: 5,
298
+ partitionBy: (item) => `p-${item.id}`,
299
+ handler: mockHandler,
300
+ });
301
+ }
302
+
303
+ const app = alepha.inject(TestApp);
304
+ await alepha.start();
305
+
306
+ app.batcher.push({ id: 1, value: "A" });
307
+ app.batcher.push({ id: 2, value: "B" });
308
+
309
+ await app.batcher.flush();
310
+
311
+ await vi.waitFor(() => {
312
+ expect(mockHandler).toHaveBeenCalledTimes(2);
313
+ });
314
+ expect(mockHandler).toHaveBeenCalledWith([{ id: 1, value: "A" }]);
315
+ expect(mockHandler).toHaveBeenCalledWith([{ id: 2, value: "B" }]);
316
+ });
317
+
318
+ test("should validate items against schema", async () => {
319
+ const mockHandler = createMockHandler();
320
+ class TestApp {
321
+ batcher = $batch({
322
+ schema: t.number(), // Expects numbers
323
+ maxSize: 1,
324
+ handler: mockHandler,
325
+ });
326
+ }
327
+
328
+ const app = alepha.inject(TestApp);
329
+ await alepha.start();
330
+
331
+ // Vitest doesn't properly catch type errors in async promises thrown by TypeBox,
332
+ // so we test the rejection with a generic Error.
333
+ await expect(app.batcher.push("not-a-number" as any)).rejects.toThrow();
334
+ expect(mockHandler).not.toHaveBeenCalled();
335
+
336
+ const id = await app.batcher.push(123);
337
+ expect(typeof id).toBe("string");
338
+ await vi.waitFor(() => {
339
+ expect(mockHandler).toHaveBeenCalledWith([123]);
340
+ });
341
+ });
342
+
343
+ test("should handle empty batches gracefully", async () => {
344
+ const mockHandler = createMockHandler();
345
+ class TestApp {
346
+ batcher = $batch({
347
+ schema: t.text(),
348
+ maxSize: 5,
349
+ handler: mockHandler,
350
+ });
351
+ }
352
+
353
+ const app = alepha.inject(TestApp);
354
+ await alepha.start();
355
+
356
+ await app.batcher.flush(); // Should not throw or call handler
357
+
358
+ expect(mockHandler).not.toHaveBeenCalled();
359
+ });
360
+
361
+ test("should handle empty partitions gracefully", async () => {
362
+ const mockHandler = createMockHandler();
363
+ class TestApp {
364
+ batcher = $batch({
365
+ schema: t.text(),
366
+ maxSize: 5,
367
+ maxDuration: [1, "second"],
368
+ partitionBy: (item) => item,
369
+ handler: mockHandler,
370
+ });
371
+ }
372
+ const app = alepha.inject(TestApp);
373
+ await alepha.start();
374
+ await app.batcher.push("D");
375
+ await app.batcher.flush("D");
376
+ });
377
+
378
+ test("should allow to get resolved items", async () => {
379
+ let tick = 0;
380
+
381
+ class TestApp {
382
+ httpBatch = $batch({
383
+ schema: t.text(),
384
+ maxSize: 10,
385
+ maxDuration: [100, "milliseconds"],
386
+ handler: async (urls) => {
387
+ tick += 1;
388
+
389
+ if (urls.length === 1) {
390
+ return { [urls[0]]: `Response for ${urls[0]}` };
391
+ }
392
+
393
+ const response: Record<string, string> = {};
394
+ for (const url of urls) {
395
+ response[url] = `(batch) Response for ${url}`;
396
+ }
397
+
398
+ return response;
399
+ },
400
+ });
401
+
402
+ async fetch(url: string) {
403
+ const id = await this.httpBatch.push(url);
404
+ const response = await this.httpBatch.wait(id);
405
+ return response[url];
406
+ }
407
+ }
408
+
409
+ const app = alepha.inject(TestApp);
410
+ await alepha.start();
411
+
412
+ const tasks: Promise<any>[] = [];
413
+
414
+ tasks.push(app.fetch("https://example.com/A"));
415
+ tasks.push(app.fetch("https://example.com/B"));
416
+ await time.wait(200); // Wait for batch to accumulate items
417
+ tasks.push(app.fetch("https://example.com/C"));
418
+
419
+ const result = await Promise.all(tasks);
420
+
421
+ expect(tick).toBe(2);
422
+ expect(result).toEqual([
423
+ "(batch) Response for https://example.com/A",
424
+ "(batch) Response for https://example.com/B",
425
+ "Response for https://example.com/C",
426
+ ]);
427
+
428
+ const response = await app.fetch("https://example.com/D");
429
+ expect(response).toBe("Response for https://example.com/D");
430
+ });
431
+
432
+ test("should handle items arriving during batch processing (race condition fix)", async () => {
433
+ let handlerCallCount = 0;
434
+ const handlerCalls: string[][] = [];
435
+
436
+ class TestApp {
437
+ batcher = $batch({
438
+ schema: t.text(),
439
+ maxSize: 2,
440
+ maxDuration: [10, "seconds"],
441
+ handler: async (items: string[]) => {
442
+ handlerCallCount++;
443
+ handlerCalls.push([...items]);
444
+ // Simulate slow processing
445
+ await time.wait(100);
446
+ return `batch-${handlerCallCount}`;
447
+ },
448
+ });
449
+ }
450
+
451
+ const app = alepha.inject(TestApp);
452
+ await alepha.start();
453
+
454
+ // Push 2 items to trigger first batch
455
+ const id1 = await app.batcher.push("A");
456
+ const id2 = await app.batcher.push("B");
457
+
458
+ // Wait a bit for processing to start but not complete
459
+ await time.wait(50);
460
+
461
+ // Push more items while first batch is processing
462
+ const id3 = await app.batcher.push("C");
463
+ const id4 = await app.batcher.push("D");
464
+
465
+ // Wait for all items to complete
466
+ const results = await Promise.all([
467
+ app.batcher.wait(id1),
468
+ app.batcher.wait(id2),
469
+ app.batcher.wait(id3),
470
+ app.batcher.wait(id4),
471
+ ]);
472
+
473
+ // Should have 2 batches
474
+ expect(handlerCallCount).toBe(2);
475
+ expect(handlerCalls[0]).toEqual(["A", "B"]);
476
+ expect(handlerCalls[1]).toEqual(["C", "D"]);
477
+ expect(results).toEqual(["batch-1", "batch-1", "batch-2", "batch-2"]);
478
+ });
479
+
480
+ test("should handle concurrent pushes to same partition during flush", async () => {
481
+ let processingBatch1 = false;
482
+
483
+ class TestApp {
484
+ batcher = $batch({
485
+ schema: t.object({ id: t.number(), value: t.text() }),
486
+ maxSize: 2,
487
+ partitionBy: (item) => `p-${item.id}`,
488
+ handler: async (items) => {
489
+ if (!processingBatch1) {
490
+ processingBatch1 = true;
491
+ // Simulate slow processing for first batch only
492
+ await time.wait(100);
493
+ }
494
+ return items.map((item) => ({ ...item, processed: true }));
495
+ },
496
+ });
497
+ }
498
+
499
+ const app = alepha.inject(TestApp);
500
+ await alepha.start();
501
+
502
+ // Push 2 items to partition p-1 to trigger flush
503
+ const id1 = await app.batcher.push({ id: 1, value: "A" });
504
+ const id2 = await app.batcher.push({ id: 1, value: "B" });
505
+
506
+ // Wait for processing to start
507
+ await time.wait(50);
508
+
509
+ // Push more items to same partition while processing
510
+ const id3 = await app.batcher.push({ id: 1, value: "C" });
511
+ const id4 = await app.batcher.push({ id: 1, value: "D" });
512
+
513
+ await Promise.all([
514
+ app.batcher.wait(id1),
515
+ app.batcher.wait(id2),
516
+ app.batcher.wait(id3),
517
+ app.batcher.wait(id4),
518
+ ]);
519
+
520
+ // All items should be processed successfully
521
+ expect(true).toBe(true); // If we got here without errors, test passes
522
+ });
523
+
524
+ test("should use default values for maxSize, concurrency, and maxDuration", async () => {
525
+ const mockHandler = createMockHandler();
526
+
527
+ class TestApp {
528
+ batcher = $batch({
529
+ schema: t.text(),
530
+ // Not providing maxSize, concurrency, or maxDuration
531
+ handler: mockHandler,
532
+ });
533
+ }
534
+
535
+ const app = alepha.inject(TestApp);
536
+ await alepha.start();
537
+
538
+ // Test default maxSize (10)
539
+ for (let i = 0; i < 9; i++) {
540
+ app.batcher.push(`item-${i}`);
541
+ }
542
+ expect(mockHandler).not.toHaveBeenCalled();
543
+
544
+ app.batcher.push("item-9"); // 10th item should trigger flush
545
+
546
+ await vi.waitFor(() => {
547
+ expect(mockHandler).toHaveBeenCalledTimes(1);
548
+ });
549
+ expect(mockHandler).toHaveBeenCalledWith([
550
+ "item-0",
551
+ "item-1",
552
+ "item-2",
553
+ "item-3",
554
+ "item-4",
555
+ "item-5",
556
+ "item-6",
557
+ "item-7",
558
+ "item-8",
559
+ "item-9",
560
+ ]);
561
+ });
562
+
563
+ test("should use default maxDuration (1 second)", async () => {
564
+ const mockHandler = createMockHandler();
565
+
566
+ class TestApp {
567
+ batcher = $batch({
568
+ schema: t.text(),
569
+ // Not providing maxDuration, should default to 1 second
570
+ handler: mockHandler,
571
+ });
572
+ }
573
+
574
+ const app = alepha.inject(TestApp);
575
+ await alepha.start();
576
+
577
+ app.batcher.push("A");
578
+ expect(mockHandler).not.toHaveBeenCalled();
579
+
580
+ // Wait for default timeout (1 second)
581
+ await time.travel([1.1, "seconds"]);
582
+
583
+ await vi.waitFor(() => {
584
+ expect(mockHandler).toHaveBeenCalledTimes(1);
585
+ });
586
+ expect(mockHandler).toHaveBeenCalledWith(["A"]);
587
+ });
588
+
589
+ test("should use default concurrency (1)", async () => {
590
+ let activeHandlers = 0;
591
+ let maxActiveHandlers = 0;
592
+
593
+ const slowHandler = vi.fn(async (items: any[]) => {
594
+ activeHandlers++;
595
+ maxActiveHandlers = Math.max(maxActiveHandlers, activeHandlers);
596
+ await time.wait(100);
597
+ activeHandlers--;
598
+ });
599
+
600
+ class TestApp {
601
+ batcher = $batch({
602
+ schema: t.text(),
603
+ maxSize: 1,
604
+ // Not providing concurrency, should default to 1
605
+ handler: slowHandler,
606
+ });
607
+ }
608
+
609
+ const app = alepha.inject(TestApp);
610
+ await alepha.start();
611
+
612
+ // Push 3 items to trigger 3 batches
613
+ const ids = await Promise.all([
614
+ app.batcher.push("A"),
615
+ app.batcher.push("B"),
616
+ app.batcher.push("C"),
617
+ ]);
618
+
619
+ await Promise.all(ids.map((id) => app.batcher.wait(id)));
620
+
621
+ expect(slowHandler).toHaveBeenCalledTimes(3);
622
+ expect(maxActiveHandlers).toBe(1); // Should never exceed default concurrency of 1
623
+ });
624
+
625
+ test("should track item status through lifecycle", async () => {
626
+ class TestApp {
627
+ batcher = $batch({
628
+ schema: t.text(),
629
+ maxSize: 10,
630
+ maxDuration: [100, "milliseconds"],
631
+ handler: async (items: string[]) => {
632
+ await time.wait(50);
633
+ return items.map((item) => `processed-${item}`);
634
+ },
635
+ });
636
+ }
637
+
638
+ const app = alepha.inject(TestApp);
639
+ await alepha.start();
640
+
641
+ // Push an item and verify it gets an ID
642
+ const id = await app.batcher.push("test-item");
643
+ expect(typeof id).toBe("string");
644
+
645
+ // Status should be pending initially
646
+ let status = app.batcher.status(id);
647
+ expect(status?.status).toMatch(/pending/);
648
+
649
+ // Wait for processing to complete
650
+ const result = await app.batcher.wait(id);
651
+ expect(result).toEqual(["processed-test-item"]);
652
+
653
+ // Status should now be completed
654
+ status = app.batcher.status(id);
655
+ expect(status?.status).toBe("completed");
656
+ if (status?.status === "completed") {
657
+ expect(status.result).toEqual(["processed-test-item"]);
658
+ }
659
+
660
+ // Calling wait again should return the cached result immediately
661
+ const result2 = await app.batcher.wait(id);
662
+ expect(result2).toEqual(["processed-test-item"]);
663
+
664
+ // Test non-existent ID
665
+ const nonExistentStatus = app.batcher.status("non-existent-id");
666
+ expect(nonExistentStatus).toBeUndefined();
667
+
668
+ await expect(app.batcher.wait("non-existent-id")).rejects.toThrow(
669
+ "Item with id 'non-existent-id' not found",
670
+ );
671
+ });
672
+
673
+ test("should restart timeout for items that arrive during flush (maxDuration bug)", async () => {
674
+ const mockHandler = vi.fn(async (items: string[]) => {
675
+ // Simulate slow processing
676
+ await time.wait(50);
677
+ return items.map((item) => `processed-${item}`);
678
+ });
679
+
680
+ class TestApp {
681
+ batcher = $batch({
682
+ schema: t.text(),
683
+ maxSize: 10,
684
+ maxDuration: [100, "milliseconds"],
685
+ handler: mockHandler,
686
+ });
687
+ }
688
+
689
+ const app = alepha.inject(TestApp);
690
+ await alepha.start();
691
+
692
+ // Push first item, timeout starts
693
+ const id1 = await app.batcher.push("A");
694
+
695
+ // Wait for timeout to fire and flush to start
696
+ await time.travel([100, "milliseconds"]);
697
+
698
+ // Handler is now processing (takes 50ms)
699
+ // At T=25ms into processing, push another item
700
+ await time.wait(25);
701
+ const id2 = await app.batcher.push("B");
702
+
703
+ // Wait for first flush to complete
704
+ await time.wait(30); // Total 55ms, first flush completes at 50ms
705
+
706
+ // At this point:
707
+ // - First batch ["A"] has been processed
708
+ // - Item "B" is waiting in the partition
709
+ // - BUG: No timeout exists for "B" because it was pushed during flush
710
+
711
+ expect(mockHandler).toHaveBeenCalledTimes(1);
712
+ expect(mockHandler).toHaveBeenCalledWith(["A"]);
713
+
714
+ // Item "B" should flush after maxDuration (100ms)
715
+ await time.travel([110, "milliseconds"]); // More than enough time
716
+
717
+ // Expected: 2 calls (first batch "A", second batch "B")
718
+ // Without fix: 1 call (only "A" was processed, "B" is stuck)
719
+ await vi.waitFor(
720
+ () => {
721
+ expect(mockHandler).toHaveBeenCalledTimes(2);
722
+ },
723
+ { timeout: 200 },
724
+ );
725
+ expect(mockHandler).toHaveBeenNthCalledWith(2, ["B"]);
726
+
727
+ // Verify both items completed successfully
728
+ const result1 = await app.batcher.wait(id1);
729
+ const result2 = await app.batcher.wait(id2);
730
+ expect(result1).toEqual(["processed-A"]);
731
+ expect(result2).toEqual(["processed-B"]);
732
+ });
733
+
734
+ test("should return unique IDs for each push", async () => {
735
+ const mockHandler = createMockHandler();
736
+ class TestApp {
737
+ batcher = $batch({
738
+ schema: t.text(),
739
+ maxSize: 10,
740
+ handler: mockHandler,
741
+ });
742
+ }
743
+
744
+ const app = alepha.inject(TestApp);
745
+ await alepha.start();
746
+
747
+ const id1 = await app.batcher.push("A");
748
+ const id2 = await app.batcher.push("B");
749
+ const id3 = await app.batcher.push("C");
750
+
751
+ expect(id1).not.toBe(id2);
752
+ expect(id2).not.toBe(id3);
753
+ expect(id1).not.toBe(id3);
754
+
755
+ // All should be valid UUIDs (rough check)
756
+ expect(id1).toMatch(
757
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
758
+ );
759
+ expect(id2).toMatch(
760
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
761
+ );
762
+ expect(id3).toMatch(
763
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
764
+ );
765
+ });
766
+ });