alepha 0.19.3 → 0.19.4

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 (215) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/audits/index.d.ts +8 -8
  3. package/dist/api/invitations/index.d.ts +790 -0
  4. package/dist/api/invitations/index.d.ts.map +1 -0
  5. package/dist/api/invitations/index.js +665 -0
  6. package/dist/api/invitations/index.js.map +1 -0
  7. package/dist/api/jobs/index.browser.js +8 -9
  8. package/dist/api/jobs/index.browser.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +99 -43
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js +257 -40
  12. package/dist/api/jobs/index.js.map +1 -1
  13. package/dist/api/keys/index.d.ts +5 -5
  14. package/dist/api/notifications/index.browser.js +0 -1
  15. package/dist/api/notifications/index.browser.js.map +1 -1
  16. package/dist/api/notifications/index.d.ts +3 -3
  17. package/dist/api/notifications/index.d.ts.map +1 -1
  18. package/dist/api/notifications/index.js +0 -1
  19. package/dist/api/notifications/index.js.map +1 -1
  20. package/dist/api/parameters/index.browser.js +112 -1
  21. package/dist/api/parameters/index.browser.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +90 -3
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +79 -12
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/{billing → api/payments}/index.d.ts +67 -49
  27. package/dist/api/payments/index.d.ts.map +1 -0
  28. package/dist/{billing → api/payments}/index.js +108 -74
  29. package/dist/api/payments/index.js.map +1 -0
  30. package/dist/api/subscriptions/index.d.ts +1692 -0
  31. package/dist/api/subscriptions/index.d.ts.map +1 -0
  32. package/dist/api/subscriptions/index.js +1870 -0
  33. package/dist/api/subscriptions/index.js.map +1 -0
  34. package/dist/api/users/index.d.ts +18 -2
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +167 -34
  37. package/dist/api/users/index.js.map +1 -1
  38. package/dist/api/verifications/index.d.ts +13 -13
  39. package/dist/api/workflows/index.browser.js +246 -0
  40. package/dist/api/workflows/index.browser.js.map +1 -0
  41. package/dist/api/workflows/index.d.ts +1618 -0
  42. package/dist/api/workflows/index.d.ts.map +1 -0
  43. package/dist/api/workflows/index.js +1504 -0
  44. package/dist/api/workflows/index.js.map +1 -0
  45. package/dist/cli/core/index.d.ts +44 -28
  46. package/dist/cli/core/index.d.ts.map +1 -1
  47. package/dist/cli/core/index.js +16 -61
  48. package/dist/cli/core/index.js.map +1 -1
  49. package/dist/cli/vendor/index.d.ts +31 -8
  50. package/dist/cli/vendor/index.d.ts.map +1 -1
  51. package/dist/cli/vendor/index.js +79 -24
  52. package/dist/cli/vendor/index.js.map +1 -1
  53. package/dist/core/index.browser.js +21 -2
  54. package/dist/core/index.browser.js.map +1 -1
  55. package/dist/core/index.d.ts +33 -2
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js +21 -2
  58. package/dist/core/index.js.map +1 -1
  59. package/dist/core/index.native.js +21 -2
  60. package/dist/core/index.native.js.map +1 -1
  61. package/dist/core/index.workerd.js +21 -2
  62. package/dist/core/index.workerd.js.map +1 -1
  63. package/dist/email/smtp/index.js +24 -8
  64. package/dist/email/smtp/index.js.map +1 -1
  65. package/dist/orm/core/index.browser.js +0 -18
  66. package/dist/orm/core/index.browser.js.map +1 -1
  67. package/dist/orm/core/index.bun.js +0 -17
  68. package/dist/orm/core/index.bun.js.map +1 -1
  69. package/dist/orm/core/index.d.ts +1 -13
  70. package/dist/orm/core/index.d.ts.map +1 -1
  71. package/dist/orm/core/index.js +0 -17
  72. package/dist/orm/core/index.js.map +1 -1
  73. package/dist/orm/postgres/index.bun.js +3 -3
  74. package/dist/orm/postgres/index.bun.js.map +1 -1
  75. package/dist/orm/postgres/index.d.ts.map +1 -1
  76. package/dist/orm/postgres/index.js +3 -3
  77. package/dist/orm/postgres/index.js.map +1 -1
  78. package/dist/react/router/index.browser.js +25 -3
  79. package/dist/react/router/index.browser.js.map +1 -1
  80. package/dist/react/router/index.d.ts +16 -1
  81. package/dist/react/router/index.d.ts.map +1 -1
  82. package/dist/react/router/index.js +25 -3
  83. package/dist/react/router/index.js.map +1 -1
  84. package/dist/security/index.d.ts +28 -0
  85. package/dist/security/index.d.ts.map +1 -1
  86. package/dist/security/index.js +28 -0
  87. package/dist/security/index.js.map +1 -1
  88. package/package.json +37 -20
  89. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  90. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  91. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  92. package/src/api/invitations/entities/invitations.ts +33 -0
  93. package/src/api/invitations/index.ts +65 -0
  94. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  95. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  96. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  97. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  98. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  99. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  100. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  101. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  102. package/src/api/invitations/services/InvitationService.ts +556 -0
  103. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  104. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  105. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  106. package/src/api/jobs/index.ts +0 -3
  107. package/src/api/jobs/primitives/$job.ts +22 -11
  108. package/src/api/jobs/providers/JobProvider.ts +229 -19
  109. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  110. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  111. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  112. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  113. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  114. package/src/api/jobs/services/JobService.ts +51 -12
  115. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  116. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  117. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  118. package/src/api/parameters/index.browser.ts +12 -0
  119. package/src/api/parameters/primitives/$parameter.ts +20 -3
  120. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  121. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  122. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  123. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  124. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  125. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  126. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  127. package/src/{billing → api/payments}/index.ts +31 -25
  128. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  129. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  130. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  131. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  132. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  133. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  134. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  135. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  136. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  137. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  138. package/src/api/subscriptions/index.ts +144 -0
  139. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  140. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  141. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  142. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  143. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  144. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  145. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  146. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  147. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  148. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  149. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  150. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  151. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  152. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  153. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  154. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  155. package/src/api/subscriptions/services/BillingService.ts +437 -0
  156. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  157. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  158. package/src/api/subscriptions/services/UsageService.ts +118 -0
  159. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  160. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  161. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  162. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  163. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  164. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  165. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  166. package/src/api/users/controllers/UserController.ts +3 -8
  167. package/src/api/users/notifications/UserNotifications.ts +23 -0
  168. package/src/api/users/schemas/loginSchema.ts +1 -1
  169. package/src/api/users/services/CredentialService.ts +51 -4
  170. package/src/api/users/services/RegistrationService.ts +38 -9
  171. package/src/api/users/services/SessionService.ts +62 -9
  172. package/src/api/users/services/UserService.ts +21 -12
  173. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  174. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  175. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  176. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  177. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  178. package/src/api/workflows/index.browser.ts +22 -0
  179. package/src/api/workflows/index.ts +124 -0
  180. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  181. package/src/api/workflows/primitives/$workflow.ts +202 -0
  182. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  183. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  184. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  185. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  186. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  187. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  188. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  189. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  190. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  191. package/src/api/workflows/services/WorkflowService.ts +382 -0
  192. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  193. package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
  194. package/src/cli/vendor/services/VendorService.ts +126 -27
  195. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  196. package/src/core/providers/SchemaValidator.ts +1 -1
  197. package/src/core/providers/TypeProvider.ts +46 -3
  198. package/src/orm/__tests__/enums.spec.ts +22 -29
  199. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  200. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  201. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  202. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  203. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  204. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  205. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  206. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  207. package/src/security/primitives/$secure.ts +28 -0
  208. package/dist/billing/index.d.ts.map +0 -1
  209. package/dist/billing/index.js.map +0 -1
  210. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  211. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  212. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  213. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  214. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  215. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -0,0 +1,15 @@
1
+ import { type Static, t } from "alepha";
2
+
3
+ export const workflowActivityPointSchema = t.object({
4
+ date: t.text(),
5
+ completed: t.integer(),
6
+ failed: t.integer(),
7
+ });
8
+
9
+ export type WorkflowActivityPoint = Static<typeof workflowActivityPointSchema>;
10
+
11
+ export const workflowActivityQuerySchema = t.object({
12
+ days: t.optional(t.integer({ minimum: 1, maximum: 90 })),
13
+ });
14
+
15
+ export type WorkflowActivityQuery = Static<typeof workflowActivityQuerySchema>;
@@ -0,0 +1,51 @@
1
+ import { $atom, type Static, t } from "alepha";
2
+
3
+ export const workflowConfig = $atom({
4
+ name: "alepha.workflows",
5
+ description: "Configuration for the workflow engine.",
6
+ schema: t.object({
7
+ defaultStepTimeout: t.integer({
8
+ description:
9
+ "Default step timeout (ms). Used when no per-step timeout is set.",
10
+ }),
11
+ retentionDays: t.integer({
12
+ description: "Days to keep completed/failed workflow executions.",
13
+ }),
14
+ recovery: t.object({
15
+ staleThreshold: t.integer({
16
+ description: "Running step age (ms) before assumed crashed.",
17
+ }),
18
+ }),
19
+ maxConcurrentWorkflows: t.integer({
20
+ description: "Max concurrent running instances per workflow name.",
21
+ }),
22
+ maxStepsPerWorkflow: t.integer({
23
+ description: "Safety limit on step count.",
24
+ }),
25
+ drainTimeout: t.integer({
26
+ description: "Max time (ms) to wait for in-flight steps during shutdown.",
27
+ }),
28
+ logMaxEntries: t.integer({
29
+ description: "Max log entries captured per step execution.",
30
+ }),
31
+ }),
32
+ default: {
33
+ defaultStepTimeout: 300_000,
34
+ retentionDays: 30,
35
+ recovery: {
36
+ staleThreshold: 1_800_000,
37
+ },
38
+ maxConcurrentWorkflows: 50,
39
+ maxStepsPerWorkflow: 100,
40
+ drainTimeout: 30_000,
41
+ logMaxEntries: 100,
42
+ },
43
+ });
44
+
45
+ export type WorkflowConfig = Static<typeof workflowConfig.schema>;
46
+
47
+ declare module "alepha" {
48
+ interface State {
49
+ [workflowConfig.key]: WorkflowConfig;
50
+ }
51
+ }
@@ -0,0 +1,18 @@
1
+ import { type Static, t } from "alepha";
2
+ import { workflowExecutionResourceSchema } from "./workflowExecutionResourceSchema.ts";
3
+ import { workflowStepExecutionResourceSchema } from "./workflowStepExecutionResourceSchema.ts";
4
+
5
+ export const workflowExecutionDetailSchema = t.extend(
6
+ workflowExecutionResourceSchema,
7
+ {
8
+ steps: t.array(workflowStepExecutionResourceSchema),
9
+ },
10
+ {
11
+ title: "WorkflowExecutionDetail",
12
+ description: "A workflow execution with step details.",
13
+ },
14
+ );
15
+
16
+ export type WorkflowExecutionDetail = Static<
17
+ typeof workflowExecutionDetailSchema
18
+ >;
@@ -0,0 +1,26 @@
1
+ import { type Static, t } from "alepha";
2
+ import { pageQuerySchema } from "alepha/orm";
3
+
4
+ export const workflowExecutionQuerySchema = t.extend(pageQuerySchema, {
5
+ workflow: t.optional(t.text({ description: "Filter by workflow name" })),
6
+ status: t.optional(
7
+ t.enum([
8
+ "pending",
9
+ "running",
10
+ "waiting_for_signal",
11
+ "completed",
12
+ "failed",
13
+ "timed_out",
14
+ "compensating",
15
+ "compensated",
16
+ "compensation_failed",
17
+ "cancelled",
18
+ ]),
19
+ ),
20
+ from: t.optional(t.datetime({ description: "From date (ISO)" })),
21
+ to: t.optional(t.datetime({ description: "To date (ISO)" })),
22
+ });
23
+
24
+ export type WorkflowExecutionQuery = Static<
25
+ typeof workflowExecutionQuerySchema
26
+ >;
@@ -0,0 +1,30 @@
1
+ import { type Static, t } from "alepha";
2
+ import { workflowExecutions } from "../entities/workflowExecutions.ts";
3
+
4
+ export const workflowExecutionCanSchema = t.object({
5
+ retry: t.boolean(),
6
+ cancel: t.boolean(),
7
+ compensate: t.boolean(),
8
+ restart: t.boolean(),
9
+ signal: t.optional(
10
+ t.object({
11
+ stepName: t.text(),
12
+ schema: t.optional(t.any()),
13
+ }),
14
+ ),
15
+ });
16
+
17
+ export type WorkflowExecutionCan = Static<typeof workflowExecutionCanSchema>;
18
+
19
+ export const workflowExecutionResourceSchema = t.extend(
20
+ workflowExecutions.schema,
21
+ { can: workflowExecutionCanSchema },
22
+ {
23
+ title: "WorkflowExecutionResource",
24
+ description: "A workflow execution resource.",
25
+ },
26
+ );
27
+
28
+ export type WorkflowExecutionResource = Static<
29
+ typeof workflowExecutionResourceSchema
30
+ >;
@@ -0,0 +1,26 @@
1
+ import { type Static, t } from "alepha";
2
+
3
+ export const workflowRegistrationSchema = t.object({
4
+ name: t.text(),
5
+ stepCount: t.integer(),
6
+ steps: t.array(
7
+ t.object({
8
+ name: t.text(),
9
+ type: t.enum(["handler", "signal", "parallel"]),
10
+ hasCompensate: t.boolean(),
11
+ hasRetry: t.boolean(),
12
+ timeout: t.optional(t.text()),
13
+ }),
14
+ ),
15
+ onError: t.enum(["compensate", "fail"]),
16
+ timeout: t.optional(t.text()),
17
+ priority: t.text(),
18
+ tags: t.optional(t.array(t.text())),
19
+ paused: t.boolean(),
20
+ running: t.integer(),
21
+ pending: t.integer(),
22
+ waiting: t.integer(),
23
+ failed: t.integer(),
24
+ });
25
+
26
+ export type WorkflowRegistration = Static<typeof workflowRegistrationSchema>;
@@ -0,0 +1,16 @@
1
+ import { type Static, t } from "alepha";
2
+
3
+ export const workflowStatsSchema = t.object({
4
+ registered: t.integer(),
5
+ running: t.integer(),
6
+ pending: t.integer(),
7
+ waiting: t.integer(),
8
+ completed: t.integer(),
9
+ failed: t.integer(),
10
+ compensated: t.integer(),
11
+ compensationFailed: t.integer(),
12
+ cancelled: t.integer(),
13
+ timedOut: t.integer(),
14
+ });
15
+
16
+ export type WorkflowStats = Static<typeof workflowStatsSchema>;
@@ -0,0 +1,15 @@
1
+ import { type Static, t } from "alepha";
2
+ import { workflowStepExecutions } from "../entities/workflowStepExecutions.ts";
3
+
4
+ export const workflowStepExecutionResourceSchema = t.extend(
5
+ workflowStepExecutions.schema,
6
+ {},
7
+ {
8
+ title: "WorkflowStepExecutionResource",
9
+ description: "A workflow step execution resource.",
10
+ },
11
+ );
12
+
13
+ export type WorkflowStepExecutionResource = Static<
14
+ typeof workflowStepExecutionResourceSchema
15
+ >;
@@ -0,0 +1,382 @@
1
+ import { $inject, Alepha, t } from "alepha";
2
+ import { DateTimeProvider } from "alepha/datetime";
3
+ import { $logger } from "alepha/logger";
4
+ import { $repository, DatabaseProvider, sql } from "alepha/orm";
5
+ import { NotFoundError } from "alepha/server";
6
+ import type { WorkflowExecutionEntity } from "../entities/workflowExecutions.ts";
7
+ import { workflowExecutions } from "../entities/workflowExecutions.ts";
8
+ import { workflowStepExecutions } from "../entities/workflowStepExecutions.ts";
9
+ import { WorkflowProvider } from "../providers/WorkflowProvider.ts";
10
+ import type { WorkflowActivityPoint } from "../schemas/workflowActivitySchema.ts";
11
+ import type { WorkflowExecutionQuery } from "../schemas/workflowExecutionQuerySchema.ts";
12
+ import type { WorkflowExecutionCan } from "../schemas/workflowExecutionResourceSchema.ts";
13
+ import type { WorkflowRegistration } from "../schemas/workflowRegistrationSchema.ts";
14
+ import type { WorkflowStats } from "../schemas/workflowStatsSchema.ts";
15
+
16
+ // -----------------------------------------------------------------------------------------------------------------
17
+
18
+ export class WorkflowService {
19
+ protected readonly alepha = $inject(Alepha);
20
+ protected readonly dt = $inject(DateTimeProvider);
21
+ protected readonly log = $logger();
22
+ protected readonly workflowProvider = $inject(WorkflowProvider);
23
+ protected readonly database = $inject(DatabaseProvider);
24
+ protected readonly executions = $repository(workflowExecutions);
25
+ protected readonly stepExecutions = $repository(workflowStepExecutions);
26
+
27
+ /**
28
+ * Compute available actions for a workflow execution based on its status.
29
+ */
30
+ protected computeCan(
31
+ status: string,
32
+ signalStepName?: string,
33
+ ): WorkflowExecutionCan {
34
+ return {
35
+ retry: status === "failed" || status === "timed_out",
36
+ cancel:
37
+ status === "pending" ||
38
+ status === "running" ||
39
+ status === "waiting_for_signal",
40
+ compensate: status === "failed" || status === "timed_out",
41
+ restart:
42
+ status === "failed" ||
43
+ status === "compensated" ||
44
+ status === "compensation_failed",
45
+ signal: signalStepName ? { stepName: signalStepName } : undefined,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Convert an ISO date string to the raw SQL parameter format
51
+ * expected by the current database dialect.
52
+ *
53
+ * - PostgreSQL: ISO string (timestamp comparison)
54
+ * - SQLite: epoch milliseconds (integer comparison)
55
+ */
56
+ protected toRawDate(iso: string): string | number {
57
+ return this.database.dialect === "sqlite" ? new Date(iso).getTime() : iso;
58
+ }
59
+
60
+ /**
61
+ * Get aggregate stats for the workflow engine.
62
+ */
63
+ public async getStats(days?: number): Promise<WorkflowStats> {
64
+ const workflows = this.workflowProvider.getRegisteredWorkflows();
65
+ const periodAgo = this.toRawDate(
66
+ this.dt
67
+ .now()
68
+ .subtract(days ?? 1, "day")
69
+ .toISOString(),
70
+ );
71
+
72
+ const rows = await this.executions.query(
73
+ (e) => sql`
74
+ SELECT
75
+ COUNT(*) FILTER (WHERE ${e.status} = 'running') AS running,
76
+ COUNT(*) FILTER (WHERE ${e.status} = 'pending') AS pending,
77
+ COUNT(*) FILTER (WHERE ${e.status} = 'waiting_for_signal') AS waiting,
78
+ COUNT(*) FILTER (WHERE ${e.status} = 'completed' AND ${e.completedAt} >= ${periodAgo}) AS completed,
79
+ COUNT(*) FILTER (WHERE ${e.status} = 'failed' AND ${e.completedAt} >= ${periodAgo}) AS failed,
80
+ COUNT(*) FILTER (WHERE ${e.status} = 'compensated' AND ${e.completedAt} >= ${periodAgo}) AS compensated,
81
+ COUNT(*) FILTER (WHERE ${e.status} = 'compensation_failed' AND ${e.completedAt} >= ${periodAgo}) AS compensation_failed,
82
+ COUNT(*) FILTER (WHERE ${e.status} = 'cancelled' AND ${e.completedAt} >= ${periodAgo}) AS cancelled,
83
+ COUNT(*) FILTER (WHERE ${e.status} = 'timed_out' AND ${e.completedAt} >= ${periodAgo}) AS timed_out
84
+ FROM ${e}
85
+ `,
86
+ t.object({
87
+ running: t.string(),
88
+ pending: t.string(),
89
+ waiting: t.string(),
90
+ completed: t.string(),
91
+ failed: t.string(),
92
+ compensated: t.string(),
93
+ compensation_failed: t.string(),
94
+ cancelled: t.string(),
95
+ timed_out: t.string(),
96
+ }),
97
+ );
98
+
99
+ const row = rows[0];
100
+ return {
101
+ registered: workflows.size,
102
+ running: Number(row.running),
103
+ pending: Number(row.pending),
104
+ waiting: Number(row.waiting),
105
+ completed: Number(row.completed),
106
+ failed: Number(row.failed),
107
+ compensated: Number(row.compensated),
108
+ compensationFailed: Number(row.compensation_failed),
109
+ cancelled: Number(row.cancelled),
110
+ timedOut: Number(row.timed_out),
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Get the full workflow registry with live counts.
116
+ */
117
+ public async getRegistry(): Promise<WorkflowRegistration[]> {
118
+ const workflows = this.workflowProvider.getRegisteredWorkflows();
119
+ const names = [...workflows.keys()];
120
+
121
+ // Get live counts per workflow name
122
+ const countRows =
123
+ names.length > 0
124
+ ? await this.executions.query(
125
+ (e) => sql`
126
+ SELECT
127
+ ${e.workflowName} AS workflow_name,
128
+ COUNT(*) FILTER (WHERE ${e.status} = 'running') AS running,
129
+ COUNT(*) FILTER (WHERE ${e.status} = 'pending') AS pending,
130
+ COUNT(*) FILTER (WHERE ${e.status} = 'waiting_for_signal') AS waiting,
131
+ COUNT(*) FILTER (WHERE ${e.status} = 'failed') AS failed
132
+ FROM ${e}
133
+ WHERE ${e.workflowName} IN (${sql.join(
134
+ names.map((n) => sql`${n}`),
135
+ sql`, `,
136
+ )})
137
+ GROUP BY ${e.workflowName}
138
+ `,
139
+ t.object({
140
+ workflow_name: t.string(),
141
+ running: t.string(),
142
+ pending: t.string(),
143
+ waiting: t.string(),
144
+ failed: t.string(),
145
+ }),
146
+ )
147
+ : [];
148
+
149
+ const countsByName = new Map(countRows.map((r) => [r.workflow_name, r]));
150
+
151
+ const result: WorkflowRegistration[] = [];
152
+
153
+ for (const [name, reg] of workflows) {
154
+ const opts = reg.options;
155
+ const counts = countsByName.get(name);
156
+
157
+ result.push({
158
+ name,
159
+ stepCount: opts.steps.length,
160
+ steps: opts.steps.map((step) => ({
161
+ name: step.name,
162
+ type: step.type ?? "handler",
163
+ hasCompensate: Boolean(step.compensate),
164
+ hasRetry: Boolean(step.retry),
165
+ timeout: step.timeout ? String(step.timeout) : undefined,
166
+ })),
167
+ onError: opts.onError ?? "compensate",
168
+ timeout: opts.timeout ? String(opts.timeout) : undefined,
169
+ priority: opts.priority ?? "normal",
170
+ tags: opts.tags,
171
+ paused: this.workflowProvider.isWorkflowPaused(name),
172
+ running: Number(counts?.running ?? 0),
173
+ pending: Number(counts?.pending ?? 0),
174
+ waiting: Number(counts?.waiting ?? 0),
175
+ failed: Number(counts?.failed ?? 0),
176
+ });
177
+ }
178
+
179
+ return result;
180
+ }
181
+
182
+ /**
183
+ * Paginated query for workflow executions.
184
+ */
185
+ public async findExecutions(query: WorkflowExecutionQuery = {}) {
186
+ query.sort ??= "-createdAt";
187
+
188
+ const where = this.executions.createQueryWhere();
189
+
190
+ if (query.workflow) {
191
+ where.workflowName = { eq: query.workflow };
192
+ }
193
+
194
+ if (query.status) {
195
+ where.status = { eq: query.status };
196
+ }
197
+
198
+ if (query.from) {
199
+ where.createdAt = { gte: query.from };
200
+ }
201
+
202
+ if (query.to) {
203
+ where.createdAt = {
204
+ ...(where.createdAt as object),
205
+ lte: query.to,
206
+ };
207
+ }
208
+
209
+ const page = await this.executions.paginate(
210
+ query,
211
+ { where },
212
+ { count: true },
213
+ );
214
+ return {
215
+ ...page,
216
+ content: page.content.map((exec: WorkflowExecutionEntity) => ({
217
+ ...exec,
218
+ can: this.computeCan(exec.status),
219
+ })),
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Get a single workflow execution with step details.
225
+ */
226
+ public async getExecution(id: string) {
227
+ const execution = await this.executions.findById(id);
228
+ if (!execution) {
229
+ throw new NotFoundError(`Workflow execution not found: ${id}`);
230
+ }
231
+
232
+ const steps = await this.stepExecutions.findMany({
233
+ where: { workflowExecutionId: { eq: id } },
234
+ orderBy: { column: "stepIndex", direction: "asc" },
235
+ });
236
+
237
+ // Determine signal step name if workflow is waiting
238
+ let signalStepName: string | undefined;
239
+ if (execution.status === "waiting_for_signal") {
240
+ const waitingStep = steps.find((s) => s.status === "waiting");
241
+ if (waitingStep) {
242
+ signalStepName = waitingStep.stepName;
243
+ }
244
+ }
245
+
246
+ return {
247
+ ...execution,
248
+ can: this.computeCan(execution.status, signalStepName),
249
+ steps,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Get daily activity (completed/failed) over a date range.
255
+ */
256
+ public async getActivity(days = 14): Promise<WorkflowActivityPoint[]> {
257
+ const rows = await this.executions.query(
258
+ (e) => sql`
259
+ WITH date_series AS (
260
+ SELECT generate_series(
261
+ CURRENT_DATE - ${days - 1}::int,
262
+ CURRENT_DATE,
263
+ '1 day'::interval
264
+ )::date AS date
265
+ )
266
+ SELECT
267
+ ds.date::text AS date,
268
+ COALESCE(COUNT(*) FILTER (WHERE ${e.status} = 'completed'), 0) AS completed,
269
+ COALESCE(COUNT(*) FILTER (WHERE ${e.status} = 'failed'), 0) AS failed
270
+ FROM date_series ds
271
+ LEFT JOIN ${e} ON DATE(${e.completedAt}) = ds.date
272
+ AND ${e.status} IN ('completed', 'failed')
273
+ GROUP BY ds.date
274
+ ORDER BY ds.date ASC
275
+ `,
276
+ t.object({
277
+ date: t.string(),
278
+ completed: t.string(),
279
+ failed: t.string(),
280
+ }),
281
+ );
282
+
283
+ return rows.map((row) => ({
284
+ date: row.date,
285
+ completed: Number(row.completed),
286
+ failed: Number(row.failed),
287
+ }));
288
+ }
289
+
290
+ /**
291
+ * Start a new workflow execution by name.
292
+ */
293
+ public async triggerWorkflow(
294
+ name: string,
295
+ payload?: Record<string, unknown>,
296
+ options?: {
297
+ key?: string;
298
+ tags?: string[];
299
+ triggeredBy?: string;
300
+ triggeredByName?: string;
301
+ },
302
+ ): Promise<{ id: string }> {
303
+ this.log.info(`Triggering workflow '${name}'`, {
304
+ triggeredBy: options?.triggeredByName ?? options?.triggeredBy,
305
+ });
306
+
307
+ const id = await this.workflowProvider.start(name, payload ?? {}, {
308
+ key: options?.key,
309
+ tags: options?.tags,
310
+ triggeredBy: options?.triggeredBy,
311
+ triggeredByName: options?.triggeredByName,
312
+ });
313
+
314
+ return { id };
315
+ }
316
+
317
+ /**
318
+ * Cancel a running workflow execution.
319
+ */
320
+ public async cancelExecution(
321
+ id: string,
322
+ context?: {
323
+ compensate?: boolean;
324
+ cancelledBy?: string;
325
+ cancelledByName?: string;
326
+ },
327
+ ): Promise<{ ok: boolean }> {
328
+ this.log.info(`Cancelling workflow execution ${id}`, {
329
+ cancelledBy: context?.cancelledByName ?? context?.cancelledBy,
330
+ });
331
+
332
+ await this.workflowProvider.cancel(id, {
333
+ compensate: context?.compensate,
334
+ cancelledBy: context?.cancelledBy,
335
+ cancelledByName: context?.cancelledByName,
336
+ });
337
+ return { ok: true };
338
+ }
339
+
340
+ /**
341
+ * Retry a failed/timed-out workflow from the failed step.
342
+ */
343
+ public async retryExecution(id: string): Promise<{ ok: boolean }> {
344
+ this.log.info(`Retrying workflow execution ${id}`);
345
+ await this.workflowProvider.retry(id);
346
+ return { ok: true };
347
+ }
348
+
349
+ /**
350
+ * Restart a terminal workflow as a new execution.
351
+ */
352
+ public async restartExecution(id: string): Promise<{ id: string }> {
353
+ this.log.info(`Restarting workflow execution ${id}`);
354
+ const newId = await this.workflowProvider.restart(id);
355
+ return { id: newId };
356
+ }
357
+
358
+ /**
359
+ * Trigger compensation on a failed/timed-out workflow.
360
+ */
361
+ public async compensateExecution(id: string): Promise<{ ok: boolean }> {
362
+ this.log.info(`Compensating workflow execution ${id}`);
363
+ await this.workflowProvider.compensate(id);
364
+ return { ok: true };
365
+ }
366
+
367
+ /**
368
+ * Send a signal to a waiting workflow step.
369
+ */
370
+ public async signalExecution(
371
+ id: string,
372
+ stepName: string,
373
+ payload?: Record<string, unknown>,
374
+ signalledBy?: string,
375
+ ): Promise<{ ok: boolean }> {
376
+ this.log.info(`Signalling workflow execution ${id} step '${stepName}'`, {
377
+ signalledBy,
378
+ });
379
+ await this.workflowProvider.signal(id, stepName, payload);
380
+ return { ok: true };
381
+ }
382
+ }
@@ -24,23 +24,8 @@ export const webAppRouterTs = (options: {
24
24
  imports.push(
25
25
  'import { AdminSessionRouter } from "@alepha/ui/admin-sessions";',
26
26
  );
27
- imports.push('import { AdminAuditRouter } from "@alepha/ui/admin-audits";');
28
- imports.push('import { AdminFileRouter } from "@alepha/ui/admin-files";');
29
- imports.push(
30
- 'import { AdminParameterRouter } from "@alepha/ui/admin-parameters";',
31
- );
32
- imports.push('import { AdminJobRouter } from "@alepha/ui/admin-jobs";');
33
- imports.push('import { AdminApiKeyRouter } from "@alepha/ui/admin-keys";');
34
- imports.push(
35
- 'import { AdminNotificationRouter } from "@alepha/ui/admin-notifications";',
36
- );
37
- imports.push(
38
- 'import { AdminBillingRouter } from "@alepha/ui/admin-billing";',
39
- );
40
27
  imports.push('import { $inject } from "alepha";');
41
- imports.push(
42
- 'import { IconLayoutDashboard, IconLockPassword, IconCreditCard } from "@tabler/icons-react";',
43
- );
28
+ imports.push('import { IconLayoutDashboard } from "@tabler/icons-react";');
44
29
  }
45
30
 
46
31
  // Page import
@@ -67,61 +52,23 @@ export const webAppRouterTs = (options: {
67
52
  classMembers.push(` // ── Admin Domain Routers ──────────────────────────
68
53
  protected users = $inject(AdminUserRouter);
69
54
  protected sessions = $inject(AdminSessionRouter);
70
- protected audits = $inject(AdminAuditRouter);
71
- protected files = $inject(AdminFileRouter);
72
- protected parameters = $inject(AdminParameterRouter);
73
- protected jobs = $inject(AdminJobRouter);
74
- protected apiKeys = $inject(AdminApiKeyRouter);
75
- protected notifications = $inject(AdminNotificationRouter);
76
- protected billing = $inject(AdminBillingRouter);
77
55
 
78
56
  // ── Admin Panel ─────────────────────────────────
79
57
  admin = $uiAdmin({
80
58
  pages: [
81
59
  this.users.adminUsers,
60
+ this.users.adminUserLayout,
82
61
  this.sessions.adminSessions,
83
- this.audits.adminAudits,
84
- this.files.adminFiles,
85
- this.parameters.adminParameters,
86
- this.jobs.adminJobs,
87
- this.apiKeys.adminApiKeys,
88
- this.notifications.adminNotifications,
89
- this.billing.adminBilling,
90
62
  ],
91
63
  sidebarItems: [
92
- {
93
- label: "Security",
94
- children: [
95
- { label: "Identity", icon: IconLockPassword, children: [
96
- this.users.adminUsers,
97
- this.sessions.adminSessions,
98
- this.apiKeys.adminApiKeys,
99
- ]},
100
- this.audits.adminAudits,
101
- ],
102
- },
103
- {
104
- label: "System",
105
- children: [
106
- this.files.adminFiles,
107
- this.jobs.adminJobs,
108
- this.notifications.adminNotifications,
109
- this.parameters.adminParameters,
110
- ],
111
- },
112
- {
113
- label: "Commerce",
114
- icon: IconCreditCard,
115
- children: [
116
- this.billing.adminBilling,
117
- ],
118
- },
64
+ this.users.adminUsers,
65
+ this.sessions.adminSessions,
119
66
  ],
120
67
  });
121
68
 
122
69
  // ── Admin Dashboard ─────────────────────────────
123
70
  adminDashboard = $page({
124
- parent: this.admin.adminLayout,
71
+ parent: this.admin,
125
72
  path: "/",
126
73
  label: "Dashboard",
127
74
  icon: IconLayoutDashboard,