alepha 0.20.1 → 0.20.2

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 (232) hide show
  1. package/dist/api/files/index.js +2 -1
  2. package/dist/api/files/index.js.map +1 -1
  3. package/dist/api/jobs/index.browser.js +64 -148
  4. package/dist/api/jobs/index.browser.js.map +1 -1
  5. package/dist/api/jobs/index.d.ts +371 -573
  6. package/dist/api/jobs/index.d.ts.map +1 -1
  7. package/dist/api/jobs/index.js +605 -1012
  8. package/dist/api/jobs/index.js.map +1 -1
  9. package/dist/api/notifications/index.d.ts +78 -17
  10. package/dist/api/notifications/index.d.ts.map +1 -1
  11. package/dist/api/notifications/index.js +90 -23
  12. package/dist/api/notifications/index.js.map +1 -1
  13. package/dist/api/payments/index.d.ts +2 -1
  14. package/dist/api/payments/index.d.ts.map +1 -1
  15. package/dist/api/payments/index.js +4 -2
  16. package/dist/api/payments/index.js.map +1 -1
  17. package/dist/api/users/index.d.ts +34 -31
  18. package/dist/api/users/index.d.ts.map +1 -1
  19. package/dist/api/users/index.js +13 -7
  20. package/dist/api/users/index.js.map +1 -1
  21. package/dist/api/verifications/index.js +2 -1
  22. package/dist/api/verifications/index.js.map +1 -1
  23. package/dist/cli/core/index.d.ts +8 -34
  24. package/dist/cli/core/index.d.ts.map +1 -1
  25. package/dist/cli/core/index.js +43 -232
  26. package/dist/cli/core/index.js.map +1 -1
  27. package/dist/cli/platform/index.d.ts +36 -11
  28. package/dist/cli/platform/index.d.ts.map +1 -1
  29. package/dist/cli/platform/index.js +93 -27
  30. package/dist/cli/platform/index.js.map +1 -1
  31. package/dist/command/index.d.ts +1 -1
  32. package/dist/core/index.browser.js +6 -0
  33. package/dist/core/index.browser.js.map +1 -1
  34. package/dist/core/index.d.ts +6 -0
  35. package/dist/core/index.d.ts.map +1 -1
  36. package/dist/core/index.js +6 -0
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/index.native.js +6 -0
  39. package/dist/core/index.native.js.map +1 -1
  40. package/dist/core/index.workerd.js +6 -0
  41. package/dist/core/index.workerd.js.map +1 -1
  42. package/dist/react/form/index.d.ts +60 -1
  43. package/dist/react/form/index.d.ts.map +1 -1
  44. package/dist/react/form/index.js +86 -1
  45. package/dist/react/form/index.js.map +1 -1
  46. package/dist/react/head/index.browser.js +16 -1
  47. package/dist/react/head/index.browser.js.map +1 -1
  48. package/dist/react/head/index.d.ts +6 -0
  49. package/dist/react/head/index.d.ts.map +1 -1
  50. package/dist/react/head/index.js +16 -1
  51. package/dist/react/head/index.js.map +1 -1
  52. package/dist/react/router/index.browser.js +0 -10
  53. package/dist/react/router/index.browser.js.map +1 -1
  54. package/dist/react/router/index.d.ts +35 -12
  55. package/dist/react/router/index.d.ts.map +1 -1
  56. package/dist/react/router/index.js +0 -10
  57. package/dist/react/router/index.js.map +1 -1
  58. package/dist/react/ui/index.d.ts +124 -0
  59. package/dist/react/ui/index.d.ts.map +1 -0
  60. package/dist/react/ui/index.js +206 -0
  61. package/dist/react/ui/index.js.map +1 -0
  62. package/dist/router/index.d.ts +13 -13
  63. package/dist/router/index.d.ts.map +1 -1
  64. package/dist/router/index.js +45 -32
  65. package/dist/router/index.js.map +1 -1
  66. package/dist/system/index.d.ts.map +1 -1
  67. package/dist/system/index.js +1 -0
  68. package/dist/system/index.js.map +1 -1
  69. package/dist/topic/core/index.js +1 -1
  70. package/dist/topic/core/index.js.map +1 -1
  71. package/package.json +6 -23
  72. package/src/api/files/jobs/FileJobs.ts +2 -1
  73. package/src/api/jobs/__tests__/$job.spec.ts +316 -2867
  74. package/src/api/jobs/controllers/AdminJobController.ts +29 -138
  75. package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
  76. package/src/api/jobs/index.browser.ts +5 -7
  77. package/src/api/jobs/index.ts +23 -51
  78. package/src/api/jobs/primitives/$job.ts +66 -58
  79. package/src/api/jobs/providers/JobProvider.ts +561 -566
  80. package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
  81. package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
  82. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
  83. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
  84. package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
  85. package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
  86. package/src/api/jobs/services/JobService.ts +90 -483
  87. package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
  88. package/src/api/notifications/index.ts +7 -4
  89. package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
  90. package/src/api/payments/services/PaymentService.ts +4 -2
  91. package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
  92. package/src/api/users/audits/UserAudits.ts +3 -1
  93. package/src/api/users/buckets/UserBuckets.ts +2 -1
  94. package/src/api/users/index.ts +1 -4
  95. package/src/api/users/jobs/UserJobs.ts +5 -4
  96. package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
  97. package/src/cli/core/__tests__/init.spec.ts +1 -1
  98. package/src/cli/core/commands/init.ts +0 -12
  99. package/src/cli/core/services/PackageManagerUtils.ts +2 -9
  100. package/src/cli/core/services/ProjectScaffolder.ts +17 -65
  101. package/src/cli/core/templates/agentMd.ts +2 -8
  102. package/src/cli/core/templates/apiIndexTs.ts +4 -18
  103. package/src/cli/core/templates/mainCss.ts +1 -36
  104. package/src/cli/core/templates/vitestConfigTs.ts +17 -0
  105. package/src/cli/core/templates/webAppRouterTs.ts +2 -85
  106. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
  107. package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
  108. package/src/cli/platform/atoms/platformOptions.ts +9 -0
  109. package/src/cli/platform/schemas/cloudflare.ts +3 -2
  110. package/src/cli/platform/services/CloudflareApi.ts +164 -25
  111. package/src/cli/platform/services/WranglerApi.ts +0 -17
  112. package/src/core/Alepha.ts +9 -0
  113. package/src/react/form/index.ts +2 -0
  114. package/src/react/form/services/parseField.ts +163 -0
  115. package/src/react/form/services/prettyName.ts +19 -0
  116. package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
  117. package/src/react/router/primitives/$page.ts +35 -12
  118. package/src/react/ui/atoms/uiAtom.ts +28 -0
  119. package/src/react/ui/components/ColorScheme.tsx +36 -0
  120. package/src/react/ui/hooks/useColorMode.ts +49 -0
  121. package/src/react/ui/hooks/useSidebarState.ts +26 -0
  122. package/src/react/ui/hooks/useTheme.ts +22 -0
  123. package/src/react/ui/index.ts +35 -0
  124. package/src/react/ui/services/UiPersistence.ts +41 -0
  125. package/src/router/TemplatedPathParser.ts +50 -51
  126. package/src/router/__tests__/RouterProvider.spec.ts +62 -0
  127. package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
  128. package/src/router/providers/RouterProvider.ts +10 -5
  129. package/src/system/providers/NodeShellProvider.ts +1 -0
  130. package/src/topic/core/providers/TopicProvider.ts +1 -1
  131. package/dist/api/invitations/index.d.ts +0 -790
  132. package/dist/api/invitations/index.d.ts.map +0 -1
  133. package/dist/api/invitations/index.js +0 -662
  134. package/dist/api/invitations/index.js.map +0 -1
  135. package/dist/api/issues/index.d.ts +0 -810
  136. package/dist/api/issues/index.d.ts.map +0 -1
  137. package/dist/api/issues/index.js +0 -444
  138. package/dist/api/issues/index.js.map +0 -1
  139. package/dist/api/subscriptions/index.d.ts +0 -1692
  140. package/dist/api/subscriptions/index.d.ts.map +0 -1
  141. package/dist/api/subscriptions/index.js +0 -1867
  142. package/dist/api/subscriptions/index.js.map +0 -1
  143. package/dist/api/workflows/index.browser.js +0 -246
  144. package/dist/api/workflows/index.browser.js.map +0 -1
  145. package/dist/api/workflows/index.d.ts +0 -1618
  146. package/dist/api/workflows/index.d.ts.map +0 -1
  147. package/dist/api/workflows/index.js +0 -1495
  148. package/dist/api/workflows/index.js.map +0 -1
  149. package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
  150. package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
  151. package/src/api/invitations/controllers/InvitationController.ts +0 -84
  152. package/src/api/invitations/entities/invitations.ts +0 -33
  153. package/src/api/invitations/index.ts +0 -58
  154. package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
  155. package/src/api/invitations/providers/InvitationProvider.ts +0 -45
  156. package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
  157. package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
  158. package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
  159. package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
  160. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
  161. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
  162. package/src/api/invitations/services/InvitationService.ts +0 -556
  163. package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
  164. package/src/api/issues/controllers/AdminIssueController.ts +0 -149
  165. package/src/api/issues/controllers/IssueController.ts +0 -44
  166. package/src/api/issues/entities/issues.ts +0 -49
  167. package/src/api/issues/index.ts +0 -50
  168. package/src/api/issues/schemas/createIssueSchema.ts +0 -13
  169. package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
  170. package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
  171. package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
  172. package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
  173. package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
  174. package/src/api/issues/services/IssueService.ts +0 -264
  175. package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
  176. package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
  177. package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
  178. package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
  179. package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
  180. package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
  181. package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
  182. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
  183. package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
  184. package/src/api/jobs/services/JobService-tests.ts +0 -157
  185. package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
  186. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
  187. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
  188. package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
  189. package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
  190. package/src/api/subscriptions/entities/subscriptions.ts +0 -68
  191. package/src/api/subscriptions/index.ts +0 -133
  192. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
  193. package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
  194. package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
  195. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
  196. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
  197. package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
  198. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
  199. package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
  200. package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
  201. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
  202. package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
  203. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
  204. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
  205. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
  206. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
  207. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
  208. package/src/api/subscriptions/services/BillingService.ts +0 -437
  209. package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
  210. package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
  211. package/src/api/subscriptions/services/UsageService.ts +0 -118
  212. package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
  213. package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
  214. package/src/api/workflows/entities/workflowExecutions.ts +0 -74
  215. package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
  216. package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
  217. package/src/api/workflows/index.browser.ts +0 -22
  218. package/src/api/workflows/index.ts +0 -115
  219. package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
  220. package/src/api/workflows/primitives/$workflow.ts +0 -202
  221. package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
  222. package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
  223. package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
  224. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
  225. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
  226. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
  227. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
  228. package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
  229. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
  230. package/src/api/workflows/services/WorkflowService.ts +0 -382
  231. package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
  232. package/src/cli/core/templates/webAdminDashboardTsx.ts +0 -17
@@ -1,210 +1,101 @@
1
1
  import { $inject, t } from "alepha";
2
2
  import { $secure } from "alepha/security";
3
3
  import { $action, okSchema } from "alepha/server";
4
- import {
5
- jobActivityPointSchema,
6
- jobActivityQuerySchema,
7
- } from "../schemas/jobActivitySchema.ts";
8
- import { jobCronInfoSchema } from "../schemas/jobCronInfoSchema.ts";
9
- import { jobExecutionDetailResourceSchema } from "../schemas/jobExecutionDetailResourceSchema.ts";
10
4
  import { jobExecutionQuerySchema } from "../schemas/jobExecutionQuerySchema.ts";
11
5
  import { jobExecutionResourceSchema } from "../schemas/jobExecutionResourceSchema.ts";
12
- import { jobFailureSchema } from "../schemas/jobFailureSchema.ts";
13
- import { jobQueueDepthSchema } from "../schemas/jobQueueDepthSchema.ts";
14
6
  import { jobRegistrationSchema } from "../schemas/jobRegistrationSchema.ts";
15
- import { jobStatsSchema } from "../schemas/jobStatsSchema.ts";
16
7
  import { triggerJobSchema } from "../schemas/triggerJobSchema.ts";
17
8
  import { JobService } from "../services/JobService.ts";
18
9
 
10
+ /**
11
+ * Minimal admin surface for the job system. Six endpoints.
12
+ */
19
13
  export class AdminJobController {
20
14
  protected readonly url: string = "/jobs";
21
15
  protected readonly group: string = "admin:jobs";
22
16
  protected readonly jobService = $inject(JobService);
23
17
 
24
- public readonly getJobStats = $action({
25
- path: `${this.url}/stats`,
26
- group: this.group,
27
- use: [$secure({ permissions: ["admin:job:read"] })],
28
- schema: {
29
- query: jobActivityQuerySchema,
30
- response: jobStatsSchema,
31
- },
32
- handler: ({ query }) => this.jobService.getStats(query.days),
33
- });
34
-
35
- public readonly getJobRegistry = $action({
18
+ public readonly listJobs = $action({
36
19
  path: this.url,
37
20
  group: this.group,
38
21
  use: [$secure({ permissions: ["admin:job:read"] })],
39
22
  schema: {
40
23
  response: t.array(jobRegistrationSchema),
41
24
  },
42
- handler: () => this.jobService.getRegistry(),
25
+ handler: () => this.jobService.listJobs(),
43
26
  });
44
27
 
45
- public readonly findJobExecutions = $action({
46
- path: `${this.url}/executions`,
28
+ public readonly listExecutions = $action({
29
+ path: `${this.url}/:name/executions`,
47
30
  group: this.group,
48
31
  use: [$secure({ permissions: ["admin:job:read"] })],
49
32
  schema: {
33
+ params: t.object({ name: t.text() }),
50
34
  query: jobExecutionQuerySchema,
51
- response: t.page(jobExecutionResourceSchema),
35
+ response: t.array(jobExecutionResourceSchema),
52
36
  },
53
- handler: ({ query }) => this.jobService.findExecutions(query),
37
+ handler: ({ params, query }) =>
38
+ this.jobService.getExecutions(params.name, query),
54
39
  });
55
40
 
56
- public readonly getJobExecution = $action({
41
+ public readonly getExecution = $action({
57
42
  path: `${this.url}/executions/:id`,
58
43
  group: this.group,
59
44
  use: [$secure({ permissions: ["admin:job:read"] })],
60
45
  schema: {
61
- params: t.object({
62
- id: t.uuid(),
63
- }),
64
- response: jobExecutionDetailResourceSchema,
46
+ params: t.object({ id: t.uuid() }),
47
+ response: jobExecutionResourceSchema,
65
48
  },
66
49
  handler: ({ params }) => this.jobService.getExecution(params.id),
67
50
  });
68
51
 
69
52
  public readonly triggerJob = $action({
70
53
  method: "POST",
71
- path: `${this.url}/trigger`,
54
+ path: `${this.url}/:name/trigger`,
72
55
  group: this.group,
73
56
  use: [$secure({ permissions: ["admin:job:trigger"] })],
74
57
  schema: {
58
+ params: t.object({ name: t.text() }),
75
59
  body: triggerJobSchema,
76
60
  response: okSchema,
77
61
  },
78
- handler: async ({ body, user }) => {
79
- return this.jobService.triggerJob(body.name, {
62
+ handler: ({ params, body, user }) =>
63
+ this.jobService.triggerJob(params.name, {
80
64
  payload: body.payload,
81
65
  triggeredBy: user?.id,
82
66
  triggeredByName: user?.name,
83
- });
84
- },
67
+ }),
85
68
  });
86
69
 
87
- public readonly retryJobExecution = $action({
70
+ public readonly retryExecution = $action({
88
71
  method: "POST",
89
72
  path: `${this.url}/executions/:id/retry`,
90
73
  group: this.group,
91
74
  use: [$secure({ permissions: ["admin:job:trigger"] })],
92
75
  schema: {
93
- params: t.object({
94
- id: t.uuid(),
95
- }),
76
+ params: t.object({ id: t.uuid() }),
96
77
  response: okSchema,
97
78
  },
98
- handler: async ({ params, user }) => {
99
- return this.jobService.retryExecution(params.id, {
79
+ handler: ({ params, user }) =>
80
+ this.jobService.retryExecution(params.id, {
100
81
  triggeredBy: user?.id,
101
82
  triggeredByName: user?.name,
102
- });
103
- },
83
+ }),
104
84
  });
105
85
 
106
- public readonly cancelJobExecution = $action({
86
+ public readonly cancelExecution = $action({
107
87
  method: "POST",
108
88
  path: `${this.url}/executions/:id/cancel`,
109
89
  group: this.group,
110
90
  use: [$secure({ permissions: ["admin:job:cancel"] })],
111
91
  schema: {
112
- params: t.object({
113
- id: t.uuid(),
114
- }),
92
+ params: t.object({ id: t.uuid() }),
115
93
  response: okSchema,
116
94
  },
117
- handler: async ({ params, user }) => {
118
- return this.jobService.cancelExecution(params.id, {
95
+ handler: ({ params, user }) =>
96
+ this.jobService.cancelExecution(params.id, {
119
97
  cancelledBy: user?.id,
120
98
  cancelledByName: user?.name,
121
- });
122
- },
123
- });
124
-
125
- public readonly getJobActivity = $action({
126
- path: `${this.url}/activity`,
127
- group: this.group,
128
- use: [$secure({ permissions: ["admin:job:read"] })],
129
- schema: {
130
- query: jobActivityQuerySchema,
131
- response: t.array(jobActivityPointSchema),
132
- },
133
- handler: ({ query }) => this.jobService.getActivity(query.days),
134
- });
135
-
136
- public readonly getCronJobs = $action({
137
- path: `${this.url}/cron`,
138
- group: this.group,
139
- use: [$secure({ permissions: ["admin:job:read"] })],
140
- schema: {
141
- response: t.array(jobCronInfoSchema),
142
- },
143
- handler: () => this.jobService.getCronJobs(),
144
- });
145
-
146
- public readonly getJobQueueDepth = $action({
147
- path: `${this.url}/queue`,
148
- group: this.group,
149
- use: [$secure({ permissions: ["admin:job:read"] })],
150
- schema: {
151
- response: t.array(jobQueueDepthSchema),
152
- },
153
- handler: () => this.jobService.getQueueDepth(),
154
- });
155
-
156
- public readonly getJobTopFailures = $action({
157
- path: `${this.url}/failures`,
158
- group: this.group,
159
- use: [$secure({ permissions: ["admin:job:read"] })],
160
- schema: {
161
- query: jobActivityQuerySchema,
162
- response: t.array(jobFailureSchema),
163
- },
164
- handler: ({ query }) => this.jobService.getTopFailures(query.days),
165
- });
166
-
167
- public readonly pauseJob = $action({
168
- method: "POST",
169
- path: `${this.url}/pause`,
170
- group: this.group,
171
- use: [$secure({ permissions: ["admin:job:trigger"] })],
172
- schema: {
173
- body: t.object({ name: t.text() }),
174
- response: okSchema,
175
- },
176
- handler: ({ body, user }) => {
177
- return this.jobService.pauseJob(body.name, {
178
- pausedBy: user?.id,
179
- pausedByName: user?.name,
180
- });
181
- },
182
- });
183
-
184
- public readonly resumeJob = $action({
185
- method: "POST",
186
- path: `${this.url}/resume`,
187
- group: this.group,
188
- use: [$secure({ permissions: ["admin:job:trigger"] })],
189
- schema: {
190
- body: t.object({ name: t.text() }),
191
- response: okSchema,
192
- },
193
- handler: async ({ body, user }) => {
194
- return this.jobService.resumeJob(body.name, {
195
- resumedBy: user?.id,
196
- resumedByName: user?.name,
197
- });
198
- },
199
- });
200
-
201
- public readonly getPausedJobs = $action({
202
- path: `${this.url}/paused`,
203
- group: this.group,
204
- use: [$secure({ permissions: ["admin:job:read"] })],
205
- schema: {
206
- response: t.array(t.text()),
207
- },
208
- handler: () => this.jobService.getPausedJobs(),
99
+ }),
209
100
  });
210
101
  }
@@ -1,6 +1,24 @@
1
1
  import { type Static, t } from "alepha";
2
+ import { logEntrySchema } from "alepha/logger";
2
3
  import { $entity, db } from "alepha/orm";
3
4
 
5
+ /**
6
+ * Job execution record.
7
+ *
8
+ * Stores durable state for queue-mode jobs (outbox pattern) and error records
9
+ * for cron-mode jobs. Successful executions are trimmed by the sweep to keep
10
+ * the last N rows per job (configurable via `jobConfig.keepLastSuccess`).
11
+ *
12
+ * Status transitions:
13
+ * - queue push → pending
14
+ * - worker claim → running
15
+ * - success → ok
16
+ * - terminal failure → error
17
+ * - retry → scheduled (with scheduledAt = now + backoff)
18
+ * - delay → scheduled (with scheduledAt = now + delay)
19
+ * - sweep picks due ones → pending
20
+ * - cancel → cancelled
21
+ */
4
22
  export const jobExecutionEntity = $entity({
5
23
  name: "job_executions",
6
24
  schema: t.object({
@@ -11,17 +29,8 @@ export const jobExecutionEntity = $entity({
11
29
  jobName: t.text(),
12
30
  key: t.optional(t.nullable(t.text())),
13
31
 
14
- payload: t.optional(t.record(t.text(), t.any())),
15
32
  status: db.default(
16
- t.enum([
17
- "pending",
18
- "scheduled",
19
- "retrying",
20
- "running",
21
- "completed",
22
- "dead",
23
- "cancelled",
24
- ]),
33
+ t.enum(["pending", "running", "scheduled", "ok", "error", "cancelled"]),
25
34
  "pending",
26
35
  ),
27
36
  priority: db.default(t.integer({ minimum: 0, maximum: 3 }), 2),
@@ -29,13 +38,14 @@ export const jobExecutionEntity = $entity({
29
38
  attempt: db.default(t.integer(), 0),
30
39
  maxAttempts: db.default(t.integer(), 1),
31
40
 
41
+ payload: t.optional(t.record(t.text(), t.any())),
42
+
32
43
  scheduledAt: t.optional(t.datetime()),
33
44
  startedAt: t.optional(t.datetime()),
34
45
  completedAt: t.optional(t.datetime()),
35
46
 
36
- result: t.optional(t.record(t.text(), t.any())),
37
47
  error: t.optional(t.text()),
38
- workerId: t.optional(t.text()),
48
+ logs: t.optional(t.array(logEntrySchema)),
39
49
 
40
50
  triggeredBy: t.optional(t.text()),
41
51
  triggeredByName: t.optional(t.text()),
@@ -43,9 +53,8 @@ export const jobExecutionEntity = $entity({
43
53
  cancelledByName: t.optional(t.text()),
44
54
  }),
45
55
  indexes: [
46
- { columns: ["jobName", "status", "priority", "scheduledAt"] },
47
- { columns: ["jobName", "status", "startedAt"] },
48
- { columns: ["jobName", "completedAt"] },
56
+ { columns: ["jobName", "status", "scheduledAt"] },
57
+ { columns: ["jobName", "startedAt"] },
49
58
  { columns: ["jobName", "key"], unique: true },
50
59
  ],
51
60
  });
@@ -54,9 +63,8 @@ export type JobExecutionEntity = Static<typeof jobExecutionEntity.schema>;
54
63
 
55
64
  export type JobStatus =
56
65
  | "pending"
57
- | "scheduled"
58
- | "retrying"
59
66
  | "running"
60
- | "completed"
61
- | "dead"
67
+ | "scheduled"
68
+ | "ok"
69
+ | "error"
62
70
  | "cancelled";
@@ -3,17 +3,10 @@ import { $module } from "alepha";
3
3
  // -----------------------------------------------------------------------------------------------------------------
4
4
 
5
5
  export * from "./entities/jobExecutionEntity.ts";
6
- export * from "./entities/jobExecutionLogEntity.ts";
7
- export * from "./schemas/jobActivitySchema.ts";
8
6
  export * from "./schemas/jobConfigAtom.ts";
9
- export * from "./schemas/jobCronInfoSchema.ts";
10
- export * from "./schemas/jobExecutionDetailResourceSchema.ts";
11
7
  export * from "./schemas/jobExecutionQuerySchema.ts";
12
8
  export * from "./schemas/jobExecutionResourceSchema.ts";
13
- export * from "./schemas/jobFailureSchema.ts";
14
- export * from "./schemas/jobQueueDepthSchema.ts";
15
9
  export * from "./schemas/jobRegistrationSchema.ts";
16
- export * from "./schemas/jobStatsSchema.ts";
17
10
  export * from "./schemas/triggerJobSchema.ts";
18
11
 
19
12
  // -----------------------------------------------------------------------------------------------------------------
@@ -22,3 +15,8 @@ export const AlephaApiJobs = $module({
22
15
  name: "alepha.api.jobs",
23
16
  services: [],
24
17
  });
18
+
19
+ export const AlephaApiJobsQueue = $module({
20
+ name: "alepha.api.jobs.queue",
21
+ services: [],
22
+ });
@@ -1,4 +1,4 @@
1
- import { $module, type Alepha, type Static, t } from "alepha";
1
+ import { $module } from "alepha";
2
2
  import type { DateTime } from "alepha/datetime";
3
3
  import { AlephaLock } from "alepha/lock";
4
4
  import { AlephaQueue } from "alepha/queue";
@@ -12,28 +12,19 @@ import { JobService } from "./services/JobService.ts";
12
12
 
13
13
  export * from "./controllers/AdminJobController.ts";
14
14
  export * from "./entities/jobExecutionEntity.ts";
15
- export * from "./entities/jobExecutionLogEntity.ts";
16
15
  export * from "./primitives/$job.ts";
17
16
  export * from "./providers/JobProvider.ts";
18
17
  export * from "./providers/JobQueueProvider.ts";
19
- export * from "./schemas/jobActivitySchema.ts";
20
18
  export * from "./schemas/jobConfigAtom.ts";
21
- export * from "./schemas/jobCronInfoSchema.ts";
22
- export * from "./schemas/jobExecutionDetailResourceSchema.ts";
23
19
  export * from "./schemas/jobExecutionQuerySchema.ts";
24
20
  export * from "./schemas/jobExecutionResourceSchema.ts";
25
- export * from "./schemas/jobFailureSchema.ts";
26
- export * from "./schemas/jobQueueDepthSchema.ts";
27
21
  export * from "./schemas/jobRegistrationSchema.ts";
28
- export * from "./schemas/jobStatsSchema.ts";
29
22
  export * from "./schemas/triggerJobSchema.ts";
30
23
  export * from "./services/JobService.ts";
31
24
 
32
25
  // -----------------------------------------------------------------------------------------------------------------
33
26
 
34
27
  declare module "alepha" {
35
- interface Env extends Partial<Static<typeof jobEnvSchema>> {}
36
-
37
28
  interface Hooks {
38
29
  "job:begin": { name: string; now: DateTime; executionId: string };
39
30
  "job:success": { name: string; executionId: string };
@@ -45,34 +36,17 @@ declare module "alepha" {
45
36
 
46
37
  // -----------------------------------------------------------------------------------------------------------------
47
38
 
48
- const jobEnvSchema = t.object({
49
- /**
50
- * Controls whether the job system dispatches work through a queue or executes inline.
51
- *
52
- * - `1` — always use queue (force queue even in serverless)
53
- * - `0` — never use queue (force inline, useful for testing)
54
- * - not set — auto: use queue when NOT serverless (default)
55
- */
56
- ALEPHA_JOBS_QUEUE: t.optional(
57
- t.integer({
58
- description:
59
- "Set to 1 to always use queue, 0 to disable queue (default: auto-detect based on environment)",
60
- }),
61
- ),
62
- });
63
-
64
- // -----------------------------------------------------------------------------------------------------------------
65
-
66
39
  /**
67
- * Job execution framework — unified primitive for deferred, scheduled, and queued work.
40
+ * Job execution framework — cron and durable queue work with a single primitive.
41
+ *
42
+ * A `$job` is either **cron-only** (declares `cron`) or **queue-only** (declares `schema`).
43
+ * Cron jobs run inline on their schedule and only record errors by default.
44
+ * Queue jobs use the outbox pattern: push commits to DB first, then notifies via queue.
68
45
  *
69
- * **Features:**
70
- * - Push-based jobs with typed payloads
71
- * - Cron scheduling with execution tracking
72
- * - Retry with exponential backoff
73
- * - Priority, delay, cancellation
74
- * - Deduplication via unique keys
75
- * - Per-execution log capture
46
+ * **This module provides cron support only.** To enable queue-mode jobs, also
47
+ * import {@link AlephaApiJobsQueue} it brings in the queue layer and infrastructure
48
+ * binding (e.g. Cloudflare Queues). Cron-only deployments (Vercel, CF-without-Queues)
49
+ * do not need `AlephaApiJobsQueue`.
76
50
  *
77
51
  * @module alepha.api.jobs
78
52
  */
@@ -80,20 +54,18 @@ export const AlephaApiJobs = $module({
80
54
  name: "alepha.api.jobs",
81
55
  imports: [AlephaScheduler, AlephaLock],
82
56
  services: [JobProvider, JobService, AdminJobController],
83
- // AlephaQueue + JobQueueProvider are conditional — injected only when queue is enabled.
84
- variants: [JobQueueProvider],
85
- register: (alepha: Alepha) => {
86
- const env = alepha.parseEnv(jobEnvSchema);
87
- const useQueue =
88
- env.ALEPHA_JOBS_QUEUE === 1
89
- ? true
90
- : env.ALEPHA_JOBS_QUEUE === 0
91
- ? false
92
- : !alepha.isServerless();
57
+ });
93
58
 
94
- if (useQueue) {
95
- alepha.with(AlephaQueue);
96
- alepha.with(JobQueueProvider);
97
- }
98
- },
59
+ /**
60
+ * Queue support for `$job`. Import alongside {@link AlephaApiJobs} when your
61
+ * app declares queue-mode jobs (any `$job` with a `schema`).
62
+ *
63
+ * Adds `JobQueueProvider` which plumbs the outbox dispatch through `AlephaQueue`.
64
+ *
65
+ * @module alepha.api.jobs.queue
66
+ */
67
+ export const AlephaApiJobsQueue = $module({
68
+ name: "alepha.api.jobs.queue",
69
+ imports: [AlephaApiJobs, AlephaQueue],
70
+ services: [JobQueueProvider],
99
71
  });
@@ -17,7 +17,12 @@ import {
17
17
  } from "../providers/JobProvider.ts";
18
18
 
19
19
  /**
20
- * Job primitive for defining scheduled and on-demand tasks with payload validation and retry policies.
20
+ * Job primitive for defining scheduled (cron) or queued (push) tasks.
21
+ *
22
+ * A job must be either **cron-only** (pass `cron`) or **queue-only**
23
+ * (pass `schema`), never both. To run scheduled work that processes
24
+ * payloads, compose two jobs: a cron that pushes payloads, and a
25
+ * queue job that handles them.
21
26
  */
22
27
  export const $job = <T extends TSchema = TSchema>(
23
28
  options: JobPrimitiveOptions<T>,
@@ -27,16 +32,12 @@ export const $job = <T extends TSchema = TSchema>(
27
32
 
28
33
  // -----------------------------------------------------------------------------------------------------------------
29
34
 
30
- export interface JobItem<T extends TSchema = TSchema> {
31
- id: string;
35
+ export interface JobHandlerArgs<T extends TSchema = TSchema> {
32
36
  payload: Static<T>;
33
37
  attempt: number;
34
- }
35
-
36
- export interface JobHandlerArgs<T extends TSchema = TSchema> {
37
- items: Array<JobItem<T>>;
38
38
  now: DateTime;
39
39
  signal: AbortSignal;
40
+ executionId: string;
40
41
  }
41
42
 
42
43
  export interface JobRetryBackoff {
@@ -57,45 +58,75 @@ export type JobPriority = "critical" | "high" | "normal" | "low";
57
58
  export interface JobPrimitiveOptions<T extends TSchema = TSchema>
58
59
  extends PipelinePrimitiveOptions {
59
60
  /**
60
- * Payload schema (TypeBox). Optional for cron-only jobs.
61
+ * Optional explicit job name. Defaults to `ClassName.propertyKey`.
62
+ * Recommended convention for framework-internal jobs: `api:module:jobName`.
61
63
  */
62
- schema?: T;
64
+ name?: string;
63
65
 
64
66
  /**
65
- * Cron expression for automatic scheduling.
67
+ * Human-readable description (shown in the admin UI).
66
68
  */
67
- cron?: string;
69
+ description?: string;
68
70
 
69
71
  /**
70
- * Whether to use a distributed lock for cron execution.
71
- * @default true
72
+ * Payload schema (TypeBox). When set, the job is queue-mode.
73
+ * Must not be combined with `cron`.
72
74
  */
73
- lock?: boolean;
75
+ schema?: T;
74
76
 
75
77
  /**
76
- * Retry policy for failed executions.
78
+ * Cron expression for recurring execution. When set, the job is cron-mode.
79
+ * Must not be combined with `schema`.
77
80
  */
78
- retry?: JobRetryOptions;
81
+ cron?: string;
79
82
 
80
83
  /**
81
- * Max execution time per attempt.
84
+ * Retry policy for queue-mode jobs.
85
+ * Cron-mode jobs do not retry — the next tick re-runs.
82
86
  */
83
- timeout?: DurationLike;
87
+ retry?: JobRetryOptions;
84
88
 
85
89
  /**
86
- * Max parallel executions.
87
- * @default 1
90
+ * Max execution time per attempt. Handler receives an `AbortSignal`.
88
91
  */
89
- concurrency?: number;
92
+ timeout?: DurationLike;
90
93
 
91
94
  /**
92
- * Default priority for pushed jobs.
95
+ * Default priority for pushed jobs. Used by the sweep to order
96
+ * dispatch when there is a backlog. Real-time queue consumption
97
+ * is FIFO.
93
98
  * @default "normal"
94
99
  */
95
100
  priority?: JobPriority;
96
101
 
97
102
  /**
98
- * Handler function for job execution.
103
+ * Whether to record successful executions.
104
+ *
105
+ * - `"error"` (default for cron, default for queue): only error/cancelled rows kept
106
+ * - `"all"`: keep success rows too (bounded by `keepLastSuccess`)
107
+ * - `"none"`: fire-and-forget, no row even on error
108
+ *
109
+ * Note: queue-mode jobs always write a `pending` row at push time (outbox).
110
+ * This setting controls whether that row is kept on success.
111
+ */
112
+ record?: "error" | "all" | "none";
113
+
114
+ /**
115
+ * Override the global ring-buffer trim for this job.
116
+ *
117
+ * - `{ ok: 0, error: 0 }` — **keep forever** (no sweep trim). Useful for
118
+ * audit-heavy jobs where retention is time-based (handled by a separate
119
+ * cron) rather than count-based.
120
+ * - `{ ok: 50 }` — keep last 50 successes; fall back to global default for errors.
121
+ * - omitted — use global `keepLastSuccess` / `keepLastError` from `jobConfig`.
122
+ */
123
+ keep?: {
124
+ ok?: number;
125
+ error?: number;
126
+ };
127
+
128
+ /**
129
+ * Handler function. For cron-mode, `payload` is `undefined`.
99
130
  */
100
131
  handler: (args: JobHandlerArgs<T>) => Async<void>;
101
132
  }
@@ -108,7 +139,10 @@ export class JobPrimitive<
108
139
  protected readonly jobProvider = $inject(JobProvider);
109
140
 
110
141
  public get name(): string {
111
- return `${this.config.service.name}.${this.config.propertyKey}`;
142
+ return (
143
+ this.options.name ??
144
+ `${this.config.service.name}.${this.config.propertyKey}`
145
+ );
112
146
  }
113
147
 
114
148
  protected onInit() {
@@ -117,62 +151,36 @@ export class JobPrimitive<
117
151
  }
118
152
 
119
153
  /**
120
- * Push a single payload or an array of payloads.
154
+ * Push a single payload to the queue (queue-mode only).
121
155
  */
122
156
  public async push(
123
- payload: Static<T> | Array<Static<T>>,
157
+ payload: Static<T>,
124
158
  options?: PushOptions,
125
- ): Promise<string | string[]> {
126
- if (Array.isArray(payload)) {
127
- const ids = await Promise.all(
128
- payload.map((p) => this.jobProvider.push(this.name, p, options)),
129
- );
130
- return ids;
131
- }
159
+ ): Promise<string> {
132
160
  return this.jobProvider.push(this.name, payload, options);
133
161
  }
134
162
 
135
163
  /**
136
- * Push multiple payloads with per-item options.
164
+ * Push multiple payloads at once (queue-mode only).
165
+ * Batched INSERT + batched queue send when supported.
137
166
  */
138
167
  public async pushMany(items: Array<PushManyItem<T>>): Promise<string[]> {
139
168
  return this.jobProvider.pushMany(this.name, items);
140
169
  }
141
170
 
142
171
  /**
143
- * Cancel a running or pending execution.
172
+ * Cancel a pending or running execution.
144
173
  */
145
174
  public async cancel(executionId: string): Promise<void> {
146
175
  return this.jobProvider.cancel(executionId);
147
176
  }
148
177
 
149
178
  /**
150
- * Manually trigger the job (admin / CLI).
179
+ * Manually fire a cron-mode job, or trigger a queue-mode job with an explicit payload.
151
180
  */
152
- public async trigger(context?: JobTriggerContext): Promise<void> {
181
+ public async trigger(context?: JobTriggerContext<T>): Promise<void> {
153
182
  return this.jobProvider.trigger(this.name, context);
154
183
  }
155
-
156
- /**
157
- * Pause this job. Pushed items are still accepted but processing is held.
158
- */
159
- public pause(): void {
160
- this.jobProvider.pauseJob(this.name);
161
- }
162
-
163
- /**
164
- * Resume a paused job and dispatch any pending items.
165
- */
166
- public async resume(): Promise<void> {
167
- return this.jobProvider.resumeJob(this.name);
168
- }
169
-
170
- /**
171
- * Whether this job is currently paused.
172
- */
173
- public get paused(): boolean {
174
- return this.jobProvider.isJobPaused(this.name);
175
- }
176
184
  }
177
185
 
178
186
  $job[KIND] = JobPrimitive;