Chain Model
Overview
Section titled “Overview”This document describes Queuert’s unified job model and the Promise-inspired chain abstraction.
Core Concepts
Section titled “Core Concepts”A Job is an individual unit of work with a lifecycle:
blocked/pending → running → completedEach job:
- Belongs to a Job Type that defines its input/output schema
- Contains typed input data and (when completed) output data
- Can
continueWithto create a linked follow-up job - Can depend on blockers (other chains that must complete first)
A Chain is a series of linked jobs where each job can continue to the next—just like a JavaScript Promise chain.
Job A → Job B → Job C → (completed)The chain completes when its final job completes without continuing.
The Promise Analogy
Section titled “The Promise Analogy”The design mirrors JavaScript Promises:
// JavaScript: A Promise chain IS the first promiseconst chain = fetch(url) // chain === this promise .then(processResponse) // continuation .then(formatResult); // continuation
// Queuert: A Chain IS its first jobconst chain = startChain(...) // chain.id === firstJob.id .continueWith(processStep) // continuation .continueWith(formatStep); // continuationThe fundamental insight: the first job IS the chain. Chains work like Promises but persist across process restarts and distribute across workers.
Identity Model
Section titled “Identity Model”For the first job in a chain: job.id === job.chainId
This isn’t redundant—it’s a meaningful signal that identifies the chain starter. Continuation jobs have job.id !== job.chainId but share the same chainId as all other jobs in the chain.
Unified Model Benefits
Section titled “Unified Model Benefits”Having the first job BE the chain (rather than a separate entity) provides:
Simplicity
Section titled “Simplicity”- One table, one type, one set of operations
- No separate
chaintable to manage - No joins, no synchronization issues
Flexibility
Section titled “Flexibility”The first job can be:
- A lightweight “alias” that immediately continues to real work
- A full job that processes and completes the chain in one step
- Anything in between
Performance
Section titled “Performance”chainTypeNamedenormalized on every job for O(1) filtering- No subqueries needed to find chains by type
- Efficient at scale (millions of jobs)
Execution Patterns
Section titled “Execution Patterns”Chains support various patterns via continueWith:
Linear
Section titled “Linear”A → B → C → doneBranched
Section titled “Branched”A → B1 (if condition) → B2 (else)A → A → A → doneGo-to (jump back)
Section titled “Go-to (jump back)”A → B → A → B → doneBlockers: Chain Dependencies
Section titled “Blockers: Chain Dependencies”Chains can depend on other chains to complete before starting:
┌──────────────┐│ Blocker A │───┐└──────────────┘ │ ├──→ Main Chain (blocked until A and B complete)┌──────────────┐ ││ Blocker B │───┘└──────────────┘Blockers are declared at the type level and provided via the blockers array when creating a chain. The main job starts as blocked and transitions to pending when all blockers complete.
Consistent Terminology
Section titled “Consistent Terminology”Parallel entities use consistent lifecycle terminology to reduce cognitive load:
- Job:
blocked/pending→running→completed - Chain:
blocked/pending→running→completed(reflects status of current job in chain)
Avoid asymmetric naming (e.g., started/finished vs created/completed) even if individual terms seem natural. Consistency across the API produces fewer questions and faster comprehension.
Summary
Section titled “Summary”The Chain model:
- Mirrors Promises: Familiar mental model for JavaScript developers
- Unified identity: The first job IS the chain—no separate entity
- Single table: Jobs and chains share storage;
chainIdlinks them - Flexible patterns: Linear, branched, looped, or jumping execution
- Distributed: Unlike Promises, chains persist and distribute across workers