@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/dist/index.js ADDED
@@ -0,0 +1,687 @@
1
+ import { Run, NewWorkflow, cloneStep, createStep, DefaultExecutionEngine } from '@mastra/core/workflows/vNext';
2
+ import { subscribe } from '@inngest/realtime';
3
+ import { serve as serve$1 } from 'inngest/hono';
4
+ import { RuntimeContext } from '@mastra/core/di';
5
+ import { randomUUID } from 'crypto';
6
+
7
+ // src/index.ts
8
+ function serve({ mastra, ingest }) {
9
+ const wfs = mastra.vnext_getWorkflows();
10
+ const functions = Object.values(wfs).flatMap((wf) => {
11
+ if (wf instanceof InngestWorkflow) {
12
+ return wf.getFunctions();
13
+ }
14
+ return [];
15
+ });
16
+ return serve$1({
17
+ client: ingest,
18
+ functions
19
+ });
20
+ }
21
+ var InngestRun = class extends Run {
22
+ inngest;
23
+ #mastra;
24
+ constructor(params, inngest) {
25
+ super(params);
26
+ this.inngest = inngest;
27
+ this.#mastra = params.mastra;
28
+ }
29
+ async getRuns(eventId) {
30
+ const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
31
+ headers: {
32
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
33
+ }
34
+ });
35
+ const json = await response.json();
36
+ return json.data;
37
+ }
38
+ async getRunOutput(eventId) {
39
+ let runs = await this.getRuns(eventId);
40
+ while (runs?.[0]?.status !== "Completed") {
41
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
42
+ runs = await this.getRuns(eventId);
43
+ if (runs?.[0]?.status === "Failed" || runs?.[0]?.status === "Cancelled") {
44
+ throw new Error(`Function run ${runs?.[0]?.status}`);
45
+ }
46
+ }
47
+ return runs?.[0];
48
+ }
49
+ async start({
50
+ inputData,
51
+ runtimeContext
52
+ }) {
53
+ const eventOutput = await this.inngest.send({
54
+ name: `workflow.${this.workflowId}`,
55
+ data: {
56
+ inputData,
57
+ runId: this.runId
58
+ }
59
+ });
60
+ const eventId = eventOutput.ids[0];
61
+ if (!eventId) {
62
+ throw new Error("Event ID is not set");
63
+ }
64
+ const runOutput = await this.getRunOutput(eventId);
65
+ const result = runOutput?.output?.result;
66
+ if (result.status === "failed") {
67
+ result.error = new Error(result.error);
68
+ }
69
+ return result;
70
+ }
71
+ async resume(params) {
72
+ const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
73
+ (step) => typeof step === "string" ? step : step?.id
74
+ );
75
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
76
+ workflowName: this.workflowId,
77
+ runId: this.runId
78
+ });
79
+ const eventOutput = await this.inngest.send({
80
+ name: `workflow.${this.workflowId}`,
81
+ data: {
82
+ inputData: params.resumeData,
83
+ runId: this.runId,
84
+ stepResults: snapshot?.context,
85
+ resume: {
86
+ steps,
87
+ stepResults: snapshot?.context,
88
+ resumePayload: params.resumeData,
89
+ // @ts-ignore
90
+ resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
91
+ }
92
+ }
93
+ });
94
+ const eventId = eventOutput.ids[0];
95
+ if (!eventId) {
96
+ throw new Error("Event ID is not set");
97
+ }
98
+ const runOutput = await this.getRunOutput(eventId);
99
+ const result = runOutput?.output?.result;
100
+ if (result.status === "failed") {
101
+ result.error = new Error(result.error);
102
+ }
103
+ return result;
104
+ }
105
+ watch(cb) {
106
+ const streamPromise = subscribe(
107
+ {
108
+ channel: `workflow:${this.workflowId}:${this.runId}`,
109
+ topics: ["watch"],
110
+ app: this.inngest
111
+ },
112
+ (message) => {
113
+ cb(message.data);
114
+ }
115
+ );
116
+ return () => {
117
+ streamPromise.then((stream) => {
118
+ stream.cancel();
119
+ });
120
+ };
121
+ }
122
+ };
123
+ var InngestWorkflow = class _InngestWorkflow extends NewWorkflow {
124
+ #mastra;
125
+ inngest;
126
+ function;
127
+ constructor(params, inngest) {
128
+ super(params);
129
+ this.#mastra = params.mastra;
130
+ this.inngest = inngest;
131
+ }
132
+ __registerMastra(mastra) {
133
+ this.#mastra = mastra;
134
+ this.executionEngine.__registerMastra(mastra);
135
+ if (this.executionGraph.steps.length) {
136
+ for (const step of this.executionGraph.steps) {
137
+ if (step.type === "step" && step.step instanceof _InngestWorkflow) {
138
+ step.step.__registerMastra(mastra);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ createRun(options) {
144
+ const runIdToUse = options?.runId || randomUUID();
145
+ return new InngestRun(
146
+ {
147
+ workflowId: this.id,
148
+ runId: runIdToUse,
149
+ executionEngine: this.executionEngine,
150
+ executionGraph: this.executionGraph,
151
+ mastra: this.#mastra,
152
+ retryConfig: this.retryConfig
153
+ },
154
+ this.inngest
155
+ );
156
+ }
157
+ getFunction() {
158
+ if (this.function) {
159
+ return this.function;
160
+ }
161
+ this.function = this.inngest.createFunction(
162
+ // @ts-ignore
163
+ { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
164
+ { event: `workflow.${this.id}` },
165
+ async ({ event, step, attempt, publish }) => {
166
+ let { inputData, runId, resume } = event.data;
167
+ if (!runId) {
168
+ runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
169
+ return randomUUID();
170
+ });
171
+ }
172
+ const emitter = {
173
+ emit: async (event2, data) => {
174
+ if (!publish) {
175
+ return;
176
+ }
177
+ try {
178
+ await publish({
179
+ channel: `workflow:${this.id}:${runId}`,
180
+ topic: "watch",
181
+ data
182
+ });
183
+ } catch (err) {
184
+ this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
185
+ }
186
+ }
187
+ };
188
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
189
+ const result = await engine.execute({
190
+ workflowId: this.id,
191
+ runId,
192
+ graph: this.executionGraph,
193
+ input: inputData,
194
+ emitter,
195
+ retryConfig: this.retryConfig,
196
+ runtimeContext: new RuntimeContext(),
197
+ // TODO
198
+ resume
199
+ });
200
+ return { result, runId };
201
+ }
202
+ );
203
+ return this.function;
204
+ }
205
+ getNestedFunctions(steps) {
206
+ return steps.flatMap((step) => {
207
+ if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
208
+ if (step.step instanceof _InngestWorkflow) {
209
+ return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
210
+ }
211
+ return [];
212
+ } else if (step.type === "parallel" || step.type === "conditional") {
213
+ return this.getNestedFunctions(step.steps);
214
+ }
215
+ return [];
216
+ });
217
+ }
218
+ getFunctions() {
219
+ return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
220
+ }
221
+ };
222
+ function cloneWorkflow(workflow, opts) {
223
+ const wf = new InngestWorkflow(
224
+ {
225
+ id: opts.id,
226
+ inputSchema: workflow.inputSchema,
227
+ outputSchema: workflow.outputSchema,
228
+ steps: workflow.stepDefs,
229
+ mastra: workflow.mastra
230
+ },
231
+ workflow.inngest
232
+ );
233
+ wf.setStepFlow(workflow.stepGraph);
234
+ wf.commit();
235
+ return wf;
236
+ }
237
+ function init(inngest) {
238
+ return {
239
+ createWorkflow(params) {
240
+ return new InngestWorkflow(params, inngest);
241
+ },
242
+ createStep,
243
+ cloneStep,
244
+ cloneWorkflow
245
+ };
246
+ }
247
+ var InngestExecutionEngine = class extends DefaultExecutionEngine {
248
+ inngestStep;
249
+ inngestAttempts;
250
+ constructor(mastra, inngestStep, inngestAttempts = 0) {
251
+ super({ mastra });
252
+ this.inngestStep = inngestStep;
253
+ this.inngestAttempts = inngestAttempts;
254
+ }
255
+ async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
256
+ const base = {
257
+ status: lastOutput.status,
258
+ steps: stepResults
259
+ };
260
+ if (lastOutput.status === "success") {
261
+ await emitter.emit("watch", {
262
+ type: "watch",
263
+ payload: {
264
+ workflowState: {
265
+ status: lastOutput.status,
266
+ steps: stepResults,
267
+ result: lastOutput.output
268
+ }
269
+ },
270
+ eventTimestamp: Date.now()
271
+ });
272
+ base.result = lastOutput.output;
273
+ } else if (lastOutput.status === "failed") {
274
+ base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
275
+ await emitter.emit("watch", {
276
+ type: "watch",
277
+ payload: {
278
+ workflowState: {
279
+ status: lastOutput.status,
280
+ steps: stepResults,
281
+ result: null,
282
+ error: base.error
283
+ }
284
+ },
285
+ eventTimestamp: Date.now()
286
+ });
287
+ } else if (lastOutput.status === "suspended") {
288
+ await emitter.emit("watch", {
289
+ type: "watch",
290
+ payload: {
291
+ workflowState: {
292
+ status: lastOutput.status,
293
+ steps: stepResults,
294
+ result: null,
295
+ error: null
296
+ }
297
+ },
298
+ eventTimestamp: Date.now()
299
+ });
300
+ const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
301
+ if (stepResult?.status === "suspended") {
302
+ const nestedPath = stepResult?.payload?.__workflow_meta?.path;
303
+ return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
304
+ }
305
+ return [];
306
+ });
307
+ base.suspended = suspendedStepIds;
308
+ }
309
+ executionSpan?.end();
310
+ return base;
311
+ }
312
+ async superExecuteStep({
313
+ workflowId,
314
+ runId,
315
+ step,
316
+ stepResults,
317
+ executionContext,
318
+ resume,
319
+ prevOutput,
320
+ emitter,
321
+ runtimeContext
322
+ }) {
323
+ return super.executeStep({
324
+ workflowId,
325
+ runId,
326
+ step,
327
+ stepResults,
328
+ executionContext,
329
+ resume,
330
+ prevOutput,
331
+ emitter,
332
+ runtimeContext
333
+ });
334
+ }
335
+ async executeStep({
336
+ step,
337
+ stepResults,
338
+ executionContext,
339
+ resume,
340
+ prevOutput,
341
+ emitter,
342
+ runtimeContext
343
+ }) {
344
+ await this.inngestStep.run(
345
+ `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
346
+ async () => {
347
+ await emitter.emit("watch", {
348
+ type: "watch",
349
+ payload: {
350
+ currentStep: {
351
+ id: step.id,
352
+ status: "running"
353
+ },
354
+ workflowState: {
355
+ status: "running",
356
+ steps: {
357
+ ...stepResults,
358
+ [step.id]: {
359
+ status: "running"
360
+ }
361
+ },
362
+ result: null,
363
+ error: null
364
+ }
365
+ },
366
+ eventTimestamp: Date.now()
367
+ });
368
+ }
369
+ );
370
+ if (step instanceof InngestWorkflow) {
371
+ const isResume = !!resume?.steps?.length;
372
+ let result;
373
+ let runId;
374
+ if (isResume) {
375
+ runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? randomUUID();
376
+ const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
377
+ workflowName: step.id,
378
+ runId
379
+ });
380
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
381
+ function: step.getFunction(),
382
+ data: {
383
+ inputData: prevOutput,
384
+ runId,
385
+ resume: {
386
+ runId,
387
+ steps: resume.steps.slice(1),
388
+ stepResults: snapshot?.context,
389
+ resumePayload: resume.resumePayload,
390
+ // @ts-ignore
391
+ resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
392
+ }
393
+ }
394
+ });
395
+ result = invokeResp.result;
396
+ runId = invokeResp.runId;
397
+ } else {
398
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
399
+ function: step.getFunction(),
400
+ data: {
401
+ inputData: prevOutput
402
+ }
403
+ });
404
+ result = invokeResp.result;
405
+ runId = invokeResp.runId;
406
+ }
407
+ const res = await this.inngestStep.run(
408
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
409
+ async () => {
410
+ if (result.status === "failed") {
411
+ await emitter.emit("watch", {
412
+ type: "watch",
413
+ payload: {
414
+ currentStep: {
415
+ id: step.id,
416
+ status: "failed",
417
+ error: result?.error
418
+ },
419
+ workflowState: {
420
+ status: "running",
421
+ steps: stepResults,
422
+ result: null,
423
+ error: null
424
+ }
425
+ },
426
+ eventTimestamp: Date.now()
427
+ });
428
+ return { executionContext, result: { status: "failed", error: result?.error } };
429
+ } else if (result.status === "suspended") {
430
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
431
+ const stepRes2 = stepResult;
432
+ return stepRes2?.status === "suspended";
433
+ });
434
+ for (const [stepName, stepResult] of suspendedSteps) {
435
+ const suspendPath = [stepName, ...stepResult?.payload?.__workflow_meta?.path ?? []];
436
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
437
+ await emitter.emit("watch", {
438
+ type: "watch",
439
+ payload: {
440
+ currentStep: {
441
+ id: step.id,
442
+ status: "suspended",
443
+ payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
444
+ },
445
+ workflowState: {
446
+ status: "running",
447
+ steps: stepResults,
448
+ result: null,
449
+ error: null
450
+ }
451
+ },
452
+ eventTimestamp: Date.now()
453
+ });
454
+ return {
455
+ executionContext,
456
+ result: {
457
+ status: "suspended",
458
+ payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
459
+ }
460
+ };
461
+ }
462
+ await emitter.emit("watch", {
463
+ type: "watch",
464
+ payload: {
465
+ currentStep: {
466
+ id: step.id,
467
+ status: "suspended",
468
+ payload: {}
469
+ },
470
+ workflowState: {
471
+ status: "running",
472
+ steps: stepResults,
473
+ result: null,
474
+ error: null
475
+ }
476
+ },
477
+ eventTimestamp: Date.now()
478
+ });
479
+ return {
480
+ executionContext,
481
+ result: {
482
+ status: "suspended",
483
+ payload: {}
484
+ }
485
+ };
486
+ }
487
+ await emitter.emit("watch", {
488
+ type: "watch",
489
+ payload: {
490
+ currentStep: {
491
+ id: step.id,
492
+ status: "success",
493
+ output: result?.result
494
+ },
495
+ workflowState: {
496
+ status: "running",
497
+ steps: stepResults,
498
+ result: null,
499
+ error: null
500
+ }
501
+ },
502
+ eventTimestamp: Date.now()
503
+ });
504
+ return { executionContext, result: { status: "success", output: result?.result } };
505
+ }
506
+ );
507
+ Object.assign(executionContext, res.executionContext);
508
+ return res.result;
509
+ }
510
+ const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
511
+ let execResults;
512
+ let suspended;
513
+ try {
514
+ const result = await step.execute({
515
+ mastra: this.mastra,
516
+ runtimeContext,
517
+ inputData: prevOutput,
518
+ resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
519
+ getInitData: () => stepResults?.input,
520
+ getStepResult: (step2) => {
521
+ const result2 = stepResults[step2.id];
522
+ if (result2?.status === "success") {
523
+ return result2.output;
524
+ }
525
+ return null;
526
+ },
527
+ suspend: async (suspendPayload) => {
528
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
529
+ suspended = { payload: suspendPayload };
530
+ },
531
+ resume: {
532
+ steps: resume?.steps?.slice(1) || [],
533
+ resumePayload: resume?.resumePayload,
534
+ // @ts-ignore
535
+ runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
536
+ },
537
+ emitter
538
+ });
539
+ execResults = { status: "success", output: result };
540
+ } catch (e) {
541
+ execResults = { status: "failed", error: e instanceof Error ? e.message : String(e) };
542
+ }
543
+ if (suspended) {
544
+ execResults = { status: "suspended", payload: suspended.payload };
545
+ }
546
+ if (execResults.status === "failed") {
547
+ if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
548
+ throw execResults.error;
549
+ }
550
+ }
551
+ await emitter.emit("watch", {
552
+ type: "watch",
553
+ payload: {
554
+ currentStep: {
555
+ id: step.id,
556
+ status: execResults.status,
557
+ output: execResults.output
558
+ },
559
+ workflowState: {
560
+ status: "running",
561
+ steps: stepResults,
562
+ result: null,
563
+ error: null
564
+ }
565
+ },
566
+ eventTimestamp: Date.now()
567
+ });
568
+ return { result: execResults, executionContext, stepResults };
569
+ });
570
+ Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
571
+ Object.assign(stepResults, stepRes.stepResults);
572
+ return stepRes.result;
573
+ }
574
+ async persistStepUpdate({
575
+ workflowId,
576
+ runId,
577
+ stepResults,
578
+ executionContext
579
+ }) {
580
+ await this.inngestStep.run(
581
+ `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
582
+ async () => {
583
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
584
+ workflowName: workflowId,
585
+ runId,
586
+ snapshot: {
587
+ runId,
588
+ value: {},
589
+ context: stepResults,
590
+ activePaths: [],
591
+ suspendedPaths: executionContext.suspendedPaths,
592
+ // @ts-ignore
593
+ timestamp: Date.now()
594
+ }
595
+ });
596
+ }
597
+ );
598
+ }
599
+ async executeConditional({
600
+ workflowId,
601
+ runId,
602
+ entry,
603
+ prevOutput,
604
+ prevStep,
605
+ stepResults,
606
+ resume,
607
+ executionContext,
608
+ emitter,
609
+ runtimeContext
610
+ }) {
611
+ let execResults;
612
+ const truthyIndexes = (await Promise.all(
613
+ entry.conditions.map(
614
+ (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
615
+ try {
616
+ const result = await cond({
617
+ mastra: this.mastra,
618
+ runtimeContext,
619
+ inputData: prevOutput,
620
+ getInitData: () => stepResults?.input,
621
+ getStepResult: (step) => {
622
+ if (!step?.id) {
623
+ return null;
624
+ }
625
+ const result2 = stepResults[step.id];
626
+ if (result2?.status === "success") {
627
+ return result2.output;
628
+ }
629
+ return null;
630
+ },
631
+ // TODO: this function shouldn't have suspend probably?
632
+ suspend: async (_suspendPayload) => {
633
+ },
634
+ emitter
635
+ });
636
+ return result ? index : null;
637
+ } catch (e) {
638
+ return null;
639
+ }
640
+ })
641
+ )
642
+ )).filter((index) => index !== null);
643
+ const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
644
+ const results = await Promise.all(
645
+ stepsToRun.map(
646
+ (step, index) => this.executeEntry({
647
+ workflowId,
648
+ runId,
649
+ entry: step,
650
+ prevStep,
651
+ stepResults,
652
+ resume,
653
+ executionContext: {
654
+ workflowId,
655
+ runId,
656
+ executionPath: [...executionContext.executionPath, index],
657
+ suspendedPaths: executionContext.suspendedPaths,
658
+ retryConfig: executionContext.retryConfig,
659
+ executionSpan: executionContext.executionSpan
660
+ },
661
+ emitter,
662
+ runtimeContext
663
+ })
664
+ )
665
+ );
666
+ const hasFailed = results.find((result) => result.status === "failed");
667
+ const hasSuspended = results.find((result) => result.status === "suspended");
668
+ if (hasFailed) {
669
+ execResults = { status: "failed", error: hasFailed.error };
670
+ } else if (hasSuspended) {
671
+ execResults = { status: "suspended", payload: hasSuspended.payload };
672
+ } else {
673
+ execResults = {
674
+ status: "success",
675
+ output: results.reduce((acc, result, index) => {
676
+ if (result.status === "success") {
677
+ acc[stepsToRun[index].step.id] = result.output;
678
+ }
679
+ return acc;
680
+ }, {})
681
+ };
682
+ }
683
+ return execResults;
684
+ }
685
+ };
686
+
687
+ export { InngestExecutionEngine, InngestRun, InngestWorkflow, init, serve };
@@ -0,0 +1,10 @@
1
+ version: '3'
2
+
3
+ services:
4
+ inngest:
5
+ image: inngest/inngest
6
+ command: inngest dev -u http://host.docker.internal:3000/api/inngest --poll-interval=1
7
+ ports:
8
+ - '8288:8288'
9
+ extra_hosts:
10
+ - 'host.docker.internal:host-gateway'