alepha 0.19.3 → 0.19.5

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 (289) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/invitations/index.d.ts +790 -0
  3. package/dist/api/invitations/index.d.ts.map +1 -0
  4. package/dist/api/invitations/index.js +665 -0
  5. package/dist/api/invitations/index.js.map +1 -0
  6. package/dist/api/issues/index.d.ts +810 -0
  7. package/dist/api/issues/index.d.ts.map +1 -0
  8. package/dist/api/issues/index.js +447 -0
  9. package/dist/api/issues/index.js.map +1 -0
  10. package/dist/api/jobs/index.browser.js +8 -9
  11. package/dist/api/jobs/index.browser.js.map +1 -1
  12. package/dist/api/jobs/index.d.ts +99 -43
  13. package/dist/api/jobs/index.d.ts.map +1 -1
  14. package/dist/api/jobs/index.js +257 -40
  15. package/dist/api/jobs/index.js.map +1 -1
  16. package/dist/api/notifications/index.browser.js +0 -1
  17. package/dist/api/notifications/index.browser.js.map +1 -1
  18. package/dist/api/notifications/index.d.ts +3 -3
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +0 -1
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.browser.js +112 -1
  23. package/dist/api/parameters/index.browser.js.map +1 -1
  24. package/dist/api/parameters/index.d.ts +90 -3
  25. package/dist/api/parameters/index.d.ts.map +1 -1
  26. package/dist/api/parameters/index.js +79 -12
  27. package/dist/api/parameters/index.js.map +1 -1
  28. package/dist/{billing → api/payments}/index.d.ts +67 -49
  29. package/dist/api/payments/index.d.ts.map +1 -0
  30. package/dist/{billing → api/payments}/index.js +108 -74
  31. package/dist/api/payments/index.js.map +1 -0
  32. package/dist/api/subscriptions/index.d.ts +1692 -0
  33. package/dist/api/subscriptions/index.d.ts.map +1 -0
  34. package/dist/api/subscriptions/index.js +1870 -0
  35. package/dist/api/subscriptions/index.js.map +1 -0
  36. package/dist/api/users/index.d.ts +24 -2
  37. package/dist/api/users/index.d.ts.map +1 -1
  38. package/dist/api/users/index.js +176 -36
  39. package/dist/api/users/index.js.map +1 -1
  40. package/dist/api/verifications/index.d.ts +13 -13
  41. package/dist/api/workflows/index.browser.js +246 -0
  42. package/dist/api/workflows/index.browser.js.map +1 -0
  43. package/dist/api/workflows/index.d.ts +1618 -0
  44. package/dist/api/workflows/index.d.ts.map +1 -0
  45. package/dist/api/workflows/index.js +1504 -0
  46. package/dist/api/workflows/index.js.map +1 -0
  47. package/dist/captcha/index.d.ts +142 -0
  48. package/dist/captcha/index.d.ts.map +1 -0
  49. package/dist/captcha/index.js +177 -0
  50. package/dist/captcha/index.js.map +1 -0
  51. package/dist/cli/core/index.d.ts +126 -30
  52. package/dist/cli/core/index.d.ts.map +1 -1
  53. package/dist/cli/core/index.js +106 -67
  54. package/dist/cli/core/index.js.map +1 -1
  55. package/dist/cli/platform/index.d.ts +84 -10
  56. package/dist/cli/platform/index.d.ts.map +1 -1
  57. package/dist/cli/platform/index.js +92 -4
  58. package/dist/cli/platform/index.js.map +1 -1
  59. package/dist/cli/vendor/index.d.ts +60 -10
  60. package/dist/cli/vendor/index.d.ts.map +1 -1
  61. package/dist/cli/vendor/index.js +177 -45
  62. package/dist/cli/vendor/index.js.map +1 -1
  63. package/dist/command/index.d.ts.map +1 -1
  64. package/dist/command/index.js +2 -3
  65. package/dist/command/index.js.map +1 -1
  66. package/dist/core/index.browser.js +21 -2
  67. package/dist/core/index.browser.js.map +1 -1
  68. package/dist/core/index.d.ts +33 -2
  69. package/dist/core/index.d.ts.map +1 -1
  70. package/dist/core/index.js +21 -2
  71. package/dist/core/index.js.map +1 -1
  72. package/dist/core/index.native.js +21 -2
  73. package/dist/core/index.native.js.map +1 -1
  74. package/dist/core/index.workerd.js +21 -2
  75. package/dist/core/index.workerd.js.map +1 -1
  76. package/dist/email/smtp/index.js +24 -8
  77. package/dist/email/smtp/index.js.map +1 -1
  78. package/dist/orm/core/index.browser.js +0 -18
  79. package/dist/orm/core/index.browser.js.map +1 -1
  80. package/dist/orm/core/index.bun.js +6 -23
  81. package/dist/orm/core/index.bun.js.map +1 -1
  82. package/dist/orm/core/index.d.ts +1 -13
  83. package/dist/orm/core/index.d.ts.map +1 -1
  84. package/dist/orm/core/index.js +6 -23
  85. package/dist/orm/core/index.js.map +1 -1
  86. package/dist/orm/postgres/index.bun.js +3 -3
  87. package/dist/orm/postgres/index.bun.js.map +1 -1
  88. package/dist/orm/postgres/index.d.ts.map +1 -1
  89. package/dist/orm/postgres/index.js +3 -3
  90. package/dist/orm/postgres/index.js.map +1 -1
  91. package/dist/react/i18n/index.d.ts +1 -0
  92. package/dist/react/i18n/index.d.ts.map +1 -1
  93. package/dist/react/i18n/index.js +8 -4
  94. package/dist/react/i18n/index.js.map +1 -1
  95. package/dist/react/router/index.browser.js +25 -3
  96. package/dist/react/router/index.browser.js.map +1 -1
  97. package/dist/react/router/index.d.ts +16 -1
  98. package/dist/react/router/index.d.ts.map +1 -1
  99. package/dist/react/router/index.js +25 -3
  100. package/dist/react/router/index.js.map +1 -1
  101. package/dist/security/index.d.ts +28 -0
  102. package/dist/security/index.d.ts.map +1 -1
  103. package/dist/security/index.js +28 -0
  104. package/dist/security/index.js.map +1 -1
  105. package/dist/server/auth/index.d.ts +145 -2
  106. package/dist/server/auth/index.d.ts.map +1 -1
  107. package/dist/server/auth/index.js +364 -63
  108. package/dist/server/auth/index.js.map +1 -1
  109. package/dist/server/cookies/index.d.ts.map +1 -1
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/websocket/index.d.ts.map +1 -1
  112. package/dist/websocket/index.js.map +1 -1
  113. package/package.json +47 -20
  114. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  115. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  116. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  117. package/src/api/invitations/entities/invitations.ts +33 -0
  118. package/src/api/invitations/index.ts +65 -0
  119. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  120. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  121. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  122. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  123. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  124. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  125. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  126. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  127. package/src/api/invitations/services/InvitationService.ts +556 -0
  128. package/src/api/issues/__tests__/IssueService.spec.ts +263 -0
  129. package/src/api/issues/controllers/AdminIssueController.ts +149 -0
  130. package/src/api/issues/controllers/IssueController.ts +44 -0
  131. package/src/api/issues/entities/issues.ts +49 -0
  132. package/src/api/issues/index.ts +53 -0
  133. package/src/api/issues/schemas/createIssueSchema.ts +13 -0
  134. package/src/api/issues/schemas/issueConfigAtom.ts +13 -0
  135. package/src/api/issues/schemas/issueQuerySchema.ts +18 -0
  136. package/src/api/issues/schemas/issueResourceSchema.ts +6 -0
  137. package/src/api/issues/schemas/myIssueQuerySchema.ts +10 -0
  138. package/src/api/issues/schemas/updateIssueSchema.ts +13 -0
  139. package/src/api/issues/services/IssueService.ts +264 -0
  140. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  141. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  142. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  143. package/src/api/jobs/index.ts +0 -3
  144. package/src/api/jobs/primitives/$job.ts +22 -11
  145. package/src/api/jobs/providers/JobProvider.ts +229 -19
  146. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  147. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  148. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  149. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  150. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  151. package/src/api/jobs/services/JobService.ts +51 -12
  152. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  153. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  154. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  155. package/src/api/parameters/index.browser.ts +12 -0
  156. package/src/api/parameters/primitives/$parameter.ts +20 -3
  157. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  158. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  159. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  160. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  161. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  162. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  163. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  164. package/src/{billing → api/payments}/index.ts +31 -25
  165. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  166. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  167. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  168. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  169. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  170. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  171. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  172. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  173. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  174. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  175. package/src/api/subscriptions/index.ts +144 -0
  176. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  177. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  178. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  179. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  180. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  181. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  182. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  183. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  184. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  185. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  186. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  187. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  188. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  189. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  190. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  191. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  192. package/src/api/subscriptions/services/BillingService.ts +437 -0
  193. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  194. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  195. package/src/api/subscriptions/services/UsageService.ts +118 -0
  196. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  197. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  198. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  199. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  200. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  201. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  202. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  203. package/src/api/users/controllers/UserController.ts +3 -8
  204. package/src/api/users/notifications/UserNotifications.ts +23 -0
  205. package/src/api/users/primitives/$realm.ts +24 -0
  206. package/src/api/users/schemas/loginSchema.ts +1 -1
  207. package/src/api/users/services/CredentialService.ts +57 -7
  208. package/src/api/users/services/RegistrationService.ts +50 -11
  209. package/src/api/users/services/SessionService.ts +64 -9
  210. package/src/api/users/services/UserService.ts +21 -12
  211. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  212. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  213. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  214. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  215. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  216. package/src/api/workflows/index.browser.ts +22 -0
  217. package/src/api/workflows/index.ts +124 -0
  218. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  219. package/src/api/workflows/primitives/$workflow.ts +202 -0
  220. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  221. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  222. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  223. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  224. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  225. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  226. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  227. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  228. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  229. package/src/api/workflows/services/WorkflowService.ts +382 -0
  230. package/src/captcha/__tests__/MemoryCaptchaProvider.spec.ts +74 -0
  231. package/src/captcha/index.ts +33 -0
  232. package/src/captcha/providers/CaptchaProvider.ts +17 -0
  233. package/src/captcha/providers/MemoryCaptchaProvider.ts +65 -0
  234. package/src/captcha/providers/TurnstileCaptchaProvider.ts +125 -0
  235. package/src/cli/core/atoms/buildOptions.ts +57 -0
  236. package/src/cli/core/commands/build.ts +2 -0
  237. package/src/cli/core/providers/ViteDevServerProvider.ts +1 -1
  238. package/src/cli/core/services/ViteUtils.ts +5 -2
  239. package/src/cli/core/tasks/BuildClientTask.ts +3 -1
  240. package/src/cli/core/tasks/BuildCloudflareTask.ts +4 -0
  241. package/src/cli/core/tasks/BuildPwaTask.ts +81 -0
  242. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  243. package/src/cli/platform/adapters/CloudflareAdapter.ts +24 -0
  244. package/src/cli/platform/atoms/platformOptions.ts +19 -3
  245. package/src/cli/platform/hooks/PlatformHook.ts +51 -0
  246. package/src/cli/platform/index.ts +1 -0
  247. package/src/cli/platform/services/CloudflareApi.ts +22 -1
  248. package/src/cli/platform/services/PlatformOrchestrator.ts +67 -2
  249. package/src/cli/vendor/__tests__/VendorService.spec.ts +322 -178
  250. package/src/cli/vendor/commands/VendorCommand.ts +41 -38
  251. package/src/cli/vendor/services/VendorService.ts +234 -31
  252. package/src/command/__tests__/CliProvider.spec.ts +45 -0
  253. package/src/command/providers/CliProvider.ts +3 -4
  254. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  255. package/src/core/providers/SchemaValidator.ts +1 -1
  256. package/src/core/providers/TypeProvider.ts +46 -3
  257. package/src/orm/__tests__/enums.spec.ts +22 -29
  258. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  259. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  260. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  261. package/src/orm/core/services/Repository.ts +20 -6
  262. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  263. package/src/react/i18n/__tests__/I18nProvider.spec.ts +83 -0
  264. package/src/react/i18n/providers/I18nProvider.ts +12 -10
  265. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  266. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  267. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  268. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  269. package/src/security/primitives/$issuer.ts +3 -1
  270. package/src/security/primitives/$secure.ts +28 -0
  271. package/src/server/auth/index.ts +7 -0
  272. package/src/server/auth/primitives/$auth.ts +37 -3
  273. package/src/server/auth/primitives/$authApple.ts +114 -4
  274. package/src/server/auth/primitives/$authFacebook.ts +98 -0
  275. package/src/server/auth/primitives/$authFranceConnect.ts +105 -0
  276. package/src/server/auth/primitives/$authGithub.ts +22 -16
  277. package/src/server/auth/primitives/$authMicrosoft.ts +88 -0
  278. package/src/server/auth/providers/ServerAuthProvider.ts +197 -72
  279. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -0
  280. package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +1 -1
  281. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -1
  282. package/dist/billing/index.d.ts.map +0 -1
  283. package/dist/billing/index.js.map +0 -1
  284. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  285. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  286. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  287. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  288. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  289. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -0,0 +1,616 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { $repository } from "alepha/orm";
3
+ import { AlephaOrmPostgres } from "alepha/orm/postgres";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import {
6
+ $workflow,
7
+ AlephaApiWorkflows,
8
+ workflowExecutions,
9
+ workflowStepExecutions,
10
+ } from "../index.ts";
11
+
12
+ // -----------------------------------------------------------------------------------------------------------------
13
+
14
+ describe("$workflow", () => {
15
+ // ----- Basic functionality -----
16
+
17
+ describe("basic functionality", () => {
18
+ it("should start and complete a single-step workflow", async () => {
19
+ const handler = vi.fn();
20
+
21
+ class App {
22
+ repo = $repository(workflowExecutions);
23
+ stepRepo = $repository(workflowStepExecutions);
24
+ myWorkflow = $workflow({
25
+ schema: t.object({ orderId: t.uuid() }),
26
+ steps: [
27
+ {
28
+ name: "processOrder",
29
+ handler: async ({ payload }) => {
30
+ handler(payload);
31
+ return { processed: true };
32
+ },
33
+ },
34
+ ],
35
+ });
36
+ }
37
+
38
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
39
+ alepha.with(AlephaApiWorkflows);
40
+ alepha.with(App);
41
+ await alepha.start();
42
+
43
+ const app = alepha.inject(App);
44
+ const orderId = crypto.randomUUID();
45
+ const executionId = await app.myWorkflow.start({ orderId });
46
+
47
+ await vi.waitFor(async () => {
48
+ const exec = await app.repo.findById(executionId);
49
+ expect(exec?.status).toBe("completed");
50
+ });
51
+
52
+ expect(handler).toHaveBeenCalledTimes(1);
53
+ expect(handler).toHaveBeenCalledWith(
54
+ expect.objectContaining({ orderId }),
55
+ );
56
+
57
+ // Verify step is completed with result
58
+ const steps = await app.stepRepo.findMany({
59
+ where: { workflowExecutionId: { eq: executionId } },
60
+ });
61
+ expect(steps).toHaveLength(1);
62
+ expect(steps[0].status).toBe("completed");
63
+ expect(steps[0].result).toEqual({ processed: true });
64
+ });
65
+
66
+ it("should execute multi-step workflow in order", async () => {
67
+ const order: string[] = [];
68
+
69
+ class App {
70
+ repo = $repository(workflowExecutions);
71
+ myWorkflow = $workflow({
72
+ schema: t.object({ id: t.text() }),
73
+ steps: [
74
+ {
75
+ name: "step1",
76
+ handler: async () => {
77
+ order.push("step1");
78
+ return { a: 1 };
79
+ },
80
+ },
81
+ {
82
+ name: "step2",
83
+ handler: async ({ results }) => {
84
+ order.push("step2");
85
+ return { b: 2, fromStep1: results.step1 };
86
+ },
87
+ },
88
+ {
89
+ name: "step3",
90
+ handler: async ({ results }) => {
91
+ order.push("step3");
92
+ return { c: 3, fromStep2: results.step2 };
93
+ },
94
+ },
95
+ ],
96
+ });
97
+ }
98
+
99
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
100
+ alepha.with(AlephaApiWorkflows);
101
+ alepha.with(App);
102
+ await alepha.start();
103
+
104
+ const app = alepha.inject(App);
105
+ const executionId = await app.myWorkflow.start({ id: "test" });
106
+
107
+ await vi.waitFor(async () => {
108
+ const exec = await app.repo.findById(executionId);
109
+ expect(exec?.status).toBe("completed");
110
+ });
111
+
112
+ expect(order).toEqual(["step1", "step2", "step3"]);
113
+ });
114
+
115
+ it("should pass accumulated results between steps", async () => {
116
+ let step2Results: Record<string, unknown> = {};
117
+
118
+ class App {
119
+ repo = $repository(workflowExecutions);
120
+ myWorkflow = $workflow({
121
+ schema: t.object({ value: t.text() }),
122
+ steps: [
123
+ {
124
+ name: "first",
125
+ handler: async () => ({ key: "from-first" }),
126
+ },
127
+ {
128
+ name: "second",
129
+ handler: async ({ results }) => {
130
+ step2Results = results;
131
+ },
132
+ },
133
+ ],
134
+ });
135
+ }
136
+
137
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
138
+ alepha.with(AlephaApiWorkflows);
139
+ alepha.with(App);
140
+ await alepha.start();
141
+
142
+ const app = alepha.inject(App);
143
+ const executionId = await app.myWorkflow.start({ value: "test" });
144
+
145
+ await vi.waitFor(async () => {
146
+ const exec = await app.repo.findById(executionId);
147
+ expect(exec?.status).toBe("completed");
148
+ });
149
+
150
+ expect(step2Results.first).toEqual({ key: "from-first" });
151
+ });
152
+
153
+ it("should use ClassName.propertyKey as workflow name", async () => {
154
+ class OrderService {
155
+ processOrder = $workflow({
156
+ schema: t.object({ id: t.text() }),
157
+ steps: [{ name: "step1", handler: async () => {} }],
158
+ });
159
+ }
160
+
161
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
162
+ alepha.with(AlephaApiWorkflows);
163
+ alepha.with(OrderService);
164
+ await alepha.start();
165
+
166
+ const app = alepha.inject(OrderService);
167
+ expect(app.processOrder.name).toBe("OrderService.processOrder");
168
+ });
169
+ });
170
+
171
+ // ----- Compensation (saga pattern) -----
172
+
173
+ describe("compensation", () => {
174
+ it("should compensate completed steps in reverse order on failure", async () => {
175
+ const compensations: string[] = [];
176
+
177
+ class App {
178
+ repo = $repository(workflowExecutions);
179
+ myWorkflow = $workflow({
180
+ schema: t.object({ id: t.text() }),
181
+ onError: "compensate",
182
+ steps: [
183
+ {
184
+ name: "step1",
185
+ handler: async () => ({ id: "r1" }),
186
+ compensate: async () => {
187
+ compensations.push("step1");
188
+ },
189
+ },
190
+ {
191
+ name: "step2",
192
+ handler: async () => ({ id: "r2" }),
193
+ compensate: async () => {
194
+ compensations.push("step2");
195
+ },
196
+ },
197
+ {
198
+ name: "step3",
199
+ handler: async () => {
200
+ throw new Error("step3 failed");
201
+ },
202
+ compensate: async () => {
203
+ compensations.push("step3");
204
+ },
205
+ },
206
+ ],
207
+ });
208
+ }
209
+
210
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
211
+ alepha.with(AlephaApiWorkflows);
212
+ alepha.with(App);
213
+ await alepha.start();
214
+
215
+ const app = alepha.inject(App);
216
+ const executionId = await app.myWorkflow.start({ id: "test" });
217
+
218
+ await vi.waitFor(async () => {
219
+ const exec = await app.repo.findById(executionId);
220
+ expect(exec?.status).toBe("compensated");
221
+ });
222
+
223
+ // step3 never completed so no compensation for it
224
+ // step2 and step1 compensated in reverse order
225
+ expect(compensations).toEqual(["step2", "step1"]);
226
+ });
227
+
228
+ it("should mark as failed when onError is fail", async () => {
229
+ class App {
230
+ repo = $repository(workflowExecutions);
231
+ myWorkflow = $workflow({
232
+ schema: t.object({ id: t.text() }),
233
+ onError: "fail",
234
+ steps: [
235
+ {
236
+ name: "step1",
237
+ handler: async () => ({ ok: true }),
238
+ },
239
+ {
240
+ name: "step2",
241
+ handler: async () => {
242
+ throw new Error("boom");
243
+ },
244
+ },
245
+ ],
246
+ });
247
+ }
248
+
249
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
250
+ alepha.with(AlephaApiWorkflows);
251
+ alepha.with(App);
252
+ await alepha.start();
253
+
254
+ const app = alepha.inject(App);
255
+ const executionId = await app.myWorkflow.start({ id: "test" });
256
+
257
+ await vi.waitFor(async () => {
258
+ const exec = await app.repo.findById(executionId);
259
+ expect(exec?.status).toBe("failed");
260
+ });
261
+
262
+ const exec = await app.repo.findById(executionId);
263
+ expect(exec?.error).toBe("boom");
264
+ expect(exec?.errorStep).toBe("step2");
265
+ });
266
+ });
267
+
268
+ // ----- Retry -----
269
+
270
+ describe("retry", () => {
271
+ it("should retry a step on failure with retries configured", async () => {
272
+ let callCount = 0;
273
+
274
+ class App {
275
+ repo = $repository(workflowExecutions);
276
+ stepRepo = $repository(workflowStepExecutions);
277
+ myWorkflow = $workflow({
278
+ schema: t.object({ id: t.text() }),
279
+ steps: [
280
+ {
281
+ name: "flaky",
282
+ retry: { retries: 2, backoff: [10, "millisecond"] },
283
+ handler: async () => {
284
+ callCount++;
285
+ if (callCount < 3) throw new Error(`fail #${callCount}`);
286
+ return { ok: true };
287
+ },
288
+ },
289
+ ],
290
+ });
291
+ }
292
+
293
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
294
+ alepha.with(AlephaApiWorkflows);
295
+ alepha.with(App);
296
+ await alepha.start();
297
+
298
+ const app = alepha.inject(App);
299
+ const executionId = await app.myWorkflow.start({ id: "test" });
300
+
301
+ await vi.waitFor(
302
+ async () => {
303
+ const exec = await app.repo.findById(executionId);
304
+ expect(exec?.status).toBe("completed");
305
+ },
306
+ { timeout: 10_000 },
307
+ );
308
+
309
+ expect(callCount).toBe(3);
310
+ });
311
+ });
312
+
313
+ // ----- Conditional steps -----
314
+
315
+ describe("conditional steps", () => {
316
+ it("should skip steps when condition returns false", async () => {
317
+ const executed: string[] = [];
318
+
319
+ class App {
320
+ repo = $repository(workflowExecutions);
321
+ stepRepo = $repository(workflowStepExecutions);
322
+ myWorkflow = $workflow({
323
+ schema: t.object({ skipMiddle: t.boolean() }),
324
+ steps: [
325
+ {
326
+ name: "step1",
327
+ handler: async () => {
328
+ executed.push("step1");
329
+ return { value: 1 };
330
+ },
331
+ },
332
+ {
333
+ name: "step2",
334
+ when: ({ payload }) => !payload.skipMiddle,
335
+ handler: async () => {
336
+ executed.push("step2");
337
+ return { value: 2 };
338
+ },
339
+ },
340
+ {
341
+ name: "step3",
342
+ handler: async () => {
343
+ executed.push("step3");
344
+ return { value: 3 };
345
+ },
346
+ },
347
+ ],
348
+ });
349
+ }
350
+
351
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
352
+ alepha.with(AlephaApiWorkflows);
353
+ alepha.with(App);
354
+ await alepha.start();
355
+
356
+ const app = alepha.inject(App);
357
+ const executionId = await app.myWorkflow.start({ skipMiddle: true });
358
+
359
+ await vi.waitFor(async () => {
360
+ const exec = await app.repo.findById(executionId);
361
+ expect(exec?.status).toBe("completed");
362
+ });
363
+
364
+ expect(executed).toEqual(["step1", "step3"]);
365
+
366
+ // Verify step2 was skipped
367
+ const steps = await app.stepRepo.findMany({
368
+ where: { workflowExecutionId: { eq: executionId } },
369
+ orderBy: { column: "stepIndex", direction: "asc" },
370
+ });
371
+ expect(steps[1].status).toBe("skipped");
372
+ });
373
+ });
374
+
375
+ // ----- Cancel -----
376
+
377
+ describe("cancel", () => {
378
+ it("should cancel a pending workflow", async () => {
379
+ class App {
380
+ repo = $repository(workflowExecutions);
381
+ stepRepo = $repository(workflowStepExecutions);
382
+ myWorkflow = $workflow({
383
+ schema: t.object({ id: t.text() }),
384
+ steps: [
385
+ {
386
+ name: "long",
387
+ handler: async ({ signal }) => {
388
+ await new Promise<void>((resolve) => {
389
+ const check = () => {
390
+ if (signal.aborted) resolve();
391
+ else setTimeout(check, 10);
392
+ };
393
+ check();
394
+ });
395
+ },
396
+ },
397
+ ],
398
+ });
399
+ }
400
+
401
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
402
+ alepha.with(AlephaApiWorkflows);
403
+ alepha.with(App);
404
+ await alepha.start();
405
+
406
+ const app = alepha.inject(App);
407
+ const executionId = await app.myWorkflow.start({ id: "test" });
408
+
409
+ // Wait for it to be running
410
+ await vi.waitFor(async () => {
411
+ const steps = await app.stepRepo.findMany({
412
+ where: {
413
+ workflowExecutionId: { eq: executionId },
414
+ status: { eq: "running" },
415
+ },
416
+ });
417
+ expect(steps).toHaveLength(1);
418
+ });
419
+
420
+ await app.myWorkflow.cancel(executionId);
421
+
422
+ await vi.waitFor(async () => {
423
+ const exec = await app.repo.findById(executionId);
424
+ expect(exec?.status).toBe("cancelled");
425
+ });
426
+ });
427
+ });
428
+
429
+ // ----- Retry (admin action) -----
430
+
431
+ describe("admin retry", () => {
432
+ it("should retry a failed workflow from the failed step", async () => {
433
+ let callCount = 0;
434
+
435
+ class App {
436
+ repo = $repository(workflowExecutions);
437
+ myWorkflow = $workflow({
438
+ schema: t.object({ id: t.text() }),
439
+ onError: "fail",
440
+ steps: [
441
+ {
442
+ name: "step1",
443
+ handler: async () => ({ ok: true }),
444
+ },
445
+ {
446
+ name: "step2",
447
+ handler: async () => {
448
+ callCount++;
449
+ if (callCount === 1) throw new Error("transient");
450
+ return { ok: true };
451
+ },
452
+ },
453
+ ],
454
+ });
455
+ }
456
+
457
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
458
+ alepha.with(AlephaApiWorkflows);
459
+ alepha.with(App);
460
+ await alepha.start();
461
+
462
+ const app = alepha.inject(App);
463
+ const executionId = await app.myWorkflow.start({ id: "test" });
464
+
465
+ await vi.waitFor(async () => {
466
+ const exec = await app.repo.findById(executionId);
467
+ expect(exec?.status).toBe("failed");
468
+ });
469
+
470
+ // Retry from the failed step
471
+ await app.myWorkflow.retry(executionId);
472
+
473
+ await vi.waitFor(async () => {
474
+ const exec = await app.repo.findById(executionId);
475
+ expect(exec?.status).toBe("completed");
476
+ });
477
+
478
+ expect(callCount).toBe(2);
479
+ });
480
+ });
481
+
482
+ // ----- Restart -----
483
+
484
+ describe("restart", () => {
485
+ it("should create a new execution from the same payload", async () => {
486
+ let callCount = 0;
487
+
488
+ class App {
489
+ repo = $repository(workflowExecutions);
490
+ myWorkflow = $workflow({
491
+ schema: t.object({ id: t.text() }),
492
+ onError: "fail",
493
+ steps: [
494
+ {
495
+ name: "step1",
496
+ handler: async () => {
497
+ callCount++;
498
+ if (callCount === 1) throw new Error("fail first time");
499
+ return { ok: true };
500
+ },
501
+ },
502
+ ],
503
+ });
504
+ }
505
+
506
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
507
+ alepha.with(AlephaApiWorkflows);
508
+ alepha.with(App);
509
+ await alepha.start();
510
+
511
+ const app = alepha.inject(App);
512
+ const executionId = await app.myWorkflow.start({ id: "test" });
513
+
514
+ await vi.waitFor(async () => {
515
+ const exec = await app.repo.findById(executionId);
516
+ expect(exec?.status).toBe("failed");
517
+ });
518
+
519
+ const newId = await app.myWorkflow.restart(executionId);
520
+ expect(newId).not.toBe(executionId);
521
+
522
+ await vi.waitFor(async () => {
523
+ const exec = await app.repo.findById(newId);
524
+ expect(exec?.status).toBe("completed");
525
+ });
526
+
527
+ expect(callCount).toBe(2);
528
+ });
529
+ });
530
+
531
+ // ----- Events -----
532
+
533
+ describe("events", () => {
534
+ it("should emit workflow lifecycle events", async () => {
535
+ const events: string[] = [];
536
+
537
+ class App {
538
+ repo = $repository(workflowExecutions);
539
+ myWorkflow = $workflow({
540
+ schema: t.object({ id: t.text() }),
541
+ steps: [{ name: "step1", handler: async () => ({ ok: true }) }],
542
+ });
543
+ }
544
+
545
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
546
+ alepha.with(AlephaApiWorkflows);
547
+ alepha.with(App);
548
+ alepha.events.on("workflow:started", () => {
549
+ events.push("started");
550
+ });
551
+ alepha.events.on("workflow:step:begin", () => {
552
+ events.push("step:begin");
553
+ });
554
+ alepha.events.on("workflow:step:completed", () => {
555
+ events.push("step:completed");
556
+ });
557
+ alepha.events.on("workflow:completed", () => {
558
+ events.push("completed");
559
+ });
560
+ await alepha.start();
561
+
562
+ const app = alepha.inject(App);
563
+ const executionId = await app.myWorkflow.start({ id: "test" });
564
+
565
+ await vi.waitFor(async () => {
566
+ const exec = await app.repo.findById(executionId);
567
+ expect(exec?.status).toBe("completed");
568
+ });
569
+
570
+ expect(events).toContain("started");
571
+ expect(events).toContain("step:begin");
572
+ expect(events).toContain("step:completed");
573
+ expect(events).toContain("completed");
574
+ });
575
+ });
576
+
577
+ // ----- Deduplication -----
578
+
579
+ describe("deduplication", () => {
580
+ it("should return existing execution for same key", async () => {
581
+ class App {
582
+ repo = $repository(workflowExecutions);
583
+ myWorkflow = $workflow({
584
+ schema: t.object({ id: t.text() }),
585
+ steps: [{ name: "step1", handler: async () => ({ ok: true }) }],
586
+ });
587
+ }
588
+
589
+ const alepha = Alepha.create().with(AlephaOrmPostgres);
590
+ alepha.with(AlephaApiWorkflows);
591
+ alepha.with(App);
592
+ await alepha.start();
593
+
594
+ const app = alepha.inject(App);
595
+
596
+ // First start with key — creates a new execution
597
+ const id1 = await app.myWorkflow.start(
598
+ { id: "test" },
599
+ { key: "dedup-key" },
600
+ );
601
+
602
+ // Wait for completion (key is cleared on completion)
603
+ await vi.waitFor(async () => {
604
+ const exec = await app.repo.findById(id1);
605
+ expect(exec?.status).toBe("completed");
606
+ });
607
+
608
+ // Second start with same key after completion — creates NEW execution
609
+ const id2 = await app.myWorkflow.start(
610
+ { id: "test" },
611
+ { key: "dedup-key" },
612
+ );
613
+ expect(id2).not.toBe(id1);
614
+ });
615
+ });
616
+ });