Answer, timeout or cancel
Every wait ends in one of three typed values: the answer, false on timeout, or null on cancel. Each branch is explicit in code.
The situation
A person might answer, might not answer before the deadline, or someone might withdraw the request. A workflow that treats all three the same either acts on missing data or stalls forever. The return type should force the decision.
The recipe
import { Durable } from '@hotmeshio/hotmesh';import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
export async function approveOrder(envelope: LTEnvelope) { const { orderId } = envelope.data; const { workflowId } = Durable.workflow.workflowInfo();
const decision = await conditional<{ approved: boolean }>(`approve-${workflowId}`, { role: 'order-reviewer', description: `Approve order ${orderId}`, metadata: { orderId }, timeout: '72h', });
if (decision === false) return { type: 'return' as const, data: { orderId, outcome: 'expired' } }; if (decision === null) return { type: 'return' as const, data: { orderId, outcome: 'cancelled' } }; return { type: 'return' as const, data: { orderId, outcome: decision.approved ? 'approved' : 'rejected' } };}conditional returns T | false | null. T is the resolver’s payload. false means the timeout timer fired first. null means the row was cancelled, explicitly or because the workflow was terminated.
The guarantee
The race between a resolve and the timer settles once. If the timer wins, the row moves to expired in a guarded update and a late resolve answers already-expired. If the resolve wins, the timer is inert. Omit timeout for an open-ended wait. See Conditional.
Related patterns
The escalation boundary
Confidence-gated escalation
The workflow finishes on its own when the model is confident and parks a review row when it is not, so people see only the uncertain cases.
Resolving from anywhere
Cancel is home
Cancelling a wait sends the item back to a known home queue. An operator's cancel is a safe reset, never a lost item.
The escalation boundary
A timeout is a delivery window
On a container, the timer closes the window and delivers what it holds. The type has no false branch to handle.
The escalation boundary
Deliver what arrived on timeout
With partialOnTimeout, a batch that misses its deadline wakes with the items filled so far instead of false, so the workflow can act on them.
