@mastra/inngest 0.11.0 → 0.11.1

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 CHANGED
@@ -10,13 +10,17 @@ import { z } from 'zod';
10
10
  // src/index.ts
11
11
  function serve({ mastra, inngest }) {
12
12
  const wfs = mastra.getWorkflows();
13
- const functions = Object.values(wfs).flatMap((wf) => {
14
- if (wf instanceof InngestWorkflow) {
15
- wf.__registerMastra(mastra);
16
- return wf.getFunctions();
17
- }
18
- return [];
19
- });
13
+ const functions = Array.from(
14
+ new Set(
15
+ Object.values(wfs).flatMap((wf) => {
16
+ if (wf instanceof InngestWorkflow) {
17
+ wf.__registerMastra(mastra);
18
+ return wf.getFunctions();
19
+ }
20
+ return [];
21
+ })
22
+ )
23
+ );
20
24
  return serve$1({
21
25
  client: inngest,
22
26
  functions
@@ -46,8 +50,14 @@ var InngestRun = class extends Run {
46
50
  while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
47
51
  await new Promise((resolve) => setTimeout(resolve, 1e3));
48
52
  runs = await this.getRuns(eventId);
49
- if (runs?.[0]?.status === "Failed" || runs?.[0]?.status === "Cancelled") {
53
+ if (runs?.[0]?.status === "Failed") {
50
54
  throw new Error(`Function run ${runs?.[0]?.status}`);
55
+ } else if (runs?.[0]?.status === "Cancelled") {
56
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
57
+ workflowName: this.workflowId,
58
+ runId: this.runId
59
+ });
60
+ return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
51
61
  }
52
62
  }
53
63
  return runs?.[0];
@@ -58,6 +68,28 @@ var InngestRun = class extends Run {
58
68
  data
59
69
  });
60
70
  }
71
+ async cancel() {
72
+ await this.inngest.send({
73
+ name: `cancel.workflow.${this.workflowId}`,
74
+ data: {
75
+ runId: this.runId
76
+ }
77
+ });
78
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
79
+ workflowName: this.workflowId,
80
+ runId: this.runId
81
+ });
82
+ if (snapshot) {
83
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
84
+ workflowName: this.workflowId,
85
+ runId: this.runId,
86
+ snapshot: {
87
+ ...snapshot,
88
+ status: "canceled"
89
+ }
90
+ });
91
+ }
92
+ }
61
93
  async start({
62
94
  inputData
63
95
  }) {
@@ -91,7 +123,9 @@ var InngestRun = class extends Run {
91
123
  if (result.status === "failed") {
92
124
  result.error = new Error(result.error);
93
125
  }
94
- this.cleanup?.();
126
+ if (result.status !== "suspended") {
127
+ this.cleanup?.();
128
+ }
95
129
  return result;
96
130
  }
97
131
  async resume(params) {
@@ -293,23 +327,26 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
293
327
  this.inngest
294
328
  );
295
329
  this.runs.set(runIdToUse, run);
296
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
297
- workflowName: this.id,
298
- runId: runIdToUse,
299
- snapshot: {
330
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse);
331
+ if (!workflowSnapshotInStorage) {
332
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
333
+ workflowName: this.id,
300
334
  runId: runIdToUse,
301
- status: "pending",
302
- value: {},
303
- context: {},
304
- activePaths: [],
305
- serializedStepGraph: this.serializedStepGraph,
306
- suspendedPaths: {},
307
- result: void 0,
308
- error: void 0,
309
- // @ts-ignore
310
- timestamp: Date.now()
311
- }
312
- });
335
+ snapshot: {
336
+ runId: runIdToUse,
337
+ status: "pending",
338
+ value: {},
339
+ context: {},
340
+ activePaths: [],
341
+ serializedStepGraph: this.serializedStepGraph,
342
+ suspendedPaths: {},
343
+ result: void 0,
344
+ error: void 0,
345
+ // @ts-ignore
346
+ timestamp: Date.now()
347
+ }
348
+ });
349
+ }
313
350
  return run;
314
351
  }
315
352
  getFunction() {
@@ -317,8 +354,12 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
317
354
  return this.function;
318
355
  }
319
356
  this.function = this.inngest.createFunction(
320
- // @ts-ignore
321
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
357
+ {
358
+ id: `workflow.${this.id}`,
359
+ // @ts-ignore
360
+ retries: this.retryConfig?.attempts ?? 0,
361
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }]
362
+ },
322
363
  { event: `workflow.${this.id}` },
323
364
  async ({ event, step, attempt, publish }) => {
324
365
  let { inputData, runId, resume } = event.data;
@@ -360,7 +401,8 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
360
401
  retryConfig: this.retryConfig,
361
402
  runtimeContext: new RuntimeContext(),
362
403
  // TODO
363
- resume
404
+ resume,
405
+ abortController: new AbortController()
364
406
  });
365
407
  return { result, runId };
366
408
  }
@@ -404,7 +446,7 @@ function createStep(params) {
404
446
  outputSchema: z.object({
405
447
  text: z.string()
406
448
  }),
407
- execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext }) => {
449
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort }) => {
408
450
  let streamPromise = {};
409
451
  streamPromise.promise = new Promise((resolve, reject) => {
410
452
  streamPromise.resolve = resolve;
@@ -424,8 +466,12 @@ function createStep(params) {
424
466
  runtimeContext,
425
467
  onFinish: (result) => {
426
468
  streamPromise.resolve(result.text);
427
- }
469
+ },
470
+ abortSignal
428
471
  });
472
+ if (abortSignal.aborted) {
473
+ return abort();
474
+ }
429
475
  for await (const chunk of fullStream) {
430
476
  switch (chunk.type) {
431
477
  case "text-delta":
@@ -600,6 +646,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
600
646
  resume,
601
647
  prevOutput,
602
648
  emitter,
649
+ abortController,
603
650
  runtimeContext
604
651
  }) {
605
652
  return super.executeStep({
@@ -611,6 +658,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
611
658
  resume,
612
659
  prevOutput,
613
660
  emitter,
661
+ abortController,
614
662
  runtimeContext
615
663
  });
616
664
  }
@@ -634,6 +682,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
634
682
  resume,
635
683
  prevOutput,
636
684
  emitter,
685
+ abortController,
637
686
  runtimeContext
638
687
  }) {
639
688
  const startedAt = await this.inngestStep.run(
@@ -664,7 +713,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
664
713
  await emitter.emit("watch-v2", {
665
714
  type: "step-start",
666
715
  payload: {
667
- id: step.id
716
+ id: step.id,
717
+ status: "running"
668
718
  }
669
719
  });
670
720
  return startedAt2;
@@ -732,7 +782,9 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
732
782
  type: "step-result",
733
783
  payload: {
734
784
  id: step.id,
735
- status: "failed"
785
+ status: "failed",
786
+ error: result?.error,
787
+ payload: prevOutput
736
788
  }
737
789
  });
738
790
  return { executionContext, result: { status: "failed", error: result?.error } };
@@ -764,7 +816,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
764
816
  await emitter.emit("watch-v2", {
765
817
  type: "step-suspended",
766
818
  payload: {
767
- id: step.id
819
+ id: step.id,
820
+ status: "suspended"
768
821
  }
769
822
  });
770
823
  return {
@@ -817,6 +870,14 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
817
870
  },
818
871
  eventTimestamp: Date.now()
819
872
  });
873
+ await emitter.emit("watch-v2", {
874
+ type: "step-result",
875
+ payload: {
876
+ id: step.id,
877
+ status: "success",
878
+ output: result?.result
879
+ }
880
+ });
820
881
  await emitter.emit("watch-v2", {
821
882
  type: "step-finish",
822
883
  payload: {
@@ -865,7 +926,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
865
926
  [EMITTER_SYMBOL]: emitter,
866
927
  engine: {
867
928
  step: this.inngestStep
868
- }
929
+ },
930
+ abortSignal: abortController.signal
869
931
  });
870
932
  const endedAt = Date.now();
871
933
  execResults = {
@@ -927,8 +989,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
927
989
  type: "step-suspended",
928
990
  payload: {
929
991
  id: step.id,
930
- status: execResults.status,
931
- output: execResults.status === "success" ? execResults?.output : void 0
992
+ ...execResults
932
993
  }
933
994
  });
934
995
  } else {
@@ -936,8 +997,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
936
997
  type: "step-result",
937
998
  payload: {
938
999
  id: step.id,
939
- status: execResults.status,
940
- output: execResults.status === "success" ? execResults?.output : void 0
1000
+ ...execResults
941
1001
  }
942
1002
  });
943
1003
  await emitter.emit("watch-v2", {
@@ -998,6 +1058,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
998
1058
  resume,
999
1059
  executionContext,
1000
1060
  emitter,
1061
+ abortController,
1001
1062
  runtimeContext
1002
1063
  }) {
1003
1064
  let execResults;
@@ -1009,6 +1070,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1009
1070
  runId,
1010
1071
  mastra: this.mastra,
1011
1072
  runtimeContext,
1073
+ runCount: -1,
1012
1074
  inputData: prevOutput,
1013
1075
  getInitData: () => stepResults?.input,
1014
1076
  getStepResult: (step) => {
@@ -1026,10 +1088,14 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1026
1088
  },
1027
1089
  bail: () => {
1028
1090
  },
1091
+ abort: () => {
1092
+ abortController.abort();
1093
+ },
1029
1094
  [EMITTER_SYMBOL]: emitter,
1030
1095
  engine: {
1031
1096
  step: this.inngestStep
1032
- }
1097
+ },
1098
+ abortSignal: abortController.signal
1033
1099
  });
1034
1100
  return result ? index : null;
1035
1101
  } catch (e) {
@@ -1058,6 +1124,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1058
1124
  executionSpan: executionContext.executionSpan
1059
1125
  },
1060
1126
  emitter,
1127
+ abortController,
1061
1128
  runtimeContext
1062
1129
  })
1063
1130
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/inngest",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Mastra Inngest integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,17 +33,17 @@
33
33
  "eslint": "^9.29.0",
34
34
  "execa": "^9.6.0",
35
35
  "get-port": "7.1.0",
36
- "hono": "^4.7.11",
36
+ "hono": "^4.8.3",
37
37
  "tsup": "^8.5.0",
38
38
  "typescript": "^5.8.3",
39
- "vitest": "^2.1.9",
40
- "@internal/lint": "0.0.14",
41
- "@mastra/core": "0.10.7",
39
+ "vitest": "^3.2.4",
40
+ "@internal/lint": "0.0.16",
42
41
  "@mastra/libsql": "0.11.0",
43
- "@mastra/deployer": "0.10.7"
42
+ "@mastra/deployer": "0.10.9",
43
+ "@mastra/core": "0.10.9"
44
44
  },
45
45
  "peerDependencies": {
46
- "@mastra/core": ">=0.10.7-0 <0.11.0-0"
46
+ "@mastra/core": ">=0.10.9-0 <0.11.0-0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
package/src/index.test.ts CHANGED
@@ -732,6 +732,203 @@ describe('MastraInngestWorkflow', () => {
732
732
  });
733
733
  });
734
734
 
735
+ describe('abort', () => {
736
+ it('should be able to abort workflow execution in between steps', async ctx => {
737
+ const inngest = new Inngest({
738
+ id: 'mastra',
739
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
740
+ middleware: [realtimeMiddleware()],
741
+ });
742
+
743
+ const { createWorkflow, createStep } = init(inngest);
744
+
745
+ const step1 = createStep({
746
+ id: 'step1',
747
+ execute: async ({ inputData }) => {
748
+ return { result: 'step1: ' + inputData.value };
749
+ },
750
+ inputSchema: z.object({ value: z.string() }),
751
+ outputSchema: z.object({ result: z.string() }),
752
+ });
753
+ const step2 = createStep({
754
+ id: 'step2',
755
+ execute: async ({ inputData }) => {
756
+ return { result: 'step2: ' + inputData.result };
757
+ },
758
+ inputSchema: z.object({ result: z.string() }),
759
+ outputSchema: z.object({ result: z.string() }),
760
+ });
761
+
762
+ const workflow = createWorkflow({
763
+ id: 'test-workflow',
764
+ inputSchema: z.object({}),
765
+ outputSchema: z.object({
766
+ result: z.string(),
767
+ }),
768
+ steps: [step1, step2],
769
+ });
770
+
771
+ workflow.then(step1).sleep(2000).then(step2).commit();
772
+
773
+ const mastra = new Mastra({
774
+ storage: new DefaultStorage({
775
+ url: ':memory:',
776
+ }),
777
+ workflows: {
778
+ 'test-workflow': workflow,
779
+ },
780
+ server: {
781
+ apiRoutes: [
782
+ {
783
+ path: '/inngest/api',
784
+ method: 'ALL',
785
+ createHandler: async ({ mastra }) => inngestServe({ mastra, inngest }),
786
+ },
787
+ ],
788
+ },
789
+ });
790
+
791
+ const app = await createHonoServer(mastra);
792
+
793
+ const srv = serve({
794
+ fetch: app.fetch,
795
+ port: (ctx as any).handlerPort,
796
+ });
797
+ await new Promise(resolve => setTimeout(resolve, 2000));
798
+
799
+ const run = await workflow.createRunAsync();
800
+ const p = run.start({ inputData: { value: 'test' } });
801
+
802
+ setTimeout(() => {
803
+ run.cancel();
804
+ }, 1000);
805
+
806
+ const result = await p;
807
+
808
+ srv.close();
809
+
810
+ expect(result.status).toBe('canceled');
811
+ expect(result.steps['step1']).toEqual({
812
+ status: 'success',
813
+ output: { result: 'step1: test' },
814
+ payload: { value: 'test' },
815
+ startedAt: expect.any(Number),
816
+ endedAt: expect.any(Number),
817
+ });
818
+
819
+ expect(result.steps['step2']).toBeUndefined();
820
+ });
821
+
822
+ it('should be able to abort workflow execution during a step', async ctx => {
823
+ const inngest = new Inngest({
824
+ id: 'mastra',
825
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
826
+ middleware: [realtimeMiddleware()],
827
+ });
828
+
829
+ const { createWorkflow, createStep } = init(inngest);
830
+
831
+ const step1 = createStep({
832
+ id: 'step1',
833
+ execute: async ({ inputData }) => {
834
+ return { result: 'step1: ' + inputData.value };
835
+ },
836
+ inputSchema: z.object({ value: z.string() }),
837
+ outputSchema: z.object({ result: z.string() }),
838
+ });
839
+ const step2 = createStep({
840
+ id: 'step2',
841
+ execute: async ({ inputData, abortSignal, abort }) => {
842
+ console.log('abort signal', abortSignal);
843
+ const timeout: Promise<string> = new Promise((resolve, _reject) => {
844
+ const ref = setTimeout(() => {
845
+ resolve('step2: ' + inputData.result);
846
+ }, 5000);
847
+
848
+ abortSignal.addEventListener('abort', () => {
849
+ resolve('');
850
+ clearTimeout(ref);
851
+ });
852
+ });
853
+
854
+ const result = await timeout;
855
+ if (abortSignal.aborted) {
856
+ return abort();
857
+ }
858
+ return { result };
859
+ },
860
+ inputSchema: z.object({ result: z.string() }),
861
+ outputSchema: z.object({ result: z.string() }),
862
+ });
863
+
864
+ const workflow = createWorkflow({
865
+ id: 'test-workflow',
866
+ inputSchema: z.object({}),
867
+ outputSchema: z.object({
868
+ result: z.string(),
869
+ }),
870
+ steps: [step1, step2],
871
+ });
872
+
873
+ workflow.then(step1).then(step2).commit();
874
+
875
+ const mastra = new Mastra({
876
+ storage: new DefaultStorage({
877
+ url: ':memory:',
878
+ }),
879
+ workflows: {
880
+ 'test-workflow': workflow,
881
+ },
882
+ server: {
883
+ apiRoutes: [
884
+ {
885
+ path: '/inngest/api',
886
+ method: 'ALL',
887
+ createHandler: async ({ mastra }) => inngestServe({ mastra, inngest }),
888
+ },
889
+ ],
890
+ },
891
+ });
892
+
893
+ const app = await createHonoServer(mastra);
894
+
895
+ const srv = serve({
896
+ fetch: app.fetch,
897
+ port: (ctx as any).handlerPort,
898
+ });
899
+ await new Promise(resolve => setTimeout(resolve, 2000));
900
+
901
+ const run = await workflow.createRunAsync();
902
+ const p = run.start({ inputData: { value: 'test' } });
903
+
904
+ setTimeout(() => {
905
+ run.cancel();
906
+ }, 1000);
907
+
908
+ const result = await p;
909
+ console.log('result', result);
910
+
911
+ srv.close();
912
+
913
+ expect(result.status).toBe('canceled');
914
+ expect(result.steps['step1']).toEqual({
915
+ status: 'success',
916
+ output: { result: 'step1: test' },
917
+ payload: { value: 'test' },
918
+ startedAt: expect.any(Number),
919
+ endedAt: expect.any(Number),
920
+ });
921
+
922
+ // expect(result.steps['step2']).toEqual({
923
+ // status: 'canceled',
924
+ // payload: { result: 'step1: test' },
925
+ // output: undefined,
926
+ // startedAt: expect.any(Number),
927
+ // endedAt: expect.any(Number),
928
+ // });
929
+ });
930
+ });
931
+
735
932
  describe('Variable Resolution', () => {
736
933
  it('should resolve trigger data', async ctx => {
737
934
  const inngest = new Inngest({
@@ -5965,68 +6162,76 @@ describe('MastraInngestWorkflow', () => {
5965
6162
  srv.close();
5966
6163
 
5967
6164
  expect(watchData.length).toBe(8);
5968
- expect(watchData).toMatchInlineSnapshot(`
5969
- [
5970
- {
5971
- "payload": {
5972
- "runId": "test-run-id",
5973
- },
5974
- "type": "start",
6165
+ expect(watchData).toMatchObject([
6166
+ {
6167
+ payload: {
6168
+ runId: 'test-run-id',
5975
6169
  },
5976
- {
5977
- "payload": {
5978
- "id": "step1",
5979
- },
5980
- "type": "step-start",
6170
+ type: 'start',
6171
+ },
6172
+ {
6173
+ payload: {
6174
+ id: 'step1',
6175
+ status: 'running',
5981
6176
  },
5982
- {
5983
- "payload": {
5984
- "id": "step1",
5985
- "output": {
5986
- "result": "success1",
5987
- },
5988
- "status": "success",
6177
+ type: 'step-start',
6178
+ },
6179
+ {
6180
+ payload: {
6181
+ id: 'step1',
6182
+ endedAt: expect.any(Number),
6183
+ startedAt: expect.any(Number),
6184
+ payload: {},
6185
+ output: {
6186
+ result: 'success1',
5989
6187
  },
5990
- "type": "step-result",
6188
+ status: 'success',
5991
6189
  },
5992
- {
5993
- "payload": {
5994
- "id": "step1",
5995
- "metadata": {},
5996
- },
5997
- "type": "step-finish",
6190
+ type: 'step-result',
6191
+ },
6192
+ {
6193
+ payload: {
6194
+ id: 'step1',
6195
+ metadata: {},
5998
6196
  },
5999
- {
6000
- "payload": {
6001
- "id": "step2",
6002
- },
6003
- "type": "step-start",
6197
+ type: 'step-finish',
6198
+ },
6199
+ {
6200
+ payload: {
6201
+ id: 'step2',
6202
+ status: 'running',
6004
6203
  },
6005
- {
6006
- "payload": {
6007
- "id": "step2",
6008
- "output": {
6009
- "result": "success2",
6010
- },
6011
- "status": "success",
6204
+ type: 'step-start',
6205
+ },
6206
+ {
6207
+ payload: {
6208
+ id: 'step2',
6209
+ endedAt: expect.any(Number),
6210
+ startedAt: expect.any(Number),
6211
+ payload: {
6212
+ result: 'success1',
6012
6213
  },
6013
- "type": "step-result",
6014
- },
6015
- {
6016
- "payload": {
6017
- "id": "step2",
6018
- "metadata": {},
6214
+ output: {
6215
+ result: 'success2',
6019
6216
  },
6020
- "type": "step-finish",
6217
+ status: 'success',
6021
6218
  },
6022
- {
6023
- "payload": {
6024
- "runId": "test-run-id",
6025
- },
6026
- "type": "finish",
6219
+ type: 'step-result',
6220
+ },
6221
+ {
6222
+ payload: {
6223
+ id: 'step2',
6224
+ metadata: {},
6027
6225
  },
6028
- ]
6029
- `);
6226
+ type: 'step-finish',
6227
+ },
6228
+ {
6229
+ payload: {
6230
+ runId: 'test-run-id',
6231
+ },
6232
+ type: 'finish',
6233
+ },
6234
+ ]);
6030
6235
  // Verify execution completed successfully
6031
6236
  expect(executionResult.steps.step1).toMatchObject({
6032
6237
  status: 'success',