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,264 +0,0 @@
1
- import { $inject, Alepha } from "alepha";
2
- import { DateTimeProvider } from "alepha/datetime";
3
- import { $logger } from "alepha/logger";
4
- import { $repository, type Page } from "alepha/orm";
5
- import { BadRequestError } from "alepha/server";
6
- import { type IssueEntity, issues } from "../entities/issues.ts";
7
- import type { CreateIssue } from "../schemas/createIssueSchema.ts";
8
- import { issueConfigAtom } from "../schemas/issueConfigAtom.ts";
9
- import type { IssueQuery } from "../schemas/issueQuerySchema.ts";
10
- import type { MyIssueQuery } from "../schemas/myIssueQuerySchema.ts";
11
- import type { UpdateIssue } from "../schemas/updateIssueSchema.ts";
12
-
13
- export class IssueService {
14
- protected readonly alepha = $inject(Alepha);
15
- protected readonly log = $logger();
16
- protected readonly repo = $repository(issues);
17
- protected readonly dateTime = $inject(DateTimeProvider);
18
-
19
- // -----------------------------------------------------------------------------------------------------------------
20
-
21
- /**
22
- * Get an issue by ID.
23
- */
24
- public async getById(id: string): Promise<IssueEntity> {
25
- return this.repo.getById(id);
26
- }
27
-
28
- // -----------------------------------------------------------------------------------------------------------------
29
-
30
- /**
31
- * Create a new issue.
32
- */
33
- public async create(
34
- data: CreateIssue,
35
- createdBy: { id: string },
36
- ): Promise<IssueEntity> {
37
- const config = this.alepha.store.get(issueConfigAtom);
38
-
39
- if (!config.enabled) {
40
- throw new BadRequestError("Issue submission is disabled");
41
- }
42
-
43
- const openCount = await this.repo.count({
44
- createdBy: { eq: createdBy.id },
45
- status: { inArray: ["open", "assigned"] },
46
- });
47
-
48
- if (openCount >= config.maxOpenPerUser) {
49
- throw new BadRequestError(
50
- `Maximum open issues per user reached (${config.maxOpenPerUser})`,
51
- );
52
- }
53
-
54
- const entity = await this.repo.create({
55
- createdBy: createdBy.id,
56
- title: data.title,
57
- type: data.type ?? "bug",
58
- priority: data.priority ?? "medium",
59
- status: "open",
60
- description: data.description,
61
- pageUrl: data.pageUrl,
62
- });
63
-
64
- this.log.info("Issue created", { id: entity.id, createdBy: createdBy.id });
65
-
66
- await this.alepha.events.emit("issue:created", { issue: entity });
67
-
68
- return entity;
69
- }
70
-
71
- // -----------------------------------------------------------------------------------------------------------------
72
-
73
- /**
74
- * Find issues for the current user.
75
- */
76
- public async findMine(
77
- userId: string,
78
- query: MyIssueQuery = {},
79
- ): Promise<Page<IssueEntity>> {
80
- query.sort ??= "-createdAt";
81
-
82
- const where = this.repo.createQueryWhere();
83
- where.createdBy = { eq: userId };
84
-
85
- if (query.status) {
86
- where.status = { eq: query.status };
87
- }
88
-
89
- return this.repo.paginate(query, { where }, { count: true });
90
- }
91
-
92
- // -----------------------------------------------------------------------------------------------------------------
93
-
94
- /**
95
- * Find issues with pagination and filtering (admin).
96
- */
97
- public async find(query: IssueQuery = {}): Promise<Page<IssueEntity>> {
98
- query.sort ??= "-createdAt";
99
-
100
- const where = this.repo.createQueryWhere();
101
-
102
- if (query.status) {
103
- where.status = { eq: query.status };
104
- }
105
-
106
- if (query.type) {
107
- where.type = { eq: query.type };
108
- }
109
-
110
- if (query.priority) {
111
- where.priority = { eq: query.priority };
112
- }
113
-
114
- if (query.assigneeId) {
115
- where.assigneeId = { eq: query.assigneeId };
116
- }
117
-
118
- if (query.search) {
119
- where.title = { ilike: `%${query.search}%` };
120
- }
121
-
122
- return this.repo.paginate(query, { where }, { count: true });
123
- }
124
-
125
- // -----------------------------------------------------------------------------------------------------------------
126
-
127
- /**
128
- * Update issue fields (admin).
129
- */
130
- public async update(id: string, data: UpdateIssue): Promise<IssueEntity> {
131
- return this.repo.updateById(id, data);
132
- }
133
-
134
- // -----------------------------------------------------------------------------------------------------------------
135
-
136
- /**
137
- * Assign an issue to a user.
138
- */
139
- public async assign(id: string, assigneeId: string): Promise<IssueEntity> {
140
- const issue = await this.repo.getById(id);
141
-
142
- if (issue.status !== "open") {
143
- throw new BadRequestError(
144
- `Cannot assign issue in "${issue.status}" status (must be "open")`,
145
- );
146
- }
147
-
148
- const updated = await this.repo.updateById(id, {
149
- status: "assigned",
150
- assigneeId,
151
- assignedAt: this.dateTime.nowISOString(),
152
- });
153
-
154
- this.log.info("Issue assigned", { id, assigneeId });
155
-
156
- await this.alepha.events.emit("issue:assigned", {
157
- issue: updated,
158
- assigneeId,
159
- });
160
-
161
- return updated;
162
- }
163
-
164
- // -----------------------------------------------------------------------------------------------------------------
165
-
166
- /**
167
- * Complete an issue with resolution notes.
168
- */
169
- public async complete(id: string, resolution: string): Promise<IssueEntity> {
170
- const issue = await this.repo.getById(id);
171
-
172
- if (issue.status !== "assigned") {
173
- throw new BadRequestError(
174
- `Cannot complete issue in "${issue.status}" status (must be "assigned")`,
175
- );
176
- }
177
-
178
- const updated = await this.repo.updateById(id, {
179
- status: "completed",
180
- resolution,
181
- completedAt: this.dateTime.nowISOString(),
182
- });
183
-
184
- this.log.info("Issue completed", { id });
185
-
186
- await this.alepha.events.emit("issue:completed", { issue: updated });
187
-
188
- return updated;
189
- }
190
-
191
- // -----------------------------------------------------------------------------------------------------------------
192
-
193
- /**
194
- * Reopen a completed issue.
195
- */
196
- public async reopen(id: string, reason: string): Promise<IssueEntity> {
197
- const issue = await this.repo.getById(id);
198
-
199
- if (issue.status !== "completed") {
200
- throw new BadRequestError(
201
- `Cannot reopen issue in "${issue.status}" status (must be "completed")`,
202
- );
203
- }
204
-
205
- const updated = await this.repo.updateById(id, {
206
- status: "open",
207
- reopenReason: reason,
208
- assigneeId: undefined,
209
- assignedAt: undefined,
210
- resolution: undefined,
211
- completedAt: undefined,
212
- });
213
-
214
- this.log.info("Issue reopened", { id, reason });
215
-
216
- await this.alepha.events.emit("issue:reopened", {
217
- issue: updated,
218
- reason,
219
- });
220
-
221
- return updated;
222
- }
223
-
224
- // -----------------------------------------------------------------------------------------------------------------
225
-
226
- /**
227
- * Archive a completed issue.
228
- */
229
- public async archive(id: string): Promise<IssueEntity> {
230
- const issue = await this.repo.getById(id);
231
-
232
- if (issue.status !== "completed") {
233
- throw new BadRequestError(
234
- `Cannot archive issue in "${issue.status}" status (must be "completed")`,
235
- );
236
- }
237
-
238
- const updated = await this.repo.updateById(id, {
239
- status: "archived",
240
- archivedAt: this.dateTime.nowISOString(),
241
- });
242
-
243
- this.log.info("Issue archived", { id });
244
-
245
- await this.alepha.events.emit("issue:archived", { issue: updated });
246
-
247
- return updated;
248
- }
249
-
250
- // -----------------------------------------------------------------------------------------------------------------
251
-
252
- /**
253
- * Delete an issue.
254
- */
255
- public async deleteIssue(id: string): Promise<void> {
256
- const issue = await this.repo.getById(id);
257
-
258
- await this.repo.deleteById(id);
259
-
260
- this.log.info("Issue deleted", { id });
261
-
262
- await this.alepha.events.emit("issue:deleted", { issue });
263
- }
264
- }
@@ -1,126 +0,0 @@
1
- import { Alepha, createMiddleware, t } from "alepha";
2
- import { $repository } from "alepha/orm";
3
- import { AlephaOrmPostgres } from "alepha/orm/postgres";
4
- import { describe, expect, test, vi } from "vitest";
5
- import { $job, jobExecutionEntity } from "../index.ts";
6
-
7
- const $track = (log: string[], tag: string) =>
8
- createMiddleware({
9
- name: `$track:${tag}`,
10
- handler:
11
- ({ next }) =>
12
- async (...args: any[]) => {
13
- log.push(`${tag}:before`);
14
- const result = await next(...args);
15
- log.push(`${tag}:after`);
16
- return result;
17
- },
18
- });
19
-
20
- describe("$job middleware", () => {
21
- test("should apply middleware to job handler", async () => {
22
- const log: string[] = [];
23
-
24
- class App {
25
- repo = $repository(jobExecutionEntity);
26
- myJob = $job({
27
- schema: t.object({ value: t.text() }),
28
- use: [$track(log, "mw")],
29
- handler: async () => {
30
- log.push("handler");
31
- },
32
- });
33
- }
34
-
35
- const alepha = Alepha.create().with(AlephaOrmPostgres);
36
- const app = alepha.inject(App);
37
- await alepha.start();
38
-
39
- await app.myJob.push({ value: "test" });
40
-
41
- await vi.waitFor(() => {
42
- expect(log).toStrictEqual(["mw:before", "handler", "mw:after"]);
43
- });
44
- });
45
-
46
- test("should compose multiple middleware in order", async () => {
47
- const log: string[] = [];
48
-
49
- class App {
50
- repo = $repository(jobExecutionEntity);
51
- myJob = $job({
52
- schema: t.object({ value: t.text() }),
53
- use: [$track(log, "first"), $track(log, "second")],
54
- handler: async () => {
55
- log.push("handler");
56
- },
57
- });
58
- }
59
-
60
- const alepha = Alepha.create().with(AlephaOrmPostgres);
61
- const app = alepha.inject(App);
62
- await alepha.start();
63
-
64
- await app.myJob.push({ value: "test" });
65
-
66
- await vi.waitFor(() => {
67
- expect(log).toStrictEqual([
68
- "first:before",
69
- "second:before",
70
- "handler",
71
- "second:after",
72
- "first:after",
73
- ]);
74
- });
75
- });
76
-
77
- test("should work without middleware (no wrapping overhead)", async () => {
78
- const handler = vi.fn();
79
-
80
- class App {
81
- repo = $repository(jobExecutionEntity);
82
- myJob = $job({
83
- schema: t.object({ value: t.text() }),
84
- handler,
85
- });
86
- }
87
-
88
- const alepha = Alepha.create().with(AlephaOrmPostgres);
89
- const app = alepha.inject(App);
90
- await alepha.start();
91
-
92
- await app.myJob.push({ value: "test" });
93
-
94
- await vi.waitFor(() => {
95
- expect(handler).toHaveBeenCalledTimes(1);
96
- });
97
-
98
- const args = handler.mock.calls[0][0];
99
- expect(args.items[0].payload).toEqual({ value: "test" });
100
- });
101
-
102
- test("should apply middleware to cron-triggered jobs", async () => {
103
- const log: string[] = [];
104
-
105
- class App {
106
- repo = $repository(jobExecutionEntity);
107
- cronJob = $job({
108
- cron: "0 0 * * *",
109
- use: [$track(log, "mw")],
110
- handler: async () => {
111
- log.push("handler");
112
- },
113
- });
114
- }
115
-
116
- const alepha = Alepha.create().with(AlephaOrmPostgres);
117
- const app = alepha.inject(App);
118
- await alepha.start();
119
-
120
- await app.cronJob.trigger();
121
-
122
- await vi.waitFor(() => {
123
- expect(log).toStrictEqual(["mw:before", "handler", "mw:after"]);
124
- });
125
- });
126
- });
@@ -1,31 +0,0 @@
1
- import { Alepha } from "alepha";
2
- import { AlephaOrmPostgres } from "alepha/orm/postgres";
3
- import { describe, it } from "vitest";
4
- import {
5
- testGetStatsEmpty,
6
- testGetStatsWithMixedStatuses,
7
- } from "../services/JobService-tests.ts";
8
-
9
- describe("JobService", () => {
10
- describe("getStats", () => {
11
- it("should return zeroes when no executions exist (sqlite)", async () => {
12
- await testGetStatsEmpty(
13
- Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
14
- );
15
- });
16
- it("should return zeroes when no executions exist (postgres)", async () => {
17
- await testGetStatsEmpty(Alepha.create().with(AlephaOrmPostgres));
18
- });
19
-
20
- it("should count executions by status (sqlite)", async () => {
21
- await testGetStatsWithMixedStatuses(
22
- Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
23
- );
24
- });
25
- it("should count executions by status (postgres)", async () => {
26
- await testGetStatsWithMixedStatuses(
27
- Alepha.create().with(AlephaOrmPostgres),
28
- );
29
- });
30
- });
31
- });
@@ -1,13 +0,0 @@
1
- import { type Static, t } from "alepha";
2
- import { logEntrySchema } from "alepha/logger";
3
- import { $entity, db } from "alepha/orm";
4
-
5
- export const jobExecutionLogEntity = $entity({
6
- name: "job_execution_logs",
7
- schema: t.object({
8
- id: db.primaryKey(t.uuid()),
9
- logs: t.array(logEntrySchema),
10
- }),
11
- });
12
-
13
- export type JobExecutionLogEntity = Static<typeof jobExecutionLogEntity.schema>;
@@ -1,15 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const jobActivityPointSchema = t.object({
4
- date: t.text(),
5
- completed: t.integer(),
6
- failed: t.integer(),
7
- });
8
-
9
- export type JobActivityPoint = Static<typeof jobActivityPointSchema>;
10
-
11
- export const jobActivityQuerySchema = t.object({
12
- days: t.optional(t.integer({ minimum: 1, maximum: 90 })),
13
- });
14
-
15
- export type JobActivityQuery = Static<typeof jobActivityQuerySchema>;
@@ -1,22 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const jobCronInfoSchema = t.object({
4
- name: t.text(),
5
- cron: t.text(),
6
- lock: t.boolean(),
7
- priority: t.enum(["critical", "high", "normal", "low"]),
8
- concurrency: t.integer(),
9
- hasSchema: t.boolean(),
10
- paused: t.boolean(),
11
- lastExecution: t.optional(
12
- t.object({
13
- id: t.uuid(),
14
- status: t.text(),
15
- startedAt: t.optional(t.datetime()),
16
- completedAt: t.optional(t.datetime()),
17
- error: t.optional(t.text()),
18
- }),
19
- ),
20
- });
21
-
22
- export type JobCronInfo = Static<typeof jobCronInfoSchema>;
@@ -1,20 +0,0 @@
1
- import { type Static, t } from "alepha";
2
- import { logEntrySchema } from "alepha/logger";
3
- import { jobExecutionEntity } from "../entities/jobExecutionEntity.ts";
4
- import { jobExecutionCanSchema } from "./jobExecutionResourceSchema.ts";
5
-
6
- export const jobExecutionDetailResourceSchema = t.extend(
7
- jobExecutionEntity.schema,
8
- {
9
- can: jobExecutionCanSchema,
10
- logs: t.optional(t.array(logEntrySchema)),
11
- },
12
- {
13
- title: "JobExecutionDetailResource",
14
- description: "A job execution resource with logs.",
15
- },
16
- );
17
-
18
- export type JobExecutionDetailResource = Static<
19
- typeof jobExecutionDetailResourceSchema
20
- >;
@@ -1,9 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const jobFailureSchema = t.object({
4
- jobName: t.text(),
5
- failures: t.integer(),
6
- lastError: t.optional(t.text()),
7
- });
8
-
9
- export type JobFailure = Static<typeof jobFailureSchema>;
@@ -1,14 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const jobQueueDepthSchema = t.object({
4
- jobName: t.text(),
5
- pending: t.integer(),
6
- running: t.integer(),
7
- scheduled: t.integer(),
8
- retrying: t.integer(),
9
- dead: t.integer(),
10
- concurrency: t.integer(),
11
- paused: t.boolean(),
12
- });
13
-
14
- export type JobQueueDepth = Static<typeof jobQueueDepthSchema>;
@@ -1,14 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const jobStatsSchema = t.object({
4
- registered: t.integer(),
5
- running: t.integer(),
6
- pending: t.integer(),
7
- scheduled: t.integer(),
8
- retrying: t.integer(),
9
- dead: t.integer(),
10
- completed: t.integer(),
11
- failed: t.integer(),
12
- });
13
-
14
- export type JobStats = Static<typeof jobStatsSchema>;
@@ -1,157 +0,0 @@
1
- import { $inject, type Alepha, t } from "alepha";
2
- import { $repository } from "alepha/orm";
3
- import { expect } from "vitest";
4
- import { jobExecutionEntity } from "../entities/jobExecutionEntity.ts";
5
- import { $job } from "../primitives/$job.ts";
6
- import { JobService } from "./JobService.ts";
7
-
8
- // =============================================================================
9
- // testGetStatsEmpty
10
- // =============================================================================
11
-
12
- export const testGetStatsEmpty = async (alepha: Alepha) => {
13
- class App {
14
- jobService = $inject(JobService);
15
- myJob = $job({
16
- schema: t.object({ value: t.text() }),
17
- handler: async () => {},
18
- });
19
- }
20
-
21
- const app = alepha.inject(App);
22
- await alepha.start();
23
-
24
- const stats = await app.jobService.getStats();
25
-
26
- expect(stats.registered).toBe(1);
27
- expect(stats.running).toBe(0);
28
- expect(stats.pending).toBe(0);
29
- expect(stats.scheduled).toBe(0);
30
- expect(stats.retrying).toBe(0);
31
- expect(stats.dead).toBe(0);
32
- expect(stats.completed).toBe(0);
33
- expect(stats.failed).toBe(0);
34
- };
35
-
36
- // =============================================================================
37
- // testGetStatsWithMixedStatuses
38
- // =============================================================================
39
-
40
- export const testGetStatsWithMixedStatuses = async (alepha: Alepha) => {
41
- class App {
42
- repo = $repository(jobExecutionEntity);
43
- jobService = $inject(JobService);
44
- jobA = $job({
45
- schema: t.object({ value: t.text() }),
46
- handler: async () => {},
47
- });
48
- jobB = $job({
49
- cron: "0 0 * * *",
50
- handler: async () => {},
51
- });
52
- }
53
-
54
- const app = alepha.inject(App);
55
- await alepha.start();
56
-
57
- const now = new Date();
58
- const oneHourAgo = new Date(now.getTime() - 3_600_000).toISOString();
59
- const twoDaysAgo = new Date(now.getTime() - 2 * 86_400_000).toISOString();
60
-
61
- // Create executions with different statuses
62
- await app.repo.create({
63
- jobName: "App.jobA",
64
- status: "running",
65
- priority: 2,
66
- maxAttempts: 1,
67
- attempt: 1,
68
- startedAt: oneHourAgo,
69
- });
70
-
71
- await app.repo.create({
72
- jobName: "App.jobA",
73
- status: "pending",
74
- priority: 2,
75
- maxAttempts: 1,
76
- });
77
-
78
- await app.repo.create({
79
- jobName: "App.jobA",
80
- status: "scheduled",
81
- priority: 2,
82
- maxAttempts: 1,
83
- scheduledAt: oneHourAgo,
84
- });
85
-
86
- await app.repo.create({
87
- jobName: "App.jobA",
88
- status: "retrying",
89
- priority: 2,
90
- maxAttempts: 3,
91
- attempt: 1,
92
- scheduledAt: oneHourAgo,
93
- });
94
-
95
- await app.repo.create({
96
- jobName: "App.jobA",
97
- status: "dead",
98
- priority: 2,
99
- maxAttempts: 1,
100
- attempt: 1,
101
- error: "boom",
102
- completedAt: oneHourAgo,
103
- });
104
-
105
- // completed within 24h
106
- await app.repo.create({
107
- jobName: "App.jobA",
108
- status: "completed",
109
- priority: 2,
110
- maxAttempts: 1,
111
- attempt: 1,
112
- completedAt: oneHourAgo,
113
- });
114
-
115
- // completed outside 24h — should NOT count
116
- await app.repo.create({
117
- jobName: "App.jobA",
118
- status: "completed",
119
- priority: 2,
120
- maxAttempts: 1,
121
- attempt: 1,
122
- completedAt: twoDaysAgo,
123
- });
124
-
125
- // dead within 24h — should count for failed
126
- await app.repo.create({
127
- jobName: "App.jobA",
128
- status: "dead",
129
- priority: 2,
130
- maxAttempts: 1,
131
- attempt: 1,
132
- error: "recent failure",
133
- completedAt: oneHourAgo,
134
- });
135
-
136
- // dead outside 24h — should NOT count for failed
137
- await app.repo.create({
138
- jobName: "App.jobA",
139
- status: "dead",
140
- priority: 2,
141
- maxAttempts: 1,
142
- attempt: 1,
143
- error: "old failure",
144
- completedAt: twoDaysAgo,
145
- });
146
-
147
- const stats = await app.jobService.getStats();
148
-
149
- expect(stats.registered).toBe(2); // jobA + jobB
150
- expect(stats.running).toBe(1);
151
- expect(stats.pending).toBe(1);
152
- expect(stats.scheduled).toBe(1);
153
- expect(stats.retrying).toBe(1);
154
- expect(stats.dead).toBe(3); // all dead regardless of time
155
- expect(stats.completed).toBe(1); // only the recent one
156
- expect(stats.failed).toBe(2); // 2 dead within 24h (the recent dead ones)
157
- };