convex-effect-workflows 0.1.0

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 (190) hide show
  1. package/README.md +107 -0
  2. package/dist/client/ConvexCtx.d.ts +12 -0
  3. package/dist/client/ConvexCtx.d.ts.map +1 -0
  4. package/dist/client/ConvexCtx.js +6 -0
  5. package/dist/client/ConvexCtx.js.map +1 -0
  6. package/dist/client/ConvexLogger.d.ts +7 -0
  7. package/dist/client/ConvexLogger.d.ts.map +1 -0
  8. package/dist/client/ConvexLogger.js +39 -0
  9. package/dist/client/ConvexLogger.js.map +1 -0
  10. package/dist/client/ConvexTracer.d.ts +7 -0
  11. package/dist/client/ConvexTracer.d.ts.map +1 -0
  12. package/dist/client/ConvexTracer.js +60 -0
  13. package/dist/client/ConvexTracer.js.map +1 -0
  14. package/dist/client/ConvexWorkflowEngine.d.ts +308 -0
  15. package/dist/client/ConvexWorkflowEngine.d.ts.map +1 -0
  16. package/dist/client/ConvexWorkflowEngine.js +88 -0
  17. package/dist/client/ConvexWorkflowEngine.js.map +1 -0
  18. package/dist/client/activityWorker.d.ts +23 -0
  19. package/dist/client/activityWorker.d.ts.map +1 -0
  20. package/dist/client/activityWorker.js +41 -0
  21. package/dist/client/activityWorker.js.map +1 -0
  22. package/dist/client/boundaries.d.ts +27 -0
  23. package/dist/client/boundaries.d.ts.map +1 -0
  24. package/dist/client/boundaries.js +17 -0
  25. package/dist/client/boundaries.js.map +1 -0
  26. package/dist/client/encoded.d.ts +22 -0
  27. package/dist/client/encoded.d.ts.map +1 -0
  28. package/dist/client/encoded.js +276 -0
  29. package/dist/client/encoded.js.map +1 -0
  30. package/dist/client/index.d.ts +13 -0
  31. package/dist/client/index.d.ts.map +1 -0
  32. package/dist/client/index.js +11 -0
  33. package/dist/client/index.js.map +1 -0
  34. package/dist/client/registry.d.ts +17 -0
  35. package/dist/client/registry.d.ts.map +1 -0
  36. package/dist/client/registry.js +21 -0
  37. package/dist/client/registry.js.map +1 -0
  38. package/dist/client/runner.d.ts +27 -0
  39. package/dist/client/runner.d.ts.map +1 -0
  40. package/dist/client/runner.js +90 -0
  41. package/dist/client/runner.js.map +1 -0
  42. package/dist/client/runtime.d.ts +10 -0
  43. package/dist/client/runtime.d.ts.map +1 -0
  44. package/dist/client/runtime.js +15 -0
  45. package/dist/client/runtime.js.map +1 -0
  46. package/dist/component/_generated/api.d.ts +148 -0
  47. package/dist/component/_generated/api.d.ts.map +1 -0
  48. package/dist/component/_generated/api.js +31 -0
  49. package/dist/component/_generated/api.js.map +1 -0
  50. package/dist/component/_generated/component.d.ts +921 -0
  51. package/dist/component/_generated/component.d.ts.map +1 -0
  52. package/dist/component/_generated/component.js +11 -0
  53. package/dist/component/_generated/component.js.map +1 -0
  54. package/dist/component/_generated/dataModel.d.ts +46 -0
  55. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  56. package/dist/component/_generated/dataModel.js +11 -0
  57. package/dist/component/_generated/dataModel.js.map +1 -0
  58. package/dist/component/_generated/server.d.ts +121 -0
  59. package/dist/component/_generated/server.d.ts.map +1 -0
  60. package/dist/component/_generated/server.js +78 -0
  61. package/dist/component/_generated/server.js.map +1 -0
  62. package/dist/component/activityCompletions.d.ts +27 -0
  63. package/dist/component/activityCompletions.d.ts.map +1 -0
  64. package/dist/component/activityCompletions.js +70 -0
  65. package/dist/component/activityCompletions.js.map +1 -0
  66. package/dist/component/boundaries.d.ts +20 -0
  67. package/dist/component/boundaries.d.ts.map +1 -0
  68. package/dist/component/boundaries.js +17 -0
  69. package/dist/component/boundaries.js.map +1 -0
  70. package/dist/component/cleanup.d.ts +11 -0
  71. package/dist/component/cleanup.d.ts.map +1 -0
  72. package/dist/component/cleanup.js +163 -0
  73. package/dist/component/cleanup.js.map +1 -0
  74. package/dist/component/clocks.d.ts +12 -0
  75. package/dist/component/clocks.d.ts.map +1 -0
  76. package/dist/component/clocks.js +26 -0
  77. package/dist/component/clocks.js.map +1 -0
  78. package/dist/component/config.d.ts +25 -0
  79. package/dist/component/config.d.ts.map +1 -0
  80. package/dist/component/config.js +110 -0
  81. package/dist/component/config.js.map +1 -0
  82. package/dist/component/convex.config.d.ts +3 -0
  83. package/dist/component/convex.config.d.ts.map +1 -0
  84. package/dist/component/convex.config.js +6 -0
  85. package/dist/component/convex.config.js.map +1 -0
  86. package/dist/component/dashboard.d.ts +268 -0
  87. package/dist/component/dashboard.d.ts.map +1 -0
  88. package/dist/component/dashboard.js +622 -0
  89. package/dist/component/dashboard.js.map +1 -0
  90. package/dist/component/deferreds.d.ts +31 -0
  91. package/dist/component/deferreds.d.ts.map +1 -0
  92. package/dist/component/deferreds.js +138 -0
  93. package/dist/component/deferreds.js.map +1 -0
  94. package/dist/component/executions.d.ts +77 -0
  95. package/dist/component/executions.d.ts.map +1 -0
  96. package/dist/component/executions.js +186 -0
  97. package/dist/component/executions.js.map +1 -0
  98. package/dist/component/journalSteps.d.ts +261 -0
  99. package/dist/component/journalSteps.d.ts.map +1 -0
  100. package/dist/component/journalSteps.js +203 -0
  101. package/dist/component/journalSteps.js.map +1 -0
  102. package/dist/component/logs.d.ts +68 -0
  103. package/dist/component/logs.d.ts.map +1 -0
  104. package/dist/component/logs.js +123 -0
  105. package/dist/component/logs.js.map +1 -0
  106. package/dist/component/onComplete.d.ts +31 -0
  107. package/dist/component/onComplete.d.ts.map +1 -0
  108. package/dist/component/onComplete.js +146 -0
  109. package/dist/component/onComplete.js.map +1 -0
  110. package/dist/component/payloads.d.ts +26 -0
  111. package/dist/component/payloads.d.ts.map +1 -0
  112. package/dist/component/payloads.js +57 -0
  113. package/dist/component/payloads.js.map +1 -0
  114. package/dist/component/queries.d.ts +2 -0
  115. package/dist/component/queries.d.ts.map +1 -0
  116. package/dist/component/queries.js +2 -0
  117. package/dist/component/queries.js.map +1 -0
  118. package/dist/component/runner.d.ts +31 -0
  119. package/dist/component/runner.d.ts.map +1 -0
  120. package/dist/component/runner.js +87 -0
  121. package/dist/component/runner.js.map +1 -0
  122. package/dist/component/schema.d.ts +282 -0
  123. package/dist/component/schema.d.ts.map +1 -0
  124. package/dist/component/schema.js +119 -0
  125. package/dist/component/schema.js.map +1 -0
  126. package/dist/component/spans.d.ts +105 -0
  127. package/dist/component/spans.d.ts.map +1 -0
  128. package/dist/component/spans.js +190 -0
  129. package/dist/component/spans.js.map +1 -0
  130. package/dist/component/utils.d.ts +15 -0
  131. package/dist/component/utils.d.ts.map +1 -0
  132. package/dist/component/utils.js +53 -0
  133. package/dist/component/utils.js.map +1 -0
  134. package/dist/shared/constants.d.ts +12 -0
  135. package/dist/shared/constants.d.ts.map +1 -0
  136. package/dist/shared/constants.js +12 -0
  137. package/dist/shared/constants.js.map +1 -0
  138. package/dist/shared/validators.d.ts +69 -0
  139. package/dist/shared/validators.d.ts.map +1 -0
  140. package/dist/shared/validators.js +30 -0
  141. package/dist/shared/validators.js.map +1 -0
  142. package/package.json +74 -0
  143. package/src/client/ConvexCtx.ts +21 -0
  144. package/src/client/ConvexLogger.ts +52 -0
  145. package/src/client/ConvexTracer.ts +75 -0
  146. package/src/client/ConvexWorkflowEngine.test.ts +124 -0
  147. package/src/client/ConvexWorkflowEngine.ts +209 -0
  148. package/src/client/activityWorker.ts +62 -0
  149. package/src/client/boundaries.test.ts +83 -0
  150. package/src/client/boundaries.ts +79 -0
  151. package/src/client/encoded.lifecycle.test.ts +336 -0
  152. package/src/client/encoded.test.ts +153 -0
  153. package/src/client/encoded.ts +484 -0
  154. package/src/client/index.ts +47 -0
  155. package/src/client/registry.ts +35 -0
  156. package/src/client/runner.ts +165 -0
  157. package/src/client/runtime.ts +30 -0
  158. package/src/component/_generated/api.ts +179 -0
  159. package/src/component/_generated/component.ts +1216 -0
  160. package/src/component/_generated/dataModel.ts +60 -0
  161. package/src/component/_generated/server.ts +156 -0
  162. package/src/component/activityCompletions.ts +73 -0
  163. package/src/component/boundaries.ts +55 -0
  164. package/src/component/cleanup.test.ts +219 -0
  165. package/src/component/cleanup.ts +218 -0
  166. package/src/component/clocks.ts +26 -0
  167. package/src/component/config.test.ts +159 -0
  168. package/src/component/config.ts +145 -0
  169. package/src/component/convex.config.ts +7 -0
  170. package/src/component/core.test.ts +829 -0
  171. package/src/component/dashboard.scaling.test.ts +268 -0
  172. package/src/component/dashboard.ts +743 -0
  173. package/src/component/deferreds.ts +162 -0
  174. package/src/component/executions.ts +225 -0
  175. package/src/component/journalSteps.ts +252 -0
  176. package/src/component/logs.ts +152 -0
  177. package/src/component/onComplete.ts +170 -0
  178. package/src/component/payloads.ts +83 -0
  179. package/src/component/queries.ts +8 -0
  180. package/src/component/runner.ts +122 -0
  181. package/src/component/schema.ts +155 -0
  182. package/src/component/setup.test.ts +15 -0
  183. package/src/component/spans.ts +241 -0
  184. package/src/component/utils.test.ts +32 -0
  185. package/src/component/utils.ts +73 -0
  186. package/src/shared/constants.test.ts +14 -0
  187. package/src/shared/constants.ts +15 -0
  188. package/src/shared/validators.ts +98 -0
  189. package/src/test.d.ts +8 -0
  190. package/src/test.ts +17 -0
@@ -0,0 +1,484 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Duration from "effect/Duration";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Exit from "effect/Exit";
5
+ import * as Workflow from "@effect/workflow/Workflow";
6
+ import type * as Activity from "@effect/workflow/Activity";
7
+ import type * as DurableDeferred from "@effect/workflow/DurableDeferred";
8
+ import {
9
+ type Encoded,
10
+ WorkflowEngine,
11
+ WorkflowInstance,
12
+ } from "@effect/workflow/WorkflowEngine";
13
+ import type { ComponentApi } from "../component/_generated/component.js";
14
+ import type { Doc } from "../component/_generated/dataModel.js";
15
+ import { ConvexCtx, type ConvexWorkflowCtx } from "./ConvexCtx.js";
16
+ import { scheduleDeferredCompletion } from "./boundaries.js";
17
+
18
+ type ExecutionDoc = Doc<"executions">;
19
+ type JournalStepDoc = Doc<"journalSteps">;
20
+ type PayloadDoc = Doc<"payloads">;
21
+
22
+ type ReplayStep = Pick<
23
+ JournalStepDoc,
24
+ "stepNumber" | "state" | "signature" | "runResult"
25
+ >;
26
+
27
+ type ReplayState = {
28
+ steps: Array<ReplayStep>;
29
+ cursor: number;
30
+ };
31
+
32
+ const ReplayStateTag = Context.GenericTag<ReplayState>(
33
+ "convex-effect-workflows/ReplayState",
34
+ );
35
+
36
+ export type RunnerEnqueueArgs = {
37
+ workflow: Workflow.Any;
38
+ executionId: string;
39
+ generation: number;
40
+ };
41
+
42
+ export type ActivityEnqueueArgs = RunnerEnqueueArgs & {
43
+ activity: Activity.Any;
44
+ stepNumber: number;
45
+ spanId: string;
46
+ attempt: number;
47
+ };
48
+
49
+ export type ConvexEncodedHooks = {
50
+ enqueueRunner: (ctx: ConvexWorkflowCtx, args: RunnerEnqueueArgs) => Promise<void>;
51
+ enqueueActivity: (
52
+ ctx: ConvexWorkflowCtx,
53
+ args: ActivityEnqueueArgs,
54
+ ) => Promise<string>;
55
+ };
56
+
57
+ type EncodedExecuteOptions = {
58
+ executionId: string;
59
+ payload: unknown;
60
+ discard: boolean;
61
+ parent?: {
62
+ executionId: string;
63
+ };
64
+ };
65
+
66
+ type EncodedDeferredDoneOptions = {
67
+ executionId: string;
68
+ deferredName: string;
69
+ exit: unknown;
70
+ };
71
+
72
+ type EncodedScheduleClockOptions = {
73
+ executionId: string;
74
+ clock: {
75
+ duration: Duration.DurationInput;
76
+ deferred: {
77
+ name: string;
78
+ };
79
+ };
80
+ };
81
+
82
+ function signaturesMatch(a: unknown, b: unknown) {
83
+ return JSON.stringify(a) === JSON.stringify(b);
84
+ }
85
+
86
+ function asExecuteOptions(options: unknown): EncodedExecuteOptions {
87
+ return options as EncodedExecuteOptions;
88
+ }
89
+
90
+ function asDeferredDoneOptions(options: unknown): EncodedDeferredDoneOptions {
91
+ return options as EncodedDeferredDoneOptions;
92
+ }
93
+
94
+ function asScheduleClockOptions(options: unknown): EncodedScheduleClockOptions {
95
+ return options as EncodedScheduleClockOptions;
96
+ }
97
+
98
+ function toStepResult(step: ReplayStep): Workflow.Result<unknown, unknown> {
99
+ const runResult = step?.runResult;
100
+ if (!runResult) {
101
+ return new Workflow.Suspended({});
102
+ }
103
+ if (runResult.kind === "success") {
104
+ return new Workflow.Complete({ exit: Exit.succeed(runResult.valuePreview) });
105
+ }
106
+ if (runResult.kind === "failed") {
107
+ return new Workflow.Complete({ exit: Exit.fail(runResult.error) });
108
+ }
109
+ return new Workflow.Complete({ exit: Exit.fail("canceled") });
110
+ }
111
+
112
+ function toExecutionTerminal(
113
+ execution: ExecutionDoc | null | undefined,
114
+ ): Workflow.Result<unknown, unknown> | undefined {
115
+ if (!execution) return undefined;
116
+ if (execution.status === "completed") {
117
+ return new Workflow.Complete({ exit: Exit.succeed(execution.resultPreview) });
118
+ }
119
+ if (execution.status === "failed" || execution.status === "interrupted") {
120
+ return new Workflow.Complete({
121
+ exit: Exit.fail(execution.error ?? "Workflow failed"),
122
+ });
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ function readExecution(
128
+ ctx: ConvexWorkflowCtx,
129
+ component: ComponentApi,
130
+ executionId: string,
131
+ ): Promise<ExecutionDoc | null> {
132
+ return ctx.runQuery(component.executions.getExecution, { executionId }) as Promise<
133
+ ExecutionDoc | null
134
+ >;
135
+ }
136
+
137
+ function readJournalSteps(
138
+ ctx: ConvexWorkflowCtx,
139
+ component: ComponentApi,
140
+ executionId: string,
141
+ ): Promise<Array<ReplayStep>> {
142
+ return ctx.runQuery(component.journalSteps.loadJournalForExecution, {
143
+ executionId,
144
+ }) as Promise<Array<ReplayStep>>;
145
+ }
146
+
147
+ function readPayload(
148
+ ctx: ConvexWorkflowCtx,
149
+ component: ComponentApi,
150
+ payloadId: PayloadDoc["_id"],
151
+ ): Promise<PayloadDoc | null> {
152
+ const runQueryPayload = ctx.runQuery as (
153
+ reference: unknown,
154
+ args: { payloadId: PayloadDoc["_id"] },
155
+ ) => Promise<PayloadDoc | null>;
156
+
157
+ return runQueryPayload(component.payloads.getPayload, { payloadId });
158
+ }
159
+
160
+ export function makeConvexEncoded(
161
+ component: ComponentApi,
162
+ hooks: ConvexEncodedHooks,
163
+ ): Encoded {
164
+ const workflows = new Map<
165
+ string,
166
+ {
167
+ workflow: Workflow.Any;
168
+ execute: (
169
+ payload: object,
170
+ executionId: string,
171
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>;
172
+ }
173
+ >();
174
+
175
+ const encoded = {
176
+ register: Effect.fnUntraced(function*(workflow: Workflow.Any, execute: unknown) {
177
+ workflows.set(workflow.name, {
178
+ workflow,
179
+ execute: execute as (
180
+ payload: object,
181
+ executionId: string,
182
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>,
183
+ });
184
+ }),
185
+
186
+ execute: Effect.fnUntraced(function*(workflow: Workflow.Any, options: unknown) {
187
+ const convex = yield* ConvexCtx;
188
+ const executeOptions = asExecuteOptions(options);
189
+
190
+ const created = yield* Effect.promise(() =>
191
+ convex.runMutation(component.executions.createExecution, {
192
+ workflowName: workflow.name,
193
+ executionId: executeOptions.executionId,
194
+ payload: executeOptions.payload,
195
+ parentExecutionId: executeOptions.parent?.executionId,
196
+ }),
197
+ );
198
+
199
+ if (executeOptions.discard) {
200
+ if (created.created) {
201
+ yield* Effect.promise(() =>
202
+ hooks.enqueueRunner(convex, {
203
+ workflow,
204
+ executionId: executeOptions.executionId,
205
+ generation: 0,
206
+ }),
207
+ );
208
+ }
209
+ return;
210
+ }
211
+
212
+ const execution = yield* Effect.promise(() =>
213
+ readExecution(convex, component, executeOptions.executionId),
214
+ );
215
+ if (!execution) {
216
+ return new Workflow.Complete({
217
+ exit: Exit.fail(`Execution not found: ${executeOptions.executionId}`),
218
+ });
219
+ }
220
+
221
+ const terminal = toExecutionTerminal(execution);
222
+ if (terminal) {
223
+ return terminal;
224
+ }
225
+
226
+ yield* Effect.promise(() =>
227
+ convex.runMutation(component.executions.setExecutionRunning, {
228
+ executionId: executeOptions.executionId,
229
+ generation: execution.generation,
230
+ }),
231
+ );
232
+
233
+ const replaySteps = yield* Effect.promise(() =>
234
+ readJournalSteps(convex, component, executeOptions.executionId),
235
+ );
236
+
237
+ const registered = workflows.get(workflow.name);
238
+ if (!registered) {
239
+ yield* Effect.promise(() =>
240
+ convex.runMutation(component.executions.completeExecution, {
241
+ executionId: executeOptions.executionId,
242
+ generation: execution.generation,
243
+ kind: "failure",
244
+ error: `Workflow not registered: ${workflow.name}`,
245
+ }),
246
+ );
247
+ return new Workflow.Complete({
248
+ exit: Exit.fail(`Workflow not registered: ${workflow.name}`),
249
+ });
250
+ }
251
+
252
+ const engine = yield* WorkflowEngine;
253
+ const instance = WorkflowInstance.initial(workflow, executeOptions.executionId);
254
+
255
+ let payload = execution.payloadPreview;
256
+ const payloadRef = execution.payloadRef;
257
+ if (payloadRef) {
258
+ const payloadDoc = yield* Effect.promise(() =>
259
+ readPayload(convex, component, payloadRef),
260
+ );
261
+ payload = payloadDoc?.data ?? execution.payloadPreview;
262
+ }
263
+
264
+ const replayState: ReplayState = { steps: replaySteps, cursor: 0 };
265
+ const result = yield* registered.execute(payload, executeOptions.executionId).pipe(
266
+ Workflow.intoResult,
267
+ Effect.provideService(ReplayStateTag, replayState),
268
+ Effect.provideService(WorkflowInstance, instance),
269
+ Effect.provideService(WorkflowEngine, engine),
270
+ );
271
+
272
+ if (result._tag === "Complete") {
273
+ yield* Exit.match(result.exit, {
274
+ onSuccess: (value) =>
275
+ Effect.promise(() =>
276
+ convex.runMutation(component.executions.completeExecution, {
277
+ executionId: executeOptions.executionId,
278
+ generation: execution.generation,
279
+ kind: "success",
280
+ result: value,
281
+ }),
282
+ ),
283
+ onFailure: (cause) =>
284
+ Effect.promise(() =>
285
+ convex.runMutation(component.executions.completeExecution, {
286
+ executionId: executeOptions.executionId,
287
+ generation: execution.generation,
288
+ kind: "failure",
289
+ error: String(cause),
290
+ }),
291
+ ),
292
+ });
293
+ } else {
294
+ yield* Effect.promise(() =>
295
+ convex.runMutation(component.executions.setExecutionSuspended, {
296
+ executionId: executeOptions.executionId,
297
+ generation: execution.generation,
298
+ }),
299
+ );
300
+ }
301
+
302
+ return result;
303
+ }),
304
+
305
+ poll: Effect.fnUntraced(function*(_workflow: Workflow.Any, executionId: string) {
306
+ const convex = yield* ConvexCtx;
307
+ const execution = yield* Effect.promise(() =>
308
+ readExecution(convex, component, executionId),
309
+ );
310
+ return toExecutionTerminal(execution);
311
+ }),
312
+
313
+ interrupt: Effect.fnUntraced(function*(_workflow: Workflow.Any, executionId: string) {
314
+ const convex = yield* ConvexCtx;
315
+ yield* Effect.promise(() =>
316
+ convex.runMutation(component.executions.interruptExecution, {
317
+ executionId,
318
+ reason: "Interrupted",
319
+ }),
320
+ );
321
+ }),
322
+
323
+ resume: Effect.fnUntraced(function*(workflow: Workflow.Any, executionId: string) {
324
+ const convex = yield* ConvexCtx;
325
+ const execution = yield* Effect.promise(() =>
326
+ readExecution(convex, component, executionId),
327
+ );
328
+ if (!execution || execution.status === "interrupted") {
329
+ return;
330
+ }
331
+ yield* Effect.promise(() =>
332
+ hooks.enqueueRunner(convex, {
333
+ workflow,
334
+ executionId,
335
+ generation: execution.generation,
336
+ }),
337
+ );
338
+ }),
339
+
340
+ activityExecute: Effect.fnUntraced(function*(activity: Activity.Any, attempt: number) {
341
+ const convex = yield* ConvexCtx;
342
+ const instance = yield* WorkflowInstance;
343
+ const replay = yield* ReplayStateTag;
344
+
345
+ const signature = {
346
+ kind: "activity" as const,
347
+ opName: activity.name,
348
+ };
349
+
350
+ const replayed = replay.steps[replay.cursor];
351
+ if (replayed) {
352
+ replay.cursor += 1;
353
+ if (!signaturesMatch(replayed.signature, signature)) {
354
+ throw new Error(
355
+ `Determinism mismatch at ${instance.executionId}/${replayed.stepNumber}`,
356
+ );
357
+ }
358
+ if (
359
+ replayed.state === "completed" ||
360
+ replayed.state === "failed" ||
361
+ replayed.state === "canceled"
362
+ ) {
363
+ return toStepResult(replayed);
364
+ }
365
+ return new Workflow.Suspended({});
366
+ }
367
+
368
+ const execution = yield* Effect.promise(() =>
369
+ readExecution(convex, component, instance.executionId),
370
+ );
371
+ if (!execution) {
372
+ return new Workflow.Complete({
373
+ exit: Exit.fail(`Execution not found: ${instance.executionId}`),
374
+ });
375
+ }
376
+
377
+ const stepNumber = replay.steps.length;
378
+ const span = yield* Effect.promise(() =>
379
+ convex.runMutation(component.spans.createSpan, {
380
+ executionId: instance.executionId,
381
+ traceId: execution.traceId,
382
+ name: activity.name,
383
+ kind: "activity",
384
+ stepNumber,
385
+ attempt,
386
+ }),
387
+ );
388
+
389
+ yield* Effect.promise(() =>
390
+ convex.runMutation(component.journalSteps.appendJournalStep, {
391
+ executionId: instance.executionId,
392
+ stepNumber,
393
+ kind: "activity",
394
+ name: activity.name,
395
+ signature,
396
+ attempt,
397
+ spanId: span.spanId,
398
+ input: null,
399
+ }),
400
+ );
401
+
402
+ const workId = yield* Effect.promise(() =>
403
+ hooks.enqueueActivity(convex, {
404
+ workflow: instance.workflow,
405
+ executionId: instance.executionId,
406
+ generation: execution.generation,
407
+ activity,
408
+ stepNumber,
409
+ spanId: span.spanId,
410
+ attempt,
411
+ }),
412
+ );
413
+
414
+ yield* Effect.promise(() =>
415
+ convex.runMutation(component.journalSteps.suspendJournalStep, {
416
+ executionId: instance.executionId,
417
+ stepNumber,
418
+ workId,
419
+ }),
420
+ );
421
+
422
+ replay.steps.push({
423
+ stepNumber,
424
+ state: "suspended",
425
+ signature,
426
+ });
427
+ replay.cursor += 1;
428
+
429
+ return new Workflow.Suspended({});
430
+ }),
431
+
432
+ deferredResult: Effect.fnUntraced(function*(deferred: DurableDeferred.Any) {
433
+ const convex = yield* ConvexCtx;
434
+ const instance = yield* WorkflowInstance;
435
+ return yield* Effect.promise(() =>
436
+ convex.runQuery(component.deferreds.deferredResult, {
437
+ executionId: instance.executionId,
438
+ deferredName: deferred.name,
439
+ }),
440
+ );
441
+ }),
442
+
443
+ deferredDone: Effect.fnUntraced(function*(options: unknown) {
444
+ const convex = yield* ConvexCtx;
445
+ const doneOptions = asDeferredDoneOptions(options);
446
+
447
+ yield* Effect.promise(() =>
448
+ convex.runMutation(component.deferreds.deferredDone, {
449
+ executionId: doneOptions.executionId,
450
+ deferredName: doneOptions.deferredName,
451
+ exit: doneOptions.exit,
452
+ }),
453
+ );
454
+ }),
455
+
456
+ scheduleClock: Effect.fnUntraced(function*(
457
+ _workflow: Workflow.Any,
458
+ options: unknown,
459
+ ) {
460
+ const convex = yield* ConvexCtx;
461
+ const clockOptions = asScheduleClockOptions(options);
462
+
463
+ const execution = yield* Effect.promise(() =>
464
+ readExecution(convex, component, clockOptions.executionId),
465
+ );
466
+ if (!execution) {
467
+ return;
468
+ }
469
+
470
+ const ms = Duration.toMillis(clockOptions.clock.duration);
471
+ const runAfter = Math.max(0, Math.ceil(ms));
472
+
473
+ yield* Effect.promise(() =>
474
+ scheduleDeferredCompletion(convex.scheduler, component, runAfter, {
475
+ executionId: clockOptions.executionId,
476
+ deferredName: clockOptions.clock.deferred.name,
477
+ exit: Exit.void,
478
+ }),
479
+ );
480
+ }),
481
+ };
482
+
483
+ return encoded as unknown as Encoded;
484
+ }
@@ -0,0 +1,47 @@
1
+ export { ConvexWorkflowEngine } from "./ConvexWorkflowEngine.js";
2
+ export type {
3
+ MutationRunCtx,
4
+ QueryRunCtx,
5
+ RunnerArgs,
6
+ RunnerHandle,
7
+ StartWorkflowArgs,
8
+ ResumeWorkflowArgs,
9
+ } from "./ConvexWorkflowEngine.js";
10
+
11
+ export { ConvexCtx, ConvexActionCtx } from "./ConvexCtx.js";
12
+ export type { ConvexWorkflowCtx, ConvexWorkflowActionCtx } from "./ConvexCtx.js";
13
+
14
+ export { makeConvexLogger } from "./ConvexLogger.js";
15
+ export { makeConvexTracer } from "./ConvexTracer.js";
16
+
17
+ export {
18
+ makeConvexEncoded,
19
+ type ConvexEncodedHooks,
20
+ type RunnerEnqueueArgs,
21
+ type ActivityEnqueueArgs,
22
+ } from "./encoded.js";
23
+
24
+ export {
25
+ makeWorkflowEngine,
26
+ provideConvexCtx,
27
+ layerConvexWorkflowEngine,
28
+ } from "./runtime.js";
29
+
30
+ export {
31
+ serializeRunnerHandle,
32
+ scheduleRunnerFanIn,
33
+ scheduleDeferredCompletion,
34
+ } from "./boundaries.js";
35
+
36
+ export { registerActivity, registerWorkflow, getActivity, getWorkflow } from "./registry.js";
37
+ export {
38
+ defineActivityWorker,
39
+ runRegisteredActivity,
40
+ activityWorkerArgs,
41
+ } from "./activityWorker.js";
42
+ export {
43
+ defineWorkflowRunner,
44
+ functionHandleForRunner,
45
+ type RunnerMutationArgs,
46
+ type RunnerOptions,
47
+ } from "./runner.js";
@@ -0,0 +1,35 @@
1
+ import type * as Activity from "@effect/workflow/Activity";
2
+ import type * as Workflow from "@effect/workflow/Workflow";
3
+ import type * as Effect from "effect/Effect";
4
+ import type { WorkflowEngine, WorkflowInstance } from "@effect/workflow/WorkflowEngine";
5
+
6
+ export type WorkflowExecute = (
7
+ payload: object,
8
+ executionId: string,
9
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>;
10
+
11
+ const workflows = new Map<string, { workflow: Workflow.Any; execute: WorkflowExecute }>();
12
+ const activities = new Map<string, Activity.Any>();
13
+
14
+ export function registerWorkflow(workflow: Workflow.Any, execute: WorkflowExecute) {
15
+ workflows.set(workflow.name, { workflow, execute });
16
+ }
17
+
18
+ export function getWorkflow(name: string) {
19
+ return workflows.get(name);
20
+ }
21
+
22
+ export function registerActivity(activity: Activity.Any) {
23
+ activities.set(activity.name, activity);
24
+ }
25
+
26
+ export function getActivity(name: string) {
27
+ return activities.get(name);
28
+ }
29
+
30
+ export function listRegistered() {
31
+ return {
32
+ workflows: [...workflows.keys()],
33
+ activities: [...activities.keys()],
34
+ };
35
+ }