@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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +22 -0
- package/dist/index.cjs +29 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/index.test.ts +266 -0
- package/src/index.ts +61 -3
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:
|
|
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
|
},
|