@mastra/inngest 0.0.0-vnext-inngest-20250506123700

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,898 @@
1
+ import {
2
+ NewWorkflow,
3
+ type NewStep as Step,
4
+ DefaultExecutionEngine,
5
+ Run,
6
+ cloneStep,
7
+ } from '@mastra/core/workflows/vNext';
8
+ import type {
9
+ ExecuteFunction,
10
+ ExecutionContext,
11
+ ExecutionEngine,
12
+ ExecutionGraph,
13
+ NewStep,
14
+ NewWorkflowConfig,
15
+ StepFlowEntry,
16
+ StepResult,
17
+ WorkflowResult,
18
+ } from '@mastra/core/workflows/vNext';
19
+ import { type Span } from '@opentelemetry/api';
20
+ import { Inngest, type BaseContext } from 'inngest';
21
+ import { subscribe } from '@inngest/realtime';
22
+ import { serve as inngestServe } from 'inngest/hono';
23
+ import { type Mastra } from '@mastra/core';
24
+ import type { z } from 'zod';
25
+ import { RuntimeContext } from '@mastra/core/di';
26
+ import { randomUUID } from 'crypto';
27
+
28
+ import { createStep } from '@mastra/core/workflows/vNext';
29
+
30
+ export function serve({ mastra, ingest }: { mastra: Mastra; ingest: Inngest }): ReturnType<typeof inngestServe> {
31
+ const wfs = mastra.vnext_getWorkflows();
32
+ const functions = Object.values(wfs).flatMap(wf => {
33
+ if (wf instanceof InngestWorkflow) {
34
+ return wf.getFunctions();
35
+ }
36
+ return [];
37
+ });
38
+ return inngestServe({
39
+ client: ingest,
40
+ functions,
41
+ });
42
+ }
43
+
44
+ export class InngestRun<
45
+ TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
46
+ TInput extends z.ZodType<any> = z.ZodType<any>,
47
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
48
+ > extends Run<TSteps, TInput, TOutput> {
49
+ private inngest: Inngest;
50
+ #mastra: Mastra;
51
+
52
+ constructor(
53
+ params: {
54
+ workflowId: string;
55
+ runId: string;
56
+ executionEngine: ExecutionEngine;
57
+ executionGraph: ExecutionGraph;
58
+ mastra?: Mastra;
59
+ retryConfig?: {
60
+ attempts?: number;
61
+ delay?: number;
62
+ };
63
+ },
64
+ inngest: Inngest,
65
+ ) {
66
+ super(params);
67
+ this.inngest = inngest;
68
+ this.#mastra = params.mastra!;
69
+ }
70
+
71
+ async getRuns(eventId: string) {
72
+ const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
73
+ headers: {
74
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`,
75
+ },
76
+ });
77
+ const json = await response.json();
78
+ return (json as any).data;
79
+ }
80
+
81
+ async getRunOutput(eventId: string) {
82
+ let runs = await this.getRuns(eventId);
83
+ while (runs?.[0]?.status !== 'Completed') {
84
+ await new Promise(resolve => setTimeout(resolve, 1000));
85
+ runs = await this.getRuns(eventId);
86
+ if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
87
+ throw new Error(`Function run ${runs?.[0]?.status}`);
88
+ }
89
+ }
90
+ return runs?.[0];
91
+ }
92
+
93
+ async start({
94
+ inputData,
95
+ runtimeContext,
96
+ }: {
97
+ inputData?: z.infer<TInput>;
98
+ runtimeContext?: RuntimeContext;
99
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
100
+ const eventOutput = await this.inngest.send({
101
+ name: `workflow.${this.workflowId}`,
102
+ data: {
103
+ inputData,
104
+ runId: this.runId,
105
+ },
106
+ });
107
+
108
+ const eventId = eventOutput.ids[0];
109
+ if (!eventId) {
110
+ throw new Error('Event ID is not set');
111
+ }
112
+ const runOutput = await this.getRunOutput(eventId);
113
+ const result = runOutput?.output?.result;
114
+ if (result.status === 'failed') {
115
+ result.error = new Error(result.error);
116
+ }
117
+ return result;
118
+ }
119
+
120
+ async resume<TResumeSchema extends z.ZodType<any>>(params: {
121
+ resumeData?: z.infer<TResumeSchema>;
122
+ step:
123
+ | Step<string, any, any, TResumeSchema, any>
124
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
125
+ | string
126
+ | string[];
127
+ runtimeContext?: RuntimeContext;
128
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
129
+ const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
130
+ typeof step === 'string' ? step : step?.id,
131
+ );
132
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
133
+ workflowName: this.workflowId,
134
+ runId: this.runId,
135
+ });
136
+
137
+ const eventOutput = await this.inngest.send({
138
+ name: `workflow.${this.workflowId}`,
139
+ data: {
140
+ inputData: params.resumeData,
141
+ runId: this.runId,
142
+ stepResults: snapshot?.context as any,
143
+ resume: {
144
+ steps,
145
+ stepResults: snapshot?.context as any,
146
+ resumePayload: params.resumeData,
147
+ // @ts-ignore
148
+ resumePath: snapshot?.suspendedPaths?.[steps?.[0]] as any,
149
+ },
150
+ },
151
+ });
152
+
153
+ const eventId = eventOutput.ids[0];
154
+ if (!eventId) {
155
+ throw new Error('Event ID is not set');
156
+ }
157
+ const runOutput = await this.getRunOutput(eventId);
158
+ const result = runOutput?.output?.result;
159
+ if (result.status === 'failed') {
160
+ result.error = new Error(result.error);
161
+ }
162
+ return result;
163
+ }
164
+
165
+ watch(cb: (event: any) => void): () => void {
166
+ const streamPromise = subscribe(
167
+ {
168
+ channel: `workflow:${this.workflowId}:${this.runId}`,
169
+ topics: ['watch'],
170
+ app: this.inngest,
171
+ },
172
+ (message: any) => {
173
+ cb(message.data);
174
+ },
175
+ );
176
+
177
+ return () => {
178
+ streamPromise.then((stream: any) => {
179
+ stream.cancel();
180
+ });
181
+ };
182
+ }
183
+ }
184
+
185
+ export class InngestWorkflow<
186
+ TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
187
+ TWorkflowId extends string = string,
188
+ TInput extends z.ZodType<any> = z.ZodType<any>,
189
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
190
+ TPrevSchema extends z.ZodType<any> = TInput,
191
+ > extends NewWorkflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
192
+ #mastra: Mastra;
193
+ public inngest: Inngest;
194
+
195
+ private function: ReturnType<Inngest['createFunction']> | undefined;
196
+
197
+ constructor(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
198
+ super(params);
199
+ this.#mastra = params.mastra!;
200
+ this.inngest = inngest;
201
+ }
202
+
203
+ __registerMastra(mastra: Mastra) {
204
+ this.#mastra = mastra;
205
+ this.executionEngine.__registerMastra(mastra);
206
+
207
+ if (this.executionGraph.steps.length) {
208
+ for (const step of this.executionGraph.steps) {
209
+ if (step.type === 'step' && step.step instanceof InngestWorkflow) {
210
+ step.step.__registerMastra(mastra);
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
217
+ const runIdToUse = options?.runId || randomUUID();
218
+
219
+ // Return a new Run instance with object parameters
220
+ return new InngestRun(
221
+ {
222
+ workflowId: this.id,
223
+ runId: runIdToUse,
224
+ executionEngine: this.executionEngine,
225
+ executionGraph: this.executionGraph,
226
+ mastra: this.#mastra,
227
+ retryConfig: this.retryConfig,
228
+ },
229
+ this.inngest,
230
+ );
231
+ }
232
+
233
+ getFunction() {
234
+ if (this.function) {
235
+ return this.function;
236
+ }
237
+ this.function = this.inngest.createFunction(
238
+ // @ts-ignore
239
+ { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
240
+ { event: `workflow.${this.id}` },
241
+ async ({ event, step, attempt, publish }) => {
242
+ let { inputData, runId, resume } = event.data;
243
+
244
+ if (!runId) {
245
+ runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
246
+ return randomUUID();
247
+ });
248
+ }
249
+
250
+ const emitter = {
251
+ emit: async (event: string, data: any) => {
252
+ if (!publish) {
253
+ return;
254
+ }
255
+
256
+ try {
257
+ await publish({
258
+ channel: `workflow:${this.id}:${runId}`,
259
+ topic: 'watch',
260
+ data,
261
+ });
262
+ } catch (err: any) {
263
+ this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
264
+ }
265
+ },
266
+ };
267
+
268
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
269
+ const result = await engine.execute<z.infer<TInput>, WorkflowResult<TOutput, TSteps>>({
270
+ workflowId: this.id,
271
+ runId,
272
+ graph: this.executionGraph,
273
+ input: inputData,
274
+ emitter,
275
+ retryConfig: this.retryConfig,
276
+ runtimeContext: new RuntimeContext(), // TODO
277
+ resume,
278
+ });
279
+
280
+ return { result, runId };
281
+ },
282
+ );
283
+ return this.function;
284
+ }
285
+
286
+ getNestedFunctions(steps: StepFlowEntry[]): ReturnType<Inngest['createFunction']>[] {
287
+ return steps.flatMap(step => {
288
+ if (step.type === 'step' || step.type === 'loop' || step.type === 'foreach') {
289
+ if (step.step instanceof InngestWorkflow) {
290
+ return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
291
+ }
292
+ return [];
293
+ } else if (step.type === 'parallel' || step.type === 'conditional') {
294
+ return this.getNestedFunctions(step.steps);
295
+ }
296
+
297
+ return [];
298
+ });
299
+ }
300
+
301
+ getFunctions() {
302
+ return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
303
+ }
304
+ }
305
+
306
+ function cloneWorkflow<
307
+ TWorkflowId extends string = string,
308
+ TInput extends z.ZodType<any> = z.ZodType<any>,
309
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
310
+ TSteps extends Step<string, any, any, any, any>[] = Step<string, any, any, any, any>[],
311
+ >(
312
+ workflow: InngestWorkflow<TSteps, string, TInput, TOutput>,
313
+ opts: { id: TWorkflowId },
314
+ ): InngestWorkflow<TSteps, TWorkflowId, TInput, TOutput> {
315
+ const wf = new InngestWorkflow(
316
+ {
317
+ id: opts.id,
318
+ inputSchema: workflow.inputSchema,
319
+ outputSchema: workflow.outputSchema,
320
+ steps: workflow.stepDefs,
321
+ mastra: workflow.mastra,
322
+ },
323
+ workflow.inngest,
324
+ );
325
+
326
+ wf.setStepFlow(workflow.stepGraph);
327
+ wf.commit();
328
+ return wf;
329
+ }
330
+
331
+ export function init(inngest: Inngest) {
332
+ return {
333
+ createWorkflow<
334
+ TWorkflowId extends string = string,
335
+ TInput extends z.ZodType<any> = z.ZodType<any>,
336
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
337
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
338
+ >(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
339
+ return new InngestWorkflow(params, inngest);
340
+ },
341
+ createStep,
342
+ cloneStep,
343
+ cloneWorkflow,
344
+ };
345
+ }
346
+
347
+ export class InngestExecutionEngine extends DefaultExecutionEngine {
348
+ private inngestStep: BaseContext<Inngest>['step'];
349
+ private inngestAttempts: number;
350
+
351
+ constructor(mastra: Mastra, inngestStep: BaseContext<Inngest>['step'], inngestAttempts: number = 0) {
352
+ super({ mastra });
353
+ this.inngestStep = inngestStep;
354
+ this.inngestAttempts = inngestAttempts;
355
+ }
356
+
357
+ protected async fmtReturnValue<TOutput>(
358
+ executionSpan: Span | undefined,
359
+ emitter: { emit: (event: string, data: any) => Promise<void> },
360
+ stepResults: Record<string, StepResult<any>>,
361
+ lastOutput: StepResult<any>,
362
+ error?: Error | string,
363
+ ): Promise<TOutput> {
364
+ const base: any = {
365
+ status: lastOutput.status,
366
+ steps: stepResults,
367
+ };
368
+ if (lastOutput.status === 'success') {
369
+ await emitter.emit('watch', {
370
+ type: 'watch',
371
+ payload: {
372
+ workflowState: {
373
+ status: lastOutput.status,
374
+ steps: stepResults,
375
+ result: lastOutput.output,
376
+ },
377
+ },
378
+ eventTimestamp: Date.now(),
379
+ });
380
+
381
+ base.result = lastOutput.output;
382
+ } else if (lastOutput.status === 'failed') {
383
+ base.error =
384
+ error instanceof Error
385
+ ? (error?.stack ?? error.message)
386
+ : lastOutput?.error instanceof Error
387
+ ? lastOutput.error.message
388
+ : (lastOutput.error ?? error ?? 'Unknown error');
389
+
390
+ await emitter.emit('watch', {
391
+ type: 'watch',
392
+ payload: {
393
+ workflowState: {
394
+ status: lastOutput.status,
395
+ steps: stepResults,
396
+ result: null,
397
+ error: base.error,
398
+ },
399
+ },
400
+ eventTimestamp: Date.now(),
401
+ });
402
+ } else if (lastOutput.status === 'suspended') {
403
+ await emitter.emit('watch', {
404
+ type: 'watch',
405
+ payload: {
406
+ workflowState: {
407
+ status: lastOutput.status,
408
+ steps: stepResults,
409
+ result: null,
410
+ error: null,
411
+ },
412
+ },
413
+ eventTimestamp: Date.now(),
414
+ });
415
+
416
+ const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
417
+ if (stepResult?.status === 'suspended') {
418
+ const nestedPath = stepResult?.payload?.__workflow_meta?.path;
419
+ return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
420
+ }
421
+
422
+ return [];
423
+ });
424
+ base.suspended = suspendedStepIds;
425
+ }
426
+
427
+ executionSpan?.end();
428
+ return base as TOutput;
429
+ }
430
+
431
+ async superExecuteStep({
432
+ workflowId,
433
+ runId,
434
+ step,
435
+ stepResults,
436
+ executionContext,
437
+ resume,
438
+ prevOutput,
439
+ emitter,
440
+ runtimeContext,
441
+ }: {
442
+ workflowId: string;
443
+ runId: string;
444
+ step: Step<string, any, any>;
445
+ stepResults: Record<string, StepResult<any>>;
446
+ executionContext: ExecutionContext;
447
+ resume?: {
448
+ steps: string[];
449
+ resumePayload: any;
450
+ };
451
+ prevOutput: any;
452
+ emitter: { emit: (event: string, data: any) => Promise<void> };
453
+ runtimeContext: RuntimeContext;
454
+ }): Promise<StepResult<any>> {
455
+ return super.executeStep({
456
+ workflowId,
457
+ runId,
458
+ step,
459
+ stepResults,
460
+ executionContext,
461
+ resume,
462
+ prevOutput,
463
+ emitter,
464
+ runtimeContext,
465
+ });
466
+ }
467
+
468
+ async executeStep({
469
+ step,
470
+ stepResults,
471
+ executionContext,
472
+ resume,
473
+ prevOutput,
474
+ emitter,
475
+ runtimeContext,
476
+ }: {
477
+ step: Step<string, any, any>;
478
+ stepResults: Record<string, StepResult<any>>;
479
+ executionContext: {
480
+ workflowId: string;
481
+ runId: string;
482
+ executionPath: number[];
483
+ suspendedPaths: Record<string, number[]>;
484
+ retryConfig: { attempts: number; delay: number };
485
+ };
486
+ resume?: {
487
+ steps: string[];
488
+ resumePayload: any;
489
+ runId?: string;
490
+ };
491
+ prevOutput: any;
492
+ emitter: { emit: (event: string, data: any) => Promise<void> };
493
+ runtimeContext: RuntimeContext;
494
+ }): Promise<StepResult<any>> {
495
+ await this.inngestStep.run(
496
+ `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
497
+ async () => {
498
+ await emitter.emit('watch', {
499
+ type: 'watch',
500
+ payload: {
501
+ currentStep: {
502
+ id: step.id,
503
+ status: 'running',
504
+ },
505
+ workflowState: {
506
+ status: 'running',
507
+ steps: {
508
+ ...stepResults,
509
+ [step.id]: {
510
+ status: 'running',
511
+ },
512
+ },
513
+ result: null,
514
+ error: null,
515
+ },
516
+ },
517
+ eventTimestamp: Date.now(),
518
+ });
519
+ },
520
+ );
521
+
522
+ if (step instanceof InngestWorkflow) {
523
+ const isResume = !!resume?.steps?.length;
524
+ let result: WorkflowResult<any, any>;
525
+ let runId: string;
526
+ if (isResume) {
527
+ // @ts-ignore
528
+ runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? randomUUID();
529
+
530
+ const snapshot: any = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
531
+ workflowName: step.id,
532
+ runId: runId,
533
+ });
534
+
535
+ const invokeResp = (await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
536
+ function: step.getFunction(),
537
+ data: {
538
+ inputData: prevOutput,
539
+ runId: runId,
540
+ resume: {
541
+ runId: runId,
542
+ steps: resume.steps.slice(1),
543
+ stepResults: snapshot?.context as any,
544
+ resumePayload: resume.resumePayload,
545
+ // @ts-ignore
546
+ resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]] as any,
547
+ },
548
+ },
549
+ })) as any;
550
+ result = invokeResp.result;
551
+ runId = invokeResp.runId;
552
+ } else {
553
+ const invokeResp = (await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
554
+ function: step.getFunction(),
555
+ data: {
556
+ inputData: prevOutput,
557
+ },
558
+ })) as any;
559
+ result = invokeResp.result;
560
+ runId = invokeResp.runId;
561
+ }
562
+
563
+ const res = await this.inngestStep.run(
564
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
565
+ async () => {
566
+ if (result.status === 'failed') {
567
+ await emitter.emit('watch', {
568
+ type: 'watch',
569
+ payload: {
570
+ currentStep: {
571
+ id: step.id,
572
+ status: 'failed',
573
+ error: result?.error,
574
+ },
575
+ workflowState: {
576
+ status: 'running',
577
+ steps: stepResults,
578
+ result: null,
579
+ error: null,
580
+ },
581
+ },
582
+ eventTimestamp: Date.now(),
583
+ });
584
+
585
+ return { executionContext, result: { status: 'failed', error: result?.error } };
586
+ } else if (result.status === 'suspended') {
587
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
588
+ const stepRes: StepResult<any> = stepResult as StepResult<any>;
589
+ return stepRes?.status === 'suspended';
590
+ });
591
+
592
+ for (const [stepName, stepResult] of suspendedSteps) {
593
+ // @ts-ignore
594
+ const suspendPath: string[] = [stepName, ...(stepResult?.payload?.__workflow_meta?.path ?? [])];
595
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
596
+
597
+ await emitter.emit('watch', {
598
+ type: 'watch',
599
+ payload: {
600
+ currentStep: {
601
+ id: step.id,
602
+ status: 'suspended',
603
+ payload: { ...(stepResult as any)?.payload, __workflow_meta: { runId: runId, path: suspendPath } },
604
+ },
605
+ workflowState: {
606
+ status: 'running',
607
+ steps: stepResults,
608
+ result: null,
609
+ error: null,
610
+ },
611
+ },
612
+ eventTimestamp: Date.now(),
613
+ });
614
+
615
+ return {
616
+ executionContext,
617
+ result: {
618
+ status: 'suspended',
619
+ payload: { ...(stepResult as any)?.payload, __workflow_meta: { runId: runId, path: suspendPath } },
620
+ },
621
+ };
622
+ }
623
+
624
+ await emitter.emit('watch', {
625
+ type: 'watch',
626
+ payload: {
627
+ currentStep: {
628
+ id: step.id,
629
+ status: 'suspended',
630
+ payload: {},
631
+ },
632
+ workflowState: {
633
+ status: 'running',
634
+ steps: stepResults,
635
+ result: null,
636
+ error: null,
637
+ },
638
+ },
639
+ eventTimestamp: Date.now(),
640
+ });
641
+
642
+ return {
643
+ executionContext,
644
+ result: {
645
+ status: 'suspended',
646
+ payload: {},
647
+ },
648
+ };
649
+ }
650
+
651
+ // is success
652
+
653
+ await emitter.emit('watch', {
654
+ type: 'watch',
655
+ payload: {
656
+ currentStep: {
657
+ id: step.id,
658
+ status: 'success',
659
+ output: result?.result,
660
+ },
661
+ workflowState: {
662
+ status: 'running',
663
+ steps: stepResults,
664
+ result: null,
665
+ error: null,
666
+ },
667
+ },
668
+ eventTimestamp: Date.now(),
669
+ });
670
+
671
+ return { executionContext, result: { status: 'success', output: result?.result } };
672
+ },
673
+ );
674
+
675
+ Object.assign(executionContext, res.executionContext);
676
+ return res.result as StepResult<any>;
677
+ }
678
+
679
+ const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
680
+ let execResults: any;
681
+ let suspended: { payload: any } | undefined;
682
+ try {
683
+ const result = await step.execute({
684
+ mastra: this.mastra!,
685
+ runtimeContext,
686
+ inputData: prevOutput,
687
+ resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
688
+ getInitData: () => stepResults?.input as any,
689
+ getStepResult: (step: any) => {
690
+ const result = stepResults[step.id];
691
+ if (result?.status === 'success') {
692
+ return result.output;
693
+ }
694
+
695
+ return null;
696
+ },
697
+ suspend: async (suspendPayload: any) => {
698
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
699
+ suspended = { payload: suspendPayload };
700
+ },
701
+ resume: {
702
+ steps: resume?.steps?.slice(1) || [],
703
+ resumePayload: resume?.resumePayload,
704
+ // @ts-ignore
705
+ runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
706
+ },
707
+ emitter,
708
+ });
709
+
710
+ execResults = { status: 'success', output: result };
711
+ } catch (e) {
712
+ execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
713
+ }
714
+
715
+ if (suspended) {
716
+ execResults = { status: 'suspended', payload: suspended.payload };
717
+ }
718
+
719
+ if (execResults.status === 'failed') {
720
+ if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
721
+ throw execResults.error;
722
+ }
723
+ }
724
+
725
+ await emitter.emit('watch', {
726
+ type: 'watch',
727
+ payload: {
728
+ currentStep: {
729
+ id: step.id,
730
+ status: execResults.status,
731
+ output: execResults.output,
732
+ },
733
+ workflowState: {
734
+ status: 'running',
735
+ steps: stepResults,
736
+ result: null,
737
+ error: null,
738
+ },
739
+ },
740
+ eventTimestamp: Date.now(),
741
+ });
742
+
743
+ return { result: execResults, executionContext, stepResults };
744
+ });
745
+
746
+ // @ts-ignore
747
+ Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
748
+ // @ts-ignore
749
+ Object.assign(stepResults, stepRes.stepResults);
750
+
751
+ // @ts-ignore
752
+ return stepRes.result;
753
+ }
754
+
755
+ async persistStepUpdate({
756
+ workflowId,
757
+ runId,
758
+ stepResults,
759
+ executionContext,
760
+ }: {
761
+ workflowId: string;
762
+ runId: string;
763
+ stepResults: Record<string, StepResult<any>>;
764
+ executionContext: ExecutionContext;
765
+ }) {
766
+ await this.inngestStep.run(
767
+ `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
768
+ async () => {
769
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
770
+ workflowName: workflowId,
771
+ runId,
772
+ snapshot: {
773
+ runId,
774
+ value: {},
775
+ context: stepResults as any,
776
+ activePaths: [],
777
+ suspendedPaths: executionContext.suspendedPaths,
778
+ // @ts-ignore
779
+ timestamp: Date.now(),
780
+ },
781
+ });
782
+ },
783
+ );
784
+ }
785
+
786
+ async executeConditional({
787
+ workflowId,
788
+ runId,
789
+ entry,
790
+ prevOutput,
791
+ prevStep,
792
+ stepResults,
793
+ resume,
794
+ executionContext,
795
+ emitter,
796
+ runtimeContext,
797
+ }: {
798
+ workflowId: string;
799
+ runId: string;
800
+ entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
801
+ prevStep: StepFlowEntry;
802
+ prevOutput: any;
803
+ stepResults: Record<string, StepResult<any>>;
804
+ resume?: {
805
+ steps: string[];
806
+ stepResults: Record<string, StepResult<any>>;
807
+ resumePayload: any;
808
+ resumePath: number[];
809
+ };
810
+ executionContext: ExecutionContext;
811
+ emitter: { emit: (event: string, data: any) => Promise<void> };
812
+ runtimeContext: RuntimeContext;
813
+ }): Promise<StepResult<any>> {
814
+ let execResults: any;
815
+ const truthyIndexes = (
816
+ await Promise.all(
817
+ entry.conditions.map((cond, index) =>
818
+ this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
819
+ try {
820
+ const result = await cond({
821
+ mastra: this.mastra!,
822
+ runtimeContext,
823
+ inputData: prevOutput,
824
+ getInitData: () => stepResults?.input as any,
825
+ getStepResult: (step: any) => {
826
+ if (!step?.id) {
827
+ return null;
828
+ }
829
+
830
+ const result = stepResults[step.id];
831
+ if (result?.status === 'success') {
832
+ return result.output;
833
+ }
834
+
835
+ return null;
836
+ },
837
+
838
+ // TODO: this function shouldn't have suspend probably?
839
+ suspend: async (_suspendPayload: any) => {},
840
+ emitter,
841
+ });
842
+ return result ? index : null;
843
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
844
+ } catch (e: unknown) {
845
+ e;
846
+ return null;
847
+ }
848
+ }),
849
+ ),
850
+ )
851
+ ).filter((index: any): index is number => index !== null);
852
+
853
+ const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
854
+ const results: StepResult<any>[] = await Promise.all(
855
+ stepsToRun.map((step, index) =>
856
+ this.executeEntry({
857
+ workflowId,
858
+ runId,
859
+ entry: step,
860
+ prevStep,
861
+ stepResults,
862
+ resume,
863
+ executionContext: {
864
+ workflowId,
865
+ runId,
866
+ executionPath: [...executionContext.executionPath, index],
867
+ suspendedPaths: executionContext.suspendedPaths,
868
+ retryConfig: executionContext.retryConfig,
869
+ executionSpan: executionContext.executionSpan,
870
+ },
871
+ emitter,
872
+ runtimeContext,
873
+ }),
874
+ ),
875
+ );
876
+ const hasFailed = results.find(result => result.status === 'failed');
877
+ const hasSuspended = results.find(result => result.status === 'suspended');
878
+ if (hasFailed) {
879
+ execResults = { status: 'failed', error: hasFailed.error };
880
+ } else if (hasSuspended) {
881
+ execResults = { status: 'suspended', payload: hasSuspended.payload };
882
+ } else {
883
+ execResults = {
884
+ status: 'success',
885
+ output: results.reduce((acc: Record<string, any>, result, index) => {
886
+ if (result.status === 'success') {
887
+ // @ts-ignore
888
+ acc[stepsToRun[index]!.step.id] = result.output;
889
+ }
890
+
891
+ return acc;
892
+ }, {}),
893
+ };
894
+ }
895
+
896
+ return execResults;
897
+ }
898
+ }