@mastra/inngest 0.0.0-ai-v5-20250626003446 → 0.0.0-ai-v5-20250710191716

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 CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import type { ReadableStream } from 'node:stream/web';
2
3
  import { subscribe } from '@inngest/realtime';
3
4
  import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
5
  import { RuntimeContext } from '@mastra/core/di';
@@ -32,13 +33,17 @@ export type InngestEngineType = {
32
33
 
33
34
  export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest }): ReturnType<typeof inngestServe> {
34
35
  const wfs = mastra.getWorkflows();
35
- const functions = Object.values(wfs).flatMap(wf => {
36
- if (wf instanceof InngestWorkflow) {
37
- wf.__registerMastra(mastra);
38
- return wf.getFunctions();
39
- }
40
- return [];
41
- });
36
+ const functions = Array.from(
37
+ new Set(
38
+ Object.values(wfs).flatMap(wf => {
39
+ if (wf instanceof InngestWorkflow) {
40
+ wf.__registerMastra(mastra);
41
+ return wf.getFunctions();
42
+ }
43
+ return [];
44
+ }),
45
+ ),
46
+ );
42
47
  return inngestServe({
43
48
  client: inngest,
44
49
  functions,
@@ -93,8 +98,15 @@ export class InngestRun<
93
98
  while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
94
99
  await new Promise(resolve => setTimeout(resolve, 1000));
95
100
  runs = await this.getRuns(eventId);
96
- if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
101
+ if (runs?.[0]?.status === 'Failed') {
102
+ console.log('run', runs?.[0]);
97
103
  throw new Error(`Function run ${runs?.[0]?.status}`);
104
+ } else if (runs?.[0]?.status === 'Cancelled') {
105
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
106
+ workflowName: this.workflowId,
107
+ runId: this.runId,
108
+ });
109
+ return { output: { result: { steps: snapshot?.context, status: 'canceled' } } };
98
110
  }
99
111
  }
100
112
  return runs?.[0];
@@ -107,6 +119,30 @@ export class InngestRun<
107
119
  });
108
120
  }
109
121
 
122
+ async cancel() {
123
+ await this.inngest.send({
124
+ name: `cancel.workflow.${this.workflowId}`,
125
+ data: {
126
+ runId: this.runId,
127
+ },
128
+ });
129
+
130
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
131
+ workflowName: this.workflowId,
132
+ runId: this.runId,
133
+ });
134
+ if (snapshot) {
135
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
136
+ workflowName: this.workflowId,
137
+ runId: this.runId,
138
+ snapshot: {
139
+ ...snapshot,
140
+ status: 'canceled' as any,
141
+ },
142
+ });
143
+ }
144
+ }
145
+
110
146
  async start({
111
147
  inputData,
112
148
  }: {
@@ -146,7 +182,9 @@ export class InngestRun<
146
182
  result.error = new Error(result.error);
147
183
  }
148
184
 
149
- this.cleanup?.();
185
+ if (result.status !== 'suspended') {
186
+ this.cleanup?.();
187
+ }
150
188
  return result;
151
189
  }
152
190
 
@@ -278,7 +316,7 @@ export class InngestRun<
278
316
  });
279
317
 
280
318
  return {
281
- stream: readable,
319
+ stream: readable as ReadableStream<StreamEvent>,
282
320
  getWorkflowState: () => this.executionResults!,
283
321
  };
284
322
  }
@@ -409,13 +447,64 @@ export class InngestWorkflow<
409
447
  return run;
410
448
  }
411
449
 
450
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
451
+ const runIdToUse = options?.runId || randomUUID();
452
+
453
+ // Return a new Run instance with object parameters
454
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
455
+ this.runs.get(runIdToUse) ??
456
+ new InngestRun(
457
+ {
458
+ workflowId: this.id,
459
+ runId: runIdToUse,
460
+ executionEngine: this.executionEngine,
461
+ executionGraph: this.executionGraph,
462
+ serializedStepGraph: this.serializedStepGraph,
463
+ mastra: this.#mastra,
464
+ retryConfig: this.retryConfig,
465
+ cleanup: () => this.runs.delete(runIdToUse),
466
+ },
467
+ this.inngest,
468
+ );
469
+
470
+ this.runs.set(runIdToUse, run);
471
+
472
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse);
473
+
474
+ if (!workflowSnapshotInStorage) {
475
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
476
+ workflowName: this.id,
477
+ runId: runIdToUse,
478
+ snapshot: {
479
+ runId: runIdToUse,
480
+ status: 'pending',
481
+ value: {},
482
+ context: {},
483
+ activePaths: [],
484
+ serializedStepGraph: this.serializedStepGraph,
485
+ suspendedPaths: {},
486
+ result: undefined,
487
+ error: undefined,
488
+ // @ts-ignore
489
+ timestamp: Date.now(),
490
+ },
491
+ });
492
+ }
493
+
494
+ return run;
495
+ }
496
+
412
497
  getFunction() {
413
498
  if (this.function) {
414
499
  return this.function;
415
500
  }
416
501
  this.function = this.inngest.createFunction(
417
- // @ts-ignore
418
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
502
+ {
503
+ id: `workflow.${this.id}`,
504
+ // @ts-ignore
505
+ retries: this.retryConfig?.attempts ?? 0,
506
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
507
+ },
419
508
  { event: `workflow.${this.id}` },
420
509
  async ({ event, step, attempt, publish }) => {
421
510
  let { inputData, runId, resume } = event.data;
@@ -464,6 +553,7 @@ export class InngestWorkflow<
464
553
  retryConfig: this.retryConfig,
465
554
  runtimeContext: new RuntimeContext(), // TODO
466
555
  resume,
556
+ abortController: new AbortController(),
467
557
  });
468
558
 
469
559
  return { result, runId };
@@ -586,7 +676,7 @@ export function createStep<
586
676
  outputSchema: z.object({
587
677
  text: z.string(),
588
678
  }),
589
- execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext }) => {
679
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort }) => {
590
680
  let streamPromise = {} as {
591
681
  promise: Promise<string>;
592
682
  resolve: (value: string) => void;
@@ -612,8 +702,13 @@ export function createStep<
612
702
  onFinish: result => {
613
703
  streamPromise.resolve(result.text);
614
704
  },
705
+ abortSignal,
615
706
  });
616
707
 
708
+ if (abortSignal.aborted) {
709
+ return abort();
710
+ }
711
+
617
712
  for await (const chunk of fullStream) {
618
713
  switch (chunk.type) {
619
714
  case 'text':
@@ -771,6 +866,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
771
866
  delay?: number;
772
867
  };
773
868
  runtimeContext: RuntimeContext;
869
+ abortController: AbortController;
774
870
  }): Promise<TOutput> {
775
871
  await params.emitter.emit('watch-v2', {
776
872
  type: 'start',
@@ -870,6 +966,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
870
966
  resume,
871
967
  prevOutput,
872
968
  emitter,
969
+ abortController,
873
970
  runtimeContext,
874
971
  }: {
875
972
  workflowId: string;
@@ -883,6 +980,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
883
980
  };
884
981
  prevOutput: any;
885
982
  emitter: Emitter;
983
+ abortController: AbortController;
886
984
  runtimeContext: RuntimeContext;
887
985
  }): Promise<StepResult<any, any, any, any>> {
888
986
  return super.executeStep({
@@ -894,12 +992,163 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
894
992
  resume,
895
993
  prevOutput,
896
994
  emitter,
995
+ abortController,
897
996
  runtimeContext,
898
997
  });
899
998
  }
900
999
 
901
- async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
902
- await this.inngestStep.sleep(id, duration);
1000
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
1001
+ // await this.inngestStep.sleep(id, duration);
1002
+ // }
1003
+
1004
+ async executeSleep({
1005
+ workflowId,
1006
+ runId,
1007
+ entry,
1008
+ prevOutput,
1009
+ stepResults,
1010
+ emitter,
1011
+ abortController,
1012
+ runtimeContext,
1013
+ }: {
1014
+ workflowId: string;
1015
+ runId: string;
1016
+ serializedStepGraph: SerializedStepFlowEntry[];
1017
+ entry: {
1018
+ type: 'sleep';
1019
+ id: string;
1020
+ duration?: number;
1021
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
1022
+ };
1023
+ prevStep: StepFlowEntry;
1024
+ prevOutput: any;
1025
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1026
+ resume?: {
1027
+ steps: string[];
1028
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1029
+ resumePayload: any;
1030
+ resumePath: number[];
1031
+ };
1032
+ executionContext: ExecutionContext;
1033
+ emitter: Emitter;
1034
+ abortController: AbortController;
1035
+ runtimeContext: RuntimeContext;
1036
+ }): Promise<void> {
1037
+ let { duration, fn } = entry;
1038
+
1039
+ if (fn) {
1040
+ duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
1041
+ return await fn({
1042
+ runId,
1043
+ mastra: this.mastra!,
1044
+ runtimeContext,
1045
+ inputData: prevOutput,
1046
+ runCount: -1,
1047
+ getInitData: () => stepResults?.input as any,
1048
+ getStepResult: (step: any) => {
1049
+ if (!step?.id) {
1050
+ return null;
1051
+ }
1052
+
1053
+ const result = stepResults[step.id];
1054
+ if (result?.status === 'success') {
1055
+ return result.output;
1056
+ }
1057
+
1058
+ return null;
1059
+ },
1060
+
1061
+ // TODO: this function shouldn't have suspend probably?
1062
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1063
+ bail: () => {},
1064
+ abort: () => {
1065
+ abortController?.abort();
1066
+ },
1067
+ [EMITTER_SYMBOL]: emitter,
1068
+ engine: { step: this.inngestStep },
1069
+ abortSignal: abortController?.signal,
1070
+ });
1071
+ });
1072
+ }
1073
+
1074
+ await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
1075
+ }
1076
+
1077
+ async executeSleepUntil({
1078
+ workflowId,
1079
+ runId,
1080
+ entry,
1081
+ prevOutput,
1082
+ stepResults,
1083
+ emitter,
1084
+ abortController,
1085
+ runtimeContext,
1086
+ }: {
1087
+ workflowId: string;
1088
+ runId: string;
1089
+ serializedStepGraph: SerializedStepFlowEntry[];
1090
+ entry: {
1091
+ type: 'sleepUntil';
1092
+ id: string;
1093
+ date?: Date;
1094
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
1095
+ };
1096
+ prevStep: StepFlowEntry;
1097
+ prevOutput: any;
1098
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1099
+ resume?: {
1100
+ steps: string[];
1101
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1102
+ resumePayload: any;
1103
+ resumePath: number[];
1104
+ };
1105
+ executionContext: ExecutionContext;
1106
+ emitter: Emitter;
1107
+ abortController: AbortController;
1108
+ runtimeContext: RuntimeContext;
1109
+ }): Promise<void> {
1110
+ let { date, fn } = entry;
1111
+
1112
+ if (fn) {
1113
+ date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
1114
+ return await fn({
1115
+ runId,
1116
+ mastra: this.mastra!,
1117
+ runtimeContext,
1118
+ inputData: prevOutput,
1119
+ runCount: -1,
1120
+ getInitData: () => stepResults?.input as any,
1121
+ getStepResult: (step: any) => {
1122
+ if (!step?.id) {
1123
+ return null;
1124
+ }
1125
+
1126
+ const result = stepResults[step.id];
1127
+ if (result?.status === 'success') {
1128
+ return result.output;
1129
+ }
1130
+
1131
+ return null;
1132
+ },
1133
+
1134
+ // TODO: this function shouldn't have suspend probably?
1135
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1136
+ bail: () => {},
1137
+ abort: () => {
1138
+ abortController?.abort();
1139
+ },
1140
+ [EMITTER_SYMBOL]: emitter,
1141
+ engine: { step: this.inngestStep },
1142
+ abortSignal: abortController?.signal,
1143
+ });
1144
+ });
1145
+ }
1146
+
1147
+ if (!(date instanceof Date)) {
1148
+ return;
1149
+ }
1150
+
1151
+ await this.inngestStep.sleepUntil(entry.id, date);
903
1152
  }
904
1153
 
905
1154
  async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
@@ -922,17 +1171,12 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
922
1171
  resume,
923
1172
  prevOutput,
924
1173
  emitter,
1174
+ abortController,
925
1175
  runtimeContext,
926
1176
  }: {
927
1177
  step: Step<string, any, any>;
928
1178
  stepResults: Record<string, StepResult<any, any, any, any>>;
929
- executionContext: {
930
- workflowId: string;
931
- runId: string;
932
- executionPath: number[];
933
- suspendedPaths: Record<string, number[]>;
934
- retryConfig: { attempts: number; delay: number };
935
- };
1179
+ executionContext: ExecutionContext;
936
1180
  resume?: {
937
1181
  steps: string[];
938
1182
  resumePayload: any;
@@ -940,6 +1184,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
940
1184
  };
941
1185
  prevOutput: any;
942
1186
  emitter: Emitter;
1187
+ abortController: AbortController;
943
1188
  runtimeContext: RuntimeContext;
944
1189
  }): Promise<StepResult<any, any, any, any>> {
945
1190
  const startedAt = await this.inngestStep.run(
@@ -972,6 +1217,9 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
972
1217
  type: 'step-start',
973
1218
  payload: {
974
1219
  id: step.id,
1220
+ status: 'running',
1221
+ payload: prevOutput,
1222
+ startedAt,
975
1223
  },
976
1224
  });
977
1225
 
@@ -1047,6 +1295,8 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1047
1295
  payload: {
1048
1296
  id: step.id,
1049
1297
  status: 'failed',
1298
+ error: result?.error,
1299
+ payload: prevOutput,
1050
1300
  },
1051
1301
  });
1052
1302
 
@@ -1084,6 +1334,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1084
1334
  type: 'step-suspended',
1085
1335
  payload: {
1086
1336
  id: step.id,
1337
+ status: 'suspended',
1087
1338
  },
1088
1339
  });
1089
1340
 
@@ -1143,6 +1394,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1143
1394
  eventTimestamp: Date.now(),
1144
1395
  });
1145
1396
 
1397
+ await emitter.emit('watch-v2', {
1398
+ type: 'step-result',
1399
+ payload: {
1400
+ id: step.id,
1401
+ status: 'success',
1402
+ output: result?.result,
1403
+ },
1404
+ });
1405
+
1146
1406
  await emitter.emit('watch-v2', {
1147
1407
  type: 'step-finish',
1148
1408
  payload: {
@@ -1162,6 +1422,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1162
1422
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1163
1423
  let execResults: any;
1164
1424
  let suspended: { payload: any } | undefined;
1425
+ let bailed: { payload: any } | undefined;
1165
1426
 
1166
1427
  try {
1167
1428
  const result = await step.execute({
@@ -1183,6 +1444,9 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1183
1444
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1184
1445
  suspended = { payload: suspendPayload };
1185
1446
  },
1447
+ bail: (result: any) => {
1448
+ bailed = { payload: result };
1449
+ },
1186
1450
  resume: {
1187
1451
  steps: resume?.steps?.slice(1) || [],
1188
1452
  resumePayload: resume?.resumePayload,
@@ -1193,6 +1457,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1193
1457
  engine: {
1194
1458
  step: this.inngestStep,
1195
1459
  },
1460
+ abortSignal: abortController.signal,
1196
1461
  });
1197
1462
  const endedAt = Date.now();
1198
1463
 
@@ -1227,6 +1492,8 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1227
1492
  resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1228
1493
  resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1229
1494
  };
1495
+ } else if (bailed) {
1496
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
1230
1497
  }
1231
1498
 
1232
1499
  if (execResults.status === 'failed') {
@@ -1257,8 +1524,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1257
1524
  type: 'step-suspended',
1258
1525
  payload: {
1259
1526
  id: step.id,
1260
- status: execResults.status,
1261
- output: execResults.status === 'success' ? execResults?.output : undefined,
1527
+ ...execResults,
1262
1528
  },
1263
1529
  });
1264
1530
  } else {
@@ -1266,8 +1532,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1266
1532
  type: 'step-result',
1267
1533
  payload: {
1268
1534
  id: step.id,
1269
- status: execResults.status,
1270
- output: execResults.status === 'success' ? execResults?.output : undefined,
1535
+ ...execResults,
1271
1536
  },
1272
1537
  });
1273
1538
 
@@ -1346,6 +1611,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1346
1611
  resume,
1347
1612
  executionContext,
1348
1613
  emitter,
1614
+ abortController,
1349
1615
  runtimeContext,
1350
1616
  }: {
1351
1617
  workflowId: string;
@@ -1367,6 +1633,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1367
1633
  };
1368
1634
  executionContext: ExecutionContext;
1369
1635
  emitter: Emitter;
1636
+ abortController: AbortController;
1370
1637
  runtimeContext: RuntimeContext;
1371
1638
  }): Promise<StepResult<any, any, any, any>> {
1372
1639
  let execResults: any;
@@ -1379,6 +1646,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1379
1646
  runId,
1380
1647
  mastra: this.mastra!,
1381
1648
  runtimeContext,
1649
+ runCount: -1,
1382
1650
  inputData: prevOutput,
1383
1651
  getInitData: () => stepResults?.input as any,
1384
1652
  getStepResult: (step: any) => {
@@ -1396,10 +1664,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1396
1664
 
1397
1665
  // TODO: this function shouldn't have suspend probably?
1398
1666
  suspend: async (_suspendPayload: any) => {},
1667
+ bail: () => {},
1668
+ abort: () => {
1669
+ abortController.abort();
1670
+ },
1399
1671
  [EMITTER_SYMBOL]: emitter,
1400
1672
  engine: {
1401
1673
  step: this.inngestStep,
1402
1674
  },
1675
+ abortSignal: abortController.signal,
1403
1676
  });
1404
1677
  return result ? index : null;
1405
1678
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1431,6 +1704,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1431
1704
  executionSpan: executionContext.executionSpan,
1432
1705
  },
1433
1706
  emitter,
1707
+ abortController,
1434
1708
  runtimeContext,
1435
1709
  }),
1436
1710
  ),
package/vitest.config.ts CHANGED
@@ -4,5 +4,11 @@ export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  include: ['src/**/*.test.ts'],
7
+ pool: 'threads',
8
+ poolOptions: {
9
+ threads: {
10
+ singleThread: true,
11
+ },
12
+ },
7
13
  },
8
14
  });