Skip to content

Commit ffca381

Browse files
authored
Include batch context on ActionContext (#521)
Adds `context.batch` so a component can tell whether the flow it is running in dispatches its trigger's items as batches, and at what size. Available to every perform — triggers need it before the first fetch to size that fetch, and CNI steps downstream of a batched trigger can branch on it too.
1 parent 05bb674 commit ffca381

5 files changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, it } from "vitest";
2+
import { flow, trigger } from ".";
3+
import { invokeFlow, invokeTrigger } from "./testing";
4+
import type { BatchInfo } from "./types/BatchContext";
5+
6+
describe("context.batch", () => {
7+
const echoBatch = trigger({
8+
display: { label: "Echo Batch", description: "Returns the batch context it saw." },
9+
inputs: {},
10+
perform: async (context) => ({
11+
payload: {
12+
...({} as never),
13+
body: { data: context.batch ?? null },
14+
},
15+
}),
16+
scheduleSupport: "invalid",
17+
synchronousResponseSupport: "invalid",
18+
});
19+
20+
it("reaches a component trigger's perform", async () => {
21+
const { result } = await invokeTrigger(echoBatch, {
22+
batch: { enabled: true, batchSize: 50 },
23+
});
24+
expect(result?.payload.body.data).toEqual({ enabled: true, batchSize: 50 });
25+
});
26+
27+
it("is undefined when the context omits it", async () => {
28+
const { result } = await invokeTrigger(echoBatch);
29+
expect(result?.payload.body.data).toBeNull();
30+
});
31+
32+
it("carries enabled false through to the perform", async () => {
33+
const { result } = await invokeTrigger(echoBatch, { batch: { enabled: false } });
34+
expect(result?.payload.body.data).toEqual({ enabled: false });
35+
});
36+
37+
it("exposes batchSize on the enabled variant", () => {
38+
const disabled: BatchInfo = { enabled: false };
39+
const enabled: BatchInfo = { enabled: true, batchSize: 25 };
40+
// @ts-expect-error batchSize belongs to the enabled variant.
41+
expect(disabled.batchSize).toBeUndefined();
42+
expect(enabled.enabled ? enabled.batchSize : 0).toBe(25);
43+
});
44+
45+
it("reaches a CNI flow's onExecution", async () => {
46+
const seen: unknown[] = [];
47+
const cniFlow = flow({
48+
name: "Batch Aware Flow",
49+
stableKey: "batch-aware-flow",
50+
description: "Records the batch context its step body saw.",
51+
onExecution: async (context) => {
52+
seen.push(context.batch ?? null);
53+
return Promise.resolve({ data: null });
54+
},
55+
});
56+
57+
await invokeFlow(cniFlow, { context: { batch: { enabled: true, batchSize: 25 } } });
58+
59+
expect(seen).toEqual([{ enabled: true, batchSize: 25 }]);
60+
});
61+
});

packages/spectral/src/serverTypes/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* component and integration definitions. */
33

44
import type {
5+
BatchInfo,
56
ComponentManifest,
67
ConfigVarResultCollection,
78
CustomerAttributes,
@@ -144,6 +145,8 @@ export type ActionContext<
144145
startedAt: string;
145146
executionFrame: ExecutionFrame;
146147
flowSchemas: FlowSchemas;
148+
/** How this flow dispatches its trigger's items. */
149+
batch?: BatchInfo;
147150

148151
// @TODO - Hidden from the user-facing ActionContext.
149152
globalDebug?: boolean;

packages/spectral/src/types/ActionPerformFunction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AxiosRequestConfig, AxiosResponse } from "axios";
22
import type { ActionInputParameters } from "./ActionInputParameters";
33
import type { ActionLogger } from "./ActionLogger";
44
import type { ActionPerformReturn } from "./ActionPerformReturn";
5+
import type { BatchInfo } from "./BatchContext";
56
import type { ComponentManifest } from "./ComponentManifest";
67
import type { CustomerAttributes } from "./CustomerAttributes";
78
import type { FlowAttributes } from "./FlowAttributes";
@@ -168,4 +169,9 @@ export type ActionContext<
168169
flowSchemas: FlowSchemas;
169170
/** Whether the execution is being run with simulated data. */
170171
isSimulatedTestExecution?: boolean;
172+
/**
173+
* How this flow dispatches its trigger's items, and when batching is enabled,
174+
* the size of the batches for each run of the flow.
175+
*/
176+
batch?: BatchInfo;
171177
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* How the flow this execution belongs to dispatches its trigger's items.
3+
*
4+
* Discriminated on `enabled` - a flow that is not using batching has no batch
5+
* size to report.
6+
*/
7+
export type BatchInfo =
8+
| {
9+
/** This flow dispatches its trigger's items one execution at a time. */
10+
enabled: false;
11+
}
12+
| {
13+
/** This flow dispatches its trigger's items as batches. */
14+
enabled: true;
15+
/** Items dispatched per batch. */
16+
batchSize: number;
17+
};

packages/spectral/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from "./ActionInputParameters";
88
export * from "./ActionLogger";
99
export * from "./ActionPerformFunction";
1010
export * from "./ActionPerformReturn";
11+
export * from "./BatchContext";
1112
export * from "./ComponentDefinition";
1213
export * from "./ComponentManifest";
1314
export * from "./ComponentRegistry";

0 commit comments

Comments
 (0)