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.
- package/dist/api/files/index.js +2 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +64 -148
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +371 -573
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +605 -1012
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +78 -17
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +90 -23
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +2 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +4 -2
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +34 -31
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +13 -7
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +8 -34
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +43 -232
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +36 -11
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +93 -27
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/command/index.d.ts +1 -1
- package/dist/core/index.browser.js +6 -0
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +6 -0
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +6 -0
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/react/form/index.d.ts +60 -1
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +86 -1
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js +16 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.d.ts +6 -0
- package/dist/react/head/index.d.ts.map +1 -1
- package/dist/react/head/index.js +16 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/router/index.browser.js +0 -10
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +35 -12
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +0 -10
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +124 -0
- package/dist/react/ui/index.d.ts.map +1 -0
- package/dist/react/ui/index.js +206 -0
- package/dist/react/ui/index.js.map +1 -0
- package/dist/router/index.d.ts +13 -13
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +45 -32
- package/dist/router/index.js.map +1 -1
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +1 -0
- package/dist/system/index.js.map +1 -1
- package/dist/topic/core/index.js +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/package.json +6 -23
- package/src/api/files/jobs/FileJobs.ts +2 -1
- package/src/api/jobs/__tests__/$job.spec.ts +316 -2867
- package/src/api/jobs/controllers/AdminJobController.ts +29 -138
- package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
- package/src/api/jobs/index.browser.ts +5 -7
- package/src/api/jobs/index.ts +23 -51
- package/src/api/jobs/primitives/$job.ts +66 -58
- package/src/api/jobs/providers/JobProvider.ts +561 -566
- package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
- package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
- package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
- package/src/api/jobs/services/JobService.ts +90 -483
- package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
- package/src/api/notifications/index.ts +7 -4
- package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
- package/src/api/payments/services/PaymentService.ts +4 -2
- package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
- package/src/api/users/audits/UserAudits.ts +3 -1
- package/src/api/users/buckets/UserBuckets.ts +2 -1
- package/src/api/users/index.ts +1 -4
- package/src/api/users/jobs/UserJobs.ts +5 -4
- package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
- package/src/cli/core/__tests__/init.spec.ts +1 -1
- package/src/cli/core/commands/init.ts +0 -12
- package/src/cli/core/services/PackageManagerUtils.ts +2 -9
- package/src/cli/core/services/ProjectScaffolder.ts +17 -65
- package/src/cli/core/templates/agentMd.ts +2 -8
- package/src/cli/core/templates/apiIndexTs.ts +4 -18
- package/src/cli/core/templates/mainCss.ts +1 -36
- package/src/cli/core/templates/vitestConfigTs.ts +17 -0
- package/src/cli/core/templates/webAppRouterTs.ts +2 -85
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
- package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
- package/src/cli/platform/atoms/platformOptions.ts +9 -0
- package/src/cli/platform/schemas/cloudflare.ts +3 -2
- package/src/cli/platform/services/CloudflareApi.ts +164 -25
- package/src/cli/platform/services/WranglerApi.ts +0 -17
- package/src/core/Alepha.ts +9 -0
- package/src/react/form/index.ts +2 -0
- package/src/react/form/services/parseField.ts +163 -0
- package/src/react/form/services/prettyName.ts +19 -0
- package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
- package/src/react/router/primitives/$page.ts +35 -12
- package/src/react/ui/atoms/uiAtom.ts +28 -0
- package/src/react/ui/components/ColorScheme.tsx +36 -0
- package/src/react/ui/hooks/useColorMode.ts +49 -0
- package/src/react/ui/hooks/useSidebarState.ts +26 -0
- package/src/react/ui/hooks/useTheme.ts +22 -0
- package/src/react/ui/index.ts +35 -0
- package/src/react/ui/services/UiPersistence.ts +41 -0
- package/src/router/TemplatedPathParser.ts +50 -51
- package/src/router/__tests__/RouterProvider.spec.ts +62 -0
- package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
- package/src/router/providers/RouterProvider.ts +10 -5
- package/src/system/providers/NodeShellProvider.ts +1 -0
- package/src/topic/core/providers/TopicProvider.ts +1 -1
- package/dist/api/invitations/index.d.ts +0 -790
- package/dist/api/invitations/index.d.ts.map +0 -1
- package/dist/api/invitations/index.js +0 -662
- package/dist/api/invitations/index.js.map +0 -1
- package/dist/api/issues/index.d.ts +0 -810
- package/dist/api/issues/index.d.ts.map +0 -1
- package/dist/api/issues/index.js +0 -444
- package/dist/api/issues/index.js.map +0 -1
- package/dist/api/subscriptions/index.d.ts +0 -1692
- package/dist/api/subscriptions/index.d.ts.map +0 -1
- package/dist/api/subscriptions/index.js +0 -1867
- package/dist/api/subscriptions/index.js.map +0 -1
- package/dist/api/workflows/index.browser.js +0 -246
- package/dist/api/workflows/index.browser.js.map +0 -1
- package/dist/api/workflows/index.d.ts +0 -1618
- package/dist/api/workflows/index.d.ts.map +0 -1
- package/dist/api/workflows/index.js +0 -1495
- package/dist/api/workflows/index.js.map +0 -1
- package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
- package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
- package/src/api/invitations/controllers/InvitationController.ts +0 -84
- package/src/api/invitations/entities/invitations.ts +0 -33
- package/src/api/invitations/index.ts +0 -58
- package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
- package/src/api/invitations/providers/InvitationProvider.ts +0 -45
- package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
- package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
- package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
- package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
- package/src/api/invitations/services/InvitationService.ts +0 -556
- package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
- package/src/api/issues/controllers/AdminIssueController.ts +0 -149
- package/src/api/issues/controllers/IssueController.ts +0 -44
- package/src/api/issues/entities/issues.ts +0 -49
- package/src/api/issues/index.ts +0 -50
- package/src/api/issues/schemas/createIssueSchema.ts +0 -13
- package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
- package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
- package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
- package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
- package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
- package/src/api/issues/services/IssueService.ts +0 -264
- package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
- package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
- package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
- package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
- package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
- package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
- package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
- package/src/api/jobs/services/JobService-tests.ts +0 -157
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
- package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
- package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
- package/src/api/subscriptions/entities/subscriptions.ts +0 -68
- package/src/api/subscriptions/index.ts +0 -133
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
- package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
- package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
- package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
- package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
- package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
- package/src/api/subscriptions/services/BillingService.ts +0 -437
- package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
- package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
- package/src/api/subscriptions/services/UsageService.ts +0 -118
- package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
- package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
- package/src/api/workflows/entities/workflowExecutions.ts +0 -74
- package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
- package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
- package/src/api/workflows/index.browser.ts +0 -22
- package/src/api/workflows/index.ts +0 -115
- package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
- package/src/api/workflows/primitives/$workflow.ts +0 -202
- package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
- package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
- package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
- package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
- package/src/api/workflows/services/WorkflowService.ts +0 -382
- package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
- 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,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
|
-
};
|