feat(workflows-sdk): Configurable retries upon step creation (#5728)
**What**
- Allow to create step that can be configured to have a max retry
- Step end retry mechanism on permanent failure
Also added an API to override a step configuration from within the createWorkflow
```ts
const step = createStep({ name: "step", maxRetries: 3 }, async (_, context) => {
return new StepResponse({ output: "output" })
})
const workflow = createWorkflow("workflow", function () {
const res = step().config({ maxRetries: 5 }) // This will override the original maxRetries of 3
})
```
**NOTE**
We can maybe find another name than config on the step workflow data to override the step config.
This commit is contained in:
committed by
GitHub
parent
1a2f513d53
commit
9cc787cac4
@@ -16,6 +16,40 @@ describe("Workflow composer", function () {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should compose a new workflow composed retryable steps", async () => {
|
||||
const maxRetries = 1
|
||||
|
||||
const mockStep1Fn = jest.fn().mockImplementation((input, context) => {
|
||||
const attempt = context.metadata.attempt || 0
|
||||
if (attempt <= maxRetries) {
|
||||
throw new Error("test error")
|
||||
}
|
||||
|
||||
return { inputs: [input], obj: "return from 1" }
|
||||
})
|
||||
|
||||
const step1 = createStep({ name: "step1", maxRetries }, mockStep1Fn)
|
||||
|
||||
const workflow = createWorkflow("workflow1", function (input) {
|
||||
return step1(input)
|
||||
})
|
||||
|
||||
const workflowInput = { test: "payload1" }
|
||||
const { result: workflowResult } = await workflow().run({
|
||||
input: workflowInput,
|
||||
})
|
||||
|
||||
expect(mockStep1Fn).toHaveBeenCalledTimes(2)
|
||||
expect(mockStep1Fn.mock.calls[0]).toHaveLength(2)
|
||||
expect(mockStep1Fn.mock.calls[0][0]).toEqual(workflowInput)
|
||||
expect(mockStep1Fn.mock.calls[1][0]).toEqual(workflowInput)
|
||||
|
||||
expect(workflowResult).toEqual({
|
||||
inputs: [{ test: "payload1" }],
|
||||
obj: "return from 1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should compose a new workflow and execute it", async () => {
|
||||
const mockStep1Fn = jest.fn().mockImplementation((input) => {
|
||||
return { inputs: [input], obj: "return from 1" }
|
||||
@@ -928,6 +962,73 @@ describe("Workflow composer", function () {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should compose a new workflow composed of retryable steps", async () => {
|
||||
const maxRetries = 1
|
||||
|
||||
const mockStep1Fn = jest.fn().mockImplementation((input, context) => {
|
||||
const attempt = context.metadata.attempt || 0
|
||||
if (attempt <= maxRetries) {
|
||||
throw new Error("test error")
|
||||
}
|
||||
|
||||
return new StepResponse({ inputs: [input], obj: "return from 1" })
|
||||
})
|
||||
|
||||
const step1 = createStep({ name: "step1", maxRetries }, mockStep1Fn)
|
||||
|
||||
const workflow = createWorkflow("workflow1", function (input) {
|
||||
return step1(input)
|
||||
})
|
||||
|
||||
const workflowInput = { test: "payload1" }
|
||||
const { result: workflowResult } = await workflow().run({
|
||||
input: workflowInput,
|
||||
})
|
||||
|
||||
expect(mockStep1Fn).toHaveBeenCalledTimes(2)
|
||||
expect(mockStep1Fn.mock.calls[0]).toHaveLength(2)
|
||||
expect(mockStep1Fn.mock.calls[0][0]).toEqual(workflowInput)
|
||||
expect(mockStep1Fn.mock.calls[1][0]).toEqual(workflowInput)
|
||||
|
||||
expect(workflowResult).toEqual({
|
||||
inputs: [{ test: "payload1" }],
|
||||
obj: "return from 1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should compose a new workflow composed of retryable steps that should stop retries on permanent failure", async () => {
|
||||
const maxRetries = 1
|
||||
|
||||
const mockStep1Fn = jest.fn().mockImplementation((input, context) => {
|
||||
return StepResponse.permanentFailure("fail permanently")
|
||||
})
|
||||
|
||||
const step1 = createStep({ name: "step1", maxRetries }, mockStep1Fn)
|
||||
|
||||
const workflow = createWorkflow("workflow1", function (input) {
|
||||
return step1(input)
|
||||
})
|
||||
|
||||
const workflowInput = { test: "payload1" }
|
||||
const { errors } = await workflow().run({
|
||||
input: workflowInput,
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(mockStep1Fn).toHaveBeenCalledTimes(1)
|
||||
expect(mockStep1Fn.mock.calls[0]).toHaveLength(2)
|
||||
expect(mockStep1Fn.mock.calls[0][0]).toEqual(workflowInput)
|
||||
|
||||
expect(errors).toHaveLength(1)
|
||||
expect(errors[0]).toEqual({
|
||||
action: "step1",
|
||||
handlerType: "invoke",
|
||||
error: expect.objectContaining({
|
||||
message: "fail permanently",
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
it("should compose a new workflow and execute it", async () => {
|
||||
const mockStep1Fn = jest.fn().mockImplementation((input) => {
|
||||
return new StepResponse({ inputs: [input], obj: "return from 1" })
|
||||
|
||||
Reference in New Issue
Block a user