Skip to content

A timeout is a delivery window

The escalation boundarySDKconditionalAccumulatorAccumulatorResult

On a container, the timer closes the window and delivers what it holds. The type has no false branch to handle.

The situation

A consolidation bin collects whatever arrives in a two-hour window. When the window closes, the bin ships with what it has. A timeout here is the normal end, not a failure.

The recipe

workflows/window-bin.ts
const bin = await conditionalAccumulator<BagV1>(signalId, {
role: 'bin',
metadata: { binKey },
accumulate: {},
timeout: '2h',
});
if (bin === null) return cancelled(binKey);
switch (bin.$trigger) {
case 'timeout': return shipWindow(binKey, bin.$accumulated);
case 'resolve': return shipEarly(binKey, bin.$accumulated);
case 'count': return shipFull(binKey, bin.$accumulated);
}

With no max, only the timer or a manual resolve ends the wait. conditionalAccumulator returns AccumulatorResult | null: false is not in the type.

The guarantee

Path $trigger Row status
Add that reaches max count resolved
Timer fires first timeout expired
Manual resolve resolve resolved
Cancel none, returns null cancelled

On every path except cancel, the row stores exactly what the workflow received. The expiry statement and every resolve re-check pending under lock, so the timeout settles once. See Batches and accumulators.