Batch: N contributions, one wake
conditionalbatchresolveBatchItemresolveBatchItemByMetadataOne row collects a named set of submissions and wakes the workflow once, when the last one lands, with the whole collection.
The situation
An order needs three stations to sign off: cut, weld and paint. Each station submits on its own schedule. The workflow should continue once, with all three results, and never wake for each one.
The recipe
const parts = await conditional<Record<'cut' | 'weld' | 'paint', StationResultV1>>(signalId, { role: 'assembly', description: 'Each station submits its result', metadata: { orderId }, batch: ['cut', 'weld', 'paint'], timeout: '24h',});if (parts === false) return expired(orderId);if (parts === null) return cancelled(orderId);parts.weld;await lt.escalations.resolveBatchItemByMetadata({ key: 'orderId', value: orderId, itemKey: 'weld', resolverPayload: { ok: true },});// interim: { outcome: 'accepted', remaining: 1, escalationId }// last: { outcome: 'completed', remaining: 0, signaled: true, workflowId }Progress lives on the row as queryable facets: batch_pending, batch_count and batch_keys. Each item validates against the role’s versioned form.
The guarantee
Each fill is one guarded statement. A payload lands only while its key is pending, so a duplicate answers 409 duplicate-item and changes nothing. The last fill resolves the row, stores the collection as resolver_payload and wakes the wait, atomically. See Batches and accumulators.
Related patterns
The escalation boundary
Start a batch partly filled
Items already satisfied before the wait begins go in filled, so the row opens with real progress and delivers them with the rest.
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.
The escalation boundary
Wake on flag
A contributor flags one pending item and the workflow wakes at once with what has arrived and the flag, so it can reroute the order.
The escalation boundary
Open container
One row is a container that fills over time. Items join one at a time and the workflow wakes once with the ordered collection.
