alepha 0.20.6 → 0.20.8

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 (243) 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 +370 -355
  7. package/dist/api/audits/index.d.ts.map +1 -1
  8. package/dist/api/audits/index.js +1 -0
  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 +179 -170
  13. package/dist/api/files/index.d.ts.map +1 -1
  14. package/dist/api/files/index.js +1 -0
  15. package/dist/api/files/index.js.map +1 -1
  16. package/dist/api/jobs/index.browser.js +7 -0
  17. package/dist/api/jobs/index.browser.js.map +1 -1
  18. package/dist/api/jobs/index.d.ts +259 -250
  19. package/dist/api/jobs/index.d.ts.map +1 -1
  20. package/dist/api/jobs/index.js +21 -3
  21. package/dist/api/jobs/index.js.map +1 -1
  22. package/dist/api/keys/index.d.ts +198 -192
  23. package/dist/api/keys/index.d.ts.map +1 -1
  24. package/dist/api/keys/index.js +1 -0
  25. package/dist/api/keys/index.js.map +1 -1
  26. package/dist/api/notifications/index.d.ts +246 -245
  27. package/dist/api/notifications/index.d.ts.map +1 -1
  28. package/dist/api/organizations/index.d.ts +100 -97
  29. package/dist/api/organizations/index.d.ts.map +1 -1
  30. package/dist/api/parameters/index.d.ts +323 -320
  31. package/dist/api/parameters/index.d.ts.map +1 -1
  32. package/dist/api/payments/index.d.ts +431 -376
  33. package/dist/api/payments/index.d.ts.map +1 -1
  34. package/dist/api/payments/index.js +202 -87
  35. package/dist/api/payments/index.js.map +1 -1
  36. package/dist/api/subscriptions/index.d.ts +1695 -0
  37. package/dist/api/subscriptions/index.d.ts.map +1 -0
  38. package/dist/api/subscriptions/index.js +1919 -0
  39. package/dist/api/subscriptions/index.js.map +1 -0
  40. package/dist/api/users/index.d.ts +857 -841
  41. package/dist/api/users/index.d.ts.map +1 -1
  42. package/dist/api/verifications/index.d.ts +128 -127
  43. package/dist/api/verifications/index.d.ts.map +1 -1
  44. package/dist/bucket/index.d.ts +3 -2
  45. package/dist/bucket/index.d.ts.map +1 -1
  46. package/dist/cache/core/index.d.ts +114 -4
  47. package/dist/cache/core/index.d.ts.map +1 -1
  48. package/dist/cache/core/index.js +181 -15
  49. package/dist/cache/core/index.js.map +1 -1
  50. package/dist/cache/core/index.workerd.js +181 -15
  51. package/dist/cache/core/index.workerd.js.map +1 -1
  52. package/dist/cache/database/index.d.ts +20 -19
  53. package/dist/cache/database/index.d.ts.map +1 -1
  54. package/dist/cache/redis/index.d.ts +3 -2
  55. package/dist/cache/redis/index.d.ts.map +1 -1
  56. package/dist/cli/core/index.d.ts +116 -132
  57. package/dist/cli/core/index.d.ts.map +1 -1
  58. package/dist/cli/core/index.js +75 -7
  59. package/dist/cli/core/index.js.map +1 -1
  60. package/dist/cli/devtools/index.d.ts +3 -2
  61. package/dist/cli/devtools/index.d.ts.map +1 -1
  62. package/dist/cli/platform/index.d.ts +346 -290
  63. package/dist/cli/platform/index.d.ts.map +1 -1
  64. package/dist/cli/platform/index.js +105 -6
  65. package/dist/cli/platform/index.js.map +1 -1
  66. package/dist/cli/vendor/index.d.ts +12 -11
  67. package/dist/cli/vendor/index.d.ts.map +1 -1
  68. package/dist/command/index.d.ts +5 -4
  69. package/dist/command/index.d.ts.map +1 -1
  70. package/dist/core/index.browser.js +1 -1
  71. package/dist/core/index.browser.js.map +1 -1
  72. package/dist/core/index.d.ts +119 -118
  73. package/dist/core/index.d.ts.map +1 -1
  74. package/dist/core/index.js +1 -1
  75. package/dist/core/index.js.map +1 -1
  76. package/dist/core/index.native.js +1 -1
  77. package/dist/core/index.native.js.map +1 -1
  78. package/dist/core/index.workerd.js +1 -1
  79. package/dist/core/index.workerd.js.map +1 -1
  80. package/dist/crypto/index.d.ts +3 -2
  81. package/dist/crypto/index.d.ts.map +1 -1
  82. package/dist/email/core/index.d.ts +3 -2
  83. package/dist/email/core/index.d.ts.map +1 -1
  84. package/dist/email/smtp/index.d.ts +7 -6
  85. package/dist/email/smtp/index.d.ts.map +1 -1
  86. package/dist/lock/core/index.d.ts +5 -4
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/logger/index.d.ts +10 -9
  89. package/dist/logger/index.d.ts.map +1 -1
  90. package/dist/mcp/index.d.ts +9 -8
  91. package/dist/mcp/index.d.ts.map +1 -1
  92. package/dist/mcp/index.js +1 -1
  93. package/dist/mcp/index.js.map +1 -1
  94. package/dist/orm/core/index.browser.js +9 -3
  95. package/dist/orm/core/index.browser.js.map +1 -1
  96. package/dist/orm/core/index.bun.js +31 -10
  97. package/dist/orm/core/index.bun.js.map +1 -1
  98. package/dist/orm/core/index.d.ts +33 -14
  99. package/dist/orm/core/index.d.ts.map +1 -1
  100. package/dist/orm/core/index.js +31 -10
  101. package/dist/orm/core/index.js.map +1 -1
  102. package/dist/orm/postgres/index.d.ts +6 -5
  103. package/dist/orm/postgres/index.d.ts.map +1 -1
  104. package/dist/queue/core/index.d.ts +5 -4
  105. package/dist/queue/core/index.d.ts.map +1 -1
  106. package/dist/queue/redis/index.d.ts +3 -2
  107. package/dist/queue/redis/index.d.ts.map +1 -1
  108. package/dist/react/form/index.d.ts +5 -0
  109. package/dist/react/form/index.d.ts.map +1 -1
  110. package/dist/react/form/index.js +6 -4
  111. package/dist/react/form/index.js.map +1 -1
  112. package/dist/react/i18n/index.d.ts +2 -1
  113. package/dist/react/i18n/index.d.ts.map +1 -1
  114. package/dist/react/router/index.d.ts +206 -205
  115. package/dist/react/router/index.d.ts.map +1 -1
  116. package/dist/react/ui/index.d.ts +11 -11
  117. package/dist/react/ui/index.d.ts.map +1 -1
  118. package/dist/scheduler/index.d.ts +3 -2
  119. package/dist/scheduler/index.d.ts.map +1 -1
  120. package/dist/security/index.browser.js +29 -1
  121. package/dist/security/index.browser.js.map +1 -1
  122. package/dist/security/index.d.ts +82 -35
  123. package/dist/security/index.d.ts.map +1 -1
  124. package/dist/security/index.js +56 -3
  125. package/dist/security/index.js.map +1 -1
  126. package/dist/server/auth/index.d.ts +163 -158
  127. package/dist/server/auth/index.d.ts.map +1 -1
  128. package/dist/server/auth/index.js +16 -4
  129. package/dist/server/auth/index.js.map +1 -1
  130. package/dist/server/core/index.d.ts +35 -34
  131. package/dist/server/core/index.d.ts.map +1 -1
  132. package/dist/server/cors/index.d.ts +7 -6
  133. package/dist/server/cors/index.d.ts.map +1 -1
  134. package/dist/server/health/index.d.ts +16 -15
  135. package/dist/server/health/index.d.ts.map +1 -1
  136. package/dist/server/links/index.d.ts +51 -50
  137. package/dist/server/links/index.d.ts.map +1 -1
  138. package/dist/server/rate-limit/index.d.ts +6 -5
  139. package/dist/server/rate-limit/index.d.ts.map +1 -1
  140. package/dist/server/swagger/index.d.ts +2 -1
  141. package/dist/server/swagger/index.d.ts.map +1 -1
  142. package/dist/topic/redis/index.d.ts +3 -2
  143. package/dist/topic/redis/index.d.ts.map +1 -1
  144. package/package.json +16 -32
  145. package/src/api/audits/entities/audits.ts +1 -0
  146. package/src/api/files/entities/files.ts +1 -0
  147. package/src/api/jobs/__tests__/$job.spec.ts +92 -40
  148. package/src/api/jobs/entities/jobExecutionEntity.ts +1 -0
  149. package/src/api/jobs/providers/JobProvider.ts +20 -5
  150. package/src/api/jobs/schemas/jobConfigAtom.ts +5 -0
  151. package/src/api/keys/entities/apiKeyEntity.ts +1 -0
  152. package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
  153. package/src/api/payments/index.ts +3 -0
  154. package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
  155. package/src/api/payments/providers/PaymentProvider.ts +25 -9
  156. package/src/api/payments/services/PaymentService.ts +3 -0
  157. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  158. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  159. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  160. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  161. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  162. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  163. package/src/api/subscriptions/index.ts +133 -0
  164. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  165. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  166. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  167. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  168. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  169. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  170. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  171. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  172. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  173. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  174. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  175. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  176. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  177. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  178. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  179. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  180. package/src/api/subscriptions/services/BillingService.ts +437 -0
  181. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  182. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  183. package/src/api/subscriptions/services/UsageService.ts +118 -0
  184. package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
  185. package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
  186. package/src/cache/core/index.ts +16 -0
  187. package/src/cache/core/primitives/$cache.ts +347 -21
  188. package/src/cli/core/tasks/BuildCloudflareTask.ts +16 -0
  189. package/src/cli/core/templates/agentMd.ts +39 -4
  190. package/src/cli/core/templates/biomeJson.ts +25 -1
  191. package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
  192. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
  193. package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
  194. package/src/cli/platform/atoms/platformOptions.ts +13 -0
  195. package/src/cli/platform/schemas/platform.ts +1 -0
  196. package/src/cli/platform/services/CloudflareApi.ts +61 -0
  197. package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
  198. package/src/core/__tests__/$module.spec.ts +2 -2
  199. package/src/core/primitives/$module.ts +4 -4
  200. package/src/mcp/providers/McpServerProvider.ts +1 -1
  201. package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
  202. package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
  203. package/src/orm/core/schemas/insertSchema.ts +10 -2
  204. package/src/orm/core/services/Repository.ts +27 -7
  205. package/src/react/form/hooks/useFormState.ts +8 -1
  206. package/src/react/form/index.ts +10 -1
  207. package/src/react/form/services/FormModel.ts +9 -3
  208. package/src/security/atoms/currentTenantAtom.ts +34 -0
  209. package/src/security/index.browser.ts +1 -0
  210. package/src/security/index.ts +12 -1
  211. package/src/security/primitives/$issuer.ts +17 -1
  212. package/src/security/providers/SecurityProvider.ts +37 -0
  213. package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
  214. package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
  215. package/tsconfig.base.json +2 -1
  216. package/dist/react/websocket/index.d.ts +0 -117
  217. package/dist/react/websocket/index.d.ts.map +0 -1
  218. package/dist/react/websocket/index.js +0 -108
  219. package/dist/react/websocket/index.js.map +0 -1
  220. package/dist/websocket/index.browser.js +0 -848
  221. package/dist/websocket/index.browser.js.map +0 -1
  222. package/dist/websocket/index.d.ts +0 -876
  223. package/dist/websocket/index.d.ts.map +0 -1
  224. package/dist/websocket/index.js +0 -1185
  225. package/dist/websocket/index.js.map +0 -1
  226. package/src/react/websocket/hooks/useRoom.tsx +0 -251
  227. package/src/react/websocket/index.ts +0 -7
  228. package/src/websocket/__tests__/$channel.spec.ts +0 -30
  229. package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
  230. package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
  231. package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
  232. package/src/websocket/errors/WebSocketError.ts +0 -34
  233. package/src/websocket/index.browser.ts +0 -25
  234. package/src/websocket/index.shared.ts +0 -8
  235. package/src/websocket/index.ts +0 -85
  236. package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
  237. package/src/websocket/primitives/$channel.ts +0 -131
  238. package/src/websocket/primitives/$websocket.ts +0 -107
  239. package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
  240. package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
  241. package/src/websocket/services/RoomManager.ts +0 -160
  242. package/src/websocket/services/WebSocketClient.ts +0 -642
  243. package/src/websocket/services/WebSocketTopicService.ts +0 -108
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutionEntity.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/jobRegistrationSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/schemas/jobConfigAtom.ts","../../../src/api/jobs/providers/JobDispatcher.ts","../../../src/api/jobs/providers/DirectJobDispatcher.ts","../../../src/api/jobs/providers/JobQueueProvider.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/AdminJobController.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const jobExecutionQuerySchema = t.object({\n status: t.optional(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n ),\n limit: t.optional(t.integer({ minimum: 1, maximum: 200, default: 20 })),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Job execution record.\n *\n * Stores durable state for queue-mode jobs (outbox pattern) and error records\n * for cron-mode jobs. Successful executions are trimmed by the sweep to keep\n * the last N rows per job (configurable via `jobConfig.keepLastSuccess`).\n *\n * Status transitions:\n * - queue push → pending (or `scheduled` if `delay`/`scheduledAt` was given)\n * - worker claim → running\n * - success → ok (or row deleted, depending on `record` and `keepLastSuccess`)\n * - terminal failure → error\n * - retryable failure → scheduled (with scheduledAt = now; sweep picks it up)\n * - delay → scheduled (with scheduledAt = now + delay)\n * - sweep picks due ones → pending\n * - cancel → cancelled\n */\nexport const jobExecutionEntity = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n jobName: t.text(),\n key: t.optional(t.nullable(t.text())),\n\n status: db.default(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n \"pending\",\n ),\n priority: db.default(t.integer({ minimum: 0, maximum: 3 }), 2),\n\n attempt: db.default(t.integer(), 0),\n maxAttempts: db.default(t.integer(), 1),\n\n payload: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(t.datetime()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n\n error: t.optional(t.text()),\n logs: t.optional(t.array(logEntrySchema)),\n\n triggeredBy: t.optional(t.text()),\n triggeredByName: t.optional(t.text()),\n cancelledBy: t.optional(t.text()),\n cancelledByName: t.optional(t.text()),\n }),\n indexes: [\n { columns: [\"jobName\", \"status\", \"scheduledAt\"] },\n { columns: [\"jobName\", \"startedAt\"] },\n { columns: [\"jobName\", \"key\"], unique: true },\n ],\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;\n\nexport type JobStatus =\n | \"pending\"\n | \"running\"\n | \"scheduled\"\n | \"ok\"\n | \"error\"\n | \"cancelled\";\n","import { type Static, t } from \"alepha\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\n\n/**\n * Public-facing schema for a job execution row.\n *\n * Diverges from the raw entity in two places, both for API ergonomics:\n *\n * - `priority` is exposed as the **string enum** (`critical`/`high`/...)\n * instead of the numeric value used internally for SQL ordering. The\n * `JobService` is responsible for the int → string transform.\n * - `can` derives the available admin actions from the row's status.\n */\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutionEntity.schema,\n {\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n can: t.object({\n retry: t.boolean(),\n cancel: t.boolean(),\n }),\n },\n {\n title: \"JobExecutionResource\",\n description: \"A job execution row with derived actions.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const jobRegistrationSchema = t.object({\n name: t.text(),\n description: t.optional(t.text()),\n type: t.enum([\"cron\", \"queue\", \"direct\"], {\n description:\n \"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.\",\n }),\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n cron: t.optional(t.text()),\n timeout: t.optional(t.text()),\n retry: t.optional(\n t.object({\n retries: t.integer(),\n }),\n ),\n recent: t.object({\n ok: t.integer(),\n error: t.integer(),\n lastRun: t.optional(t.datetime()),\n }),\n});\n\nexport type JobRegistration = Static<typeof jobRegistrationSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n payload: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\nexport const jobConfig = $atom({\n name: \"alepha.jobs\",\n description: \"Configuration for the $job primitive.\",\n schema: t.object({\n sweepCron: t.text({\n description:\n \"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.\",\n }),\n staleThreshold: t.integer({\n description: \"Pending age (ms) before the sweep re-dispatches it.\",\n }),\n runTimeout: t.integer({\n description:\n \"Running age (ms) before assumed crash (fallback when no per-job timeout).\",\n }),\n keepLastSuccess: t.integer({\n description:\n \"Max successful rows to keep per job. Set 0 to disable and delete on success.\",\n }),\n keepLastError: t.integer({\n description: \"Max error rows to keep per job.\",\n }),\n logMaxEntries: t.integer({\n description: \"Max log entries captured per execution.\",\n }),\n drainTimeout: t.integer({\n description: \"Max time (ms) to wait for in-flight jobs during shutdown.\",\n }),\n }),\n default: {\n sweepCron: \"*/5 * * * *\",\n staleThreshold: 300_000,\n runTimeout: 1_800_000,\n keepLastSuccess: 10,\n keepLastError: 10,\n logMaxEntries: 100,\n drainTimeout: 30_000,\n },\n});\n\nexport type JobConfig = Static<typeof jobConfig.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [jobConfig.key]: JobConfig;\n }\n}\n","/**\n * Abstract dispatcher for queued/direct job executions.\n *\n * The default implementation, {@link DirectJobDispatcher}, runs the handler\n * in-process after the caller's `push()` returns — fast and dependency-free.\n *\n * `AlephaApiJobsQueue` substitutes this with `JobQueueProvider`, which\n * publishes the executionId to `AlephaQueue` so a worker pool can consume\n * the work asynchronously.\n *\n * Substitute via DI:\n * ```ts\n * Alepha.create()\n * .with({ provide: JobDispatcher, use: MyCustomDispatcher })\n * .with(AlephaApiJobs);\n * ```\n *\n * The `kind` getter is read by the `JobProvider.effectiveMode` accessor\n * and by the admin UI so users can see which dispatcher is currently active.\n */\nexport abstract class JobDispatcher {\n /**\n * Identifier for this dispatcher's effective mode. Reported to the admin\n * UI so operators can see whether `$job` is running in `queue` or\n * `direct` mode.\n */\n public abstract readonly kind: \"queue\" | \"direct\";\n\n /**\n * Hand off a single execution. The caller's `push()` awaits this so the\n * caller can be sure the dispatch has at least been initiated. Long-running\n * work must NOT be awaited here (use background scheduling instead) — this\n * call should return as quickly as possible.\n */\n public abstract dispatch(jobName: string, executionId: string): Promise<void>;\n\n /**\n * Optional batch dispatch. The default implementation loops, but\n * dispatchers backed by a real queue should override this to use the\n * provider's batch send (e.g. Cloudflare Queues `sendBatch`).\n */\n public async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n for (const item of items) {\n await this.dispatch(item.jobName, item.executionId);\n }\n }\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Default `JobDispatcher` for environments without `AlephaApiJobsQueue`.\n *\n * Runs `JobProvider.processExecution` in the background — the caller's\n * `push()` returns immediately while the handler continues to completion\n * in the same process. The DB outbox row is the durability guarantee:\n * if the process dies before the handler finishes, the next sweep tick\n * picks the row up and re-dispatches.\n *\n * **Cloudflare Workers** — when an `executionCtx.waitUntil` is available\n * in the alepha store at `cloudflare.waitUntil`, the dispatch wraps the\n * background promise with `waitUntil` so the runtime keeps the isolate\n * alive past the HTTP response. Without this, the handler would be\n * terminated when the response is returned and only the next sweep\n * (every 5 min by default) would re-dispatch.\n *\n * **Vercel / single-Node** — on long-running runtimes the event loop\n * keeps the promise alive naturally; no special wiring is required.\n */\nexport class DirectJobDispatcher extends JobDispatcher {\n public readonly kind = \"direct\" as const;\n\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n\n // Lazy: resolved on first dispatch to break the JobProvider ↔ Dispatcher\n // injection cycle (JobProvider injects JobDispatcher to dispatch; we need\n // JobProvider to actually run executions).\n protected jobProviderRef?: JobProvider;\n protected getJobProvider(): JobProvider {\n if (!this.jobProviderRef) {\n this.jobProviderRef = this.alepha.inject(JobProvider);\n }\n return this.jobProviderRef;\n }\n\n public async dispatch(jobName: string, executionId: string): Promise<void> {\n const promise = this.getJobProvider()\n .processExecution(jobName, executionId)\n .catch((err) => {\n this.log.warn(\n `Direct execution failed for '${jobName}' (sweep will retry)`,\n err,\n );\n });\n\n // Cloudflare Workers: keep the isolate alive past the HTTP response so\n // the handler actually finishes. Outside CF this read returns undefined\n // and we fall through to plain fire-and-track.\n const waitUntil = this.alepha.store.get(\"cloudflare.waitUntil\") as\n | ((p: Promise<unknown>) => void)\n | undefined;\n if (typeof waitUntil === \"function\") {\n try {\n waitUntil(promise);\n } catch (e) {\n // The runtime may reject waitUntil if called outside a request scope.\n // Promise still runs; just log.\n this.log.debug(\n \"waitUntil rejected — falling back to fire-and-track\",\n e,\n );\n }\n }\n }\n}\n","import { $inject, Alepha, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Queue-backed `JobDispatcher` registered by `AlephaApiJobsQueue`.\n *\n * Extends {@link JobDispatcher} and substitutes the default\n * `DirectJobDispatcher` so that `$job.push()` is delivered through\n * `AlephaQueue` (e.g. Cloudflare Queues, Redis, in-memory) instead of\n * being processed in-process.\n *\n * The class is also kept as a `JobQueueProvider` export name for backwards\n * compatibility — it has always been the queue path's entry point.\n */\nexport class JobQueueProvider extends JobDispatcher {\n public readonly kind = \"queue\" as const;\n protected readonly alepha = $inject(Alepha);\n\n // Lazy to avoid the JobProvider ↔ JobDispatcher injection cycle\n // (JobProvider injects JobDispatcher; the queue consumer needs\n // JobProvider to process). Resolved at message-receive time.\n protected jobProviderRef?: JobProvider;\n protected getJobProvider(): JobProvider {\n if (!this.jobProviderRef) {\n this.jobProviderRef = this.alepha.inject(JobProvider);\n }\n return this.jobProviderRef;\n }\n\n protected readonly queue = $queue({\n name: \"api:jobs:dispatch\",\n schema: t.object({ jobName: t.text(), executionId: t.text() }),\n handler: async (msg) => {\n await this.getJobProvider().processExecution(\n msg.payload.jobName,\n msg.payload.executionId,\n );\n },\n });\n\n public async dispatch(jobName: string, executionId: string): Promise<void> {\n await this.queue.push({ jobName, executionId });\n }\n\n /**\n * Fan-out to a single variadic `queue.push(...payloads)` call so the\n * underlying queue provider can batch the network round-trips when it\n * supports it (Cloudflare Queues, Redis pipelines).\n */\n public override async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n if (items.length === 0) return;\n await this.queue.push(...items);\n }\n\n /**\n * Backwards-compatible alias for {@link dispatch}. Older code paths called\n * `JobQueueProvider.push(jobName, executionId)` directly; new code should\n * go through the `JobDispatcher.dispatch` API.\n */\n public async push(jobName: string, executionId: string): Promise<void> {\n return this.dispatch(jobName, executionId);\n }\n}\n","import {\n $hook,\n $inject,\n $state,\n Alepha,\n AlephaError,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { LockProvider } from \"alepha/lock\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, DbEntityNotFoundError } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport {\n type JobStatus,\n jobExecutionEntity,\n} from \"../entities/jobExecutionEntity.ts\";\nimport type { JobPrimitiveOptions, JobPriority } from \"../primitives/$job.ts\";\nimport { jobConfig } from \"../schemas/jobConfigAtom.ts\";\nimport { DirectJobDispatcher } from \"./DirectJobDispatcher.ts\";\nimport type { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobQueueProvider } from \"./JobQueueProvider.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nconst PRIORITY_MAP: Record<JobPriority, number> = {\n critical: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\nconst PRIORITY_REVERSE: Record<number, JobPriority> = {\n 0: \"critical\",\n 1: \"high\",\n 2: \"normal\",\n 3: \"low\",\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface PushOptions {\n delay?: DurationLike;\n key?: string;\n priority?: JobPriority;\n scheduledAt?: Date;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface PushManyItem<T extends TSchema = TSchema> {\n payload: Static<T>;\n key?: string;\n delay?: DurationLike;\n priority?: JobPriority;\n scheduledAt?: Date;\n}\n\nexport interface JobTriggerContext<T extends TSchema = TSchema> {\n payload?: Static<T>;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface CancelContext {\n cancelledBy?: string;\n cancelledByName?: string;\n}\n\n/**\n * The declared shape of the job (set at registration time).\n *\n * **Important** — this `kind` is the *declared* form. The *effective*\n * runtime mode (cron / queue / direct) is exposed by\n * `JobProvider.effectiveMode(name)` and the `JobRegistration.type` field on\n * the admin schema. Don't conflate the two: a `queue` kind can run as\n * `direct` at runtime when no queue dispatcher is loaded.\n */\ninterface JobRuntimeRegistration {\n name: string;\n options: JobPrimitiveOptions;\n kind: \"cron\" | \"queue\";\n}\n\nexport type JobEffectiveMode = \"cron\" | \"queue\" | \"direct\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Coordinates cron and push jobs with a durable outbox table and a single\n * reconciliation sweep. The actual delivery channel (queue / direct) is\n * abstracted behind {@link JobDispatcher}, substituted by DI:\n *\n * - **DirectJobDispatcher** (default, registered by `AlephaApiJobs`) —\n * runs the handler in-process right after `push()` returns.\n * - **QueueJobDispatcher** (registered by `AlephaApiJobsQueue`) — sends\n * the executionId through `AlephaQueue` so a pool of workers can pick\n * it up.\n *\n * Push flow:\n * push() → INSERT row (pending) → dispatcher.dispatch(jobName, id)\n * worker → claim → UPDATE running → handler → DELETE/UPDATE on success\n * → UPDATE error / scheduled (retry) on failure\n *\n * Cron flow:\n * scheduler tick → acquire lock → executeInline (no retry)\n * → enqueue + dispatch (retry declared)\n *\n * Sweep responsibilities (every `sweepCron`):\n * - re-enqueue pending rows older than `staleThreshold`\n * - mark crashed running rows as failed and apply retry policy\n * - move `scheduled` rows with `scheduledAt <= now` to pending + dispatch\n * - trim per-job history beyond `keepLastSuccess` / `keepLastError`\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly lockProvider = $inject(LockProvider);\n protected readonly config = $state(jobConfig);\n\n /**\n * Resolved at first use (after the container is fully wired) — picks\n * the queue dispatcher when `AlephaApiJobsQueue` was loaded, otherwise\n * the direct dispatcher. Lazy because both dispatchers inject\n * `JobProvider` themselves; resolving them at field-init time would\n * create a circular construction.\n */\n protected dispatcherRef?: JobDispatcher;\n public get dispatcher(): JobDispatcher {\n if (!this.dispatcherRef) {\n this.dispatcherRef = this.alepha.has(JobQueueProvider)\n ? this.alepha.inject(JobQueueProvider)\n : this.alepha.inject(DirectJobDispatcher);\n }\n return this.dispatcherRef;\n }\n protected readonly log = $logger();\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected readonly jobs = new Map<string, JobRuntimeRegistration>();\n protected readonly inFlight = new Set<Promise<void>>();\n protected readonly abortControllers = new Map<string, AbortController>();\n protected readonly perExecutionLogs = new Map<string, LogEntry[]>();\n protected stopping = false;\n\n // --- Registration -----------------------------------------------------------------------------------------------\n\n public registerJob(name: string, options: JobPrimitiveOptions): void {\n if (this.jobs.has(name)) {\n throw new AlephaError(`Job already registered: ${name}`);\n }\n if (options.cron && options.schema) {\n throw new AlephaError(\n `Job '${name}' declares both 'cron' and 'schema'. A job must be either cron-only (recurring) or queue-only (push-based). Split into two jobs.`,\n );\n }\n if (!options.cron && !options.schema) {\n throw new AlephaError(\n `Job '${name}' must declare either 'cron' (for recurring tasks) or 'schema' (for queue-mode tasks).`,\n );\n }\n\n const kind: \"cron\" | \"queue\" = options.cron ? \"cron\" : \"queue\";\n this.jobs.set(name, { name, options, kind });\n this.log.debug(`Registered ${kind} job '${name}'`, {\n cron: options.cron,\n priority: options.priority ?? \"normal\",\n retries: options.retry?.retries ?? 0,\n });\n\n if (options.cron) {\n this.cronProvider.createCronJob(name, options.cron, async () => {\n try {\n await this.runCron(name);\n } catch (error) {\n this.log.error(`Cron tick failed for job '${name}'`, error);\n }\n });\n }\n }\n\n public getRegisteredJobs(): Map<string, JobRuntimeRegistration> {\n return this.jobs;\n }\n\n /**\n * Resolves what *actually* runs at dispatch time. Cron jobs are always\n * \"cron\"; non-cron jobs delegate to the active `JobDispatcher` (queue\n * vs. direct), which is determined by which modules the app loaded.\n */\n public effectiveMode(name: string): JobEffectiveMode {\n const reg = this.getRegistration(name);\n if (reg.kind === \"cron\") return \"cron\";\n return this.dispatcher.kind;\n }\n\n // --- Cron execution (inline, no queue) --------------------------------------------------------------------------\n\n protected async runCron(name: string): Promise<void> {\n const registration = this.getRegistration(name);\n if (registration.kind !== \"cron\") {\n throw new AlephaError(`Job '${name}' is not cron-mode`);\n }\n await this.runCronLocked(registration, {\n triggeredBy: \"system\",\n triggeredByName: \"system (cron)\",\n });\n }\n\n /**\n * Cron-mode runner that respects the per-job distributed lock.\n * Used by both the scheduled tick and manual `trigger()` calls so that an\n * admin-triggered run on one instance can't race a scheduled run on another.\n *\n * **Two paths depending on `retry`:**\n *\n * - **No `retry`** — runs the handler inline. No DB row on success;\n * error row only on failure. The \"next tick\" is the implicit retry.\n * - **`retry` declared** — enqueues a synthetic execution row and hands\n * it to the dispatcher. The handler then runs through the same path\n * as a queue/direct push (claim, retry-on-fail, sweep recovery). Use\n * this when a single failed tick must not block work for the whole\n * `cron` interval (e.g. once-daily jobs).\n */\n protected async runCronLocked(\n registration: JobRuntimeRegistration,\n ctx: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<void> {\n if (this.stopping) return;\n\n const useLock = registration.options.lock !== false;\n if (useLock) {\n const acquired = await this.acquireCronLock(registration);\n if (!acquired) {\n this.log.debug(\n `Cron '${registration.name}' skipped — another instance holds the lock`,\n );\n return;\n }\n }\n\n try {\n if (registration.options.retry) {\n await this.enqueueCronExecution(registration, ctx);\n return;\n }\n\n const executionId = crypto.randomUUID();\n const promise = this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n } finally {\n if (useLock) {\n await this.releaseCronLock(registration);\n }\n }\n }\n\n /**\n * Materialize a cron tick into the outbox so it goes through the normal\n * retry/sweep path. Used when the user opts into `retry` on a cron job —\n * a transient failure no longer means \"wait for the next cron tick\", it\n * means \"the sweep will retry within `sweepCron`\".\n */\n protected async enqueueCronExecution(\n registration: JobRuntimeRegistration,\n ctx: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<void> {\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n const execution = await this.executions.create({\n jobName: registration.name,\n payload: undefined,\n status: \"pending\",\n priority: PRIORITY_MAP[opts.priority ?? \"normal\"],\n maxAttempts,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n await this.dispatch(registration.name, execution.id);\n }\n\n /**\n * Acquire a per-job NX lock keyed by `cron-job:<name>` so that a single\n * tick across all replicas runs exactly one execution. Auto-expires after\n * `2 * timeout` (or 5 minutes if no per-job timeout) so a crashed worker\n * cannot permanently block the cron from firing.\n *\n * **Caveat — same-process double-fire is not prevented.** The lock value\n * is a per-process holder id, so two concurrent ticks on the same process\n * (e.g. a scheduled tick overlapping an admin `trigger()` call) will both\n * see \"we own it\". This is acceptable for the multi-replica use case the\n * lock targets; a process that overlaps its own cron handler should set a\n * smaller `timeout` or use idempotent handler logic. A future fix can add\n * a per-process Set guard before reaching the LockProvider.\n */\n protected async acquireCronLock(\n registration: JobRuntimeRegistration,\n ): Promise<boolean> {\n const lockKey = this.cronLockKey(registration.name);\n const ttlMs = registration.options.timeout\n ? this.dt.duration(registration.options.timeout).as(\"milliseconds\") * 2\n : 5 * 60 * 1000;\n const value = `${this.lockHolderId},${this.dt.nowISOString()}`;\n try {\n const stored = await this.lockProvider.set(lockKey, value, true, ttlMs);\n const [holderId] = stored.split(\",\");\n return holderId === this.lockHolderId;\n } catch (e) {\n this.log.warn(`Cron lock acquire failed for '${registration.name}'`, e);\n return true; // Fail-open: better to risk a duplicate than to silently miss a tick.\n }\n }\n\n /**\n * Update only when the row is still in one of the expected statuses.\n * Logs and returns silently when the guard rejects — this happens when a\n * concurrent operation (most often `cancel()`) has already moved the row\n * into a terminal state. We must not overwrite that.\n */\n protected async guardedUpdate(\n executionId: string,\n expectedStatuses: JobStatus[],\n patch: Parameters<typeof this.executions.updateById>[1],\n label: string,\n ): Promise<void> {\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { inArray: expectedStatuses } },\n patch,\n );\n } catch (e) {\n if (e instanceof DbEntityNotFoundError) {\n this.log.debug(\n `${label}: row ${executionId} not in expected status — skipping write`,\n );\n return;\n }\n throw e;\n }\n }\n\n protected async releaseCronLock(\n registration: JobRuntimeRegistration,\n ): Promise<void> {\n try {\n await this.lockProvider.del(this.cronLockKey(registration.name));\n } catch (e) {\n this.log.debug(\n `Cron lock release failed for '${registration.name}' (will expire by TTL)`,\n e,\n );\n }\n }\n\n protected cronLockKey(jobName: string): string {\n return `alepha.api.jobs.cron:${jobName}`;\n }\n\n /**\n * Stable per-process id used as the lock value — survives multiple ticks.\n * Lazy so that Cloudflare Workers (which forbid random in global scope)\n * stay happy.\n */\n protected lockHolderIdValue?: string;\n protected get lockHolderId(): string {\n if (!this.lockHolderIdValue) {\n this.lockHolderIdValue = crypto.randomUUID();\n }\n return this.lockHolderIdValue;\n }\n\n /**\n * Execute a cron handler inline. Records a row only on error (or always,\n * when `record: 'all'`). No DB writes on the happy path by default.\n */\n protected async executeInline(\n registration: JobRuntimeRegistration,\n executionId: string,\n ctx: {\n payload: unknown;\n attempt: number;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n const opts = registration.options;\n const name = registration.name;\n const record = opts.record ?? \"error\";\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const startedAt = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name,\n now: startedAt,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: ctx.payload,\n attempt: ctx.attempt,\n now: startedAt,\n signal: abortController.signal,\n executionId,\n });\n\n if (record === \"all\") {\n await this.writeTerminalRow(executionId, name, \"ok\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: undefined,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n if (record !== \"none\") {\n await this.writeTerminalRow(executionId, name, \"error\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: err,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n await this.alepha.events.emit(\n \"job:error\",\n { name, error: err, executionId },\n { catch: true },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async writeTerminalRow(\n executionId: string,\n jobName: string,\n status: \"ok\" | \"error\",\n fields: {\n payload: unknown;\n attempt: number;\n startedAt: ReturnType<DateTimeProvider[\"now\"]>;\n error?: Error;\n context: string;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n try {\n const logs =\n status === \"error\" ? this.snapshotLogs(fields.context) : undefined;\n await this.executions.create({\n id: executionId,\n jobName,\n status,\n payload: fields.payload as Record<string, unknown> | undefined,\n attempt: fields.attempt,\n maxAttempts: fields.attempt,\n startedAt: fields.startedAt.toISOString(),\n completedAt: this.dt.nowISOString(),\n error: fields.error?.message,\n logs,\n triggeredBy: fields.triggeredBy,\n triggeredByName: fields.triggeredByName,\n });\n } catch (e) {\n this.log.warn(`Failed to write terminal row for ${executionId}`, e);\n }\n }\n\n // --- Queue push -------------------------------------------------------------------------------------------------\n\n public async push(\n name: string,\n payload: unknown,\n options?: PushOptions,\n ): Promise<string> {\n const registration = this.getRegistration(name);\n if (registration.kind !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared). Use trigger() instead.`,\n );\n }\n const opts = registration.options;\n const validated = this.alepha.codec.validate(opts.schema!, payload);\n\n const priority =\n PRIORITY_MAP[options?.priority ?? opts.priority ?? \"normal\"];\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const isDelayed = options?.delay || options?.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n\n let scheduledAt: string | undefined;\n if (options?.scheduledAt) {\n scheduledAt = options.scheduledAt.toISOString();\n } else if (options?.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(options.delay))\n .toISOString();\n }\n\n if (options?.key) {\n // Key-based dedup: check for existing row first, then insert.\n // Two queries in the no-conflict path, but deterministic across dialects.\n const existing = await this.executions.findMany({\n where: { jobName: { eq: name }, key: { eq: options.key } },\n limit: 1,\n });\n if (existing.length > 0) {\n return existing[0].id;\n }\n const execution = await this.executions.create({\n jobName: name,\n key: options.key,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options.triggeredBy,\n triggeredByName: options.triggeredByName,\n });\n if (status === \"pending\") {\n await this.dispatch(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n const execution = await this.executions.create({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options?.triggeredBy,\n triggeredByName: options?.triggeredByName,\n });\n\n if (status === \"pending\") {\n await this.dispatch(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n /**\n * Fire a local setTimeout so delayed/retrying rows dispatch as close to\n * `scheduledAt` as possible, rather than waiting for the next sweep tick.\n * No-op on stateless runtimes where timers won't survive (the sweep\n * handles those).\n */\n protected scheduleOptimisticDispatch(\n jobName: string,\n executionId: string,\n scheduledAt: string,\n ): void {\n const delayMs = Math.max(\n 0,\n new Date(scheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n }\n\n public async pushMany(\n name: string,\n items: Array<PushManyItem>,\n ): Promise<string[]> {\n if (items.length === 0) return [];\n\n const registration = this.getRegistration(name);\n if (registration.kind !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared).`,\n );\n }\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const keyed: PushManyItem[] = [];\n const bulk: Array<{\n jobName: string;\n payload: Record<string, unknown>;\n status: JobStatus;\n priority: number;\n maxAttempts: number;\n scheduledAt?: string;\n }> = [];\n\n for (const item of items) {\n const validated = this.alepha.codec.validate(opts.schema!, item.payload);\n if (item.key) {\n keyed.push({ ...item, payload: validated as Static<TSchema> });\n continue;\n }\n const isDelayed = item.delay || item.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n let scheduledAt: string | undefined;\n if (item.scheduledAt) {\n scheduledAt = item.scheduledAt.toISOString();\n } else if (item.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(item.delay))\n .toISOString();\n }\n bulk.push({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority: PRIORITY_MAP[item.priority ?? opts.priority ?? \"normal\"],\n maxAttempts,\n scheduledAt,\n });\n }\n\n const ids: string[] = [];\n\n for (const item of keyed) {\n const id = await this.push(name, item.payload, {\n key: item.key,\n delay: item.delay,\n priority: item.priority,\n scheduledAt: item.scheduledAt,\n });\n ids.push(id);\n }\n\n if (bulk.length > 0) {\n const created = await this.executions.createMany(bulk);\n // Collect pending rows so we hand them to the dispatcher in a single\n // batch — the queue dispatcher can fan them out in one network call.\n const toDispatch: Array<{ jobName: string; executionId: string }> = [];\n for (const exec of created) {\n ids.push(exec.id);\n if (exec.status === \"pending\" && !this.stopping) {\n toDispatch.push({ jobName: name, executionId: exec.id });\n } else if (\n exec.status === \"scheduled\" &&\n exec.scheduledAt &&\n !this.stopping\n ) {\n this.scheduleOptimisticDispatch(name, exec.id, exec.scheduledAt);\n }\n }\n if (toDispatch.length > 0) {\n await this.dispatchMany(toDispatch);\n }\n }\n\n this.log.debug(`pushMany '${name}': ${ids.length} jobs created`, {\n bulk: bulk.length,\n keyed: keyed.length,\n });\n\n return ids;\n }\n\n /**\n * Hand a single execution to the active `JobDispatcher`. Whether that\n * results in a queue send or in-process execution depends on which\n * dispatcher is wired (see {@link JobDispatcher}).\n */\n protected async dispatch(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n await this.dispatcher.dispatch(jobName, executionId);\n }\n\n /**\n * Batched variant. Used by `pushMany` so a backing queue can do a single\n * batch network call (e.g. Cloudflare Queues `sendBatch`).\n */\n protected async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n if (this.stopping || items.length === 0) return;\n await this.dispatcher.dispatchMany(items);\n }\n\n // --- Manual trigger (admin / CLI) ------------------------------------------------------------------------------\n\n public async trigger(\n name: string,\n context?: JobTriggerContext,\n ): Promise<void> {\n const registration = this.getRegistration(name);\n\n if (registration.kind === \"cron\") {\n await this.runCronLocked(registration, {\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n return;\n }\n\n // queue-mode: treat as a normal push with the given payload\n if (!context?.payload) {\n throw new AlephaError(\n `Queue-mode job '${name}' requires a payload for manual trigger.`,\n );\n }\n await this.push(name, context.payload, {\n triggeredBy: context.triggeredBy,\n triggeredByName: context.triggeredByName,\n });\n }\n\n // --- Cancel ----------------------------------------------------------------------------------------------------\n\n public async cancel(\n executionId: string,\n context?: CancelContext,\n ): Promise<void> {\n const execution = await this.executions.findById(executionId);\n if (!execution) {\n throw new AlephaError(`Execution not found: ${executionId}`);\n }\n if (\n execution.status === \"ok\" ||\n execution.status === \"error\" ||\n execution.status === \"cancelled\"\n ) {\n throw new AlephaError(\n `Cannot cancel execution in '${execution.status}' status`,\n );\n }\n\n const controller = this.abortControllers.get(executionId);\n if (controller) controller.abort();\n\n await this.executions.updateById(executionId, {\n status: \"cancelled\",\n key: null,\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n completedAt: this.dt.nowISOString(),\n });\n\n this.log.info(`Cancelled execution ${executionId}`, {\n jobName: execution.jobName,\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n }\n\n // --- Queue consumer (called by JobQueueProvider) --------------------------------------------------------------\n\n public async processExecution(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n this.log.warn(`Unknown job '${jobName}' — skipping execution`, {\n executionId,\n });\n return;\n }\n // Both `queue` and cron-with-retry execute through the outbox path —\n // the DB-level `claim()` is the actual concurrency guard.\n if (registration.kind !== \"queue\" && !registration.options.retry) {\n this.log.warn(\n `Job '${jobName}' has no outbox path (no schema and no retry) — skipping`,\n { executionId },\n );\n return;\n }\n\n const promise = this.processQueueExecution(registration, executionId);\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n protected async processQueueExecution(\n registration: JobRuntimeRegistration,\n executionId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const record = opts.record ?? \"error\";\n\n const execution = await this.claim(executionId);\n if (!execution) {\n this.log.debug(`Execution ${executionId} already claimed, skipping`);\n return;\n }\n\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const now = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name: jobName,\n now,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: execution.payload,\n attempt: execution.attempt,\n now,\n signal: abortController.signal,\n executionId,\n });\n\n // Success: either DELETE (keepLastSuccess=0 or record=error)\n // or UPDATE to 'ok' (record=all and keepLastSuccess>0).\n const keepSuccess =\n record === \"all\" && this.config.keepLastSuccess > 0;\n if (keepSuccess) {\n await this.executions.updateById(executionId, {\n status: \"ok\",\n completedAt: this.dt.nowISOString(),\n key: null,\n });\n } else {\n await this.executions.deleteById(executionId);\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name: jobName, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n\n if (abortController.signal.aborted) {\n const current = await this.executions.findById(executionId);\n if (current?.status === \"cancelled\") {\n await this.alepha.events.emit(\n \"job:cancel\",\n { name: jobName, executionId },\n { catch: true },\n );\n return;\n }\n }\n\n await this.handleFailure(\n executionId,\n registration,\n execution.attempt,\n err,\n contextId,\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name: jobName, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n /**\n * Transition pending → running and return the post-update row.\n * Two round-trips: read current attempt, then guarded UPDATE … RETURNING.\n * Returns null when the row is gone or already claimed by another worker.\n * The returned row replaces a separate post-claim findById, so the dispatch\n * path is 2 queries instead of 3.\n */\n protected async claim(executionId: string) {\n const current = await this.executions.findById(executionId);\n if (!current) return null;\n try {\n return await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"pending\" } },\n {\n status: \"running\",\n attempt: current.attempt + 1,\n startedAt: this.dt.nowISOString(),\n },\n );\n } catch (e) {\n if (e instanceof DbEntityNotFoundError) return null;\n throw e;\n }\n }\n\n protected async handleFailure(\n executionId: string,\n registration: JobRuntimeRegistration,\n currentAttempt: number,\n error: Error,\n contextId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const retry = opts.retry;\n const maxAttempts = (retry?.retries ?? 0) + 1;\n\n // `retries: 2` means \"1 initial + 2 retries = 3 total attempts\". Retry\n // while we have not yet *executed* the maxAttempts'th attempt — i.e.\n // currentAttempt (the one that just failed) is strictly less than\n // maxAttempts. The off-by-one fix: previously this was\n // `currentAttempt + 1 < maxAttempts`, which only ran 2 attempts for\n // `retries: 2`.\n const canRetry =\n retry &&\n currentAttempt < maxAttempts &&\n (retry.when ? retry.when(error) : true);\n\n if (canRetry) {\n // Retries are sweep-driven: write the row as `scheduled` with\n // `scheduledAt = now`. The next sweep tick (every `sweepCron`,\n // default 5 minutes) re-dispatches it. This means the first retry\n // can land anywhere from a few seconds to ~5 minutes later — the\n // exact moment depends on when the next sweep tick fires.\n //\n // We deliberately do NOT use exponential backoff or a local timer.\n // - Exponential backoff was platform-dependent (precise on Node\n // via setTimeout, but degraded to \"next sweep\" on Cloudflare\n // Workers where timers don't survive invocations). The behavior\n // is now identical everywhere.\n // - Sweep-only retry keeps the retry path observable from a single\n // place (the DB) and removes a class of races between the timer\n // and the sweep.\n const nextScheduledAt = this.dt.nowISOString();\n this.log.info(\n `Job '${jobName}' failed, scheduling retry ${currentAttempt + 1}/${maxAttempts} (sweep will pick up)`,\n { executionId, error: error.message },\n );\n // Guard with `status: running` so a concurrent cancel that has already\n // flipped the row to 'cancelled' is not overwritten by the retry write.\n await this.guardedUpdate(\n executionId,\n [\"running\"],\n {\n status: \"scheduled\",\n error: error.message,\n scheduledAt: nextScheduledAt,\n logs: this.snapshotLogs(contextId),\n },\n \"retry-after-failure\",\n );\n } else {\n this.log.info(\n `Job '${jobName}' dead after ${currentAttempt} attempt(s)`,\n { executionId, error: error.message },\n );\n await this.guardedUpdate(\n executionId,\n [\"running\"],\n {\n status: \"error\",\n error: error.message,\n completedAt: this.dt.nowISOString(),\n key: null,\n logs: this.snapshotLogs(contextId),\n },\n \"terminal-failure\",\n );\n }\n\n await this.alepha.events.emit(\n \"job:error\",\n { name: jobName, error, executionId },\n { catch: true },\n );\n }\n\n protected snapshotLogs(contextId: string): LogEntry[] | undefined {\n const entries = this.perExecutionLogs.get(contextId);\n if (!entries || entries.length === 0) return undefined;\n const max = this.config.logMaxEntries;\n if (max === 0) return undefined;\n if (entries.length <= max) return [...entries];\n const truncated = entries.slice(0, max);\n truncated.push({\n level: \"WARN\",\n message: `Log entries truncated at ${max}`,\n timestamp: this.dt.nowMillis(),\n service: \"alepha.jobs\",\n module: \"JobProvider\",\n } as LogEntry);\n return truncated;\n }\n\n // --- Sweep ----------------------------------------------------------------------------------------------------\n\n protected async sweep(): Promise<void> {\n if (this.stopping) return;\n this.log.trace(\"Starting job sweep\");\n const now = this.dt.now();\n const nowIso = now.toISOString();\n\n try {\n // 1. Due scheduled rows → pending + dispatch\n const dueWhere = this.executions.createQueryWhere();\n dueWhere.status = { eq: \"scheduled\" };\n dueWhere.scheduledAt = { lte: nowIso };\n const due = await this.executions.findMany({\n where: dueWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of due) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.executions.updateById(exec.id, { status: \"pending\" });\n await this.dispatchSafe(exec.jobName, exec.id);\n }\n\n // 2. Stale pending rows → re-dispatch\n const staleIso = now\n .subtract(this.config.staleThreshold, \"millisecond\")\n .toISOString();\n const staleWhere = this.executions.createQueryWhere();\n staleWhere.status = { eq: \"pending\" };\n staleWhere.createdAt = { lte: staleIso };\n const stale = await this.executions.findMany({\n where: staleWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of stale) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.dispatchSafe(exec.jobName, exec.id);\n }\n\n // 3. Crashed running rows → mark as failed + apply retry\n const runningWhere = this.executions.createQueryWhere();\n runningWhere.status = { eq: \"running\" };\n const running = await this.executions.findMany({ where: runningWhere });\n const nowMs = now.valueOf();\n for (const exec of running) {\n const reg = this.jobs.get(exec.jobName);\n if (!reg) continue;\n if (this.abortControllers.has(exec.id)) continue; // still alive locally\n const crashThresholdMs = reg.options.timeout\n ? this.dt.duration(reg.options.timeout).as(\"milliseconds\") * 2\n : this.config.runTimeout;\n const startedAtMs = exec.startedAt\n ? new Date(exec.startedAt).getTime()\n : 0;\n if (startedAtMs > 0 && nowMs - startedAtMs > crashThresholdMs) {\n this.log.warn(\n `Sweep: marking crashed ${exec.jobName} (${exec.id}) as failed`,\n );\n const err = new Error(\n \"Execution assumed crashed (recovered by sweep)\",\n );\n await this.handleFailure(exec.id, reg, exec.attempt, err, \"\");\n }\n }\n\n // 4. Trim ring buffer per job\n await this.trimRingBuffers();\n } catch (e) {\n this.log.error(\"Sweep failed\", { error: e });\n }\n }\n\n protected async dispatchSafe(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n try {\n await this.dispatch(jobName, executionId);\n } catch (e) {\n this.log.warn(`Sweep failed to dispatch ${jobName} (${executionId})`, e);\n }\n }\n\n /**\n * Move a row from `scheduled` → `pending` and dispatch it.\n * Used by the optimistic retry/delay timer. If the sweep has already moved\n * the row, or another worker has claimed it, the UPDATE guard fails silently.\n */\n protected async dispatchScheduled(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"scheduled\" } },\n { status: \"pending\" },\n );\n await this.dispatchSafe(jobName, executionId);\n } catch {\n // Row already transitioned (sweep ran, another worker claimed, etc.)\n }\n }\n\n protected async trimRingBuffers(): Promise<void> {\n for (const [jobName, reg] of this.jobs) {\n const okLimit = reg.options.keep?.ok ?? this.config.keepLastSuccess;\n const errLimit = reg.options.keep?.error ?? this.config.keepLastError;\n if (okLimit > 0) {\n await this.trimByStatus(jobName, \"ok\", okLimit);\n }\n if (errLimit > 0) {\n await this.trimByStatus(jobName, \"error\", errLimit);\n }\n }\n }\n\n protected async trimByStatus(\n jobName: string,\n status: \"ok\" | \"error\",\n keep: number,\n ): Promise<void> {\n try {\n const rows = await this.executions.findMany({\n where: { jobName: { eq: jobName }, status: { eq: status } },\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: keep + 50,\n });\n if (rows.length <= keep) return;\n const toDelete = rows.slice(keep).map((r) => r.id);\n if (toDelete.length > 0) {\n await this.executions.deleteMany({ id: { inArray: toDelete } });\n this.log.debug(\n `Trimmed ${toDelete.length} ${status} rows for '${jobName}'`,\n );\n }\n } catch (e) {\n this.log.warn(`Failed to trim ${status} rows for '${jobName}'`, e);\n }\n }\n\n // --- Lifecycle -----------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Summarize effective modes once at start so operators can see at a\n // glance what each job will do. No validation is needed here: queue\n // jobs gracefully fall back to direct mode when AlephaApiJobsQueue\n // isn't loaded.\n const modes: Record<JobEffectiveMode, number> = {\n cron: 0,\n queue: 0,\n direct: 0,\n };\n const perJob: Record<string, JobEffectiveMode> = {};\n for (const [name] of this.jobs) {\n const m = this.effectiveMode(name);\n modes[m]++;\n perJob[name] = m;\n }\n this.log.info(`Job system OK`, {\n modes,\n jobs: this.jobs.size,\n perJob,\n });\n\n // Capture logs per execution context.\n this.alepha.events.on(\"log\", ({ entry }) => {\n const ctx = entry.context;\n if (!ctx) return;\n const entries = this.perExecutionLogs.get(ctx);\n if (!entries) return;\n entries.push(entry);\n });\n\n if (!this.alepha.isServerless()) {\n await this.sweep();\n }\n\n this.cronProvider.createCronJob(\n \"api:jobs:sweep\",\n this.config.sweepCron,\n async () => {\n await this.sweep();\n },\n true,\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: async () => {\n this.stopping = true;\n if (this.inFlight.size > 0) {\n this.log.info(`Draining ${this.inFlight.size} in-flight job(s)...`);\n await Promise.race([\n Promise.allSettled([...this.inFlight]),\n this.dt.wait([this.config.drainTimeout, \"millisecond\"]),\n ]);\n }\n if (this.abortControllers.size > 0) {\n this.log.warn(\n `Aborting ${this.abortControllers.size} remaining job(s) after drain timeout`,\n );\n for (const controller of this.abortControllers.values()) {\n controller.abort();\n }\n }\n },\n });\n\n // --- Helpers -------------------------------------------------------------------------------------------------\n\n protected getRegistration(name: string): JobRuntimeRegistration {\n const registration = this.jobs.get(name);\n if (!registration) {\n throw new AlephaError(`Job not registered: ${name}`);\n }\n return registration;\n }\n}\n\nexport { PRIORITY_MAP, PRIORITY_REVERSE };\n","import {\n $inject,\n type Async,\n createPrimitive,\n KIND,\n PipelinePrimitive,\n type PipelinePrimitiveOptions,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DateTime, DurationLike } from \"alepha/datetime\";\nimport {\n JobProvider,\n type JobTriggerContext,\n type PushManyItem,\n type PushOptions,\n} from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive for defining scheduled (cron) or queued (push) tasks.\n *\n * A job must be either **cron-only** (pass `cron`) or **queue-only**\n * (pass `schema`), never both. To run scheduled work that processes\n * payloads, compose two jobs: a cron that pushes payloads, and a\n * queue job that handles them.\n */\nexport const $job = <T extends TSchema = TSchema>(\n options: JobPrimitiveOptions<T>,\n): JobPrimitive<T> => {\n return createPrimitive(JobPrimitive<T>, options);\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArgs<T extends TSchema = TSchema> {\n payload: Static<T>;\n attempt: number;\n now: DateTime;\n signal: AbortSignal;\n executionId: string;\n}\n\nexport interface JobRetryOptions {\n retries: number;\n when?: (error: Error) => boolean;\n}\n\nexport type JobPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface JobPrimitiveOptions<T extends TSchema = TSchema>\n extends PipelinePrimitiveOptions {\n /**\n * Optional explicit job name. Defaults to `ClassName.propertyKey`.\n * Recommended convention for framework-internal jobs: `api:module:jobName`.\n */\n name?: string;\n\n /**\n * Human-readable description (shown in the admin UI).\n */\n description?: string;\n\n /**\n * Payload schema (TypeBox). When set, the job is queue-mode.\n * Must not be combined with `cron`.\n */\n schema?: T;\n\n /**\n * Cron expression for recurring execution. When set, the job is cron-mode.\n * Must not be combined with `schema`.\n */\n cron?: string;\n\n /**\n * Retry policy for queue-mode and direct-mode jobs.\n * Cron-mode jobs do not retry — the next tick re-runs.\n *\n * Retries are picked up by the reconciliation sweep, so retry granularity\n * is bounded by `sweepCron` (default 5 minutes). The first retry may run\n * earlier than 5 minutes if the sweep tick happens sooner.\n */\n retry?: JobRetryOptions;\n\n /**\n * **Cron-mode only.** Whether to acquire a distributed lock around the\n * cron tick so that only one instance of a multi-replica deployment runs\n * the handler per tick.\n *\n * Has **no effect** on queue-mode and direct-mode jobs — those rely on\n * the outbox `claim()` UPDATE-guard to serialize work instead, which is\n * always on.\n *\n * To get cross-instance coordination on Docker / Node deployments,\n * register a real `LockProvider` (e.g. `alepha/lock/redis`). The default\n * `MemoryLockProvider` is per-process only.\n *\n * @default true\n */\n lock?: boolean;\n\n /**\n * Max execution time per attempt. Handler receives an `AbortSignal`.\n */\n timeout?: DurationLike;\n\n /**\n * Default priority for pushed jobs. Used by the sweep to order\n * dispatch when there is a backlog. Real-time queue consumption\n * is FIFO.\n * @default \"normal\"\n */\n priority?: JobPriority;\n\n /**\n * Whether to record successful executions.\n *\n * - `\"error\"` (default for cron, default for queue): only error/cancelled rows kept\n * - `\"all\"`: keep success rows too (bounded by `keepLastSuccess`)\n * - `\"none\"`: fire-and-forget, no row even on error\n *\n * Note: queue-mode jobs always write a `pending` row at push time (outbox).\n * This setting controls whether that row is kept on success.\n */\n record?: \"error\" | \"all\" | \"none\";\n\n /**\n * Override the global ring-buffer trim for this job.\n *\n * - `{ ok: 0, error: 0 }` — **keep forever** (no sweep trim). Useful for\n * audit-heavy jobs where retention is time-based (handled by a separate\n * cron) rather than count-based.\n * - `{ ok: 50 }` — keep last 50 successes; fall back to global default for errors.\n * - omitted — use global `keepLastSuccess` / `keepLastError` from `jobConfig`.\n */\n keep?: {\n ok?: number;\n error?: number;\n };\n\n /**\n * Handler function. For cron-mode, `payload` is `undefined`.\n */\n handler: (args: JobHandlerArgs<T>) => Async<void>;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive<\n T extends TSchema = TSchema,\n> extends PipelinePrimitive<JobPrimitiveOptions<T>> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n const handler = this.handler.run.bind(this.handler);\n this.jobProvider.registerJob(this.name, { ...this.options, handler });\n }\n\n /**\n * Push a single payload to the queue (queue-mode only).\n */\n public async push(\n payload: Static<T>,\n options?: PushOptions,\n ): Promise<string> {\n return this.jobProvider.push(this.name, payload, options);\n }\n\n /**\n * Push multiple payloads at once (queue-mode only).\n * Batched INSERT + batched queue send when supported.\n */\n public async pushMany(items: Array<PushManyItem<T>>): Promise<string[]> {\n return this.jobProvider.pushMany(this.name, items);\n }\n\n /**\n * Cancel a pending or running execution.\n */\n public async cancel(executionId: string): Promise<void> {\n return this.jobProvider.cancel(executionId);\n }\n\n /**\n * Manually fire a cron-mode job, or trigger a queue-mode job with an explicit payload.\n */\n public async trigger(context?: JobTriggerContext<T>): Promise<void> {\n return this.jobProvider.trigger(this.name, context);\n }\n}\n\n$job[KIND] = JobPrimitive;\n","import { $inject, Alepha, AlephaError, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { NotFoundError } from \"alepha/server\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobTriggerContext } from \"../providers/JobProvider.ts\";\nimport { JobProvider, PRIORITY_REVERSE } from \"../providers/JobProvider.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport type { JobExecutionResource } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport type { JobRegistration } from \"../schemas/jobRegistrationSchema.ts\";\n\n/**\n * Admin surface for the job system.\n *\n * Six methods: list jobs, list executions, get execution,\n * trigger, retry, cancel. Everything else lives in events — any\n * analytics/observability is an external concern that subscribes\n * to `job:begin` / `job:success` / `job:error`.\n */\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly jobProvider = $inject(JobProvider);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected computeCan(status: string) {\n return {\n retry: status === \"error\" || status === \"cancelled\",\n cancel:\n status === \"pending\" || status === \"running\" || status === \"scheduled\",\n };\n }\n\n /**\n * Convert the int-priority storage column into the public enum string.\n * The cast through `unknown` skips TypeScript's structural check between\n * the entity-level row (`priority: number`) and the resource schema\n * (`priority: enum`); the runtime values are correct.\n */\n protected toResource<T extends { priority: number; status: string }>(\n row: T,\n ): JobExecutionResource {\n return {\n ...row,\n priority: PRIORITY_REVERSE[row.priority] ?? \"normal\",\n can: this.computeCan(row.status),\n } as unknown as JobExecutionResource;\n }\n\n /**\n * List every registered job with recent ok/error counts and lastRun.\n * One aggregate query covers all jobs.\n */\n public async listJobs(): Promise<JobRegistration[]> {\n const registry = this.jobProvider.getRegisteredJobs();\n\n const aggRows = await this.executions.query(\n (e) => sql`\n SELECT\n ${e.jobName} AS job_name,\n ${e.status} AS status,\n COUNT(*) AS count,\n MAX(${e.completedAt}) AS last_run\n FROM ${e}\n WHERE ${e.status} IN ('ok', 'error')\n GROUP BY ${e.jobName}, ${e.status}\n `,\n t.object({\n job_name: t.string(),\n status: t.string(),\n count: t.string(),\n last_run: t.optional(t.nullable(t.union([t.string(), t.number()]))),\n }),\n );\n\n const toIso = (\n v: string | number | null | undefined,\n ): string | undefined => {\n if (v === null || v === undefined) return undefined;\n if (typeof v === \"number\") return new Date(v).toISOString();\n return v;\n };\n\n const byJob = new Map<\n string,\n { ok: number; error: number; lastRun?: string }\n >();\n for (const row of aggRows) {\n const entry = byJob.get(row.job_name) ?? { ok: 0, error: 0 };\n if (row.status === \"ok\") entry.ok = Number(row.count);\n if (row.status === \"error\") entry.error = Number(row.count);\n const iso = toIso(row.last_run);\n if (iso && (!entry.lastRun || iso > entry.lastRun)) {\n entry.lastRun = iso;\n }\n byJob.set(row.job_name, entry);\n }\n\n const result: JobRegistration[] = [];\n for (const [name, reg] of registry) {\n const opts = reg.options;\n const counts = byJob.get(name) ?? { ok: 0, error: 0 };\n result.push({\n name,\n description: opts.description,\n type: this.jobProvider.effectiveMode(name),\n cron: opts.cron,\n priority: (opts.priority ?? \"normal\") as JobRegistration[\"priority\"],\n timeout: opts.timeout ? String(opts.timeout) : undefined,\n retry: opts.retry\n ? {\n retries: opts.retry.retries,\n }\n : undefined,\n recent: counts,\n });\n }\n return result;\n }\n\n /**\n * Recent executions for a single job, ORDER BY startedAt DESC.\n */\n public async getExecutions(jobName: string, query: JobExecutionQuery = {}) {\n const registry = this.jobProvider.getRegisteredJobs();\n if (!registry.has(jobName)) {\n throw new NotFoundError(`Job not found: ${jobName}`);\n }\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: jobName };\n if (query.status) {\n where.status = { eq: query.status };\n }\n const rows = await this.executions.findMany({\n where,\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: query.limit ?? 20,\n });\n return rows.map((row) => this.toResource(row));\n }\n\n /**\n * Full execution detail (includes captured logs).\n */\n public async getExecution(id: string) {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n return this.toResource(execution);\n }\n\n /**\n * Manual trigger (cron jobs) or push-with-payload (queue jobs).\n */\n public async triggerJob(\n name: string,\n context?: JobTriggerContext,\n ): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n if (!job) {\n throw new NotFoundError(`Job not found: ${name}`);\n }\n this.log.info(`Triggering job '${name}'`, {\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n await job.trigger(context);\n return { ok: true };\n }\n\n /**\n * Retry a dead or cancelled execution by re-pushing with the original payload.\n */\n public async retryExecution(\n id: string,\n context?: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<{ ok: boolean }> {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n if (execution.status !== \"error\" && execution.status !== \"cancelled\") {\n throw new AlephaError(\n `Cannot retry execution in '${execution.status}' status`,\n );\n }\n\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === execution.jobName);\n if (!job) {\n throw new NotFoundError(`Job not found: ${execution.jobName}`);\n }\n\n this.log.info(`Retrying execution ${id}`, {\n jobName: execution.jobName,\n previousStatus: execution.status,\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n\n if (execution.payload) {\n await job.push(execution.payload as any);\n } else {\n await job.trigger({\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n }\n return { ok: true };\n }\n\n public async cancelExecution(\n id: string,\n context?: { cancelledBy?: string; cancelledByName?: string },\n ): Promise<{ ok: boolean }> {\n this.log.info(`Cancelling execution ${id}`, {\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n await this.jobProvider.cancel(id, {\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n });\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { jobRegistrationSchema } from \"../schemas/jobRegistrationSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\n/**\n * Minimal admin surface for the job system. Six endpoints.\n */\nexport class AdminJobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"admin:jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly listJobs = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n response: t.array(jobRegistrationSchema),\n },\n handler: () => this.jobService.listJobs(),\n });\n\n public readonly listExecutions = $action({\n path: `${this.url}/:name/executions`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n query: jobExecutionQuerySchema,\n response: t.array(jobExecutionResourceSchema),\n },\n handler: ({ params, query }) =>\n this.jobService.getExecutions(params.name, query),\n });\n\n public readonly getExecution = $action({\n path: `${this.url}/executions/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: jobExecutionResourceSchema,\n },\n handler: ({ params }) => this.jobService.getExecution(params.id),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/:name/trigger`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ params, body, user }) =>\n this.jobService.triggerJob(params.name, {\n payload: body.payload,\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly retryExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/retry`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.retryExecution(params.id, {\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly cancelExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:cancel\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.cancelExecution(params.id, {\n cancelledBy: user?.id,\n cancelledByName: user?.name,\n }),\n });\n}\n","import { $module } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { AlephaLock } from \"alepha/lock\";\nimport { AlephaQueue } from \"alepha/queue\";\nimport { AlephaScheduler } from \"alepha/scheduler\";\nimport { AdminJobController } from \"./controllers/AdminJobController.ts\";\nimport { $job } from \"./primitives/$job.ts\";\nimport { DirectJobDispatcher } from \"./providers/DirectJobDispatcher.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobQueueProvider } from \"./providers/JobQueueProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminJobController.ts\";\nexport * from \"./entities/jobExecutionEntity.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/DirectJobDispatcher.ts\";\nexport * from \"./providers/JobDispatcher.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./providers/JobQueueProvider.ts\";\nexport * from \"./schemas/jobConfigAtom.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/jobRegistrationSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"job:begin\": { name: string; now: DateTime; executionId: string };\n \"job:success\": { name: string; executionId: string };\n \"job:error\": { name: string; error: Error; executionId: string };\n \"job:cancel\": { name: string; executionId: string };\n \"job:end\": { name: string; executionId: string };\n }\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Job execution framework — cron and durable queue work with a single primitive.\n *\n * A `$job` is either **cron-only** (declares `cron`) or **payload-only** (declares `schema`).\n *\n * **Three runtime modes:**\n *\n * - **cron** — fires on a schedule. Cron-mode jobs are protected by a\n * distributed lock by default (`lock: true`), so multi-replica Docker\n * deployments only run the handler once per tick. Override with\n * `lock: false` if you genuinely want every replica to fire.\n * - **queue** — push-driven, dispatched through the queue infrastructure\n * (`AlephaQueue`, e.g. Cloudflare Queues, Redis). Real-time delivery,\n * ideal for high-volume systems. Requires `AlephaApiJobsQueue`.\n * - **direct** — push-driven, processed in-process right after the caller\n * awaits the push. The DB outbox row is the durability guarantee — if\n * the process dies, the reconciliation sweep re-dispatches. Default\n * when `AlephaApiJobsQueue` is *not* loaded. Best for cheap deployments\n * (Cloudflare Workers, single-instance Node) where standing up a queue\n * is overkill.\n *\n * **Retries** are sweep-driven across all modes (no exponential backoff).\n * Granularity is bounded by `sweepCron` (default 5 min). The first retry\n * may land anywhere from a few seconds to ~5 min later depending on when\n * the next sweep tick fires. Cron jobs that declare `retry` go through\n * the same sweep path — a transient failure no longer means waiting for\n * the next cron tick (useful for once-daily jobs).\n *\n * **Runtime support for cron triggers**\n *\n * - **Long-running Node / Docker** — `CronProvider` runs an in-process\n * timer loop. Multi-replica deployments serialize ticks via the cron\n * lock (see `$job.lock`).\n * - **Cloudflare Workers** — the build emits cron expressions into\n * `wrangler.jsonc`; Cloudflare invokes the worker on schedule and the\n * `cloudflare:scheduled` hook routes the event to the matching jobs.\n * - **Vercel** — the build emits cron entries into\n * `.vercel/output/config.json` mapped to `/_alepha/cron/:name`; the\n * serverless handler emits `serverless:cron` and `CronProvider` runs\n * the matching job. Set `CRON_SECRET` to require authenticated calls.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n primitives: [$job],\n imports: [AlephaScheduler, AlephaLock],\n services: [JobProvider, JobService, AdminJobController, DirectJobDispatcher],\n});\n\n/**\n * Queue support for `$job`. Import alongside {@link AlephaApiJobs} when your\n * app declares queue-mode jobs (any `$job` with a `schema`) and you want a\n * real queue (e.g. Cloudflare Queues, Redis) instead of in-process direct\n * execution.\n *\n * Adds `JobQueueProvider` to the container. `JobProvider` detects its\n * presence at start-up and routes dispatches through it.\n *\n * @module alepha.api.jobs.queue\n */\nexport const AlephaApiJobsQueue = $module({\n name: \"alepha.api.jobs.queue\",\n imports: [AlephaApiJobs, AlephaQueue],\n services: [JobQueueProvider],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutionEntity.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/jobRegistrationSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/schemas/jobConfigAtom.ts","../../../src/api/jobs/providers/JobDispatcher.ts","../../../src/api/jobs/providers/DirectJobDispatcher.ts","../../../src/api/jobs/providers/JobQueueProvider.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/AdminJobController.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const jobExecutionQuerySchema = t.object({\n status: t.optional(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n ),\n limit: t.optional(t.integer({ minimum: 1, maximum: 200, default: 20 })),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Job execution record.\n *\n * Stores durable state for queue-mode jobs (outbox pattern) and error records\n * for cron-mode jobs. Successful executions are trimmed by the sweep to keep\n * the last N rows per job (configurable via `jobConfig.keepLastSuccess`).\n *\n * Status transitions:\n * - queue push → pending (or `scheduled` if `delay`/`scheduledAt` was given)\n * - worker claim → running\n * - success → ok (or row deleted, depending on `record` and `keepLastSuccess`)\n * - terminal failure → error\n * - retryable failure → scheduled (with scheduledAt = now; sweep picks it up)\n * - delay → scheduled (with scheduledAt = now + delay)\n * - sweep picks due ones → pending\n * - cancel → cancelled\n */\nexport const jobExecutionEntity = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n jobName: t.text(),\n key: t.optional(t.nullable(t.text())),\n\n status: db.default(\n t.enum([\"pending\", \"running\", \"scheduled\", \"ok\", \"error\", \"cancelled\"]),\n \"pending\",\n ),\n priority: db.default(t.integer({ minimum: 0, maximum: 3 }), 2),\n\n attempt: db.default(t.integer(), 0),\n maxAttempts: db.default(t.integer(), 1),\n\n payload: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(t.datetime()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n\n error: t.optional(t.text()),\n logs: t.optional(t.array(logEntrySchema)),\n\n triggeredBy: t.optional(t.text()),\n triggeredByName: t.optional(t.text()),\n cancelledBy: t.optional(t.text()),\n cancelledByName: t.optional(t.text()),\n }),\n indexes: [\n { columns: [\"jobName\", \"status\", \"scheduledAt\"] },\n { columns: [\"jobName\", \"status\", \"createdAt\"] },\n { columns: [\"jobName\", \"startedAt\"] },\n { columns: [\"jobName\", \"key\"], unique: true },\n ],\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;\n\nexport type JobStatus =\n | \"pending\"\n | \"running\"\n | \"scheduled\"\n | \"ok\"\n | \"error\"\n | \"cancelled\";\n","import { type Static, t } from \"alepha\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\n\n/**\n * Public-facing schema for a job execution row.\n *\n * Diverges from the raw entity in two places, both for API ergonomics:\n *\n * - `priority` is exposed as the **string enum** (`critical`/`high`/...)\n * instead of the numeric value used internally for SQL ordering. The\n * `JobService` is responsible for the int → string transform.\n * - `can` derives the available admin actions from the row's status.\n */\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutionEntity.schema,\n {\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n can: t.object({\n retry: t.boolean(),\n cancel: t.boolean(),\n }),\n },\n {\n title: \"JobExecutionResource\",\n description: \"A job execution row with derived actions.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const jobRegistrationSchema = t.object({\n name: t.text(),\n description: t.optional(t.text()),\n type: t.enum([\"cron\", \"queue\", \"direct\"], {\n description:\n \"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.\",\n }),\n priority: t.enum([\"critical\", \"high\", \"normal\", \"low\"]),\n cron: t.optional(t.text()),\n timeout: t.optional(t.text()),\n retry: t.optional(\n t.object({\n retries: t.integer(),\n }),\n ),\n recent: t.object({\n ok: t.integer(),\n error: t.integer(),\n lastRun: t.optional(t.datetime()),\n }),\n});\n\nexport type JobRegistration = Static<typeof jobRegistrationSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n payload: t.optional(t.record(t.text(), t.any())),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\nexport const jobConfig = $atom({\n name: \"alepha.jobs\",\n description: \"Configuration for the $job primitive.\",\n schema: t.object({\n sweepCron: t.text({\n description:\n \"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.\",\n }),\n trimCron: t.text({\n description:\n \"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.\",\n }),\n staleThreshold: t.integer({\n description: \"Pending age (ms) before the sweep re-dispatches it.\",\n }),\n runTimeout: t.integer({\n description:\n \"Running age (ms) before assumed crash (fallback when no per-job timeout).\",\n }),\n keepLastSuccess: t.integer({\n description:\n \"Max successful rows to keep per job. Set 0 to disable and delete on success.\",\n }),\n keepLastError: t.integer({\n description: \"Max error rows to keep per job.\",\n }),\n logMaxEntries: t.integer({\n description: \"Max log entries captured per execution.\",\n }),\n drainTimeout: t.integer({\n description: \"Max time (ms) to wait for in-flight jobs during shutdown.\",\n }),\n }),\n default: {\n sweepCron: \"*/5 * * * *\",\n trimCron: \"0 * * * *\",\n staleThreshold: 300_000,\n runTimeout: 1_800_000,\n keepLastSuccess: 10,\n keepLastError: 10,\n logMaxEntries: 100,\n drainTimeout: 30_000,\n },\n});\n\nexport type JobConfig = Static<typeof jobConfig.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [jobConfig.key]: JobConfig;\n }\n}\n","/**\n * Abstract dispatcher for queued/direct job executions.\n *\n * The default implementation, {@link DirectJobDispatcher}, runs the handler\n * in-process after the caller's `push()` returns — fast and dependency-free.\n *\n * `AlephaApiJobsQueue` substitutes this with `JobQueueProvider`, which\n * publishes the executionId to `AlephaQueue` so a worker pool can consume\n * the work asynchronously.\n *\n * Substitute via DI:\n * ```ts\n * Alepha.create()\n * .with({ provide: JobDispatcher, use: MyCustomDispatcher })\n * .with(AlephaApiJobs);\n * ```\n *\n * The `kind` getter is read by the `JobProvider.effectiveMode` accessor\n * and by the admin UI so users can see which dispatcher is currently active.\n */\nexport abstract class JobDispatcher {\n /**\n * Identifier for this dispatcher's effective mode. Reported to the admin\n * UI so operators can see whether `$job` is running in `queue` or\n * `direct` mode.\n */\n public abstract readonly kind: \"queue\" | \"direct\";\n\n /**\n * Hand off a single execution. The caller's `push()` awaits this so the\n * caller can be sure the dispatch has at least been initiated. Long-running\n * work must NOT be awaited here (use background scheduling instead) — this\n * call should return as quickly as possible.\n */\n public abstract dispatch(jobName: string, executionId: string): Promise<void>;\n\n /**\n * Optional batch dispatch. The default implementation loops, but\n * dispatchers backed by a real queue should override this to use the\n * provider's batch send (e.g. Cloudflare Queues `sendBatch`).\n */\n public async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n for (const item of items) {\n await this.dispatch(item.jobName, item.executionId);\n }\n }\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Default `JobDispatcher` for environments without `AlephaApiJobsQueue`.\n *\n * Runs `JobProvider.processExecution` in the background — the caller's\n * `push()` returns immediately while the handler continues to completion\n * in the same process. The DB outbox row is the durability guarantee:\n * if the process dies before the handler finishes, the next sweep tick\n * picks the row up and re-dispatches.\n *\n * **Cloudflare Workers** — when an `executionCtx.waitUntil` is available\n * in the alepha store at `cloudflare.waitUntil`, the dispatch wraps the\n * background promise with `waitUntil` so the runtime keeps the isolate\n * alive past the HTTP response. Without this, the handler would be\n * terminated when the response is returned and only the next sweep\n * (every 5 min by default) would re-dispatch.\n *\n * **Vercel / single-Node** — on long-running runtimes the event loop\n * keeps the promise alive naturally; no special wiring is required.\n */\nexport class DirectJobDispatcher extends JobDispatcher {\n public readonly kind = \"direct\" as const;\n\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n\n // Lazy: resolved on first dispatch to break the JobProvider ↔ Dispatcher\n // injection cycle (JobProvider injects JobDispatcher to dispatch; we need\n // JobProvider to actually run executions).\n protected jobProviderRef?: JobProvider;\n protected getJobProvider(): JobProvider {\n if (!this.jobProviderRef) {\n this.jobProviderRef = this.alepha.inject(JobProvider);\n }\n return this.jobProviderRef;\n }\n\n public async dispatch(jobName: string, executionId: string): Promise<void> {\n const promise = this.getJobProvider()\n .processExecution(jobName, executionId)\n .catch((err) => {\n this.log.warn(\n `Direct execution failed for '${jobName}' (sweep will retry)`,\n err,\n );\n });\n\n // Cloudflare Workers: keep the isolate alive past the HTTP response so\n // the handler actually finishes. Outside CF this read returns undefined\n // and we fall through to plain fire-and-track.\n const waitUntil = this.alepha.store.get(\"cloudflare.waitUntil\") as\n | ((p: Promise<unknown>) => void)\n | undefined;\n if (typeof waitUntil === \"function\") {\n try {\n waitUntil(promise);\n } catch (e) {\n // The runtime may reject waitUntil if called outside a request scope.\n // Promise still runs; just log.\n this.log.debug(\n \"waitUntil rejected — falling back to fire-and-track\",\n e,\n );\n }\n }\n }\n}\n","import { $inject, Alepha, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobProvider } from \"./JobProvider.ts\";\n\n/**\n * Queue-backed `JobDispatcher` registered by `AlephaApiJobsQueue`.\n *\n * Extends {@link JobDispatcher} and substitutes the default\n * `DirectJobDispatcher` so that `$job.push()` is delivered through\n * `AlephaQueue` (e.g. Cloudflare Queues, Redis, in-memory) instead of\n * being processed in-process.\n *\n * The class is also kept as a `JobQueueProvider` export name for backwards\n * compatibility — it has always been the queue path's entry point.\n */\nexport class JobQueueProvider extends JobDispatcher {\n public readonly kind = \"queue\" as const;\n protected readonly alepha = $inject(Alepha);\n\n // Lazy to avoid the JobProvider ↔ JobDispatcher injection cycle\n // (JobProvider injects JobDispatcher; the queue consumer needs\n // JobProvider to process). Resolved at message-receive time.\n protected jobProviderRef?: JobProvider;\n protected getJobProvider(): JobProvider {\n if (!this.jobProviderRef) {\n this.jobProviderRef = this.alepha.inject(JobProvider);\n }\n return this.jobProviderRef;\n }\n\n protected readonly queue = $queue({\n name: \"api:jobs:dispatch\",\n schema: t.object({ jobName: t.text(), executionId: t.text() }),\n handler: async (msg) => {\n await this.getJobProvider().processExecution(\n msg.payload.jobName,\n msg.payload.executionId,\n );\n },\n });\n\n public async dispatch(jobName: string, executionId: string): Promise<void> {\n await this.queue.push({ jobName, executionId });\n }\n\n /**\n * Fan-out to a single variadic `queue.push(...payloads)` call so the\n * underlying queue provider can batch the network round-trips when it\n * supports it (Cloudflare Queues, Redis pipelines).\n */\n public override async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n if (items.length === 0) return;\n await this.queue.push(...items);\n }\n\n /**\n * Backwards-compatible alias for {@link dispatch}. Older code paths called\n * `JobQueueProvider.push(jobName, executionId)` directly; new code should\n * go through the `JobDispatcher.dispatch` API.\n */\n public async push(jobName: string, executionId: string): Promise<void> {\n return this.dispatch(jobName, executionId);\n }\n}\n","import {\n $hook,\n $inject,\n $state,\n Alepha,\n AlephaError,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { LockProvider } from \"alepha/lock\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, DbEntityNotFoundError } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport {\n type JobStatus,\n jobExecutionEntity,\n} from \"../entities/jobExecutionEntity.ts\";\nimport type { JobPrimitiveOptions, JobPriority } from \"../primitives/$job.ts\";\nimport { jobConfig } from \"../schemas/jobConfigAtom.ts\";\nimport { DirectJobDispatcher } from \"./DirectJobDispatcher.ts\";\nimport type { JobDispatcher } from \"./JobDispatcher.ts\";\nimport { JobQueueProvider } from \"./JobQueueProvider.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nconst PRIORITY_MAP: Record<JobPriority, number> = {\n critical: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\nconst PRIORITY_REVERSE: Record<number, JobPriority> = {\n 0: \"critical\",\n 1: \"high\",\n 2: \"normal\",\n 3: \"low\",\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface PushOptions {\n delay?: DurationLike;\n key?: string;\n priority?: JobPriority;\n scheduledAt?: Date;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface PushManyItem<T extends TSchema = TSchema> {\n payload: Static<T>;\n key?: string;\n delay?: DurationLike;\n priority?: JobPriority;\n scheduledAt?: Date;\n}\n\nexport interface JobTriggerContext<T extends TSchema = TSchema> {\n payload?: Static<T>;\n triggeredBy?: string;\n triggeredByName?: string;\n}\n\nexport interface CancelContext {\n cancelledBy?: string;\n cancelledByName?: string;\n}\n\n/**\n * The declared shape of the job (set at registration time).\n *\n * **Important** — this `kind` is the *declared* form. The *effective*\n * runtime mode (cron / queue / direct) is exposed by\n * `JobProvider.effectiveMode(name)` and the `JobRegistration.type` field on\n * the admin schema. Don't conflate the two: a `queue` kind can run as\n * `direct` at runtime when no queue dispatcher is loaded.\n */\ninterface JobRuntimeRegistration {\n name: string;\n options: JobPrimitiveOptions;\n kind: \"cron\" | \"queue\";\n}\n\nexport type JobEffectiveMode = \"cron\" | \"queue\" | \"direct\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Coordinates cron and push jobs with a durable outbox table and a single\n * reconciliation sweep. The actual delivery channel (queue / direct) is\n * abstracted behind {@link JobDispatcher}, substituted by DI:\n *\n * - **DirectJobDispatcher** (default, registered by `AlephaApiJobs`) —\n * runs the handler in-process right after `push()` returns.\n * - **QueueJobDispatcher** (registered by `AlephaApiJobsQueue`) — sends\n * the executionId through `AlephaQueue` so a pool of workers can pick\n * it up.\n *\n * Push flow:\n * push() → INSERT row (pending) → dispatcher.dispatch(jobName, id)\n * worker → claim → UPDATE running → handler → DELETE/UPDATE on success\n * → UPDATE error / scheduled (retry) on failure\n *\n * Cron flow:\n * scheduler tick → acquire lock → executeInline (no retry)\n * → enqueue + dispatch (retry declared)\n *\n * Sweep responsibilities (every `sweepCron`):\n * - re-enqueue pending rows older than `staleThreshold`\n * - mark crashed running rows as failed and apply retry policy\n * - move `scheduled` rows with `scheduledAt <= now` to pending + dispatch\n *\n * Trim runs on its own cron (`trimCron`, default hourly):\n * - per-job history trimmed beyond `keepLastSuccess` / `keepLastError`\n * - decoupled from sweep because trim cost scales with job count, not\n * retry latency — running it every sweep is wasted work for most apps.\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly lockProvider = $inject(LockProvider);\n protected readonly config = $state(jobConfig);\n\n /**\n * Resolved at first use (after the container is fully wired) — picks\n * the queue dispatcher when `AlephaApiJobsQueue` was loaded, otherwise\n * the direct dispatcher. Lazy because both dispatchers inject\n * `JobProvider` themselves; resolving them at field-init time would\n * create a circular construction.\n */\n protected dispatcherRef?: JobDispatcher;\n public get dispatcher(): JobDispatcher {\n if (!this.dispatcherRef) {\n this.dispatcherRef = this.alepha.has(JobQueueProvider)\n ? this.alepha.inject(JobQueueProvider)\n : this.alepha.inject(DirectJobDispatcher);\n }\n return this.dispatcherRef;\n }\n protected readonly log = $logger();\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected readonly jobs = new Map<string, JobRuntimeRegistration>();\n protected readonly inFlight = new Set<Promise<void>>();\n protected readonly abortControllers = new Map<string, AbortController>();\n protected readonly perExecutionLogs = new Map<string, LogEntry[]>();\n protected stopping = false;\n\n // --- Registration -----------------------------------------------------------------------------------------------\n\n public registerJob(name: string, options: JobPrimitiveOptions): void {\n if (this.jobs.has(name)) {\n throw new AlephaError(`Job already registered: ${name}`);\n }\n if (options.cron && options.schema) {\n throw new AlephaError(\n `Job '${name}' declares both 'cron' and 'schema'. A job must be either cron-only (recurring) or queue-only (push-based). Split into two jobs.`,\n );\n }\n if (!options.cron && !options.schema) {\n throw new AlephaError(\n `Job '${name}' must declare either 'cron' (for recurring tasks) or 'schema' (for queue-mode tasks).`,\n );\n }\n\n const kind: \"cron\" | \"queue\" = options.cron ? \"cron\" : \"queue\";\n this.jobs.set(name, { name, options, kind });\n this.log.debug(`Registered ${kind} job '${name}'`, {\n cron: options.cron,\n priority: options.priority ?? \"normal\",\n retries: options.retry?.retries ?? 0,\n });\n\n if (options.cron) {\n this.cronProvider.createCronJob(name, options.cron, async () => {\n try {\n await this.runCron(name);\n } catch (error) {\n this.log.error(`Cron tick failed for job '${name}'`, error);\n }\n });\n }\n }\n\n public getRegisteredJobs(): Map<string, JobRuntimeRegistration> {\n return this.jobs;\n }\n\n /**\n * Resolves what *actually* runs at dispatch time. Cron jobs are always\n * \"cron\"; non-cron jobs delegate to the active `JobDispatcher` (queue\n * vs. direct), which is determined by which modules the app loaded.\n */\n public effectiveMode(name: string): JobEffectiveMode {\n const reg = this.getRegistration(name);\n if (reg.kind === \"cron\") return \"cron\";\n return this.dispatcher.kind;\n }\n\n // --- Cron execution (inline, no queue) --------------------------------------------------------------------------\n\n protected async runCron(name: string): Promise<void> {\n const registration = this.getRegistration(name);\n if (registration.kind !== \"cron\") {\n throw new AlephaError(`Job '${name}' is not cron-mode`);\n }\n await this.runCronLocked(registration, {\n triggeredBy: \"system\",\n triggeredByName: \"system (cron)\",\n });\n }\n\n /**\n * Cron-mode runner that respects the per-job distributed lock.\n * Used by both the scheduled tick and manual `trigger()` calls so that an\n * admin-triggered run on one instance can't race a scheduled run on another.\n *\n * **Two paths depending on `retry`:**\n *\n * - **No `retry`** — runs the handler inline. No DB row on success;\n * error row only on failure. The \"next tick\" is the implicit retry.\n * - **`retry` declared** — enqueues a synthetic execution row and hands\n * it to the dispatcher. The handler then runs through the same path\n * as a queue/direct push (claim, retry-on-fail, sweep recovery). Use\n * this when a single failed tick must not block work for the whole\n * `cron` interval (e.g. once-daily jobs).\n */\n protected async runCronLocked(\n registration: JobRuntimeRegistration,\n ctx: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<void> {\n if (this.stopping) return;\n\n const useLock = registration.options.lock !== false;\n if (useLock) {\n const acquired = await this.acquireCronLock(registration);\n if (!acquired) {\n this.log.debug(\n `Cron '${registration.name}' skipped — another instance holds the lock`,\n );\n return;\n }\n }\n\n try {\n if (registration.options.retry) {\n await this.enqueueCronExecution(registration, ctx);\n return;\n }\n\n const executionId = crypto.randomUUID();\n const promise = this.executeInline(registration, executionId, {\n payload: undefined,\n attempt: 1,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n } finally {\n if (useLock) {\n await this.releaseCronLock(registration);\n }\n }\n }\n\n /**\n * Materialize a cron tick into the outbox so it goes through the normal\n * retry/sweep path. Used when the user opts into `retry` on a cron job —\n * a transient failure no longer means \"wait for the next cron tick\", it\n * means \"the sweep will retry within `sweepCron`\".\n */\n protected async enqueueCronExecution(\n registration: JobRuntimeRegistration,\n ctx: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<void> {\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n const execution = await this.executions.create({\n jobName: registration.name,\n payload: undefined,\n status: \"pending\",\n priority: PRIORITY_MAP[opts.priority ?? \"normal\"],\n maxAttempts,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n await this.dispatch(registration.name, execution.id);\n }\n\n /**\n * Acquire a per-job NX lock keyed by `cron-job:<name>` so that a single\n * tick across all replicas runs exactly one execution. Auto-expires after\n * `2 * timeout` (or 5 minutes if no per-job timeout) so a crashed worker\n * cannot permanently block the cron from firing.\n *\n * **Caveat — same-process double-fire is not prevented.** The lock value\n * is a per-process holder id, so two concurrent ticks on the same process\n * (e.g. a scheduled tick overlapping an admin `trigger()` call) will both\n * see \"we own it\". This is acceptable for the multi-replica use case the\n * lock targets; a process that overlaps its own cron handler should set a\n * smaller `timeout` or use idempotent handler logic. A future fix can add\n * a per-process Set guard before reaching the LockProvider.\n */\n protected async acquireCronLock(\n registration: JobRuntimeRegistration,\n ): Promise<boolean> {\n const lockKey = this.cronLockKey(registration.name);\n const ttlMs = registration.options.timeout\n ? this.dt.duration(registration.options.timeout).as(\"milliseconds\") * 2\n : 5 * 60 * 1000;\n const value = `${this.lockHolderId},${this.dt.nowISOString()}`;\n try {\n const stored = await this.lockProvider.set(lockKey, value, true, ttlMs);\n const [holderId] = stored.split(\",\");\n return holderId === this.lockHolderId;\n } catch (e) {\n this.log.warn(`Cron lock acquire failed for '${registration.name}'`, e);\n return true; // Fail-open: better to risk a duplicate than to silently miss a tick.\n }\n }\n\n /**\n * Update only when the row is still in one of the expected statuses.\n * Logs and returns silently when the guard rejects — this happens when a\n * concurrent operation (most often `cancel()`) has already moved the row\n * into a terminal state. We must not overwrite that.\n */\n protected async guardedUpdate(\n executionId: string,\n expectedStatuses: JobStatus[],\n patch: Parameters<typeof this.executions.updateById>[1],\n label: string,\n ): Promise<void> {\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { inArray: expectedStatuses } },\n patch,\n );\n } catch (e) {\n if (e instanceof DbEntityNotFoundError) {\n this.log.debug(\n `${label}: row ${executionId} not in expected status — skipping write`,\n );\n return;\n }\n throw e;\n }\n }\n\n protected async releaseCronLock(\n registration: JobRuntimeRegistration,\n ): Promise<void> {\n try {\n await this.lockProvider.del(this.cronLockKey(registration.name));\n } catch (e) {\n this.log.debug(\n `Cron lock release failed for '${registration.name}' (will expire by TTL)`,\n e,\n );\n }\n }\n\n protected cronLockKey(jobName: string): string {\n return `alepha.api.jobs.cron:${jobName}`;\n }\n\n /**\n * Stable per-process id used as the lock value — survives multiple ticks.\n * Lazy so that Cloudflare Workers (which forbid random in global scope)\n * stay happy.\n */\n protected lockHolderIdValue?: string;\n protected get lockHolderId(): string {\n if (!this.lockHolderIdValue) {\n this.lockHolderIdValue = crypto.randomUUID();\n }\n return this.lockHolderIdValue;\n }\n\n /**\n * Execute a cron handler inline. Records a row only on error (or always,\n * when `record: 'all'`). No DB writes on the happy path by default.\n */\n protected async executeInline(\n registration: JobRuntimeRegistration,\n executionId: string,\n ctx: {\n payload: unknown;\n attempt: number;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n const opts = registration.options;\n const name = registration.name;\n const record = opts.record ?? \"error\";\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const startedAt = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name,\n now: startedAt,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: ctx.payload,\n attempt: ctx.attempt,\n now: startedAt,\n signal: abortController.signal,\n executionId,\n });\n\n if (record === \"all\") {\n await this.writeTerminalRow(executionId, name, \"ok\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: undefined,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n if (record !== \"none\") {\n await this.writeTerminalRow(executionId, name, \"error\", {\n payload: ctx.payload,\n attempt: ctx.attempt,\n startedAt,\n error: err,\n context: contextId,\n triggeredBy: ctx.triggeredBy,\n triggeredByName: ctx.triggeredByName,\n });\n }\n await this.alepha.events.emit(\n \"job:error\",\n { name, error: err, executionId },\n { catch: true },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n protected async writeTerminalRow(\n executionId: string,\n jobName: string,\n status: \"ok\" | \"error\",\n fields: {\n payload: unknown;\n attempt: number;\n startedAt: ReturnType<DateTimeProvider[\"now\"]>;\n error?: Error;\n context: string;\n triggeredBy?: string;\n triggeredByName?: string;\n },\n ): Promise<void> {\n try {\n const logs =\n status === \"error\" ? this.snapshotLogs(fields.context) : undefined;\n await this.executions.create({\n id: executionId,\n jobName,\n status,\n payload: fields.payload as Record<string, unknown> | undefined,\n attempt: fields.attempt,\n maxAttempts: fields.attempt,\n startedAt: fields.startedAt.toISOString(),\n completedAt: this.dt.nowISOString(),\n error: fields.error?.message,\n logs,\n triggeredBy: fields.triggeredBy,\n triggeredByName: fields.triggeredByName,\n });\n } catch (e) {\n this.log.warn(`Failed to write terminal row for ${executionId}`, e);\n }\n }\n\n // --- Queue push -------------------------------------------------------------------------------------------------\n\n public async push(\n name: string,\n payload: unknown,\n options?: PushOptions,\n ): Promise<string> {\n const registration = this.getRegistration(name);\n if (registration.kind !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared). Use trigger() instead.`,\n );\n }\n const opts = registration.options;\n const validated = this.alepha.codec.validate(opts.schema!, payload);\n\n const priority =\n PRIORITY_MAP[options?.priority ?? opts.priority ?? \"normal\"];\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const isDelayed = options?.delay || options?.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n\n let scheduledAt: string | undefined;\n if (options?.scheduledAt) {\n scheduledAt = options.scheduledAt.toISOString();\n } else if (options?.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(options.delay))\n .toISOString();\n }\n\n if (options?.key) {\n // Key-based dedup: check for existing row first, then insert.\n // Two queries in the no-conflict path, but deterministic across dialects.\n const existing = await this.executions.findMany({\n where: { jobName: { eq: name }, key: { eq: options.key } },\n limit: 1,\n });\n if (existing.length > 0) {\n return existing[0].id;\n }\n const execution = await this.executions.create({\n jobName: name,\n key: options.key,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options.triggeredBy,\n triggeredByName: options.triggeredByName,\n });\n if (status === \"pending\") {\n await this.dispatch(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n const execution = await this.executions.create({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority,\n maxAttempts,\n scheduledAt,\n triggeredBy: options?.triggeredBy,\n triggeredByName: options?.triggeredByName,\n });\n\n if (status === \"pending\") {\n await this.dispatch(name, execution.id);\n } else if (status === \"scheduled\" && scheduledAt) {\n this.scheduleOptimisticDispatch(name, execution.id, scheduledAt);\n }\n return execution.id;\n }\n\n /**\n * Fire a local setTimeout so delayed/retrying rows dispatch as close to\n * `scheduledAt` as possible, rather than waiting for the next sweep tick.\n * No-op on stateless runtimes where timers won't survive (the sweep\n * handles those).\n */\n protected scheduleOptimisticDispatch(\n jobName: string,\n executionId: string,\n scheduledAt: string,\n ): void {\n const delayMs = Math.max(\n 0,\n new Date(scheduledAt).getTime() - this.dt.nowMillis(),\n );\n this.dt.createTimeout(() => {\n void this.dispatchScheduled(jobName, executionId);\n }, delayMs);\n }\n\n public async pushMany(\n name: string,\n items: Array<PushManyItem>,\n ): Promise<string[]> {\n if (items.length === 0) return [];\n\n const registration = this.getRegistration(name);\n if (registration.kind !== \"queue\") {\n throw new AlephaError(\n `Job '${name}' is not queue-mode (no schema declared).`,\n );\n }\n const opts = registration.options;\n const maxAttempts = (opts.retry?.retries ?? 0) + 1;\n\n const keyed: PushManyItem[] = [];\n const bulk: Array<{\n jobName: string;\n payload: Record<string, unknown>;\n status: JobStatus;\n priority: number;\n maxAttempts: number;\n scheduledAt?: string;\n }> = [];\n\n for (const item of items) {\n const validated = this.alepha.codec.validate(opts.schema!, item.payload);\n if (item.key) {\n keyed.push({ ...item, payload: validated as Static<TSchema> });\n continue;\n }\n const isDelayed = item.delay || item.scheduledAt;\n const status: JobStatus = isDelayed ? \"scheduled\" : \"pending\";\n let scheduledAt: string | undefined;\n if (item.scheduledAt) {\n scheduledAt = item.scheduledAt.toISOString();\n } else if (item.delay) {\n scheduledAt = this.dt\n .now()\n .add(this.dt.duration(item.delay))\n .toISOString();\n }\n bulk.push({\n jobName: name,\n payload: validated as Record<string, unknown>,\n status,\n priority: PRIORITY_MAP[item.priority ?? opts.priority ?? \"normal\"],\n maxAttempts,\n scheduledAt,\n });\n }\n\n const ids: string[] = [];\n\n for (const item of keyed) {\n const id = await this.push(name, item.payload, {\n key: item.key,\n delay: item.delay,\n priority: item.priority,\n scheduledAt: item.scheduledAt,\n });\n ids.push(id);\n }\n\n if (bulk.length > 0) {\n const created = await this.executions.createMany(bulk);\n // Collect pending rows so we hand them to the dispatcher in a single\n // batch — the queue dispatcher can fan them out in one network call.\n const toDispatch: Array<{ jobName: string; executionId: string }> = [];\n for (const exec of created) {\n ids.push(exec.id);\n if (exec.status === \"pending\" && !this.stopping) {\n toDispatch.push({ jobName: name, executionId: exec.id });\n } else if (\n exec.status === \"scheduled\" &&\n exec.scheduledAt &&\n !this.stopping\n ) {\n this.scheduleOptimisticDispatch(name, exec.id, exec.scheduledAt);\n }\n }\n if (toDispatch.length > 0) {\n await this.dispatchMany(toDispatch);\n }\n }\n\n this.log.debug(`pushMany '${name}': ${ids.length} jobs created`, {\n bulk: bulk.length,\n keyed: keyed.length,\n });\n\n return ids;\n }\n\n /**\n * Hand a single execution to the active `JobDispatcher`. Whether that\n * results in a queue send or in-process execution depends on which\n * dispatcher is wired (see {@link JobDispatcher}).\n */\n protected async dispatch(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n await this.dispatcher.dispatch(jobName, executionId);\n }\n\n /**\n * Batched variant. Used by `pushMany` so a backing queue can do a single\n * batch network call (e.g. Cloudflare Queues `sendBatch`).\n */\n protected async dispatchMany(\n items: Array<{ jobName: string; executionId: string }>,\n ): Promise<void> {\n if (this.stopping || items.length === 0) return;\n await this.dispatcher.dispatchMany(items);\n }\n\n // --- Manual trigger (admin / CLI) ------------------------------------------------------------------------------\n\n public async trigger(\n name: string,\n context?: JobTriggerContext,\n ): Promise<void> {\n const registration = this.getRegistration(name);\n\n if (registration.kind === \"cron\") {\n await this.runCronLocked(registration, {\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n return;\n }\n\n // queue-mode: treat as a normal push with the given payload\n if (!context?.payload) {\n throw new AlephaError(\n `Queue-mode job '${name}' requires a payload for manual trigger.`,\n );\n }\n await this.push(name, context.payload, {\n triggeredBy: context.triggeredBy,\n triggeredByName: context.triggeredByName,\n });\n }\n\n // --- Cancel ----------------------------------------------------------------------------------------------------\n\n public async cancel(\n executionId: string,\n context?: CancelContext,\n ): Promise<void> {\n const execution = await this.executions.findById(executionId);\n if (!execution) {\n throw new AlephaError(`Execution not found: ${executionId}`);\n }\n if (\n execution.status === \"ok\" ||\n execution.status === \"error\" ||\n execution.status === \"cancelled\"\n ) {\n throw new AlephaError(\n `Cannot cancel execution in '${execution.status}' status`,\n );\n }\n\n const controller = this.abortControllers.get(executionId);\n if (controller) controller.abort();\n\n await this.executions.updateById(executionId, {\n status: \"cancelled\",\n key: null,\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n completedAt: this.dt.nowISOString(),\n });\n\n this.log.info(`Cancelled execution ${executionId}`, {\n jobName: execution.jobName,\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n }\n\n // --- Queue consumer (called by JobQueueProvider) --------------------------------------------------------------\n\n public async processExecution(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n this.log.warn(`Unknown job '${jobName}' — skipping execution`, {\n executionId,\n });\n return;\n }\n // Both `queue` and cron-with-retry execute through the outbox path —\n // the DB-level `claim()` is the actual concurrency guard.\n if (registration.kind !== \"queue\" && !registration.options.retry) {\n this.log.warn(\n `Job '${jobName}' has no outbox path (no schema and no retry) — skipping`,\n { executionId },\n );\n return;\n }\n\n const promise = this.processQueueExecution(registration, executionId);\n this.inFlight.add(promise);\n try {\n await promise;\n } finally {\n this.inFlight.delete(promise);\n }\n }\n\n protected async processQueueExecution(\n registration: JobRuntimeRegistration,\n executionId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const record = opts.record ?? \"error\";\n\n const execution = await this.claim(executionId);\n if (!execution) {\n this.log.debug(`Execution ${executionId} already claimed, skipping`);\n return;\n }\n\n const contextId = this.alepha.context.createContextId();\n this.perExecutionLogs.set(contextId, []);\n\n const abortController = new AbortController();\n this.abortControllers.set(executionId, abortController);\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n if (opts.timeout) {\n const ms = this.dt.duration(opts.timeout).as(\"milliseconds\");\n timeoutId = setTimeout(() => abortController.abort(), ms);\n }\n\n const now = this.dt.now();\n\n try {\n await this.alepha.context.run(\n async () => {\n await this.alepha.events.emit(\"job:begin\", {\n name: jobName,\n now,\n executionId,\n });\n\n try {\n await opts.handler({\n payload: execution.payload,\n attempt: execution.attempt,\n now,\n signal: abortController.signal,\n executionId,\n });\n\n // Success: either DELETE (keepLastSuccess=0 or record=error)\n // or UPDATE to 'ok' (record=all and keepLastSuccess>0).\n const keepSuccess =\n record === \"all\" && this.config.keepLastSuccess > 0;\n if (keepSuccess) {\n await this.executions.updateById(executionId, {\n status: \"ok\",\n completedAt: this.dt.nowISOString(),\n key: null,\n });\n } else {\n await this.executions.deleteById(executionId);\n }\n\n await this.alepha.events.emit(\n \"job:success\",\n { name: jobName, executionId },\n { catch: true },\n );\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error));\n\n if (abortController.signal.aborted) {\n const current = await this.executions.findById(executionId);\n if (current?.status === \"cancelled\") {\n await this.alepha.events.emit(\n \"job:cancel\",\n { name: jobName, executionId },\n { catch: true },\n );\n return;\n }\n }\n\n await this.handleFailure(\n executionId,\n registration,\n execution.attempt,\n err,\n contextId,\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n this.abortControllers.delete(executionId);\n await this.alepha.events.emit(\n \"job:end\",\n { name: jobName, executionId },\n { catch: true },\n );\n }\n },\n { context: contextId },\n );\n } finally {\n this.perExecutionLogs.delete(contextId);\n }\n }\n\n /**\n * Transition pending → running and return the post-update row.\n * Two round-trips: read current attempt, then guarded UPDATE … RETURNING.\n * Returns null when the row is gone or already claimed by another worker.\n * The returned row replaces a separate post-claim findById, so the dispatch\n * path is 2 queries instead of 3.\n */\n protected async claim(executionId: string) {\n const current = await this.executions.findById(executionId);\n if (!current) return null;\n try {\n return await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"pending\" } },\n {\n status: \"running\",\n attempt: current.attempt + 1,\n startedAt: this.dt.nowISOString(),\n },\n );\n } catch (e) {\n if (e instanceof DbEntityNotFoundError) return null;\n throw e;\n }\n }\n\n protected async handleFailure(\n executionId: string,\n registration: JobRuntimeRegistration,\n currentAttempt: number,\n error: Error,\n contextId: string,\n ): Promise<void> {\n const jobName = registration.name;\n const opts = registration.options;\n const retry = opts.retry;\n const maxAttempts = (retry?.retries ?? 0) + 1;\n\n // `retries: 2` means \"1 initial + 2 retries = 3 total attempts\". Retry\n // while we have not yet *executed* the maxAttempts'th attempt — i.e.\n // currentAttempt (the one that just failed) is strictly less than\n // maxAttempts. The off-by-one fix: previously this was\n // `currentAttempt + 1 < maxAttempts`, which only ran 2 attempts for\n // `retries: 2`.\n const canRetry =\n retry &&\n currentAttempt < maxAttempts &&\n (retry.when ? retry.when(error) : true);\n\n if (canRetry) {\n // Retries are sweep-driven: write the row as `scheduled` with\n // `scheduledAt = now`. The next sweep tick (every `sweepCron`,\n // default 5 minutes) re-dispatches it. This means the first retry\n // can land anywhere from a few seconds to ~5 minutes later — the\n // exact moment depends on when the next sweep tick fires.\n //\n // We deliberately do NOT use exponential backoff or a local timer.\n // - Exponential backoff was platform-dependent (precise on Node\n // via setTimeout, but degraded to \"next sweep\" on Cloudflare\n // Workers where timers don't survive invocations). The behavior\n // is now identical everywhere.\n // - Sweep-only retry keeps the retry path observable from a single\n // place (the DB) and removes a class of races between the timer\n // and the sweep.\n const nextScheduledAt = this.dt.nowISOString();\n this.log.info(\n `Job '${jobName}' failed, scheduling retry ${currentAttempt + 1}/${maxAttempts} (sweep will pick up)`,\n { executionId, error: error.message },\n );\n // Guard with `status: running` so a concurrent cancel that has already\n // flipped the row to 'cancelled' is not overwritten by the retry write.\n await this.guardedUpdate(\n executionId,\n [\"running\"],\n {\n status: \"scheduled\",\n error: error.message,\n scheduledAt: nextScheduledAt,\n logs: this.snapshotLogs(contextId),\n },\n \"retry-after-failure\",\n );\n } else {\n this.log.info(\n `Job '${jobName}' dead after ${currentAttempt} attempt(s)`,\n { executionId, error: error.message },\n );\n await this.guardedUpdate(\n executionId,\n [\"running\"],\n {\n status: \"error\",\n error: error.message,\n completedAt: this.dt.nowISOString(),\n key: null,\n logs: this.snapshotLogs(contextId),\n },\n \"terminal-failure\",\n );\n }\n\n await this.alepha.events.emit(\n \"job:error\",\n { name: jobName, error, executionId },\n { catch: true },\n );\n }\n\n protected snapshotLogs(contextId: string): LogEntry[] | undefined {\n const entries = this.perExecutionLogs.get(contextId);\n if (!entries || entries.length === 0) return undefined;\n const max = this.config.logMaxEntries;\n if (max === 0) return undefined;\n if (entries.length <= max) return [...entries];\n const truncated = entries.slice(0, max);\n truncated.push({\n level: \"WARN\",\n message: `Log entries truncated at ${max}`,\n timestamp: this.dt.nowMillis(),\n service: \"alepha.jobs\",\n module: \"JobProvider\",\n } as LogEntry);\n return truncated;\n }\n\n // --- Sweep ----------------------------------------------------------------------------------------------------\n\n protected async sweep(): Promise<void> {\n if (this.stopping) return;\n this.log.trace(\"Starting job sweep\");\n const now = this.dt.now();\n const nowIso = now.toISOString();\n\n try {\n // 1. Due scheduled rows → pending + dispatch\n const dueWhere = this.executions.createQueryWhere();\n dueWhere.status = { eq: \"scheduled\" };\n dueWhere.scheduledAt = { lte: nowIso };\n const due = await this.executions.findMany({\n where: dueWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of due) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.executions.updateById(exec.id, { status: \"pending\" });\n await this.dispatchSafe(exec.jobName, exec.id);\n }\n\n // 2. Stale pending rows → re-dispatch\n const staleIso = now\n .subtract(this.config.staleThreshold, \"millisecond\")\n .toISOString();\n const staleWhere = this.executions.createQueryWhere();\n staleWhere.status = { eq: \"pending\" };\n staleWhere.createdAt = { lte: staleIso };\n const stale = await this.executions.findMany({\n where: staleWhere,\n orderBy: { column: \"priority\", direction: \"asc\" },\n });\n for (const exec of stale) {\n if (!this.jobs.has(exec.jobName)) continue;\n await this.dispatchSafe(exec.jobName, exec.id);\n }\n\n // 3. Crashed running rows → mark as failed + apply retry\n const runningWhere = this.executions.createQueryWhere();\n runningWhere.status = { eq: \"running\" };\n const running = await this.executions.findMany({ where: runningWhere });\n const nowMs = now.valueOf();\n for (const exec of running) {\n const reg = this.jobs.get(exec.jobName);\n if (!reg) continue;\n if (this.abortControllers.has(exec.id)) continue; // still alive locally\n const crashThresholdMs = reg.options.timeout\n ? this.dt.duration(reg.options.timeout).as(\"milliseconds\") * 2\n : this.config.runTimeout;\n const startedAtMs = exec.startedAt\n ? new Date(exec.startedAt).getTime()\n : 0;\n if (startedAtMs > 0 && nowMs - startedAtMs > crashThresholdMs) {\n this.log.warn(\n `Sweep: marking crashed ${exec.jobName} (${exec.id}) as failed`,\n );\n const err = new Error(\n \"Execution assumed crashed (recovered by sweep)\",\n );\n await this.handleFailure(exec.id, reg, exec.attempt, err, \"\");\n }\n }\n } catch (e) {\n this.log.error(\"Sweep failed\", { error: e });\n }\n }\n\n protected async dispatchSafe(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n try {\n await this.dispatch(jobName, executionId);\n } catch (e) {\n this.log.warn(`Sweep failed to dispatch ${jobName} (${executionId})`, e);\n }\n }\n\n /**\n * Move a row from `scheduled` → `pending` and dispatch it.\n * Used by the optimistic retry/delay timer. If the sweep has already moved\n * the row, or another worker has claimed it, the UPDATE guard fails silently.\n */\n protected async dispatchScheduled(\n jobName: string,\n executionId: string,\n ): Promise<void> {\n if (this.stopping) return;\n try {\n await this.executions.updateOne(\n { id: { eq: executionId }, status: { eq: \"scheduled\" } },\n { status: \"pending\" },\n );\n await this.dispatchSafe(jobName, executionId);\n } catch {\n // Row already transitioned (sweep ran, another worker claimed, etc.)\n }\n }\n\n protected async trimRingBuffers(): Promise<void> {\n for (const [jobName, reg] of this.jobs) {\n const okLimit = reg.options.keep?.ok ?? this.config.keepLastSuccess;\n const errLimit = reg.options.keep?.error ?? this.config.keepLastError;\n if (okLimit > 0) {\n await this.trimByStatus(jobName, \"ok\", okLimit);\n }\n if (errLimit > 0) {\n await this.trimByStatus(jobName, \"error\", errLimit);\n }\n }\n }\n\n protected async trimByStatus(\n jobName: string,\n status: \"ok\" | \"error\",\n keep: number,\n ): Promise<void> {\n try {\n const rows = await this.executions.findMany({\n where: { jobName: { eq: jobName }, status: { eq: status } },\n orderBy: { column: \"createdAt\", direction: \"desc\" },\n limit: keep + 50,\n });\n if (rows.length <= keep) return;\n const toDelete = rows.slice(keep).map((r) => r.id);\n if (toDelete.length > 0) {\n await this.executions.deleteMany({ id: { inArray: toDelete } });\n this.log.debug(\n `Trimmed ${toDelete.length} ${status} rows for '${jobName}'`,\n );\n }\n } catch (e) {\n this.log.warn(`Failed to trim ${status} rows for '${jobName}'`, e);\n }\n }\n\n // --- Lifecycle -----------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Summarize effective modes once at start so operators can see at a\n // glance what each job will do. No validation is needed here: queue\n // jobs gracefully fall back to direct mode when AlephaApiJobsQueue\n // isn't loaded.\n const modes: Record<JobEffectiveMode, number> = {\n cron: 0,\n queue: 0,\n direct: 0,\n };\n const perJob: Record<string, JobEffectiveMode> = {};\n for (const [name] of this.jobs) {\n const m = this.effectiveMode(name);\n modes[m]++;\n perJob[name] = m;\n }\n this.log.info(`Job system OK`, {\n modes,\n jobs: this.jobs.size,\n perJob,\n });\n\n // Capture logs per execution context.\n this.alepha.events.on(\"log\", ({ entry }) => {\n const ctx = entry.context;\n if (!ctx) return;\n const entries = this.perExecutionLogs.get(ctx);\n if (!entries) return;\n entries.push(entry);\n });\n\n if (!this.alepha.isServerless()) {\n await this.sweep();\n }\n\n this.cronProvider.createCronJob(\n \"api:jobs:sweep\",\n this.config.sweepCron,\n async () => {\n await this.sweep();\n },\n true,\n );\n\n this.cronProvider.createCronJob(\n \"api:jobs:trim\",\n this.config.trimCron,\n async () => {\n if (this.stopping) return;\n try {\n await this.trimRingBuffers();\n } catch (e) {\n this.log.error(\"Trim failed\", { error: e });\n }\n },\n true,\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: async () => {\n this.stopping = true;\n if (this.inFlight.size > 0) {\n this.log.info(`Draining ${this.inFlight.size} in-flight job(s)...`);\n await Promise.race([\n Promise.allSettled([...this.inFlight]),\n this.dt.wait([this.config.drainTimeout, \"millisecond\"]),\n ]);\n }\n if (this.abortControllers.size > 0) {\n this.log.warn(\n `Aborting ${this.abortControllers.size} remaining job(s) after drain timeout`,\n );\n for (const controller of this.abortControllers.values()) {\n controller.abort();\n }\n }\n },\n });\n\n // --- Helpers -------------------------------------------------------------------------------------------------\n\n protected getRegistration(name: string): JobRuntimeRegistration {\n const registration = this.jobs.get(name);\n if (!registration) {\n throw new AlephaError(`Job not registered: ${name}`);\n }\n return registration;\n }\n}\n\nexport { PRIORITY_MAP, PRIORITY_REVERSE };\n","import {\n $inject,\n type Async,\n createPrimitive,\n KIND,\n PipelinePrimitive,\n type PipelinePrimitiveOptions,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DateTime, DurationLike } from \"alepha/datetime\";\nimport {\n JobProvider,\n type JobTriggerContext,\n type PushManyItem,\n type PushOptions,\n} from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive for defining scheduled (cron) or queued (push) tasks.\n *\n * A job must be either **cron-only** (pass `cron`) or **queue-only**\n * (pass `schema`), never both. To run scheduled work that processes\n * payloads, compose two jobs: a cron that pushes payloads, and a\n * queue job that handles them.\n */\nexport const $job = <T extends TSchema = TSchema>(\n options: JobPrimitiveOptions<T>,\n): JobPrimitive<T> => {\n return createPrimitive(JobPrimitive<T>, options);\n};\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArgs<T extends TSchema = TSchema> {\n payload: Static<T>;\n attempt: number;\n now: DateTime;\n signal: AbortSignal;\n executionId: string;\n}\n\nexport interface JobRetryOptions {\n retries: number;\n when?: (error: Error) => boolean;\n}\n\nexport type JobPriority = \"critical\" | \"high\" | \"normal\" | \"low\";\n\nexport interface JobPrimitiveOptions<T extends TSchema = TSchema>\n extends PipelinePrimitiveOptions {\n /**\n * Optional explicit job name. Defaults to `ClassName.propertyKey`.\n * Recommended convention for framework-internal jobs: `api:module:jobName`.\n */\n name?: string;\n\n /**\n * Human-readable description (shown in the admin UI).\n */\n description?: string;\n\n /**\n * Payload schema (TypeBox). When set, the job is queue-mode.\n * Must not be combined with `cron`.\n */\n schema?: T;\n\n /**\n * Cron expression for recurring execution. When set, the job is cron-mode.\n * Must not be combined with `schema`.\n */\n cron?: string;\n\n /**\n * Retry policy for queue-mode and direct-mode jobs.\n * Cron-mode jobs do not retry — the next tick re-runs.\n *\n * Retries are picked up by the reconciliation sweep, so retry granularity\n * is bounded by `sweepCron` (default 5 minutes). The first retry may run\n * earlier than 5 minutes if the sweep tick happens sooner.\n */\n retry?: JobRetryOptions;\n\n /**\n * **Cron-mode only.** Whether to acquire a distributed lock around the\n * cron tick so that only one instance of a multi-replica deployment runs\n * the handler per tick.\n *\n * Has **no effect** on queue-mode and direct-mode jobs — those rely on\n * the outbox `claim()` UPDATE-guard to serialize work instead, which is\n * always on.\n *\n * To get cross-instance coordination on Docker / Node deployments,\n * register a real `LockProvider` (e.g. `alepha/lock/redis`). The default\n * `MemoryLockProvider` is per-process only.\n *\n * @default true\n */\n lock?: boolean;\n\n /**\n * Max execution time per attempt. Handler receives an `AbortSignal`.\n */\n timeout?: DurationLike;\n\n /**\n * Default priority for pushed jobs. Used by the sweep to order\n * dispatch when there is a backlog. Real-time queue consumption\n * is FIFO.\n * @default \"normal\"\n */\n priority?: JobPriority;\n\n /**\n * Whether to record successful executions.\n *\n * - `\"error\"` (default for cron, default for queue): only error/cancelled rows kept\n * - `\"all\"`: keep success rows too (bounded by `keepLastSuccess`)\n * - `\"none\"`: fire-and-forget, no row even on error\n *\n * Note: queue-mode jobs always write a `pending` row at push time (outbox).\n * This setting controls whether that row is kept on success.\n */\n record?: \"error\" | \"all\" | \"none\";\n\n /**\n * Override the global ring-buffer trim for this job.\n *\n * - `{ ok: 0, error: 0 }` — **keep forever** (no sweep trim). Useful for\n * audit-heavy jobs where retention is time-based (handled by a separate\n * cron) rather than count-based.\n * - `{ ok: 50 }` — keep last 50 successes; fall back to global default for errors.\n * - omitted — use global `keepLastSuccess` / `keepLastError` from `jobConfig`.\n */\n keep?: {\n ok?: number;\n error?: number;\n };\n\n /**\n * Handler function. For cron-mode, `payload` is `undefined`.\n */\n handler: (args: JobHandlerArgs<T>) => Async<void>;\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive<\n T extends TSchema = TSchema,\n> extends PipelinePrimitive<JobPrimitiveOptions<T>> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n const handler = this.handler.run.bind(this.handler);\n this.jobProvider.registerJob(this.name, { ...this.options, handler });\n }\n\n /**\n * Push a single payload to the queue (queue-mode only).\n */\n public async push(\n payload: Static<T>,\n options?: PushOptions,\n ): Promise<string> {\n return this.jobProvider.push(this.name, payload, options);\n }\n\n /**\n * Push multiple payloads at once (queue-mode only).\n * Batched INSERT + batched queue send when supported.\n */\n public async pushMany(items: Array<PushManyItem<T>>): Promise<string[]> {\n return this.jobProvider.pushMany(this.name, items);\n }\n\n /**\n * Cancel a pending or running execution.\n */\n public async cancel(executionId: string): Promise<void> {\n return this.jobProvider.cancel(executionId);\n }\n\n /**\n * Manually fire a cron-mode job, or trigger a queue-mode job with an explicit payload.\n */\n public async trigger(context?: JobTriggerContext<T>): Promise<void> {\n return this.jobProvider.trigger(this.name, context);\n }\n}\n\n$job[KIND] = JobPrimitive;\n","import { $inject, Alepha, AlephaError, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { NotFoundError } from \"alepha/server\";\nimport { jobExecutionEntity } from \"../entities/jobExecutionEntity.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobTriggerContext } from \"../providers/JobProvider.ts\";\nimport { JobProvider, PRIORITY_REVERSE } from \"../providers/JobProvider.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport type { JobExecutionResource } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport type { JobRegistration } from \"../schemas/jobRegistrationSchema.ts\";\n\n/**\n * Admin surface for the job system.\n *\n * Six methods: list jobs, list executions, get execution,\n * trigger, retry, cancel. Everything else lives in events — any\n * analytics/observability is an external concern that subscribes\n * to `job:begin` / `job:success` / `job:error`.\n */\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly jobProvider = $inject(JobProvider);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected computeCan(status: string) {\n return {\n retry: status === \"error\" || status === \"cancelled\",\n cancel:\n status === \"pending\" || status === \"running\" || status === \"scheduled\",\n };\n }\n\n /**\n * Convert the int-priority storage column into the public enum string.\n * The cast through `unknown` skips TypeScript's structural check between\n * the entity-level row (`priority: number`) and the resource schema\n * (`priority: enum`); the runtime values are correct.\n */\n protected toResource<T extends { priority: number; status: string }>(\n row: T,\n ): JobExecutionResource {\n return {\n ...row,\n priority: PRIORITY_REVERSE[row.priority] ?? \"normal\",\n can: this.computeCan(row.status),\n } as unknown as JobExecutionResource;\n }\n\n /**\n * List every registered job with recent ok/error counts and lastRun.\n * One aggregate query covers all jobs.\n */\n public async listJobs(): Promise<JobRegistration[]> {\n const registry = this.jobProvider.getRegisteredJobs();\n\n const aggRows = await this.executions.query(\n (e) => sql`\n SELECT\n ${e.jobName} AS job_name,\n ${e.status} AS status,\n COUNT(*) AS count,\n MAX(${e.completedAt}) AS last_run\n FROM ${e}\n WHERE ${e.status} IN ('ok', 'error')\n GROUP BY ${e.jobName}, ${e.status}\n `,\n t.object({\n job_name: t.string(),\n status: t.string(),\n count: t.string(),\n last_run: t.optional(t.nullable(t.union([t.string(), t.number()]))),\n }),\n );\n\n const toIso = (\n v: string | number | null | undefined,\n ): string | undefined => {\n if (v === null || v === undefined) return undefined;\n if (typeof v === \"number\") return new Date(v).toISOString();\n return v;\n };\n\n const byJob = new Map<\n string,\n { ok: number; error: number; lastRun?: string }\n >();\n for (const row of aggRows) {\n const entry = byJob.get(row.job_name) ?? { ok: 0, error: 0 };\n if (row.status === \"ok\") entry.ok = Number(row.count);\n if (row.status === \"error\") entry.error = Number(row.count);\n const iso = toIso(row.last_run);\n if (iso && (!entry.lastRun || iso > entry.lastRun)) {\n entry.lastRun = iso;\n }\n byJob.set(row.job_name, entry);\n }\n\n const result: JobRegistration[] = [];\n for (const [name, reg] of registry) {\n const opts = reg.options;\n const counts = byJob.get(name) ?? { ok: 0, error: 0 };\n result.push({\n name,\n description: opts.description,\n type: this.jobProvider.effectiveMode(name),\n cron: opts.cron,\n priority: (opts.priority ?? \"normal\") as JobRegistration[\"priority\"],\n timeout: opts.timeout ? String(opts.timeout) : undefined,\n retry: opts.retry\n ? {\n retries: opts.retry.retries,\n }\n : undefined,\n recent: counts,\n });\n }\n return result;\n }\n\n /**\n * Recent executions for a single job, ORDER BY startedAt DESC.\n */\n public async getExecutions(jobName: string, query: JobExecutionQuery = {}) {\n const registry = this.jobProvider.getRegisteredJobs();\n if (!registry.has(jobName)) {\n throw new NotFoundError(`Job not found: ${jobName}`);\n }\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: jobName };\n if (query.status) {\n where.status = { eq: query.status };\n }\n const rows = await this.executions.findMany({\n where,\n orderBy: { column: \"startedAt\", direction: \"desc\" },\n limit: query.limit ?? 20,\n });\n return rows.map((row) => this.toResource(row));\n }\n\n /**\n * Full execution detail (includes captured logs).\n */\n public async getExecution(id: string) {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n return this.toResource(execution);\n }\n\n /**\n * Manual trigger (cron jobs) or push-with-payload (queue jobs).\n */\n public async triggerJob(\n name: string,\n context?: JobTriggerContext,\n ): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n if (!job) {\n throw new NotFoundError(`Job not found: ${name}`);\n }\n this.log.info(`Triggering job '${name}'`, {\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n await job.trigger(context);\n return { ok: true };\n }\n\n /**\n * Retry a dead or cancelled execution by re-pushing with the original payload.\n */\n public async retryExecution(\n id: string,\n context?: { triggeredBy?: string; triggeredByName?: string },\n ): Promise<{ ok: boolean }> {\n const execution = await this.executions.findById(id);\n if (!execution) {\n throw new NotFoundError(`Execution not found: ${id}`);\n }\n if (execution.status !== \"error\" && execution.status !== \"cancelled\") {\n throw new AlephaError(\n `Cannot retry execution in '${execution.status}' status`,\n );\n }\n\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === execution.jobName);\n if (!job) {\n throw new NotFoundError(`Job not found: ${execution.jobName}`);\n }\n\n this.log.info(`Retrying execution ${id}`, {\n jobName: execution.jobName,\n previousStatus: execution.status,\n triggeredBy: context?.triggeredByName ?? context?.triggeredBy,\n });\n\n if (execution.payload) {\n await job.push(execution.payload as any);\n } else {\n await job.trigger({\n triggeredBy: context?.triggeredBy,\n triggeredByName: context?.triggeredByName,\n });\n }\n return { ok: true };\n }\n\n public async cancelExecution(\n id: string,\n context?: { cancelledBy?: string; cancelledByName?: string },\n ): Promise<{ ok: boolean }> {\n this.log.info(`Cancelling execution ${id}`, {\n cancelledBy: context?.cancelledByName ?? context?.cancelledBy,\n });\n await this.jobProvider.cancel(id, {\n cancelledBy: context?.cancelledBy,\n cancelledByName: context?.cancelledByName,\n });\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { jobRegistrationSchema } from \"../schemas/jobRegistrationSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\n/**\n * Minimal admin surface for the job system. Six endpoints.\n */\nexport class AdminJobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"admin:jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly listJobs = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n response: t.array(jobRegistrationSchema),\n },\n handler: () => this.jobService.listJobs(),\n });\n\n public readonly listExecutions = $action({\n path: `${this.url}/:name/executions`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n query: jobExecutionQuerySchema,\n response: t.array(jobExecutionResourceSchema),\n },\n handler: ({ params, query }) =>\n this.jobService.getExecutions(params.name, query),\n });\n\n public readonly getExecution = $action({\n path: `${this.url}/executions/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:read\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: jobExecutionResourceSchema,\n },\n handler: ({ params }) => this.jobService.getExecution(params.id),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/:name/trigger`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ name: t.text() }),\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ params, body, user }) =>\n this.jobService.triggerJob(params.name, {\n payload: body.payload,\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly retryExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/retry`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:trigger\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.retryExecution(params.id, {\n triggeredBy: user?.id,\n triggeredByName: user?.name,\n }),\n });\n\n public readonly cancelExecution = $action({\n method: \"POST\",\n path: `${this.url}/executions/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:job:cancel\"] })],\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: ({ params, user }) =>\n this.jobService.cancelExecution(params.id, {\n cancelledBy: user?.id,\n cancelledByName: user?.name,\n }),\n });\n}\n","import { $module } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { AlephaLock } from \"alepha/lock\";\nimport { AlephaQueue } from \"alepha/queue\";\nimport { AlephaScheduler } from \"alepha/scheduler\";\nimport { AdminJobController } from \"./controllers/AdminJobController.ts\";\nimport { $job } from \"./primitives/$job.ts\";\nimport { DirectJobDispatcher } from \"./providers/DirectJobDispatcher.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobQueueProvider } from \"./providers/JobQueueProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminJobController.ts\";\nexport * from \"./entities/jobExecutionEntity.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/DirectJobDispatcher.ts\";\nexport * from \"./providers/JobDispatcher.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./providers/JobQueueProvider.ts\";\nexport * from \"./schemas/jobConfigAtom.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/jobRegistrationSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// -----------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"job:begin\": { name: string; now: DateTime; executionId: string };\n \"job:success\": { name: string; executionId: string };\n \"job:error\": { name: string; error: Error; executionId: string };\n \"job:cancel\": { name: string; executionId: string };\n \"job:end\": { name: string; executionId: string };\n }\n}\n\n// -----------------------------------------------------------------------------------------------------------------\n\n/**\n * Job execution framework — cron and durable queue work with a single primitive.\n *\n * A `$job` is either **cron-only** (declares `cron`) or **payload-only** (declares `schema`).\n *\n * **Three runtime modes:**\n *\n * - **cron** — fires on a schedule. Cron-mode jobs are protected by a\n * distributed lock by default (`lock: true`), so multi-replica Docker\n * deployments only run the handler once per tick. Override with\n * `lock: false` if you genuinely want every replica to fire.\n * - **queue** — push-driven, dispatched through the queue infrastructure\n * (`AlephaQueue`, e.g. Cloudflare Queues, Redis). Real-time delivery,\n * ideal for high-volume systems. Requires `AlephaApiJobsQueue`.\n * - **direct** — push-driven, processed in-process right after the caller\n * awaits the push. The DB outbox row is the durability guarantee — if\n * the process dies, the reconciliation sweep re-dispatches. Default\n * when `AlephaApiJobsQueue` is *not* loaded. Best for cheap deployments\n * (Cloudflare Workers, single-instance Node) where standing up a queue\n * is overkill.\n *\n * **Retries** are sweep-driven across all modes (no exponential backoff).\n * Granularity is bounded by `sweepCron` (default 5 min). The first retry\n * may land anywhere from a few seconds to ~5 min later depending on when\n * the next sweep tick fires. Cron jobs that declare `retry` go through\n * the same sweep path — a transient failure no longer means waiting for\n * the next cron tick (useful for once-daily jobs).\n *\n * **Runtime support for cron triggers**\n *\n * - **Long-running Node / Docker** — `CronProvider` runs an in-process\n * timer loop. Multi-replica deployments serialize ticks via the cron\n * lock (see `$job.lock`).\n * - **Cloudflare Workers** — the build emits cron expressions into\n * `wrangler.jsonc`; Cloudflare invokes the worker on schedule and the\n * `cloudflare:scheduled` hook routes the event to the matching jobs.\n * - **Vercel** — the build emits cron entries into\n * `.vercel/output/config.json` mapped to `/_alepha/cron/:name`; the\n * serverless handler emits `serverless:cron` and `CronProvider` runs\n * the matching job. Set `CRON_SECRET` to require authenticated calls.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n primitives: [$job],\n imports: [AlephaScheduler, AlephaLock],\n services: [JobProvider, JobService, AdminJobController, DirectJobDispatcher],\n});\n\n/**\n * Queue support for `$job`. Import alongside {@link AlephaApiJobs} when your\n * app declares queue-mode jobs (any `$job` with a `schema`) and you want a\n * real queue (e.g. Cloudflare Queues, Redis) instead of in-process direct\n * execution.\n *\n * Adds `JobQueueProvider` to the container. `JobProvider` detects its\n * presence at start-up and routes dispatches through it.\n *\n * @module alepha.api.jobs.queue\n */\nexport const AlephaApiJobsQueue = $module({\n name: \"alepha.api.jobs.queue\",\n imports: [AlephaApiJobs, AlephaQueue],\n services: [JobQueueProvider],\n});\n"],"mappings":"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"}