Skip to content
Get started
Get started
Flow Producer: Parent and Child Jobs in Bun
guide · flow producer

Jobs that wait for each other.

Some work only makes sense in order: resize every image, then build the album; charge each line, then close the invoice. A flow declares that shape once and bunqueue holds the parent until its children are done.

A flow is a tree of jobs. Children are queued immediately, the parent stays blocked until every child has completed, and then runs with access to what they returned.

The most common shape: a parent job that waits for its children. Children run first, then the parent runs with access to their results:

import { FlowProducer, Worker } from 'bunqueue/client';
type ReportData = {
month?: string;
source?: 'sales' | 'costs';
};
const flow = new FlowProducer({ embedded: true });
const rowsBySource = { sales: [120, 80], costs: [50, 20] };
const worker = new Worker<ReportData>('reports', async (job) => {
if (job.name === 'build-report') {
const values = await job.getChildrenValues<{ rows: number[] }>();
return { report: Object.values(values) };
}
if (!job.data.source) throw new Error('source is required');
return { rows: rowsBySource[job.data.source] };
}, { embedded: true });
const node = await flow.add<ReportData>({
name: 'build-report',
queueName: 'reports',
data: { month: '2026-01' },
children: [
{ name: 'fetch-sales', queueName: 'reports', data: { source: 'sales' } },
{ name: 'fetch-costs', queueName: 'reports', data: { source: 'costs' } },
],
});
const result = await node.job.waitUntilFinished(null, 10_000);
console.log(result);
await worker.close();
await flow.close();

Flow creation is one broker-side transaction in the Bun package and all six current external SDKs: addBulk commits every tree or none, and workers cannot see a leaf before the full graph exists. Previously published SDK versions that compose PUSH and UpdateParent remain compatible with the server, but their already-sent requests cannot gain PUSHF all-or-nothing visibility.

The Bun producer validates the complete graph before sending it and the broker validates it again. When the broker/embedded manager has a dataPath, one immediate SQLite transaction commits the graph before it is published to workers, even when individual nodes omit durable: true. Without a dataPath, embedded mode is intentionally memory-only: creation is still atomically visible to workers, but a process crash cannot recover it.

  • A flow may contain at most 10,000 jobs, at most 10 MB of data per job and 64 MB across the batch. A root is depth 0; descendants may be at most 100 edges below it.
  • jobId is allowed, but cannot be empty or contain :. Reusing any existing or retained flow ID—including SQLite/DLQ rows, completion/timeout tombstones, retained results, and IDs still referenced by a waiting parent—rejects the whole request.
  • name inside user data is preserved independently from the job’s own name. Keys beginning with __ are reserved for engine-owned flow metadata.
  • repeat, deduplication, debounce, and opts.parent are rejected inside an atomic flow because their independent lifetime/ownership semantics cannot participate safely in the graph transaction.
  • The four child-failure policies are mutually exclusive.

Wire values are checked at runtime too: IDs must be strings, link fields must be string arrays, booleans must actually be booleans, and parent metadata must match both sides of every edge. These checks happen before any queue counter, heap, dependency index, or SQLite row changes.

Flow PatternsChains, fan-in, trees, reading child results, options
Flow Failure HandlingWhat a parent does when a child dies for good
Flow Producer ReferenceEvery producer method, job helper and step field