Inferable provides two helpers for handling side effects: effect and result.

  • effect ensures a side effect runs at most once per workflow execution.
  • result ensures a side effect runs at least once per workflow execution.
EffectResult
Will run the side effect 0 or 1 timesWill run the side effect 1 or more times
Returns no resultReturns a result
Failures don’t affect the workflowFailures affect the workflow

It’s possible to model idempotency using results, at-least-once semantics (results) and an idempotency key passed in from your workflow input.

At Least Once: Using Results

To make sure your side-effect runs at least once, Inferable provides a result helper that ensures a side effect runs at least once per workflow execution.

Semantics

  • Everything inside the result function may be executed multiple times, but the result that’s returned to the workflow will be the same.
  • If the result is computed using another workflow instance, the computed result is returned.
  • If the result is not computed using another workflow instance, the result is computed, stored, and returned.

Results will be consistent across executions

It’s worth repeating that the result returned to the workflow will be the same, regardless of how many times the result function is executed.

For example, if your run something like Date.now() inside the result function, the result will be the same for every execution.

Best Practices

When working with side effects in workflows:

  1. Always use ctx.result for operations that should run at least once:

  2. Name effects uniquely within the workflow execution context:

const result = await ctx.result(`processRecord_${recordId}`, async () => {
  // Side effect code
});

Important Notes

  • The result function will return a result.
  • Any errors thrown inside the result function are returned directly to the workflow.
    • If your intent is to fail the workflow as a result of a side-effect, you can let the error bubble up.
    • If your intent is to continue the workflow despite the error, you must catch the error and continue.
  • Results are guaranteed to execute at least once in the context of the durable workflow.
  • What happens inside the result function will affect the workflow’s control flow.

At Most Once: Using Effects

To handle side effects safely, Inferable provides a effect helper that ensures a side effect runs at most once per workflow execution:

Semantics

  • The effect function will run at most once per workflow execution.
  • If the effect function is called multiple times, it will only run once.
  • Effect functions are not guaranteed to run at all.
  • Effect functions are not guaranteed to run in the order they are called.
  • Effect function failures are caught by the SDK and won’t affect the workflow execution.

Best Practices

When working with side effects in workflows:

  1. Always use ctx.effect for operations that should run exactly once:

    • Sending emails or notifications
    • Logging important events
    • Making API calls with side effects
    • Database operations
  2. Name effects uniquely within the workflow execution context:

ctx.effect(`processRecord_${recordId}`, async () => {
  // Side effect code
});
  1. Keep effects isolated - The effect function should be self-contained and not return any values:
// ✅ Good: Self-contained effect
ctx.effect("notifyTeam", async () => {
  await sendEmail({ ... });
});

// ❌ Bad: Don't return values from effects
const result = ctx.effect("getData", async () => {
  return await fetchData(); // Don't do this!
});

Important Notes

  • The effect function must return void - it cannot return a result
  • Any errors thrown inside the effect function are caught by the SDK and won’t affect the workflow execution
  • Effects are guaranteed to execute at most once in the context of the durable workflow
  • What happens inside the effect function will not affect the workflow’s control flow