alepha 0.20.5 → 0.20.7

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 (367) hide show
  1. package/AGENTS.md +0 -1
  2. package/CLAUDE.md +0 -1
  3. package/assets/agents-template.md +0 -1
  4. package/dist/api/audits/index.browser.js +1 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts +701 -654
  7. package/dist/api/audits/index.d.ts.map +1 -1
  8. package/dist/api/audits/index.js +24 -1
  9. package/dist/api/audits/index.js.map +1 -1
  10. package/dist/api/files/index.browser.js +1 -0
  11. package/dist/api/files/index.browser.js.map +1 -1
  12. package/dist/api/files/index.d.ts +193 -166
  13. package/dist/api/files/index.d.ts.map +1 -1
  14. package/dist/api/files/index.js +52 -0
  15. package/dist/api/files/index.js.map +1 -1
  16. package/dist/api/jobs/index.browser.js +40 -14
  17. package/dist/api/jobs/index.browser.js.map +1 -1
  18. package/dist/api/jobs/index.d.ts +639 -333
  19. package/dist/api/jobs/index.d.ts.map +1 -1
  20. package/dist/api/jobs/index.js +495 -162
  21. package/dist/api/jobs/index.js.map +1 -1
  22. package/dist/api/keys/index.d.ts +222 -188
  23. package/dist/api/keys/index.d.ts.map +1 -1
  24. package/dist/api/keys/index.js +54 -0
  25. package/dist/api/keys/index.js.map +1 -1
  26. package/dist/api/notifications/index.d.ts +265 -236
  27. package/dist/api/notifications/index.d.ts.map +1 -1
  28. package/dist/api/notifications/index.js +55 -13
  29. package/dist/api/notifications/index.js.map +1 -1
  30. package/dist/api/organizations/index.d.ts +100 -97
  31. package/dist/api/organizations/index.d.ts.map +1 -1
  32. package/dist/api/organizations/index.js.map +1 -1
  33. package/dist/api/parameters/index.d.ts +332 -314
  34. package/dist/api/parameters/index.d.ts.map +1 -1
  35. package/dist/api/parameters/index.js +37 -0
  36. package/dist/api/parameters/index.js.map +1 -1
  37. package/dist/api/payments/index.d.ts +431 -376
  38. package/dist/api/payments/index.d.ts.map +1 -1
  39. package/dist/api/payments/index.js +202 -87
  40. package/dist/api/payments/index.js.map +1 -1
  41. package/dist/api/subscriptions/index.d.ts +1695 -0
  42. package/dist/api/subscriptions/index.d.ts.map +1 -0
  43. package/dist/api/subscriptions/index.js +1919 -0
  44. package/dist/api/subscriptions/index.js.map +1 -0
  45. package/dist/api/users/index.d.ts +1001 -844
  46. package/dist/api/users/index.d.ts.map +1 -1
  47. package/dist/api/users/index.js +237 -28
  48. package/dist/api/users/index.js.map +1 -1
  49. package/dist/api/verifications/index.d.ts +123 -122
  50. package/dist/api/verifications/index.d.ts.map +1 -1
  51. package/dist/api/verifications/index.js.map +1 -1
  52. package/dist/batch/index.js.map +1 -1
  53. package/dist/bucket/index.d.ts +21 -2
  54. package/dist/bucket/index.d.ts.map +1 -1
  55. package/dist/bucket/index.js +47 -0
  56. package/dist/bucket/index.js.map +1 -1
  57. package/dist/bucket/index.workerd.js +24 -0
  58. package/dist/bucket/index.workerd.js.map +1 -1
  59. package/dist/cache/core/index.d.ts +134 -7
  60. package/dist/cache/core/index.d.ts.map +1 -1
  61. package/dist/cache/core/index.js +181 -15
  62. package/dist/cache/core/index.js.map +1 -1
  63. package/dist/cache/core/index.workerd.js +181 -15
  64. package/dist/cache/core/index.workerd.js.map +1 -1
  65. package/dist/cache/database/index.d.ts +156 -0
  66. package/dist/cache/database/index.d.ts.map +1 -0
  67. package/dist/cache/database/index.js +266 -0
  68. package/dist/cache/database/index.js.map +1 -0
  69. package/dist/cache/redis/index.d.ts +3 -2
  70. package/dist/cache/redis/index.d.ts.map +1 -1
  71. package/dist/cache/redis/index.js.map +1 -1
  72. package/dist/captcha/index.js.map +1 -1
  73. package/dist/cli/config/index.js.map +1 -1
  74. package/dist/cli/core/index.d.ts +142 -128
  75. package/dist/cli/core/index.d.ts.map +1 -1
  76. package/dist/cli/core/index.js +160 -13
  77. package/dist/cli/core/index.js.map +1 -1
  78. package/dist/cli/devtools/index.d.ts +3 -2
  79. package/dist/cli/devtools/index.d.ts.map +1 -1
  80. package/dist/cli/devtools/index.js.map +1 -1
  81. package/dist/cli/platform/index.d.ts +346 -290
  82. package/dist/cli/platform/index.d.ts.map +1 -1
  83. package/dist/cli/platform/index.js +106 -7
  84. package/dist/cli/platform/index.js.map +1 -1
  85. package/dist/cli/vendor/index.d.ts +12 -11
  86. package/dist/cli/vendor/index.d.ts.map +1 -1
  87. package/dist/cli/vendor/index.js.map +1 -1
  88. package/dist/command/index.d.ts +6 -5
  89. package/dist/command/index.d.ts.map +1 -1
  90. package/dist/command/index.js.map +1 -1
  91. package/dist/core/index.browser.js +1 -1
  92. package/dist/core/index.browser.js.map +1 -1
  93. package/dist/core/index.d.ts +119 -118
  94. package/dist/core/index.d.ts.map +1 -1
  95. package/dist/core/index.js +1 -1
  96. package/dist/core/index.js.map +1 -1
  97. package/dist/core/index.native.js +1 -1
  98. package/dist/core/index.native.js.map +1 -1
  99. package/dist/core/index.workerd.js +1 -1
  100. package/dist/core/index.workerd.js.map +1 -1
  101. package/dist/crypto/index.browser.js.map +1 -1
  102. package/dist/crypto/index.d.ts +3 -2
  103. package/dist/crypto/index.d.ts.map +1 -1
  104. package/dist/crypto/index.js.map +1 -1
  105. package/dist/datetime/index.js.map +1 -1
  106. package/dist/email/brevo/index.js.map +1 -1
  107. package/dist/email/core/index.d.ts +3 -2
  108. package/dist/email/core/index.d.ts.map +1 -1
  109. package/dist/email/core/index.js.map +1 -1
  110. package/dist/email/core/index.workerd.js.map +1 -1
  111. package/dist/email/smtp/index.d.ts +7 -6
  112. package/dist/email/smtp/index.d.ts.map +1 -1
  113. package/dist/email/smtp/index.js.map +1 -1
  114. package/dist/fake/index.js.map +1 -1
  115. package/dist/lock/core/index.d.ts +5 -4
  116. package/dist/lock/core/index.d.ts.map +1 -1
  117. package/dist/lock/core/index.js.map +1 -1
  118. package/dist/lock/redis/index.js.map +1 -1
  119. package/dist/logger/index.d.ts +10 -9
  120. package/dist/logger/index.d.ts.map +1 -1
  121. package/dist/logger/index.js.map +1 -1
  122. package/dist/mcp/index.d.ts +9 -8
  123. package/dist/mcp/index.d.ts.map +1 -1
  124. package/dist/mcp/index.js +1 -1
  125. package/dist/mcp/index.js.map +1 -1
  126. package/dist/orm/core/index.browser.js +9 -3
  127. package/dist/orm/core/index.browser.js.map +1 -1
  128. package/dist/orm/core/index.bun.js +31 -10
  129. package/dist/orm/core/index.bun.js.map +1 -1
  130. package/dist/orm/core/index.d.ts +33 -14
  131. package/dist/orm/core/index.d.ts.map +1 -1
  132. package/dist/orm/core/index.js +31 -10
  133. package/dist/orm/core/index.js.map +1 -1
  134. package/dist/orm/postgres/index.bun.js.map +1 -1
  135. package/dist/orm/postgres/index.d.ts +6 -5
  136. package/dist/orm/postgres/index.d.ts.map +1 -1
  137. package/dist/orm/postgres/index.js.map +1 -1
  138. package/dist/queue/core/index.d.ts +5 -4
  139. package/dist/queue/core/index.d.ts.map +1 -1
  140. package/dist/queue/core/index.js.map +1 -1
  141. package/dist/queue/core/index.workerd.js.map +1 -1
  142. package/dist/queue/redis/index.d.ts +3 -2
  143. package/dist/queue/redis/index.d.ts.map +1 -1
  144. package/dist/queue/redis/index.js.map +1 -1
  145. package/dist/react/auth/index.browser.js.map +1 -1
  146. package/dist/react/auth/index.js.map +1 -1
  147. package/dist/react/core/index.js.map +1 -1
  148. package/dist/react/form/index.d.ts +5 -0
  149. package/dist/react/form/index.d.ts.map +1 -1
  150. package/dist/react/form/index.js +8 -4
  151. package/dist/react/form/index.js.map +1 -1
  152. package/dist/react/head/index.browser.js.map +1 -1
  153. package/dist/react/head/index.js.map +1 -1
  154. package/dist/react/i18n/index.d.ts +2 -1
  155. package/dist/react/i18n/index.d.ts.map +1 -1
  156. package/dist/react/i18n/index.js.map +1 -1
  157. package/dist/react/intro/index.js.map +1 -1
  158. package/dist/react/router/index.browser.js.map +1 -1
  159. package/dist/react/router/index.d.ts +206 -205
  160. package/dist/react/router/index.d.ts.map +1 -1
  161. package/dist/react/router/index.js.map +1 -1
  162. package/dist/react/testing/index.js.map +1 -1
  163. package/dist/react/ui/index.d.ts +11 -11
  164. package/dist/react/ui/index.d.ts.map +1 -1
  165. package/dist/react/ui/index.js.map +1 -1
  166. package/dist/redis/index.bun.js.map +1 -1
  167. package/dist/redis/index.js.map +1 -1
  168. package/dist/retry/index.js.map +1 -1
  169. package/dist/router/index.js.map +1 -1
  170. package/dist/scheduler/index.d.ts +25 -2
  171. package/dist/scheduler/index.d.ts.map +1 -1
  172. package/dist/scheduler/index.js +12 -0
  173. package/dist/scheduler/index.js.map +1 -1
  174. package/dist/scheduler/index.workerd.js +12 -0
  175. package/dist/scheduler/index.workerd.js.map +1 -1
  176. package/dist/security/index.browser.js +29 -1
  177. package/dist/security/index.browser.js.map +1 -1
  178. package/dist/security/index.d.ts +82 -35
  179. package/dist/security/index.d.ts.map +1 -1
  180. package/dist/security/index.js +56 -3
  181. package/dist/security/index.js.map +1 -1
  182. package/dist/server/auth/index.d.ts +163 -158
  183. package/dist/server/auth/index.d.ts.map +1 -1
  184. package/dist/server/auth/index.js +16 -4
  185. package/dist/server/auth/index.js.map +1 -1
  186. package/dist/server/cookies/index.browser.js.map +1 -1
  187. package/dist/server/cookies/index.js.map +1 -1
  188. package/dist/server/core/index.browser.js.map +1 -1
  189. package/dist/server/core/index.d.ts +35 -34
  190. package/dist/server/core/index.d.ts.map +1 -1
  191. package/dist/server/core/index.js.map +1 -1
  192. package/dist/server/cors/index.d.ts +7 -6
  193. package/dist/server/cors/index.d.ts.map +1 -1
  194. package/dist/server/cors/index.js.map +1 -1
  195. package/dist/server/etag/index.js.map +1 -1
  196. package/dist/server/health/index.d.ts +16 -15
  197. package/dist/server/health/index.d.ts.map +1 -1
  198. package/dist/server/health/index.js.map +1 -1
  199. package/dist/server/links/index.browser.js.map +1 -1
  200. package/dist/server/links/index.d.ts +51 -50
  201. package/dist/server/links/index.d.ts.map +1 -1
  202. package/dist/server/links/index.js.map +1 -1
  203. package/dist/server/metrics/index.js.map +1 -1
  204. package/dist/server/proxy/index.js.map +1 -1
  205. package/dist/server/rate-limit/index.d.ts +6 -5
  206. package/dist/server/rate-limit/index.d.ts.map +1 -1
  207. package/dist/server/rate-limit/index.js.map +1 -1
  208. package/dist/server/static/index.js.map +1 -1
  209. package/dist/server/swagger/index.d.ts +2 -1
  210. package/dist/server/swagger/index.d.ts.map +1 -1
  211. package/dist/server/swagger/index.js.map +1 -1
  212. package/dist/sms/index.js.map +1 -1
  213. package/dist/system/index.browser.js.map +1 -1
  214. package/dist/system/index.js.map +1 -1
  215. package/dist/system/index.workerd.js.map +1 -1
  216. package/dist/topic/core/index.js.map +1 -1
  217. package/dist/topic/redis/index.d.ts +3 -2
  218. package/dist/topic/redis/index.d.ts.map +1 -1
  219. package/dist/topic/redis/index.js.map +1 -1
  220. package/package.json +33 -39
  221. package/src/api/audits/controllers/AdminAuditController.ts +29 -0
  222. package/src/api/audits/entities/audits.ts +1 -0
  223. package/src/api/files/controllers/FileController.ts +24 -0
  224. package/src/api/files/entities/files.ts +1 -0
  225. package/src/api/files/services/FileService.ts +41 -0
  226. package/src/api/jobs/__tests__/$job.spec.ts +501 -24
  227. package/src/api/jobs/entities/jobExecutionEntity.ts +4 -3
  228. package/src/api/jobs/index.ts +47 -10
  229. package/src/api/jobs/primitives/$job.ts +22 -9
  230. package/src/api/jobs/providers/DirectJobDispatcher.ts +71 -0
  231. package/src/api/jobs/providers/JobDispatcher.ts +49 -0
  232. package/src/api/jobs/providers/JobProvider.ts +385 -147
  233. package/src/api/jobs/providers/JobQueueProvider.ts +43 -18
  234. package/src/api/jobs/schemas/jobConfigAtom.ts +9 -3
  235. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +11 -0
  236. package/src/api/jobs/schemas/jobRegistrationSchema.ts +4 -2
  237. package/src/api/jobs/services/JobService.ts +21 -11
  238. package/src/api/keys/controllers/AdminApiKeyController.ts +23 -0
  239. package/src/api/keys/entities/apiKeyEntity.ts +1 -0
  240. package/src/api/keys/services/ApiKeyService.ts +42 -0
  241. package/src/api/notifications/__tests__/AlephaApiNotifications.spec.ts +63 -0
  242. package/src/api/notifications/controllers/AdminNotificationController.ts +48 -1
  243. package/src/api/notifications/index.ts +13 -3
  244. package/src/api/notifications/jobs/NotificationJobs.ts +0 -6
  245. package/src/api/parameters/controllers/AdminParameterController.ts +26 -0
  246. package/src/api/parameters/services/ParameterProvider.ts +18 -0
  247. package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
  248. package/src/api/payments/index.ts +3 -0
  249. package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
  250. package/src/api/payments/providers/PaymentProvider.ts +25 -9
  251. package/src/api/payments/services/PaymentService.ts +3 -0
  252. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  253. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  254. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  255. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  256. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  257. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  258. package/src/api/subscriptions/index.ts +133 -0
  259. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  260. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  261. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  262. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  263. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  264. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  265. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  266. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  267. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  268. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  269. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  270. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  271. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  272. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  273. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  274. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  275. package/src/api/subscriptions/services/BillingService.ts +437 -0
  276. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  277. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  278. package/src/api/subscriptions/services/UsageService.ts +118 -0
  279. package/src/api/users/__tests__/Registration-emailMode.spec.ts +203 -0
  280. package/src/api/users/__tests__/UsernameSlugger.spec.ts +138 -0
  281. package/src/api/users/atoms/realmAuthSettingsAtom.ts +41 -3
  282. package/src/api/users/controllers/AdminSessionController.ts +29 -0
  283. package/src/api/users/controllers/AdminUserController.ts +32 -0
  284. package/src/api/users/index.ts +3 -0
  285. package/src/api/users/services/CredentialService.ts +5 -0
  286. package/src/api/users/services/RegistrationService.ts +49 -1
  287. package/src/api/users/services/SessionCrudService.ts +16 -0
  288. package/src/api/users/services/SessionService.ts +17 -59
  289. package/src/api/users/services/UsernameSlugger.ts +195 -0
  290. package/src/bucket/primitives/$bucket.ts +21 -0
  291. package/src/bucket/providers/CloudflareR2Provider.ts +15 -0
  292. package/src/bucket/providers/FileStorageProvider.ts +9 -0
  293. package/src/bucket/providers/LocalFileStorageProvider.ts +14 -0
  294. package/src/bucket/providers/MemoryFileStorageProvider.ts +9 -0
  295. package/src/bucket/providers/NodeS3BucketProvider.ts +35 -0
  296. package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
  297. package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
  298. package/src/cache/core/index.ts +16 -0
  299. package/src/cache/core/primitives/$cache.ts +367 -24
  300. package/src/cache/database/__tests__/DatabaseCacheProvider.behavior.spec.ts +203 -0
  301. package/src/cache/database/__tests__/DatabaseCacheProvider.spec.ts +110 -0
  302. package/src/cache/database/entities/cacheEntries.ts +55 -0
  303. package/src/cache/database/index.ts +36 -0
  304. package/src/cache/database/providers/DatabaseCacheProvider.ts +348 -0
  305. package/src/cli/core/services/ProjectScaffolder.ts +0 -2
  306. package/src/cli/core/tasks/BuildCloudflareTask.ts +33 -3
  307. package/src/cli/core/tasks/BuildSitemapTask.ts +7 -0
  308. package/src/cli/core/tasks/BuildVercelTask.ts +82 -3
  309. package/src/cli/core/templates/agentMd.ts +39 -4
  310. package/src/cli/core/templates/biomeJson.ts +25 -1
  311. package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
  312. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
  313. package/src/cli/platform/__tests__/detectResources.spec.ts +96 -0
  314. package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
  315. package/src/cli/platform/atoms/platformOptions.ts +13 -0
  316. package/src/cli/platform/commands/platform.ts +7 -1
  317. package/src/cli/platform/schemas/platform.ts +1 -0
  318. package/src/cli/platform/services/CloudflareApi.ts +61 -0
  319. package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
  320. package/src/core/__tests__/$module.spec.ts +2 -2
  321. package/src/core/primitives/$module.ts +4 -4
  322. package/src/mcp/providers/McpServerProvider.ts +1 -1
  323. package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
  324. package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
  325. package/src/orm/core/schemas/insertSchema.ts +10 -2
  326. package/src/orm/core/services/Repository.ts +27 -7
  327. package/src/react/form/hooks/useFormState.ts +8 -1
  328. package/src/react/form/index.ts +10 -1
  329. package/src/react/form/services/FormModel.ts +9 -3
  330. package/src/scheduler/index.ts +14 -0
  331. package/src/scheduler/providers/CronProvider.ts +13 -0
  332. package/src/security/atoms/currentTenantAtom.ts +34 -0
  333. package/src/security/index.browser.ts +1 -0
  334. package/src/security/index.ts +12 -1
  335. package/src/security/primitives/$issuer.ts +17 -1
  336. package/src/security/providers/SecurityProvider.ts +37 -0
  337. package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
  338. package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
  339. package/tsconfig.base.json +2 -1
  340. package/dist/react/websocket/index.d.ts +0 -117
  341. package/dist/react/websocket/index.d.ts.map +0 -1
  342. package/dist/react/websocket/index.js +0 -108
  343. package/dist/react/websocket/index.js.map +0 -1
  344. package/dist/websocket/index.browser.js +0 -844
  345. package/dist/websocket/index.browser.js.map +0 -1
  346. package/dist/websocket/index.d.ts +0 -876
  347. package/dist/websocket/index.d.ts.map +0 -1
  348. package/dist/websocket/index.js +0 -1175
  349. package/dist/websocket/index.js.map +0 -1
  350. package/src/react/websocket/hooks/useRoom.tsx +0 -251
  351. package/src/react/websocket/index.ts +0 -7
  352. package/src/websocket/__tests__/$channel.spec.ts +0 -30
  353. package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
  354. package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
  355. package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
  356. package/src/websocket/errors/WebSocketError.ts +0 -34
  357. package/src/websocket/index.browser.ts +0 -25
  358. package/src/websocket/index.shared.ts +0 -8
  359. package/src/websocket/index.ts +0 -85
  360. package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
  361. package/src/websocket/primitives/$channel.ts +0 -131
  362. package/src/websocket/primitives/$websocket.ts +0 -107
  363. package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
  364. package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
  365. package/src/websocket/services/RoomManager.ts +0 -160
  366. package/src/websocket/services/WebSocketClient.ts +0 -642
  367. package/src/websocket/services/WebSocketTopicService.ts +0 -108
@@ -1,42 +1,67 @@
1
- import { $inject, t } from "alepha";
1
+ import { $inject, Alepha, t } from "alepha";
2
2
  import { $queue } from "alepha/queue";
3
+ import { JobDispatcher } from "./JobDispatcher.ts";
3
4
  import { JobProvider } from "./JobProvider.ts";
4
5
 
5
6
  /**
6
- * Plumbs outbox-style dispatch through `AlephaQueue`.
7
+ * Queue-backed `JobDispatcher` registered by `AlephaApiJobsQueue`.
7
8
  *
8
- * Registered only when the app imports `AlephaApiJobsQueue`. Sets
9
- * `JobProvider.queueDispatch` eagerly at instantiation so queue-mode jobs
10
- * can dispatch regardless of start-hook ordering.
9
+ * Extends {@link JobDispatcher} and substitutes the default
10
+ * `DirectJobDispatcher` so that `$job.push()` is delivered through
11
+ * `AlephaQueue` (e.g. Cloudflare Queues, Redis, in-memory) instead of
12
+ * being processed in-process.
13
+ *
14
+ * The class is also kept as a `JobQueueProvider` export name for backwards
15
+ * compatibility — it has always been the queue path's entry point.
11
16
  */
12
- export class JobQueueProvider {
13
- protected readonly jobProvider = $inject(JobProvider);
17
+ export class JobQueueProvider extends JobDispatcher {
18
+ public readonly kind = "queue" as const;
19
+ protected readonly alepha = $inject(Alepha);
20
+
21
+ // Lazy to avoid the JobProvider ↔ JobDispatcher injection cycle
22
+ // (JobProvider injects JobDispatcher; the queue consumer needs
23
+ // JobProvider to process). Resolved at message-receive time.
24
+ protected jobProviderRef?: JobProvider;
25
+ protected getJobProvider(): JobProvider {
26
+ if (!this.jobProviderRef) {
27
+ this.jobProviderRef = this.alepha.inject(JobProvider);
28
+ }
29
+ return this.jobProviderRef;
30
+ }
14
31
 
15
32
  protected readonly queue = $queue({
16
33
  name: "api:jobs:dispatch",
17
34
  schema: t.object({ jobName: t.text(), executionId: t.text() }),
18
35
  handler: async (msg) => {
19
- await this.jobProvider.processExecution(
36
+ await this.getJobProvider().processExecution(
20
37
  msg.payload.jobName,
21
38
  msg.payload.executionId,
22
39
  );
23
40
  },
24
41
  });
25
42
 
26
- constructor() {
27
- // Install the dispatcher immediately — before any start hook runs,
28
- // so JobProvider can validate presence and queue-mode push works
29
- // from any lifecycle point.
30
- this.wireDispatcher();
43
+ public async dispatch(jobName: string, executionId: string): Promise<void> {
44
+ await this.queue.push({ jobName, executionId });
31
45
  }
32
46
 
33
- protected wireDispatcher(): void {
34
- // `$inject` is resolved by the time constructor runs; assignment is safe.
35
- this.jobProvider.queueDispatch = (jobName, executionId) =>
36
- this.push(jobName, executionId);
47
+ /**
48
+ * Fan-out to a single variadic `queue.push(...payloads)` call so the
49
+ * underlying queue provider can batch the network round-trips when it
50
+ * supports it (Cloudflare Queues, Redis pipelines).
51
+ */
52
+ public override async dispatchMany(
53
+ items: Array<{ jobName: string; executionId: string }>,
54
+ ): Promise<void> {
55
+ if (items.length === 0) return;
56
+ await this.queue.push(...items);
37
57
  }
38
58
 
59
+ /**
60
+ * Backwards-compatible alias for {@link dispatch}. Older code paths called
61
+ * `JobQueueProvider.push(jobName, executionId)` directly; new code should
62
+ * go through the `JobDispatcher.dispatch` API.
63
+ */
39
64
  public async push(jobName: string, executionId: string): Promise<void> {
40
- await this.queue.push({ jobName, executionId });
65
+ return this.dispatch(jobName, executionId);
41
66
  }
42
67
  }
@@ -4,8 +4,13 @@ export const jobConfig = $atom({
4
4
  name: "alepha.jobs",
5
5
  description: "Configuration for the $job primitive.",
6
6
  schema: t.object({
7
- sweepInterval: t.integer({
8
- description: "Sweep cron interval in milliseconds.",
7
+ sweepCron: t.text({
8
+ description:
9
+ "Cron expression for the sweep tick. Must be minute-granular at minimum (cron resolution). On Cloudflare Workers this expression is emitted into wrangler.jsonc by the build.",
10
+ }),
11
+ trimCron: t.text({
12
+ description:
13
+ "Cron expression for the ring-buffer trim tick (per-job keepLastSuccess/keepLastError enforcement). Decoupled from `sweepCron` because trim is bounded by job execution rate, not retry latency — running it every sweep is wasted work for most apps.",
9
14
  }),
10
15
  staleThreshold: t.integer({
11
16
  description: "Pending age (ms) before the sweep re-dispatches it.",
@@ -29,7 +34,8 @@ export const jobConfig = $atom({
29
34
  }),
30
35
  }),
31
36
  default: {
32
- sweepInterval: 300_000,
37
+ sweepCron: "*/5 * * * *",
38
+ trimCron: "0 * * * *",
33
39
  staleThreshold: 300_000,
34
40
  runTimeout: 1_800_000,
35
41
  keepLastSuccess: 10,
@@ -1,9 +1,20 @@
1
1
  import { type Static, t } from "alepha";
2
2
  import { jobExecutionEntity } from "../entities/jobExecutionEntity.ts";
3
3
 
4
+ /**
5
+ * Public-facing schema for a job execution row.
6
+ *
7
+ * Diverges from the raw entity in two places, both for API ergonomics:
8
+ *
9
+ * - `priority` is exposed as the **string enum** (`critical`/`high`/...)
10
+ * instead of the numeric value used internally for SQL ordering. The
11
+ * `JobService` is responsible for the int → string transform.
12
+ * - `can` derives the available admin actions from the row's status.
13
+ */
4
14
  export const jobExecutionResourceSchema = t.extend(
5
15
  jobExecutionEntity.schema,
6
16
  {
17
+ priority: t.enum(["critical", "high", "normal", "low"]),
7
18
  can: t.object({
8
19
  retry: t.boolean(),
9
20
  cancel: t.boolean(),
@@ -3,14 +3,16 @@ import { type Static, t } from "alepha";
3
3
  export const jobRegistrationSchema = t.object({
4
4
  name: t.text(),
5
5
  description: t.optional(t.text()),
6
- type: t.enum(["cron", "queue"]),
6
+ type: t.enum(["cron", "queue", "direct"], {
7
+ description:
8
+ "Effective runtime mode. 'cron' = scheduled. 'queue' = push-driven, dispatched via AlephaApiJobsQueue. 'direct' = push-driven, processed in-process (no queue infrastructure loaded), with the sweep as the safety net.",
9
+ }),
7
10
  priority: t.enum(["critical", "high", "normal", "low"]),
8
11
  cron: t.optional(t.text()),
9
12
  timeout: t.optional(t.text()),
10
13
  retry: t.optional(
11
14
  t.object({
12
15
  retries: t.integer(),
13
- hasBackoff: t.boolean(),
14
16
  }),
15
17
  ),
16
18
  recent: t.object({
@@ -5,8 +5,9 @@ import { NotFoundError } from "alepha/server";
5
5
  import { jobExecutionEntity } from "../entities/jobExecutionEntity.ts";
6
6
  import { $job } from "../primitives/$job.ts";
7
7
  import type { JobTriggerContext } from "../providers/JobProvider.ts";
8
- import { JobProvider } from "../providers/JobProvider.ts";
8
+ import { JobProvider, PRIORITY_REVERSE } from "../providers/JobProvider.ts";
9
9
  import type { JobExecutionQuery } from "../schemas/jobExecutionQuerySchema.ts";
10
+ import type { JobExecutionResource } from "../schemas/jobExecutionResourceSchema.ts";
10
11
  import type { JobRegistration } from "../schemas/jobRegistrationSchema.ts";
11
12
 
12
13
  /**
@@ -31,6 +32,22 @@ export class JobService {
31
32
  };
32
33
  }
33
34
 
35
+ /**
36
+ * Convert the int-priority storage column into the public enum string.
37
+ * The cast through `unknown` skips TypeScript's structural check between
38
+ * the entity-level row (`priority: number`) and the resource schema
39
+ * (`priority: enum`); the runtime values are correct.
40
+ */
41
+ protected toResource<T extends { priority: number; status: string }>(
42
+ row: T,
43
+ ): JobExecutionResource {
44
+ return {
45
+ ...row,
46
+ priority: PRIORITY_REVERSE[row.priority] ?? "normal",
47
+ can: this.computeCan(row.status),
48
+ } as unknown as JobExecutionResource;
49
+ }
50
+
34
51
  /**
35
52
  * List every registered job with recent ok/error counts and lastRun.
36
53
  * One aggregate query covers all jobs.
@@ -87,14 +104,13 @@ export class JobService {
87
104
  result.push({
88
105
  name,
89
106
  description: opts.description,
90
- type: reg.type,
107
+ type: this.jobProvider.effectiveMode(name),
91
108
  cron: opts.cron,
92
109
  priority: (opts.priority ?? "normal") as JobRegistration["priority"],
93
110
  timeout: opts.timeout ? String(opts.timeout) : undefined,
94
111
  retry: opts.retry
95
112
  ? {
96
113
  retries: opts.retry.retries,
97
- hasBackoff: Boolean(opts.retry.backoff),
98
114
  }
99
115
  : undefined,
100
116
  recent: counts,
@@ -121,10 +137,7 @@ export class JobService {
121
137
  orderBy: { column: "startedAt", direction: "desc" },
122
138
  limit: query.limit ?? 20,
123
139
  });
124
- return rows.map((row) => ({
125
- ...row,
126
- can: this.computeCan(row.status),
127
- }));
140
+ return rows.map((row) => this.toResource(row));
128
141
  }
129
142
 
130
143
  /**
@@ -135,10 +148,7 @@ export class JobService {
135
148
  if (!execution) {
136
149
  throw new NotFoundError(`Execution not found: ${id}`);
137
150
  }
138
- return {
139
- ...execution,
140
- can: this.computeCan(execution.status),
141
- };
151
+ return this.toResource(execution);
142
152
  }
143
153
 
144
154
  /**
@@ -73,4 +73,27 @@ export class AdminApiKeyController {
73
73
  return { ok: true, id: params.id };
74
74
  },
75
75
  });
76
+
77
+ /**
78
+ * Revoke many API keys in one request.
79
+ */
80
+ public readonly revokeApiKeys = $action({
81
+ method: "POST",
82
+ path: `${this.url}/revoke`,
83
+ group: this.group,
84
+ use: [$secure({ permissions: ["admin:api-key:delete"] })],
85
+ description: "Revoke many API keys",
86
+ schema: {
87
+ body: t.object({
88
+ ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),
89
+ }),
90
+ response: t.object({
91
+ revoked: t.array(t.uuid()),
92
+ }),
93
+ },
94
+ handler: async ({ body }) => {
95
+ const revoked = await this.apiKeyService.revokeManyByAdmin(body.ids);
96
+ return { revoked };
97
+ },
98
+ });
76
99
  }
@@ -7,6 +7,7 @@ export const apiKeyEntity = $entity({
7
7
  id: db.primaryKey(t.uuid()),
8
8
  createdAt: db.createdAt(),
9
9
  updatedAt: db.updatedAt(),
10
+ organizationId: db.organization(),
10
11
 
11
12
  // Owner
12
13
  userId: t.uuid(),
@@ -16,8 +16,21 @@ export class ApiKeyService {
16
16
 
17
17
  /**
18
18
  * Cache validated API keys for 15 minutes.
19
+ *
20
+ * Pinned to per-isolate memory:
21
+ * - The cache replaces a single indexed SELECT on `api_keys`. Routing it
22
+ * through a distributed K/V (KV/Redis) buys little — the SELECT is
23
+ * already cheap — and pinning to DB would actively trade one SQL read
24
+ * for another.
25
+ * - Cold-start gives a fresh DB read on every new isolate, which is
26
+ * *better* for revocation visibility than a distributed cache that
27
+ * keeps serving stale entries until its own TTL.
28
+ * - Avoids provisioning KV/Redis just for this one cache. Users who need
29
+ * cross-isolate sharing for high-throughput API auth can override
30
+ * globally via `alepha.with({ provide: CacheProvider, use: ... })`.
19
31
  */
20
32
  protected readonly validationCache = $cache<ApiKeyEntity | null, [string]>({
33
+ provider: "memory",
21
34
  name: "api:keys:validation",
22
35
  ttl: [15, "minutes"],
23
36
  });
@@ -180,6 +193,35 @@ export class ApiKeyService {
180
193
  });
181
194
  }
182
195
 
196
+ /**
197
+ * Revoke many API keys in one repository call (admin only). Already-revoked
198
+ * keys are silently skipped. Returns the ids that were actually revoked.
199
+ */
200
+ public async revokeManyByAdmin(ids: string[]): Promise<string[]> {
201
+ if (ids.length === 0) return [];
202
+
203
+ const keys = await this.repo.findMany({
204
+ where: { id: { inArray: ids } },
205
+ columns: ["id", "tokenHash", "revokedAt"],
206
+ });
207
+ const toRevoke = keys.filter((k) => !k.revokedAt);
208
+ if (toRevoke.length === 0) return [];
209
+
210
+ await Promise.all(
211
+ toRevoke.map((k) => this.validationCache.invalidate(k.tokenHash)),
212
+ );
213
+
214
+ await this.repo.updateMany(
215
+ { id: { inArray: toRevoke.map((k) => k.id) } },
216
+ {
217
+ revokedAt: this.dateTimeProvider.now().toISOString(),
218
+ },
219
+ );
220
+
221
+ this.log.info("API keys revoked by admin", { count: toRevoke.length });
222
+ return toRevoke.map((k) => k.id);
223
+ }
224
+
183
225
  // -------------------------------------------------------------------------
184
226
  // User Operations
185
227
  // -------------------------------------------------------------------------
@@ -0,0 +1,63 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { AlephaApiJobs, JobProvider } from "alepha/api/jobs";
3
+ import { AlephaEmail, MemoryEmailProvider } from "alepha/email";
4
+ import { AlephaOrmPostgres } from "alepha/orm/postgres";
5
+ import { AlephaSms } from "alepha/sms";
6
+ import { describe, it } from "vitest";
7
+ import {
8
+ $notification,
9
+ AlephaApiNotifications,
10
+ NotificationJobs,
11
+ } from "../index.ts";
12
+
13
+ /**
14
+ * The notifications module historically forced `AlephaApiJobsQueue`. After
15
+ * the direct-mode change it must boot without any queue infrastructure and
16
+ * deliver via direct mode (push → process in-process → row removed on success).
17
+ */
18
+ describe("AlephaApiNotifications — runs without AlephaApiJobsQueue", () => {
19
+ it("module starts in direct mode and delivers an email notification", async ({
20
+ expect,
21
+ }) => {
22
+ const alepha = Alepha.create()
23
+ .with(AlephaOrmPostgres)
24
+ .with(AlephaEmail)
25
+ .with(AlephaSms)
26
+ .with(AlephaApiJobs)
27
+ .with(AlephaApiNotifications);
28
+
29
+ class Templates {
30
+ readonly welcome = $notification({
31
+ name: "welcome-email",
32
+ schema: t.object({ name: t.text() }),
33
+ email: {
34
+ subject: "Welcome",
35
+ body: (vars) => `Hello ${vars.name}`,
36
+ },
37
+ });
38
+ }
39
+
40
+ const templates = alepha.inject(Templates);
41
+ await alepha.start();
42
+
43
+ // Confirm the underlying $job runs in direct mode (no queue loaded).
44
+ const jobs = alepha.inject(JobProvider);
45
+ const sendName = alepha.inject(NotificationJobs).sendNotification.name;
46
+ expect(jobs.effectiveMode(sendName)).toBe("direct");
47
+
48
+ await templates.welcome.push({
49
+ contact: "alice@example.com",
50
+ variables: { name: "Alice" },
51
+ });
52
+
53
+ const mail = alepha.inject(MemoryEmailProvider);
54
+ const deadline = Date.now() + 1500;
55
+ while (mail.records.length === 0 && Date.now() < deadline) {
56
+ await new Promise((r) => setTimeout(r, 25));
57
+ }
58
+ expect(mail.records).toHaveLength(1);
59
+ expect(mail.records[0].to).toBe("alice@example.com");
60
+ expect(mail.records[0].subject).toBe("Welcome");
61
+ expect(mail.records[0].body).toBe("Hello Alice");
62
+ });
63
+ });
@@ -2,7 +2,7 @@ import { $inject, t } from "alepha";
2
2
  import { jobExecutionEntity } from "alepha/api/jobs";
3
3
  import { $repository } from "alepha/orm";
4
4
  import { $secure } from "alepha/security";
5
- import { $action, NotFoundError } from "alepha/server";
5
+ import { $action, NotFoundError, okSchema } from "alepha/server";
6
6
  import { NotificationJobs } from "../jobs/NotificationJobs.ts";
7
7
  import { notificationDetailResourceSchema } from "../schemas/notificationDetailResourceSchema.ts";
8
8
  import { notificationQuerySchema } from "../schemas/notificationQuerySchema.ts";
@@ -61,6 +61,53 @@ export class AdminNotificationController {
61
61
  },
62
62
  });
63
63
 
64
+ public readonly deleteNotification = $action({
65
+ method: "DELETE",
66
+ path: `${this.url}/:id`,
67
+ group: this.group,
68
+ use: [$secure({ permissions: ["admin:notification:delete"] })],
69
+ description: "Delete a notification record",
70
+ schema: {
71
+ params: t.object({
72
+ id: t.uuid(),
73
+ }),
74
+ response: okSchema,
75
+ },
76
+ handler: async ({ params }) => {
77
+ const exec = await this.executions.findById(params.id);
78
+ if (!exec || exec.jobName !== this.jobName) {
79
+ throw new NotFoundError(`Notification not found: ${params.id}`);
80
+ }
81
+ await this.executions.deleteById(params.id);
82
+ return { ok: true, id: params.id };
83
+ },
84
+ });
85
+
86
+ public readonly deleteNotifications = $action({
87
+ method: "POST",
88
+ path: `${this.url}/delete`,
89
+ group: this.group,
90
+ use: [$secure({ permissions: ["admin:notification:delete"] })],
91
+ description: "Delete many notification records in one call",
92
+ schema: {
93
+ body: t.object({
94
+ ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),
95
+ }),
96
+ response: t.object({
97
+ deleted: t.array(t.uuid()),
98
+ }),
99
+ },
100
+ handler: async ({ body }) => {
101
+ // Constrain to this job's executions so an admin can't delete arbitrary
102
+ // job rows through this endpoint.
103
+ const deleted = await this.executions.deleteMany({
104
+ id: { inArray: body.ids },
105
+ jobName: { eq: this.jobName },
106
+ });
107
+ return { deleted: deleted.map(String) };
108
+ },
109
+ });
110
+
64
111
  protected toResource(exec: Record<string, unknown>) {
65
112
  const payload = (exec.payload ?? {}) as Record<string, unknown>;
66
113
  return {
@@ -1,5 +1,5 @@
1
1
  import { $module } from "alepha";
2
- import { AlephaApiJobsQueue } from "alepha/api/jobs";
2
+ import { AlephaApiJobs } from "alepha/api/jobs";
3
3
  import { AlephaApiParameters } from "alepha/api/parameters";
4
4
  import { AdminNotificationController } from "./controllers/AdminNotificationController.ts";
5
5
  import { NotificationJobs } from "./jobs/NotificationJobs.ts";
@@ -26,15 +26,25 @@ export * from "./services/NotificationSenderService.ts";
26
26
  *
27
27
  * **Features:**
28
28
  * - Notification definitions (email/SMS templates)
29
- * - Queue-based delivery with retry and audit trail (`record: "all"` + no ring buffer trim)
29
+ * - Delivery via `$job` with retry and audit trail (`record: "all"` + no ring buffer trim)
30
30
  * - Runtime-editable retention window via `$parameter` — purge cron respects it live
31
31
  * - Admin API for inspecting sent notifications
32
32
  *
33
+ * **Delivery mode** is decided at runtime by the `$job` system:
34
+ * - If your app loads `AlephaApiJobsQueue` (and thus `AlephaQueue`), notifications
35
+ * go through the queue (best for high-volume systems).
36
+ * - Otherwise, notifications run in **direct** mode: pushed to the outbox table
37
+ * and processed in the same process right after the HTTP response is returned.
38
+ * The reconciliation sweep is the safety net for crashes / retries.
39
+ *
40
+ * Direct mode is the recommended default for small / cheap deployments
41
+ * (Cloudflare Workers, single-instance Node) — no queue infrastructure required.
42
+ *
33
43
  * @module alepha.api.notifications
34
44
  */
35
45
  export const AlephaApiNotifications = $module({
36
46
  name: "alepha.api.notifications",
37
- imports: [AlephaApiJobsQueue, AlephaApiParameters],
47
+ imports: [AlephaApiJobs, AlephaApiParameters],
38
48
  primitives: [$notification],
39
49
  services: [
40
50
  NotificationSenderService,
@@ -55,12 +55,6 @@ export class NotificationJobs {
55
55
  schema: notificationPayloadSchema,
56
56
  retry: {
57
57
  retries: 3,
58
- backoff: {
59
- initial: [5, "seconds"],
60
- factor: 4,
61
- max: [10, "minutes"],
62
- jitter: true,
63
- },
64
58
  },
65
59
  timeout: [30, "seconds"],
66
60
  record: "all",
@@ -267,4 +267,30 @@ export class AdminParameterController {
267
267
  return { ok: true };
268
268
  },
269
269
  });
270
+
271
+ /**
272
+ * Delete many parameters (all versions of each) in one request.
273
+ */
274
+ deleteParameters = $action({
275
+ group: this.group,
276
+ use: [$secure({ permissions: ["admin:parameter:delete"] })],
277
+ description: "Delete all versions of many parameters by name.",
278
+ path: "/parameters/delete",
279
+ method: "POST",
280
+ schema: {
281
+ body: t.object({
282
+ names: t.array(t.string({ minLength: 1 }), {
283
+ minItems: 1,
284
+ maxItems: 1000,
285
+ }),
286
+ }),
287
+ response: t.object({
288
+ deleted: t.array(t.string()),
289
+ }),
290
+ },
291
+ handler: async ({ body }) => {
292
+ const deleted = await this.provider.deleteMany(body.names);
293
+ return { deleted };
294
+ },
295
+ });
270
296
  }
@@ -486,6 +486,24 @@ export class ParameterProvider {
486
486
  this.log.info("Parameter deleted", { name });
487
487
  }
488
488
 
489
+ /**
490
+ * Delete all versions of many parameters by name in one repository call.
491
+ */
492
+ public async deleteMany(names: string[]): Promise<string[]> {
493
+ if (names.length === 0) return [];
494
+ await this.repo.deleteMany({ name: { inArray: names } });
495
+ for (const name of names) {
496
+ this.cachedCurrent.delete(name);
497
+ this.cachedNext.delete(name);
498
+ this.loaded.delete(name);
499
+ this.loadPromises.delete(name);
500
+ this.loadGeneration.delete(name);
501
+ this.migrationChecked.delete(name);
502
+ }
503
+ this.log.info("Parameters deleted", { count: names.length });
504
+ return names;
505
+ }
506
+
489
507
  /**
490
508
  * Get a specific version of a parameter.
491
509
  */