Skip to content

Commit 0711dba

Browse files
ericsorensonhenri-hulski
authored andcommitted
fix(overmind): fix 'pipe'/'branch' operators re-invoking on error instead of propagating it
he bug was in packages/overmind/src/operators.ts in both pipe (line 105) and branch (line 217). The catch blocks were re-invoking the same operator that just threw: // Before (bug): re-invokes the throwing operator → infinite recursion catch (operatorError) { operatorToRun(operatorErr, operatorContext, run, final) } // After (fix): propagates the error to the next operator in the chain catch (operatorError) { run(operatorError, operatorContext) } By calling run(operatorError, operatorContext), the error is passed forward through the operator chain where it can be handled by catchError or tryCatch — matching how createOperator and createMutationOperator already handle errors internally.
1 parent b211377 commit 0711dba

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

packages/overmind/src/operators.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
waitUntil,
1515
IContext,
1616
} from './'
17+
import { IS_OPERATOR } from './utils'
1718

1819
describe('OPERATORS', () => {
1920
test('branch - passes input as output', async () => {
@@ -514,4 +515,78 @@ describe('OPERATORS', () => {
514515
expect(overmind.state.runCount).toBe(1)
515516
})
516517
})
518+
519+
test('pipe - propagates error from throwing IS_OPERATOR instead of re-invoking', async () => {
520+
let callCount = 0
521+
const throwingOperator: any = (_err: any, _context: any, _next: any) => {
522+
callCount++
523+
throw new Error('sync throw')
524+
}
525+
throwingOperator[IS_OPERATOR] = true
526+
527+
const state = {
528+
error: '',
529+
}
530+
531+
const test = (pipe as any)(
532+
throwingOperator,
533+
catchError(({ state }: any, error: Error) => {
534+
state.error = error.message
535+
return 'caught'
536+
})
537+
)
538+
539+
const actions = { test }
540+
541+
const config = {
542+
state,
543+
actions,
544+
}
545+
546+
const overmind = new Overmind(config) as any
547+
548+
await overmind.actions.test()
549+
550+
expect(callCount).toBe(1)
551+
expect(overmind.state.error).toBe('sync throw')
552+
})
553+
554+
test('branch - propagates error from throwing IS_OPERATOR instead of re-invoking', async () => {
555+
let callCount = 0
556+
const throwingOperator: any = (_err: any, _context: any, _next: any) => {
557+
callCount++
558+
throw new Error('sync throw')
559+
}
560+
throwingOperator[IS_OPERATOR] = true
561+
562+
const state = {
563+
error: '',
564+
value: '',
565+
}
566+
567+
const test = (pipe as any)(
568+
(branch as any)(throwingOperator),
569+
catchError(({ state }: any, error: Error) => {
570+
state.error = error.message
571+
return 'caught'
572+
}),
573+
({ state }: any, value: string) => {
574+
state.value = value
575+
}
576+
)
577+
578+
const actions = { test }
579+
580+
const config = {
581+
state,
582+
actions,
583+
}
584+
585+
const overmind = new Overmind(config) as any
586+
587+
await overmind.actions.test()
588+
589+
expect(callCount).toBe(1)
590+
expect(overmind.state.error).toBe('sync throw')
591+
})
517592
})

packages/overmind/src/operators.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function pipe(...operators: any[]) {
102102
try {
103103
operatorToRun(operatorErr, operatorContext, run, final)
104104
} catch (operatorError) {
105-
operatorToRun(operatorErr, operatorContext, run, final)
105+
run(operatorError, operatorContext)
106106
}
107107
}
108108

@@ -214,7 +214,7 @@ export function branch(...operators: any[]) {
214214
try {
215215
operatorToRun(operatorErr, operatorContext, run, final)
216216
} catch (operatorError) {
217-
operatorToRun(operatorErr, operatorContext, run, final)
217+
run(operatorError, operatorContext)
218218
}
219219
}
220220

0 commit comments

Comments
 (0)