Skip to content

Commit

Permalink
[sc-44597] Support optional stage name property (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
Blacksmoke16 authored Oct 21, 2024
1 parent 5c63900 commit b7d3074
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 23 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ As of version `0.1.0` stages can be one of two types
- PipelineStageConfiguration

`PipelineStageConfiguration` adds the ability for the user to define a `rollback` function, which should undo changes made by the `execute` function.
It also allows providing an optional `name` property for debugging/logging purposes.
Defaults to `Stage ${0..n}` based on the index of the stage in the pipeline.

The pipeline can support processing a collection of stages of either type.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/__mocks__/TestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ export const errorStage: TestStage = () => {
*/
export function generateStageWithRollback(
rollbackFunction: () => Promise<void> | void,
name?: string,
): TestStageWithRollback {
return {
execute: noop,
rollback: rollbackFunction,
name: name,
};
}

Expand Down
41 changes: 26 additions & 15 deletions src/__tests__/buildPipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { buildPipeline } from "buildPipeline";
import { PipelineError, PipelineRollbackError } from "error";
import { logStageMiddlewareFactory } from "middleware/logStageMiddlewareFactory";
import {
TestMiddleware,
TestPipelineArguments,
TestPipelineContext,
TestPipelineResults,
TestStage,
TestStageWithRollback,
additionStage,
errorStage,
generateStageWithRollback,
initializer,
returnHistoryResult,
returnSumResult,
TestMiddleware,
TestPipelineArguments,
TestPipelineContext,
TestPipelineResults,
testPipelineResultValidator,
TestStage,
TestStageWithRollback,
} from "../__mocks__/TestPipeline";

const INCREMENT = 5;
Expand All @@ -33,10 +33,14 @@ const errorStages: TestStage[] = [errorStage, returnHistoryResult];
const rollback1 = jest.fn();
const rollback2 = jest.fn();

const successfulStagesWithRollback: (TestStage | TestStageWithRollback)[] = [
generateStageWithRollback(rollback1),
generateStageWithRollback(rollback2, "rollback1"),
];

const stagesWithRollback: (TestStage | TestStageWithRollback)[] = [
additionStage,
generateStageWithRollback(rollback1),
generateStageWithRollback(rollback2),
...successfulStagesWithRollback,
errorStage,
];

Expand Down Expand Up @@ -89,16 +93,19 @@ describe("buildPipeline", () => {
testMiddleware1 = createMiddlewareMock("testMiddleware1");
testMiddleware2 = createMiddlewareMock("testMiddleware2");

await runPipelineForStages(successfulStages, [
logStageMiddlewareFactory(),
testMiddleware1,
testMiddleware2,
]);
await runPipelineForStages(
[...successfulStages, ...successfulStagesWithRollback],
[logStageMiddlewareFactory(), testMiddleware1, testMiddleware2],
);
});

it(`should run each middleware ${successfulStages.length} times`, () => {
expect(testMiddleware1).toHaveBeenCalledTimes(successfulStages.length);
expect(testMiddleware2).toHaveBeenCalledTimes(successfulStages.length);
expect(testMiddleware1).toHaveBeenCalledTimes(
successfulStages.length + successfulStagesWithRollback.length,
);
expect(testMiddleware2).toHaveBeenCalledTimes(
successfulStages.length + successfulStagesWithRollback.length,
);
});

it("should run middleware in the correct order", () => {
Expand All @@ -111,6 +118,10 @@ describe("buildPipeline", () => {
"returnSumResult: testMiddleware2",
"returnHistoryResult: testMiddleware1",
"returnHistoryResult: testMiddleware2",
"Stage 4: testMiddleware1",
"Stage 4: testMiddleware2",
"rollback1: testMiddleware1",
"rollback1: testMiddleware2",
]);
});
});
Expand Down
8 changes: 5 additions & 3 deletions src/buildPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ export function buildPipeline<

return {
execute: stage,
name: stage.name,
};
});

const potentiallyProcessedStages = [];

try {
const stageNames: string[] = stageConfigurations.map(
(s) => s.execute.name,
(s, idx) => s.name || `Stage ${idx}`,
);
maybeContext = context;

Expand All @@ -88,14 +89,15 @@ export function buildPipeline<
};
};

for (const stage of stageConfigurations) {
for (const [idx, stage] of stageConfigurations.entries()) {
// initialize next() with the stage itself
let next = () =>
stage.execute(context, metadata) as Promise<Partial<R>>;

// wrap stage with middleware such that the first middleware is the outermost function
for (const middleware of reversedMiddleware) {
next = wrapMiddleware(middleware, stage.execute.name, next);
// A stage name for the current index is assured to exist given it was built using the same collection we're iterating over now
next = wrapMiddleware(middleware, stageNames[idx]!, next);
}

// Add stage to a stack that can be rolled back if necessary
Expand Down
8 changes: 5 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ export type PipelineStage<
> = (context: C, metadata: PipelineMetadata<A>) => PipelineStageResult<R>;

/**
* A more explicit configuration for a pipeline stage
* * execute: the function that executes the stage (identical to `PipelineStage`)
* * rollback: the function that rolls back any changes made within `execute` should an error occur
* A more explicit configuration for a pipeline stage:
* * name: the name of the stage for debugging/logging purposes. Defaults to `Stage ${0..n}` based on the index of the stage in the pipeline.
* * execute: the function that executes the stage (identical to {@link PipelineStage}).
* * rollback: the function that rolls back any changes made within `execute` should an error occur.
*/
export interface PipelineStageConfiguration<
A extends object,
C extends object,
R extends object,
> {
name?: string;
execute: PipelineStage<A, C, R>;
rollback?: (
context: C,
Expand Down

0 comments on commit b7d3074

Please sign in to comment.