@mastra/inngest 0.12.2 → 0.12.3-alpha.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/src/index.test.ts CHANGED
@@ -6,6 +6,8 @@ import { realtimeMiddleware } from '@inngest/realtime';
6
6
  import { Agent } from '@mastra/core/agent';
7
7
  import { Mastra } from '@mastra/core/mastra';
8
8
  import { RuntimeContext } from '@mastra/core/runtime-context';
9
+ import type { MastraScorer } from '@mastra/core/scores';
10
+ import { createScorer, runExperiment } from '@mastra/core/scores';
9
11
  import { Telemetry } from '@mastra/core/telemetry';
10
12
  import { createTool } from '@mastra/core/tools';
11
13
  import type { StreamEvent } from '@mastra/core/workflows';
@@ -7813,5 +7815,269 @@ describe('MastraInngestWorkflow', () => {
7813
7815
  },
7814
7816
  ]);
7815
7817
  });
7818
+
7819
+ describe('Workflow integration', () => {
7820
+ let mockScorers: MastraScorer[];
7821
+ beforeEach(() => {
7822
+ const createMockScorer = (name: string, score: number = 0.8): MastraScorer => {
7823
+ const scorer = createScorer({
7824
+ description: 'Mock scorer',
7825
+ name,
7826
+ }).generateScore(() => {
7827
+ return score;
7828
+ });
7829
+
7830
+ vi.spyOn(scorer, 'run');
7831
+
7832
+ return scorer;
7833
+ };
7834
+
7835
+ vi.clearAllMocks();
7836
+ mockScorers = [createMockScorer('toxicity', 0.9), createMockScorer('relevance', 0.7)];
7837
+ });
7838
+
7839
+ it('should run experiment with workflow target', async ctx => {
7840
+ const inngest = new Inngest({
7841
+ id: 'mastra',
7842
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
7843
+ middleware: [realtimeMiddleware()],
7844
+ });
7845
+
7846
+ const { createWorkflow, createStep } = init(inngest);
7847
+
7848
+ // Create a simple workflow
7849
+ const mockStep = createStep({
7850
+ id: 'test-step',
7851
+ inputSchema: z.object({ input: z.string() }),
7852
+ outputSchema: z.object({ output: z.string() }),
7853
+ execute: async ({ inputData }) => {
7854
+ return { output: `Processed: ${inputData.input}` };
7855
+ },
7856
+ });
7857
+
7858
+ const workflow = createWorkflow({
7859
+ id: 'test-workflow',
7860
+ inputSchema: z.object({ input: z.string() }),
7861
+ outputSchema: z.object({ output: z.string() }),
7862
+ })
7863
+ .then(mockStep)
7864
+ .commit();
7865
+
7866
+ const mastra = new Mastra({
7867
+ storage: new DefaultStorage({
7868
+ url: ':memory:',
7869
+ }),
7870
+ workflows: {
7871
+ 'test-workflow': workflow,
7872
+ },
7873
+ server: {
7874
+ apiRoutes: [
7875
+ {
7876
+ path: '/inngest/api',
7877
+ method: 'ALL',
7878
+ createHandler: async ({ mastra }) => inngestServe({ mastra, inngest }),
7879
+ },
7880
+ ],
7881
+ },
7882
+ });
7883
+
7884
+ const app = await createHonoServer(mastra);
7885
+
7886
+ const srv = (globServer = serve({
7887
+ fetch: app.fetch,
7888
+ port: (ctx as any).handlerPort,
7889
+ }));
7890
+
7891
+ await resetInngest();
7892
+
7893
+ const result = await runExperiment({
7894
+ data: [
7895
+ { input: { input: 'Test input 1' }, groundTruth: 'Expected 1' },
7896
+ { input: { input: 'Test input 2' }, groundTruth: 'Expected 2' },
7897
+ ],
7898
+ scorers: [mockScorers[0]],
7899
+ target: workflow,
7900
+ });
7901
+ srv.close();
7902
+ expect(result.scores.toxicity).toBe(0.9);
7903
+ expect(result.summary.totalItems).toBe(2);
7904
+ });
7905
+ });
7906
+ });
7907
+
7908
+ describe.sequential('Flow Control Configuration', () => {
7909
+ it('should accept workflow configuration with flow control properties', async ctx => {
7910
+ const inngest = new Inngest({
7911
+ id: 'mastra-flow-control',
7912
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
7913
+ middleware: [realtimeMiddleware()],
7914
+ });
7915
+
7916
+ const { createWorkflow, createStep } = init(inngest);
7917
+
7918
+ const step1 = createStep({
7919
+ id: 'step1',
7920
+ execute: async ({ inputData }) => {
7921
+ return { result: 'step1: ' + inputData.value };
7922
+ },
7923
+ inputSchema: z.object({ value: z.string() }),
7924
+ outputSchema: z.object({ result: z.string() }),
7925
+ });
7926
+
7927
+ // Test workflow with flow control configuration
7928
+ const workflow = createWorkflow({
7929
+ id: 'flow-control-test',
7930
+ inputSchema: z.object({ value: z.string() }),
7931
+ outputSchema: z.object({ result: z.string() }),
7932
+ steps: [step1],
7933
+ // Flow control properties
7934
+ concurrency: {
7935
+ limit: 5,
7936
+ key: 'event.data.userId',
7937
+ },
7938
+ rateLimit: {
7939
+ period: '1h',
7940
+ limit: 100,
7941
+ },
7942
+ priority: {
7943
+ run: 'event.data.priority ?? 50',
7944
+ },
7945
+ });
7946
+
7947
+ expect(workflow).toBeDefined();
7948
+ expect(workflow.id).toBe('flow-control-test');
7949
+
7950
+ // Verify that function creation includes flow control config
7951
+ const inngestFunction = workflow.getFunction();
7952
+ expect(inngestFunction).toBeDefined();
7953
+ });
7954
+
7955
+ it('should handle workflow configuration with partial flow control properties', async ctx => {
7956
+ const inngest = new Inngest({
7957
+ id: 'mastra-partial-flow-control',
7958
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
7959
+ middleware: [realtimeMiddleware()],
7960
+ });
7961
+
7962
+ const { createWorkflow, createStep } = init(inngest);
7963
+
7964
+ const step1 = createStep({
7965
+ id: 'step1',
7966
+ execute: async ({ inputData }) => {
7967
+ return { result: 'step1: ' + inputData.value };
7968
+ },
7969
+ inputSchema: z.object({ value: z.string() }),
7970
+ outputSchema: z.object({ result: z.string() }),
7971
+ });
7972
+
7973
+ // Test workflow with only some flow control properties
7974
+ const workflow = createWorkflow({
7975
+ id: 'partial-flow-control-test',
7976
+ inputSchema: z.object({ value: z.string() }),
7977
+ outputSchema: z.object({ result: z.string() }),
7978
+ steps: [step1],
7979
+ // Only concurrency control
7980
+ concurrency: {
7981
+ limit: 10,
7982
+ },
7983
+ });
7984
+
7985
+ expect(workflow).toBeDefined();
7986
+ expect(workflow.id).toBe('partial-flow-control-test');
7987
+
7988
+ const inngestFunction = workflow.getFunction();
7989
+ expect(inngestFunction).toBeDefined();
7990
+ });
7991
+
7992
+ it('should handle workflow configuration without flow control properties (backward compatibility)', async ctx => {
7993
+ const inngest = new Inngest({
7994
+ id: 'mastra-backward-compat',
7995
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
7996
+ middleware: [realtimeMiddleware()],
7997
+ });
7998
+
7999
+ const { createWorkflow, createStep } = init(inngest);
8000
+
8001
+ const step1 = createStep({
8002
+ id: 'step1',
8003
+ execute: async ({ inputData }) => {
8004
+ return { result: 'step1: ' + inputData.value };
8005
+ },
8006
+ inputSchema: z.object({ value: z.string() }),
8007
+ outputSchema: z.object({ result: z.string() }),
8008
+ });
8009
+
8010
+ // Test workflow without any flow control properties (existing behavior)
8011
+ const workflow = createWorkflow({
8012
+ id: 'backward-compat-test',
8013
+ inputSchema: z.object({ value: z.string() }),
8014
+ outputSchema: z.object({ result: z.string() }),
8015
+ steps: [step1],
8016
+ retryConfig: {
8017
+ attempts: 3,
8018
+ delay: 1000,
8019
+ },
8020
+ });
8021
+
8022
+ expect(workflow).toBeDefined();
8023
+ expect(workflow.id).toBe('backward-compat-test');
8024
+
8025
+ const inngestFunction = workflow.getFunction();
8026
+ expect(inngestFunction).toBeDefined();
8027
+ });
8028
+
8029
+ it('should support all flow control configuration types', async ctx => {
8030
+ const inngest = new Inngest({
8031
+ id: 'mastra-all-flow-control',
8032
+ baseUrl: `http://localhost:${(ctx as any).inngestPort}`,
8033
+ middleware: [realtimeMiddleware()],
8034
+ });
8035
+
8036
+ const { createWorkflow, createStep } = init(inngest);
8037
+
8038
+ const step1 = createStep({
8039
+ id: 'step1',
8040
+ execute: async ({ inputData }) => {
8041
+ return { result: 'step1: ' + inputData.value };
8042
+ },
8043
+ inputSchema: z.object({ value: z.string() }),
8044
+ outputSchema: z.object({ result: z.string() }),
8045
+ });
8046
+
8047
+ // Test workflow with all flow control configuration types
8048
+ const workflow = createWorkflow({
8049
+ id: 'all-flow-control-test',
8050
+ inputSchema: z.object({ value: z.string() }),
8051
+ outputSchema: z.object({ result: z.string() }),
8052
+ steps: [step1],
8053
+ // All flow control properties
8054
+ concurrency: {
8055
+ limit: 5,
8056
+ key: 'event.data.userId',
8057
+ },
8058
+ rateLimit: {
8059
+ period: '1m',
8060
+ limit: 10,
8061
+ },
8062
+ throttle: {
8063
+ period: '10s',
8064
+ limit: 1,
8065
+ key: 'event.data.organizationId',
8066
+ },
8067
+ debounce: {
8068
+ period: '5s',
8069
+ key: 'event.data.messageId',
8070
+ },
8071
+ priority: {
8072
+ run: 'event.data.priority ?? 0',
8073
+ },
8074
+ });
8075
+
8076
+ expect(workflow).toBeDefined();
8077
+ expect(workflow.id).toBe('all-flow-control-test');
8078
+
8079
+ const inngestFunction = workflow.getFunction();
8080
+ expect(inngestFunction).toBeDefined();
8081
+ });
7816
8082
  });
7817
8083
  }, 40e3);
package/src/index.ts CHANGED
@@ -33,6 +33,30 @@ import type { Inngest, BaseContext } from 'inngest';
33
33
  import { serve as inngestServe } from 'inngest/hono';
34
34
  import { z } from 'zod';
35
35
 
36
+ // Extract Inngest's native flow control configuration types
37
+ type InngestCreateFunctionConfig = Parameters<Inngest['createFunction']>[0];
38
+
39
+ // Extract specific flow control properties (excluding batching)
40
+ export type InngestFlowControlConfig = Pick<
41
+ InngestCreateFunctionConfig,
42
+ 'concurrency' | 'rateLimit' | 'throttle' | 'debounce' | 'priority'
43
+ >;
44
+
45
+ // Union type for Inngest workflows with flow control
46
+ export type InngestWorkflowConfig<
47
+ TWorkflowId extends string = string,
48
+ TInput extends z.ZodType<any> = z.ZodType<any>,
49
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
50
+ TSteps extends Step<string, any, any, any, any, any>[] = Step<string, any, any, any, any, any>[],
51
+ > = WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps> & InngestFlowControlConfig;
52
+
53
+ // Compile-time compatibility assertion
54
+ type _AssertInngestCompatibility =
55
+ InngestFlowControlConfig extends Pick<Parameters<Inngest['createFunction']>[0], keyof InngestFlowControlConfig>
56
+ ? true
57
+ : never;
58
+ const _compatibilityCheck: _AssertInngestCompatibility = true;
59
+
36
60
  export type InngestEngineType = {
37
61
  step: any;
38
62
  };
@@ -342,9 +366,19 @@ export class InngestWorkflow<
342
366
  public inngest: Inngest;
343
367
 
344
368
  private function: ReturnType<Inngest['createFunction']> | undefined;
369
+ private readonly flowControlConfig?: InngestFlowControlConfig;
370
+
371
+ constructor(params: InngestWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
372
+ const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
373
+
374
+ super(workflowParams as WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>);
375
+
376
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
377
+ ([_, value]) => value !== undefined,
378
+ );
379
+
380
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : undefined;
345
381
 
346
- constructor(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
347
- super(params);
348
382
  this.#mastra = params.mastra!;
349
383
  this.inngest = inngest;
350
384
  }
@@ -513,6 +547,8 @@ export class InngestWorkflow<
513
547
  // @ts-ignore
514
548
  retries: this.retryConfig?.attempts ?? 0,
515
549
  cancelOn: [{ event: `cancel.workflow.${this.id}` }],
550
+ // Spread flow control configuration
551
+ ...this.flowControlConfig,
516
552
  },
517
553
  { event: `workflow.${this.id}` },
518
554
  async ({ event, step, attempt, publish }) => {
@@ -801,7 +837,7 @@ export function init(inngest: Inngest) {
801
837
  any,
802
838
  InngestEngineType
803
839
  >[],
804
- >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
840
+ >(params: InngestWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
805
841
  return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
806
842
  },
807
843
  createStep,
@@ -1230,6 +1266,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1230
1266
  abortController,
1231
1267
  runtimeContext,
1232
1268
  writableStream,
1269
+ disableScorers,
1233
1270
  tracingContext,
1234
1271
  }: {
1235
1272
  step: Step<string, any, any>;
@@ -1245,6 +1282,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1245
1282
  abortController: AbortController;
1246
1283
  runtimeContext: RuntimeContext;
1247
1284
  writableStream?: WritableStream<ChunkType>;
1285
+ disableScorers?: boolean;
1248
1286
  tracingContext?: TracingContext;
1249
1287
  }): Promise<StepResult<any, any, any, any>> {
1250
1288
  const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
@@ -1636,6 +1674,23 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1636
1674
  return { result: execResults, executionContext, stepResults };
1637
1675
  });
1638
1676
 
1677
+ if (disableScorers !== false) {
1678
+ await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1679
+ if (step.scorers) {
1680
+ await this.runScorers({
1681
+ scorers: step.scorers,
1682
+ runId: executionContext.runId,
1683
+ input: prevOutput,
1684
+ output: stepRes.result,
1685
+ workflowId: executionContext.workflowId,
1686
+ stepId: step.id,
1687
+ runtimeContext,
1688
+ disableScorers,
1689
+ });
1690
+ }
1691
+ });
1692
+ }
1693
+
1639
1694
  // @ts-ignore
1640
1695
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1641
1696
  // @ts-ignore
@@ -1704,6 +1759,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1704
1759
  abortController,
1705
1760
  runtimeContext,
1706
1761
  writableStream,
1762
+ disableScorers,
1707
1763
  tracingContext,
1708
1764
  }: {
1709
1765
  workflowId: string;
@@ -1728,6 +1784,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1728
1784
  abortController: AbortController;
1729
1785
  runtimeContext: RuntimeContext;
1730
1786
  writableStream?: WritableStream<ChunkType>;
1787
+ disableScorers?: boolean;
1731
1788
  tracingContext?: TracingContext;
1732
1789
  }): Promise<StepResult<any, any, any, any>> {
1733
1790
  const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
@@ -1855,6 +1912,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1855
1912
  abortController,
1856
1913
  runtimeContext,
1857
1914
  writableStream,
1915
+ disableScorers,
1858
1916
  tracingContext: {
1859
1917
  currentSpan: conditionalSpan,
1860
1918
  },